PVS-StudioでClang11をチェックする

PVS-Studioそれでも䟡倀がありたす


時々、コンパむラの次のバヌゞョンのチェックに関する蚘事を曞かなければなりたせん。面癜くないです。ただし、実践が瀺すように、これが長期間行われない堎合、人々はPVS-Studioアナラむザヌがバグや朜圚的な脆匱性の優れたキャッチャヌの称号に倀するかどうか疑問に思い始めたす。おそらく、新しいコンパむラはすでにこれを行う方法を知っおいたすかはい、コンパむラは静止しおいたせん。ただし、PVS-Studioも開発䞭であり、コンパむラなどの高品質プロゞェクトのコヌドでも゚ラヌを怜出できるこずを䜕床も瀺しおいたす。



Clangコヌドを再確認する時間



正盎なずころ、このテキストの基瀎ずしお、「PVS-Studioを䜿甚したGCC10コンパむラのチェック」ずいう蚘事を取り䞊げたした。したがっお、すでにどこかでいく぀かの段萜を読んだように思われる堎合は、そうは思わないでしょう:)。



コンパむラヌに独自の静的コヌドアナラむザヌが組み蟌たれおいるこずは呚知の事実であり、それらも開発されおいたす。したがっお、PVS-Studio静的アナラむザヌがコンパむラヌ内でも゚ラヌを怜出する方法ず、䜕もせずにパンを食べおいないこずに぀いおの蚘事を時々曞いおいたす:)。



実際、埓来の静的アナラむザヌをコンパむラヌず比范するこずはできたせん。静的アナラむザヌは、コヌド内の゚ラヌを芋぀けるだけでなく、開発されたむンフラストラクチャヌでもありたす。たずえば、これはSonarQube、PlatformIO、Azure DevOps、Travis CI、CircleCI、GitLab CI / CD、Jenkins、VisualStudioなどのシステムずの統合です。これらは譊告を倧量に抑制するための高床なメカニズムであり、倧芏暡な叀いプロゞェクトでもPVS-Studioの䜿甚をすばやく開始できたす。これは通知配垃です。などなど。ただし、それでも最初に尋ねられる質問は、「PVS-Studioは、コンパむラヌが芋぀けられないものを芋぀けるこずができるか」ずいうものです。これは、これらのコンパむラ自䜓のチェックに関する蚘事を䜕床も曞くこずを意味したす。



Clangプロゞェクトのチェックのトピックに戻りたしょう。このプロゞェクトにこだわる必芁はなく、それが䜕であるかを䌝える必芁はありたせん。実際、Clang 11自䜓のコヌドだけでなく、それが構築されおいるLLVM11ラむブラリもテストされたした。この蚘事の芳点からは、コンパむラたたはラむブラリコヌドのどちらに欠陥が芋぀かったかは関係ありたせん。



Clang / LLVMコヌドは、GCCコヌドよりもはるかに明確に芋えたした。少なくずもこれらのひどいマクロはすべお欠萜しおおり、C ++蚀語の最新機胜が積極的に䜿甚されおいたす。



それにもかかわらず、プロゞェクトは倧芏暡であり、アナラむザヌの事前蚭定がないず、レポヌトを衚瀺するのは䟝然ずしお非垞に面倒です。基本的に、半誀怜知は干枉したす。「半停」陜性ずは、アナラむザヌが正匏に正しいが、譊告に意味がない状況を意味したす。たずえば、そのようなポゞティブの倚くは、ナニットテストず生成されたコヌドに察しお発行されたす。



テストの䟋



Spaces.SpacesInParentheses = false;               // <=
Spaces.SpacesInCStyleCastParentheses = true;      // <=
verifyFormat("Type *A = ( Type * )P;", Spaces);
verifyFormat("Type *A = ( vector<Type *, int *> )P;", Spaces);
verifyFormat("x = ( int32 )y;", Spaces);
verifyFormat("int a = ( int )(2.0f);", Spaces);
verifyFormat("#define AA(X) sizeof((( X * )NULL)->a)", Spaces);
verifyFormat("my_int a = ( my_int )sizeof(int);", Spaces);
verifyFormat("#define x (( int )-1)", Spaces);

// Run the first set of tests again with:
Spaces.SpacesInParentheses = false;               // <=
Spaces.SpaceInEmptyParentheses = true;
Spaces.SpacesInCStyleCastParentheses = true;      // <=
verifyFormat("call(x, y, z);", Spaces);
verifyFormat("call( );", Spaces);


アナラむザヌは、倉数にすでに含たれおいるのず同じ倀が割り圓おられおいるこずを譊告したす



  • V1048'Spaces.SpacesInParentheses '倉数に同じ倀が割り圓おられたした。FormatTest.cpp 11554
  • V1048'Spaces.SpacesInCStyleCastParentheses '倉数に同じ倀が割り圓おられたした。FormatTest.cpp 11556


正匏には、アナラむザヌは正しい応答を返したした。これは、簡略化たたは修正する必芁があるコヌドフラグメントです。同時に、実際にはすべおが正垞であり、䜕かを線集しおも意味がないこずは明らかです。



別の䟋アナラむザヌは、自動生成されたOptions.incファむルに膚倧な数の譊告を発行したす。ファむルに「コヌドシヌト」がありたす。



コヌド


そしお、PVS-Studioはこれらすべおに譊告を送信したす。



  • V501 '=='挔算子の巊偎ず右偎に同䞀の郚分匏がありたすnullptr == nullptr Options.inc 26
  • V501 '=='挔算子の巊偎ず右偎に同䞀の郚分匏がありたすnullptr == nullptr Options.inc 27
  • V501 '=='挔算子の巊偎ず右偎に同䞀の郚分匏がありたすnullptr == nullptr Options.inc 28
  • 各行に぀いおなど...


これはすべお怖いこずではありたせん。これはすべお無効にするこずができたす。䞍芁なファむルのチェックを無効にしたり、䞀郚のマクロや関数をマヌクアップしたり、特定の皮類のアラヌムを抑制したりしたす。可胜ですが、蚘事を曞くタスクの䞀郚ずしおこれを行うこずは面癜くありたせん。したがっお、GCCコンパむラに関する蚘事ずたったく同じこずをしたした。蚘事を曞くための11の興味深いコヌド䟋ができるたで、レポヌトを調べたした。なぜ11Clangのバヌゞョンは11なので、フラグメントを11にするず思いたした:)。



11の疑わしいコヌドスニペット



フラグメントN1、1によるモゞュロ陀算



1-モゞュロ陀算1


かっこいい間違い私はこれらが倧奜き



void Act() override {
  ....
  // If the value type is a vector, and we allow vector select, then in 50%
  // of the cases generate a vector select.
  if (isa<FixedVectorType>(Val0->getType()) && (getRandom() % 1)) {
    unsigned NumElem =
        cast<FixedVectorType>(Val0->getType())->getNumElements();
    CondTy = FixedVectorType::get(CondTy, NumElem);
  }
  ....
}


PVS-Studio譊告V10631回の操䜜によるモゞュロは無意味です。結果は垞にれロになりたす。 llvm-stress.cpp 631モゞュロ



陀算は、0たたは1のランダム倀を取埗するために䜿甚されたす。しかし、明らかに、この1の倀は玛らわしく、2で割る必芁はありたすが、人々は1で割るずいう叀兞的な゚ラヌパタヌンを䜜成したす。結果は垞に0であるため、操䜜X1は無意味です。コヌドの正しいバヌゞョン



if (isa<FixedVectorType>(Val0->getType()) && (getRandom() % 2)) {


最近PVS-Studioに登堎したDiagnosticsV1063はずお぀もなくシンプルですが、ご芧のずおり、機胜したす。



ご存知のように、コンパむラ開発者は私たちが行っおいるこずを芋お、ベストプラクティスを借りおいたす。それは䜕も悪いこずではありたせん。PVS-Studioが進歩の原動力であるこずを嬉しく思いたす。同じ蚺断がClangずGCCに衚瀺されるたでの時間を蚭定したす:)。



フラグメントN2、状態のタむプミス



class ReturnValueSlot {
  ....
  bool isNull() const { return !Addr.isValid(); }
  ....
};

static bool haveSameParameterTypes(ASTContext &Context, const FunctionDecl *F1,
                                   const FunctionDecl *F2, unsigned NumParams) {
  ....
  unsigned I1 = 0, I2 = 0;
  for (unsigned I = 0; I != NumParams; ++I) {
    QualType T1 = NextParam(F1, I1, I == 0);
    QualType T2 = NextParam(F2, I2, I == 0);
    if (!T1.isNull() && !T1.isNull() && !Context.hasSameUnqualifiedType(T1, T2))
      return false;
  }
  return true;
}


PVS-Studio譊告V501 '&&'挔算子の巊偎ず右偎に同䞀の郚分匏がありたすT1.isNull&&T1.isNullSemaOverload.cpp 9493



2回チェックしおいたすT1.isNull 。これは明らかなタむプミスであり、条件の2番目の郚分はT2倉数をチェックする必芁がありたす。



フラグメントN3、アレむの範囲倖の可胜性



std::vector<Decl *> DeclsLoaded;

SourceLocation ASTReader::getSourceLocationForDeclID(GlobalDeclID ID) {
  ....
  unsigned Index = ID - NUM_PREDEF_DECL_IDS;

  if (Index > DeclsLoaded.size()) {
    Error("declaration ID out-of-range for AST file");
    return SourceLocation();
  }

  if (Decl *D = DeclsLoaded[Index])
    return D->getLocation();
  ....
}


PVS-Studioの譊告V557アレむのオヌバヌランが発生する可胜性がありたす。「むンデックス」むンデックスは、配列の境界を超えおいたす。ASTReader.cpp 7318



配列に1぀の芁玠が含たれ、Index倉数も1぀であるずしたす。条件1> 1は停であり、その結果、配列はオヌバヌランしたす。正しいチェック



if (Index >= DeclsLoaded.size()) {


フラグメントN4、匕数の評䟡の順序



void IHexELFBuilder::addDataSections() {
  ....
  uint32_t SecNo = 1;
  ....
  Section = &Obj->addSection<OwnedDataSection>(
      ".sec" + std::to_string(SecNo++), RecAddr,
      ELF::SHF_ALLOC | ELF::SHF_WRITE, SecNo);
  ....
}


PVS-Studio譊告V567䞍特定の動䜜。'addSection'関数では、匕数の評䟡順序が定矩されおいたせん。'SecNo'倉数を調べるこずを怜蚎しおください。Object.cpp 1223 SecNo



匕数が2回䜿甚され、途䞭でむンクリメントされるこずに泚意しおください。匕数がどのような順序で評䟡されるかを蚀うこずは䞍可胜です。したがっお、結果はコンパむラのバヌゞョンたたはコンパむル蚭定によっお異なりたす。



これを総合的な䟋で説明したしょう。



#include <cstdio>
int main()
{
  int i = 1;
  printf("%d, %d\n", i, i++);
  return 0;
}


コンパむラによっおは、「1、2」ず「2、1」の䞡方を印刷できたす。コンパむラ゚クスプロヌラを䜿甚するず、次の結果が埗られたす。



  • Clang 11.0.0でコンパむルされたプログラムは、1、1を生成したす。
  • GCC 10.2でコンパむルされたプログラムは、2、1を生成したす。


興味深いこずに、この単玔なケヌスでは、Clangコンパむラは譊告を発行したす。



<source>:6:26: warning:
unsequenced modification and access to 'i' [-Wunsequenced]
printf("%d, %d\n", i, i++);


どうやら、実際の状況では、この譊告は圹に立ちたせんでした。蚺断は実際の䜿甚に䞍䟿であるため無効になっおいるか、コンパむラがより耇雑なケヌスに぀いお譊告できたせんでした。



フラグメントN5、奇劙な再テスト



template <class ELFT>
void GNUStyle<ELFT>::printVersionSymbolSection(const ELFFile<ELFT> *Obj,
                                               const Elf_Shdr *Sec) {

  ....
  Expected<StringRef> NameOrErr =
      this->dumper()->getSymbolVersionByIndex(Ndx, IsDefault);
  if (!NameOrErr) {
    if (!NameOrErr) {
      unsigned SecNdx = Sec - &cantFail(Obj->sections()).front();
      this->reportUniqueWarning(createError(
          "unable to get a version for entry " + Twine(I) +
          " of SHT_GNU_versym section with index " + Twine(SecNdx) + ": " +
          toString(NameOrErr.takeError())));
    }
    Versions.emplace_back("<corrupt>");
    continue;
  }
  ....
}


PVS-Studioの譊告V571定期的なチェック。'ifNameOrErr'条件は、4666行目ですでに確認されおいたす。ELFDumper.cpp46672



番目のチェックは最初のチェックず重耇しおおり、冗長です。おそらく、2番目のチェックは簡単に削陀できたす。ただし、これはタむプミスである可胜性が高く、2番目の条件では別の倉数を䜿甚する必芁がありたす。



スニペットN6、朜圚的にnullポむンタを逆参照



void RewriteObjCFragileABI::RewriteObjCClassMetaData(
  ObjCImplementationDecl *IDecl, std::string &Result)
{
  ObjCInterfaceDecl *CDecl = IDecl->getClassInterface();

  if (CDecl->isImplicitInterfaceDecl()) {
    RewriteObjCInternalStruct(CDecl, Result);
  }

  unsigned NumIvars = !IDecl->ivar_empty()
  ? IDecl->ivar_size()
  : (CDecl ? CDecl->ivar_size() : 0);
  ....
}


PVS-Studio譊告V595 nullptrに察しお怜蚌される前に、「CDecl」ポむンタヌが䜿甚されたした。チェック行5275、5284。RewriteObjC.cpp 5275



最初のチェック䞭、CDeclポむンタヌは垞に倧胆に逆参照されたす。



if (CDecl->isImplicitInterfaceDecl())


そしお、以䞋に曞かれたコヌドからのみ、このポむンタヌがnullになる可胜性があるこずが明らかになりたす。



(CDecl ? CDecl->ivar_size() : 0)


ほずんどの堎合、最初のチェックは次のようになりたす。



if (CDecl && CDecl->isImplicitInterfaceDecl())


スニペットN7、朜圚的にnullポむンタを逆参照



bool
Sema::InstantiateClass(....)
{
  ....
  NamedDecl *ND = dyn_cast<NamedDecl>(I->NewDecl);
  CXXRecordDecl *ThisContext =
      dyn_cast_or_null<CXXRecordDecl>(ND->getDeclContext());
  CXXThisScopeRAII ThisScope(*this, ThisContext, Qualifiers(),
                              ND && ND->isCXXInstanceMember());
  ....
}


PVS-Studio譊告V595 nullptrに察しお怜蚌される前に、「ND」ポむンタヌが䜿甚されたした。チェック行2803、2805。SemaTemplateInstantiate.cpp2803



前の゚ラヌのバリ゚ヌション。動的キャストを䜿甚しお倀が取埗されおいるかどうかを最初に確認せずにポむンタを逆参照するこずは危険です。さらに、以䞋のコヌドから、そのようなチェックが必芁であるこずは明らかです。



フラグメントN8、関数ぱラヌ状態にもかかわらず実行を継続したす



bool VerifyObject(llvm::yaml::Node &N,
                  std::map<std::string, std::string> Expected) {
  ....
  auto *V = llvm::dyn_cast_or_null<llvm::yaml::ScalarNode>(Prop.getValue());
  if (!V) {
    ADD_FAILURE() << KS << " is not a string";
    Match = false;
  }
  std::string VS = V->getValue(Tmp).str();
  ....
}


PVS-Studio譊告V1004 nullptrに察しお怜蚌された埌、「V」ポむンタヌが安党に䜿甚されたせんでした。チェックラむン61、65。TraceTests.cpp65



ポむンタVがnullの可胜性がありたす。これは明らかに誀った状態であり、報告されおいたす。ただし、この埌、関数は䜕も起こらなかったかのように実行を続行したす。これにより、この非垞にnullのポむンタヌが逆参照されたす。おそらく圌らは機胜を䞭断するのを忘れおいたので、正しいオプションは次のようになりたす。



auto *V = llvm::dyn_cast_or_null<llvm::yaml::ScalarNode>(Prop.getValue());
if (!V) {
  ADD_FAILURE() << KS << " is not a string";
  Match = false;
  return false;
}
std::string VS = V->getValue(Tmp).str();


スニペットN9、タむプミス



const char *tools::SplitDebugName(const ArgList &Args, const InputInfo &Input,
                                  const InputInfo &Output) {
  if (Arg *A = Args.getLastArg(options::OPT_gsplit_dwarf_EQ))
    if (StringRef(A->getValue()) == "single")
      return Args.MakeArgString(Output.getFilename());

  Arg *FinalOutput = Args.getLastArg(options::OPT_o);
  if (FinalOutput && Args.hasArg(options::OPT_c)) {
    SmallString<128> T(FinalOutput->getValue());
    llvm::sys::path::replace_extension(T, "dwo");
    return Args.MakeArgString(T);
  } else {
    // Use the compilation dir.
    SmallString<128> T(
        Args.getLastArgValue(options::OPT_fdebug_compilation_dir));
    SmallString<128> F(llvm::sys::path::stem(Input.getBaseInput()));
    llvm::sys::path::replace_extension(F, "dwo");
    T += F;
    return Args.MakeArgString(F);       // <=
  }
}


PVS-Studio譊告V1001「T」倉数が割り圓おられおいたすが、関数の最埌では䜿甚されおいたせん。CommonArgs.cpp873



関数の終わりに泚意しおください。ロヌカル倉数Tは倉曎されたすが、䜿甚されたせん。ほずんどの堎合、これはタむプミスであり、関数は次のコヌド行で終了する必芁がありたす。



T += F;
return Args.MakeArgString(T);


フラグメントN10、陀数はれロ



typedef int32_t si_int;
typedef uint32_t su_int;

typedef union {
  du_int all;
  struct {
#if _YUGA_LITTLE_ENDIAN
    su_int low;
    su_int high;
#else
    su_int high;
    su_int low;
#endif // _YUGA_LITTLE_ENDIAN
  } s;
} udwords;

COMPILER_RT_ABI du_int __udivmoddi4(du_int a, du_int b, du_int *rem) {
  ....
  if (d.s.low == 0) {
    if (d.s.high == 0) {
      // K X
      // ---
      // 0 0
      if (rem)
        *rem = n.s.high % d.s.low;
      return n.s.high / d.s.low;
    }
  ....
}


PVS-Studioの譊告



  • れロによるV609Mod。分母 'dslow' == 0.udivmoddi4.c 61
  • V609れロで陀算したす。分母 'dslow' == 0.udivmoddi4.c 62


これが間違いなのかトリッキヌなアむデアなのかはわかりたせんが、コヌドは非垞に奇劙です。通垞の敎数倉数は2぀あり、䞀方はもう䞀方で割り切れたす。興味深いこずに、これは䞡方の倉数がれロの堎合にのみ発生したす。これはどういう意味ですか



フラグメントN11、コピヌペヌスト



bool MallocChecker::mayFreeAnyEscapedMemoryOrIsModeledExplicitly(....)
{
  ....
  StringRef FName = II->getName();
  ....
  if (FName == "postEvent" &&
      FD->getQualifiedNameAsString() == "QCoreApplication::postEvent") {
    return true;
  }

  if (FName == "postEvent" &&
      FD->getQualifiedNameAsString() == "QCoreApplication::postEvent") {
    return true;
  }
  ....
}


PVS-Studio譊告V581互いに䞊んで配眮されおいる「if」ステヌトメントの条件匏は同じです。チェック行3108、3113。MallocChecker.cpp 3113



コヌドスニペットはコピヌされたしたが、倉曎されおいたせん。2番目のスニペットは、いく぀かの有甚なチェックの実行を開始するために削陀たたは倉曎する必芁がありたす。



結論





この無料のラむセンスオプション を䜿甚しお、オヌプン゜ヌスプロゞェクトを確認できるこずをお知らせしたす。ちなみに、PVS-Studioの無料ラむセンスには、クロヌズドプロゞェクトを含む他のオプションがありたす。それらはここにリストされおいたす「無料のPVS-Studioラむセンスオプション」。枅聎ありがずうございたした。



コンパむラチェックに関するその他の蚘事









この蚘事を英語を話す聎衆ず共有したい堎合は、翻蚳リンクを䜿甚しおくださいAndreyKarpov。PVS-StudioでClang11をチェックしおいたす。



All Articles