From a129173a8a4ef8ebc696fc9d281c507a11da25ec Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Wed, 19 Jun 2019 23:33:34 +0000 Subject: [PATCH 001/181] [analyzer] RetainCount: Add support for OSRequiredCast(). It's a new API for custom RTTI in Apple IOKit/DriverKit framework that is similar to OSDynamicCast() that's already supported, but crashes instead of returning null (and therefore causing UB when the cast fails unexpectedly). Kind of like cast_or_null<> as opposed to dyn_cast_or_null<> in LLVM's RTTI. Historically, RetainCountChecker was responsible for modeling OSDynamicCast. This is simply an extension of the same functionality. Differential Revision: https://reviews.llvm.org/D63117 llvm-svn: 363891 (cherry picked from commit b03854f8e87e89051da5eae6c267020801fb39a0) --- clang/lib/Analysis/RetainSummaryManager.cpp | 9 +++++++- clang/test/Analysis/os_object_base.h | 4 ++++ .../test/Analysis/osobject-retain-release.cpp | 21 ++++++++++++++++++- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/clang/lib/Analysis/RetainSummaryManager.cpp b/clang/lib/Analysis/RetainSummaryManager.cpp index 4f0fced60bfea..132053fd2c244 100644 --- a/clang/lib/Analysis/RetainSummaryManager.cpp +++ b/clang/lib/Analysis/RetainSummaryManager.cpp @@ -152,6 +152,10 @@ static bool isOSObjectDynamicCast(StringRef S) { return S == "safeMetaCast"; } +static bool isOSObjectRequiredCast(StringRef S) { + return S == "requiredMetaCast"; +} + static bool isOSObjectThisCast(StringRef S) { return S == "metaCast"; } @@ -234,7 +238,8 @@ RetainSummaryManager::getSummaryForOSObject(const FunctionDecl *FD, if (RetTy->isPointerType()) { const CXXRecordDecl *PD = RetTy->getPointeeType()->getAsCXXRecordDecl(); if (PD && isOSObjectSubclass(PD)) { - if (isOSObjectDynamicCast(FName) || isOSObjectThisCast(FName)) + if (isOSObjectDynamicCast(FName) || isOSObjectRequiredCast(FName) || + isOSObjectThisCast(FName)) return getDefaultSummary(); // TODO: Add support for the slightly common *Matching(table) idiom. @@ -745,6 +750,8 @@ RetainSummaryManager::canEval(const CallExpr *CE, const FunctionDecl *FD, if (TrackOSObjects) { if (isOSObjectDynamicCast(FName) && FD->param_size() >= 1) { return BehaviorSummary::IdentityOrZero; + } else if (isOSObjectRequiredCast(FName) && FD->param_size() >= 1) { + return BehaviorSummary::Identity; } else if (isOSObjectThisCast(FName) && isa(FD) && !cast(FD)->isStatic()) { return BehaviorSummary::IdentityThis; diff --git a/clang/test/Analysis/os_object_base.h b/clang/test/Analysis/os_object_base.h index cd59e4f0bca09..37a3fc07df71c 100644 --- a/clang/test/Analysis/os_object_base.h +++ b/clang/test/Analysis/os_object_base.h @@ -12,6 +12,8 @@ #define OSDynamicCast(type, inst) \ ((type *) OSMetaClassBase::safeMetaCast((inst), OSTypeID(type))) +#define OSRequiredCast(type, inst) \ + ((type *) OSMetaClassBase::requiredMetaCast((inst), OSTypeID(type))) #define OSTypeAlloc(type) ((type *) ((type::metaClass)->alloc())) @@ -22,6 +24,8 @@ struct OSMetaClass; struct OSMetaClassBase { static OSMetaClassBase *safeMetaCast(const OSMetaClassBase *inst, const OSMetaClass *meta); + static OSMetaClassBase *requiredMetaCast(const OSMetaClassBase *inst, + const OSMetaClass *meta); OSMetaClassBase *metaCast(const char *toMeta); diff --git a/clang/test/Analysis/osobject-retain-release.cpp b/clang/test/Analysis/osobject-retain-release.cpp index 10ef144bf36e9..afcc242583588 100644 --- a/clang/test/Analysis/osobject-retain-release.cpp +++ b/clang/test/Analysis/osobject-retain-release.cpp @@ -1,9 +1,11 @@ // RUN: %clang_analyze_cc1 -fblocks -analyze -analyzer-output=text\ -// RUN: -analyzer-checker=core,osx -verify %s +// RUN: -analyzer-checker=core,osx,debug.ExprInspection -verify %s #include "os_object_base.h" #include "os_smart_ptr.h" +void clang_analyzer_eval(bool); + struct OSIterator : public OSObject { static const OSMetaClass * const metaClass; }; @@ -483,6 +485,23 @@ void check_dynamic_cast() { arr->release(); } +void check_required_cast() { + OSArray *arr = OSRequiredCast(OSArray, OSObject::generateObject(1)); + arr->release(); // no-warning +} + +void check_cast_behavior(OSObject *obj) { + OSArray *arr1 = OSDynamicCast(OSArray, obj); + clang_analyzer_eval(arr1 == obj); // expected-warning{{TRUE}} + // expected-note@-1{{TRUE}} + // expected-note@-2{{Assuming 'arr1' is not equal to 'obj'}} + // expected-warning@-3{{FALSE}} + // expected-note@-4 {{FALSE}} + OSArray *arr2 = OSRequiredCast(OSArray, obj); + clang_analyzer_eval(arr2 == obj); // expected-warning{{TRUE}} + // expected-note@-1{{TRUE}} +} + unsigned int check_dynamic_cast_no_null_on_orig(OSObject *obj) { OSArray *arr = OSDynamicCast(OSArray, obj); if (arr) { From 1a1eb30863a3d4c9ca976c24db27f445659be3c4 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Wed, 19 Jun 2019 23:33:39 +0000 Subject: [PATCH 002/181] [analyzer] DeadStores: Add a crude suppression files generated by DriverKit IIG. IIG is a replacement for MIG in DriverKit: IIG is autogenerating C++ code. Suppress dead store warnings on such code, as the tool seems to be producing them regularly, and the users of IIG are not in position to address these warnings, as they don't control the autogenerated code. IIG-generated code is identified by looking at the comments at the top of the file. Differential Revision: https://reviews.llvm.org/D63118 llvm-svn: 363892 (cherry picked from commit 3707b05211f90f2d3b8f31d15b59e8f6c12f3b8b) --- .../Checkers/DeadStoresChecker.cpp | 23 ++++++++++++++++++ clang/test/Analysis/deadstores-driverkit.cpp | 24 +++++++++++++++++++ clang/test/Analysis/os_object_base.h | 8 +++++++ 3 files changed, 55 insertions(+) create mode 100644 clang/test/Analysis/deadstores-driverkit.cpp diff --git a/clang/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp index e316c9120b28e..b6fa47e469949 100644 --- a/clang/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp @@ -160,6 +160,26 @@ class DeadStoreObs : public LiveVariables::Observer { return InEH->count(D); } + bool isSuppressed(SourceRange R) { + SourceManager &SMgr = Ctx.getSourceManager(); + SourceLocation Loc = R.getBegin(); + if (!Loc.isValid()) + return false; + + FileID FID = SMgr.getFileID(Loc); + bool Invalid = false; + StringRef Data = SMgr.getBufferData(FID, &Invalid); + if (Invalid) + return false; + + // Files autogenerated by DriverKit IIG contain some dead stores that + // we don't want to report. + if (Data.startswith("/* iig generated from")) + return true; + + return false; + } + void Report(const VarDecl *V, DeadStoreKind dsk, PathDiagnosticLocation L, SourceRange R) { if (Escaped.count(V)) @@ -175,6 +195,9 @@ class DeadStoreObs : public LiveVariables::Observer { if (!reachableCode->isReachable(currentBlock)) return; + if (isSuppressed(R)) + return; + SmallString<64> buf; llvm::raw_svector_ostream os(buf); const char *BugType = nullptr; diff --git a/clang/test/Analysis/deadstores-driverkit.cpp b/clang/test/Analysis/deadstores-driverkit.cpp new file mode 100644 index 0000000000000..446821b32cab9 --- /dev/null +++ b/clang/test/Analysis/deadstores-driverkit.cpp @@ -0,0 +1,24 @@ +/* iig generated from SomethingSomething.iig */ + +// The comment above is the whole point of the test. +// That's how the suppression works. +// It needs to be on the top. +// Run-lines can wait. + +// RUN: %clang_analyze_cc1 -w -triple x86_64-apple-driverkit19.0 \ +// RUN: -analyzer-checker=deadcode -verify %s + +// expected-no-diagnostics + +#include "os_object_base.h" + +class OSSomething { + kern_return_t Invoke(const IORPC); + void foo(OSDispatchMethod supermethod) { + kern_return_t ret; + IORPC rpc; + // Test the DriverKit specific suppression in the dead stores checker. + if (supermethod) ret = supermethod((OSObject *)this, rpc); // no-warning + else ret = ((OSObject *)this)->Invoke(rpc); // no-warning + } +}; diff --git a/clang/test/Analysis/os_object_base.h b/clang/test/Analysis/os_object_base.h index 37a3fc07df71c..4698185f2b3cb 100644 --- a/clang/test/Analysis/os_object_base.h +++ b/clang/test/Analysis/os_object_base.h @@ -19,6 +19,9 @@ using size_t = decltype(sizeof(int)); +typedef int kern_return_t; +struct IORPC {}; + struct OSMetaClass; struct OSMetaClassBase { @@ -37,8 +40,13 @@ struct OSMetaClassBase { virtual void free(); virtual ~OSMetaClassBase(){}; + + kern_return_t Invoke(IORPC invoke); }; +typedef kern_return_t (*OSDispatchMethod)(OSMetaClassBase *self, + const IORPC rpc); + struct OSObject : public OSMetaClassBase { virtual ~OSObject(){} From fbcd0ffa95dce00929c3a664ce6a4f0bc0dfab4e Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Wed, 19 Jun 2019 23:33:42 +0000 Subject: [PATCH 003/181] [analyzer] NFC: Change evalCall() to provide a CallEvent. This changes the checker callback signature to use the modern, easy to use interface. Additionally, this unblocks future work on allowing checkers to implement evalCall() for calls that don't correspond to any call-expression or require additional information that's only available as part of the CallEvent, such as C++ constructors and destructors. Differential Revision: https://reviews.llvm.org/D62440 llvm-svn: 363893 (cherry picked from commit 44820630dfa45bc47748a5abda7d4a9cb86da2c1) --- .../clang/StaticAnalyzer/Core/Checker.h | 5 +- .../StaticAnalyzer/Core/CheckerManager.h | 2 +- .../Checkers/BuiltinFunctionChecker.cpp | 31 +++++----- .../Checkers/CStringChecker.cpp | 7 ++- .../StaticAnalyzer/Checkers/ChrootChecker.cpp | 57 +++++++------------ .../Checkers/ExprInspectionChecker.cpp | 9 ++- .../RetainCountChecker/RetainCountChecker.cpp | 11 +++- .../RetainCountChecker/RetainCountChecker.h | 2 +- .../Checkers/SmartPtrModeling.cpp | 24 ++++---- .../Checkers/StdLibraryFunctionsChecker.cpp | 10 +++- .../StaticAnalyzer/Checkers/StreamChecker.cpp | 11 +++- .../StaticAnalyzer/Core/CheckerManager.cpp | 14 +++-- 12 files changed, 96 insertions(+), 87 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/Checker.h b/clang/include/clang/StaticAnalyzer/Core/Checker.h index db3ae74f3e71c..d0fe15f8b8963 100644 --- a/clang/include/clang/StaticAnalyzer/Core/Checker.h +++ b/clang/include/clang/StaticAnalyzer/Core/Checker.h @@ -474,8 +474,9 @@ class Assume { class Call { template - static bool _evalCall(void *checker, const CallExpr *CE, CheckerContext &C) { - return ((const CHECKER *)checker)->evalCall(CE, C); + static bool _evalCall(void *checker, const CallEvent &Call, + CheckerContext &C) { + return ((const CHECKER *)checker)->evalCall(Call, C); } public: diff --git a/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h b/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h index 98c69039efd1f..6cc4baa1687fd 100644 --- a/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h +++ b/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h @@ -490,7 +490,7 @@ class CheckerManager { CheckerFn; - using EvalCallFunc = CheckerFn; + using EvalCallFunc = CheckerFn; using CheckEndOfTranslationUnit = CheckerFn { public: - bool evalCall(const CallExpr *CE, CheckerContext &C) const; + bool evalCall(const CallEvent &Call, CheckerContext &C) const; }; } -bool BuiltinFunctionChecker::evalCall(const CallExpr *CE, +bool BuiltinFunctionChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { ProgramStateRef state = C.getState(); - const FunctionDecl *FD = C.getCalleeDecl(CE); - const LocationContext *LCtx = C.getLocationContext(); + const auto *FD = dyn_cast_or_null(Call.getDecl()); if (!FD) return false; + const LocationContext *LCtx = C.getLocationContext(); + const Expr *CE = Call.getOriginExpr(); + switch (FD->getBuiltinID()) { default: return false; case Builtin::BI__builtin_assume: { - assert (CE->arg_begin() != CE->arg_end()); - SVal ArgSVal = C.getSVal(CE->getArg(0)); - if (ArgSVal.isUndef()) + assert (Call.getNumArgs() > 0); + SVal Arg = Call.getArgSVal(0); + if (Arg.isUndef()) return true; // Return true to model purity. - state = state->assume(ArgSVal.castAs(), true); + state = state->assume(Arg.castAs(), true); // FIXME: do we want to warn here? Not right now. The most reports might // come from infeasible paths, thus being false positives. if (!state) { @@ -66,9 +69,9 @@ bool BuiltinFunctionChecker::evalCall(const CallExpr *CE, // __builtin_assume_aligned, just return the value of the subexpression. // __builtin_addressof is going from a reference to a pointer, but those // are represented the same way in the analyzer. - assert (CE->arg_begin() != CE->arg_end()); - SVal X = C.getSVal(*(CE->arg_begin())); - C.addTransition(state->BindExpr(CE, LCtx, X)); + assert (Call.getNumArgs() > 0); + SVal Arg = Call.getArgSVal(0); + C.addTransition(state->BindExpr(CE, LCtx, Arg)); return true; } @@ -82,12 +85,14 @@ bool BuiltinFunctionChecker::evalCall(const CallExpr *CE, // Set the extent of the region in bytes. This enables us to use the // SVal of the argument directly. If we save the extent in bits, we // cannot represent values like symbol*8. - auto Size = C.getSVal(*(CE->arg_begin())).castAs(); + auto Size = Call.getArgSVal(0); + if (Size.isUndef()) + return true; // Return true to model purity. SValBuilder& svalBuilder = C.getSValBuilder(); DefinedOrUnknownSVal Extent = R->getExtent(svalBuilder); DefinedOrUnknownSVal extentMatchesSizeArg = - svalBuilder.evalEQ(state, Extent, Size); + svalBuilder.evalEQ(state, Extent, Size.castAs()); state = state->assume(extentMatchesSizeArg, true); assert(state && "The region should not have any previous constraints"); diff --git a/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp index 73a5d58d9eeab..2aa1b2223ac52 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp @@ -17,6 +17,7 @@ #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" #include "llvm/ADT/STLExtras.h" @@ -57,7 +58,7 @@ class CStringChecker : public Checker< eval::Call, static void *getTag() { static int tag; return &tag; } - bool evalCall(const CallExpr *CE, CheckerContext &C) const; + bool evalCall(const CallEvent &Call, CheckerContext &C) const; void checkPreStmt(const DeclStmt *DS, CheckerContext &C) const; void checkLiveSymbols(ProgramStateRef state, SymbolReaper &SR) const; void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const; @@ -2334,8 +2335,8 @@ static CStringChecker::FnCheck identifyCall(const CallExpr *CE, return nullptr; } -bool CStringChecker::evalCall(const CallExpr *CE, CheckerContext &C) const { - +bool CStringChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { + const auto *CE = dyn_cast_or_null(Call.getOriginExpr()); FnCheck evalFunction = identifyCall(CE, C); // If the callee isn't a string function, let another checker handle it. diff --git a/clang/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp index cbc5b32931b6d..9fffedfccd871 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp @@ -14,6 +14,7 @@ #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" @@ -37,53 +38,44 @@ bool isRootChanged(intptr_t k) { return k == ROOT_CHANGED; } // ROOT_CHANGED<--chdir(..)-- JAIL_ENTERED<--chdir(..)-- // | | // bug<--foo()-- JAIL_ENTERED<--foo()-- -class ChrootChecker : public Checker > { - mutable IdentifierInfo *II_chroot, *II_chdir; +class ChrootChecker : public Checker { // This bug refers to possibly break out of a chroot() jail. mutable std::unique_ptr BT_BreakJail; + const CallDescription Chroot{"chroot", 1}, Chdir{"chdir", 1}; + public: - ChrootChecker() : II_chroot(nullptr), II_chdir(nullptr) {} + ChrootChecker() {} static void *getTag() { static int x; return &x; } - bool evalCall(const CallExpr *CE, CheckerContext &C) const; - void checkPreStmt(const CallExpr *CE, CheckerContext &C) const; + bool evalCall(const CallEvent &Call, CheckerContext &C) const; + void checkPreCall(const CallEvent &Call, CheckerContext &C) const; private: - void Chroot(CheckerContext &C, const CallExpr *CE) const; - void Chdir(CheckerContext &C, const CallExpr *CE) const; + void evalChroot(const CallEvent &Call, CheckerContext &C) const; + void evalChdir(const CallEvent &Call, CheckerContext &C) const; }; } // end anonymous namespace -bool ChrootChecker::evalCall(const CallExpr *CE, CheckerContext &C) const { - const FunctionDecl *FD = C.getCalleeDecl(CE); - if (!FD) - return false; - - ASTContext &Ctx = C.getASTContext(); - if (!II_chroot) - II_chroot = &Ctx.Idents.get("chroot"); - if (!II_chdir) - II_chdir = &Ctx.Idents.get("chdir"); - - if (FD->getIdentifier() == II_chroot) { - Chroot(C, CE); +bool ChrootChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { + if (Call.isCalled(Chroot)) { + evalChroot(Call, C); return true; } - if (FD->getIdentifier() == II_chdir) { - Chdir(C, CE); + if (Call.isCalled(Chdir)) { + evalChdir(Call, C); return true; } return false; } -void ChrootChecker::Chroot(CheckerContext &C, const CallExpr *CE) const { +void ChrootChecker::evalChroot(const CallEvent &Call, CheckerContext &C) const { ProgramStateRef state = C.getState(); ProgramStateManager &Mgr = state->getStateManager(); @@ -93,7 +85,7 @@ void ChrootChecker::Chroot(CheckerContext &C, const CallExpr *CE) const { C.addTransition(state); } -void ChrootChecker::Chdir(CheckerContext &C, const CallExpr *CE) const { +void ChrootChecker::evalChdir(const CallEvent &Call, CheckerContext &C) const { ProgramStateRef state = C.getState(); ProgramStateManager &Mgr = state->getStateManager(); @@ -103,7 +95,7 @@ void ChrootChecker::Chdir(CheckerContext &C, const CallExpr *CE) const { return; // After chdir("/"), enter the jail, set the enum value JAIL_ENTERED. - const Expr *ArgExpr = CE->getArg(0); + const Expr *ArgExpr = Call.getArgExpr(0); SVal ArgVal = C.getSVal(ArgExpr); if (const MemRegion *R = ArgVal.getAsRegion()) { @@ -120,19 +112,10 @@ void ChrootChecker::Chdir(CheckerContext &C, const CallExpr *CE) const { } // Check the jail state before any function call except chroot and chdir(). -void ChrootChecker::checkPreStmt(const CallExpr *CE, CheckerContext &C) const { - const FunctionDecl *FD = C.getCalleeDecl(CE); - if (!FD) - return; - - ASTContext &Ctx = C.getASTContext(); - if (!II_chroot) - II_chroot = &Ctx.Idents.get("chroot"); - if (!II_chdir) - II_chdir = &Ctx.Idents.get("chdir"); - +void ChrootChecker::checkPreCall(const CallEvent &Call, + CheckerContext &C) const { // Ignore chroot and chdir. - if (FD->getIdentifier() == II_chroot || FD->getIdentifier() == II_chdir) + if (Call.isCalled(Chroot) || Call.isCalled(Chdir)) return; // If jail state is ROOT_CHANGED, generate BugReport. diff --git a/clang/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp index 7f715c9ba201f..f23f784016d88 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp @@ -11,6 +11,7 @@ #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/IssueHash.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/Support/ScopedPrinter.h" @@ -53,7 +54,7 @@ class ExprInspectionChecker : public Checker(Call.getOriginExpr()); + if (!CE) + return false; + // These checks should have no effect on the surrounding environment // (globals should not be invalidated, etc), hence the use of evalCall. FnCheck Handler = llvm::StringSwitch(C.getCalleeName(CE)) diff --git a/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.cpp index b7cbcc7d53bb5..31d2d7c125e26 100644 --- a/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.cpp @@ -886,14 +886,19 @@ void RetainCountChecker::processNonLeakError(ProgramStateRef St, // Handle the return values of retain-count-related functions. //===----------------------------------------------------------------------===// -bool RetainCountChecker::evalCall(const CallExpr *CE, CheckerContext &C) const { +bool RetainCountChecker::evalCall(const CallEvent &Call, + CheckerContext &C) const { ProgramStateRef state = C.getState(); - const FunctionDecl *FD = C.getCalleeDecl(CE); + const auto *FD = dyn_cast_or_null(Call.getDecl()); if (!FD) return false; + const auto *CE = dyn_cast_or_null(Call.getOriginExpr()); + if (!CE) + return false; + RetainSummaryManager &SmrMgr = getSummaryManager(C); - QualType ResultTy = CE->getCallReturnType(C.getASTContext()); + QualType ResultTy = Call.getResultType(); // See if the function has 'rc_ownership_trusted_implementation' // annotate attribute. If it does, we will not inline it. diff --git a/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.h b/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.h index 506ece1e57858..124c0e5040b99 100644 --- a/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.h +++ b/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.h @@ -310,7 +310,7 @@ class RetainCountChecker const CallEvent &Call, CheckerContext &C) const; - bool evalCall(const CallExpr *CE, CheckerContext &C) const; + bool evalCall(const CallEvent &Call, CheckerContext &C) const; ProgramStateRef evalAssume(ProgramStateRef state, SVal Cond, bool Assumption) const; diff --git a/clang/lib/StaticAnalyzer/Checkers/SmartPtrModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/SmartPtrModeling.cpp index 4b321f0f6aa99..fd372aafa50d9 100644 --- a/clang/lib/StaticAnalyzer/Checkers/SmartPtrModeling.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/SmartPtrModeling.cpp @@ -26,32 +26,30 @@ using namespace ento; namespace { class SmartPtrModeling : public Checker { - bool isNullAfterMoveMethod(const CXXInstanceCall *Call) const; + bool isNullAfterMoveMethod(const CallEvent &Call) const; public: - bool evalCall(const CallExpr *CE, CheckerContext &C) const; + bool evalCall(const CallEvent &Call, CheckerContext &C) const; }; } // end of anonymous namespace -bool SmartPtrModeling::isNullAfterMoveMethod( - const CXXInstanceCall *Call) const { +bool SmartPtrModeling::isNullAfterMoveMethod(const CallEvent &Call) const { // TODO: Update CallDescription to support anonymous calls? // TODO: Handle other methods, such as .get() or .release(). // But once we do, we'd need a visitor to explain null dereferences // that are found via such modeling. - const auto *CD = dyn_cast_or_null(Call->getDecl()); + const auto *CD = dyn_cast_or_null(Call.getDecl()); return CD && CD->getConversionType()->isBooleanType(); } -bool SmartPtrModeling::evalCall(const CallExpr *CE, CheckerContext &C) const { - CallEventRef<> CallRef = C.getStateManager().getCallEventManager().getCall( - CE, C.getState(), C.getLocationContext()); - const auto *Call = dyn_cast_or_null(CallRef); - if (!Call || !isNullAfterMoveMethod(Call)) +bool SmartPtrModeling::evalCall(const CallEvent &Call, + CheckerContext &C) const { + if (!isNullAfterMoveMethod(Call)) return false; ProgramStateRef State = C.getState(); - const MemRegion *ThisR = Call->getCXXThisVal().getAsRegion(); + const MemRegion *ThisR = + cast(&Call)->getCXXThisVal().getAsRegion(); if (!move::isMovedFrom(State, ThisR)) { // TODO: Model this case as well. At least, avoid invalidation of globals. @@ -60,8 +58,8 @@ bool SmartPtrModeling::evalCall(const CallExpr *CE, CheckerContext &C) const { // TODO: Add a note to bug reports describing this decision. C.addTransition( - State->BindExpr(CE, C.getLocationContext(), - C.getSValBuilder().makeZeroVal(CE->getType()))); + State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), + C.getSValBuilder().makeZeroVal(Call.getResultType()))); return true; } diff --git a/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp index 13f39bd8e72c3..2cdee8da375e7 100644 --- a/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp @@ -224,7 +224,7 @@ class StdLibraryFunctionsChecker : public Checker { public: void checkPostCall(const CallEvent &Call, CheckerContext &C) const; - bool evalCall(const CallExpr *CE, CheckerContext &C) const; + bool evalCall(const CallEvent &Call, CheckerContext &C) const; private: Optional findFunctionSummary(const FunctionDecl *FD, @@ -367,12 +367,16 @@ void StdLibraryFunctionsChecker::checkPostCall(const CallEvent &Call, } } -bool StdLibraryFunctionsChecker::evalCall(const CallExpr *CE, +bool StdLibraryFunctionsChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { - const FunctionDecl *FD = dyn_cast_or_null(CE->getCalleeDecl()); + const auto *FD = dyn_cast_or_null(Call.getDecl()); if (!FD) return false; + const auto *CE = dyn_cast_or_null(Call.getOriginExpr()); + if (!CE) + return false; + Optional FoundSummary = findFunctionSummary(FD, CE, C); if (!FoundSummary) return false; diff --git a/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp index 1e690bc6ca4ed..1ea5e07695133 100644 --- a/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp @@ -14,6 +14,7 @@ #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" @@ -71,7 +72,7 @@ class StreamChecker : public Checker(Call.getDecl()); if (!FD || FD->getKind() != Decl::Function) return false; + const auto *CE = dyn_cast_or_null(Call.getOriginExpr()); + if (!CE) + return false; + ASTContext &Ctx = C.getASTContext(); if (!II_fopen) II_fopen = &Ctx.Idents.get("fopen"); diff --git a/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp b/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp index cda9fe9bf5c8f..27d5797b4cbc9 100644 --- a/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp +++ b/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp @@ -651,7 +651,6 @@ void CheckerManager::runCheckersForEvalCall(ExplodedNodeSet &Dst, const ExplodedNodeSet &Src, const CallEvent &Call, ExprEngine &Eng) { - const CallExpr *CE = cast(Call.getOriginExpr()); for (const auto Pred : Src) { bool anyEvaluated = false; @@ -660,16 +659,19 @@ void CheckerManager::runCheckersForEvalCall(ExplodedNodeSet &Dst, // Check if any of the EvalCall callbacks can evaluate the call. for (const auto EvalCallChecker : EvalCallCheckers) { - ProgramPoint::Kind K = ProgramPoint::PostStmtKind; - const ProgramPoint &L = - ProgramPoint::getProgramPoint(CE, K, Pred->getLocationContext(), - EvalCallChecker.Checker); + // TODO: Support the situation when the call doesn't correspond + // to any Expr. + ProgramPoint L = ProgramPoint::getProgramPoint( + cast(Call.getOriginExpr()), + ProgramPoint::PostStmtKind, + Pred->getLocationContext(), + EvalCallChecker.Checker); bool evaluated = false; { // CheckerContext generates transitions(populates checkDest) on // destruction, so introduce the scope to make sure it gets properly // populated. CheckerContext C(B, Eng, Pred, L); - evaluated = EvalCallChecker(CE, C); + evaluated = EvalCallChecker(Call, C); } assert(!(evaluated && anyEvaluated) && "There are more than one checkers evaluating the call"); From 767a5c4bb0bd591c2e31ee2b2cb3f90bc25357a1 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Wed, 19 Jun 2019 23:33:45 +0000 Subject: [PATCH 004/181] [analyzer] Fix JSON dumps for dynamic type information. They're now valid JSON. Differential Revision: https://reviews.llvm.org/D62716 llvm-svn: 363894 (cherry picked from commit 3bb7b2ec7f7bcb913dc4a44fdbe14af508acefb7) --- clang/lib/StaticAnalyzer/Core/DynamicTypeMap.cpp | 4 ++-- clang/test/Analysis/dump_egraph.cpp | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Core/DynamicTypeMap.cpp b/clang/lib/StaticAnalyzer/Core/DynamicTypeMap.cpp index 22c4cc4a624a7..75ae2606910ac 100644 --- a/clang/lib/StaticAnalyzer/Core/DynamicTypeMap.cpp +++ b/clang/lib/StaticAnalyzer/Core/DynamicTypeMap.cpp @@ -72,12 +72,12 @@ void printDynamicTypeInfoJson(raw_ostream &Out, ProgramStateRef State, Out << "{ \"region\": \"" << MR << "\", \"dyn_type\": "; if (DTI.isValid()) { Out << '\"' << DTI.getType()->getPointeeType().getAsString() - << "\" \"sub_classable\": " + << "\", \"sub_classable\": " << (DTI.canBeASubClass() ? "true" : "false"); } else { Out << "null"; // Invalid type info } - Out << "\" }"; + Out << "}"; if (std::next(I) != DTM.end()) Out << ','; diff --git a/clang/test/Analysis/dump_egraph.cpp b/clang/test/Analysis/dump_egraph.cpp index c62e4bfd4c106..d74009589f0d4 100644 --- a/clang/test/Analysis/dump_egraph.cpp +++ b/clang/test/Analysis/dump_egraph.cpp @@ -14,11 +14,14 @@ struct T { void foo() { // Test that dumping symbols conjured on null statements doesn't crash. T t; + + new S; } // CHECK: \"location_context\": \"#0 Call\", \"calling\": \"foo\", \"call_line\": null, \"items\": [\l        \{ \"lctx_id\": 1, \"stmt_id\": {{[0-9]+}}, \"kind\": \"construct into local variable\", \"argument_index\": null, \"pretty\": \"T t;\", \"value\": \"&t\" // CHECK: \"location_context\": \"#0 Call\", \"calling\": \"T::T\", \"call_line\": \"16\", \"items\": [\l        \{ \"lctx_id\": 2, \"init_id\": {{[0-9]+}}, \"kind\": \"construct into member variable\", \"argument_index\": null, \"pretty\": \"s\", \"value\": \"&t-\>s\" -// CHECK: \"cluster\": \"t\", \"items\": [\l        \{ \"kind\": \"Default\", \"offset\": 0, \"value\": \"conj_$3\{int, LC3, no stmt, #1\}\" +// CHECK: \"cluster\": \"t\", \"items\": [\l        \{ \"kind\": \"Default\", \"offset\": 0, \"value\": \"conj_$2\{int, LC5, no stmt, #1\}\" +// CHECK: \"dynamic_types\": [\l\{ \"region\": \"HeapSymRegion\{conj_$1\{struct S *, LC1, S{{[0-9]+}}, #1\}\}\", \"dyn_type\": \"struct S\", \"sub_classable\": false\}\l From 7a8f00cc2647d2f5d19aefe020ab0417b5af81fd Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Wed, 19 Jun 2019 23:33:48 +0000 Subject: [PATCH 005/181] [analyzer] Fix JSON dumps for location contexts. Location context ID is a property of the location context, not of an item within it. It's useful to know the id even when there are no items in the context, eg. for the purposes of figuring out how did contents of the Environment for the same location context changed across states. Differential Revision: https://reviews.llvm.org/D62754 llvm-svn: 363895 (cherry picked from commit f9f6cdb1a8d05585256f6f2285aa684e557e5676) --- clang/lib/Analysis/AnalysisDeclContext.cpp | 5 +++-- clang/lib/StaticAnalyzer/Core/Environment.cpp | 3 +-- clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 3 --- clang/test/Analysis/dump_egraph.cpp | 4 ++-- clang/test/Analysis/expr-inspection.c | 4 ++-- 5 files changed, 8 insertions(+), 11 deletions(-) diff --git a/clang/lib/Analysis/AnalysisDeclContext.cpp b/clang/lib/Analysis/AnalysisDeclContext.cpp index ba1f8375124ea..28d308132fdbd 100644 --- a/clang/lib/Analysis/AnalysisDeclContext.cpp +++ b/clang/lib/Analysis/AnalysisDeclContext.cpp @@ -527,7 +527,8 @@ void LocationContext::printJson(raw_ostream &Out, const char *NL, unsigned Frame = 0; for (const LocationContext *LCtx = this; LCtx; LCtx = LCtx->getParent()) { - Indent(Out, Space, IsDot) << "{ \"location_context\": \""; + Indent(Out, Space, IsDot) + << "{ \"lctx_id\": " << LCtx->getID() << ", \"location_context\": \""; switch (LCtx->getKind()) { case StackFrame: Out << '#' << Frame << " Call\", \"calling\": \""; @@ -541,7 +542,7 @@ void LocationContext::printJson(raw_ostream &Out, const char *NL, if (const Stmt *S = cast(LCtx)->getCallSite()) { Out << '\"'; printLocation(Out, SM, S->getBeginLoc()); - Out << '\"'; + Out << '\"'; } else { Out << "null"; } diff --git a/clang/lib/StaticAnalyzer/Core/Environment.cpp b/clang/lib/StaticAnalyzer/Core/Environment.cpp index 94cc4d6dbb2fd..0ca3f778f1b9c 100644 --- a/clang/lib/StaticAnalyzer/Core/Environment.cpp +++ b/clang/lib/StaticAnalyzer/Core/Environment.cpp @@ -261,8 +261,7 @@ void Environment::printJson(raw_ostream &Out, const ASTContext &Ctx, const Stmt *S = I->first.getStmt(); Indent(Out, InnerSpace, IsDot) - << "{ \"lctx_id\": " << LC->getID() - << ", \"stmt_id\": " << S->getID(Ctx) << ", \"pretty\": "; + << "{ \"stmt_id\": " << S->getID(Ctx) << ", \"pretty\": "; S->printJson(Out, nullptr, PP, /*AddQuotes=*/true); Out << ", \"value\": "; diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp index b217889390414..2cef99157c7fe 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -149,9 +149,6 @@ class ConstructedObjectKey { if (!S) I = getItem().getCXXCtorInitializer(); - // IDs - Out << "\"lctx_id\": " << getLocationContext()->getID() << ", "; - if (S) Out << "\"stmt_id\": " << S->getID(getASTContext()); else diff --git a/clang/test/Analysis/dump_egraph.cpp b/clang/test/Analysis/dump_egraph.cpp index d74009589f0d4..3609420198f74 100644 --- a/clang/test/Analysis/dump_egraph.cpp +++ b/clang/test/Analysis/dump_egraph.cpp @@ -18,9 +18,9 @@ void foo() { new S; } -// CHECK: \"location_context\": \"#0 Call\", \"calling\": \"foo\", \"call_line\": null, \"items\": [\l        \{ \"lctx_id\": 1, \"stmt_id\": {{[0-9]+}}, \"kind\": \"construct into local variable\", \"argument_index\": null, \"pretty\": \"T t;\", \"value\": \"&t\" +// CHECK: \"location_context\": \"#0 Call\", \"calling\": \"foo\", \"call_line\": null, \"items\": [\l        \{ \"stmt_id\": {{[0-9]+}}, \"kind\": \"construct into local variable\", \"argument_index\": null, \"pretty\": \"T t;\", \"value\": \"&t\" -// CHECK: \"location_context\": \"#0 Call\", \"calling\": \"T::T\", \"call_line\": \"16\", \"items\": [\l        \{ \"lctx_id\": 2, \"init_id\": {{[0-9]+}}, \"kind\": \"construct into member variable\", \"argument_index\": null, \"pretty\": \"s\", \"value\": \"&t-\>s\" +// CHECK: \"location_context\": \"#0 Call\", \"calling\": \"T::T\", \"call_line\": \"16\", \"items\": [\l        \{ \"init_id\": {{[0-9]+}}, \"kind\": \"construct into member variable\", \"argument_index\": null, \"pretty\": \"s\", \"value\": \"&t-\>s\" // CHECK: \"cluster\": \"t\", \"items\": [\l        \{ \"kind\": \"Default\", \"offset\": 0, \"value\": \"conj_$2\{int, LC5, no stmt, #1\}\" diff --git a/clang/test/Analysis/expr-inspection.c b/clang/test/Analysis/expr-inspection.c index a1fd952b26d59..230ee5007c0e2 100644 --- a/clang/test/Analysis/expr-inspection.c +++ b/clang/test/Analysis/expr-inspection.c @@ -30,8 +30,8 @@ void foo(int x) { // CHECK-NEXT: ]} // CHECK-NEXT: ], // CHECK-NEXT: "environment": [ -// CHECK-NEXT: { "location_context": "#0 Call", "calling": "foo", "call_line": null, "items": [ -// CHECK-NEXT: { "lctx_id": 1, "stmt_id": {{[0-9]+}}, "pretty": "clang_analyzer_printState", "value": "&code{clang_analyzer_printState}" } +// CHECK-NEXT: { "lctx_id": 1, "location_context": "#0 Call", "calling": "foo", "call_line": null, "items": [ +// CHECK-NEXT: { "stmt_id": {{[0-9]+}}, "pretty": "clang_analyzer_printState", "value": "&code{clang_analyzer_printState}" } // CHECK-NEXT: ]} // CHECK-NEXT: ], // CHECK-NEXT: "constraints": [ From 8ad26fd189bc85ba7b4473e3a906a452ec0688ad Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Wed, 19 Jun 2019 23:33:51 +0000 Subject: [PATCH 006/181] [analyzer] Fix JSON dumps for store clusters. Include a unique pointer so that it was possible to figure out if it's the same cluster in different program states. This allows comparing dumps of different states against each other. Differential Revision: https://reviews.llvm.org/D63362 llvm-svn: 363896 (cherry picked from commit 064c8c689a38bab87c7d8cabe34f4b67ce6b9d35) --- clang/lib/StaticAnalyzer/Core/RegionStore.cpp | 3 ++- clang/test/Analysis/dump_egraph.cpp | 2 +- clang/test/Analysis/expr-inspection.c | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp index 53d0cf54d703f..fa9f751c3f74f 100644 --- a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp +++ b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp @@ -211,7 +211,8 @@ class RegionBindingsRef : public llvm::ImmutableMapRefs\" -// CHECK: \"cluster\": \"t\", \"items\": [\l        \{ \"kind\": \"Default\", \"offset\": 0, \"value\": \"conj_$2\{int, LC5, no stmt, #1\}\" +// CHECK: \"cluster\": \"t\", \"pointer\": \"{{0x[0-9a-f]+}}\", \"items\": [\l        \{ \"kind\": \"Default\", \"offset\": 0, \"value\": \"conj_$2\{int, LC5, no stmt, #1\}\" // CHECK: \"dynamic_types\": [\l\{ \"region\": \"HeapSymRegion\{conj_$1\{struct S *, LC1, S{{[0-9]+}}, #1\}\}\", \"dyn_type\": \"struct S\", \"sub_classable\": false\}\l diff --git a/clang/test/Analysis/expr-inspection.c b/clang/test/Analysis/expr-inspection.c index 230ee5007c0e2..841b30a9b99d7 100644 --- a/clang/test/Analysis/expr-inspection.c +++ b/clang/test/Analysis/expr-inspection.c @@ -25,7 +25,7 @@ void foo(int x) { // CHECK: "program_state": { // CHECK-NEXT: "store": [ -// CHECK-NEXT: { "cluster": "y", "items": [ +// CHECK-NEXT: { "cluster": "y", "pointer": "{{0x[0-9a-f]+}}", "items": [ // CHECK-NEXT: { "kind": "Direct", "offset": 0, "value": "2 S32b" } // CHECK-NEXT: ]} // CHECK-NEXT: ], From 57a50d232ac31321697bf03d652615cddcb8b53c Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Wed, 19 Jun 2019 23:33:55 +0000 Subject: [PATCH 007/181] [analyzer] exploded-graph-rewriter: Fix escaping StringRegions. Quotes around StringRegions are now escaped and unescaped correctly, producing valid JSON. Additionally, add a forgotten escape for Store values. Differential Revision: https://reviews.llvm.org/D63519 llvm-svn: 363897 (cherry picked from commit b50d1673581a4f8dd33c36eb5a9964ad5fe4b2c4) --- clang/lib/StaticAnalyzer/Core/RegionStore.cpp | 6 ++++-- .../Analysis/exploded-graph-rewriter/escapes.c | 18 ++++++++++++++++++ .../exploded-graph-rewriter/lit.local.cfg | 2 +- .../utils/analyzer/exploded-graph-rewriter.py | 1 + 4 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 clang/test/Analysis/exploded-graph-rewriter/escapes.c diff --git a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp index fa9f751c3f74f..9bc0e62c27ff3 100644 --- a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp +++ b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp @@ -210,6 +210,7 @@ class RegionBindingsRef : public llvm::ImmutableMapRefStore: + // CHECK-SAME: foo0 + // CHECK-SAME: &Element\{"foo",0 S64b,char\} + // CHECK: Environment: + // CHECK-SAME: "foo" + // CHECK-SAME: &Element\{"foo",0 S64b,char\} + const char *const foo = "foo"; +} diff --git a/clang/test/Analysis/exploded-graph-rewriter/lit.local.cfg b/clang/test/Analysis/exploded-graph-rewriter/lit.local.cfg index 7bc2e107f6423..dfeb0a86c4f3c 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/lit.local.cfg +++ b/clang/test/Analysis/exploded-graph-rewriter/lit.local.cfg @@ -15,4 +15,4 @@ config.substitutions.append(('%exploded_graph_rewriter', config.clang_src_dir, 'utils', 'analyzer'))))) -config.suffixes = ['.dot'] +config.suffixes.add('.dot') diff --git a/clang/utils/analyzer/exploded-graph-rewriter.py b/clang/utils/analyzer/exploded-graph-rewriter.py index dbfd086215385..7f83f801aea1e 100755 --- a/clang/utils/analyzer/exploded-graph-rewriter.py +++ b/clang/utils/analyzer/exploded-graph-rewriter.py @@ -199,6 +199,7 @@ def add_raw_line(self, raw_line): .replace('\\"', '"') \ .replace('\\{', '{') \ .replace('\\}', '}') \ + .replace('\\\\', '\\') \ .replace('\\<', '\\\\<') \ .replace('\\>', '\\\\>') \ .rstrip(',') From 6aecb50e83d9510e1d6f07d75f8f08efeb1b33e5 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Wed, 19 Jun 2019 23:33:59 +0000 Subject: [PATCH 008/181] [analyzer] exploded-graph-rewriter: Implement a --diff mode. In this mode the tool would avoid duplicating the contents of the program state on every node, replacing them with a diff-like dump of changes that happened on that node. This is useful because most of the time we only interested in whether the effect of the statement was modeled correctly. A diffed graph would also be much faster to load and navigate, being much smaller than the original graph. The diffs are computed "semantically" as opposed to plain text diffs. I.e., the diff algorithm is hand-crafted separately for every state trait, taking the underlying data structures into account. This is especially nice for Environment because textual diffs would have been terrible. On the other hand, it requires some boilerplate to implement. Differential Revision: https://reviews.llvm.org/D62761 llvm-svn: 363898 (cherry picked from commit 5740e77f03edd81d7c8832ad4ce45ed82c3a3556) --- .../environment_diff.dot | 110 +++++++ .../program_points.dot | 1 - .../exploded-graph-rewriter/store.dot | 1 + .../exploded-graph-rewriter/store_diff.dot | 82 +++++ .../utils/analyzer/exploded-graph-rewriter.py | 279 ++++++++++++++---- 5 files changed, 411 insertions(+), 62 deletions(-) create mode 100644 clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot create mode 100644 clang/test/Analysis/exploded-graph-rewriter/store_diff.dot diff --git a/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot b/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot new file mode 100644 index 0000000000000..83cf0eb94716f --- /dev/null +++ b/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot @@ -0,0 +1,110 @@ +// RUN: %exploded_graph_rewriter -d %s | FileCheck %s + +// FIXME: Substitution doesn't seem to work on Windows. +// UNSUPPORTED: system-windows + +// No diffs on the first node, nothing to check. +Node0x1 [shape=record,label= + "{ + { "node_id": 1, + "pointer": "0x1", + "state_id": 2, + "program_points": [], + "program_state": { + "store": null, + "environment": [ + { + "location_context": "#0 Call", + "lctx_id": 3, + "calling": "foo", + "call_line": 4, + "items": [ + { + "stmt_id": 5, + "pretty": "bar()", + "value": "Unknown" + } + ] + } + ] + } + } +\l}"]; + +Node0x1 -> Node0x6; + +// CHECK: Node0x6 [ +// CHECK-SAME: +// CHECK-SAME: - +// CHECK-SAME: S5 +// CHECK-SAME: bar() +// CHECK-SAME: Unknown +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: + +// CHECK-SAME: S9 +// CHECK-SAME: baz() +// CHECK-SAME: Undefined +// CHECK-SAME: +Node0x6 [shape=record,label= + "{ + { "node_id": 6, + "pointer": "0x6", + "state_id": 7, + "program_points": [], + "program_state": { + "store": null, + "environment": [ + { + "location_context": "#0 Call", + "lctx_id": 3, + "calling": "foo", + "call_line": 4, + "items": [ + { + "stmt_id": 9, + "pretty": "baz()", + "value": "Undefined" + } + ] + } + ] + } + } +\l}"]; + +Node0x6 -> Node0x9; +// Make sure that the last node in the path is not diffed. +// CHECK: Node0x9 [ +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: S9 +// CHECK-SAME: baz() +// CHECK-SAME: Undefined +// CHECK-SAME: +Node0x9 [shape=record,label= + "{ + { "node_id": 9, + "pointer": "0x9", + "state_id": 7, + "program_points": [], + "program_state": { + "store": null, + "environment": [ + { + "location_context": "#0 Call", + "lctx_id": 3, + "calling": "foo", + "call_line": 4, + "items": [ + { + "stmt_id": 9, + "pretty": "baz()", + "value": "Undefined" + } + ] + } + ] + } + } +\l}"]; diff --git a/clang/test/Analysis/exploded-graph-rewriter/program_points.dot b/clang/test/Analysis/exploded-graph-rewriter/program_points.dot index aadabf3955641..26beff4be895c 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/program_points.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/program_points.dot @@ -7,7 +7,6 @@ // CHECK-SAME: // CHECK-SAME: // CHECK-SAME: // CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +Node0x4 [shape=record,label= + "{ + { "node_id": 4, + "pointer": "0x4", + "state_id": 5, + "program_points": [], + "program_state": { + "environment": null, + "store": [ + { + "cluster": "x", + "pointer": "0x3", + "items": [ + { + "kind": "Default", + "offset": 0, + "value": "Unknown" + } + ] + } + ] + } + } +\l}"]; + +Node0x4 -> Node0x6; + +Node0x6 [shape=record,label= + "{ + { "node_id": 6, + "pointer": "0x6", + "state_id": 7, + "program_points": [], + "program_state": null + } +\l}"]; diff --git a/clang/utils/analyzer/exploded-graph-rewriter.py b/clang/utils/analyzer/exploded-graph-rewriter.py index 7f83f801aea1e..1ee1b34afa0d4 100755 --- a/clang/utils/analyzer/exploded-graph-rewriter.py +++ b/clang/utils/analyzer/exploded-graph-rewriter.py @@ -18,6 +18,13 @@ import re +# A helper function for finding the difference between two dictionaries. +def diff_dicts(curr, prev): + removed = [k for k in prev if k not in curr or curr[k] != prev[k]] + added = [k for k in curr if k not in prev or curr[k] != prev[k]] + return (removed, added) + + # A deserialized source location. class SourceLocation(object): def __init__(self, json_loc): @@ -47,13 +54,21 @@ def __init__(self, json_pp): self.block_id = json_pp['block_id'] -# A value of a single expression in a deserialized Environment. -class EnvironmentBinding(object): - def __init__(self, json_eb): - super(EnvironmentBinding, self).__init__() - self.stmt_id = json_eb['stmt_id'] - self.pretty = json_eb['pretty'] - self.value = json_eb['value'] +# A single expression acting as a key in a deserialized Environment. +class EnvironmentBindingKey(object): + def __init__(self, json_ek): + super(EnvironmentBindingKey, self).__init__() + self.stmt_id = json_ek['stmt_id'] + self.pretty = json_ek['pretty'] + + def _key(self): + return self.stmt_id + + def __eq__(self, other): + return self._key() == other._key() + + def __hash__(self): + return hash(self._key()) # Deserialized description of a location context. @@ -65,6 +80,15 @@ def __init__(self, json_frame): self.decl = json_frame['calling'] self.line = json_frame['call_line'] + def _key(self): + return self.lctx_id + + def __eq__(self, other): + return self._key() == other._key() + + def __hash__(self): + return hash(self._key()) + # A group of deserialized Environment bindings that correspond to a specific # location context. @@ -72,8 +96,17 @@ class EnvironmentFrame(object): def __init__(self, json_frame): super(EnvironmentFrame, self).__init__() self.location_context = LocationContext(json_frame) - self.bindings = [EnvironmentBinding(b) for b in json_frame['items']] \ - if json_frame['items'] is not None else [] + self.bindings = collections.OrderedDict( + [(EnvironmentBindingKey(b), + b['value']) for b in json_frame['items']] + if json_frame['items'] is not None else []) + + def diff_bindings(self, prev): + return diff_dicts(self.bindings, prev.bindings) + + def is_different(self, prev): + removed, added = self.diff_bindings(prev) + return len(removed) != 0 or len(added) != 0 # A deserialized Environment. @@ -82,14 +115,46 @@ def __init__(self, json_e): super(Environment, self).__init__() self.frames = [EnvironmentFrame(f) for f in json_e] + def diff_frames(self, prev): + # TODO: It's difficult to display a good diff when frame numbers shift. + if len(self.frames) != len(prev.frames): + return None + + updated = [] + for i in range(len(self.frames)): + f = self.frames[i] + prev_f = prev.frames[i] + if f.location_context == prev_f.location_context: + if f.is_different(prev_f): + updated.append(i) + else: + # We have the whole frame replaced with another frame. + # TODO: Produce a nice diff. + return None + + # TODO: Add support for added/removed. + return updated + + def is_different(self, prev): + updated = self.diff_frames(prev) + return updated is None or len(updated) > 0 + + +# A single binding key in a deserialized RegionStore cluster. +class StoreBindingKey(object): + def __init__(self, json_sk): + super(StoreBindingKey, self).__init__() + self.kind = json_sk['kind'] + self.offset = json_sk['offset'] -# A single binding in a deserialized RegionStore cluster. -class StoreBinding(object): - def __init__(self, json_sb): - super(StoreBinding, self).__init__() - self.kind = json_sb['kind'] - self.offset = json_sb['offset'] - self.value = json_sb['value'] + def _key(self): + return (self.kind, self.offset) + + def __eq__(self, other): + return self._key() == other._key() + + def __hash__(self): + return hash(self._key()) # A single cluster of the deserialized RegionStore. @@ -97,14 +162,34 @@ class StoreCluster(object): def __init__(self, json_sc): super(StoreCluster, self).__init__() self.base_region = json_sc['cluster'] - self.bindings = [StoreBinding(b) for b in json_sc['items']] + self.bindings = collections.OrderedDict( + [(StoreBindingKey(b), b['value']) for b in json_sc['items']]) + + def diff_bindings(self, prev): + return diff_dicts(self.bindings, prev.bindings) + + def is_different(self, prev): + removed, added = self.diff_bindings(prev) + return len(removed) != 0 or len(added) != 0 # A deserialized RegionStore. class Store(object): def __init__(self, json_s): super(Store, self).__init__() - self.clusters = [StoreCluster(c) for c in json_s] + self.clusters = collections.OrderedDict( + [(c['pointer'], StoreCluster(c)) for c in json_s]) + + def diff_clusters(self, prev): + removed = [k for k in prev.clusters if k not in self.clusters] + added = [k for k in self.clusters if k not in prev.clusters] + updated = [k for k in prev.clusters if k in self.clusters + and prev.clusters[k].is_different(self.clusters[k])] + return (removed, added, updated) + + def is_different(self, prev): + removed, added, updated = self.diff_clusters(prev) + return len(removed) != 0 or len(added) != 0 or len(updated) != 0 # A deserialized program state. @@ -213,8 +298,9 @@ def add_raw_line(self, raw_line): # A visitor that dumps the ExplodedGraph into a DOT file with fancy HTML-based # syntax highlighing. class DotDumpVisitor(object): - def __init__(self): + def __init__(self, do_diffs): super(DotDumpVisitor, self).__init__() + self._do_diffs = do_diffs @staticmethod def _dump_raw(s): @@ -230,6 +316,14 @@ def _dump(s): .replace('\\l', '
') .replace('|', ''), end='') + @staticmethod + def _diff_plus_minus(is_added): + if is_added is None: + return '' + if is_added: + return '+' + return '-' + def visit_begin_graph(self, graph): self._graph = graph self._dump_raw('digraph "ExplodedGraph" {\n') @@ -263,72 +357,128 @@ def visit_program_point(self, p): '%s' % (color, p.stmt_kind, p.pretty)) elif p.kind == 'Edge': - self._dump('' + self._dump('' '' % (color, p.kind, p.src_id, p.dst_id)) else: # TODO: Print more stuff for other kinds of points. - self._dump('' + self._dump('' '' % (color, p.kind)) - def visit_environment(self, e): + def visit_environment(self, e, prev_e=None): self._dump('
-// CHECK-SAME: - // CHECK-SAME: // CHECK-SAME: Edge diff --git a/clang/test/Analysis/exploded-graph-rewriter/store.dot b/clang/test/Analysis/exploded-graph-rewriter/store.dot index 8152a9929fe90..3f1a937b737d2 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/store.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/store.dot @@ -31,6 +31,7 @@ Node0x1 [shape=record,label= "store": [ { "cluster": "x", + "pointer": "0x3", "items": [ { "kind": "Default", diff --git a/clang/test/Analysis/exploded-graph-rewriter/store_diff.dot b/clang/test/Analysis/exploded-graph-rewriter/store_diff.dot new file mode 100644 index 0000000000000..47ea9af014b3d --- /dev/null +++ b/clang/test/Analysis/exploded-graph-rewriter/store_diff.dot @@ -0,0 +1,82 @@ +// RUN: %exploded_graph_rewriter -d %s | FileCheck %s + +// FIXME: Substitution doesn't seem to work on Windows. +// UNSUPPORTED: system-windows + +Node0x1 [shape=record,label= + "{ + { "node_id": 1, + "pointer": "0x1", + "state_id": 2, + "program_points": [], + "program_state": { + "environment": null, + "store": [ + { + "cluster": "x", + "pointer": "0x3", + "items": [ + { + "kind": "Default", + "offset": 0, + "value": "Undefined" + } + ] + } + ] + } + } +\l}"]; + +Node0x1 -> Node0x4; + +// CHECK: Node0x4 [ +// CHECK-SAME:
-x0(Default)Undefined
+x0(Default)Unknown
%s
-
' '%s' '[B%d] -\\> [B%d]
-
' '%s
') - for f in e.frames: - self._dump('' + def dump_location_context(lc, is_added=None): + self._dump('' + '' '' - % (f.location_context.caption, - f.location_context.decl, - ('(line %s)' % f.location_context.line) - if f.location_context.line is not None else '')) - for b in f.bindings: - self._dump('' - '' - '' - % (b.stmt_id, b.pretty, b.value)) + % (self._diff_plus_minus(is_added), + lc.caption, lc.decl, + ('(line %s)' % lc.line) if lc.line is not None + else '')) + + def dump_binding(f, b, is_added=None): + self._dump('' + '' + '' + '' + % (self._diff_plus_minus(is_added), + b.stmt_id, b.pretty, f.bindings[b])) + + frames_updated = e.diff_frames(prev_e) if prev_e is not None else None + if frames_updated: + for i in frames_updated: + f = e.frames[i] + prev_f = prev_e.frames[i] + dump_location_context(f.location_context) + bindings_removed, bindings_added = f.diff_bindings(prev_f) + for b in bindings_removed: + dump_binding(prev_f, b, False) + for b in bindings_added: + dump_binding(f, b, True) + else: + for f in e.frames: + dump_location_context(f.location_context) + for b in f.bindings: + dump_binding(f, b) self._dump('
%s
%s%s%s ' '%s
S%s%s%s
%sS%s%s%s
') - def visit_store(self, s): + def visit_store(self, s, prev_s=None): self._dump('') - for c in s.clusters: - for b in c.bindings: - self._dump('' - '' - '' - '' - % (c.base_region, b.offset, - '(Default)' if b.kind == 'Default' - else '', - b.value)) + def dump_binding(s, c, b, is_added=None): + self._dump('' + '' + '' + '' + '' + % (self._diff_plus_minus(is_added), + s.clusters[c].base_region, b.offset, + '(Default)' if b.kind == 'Default' + else '', + s.clusters[c].bindings[b])) + + if prev_s is not None: + clusters_removed, clusters_added, clusters_updated = \ + s.diff_clusters(prev_s) + for c in clusters_removed: + for b in prev_s.clusters[c].bindings: + dump_binding(prev_s, c, b, False) + for c in clusters_updated: + bindings_removed, bindings_added = \ + s.clusters[c].diff_bindings(prev_s.clusters[c]) + for b in bindings_removed: + dump_binding(prev_s, c, b, False) + for b in bindings_added: + dump_binding(s, c, b, True) + for c in clusters_added: + for b in s.clusters[c].bindings: + dump_binding(s, c, b, True) + else: + for c in s.clusters: + for b in s.clusters[c].bindings: + dump_binding(s, c, b) self._dump('
%s%s%s%s
%s%s%s%s%s
') - def visit_state(self, s): - self._dump('' - 'Store: ') + def visit_state(self, s, prev_s): + # == Store == + self._dump('Store: ') if s.store is None: self._dump(' Nothing!') else: - self._dump('' - '') - self.visit_store(s.store) + if prev_s is not None and prev_s.store is not None: + if s.store.is_different(prev_s.store): + self._dump('') + self.visit_store(s.store, prev_s.store) + else: + self._dump(' No changes!') + else: + self._dump('') + self.visit_store(s.store) + self._dump('
') - self._dump('
' - '' + # == Environment == + self._dump('' 'Environment: ') if s.environment is None: self._dump(' Nothing!') else: - self._dump('' - '') - self.visit_environment(s.environment) + if prev_s is not None and prev_s.environment is not None: + if s.environment.is_different(prev_s.environment): + self._dump('') + self.visit_environment(s.environment, prev_s.environment) + else: + self._dump(' No changes!') + else: + self._dump('') + self.visit_environment(s.environment) self._dump('') @@ -353,7 +503,14 @@ def visit_node(self, node): if node.state is not None: self._dump('
') - self.visit_state(node.state) + prev_s = None + # Do diffs only when we have a unique predecessor. + # Don't do diffs on the leaf nodes because they're + # the important ones. + if self._do_diffs and len(node.predecessors) == 1 \ + and len(node.successors) > 0: + prev_s = self._graph.nodes[node.predecessors[0]].state + self.visit_state(node.state, prev_s) self._dump_raw('>];\n') def visit_edge(self, pred, succ): @@ -383,13 +540,13 @@ def explore(self, graph, visitor): def main(): parser = argparse.ArgumentParser() parser.add_argument('filename', type=str) - parser.add_argument('-d', '--debug', action='store_const', dest='loglevel', - const=logging.DEBUG, default=logging.WARNING, - help='enable debug prints') parser.add_argument('-v', '--verbose', action='store_const', - dest='loglevel', const=logging.INFO, + dest='loglevel', const=logging.DEBUG, default=logging.WARNING, help='enable info prints') + parser.add_argument('-d', '--diff', action='store_const', dest='diff', + const=True, default=False, + help='display differences between states') args = parser.parse_args() logging.basicConfig(level=args.loglevel) @@ -400,7 +557,7 @@ def main(): graph.add_raw_line(raw_line) explorer = Explorer() - visitor = DotDumpVisitor() + visitor = DotDumpVisitor(args.diff) explorer.explore(graph, visitor) From 4ba630a072e8f0615da565dafce1782453e61b7e Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Thu, 20 Jun 2019 22:29:40 +0000 Subject: [PATCH 009/181] [analyzer] DeadStores: Update the crude suppression for files generated by IIG. They changed the comments that we were looking for. llvm-svn: 363995 (cherry picked from commit 5c287f751aa1e0f6e5ede0cf612e222cb24889b4) --- clang/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp | 2 +- clang/test/Analysis/deadstores-driverkit.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp index b6fa47e469949..d5baa2bcba6fc 100644 --- a/clang/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp @@ -174,7 +174,7 @@ class DeadStoreObs : public LiveVariables::Observer { // Files autogenerated by DriverKit IIG contain some dead stores that // we don't want to report. - if (Data.startswith("/* iig generated from")) + if (Data.startswith("/* iig")) return true; return false; diff --git a/clang/test/Analysis/deadstores-driverkit.cpp b/clang/test/Analysis/deadstores-driverkit.cpp index 446821b32cab9..0885367b1b9bf 100644 --- a/clang/test/Analysis/deadstores-driverkit.cpp +++ b/clang/test/Analysis/deadstores-driverkit.cpp @@ -1,4 +1,4 @@ -/* iig generated from SomethingSomething.iig */ +/* iig(DriverKit-60) generated from SomethingSomething.iig */ // The comment above is the whole point of the test. // That's how the suppression works. From 292d81fe522b860c8b501053e70fbcd6e30464d8 Mon Sep 17 00:00:00 2001 From: Csaba Dabis Date: Mon, 24 Jun 2019 16:06:44 +0000 Subject: [PATCH 010/181] [analyzer] Fix JSON dumps for ExplodedNodes Summary: - Now we could see the `has_report` property in `trim-egraph` mode. - This patch also removes the trailing comma after each node. Reviewers: NoQ Reviewed By: NoQ Subscribers: xazax.hun, baloghadamsoftware, szepet, a.sidorin, mikhail.ramalho, Szelethus, donat.nagy, dkrupp, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D63436 llvm-svn: 364193 (cherry picked from commit 906d494b6e7eb0d8bde19bec2de7d93a9516ebe3) --- clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 8 ++------ clang/test/Analysis/dump_egraph.c | 12 ++++++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp index 2cef99157c7fe..38422eb1b1bee 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -3009,7 +3009,7 @@ struct DOTGraphTraits : public DefaultDOTGraphTraits { for (const auto &EQ : EQClasses) { for (const BugReport &Report : EQ) { - if (Report.getErrorNode() == N) + if (Report.getErrorNode()->getState() == N->getState()) return true; } } @@ -3109,11 +3109,7 @@ struct DOTGraphTraits : public DefaultDOTGraphTraits { Indent(Out, Space, IsDot) << "\"program_state\": null"; } - Out << "\\l}"; - if (!N->succ_empty()) - Out << ','; - Out << "\\l"; - + Out << "\\l}\\l"; return Out.str(); } }; diff --git a/clang/test/Analysis/dump_egraph.c b/clang/test/Analysis/dump_egraph.c index f1ac03b10cc94..701e985079832 100644 --- a/clang/test/Analysis/dump_egraph.c +++ b/clang/test/Analysis/dump_egraph.c @@ -1,6 +1,12 @@ -// RUN: %clang_analyze_cc1 -analyzer-checker=core -analyzer-dump-egraph=%t.dot %s +// RUN: %clang_analyze_cc1 -analyzer-checker=core \ +// RUN: -analyzer-dump-egraph=%t.dot %s // RUN: cat %t.dot | FileCheck %s -// RUN: %clang_analyze_cc1 -analyzer-checker=core -analyzer-dump-egraph=%t.dot -trim-egraph %s + +// RUN: %clang_analyze_cc1 -analyzer-checker=core \ +// RUN: -analyzer-dump-egraph=%t.dot \ +// RUN: -trim-egraph %s +// RUN: cat %t.dot | FileCheck %s + // REQUIRES: asserts int getJ(); @@ -10,8 +16,6 @@ int foo() { return *x + *y; } -// CHECK: digraph "Exploded Graph" { - // CHECK: \"program_points\": [\l    \{ \"kind\": \"Edge\", \"src_id\": 2, \"dst_id\": 1, \"terminator\": null, \"term_kind\": null, \"tag\": null \}\l  ],\l  \"program_state\": null // CHECK: \"program_points\": [\l    \{ \"kind\": \"BlockEntrance\", \"block_id\": 1 From 1c919cf4d6bed358130a05e2c1416149b30f94de Mon Sep 17 00:00:00 2001 From: Csaba Dabis Date: Mon, 24 Jun 2019 16:19:39 +0000 Subject: [PATCH 011/181] [analyzer] print() JSONify: ProgramPoint revision Summary: Now we also print out the filename with its path. Reviewers: NoQ Reviewed By: NoQ Subscribers: xazax.hun, baloghadamsoftware, szepet, a.sidorin, mikhail.ramalho, Szelethus, donat.nagy, dkrupp, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D63438 llvm-svn: 364197 (cherry picked from commit 3a4a60eb6a73e4f927895ca567bb714885648c36) --- clang/lib/Analysis/ProgramPoint.cpp | 3 ++- clang/test/Analysis/dump_egraph.c | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/clang/lib/Analysis/ProgramPoint.cpp b/clang/lib/Analysis/ProgramPoint.cpp index 45f165677f99d..0398251b5eab6 100644 --- a/clang/lib/Analysis/ProgramPoint.cpp +++ b/clang/lib/Analysis/ProgramPoint.cpp @@ -55,7 +55,8 @@ static void printLocJson(raw_ostream &Out, SourceLocation Loc, } Out << "{ \"line\": " << SM.getExpansionLineNumber(Loc) - << ", \"column\": " << SM.getExpansionColumnNumber(Loc) << " }"; + << ", \"column\": " << SM.getExpansionColumnNumber(Loc) + << ", \"file\": \"" << SM.getFilename(Loc) << "\" }"; } void ProgramPoint::printJson(llvm::raw_ostream &Out, const char *NL) const { diff --git a/clang/test/Analysis/dump_egraph.c b/clang/test/Analysis/dump_egraph.c index 701e985079832..5544d9fae2e26 100644 --- a/clang/test/Analysis/dump_egraph.c +++ b/clang/test/Analysis/dump_egraph.c @@ -22,3 +22,5 @@ int foo() { // CHECK: \"has_report\": true +// CHECK: \"pretty\": \"*x\", \"location\": \{ \"line\": 16, \"column\": 10, \"file\": \"{{(.+)}}dump_egraph.c\" \} + From c08ef72efc3006a9943fbff966210a82cda105d7 Mon Sep 17 00:00:00 2001 From: Csaba Dabis Date: Tue, 25 Jun 2019 00:44:33 +0000 Subject: [PATCH 012/181] [analyzer] ExprEngine: Escape pointers in bitwise operations Summary: After evaluation it would be an Unknown value and tracking would be lost. Reviewers: NoQ, xazax.hun, ravikandhadai, baloghadamsoftware, Szelethus Reviewed By: NoQ Subscribers: szepet, rnkovacs, a.sidorin, mikhail.ramalho, donat.nagy, dkrupp, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D63720 llvm-svn: 364259 (cherry picked from commit 49885b1245c2678299e1e22c18add2511377b37e) --- clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp | 4 +++ clang/test/Analysis/symbol-escape.cpp | 33 +++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 clang/test/Analysis/symbol-escape.cpp diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp index cc62cf1048c03..3b5a437e3c15d 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp @@ -100,6 +100,10 @@ void ExprEngine::VisitBinaryOperator(const BinaryOperator* B, SVal Result = evalBinOp(state, Op, LeftV, RightV, B->getType()); if (!Result.isUnknown()) { state = state->BindExpr(B, LCtx, Result); + } else { + // If we cannot evaluate the operation escape the operands. + state = escapeValue(state, LeftV, PSK_EscapeOther); + state = escapeValue(state, RightV, PSK_EscapeOther); } Bldr.generateNode(B, *it, state); diff --git a/clang/test/Analysis/symbol-escape.cpp b/clang/test/Analysis/symbol-escape.cpp new file mode 100644 index 0000000000000..be5dfbcd9ef58 --- /dev/null +++ b/clang/test/Analysis/symbol-escape.cpp @@ -0,0 +1,33 @@ +// RUN: %clang_analyze_cc1 \ +// RUN: -analyzer-checker=core,cplusplus.NewDeleteLeaks \ +// RUN: -verify %s + +// expected-no-diagnostics: Whenever we cannot evaluate an operation we escape +// the operands. After the evaluation it would be an +// Unknown value and the tracking would be lost. + +typedef unsigned __INTPTR_TYPE__ uintptr_t; + +class C {}; + +C *simple_escape_in_bitwise_op(C *Foo) { + C *Bar = new C(); + Bar = reinterpret_cast(reinterpret_cast(Bar) & 0x1); + (void)Bar; + // no-warning: "Potential leak of memory pointed to by 'Bar'" was here. + + return Bar; +} + +C **indirect_escape_in_bitwise_op() { + C *Qux = new C(); + C **Baz = &Qux; + Baz = reinterpret_cast(reinterpret_cast(Baz) | 0x1); + Baz = reinterpret_cast(reinterpret_cast(Baz) & + ~static_cast(0x1)); + // no-warning: "Potential leak of memory pointed to by 'Qux'" was here. + + delete *Baz; + return Baz; +} + From 7266f07779bc3cf7d653eb8352564e1e740cbf47 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Tue, 25 Jun 2019 02:16:47 +0000 Subject: [PATCH 013/181] [analyzer] Add more timers for performance profiling. The -analyzer-stats flag now allows you to find out how much time was spent on AST-based analysis and on path-sensitive analysis and, separately, on bug visitors, as they're occasionally a performance problem on their own. The total timer wasn't useful because there's anyway a total time printed out. Remove it. Differential Revision: https://reviews.llvm.org/D63227 llvm-svn: 364266 (cherry picked from commit c48be7fc1cd3e45fbae1a96f54eb7b7666a29332) --- .../Core/PathSensitive/ExprEngine.h | 2 +- clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 4 -- .../Frontend/AnalysisConsumer.cpp | 43 +++++++++++++++---- 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h index 6daaa129fc7cb..2629d7121de4c 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h @@ -161,7 +161,7 @@ class ExprEngine : public SubEngine { SetOfConstDecls *VisitedCalleesIn, FunctionSummariesTy *FS, InliningModes HowToInlineIn); - ~ExprEngine() override; + ~ExprEngine() override = default; /// Returns true if there is still simulation state on the worklist. bool ExecuteWorkList(const LocationContext *L, unsigned Steps = 150000) { diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp index 38422eb1b1bee..628d4315bcbdd 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -225,10 +225,6 @@ ExprEngine::ExprEngine(cross_tu::CrossTranslationUnitContext &CTU, } } -ExprEngine::~ExprEngine() { - BR.FlushReports(); -} - //===----------------------------------------------------------------------===// // Utility methods. //===----------------------------------------------------------------------===// diff --git a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp index a75ff2563cf51..454b61fd51a14 100644 --- a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp +++ b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp @@ -196,7 +196,9 @@ class AnalysisConsumer : public AnalysisASTConsumer, /// Time the analyzes time of each translation unit. std::unique_ptr AnalyzerTimers; - std::unique_ptr TUTotalTimer; + std::unique_ptr SyntaxCheckTimer; + std::unique_ptr ExprEngineTimer; + std::unique_ptr BugReporterTimer; /// The information about analyzed functions shared throughout the /// translation unit. @@ -212,8 +214,13 @@ class AnalysisConsumer : public AnalysisASTConsumer, if (Opts->PrintStats || Opts->ShouldSerializeStats) { AnalyzerTimers = llvm::make_unique( "analyzer", "Analyzer timers"); - TUTotalTimer = llvm::make_unique( - "time", "Analyzer total time", *AnalyzerTimers); + SyntaxCheckTimer = llvm::make_unique( + "syntaxchecks", "Syntax-based analysis time", *AnalyzerTimers); + ExprEngineTimer = llvm::make_unique( + "exprengine", "Path exploration time", *AnalyzerTimers); + BugReporterTimer = llvm::make_unique( + "bugreporter", "Path-sensitive report post-processing time", + *AnalyzerTimers); llvm::EnableStatistics(/* PrintOnExit= */ false); } } @@ -346,8 +353,13 @@ class AnalysisConsumer : public AnalysisASTConsumer, /// Handle callbacks for arbitrary Decls. bool VisitDecl(Decl *D) { AnalysisMode Mode = getModeForDecl(D, RecVisitorMode); - if (Mode & AM_Syntax) + if (Mode & AM_Syntax) { + if (SyntaxCheckTimer) + SyntaxCheckTimer->startTimer(); checkerMgr->runCheckersOnASTDecl(D, *Mgr, *RecVisitorBR); + if (SyntaxCheckTimer) + SyntaxCheckTimer->stopTimer(); + } return true; } @@ -566,7 +578,11 @@ static bool isBisonFile(ASTContext &C) { void AnalysisConsumer::runAnalysisOnTranslationUnit(ASTContext &C) { BugReporter BR(*Mgr); TranslationUnitDecl *TU = C.getTranslationUnitDecl(); + if (SyntaxCheckTimer) + SyntaxCheckTimer->startTimer(); checkerMgr->runCheckersOnASTDecl(TU, *Mgr, BR); + if (SyntaxCheckTimer) + SyntaxCheckTimer->stopTimer(); // Run the AST-only checks using the order in which functions are defined. // If inlining is not turned on, use the simplest function order for path @@ -608,8 +624,6 @@ void AnalysisConsumer::HandleTranslationUnit(ASTContext &C) { if (Diags.hasErrorOccurred() || Diags.hasFatalErrorOccurred()) return; - if (TUTotalTimer) TUTotalTimer->startTimer(); - if (isBisonFile(C)) { reportAnalyzerProgress("Skipping bison-generated file\n"); } else if (Opts->DisableAllChecks) { @@ -622,8 +636,6 @@ void AnalysisConsumer::HandleTranslationUnit(ASTContext &C) { runAnalysisOnTranslationUnit(C); } - if (TUTotalTimer) TUTotalTimer->stopTimer(); - // Count how many basic blocks we have not covered. NumBlocksInAnalyzedFunctions = FunctionSummaries.getTotalNumBasicBlocks(); NumVisitedBlocksInAnalyzedFunctions = @@ -747,8 +759,13 @@ void AnalysisConsumer::HandleCode(Decl *D, AnalysisMode Mode, BugReporter BR(*Mgr); - if (Mode & AM_Syntax) + if (Mode & AM_Syntax) { + if (SyntaxCheckTimer) + SyntaxCheckTimer->startTimer(); checkerMgr->runCheckersOnASTBody(D, *Mgr, BR); + if (SyntaxCheckTimer) + SyntaxCheckTimer->stopTimer(); + } if ((Mode & AM_Path) && checkerMgr->hasPathSensitiveCheckers()) { RunPathSensitiveChecks(D, IMode, VisitedCallees); if (IMode != ExprEngine::Inline_Minimal) @@ -775,8 +792,12 @@ void AnalysisConsumer::RunPathSensitiveChecks(Decl *D, ExprEngine Eng(CTU, *Mgr, VisitedCallees, &FunctionSummaries, IMode); // Execute the worklist algorithm. + if (ExprEngineTimer) + ExprEngineTimer->startTimer(); Eng.ExecuteWorkList(Mgr->getAnalysisDeclContextManager().getStackFrame(D), Mgr->options.MaxNodesPerTopLevelFunction); + if (ExprEngineTimer) + ExprEngineTimer->stopTimer(); if (!Mgr->options.DumpExplodedGraphTo.empty()) Eng.DumpGraph(Mgr->options.TrimGraph, Mgr->options.DumpExplodedGraphTo); @@ -786,7 +807,11 @@ void AnalysisConsumer::RunPathSensitiveChecks(Decl *D, Eng.ViewGraph(Mgr->options.TrimGraph); // Display warnings. + if (BugReporterTimer) + BugReporterTimer->startTimer(); Eng.getBugReporter().FlushReports(); + if (BugReporterTimer) + BugReporterTimer->stopTimer(); } //===----------------------------------------------------------------------===// From ade7a5e49ebc7f871b10607ea0bef2c5278ed671 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Tue, 25 Jun 2019 02:16:50 +0000 Subject: [PATCH 014/181] [analyzer] NFC: exploded-graph-rewriter: Extract some code into functions. Differential Revision: https://reviews.llvm.org/D63684 llvm-svn: 364267 (cherry picked from commit b9c94f946f2ca45734af666d6afba0234b21411b) --- .../utils/analyzer/exploded-graph-rewriter.py | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/clang/utils/analyzer/exploded-graph-rewriter.py b/clang/utils/analyzer/exploded-graph-rewriter.py index 1ee1b34afa0d4..1eb2f896bee24 100755 --- a/clang/utils/analyzer/exploded-graph-rewriter.py +++ b/clang/utils/analyzer/exploded-graph-rewriter.py @@ -409,6 +409,24 @@ def dump_binding(f, b, is_added=None): self._dump('') + def visit_environment_in_state(self, s, prev_s=None): + self._dump('' + 'Environment: ') + if s.environment is None: + self._dump(' Nothing!') + else: + if prev_s is not None and prev_s.environment is not None: + if s.environment.is_different(prev_s.environment): + self._dump('') + self.visit_environment(s.environment, prev_s.environment) + else: + self._dump(' No changes!') + else: + self._dump('') + self.visit_environment(s.environment) + + self._dump('') + def visit_store(self, s, prev_s=None): self._dump('') @@ -447,8 +465,7 @@ def dump_binding(s, c, b, is_added=None): self._dump('
') - def visit_state(self, s, prev_s): - # == Store == + def visit_store_in_state(self, s, prev_s=None): self._dump('Store: ') if s.store is None: self._dump(' Nothing!') @@ -464,23 +481,9 @@ def visit_state(self, s, prev_s): self.visit_store(s.store) self._dump('
') - # == Environment == - self._dump('' - 'Environment: ') - if s.environment is None: - self._dump(' Nothing!') - else: - if prev_s is not None and prev_s.environment is not None: - if s.environment.is_different(prev_s.environment): - self._dump('') - self.visit_environment(s.environment, prev_s.environment) - else: - self._dump(' No changes!') - else: - self._dump('') - self.visit_environment(s.environment) - - self._dump('') + def visit_state(self, s, prev_s): + self.visit_store_in_state(s, prev_s) + self.visit_environment_in_state(s, prev_s) def visit_node(self, node): self._dump('%s [shape=record,label=<' From cec5820c87c07e68dc2275ffdb3c4b92c3b37fac Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Tue, 25 Jun 2019 02:16:53 +0000 Subject: [PATCH 015/181] [analyzer] exploded-graph-rewriter: Add support for range constraints. Diff support included. A cheap solution is implemented that treats range constraints as "some sort of key-value map", so it's going to be trivial to add support for other such maps later, such as dynamic type info. Differential Revision: https://reviews.llvm.org/D63685 llvm-svn: 364268 (cherry picked from commit beb85ad66de5af11e45bae236a3dd5a6a4762150) --- .../exploded-graph-rewriter/constraints.dot | 27 ++++++++ .../constraints_diff.dot | 65 +++++++++++++++++++ .../exploded-graph-rewriter/environment.dot | 1 + .../environment_diff.dot | 3 + .../exploded-graph-rewriter/store.dot | 1 + .../exploded-graph-rewriter/store_diff.dot | 2 + .../utils/analyzer/exploded-graph-rewriter.py | 65 ++++++++++++++++++- 7 files changed, 162 insertions(+), 2 deletions(-) create mode 100644 clang/test/Analysis/exploded-graph-rewriter/constraints.dot create mode 100644 clang/test/Analysis/exploded-graph-rewriter/constraints_diff.dot diff --git a/clang/test/Analysis/exploded-graph-rewriter/constraints.dot b/clang/test/Analysis/exploded-graph-rewriter/constraints.dot new file mode 100644 index 0000000000000..f9ffde74316ac --- /dev/null +++ b/clang/test/Analysis/exploded-graph-rewriter/constraints.dot @@ -0,0 +1,27 @@ +// RUN: %exploded_graph_rewriter %s | FileCheck %s + +// FIXME: Substitution doesn't seem to work on Windows. +// UNSUPPORTED: system-windows + +// CHECK: +// CHECK-SAME: +Node0x1 [shape=record,label= + "{ + { "node_id": 1, + "pointer": "0x1", + "state_id": 2, + "program_points": [], + "program_state": { + "store": null, + "environment": null, + "constraints": [ + { "symbol": "reg_$0", "range": "{ [0, 0] }" } + ] + } + } +\l}"]; diff --git a/clang/test/Analysis/exploded-graph-rewriter/constraints_diff.dot b/clang/test/Analysis/exploded-graph-rewriter/constraints_diff.dot new file mode 100644 index 0000000000000..a65a3a2464c9a --- /dev/null +++ b/clang/test/Analysis/exploded-graph-rewriter/constraints_diff.dot @@ -0,0 +1,65 @@ +// RUN: %exploded_graph_rewriter -d %s | FileCheck %s + +// FIXME: Substitution doesn't seem to work on Windows. +// UNSUPPORTED: system-windows + +Node0x1 [shape=record,label= + "{ + { "node_id": 1, + "pointer": "0x1", + "state_id": 2, + "program_points": [], + "program_state": { + "store": null, + "environment": null, + "constraints": [ + { "symbol": "reg_$0", "range": "{ [0, 10] }" } + ] + } + } +\l}"]; + +Node0x1 -> Node0x3; + +// CHECK: Node0x3 [ +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +Node0x3 [shape=record,label= + "{ + { "node_id": 3, + "pointer": "0x3", + "state_id": 4, + "program_points": [], + "program_state": { + "store": null, + "environment": null, + "constraints": [ + { "symbol": "reg_$0", "range": "{ [0, 5] }" } + ] + } + } +\l}"]; + +Node0x3 -> Node0x5; + +Node0x5 [shape=record,label= + "{ + { "node_id": 5, + "pointer": "0x5", + "state_id": 6, + "program_points": [], + "program_state": { + "store": null, + "environment": null, + "constraints": null + } + } +\l}"]; diff --git a/clang/test/Analysis/exploded-graph-rewriter/environment.dot b/clang/test/Analysis/exploded-graph-rewriter/environment.dot index 7271684642a28..e18cc7fc192d4 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/environment.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/environment.dot @@ -33,6 +33,7 @@ Node0x1 [shape=record,label= "program_points": [], "program_state": { "store": null, + "constraints": null, "environment": [ { "location_context": "#0 Call", diff --git a/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot b/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot index 83cf0eb94716f..33fe07d8789ff 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot @@ -12,6 +12,7 @@ Node0x1 [shape=record,label= "program_points": [], "program_state": { "store": null, + "constraints": null, "environment": [ { "location_context": "#0 Call", @@ -54,6 +55,7 @@ Node0x6 [shape=record,label= "program_points": [], "program_state": { "store": null, + "constraints": null, "environment": [ { "location_context": "#0 Call", @@ -90,6 +92,7 @@ Node0x9 [shape=record,label= "program_points": [], "program_state": { "store": null, + "constraints": null, "environment": [ { "location_context": "#0 Call", diff --git a/clang/test/Analysis/exploded-graph-rewriter/store.dot b/clang/test/Analysis/exploded-graph-rewriter/store.dot index 3f1a937b737d2..22f4eba623cd2 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/store.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/store.dot @@ -28,6 +28,7 @@ Node0x1 [shape=record,label= "program_points": [], "program_state": { "environment": null, + "constraints": null, "store": [ { "cluster": "x", diff --git a/clang/test/Analysis/exploded-graph-rewriter/store_diff.dot b/clang/test/Analysis/exploded-graph-rewriter/store_diff.dot index 47ea9af014b3d..d0a4e19817af9 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/store_diff.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/store_diff.dot @@ -11,6 +11,7 @@ Node0x1 [shape=record,label= "program_points": [], "program_state": { "environment": null, + "constraints": null, "store": [ { "cluster": "x", @@ -52,6 +53,7 @@ Node0x4 [shape=record,label= "program_points": [], "program_state": { "environment": null, + "constraints": null, "store": [ { "cluster": "x", diff --git a/clang/utils/analyzer/exploded-graph-rewriter.py b/clang/utils/analyzer/exploded-graph-rewriter.py index 1eb2f896bee24..4aae72c0da6ed 100755 --- a/clang/utils/analyzer/exploded-graph-rewriter.py +++ b/clang/utils/analyzer/exploded-graph-rewriter.py @@ -25,6 +25,19 @@ def diff_dicts(curr, prev): return (removed, added) +# Represents any program state trait that is a dictionary of key-value pairs. +class GenericMap(object): + def __init__(self, generic_map): + self.generic_map = generic_map + + def diff(self, prev): + return diff_dicts(self.generic_map, prev.generic_map) + + def is_different(self, prev): + removed, added = self.diff(prev) + return len(removed) != 0 or len(added) != 0 + + # A deserialized source location. class SourceLocation(object): def __init__(self, json_loc): @@ -203,8 +216,10 @@ def __init__(self, state_id, json_ps): if json_ps['store'] is not None else None self.environment = Environment(json_ps['environment']) \ if json_ps['environment'] is not None else None + self.constraints = GenericMap(collections.OrderedDict([ + (c['symbol'], c['range']) for c in json_ps['constraints'] + ])) if json_ps['constraints'] is not None else None # TODO: Objects under construction. - # TODO: Constraint ranges. # TODO: Dynamic types of objects. # TODO: Checker messages. @@ -479,11 +494,57 @@ def visit_store_in_state(self, s, prev_s=None): else: self._dump('
') + self._dump('') + + def visit_generic_map(self, m, prev_m=None): + self._dump('
Ranges:
+// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME:
reg_$0\{ [0, 0] \}
-reg_$0\{ [0, 10] \}
+reg_$0\{ [0, 5] \}
') self.visit_store(s.store) - self._dump('
') + + def dump_pair(m, k, is_added=None): + self._dump('' + '' + '' + % (self._diff_plus_minus(is_added), + k, m.generic_map[k])) + + if prev_m is not None: + removed, added = m.diff(prev_m) + for k in removed: + dump_pair(prev_m, k, False) + for k in added: + dump_pair(m, k, True) + else: + for k in m.generic_map: + dump_pair(m, k, None) + + self._dump('
%s%s%s
') + + def visit_generic_map_in_state(self, selector, s, prev_s=None): + self._dump('' + 'Ranges: ') + m = getattr(s, selector) + if m is None: + self._dump(' Nothing!') + else: + prev_m = None + if prev_s is not None: + prev_m = getattr(prev_s, selector) + if prev_m is not None: + if m.is_different(prev_m): + self._dump('') + self.visit_generic_map(m, prev_m) + else: + self._dump(' No changes!') + if prev_m is None: + self._dump('') + self.visit_generic_map(m) + self._dump('') def visit_state(self, s, prev_s): self.visit_store_in_state(s, prev_s) + self._dump('
') self.visit_environment_in_state(s, prev_s) + self._dump('
') + self.visit_generic_map_in_state('constraints', s, prev_s) def visit_node(self, node): self._dump('%s [shape=record,label=<' From 26838f10a3fca2cc52eae5916d8b75c3d330b5bb Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Tue, 25 Jun 2019 02:16:56 +0000 Subject: [PATCH 016/181] [analyzer] exploded-graph-rewriter: Fix escaping for bitwise-or. '|' is a special character in graphviz, so it needs to be properly escaped and unescaped. llvm-svn: 364269 (cherry picked from commit 14f4de9bb9dd04bbdf784082b78b25f0d41b186e) --- clang/test/Analysis/exploded-graph-rewriter/escapes.c | 7 ++++++- clang/utils/analyzer/exploded-graph-rewriter.py | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/clang/test/Analysis/exploded-graph-rewriter/escapes.c b/clang/test/Analysis/exploded-graph-rewriter/escapes.c index 2fd564cc5d72a..050f724f1fbd7 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/escapes.c +++ b/clang/test/Analysis/exploded-graph-rewriter/escapes.c @@ -7,7 +7,7 @@ // FIXME: Substitution doesn't seem to work on Windows. // UNSUPPORTED: system-windows -void string_region_escapes() { +void escapes() { // CHECK: // CHECK-SAME: // CHECK-SAME: @@ -15,4 +15,9 @@ void string_region_escapes() { // CHECK-SAME: // CHECK-SAME: const char *const foo = "foo"; + + // CHECK: BinaryOperator + // CHECK-SAME: + // CHECK-SAME: + int x = 1 | 2; } diff --git a/clang/utils/analyzer/exploded-graph-rewriter.py b/clang/utils/analyzer/exploded-graph-rewriter.py index 4aae72c0da6ed..668b12110b4c3 100755 --- a/clang/utils/analyzer/exploded-graph-rewriter.py +++ b/clang/utils/analyzer/exploded-graph-rewriter.py @@ -300,6 +300,7 @@ def add_raw_line(self, raw_line): .replace('\\{', '{') \ .replace('\\}', '}') \ .replace('\\\\', '\\') \ + .replace('\\|', '|') \ .replace('\\<', '\\\\<') \ .replace('\\>', '\\\\>') \ .rstrip(',') @@ -329,7 +330,7 @@ def _dump(s): .replace('\\<', '<') .replace('\\>', '>') .replace('\\l', '
') - .replace('|', ''), end='') + .replace('|', '\\|'), end='') @staticmethod def _diff_plus_minus(is_added): From db1432523cd09f9f1c04bcc225b698e47c84a7d7 Mon Sep 17 00:00:00 2001 From: Csaba Dabis Date: Tue, 25 Jun 2019 03:17:55 +0000 Subject: [PATCH 017/181] [analyzer] print() JSONify: Create pointers Summary: - Reviewers: NoQ Reviewed By: NoQ Subscribers: xazax.hun, baloghadamsoftware, szepet, a.sidorin, mikhail.ramalho, Szelethus, donat.nagy, dkrupp, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D63726 llvm-svn: 364271 (cherry picked from commit 0cdd13c05a16c90aa4dde56e1e46ca7e0a7261d6) --- clang/lib/StaticAnalyzer/Core/Environment.cpp | 7 +- clang/lib/StaticAnalyzer/Core/RegionStore.cpp | 6 +- .../exploded-graph-rewriter/environment.dot | 33 ++++--- .../environment_diff.dot | 99 ++++++++++--------- .../exploded-graph-rewriter/store.dot | 29 +++--- .../exploded-graph-rewriter/store_diff.dot | 58 ++++++----- clang/test/Analysis/expr-inspection.c | 8 +- .../utils/analyzer/exploded-graph-rewriter.py | 6 +- 8 files changed, 135 insertions(+), 111 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Core/Environment.cpp b/clang/lib/StaticAnalyzer/Core/Environment.cpp index 0ca3f778f1b9c..551c89b04db4b 100644 --- a/clang/lib/StaticAnalyzer/Core/Environment.cpp +++ b/clang/lib/StaticAnalyzer/Core/Environment.cpp @@ -204,13 +204,13 @@ void Environment::printJson(raw_ostream &Out, const ASTContext &Ctx, const LocationContext *LCtx, const char *NL, unsigned int Space, bool IsDot) const { Indent(Out, Space, IsDot) << "\"environment\": "; - ++Space; if (ExprBindings.isEmpty()) { Out << "null," << NL; return; } + ++Space; if (!LCtx) { // Find the freshest location context. llvm::SmallPtrSet FoundContexts; @@ -227,7 +227,8 @@ void Environment::printJson(raw_ostream &Out, const ASTContext &Ctx, assert(LCtx); - Out << '[' << NL; // Start of Environment. + Out << "{ \"pointer\": \"" << (const void *)LCtx->getStackFrame() + << "\", \"items\": [" << NL; PrintingPolicy PP = Ctx.getPrintingPolicy(); LCtx->printJson(Out, NL, Space, IsDot, [&](const LocationContext *LC) { @@ -280,5 +281,5 @@ void Environment::printJson(raw_ostream &Out, const ASTContext &Ctx, Out << "null "; }); - Indent(Out, --Space, IsDot) << "]," << NL; // End of Environment. + Indent(Out, --Space, IsDot) << "]}," << NL; } diff --git a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp index 9bc0e62c27ff3..a389619f84260 100644 --- a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp +++ b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp @@ -2640,7 +2640,7 @@ void RegionStoreManager::printJson(raw_ostream &Out, Store S, const char *NL, return; } - Out << '[' << NL; - Bindings.printJson(Out, NL, ++Space, IsDot); - Indent(Out, --Space, IsDot) << "]," << NL; + Out << "{ \"pointer\": \"" << Bindings.asStore() << "\", \"items\": [" << NL; + Bindings.printJson(Out, NL, Space + 1, IsDot); + Indent(Out, Space, IsDot) << "]}," << NL; } diff --git a/clang/test/Analysis/exploded-graph-rewriter/environment.dot b/clang/test/Analysis/exploded-graph-rewriter/environment.dot index e18cc7fc192d4..052f35b28fc72 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/environment.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/environment.dot @@ -34,21 +34,24 @@ Node0x1 [shape=record,label= "program_state": { "store": null, "constraints": null, - "environment": [ - { - "location_context": "#0 Call", - "lctx_id": 3, - "calling": "foo", - "call_line": 4, - "items": [ - { - "stmt_id": 5, - "pretty": "bar()", - "value": "Unknown" - } - ] - } - ] + "environment": { + "pointer": "0x2", + "items": [ + { + "location_context": "#0 Call", + "lctx_id": 3, + "calling": "foo", + "call_line": 4, + "items": [ + { + "stmt_id": 5, + "pretty": "bar()", + "value": "Unknown" + } + ] + } + ] + } } } \l}"]; diff --git a/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot b/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot index 33fe07d8789ff..84f89dd469e4a 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot @@ -13,21 +13,24 @@ Node0x1 [shape=record,label= "program_state": { "store": null, "constraints": null, - "environment": [ - { - "location_context": "#0 Call", - "lctx_id": 3, - "calling": "foo", - "call_line": 4, - "items": [ - { - "stmt_id": 5, - "pretty": "bar()", - "value": "Unknown" - } - ] - } - ] + "environment": { + "pointer": "0x2", + "items": [ + { + "location_context": "#0 Call", + "lctx_id": 3, + "calling": "foo", + "call_line": 4, + "items": [ + { + "stmt_id": 5, + "pretty": "bar()", + "value": "Unknown" + } + ] + } + ] + } } } \l}"]; @@ -56,21 +59,24 @@ Node0x6 [shape=record,label= "program_state": { "store": null, "constraints": null, - "environment": [ - { - "location_context": "#0 Call", - "lctx_id": 3, - "calling": "foo", - "call_line": 4, - "items": [ - { - "stmt_id": 9, - "pretty": "baz()", - "value": "Undefined" - } - ] - } - ] + "environment": { + "pointer": "0x2", + "items": [ + { + "location_context": "#0 Call", + "lctx_id": 3, + "calling": "foo", + "call_line": 4, + "items": [ + { + "stmt_id": 9, + "pretty": "baz()", + "value": "Undefined" + } + ] + } + ] + } } } \l}"]; @@ -93,21 +99,24 @@ Node0x9 [shape=record,label= "program_state": { "store": null, "constraints": null, - "environment": [ - { - "location_context": "#0 Call", - "lctx_id": 3, - "calling": "foo", - "call_line": 4, - "items": [ - { - "stmt_id": 9, - "pretty": "baz()", - "value": "Undefined" - } - ] - } - ] + "environment": { + "pointer": "0x2", + "items": [ + { + "location_context": "#0 Call", + "lctx_id": 3, + "calling": "foo", + "call_line": 4, + "items": [ + { + "stmt_id": 9, + "pretty": "baz()", + "value": "Undefined" + } + ] + } + ] + } } } \l}"]; diff --git a/clang/test/Analysis/exploded-graph-rewriter/store.dot b/clang/test/Analysis/exploded-graph-rewriter/store.dot index 22f4eba623cd2..8f33b513b10e4 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/store.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/store.dot @@ -29,19 +29,22 @@ Node0x1 [shape=record,label= "program_state": { "environment": null, "constraints": null, - "store": [ - { - "cluster": "x", - "pointer": "0x3", - "items": [ - { - "kind": "Default", - "offset": 0, - "value": "Undefined" - } - ] - } - ] + "store": { + "pointer": "0x2", + "items": [ + { + "cluster": "x", + "pointer": "0x3", + "items": [ + { + "kind": "Default", + "offset": 0, + "value": "Undefined" + } + ] + } + ] + } } } \l}"]; diff --git a/clang/test/Analysis/exploded-graph-rewriter/store_diff.dot b/clang/test/Analysis/exploded-graph-rewriter/store_diff.dot index d0a4e19817af9..af92c62163613 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/store_diff.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/store_diff.dot @@ -12,19 +12,22 @@ Node0x1 [shape=record,label= "program_state": { "environment": null, "constraints": null, - "store": [ - { - "cluster": "x", - "pointer": "0x3", - "items": [ - { - "kind": "Default", - "offset": 0, - "value": "Undefined" - } - ] - } - ] + "store": { + "pointer": "0x2", + "items": [ + { + "cluster": "x", + "pointer": "0x3", + "items": [ + { + "kind": "Default", + "offset": 0, + "value": "Undefined" + } + ] + } + ] + } } } \l}"]; @@ -54,19 +57,22 @@ Node0x4 [shape=record,label= "program_state": { "environment": null, "constraints": null, - "store": [ - { - "cluster": "x", - "pointer": "0x3", - "items": [ - { - "kind": "Default", - "offset": 0, - "value": "Unknown" - } - ] - } - ] + "store": { + "pointer": "0x5", + "items": [ + { + "cluster": "x", + "pointer": "0x3", + "items": [ + { + "kind": "Default", + "offset": 0, + "value": "Unknown" + } + ] + } + ] + } } } \l}"]; diff --git a/clang/test/Analysis/expr-inspection.c b/clang/test/Analysis/expr-inspection.c index 841b30a9b99d7..5d07b70965f43 100644 --- a/clang/test/Analysis/expr-inspection.c +++ b/clang/test/Analysis/expr-inspection.c @@ -24,16 +24,16 @@ void foo(int x) { } // CHECK: "program_state": { -// CHECK-NEXT: "store": [ +// CHECK-NEXT: "store": { "pointer": "{{0x[0-9a-f]+}}", "items": [ // CHECK-NEXT: { "cluster": "y", "pointer": "{{0x[0-9a-f]+}}", "items": [ // CHECK-NEXT: { "kind": "Direct", "offset": 0, "value": "2 S32b" } // CHECK-NEXT: ]} -// CHECK-NEXT: ], -// CHECK-NEXT: "environment": [ +// CHECK-NEXT: ]}, +// CHECK-NEXT: "environment": { "pointer": "{{0x[0-9a-f]+}}", "items": [ // CHECK-NEXT: { "lctx_id": 1, "location_context": "#0 Call", "calling": "foo", "call_line": null, "items": [ // CHECK-NEXT: { "stmt_id": {{[0-9]+}}, "pretty": "clang_analyzer_printState", "value": "&code{clang_analyzer_printState}" } // CHECK-NEXT: ]} -// CHECK-NEXT: ], +// CHECK-NEXT: ]}, // CHECK-NEXT: "constraints": [ // CHECK-NEXT: { "symbol": "reg_$0", "range": "{ [-2147483648, 13] }" } // CHECK-NEXT: ], diff --git a/clang/utils/analyzer/exploded-graph-rewriter.py b/clang/utils/analyzer/exploded-graph-rewriter.py index 668b12110b4c3..7714e59656657 100755 --- a/clang/utils/analyzer/exploded-graph-rewriter.py +++ b/clang/utils/analyzer/exploded-graph-rewriter.py @@ -126,7 +126,8 @@ def is_different(self, prev): class Environment(object): def __init__(self, json_e): super(Environment, self).__init__() - self.frames = [EnvironmentFrame(f) for f in json_e] + self.ptr = json_e['pointer'] + self.frames = [EnvironmentFrame(f) for f in json_e['items']] def diff_frames(self, prev): # TODO: It's difficult to display a good diff when frame numbers shift. @@ -190,8 +191,9 @@ def is_different(self, prev): class Store(object): def __init__(self, json_s): super(Store, self).__init__() + self.ptr = json_s['pointer'] self.clusters = collections.OrderedDict( - [(c['pointer'], StoreCluster(c)) for c in json_s]) + [(c['pointer'], StoreCluster(c)) for c in json_s['items']]) def diff_clusters(self, prev): removed = [k for k in prev.clusters if k not in self.clusters] From a7a7930310870a51f6852447b9cfe276f5dd1a30 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Wed, 26 Jun 2019 00:14:49 +0000 Subject: [PATCH 018/181] [analyzer] exploded-graph-rewriter: Prettier location context dumps. Make them span wider. llvm-svn: 364365 (cherry picked from commit 628f36ff700f1b94eedc66d702efdee0a3e92322) --- clang/test/Analysis/exploded-graph-rewriter/environment.dot | 2 +- clang/utils/analyzer/exploded-graph-rewriter.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/clang/test/Analysis/exploded-graph-rewriter/environment.dot b/clang/test/Analysis/exploded-graph-rewriter/environment.dot index 052f35b28fc72..f624975db652c 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/environment.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/environment.dot @@ -9,7 +9,7 @@ // CHECK-SAME: -// CHECK-SAME: // CHECK-SAME: diff --git a/clang/utils/analyzer/exploded-graph-rewriter.py b/clang/utils/analyzer/exploded-graph-rewriter.py index 7714e59656657..f0f0316da917f 100755 --- a/clang/utils/analyzer/exploded-graph-rewriter.py +++ b/clang/utils/analyzer/exploded-graph-rewriter.py @@ -393,7 +393,8 @@ def visit_environment(self, e, prev_e=None): def dump_location_context(lc, is_added=None): self._dump('' '' - '' % (self._diff_plus_minus(is_added), lc.caption, lc.decl, From 8496feeba70dcd8417b08bb52f8c5617ee00c0d4 Mon Sep 17 00:00:00 2001 From: Nathan Huckleberry Date: Thu, 27 Jun 2019 22:46:40 +0000 Subject: [PATCH 019/181] [analyzer] Fix clang-tidy crash on GCCAsmStmt Summary: Added entry in switch statement to recognize GCCAsmStmt as a possible block terminator. Handling to build CFG using GCCAsmStmt was already implemented. Reviewers: nickdesaulniers, george.karpenkov, NoQ Reviewed By: nickdesaulniers, NoQ Subscribers: xbolva00, tmroeder, xazax.hun, baloghadamsoftware, szepet, a.sidorin, mikhail.ramalho, Szelethus, donat.nagy, dkrupp, Charusso, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D63533 llvm-svn: 364605 (cherry picked from commit 13fde7a89a53d5a9ed77579b038eb39bcf8975a1) --- clang/lib/StaticAnalyzer/Core/CoreEngine.cpp | 5 ++++ .../Analysis/egraph-asm-goto-no-crash.cpp | 26 +++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 clang/test/Analysis/egraph-asm-goto-no-crash.cpp diff --git a/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp b/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp index 431d07dab1e18..94cf74de82931 100644 --- a/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp @@ -396,6 +396,11 @@ void CoreEngine::HandleBlockExit(const CFGBlock * B, ExplodedNode *Pred) { case Stmt::WhileStmtClass: HandleBranch(cast(Term)->getCond(), Term, B, Pred); return; + + case Stmt::GCCAsmStmtClass: + assert(cast(Term)->isAsmGoto() && "Encountered GCCAsmStmt without labels"); + // TODO: Handle jumping to labels + return; } } diff --git a/clang/test/Analysis/egraph-asm-goto-no-crash.cpp b/clang/test/Analysis/egraph-asm-goto-no-crash.cpp new file mode 100644 index 0000000000000..37f8fc533abe3 --- /dev/null +++ b/clang/test/Analysis/egraph-asm-goto-no-crash.cpp @@ -0,0 +1,26 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -verify %s + +// expected-no-diagnostics + +void clang_analyzer_warnIfReached(); + +void testAsmGoto() { + asm goto("xor %0, %0\n je %l[label1]\n jl %l[label2]" + : /* no outputs */ + : /* inputs */ + : /* clobbers */ + : label1, label2 /* any labels used */); + + // FIXME: Should be reachable. + clang_analyzer_warnIfReached(); + + label1: + // FIXME: Should be reachable. + clang_analyzer_warnIfReached(); + return; + + label2: + // FIXME: Should be reachable. + clang_analyzer_warnIfReached(); + return; +} From 2a91883a2f4312fdb71effb62ca3d1682720c46c Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Mon, 1 Jul 2019 23:01:55 +0000 Subject: [PATCH 020/181] [analyzer] exploded-graph-rewriter: Implement program point tags. Keep them on a separate line for more visibility. Differential Revision: https://reviews.llvm.org/D63965 llvm-svn: 364864 (cherry picked from commit 5a72338bf50e29c034443d5075cb83a5386851d5) --- .../exploded-graph-rewriter/program_points.dot | 10 +++++++++- clang/utils/analyzer/exploded-graph-rewriter.py | 6 ++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/clang/test/Analysis/exploded-graph-rewriter/program_points.dot b/clang/test/Analysis/exploded-graph-rewriter/program_points.dot index 26beff4be895c..31dcc4adb62c8 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/program_points.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/program_points.dot @@ -41,6 +41,14 @@ Node0x1 [shape=record,label= // CHECK-SAME: // CHECK-SAME: // CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: // CHECK-SAME:
Store: foo0&Element\{"foo",0 S64b,char\}"foo"&Element\{"foo",0 S64b,char\}1 \| 23 S32b // CHECK-SAME: #0 Call // CHECK-SAME: +// CHECK-SAME: // CHECK-SAME: foo (line 4) // CHECK-SAME:
%s%s%s ' + '' + '%s ' '%s
x
+// CHECK-SAME: +// CHECK-SAME: Tag: +// CHECK-SAME: ExprEngine : Clean Node +// CHECK-SAME:
Node0x2 [shape=record,label= "{ @@ -56,7 +64,7 @@ Node0x2 [shape=record,label= "line": 4, "column": 5 }, - "tag": null + "tag": "ExprEngine : Clean Node" } ]} \l}"]; diff --git a/clang/utils/analyzer/exploded-graph-rewriter.py b/clang/utils/analyzer/exploded-graph-rewriter.py index f0f0316da917f..c4b015e5ded91 100755 --- a/clang/utils/analyzer/exploded-graph-rewriter.py +++ b/clang/utils/analyzer/exploded-graph-rewriter.py @@ -387,6 +387,12 @@ def visit_program_point(self, p): '%s' % (color, p.kind)) + if p.tag is not None: + self._dump('' + '' + 'Tag: ' + '%s' % p.tag) + def visit_environment(self, e, prev_e=None): self._dump('') From b30e422f7bca559e3b5fe9a612618a98c66fc3d7 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Mon, 1 Jul 2019 23:01:59 +0000 Subject: [PATCH 021/181] [analyzer] exploded-graph-rewriter: Add support for dynamic types. Slightly cleanup emission of horizontal lines and unhardcode the title for generic maps. Differential Revision: https://reviews.llvm.org/D64041 llvm-svn: 364865 (cherry picked from commit 02f91ddf1b353b28697b288edbe6a8a828705781) --- .../exploded-graph-rewriter/constraints.dot | 1 + .../constraints_diff.dot | 5 ++- .../exploded-graph-rewriter/environment.dot | 1 + .../environment_diff.dot | 3 ++ .../exploded-graph-rewriter/store.dot | 1 + .../exploded-graph-rewriter/store_diff.dot | 2 + .../utils/analyzer/exploded-graph-rewriter.py | 42 +++++++++++-------- 7 files changed, 37 insertions(+), 18 deletions(-) diff --git a/clang/test/Analysis/exploded-graph-rewriter/constraints.dot b/clang/test/Analysis/exploded-graph-rewriter/constraints.dot index f9ffde74316ac..49aba4b15e19f 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/constraints.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/constraints.dot @@ -19,6 +19,7 @@ Node0x1 [shape=record,label= "program_state": { "store": null, "environment": null, + "dynamic_types": null, "constraints": [ { "symbol": "reg_$0", "range": "{ [0, 0] }" } ] diff --git a/clang/test/Analysis/exploded-graph-rewriter/constraints_diff.dot b/clang/test/Analysis/exploded-graph-rewriter/constraints_diff.dot index a65a3a2464c9a..068ef58d25c22 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/constraints_diff.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/constraints_diff.dot @@ -12,6 +12,7 @@ Node0x1 [shape=record,label= "program_state": { "store": null, "environment": null, + "dynamic_types": null, "constraints": [ { "symbol": "reg_$0", "range": "{ [0, 10] }" } ] @@ -41,6 +42,7 @@ Node0x3 [shape=record,label= "program_state": { "store": null, "environment": null, + "dynamic_types": null, "constraints": [ { "symbol": "reg_$0", "range": "{ [0, 5] }" } ] @@ -59,7 +61,8 @@ Node0x5 [shape=record,label= "program_state": { "store": null, "environment": null, - "constraints": null + "constraints": null, + "dynamic_types": null } } \l}"]; diff --git a/clang/test/Analysis/exploded-graph-rewriter/environment.dot b/clang/test/Analysis/exploded-graph-rewriter/environment.dot index f624975db652c..4c6c93b85ded2 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/environment.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/environment.dot @@ -34,6 +34,7 @@ Node0x1 [shape=record,label= "program_state": { "store": null, "constraints": null, + "dynamic_types": null, "environment": { "pointer": "0x2", "items": [ diff --git a/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot b/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot index 84f89dd469e4a..ac41cdf97f02a 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot @@ -13,6 +13,7 @@ Node0x1 [shape=record,label= "program_state": { "store": null, "constraints": null, + "dynamic_types": null, "environment": { "pointer": "0x2", "items": [ @@ -59,6 +60,7 @@ Node0x6 [shape=record,label= "program_state": { "store": null, "constraints": null, + "dynamic_types": null, "environment": { "pointer": "0x2", "items": [ @@ -99,6 +101,7 @@ Node0x9 [shape=record,label= "program_state": { "store": null, "constraints": null, + "dynamic_types": null, "environment": { "pointer": "0x2", "items": [ diff --git a/clang/test/Analysis/exploded-graph-rewriter/store.dot b/clang/test/Analysis/exploded-graph-rewriter/store.dot index 8f33b513b10e4..f431319cc6db5 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/store.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/store.dot @@ -29,6 +29,7 @@ Node0x1 [shape=record,label= "program_state": { "environment": null, "constraints": null, + "dynamic_types": null, "store": { "pointer": "0x2", "items": [ diff --git a/clang/test/Analysis/exploded-graph-rewriter/store_diff.dot b/clang/test/Analysis/exploded-graph-rewriter/store_diff.dot index af92c62163613..ab36e5f9869b1 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/store_diff.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/store_diff.dot @@ -12,6 +12,7 @@ Node0x1 [shape=record,label= "program_state": { "environment": null, "constraints": null, + "dynamic_types": null, "store": { "pointer": "0x2", "items": [ @@ -57,6 +58,7 @@ Node0x4 [shape=record,label= "program_state": { "environment": null, "constraints": null, + "dynamic_types": null, "store": { "pointer": "0x5", "items": [ diff --git a/clang/utils/analyzer/exploded-graph-rewriter.py b/clang/utils/analyzer/exploded-graph-rewriter.py index c4b015e5ded91..b033814ed9704 100755 --- a/clang/utils/analyzer/exploded-graph-rewriter.py +++ b/clang/utils/analyzer/exploded-graph-rewriter.py @@ -27,8 +27,8 @@ def diff_dicts(curr, prev): # Represents any program state trait that is a dictionary of key-value pairs. class GenericMap(object): - def __init__(self, generic_map): - self.generic_map = generic_map + def __init__(self, items): + self.generic_map = collections.OrderedDict(items) def diff(self, prev): return diff_dicts(self.generic_map, prev.generic_map) @@ -218,11 +218,17 @@ def __init__(self, state_id, json_ps): if json_ps['store'] is not None else None self.environment = Environment(json_ps['environment']) \ if json_ps['environment'] is not None else None - self.constraints = GenericMap(collections.OrderedDict([ + self.constraints = GenericMap([ (c['symbol'], c['range']) for c in json_ps['constraints'] - ])) if json_ps['constraints'] is not None else None + ]) if json_ps['constraints'] is not None else None + self.dynamic_types = GenericMap([ + (t['region'], '%s%s' % (t['dyn_type'], + ' (or a sub-class)' + if t['sub_classable'] else '')) + for t in json_ps['dynamic_types']]) \ + if json_ps['dynamic_types'] is not None else None + # TODO: Objects under construction. - # TODO: Dynamic types of objects. # TODO: Checker messages. @@ -435,8 +441,7 @@ def dump_binding(f, b, is_added=None): self._dump('
') def visit_environment_in_state(self, s, prev_s=None): - self._dump('' - 'Environment: ') + self._dump('
Environment: ') if s.environment is None: self._dump(' Nothing!') else: @@ -491,7 +496,7 @@ def dump_binding(s, c, b, is_added=None): self._dump('') def visit_store_in_state(self, s, prev_s=None): - self._dump('Store: ') + self._dump('
Store: ') if s.store is None: self._dump(' Nothing!') else: @@ -528,16 +533,19 @@ def dump_pair(m, k, is_added=None): self._dump('') - def visit_generic_map_in_state(self, selector, s, prev_s=None): - self._dump('' - 'Ranges: ') + def visit_generic_map_in_state(self, selector, title, s, prev_s=None): m = getattr(s, selector) + prev_m = getattr(prev_s, selector) if prev_s is not None else None + if m is None and prev_m is None: + return + + self._dump('
') + self._dump('' + '%s: ' % title) if m is None: self._dump(' Nothing!') else: - prev_m = None if prev_s is not None: - prev_m = getattr(prev_s, selector) if prev_m is not None: if m.is_different(prev_m): self._dump('') @@ -551,10 +559,11 @@ def visit_generic_map_in_state(self, selector, s, prev_s=None): def visit_state(self, s, prev_s): self.visit_store_in_state(s, prev_s) - self._dump('
') self.visit_environment_in_state(s, prev_s) - self._dump('
') - self.visit_generic_map_in_state('constraints', s, prev_s) + self.visit_generic_map_in_state('constraints', 'Ranges', + s, prev_s) + self.visit_generic_map_in_state('dynamic_types', 'Dynamic Types', + s, prev_s) def visit_node(self, node): self._dump('%s [shape=record,label=<' @@ -576,7 +585,6 @@ def visit_node(self, node): self._dump('
') if node.state is not None: - self._dump('
') prev_s = None # Do diffs only when we have a unique predecessor. # Don't do diffs on the leaf nodes because they're From 8b8a2a8957545d010bf99f4180424d2590a95af2 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Mon, 1 Jul 2019 23:02:03 +0000 Subject: [PATCH 022/181] [analyzer] NFC: Add a convenient CallDescriptionMap class. It encapsulates the procedure of figuring out whether a call event corresponds to a function that's modeled by a checker. Checker developers no longer need to worry about performance of lookups into their own custom maps. Add unittests - which finally test CallDescription itself as well. Differential Revision: https://reviews.llvm.org/D62441 llvm-svn: 364866 (cherry picked from commit ec8e95640f066dfe2b97865cc2ce018e7a5f64b9) --- .../Core/PathSensitive/CallEvent.h | 98 ++++++++---- clang/lib/StaticAnalyzer/Core/CallEvent.cpp | 3 +- clang/unittests/StaticAnalyzer/CMakeLists.txt | 1 + .../StaticAnalyzer/CallDescriptionTest.cpp | 150 ++++++++++++++++++ clang/unittests/StaticAnalyzer/Reusables.h | 20 ++- 5 files changed, 231 insertions(+), 41 deletions(-) create mode 100644 clang/unittests/StaticAnalyzer/CallDescriptionTest.cpp diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h index 19996cf9a1b11..19d6e62c1655d 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h @@ -71,39 +71,7 @@ enum CallEventKind { }; class CallEvent; - -/// This class represents a description of a function call using the number of -/// arguments and the name of the function. -class CallDescription { - friend CallEvent; - - mutable IdentifierInfo *II = nullptr; - mutable bool IsLookupDone = false; - // The list of the qualified names used to identify the specified CallEvent, - // e.g. "{a, b}" represent the qualified names, like "a::b". - std::vector QualifiedName; - unsigned RequiredArgs; - -public: - const static unsigned NoArgRequirement = std::numeric_limits::max(); - - /// Constructs a CallDescription object. - /// - /// @param QualifiedName The list of the name qualifiers of the function that - /// will be matched. The user is allowed to skip any of the qualifiers. - /// For example, {"std", "basic_string", "c_str"} would match both - /// std::basic_string<...>::c_str() and std::__1::basic_string<...>::c_str(). - /// - /// @param RequiredArgs The number of arguments that is expected to match a - /// call. Omit this parameter to match every occurrence of call with a given - /// name regardless the number of arguments. - CallDescription(ArrayRef QualifiedName, - unsigned RequiredArgs = NoArgRequirement) - : QualifiedName(QualifiedName), RequiredArgs(RequiredArgs) {} - - /// Get the name of the function that this object matches. - StringRef getFunctionName() const { return QualifiedName.back(); } -}; +class CallDescription; template class CallEventRef : public IntrusiveRefCntPtr { @@ -1076,6 +1044,70 @@ class ObjCMethodCall : public CallEvent { } }; +/// This class represents a description of a function call using the number of +/// arguments and the name of the function. +class CallDescription { + friend CallEvent; + + mutable IdentifierInfo *II = nullptr; + mutable bool IsLookupDone = false; + // The list of the qualified names used to identify the specified CallEvent, + // e.g. "{a, b}" represent the qualified names, like "a::b". + std::vector QualifiedName; + Optional RequiredArgs; + +public: + /// Constructs a CallDescription object. + /// + /// @param QualifiedName The list of the name qualifiers of the function that + /// will be matched. The user is allowed to skip any of the qualifiers. + /// For example, {"std", "basic_string", "c_str"} would match both + /// std::basic_string<...>::c_str() and std::__1::basic_string<...>::c_str(). + /// + /// @param RequiredArgs The number of arguments that is expected to match a + /// call. Omit this parameter to match every occurrence of call with a given + /// name regardless the number of arguments. + CallDescription(ArrayRef QualifiedName, + Optional RequiredArgs = None) + : QualifiedName(QualifiedName), RequiredArgs(RequiredArgs) {} + + /// Get the name of the function that this object matches. + StringRef getFunctionName() const { return QualifiedName.back(); } +}; + +/// An immutable map from CallDescriptions to arbitrary data. Provides a unified +/// way for checkers to react on function calls. +template class CallDescriptionMap { + // Some call descriptions aren't easily hashable (eg., the ones with qualified + // names in which some sections are omitted), so let's put them + // in a simple vector and use linear lookup. + // TODO: Implement an actual map for fast lookup for "hashable" call + // descriptions (eg., the ones for C functions that just match the name). + std::vector> LinearMap; + +public: + CallDescriptionMap( + std::initializer_list> &&List) + : LinearMap(List) {} + + ~CallDescriptionMap() = default; + + // These maps are usually stored once per checker, so let's make sure + // we don't do redundant copies. + CallDescriptionMap(const CallDescriptionMap &) = delete; + CallDescriptionMap &operator=(const CallDescription &) = delete; + + const T *lookup(const CallEvent &Call) const { + // Slow path: linear lookup. + // TODO: Implement some sort of fast path. + for (const std::pair &I : LinearMap) + if (Call.isCalled(I.first)) + return &I.second; + + return nullptr; + } +}; + /// Manages the lifetime of CallEvent objects. /// /// CallEventManager provides a way to create arbitrary CallEvents "on the diff --git a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp index 11dda7c3acb7c..6339423f1125b 100644 --- a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp +++ b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp @@ -393,8 +393,7 @@ bool CallEvent::isCalled(const CallDescription &CD) const { return false; } - return (CD.RequiredArgs == CallDescription::NoArgRequirement || - CD.RequiredArgs == getNumArgs()); + return (!CD.RequiredArgs || CD.RequiredArgs == getNumArgs()); } SVal CallEvent::getArgSVal(unsigned Index) const { diff --git a/clang/unittests/StaticAnalyzer/CMakeLists.txt b/clang/unittests/StaticAnalyzer/CMakeLists.txt index 5348a0a2bc8b7..1b8044c9e1fd7 100644 --- a/clang/unittests/StaticAnalyzer/CMakeLists.txt +++ b/clang/unittests/StaticAnalyzer/CMakeLists.txt @@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS add_clang_unittest(StaticAnalysisTests AnalyzerOptionsTest.cpp + CallDescriptionTest.cpp StoreTest.cpp RegisterCustomCheckersTest.cpp SymbolReaperTest.cpp diff --git a/clang/unittests/StaticAnalyzer/CallDescriptionTest.cpp b/clang/unittests/StaticAnalyzer/CallDescriptionTest.cpp new file mode 100644 index 0000000000000..573b6909b1805 --- /dev/null +++ b/clang/unittests/StaticAnalyzer/CallDescriptionTest.cpp @@ -0,0 +1,150 @@ +//===- unittests/StaticAnalyzer/CallDescriptionTest.cpp -------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "Reusables.h" + +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/Tooling/Tooling.h" +#include "gtest/gtest.h" + +namespace clang { +namespace ento { +namespace { + +// A wrapper around CallDescriptionMap that allows verifying that +// all functions have been found. This is needed because CallDescriptionMap +// isn't supposed to support iteration. +class ResultMap { + size_t Found, Total; + CallDescriptionMap Impl; + +public: + ResultMap(std::initializer_list> Data) + : Found(0), + Total(std::count_if(Data.begin(), Data.end(), + [](const std::pair &Pair) { + return Pair.second == true; + })), + Impl(std::move(Data)) {} + + const bool *lookup(const CallEvent &Call) { + const bool *Result = Impl.lookup(Call); + // If it's a function we expected to find, remember that we've found it. + if (Result && *Result) + ++Found; + return Result; + } + + // Fail the test if we haven't found all the true-calls we were looking for. + ~ResultMap() { EXPECT_EQ(Found, Total); } +}; + +// Scan the code body for call expressions and see if we find all calls that +// we were supposed to find ("true" in the provided ResultMap) and that we +// don't find the ones that we weren't supposed to find +// ("false" in the ResultMap). +class CallDescriptionConsumer : public ExprEngineConsumer { + ResultMap &RM; + void performTest(const Decl *D) { + using namespace ast_matchers; + + if (!D->hasBody()) + return; + + const CallExpr *CE = findNode(D, callExpr()); + const StackFrameContext *SFC = + Eng.getAnalysisDeclContextManager().getStackFrame(D); + ProgramStateRef State = Eng.getInitialState(SFC); + CallEventRef<> Call = + Eng.getStateManager().getCallEventManager().getCall(CE, State, SFC); + + const bool *LookupResult = RM.lookup(*Call); + // Check that we've found the function in the map + // with the correct description. + EXPECT_TRUE(LookupResult && *LookupResult); + + // ResultMap is responsible for making sure that we've found *all* calls. + } + +public: + CallDescriptionConsumer(CompilerInstance &C, + ResultMap &RM) + : ExprEngineConsumer(C), RM(RM) {} + + bool HandleTopLevelDecl(DeclGroupRef DG) override { + for (const auto *D : DG) + performTest(D); + return true; + } +}; + +class CallDescriptionAction : public ASTFrontendAction { + ResultMap RM; + +public: + CallDescriptionAction( + std::initializer_list> Data) + : RM(Data) {} + + std::unique_ptr CreateASTConsumer(CompilerInstance &Compiler, + StringRef File) override { + return llvm::make_unique(Compiler, RM); + } +}; + +TEST(CallEvent, CallDescription) { + // Test simple name matching. + EXPECT_TRUE(tooling::runToolOnCode( + new CallDescriptionAction({ + {{"bar"}, false}, // false: there's no call to 'bar' in this code. + {{"foo"}, true}, // true: there's a call to 'foo' in this code. + }), "void foo(); void bar() { foo(); }")); + + // Test arguments check. + EXPECT_TRUE(tooling::runToolOnCode( + new CallDescriptionAction({ + {{"foo", 1}, true}, + {{"foo", 2}, false}, + }), "void foo(int); void foo(int, int); void bar() { foo(1); }")); + + // Test lack of arguments check. + EXPECT_TRUE(tooling::runToolOnCode( + new CallDescriptionAction({ + {{"foo", None}, true}, + {{"foo", 2}, false}, + }), "void foo(int); void foo(int, int); void bar() { foo(1); }")); + + // Test qualified names. + EXPECT_TRUE(tooling::runToolOnCode( + new CallDescriptionAction({ + {{{"std", "basic_string", "c_str"}}, true}, + }), + "namespace std { inline namespace __1 {" + " template class basic_string {" + " public:" + " T *c_str();" + " };" + "}}" + "void foo() {" + " using namespace std;" + " basic_string s;" + " s.c_str();" + "}")); + + // A negative test for qualified names. + EXPECT_TRUE(tooling::runToolOnCode( + new CallDescriptionAction({ + {{{"foo", "bar"}}, false}, + {{{"bar", "foo"}}, false}, + {{"foo"}, true}, + }), "void foo(); struct bar { void foo(); }; void test() { foo(); }")); +} + +} // namespace +} // namespace ento +} // namespace clang diff --git a/clang/unittests/StaticAnalyzer/Reusables.h b/clang/unittests/StaticAnalyzer/Reusables.h index 06aed884f6bf4..49b96f63960a1 100644 --- a/clang/unittests/StaticAnalyzer/Reusables.h +++ b/clang/unittests/StaticAnalyzer/Reusables.h @@ -17,16 +17,24 @@ namespace clang { namespace ento { +// Find a node in the current AST that matches a matcher. +template +const T *findNode(const Decl *Where, MatcherT What) { + using namespace ast_matchers; + auto Matches = match(decl(hasDescendant(What.bind("root"))), + *Where, Where->getASTContext()); + assert(Matches.size() <= 1 && "Ambiguous match!"); + assert(Matches.size() >= 1 && "Match not found!"); + const T *Node = selectFirst("root", Matches); + assert(Node && "Type mismatch!"); + return Node; +} + // Find a declaration in the current AST by name. template const T *findDeclByName(const Decl *Where, StringRef Name) { using namespace ast_matchers; - auto Matcher = decl(hasDescendant(namedDecl(hasName(Name)).bind("d"))); - auto Matches = match(Matcher, *Where, Where->getASTContext()); - assert(Matches.size() == 1 && "Ambiguous name!"); - const T *Node = selectFirst("d", Matches); - assert(Node && "Name not found!"); - return Node; + return findNode(Where, namedDecl(hasName(Name))); } // A re-usable consumer that constructs ExprEngine out of CompilerInvocation. From 769533c3f2dfc97e6957334139634792feb3dbc8 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Mon, 1 Jul 2019 23:02:07 +0000 Subject: [PATCH 023/181] [analyzer] NFC: CallDescription: Implement describing C library functions. When matching C standard library functions in the checker, it's easy to forget that they are often implemented as macros that are expanded to builtins. Such builtins would have a different name, so matching the callee identifier would fail, or may sometimes have more arguments than expected, so matching the exact number of arguments would fail, but this is fine as long as we have all the arguments that we need in their respective places. This patch adds a set of flags to the CallDescription class so that to handle various special matching rules, and adds the first flag into this set, which enables a more fuzzy matching for functions that may be implemented as compiler builtins. Differential Revision: https://reviews.llvm.org/D62556 llvm-svn: 364867 (cherry picked from commit f301096f51133b4f18c383deb9679f217c1ea08b) --- .../Core/PathSensitive/CallEvent.h | 17 +++++++++++++- clang/lib/StaticAnalyzer/Core/CallEvent.cpp | 22 ++++++++++++++----- .../StaticAnalyzer/CallDescriptionTest.cpp | 12 ++++++++++ 3 files changed, 45 insertions(+), 6 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h index 19d6e62c1655d..db84102983af1 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h @@ -1044,6 +1044,14 @@ class ObjCMethodCall : public CallEvent { } }; +enum CallDescriptionFlags : int { + /// Describes a C standard function that is sometimes implemented as a macro + /// that expands to a compiler builtin with some __builtin prefix. + /// The builtin may as well have a few extra arguments on top of the requested + /// number of arguments. + CDF_MaybeBuiltin = 1 << 0, +}; + /// This class represents a description of a function call using the number of /// arguments and the name of the function. class CallDescription { @@ -1055,6 +1063,7 @@ class CallDescription { // e.g. "{a, b}" represent the qualified names, like "a::b". std::vector QualifiedName; Optional RequiredArgs; + int Flags; public: /// Constructs a CallDescription object. @@ -1067,9 +1076,15 @@ class CallDescription { /// @param RequiredArgs The number of arguments that is expected to match a /// call. Omit this parameter to match every occurrence of call with a given /// name regardless the number of arguments. + CallDescription(int Flags, ArrayRef QualifiedName, + Optional RequiredArgs = None) + : QualifiedName(QualifiedName), RequiredArgs(RequiredArgs), + Flags(Flags) {} + + /// Construct a CallDescription with default flags. CallDescription(ArrayRef QualifiedName, Optional RequiredArgs = None) - : QualifiedName(QualifiedName), RequiredArgs(RequiredArgs) {} + : CallDescription(0, QualifiedName, RequiredArgs) {} /// Get the name of the function that this object matches. StringRef getFunctionName() const { return QualifiedName.back(); } diff --git a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp index 6339423f1125b..81a9ee4d90b0a 100644 --- a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp +++ b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp @@ -356,20 +356,32 @@ bool CallEvent::isCalled(const CallDescription &CD) const { // FIXME: Add ObjC Message support. if (getKind() == CE_ObjCMessage) return false; + + const IdentifierInfo *II = getCalleeIdentifier(); + if (!II) + return false; + const FunctionDecl *FD = dyn_cast_or_null(getDecl()); + if (!FD) + return false; + + if (CD.Flags & CDF_MaybeBuiltin) { + return CheckerContext::isCLibraryFunction(FD, CD.getFunctionName()) && + (!CD.RequiredArgs || CD.RequiredArgs <= getNumArgs()); + } + if (!CD.IsLookupDone) { CD.IsLookupDone = true; CD.II = &getState()->getStateManager().getContext().Idents.get( CD.getFunctionName()); } - const IdentifierInfo *II = getCalleeIdentifier(); - if (!II || II != CD.II) + + if (II != CD.II) return false; - const Decl *D = getDecl(); // If CallDescription provides prefix names, use them to improve matching // accuracy. - if (CD.QualifiedName.size() > 1 && D) { - const DeclContext *Ctx = D->getDeclContext(); + if (CD.QualifiedName.size() > 1 && FD) { + const DeclContext *Ctx = FD->getDeclContext(); // See if we'll be able to match them all. size_t NumUnmatched = CD.QualifiedName.size() - 1; for (; Ctx && isa(Ctx); Ctx = Ctx->getParent()) { diff --git a/clang/unittests/StaticAnalyzer/CallDescriptionTest.cpp b/clang/unittests/StaticAnalyzer/CallDescriptionTest.cpp index 573b6909b1805..9201922f5be05 100644 --- a/clang/unittests/StaticAnalyzer/CallDescriptionTest.cpp +++ b/clang/unittests/StaticAnalyzer/CallDescriptionTest.cpp @@ -143,6 +143,18 @@ TEST(CallEvent, CallDescription) { {{{"bar", "foo"}}, false}, {{"foo"}, true}, }), "void foo(); struct bar { void foo(); }; void test() { foo(); }")); + + // Test CDF_MaybeBuiltin - a flag that allows matching weird builtins. + EXPECT_TRUE(tooling::runToolOnCode( + new CallDescriptionAction({ + {{"memset", 3}, false}, + {{CDF_MaybeBuiltin, "memset", 3}, true} + }), + "void foo() {" + " int x;" + " __builtin___memset_chk(&x, 0, sizeof(x)," + " __builtin_object_size(&x, 0));" + "}")); } } // namespace From 9d48193068eddea712464e12b0ee66ece7b6c193 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Mon, 1 Jul 2019 23:02:10 +0000 Subject: [PATCH 024/181] [analyzer] CStringChecker: Modernize to use CallDescriptions. This patch uses the new CDF_MaybeBuiltin flag to handle C library functions. It's mostly an NFC/refactoring pass, but it does fix a bug in handling memset() when it expands to __builtin___memset_chk() because the latter has one more argument and memset() handling code was trying to match the exact number of arguments. Now the code is deduplicated and there's less room for mistakes. Differential Revision: https://reviews.llvm.org/D62557 llvm-svn: 364868 (cherry picked from commit 35fdec1b54c6a8b1ca7bee542836abf29c75a85d) --- .../Checkers/CStringChecker.cpp | 205 +++++------------- clang/test/Analysis/string.c | 6 + 2 files changed, 64 insertions(+), 147 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp index 2aa1b2223ac52..44f4530781a8e 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp @@ -73,7 +73,38 @@ class CStringChecker : public Checker< eval::Call, typedef void (CStringChecker::*FnCheck)(CheckerContext &, const CallExpr *) const; + CallDescriptionMap Callbacks = { + {{CDF_MaybeBuiltin, "memcpy", 3}, &CStringChecker::evalMemcpy}, + {{CDF_MaybeBuiltin, "mempcpy", 3}, &CStringChecker::evalMempcpy}, + {{CDF_MaybeBuiltin, "memcmp", 3}, &CStringChecker::evalMemcmp}, + {{CDF_MaybeBuiltin, "memmove", 3}, &CStringChecker::evalMemmove}, + {{CDF_MaybeBuiltin, "memset", 3}, &CStringChecker::evalMemset}, + {{CDF_MaybeBuiltin, "explicit_memset", 3}, &CStringChecker::evalMemset}, + {{CDF_MaybeBuiltin, "strcpy", 2}, &CStringChecker::evalStrcpy}, + {{CDF_MaybeBuiltin, "strncpy", 3}, &CStringChecker::evalStrncpy}, + {{CDF_MaybeBuiltin, "stpcpy", 2}, &CStringChecker::evalStpcpy}, + {{CDF_MaybeBuiltin, "strlcpy", 3}, &CStringChecker::evalStrlcpy}, + {{CDF_MaybeBuiltin, "strcat", 2}, &CStringChecker::evalStrcat}, + {{CDF_MaybeBuiltin, "strncat", 3}, &CStringChecker::evalStrncat}, + {{CDF_MaybeBuiltin, "strlcat", 3}, &CStringChecker::evalStrlcat}, + {{CDF_MaybeBuiltin, "strlen", 1}, &CStringChecker::evalstrLength}, + {{CDF_MaybeBuiltin, "strnlen", 2}, &CStringChecker::evalstrnLength}, + {{CDF_MaybeBuiltin, "strcmp", 2}, &CStringChecker::evalStrcmp}, + {{CDF_MaybeBuiltin, "strncmp", 3}, &CStringChecker::evalStrncmp}, + {{CDF_MaybeBuiltin, "strcasecmp", 2}, &CStringChecker::evalStrcasecmp}, + {{CDF_MaybeBuiltin, "strncasecmp", 3}, &CStringChecker::evalStrncasecmp}, + {{CDF_MaybeBuiltin, "strsep", 2}, &CStringChecker::evalStrsep}, + {{CDF_MaybeBuiltin, "bcopy", 3}, &CStringChecker::evalBcopy}, + {{CDF_MaybeBuiltin, "bcmp", 3}, &CStringChecker::evalMemcmp}, + {{CDF_MaybeBuiltin, "bzero", 2}, &CStringChecker::evalBzero}, + {{CDF_MaybeBuiltin, "explicit_bzero", 2}, &CStringChecker::evalBzero}, + }; + + // These require a bit of special handling. + CallDescription StdCopy{{"std", "copy"}, 3}, + StdCopyBackward{{"std", "copy_backward"}, 3}; + FnCheck identifyCall(const CallEvent &Call, CheckerContext &C) const; void evalMemcpy(CheckerContext &C, const CallExpr *CE) const; void evalMempcpy(CheckerContext &C, const CallExpr *CE) const; void evalMemmove(CheckerContext &C, const CallExpr *CE) const; @@ -1201,9 +1232,6 @@ void CStringChecker::evalCopyCommon(CheckerContext &C, void CStringChecker::evalMemcpy(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() < 3) - return; - // void *memcpy(void *restrict dst, const void *restrict src, size_t n); // The return value is the address of the destination buffer. const Expr *Dest = CE->getArg(0); @@ -1213,9 +1241,6 @@ void CStringChecker::evalMemcpy(CheckerContext &C, const CallExpr *CE) const { } void CStringChecker::evalMempcpy(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() < 3) - return; - // void *mempcpy(void *restrict dst, const void *restrict src, size_t n); // The return value is a pointer to the byte following the last written byte. const Expr *Dest = CE->getArg(0); @@ -1225,9 +1250,6 @@ void CStringChecker::evalMempcpy(CheckerContext &C, const CallExpr *CE) const { } void CStringChecker::evalMemmove(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() < 3) - return; - // void *memmove(void *dst, const void *src, size_t n); // The return value is the address of the destination buffer. const Expr *Dest = CE->getArg(0); @@ -1237,18 +1259,12 @@ void CStringChecker::evalMemmove(CheckerContext &C, const CallExpr *CE) const { } void CStringChecker::evalBcopy(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() < 3) - return; - // void bcopy(const void *src, void *dst, size_t n); evalCopyCommon(C, CE, C.getState(), CE->getArg(2), CE->getArg(1), CE->getArg(0)); } void CStringChecker::evalMemcmp(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() < 3) - return; - // int memcmp(const void *s1, const void *s2, size_t n); CurrentFunctionDescription = "memory comparison function"; @@ -1323,18 +1339,12 @@ void CStringChecker::evalMemcmp(CheckerContext &C, const CallExpr *CE) const { void CStringChecker::evalstrLength(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() < 1) - return; - // size_t strlen(const char *s); evalstrLengthCommon(C, CE, /* IsStrnlen = */ false); } void CStringChecker::evalstrnLength(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() < 2) - return; - // size_t strnlen(const char *s, size_t maxlen); evalstrLengthCommon(C, CE, /* IsStrnlen = */ true); } @@ -1459,9 +1469,6 @@ void CStringChecker::evalstrLengthCommon(CheckerContext &C, const CallExpr *CE, } void CStringChecker::evalStrcpy(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() < 2) - return; - // char *strcpy(char *restrict dst, const char *restrict src); evalStrcpyCommon(C, CE, /* returnEnd = */ false, @@ -1470,9 +1477,6 @@ void CStringChecker::evalStrcpy(CheckerContext &C, const CallExpr *CE) const { } void CStringChecker::evalStrncpy(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() < 3) - return; - // char *strncpy(char *restrict dst, const char *restrict src, size_t n); evalStrcpyCommon(C, CE, /* returnEnd = */ false, @@ -1481,9 +1485,6 @@ void CStringChecker::evalStrncpy(CheckerContext &C, const CallExpr *CE) const { } void CStringChecker::evalStpcpy(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() < 2) - return; - // char *stpcpy(char *restrict dst, const char *restrict src); evalStrcpyCommon(C, CE, /* returnEnd = */ true, @@ -1492,9 +1493,6 @@ void CStringChecker::evalStpcpy(CheckerContext &C, const CallExpr *CE) const { } void CStringChecker::evalStrlcpy(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() < 3) - return; - // char *strlcpy(char *dst, const char *src, size_t n); evalStrcpyCommon(C, CE, /* returnEnd = */ true, @@ -1504,9 +1502,6 @@ void CStringChecker::evalStrlcpy(CheckerContext &C, const CallExpr *CE) const { } void CStringChecker::evalStrcat(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() < 2) - return; - //char *strcat(char *restrict s1, const char *restrict s2); evalStrcpyCommon(C, CE, /* returnEnd = */ false, @@ -1515,9 +1510,6 @@ void CStringChecker::evalStrcat(CheckerContext &C, const CallExpr *CE) const { } void CStringChecker::evalStrncat(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() < 3) - return; - //char *strncat(char *restrict s1, const char *restrict s2, size_t n); evalStrcpyCommon(C, CE, /* returnEnd = */ false, @@ -1526,9 +1518,6 @@ void CStringChecker::evalStrncat(CheckerContext &C, const CallExpr *CE) const { } void CStringChecker::evalStrlcat(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() < 3) - return; - // FIXME: strlcat() uses a different rule for bound checking, i.e. 'n' means // a different thing as compared to strncat(). This currently causes // false positives in the alpha string bound checker. @@ -1885,35 +1874,23 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, } void CStringChecker::evalStrcmp(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() < 2) - return; - //int strcmp(const char *s1, const char *s2); evalStrcmpCommon(C, CE, /* isBounded = */ false, /* ignoreCase = */ false); } void CStringChecker::evalStrncmp(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() < 3) - return; - //int strncmp(const char *s1, const char *s2, size_t n); evalStrcmpCommon(C, CE, /* isBounded = */ true, /* ignoreCase = */ false); } void CStringChecker::evalStrcasecmp(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() < 2) - return; - //int strcasecmp(const char *s1, const char *s2); evalStrcmpCommon(C, CE, /* isBounded = */ false, /* ignoreCase = */ true); } void CStringChecker::evalStrncasecmp(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() < 3) - return; - //int strncasecmp(const char *s1, const char *s2, size_t n); evalStrcmpCommon(C, CE, /* isBounded = */ true, /* ignoreCase = */ true); } @@ -2047,9 +2024,6 @@ void CStringChecker::evalStrcmpCommon(CheckerContext &C, const CallExpr *CE, void CStringChecker::evalStrsep(CheckerContext &C, const CallExpr *CE) const { //char *strsep(char **stringp, const char *delim); - if (CE->getNumArgs() < 2) - return; - // Sanity: does the search string parameter match the return type? const Expr *SearchStrPtr = CE->getArg(0); QualType CharPtrTy = SearchStrPtr->getType()->getPointeeType(); @@ -2118,7 +2092,7 @@ void CStringChecker::evalStdCopyBackward(CheckerContext &C, void CStringChecker::evalStdCopyCommon(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() < 3) + if (!CE->getArg(2)->getType()->isPointerType()) return; ProgramStateRef State = C.getState(); @@ -2145,9 +2119,6 @@ void CStringChecker::evalStdCopyCommon(CheckerContext &C, } void CStringChecker::evalMemset(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() != 3) - return; - CurrentFunctionDescription = "memory set function"; const Expr *Mem = CE->getArg(0); @@ -2196,9 +2167,6 @@ void CStringChecker::evalMemset(CheckerContext &C, const CallExpr *CE) const { } void CStringChecker::evalBzero(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() != 2) - return; - CurrentFunctionDescription = "memory clearance function"; const Expr *Mem = CE->getArg(0); @@ -2241,110 +2209,53 @@ void CStringChecker::evalBzero(CheckerContext &C, const CallExpr *CE) const { C.addTransition(State); } -static bool isCPPStdLibraryFunction(const FunctionDecl *FD, StringRef Name) { - IdentifierInfo *II = FD->getIdentifier(); - if (!II) - return false; - - if (!AnalysisDeclContext::isInStdNamespace(FD)) - return false; - - if (II->getName().equals(Name)) - return true; - - return false; -} //===----------------------------------------------------------------------===// // The driver method, and other Checker callbacks. //===----------------------------------------------------------------------===// -static CStringChecker::FnCheck identifyCall(const CallExpr *CE, - CheckerContext &C) { - const FunctionDecl *FDecl = C.getCalleeDecl(CE); - if (!FDecl) +CStringChecker::FnCheck CStringChecker::identifyCall(const CallEvent &Call, + CheckerContext &C) const { + const auto *CE = dyn_cast_or_null(Call.getOriginExpr()); + if (!CE) + return nullptr; + + const FunctionDecl *FD = dyn_cast_or_null(Call.getDecl()); + if (!FD) return nullptr; + if (Call.isCalled(StdCopy)) { + return &CStringChecker::evalStdCopy; + } else if (Call.isCalled(StdCopyBackward)) { + return &CStringChecker::evalStdCopyBackward; + } + // Pro-actively check that argument types are safe to do arithmetic upon. // We do not want to crash if someone accidentally passes a structure - // into, say, a C++ overload of any of these functions. - if (isCPPStdLibraryFunction(FDecl, "copy")) { - if (CE->getNumArgs() < 3 || !CE->getArg(2)->getType()->isPointerType()) - return nullptr; - return &CStringChecker::evalStdCopy; - } else if (isCPPStdLibraryFunction(FDecl, "copy_backward")) { - if (CE->getNumArgs() < 3 || !CE->getArg(2)->getType()->isPointerType()) + // into, say, a C++ overload of any of these functions. We could not check + // that for std::copy because they may have arguments of other types. + for (auto I : CE->arguments()) { + QualType T = I->getType(); + if (!T->isIntegralOrEnumerationType() && !T->isPointerType()) return nullptr; - return &CStringChecker::evalStdCopyBackward; - } else { - // An umbrella check for all C library functions. - for (auto I: CE->arguments()) { - QualType T = I->getType(); - if (!T->isIntegralOrEnumerationType() && !T->isPointerType()) - return nullptr; - } } - // FIXME: Poorly-factored string switches are slow. - if (C.isCLibraryFunction(FDecl, "memcpy")) - return &CStringChecker::evalMemcpy; - else if (C.isCLibraryFunction(FDecl, "mempcpy")) - return &CStringChecker::evalMempcpy; - else if (C.isCLibraryFunction(FDecl, "memcmp")) - return &CStringChecker::evalMemcmp; - else if (C.isCLibraryFunction(FDecl, "memmove")) - return &CStringChecker::evalMemmove; - else if (C.isCLibraryFunction(FDecl, "memset") || - C.isCLibraryFunction(FDecl, "explicit_memset")) - return &CStringChecker::evalMemset; - else if (C.isCLibraryFunction(FDecl, "strcpy")) - return &CStringChecker::evalStrcpy; - else if (C.isCLibraryFunction(FDecl, "strncpy")) - return &CStringChecker::evalStrncpy; - else if (C.isCLibraryFunction(FDecl, "stpcpy")) - return &CStringChecker::evalStpcpy; - else if (C.isCLibraryFunction(FDecl, "strlcpy")) - return &CStringChecker::evalStrlcpy; - else if (C.isCLibraryFunction(FDecl, "strcat")) - return &CStringChecker::evalStrcat; - else if (C.isCLibraryFunction(FDecl, "strncat")) - return &CStringChecker::evalStrncat; - else if (C.isCLibraryFunction(FDecl, "strlcat")) - return &CStringChecker::evalStrlcat; - else if (C.isCLibraryFunction(FDecl, "strlen")) - return &CStringChecker::evalstrLength; - else if (C.isCLibraryFunction(FDecl, "strnlen")) - return &CStringChecker::evalstrnLength; - else if (C.isCLibraryFunction(FDecl, "strcmp")) - return &CStringChecker::evalStrcmp; - else if (C.isCLibraryFunction(FDecl, "strncmp")) - return &CStringChecker::evalStrncmp; - else if (C.isCLibraryFunction(FDecl, "strcasecmp")) - return &CStringChecker::evalStrcasecmp; - else if (C.isCLibraryFunction(FDecl, "strncasecmp")) - return &CStringChecker::evalStrncasecmp; - else if (C.isCLibraryFunction(FDecl, "strsep")) - return &CStringChecker::evalStrsep; - else if (C.isCLibraryFunction(FDecl, "bcopy")) - return &CStringChecker::evalBcopy; - else if (C.isCLibraryFunction(FDecl, "bcmp")) - return &CStringChecker::evalMemcmp; - else if (C.isCLibraryFunction(FDecl, "bzero") || - C.isCLibraryFunction(FDecl, "explicit_bzero")) - return &CStringChecker::evalBzero; + const FnCheck *Callback = Callbacks.lookup(Call); + if (Callback) + return *Callback; return nullptr; } bool CStringChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { - const auto *CE = dyn_cast_or_null(Call.getOriginExpr()); - FnCheck evalFunction = identifyCall(CE, C); + FnCheck Callback = identifyCall(Call, C); // If the callee isn't a string function, let another checker handle it. - if (!evalFunction) + if (!Callback) return false; // Check and evaluate the call. - (this->*evalFunction)(C, CE); + const auto *CE = cast(Call.getOriginExpr()); + (this->*Callback)(C, CE); // If the evaluate call resulted in no change, chain to the next eval call // handler. diff --git a/clang/test/Analysis/string.c b/clang/test/Analysis/string.c index d3b131ec9a64f..841bc157ab57b 100644 --- a/clang/test/Analysis/string.c +++ b/clang/test/Analysis/string.c @@ -1598,3 +1598,9 @@ void memset29_plain_int_zero() { memset(&x, 0, sizeof(short)); clang_analyzer_eval(x == 0); // expected-warning{{TRUE}} } + +void test_memset_chk() { + int x; + __builtin___memset_chk(&x, 0, sizeof(x), __builtin_object_size(&x, 0)); + clang_analyzer_eval(x == 0); // expected-warning{{TRUE}} +} From 264ac3e716280e6e8f46f79dfa26c0efd728d3ed Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Mon, 1 Jul 2019 23:02:14 +0000 Subject: [PATCH 025/181] [analyzer] NonnullGlobalConstants: Don't be confused by a _Nonnull attribute. The NonnullGlobalConstants checker models the rule "it doesn't make sense to make a constant global pointer and initialize it to null"; it makes sure that whatever it's initialized with is known to be non-null. Ironically, annotating the type of the pointer as _Nonnull breaks the checker. Fix handling of the _Nonnull annotation so that it was instead one more reason to believe that the value is non-null. Differential Revision: https://reviews.llvm.org/D63956 llvm-svn: 364869 (cherry picked from commit 512f4838c47c5bab13d8bb0eabc01d64222825ae) --- .../NonnullGlobalConstantsChecker.cpp | 23 ++++++++++++------- .../test/Analysis/nonnull-global-constants.mm | 12 ++++++++++ 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/NonnullGlobalConstantsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/NonnullGlobalConstantsChecker.cpp index dd76fd2f1294e..43dbe57b8432b 100644 --- a/clang/lib/StaticAnalyzer/Checkers/NonnullGlobalConstantsChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/NonnullGlobalConstantsChecker.cpp @@ -106,14 +106,21 @@ bool NonnullGlobalConstantsChecker::isGlobalConstString(SVal V) const { return true; // Look through the typedefs. - while (auto *T = dyn_cast(Ty)) { - Ty = T->getDecl()->getUnderlyingType(); - - // It is sufficient for any intermediate typedef - // to be classified const. - HasConst = HasConst || Ty.isConstQualified(); - if (isNonnullType(Ty) && HasConst) - return true; + while (const Type *T = Ty.getTypePtr()) { + if (const auto *TT = dyn_cast(T)) { + Ty = TT->getDecl()->getUnderlyingType(); + // It is sufficient for any intermediate typedef + // to be classified const. + HasConst = HasConst || Ty.isConstQualified(); + if (isNonnullType(Ty) && HasConst) + return true; + } else if (const auto *AT = dyn_cast(T)) { + if (AT->getAttrKind() == attr::TypeNonNull) + return true; + Ty = AT->getModifiedType(); + } else { + return false; + } } return false; } diff --git a/clang/test/Analysis/nonnull-global-constants.mm b/clang/test/Analysis/nonnull-global-constants.mm index 7900b9dd1286a..9e1a588ba47a8 100644 --- a/clang/test/Analysis/nonnull-global-constants.mm +++ b/clang/test/Analysis/nonnull-global-constants.mm @@ -101,3 +101,15 @@ void testNonnullBool() { void testNonnullNonconstBool() { clang_analyzer_eval(kBoolMutable); // expected-warning{{UNKNOWN}} } + +// If it's annotated as nonnull, it doesn't even need to be const. +extern CFStringRef _Nonnull str3; +void testNonnullNonconstCFString() { + clang_analyzer_eval(str3); // expected-warning{{TRUE}} +} + +// This one's nonnull for two reasons. +extern const CFStringRef _Nonnull str4; +void testNonnullNonnullCFString() { + clang_analyzer_eval(str4); // expected-warning{{TRUE}} +} From 7f5f4d5b7c143c816178d7e60d32ff77b90462eb Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Mon, 1 Jul 2019 23:02:18 +0000 Subject: [PATCH 026/181] [analyzer] Fix invalidation when returning into a ctor initializer. Due to RVO the target region of a function that returns an object by value isn't necessarily a temporary object region; it may be an arbitrary memory region. In particular, it may be a field of a bigger object. Make sure we don't invalidate the bigger object when said function is evaluated conservatively. Differential Revision: https://reviews.llvm.org/D63968 llvm-svn: 364870 (cherry picked from commit ceb639dbeea97c901b2642ebe296eb358d27b2f6) --- .../Core/ExprEngineCallAndReturn.cpp | 17 +++++++++---- clang/test/Analysis/rvo.cpp | 25 +++++++++++++++++++ 2 files changed, 37 insertions(+), 5 deletions(-) create mode 100644 clang/test/Analysis/rvo.cpp diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp index 3fe06aea63e8e..e00a08b2162c1 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp @@ -634,12 +634,19 @@ ProgramStateRef ExprEngine::bindReturnValue(const CallEvent &Call, std::tie(State, Target) = prepareForObjectConstruction(Call.getOriginExpr(), State, LCtx, RTC->getConstructionContext(), CallOpts); - assert(Target.getAsRegion()); - // Invalidate the region so that it didn't look uninitialized. Don't notify - // the checkers. - State = State->invalidateRegions(Target.getAsRegion(), E, Count, LCtx, + const MemRegion *TargetR = Target.getAsRegion(); + assert(TargetR); + // Invalidate the region so that it didn't look uninitialized. If this is + // a field or element constructor, we do not want to invalidate + // the whole structure. Pointer escape is meaningless because + // the structure is a product of conservative evaluation + // and therefore contains nothing interesting at this point. + RegionAndSymbolInvalidationTraits ITraits; + ITraits.setTrait(TargetR, + RegionAndSymbolInvalidationTraits::TK_DoNotInvalidateSuperRegion); + State = State->invalidateRegions(TargetR, E, Count, LCtx, /* CausedByPointerEscape=*/false, nullptr, - &Call, nullptr); + &Call, &ITraits); R = State->getSVal(Target.castAs(), E->getType()); } else { diff --git a/clang/test/Analysis/rvo.cpp b/clang/test/Analysis/rvo.cpp new file mode 100644 index 0000000000000..cf06a9570b368 --- /dev/null +++ b/clang/test/Analysis/rvo.cpp @@ -0,0 +1,25 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker core,cplusplus \ +// RUN: -analyzer-checker debug.ExprInspection -verify %s + +void clang_analyzer_eval(bool); + +struct A { + int x; +}; + +A getA(); + +struct B { + int *p; + A a; + + B(int *p) : p(p), a(getA()) {} +}; + +void foo() { + B b1(nullptr); + clang_analyzer_eval(b1.p == nullptr); // expected-warning{{TRUE}} + B b2(new int); // No leak yet! + clang_analyzer_eval(b2.p == nullptr); // expected-warning{{FALSE}} + // expected-warning@-1{{Potential leak of memory pointed to by 'b2.p'}} +} From 75e90a7744d301a79894c865d0fa7ebc05ebb5b0 Mon Sep 17 00:00:00 2001 From: Nathan Huckleberry Date: Mon, 1 Jul 2019 23:29:10 +0000 Subject: [PATCH 027/181] [analyzer] Support kfree in MallocChecker Summary: kmalloc is freed with kfree in the linux kernel. kmalloc support was added in r204832, but kfree was not. Adding kfree fixes incorrectly detected memory leaks. Reviewers: NoQ, nickdesaulniers, dcoughlin, Szelethus Reviewed By: NoQ, Szelethus Subscribers: xazax.hun, baloghadamsoftware, szepet, a.sidorin, mikhail.ramalho, Szelethus, donat.nagy, dkrupp, Charusso, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D64030 llvm-svn: 364875 (cherry picked from commit 121401425d4d56eb6faed74c0307d161e77f7434) --- .../StaticAnalyzer/Checkers/MallocChecker.cpp | 26 ++++++++++--------- clang/test/Analysis/kmalloc-linux.c | 6 ++--- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index c416b98cf4bd5..03e779f3c52d8 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -177,9 +177,10 @@ class MallocChecker : public Checker KernelZeroFlagVal; void initIdentifierInfo(ASTContext &C) const; @@ -598,6 +599,7 @@ void MallocChecker::initIdentifierInfo(ASTContext &Ctx) const { II_strndup = &Ctx.Idents.get("strndup"); II_wcsdup = &Ctx.Idents.get("wcsdup"); II_kmalloc = &Ctx.Idents.get("kmalloc"); + II_kfree = &Ctx.Idents.get("kfree"); II_if_nameindex = &Ctx.Idents.get("if_nameindex"); II_if_freenameindex = &Ctx.Idents.get("if_freenameindex"); @@ -657,7 +659,7 @@ bool MallocChecker::isCMemFunction(const FunctionDecl *FD, if (Family == AF_Malloc && CheckFree) { if (FunI == II_free || FunI == II_realloc || FunI == II_reallocf || - FunI == II_g_free) + FunI == II_g_free || FunI == II_kfree) return true; } @@ -874,7 +876,7 @@ void MallocChecker::checkPostStmt(const CallExpr *CE, CheckerContext &C) const { State = CallocMem(C, CE, State); State = ProcessZeroAllocation(C, CE, 0, State); State = ProcessZeroAllocation(C, CE, 1, State); - } else if (FunI == II_free || FunI == II_g_free) { + } else if (FunI == II_free || FunI == II_g_free || FunI == II_kfree) { State = FreeMemAux(C, CE, State, 0, false, ReleasedAllocatedMemory); } else if (FunI == II_strdup || FunI == II_win_strdup || FunI == II_wcsdup || FunI == II_win_wcsdup) { diff --git a/clang/test/Analysis/kmalloc-linux.c b/clang/test/Analysis/kmalloc-linux.c index bac71388a7a81..f183446bc0638 100644 --- a/clang/test/Analysis/kmalloc-linux.c +++ b/clang/test/Analysis/kmalloc-linux.c @@ -24,7 +24,7 @@ void test_zeroed() { t = list[i]; foo(t); } - free(list); // no-warning + kfree(list); // no-warning } void test_nonzero() { @@ -39,7 +39,7 @@ void test_nonzero() { t = list[i]; // expected-warning{{undefined}} foo(t); } - free(list); + kfree(list); } void test_indeterminate(int flags) { @@ -54,5 +54,5 @@ void test_indeterminate(int flags) { t = list[i]; // expected-warning{{undefined}} foo(t); } - free(list); + kfree(list); } From c2680c66678a77c1e40a48f1ae425268583e7bef Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Tue, 2 Jul 2019 02:17:50 +0000 Subject: [PATCH 028/181] [analyzer] exploded-graph-rewriter: Add support for objects under construction. This trait is Environment-like, so there was a chance to re-use a lot of code. Differential Revision: https://reviews.llvm.org/D64047 llvm-svn: 364880 (cherry picked from commit 0a77d9192ab75b8fcf218747d6bcd213dce1f4ce) --- .../exploded-graph-rewriter/constraints.dot | 1 + .../constraints_diff.dot | 5 +- .../exploded-graph-rewriter/environment.dot | 1 + .../environment_diff.dot | 3 + .../initializers_under_construction.cpp | 25 +++++++ .../objects_under_construction.cpp | 48 ++++++++++++ .../exploded-graph-rewriter/store.dot | 1 + .../exploded-graph-rewriter/store_diff.dot | 2 + .../utils/analyzer/exploded-graph-rewriter.py | 74 +++++++++++++------ 9 files changed, 137 insertions(+), 23 deletions(-) create mode 100644 clang/test/Analysis/exploded-graph-rewriter/initializers_under_construction.cpp create mode 100644 clang/test/Analysis/exploded-graph-rewriter/objects_under_construction.cpp diff --git a/clang/test/Analysis/exploded-graph-rewriter/constraints.dot b/clang/test/Analysis/exploded-graph-rewriter/constraints.dot index 49aba4b15e19f..58faafc0f2607 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/constraints.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/constraints.dot @@ -20,6 +20,7 @@ Node0x1 [shape=record,label= "store": null, "environment": null, "dynamic_types": null, + "constructing_objects": null, "constraints": [ { "symbol": "reg_$0", "range": "{ [0, 0] }" } ] diff --git a/clang/test/Analysis/exploded-graph-rewriter/constraints_diff.dot b/clang/test/Analysis/exploded-graph-rewriter/constraints_diff.dot index 068ef58d25c22..24aa9b41a7aee 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/constraints_diff.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/constraints_diff.dot @@ -13,6 +13,7 @@ Node0x1 [shape=record,label= "store": null, "environment": null, "dynamic_types": null, + "constructing_objects": null, "constraints": [ { "symbol": "reg_$0", "range": "{ [0, 10] }" } ] @@ -43,6 +44,7 @@ Node0x3 [shape=record,label= "store": null, "environment": null, "dynamic_types": null, + "constructing_objects": null, "constraints": [ { "symbol": "reg_$0", "range": "{ [0, 5] }" } ] @@ -62,7 +64,8 @@ Node0x5 [shape=record,label= "store": null, "environment": null, "constraints": null, - "dynamic_types": null + "dynamic_types": null, + "constructing_objects": null } } \l}"]; diff --git a/clang/test/Analysis/exploded-graph-rewriter/environment.dot b/clang/test/Analysis/exploded-graph-rewriter/environment.dot index 4c6c93b85ded2..3a54c6ecb1efe 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/environment.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/environment.dot @@ -35,6 +35,7 @@ Node0x1 [shape=record,label= "store": null, "constraints": null, "dynamic_types": null, + "constructing_objects": null, "environment": { "pointer": "0x2", "items": [ diff --git a/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot b/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot index ac41cdf97f02a..5264b40f802f7 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot @@ -14,6 +14,7 @@ Node0x1 [shape=record,label= "store": null, "constraints": null, "dynamic_types": null, + "constructing_objects": null, "environment": { "pointer": "0x2", "items": [ @@ -61,6 +62,7 @@ Node0x6 [shape=record,label= "store": null, "constraints": null, "dynamic_types": null, + "constructing_objects": null, "environment": { "pointer": "0x2", "items": [ @@ -102,6 +104,7 @@ Node0x9 [shape=record,label= "store": null, "constraints": null, "dynamic_types": null, + "constructing_objects": null, "environment": { "pointer": "0x2", "items": [ diff --git a/clang/test/Analysis/exploded-graph-rewriter/initializers_under_construction.cpp b/clang/test/Analysis/exploded-graph-rewriter/initializers_under_construction.cpp new file mode 100644 index 0000000000000..472627ef61951 --- /dev/null +++ b/clang/test/Analysis/exploded-graph-rewriter/initializers_under_construction.cpp @@ -0,0 +1,25 @@ +// FIXME: Figure out how to use %clang_analyze_cc1 with our lit.local.cfg. +// RUN: %clang_cc1 -analyze -triple x86_64-unknown-linux-gnu \ +// RUN: -analyzer-checker=core \ +// RUN: -analyzer-dump-egraph=%t.dot %s +// RUN: %exploded_graph_rewriter %t.dot | FileCheck %s +// REQUIRES: asserts + +// FIXME: Substitution doesn't seem to work on Windows. +// UNSUPPORTED: system-windows + +struct A { + A() {} +}; + +struct B { + A a; + B() : a() {} +}; + +void test() { + // CHECK: (construct into member variable) + // CHECK-SAME: a + // CHECK-SAME: &b->a + B b; +} diff --git a/clang/test/Analysis/exploded-graph-rewriter/objects_under_construction.cpp b/clang/test/Analysis/exploded-graph-rewriter/objects_under_construction.cpp new file mode 100644 index 0000000000000..c2a805f099c07 --- /dev/null +++ b/clang/test/Analysis/exploded-graph-rewriter/objects_under_construction.cpp @@ -0,0 +1,48 @@ +// FIXME: Figure out how to use %clang_analyze_cc1 with our lit.local.cfg. +// RUN: %clang_cc1 -analyze -triple x86_64-unknown-linux-gnu \ +// RUN: -analyzer-checker=core \ +// RUN: -analyzer-dump-egraph=%t.dot %s +// RUN: %exploded_graph_rewriter %t.dot | FileCheck %s +// REQUIRES: asserts + +// FIXME: Substitution doesn't seem to work on Windows. +// UNSUPPORTED: system-windows + +struct S { + S() {} +}; + +void test() { + // CHECK: Objects Under Construction: + // CHECK-SAME: + // CHECK-SAME: #0 Call + // CHECK-SAME: + // CHECK-SAME: test + // CHECK-SAME: + // CHECK-SAME: + // CHECK-SAME: + // CHECK-SAME: S{{[0-9]*}} + // CHECK-SAME: + // CHECK-SAME: (materialize temporary) + // CHECK-SAME: + // CHECK-SAME: S() + // CHECK-SAME: &s + // CHECK-SAME: + // CHECK-SAME: + // CHECK-SAME: S{{[0-9]*}} + // CHECK-SAME: + // CHECK-SAME: (elide constructor) + // CHECK-SAME: + // CHECK-SAME: S() + // CHECK-SAME: &s + // CHECK-SAME: + // CHECK-SAME: + // CHECK-SAME: S{{[0-9]*}} + // CHECK-SAME: + // CHECK-SAME: (construct into local variable) + // CHECK-SAME: + // CHECK-SAME: S s = S(); + // CHECK-SAME: &s + // CHECK-SAME: + S s = S(); +} diff --git a/clang/test/Analysis/exploded-graph-rewriter/store.dot b/clang/test/Analysis/exploded-graph-rewriter/store.dot index f431319cc6db5..1c306ce160f25 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/store.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/store.dot @@ -30,6 +30,7 @@ Node0x1 [shape=record,label= "environment": null, "constraints": null, "dynamic_types": null, + "constructing_objects": null, "store": { "pointer": "0x2", "items": [ diff --git a/clang/test/Analysis/exploded-graph-rewriter/store_diff.dot b/clang/test/Analysis/exploded-graph-rewriter/store_diff.dot index ab36e5f9869b1..ddd26ce5dcd62 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/store_diff.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/store_diff.dot @@ -13,6 +13,7 @@ Node0x1 [shape=record,label= "environment": null, "constraints": null, "dynamic_types": null, + "constructing_objects": null, "store": { "pointer": "0x2", "items": [ @@ -59,6 +60,7 @@ Node0x4 [shape=record,label= "environment": null, "constraints": null, "dynamic_types": null, + "constructing_objects": null, "store": { "pointer": "0x5", "items": [ diff --git a/clang/utils/analyzer/exploded-graph-rewriter.py b/clang/utils/analyzer/exploded-graph-rewriter.py index b033814ed9704..7278c708036ff 100755 --- a/clang/utils/analyzer/exploded-graph-rewriter.py +++ b/clang/utils/analyzer/exploded-graph-rewriter.py @@ -71,8 +71,11 @@ def __init__(self, json_pp): class EnvironmentBindingKey(object): def __init__(self, json_ek): super(EnvironmentBindingKey, self).__init__() - self.stmt_id = json_ek['stmt_id'] + # CXXCtorInitializer is not a Stmt! + self.stmt_id = json_ek['stmt_id'] if 'stmt_id' in json_ek \ + else json_ek['init_id'] self.pretty = json_ek['pretty'] + self.kind = json_ek['kind'] if 'kind' in json_ek else None def _key(self): return self.stmt_id @@ -122,12 +125,12 @@ def is_different(self, prev): return len(removed) != 0 or len(added) != 0 -# A deserialized Environment. -class Environment(object): +# A deserialized Environment. This class can also hold other entities that +# are similar to Environment, such as Objects Under Construction. +class GenericEnvironment(object): def __init__(self, json_e): - super(Environment, self).__init__() - self.ptr = json_e['pointer'] - self.frames = [EnvironmentFrame(f) for f in json_e['items']] + super(GenericEnvironment, self).__init__() + self.frames = [EnvironmentFrame(f) for f in json_e] def diff_frames(self, prev): # TODO: It's difficult to display a good diff when frame numbers shift. @@ -214,13 +217,18 @@ def __init__(self, state_id, json_ps): logging.debug('Adding ProgramState ' + str(state_id)) self.state_id = state_id + self.store = Store(json_ps['store']) \ if json_ps['store'] is not None else None - self.environment = Environment(json_ps['environment']) \ + + self.environment = \ + GenericEnvironment(json_ps['environment']['items']) \ if json_ps['environment'] is not None else None + self.constraints = GenericMap([ (c['symbol'], c['range']) for c in json_ps['constraints'] ]) if json_ps['constraints'] is not None else None + self.dynamic_types = GenericMap([ (t['region'], '%s%s' % (t['dyn_type'], ' (or a sub-class)' @@ -228,7 +236,10 @@ def __init__(self, state_id, json_ps): for t in json_ps['dynamic_types']]) \ if json_ps['dynamic_types'] is not None else None - # TODO: Objects under construction. + self.constructing_objects = \ + GenericEnvironment(json_ps['constructing_objects']) \ + if json_ps['constructing_objects'] is not None else None + # TODO: Checker messages. @@ -416,10 +427,15 @@ def dump_location_context(lc, is_added=None): def dump_binding(f, b, is_added=None): self._dump('%s' 'S%s' + '%s' '%s' '%s' % (self._diff_plus_minus(is_added), - b.stmt_id, b.pretty, f.bindings[b])) + b.stmt_id, + '' + '(%s)' % b.kind + if b.kind is not None else '', + b.pretty, f.bindings[b])) frames_updated = e.diff_frames(prev_e) if prev_e is not None else None if frames_updated: @@ -440,20 +456,25 @@ def dump_binding(f, b, is_added=None): self._dump('') - def visit_environment_in_state(self, s, prev_s=None): - self._dump('
Environment: ') - if s.environment is None: + def visit_environment_in_state(self, selector, title, s, prev_s=None): + e = getattr(s, selector) + prev_e = getattr(prev_s, selector) if prev_s is not None else None + if e is None and prev_e is None: + return + + self._dump('
%s: ' % title) + if e is None: self._dump(' Nothing!') else: - if prev_s is not None and prev_s.environment is not None: - if s.environment.is_different(prev_s.environment): + if prev_e is not None: + if e.is_different(prev_e): self._dump('') - self.visit_environment(s.environment, prev_s.environment) + self.visit_environment(e, prev_e) else: self._dump(' No changes!') else: self._dump('') - self.visit_environment(s.environment) + self.visit_environment(e) self._dump('') @@ -496,19 +517,24 @@ def dump_binding(s, c, b, is_added=None): self._dump('') def visit_store_in_state(self, s, prev_s=None): + st = s.store + prev_st = prev_s.store if prev_s is not None else None + if st is None and prev_st is None: + return + self._dump('
Store: ') - if s.store is None: + if st is None: self._dump(' Nothing!') else: - if prev_s is not None and prev_s.store is not None: - if s.store.is_different(prev_s.store): + if prev_st is not None: + if s.store.is_different(prev_st): self._dump('') - self.visit_store(s.store, prev_s.store) + self.visit_store(st, prev_st) else: self._dump(' No changes!') else: self._dump('') - self.visit_store(s.store) + self.visit_store(st) self._dump('') def visit_generic_map(self, m, prev_m=None): @@ -559,11 +585,15 @@ def visit_generic_map_in_state(self, selector, title, s, prev_s=None): def visit_state(self, s, prev_s): self.visit_store_in_state(s, prev_s) - self.visit_environment_in_state(s, prev_s) + self.visit_environment_in_state('environment', 'Environment', + s, prev_s) self.visit_generic_map_in_state('constraints', 'Ranges', s, prev_s) self.visit_generic_map_in_state('dynamic_types', 'Dynamic Types', s, prev_s) + self.visit_environment_in_state('constructing_objects', + 'Objects Under Construction', + s, prev_s) def visit_node(self, node): self._dump('%s [shape=record,label=<' From dc3ce0e434b7cd99049600aff6cb2ff9f73e29f5 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Tue, 2 Jul 2019 02:17:53 +0000 Subject: [PATCH 029/181] [analyzer] exploded-graph-rewriter: Improve program point dumps. - Take advantage of the stmt_point_kind. - Dump block IDs for BlockEntrance nodes. - Don't dump huge compound statements on PurgeDeadSymbols nodes. - Rename Edge to BlockEdge for consistency. - Tweak colors. Differential Revision: https://reviews.llvm.org/D64051 llvm-svn: 364881 (cherry picked from commit 2ca5355712fc6b7ad95f8ce9b8e2054b621cda9b) --- .../exploded-graph-rewriter/escapes.c | 2 +- .../program_points.dot | 32 +++++++++++++++--- .../utils/analyzer/exploded-graph-rewriter.py | 33 +++++++++++++++---- 3 files changed, 54 insertions(+), 13 deletions(-) diff --git a/clang/test/Analysis/exploded-graph-rewriter/escapes.c b/clang/test/Analysis/exploded-graph-rewriter/escapes.c index 050f724f1fbd7..d35e41ae28d4e 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/escapes.c +++ b/clang/test/Analysis/exploded-graph-rewriter/escapes.c @@ -16,7 +16,7 @@ void escapes() { // CHECK-SAME: const char *const foo = "foo"; - // CHECK: BinaryOperator + // CHECK: BinaryOperator // CHECK-SAME: // CHECK-SAME: int x = 1 | 2; diff --git a/clang/test/Analysis/exploded-graph-rewriter/program_points.dot b/clang/test/Analysis/exploded-graph-rewriter/program_points.dot index 31dcc4adb62c8..2c03ac92d0f4f 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/program_points.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/program_points.dot @@ -3,18 +3,28 @@ // FIXME: Substitution doesn't seem to work on Windows. // UNSUPPORTED: system-windows -// CHECK: Program point: +// CHECK: Program points: // CHECK-SAME:
&Element\{"foo",0 S64b,char\}1 \| 23 S32b
// CHECK-SAME: // CHECK-SAME: // CHECK-SAME: // CHECK-SAME: // CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: // CHECK-SAME:
// CHECK-SAME: -// CHECK-SAME: Edge +// CHECK-SAME: BlockEdge // CHECK-SAME: // CHECK-SAME: [B0] -> [B1] // CHECK-SAME:
+// CHECK-SAME: +// CHECK-SAME: BlockEntrance +// CHECK-SAME: +// CHECK-SAME: [B1] +// CHECK-SAME:
Node0x1 [shape=record,label= "{ @@ -26,7 +36,15 @@ Node0x1 [shape=record,label= "dst_id": 1, "terminator": null, "term_kind": null, - "tag": null } + "tag": null + }, + { + "kind": "BlockEntrance", + "block_id": 1, + "terminator": null, + "term_kind": null, + "tag": null + } ]} \l}"]; @@ -37,14 +55,17 @@ Node0x1 [shape=record,label= // CHECK-SAME: (main file):4:5: // CHECK-SAME: // CHECK-SAME: -// CHECK-SAME: DeclRefExpr +// CHECK-SAME: DeclRefExpr +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: PreStmt // CHECK-SAME: // CHECK-SAME: x // CHECK-SAME: // CHECK-SAME: // CHECK-SAME: // CHECK-SAME: -// CHECK-SAME: +// CHECK-SAME: // CHECK-SAME: Tag: // CHECK-SAME: ExprEngine : Clean Node // CHECK-SAME: @@ -57,6 +78,7 @@ Node0x2 [shape=record,label= { "kind": "Statement", "stmt_kind": "DeclRefExpr", + "stmt_point_kind": "PreStmt", "stmd_id": 3, "pointer": "0x3", "pretty": "x", diff --git a/clang/utils/analyzer/exploded-graph-rewriter.py b/clang/utils/analyzer/exploded-graph-rewriter.py index 7278c708036ff..c5b95071233c7 100755 --- a/clang/utils/analyzer/exploded-graph-rewriter.py +++ b/clang/utils/analyzer/exploded-graph-rewriter.py @@ -59,6 +59,7 @@ def __init__(self, json_pp): self.dst_id = json_pp['dst_id'] elif self.kind == 'Statement': self.stmt_kind = json_pp['stmt_kind'] + self.stmt_point_kind = json_pp['stmt_point_kind'] self.pointer = json_pp['pointer'] self.pretty = json_pp['pretty'] self.loc = SourceLocation(json_pp['location']) \ @@ -373,30 +374,48 @@ def visit_program_point(self, p): elif p.kind in ['CallEnter', 'CallExitBegin', 'CallExitEnd']: color = 'blue' elif p.kind in ['Statement']: - color = 'cyan3' + color = 'cyan4' else: color = 'forestgreen' if p.kind == 'Statement': + # This avoids pretty-printing huge statements such as CompoundStmt. + # Such statements show up only at [Pre|Post]StmtPurgeDeadSymbols + skip_pretty = 'PurgeDeadSymbols' in p.stmt_point_kind + stmt_color = 'cyan3' if p.loc is not None: self._dump('' '%s:%s:%s:' '' - '%s%s' + '%s' + '%s' + '%s' % (p.loc.filename, p.loc.line, - p.loc.col, color, p.stmt_kind, p.pretty)) + p.loc.col, color, p.stmt_kind, + stmt_color, p.stmt_point_kind, + p.pretty if not skip_pretty else '')) else: self._dump('' 'Invalid Source Location:' '' - '%s%s' - % (color, p.stmt_kind, p.pretty)) + '%s' + '%s' + '%s' + % (color, p.stmt_kind, + stmt_color, p.stmt_point_kind, + p.pretty if not skip_pretty else '')) elif p.kind == 'Edge': self._dump('' '' '%s' '[B%d] -\\> [B%d]' - % (color, p.kind, p.src_id, p.dst_id)) + % (color, 'BlockEdge', p.src_id, p.dst_id)) + elif p.kind == 'BlockEntrance': + self._dump('' + '' + '%s' + '[B%d]' + % (color, p.kind, p.block_id)) else: # TODO: Print more stuff for other kinds of points. self._dump('' @@ -406,7 +425,7 @@ def visit_program_point(self, p): if p.tag is not None: self._dump('' - '' + '' 'Tag: ' '%s' % p.tag) From 5497e332d192da58a31a84f9de92dd8b1f827f31 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Tue, 2 Jul 2019 02:17:56 +0000 Subject: [PATCH 030/181] [analyzer] exploded-graph-rewriter: Implement a dark color scheme. Addresses a popular request. Activated via --dark. Differential Revision: https://reviews.llvm.org/D64056 llvm-svn: 364882 (cherry picked from commit ad38e58ef2412aaac7a5da1b15ca763787071742) --- .../Analysis/exploded-graph-rewriter/edge.dot | 6 ++- .../exploded-graph-rewriter/empty.dot | 3 ++ .../exploded-graph-rewriter/environment.dot | 2 +- .../exploded-graph-rewriter/node_labels.dot | 22 +++++++++++ .../objects_under_construction.cpp | 2 +- .../utils/analyzer/exploded-graph-rewriter.py | 37 +++++++++++++------ 6 files changed, 57 insertions(+), 15 deletions(-) create mode 100644 clang/test/Analysis/exploded-graph-rewriter/node_labels.dot diff --git a/clang/test/Analysis/exploded-graph-rewriter/edge.dot b/clang/test/Analysis/exploded-graph-rewriter/edge.dot index fa4b017e8a971..2d3a4fe7bd7aa 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/edge.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/edge.dot @@ -1,4 +1,5 @@ -// RUN: %exploded_graph_rewriter %s | FileCheck %s +// RUN: %exploded_graph_rewriter %s | FileCheck %s -check-prefix=LIGHT +// RUN: %exploded_graph_rewriter --dark %s | FileCheck %s -check-prefixes=DARK // FIXME: Substitution doesn't seem to work on Windows. // UNSUPPORTED: system-windows @@ -7,7 +8,8 @@ Node0x1 [shape=record,label= "{{ "node_id": 1, "pointer": "0x1", "program_state": null, "program_points": []}\l}"]; -// CHECK: Node0x1 -> Node0x2; +// LIGHT: Node0x1 -> Node0x2; +// DARK: Node0x1 -> Node0x2 [color="white"]; Node0x1 -> Node0x2; Node0x2 [shape=record,label= diff --git a/clang/test/Analysis/exploded-graph-rewriter/empty.dot b/clang/test/Analysis/exploded-graph-rewriter/empty.dot index 3e0733c5173e7..7f67e9cfed8a7 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/empty.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/empty.dot @@ -1,4 +1,6 @@ // RUN: %exploded_graph_rewriter %s | FileCheck %s +// RUN: %exploded_graph_rewriter --dark %s | FileCheck %s \ +// RUN: -check-prefixes=CHECK,DARK // FIXME: Substitution doesn't seem to work on Windows. // UNSUPPORTED: system-windows @@ -8,5 +10,6 @@ digraph "Exploded Graph" { } // CHECK: digraph "ExplodedGraph" { +// DARK-NEXT: bgcolor="gray10"; // CHECK-NEXT: label=""; // CHECK-NEXT: } diff --git a/clang/test/Analysis/exploded-graph-rewriter/environment.dot b/clang/test/Analysis/exploded-graph-rewriter/environment.dot index 3a54c6ecb1efe..8c451671b4c01 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/environment.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/environment.dot @@ -10,7 +10,7 @@ // CHECK-SAME: #0 Call // CHECK-SAME: // CHECK-SAME: -// CHECK-SAME: foo (line 4) +// CHECK-SAME: foo (line 4) // CHECK-SAME: // CHECK-SAME: // CHECK-SAME: diff --git a/clang/test/Analysis/exploded-graph-rewriter/node_labels.dot b/clang/test/Analysis/exploded-graph-rewriter/node_labels.dot new file mode 100644 index 0000000000000..60b2b29c06583 --- /dev/null +++ b/clang/test/Analysis/exploded-graph-rewriter/node_labels.dot @@ -0,0 +1,22 @@ +// RUN: %exploded_graph_rewriter %s | FileCheck %s -check-prefixes=CHECK,LIGHT +// RUN: %exploded_graph_rewriter %s --dark | FileCheck %s \ +// RUN: -check-prefixes CHECK,DARK + +// FIXME: Substitution doesn't seem to work on Windows. +// UNSUPPORTED: system-windows + +// LIGHT: Node0x1 [shape=record,label=< +// DARK: Node0x1 [shape=record,color="white",fontcolor="gray80",label=< +// CHECK-SAME: +// LIGHT-SAME: +// DARK-SAME: +// CHECK-SAME: Node 1 (0x1) - State Unspecified +// CHECK-SAME: +// CHECK-SAME: +Node0x1 [shape=record,label= + "{ + { "node_id": 1, "pointer": "0x1", + "program_state": null, + "program_points": [] + } +\l}"]; diff --git a/clang/test/Analysis/exploded-graph-rewriter/objects_under_construction.cpp b/clang/test/Analysis/exploded-graph-rewriter/objects_under_construction.cpp index c2a805f099c07..b3d4aef8bc8cf 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/objects_under_construction.cpp +++ b/clang/test/Analysis/exploded-graph-rewriter/objects_under_construction.cpp @@ -17,7 +17,7 @@ void test() { // CHECK-SAME: // CHECK-SAME: #0 Call // CHECK-SAME: - // CHECK-SAME: test + // CHECK-SAME: test // CHECK-SAME: // CHECK-SAME: // CHECK-SAME: diff --git a/clang/utils/analyzer/exploded-graph-rewriter.py b/clang/utils/analyzer/exploded-graph-rewriter.py index c5b95071233c7..fffe2f53ee429 100755 --- a/clang/utils/analyzer/exploded-graph-rewriter.py +++ b/clang/utils/analyzer/exploded-graph-rewriter.py @@ -334,9 +334,10 @@ def add_raw_line(self, raw_line): # A visitor that dumps the ExplodedGraph into a DOT file with fancy HTML-based # syntax highlighing. class DotDumpVisitor(object): - def __init__(self, do_diffs): + def __init__(self, do_diffs, dark_mode): super(DotDumpVisitor, self).__init__() self._do_diffs = do_diffs + self._dark_mode = dark_mode @staticmethod def _dump_raw(s): @@ -363,6 +364,8 @@ def _diff_plus_minus(is_added): def visit_begin_graph(self, graph): self._graph = graph self._dump_raw('digraph "ExplodedGraph" {\n') + if self._dark_mode: + self._dump_raw('bgcolor="gray10";\n') self._dump_raw('label="";\n') def visit_program_point(self, p): @@ -372,7 +375,7 @@ def visit_program_point(self, p): 'PostStmtPurgeDeadSymbols']: color = 'red' elif p.kind in ['CallEnter', 'CallExitBegin', 'CallExitEnd']: - color = 'blue' + color = 'dodgerblue' if self._dark_mode else 'blue' elif p.kind in ['Statement']: color = 'cyan4' else: @@ -436,7 +439,7 @@ def dump_location_context(lc, is_added=None): self._dump('%s' '%s' '' - '%s ' + '%s ' '%s' % (self._diff_plus_minus(is_added), lc.caption, lc.decl, @@ -451,9 +454,11 @@ def dump_binding(f, b, is_added=None): '%s' % (self._diff_plus_minus(is_added), b.stmt_id, - '' - '(%s)' % b.kind - if b.kind is not None else '', + '' + '%s' % ( + 'lavender' if self._dark_mode else 'darkgreen', + ('(%s)' % b.kind) if b.kind is not None else ' ' + ), b.pretty, f.bindings[b])) frames_updated = e.diff_frames(prev_e) if prev_e is not None else None @@ -615,12 +620,16 @@ def visit_state(self, s, prev_s): s, prev_s) def visit_node(self, node): - self._dump('%s [shape=record,label=<' + self._dump('%s [shape=record,' % (node.node_name())) + if self._dark_mode: + self._dump('color="white",fontcolor="gray80",') + self._dump('label=<
') - self._dump('' - % (node.node_id, node.ptr, node.state.state_id + % ("gray20" if self._dark_mode else "gray", + node.node_id, node.ptr, node.state.state_id if node.state is not None else 'Unspecified')) self._dump('
Node %d (%s) - ' + self._dump('
Node %d (%s) - ' 'State %s
') if len(node.points) > 1: @@ -645,7 +654,10 @@ def visit_node(self, node): self._dump_raw('
>];\n') def visit_edge(self, pred, succ): - self._dump_raw('%s -> %s;\n' % (pred.node_name(), succ.node_name())) + self._dump_raw('%s -> %s%s;\n' % ( + pred.node_name(), succ.node_name(), + ' [color="white"]' if self._dark_mode else '' + )) def visit_end_of_graph(self): self._dump_raw('}\n') @@ -678,6 +690,9 @@ def main(): parser.add_argument('-d', '--diff', action='store_const', dest='diff', const=True, default=False, help='display differences between states') + parser.add_argument('--dark', action='store_const', dest='dark', + const=True, default=False, + help='dark mode') args = parser.parse_args() logging.basicConfig(level=args.loglevel) @@ -688,7 +703,7 @@ def main(): graph.add_raw_line(raw_line) explorer = Explorer() - visitor = DotDumpVisitor(args.diff) + visitor = DotDumpVisitor(args.diff, args.dark) explorer.explore(graph, visitor) From 9bb31d3e8000c85a37ae8db0f2bdf7cc06c5c70e Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Tue, 2 Jul 2019 11:30:12 +0000 Subject: [PATCH 031/181] [IDF] Generalize IDFCalculator to be used with Clang's CFG I'm currently working on a GSoC project that aims to improve the the bug reports of the analyzer. The main heuristic I plan to use is to explain values that are a control dependency of the bug location better. 01 bool b = messyComputation(); 02 int i = 0; 03 if (b) // control dependency of the bug site, let's explain why we assume val 04 // to be true 05 10 / i; // warn: division by zero Because of this, I'd like to generalize IDFCalculator so that I could use it for Clang's CFG: D62883. In detail: * Rename IDFCalculator to IDFCalculatorBase, make it take a general CFG node type as a template argument rather then strictly BasicBlock (but preserve ForwardIDFCalculator and ReverseIDFCalculator) * Move IDFCalculatorBase from llvm/include/llvm/Analysis to llvm/include/llvm/Support (but leave the BasicBlock variants in llvm/include/llvm/Analysis) * clang-format the file since this patch messes up git blame anyways * Change typedef to using * Add the new type ChildrenGetterTy, and store an instance of it in IDFCalculatorBase. This is important because I'll have to specialize it for Clang's CFG to filter out nullpointer successors, similarly to D62507. Differential Revision: https://reviews.llvm.org/D63389 llvm-svn: 364911 (cherry picked from commit 9353421ecd12d071a27a0f05948113a45888f16b) --- .../llvm/Analysis/IteratedDominanceFrontier.h | 151 ++++++------- .../GenericIteratedDominanceFrontier.h | 210 ++++++++++++++++++ llvm/lib/Analysis/CMakeLists.txt | 1 - .../Analysis/IteratedDominanceFrontier.cpp | 104 --------- 4 files changed, 282 insertions(+), 184 deletions(-) create mode 100644 llvm/include/llvm/Support/GenericIteratedDominanceFrontier.h delete mode 100644 llvm/lib/Analysis/IteratedDominanceFrontier.cpp diff --git a/llvm/include/llvm/Analysis/IteratedDominanceFrontier.h b/llvm/include/llvm/Analysis/IteratedDominanceFrontier.h index e7d19d1a815f7..90e096f8c7173 100644 --- a/llvm/include/llvm/Analysis/IteratedDominanceFrontier.h +++ b/llvm/include/llvm/Analysis/IteratedDominanceFrontier.h @@ -5,96 +5,89 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// -/// \file -/// Compute iterated dominance frontiers using a linear time algorithm. -/// -/// The algorithm used here is based on: -/// -/// Sreedhar and Gao. A linear time algorithm for placing phi-nodes. -/// In Proceedings of the 22nd ACM SIGPLAN-SIGACT Symposium on Principles of -/// Programming Languages -/// POPL '95. ACM, New York, NY, 62-73. -/// -/// It has been modified to not explicitly use the DJ graph data structure and -/// to directly compute pruned SSA using per-variable liveness information. -// -//===----------------------------------------------------------------------===// #ifndef LLVM_ANALYSIS_IDF_H #define LLVM_ANALYSIS_IDF_H -#include "llvm/ADT/DenseMap.h" -#include "llvm/ADT/SmallPtrSet.h" -#include "llvm/ADT/SmallVector.h" -#include "llvm/IR/BasicBlock.h" #include "llvm/IR/CFGDiff.h" -#include "llvm/IR/Dominators.h" +#include "llvm/Support/GenericIteratedDominanceFrontier.h" namespace llvm { -/// Determine the iterated dominance frontier, given a set of defining -/// blocks, and optionally, a set of live-in blocks. -/// -/// In turn, the results can be used to place phi nodes. -/// -/// This algorithm is a linear time computation of Iterated Dominance Frontiers, -/// pruned using the live-in set. -/// By default, liveness is not used to prune the IDF computation. -/// The template parameters should be either BasicBlock* or Inverse, depending on if you want the forward or reverse IDF. -template -class IDFCalculator { - public: - IDFCalculator(DominatorTreeBase &DT) - : DT(DT), GD(nullptr), useLiveIn(false) {} - - IDFCalculator(DominatorTreeBase &DT, - const GraphDiff *GD) - : DT(DT), GD(GD), useLiveIn(false) {} - - /// Give the IDF calculator the set of blocks in which the value is - /// defined. This is equivalent to the set of starting blocks it should be - /// calculating the IDF for (though later gets pruned based on liveness). - /// - /// Note: This set *must* live for the entire lifetime of the IDF calculator. - void setDefiningBlocks(const SmallPtrSetImpl &Blocks) { - DefBlocks = &Blocks; - } - - /// Give the IDF calculator the set of blocks in which the value is - /// live on entry to the block. This is used to prune the IDF calculation to - /// not include blocks where any phi insertion would be dead. - /// - /// Note: This set *must* live for the entire lifetime of the IDF calculator. - - void setLiveInBlocks(const SmallPtrSetImpl &Blocks) { - LiveInBlocks = &Blocks; - useLiveIn = true; - } +class BasicBlock; + +namespace IDFCalculatorDetail { - /// Reset the live-in block set to be empty, and tell the IDF - /// calculator to not use liveness anymore. - void resetLiveInBlocks() { - LiveInBlocks = nullptr; - useLiveIn = false; +/// Specialization for BasicBlock for the optional use of GraphDiff. +template struct ChildrenGetterTy { + using NodeRef = BasicBlock *; + using ChildrenTy = SmallVector; + + ChildrenGetterTy() = default; + ChildrenGetterTy(const GraphDiff *GD) : GD(GD) { + assert(GD); } - /// Calculate iterated dominance frontiers - /// - /// This uses the linear-time phi algorithm based on DJ-graphs mentioned in - /// the file-level comment. It performs DF->IDF pruning using the live-in - /// set, to avoid computing the IDF for blocks where an inserted PHI node - /// would be dead. - void calculate(SmallVectorImpl &IDFBlocks); - -private: - DominatorTreeBase &DT; - const GraphDiff *GD; - bool useLiveIn; - const SmallPtrSetImpl *LiveInBlocks; - const SmallPtrSetImpl *DefBlocks; + ChildrenTy get(const NodeRef &N); + + const GraphDiff *GD = nullptr; +}; + +} // end of namespace IDFCalculatorDetail + +template +class IDFCalculator final : public IDFCalculatorBase { +public: + using IDFCalculatorBase = + typename llvm::IDFCalculatorBase; + using ChildrenGetterTy = typename IDFCalculatorBase::ChildrenGetterTy; + + IDFCalculator(DominatorTreeBase &DT) + : IDFCalculatorBase(DT) {} + + IDFCalculator(DominatorTreeBase &DT, + const GraphDiff *GD) + : IDFCalculatorBase(DT, ChildrenGetterTy(GD)) { + assert(GD); + } }; -typedef IDFCalculator ForwardIDFCalculator; -typedef IDFCalculator, true> ReverseIDFCalculator; + +using ForwardIDFCalculator = IDFCalculator; +using ReverseIDFCalculator = IDFCalculator; + +//===----------------------------------------------------------------------===// +// Implementation. +//===----------------------------------------------------------------------===// + +namespace IDFCalculatorDetail { + +template +using BBChildrenGetterTy = ChildrenGetterTy; + +template +typename BBChildrenGetterTy::ChildrenTy +BBChildrenGetterTy::get( + const BBChildrenGetterTy::NodeRef &N) { + + using OrderedNodeTy = + typename IDFCalculatorBase::OrderedNodeTy; + + if (!GD) { + auto Children = children(N); + return {Children.begin(), Children.end()}; + } + + using SnapShotBBPairTy = + std::pair *, OrderedNodeTy>; + + ChildrenTy Ret; + for (const auto &SnapShotBBPair : children({GD, N})) + Ret.emplace_back(SnapShotBBPair.second); + return Ret; } + +} // end of namespace IDFCalculatorDetail + +} // end of namespace llvm + #endif diff --git a/llvm/include/llvm/Support/GenericIteratedDominanceFrontier.h b/llvm/include/llvm/Support/GenericIteratedDominanceFrontier.h new file mode 100644 index 0000000000000..975ebafa0635d --- /dev/null +++ b/llvm/include/llvm/Support/GenericIteratedDominanceFrontier.h @@ -0,0 +1,210 @@ +//===- IteratedDominanceFrontier.h - Calculate IDF --------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// \file +/// Compute iterated dominance frontiers using a linear time algorithm. +/// +/// The algorithm used here is based on: +/// +/// Sreedhar and Gao. A linear time algorithm for placing phi-nodes. +/// In Proceedings of the 22nd ACM SIGPLAN-SIGACT Symposium on Principles of +/// Programming Languages +/// POPL '95. ACM, New York, NY, 62-73. +/// +/// It has been modified to not explicitly use the DJ graph data structure and +/// to directly compute pruned SSA using per-variable liveness information. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_SUPPORT_GENERIC_IDF_H +#define LLVM_SUPPORT_GENERIC_IDF_H + +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/SmallPtrSet.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Support/GenericDomTree.h" +#include + +namespace llvm { + +namespace IDFCalculatorDetail { + +/// Generic utility class used for getting the children of a basic block. +/// May be specialized if, for example, one wouldn't like to return nullpointer +/// successors. +template struct ChildrenGetterTy { + using NodeRef = typename GraphTraits::NodeRef; + using ChildrenTy = SmallVector; + + ChildrenTy get(const NodeRef &N); +}; + +} // end of namespace IDFCalculatorDetail + +/// Determine the iterated dominance frontier, given a set of defining +/// blocks, and optionally, a set of live-in blocks. +/// +/// In turn, the results can be used to place phi nodes. +/// +/// This algorithm is a linear time computation of Iterated Dominance Frontiers, +/// pruned using the live-in set. +/// By default, liveness is not used to prune the IDF computation. +/// The template parameters should be of a CFG block type. +template class IDFCalculatorBase { +public: + using OrderedNodeTy = + typename std::conditional, NodeTy *>::type; + using ChildrenGetterTy = + IDFCalculatorDetail::ChildrenGetterTy; + + IDFCalculatorBase(DominatorTreeBase &DT) : DT(DT) {} + + IDFCalculatorBase(DominatorTreeBase &DT, + const ChildrenGetterTy &C) + : DT(DT), ChildrenGetter(C) {} + + /// Give the IDF calculator the set of blocks in which the value is + /// defined. This is equivalent to the set of starting blocks it should be + /// calculating the IDF for (though later gets pruned based on liveness). + /// + /// Note: This set *must* live for the entire lifetime of the IDF calculator. + void setDefiningBlocks(const SmallPtrSetImpl &Blocks) { + DefBlocks = &Blocks; + } + + /// Give the IDF calculator the set of blocks in which the value is + /// live on entry to the block. This is used to prune the IDF calculation to + /// not include blocks where any phi insertion would be dead. + /// + /// Note: This set *must* live for the entire lifetime of the IDF calculator. + void setLiveInBlocks(const SmallPtrSetImpl &Blocks) { + LiveInBlocks = &Blocks; + useLiveIn = true; + } + + /// Reset the live-in block set to be empty, and tell the IDF + /// calculator to not use liveness anymore. + void resetLiveInBlocks() { + LiveInBlocks = nullptr; + useLiveIn = false; + } + + /// Calculate iterated dominance frontiers + /// + /// This uses the linear-time phi algorithm based on DJ-graphs mentioned in + /// the file-level comment. It performs DF->IDF pruning using the live-in + /// set, to avoid computing the IDF for blocks where an inserted PHI node + /// would be dead. + void calculate(SmallVectorImpl &IDFBlocks); + +private: + DominatorTreeBase &DT; + ChildrenGetterTy ChildrenGetter; + bool useLiveIn = false; + const SmallPtrSetImpl *LiveInBlocks; + const SmallPtrSetImpl *DefBlocks; +}; + +//===----------------------------------------------------------------------===// +// Implementation. +//===----------------------------------------------------------------------===// + +namespace IDFCalculatorDetail { + +template +typename ChildrenGetterTy::ChildrenTy +ChildrenGetterTy::get( + const ChildrenGetterTy::NodeRef &N) { + using OrderedNodeTy = + typename IDFCalculatorBase::OrderedNodeTy; + + auto Children = children(N); + return {Children.begin(), Children.end()}; +}; + +} // end of namespace IDFCalculatorDetail + +template +void IDFCalculatorBase::calculate( + SmallVectorImpl &PHIBlocks) { + // Use a priority queue keyed on dominator tree level so that inserted nodes + // are handled from the bottom of the dominator tree upwards. We also augment + // the level with a DFS number to ensure that the blocks are ordered in a + // deterministic way. + using DomTreeNodePair = + std::pair *, std::pair>; + using IDFPriorityQueue = + std::priority_queue, + less_second>; + + IDFPriorityQueue PQ; + + DT.updateDFSNumbers(); + + for (NodeTy *BB : *DefBlocks) { + if (DomTreeNodeBase *Node = DT.getNode(BB)) + PQ.push({Node, std::make_pair(Node->getLevel(), Node->getDFSNumIn())}); + } + + SmallVector *, 32> Worklist; + SmallPtrSet *, 32> VisitedPQ; + SmallPtrSet *, 32> VisitedWorklist; + + while (!PQ.empty()) { + DomTreeNodePair RootPair = PQ.top(); + PQ.pop(); + DomTreeNodeBase *Root = RootPair.first; + unsigned RootLevel = RootPair.second.first; + + // Walk all dominator tree children of Root, inspecting their CFG edges with + // targets elsewhere on the dominator tree. Only targets whose level is at + // most Root's level are added to the iterated dominance frontier of the + // definition set. + + Worklist.clear(); + Worklist.push_back(Root); + VisitedWorklist.insert(Root); + + while (!Worklist.empty()) { + DomTreeNodeBase *Node = Worklist.pop_back_val(); + NodeTy *BB = Node->getBlock(); + // Succ is the successor in the direction we are calculating IDF, so it is + // successor for IDF, and predecessor for Reverse IDF. + auto DoWork = [&](NodeTy *Succ) { + DomTreeNodeBase *SuccNode = DT.getNode(Succ); + + const unsigned SuccLevel = SuccNode->getLevel(); + if (SuccLevel > RootLevel) + return; + + if (!VisitedPQ.insert(SuccNode).second) + return; + + NodeTy *SuccBB = SuccNode->getBlock(); + if (useLiveIn && !LiveInBlocks->count(SuccBB)) + return; + + PHIBlocks.emplace_back(SuccBB); + if (!DefBlocks->count(SuccBB)) + PQ.push(std::make_pair( + SuccNode, std::make_pair(SuccLevel, SuccNode->getDFSNumIn()))); + }; + + for (auto Succ : ChildrenGetter.get(BB)) + DoWork(Succ); + + for (auto DomChild : *Node) { + if (VisitedWorklist.insert(DomChild).second) + Worklist.push_back(DomChild); + } + } + } +} + +} // end of namespace llvm + +#endif diff --git a/llvm/lib/Analysis/CMakeLists.txt b/llvm/lib/Analysis/CMakeLists.txt index 3cc9fe3c17155..dd5d6251414bf 100644 --- a/llvm/lib/Analysis/CMakeLists.txt +++ b/llvm/lib/Analysis/CMakeLists.txt @@ -41,7 +41,6 @@ add_llvm_library(LLVMAnalysis InstructionSimplify.cpp Interval.cpp IntervalPartition.cpp - IteratedDominanceFrontier.cpp LazyBranchProbabilityInfo.cpp LazyBlockFrequencyInfo.cpp LazyCallGraph.cpp diff --git a/llvm/lib/Analysis/IteratedDominanceFrontier.cpp b/llvm/lib/Analysis/IteratedDominanceFrontier.cpp deleted file mode 100644 index 89257121c6ad7..0000000000000 --- a/llvm/lib/Analysis/IteratedDominanceFrontier.cpp +++ /dev/null @@ -1,104 +0,0 @@ -//===- IteratedDominanceFrontier.cpp - Compute IDF ------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// Compute iterated dominance frontiers using a linear time algorithm. -// -//===----------------------------------------------------------------------===// - -#include "llvm/Analysis/IteratedDominanceFrontier.h" -#include "llvm/IR/CFG.h" -#include "llvm/IR/Dominators.h" -#include - -namespace llvm { - -template -void IDFCalculator::calculate( - SmallVectorImpl &PHIBlocks) { - // Use a priority queue keyed on dominator tree level so that inserted nodes - // are handled from the bottom of the dominator tree upwards. We also augment - // the level with a DFS number to ensure that the blocks are ordered in a - // deterministic way. - typedef std::pair> - DomTreeNodePair; - typedef std::priority_queue, - less_second> IDFPriorityQueue; - IDFPriorityQueue PQ; - - DT.updateDFSNumbers(); - - for (BasicBlock *BB : *DefBlocks) { - if (DomTreeNode *Node = DT.getNode(BB)) - PQ.push({Node, std::make_pair(Node->getLevel(), Node->getDFSNumIn())}); - } - - SmallVector Worklist; - SmallPtrSet VisitedPQ; - SmallPtrSet VisitedWorklist; - - while (!PQ.empty()) { - DomTreeNodePair RootPair = PQ.top(); - PQ.pop(); - DomTreeNode *Root = RootPair.first; - unsigned RootLevel = RootPair.second.first; - - // Walk all dominator tree children of Root, inspecting their CFG edges with - // targets elsewhere on the dominator tree. Only targets whose level is at - // most Root's level are added to the iterated dominance frontier of the - // definition set. - - Worklist.clear(); - Worklist.push_back(Root); - VisitedWorklist.insert(Root); - - while (!Worklist.empty()) { - DomTreeNode *Node = Worklist.pop_back_val(); - BasicBlock *BB = Node->getBlock(); - // Succ is the successor in the direction we are calculating IDF, so it is - // successor for IDF, and predecessor for Reverse IDF. - auto DoWork = [&](BasicBlock *Succ) { - DomTreeNode *SuccNode = DT.getNode(Succ); - - const unsigned SuccLevel = SuccNode->getLevel(); - if (SuccLevel > RootLevel) - return; - - if (!VisitedPQ.insert(SuccNode).second) - return; - - BasicBlock *SuccBB = SuccNode->getBlock(); - if (useLiveIn && !LiveInBlocks->count(SuccBB)) - return; - - PHIBlocks.emplace_back(SuccBB); - if (!DefBlocks->count(SuccBB)) - PQ.push(std::make_pair( - SuccNode, std::make_pair(SuccLevel, SuccNode->getDFSNumIn()))); - }; - - if (GD) { - for (auto Pair : children< - std::pair *, NodeTy>>( - {GD, BB})) - DoWork(Pair.second); - } else { - for (auto *Succ : children(BB)) - DoWork(Succ); - } - - for (auto DomChild : *Node) { - if (VisitedWorklist.insert(DomChild).second) - Worklist.push_back(DomChild); - } - } - } -} - -template class IDFCalculator; -template class IDFCalculator, true>; -} From 8a0a8b3c58bc666c8c133f3c64e55014ebbdefc1 Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Tue, 2 Jul 2019 13:25:41 +0000 Subject: [PATCH 032/181] Removed extra ; after function definition llvm-svn: 364923 (cherry picked from commit 32d467041ee642c2fc5cf57317511d3da385c100) --- llvm/include/llvm/Support/GenericIteratedDominanceFrontier.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llvm/include/llvm/Support/GenericIteratedDominanceFrontier.h b/llvm/include/llvm/Support/GenericIteratedDominanceFrontier.h index 975ebafa0635d..fcd21339ef1f5 100644 --- a/llvm/include/llvm/Support/GenericIteratedDominanceFrontier.h +++ b/llvm/include/llvm/Support/GenericIteratedDominanceFrontier.h @@ -124,7 +124,7 @@ ChildrenGetterTy::get( auto Children = children(N); return {Children.begin(), Children.end()}; -}; +} } // end of namespace IDFCalculatorDetail From e2711bbe1c60040f55b455798ef10170d85f916c Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Tue, 2 Jul 2019 12:40:29 +0000 Subject: [PATCH 033/181] Attempt to fix buildbot failures with MSVC llvm-svn: 364914 (cherry picked from commit 6c0dcf65e7105611d4fe46e2a10629ce3c9abe5c) --- llvm/include/llvm/Analysis/IteratedDominanceFrontier.h | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/llvm/include/llvm/Analysis/IteratedDominanceFrontier.h b/llvm/include/llvm/Analysis/IteratedDominanceFrontier.h index 90e096f8c7173..1fa1db537860f 100644 --- a/llvm/include/llvm/Analysis/IteratedDominanceFrontier.h +++ b/llvm/include/llvm/Analysis/IteratedDominanceFrontier.h @@ -62,12 +62,9 @@ using ReverseIDFCalculator = IDFCalculator; namespace IDFCalculatorDetail { template -using BBChildrenGetterTy = ChildrenGetterTy; - -template -typename BBChildrenGetterTy::ChildrenTy -BBChildrenGetterTy::get( - const BBChildrenGetterTy::NodeRef &N) { +typename ChildrenGetterTy::ChildrenTy +ChildrenGetterTy::get( + const ChildrenGetterTy::NodeRef &N) { using OrderedNodeTy = typename IDFCalculatorBase::OrderedNodeTy; From 76b8d1b1d48d2749ff69995c12548d7c76bb01f5 Mon Sep 17 00:00:00 2001 From: Artem Belevich Date: Fri, 12 Jul 2019 16:13:29 +0000 Subject: [PATCH 034/181] Minor cleanup. Simplify things a bit by removing unnecessary full type qualification. This also happens to avoid a build break with now-unsupported Visual Studio 2015. Differential Review: https://reviews.llvm.org/D64588 llvm-svn: 365913 (cherry picked from commit d9963b627ad281ac74321d92b7089266e30e007b) --- llvm/include/llvm/Analysis/IteratedDominanceFrontier.h | 3 +-- llvm/include/llvm/Support/GenericIteratedDominanceFrontier.h | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/llvm/include/llvm/Analysis/IteratedDominanceFrontier.h b/llvm/include/llvm/Analysis/IteratedDominanceFrontier.h index 1fa1db537860f..7c826780c3185 100644 --- a/llvm/include/llvm/Analysis/IteratedDominanceFrontier.h +++ b/llvm/include/llvm/Analysis/IteratedDominanceFrontier.h @@ -63,8 +63,7 @@ namespace IDFCalculatorDetail { template typename ChildrenGetterTy::ChildrenTy -ChildrenGetterTy::get( - const ChildrenGetterTy::NodeRef &N) { +ChildrenGetterTy::get(const NodeRef &N) { using OrderedNodeTy = typename IDFCalculatorBase::OrderedNodeTy; diff --git a/llvm/include/llvm/Support/GenericIteratedDominanceFrontier.h b/llvm/include/llvm/Support/GenericIteratedDominanceFrontier.h index fcd21339ef1f5..25eb7cd7b6d57 100644 --- a/llvm/include/llvm/Support/GenericIteratedDominanceFrontier.h +++ b/llvm/include/llvm/Support/GenericIteratedDominanceFrontier.h @@ -117,8 +117,7 @@ namespace IDFCalculatorDetail { template typename ChildrenGetterTy::ChildrenTy -ChildrenGetterTy::get( - const ChildrenGetterTy::NodeRef &N) { +ChildrenGetterTy::get(const NodeRef &N) { using OrderedNodeTy = typename IDFCalculatorBase::OrderedNodeTy; From 56e5cd9f4fc4edf44bbd5b16271418a74a72affa Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Wed, 3 Jul 2019 01:26:32 +0000 Subject: [PATCH 035/181] [analyzer] exploded-graph-rewriter: Implement checker messages. They are displayed as raw lines and diffed via difflib on a per-checker basis. Differential Revision: https://reviews.llvm.org/D64100 llvm-svn: 364989 (cherry picked from commit deb7accbea782c9ef8eb22269c813c042d9afcc3) --- .../checker_messages.dot | 30 +++++ .../checker_messages_diff.dot | 93 ++++++++++++++ .../exploded-graph-rewriter/constraints.dot | 1 + .../constraints_diff.dot | 5 +- .../exploded-graph-rewriter/environment.dot | 1 + .../environment_diff.dot | 3 + .../exploded-graph-rewriter/store.dot | 1 + .../exploded-graph-rewriter/store_diff.dot | 2 + .../utils/analyzer/exploded-graph-rewriter.py | 113 ++++++++++++++++-- 9 files changed, 239 insertions(+), 10 deletions(-) create mode 100644 clang/test/Analysis/exploded-graph-rewriter/checker_messages.dot create mode 100644 clang/test/Analysis/exploded-graph-rewriter/checker_messages_diff.dot diff --git a/clang/test/Analysis/exploded-graph-rewriter/checker_messages.dot b/clang/test/Analysis/exploded-graph-rewriter/checker_messages.dot new file mode 100644 index 0000000000000..e7a7d7131d978 --- /dev/null +++ b/clang/test/Analysis/exploded-graph-rewriter/checker_messages.dot @@ -0,0 +1,30 @@ +// RUN: %exploded_graph_rewriter %s | FileCheck %s + +// FIXME: Substitution doesn't seem to work on Windows. +// UNSUPPORTED: system-windows + +// CHECK: Checker State: +// CHECK-SAME: alpha.core.FooChecker: +// CHECK-SAME: Foo stuff: +// CHECK-SAME: Foo: Bar +Node0x1 [shape=record,label= + "{ + { "node_id": 1, + "pointer": "0x1", + "state_id": 2, + "program_points": [], + "program_state": { + "store": null, + "constraints": null, + "dynamic_types": null, + "constructing_objects": null, + "environment": null, + "checker_messages": [ + { "checker": "alpha.core.FooChecker", "messages": [ + "Foo stuff:", + "Foo: Bar" + ]} + ] + } + } +\l}"]; diff --git a/clang/test/Analysis/exploded-graph-rewriter/checker_messages_diff.dot b/clang/test/Analysis/exploded-graph-rewriter/checker_messages_diff.dot new file mode 100644 index 0000000000000..57cbb5e83ad2f --- /dev/null +++ b/clang/test/Analysis/exploded-graph-rewriter/checker_messages_diff.dot @@ -0,0 +1,93 @@ +// RUN: %exploded_graph_rewriter -d %s | FileCheck %s + +// FIXME: Substitution doesn't seem to work on Windows. +// UNSUPPORTED: system-windows + +Node0x1 [shape=record,label= + "{ + { "node_id": 1, + "pointer": "0x1", + "state_id": 2, + "program_points": [], + "program_state": { + "environment": null, + "store": null, + "constraints": null, + "dynamic_types": null, + "constructing_objects": null, + "checker_messages": [ + { "checker": "FooChecker", "messages": [ + "Foo: Bar" + ]}, + { "checker": "BarChecker", "messages": [ + "Bar: Foo" + ]} + ] + } + } +\l}"]; + +Node0x1 -> Node0x4; + + +// CHECK: Node0x4 [ +// CHECK-SAME: +// CHECK-SAME: - +// CHECK-SAME: BarChecker: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: - +// CHECK-SAME: Bar: Foo +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: FooChecker: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: + +// CHECK-SAME: Bar: Foo +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: + +// CHECK-SAME: DunnoWhateverSomeOtherChecker: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: + +// CHECK-SAME: Dunno, some other message. +// CHECK-SAME: +Node0x4 [shape=record,label= + "{ + { "node_id": 4, + "pointer": "0x4", + "state_id": 5, + "program_points": [], + "program_state": { + "environment": null, + "store": null, + "constraints": null, + "dynamic_types": null, + "constructing_objects": null, + "checker_messages": [ + { "checker": "FooChecker", "messages": [ + "Foo: Bar", + "Bar: Foo" + ]}, + { "checker": "DunnoWhateverSomeOtherChecker", "messages": [ + "Dunno, some other message." + ]} + ] + } + } +\l}"]; + +Node0x4 -> Node0x6; + +Node0x6 [shape=record,label= + "{ + { "node_id": 6, + "pointer": "0x6", + "state_id": 7, + "program_points": [], + "program_state": null + } +\l}"]; diff --git a/clang/test/Analysis/exploded-graph-rewriter/constraints.dot b/clang/test/Analysis/exploded-graph-rewriter/constraints.dot index 58faafc0f2607..10e72d67df7ca 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/constraints.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/constraints.dot @@ -21,6 +21,7 @@ Node0x1 [shape=record,label= "environment": null, "dynamic_types": null, "constructing_objects": null, + "checker_messages": null, "constraints": [ { "symbol": "reg_$0", "range": "{ [0, 0] }" } ] diff --git a/clang/test/Analysis/exploded-graph-rewriter/constraints_diff.dot b/clang/test/Analysis/exploded-graph-rewriter/constraints_diff.dot index 24aa9b41a7aee..cd7bc62299dd6 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/constraints_diff.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/constraints_diff.dot @@ -14,6 +14,7 @@ Node0x1 [shape=record,label= "environment": null, "dynamic_types": null, "constructing_objects": null, + "checker_messages": null, "constraints": [ { "symbol": "reg_$0", "range": "{ [0, 10] }" } ] @@ -45,6 +46,7 @@ Node0x3 [shape=record,label= "environment": null, "dynamic_types": null, "constructing_objects": null, + "checker_messages": null, "constraints": [ { "symbol": "reg_$0", "range": "{ [0, 5] }" } ] @@ -65,7 +67,8 @@ Node0x5 [shape=record,label= "environment": null, "constraints": null, "dynamic_types": null, - "constructing_objects": null + "constructing_objects": null, + "checker_messages": null } } \l}"]; diff --git a/clang/test/Analysis/exploded-graph-rewriter/environment.dot b/clang/test/Analysis/exploded-graph-rewriter/environment.dot index 8c451671b4c01..4167a8c4cb76c 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/environment.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/environment.dot @@ -36,6 +36,7 @@ Node0x1 [shape=record,label= "constraints": null, "dynamic_types": null, "constructing_objects": null, + "checker_messages": null, "environment": { "pointer": "0x2", "items": [ diff --git a/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot b/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot index 5264b40f802f7..a624910330abf 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot @@ -15,6 +15,7 @@ Node0x1 [shape=record,label= "constraints": null, "dynamic_types": null, "constructing_objects": null, + "checker_messages": null, "environment": { "pointer": "0x2", "items": [ @@ -63,6 +64,7 @@ Node0x6 [shape=record,label= "constraints": null, "dynamic_types": null, "constructing_objects": null, + "checker_messages": null, "environment": { "pointer": "0x2", "items": [ @@ -105,6 +107,7 @@ Node0x9 [shape=record,label= "constraints": null, "dynamic_types": null, "constructing_objects": null, + "checker_messages": null, "environment": { "pointer": "0x2", "items": [ diff --git a/clang/test/Analysis/exploded-graph-rewriter/store.dot b/clang/test/Analysis/exploded-graph-rewriter/store.dot index 1c306ce160f25..8331d099c050a 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/store.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/store.dot @@ -31,6 +31,7 @@ Node0x1 [shape=record,label= "constraints": null, "dynamic_types": null, "constructing_objects": null, + "checker_messages": null, "store": { "pointer": "0x2", "items": [ diff --git a/clang/test/Analysis/exploded-graph-rewriter/store_diff.dot b/clang/test/Analysis/exploded-graph-rewriter/store_diff.dot index ddd26ce5dcd62..f8dfe51178358 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/store_diff.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/store_diff.dot @@ -14,6 +14,7 @@ Node0x1 [shape=record,label= "constraints": null, "dynamic_types": null, "constructing_objects": null, + "checker_messages": null, "store": { "pointer": "0x2", "items": [ @@ -61,6 +62,7 @@ Node0x4 [shape=record,label= "constraints": null, "dynamic_types": null, "constructing_objects": null, + "checker_messages": null, "store": { "pointer": "0x5", "items": [ diff --git a/clang/utils/analyzer/exploded-graph-rewriter.py b/clang/utils/analyzer/exploded-graph-rewriter.py index fffe2f53ee429..5b4fa1a501a16 100755 --- a/clang/utils/analyzer/exploded-graph-rewriter.py +++ b/clang/utils/analyzer/exploded-graph-rewriter.py @@ -13,6 +13,7 @@ import argparse import collections +import difflib import json import logging import re @@ -211,6 +212,41 @@ def is_different(self, prev): return len(removed) != 0 or len(added) != 0 or len(updated) != 0 +# Deserialized messages from a single checker in a single program state. +# Basically a list of raw strings. +class CheckerLines(object): + def __init__(self, json_lines): + super(CheckerLines, self).__init__() + self.lines = json_lines + + def diff_lines(self, prev): + lines = difflib.ndiff(prev.lines, self.lines) + return [l.strip() for l in lines + if l.startswith('+') or l.startswith('-')] + + def is_different(self, prev): + return len(self.diff_lines(prev)) > 0 + + +# Deserialized messages of all checkers, separated by checker. +class CheckerMessages(object): + def __init__(self, json_m): + super(CheckerMessages, self).__init__() + self.items = collections.OrderedDict( + [(m['checker'], CheckerLines(m['messages'])) for m in json_m]) + + def diff_messages(self, prev): + removed = [k for k in prev.items if k not in self.items] + added = [k for k in self.items if k not in prev.items] + updated = [k for k in prev.items if k in self.items + and prev.items[k].is_different(self.items[k])] + return (removed, added, updated) + + def is_different(self, prev): + removed, added, updated = self.diff_messages(prev) + return len(removed) != 0 or len(added) != 0 or len(updated) != 0 + + # A deserialized program state. class ProgramState(object): def __init__(self, state_id, json_ps): @@ -241,7 +277,8 @@ def __init__(self, state_id, json_ps): GenericEnvironment(json_ps['constructing_objects']) \ if json_ps['constructing_objects'] is not None else None - # TODO: Checker messages. + self.checker_messages = CheckerMessages(json_ps['checker_messages']) \ + if json_ps['checker_messages'] is not None else None # A deserialized exploded graph node. Has a default constructor because it @@ -595,16 +632,73 @@ def visit_generic_map_in_state(self, selector, title, s, prev_s=None): if m is None: self._dump(' Nothing!') else: - if prev_s is not None: - if prev_m is not None: - if m.is_different(prev_m): - self._dump('') - self.visit_generic_map(m, prev_m) - else: - self._dump(' No changes!') - if prev_m is None: + if prev_m is not None: + if m.is_different(prev_m): + self._dump('') + self.visit_generic_map(m, prev_m) + else: + self._dump(' No changes!') + else: self._dump('') self.visit_generic_map(m) + + self._dump('') + + def visit_checker_messages(self, m, prev_m=None): + self._dump('') + + def dump_line(l, is_added=None): + self._dump('' + '' + % (self._diff_plus_minus(is_added), l)) + + def dump_chk(chk, is_added=None): + dump_line('%s:' % chk, is_added) + + if prev_m is not None: + removed, added, updated = m.diff_messages(prev_m) + for chk in removed: + dump_chk(chk, False) + for l in prev_m.items[chk].lines: + dump_line(l, False) + for chk in updated: + dump_chk(chk) + for l in m.items[chk].diff_lines(prev_m.items[chk]): + dump_line(l[1:], l.startswith('+')) + for chk in added: + dump_chk(chk, True) + for l in m.items[chk].lines: + dump_line(l, True) + else: + for chk in m.items: + dump_chk(chk) + for l in m.items[chk].lines: + dump_line(l) + + self._dump('
%s%s
') + + def visit_checker_messages_in_state(self, s, prev_s=None): + m = s.checker_messages + prev_m = prev_s.checker_messages if prev_s is not None else None + if m is None and prev_m is None: + return + + self._dump('
') + self._dump('' + 'Checker State: ') + if m is None: + self._dump(' Nothing!') + else: + if prev_m is not None: + if m.is_different(prev_m): + self._dump('') + self.visit_checker_messages(m, prev_m) + else: + self._dump(' No changes!') + else: + self._dump('') + self.visit_checker_messages(m) + self._dump('') def visit_state(self, s, prev_s): @@ -618,6 +712,7 @@ def visit_state(self, s, prev_s): self.visit_environment_in_state('constructing_objects', 'Objects Under Construction', s, prev_s) + self.visit_checker_messages_in_state(s, prev_s) def visit_node(self, node): self._dump('%s [shape=record,' From 0cb95095505c0f30bf9e36c914dbb5a16e9ebe57 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Wed, 3 Jul 2019 01:26:35 +0000 Subject: [PATCH 036/181] [analyzer] exploded-graph-rewriter: Collapse very long statement pretty-prints. When printing various statements that include braces (compound statements, lambda expressions, statement-expressions, etc.), replace the code between braces with '...'. Differential Revision: https://reviews.llvm.org/D64104 llvm-svn: 364990 (cherry picked from commit 48a5c83af44235130494fce4e7e08d7128475414) --- .../program_points.dot | 24 +++++++++++++++++++ .../utils/analyzer/exploded-graph-rewriter.py | 23 +++++++++++++++--- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/clang/test/Analysis/exploded-graph-rewriter/program_points.dot b/clang/test/Analysis/exploded-graph-rewriter/program_points.dot index 2c03ac92d0f4f..d7f69696fd3a0 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/program_points.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/program_points.dot @@ -90,3 +90,27 @@ Node0x2 [shape=record,label= } ]} \l}"]; + +// Test collapsing large pretty prints with braces. + +// CHECK-NEXT: Program point: +// CHECK-SAME: \{ ... \} +Node0x3 [shape=record,label= + "{ + { "node_id": 3, "pointer": "0x3", + "program_state": null, "program_points": [ + { + "kind": "Statement", + "stmt_kind": "CompoundStmt", + "stmt_point_kind": "PostStmt", + "stmd_id": 6, + "pointer": "0x6", + "pretty": "{ very very very very very very long pretty print }", + "location": { + "line": 7, + "column": 8 + }, + "tag": "ExprEngine : Clean Node" + } + ]} +\l}"]; diff --git a/clang/utils/analyzer/exploded-graph-rewriter.py b/clang/utils/analyzer/exploded-graph-rewriter.py index 5b4fa1a501a16..cb8af928d8e3d 100755 --- a/clang/utils/analyzer/exploded-graph-rewriter.py +++ b/clang/utils/analyzer/exploded-graph-rewriter.py @@ -398,6 +398,21 @@ def _diff_plus_minus(is_added): return '+' return '-' + @staticmethod + def _short_pretty(s): + if s is None: + return None + if len(s) < 20: + return s + left = s.find('{') + right = s.rfind('}') + if left == -1 or right == -1 or left >= right: + return s + candidate = s[0:left + 1] + ' ... ' + s[right:] + if len(candidate) >= len(s): + return s + return candidate + def visit_begin_graph(self, graph): self._graph = graph self._dump_raw('digraph "ExplodedGraph" {\n') @@ -433,7 +448,8 @@ def visit_program_point(self, p): % (p.loc.filename, p.loc.line, p.loc.col, color, p.stmt_kind, stmt_color, p.stmt_point_kind, - p.pretty if not skip_pretty else '')) + self._short_pretty(p.pretty) + if not skip_pretty else '')) else: self._dump('' 'Invalid Source Location:' @@ -443,7 +459,8 @@ def visit_program_point(self, p): '%s' % (color, p.stmt_kind, stmt_color, p.stmt_point_kind, - p.pretty if not skip_pretty else '')) + self._short_pretty(p.pretty) + if not skip_pretty else '')) elif p.kind == 'Edge': self._dump('' '' @@ -496,7 +513,7 @@ def dump_binding(f, b, is_added=None): 'lavender' if self._dark_mode else 'darkgreen', ('(%s)' % b.kind) if b.kind is not None else ' ' ), - b.pretty, f.bindings[b])) + self._short_pretty(b.pretty), f.bindings[b])) frames_updated = e.diff_frames(prev_e) if prev_e is not None else None if frames_updated: From 577a874874014b4156b163cea7ebda36bd556ac9 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Wed, 3 Jul 2019 01:26:38 +0000 Subject: [PATCH 037/181] [analyzer] exploded-graph-rewriter: NFC: Add more comments. llvm-svn: 364991 (cherry picked from commit 5fcf92e153806e8fb3f195104f3d057580236132) --- .../utils/analyzer/exploded-graph-rewriter.py | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/clang/utils/analyzer/exploded-graph-rewriter.py b/clang/utils/analyzer/exploded-graph-rewriter.py index cb8af928d8e3d..463ae02fe8611 100755 --- a/clang/utils/analyzer/exploded-graph-rewriter.py +++ b/clang/utils/analyzer/exploded-graph-rewriter.py @@ -19,6 +19,11 @@ import re +#===-----------------------------------------------------------------------===# +# These data structures represent a deserialized ExplodedGraph. +#===-----------------------------------------------------------------------===# + + # A helper function for finding the difference between two dictionaries. def diff_dicts(curr, prev): removed = [k for k in prev if k not in curr or curr[k] != prev[k]] @@ -368,6 +373,12 @@ def add_raw_line(self, raw_line): logging.debug('Skipping.') +#===-----------------------------------------------------------------------===# +# Visitors traverse a deserialized ExplodedGraph and do different things +# with every node and edge. +#===-----------------------------------------------------------------------===# + + # A visitor that dumps the ExplodedGraph into a DOT file with fancy HTML-based # syntax highlighing. class DotDumpVisitor(object): @@ -775,11 +786,17 @@ def visit_end_of_graph(self): self._dump_raw('}\n') +#===-----------------------------------------------------------------------===# +# Explorers know how to traverse the ExplodedGraph in a certain order. +# They would invoke a Visitor on every node or edge they encounter. +#===-----------------------------------------------------------------------===# + + # A class that encapsulates traversal of the ExplodedGraph. Different explorer # kinds could potentially traverse specific sub-graphs. -class Explorer(object): +class BasicExplorer(object): def __init__(self): - super(Explorer, self).__init__() + super(BasicExplorer, self).__init__() def explore(self, graph, visitor): visitor.visit_begin_graph(graph) @@ -792,6 +809,11 @@ def explore(self, graph, visitor): visitor.visit_end_of_graph() +#===-----------------------------------------------------------------------===# +# The entry point to the script. +#===-----------------------------------------------------------------------===# + + def main(): parser = argparse.ArgumentParser() parser.add_argument('filename', type=str) @@ -814,7 +836,7 @@ def main(): raw_line = raw_line.strip() graph.add_raw_line(raw_line) - explorer = Explorer() + explorer = BasicExplorer() visitor = DotDumpVisitor(args.diff, args.dark) explorer.explore(graph, visitor) From f7beaf3a2acb256bb9a0aace9003804090dc78c5 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Wed, 3 Jul 2019 01:26:41 +0000 Subject: [PATCH 038/181] [analyzer] exploded-graph-rewriter: Implement bug nodes and sink nodes. Add a label to nodes that have a bug report attached or on which the analysis was generally interrupted. Fix printing has_report and implement printing is_sink in the graph dumper. Differential Revision: https://reviews.llvm.org/D64110 llvm-svn: 364992 (cherry picked from commit ab758ba128c46ba30cad058b89991852f7be5543) --- clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 27 +++++++------------ clang/test/Analysis/dump_egraph.c | 2 +- .../checker_messages.dot | 2 ++ .../checker_messages_diff.dot | 6 +++++ .../exploded-graph-rewriter/constraints.dot | 2 ++ .../constraints_diff.dot | 6 +++++ .../Analysis/exploded-graph-rewriter/edge.dot | 4 +-- .../exploded-graph-rewriter/environment.dot | 2 ++ .../environment_diff.dot | 6 +++++ .../exploded-graph-rewriter/node_labels.dot | 17 +++++++++++- .../program_points.dot | 6 ++--- .../exploded-graph-rewriter/store.dot | 2 ++ .../exploded-graph-rewriter/store_diff.dot | 6 +++++ .../utils/analyzer/exploded-graph-rewriter.py | 8 ++++++ 14 files changed, 72 insertions(+), 24 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp index 628d4315bcbdd..72bcd33438c44 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -3005,7 +3005,8 @@ struct DOTGraphTraits : public DefaultDOTGraphTraits { for (const auto &EQ : EQClasses) { for (const BugReport &Report : EQ) { - if (Report.getErrorNode()->getState() == N->getState()) + if (Report.getErrorNode()->getState() == N->getState() && + Report.getErrorNode()->getLocation() == N->getLocation()) return true; } } @@ -3041,21 +3042,6 @@ struct DOTGraphTraits : public DefaultDOTGraphTraits { return false; } - static std::string getNodeAttributes(const ExplodedNode *N, - ExplodedGraph *) { - SmallVector Out; - auto Noop = [](const ExplodedNode*){}; - if (traverseHiddenNodes(N, Noop, Noop, &nodeHasBugReport)) { - Out.push_back("style=filled"); - Out.push_back("fillcolor=red"); - } - - if (traverseHiddenNodes(N, Noop, Noop, - [](const ExplodedNode *C) { return C->isSink(); })) - Out.push_back("color=blue"); - return llvm::join(Out, ","); - } - static bool isNodeHidden(const ExplodedNode *N) { return N->isTrivial(); } @@ -3068,9 +3054,16 @@ struct DOTGraphTraits : public DefaultDOTGraphTraits { const unsigned int Space = 1; ProgramStateRef State = N->getState(); + auto Noop = [](const ExplodedNode*){}; + bool HasReport = traverseHiddenNodes( + N, Noop, Noop, &nodeHasBugReport); + bool IsSink = traverseHiddenNodes( + N, Noop, Noop, [](const ExplodedNode *N) { return N->isSink(); }); + Out << "{ \"node_id\": " << N->getID(G) << ", \"pointer\": \"" << (const void *)N << "\", \"state_id\": " << State->getID() - << ", \"has_report\": " << (nodeHasBugReport(N) ? "true" : "false") + << ", \"has_report\": " << (HasReport ? "true" : "false") + << ", \"is_sink\": " << (IsSink ? "true" : "false") << ",\\l"; Indent(Out, Space, IsDot) << "\"program_points\": [\\l"; diff --git a/clang/test/Analysis/dump_egraph.c b/clang/test/Analysis/dump_egraph.c index 5544d9fae2e26..4b7352ff1840e 100644 --- a/clang/test/Analysis/dump_egraph.c +++ b/clang/test/Analysis/dump_egraph.c @@ -20,7 +20,7 @@ int foo() { // CHECK: \"program_points\": [\l    \{ \"kind\": \"BlockEntrance\", \"block_id\": 1 -// CHECK: \"has_report\": true // CHECK: \"pretty\": \"*x\", \"location\": \{ \"line\": 16, \"column\": 10, \"file\": \"{{(.+)}}dump_egraph.c\" \} +// CHECK: \"has_report\": true diff --git a/clang/test/Analysis/exploded-graph-rewriter/checker_messages.dot b/clang/test/Analysis/exploded-graph-rewriter/checker_messages.dot index e7a7d7131d978..84185db5af617 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/checker_messages.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/checker_messages.dot @@ -11,6 +11,8 @@ Node0x1 [shape=record,label= "{ { "node_id": 1, "pointer": "0x1", + "has_report": false, + "is_sink": false, "state_id": 2, "program_points": [], "program_state": { diff --git a/clang/test/Analysis/exploded-graph-rewriter/checker_messages_diff.dot b/clang/test/Analysis/exploded-graph-rewriter/checker_messages_diff.dot index 57cbb5e83ad2f..2f0bcbd4e5f2f 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/checker_messages_diff.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/checker_messages_diff.dot @@ -7,6 +7,8 @@ Node0x1 [shape=record,label= "{ { "node_id": 1, "pointer": "0x1", + "has_report": false, + "is_sink": false, "state_id": 2, "program_points": [], "program_state": { @@ -59,6 +61,8 @@ Node0x4 [shape=record,label= "{ { "node_id": 4, "pointer": "0x4", + "has_report": false, + "is_sink": false, "state_id": 5, "program_points": [], "program_state": { @@ -86,6 +90,8 @@ Node0x6 [shape=record,label= "{ { "node_id": 6, "pointer": "0x6", + "has_report": false, + "is_sink": false, "state_id": 7, "program_points": [], "program_state": null diff --git a/clang/test/Analysis/exploded-graph-rewriter/constraints.dot b/clang/test/Analysis/exploded-graph-rewriter/constraints.dot index 10e72d67df7ca..075df98ce942e 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/constraints.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/constraints.dot @@ -14,6 +14,8 @@ Node0x1 [shape=record,label= "{ { "node_id": 1, "pointer": "0x1", + "has_report": false, + "is_sink": false, "state_id": 2, "program_points": [], "program_state": { diff --git a/clang/test/Analysis/exploded-graph-rewriter/constraints_diff.dot b/clang/test/Analysis/exploded-graph-rewriter/constraints_diff.dot index cd7bc62299dd6..00b2f1456f313 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/constraints_diff.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/constraints_diff.dot @@ -7,6 +7,8 @@ Node0x1 [shape=record,label= "{ { "node_id": 1, "pointer": "0x1", + "has_report": false, + "is_sink": false, "state_id": 2, "program_points": [], "program_state": { @@ -39,6 +41,8 @@ Node0x3 [shape=record,label= "{ { "node_id": 3, "pointer": "0x3", + "has_report": false, + "is_sink": false, "state_id": 4, "program_points": [], "program_state": { @@ -60,6 +64,8 @@ Node0x5 [shape=record,label= "{ { "node_id": 5, "pointer": "0x5", + "has_report": false, + "is_sink": false, "state_id": 6, "program_points": [], "program_state": { diff --git a/clang/test/Analysis/exploded-graph-rewriter/edge.dot b/clang/test/Analysis/exploded-graph-rewriter/edge.dot index 2d3a4fe7bd7aa..15e55612b8037 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/edge.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/edge.dot @@ -5,7 +5,7 @@ // UNSUPPORTED: system-windows Node0x1 [shape=record,label= - "{{ "node_id": 1, "pointer": "0x1", + "{{ "node_id": 1, "pointer": "0x1", "has_report": false, "is_sink": false, "program_state": null, "program_points": []}\l}"]; // LIGHT: Node0x1 -> Node0x2; @@ -13,5 +13,5 @@ Node0x1 [shape=record,label= Node0x1 -> Node0x2; Node0x2 [shape=record,label= - "{{ "node_id": 2, "pointer": "0x2", + "{{ "node_id": 2, "pointer": "0x2", "has_report": false, "is_sink": false, "program_state": null, "program_points": []}\l}"]; diff --git a/clang/test/Analysis/exploded-graph-rewriter/environment.dot b/clang/test/Analysis/exploded-graph-rewriter/environment.dot index 4167a8c4cb76c..2b8a402cd8eb6 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/environment.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/environment.dot @@ -29,6 +29,8 @@ Node0x1 [shape=record,label= "{ { "node_id": 1, "pointer": "0x1", + "has_report": false, + "is_sink": false, "state_id": 2, "program_points": [], "program_state": { diff --git a/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot b/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot index a624910330abf..c3ba88622f1f0 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot @@ -8,6 +8,8 @@ Node0x1 [shape=record,label= "{ { "node_id": 1, "pointer": "0x1", + "has_report": false, + "is_sink": false, "state_id": 2, "program_points": [], "program_state": { @@ -57,6 +59,8 @@ Node0x6 [shape=record,label= "{ { "node_id": 6, "pointer": "0x6", + "has_report": false, + "is_sink": false, "state_id": 7, "program_points": [], "program_state": { @@ -100,6 +104,8 @@ Node0x9 [shape=record,label= "{ { "node_id": 9, "pointer": "0x9", + "has_report": false, + "is_sink": false, "state_id": 7, "program_points": [], "program_state": { diff --git a/clang/test/Analysis/exploded-graph-rewriter/node_labels.dot b/clang/test/Analysis/exploded-graph-rewriter/node_labels.dot index 60b2b29c06583..db56c0585c1fe 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/node_labels.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/node_labels.dot @@ -15,7 +15,22 @@ // CHECK-SAME: Node0x1 [shape=record,label= "{ - { "node_id": 1, "pointer": "0x1", + { "node_id": 1, "pointer": "0x1", "has_report": false, "is_sink": false, + "program_state": null, + "program_points": [] + } +\l}"]; + +// CHECK: Node0x2 [ +// CHECK-SAME: +// CHECK-SAME: Bug Report Attached +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: Sink Node +// CHECK-SAME: +Node0x2 [shape=record,label= + "{ + { "node_id": 2, "pointer": "0x2", "has_report": true, "is_sink": true, "program_state": null, "program_points": [] } diff --git a/clang/test/Analysis/exploded-graph-rewriter/program_points.dot b/clang/test/Analysis/exploded-graph-rewriter/program_points.dot index d7f69696fd3a0..342a923725ec5 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/program_points.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/program_points.dot @@ -28,7 +28,7 @@ // CHECK-SAME: Node0x1 [shape=record,label= "{ - { "node_id": 1, "pointer": "0x1", + { "node_id": 1, "pointer": "0x1", "has_report": false, "is_sink": false, "program_state": null, "program_points": [ { "kind": "Edge", @@ -73,7 +73,7 @@ Node0x1 [shape=record,label= // CHECK-SAME: Node0x2 [shape=record,label= "{ - { "node_id": 2, "pointer": "0x2", + { "node_id": 2, "pointer": "0x2", "has_report": false, "is_sink": false, "program_state": null, "program_points": [ { "kind": "Statement", @@ -97,7 +97,7 @@ Node0x2 [shape=record,label= // CHECK-SAME: \{ ... \} Node0x3 [shape=record,label= "{ - { "node_id": 3, "pointer": "0x3", + { "node_id": 3, "pointer": "0x3", "has_report": false, "is_sink": false, "program_state": null, "program_points": [ { "kind": "Statement", diff --git a/clang/test/Analysis/exploded-graph-rewriter/store.dot b/clang/test/Analysis/exploded-graph-rewriter/store.dot index 8331d099c050a..d47a02295e493 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/store.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/store.dot @@ -24,6 +24,8 @@ Node0x1 [shape=record,label= "{ { "node_id": 1, "pointer": "0x1", + "has_report": false, + "is_sink": false, "state_id": 2, "program_points": [], "program_state": { diff --git a/clang/test/Analysis/exploded-graph-rewriter/store_diff.dot b/clang/test/Analysis/exploded-graph-rewriter/store_diff.dot index f8dfe51178358..94d1d8d9f1f5b 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/store_diff.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/store_diff.dot @@ -7,6 +7,8 @@ Node0x1 [shape=record,label= "{ { "node_id": 1, "pointer": "0x1", + "has_report": false, + "is_sink": false, "state_id": 2, "program_points": [], "program_state": { @@ -55,6 +57,8 @@ Node0x4 [shape=record,label= "{ { "node_id": 4, "pointer": "0x4", + "has_report": false, + "is_sink": false, "state_id": 5, "program_points": [], "program_state": { @@ -89,6 +93,8 @@ Node0x6 [shape=record,label= "{ { "node_id": 6, "pointer": "0x6", + "has_report": false, + "is_sink": false, "state_id": 7, "program_points": [], "program_state": null diff --git a/clang/utils/analyzer/exploded-graph-rewriter.py b/clang/utils/analyzer/exploded-graph-rewriter.py index 463ae02fe8611..0926ba2511ee7 100755 --- a/clang/utils/analyzer/exploded-graph-rewriter.py +++ b/clang/utils/analyzer/exploded-graph-rewriter.py @@ -299,6 +299,8 @@ def construct(self, node_id, json_node): logging.debug('Adding ' + node_id) self.node_id = json_node['node_id'] self.ptr = json_node['pointer'] + self.has_report = json_node['has_report'] + self.is_sink = json_node['is_sink'] self.points = [ProgramPoint(p) for p in json_node['program_points']] self.state = ProgramState(json_node['state_id'], json_node['program_state']) \ @@ -754,6 +756,12 @@ def visit_node(self, node): % ("gray20" if self._dark_mode else "gray", node.node_id, node.ptr, node.state.state_id if node.state is not None else 'Unspecified')) + if node.has_report: + self._dump('Bug Report Attached' + '') + if node.is_sink: + self._dump('Sink Node' + '') self._dump('') if len(node.points) > 1: self._dump('Program points:') From 1bd5a87d2d290a87a13486afe79e5e5a3e53376a Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Wed, 3 Jul 2019 11:14:42 +0000 Subject: [PATCH 039/181] [Dominators] PR42041: Skip nullpointer successors https://bugs.llvm.org/show_bug.cgi?id=42041 In Clang's CFG, we use nullpointers to represent unreachable nodes, for example, in the included testfile, block B0 is unreachable from block B1, resulting in a nullpointer dereference somewhere in llvm::DominatorTreeBase::recalculate. This patch fixes this issue by specializing llvm::DomTreeBuilder::SemiNCAInfo::ChildrenGetter::Get for clang::CFG to not contain nullpointer successors. Differential Revision: https://reviews.llvm.org/D62507 llvm-svn: 365026 (cherry picked from commit 85211c0835731b37c8a943944b5e6c8165c2d6ab) --- .../clang/Analysis/Analyses/Dominators.h | 44 ++++- clang/test/Analysis/domtest.c | 168 +++++++++++------- clang/test/Analysis/domtest.cpp | 51 ++++++ 3 files changed, 198 insertions(+), 65 deletions(-) create mode 100644 clang/test/Analysis/domtest.cpp diff --git a/clang/include/clang/Analysis/Analyses/Dominators.h b/clang/include/clang/Analysis/Analyses/Dominators.h index 0015b0d7fd708..97db381e437ef 100644 --- a/clang/include/clang/Analysis/Analyses/Dominators.h +++ b/clang/include/clang/Analysis/Analyses/Dominators.h @@ -155,13 +155,51 @@ class DominatorTree : public ManagedAnalysis { } // namespace clang +namespace llvm { + +/// Clang's CFG contains nullpointers for unreachable succesors, e.g. when an +/// if statement's condition is always false, it's 'then' branch is represented +/// with a nullptr. This however will result in a nullpointer derefernece for +/// dominator tree calculation. +/// +/// To circumvent this, let's just crudely specialize the children getters +/// used in LLVM's dominator tree builder. +namespace DomTreeBuilder { + +using ClangCFGDomChildrenGetter = +SemiNCAInfo>::ChildrenGetter; + +template <> +template <> +inline ClangCFGDomChildrenGetter::ResultTy ClangCFGDomChildrenGetter::Get( + clang::CFGBlock *N, std::integral_constant) { + auto RChildren = reverse(children(N)); + ResultTy Ret(RChildren.begin(), RChildren.end()); + Ret.erase(std::remove(Ret.begin(), Ret.end(), nullptr), Ret.end()); + return Ret; +} + +using ClangCFGDomReverseChildrenGetter = +SemiNCAInfo>::ChildrenGetter; + +template <> +template <> +inline ClangCFGDomReverseChildrenGetter::ResultTy +ClangCFGDomReverseChildrenGetter::Get( + clang::CFGBlock *N, std::integral_constant) { + auto IChildren = inverse_children(N); + ResultTy Ret(IChildren.begin(), IChildren.end()); + Ret.erase(std::remove(Ret.begin(), Ret.end(), nullptr), Ret.end()); + return Ret; +} + +} // end of namespace DomTreeBuilder + //===------------------------------------- /// DominatorTree GraphTraits specialization so the DominatorTree can be /// iterable by generic graph iterators. /// -namespace llvm { - -template <> struct GraphTraits< ::clang::DomTreeNode* > { +template <> struct GraphTraits { using NodeRef = ::clang::DomTreeNode *; using ChildIteratorType = ::clang::DomTreeNode::iterator; diff --git a/clang/test/Analysis/domtest.c b/clang/test/Analysis/domtest.c index e957c8d70e02b..cbd75b991db9f 100644 --- a/clang/test/Analysis/domtest.c +++ b/clang/test/Analysis/domtest.c @@ -1,6 +1,6 @@ -// RUN: rm -f %t -// RUN: %clang_analyze_cc1 -analyzer-checker=debug.DumpDominators %s > %t 2>&1 -// RUN: FileCheck --input-file=%t %s +// RUN: %clang_analyze_cc1 %s \ +// RUN: -analyzer-checker=debug.DumpDominators \ +// RUN: 2>&1 | FileCheck %s // Test the DominatorsTree implementation with various control flows int test1() @@ -24,17 +24,24 @@ int test1() return 0; } -// CHECK: Immediate dominance tree (Node#,IDom#): -// CHECK: (0,1) -// CHECK: (1,7) -// CHECK: (2,3) -// CHECK: (3,6) -// CHECK: (4,6) -// CHECK: (5,6) -// CHECK: (6,7) -// CHECK: (7,8) -// CHECK: (8,9) -// CHECK: (9,9) +// [B9 (ENTRY)] -> [B8] -> [B7] -> [B6] -> [B5] -> [B3] -> [B2] +// |\ \ / / +// | \ ---> [B4] ---> / +// | <--------------------------- +// V +// [B1] -> [B0 (EXIT)] + +// CHECK: Immediate dominance tree (Node#,IDom#): +// CHECK-NEXT: (0,1) +// CHECK-NEXT: (1,7) +// CHECK-NEXT: (2,3) +// CHECK-NEXT: (3,6) +// CHECK-NEXT: (4,6) +// CHECK-NEXT: (5,6) +// CHECK-NEXT: (6,7) +// CHECK-NEXT: (7,8) +// CHECK-NEXT: (8,9) +// CHECK-NEXT: (9,9) int test2() { @@ -54,15 +61,22 @@ int test2() return 0; } -// CHECK: Immediate dominance tree (Node#,IDom#): -// CHECK: (0,1) -// CHECK: (1,6) -// CHECK: (2,3) -// CHECK: (3,4) -// CHECK: (4,6) -// CHECK: (5,6) -// CHECK: (6,7) -// CHECK: (7,7) +// <------------- +// / \ +// -----------> [B4] -> [B3] -> [B2] +// / | +// / V +// [B7 (ENTRY)] -> [B6] -> [B5] -> [B1] -> [B0 (EXIT)] + +// CHECK: Immediate dominance tree (Node#,IDom#): +// CHECK-NEXT: (0,1) +// CHECK-NEXT: (1,6) +// CHECK-NEXT: (2,3) +// CHECK-NEXT: (3,4) +// CHECK-NEXT: (4,6) +// CHECK-NEXT: (5,6) +// CHECK-NEXT: (6,7) +// CHECK-NEXT: (7,7) int test3() { @@ -82,16 +96,26 @@ int test3() return 0; } -// CHECK: Immediate dominance tree (Node#,IDom#): -// CHECK: (0,1) -// CHECK: (1,7) -// CHECK: (2,5) -// CHECK: (3,4) -// CHECK: (4,5) -// CHECK: (5,6) -// CHECK: (6,7) -// CHECK: (7,8) -// CHECK: (8,8) +// <------------- +// / \ +// | ---> [B2] +// | / +// [B8 (ENTRY)] -> [B7] -> [B6] -> [B5] -> [B4] -> [B3] +// \ | \ / +// \ | <------------- +// \ \ +// --------> [B1] -> [B0 (EXIT)] + +// CHECK: Immediate dominance tree (Node#,IDom#): +// CHECK-NEXT: (0,1) +// CHECK-NEXT: (1,7) +// CHECK-NEXT: (2,5) +// CHECK-NEXT: (3,4) +// CHECK-NEXT: (4,5) +// CHECK-NEXT: (5,6) +// CHECK-NEXT: (6,7) +// CHECK-NEXT: (7,8) +// CHECK-NEXT: (8,8) int test4() { @@ -108,20 +132,33 @@ int test4() return 0; } -// CHECK: Immediate dominance tree (Node#,IDom#): -// CHECK: (0,1) -// CHECK: (1,10) -// CHECK: (2,9) -// CHECK: (3,4) -// CHECK: (4,5) -// CHECK: (5,9) -// CHECK: (6,7) -// CHECK: (7,8) -// CHECK: (8,9) -// CHECK: (9,10) -// CHECK: (10,11) -// CHECK: (11,12) -// CHECK: (12,12) +// <---------------------------------- +// / <----------------- \ +// / / \ \ +// [B12 (ENTRY)] -> [B11] -> [B10]-> [B9] -> [B8] ---> [B7] -> [B6] | +// | \ \ / +// | \ -----> [B2] --------/ +// | \ / +// | -> [B5] -> [B4] -> [B3] +// | \ / +// | <------------ +// \ +// -> [B1] -> [B0 (EXIT)] + +// CHECK: Immediate dominance tree (Node#,IDom#): +// CHECK-NEXT: (0,1) +// CHECK-NEXT: (1,10) +// CHECK-NEXT: (2,9) +// CHECK-NEXT: (3,4) +// CHECK-NEXT: (4,5) +// CHECK-NEXT: (5,9) +// CHECK-NEXT: (6,7) +// CHECK-NEXT: (7,8) +// CHECK-NEXT: (8,9) +// CHECK-NEXT: (9,10) +// CHECK-NEXT: (10,11) +// CHECK-NEXT: (11,12) +// CHECK-NEXT: (12,12) int test5() { @@ -151,18 +188,25 @@ int test5() return 0; } -// CHECK: Immediate dominance tree (Node#,IDom#): -// CHECK: (0,1) -// CHECK: (1,10) -// CHECK: (2,10) -// CHECK: (3,9) -// CHECK: (4,9) -// CHECK: (5,8) -// CHECK: (6,8) -// CHECK: (7,8) -// CHECK: (8,9) -// CHECK: (9,10) -// CHECK: (10,11) -// CHECK: (11,11) - - +// [B0 (EXIT)] <-- +// \ +// [B11 (ENTY)] -> [B10] -> [B9] -> [B8] -> [B7] -> [B5] -> [B3] -> [B1] +// | | | / / / +// | | V / / / +// | V [B6] --> / / +// V [B4] -----------------> / +// [B2]---------------------------------> + +// CHECK: Immediate dominance tree (Node#,IDom#): +// CHECK-NEXT: (0,1) +// CHECK-NEXT: (1,10) +// CHECK-NEXT: (2,10) +// CHECK-NEXT: (3,9) +// CHECK-NEXT: (4,9) +// CHECK-NEXT: (5,8) +// CHECK-NEXT: (6,8) +// CHECK-NEXT: (7,8) +// CHECK-NEXT: (8,9) +// CHECK-NEXT: (9,10) +// CHECK-NEXT: (10,11) +// CHECK-NEXT: (11,11) diff --git a/clang/test/Analysis/domtest.cpp b/clang/test/Analysis/domtest.cpp new file mode 100644 index 0000000000000..3530fca3d7821 --- /dev/null +++ b/clang/test/Analysis/domtest.cpp @@ -0,0 +1,51 @@ +// RUN: %clang_analyze_cc1 %s \ +// RUN: -analyzer-checker=debug.DumpDominators \ +// RUN: 2>&1 | FileCheck %s + +bool coin(); + +namespace pr42041_unreachable_cfg_successor { +enum Kind { + A +}; + +void f() { + switch(Kind{}) { + case A: + break; + } +} +} // end of namespace pr42041_unreachable_cfg_successor + +// [B3 (ENTRY)] -> [B1] -> [B2] -> [B0 (EXIT)] + +// CHECK: Immediate dominance tree (Node#,IDom#): +// CHECK-NEXT: (0,2) +// CHECK-NEXT: (1,3) +// CHECK-NEXT: (2,1) +// CHECK-NEXT: (3,3) + +void funcWithBranch() { + int x = 0; + if (coin()) { + if (coin()) { + x = 5; + } + int j = 10 / x; + (void)j; + } +} + +// ----> [B2] ----> +// / \ +// [B5 (ENTRY)] -> [B4] -> [B3] -----------> [B1] +// \ / +// ----> [B0 (EXIT)] <---- + +// CHECK: Immediate dominance tree (Node#,IDom#): +// CHECK-NEXT: (0,4) +// CHECK-NEXT: (1,3) +// CHECK-NEXT: (2,3) +// CHECK-NEXT: (3,4) +// CHECK-NEXT: (4,5) +// CHECK-NEXT: (5,5) From b342985d16581adee9eb2cce77930923022fc0cb Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Wed, 3 Jul 2019 11:39:12 +0000 Subject: [PATCH 040/181] [analyzer][Dominator] Add post dominators to CFG + a new debug checker Transform clang::DominatorTree to be able to also calculate post dominators. * Tidy up the documentation * Make it clang::DominatorTree template class (similarly to how llvm::DominatorTreeBase works), rename it to clang::CFGDominatorTreeImpl * Clang's dominator tree is now called clang::CFGDomTree * Clang's brand new post dominator tree is called clang::CFGPostDomTree * Add a lot of asserts to the dump() function * Create a new checker to test the functionality Differential Revision: https://reviews.llvm.org/D62551 llvm-svn: 365028 (cherry picked from commit 71a9dc39e4faf74eecdcb46f18ca21ee1c41dfe1) --- .../clang/Analysis/Analyses/Dominators.h | 176 ++++++++++++------ .../clang/StaticAnalyzer/Checkers/Checkers.td | 4 + clang/lib/Analysis/Dominators.cpp | 6 +- .../StaticAnalyzer/Checkers/DebugCheckers.cpp | 30 ++- clang/test/Analysis/domtest.c | 58 ++++++ clang/test/Analysis/domtest.cpp | 13 ++ 6 files changed, 224 insertions(+), 63 deletions(-) diff --git a/clang/include/clang/Analysis/Analyses/Dominators.h b/clang/include/clang/Analysis/Analyses/Dominators.h index 97db381e437ef..bdd48198d0d59 100644 --- a/clang/include/clang/Analysis/Analyses/Dominators.h +++ b/clang/include/clang/Analysis/Analyses/Dominators.h @@ -36,123 +36,147 @@ namespace clang { using DomTreeNode = llvm::DomTreeNodeBase; -/// Concrete subclass of DominatorTreeBase for Clang -/// This class implements the dominators tree functionality given a Clang CFG. -/// -class DominatorTree : public ManagedAnalysis { +/// Dominator tree builder for Clang's CFG based on llvm::DominatorTreeBase. +template +class CFGDominatorTreeImpl : public ManagedAnalysis { virtual void anchor(); public: - llvm::DomTreeBase *DT; + using DominatorTreeBase = llvm::DominatorTreeBase; - DominatorTree() { - DT = new llvm::DomTreeBase(); - } + DominatorTreeBase DT; - ~DominatorTree() override { delete DT; } + CFGDominatorTreeImpl() = default; + ~CFGDominatorTreeImpl() override = default; - llvm::DomTreeBase& getBase() { return *DT; } + DominatorTreeBase& getBase() { return *DT; } - /// This method returns the root CFGBlock of the dominators tree. + /// \returns the root CFGBlock of the dominators tree. CFGBlock *getRoot() const { - return DT->getRoot(); + return DT.getRoot(); } - /// This method returns the root DomTreeNode, which is the wrapper - /// for CFGBlock. - DomTreeNode *getRootNode() const { - return DT->getRootNode(); + /// \returns the root DomTreeNode, which is the wrapper for CFGBlock. + DomTreeNode *getRootNode() { + return DT.getRootNode(); } - /// This method compares two dominator trees. - /// The method returns false if the other dominator tree matches this - /// dominator tree, otherwise returns true. - bool compare(DominatorTree &Other) const { + /// Compares two dominator trees. + /// \returns false if the other dominator tree matches this dominator tree, + /// false otherwise. + bool compare(CFGDominatorTreeImpl &Other) const { DomTreeNode *R = getRootNode(); DomTreeNode *OtherR = Other.getRootNode(); if (!R || !OtherR || R->getBlock() != OtherR->getBlock()) return true; - if (DT->compare(Other.getBase())) + if (DT.compare(Other.getBase())) return true; return false; } - /// This method builds the dominator tree for a given CFG - /// The CFG information is passed via AnalysisDeclContext - void buildDominatorTree(AnalysisDeclContext &AC) { - cfg = AC.getCFG(); - DT->recalculate(*cfg); + /// Builds the dominator tree for a given CFG. + void buildDominatorTree(CFG *cfg) { + assert(cfg); + this->cfg = cfg; + DT.recalculate(*cfg); } - /// This method dumps immediate dominators for each block, - /// mainly used for debug purposes. + /// Dumps immediate dominators for each block. void dump() { - llvm::errs() << "Immediate dominance tree (Node#,IDom#):\n"; + llvm::errs() << "Immediate " << (IsPostDom ? "post " : "") + << "dominance tree (Node#,IDom#):\n"; for (CFG::const_iterator I = cfg->begin(), E = cfg->end(); I != E; ++I) { - if(DT->getNode(*I)->getIDom()) + + assert(*I && + "LLVM's Dominator tree builder uses nullpointers to signify the " + "virtual root!"); + + DomTreeNode *IDom = DT.getNode(*I)->getIDom(); + if (IDom && IDom->getBlock()) llvm::errs() << "(" << (*I)->getBlockID() << "," - << DT->getNode(*I)->getIDom()->getBlock()->getBlockID() + << IDom->getBlock()->getBlockID() << ")\n"; - else llvm::errs() << "(" << (*I)->getBlockID() - << "," << (*I)->getBlockID() << ")\n"; + else { + bool IsEntryBlock = *I == &(*I)->getParent()->getEntry(); + bool IsExitBlock = *I == &(*I)->getParent()->getExit(); + + bool IsDomTreeRoot = !IDom && !IsPostDom && IsEntryBlock; + bool IsPostDomTreeRoot = + IDom && !IDom->getBlock() && IsPostDom && IsExitBlock; + + assert((IsDomTreeRoot || IsPostDomTreeRoot) && + "If the immediate dominator node is nullptr, the CFG block " + "should be the exit point (since it's the root of the dominator " + "tree), or if the CFG block it refers to is a nullpointer, it " + "must be the entry block (since it's the root of the post " + "dominator tree)"); + + (void)IsDomTreeRoot; + (void)IsPostDomTreeRoot; + + llvm::errs() << "(" << (*I)->getBlockID() + << "," << (*I)->getBlockID() << ")\n"; + } } } - /// This method tests if one CFGBlock dominates the other. - /// The method return true if A dominates B, false otherwise. + /// Tests whether \p A dominates \p B. /// Note a block always dominates itself. bool dominates(const CFGBlock *A, const CFGBlock *B) const { - return DT->dominates(A, B); + return DT.dominates(A, B); } - /// This method tests if one CFGBlock properly dominates the other. - /// The method return true if A properly dominates B, false otherwise. + /// Tests whether \p A properly dominates \p B. + /// \returns false if \p A is the same block as \p B, otherwise whether A + /// dominates B. bool properlyDominates(const CFGBlock *A, const CFGBlock *B) const { - return DT->properlyDominates(A, B); + return DT.properlyDominates(A, B); } - /// This method finds the nearest common dominator CFG block - /// for CFG block A and B. If there is no such block then return NULL. + /// \returns the nearest common dominator CFG block for CFG block \p A and \p + /// B. If there is no such block then return NULL. CFGBlock *findNearestCommonDominator(CFGBlock *A, CFGBlock *B) { - return DT->findNearestCommonDominator(A, B); + return DT.findNearestCommonDominator(A, B); } const CFGBlock *findNearestCommonDominator(const CFGBlock *A, const CFGBlock *B) { - return DT->findNearestCommonDominator(A, B); + return DT.findNearestCommonDominator(A, B); } - /// This method is used to update the dominator - /// tree information when a node's immediate dominator changes. + /// Update the dominator tree information when a node's immediate dominator + /// changes. void changeImmediateDominator(CFGBlock *N, CFGBlock *NewIDom) { - DT->changeImmediateDominator(N, NewIDom); + DT.changeImmediateDominator(N, NewIDom); } - /// This method tests if the given CFGBlock can be reachable from root. - /// Returns true if reachable, false otherwise. + /// Tests whether \p A is reachable from the entry block. bool isReachableFromEntry(const CFGBlock *A) { - return DT->isReachableFromEntry(A); + return DT.isReachableFromEntry(A); } - /// This method releases the memory held by the dominator tree. + /// Releases the memory held by the dominator tree. virtual void releaseMemory() { - DT->releaseMemory(); + DT.releaseMemory(); } - /// This method converts the dominator tree to human readable form. + /// Converts the dominator tree to human readable form. virtual void print(raw_ostream &OS, const llvm::Module* M= nullptr) const { - DT->print(OS); + DT.print(OS); } private: CFG *cfg; }; +using CFGDomTree = CFGDominatorTreeImpl; +using CFGPostDomTree = CFGDominatorTreeImpl; + } // namespace clang namespace llvm { @@ -167,7 +191,8 @@ namespace llvm { namespace DomTreeBuilder { using ClangCFGDomChildrenGetter = -SemiNCAInfo>::ChildrenGetter; +SemiNCAInfo::ChildrenGetter< + /*Inverse=*/false>; template <> template <> @@ -180,7 +205,8 @@ inline ClangCFGDomChildrenGetter::ResultTy ClangCFGDomChildrenGetter::Get( } using ClangCFGDomReverseChildrenGetter = -SemiNCAInfo>::ChildrenGetter; +SemiNCAInfo::ChildrenGetter< + /*Inverse=*/true>; template <> template <> @@ -193,6 +219,36 @@ ClangCFGDomReverseChildrenGetter::Get( return Ret; } +using ClangCFGPostDomChildrenGetter = +SemiNCAInfo::ChildrenGetter< + /*Inverse=*/false>; + +template <> +template <> +inline ClangCFGPostDomChildrenGetter::ResultTy +ClangCFGPostDomChildrenGetter::Get( + clang::CFGBlock *N, std::integral_constant) { + auto RChildren = reverse(children(N)); + ResultTy Ret(RChildren.begin(), RChildren.end()); + Ret.erase(std::remove(Ret.begin(), Ret.end(), nullptr), Ret.end()); + return Ret; +} + +using ClangCFGPostDomReverseChildrenGetter = +SemiNCAInfo::ChildrenGetter< + /*Inverse=*/true>; + +template <> +template <> +inline ClangCFGPostDomReverseChildrenGetter::ResultTy +ClangCFGPostDomReverseChildrenGetter::Get( + clang::CFGBlock *N, std::integral_constant) { + auto IChildren = inverse_children(N); + ResultTy Ret(IChildren.begin(), IChildren.end()); + Ret.erase(std::remove(Ret.begin(), Ret.end(), nullptr), Ret.end()); + return Ret; +} + } // end of namespace DomTreeBuilder //===------------------------------------- @@ -219,17 +275,17 @@ template <> struct GraphTraits { } }; -template <> struct GraphTraits< ::clang::DominatorTree* > - : public GraphTraits< ::clang::DomTreeNode* > { - static NodeRef getEntryNode(::clang::DominatorTree *DT) { +template <> struct GraphTraits + : public GraphTraits { + static NodeRef getEntryNode(clang::CFGDomTree *DT) { return DT->getRootNode(); } - static nodes_iterator nodes_begin(::clang::DominatorTree *N) { + static nodes_iterator nodes_begin(clang::CFGDomTree *N) { return nodes_iterator(df_begin(getEntryNode(N))); } - static nodes_iterator nodes_end(::clang::DominatorTree *N) { + static nodes_iterator nodes_end(clang::CFGDomTree *N) { return nodes_iterator(df_end(getEntryNode(N))); } }; diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index bc081498ac9e2..ecb060cf555a6 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -1229,6 +1229,10 @@ def DominatorsTreeDumper : Checker<"DumpDominators">, HelpText<"Print the dominance tree for a given CFG">, Documentation; +def PostDominatorsTreeDumper : Checker<"DumpPostDominators">, + HelpText<"Print the post dominance tree for a given CFG">, + Documentation; + def LiveVariablesDumper : Checker<"DumpLiveVars">, HelpText<"Print results of live variable analysis">, Documentation; diff --git a/clang/lib/Analysis/Dominators.cpp b/clang/lib/Analysis/Dominators.cpp index b872869f8cf81..a350d4ca18bdd 100644 --- a/clang/lib/Analysis/Dominators.cpp +++ b/clang/lib/Analysis/Dominators.cpp @@ -10,4 +10,8 @@ using namespace clang; -void DominatorTree::anchor() {} +template <> +void CFGDominatorTreeImpl::anchor() {} + +template <> +void CFGDominatorTreeImpl::anchor() {} diff --git a/clang/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp b/clang/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp index 63215e6bd3b4d..6d32b9191419d 100644 --- a/clang/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp @@ -35,8 +35,8 @@ class DominatorsTreeDumper : public Checker { void checkASTCodeBody(const Decl *D, AnalysisManager& mgr, BugReporter &BR) const { if (AnalysisDeclContext *AC = mgr.getAnalysisDeclContext(D)) { - DominatorTree dom; - dom.buildDominatorTree(*AC); + CFGDomTree dom; + dom.buildDominatorTree(AC->getCFG()); dom.dump(); } } @@ -51,6 +51,32 @@ bool ento::shouldRegisterDominatorsTreeDumper(const LangOptions &LO) { return true; } +//===----------------------------------------------------------------------===// +// PostDominatorsTreeDumper +//===----------------------------------------------------------------------===// + +namespace { +class PostDominatorsTreeDumper : public Checker { +public: + void checkASTCodeBody(const Decl *D, AnalysisManager& mgr, + BugReporter &BR) const { + if (AnalysisDeclContext *AC = mgr.getAnalysisDeclContext(D)) { + CFGPostDomTree dom; + dom.buildDominatorTree(AC->getCFG()); + dom.dump(); + } + } +}; +} + +void ento::registerPostDominatorsTreeDumper(CheckerManager &mgr) { + mgr.registerChecker(); +} + +bool ento::shouldRegisterPostDominatorsTreeDumper(const LangOptions &LO) { + return true; +} + //===----------------------------------------------------------------------===// // LiveVariablesDumper //===----------------------------------------------------------------------===// diff --git a/clang/test/Analysis/domtest.c b/clang/test/Analysis/domtest.c index cbd75b991db9f..91cd7de420fa0 100644 --- a/clang/test/Analysis/domtest.c +++ b/clang/test/Analysis/domtest.c @@ -1,5 +1,6 @@ // RUN: %clang_analyze_cc1 %s \ // RUN: -analyzer-checker=debug.DumpDominators \ +// RUN: -analyzer-checker=debug.DumpPostDominators \ // RUN: 2>&1 | FileCheck %s // Test the DominatorsTree implementation with various control flows @@ -42,6 +43,17 @@ int test1() // CHECK-NEXT: (7,8) // CHECK-NEXT: (8,9) // CHECK-NEXT: (9,9) +// CHECK-NEXT: Immediate post dominance tree (Node#,IDom#): +// CHECK-NEXT: (0,0) +// CHECK-NEXT: (1,0) +// CHECK-NEXT: (2,7) +// CHECK-NEXT: (3,2) +// CHECK-NEXT: (4,3) +// CHECK-NEXT: (5,3) +// CHECK-NEXT: (6,3) +// CHECK-NEXT: (7,1) +// CHECK-NEXT: (8,7) +// CHECK-NEXT: (9,8) int test2() { @@ -77,6 +89,15 @@ int test2() // CHECK-NEXT: (5,6) // CHECK-NEXT: (6,7) // CHECK-NEXT: (7,7) +// CHECK-NEXT: Immediate post dominance tree (Node#,IDom#): +// CHECK-NEXT: (0,0) +// CHECK-NEXT: (1,0) +// CHECK-NEXT: (2,4) +// CHECK-NEXT: (3,2) +// CHECK-NEXT: (4,1) +// CHECK-NEXT: (5,1) +// CHECK-NEXT: (6,1) +// CHECK-NEXT: (7,6) int test3() { @@ -116,6 +137,16 @@ int test3() // CHECK-NEXT: (6,7) // CHECK-NEXT: (7,8) // CHECK-NEXT: (8,8) +// CHECK-NEXT: Immediate post dominance tree (Node#,IDom#): +// CHECK-NEXT: (0,0) +// CHECK-NEXT: (1,0) +// CHECK-NEXT: (2,6) +// CHECK-NEXT: (3,5) +// CHECK-NEXT: (4,3) +// CHECK-NEXT: (5,2) +// CHECK-NEXT: (6,1) +// CHECK-NEXT: (7,1) +// CHECK-NEXT: (8,7) int test4() { @@ -159,6 +190,20 @@ int test4() // CHECK-NEXT: (10,11) // CHECK-NEXT: (11,12) // CHECK-NEXT: (12,12) +// CHECK-NEXT: Immediate post dominance tree (Node#,IDom#): +// CHECK-NEXT: (0,0) +// CHECK-NEXT: (1,0) +// CHECK-NEXT: (2,10) +// CHECK-NEXT: (3,5) +// CHECK-NEXT: (4,3) +// CHECK-NEXT: (5,2) +// CHECK-NEXT: (6,8) +// CHECK-NEXT: (7,6) +// CHECK-NEXT: (8,2) +// CHECK-NEXT: (9,2) +// CHECK-NEXT: (10,1) +// CHECK-NEXT: (11,10) +// CHECK-NEXT: (12,11) int test5() { @@ -210,3 +255,16 @@ int test5() // CHECK-NEXT: (9,10) // CHECK-NEXT: (10,11) // CHECK-NEXT: (11,11) +// CHECK-NEXT: Immediate post dominance tree (Node#,IDom#): +// CHECK-NEXT: (0,0) +// CHECK-NEXT: (1,0) +// CHECK-NEXT: (2,1) +// CHECK-NEXT: (3,1) +// CHECK-NEXT: (4,3) +// CHECK-NEXT: (5,3) +// CHECK-NEXT: (6,5) +// CHECK-NEXT: (7,5) +// CHECK-NEXT: (8,5) +// CHECK-NEXT: (9,3) +// CHECK-NEXT: (10,1) +// CHECK-NEXT: (11,10) diff --git a/clang/test/Analysis/domtest.cpp b/clang/test/Analysis/domtest.cpp index 3530fca3d7821..d57f94e44bbc6 100644 --- a/clang/test/Analysis/domtest.cpp +++ b/clang/test/Analysis/domtest.cpp @@ -1,5 +1,6 @@ // RUN: %clang_analyze_cc1 %s \ // RUN: -analyzer-checker=debug.DumpDominators \ +// RUN: -analyzer-checker=debug.DumpPostDominators \ // RUN: 2>&1 | FileCheck %s bool coin(); @@ -24,6 +25,11 @@ void f() { // CHECK-NEXT: (1,3) // CHECK-NEXT: (2,1) // CHECK-NEXT: (3,3) +// CHECK-NEXT: Immediate post dominance tree (Node#,IDom#): +// CHECK-NEXT: (0,0) +// CHECK-NEXT: (1,2) +// CHECK-NEXT: (2,0) +// CHECK-NEXT: (3,1) void funcWithBranch() { int x = 0; @@ -49,3 +55,10 @@ void funcWithBranch() { // CHECK-NEXT: (3,4) // CHECK-NEXT: (4,5) // CHECK-NEXT: (5,5) +// CHECK-NEXT: Immediate post dominance tree (Node#,IDom#): +// CHECK-NEXT: (0,0) +// CHECK-NEXT: (1,0) +// CHECK-NEXT: (2,1) +// CHECK-NEXT: (3,1) +// CHECK-NEXT: (4,0) +// CHECK-NEXT: (5,4) From e0396019d9adff4ef53a6339b03da799c38a5abe Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Wed, 3 Jul 2019 11:54:47 +0000 Subject: [PATCH 041/181] Specialize an anchor() function in the correct namespace llvm-svn: 365029 (cherry picked from commit c4b89fec5fc44e4b724f4ab3e07a6519dcba94f4) --- clang/lib/Analysis/Dominators.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clang/lib/Analysis/Dominators.cpp b/clang/lib/Analysis/Dominators.cpp index a350d4ca18bdd..374746d78b76c 100644 --- a/clang/lib/Analysis/Dominators.cpp +++ b/clang/lib/Analysis/Dominators.cpp @@ -11,7 +11,7 @@ using namespace clang; template <> -void CFGDominatorTreeImpl::anchor() {} +void clang::CFGDominatorTreeImpl::anchor() {} template <> -void CFGDominatorTreeImpl::anchor() {} +void clang::CFGDominatorTreeImpl::anchor() {} From 34e0c857c5dee5e3d81e35038de567d93af343c8 Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Wed, 3 Jul 2019 12:06:10 +0000 Subject: [PATCH 042/181] Make a buildbot using a buggy gcc happy When specializing a template in a namespace, it has to be in a namespace block, else gcc will get confused. Hopefully this fixes the issue. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56480 llvm-svn: 365030 (cherry picked from commit b069bbf510e6a24431c22055297b1e58f6d3e312) --- clang/lib/Analysis/Dominators.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/clang/lib/Analysis/Dominators.cpp b/clang/lib/Analysis/Dominators.cpp index 374746d78b76c..f7ad68673d0f2 100644 --- a/clang/lib/Analysis/Dominators.cpp +++ b/clang/lib/Analysis/Dominators.cpp @@ -8,10 +8,12 @@ #include "clang/Analysis/Analyses/Dominators.h" -using namespace clang; +namespace clang { template <> -void clang::CFGDominatorTreeImpl::anchor() {} +void CFGDominatorTreeImpl::anchor() {} template <> -void clang::CFGDominatorTreeImpl::anchor() {} +void CFGDominatorTreeImpl::anchor() {} + +} // end of namespace clang From 01bae8411e4da7f8528b6a2da82a57850b75d14a Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Wed, 3 Jul 2019 12:53:19 +0000 Subject: [PATCH 043/181] [analyzer][CFG] Return the correct terminator condition For the following terminator statement: if (A && B && C && D) The built CFG is the following: [B5 (ENTRY)] Succs (1): B4 [B1] 1: 10 2: j 3: [B1.2] (ImplicitCastExpr, LValueToRValue, int) 4: [B1.1] / [B1.3] 5: int x = 10 / j; Preds (1): B2 Succs (1): B0 [B2] 1: C 2: [B2.1] (ImplicitCastExpr, LValueToRValue, _Bool) T: if [B4.4] && [B3.2] && [B2.2] Preds (1): B3 Succs (2): B1 B0 [B3] 1: B 2: [B3.1] (ImplicitCastExpr, LValueToRValue, _Bool) T: [B4.4] && [B3.2] && ... Preds (1): B4 Succs (2): B2 B0 [B4] 1: 0 2: int j = 0; 3: A 4: [B4.3] (ImplicitCastExpr, LValueToRValue, _Bool) T: [B4.4] && ... Preds (1): B5 Succs (2): B3 B0 [B0 (EXIT)] Preds (4): B1 B2 B3 B4 However, even though the path of execution in B2 only depends on C's value, CFGBlock::getCondition() would return the entire condition (A && B && C). For B3, it would return A && B. I changed this the actual condition. Differential Revision: https://reviews.llvm.org/D63538 llvm-svn: 365036 (cherry picked from commit 00aab1d45e1f67fbc265ed5ea4b2230148181970) --- clang/include/clang/Analysis/CFG.h | 8 ++-- clang/lib/Analysis/CFG.cpp | 70 +++++------------------------- 2 files changed, 16 insertions(+), 62 deletions(-) diff --git a/clang/include/clang/Analysis/CFG.h b/clang/include/clang/Analysis/CFG.h index d8b3d6ff71ede..945d36498cfd0 100644 --- a/clang/include/clang/Analysis/CFG.h +++ b/clang/include/clang/Analysis/CFG.h @@ -860,10 +860,12 @@ class CFGBlock { Stmt *getTerminatorStmt() { return Terminator.getStmt(); } const Stmt *getTerminatorStmt() const { return Terminator.getStmt(); } - Stmt *getTerminatorCondition(bool StripParens = true); + /// \returns the condition of the terminator (condition of an if statement, + /// for loop, etc). + const Stmt *getTerminatorCondition(bool StripParens = true) const; - const Stmt *getTerminatorCondition(bool StripParens = true) const { - return const_cast(this)->getTerminatorCondition(StripParens); + const Expr *getTerminatorConditionExpr(bool StripParens = true) const { + return dyn_cast_or_null(getTerminatorCondition(StripParens)); } const Stmt *getLoopTarget() const { return LoopTarget; } diff --git a/clang/lib/Analysis/CFG.cpp b/clang/lib/Analysis/CFG.cpp index b53bfcca37cd4..3e6185c76c955 100644 --- a/clang/lib/Analysis/CFG.cpp +++ b/clang/lib/Analysis/CFG.cpp @@ -5615,69 +5615,21 @@ void CFGBlock::printTerminatorJson(raw_ostream &Out, const LangOptions &LO, Out << JsonFormat(TempOut.str(), AddQuotes); } -Stmt *CFGBlock::getTerminatorCondition(bool StripParens) { - Stmt *Terminator = getTerminatorStmt(); - if (!Terminator) +const Stmt *CFGBlock::getTerminatorCondition(bool StripParens) const { + // If the terminator is a temporary dtor or a virtual base, etc, we can't + // retrieve a meaningful condition, bail out. + if (rbegin()->getKind() != CFGElement::Kind::Statement) return nullptr; - Expr *E = nullptr; - - switch (Terminator->getStmtClass()) { - default: - break; - - case Stmt::CXXForRangeStmtClass: - E = cast(Terminator)->getCond(); - break; - - case Stmt::ForStmtClass: - E = cast(Terminator)->getCond(); - break; - - case Stmt::WhileStmtClass: - E = cast(Terminator)->getCond(); - break; - - case Stmt::DoStmtClass: - E = cast(Terminator)->getCond(); - break; - - case Stmt::IfStmtClass: - E = cast(Terminator)->getCond(); - break; - - case Stmt::ChooseExprClass: - E = cast(Terminator)->getCond(); - break; - - case Stmt::IndirectGotoStmtClass: - E = cast(Terminator)->getTarget(); - break; - - case Stmt::SwitchStmtClass: - E = cast(Terminator)->getCond(); - break; - - case Stmt::BinaryConditionalOperatorClass: - E = cast(Terminator)->getCond(); - break; - - case Stmt::ConditionalOperatorClass: - E = cast(Terminator)->getCond(); - break; - - case Stmt::BinaryOperatorClass: // '&&' and '||' - E = cast(Terminator)->getLHS(); - break; - - case Stmt::ObjCForCollectionStmtClass: - return Terminator; + // This should be the condition of the terminator block. + const Stmt *S = rbegin()->castAs().getStmt(); + if (isa(S)) { + return getTerminatorStmt(); } - if (!StripParens) - return E; - - return E ? E->IgnoreParens() : nullptr; + // Only ObjCForCollectionStmt is known not to be a non-Expr terminator. + const Expr *Cond = cast(S); + return StripParens ? Cond->IgnoreParens() : Cond; } //===----------------------------------------------------------------------===// From 62165144b9ef18d3e8aa9a6cd35b04aeee5926fa Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Wed, 3 Jul 2019 13:03:33 +0000 Subject: [PATCH 044/181] Revert "[analyzer][CFG] Return the correct terminator condition" This reverts commit 7a57118a6fcfa3770f984453543bbdfd0b233e84. Causes a bunch of crashes, I need to time to evaluate this. llvm-svn: 365037 (cherry picked from commit 9854d771bd11629df5ea21403457dcc7c7b84a7c) --- clang/include/clang/Analysis/CFG.h | 8 ++-- clang/lib/Analysis/CFG.cpp | 70 +++++++++++++++++++++++++----- 2 files changed, 62 insertions(+), 16 deletions(-) diff --git a/clang/include/clang/Analysis/CFG.h b/clang/include/clang/Analysis/CFG.h index 945d36498cfd0..d8b3d6ff71ede 100644 --- a/clang/include/clang/Analysis/CFG.h +++ b/clang/include/clang/Analysis/CFG.h @@ -860,12 +860,10 @@ class CFGBlock { Stmt *getTerminatorStmt() { return Terminator.getStmt(); } const Stmt *getTerminatorStmt() const { return Terminator.getStmt(); } - /// \returns the condition of the terminator (condition of an if statement, - /// for loop, etc). - const Stmt *getTerminatorCondition(bool StripParens = true) const; + Stmt *getTerminatorCondition(bool StripParens = true); - const Expr *getTerminatorConditionExpr(bool StripParens = true) const { - return dyn_cast_or_null(getTerminatorCondition(StripParens)); + const Stmt *getTerminatorCondition(bool StripParens = true) const { + return const_cast(this)->getTerminatorCondition(StripParens); } const Stmt *getLoopTarget() const { return LoopTarget; } diff --git a/clang/lib/Analysis/CFG.cpp b/clang/lib/Analysis/CFG.cpp index 3e6185c76c955..b53bfcca37cd4 100644 --- a/clang/lib/Analysis/CFG.cpp +++ b/clang/lib/Analysis/CFG.cpp @@ -5615,21 +5615,69 @@ void CFGBlock::printTerminatorJson(raw_ostream &Out, const LangOptions &LO, Out << JsonFormat(TempOut.str(), AddQuotes); } -const Stmt *CFGBlock::getTerminatorCondition(bool StripParens) const { - // If the terminator is a temporary dtor or a virtual base, etc, we can't - // retrieve a meaningful condition, bail out. - if (rbegin()->getKind() != CFGElement::Kind::Statement) +Stmt *CFGBlock::getTerminatorCondition(bool StripParens) { + Stmt *Terminator = getTerminatorStmt(); + if (!Terminator) return nullptr; - // This should be the condition of the terminator block. - const Stmt *S = rbegin()->castAs().getStmt(); - if (isa(S)) { - return getTerminatorStmt(); + Expr *E = nullptr; + + switch (Terminator->getStmtClass()) { + default: + break; + + case Stmt::CXXForRangeStmtClass: + E = cast(Terminator)->getCond(); + break; + + case Stmt::ForStmtClass: + E = cast(Terminator)->getCond(); + break; + + case Stmt::WhileStmtClass: + E = cast(Terminator)->getCond(); + break; + + case Stmt::DoStmtClass: + E = cast(Terminator)->getCond(); + break; + + case Stmt::IfStmtClass: + E = cast(Terminator)->getCond(); + break; + + case Stmt::ChooseExprClass: + E = cast(Terminator)->getCond(); + break; + + case Stmt::IndirectGotoStmtClass: + E = cast(Terminator)->getTarget(); + break; + + case Stmt::SwitchStmtClass: + E = cast(Terminator)->getCond(); + break; + + case Stmt::BinaryConditionalOperatorClass: + E = cast(Terminator)->getCond(); + break; + + case Stmt::ConditionalOperatorClass: + E = cast(Terminator)->getCond(); + break; + + case Stmt::BinaryOperatorClass: // '&&' and '||' + E = cast(Terminator)->getLHS(); + break; + + case Stmt::ObjCForCollectionStmtClass: + return Terminator; } - // Only ObjCForCollectionStmt is known not to be a non-Expr terminator. - const Expr *Cond = cast(S); - return StripParens ? Cond->IgnoreParens() : Cond; + if (!StripParens) + return E; + + return E ? E->IgnoreParens() : nullptr; } //===----------------------------------------------------------------------===// From 509b06f4fbf128f6d30b45e96366e6ff0bb53771 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Wed, 3 Jul 2019 20:48:23 +0000 Subject: [PATCH 045/181] [analyzer] exploded-graph-rewriter: Implement a black-and-white color scheme. For accessibility! Differential Revision: https://reviews.llvm.org/D64153 llvm-svn: 365085 (cherry picked from commit 78c0aefb220b42bd113a0ec834aa729ac40b450d) --- .../exploded-graph-rewriter/node_labels.dot | 17 +++++++---- .../utils/analyzer/exploded-graph-rewriter.py | 29 ++++++++++++------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/clang/test/Analysis/exploded-graph-rewriter/node_labels.dot b/clang/test/Analysis/exploded-graph-rewriter/node_labels.dot index db56c0585c1fe..b8f69192a6c19 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/node_labels.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/node_labels.dot @@ -1,6 +1,11 @@ -// RUN: %exploded_graph_rewriter %s | FileCheck %s -check-prefixes=CHECK,LIGHT -// RUN: %exploded_graph_rewriter %s --dark | FileCheck %s \ -// RUN: -check-prefixes CHECK,DARK +// RUN: %exploded_graph_rewriter %s \ +// RUN: | FileCheck %s -check-prefixes=CHECK,LIGHT,COLOR +// RUN: %exploded_graph_rewriter %s --dark \ +// RUN: | FileCheck %s -check-prefixes CHECK,DARK,COLOR +// RUN: %exploded_graph_rewriter %s --gray \ +// RUN: | FileCheck %s -check-prefixes=CHECK,LIGHT,GRAY +// RUN: %exploded_graph_rewriter %s --gray --dark \ +// RUN: | FileCheck %s -check-prefixes CHECK,DARK,GRAY // FIXME: Substitution doesn't seem to work on Windows. // UNSUPPORTED: system-windows @@ -23,10 +28,12 @@ Node0x1 [shape=record,label= // CHECK: Node0x2 [ // CHECK-SAME: -// CHECK-SAME: Bug Report Attached +// COLOR-SAME: Bug Report Attached +// GRAY-SAME: Bug Report Attached // CHECK-SAME: // CHECK-SAME: -// CHECK-SAME: Sink Node +// COLOR-SAME: Sink Node +// GRAY-SAME: Sink Node // CHECK-SAME: Node0x2 [shape=record,label= "{ diff --git a/clang/utils/analyzer/exploded-graph-rewriter.py b/clang/utils/analyzer/exploded-graph-rewriter.py index 0926ba2511ee7..b78789f0b647b 100755 --- a/clang/utils/analyzer/exploded-graph-rewriter.py +++ b/clang/utils/analyzer/exploded-graph-rewriter.py @@ -384,24 +384,28 @@ def add_raw_line(self, raw_line): # A visitor that dumps the ExplodedGraph into a DOT file with fancy HTML-based # syntax highlighing. class DotDumpVisitor(object): - def __init__(self, do_diffs, dark_mode): + def __init__(self, do_diffs, dark_mode, gray_mode): super(DotDumpVisitor, self).__init__() self._do_diffs = do_diffs self._dark_mode = dark_mode + self._gray_mode = gray_mode @staticmethod def _dump_raw(s): print(s, end='') - @staticmethod - def _dump(s): - print(s.replace('&', '&') - .replace('{', '\\{') - .replace('}', '\\}') - .replace('\\<', '<') - .replace('\\>', '>') - .replace('\\l', '
') - .replace('|', '\\|'), end='') + def _dump(self, s): + s = s.replace('&', '&') \ + .replace('{', '\\{') \ + .replace('}', '\\}') \ + .replace('\\<', '<') \ + .replace('\\>', '>') \ + .replace('\\l', '
') \ + .replace('|', '\\|') + if self._gray_mode: + s = re.sub(r'', '', s) + s = re.sub(r'', '', s) + self._dump_raw(s) @staticmethod def _diff_plus_minus(is_added): @@ -835,6 +839,9 @@ def main(): parser.add_argument('--dark', action='store_const', dest='dark', const=True, default=False, help='dark mode') + parser.add_argument('--gray', action='store_const', dest='gray', + const=True, default=False, + help='black-and-white mode') args = parser.parse_args() logging.basicConfig(level=args.loglevel) @@ -845,7 +852,7 @@ def main(): graph.add_raw_line(raw_line) explorer = BasicExplorer() - visitor = DotDumpVisitor(args.diff, args.dark) + visitor = DotDumpVisitor(args.diff, args.dark, args.gray) explorer.explore(graph, visitor) From b23a7c1938706c68f98b4856fccf23a33e7620b7 Mon Sep 17 00:00:00 2001 From: Csaba Dabis Date: Thu, 4 Jul 2019 00:50:50 +0000 Subject: [PATCH 046/181] [analyzer] ReturnValueChecker: Model the guaranteed boolean return value of function calls Summary: It models the known LLVM methods paired with their class. Reviewers: NoQ, xazax.hun, ravikandhadai, baloghadamsoftware, Szelethus Reviewed By: NoQ Subscribers: dschuff, aheejin, mgorny, szepet, rnkovacs, a.sidorin, mikhail.ramalho, donat.nagy, dkrupp, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D63915 llvm-svn: 365103 (cherry picked from commit 57835bcfbd862b169e1159bb2cc0c4980ebb5af4) --- .../clang/StaticAnalyzer/Checkers/Checkers.td | 4 + .../Core/PathSensitive/CheckerContext.h | 22 ++- .../StaticAnalyzer/Checkers/CMakeLists.txt | 1 + .../Checkers/ReturnValueChecker.cpp | 170 ++++++++++++++++++ .../StaticAnalyzer/Core/PathDiagnostic.cpp | 3 + .../test/Analysis/return-value-guaranteed.cpp | 91 ++++++++++ 6 files changed, 285 insertions(+), 6 deletions(-) create mode 100644 clang/lib/StaticAnalyzer/Checkers/ReturnValueChecker.cpp create mode 100644 clang/test/Analysis/return-value-guaranteed.cpp diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index ecb060cf555a6..8e0122bcc8379 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -274,6 +274,10 @@ def NullableReturnedFromNonnullChecker : Checker<"NullableReturnedFromNonnull">, let ParentPackage = APIModeling in { +def ReturnValueChecker : Checker<"ReturnValue">, + HelpText<"Model the guaranteed boolean return value of function calls">, + Documentation; + def StdCLibraryFunctionsChecker : Checker<"StdCLibraryFunctions">, HelpText<"Improve modeling of the C standard library functions">, Documentation; diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h index 6a49aebc14ce4..0c68078bb3541 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h @@ -219,24 +219,34 @@ class CheckerContext { Eng.getBugReporter().emitReport(std::move(R)); } - /// Produce a program point tag that displays an additional path note /// to the user. This is a lightweight alternative to the /// BugReporterVisitor mechanism: instead of visiting the bug report /// node-by-node to restore the sequence of events that led to discovering /// a bug, you can add notes as you add your transitions. - const NoteTag *getNoteTag(NoteTag::Callback &&Cb) { - return Eng.getNoteTags().makeNoteTag(std::move(Cb)); + /// + /// @param Cb Callback with 'BugReporterContext &, BugReport &' parameters. + /// @param IsPrunable Whether the note is prunable. It allows BugReporter + /// to omit the note from the report if it would make the displayed + /// bug path significantly shorter. + const NoteTag *getNoteTag(NoteTag::Callback &&Cb, bool IsPrunable = false) { + return Eng.getNoteTags().makeNoteTag(std::move(Cb), IsPrunable); } /// A shorthand version of getNoteTag that doesn't require you to accept /// the BugReporterContext arguments when you don't need it. - const NoteTag *getNoteTag(std::function &&Cb) { + /// + /// @param Cb Callback only with 'BugReport &' parameter. + /// @param IsPrunable Whether the note is prunable. It allows BugReporter + /// to omit the note from the report if it would make the displayed + /// bug path significantly shorter. + const NoteTag *getNoteTag(std::function &&Cb, + bool IsPrunable = false) { return getNoteTag( - [Cb](BugReporterContext &, BugReport &BR) { return Cb(BR); }); + [Cb](BugReporterContext &, BugReport &BR) { return Cb(BR); }, + IsPrunable); } - /// Returns the word that should be used to refer to the declaration /// in the report. StringRef getDeclDescription(const Decl *D); diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt index df12fa5c9a11a..63ff770531837 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -83,6 +83,7 @@ add_clang_library(clangStaticAnalyzerCheckers RetainCountChecker/RetainCountDiagnostics.cpp ReturnPointerRangeChecker.cpp ReturnUndefChecker.cpp + ReturnValueChecker.cpp RunLoopAutoreleaseLeakChecker.cpp SimpleStreamChecker.cpp SmartPtrModeling.cpp diff --git a/clang/lib/StaticAnalyzer/Checkers/ReturnValueChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ReturnValueChecker.cpp new file mode 100644 index 0000000000000..103208d8b5a51 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/ReturnValueChecker.cpp @@ -0,0 +1,170 @@ +//===- ReturnValueChecker - Applies guaranteed return values ----*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This defines ReturnValueChecker, which checks for calls with guaranteed +// boolean return value. It ensures the return value of each function call. +// +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/SmallVector.h" + +using namespace clang; +using namespace ento; + +namespace { +class ReturnValueChecker : public Checker { +public: + // It sets the predefined invariant ('CDM') if the current call not break it. + void checkPostCall(const CallEvent &Call, CheckerContext &C) const; + + // It reports whether a predefined invariant ('CDM') is broken. + void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const; + +private: + // The pairs are in the following form: {{{class, call}}, return value} + const CallDescriptionMap CDM = { + // These are known in the LLVM project: 'Error()' + {{{"ARMAsmParser", "Error"}}, true}, + {{{"HexagonAsmParser", "Error"}}, true}, + {{{"LLLexer", "Error"}}, true}, + {{{"LLParser", "Error"}}, true}, + {{{"MCAsmParser", "Error"}}, true}, + {{{"MCAsmParserExtension", "Error"}}, true}, + {{{"TGParser", "Error"}}, true}, + {{{"X86AsmParser", "Error"}}, true}, + // 'TokError()' + {{{"LLParser", "TokError"}}, true}, + {{{"MCAsmParser", "TokError"}}, true}, + {{{"MCAsmParserExtension", "TokError"}}, true}, + {{{"TGParser", "TokError"}}, true}, + // 'error()' + {{{"MIParser", "error"}}, true}, + {{{"WasmAsmParser", "error"}}, true}, + {{{"WebAssemblyAsmParser", "error"}}, true}, + // Other + {{{"AsmParser", "printError"}}, true}}; +}; +} // namespace + +static std::string getName(const CallEvent &Call) { + std::string Name = ""; + if (const auto *MD = dyn_cast(Call.getDecl())) + if (const CXXRecordDecl *RD = MD->getParent()) + Name += RD->getNameAsString() + "::"; + + Name += Call.getCalleeIdentifier()->getName(); + return Name; +} + +// The predefinitions ('CDM') could break due to the ever growing code base. +// Check for the expected invariants and see whether they apply. +static Optional isInvariantBreak(bool ExpectedValue, SVal ReturnV, + CheckerContext &C) { + auto ReturnDV = ReturnV.getAs(); + if (!ReturnDV) + return None; + + if (ExpectedValue) + return C.getState()->isNull(*ReturnDV).isConstrainedTrue(); + + return C.getState()->isNull(*ReturnDV).isConstrainedFalse(); +} + +void ReturnValueChecker::checkPostCall(const CallEvent &Call, + CheckerContext &C) const { + const bool *RawExpectedValue = CDM.lookup(Call); + if (!RawExpectedValue) + return; + + SVal ReturnV = Call.getReturnValue(); + bool ExpectedValue = *RawExpectedValue; + Optional IsInvariantBreak = isInvariantBreak(ExpectedValue, ReturnV, C); + if (!IsInvariantBreak) + return; + + // If the invariant is broken it is reported by 'checkEndFunction()'. + if (*IsInvariantBreak) + return; + + std::string Name = getName(Call); + const NoteTag *CallTag = C.getNoteTag( + [Name, ExpectedValue](BugReport &) -> std::string { + SmallString<128> Msg; + llvm::raw_svector_ostream Out(Msg); + + Out << '\'' << Name << "' returns " + << (ExpectedValue ? "true" : "false"); + return Out.str(); + }, + /*IsPrunable=*/true); + + ProgramStateRef State = C.getState(); + State = State->assume(ReturnV.castAs(), ExpectedValue); + C.addTransition(State, CallTag); +} + +void ReturnValueChecker::checkEndFunction(const ReturnStmt *RS, + CheckerContext &C) const { + if (!RS || !RS->getRetValue()) + return; + + // We cannot get the caller in the top-frame. + const StackFrameContext *SFC = C.getStackFrame(); + if (C.getStackFrame()->inTopFrame()) + return; + + ProgramStateRef State = C.getState(); + CallEventManager &CMgr = C.getStateManager().getCallEventManager(); + CallEventRef<> Call = CMgr.getCaller(SFC, State); + if (!Call) + return; + + const bool *RawExpectedValue = CDM.lookup(*Call); + if (!RawExpectedValue) + return; + + SVal ReturnV = State->getSVal(RS->getRetValue(), C.getLocationContext()); + bool ExpectedValue = *RawExpectedValue; + Optional IsInvariantBreak = isInvariantBreak(ExpectedValue, ReturnV, C); + if (!IsInvariantBreak) + return; + + // If the invariant is appropriate it is reported by 'checkPostCall()'. + if (!*IsInvariantBreak) + return; + + std::string Name = getName(*Call); + const NoteTag *CallTag = C.getNoteTag( + [Name, ExpectedValue](BugReport &BR) -> std::string { + SmallString<128> Msg; + llvm::raw_svector_ostream Out(Msg); + + // The following is swapped because the invariant is broken. + Out << '\'' << Name << "' returns " + << (ExpectedValue ? "false" : "true"); + + return Out.str(); + }, + /*IsPrunable=*/false); + + C.addTransition(State, CallTag); +} + +void ento::registerReturnValueChecker(CheckerManager &Mgr) { + Mgr.registerChecker(); +} + +bool ento::shouldRegisterReturnValueChecker(const LangOptions &LO) { + return true; +} diff --git a/clang/lib/StaticAnalyzer/Core/PathDiagnostic.cpp b/clang/lib/StaticAnalyzer/Core/PathDiagnostic.cpp index 1f642064827d5..54fbd6a5bc496 100644 --- a/clang/lib/StaticAnalyzer/Core/PathDiagnostic.cpp +++ b/clang/lib/StaticAnalyzer/Core/PathDiagnostic.cpp @@ -780,6 +780,9 @@ PathDiagnosticLocation::create(const ProgramPoint& P, NewAllocElt->getAllocatorExpr()->getBeginLoc(), SMng); } llvm_unreachable("Unexpected CFG element at front of block"); + } else if (Optional FE = P.getAs()) { + return PathDiagnosticLocation(FE->getStmt(), SMng, + FE->getLocationContext()); } else { llvm_unreachable("Unexpected ProgramPoint"); } diff --git a/clang/test/Analysis/return-value-guaranteed.cpp b/clang/test/Analysis/return-value-guaranteed.cpp new file mode 100644 index 0000000000000..6461a98a7ade3 --- /dev/null +++ b/clang/test/Analysis/return-value-guaranteed.cpp @@ -0,0 +1,91 @@ +// RUN: %clang_analyze_cc1 \ +// RUN: -analyzer-checker=core,apiModeling.ReturnValue \ +// RUN: -analyzer-output=text -verify=class %s + +struct Foo { int Field; }; +bool problem(); +void doSomething(); + +// We predefined the return value of 'MCAsmParser::Error' as true and we cannot +// take the false-branches which leads to a "garbage value" false positive. +namespace test_classes { +struct MCAsmParser { + static bool Error(); +}; + +bool parseFoo(Foo &F) { + if (problem()) { + // class-note@-1 {{Assuming the condition is false}} + // class-note@-2 {{Taking false branch}} + return MCAsmParser::Error(); + } + + F.Field = 0; + // class-note@-1 {{The value 0 is assigned to 'F.Field'}} + return !MCAsmParser::Error(); + // class-note@-1 {{'MCAsmParser::Error' returns true}} +} + +bool parseFile() { + Foo F; + if (parseFoo(F)) { + // class-note@-1 {{Calling 'parseFoo'}} + // class-note@-2 {{Returning from 'parseFoo'}} + // class-note@-3 {{Taking false branch}} + return true; + } + + if (F.Field == 0) { + // class-note@-1 {{Field 'Field' is equal to 0}} + // class-note@-2 {{Taking true branch}} + + // no-warning: "The left operand of '==' is a garbage value" was here. + doSomething(); + } + + (void)(1 / F.Field); + // class-warning@-1 {{Division by zero}} + // class-note@-2 {{Division by zero}} + return false; +} +} // namespace test_classes + + +// We predefined 'MCAsmParser::Error' as returning true, but now it returns +// false, which breaks our invariant. Test the notes. +namespace test_break { +struct MCAsmParser { + static bool Error() { + return false; // class-note {{'MCAsmParser::Error' returns false}} + } +}; + +bool parseFoo(Foo &F) { + if (problem()) { + // class-note@-1 {{Assuming the condition is false}} + // class-note@-2 {{Taking false branch}} + return !MCAsmParser::Error(); + } + + F.Field = 0; + // class-note@-1 {{The value 0 is assigned to 'F.Field'}} + return MCAsmParser::Error(); + // class-note@-1 {{Calling 'MCAsmParser::Error'}} + // class-note@-2 {{Returning from 'MCAsmParser::Error'}} +} + +bool parseFile() { + Foo F; + if (parseFoo(F)) { + // class-note@-1 {{Calling 'parseFoo'}} + // class-note@-2 {{Returning from 'parseFoo'}} + // class-note@-3 {{Taking false branch}} + return true; + } + + (void)(1 / F.Field); + // class-warning@-1 {{Division by zero}} + // class-note@-2 {{Division by zero}} + return false; +} +} // namespace test_classes From 697bc2b48ee6b354216e45c879ba7ab408b55275 Mon Sep 17 00:00:00 2001 From: Gabor Marton Date: Thu, 4 Jul 2019 11:39:00 +0000 Subject: [PATCH 047/181] [CTU] Add support for virtual functions Reviewers: Szelethus, xazax.hun Subscribers: rnkovacs, dkrupp, gamesh411, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D63920 llvm-svn: 365133 (cherry picked from commit e712295f11bb90d38c8749a338293eaba06d96a5) --- clang/lib/StaticAnalyzer/Core/CallEvent.cpp | 5 ++++- clang/test/Analysis/Inputs/ctu-other.cpp | 13 +++++++++++++ .../Inputs/ctu-other.cpp.externalDefMap.txt | 2 ++ clang/test/Analysis/ctu-main.cpp | 16 +++++++++++++++- 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp index 81a9ee4d90b0a..a5f7500e63074 100644 --- a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp +++ b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp @@ -766,8 +766,11 @@ RuntimeDefinition CXXInstanceCall::getRuntimeDefinition() const { // Does the decl that we found have an implementation? const FunctionDecl *Definition; - if (!Result->hasBody(Definition)) + if (!Result->hasBody(Definition)) { + if (!DynType.canBeASubClass()) + return AnyFunctionCall::getRuntimeDefinition(); return {}; + } // We found a definition. If we're not sure that this devirtualization is // actually what will happen at runtime, make sure to provide the region so diff --git a/clang/test/Analysis/Inputs/ctu-other.cpp b/clang/test/Analysis/Inputs/ctu-other.cpp index de7d064135f4d..a9ff6b5a93a57 100644 --- a/clang/test/Analysis/Inputs/ctu-other.cpp +++ b/clang/test/Analysis/Inputs/ctu-other.cpp @@ -38,6 +38,7 @@ int embed_cls::fecl(int x) { class mycls { public: int fcl(int x); + virtual int fvcl(int x); static int fscl(int x); class embed_cls2 { @@ -49,6 +50,9 @@ class mycls { int mycls::fcl(int x) { return x + 5; } +int mycls::fvcl(int x) { + return x + 7; +} int mycls::fscl(int x) { return x + 6; } @@ -56,6 +60,15 @@ int mycls::embed_cls2::fecl2(int x) { return x - 11; } +class derived : public mycls { +public: + virtual int fvcl(int x) override; +}; + +int derived::fvcl(int x) { + return x + 8; +} + namespace chns { int chf2(int x); diff --git a/clang/test/Analysis/Inputs/ctu-other.cpp.externalDefMap.txt b/clang/test/Analysis/Inputs/ctu-other.cpp.externalDefMap.txt index 57f4194831afa..3df181b29d51e 100644 --- a/clang/test/Analysis/Inputs/ctu-other.cpp.externalDefMap.txt +++ b/clang/test/Analysis/Inputs/ctu-other.cpp.externalDefMap.txt @@ -3,8 +3,10 @@ c:@N@myns@N@embed_ns@F@fens#I# ctu-other.cpp.ast c:@F@g#I# ctu-other.cpp.ast c:@S@mycls@F@fscl#I#S ctu-other.cpp.ast c:@S@mycls@F@fcl#I# ctu-other.cpp.ast +c:@S@mycls@F@fvcl#I# ctu-other.cpp.ast c:@N@myns@S@embed_cls@F@fecl#I# ctu-other.cpp.ast c:@S@mycls@S@embed_cls2@F@fecl2#I# ctu-other.cpp.ast +c:@S@derived@F@fvcl#I# ctu-other.cpp.ast c:@F@f#I# ctu-other.cpp.ast c:@N@myns@F@fns#I# ctu-other.cpp.ast c:@F@h#I# ctu-other.cpp.ast diff --git a/clang/test/Analysis/ctu-main.cpp b/clang/test/Analysis/ctu-main.cpp index a5de18bb3ec62..1cb0d4a9d774e 100644 --- a/clang/test/Analysis/ctu-main.cpp +++ b/clang/test/Analysis/ctu-main.cpp @@ -45,6 +45,7 @@ class embed_cls { class mycls { public: int fcl(int x); + virtual int fvcl(int x); static int fscl(int x); class embed_cls2 { @@ -53,6 +54,11 @@ class mycls { }; }; +class derived : public mycls { +public: + virtual int fvcl(int x) override; +}; + namespace chns { int chf1(int x); } @@ -98,6 +104,14 @@ union U { }; extern U extU; +void test_virtual_functions(mycls* obj) { + // The dynamic type is known. + clang_analyzer_eval(mycls().fvcl(1) == 8); // expected-warning{{TRUE}} + clang_analyzer_eval(derived().fvcl(1) == 9); // expected-warning{{TRUE}} + // We cannot decide about the dynamic type. + clang_analyzer_eval(obj->fvcl(1) == 8); // expected-warning{{FALSE}} expected-warning{{TRUE}} +} + int main() { clang_analyzer_eval(f(3) == 2); // expected-warning{{TRUE}} clang_analyzer_eval(f(4) == 3); // expected-warning{{TRUE}} @@ -116,7 +130,7 @@ int main() { clang_analyzer_eval(fun_using_anon_struct(8) == 8); // expected-warning{{TRUE}} clang_analyzer_eval(other_macro_diag(1) == 1); // expected-warning{{TRUE}} - // expected-warning@Inputs/ctu-other.cpp:80{{REACHABLE}} + // expected-warning@Inputs/ctu-other.cpp:93{{REACHABLE}} MACRODIAG(); // expected-warning{{REACHABLE}} clang_analyzer_eval(extInt == 2); // expected-warning{{TRUE}} From 727894b511d901a0947683fdae4267a9797a992d Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Fri, 5 Jul 2019 09:52:00 +0000 Subject: [PATCH 048/181] [CFG] Add a new function to get the proper condition of a CFGBlock getTerminatorCondition() returned a condition that may be outside of the block, while the new function returns the proper one: if (A && B && C) {} Return C instead of A && B && C. Differential Revision: https://reviews.llvm.org/D63538 llvm-svn: 365177 (cherry picked from commit d5c9d9b6820f269f105ac278ba26d040630fe901) --- clang/include/clang/Analysis/CFG.h | 8 +++++ clang/lib/Analysis/CFG.cpp | 24 +++++++++++++ clang/unittests/Analysis/CFGTest.cpp | 52 ++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+) diff --git a/clang/include/clang/Analysis/CFG.h b/clang/include/clang/Analysis/CFG.h index d8b3d6ff71ede..c2f49be123f83 100644 --- a/clang/include/clang/Analysis/CFG.h +++ b/clang/include/clang/Analysis/CFG.h @@ -860,6 +860,14 @@ class CFGBlock { Stmt *getTerminatorStmt() { return Terminator.getStmt(); } const Stmt *getTerminatorStmt() const { return Terminator.getStmt(); } + /// \returns the last (\c rbegin()) condition, e.g. observe the following code + /// snippet: + /// if (A && B && C) + /// A block would be created for \c A, \c B, and \c C. For the latter, + /// \c getTerminatorStmt() would retrieve the entire condition, rather than + /// C itself, while this method would only return C. + const Expr *getLastCondition() const; + Stmt *getTerminatorCondition(bool StripParens = true); const Stmt *getTerminatorCondition(bool StripParens = true) const { diff --git a/clang/lib/Analysis/CFG.cpp b/clang/lib/Analysis/CFG.cpp index b53bfcca37cd4..80f5a46ceab47 100644 --- a/clang/lib/Analysis/CFG.cpp +++ b/clang/lib/Analysis/CFG.cpp @@ -5615,6 +5615,30 @@ void CFGBlock::printTerminatorJson(raw_ostream &Out, const LangOptions &LO, Out << JsonFormat(TempOut.str(), AddQuotes); } +const Expr *CFGBlock::getLastCondition() const { + // If the terminator is a temporary dtor or a virtual base, etc, we can't + // retrieve a meaningful condition, bail out. + if (Terminator.getKind() != CFGTerminator::StmtBranch) + return nullptr; + + // Also, if this method was called on a block that doesn't have 2 successors, + // this block doesn't have retrievable condition. + if (succ_size() < 2) + return nullptr; + + auto StmtElem = rbegin()->getAs(); + if (!StmtElem) + return nullptr; + + const Stmt *Cond = StmtElem->getStmt(); + if (isa(Cond)) + return nullptr; + + // Only ObjCForCollectionStmt is known not to be a non-Expr terminator, hence + // the cast<>. + return cast(Cond)->IgnoreParens(); +} + Stmt *CFGBlock::getTerminatorCondition(bool StripParens) { Stmt *Terminator = getTerminatorStmt(); if (!Terminator) diff --git a/clang/unittests/Analysis/CFGTest.cpp b/clang/unittests/Analysis/CFGTest.cpp index 2c2522d2620dc..69cf78b97371f 100644 --- a/clang/unittests/Analysis/CFGTest.cpp +++ b/clang/unittests/Analysis/CFGTest.cpp @@ -117,6 +117,58 @@ TEST(CFG, IsLinear) { expectLinear(true, "void foo() { foo(); }"); // Recursion is not our problem. } +TEST(CFG, ConditionExpr) { + const char *Code = R"(void f(bool A, bool B, bool C) { + if (A && B && C) + int x; + })"; + BuildResult Result = BuildCFG(Code); + EXPECT_EQ(BuildResult::BuiltCFG, Result.getStatus()); + + // [B5 (ENTRY)] -> [B4] -> [B3] -> [B2] -> [B1] -> [B0 (EXIT)] + // \ \ \ / + // -------------------------------> + + CFG *cfg = Result.getCFG(); + + auto GetBlock = [cfg] (unsigned Index) -> CFGBlock * { + assert(Index < cfg->size()); + return *(cfg->begin() + Index); + }; + + auto GetExprText = [] (const Expr *E) -> std::string { + // It's very awkward trying to recover the actual expression text without + // a real source file, so use this as a workaround. We know that the + // condition expression looks like this: + // + // ImplicitCastExpr 0xd07bf8 '_Bool' + // `-DeclRefExpr 0xd07bd8 '_Bool' lvalue ParmVar 0xd07960 'C' '_Bool' + + assert(isa(E)); + assert(++E->child_begin() == E->child_end()); + const auto *D = dyn_cast(*E->child_begin()); + return D->getFoundDecl()->getNameAsString(); + }; + + EXPECT_EQ(GetBlock(1)->getLastCondition(), nullptr); + EXPECT_EQ(GetExprText(GetBlock(4)->getLastCondition()), "A"); + EXPECT_EQ(GetExprText(GetBlock(3)->getLastCondition()), "B"); + EXPECT_EQ(GetExprText(GetBlock(2)->getLastCondition()), "C"); + + //===--------------------------------------------------------------------===// + + Code = R"(void foo(int x, int y) { + (void)(x + y); + })"; + Result = BuildCFG(Code); + EXPECT_EQ(BuildResult::BuiltCFG, Result.getStatus()); + + // [B2 (ENTRY)] -> [B1] -> [B0 (EXIT)] + + cfg = Result.getCFG(); + EXPECT_EQ(GetBlock(1)->getLastCondition(), nullptr); +} + } // namespace } // namespace analysis } // namespace clang From 7197458150c4c3e2f700e0a0a26b08dcab489c96 Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Fri, 5 Jul 2019 10:16:36 +0000 Subject: [PATCH 049/181] [analyzer][Dominators][NFC] Add unit tests Differential Revision: https://reviews.llvm.org/D62611 llvm-svn: 365179 (cherry picked from commit 2e2db937cd2f3ee2caa3a05f80a761b42dcfb8cb) --- clang/unittests/Analysis/CFGBuildResult.h | 69 ++++++++++++ clang/unittests/Analysis/CFGDominatorTree.cpp | 103 ++++++++++++++++++ clang/unittests/Analysis/CFGTest.cpp | 52 +-------- clang/unittests/Analysis/CMakeLists.txt | 1 + 4 files changed, 174 insertions(+), 51 deletions(-) create mode 100644 clang/unittests/Analysis/CFGBuildResult.h create mode 100644 clang/unittests/Analysis/CFGDominatorTree.cpp diff --git a/clang/unittests/Analysis/CFGBuildResult.h b/clang/unittests/Analysis/CFGBuildResult.h new file mode 100644 index 0000000000000..17511dcd46c8e --- /dev/null +++ b/clang/unittests/Analysis/CFGBuildResult.h @@ -0,0 +1,69 @@ +//===- unittests/Analysis/CFGBuildResult.h - CFG tests --------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "clang/Analysis/CFG.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Tooling/Tooling.h" + +namespace clang { +namespace analysis { + +class BuildResult { +public: + enum Status { + ToolFailed, + ToolRan, + SawFunctionBody, + BuiltCFG, + }; + + BuildResult(Status S, std::unique_ptr Cfg = nullptr) + : S(S), Cfg(std::move(Cfg)) {} + + Status getStatus() const { return S; } + CFG *getCFG() const { return Cfg.get(); } + +private: + Status S; + std::unique_ptr Cfg; +}; + +class CFGCallback : public ast_matchers::MatchFinder::MatchCallback { +public: + BuildResult TheBuildResult = BuildResult::ToolRan; + + void run(const ast_matchers::MatchFinder::MatchResult &Result) override { + const auto *Func = Result.Nodes.getNodeAs("func"); + Stmt *Body = Func->getBody(); + if (!Body) + return; + TheBuildResult = BuildResult::SawFunctionBody; + CFG::BuildOptions Options; + Options.AddImplicitDtors = true; + if (std::unique_ptr Cfg = + CFG::buildCFG(nullptr, Body, Result.Context, Options)) + TheBuildResult = {BuildResult::BuiltCFG, std::move(Cfg)}; + } +}; + +inline BuildResult BuildCFG(const char *Code) { + CFGCallback Callback; + + ast_matchers::MatchFinder Finder; + Finder.addMatcher(ast_matchers::functionDecl().bind("func"), &Callback); + std::unique_ptr Factory( + tooling::newFrontendActionFactory(&Finder)); + std::vector Args = {"-std=c++11", + "-fno-delayed-template-parsing"}; + if (!tooling::runToolOnCodeWithArgs(Factory->create(), Code, Args)) + return BuildResult::ToolFailed; + return std::move(Callback.TheBuildResult); +} + +} // namespace analysis +} // namespace clang diff --git a/clang/unittests/Analysis/CFGDominatorTree.cpp b/clang/unittests/Analysis/CFGDominatorTree.cpp new file mode 100644 index 0000000000000..1df8c2f7d05d5 --- /dev/null +++ b/clang/unittests/Analysis/CFGDominatorTree.cpp @@ -0,0 +1,103 @@ +//===- unittests/Analysis/CFGDominatorTree.cpp - CFG tests ----------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "CFGBuildResult.h" +#include "clang/Analysis/Analyses/Dominators.h" +#include "gtest/gtest.h" + +namespace clang { +namespace analysis { +namespace { + +TEST(CFGDominatorTree, DomTree) { + const char *Code = R"(enum Kind { + A + }; + + void f() { + switch(Kind{}) { + case A: + break; + } + })"; + BuildResult Result = BuildCFG(Code); + EXPECT_EQ(BuildResult::BuiltCFG, Result.getStatus()); + + // [B3 (ENTRY)] -> [B1] -> [B2] -> [B0 (EXIT)] + // switch case A + + CFG *cfg = Result.getCFG(); + + // Sanity checks. + EXPECT_EQ(cfg->size(), 4u); + + CFGBlock *ExitBlock = *cfg->begin(); + EXPECT_EQ(ExitBlock, &cfg->getExit()); + + CFGBlock *SwitchBlock = *(cfg->begin() + 1); + + CFGBlock *CaseABlock = *(cfg->begin() + 2); + + CFGBlock *EntryBlock = *(cfg->begin() + 3); + EXPECT_EQ(EntryBlock, &cfg->getEntry()); + + // Test the dominator tree. + CFGDomTree Dom; + Dom.buildDominatorTree(cfg); + + EXPECT_TRUE(Dom.dominates(ExitBlock, ExitBlock)); + EXPECT_FALSE(Dom.properlyDominates(ExitBlock, ExitBlock)); + EXPECT_TRUE(Dom.dominates(CaseABlock, ExitBlock)); + EXPECT_TRUE(Dom.dominates(SwitchBlock, ExitBlock)); + EXPECT_TRUE(Dom.dominates(EntryBlock, ExitBlock)); + + EXPECT_TRUE(Dom.dominates(CaseABlock, CaseABlock)); + EXPECT_FALSE(Dom.properlyDominates(CaseABlock, CaseABlock)); + EXPECT_TRUE(Dom.dominates(SwitchBlock, CaseABlock)); + EXPECT_TRUE(Dom.dominates(EntryBlock, CaseABlock)); + + EXPECT_TRUE(Dom.dominates(SwitchBlock, SwitchBlock)); + EXPECT_FALSE(Dom.properlyDominates(SwitchBlock, SwitchBlock)); + EXPECT_TRUE(Dom.dominates(EntryBlock, SwitchBlock)); + + EXPECT_TRUE(Dom.dominates(EntryBlock, EntryBlock)); + EXPECT_FALSE(Dom.properlyDominates(EntryBlock, EntryBlock)); + + // Test the post dominator tree. + + CFGPostDomTree PostDom; + PostDom.buildDominatorTree(cfg); + + EXPECT_TRUE(PostDom.dominates(ExitBlock, EntryBlock)); + EXPECT_TRUE(PostDom.dominates(CaseABlock, EntryBlock)); + EXPECT_TRUE(PostDom.dominates(SwitchBlock, EntryBlock)); + EXPECT_TRUE(PostDom.dominates(EntryBlock, EntryBlock)); + EXPECT_FALSE(Dom.properlyDominates(EntryBlock, EntryBlock)); + + EXPECT_TRUE(PostDom.dominates(ExitBlock, SwitchBlock)); + EXPECT_TRUE(PostDom.dominates(CaseABlock, SwitchBlock)); + EXPECT_TRUE(PostDom.dominates(SwitchBlock, SwitchBlock)); + EXPECT_FALSE(Dom.properlyDominates(SwitchBlock, SwitchBlock)); + + EXPECT_TRUE(PostDom.dominates(ExitBlock, CaseABlock)); + EXPECT_TRUE(PostDom.dominates(CaseABlock, CaseABlock)); + EXPECT_FALSE(Dom.properlyDominates(CaseABlock, CaseABlock)); + + EXPECT_TRUE(PostDom.dominates(ExitBlock, ExitBlock)); + EXPECT_FALSE(Dom.properlyDominates(ExitBlock, ExitBlock)); + + // Tests for the post dominator tree's virtual root. + EXPECT_TRUE(PostDom.dominates(nullptr, EntryBlock)); + EXPECT_TRUE(PostDom.dominates(nullptr, SwitchBlock)); + EXPECT_TRUE(PostDom.dominates(nullptr, CaseABlock)); + EXPECT_TRUE(PostDom.dominates(nullptr, ExitBlock)); +} + +} // namespace +} // namespace analysis +} // namespace clang diff --git a/clang/unittests/Analysis/CFGTest.cpp b/clang/unittests/Analysis/CFGTest.cpp index 69cf78b97371f..bf0116050adc7 100644 --- a/clang/unittests/Analysis/CFGTest.cpp +++ b/clang/unittests/Analysis/CFGTest.cpp @@ -6,6 +6,7 @@ // //===----------------------------------------------------------------------===// +#include "CFGBuildResult.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/Analysis/CFG.h" #include "clang/Tooling/Tooling.h" @@ -17,57 +18,6 @@ namespace clang { namespace analysis { namespace { -class BuildResult { -public: - enum Status { - ToolFailed, - ToolRan, - SawFunctionBody, - BuiltCFG, - }; - - BuildResult(Status S, std::unique_ptr Cfg = nullptr) - : S(S), Cfg(std::move(Cfg)) {} - - Status getStatus() const { return S; } - CFG *getCFG() const { return Cfg.get(); } - -private: - Status S; - std::unique_ptr Cfg; -}; - -class CFGCallback : public ast_matchers::MatchFinder::MatchCallback { -public: - BuildResult TheBuildResult = BuildResult::ToolRan; - - void run(const ast_matchers::MatchFinder::MatchResult &Result) override { - const auto *Func = Result.Nodes.getNodeAs("func"); - Stmt *Body = Func->getBody(); - if (!Body) - return; - TheBuildResult = BuildResult::SawFunctionBody; - CFG::BuildOptions Options; - Options.AddImplicitDtors = true; - if (std::unique_ptr Cfg = - CFG::buildCFG(nullptr, Body, Result.Context, Options)) - TheBuildResult = {BuildResult::BuiltCFG, std::move(Cfg)}; - } -}; - -BuildResult BuildCFG(const char *Code) { - CFGCallback Callback; - - ast_matchers::MatchFinder Finder; - Finder.addMatcher(ast_matchers::functionDecl().bind("func"), &Callback); - std::unique_ptr Factory( - tooling::newFrontendActionFactory(&Finder)); - std::vector Args = {"-std=c++11", "-fno-delayed-template-parsing"}; - if (!tooling::runToolOnCodeWithArgs(Factory->create(), Code, Args)) - return BuildResult::ToolFailed; - return std::move(Callback.TheBuildResult); -} - // Constructing a CFG for a range-based for over a dependent type fails (but // should not crash). TEST(CFG, RangeBasedForOverDependentType) { diff --git a/clang/unittests/Analysis/CMakeLists.txt b/clang/unittests/Analysis/CMakeLists.txt index c760ae2d82b7e..e1f7a16a450d3 100644 --- a/clang/unittests/Analysis/CMakeLists.txt +++ b/clang/unittests/Analysis/CMakeLists.txt @@ -3,6 +3,7 @@ set(LLVM_LINK_COMPONENTS ) add_clang_unittest(ClangAnalysisTests + CFGDominatorTree.cpp CFGTest.cpp CloneDetectionTest.cpp ExprMutationAnalyzerTest.cpp From e3c67d051763d79dba65c7d971361c6a25854c62 Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Fri, 5 Jul 2019 11:14:57 +0000 Subject: [PATCH 050/181] Fix a buildbot failure due to the AST's lifetime ending before the test llvm-svn: 365181 (cherry picked from commit 433edaed127c16fd641d9515d1340a01086058ff) --- clang/unittests/Analysis/CFGTest.cpp | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/clang/unittests/Analysis/CFGTest.cpp b/clang/unittests/Analysis/CFGTest.cpp index bf0116050adc7..2ab3b6403a09b 100644 --- a/clang/unittests/Analysis/CFGTest.cpp +++ b/clang/unittests/Analysis/CFGTest.cpp @@ -86,24 +86,10 @@ TEST(CFG, ConditionExpr) { return *(cfg->begin() + Index); }; - auto GetExprText = [] (const Expr *E) -> std::string { - // It's very awkward trying to recover the actual expression text without - // a real source file, so use this as a workaround. We know that the - // condition expression looks like this: - // - // ImplicitCastExpr 0xd07bf8 '_Bool' - // `-DeclRefExpr 0xd07bd8 '_Bool' lvalue ParmVar 0xd07960 'C' '_Bool' - - assert(isa(E)); - assert(++E->child_begin() == E->child_end()); - const auto *D = dyn_cast(*E->child_begin()); - return D->getFoundDecl()->getNameAsString(); - }; - EXPECT_EQ(GetBlock(1)->getLastCondition(), nullptr); - EXPECT_EQ(GetExprText(GetBlock(4)->getLastCondition()), "A"); - EXPECT_EQ(GetExprText(GetBlock(3)->getLastCondition()), "B"); - EXPECT_EQ(GetExprText(GetBlock(2)->getLastCondition()), "C"); + // Unfortunately, we can't check whether the correct Expr was returned by + // getLastCondition, because the lifetime of the AST ends by the time we + // retrieve the CFG. //===--------------------------------------------------------------------===// From 224dd18ee414dac1223b65ace506676c168dd417 Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Fri, 5 Jul 2019 12:17:44 +0000 Subject: [PATCH 051/181] [analyzer][IDF] Add a control dependency calculator + a new debug checker I intend to improve the analyzer's bug reports by tracking condition expressions. 01 bool b = messyComputation(); 02 int i = 0; 03 if (b) // control dependency of the bug site, let's explain why we assume val 04 // to be true 05 10 / i; // warn: division by zero I'll detail this heuristic in the followup patch, strictly related to this one however: * Create the new ControlDependencyCalculator class that uses llvm::IDFCalculator to (lazily) calculate control dependencies for Clang's CFG. * A new debug checker debug.DumpControlDependencies is added for lit tests * Add unittests Differential Revision: https://reviews.llvm.org/D62619 llvm-svn: 365197 (cherry picked from commit 5e17ee1e35e4535b51c749186ffbec694b06494b) --- .../clang/Analysis/Analyses/Dominators.h | 97 ++++++++++++++++++- clang/include/clang/Analysis/CFG.h | 12 +++ .../clang/StaticAnalyzer/Checkers/Checkers.td | 4 + .../StaticAnalyzer/Checkers/DebugCheckers.cpp | 37 +++++-- clang/test/Analysis/domtest.c | 92 +++++++++++++++--- clang/test/Analysis/domtest.cpp | 21 ++-- clang/unittests/Analysis/CFGDominatorTree.cpp | 91 +++++++++++++++++ 7 files changed, 326 insertions(+), 28 deletions(-) diff --git a/clang/include/clang/Analysis/Analyses/Dominators.h b/clang/include/clang/Analysis/Analyses/Dominators.h index bdd48198d0d59..bc672eb7d52f3 100644 --- a/clang/include/clang/Analysis/Analyses/Dominators.h +++ b/clang/include/clang/Analysis/Analyses/Dominators.h @@ -18,6 +18,7 @@ #include "llvm/ADT/DepthFirstIterator.h" #include "llvm/ADT/GraphTraits.h" #include "llvm/ADT/iterator.h" +#include "llvm/Support/GenericIteratedDominanceFrontier.h" #include "llvm/Support/GenericDomTree.h" #include "llvm/Support/GenericDomTreeConstruction.h" #include "llvm/Support/raw_ostream.h" @@ -44,12 +45,17 @@ class CFGDominatorTreeImpl : public ManagedAnalysis { public: using DominatorTreeBase = llvm::DominatorTreeBase; - DominatorTreeBase DT; - CFGDominatorTreeImpl() = default; + + CFGDominatorTreeImpl(CFG *cfg) { + buildDominatorTree(cfg); + } + ~CFGDominatorTreeImpl() override = default; - DominatorTreeBase& getBase() { return *DT; } + DominatorTreeBase &getBase() { return DT; } + + CFG *getCFG() { return cfg; } /// \returns the root CFGBlock of the dominators tree. CFGBlock *getRoot() const { @@ -172,11 +178,96 @@ class CFGDominatorTreeImpl : public ManagedAnalysis { private: CFG *cfg; + DominatorTreeBase DT; }; using CFGDomTree = CFGDominatorTreeImpl; using CFGPostDomTree = CFGDominatorTreeImpl; +} // end of namespace clang + +namespace llvm { +namespace IDFCalculatorDetail { + +/// Specialize ChildrenGetterTy to skip nullpointer successors. +template +struct ChildrenGetterTy { + using NodeRef = typename GraphTraits::NodeRef; + using ChildrenTy = SmallVector; + + ChildrenTy get(const NodeRef &N) { + using OrderedNodeTy = + typename IDFCalculatorBase::OrderedNodeTy; + + auto Children = children(N); + ChildrenTy Ret{Children.begin(), Children.end()}; + Ret.erase(std::remove(Ret.begin(), Ret.end(), nullptr), Ret.end()); + return Ret; + } +}; + +} // end of namespace IDFCalculatorDetail +} // end of namespace llvm + +namespace clang { + +class ControlDependencyCalculator : public ManagedAnalysis { + using IDFCalculator = llvm::IDFCalculatorBase; + using CFGBlockVector = llvm::SmallVector; + using CFGBlockSet = llvm::SmallPtrSet; + + CFGPostDomTree PostDomTree; + IDFCalculator IDFCalc; + + llvm::DenseMap ControlDepenencyMap; + +public: + ControlDependencyCalculator(CFG *cfg) + : PostDomTree(cfg), IDFCalc(PostDomTree.getBase()) {} + + const CFGPostDomTree &getCFGPostDomTree() const { return PostDomTree; } + + // Lazily retrieves the set of control dependencies to \p A. + const CFGBlockVector &getControlDependencies(CFGBlock *A) { + auto It = ControlDepenencyMap.find(A); + if (It == ControlDepenencyMap.end()) { + CFGBlockSet DefiningBlock = {A}; + IDFCalc.setDefiningBlocks(DefiningBlock); + + CFGBlockVector ControlDependencies; + IDFCalc.calculate(ControlDependencies); + + It = ControlDepenencyMap.insert({A, ControlDependencies}).first; + } + + assert(It != ControlDepenencyMap.end()); + return It->second; + } + + /// Whether \p A is control dependent on \p B. + bool isControlDependent(CFGBlock *A, CFGBlock *B) { + return llvm::is_contained(getControlDependencies(A), B); + } + + // Dumps immediate control dependencies for each block. + LLVM_DUMP_METHOD void dump() { + CFG *cfg = PostDomTree.getCFG(); + llvm::errs() << "Control dependencies (Node#,Dependency#):\n"; + for (CFGBlock *BB : *cfg) { + + assert(BB && + "LLVM's Dominator tree builder uses nullpointers to signify the " + "virtual root!"); + + for (CFGBlock *isControlDependency : getControlDependencies(BB)) + llvm::errs() << "(" << BB->getBlockID() + << "," + << isControlDependency->getBlockID() + << ")\n"; + } + } +}; + } // namespace clang namespace llvm { diff --git a/clang/include/clang/Analysis/CFG.h b/clang/include/clang/Analysis/CFG.h index c2f49be123f83..277b2292e5eac 100644 --- a/clang/include/clang/Analysis/CFG.h +++ b/clang/include/clang/Analysis/CFG.h @@ -1285,6 +1285,9 @@ template <> struct GraphTraits< ::clang::CFGBlock *> { static ChildIteratorType child_end(NodeRef N) { return N->succ_end(); } }; +template <> struct GraphTraits + : GraphTraits {}; + template <> struct GraphTraits< const ::clang::CFGBlock *> { using NodeRef = const ::clang::CFGBlock *; using ChildIteratorType = ::clang::CFGBlock::const_succ_iterator; @@ -1294,6 +1297,9 @@ template <> struct GraphTraits< const ::clang::CFGBlock *> { static ChildIteratorType child_end(NodeRef N) { return N->succ_end(); } }; +template <> struct GraphTraits + : GraphTraits {}; + template <> struct GraphTraits> { using NodeRef = ::clang::CFGBlock *; using ChildIteratorType = ::clang::CFGBlock::const_pred_iterator; @@ -1306,6 +1312,9 @@ template <> struct GraphTraits> { static ChildIteratorType child_end(NodeRef N) { return N->pred_end(); } }; +template <> struct GraphTraits> + : GraphTraits {}; + template <> struct GraphTraits> { using NodeRef = const ::clang::CFGBlock *; using ChildIteratorType = ::clang::CFGBlock::const_pred_iterator; @@ -1318,6 +1327,9 @@ template <> struct GraphTraits> { static ChildIteratorType child_end(NodeRef N) { return N->pred_end(); } }; +template <> struct GraphTraits> + : GraphTraits {}; + // Traits for: CFG template <> struct GraphTraits< ::clang::CFG* > diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index 8e0122bcc8379..95ea2d5234ea5 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -1237,6 +1237,10 @@ def PostDominatorsTreeDumper : Checker<"DumpPostDominators">, HelpText<"Print the post dominance tree for a given CFG">, Documentation; +def ControlDependencyTreeDumper : Checker<"DumpControlDependencies">, + HelpText<"Print the post control dependency tree for a given CFG">, + Documentation; + def LiveVariablesDumper : Checker<"DumpLiveVars">, HelpText<"Print results of live variable analysis">, Documentation; diff --git a/clang/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp b/clang/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp index 6d32b9191419d..bb9e8110b6479 100644 --- a/clang/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp @@ -35,9 +35,9 @@ class DominatorsTreeDumper : public Checker { void checkASTCodeBody(const Decl *D, AnalysisManager& mgr, BugReporter &BR) const { if (AnalysisDeclContext *AC = mgr.getAnalysisDeclContext(D)) { - CFGDomTree dom; - dom.buildDominatorTree(AC->getCFG()); - dom.dump(); + CFGDomTree Dom; + Dom.buildDominatorTree(AC->getCFG()); + Dom.dump(); } } }; @@ -61,9 +61,9 @@ class PostDominatorsTreeDumper : public Checker { void checkASTCodeBody(const Decl *D, AnalysisManager& mgr, BugReporter &BR) const { if (AnalysisDeclContext *AC = mgr.getAnalysisDeclContext(D)) { - CFGPostDomTree dom; - dom.buildDominatorTree(AC->getCFG()); - dom.dump(); + CFGPostDomTree Dom; + Dom.buildDominatorTree(AC->getCFG()); + Dom.dump(); } } }; @@ -77,6 +77,31 @@ bool ento::shouldRegisterPostDominatorsTreeDumper(const LangOptions &LO) { return true; } +//===----------------------------------------------------------------------===// +// ControlDependencyTreeDumper +//===----------------------------------------------------------------------===// + +namespace { +class ControlDependencyTreeDumper : public Checker { +public: + void checkASTCodeBody(const Decl *D, AnalysisManager& mgr, + BugReporter &BR) const { + if (AnalysisDeclContext *AC = mgr.getAnalysisDeclContext(D)) { + ControlDependencyCalculator Dom(AC->getCFG()); + Dom.dump(); + } + } +}; +} + +void ento::registerControlDependencyTreeDumper(CheckerManager &mgr) { + mgr.registerChecker(); +} + +bool ento::shouldRegisterControlDependencyTreeDumper(const LangOptions &LO) { + return true; +} + //===----------------------------------------------------------------------===// // LiveVariablesDumper //===----------------------------------------------------------------------===// diff --git a/clang/test/Analysis/domtest.c b/clang/test/Analysis/domtest.c index 91cd7de420fa0..b642bd35319cf 100644 --- a/clang/test/Analysis/domtest.c +++ b/clang/test/Analysis/domtest.c @@ -1,6 +1,7 @@ // RUN: %clang_analyze_cc1 %s \ // RUN: -analyzer-checker=debug.DumpDominators \ // RUN: -analyzer-checker=debug.DumpPostDominators \ +// RUN: -analyzer-checker=debug.DumpControlDependencies \ // RUN: 2>&1 | FileCheck %s // Test the DominatorsTree implementation with various control flows @@ -32,7 +33,16 @@ int test1() // V // [B1] -> [B0 (EXIT)] -// CHECK: Immediate dominance tree (Node#,IDom#): +// CHECK: Control dependencies (Node#,Dependency#): +// CHECK-NEXT: (2,7) +// CHECK-NEXT: (3,7) +// CHECK-NEXT: (4,6) +// CHECK-NEXT: (4,7) +// CHECK-NEXT: (5,6) +// CHECK-NEXT: (5,7) +// CHECK-NEXT: (6,7) +// CHECK-NEXT: (7,7) +// CHECK-NEXT: Immediate dominance tree (Node#,IDom#): // CHECK-NEXT: (0,1) // CHECK-NEXT: (1,7) // CHECK-NEXT: (2,3) @@ -80,7 +90,15 @@ int test2() // / V // [B7 (ENTRY)] -> [B6] -> [B5] -> [B1] -> [B0 (EXIT)] -// CHECK: Immediate dominance tree (Node#,IDom#): +// CHECK: Control dependencies (Node#,Dependency#): +// CHECK-NEXT: (2,4) +// CHECK-NEXT: (2,6) +// CHECK-NEXT: (3,4) +// CHECK-NEXT: (3,6) +// CHECK-NEXT: (4,6) +// CHECK-NEXT: (4,4) +// CHECK-NEXT: (5,6) +// CHECK-NEXT: Immediate dominance tree (Node#,IDom#): // CHECK-NEXT: (0,1) // CHECK-NEXT: (1,6) // CHECK-NEXT: (2,3) @@ -117,17 +135,29 @@ int test3() return 0; } -// <------------- -// / \ -// | ---> [B2] -// | / -// [B8 (ENTRY)] -> [B7] -> [B6] -> [B5] -> [B4] -> [B3] -// \ | \ / -// \ | <------------- +// <- [B2] <- +// / \ +// [B8 (ENTRY)] -> [B7] -> [B6] ---> [B5] -> [B4] -> [B3] +// \ | \ / +// \ | <------------- // \ \ // --------> [B1] -> [B0 (EXIT)] -// CHECK: Immediate dominance tree (Node#,IDom#): +// CHECK: Control dependencies (Node#,Dependency#): +// CHECK-NEXT: (2,6) +// CHECK-NEXT: (2,7) +// CHECK-NEXT: (3,5) +// CHECK-NEXT: (3,6) +// CHECK-NEXT: (3,7) +// CHECK-NEXT: (4,5) +// CHECK-NEXT: (4,6) +// CHECK-NEXT: (4,7) +// CHECK-NEXT: (5,6) +// CHECK-NEXT: (5,5) +// CHECK-NEXT: (5,7) +// CHECK-NEXT: (6,7) +// CHECK-NEXT: (6,6) +// CHECK-NEXT: Immediate dominance tree (Node#,IDom#): // CHECK-NEXT: (0,1) // CHECK-NEXT: (1,7) // CHECK-NEXT: (2,5) @@ -176,7 +206,29 @@ int test4() // \ // -> [B1] -> [B0 (EXIT)] -// CHECK: Immediate dominance tree (Node#,IDom#): +// CHECK: Control dependencies (Node#,Dependency#): +// CHECK-NEXT: (2,10) +// CHECK-NEXT: (3,5) +// CHECK-NEXT: (3,9) +// CHECK-NEXT: (3,10) +// CHECK-NEXT: (4,5) +// CHECK-NEXT: (4,9) +// CHECK-NEXT: (4,10) +// CHECK-NEXT: (5,9) +// CHECK-NEXT: (5,5) +// CHECK-NEXT: (5,10) +// CHECK-NEXT: (6,8) +// CHECK-NEXT: (6,9) +// CHECK-NEXT: (6,10) +// CHECK-NEXT: (7,8) +// CHECK-NEXT: (7,9) +// CHECK-NEXT: (7,10) +// CHECK-NEXT: (8,9) +// CHECK-NEXT: (8,8) +// CHECK-NEXT: (8,10) +// CHECK-NEXT: (9,10) +// CHECK-NEXT: (10,10) +// CHECK-NEXT: Immediate dominance tree (Node#,IDom#): // CHECK-NEXT: (0,1) // CHECK-NEXT: (1,10) // CHECK-NEXT: (2,9) @@ -242,7 +294,23 @@ int test5() // V [B4] -----------------> / // [B2]---------------------------------> -// CHECK: Immediate dominance tree (Node#,IDom#): +// CHECK: Control dependencies (Node#,Dependency#): +// CHECK-NEXT: (2,10) +// CHECK-NEXT: (3,10) +// CHECK-NEXT: (4,9) +// CHECK-NEXT: (4,10) +// CHECK-NEXT: (5,9) +// CHECK-NEXT: (5,10) +// CHECK-NEXT: (6,8) +// CHECK-NEXT: (6,9) +// CHECK-NEXT: (6,10) +// CHECK-NEXT: (7,8) +// CHECK-NEXT: (7,9) +// CHECK-NEXT: (7,10) +// CHECK-NEXT: (8,9) +// CHECK-NEXT: (8,10) +// CHECK-NEXT: (9,10) +// CHECK-NEXT: Immediate dominance tree (Node#,IDom#): // CHECK-NEXT: (0,1) // CHECK-NEXT: (1,10) // CHECK-NEXT: (2,10) diff --git a/clang/test/Analysis/domtest.cpp b/clang/test/Analysis/domtest.cpp index d57f94e44bbc6..078117ef85dc1 100644 --- a/clang/test/Analysis/domtest.cpp +++ b/clang/test/Analysis/domtest.cpp @@ -1,6 +1,7 @@ // RUN: %clang_analyze_cc1 %s \ // RUN: -analyzer-checker=debug.DumpDominators \ // RUN: -analyzer-checker=debug.DumpPostDominators \ +// RUN: -analyzer-checker=debug.DumpControlDependencies \ // RUN: 2>&1 | FileCheck %s bool coin(); @@ -20,7 +21,8 @@ void f() { // [B3 (ENTRY)] -> [B1] -> [B2] -> [B0 (EXIT)] -// CHECK: Immediate dominance tree (Node#,IDom#): +// CHECK: Control dependencies (Node#,Dependency#): +// CHECK-NEXT: Immediate dominance tree (Node#,IDom#): // CHECK-NEXT: (0,2) // CHECK-NEXT: (1,3) // CHECK-NEXT: (2,1) @@ -42,13 +44,18 @@ void funcWithBranch() { } } -// ----> [B2] ----> -// / \ -// [B5 (ENTRY)] -> [B4] -> [B3] -----------> [B1] -// \ / -// ----> [B0 (EXIT)] <---- +// 1st if 2nd if +// [B5 (ENTRY)] -> [B4] -> [B3] -> [B2] -> [B1] -> [B0 (EXIT)] +// \ \ / / +// \ -------------> / +// ------------------------------> -// CHECK: Immediate dominance tree (Node#,IDom#): +// CHECK: Control dependencies (Node#,Dependency#): +// CHECK-NEXT: (1,4) +// CHECK-NEXT: (2,3) +// CHECK-NEXT: (2,4) +// CHECK-NEXT: (3,4) +// CHECK-NEXT: Immediate dominance tree (Node#,IDom#): // CHECK-NEXT: (0,4) // CHECK-NEXT: (1,3) // CHECK-NEXT: (2,3) diff --git a/clang/unittests/Analysis/CFGDominatorTree.cpp b/clang/unittests/Analysis/CFGDominatorTree.cpp index 1df8c2f7d05d5..8cbd72c94e677 100644 --- a/clang/unittests/Analysis/CFGDominatorTree.cpp +++ b/clang/unittests/Analysis/CFGDominatorTree.cpp @@ -98,6 +98,97 @@ TEST(CFGDominatorTree, DomTree) { EXPECT_TRUE(PostDom.dominates(nullptr, ExitBlock)); } +TEST(CFGDominatorTree, ControlDependency) { + const char *Code = R"(bool coin(); + + void funcWithBranch() { + int x = 0; + if (coin()) { + if (coin()) { + x = 5; + } + int j = 10 / x; + (void)j; + } + };)"; + BuildResult Result = BuildCFG(Code); + EXPECT_EQ(BuildResult::BuiltCFG, Result.getStatus()); + + // 1st if 2nd if + // [B5 (ENTRY)] -> [B4] -> [B3] -> [B2] -> [B1] -> [B0 (EXIT)] + // \ \ / / + // \ -------------> / + // ------------------------------> + + CFG *cfg = Result.getCFG(); + + // Sanity checks. + EXPECT_EQ(cfg->size(), 6u); + + CFGBlock *ExitBlock = *cfg->begin(); + EXPECT_EQ(ExitBlock, &cfg->getExit()); + + CFGBlock *NullDerefBlock = *(cfg->begin() + 1); + + CFGBlock *SecondThenBlock = *(cfg->begin() + 2); + + CFGBlock *SecondIfBlock = *(cfg->begin() + 3); + + CFGBlock *FirstIfBlock = *(cfg->begin() + 4); + + CFGBlock *EntryBlock = *(cfg->begin() + 5); + EXPECT_EQ(EntryBlock, &cfg->getEntry()); + + ControlDependencyCalculator Control(cfg); + + EXPECT_TRUE(Control.isControlDependent(SecondThenBlock, SecondIfBlock)); + EXPECT_TRUE(Control.isControlDependent(SecondIfBlock, FirstIfBlock)); + EXPECT_FALSE(Control.isControlDependent(NullDerefBlock, SecondIfBlock)); +} + +TEST(CFGDominatorTree, ControlDependencyWithLoops) { + const char *Code = R"(int test3() { + int x,y,z; + + x = y = z = 1; + if (x > 0) { + while (x >= 0){ + while (y >= x) { + x = x-1; + y = y/2; + } + } + } + z = y; + + return 0; + })"; + BuildResult Result = BuildCFG(Code); + EXPECT_EQ(BuildResult::BuiltCFG, Result.getStatus()); + + // <- [B2] <- + // / \ + // [B8 (ENTRY)] -> [B7] -> [B6] ---> [B5] -> [B4] -> [B3] + // \ | \ / + // \ | <------------- + // \ \ + // --------> [B1] -> [B0 (EXIT)] + + CFG *cfg = Result.getCFG(); + + ControlDependencyCalculator Control(cfg); + + auto GetBlock = [cfg] (unsigned Index) -> CFGBlock * { + assert(Index < cfg->size()); + return *(cfg->begin() + Index); + }; + + // While not immediately obvious, the second block in fact post dominates the + // fifth, hence B5 is not a control dependency of 2. + EXPECT_FALSE(Control.isControlDependent(GetBlock(5), GetBlock(2))); +} + + } // namespace } // namespace analysis } // namespace clang From 7c6fe06dc074ac2063e2dc13c8851afa8a6a07ac Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Fri, 5 Jul 2019 13:29:54 +0000 Subject: [PATCH 052/181] [analyzer] Track terminator conditions on which a tracked expression depends This patch is a major part of my GSoC project, aimed to improve the bug reports of the analyzer. TL;DR: Help the analyzer understand that some conditions are important, and should be explained better. If an CFGBlock is a control dependency of a block where an expression value is tracked, explain the condition expression better by tracking it. if (A) // let's explain why we believe A to be true 10 / x; // division by zero This is an experimental feature, and can be enabled by the off-by-default analyzer configuration "track-conditions". In detail: This idea was inspired by the program slicing algorithm. Essentially, two things are used to produce a program slice (a subset of the program relevant to a (statement, variable) pair): data and control dependencies. The bug path (the linear path in the ExplodedGraph that leads from the beginning of the analysis to the error node) enables to analyzer to argue about data dependencies with relative ease. Control dependencies are a different slice of the cake entirely. Just because we reached a branch during symbolic execution, it doesn't mean that that particular branch has any effect on whether the bug would've occured. This means that we can't simply rely on the bug path to gather control dependencies. In previous patches, LLVM's IDFCalculator, which works on a control flow graph rather than the ExplodedGraph was generalized to solve this issue. We use this information to heuristically guess that the value of a tracked expression depends greatly on it's control dependencies, and start tracking them as well. After plenty of evaluations this was seen as great idea, but still lacking refinements (we should have different descriptions about a conditions value), hence it's off-by-default. Differential Revision: https://reviews.llvm.org/D62883 llvm-svn: 365207 (cherry picked from commit 258e5e457a6da5916f083a4a45fdddef5b84da2a) --- .../StaticAnalyzer/Core/AnalyzerOptions.def | 5 + .../Core/BugReporter/BugReporter.h | 10 + .../Core/BugReporterVisitors.cpp | 93 ++++++ clang/test/Analysis/analyzer-config.c | 3 +- .../track-control-dependency-conditions.cpp | 285 ++++++++++++++++++ 5 files changed, 395 insertions(+), 1 deletion(-) create mode 100644 clang/test/Analysis/track-control-dependency-conditions.cpp diff --git a/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.def b/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.def index 40d5d47bbcea3..77e99297d2d10 100644 --- a/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.def +++ b/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.def @@ -291,6 +291,11 @@ ANALYZER_OPTION(bool, DisplayCTUProgress, "display-ctu-progress", "the analyzer's progress related to ctu.", false) +ANALYZER_OPTION(bool, ShouldTrackConditions, "track-conditions", + "Whether to track conditions that are a control dependency of " + "an already tracked variable.", + false) + //===----------------------------------------------------------------------===// // Unsinged analyzer options. //===----------------------------------------------------------------------===// diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h index 4cccb38ce24fa..d30ad19b20f85 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h @@ -153,6 +153,9 @@ class BugReport : public llvm::ilist_node { /// \sa removeInvalidation llvm::SmallSet Invalidations; + /// Conditions we're already tracking. + llvm::SmallSet TrackedConditions; + private: // Used internally by BugReporter. Symbols &getInterestingSymbols(); @@ -349,6 +352,13 @@ class BugReport : public llvm::ilist_node { visitor_iterator visitor_begin() { return Callbacks.begin(); } visitor_iterator visitor_end() { return Callbacks.end(); } + /// Notes that the condition of the CFGBlock associated with \p Cond is + /// being tracked. + /// \returns false if the condition is already being tracked. + bool addTrackedCondition(const ExplodedNode *Cond) { + return TrackedConditions.insert(Cond).second; + } + /// Profile to identify equivalent bug reports for error report coalescing. /// Reports are uniqued to ensure that we do not emit multiple diagnostics /// for each bug. diff --git a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp index c830bfa98023b..14e08d453fa43 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp @@ -22,6 +22,7 @@ #include "clang/AST/Stmt.h" #include "clang/AST/Type.h" #include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Analysis/Analyses/Dominators.h" #include "clang/Analysis/AnalysisDeclContext.h" #include "clang/Analysis/CFG.h" #include "clang/Analysis/CFGStmtMap.h" @@ -1618,6 +1619,89 @@ SuppressInlineDefensiveChecksVisitor::VisitNode(const ExplodedNode *Succ, return nullptr; } +//===----------------------------------------------------------------------===// +// TrackControlDependencyCondBRVisitor. +//===----------------------------------------------------------------------===// + +namespace { +/// Tracks the expressions that are a control dependency of the node that was +/// supplied to the constructor. +/// For example: +/// +/// cond = 1; +/// if (cond) +/// 10 / 0; +/// +/// An error is emitted at line 3. This visitor realizes that the branch +/// on line 2 is a control dependency of line 3, and tracks it's condition via +/// trackExpressionValue(). +class TrackControlDependencyCondBRVisitor final : public BugReporterVisitor { + const ExplodedNode *Origin; + ControlDependencyCalculator ControlDeps; + llvm::SmallSet VisitedBlocks; + +public: + TrackControlDependencyCondBRVisitor(const ExplodedNode *O) + : Origin(O), ControlDeps(&O->getCFG()) {} + + void Profile(llvm::FoldingSetNodeID &ID) const override { + static int x = 0; + ID.AddPointer(&x); + } + + std::shared_ptr VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + BugReport &BR) override; +}; +} // end of anonymous namespace + +static CFGBlock *GetRelevantBlock(const ExplodedNode *Node) { + if (auto SP = Node->getLocationAs()) { + const Stmt *S = SP->getStmt(); + assert(S); + + return const_cast(Node->getLocationContext() + ->getAnalysisDeclContext()->getCFGStmtMap()->getBlock(S)); + } + + return nullptr; +} + +std::shared_ptr +TrackControlDependencyCondBRVisitor::VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + BugReport &BR) { + // We can only reason about control dependencies within the same stack frame. + if (Origin->getStackFrame() != N->getStackFrame()) + return nullptr; + + CFGBlock *NB = GetRelevantBlock(N); + + // Skip if we already inspected this block. + if (!VisitedBlocks.insert(NB).second) + return nullptr; + + CFGBlock *OriginB = GetRelevantBlock(Origin); + + // TODO: Cache CFGBlocks for each ExplodedNode. + if (!OriginB || !NB) + return nullptr; + + if (ControlDeps.isControlDependent(OriginB, NB)) { + if (const Expr *Condition = NB->getLastCondition()) { + // Keeping track of the already tracked conditions on a visitor level + // isn't sufficient, because a new visitor is created for each tracked + // expression, hence the BugReport level set. + if (BR.addTrackedCondition(N)) { + bugreporter::trackExpressionValue( + N, Condition, BR, /*EnableNullFPSuppression=*/false); + } + } + } + + return nullptr; +} + //===----------------------------------------------------------------------===// // Implementation of trackExpressionValue. //===----------------------------------------------------------------------===// @@ -1734,6 +1818,15 @@ bool bugreporter::trackExpressionValue(const ExplodedNode *InputNode, ProgramStateRef LVState = LVNode->getState(); + // We only track expressions if we believe that they are important. Chances + // are good that control dependencies to the tracking point are also improtant + // because of this, let's explain why we believe control reached this point. + // TODO: Shouldn't we track control dependencies of every bug location, rather + // than only tracked expressions? + if (LVState->getAnalysisManager().getAnalyzerOptions().ShouldTrackConditions) + report.addVisitor(llvm::make_unique( + InputNode)); + // The message send could be nil due to the receiver being nil. // At this point in the path, the receiver should be live since we are at the // message send expr. If it is nil, start tracking it. diff --git a/clang/test/Analysis/analyzer-config.c b/clang/test/Analysis/analyzer-config.c index a9c2eaa353b34..ac2a83ca182fa 100644 --- a/clang/test/Analysis/analyzer-config.c +++ b/clang/test/Analysis/analyzer-config.c @@ -84,8 +84,9 @@ // CHECK-NEXT: suppress-c++-stdlib = true // CHECK-NEXT: suppress-inlined-defensive-checks = true // CHECK-NEXT: suppress-null-return-paths = true +// CHECK-NEXT: track-conditions = false // CHECK-NEXT: unix.DynamicMemoryModeling:Optimistic = false // CHECK-NEXT: unroll-loops = false // CHECK-NEXT: widen-loops = false // CHECK-NEXT: [stats] -// CHECK-NEXT: num-entries = 85 +// CHECK-NEXT: num-entries = 86 diff --git a/clang/test/Analysis/track-control-dependency-conditions.cpp b/clang/test/Analysis/track-control-dependency-conditions.cpp new file mode 100644 index 0000000000000..1f64eeefedee9 --- /dev/null +++ b/clang/test/Analysis/track-control-dependency-conditions.cpp @@ -0,0 +1,285 @@ +// RUN: %clang_analyze_cc1 %s \ +// RUN: -verify=expected,tracking \ +// RUN: -analyzer-config track-conditions=true \ +// RUN: -analyzer-output=text \ +// RUN: -analyzer-checker=core +// +// RUN: %clang_analyze_cc1 %s -verify \ +// RUN: -analyzer-output=text \ +// RUN: -analyzer-checker=core + +namespace example_1 { +int flag; +bool coin(); + +void foo() { + flag = coin(); // tracking-note{{Value assigned to 'flag'}} +} + +void test() { + int *x = 0; // expected-note{{'x' initialized to a null pointer value}} + flag = 1; + + foo(); // TODO: Add nodes here about flag's value being invalidated. + if (flag) // expected-note {{Assuming 'flag' is 0}} + // expected-note@-1{{Taking false branch}} + x = new int; + + foo(); // tracking-note{{Calling 'foo'}} + // tracking-note@-1{{Returning from 'foo'}} + + if (flag) // expected-note {{Assuming 'flag' is not equal to 0}} + // expected-note@-1{{Taking true branch}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} +} // end of namespace example_1 + +namespace example_2 { +int flag; +bool coin(); + +void foo() { + flag = coin(); // tracking-note{{Value assigned to 'flag'}} +} + +void test() { + int *x = 0; + flag = 1; + + foo(); + if (flag) // expected-note {{Assuming 'flag' is 0}} + // expected-note@-1{{Taking false branch}} + x = new int; + + x = 0; // expected-note{{Null pointer value stored to 'x'}} + + foo(); // tracking-note{{Calling 'foo'}} + // tracking-note@-1{{Returning from 'foo'}} + + if (flag) // expected-note {{Assuming 'flag' is not equal to 0}} + // expected-note@-1{{Taking true branch}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} +} // end of namespace example_2 + +namespace global_variable_invalidation { +int flag; +bool coin(); + +void foo() { + // coin() could write bar, do it's invalidated. + flag = coin(); // tracking-note{{Value assigned to 'flag'}} + // tracking-note@-1{{Value assigned to 'bar'}} +} + +int bar; + +void test() { + int *x = 0; // expected-note{{'x' initialized to a null pointer value}} + flag = 1; + + foo(); // tracking-note{{Calling 'foo'}} + // tracking-note@-1{{Returning from 'foo'}} + + if (bar) // expected-note {{Assuming 'bar' is not equal to 0}} + // expected-note@-1{{Taking true branch}} + if (flag) // expected-note {{Assuming 'flag' is not equal to 0}} + // expected-note@-1{{Taking true branch}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} +} // end of namespace global_variable_invalidation + +namespace variable_declaration_in_condition { +bool coin(); + +bool foo() { + return coin(); // tracking-note{{Returning value}} +} + +int bar; + +void test() { + int *x = 0; // expected-note{{'x' initialized to a null pointer value}} + + if (int flag = foo()) // tracking-note{{Calling 'foo'}} + // tracking-note@-1{{Returning from 'foo'}} + // tracking-note@-2{{'flag' initialized here}} + + // expected-note@-4{{Assuming 'flag' is not equal to 0}} + // expected-note@-5{{Taking true branch}} + + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} +} // end of namespace variable_declaration_in_condition + +namespace conversion_to_bool { +bool coin(); + +struct ConvertsToBool { + operator bool() const { return coin(); } // tracking-note{{Returning value}} +}; + +void test() { + int *x = 0; // expected-note{{'x' initialized to a null pointer value}} + + if (ConvertsToBool()) + // tracking-note@-1 {{Calling 'ConvertsToBool::operator bool'}} + // tracking-note@-2{{Returning from 'ConvertsToBool::operator bool'}} + + // expected-note@-4{{Assuming the condition is true}} + // expected-note@-5{{Taking true branch}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} + +} // end of namespace variable_declaration_in_condition + +namespace unimportant_returning_value_note { +bool coin(); + +bool flipCoin() { return coin(); } // tracking-note{{Returning value}} + +void i(int *ptr) { + if (ptr) // expected-note{{Assuming 'ptr' is null}} + // expected-note@-1{{Taking false branch}} + ; + if (!flipCoin()) + // tracking-note@-1{{Calling 'flipCoin'}} + // tracking-note@-2{{Returning from 'flipCoin'}} + // expected-note@-3{{Assuming the condition is true}} + // expected-note@-4{{Taking true branch}} + *ptr = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} +} // end of namespace unimportant_returning_value_note + +namespace important_returning_value_note { +bool coin(); + +bool flipCoin() { + if (coin()) // tracking-note{{Assuming the condition is false}} + // tracking-note@-1{{Taking false branch}} + return true; + return coin(); // tracking-note{{Returning value}} +} + +void i(int *ptr) { + if (ptr) // expected-note{{Assuming 'ptr' is null}} + // expected-note@-1{{Taking false branch}} + ; + if (!flipCoin()) + // tracking-note@-1{{Calling 'flipCoin'}} + // tracking-note@-2{{Returning from 'flipCoin'}} + // expected-note@-3{{Assuming the condition is true}} + // expected-note@-4{{Taking true branch}} + *ptr = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} +} // end of namespace important_returning_value_note + +namespace tracked_condition_is_only_initialized { +int getInt(); + +void f() { + int flag = getInt(); // tracking-note{{'flag' initialized here}} + int *x = 0; // expected-note{{'x' initialized to a null pointer value}} + if (flag) // expected-note{{Assuming 'flag' is not equal to 0}} + // expected-note@-1{{Taking true branch}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} +} // end of namespace tracked_condition_is_only_initialized + +namespace tracked_condition_written_in_same_stackframe { +int flag; +int getInt(); + +void f(int y) { + y = 1; // tracking-note{{The value 1 is assigned to 'y'}} + flag = y; // tracking-note{{The value 1 is assigned to 'flag'}} + + int *x = 0; // expected-note{{'x' initialized to a null pointer value}} + if (flag) // expected-note{{'flag' is 1}} + // expected-note@-1{{Taking true branch}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} +} // end of namespace tracked_condition_written_in_same_stackframe + +namespace tracked_condition_written_in_nested_stackframe { +int flag; +int getInt(); + +void foo() { + int y; + y = 1; // tracking-note{{The value 1 is assigned to 'y'}} + flag = y; // tracking-note{{The value 1 is assigned to 'flag'}} + +} + +void f(int y) { + int *x = 0; // expected-note{{'x' initialized to a null pointer value}} + + foo(); // tracking-note{{Calling 'foo'}} + // tracking-note@-1{{Returning from 'foo'}} + + if (flag) // expected-note{{'flag' is 1}} + // expected-note@-1{{Taking true branch}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} +} // end of namespace tracked_condition_written_in_nested_stackframe + +namespace collapse_point_not_in_condition { + +[[noreturn]] void halt(); + +void assert(int b) { + if (!b) // tracking-note{{Assuming 'b' is not equal to 0}} + // tracking-note@-1{{Taking false branch}} + halt(); +} + +void f(int flag) { + int *x = 0; // expected-note{{'x' initialized to a null pointer value}} + + assert(flag); // tracking-note{{Calling 'assert'}} + // tracking-note@-1{{Returning from 'assert'}} + + if (flag) // expected-note{{'flag' is not equal to 0}} + // expected-note@-1{{Taking true branch}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} + +} // end of namespace collapse_point_not_in_condition + +namespace unimportant_write_before_collapse_point { + +[[noreturn]] void halt(); + +void assert(int b) { + if (!b) // tracking-note{{Assuming 'b' is not equal to 0}} + // tracking-note@-1{{Taking false branch}} + halt(); +} +int getInt(); + +void f(int flag) { + int *x = 0; // expected-note{{'x' initialized to a null pointer value}} + + flag = getInt(); // tracking-note{{Value assigned to 'flag'}} + assert(flag); // tracking-note{{Calling 'assert'}} + // tracking-note@-1{{Returning from 'assert'}} + + if (flag) // expected-note{{'flag' is not equal to 0}} + // expected-note@-1{{Taking true branch}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} + +} // end of namespace unimportant_write_before_collapse_point From de2cf58bffa73d1b6ab6f8b39e8d4e7aeed44875 Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Fri, 5 Jul 2019 14:00:08 +0000 Subject: [PATCH 053/181] [analyzer] Add a debug analyzer config to place an event for each tracked condition Differential Revision: https://reviews.llvm.org/D63642 llvm-svn: 365208 (cherry picked from commit b55745606fa6b905a1324fc07d792a4eb7493974) --- .../StaticAnalyzer/Core/AnalyzerOptions.def | 4 ++ clang/lib/Frontend/CompilerInvocation.cpp | 4 ++ .../Core/BugReporterVisitors.cpp | 21 +++++++++ clang/test/Analysis/analyzer-config.c | 3 +- .../track-control-dependency-conditions.cpp | 43 ++++++++++++++++--- 5 files changed, 68 insertions(+), 7 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.def b/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.def index 77e99297d2d10..85896100487ec 100644 --- a/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.def +++ b/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.def @@ -296,6 +296,10 @@ ANALYZER_OPTION(bool, ShouldTrackConditions, "track-conditions", "an already tracked variable.", false) +ANALYZER_OPTION(bool, ShouldTrackConditionsDebug, "track-conditions-debug", + "Whether to place an event at each tracked condition.", + false) + //===----------------------------------------------------------------------===// // Unsinged analyzer options. //===----------------------------------------------------------------------===// diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp index dfc74e7ffb09f..210f01bd10268 100644 --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -467,6 +467,10 @@ static void parseAnalyzerConfigs(AnalyzerOptions &AnOpts, if (!Diags) return; + if (AnOpts.ShouldTrackConditionsDebug && !AnOpts.ShouldTrackConditions) + Diags->Report(diag::err_analyzer_config_invalid_input) + << "track-conditions-debug" << "'track-conditions' to also be enabled"; + if (!AnOpts.CTUDir.empty() && !llvm::sys::fs::is_directory(AnOpts.CTUDir)) Diags->Report(diag::err_analyzer_config_invalid_input) << "ctu-dir" << "a filename"; diff --git a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp index 14e08d453fa43..250793c4baff0 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp @@ -1667,6 +1667,26 @@ static CFGBlock *GetRelevantBlock(const ExplodedNode *Node) { return nullptr; } +static std::shared_ptr +constructDebugPieceForTrackedCondition(const Expr *Cond, + const ExplodedNode *N, + BugReporterContext &BRC) { + + if (BRC.getAnalyzerOptions().AnalysisDiagOpt == PD_NONE || + !BRC.getAnalyzerOptions().ShouldTrackConditionsDebug) + return nullptr; + + std::string ConditionText = Lexer::getSourceText( + CharSourceRange::getTokenRange(Cond->getSourceRange()), + BRC.getSourceManager(), + BRC.getASTContext().getLangOpts()); + + return std::make_shared( + PathDiagnosticLocation::createBegin( + Cond, BRC.getSourceManager(), N->getLocationContext()), + (Twine() + "Tracking condition '" + ConditionText + "'").str()); +} + std::shared_ptr TrackControlDependencyCondBRVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &BRC, @@ -1695,6 +1715,7 @@ TrackControlDependencyCondBRVisitor::VisitNode(const ExplodedNode *N, if (BR.addTrackedCondition(N)) { bugreporter::trackExpressionValue( N, Condition, BR, /*EnableNullFPSuppression=*/false); + return constructDebugPieceForTrackedCondition(Condition, N, BRC); } } } diff --git a/clang/test/Analysis/analyzer-config.c b/clang/test/Analysis/analyzer-config.c index ac2a83ca182fa..9ceb8e94727dd 100644 --- a/clang/test/Analysis/analyzer-config.c +++ b/clang/test/Analysis/analyzer-config.c @@ -85,8 +85,9 @@ // CHECK-NEXT: suppress-inlined-defensive-checks = true // CHECK-NEXT: suppress-null-return-paths = true // CHECK-NEXT: track-conditions = false +// CHECK-NEXT: track-conditions-debug = false // CHECK-NEXT: unix.DynamicMemoryModeling:Optimistic = false // CHECK-NEXT: unroll-loops = false // CHECK-NEXT: widen-loops = false // CHECK-NEXT: [stats] -// CHECK-NEXT: num-entries = 86 +// CHECK-NEXT: num-entries = 87 diff --git a/clang/test/Analysis/track-control-dependency-conditions.cpp b/clang/test/Analysis/track-control-dependency-conditions.cpp index 1f64eeefedee9..d4e59a7ad675e 100644 --- a/clang/test/Analysis/track-control-dependency-conditions.cpp +++ b/clang/test/Analysis/track-control-dependency-conditions.cpp @@ -3,7 +3,23 @@ // RUN: -analyzer-config track-conditions=true \ // RUN: -analyzer-output=text \ // RUN: -analyzer-checker=core + +// RUN: not %clang_analyze_cc1 -verify %s \ +// RUN: -analyzer-checker=core \ +// RUN: -analyzer-config track-conditions-debug=true \ +// RUN: 2>&1 | FileCheck %s -check-prefix=CHECK-INVALID-DEBUG + +// CHECK-INVALID-DEBUG: (frontend): invalid input for analyzer-config option +// CHECK-INVALID-DEBUG-SAME: 'track-conditions-debug', that expects +// CHECK-INVALID-DEBUG-SAME: 'track-conditions' to also be enabled // +// RUN: %clang_analyze_cc1 %s \ +// RUN: -verify=expected,tracking,debug \ +// RUN: -analyzer-config track-conditions=true \ +// RUN: -analyzer-config track-conditions-debug=true \ +// RUN: -analyzer-output=text \ +// RUN: -analyzer-checker=core + // RUN: %clang_analyze_cc1 %s -verify \ // RUN: -analyzer-output=text \ // RUN: -analyzer-checker=core @@ -30,6 +46,8 @@ void test() { if (flag) // expected-note {{Assuming 'flag' is not equal to 0}} // expected-note@-1{{Taking true branch}} + // debug-note@-2{{Tracking condition 'flag'}} + *x = 5; // expected-warning{{Dereference of null pointer}} // expected-note@-1{{Dereference of null pointer}} } @@ -59,6 +77,8 @@ void test() { if (flag) // expected-note {{Assuming 'flag' is not equal to 0}} // expected-note@-1{{Taking true branch}} + // debug-note@-2{{Tracking condition 'flag'}} + *x = 5; // expected-warning{{Dereference of null pointer}} // expected-note@-1{{Dereference of null pointer}} } @@ -85,8 +105,11 @@ void test() { if (bar) // expected-note {{Assuming 'bar' is not equal to 0}} // expected-note@-1{{Taking true branch}} + // debug-note@-2{{Tracking condition 'bar'}} if (flag) // expected-note {{Assuming 'flag' is not equal to 0}} // expected-note@-1{{Taking true branch}} + // debug-note@-2{{Tracking condition 'flag'}} + *x = 5; // expected-warning{{Dereference of null pointer}} // expected-note@-1{{Dereference of null pointer}} } @@ -107,7 +130,7 @@ void test() { if (int flag = foo()) // tracking-note{{Calling 'foo'}} // tracking-note@-1{{Returning from 'foo'}} // tracking-note@-2{{'flag' initialized here}} - + // debug-note@-3{{Tracking condition 'flag'}} // expected-note@-4{{Assuming 'flag' is not equal to 0}} // expected-note@-5{{Taking true branch}} @@ -129,7 +152,7 @@ void test() { if (ConvertsToBool()) // tracking-note@-1 {{Calling 'ConvertsToBool::operator bool'}} // tracking-note@-2{{Returning from 'ConvertsToBool::operator bool'}} - + // debug-note@-3{{Tracking condition 'ConvertsToBool()'}} // expected-note@-4{{Assuming the condition is true}} // expected-note@-5{{Taking true branch}} *x = 5; // expected-warning{{Dereference of null pointer}} @@ -150,8 +173,9 @@ void i(int *ptr) { if (!flipCoin()) // tracking-note@-1{{Calling 'flipCoin'}} // tracking-note@-2{{Returning from 'flipCoin'}} - // expected-note@-3{{Assuming the condition is true}} - // expected-note@-4{{Taking true branch}} + // debug-note@-3{{Tracking condition '!flipCoin()'}} + // expected-note@-4{{Assuming the condition is true}} + // expected-note@-5{{Taking true branch}} *ptr = 5; // expected-warning{{Dereference of null pointer}} // expected-note@-1{{Dereference of null pointer}} } @@ -163,6 +187,7 @@ bool coin(); bool flipCoin() { if (coin()) // tracking-note{{Assuming the condition is false}} // tracking-note@-1{{Taking false branch}} + // debug-note@-2{{Tracking condition 'coin()'}} return true; return coin(); // tracking-note{{Returning value}} } @@ -174,8 +199,9 @@ void i(int *ptr) { if (!flipCoin()) // tracking-note@-1{{Calling 'flipCoin'}} // tracking-note@-2{{Returning from 'flipCoin'}} - // expected-note@-3{{Assuming the condition is true}} - // expected-note@-4{{Taking true branch}} + // debug-note@-3{{Tracking condition '!flipCoin()'}} + // expected-note@-4{{Assuming the condition is true}} + // expected-note@-5{{Taking true branch}} *ptr = 5; // expected-warning{{Dereference of null pointer}} // expected-note@-1{{Dereference of null pointer}} } @@ -189,6 +215,7 @@ void f() { int *x = 0; // expected-note{{'x' initialized to a null pointer value}} if (flag) // expected-note{{Assuming 'flag' is not equal to 0}} // expected-note@-1{{Taking true branch}} + // debug-note@-2{{Tracking condition 'flag'}} *x = 5; // expected-warning{{Dereference of null pointer}} // expected-note@-1{{Dereference of null pointer}} } @@ -205,6 +232,7 @@ void f(int y) { int *x = 0; // expected-note{{'x' initialized to a null pointer value}} if (flag) // expected-note{{'flag' is 1}} // expected-note@-1{{Taking true branch}} + // debug-note@-2{{Tracking condition 'flag'}} *x = 5; // expected-warning{{Dereference of null pointer}} // expected-note@-1{{Dereference of null pointer}} } @@ -229,6 +257,7 @@ void f(int y) { if (flag) // expected-note{{'flag' is 1}} // expected-note@-1{{Taking true branch}} + // debug-note@-2{{Tracking condition 'flag'}} *x = 5; // expected-warning{{Dereference of null pointer}} // expected-note@-1{{Dereference of null pointer}} } @@ -252,6 +281,7 @@ void f(int flag) { if (flag) // expected-note{{'flag' is not equal to 0}} // expected-note@-1{{Taking true branch}} + // debug-note@-2{{Tracking condition 'flag'}} *x = 5; // expected-warning{{Dereference of null pointer}} // expected-note@-1{{Dereference of null pointer}} } @@ -278,6 +308,7 @@ void f(int flag) { if (flag) // expected-note{{'flag' is not equal to 0}} // expected-note@-1{{Taking true branch}} + // debug-note@-2{{Tracking condition 'flag'}} *x = 5; // expected-warning{{Dereference of null pointer}} // expected-note@-1{{Dereference of null pointer}} } From b4f5ac134263d38fbb1ee6b79d5be8ba3025291e Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Fri, 5 Jul 2019 14:22:10 +0000 Subject: [PATCH 054/181] Removed the test case added in D63538 due to windows buildbot failures llvm-svn: 365209 (cherry picked from commit c8499ae35ae16d51582eb463cc02cca293afbb61) --- clang/unittests/Analysis/CFGTest.cpp | 38 ---------------------------- 1 file changed, 38 deletions(-) diff --git a/clang/unittests/Analysis/CFGTest.cpp b/clang/unittests/Analysis/CFGTest.cpp index 2ab3b6403a09b..7cd3da2db5fb1 100644 --- a/clang/unittests/Analysis/CFGTest.cpp +++ b/clang/unittests/Analysis/CFGTest.cpp @@ -67,44 +67,6 @@ TEST(CFG, IsLinear) { expectLinear(true, "void foo() { foo(); }"); // Recursion is not our problem. } -TEST(CFG, ConditionExpr) { - const char *Code = R"(void f(bool A, bool B, bool C) { - if (A && B && C) - int x; - })"; - BuildResult Result = BuildCFG(Code); - EXPECT_EQ(BuildResult::BuiltCFG, Result.getStatus()); - - // [B5 (ENTRY)] -> [B4] -> [B3] -> [B2] -> [B1] -> [B0 (EXIT)] - // \ \ \ / - // -------------------------------> - - CFG *cfg = Result.getCFG(); - - auto GetBlock = [cfg] (unsigned Index) -> CFGBlock * { - assert(Index < cfg->size()); - return *(cfg->begin() + Index); - }; - - EXPECT_EQ(GetBlock(1)->getLastCondition(), nullptr); - // Unfortunately, we can't check whether the correct Expr was returned by - // getLastCondition, because the lifetime of the AST ends by the time we - // retrieve the CFG. - - //===--------------------------------------------------------------------===// - - Code = R"(void foo(int x, int y) { - (void)(x + y); - })"; - Result = BuildCFG(Code); - EXPECT_EQ(BuildResult::BuiltCFG, Result.getStatus()); - - // [B2 (ENTRY)] -> [B1] -> [B0 (EXIT)] - - cfg = Result.getCFG(); - EXPECT_EQ(GetBlock(1)->getLastCondition(), nullptr); -} - } // namespace } // namespace analysis } // namespace clang From b502c829de5954ff07de7840d565449ab8b3da34 Mon Sep 17 00:00:00 2001 From: Endre Fulop Date: Mon, 8 Jul 2019 12:37:10 +0000 Subject: [PATCH 055/181] [analyzer] Add analyzer option to limit the number of imported TUs Summary: During CTU analysis of complex projects, the loaded AST-contents of imported TUs can grow bigger than available system memory. This option introduces a threshold on the number of TUs to be imported for a single TU in order to prevent such cases. Differential Revision: https://reviews.llvm.org/D59798 llvm-svn: 365314 (cherry picked from commit 0752d12c0910ece5041806e5d967ce48039df9f7) --- .../clang/CrossTU/CrossTranslationUnit.h | 10 +++++-- .../StaticAnalyzer/Core/AnalyzerOptions.def | 8 ++++++ clang/lib/CrossTU/CrossTranslationUnit.cpp | 19 +++++++++++-- clang/test/Analysis/analyzer-config.c | 3 +- clang/test/Analysis/ctu-import-threshold.c | 5 ++++ .../CrossTU/CrossTranslationUnitTest.cpp | 28 ++++++++++++++----- 6 files changed, 60 insertions(+), 13 deletions(-) create mode 100644 clang/test/Analysis/ctu-import-threshold.c diff --git a/clang/include/clang/CrossTU/CrossTranslationUnit.h b/clang/include/clang/CrossTU/CrossTranslationUnit.h index d1d38e09ef58b..d64329cdff3e6 100644 --- a/clang/include/clang/CrossTU/CrossTranslationUnit.h +++ b/clang/include/clang/CrossTU/CrossTranslationUnit.h @@ -45,7 +45,8 @@ enum class index_error_code { failed_to_generate_usr, triple_mismatch, lang_mismatch, - lang_dialect_mismatch + lang_dialect_mismatch, + load_threshold_reached }; class IndexError : public llvm::ErrorInfo { @@ -134,7 +135,8 @@ class CrossTranslationUnitContext { /// A definition with the same declaration will be looked up in the /// index file which should be in the \p CrossTUDir directory, called /// \p IndexName. In case the declaration is found in the index the - /// corresponding AST file will be loaded. + /// corresponding AST file will be loaded. If the number of TUs imported + /// reaches \p CTULoadTreshold, no loading is performed. /// /// \return Returns a pointer to the ASTUnit that contains the definition of /// the looked up name or an Error. @@ -182,6 +184,10 @@ class CrossTranslationUnitContext { CompilerInstance &CI; ASTContext &Context; std::shared_ptr ImporterSharedSt; + /// \p CTULoadTreshold should serve as an upper limit to the number of TUs + /// imported in order to reduce the memory footprint of CTU analysis. + const unsigned CTULoadThreshold; + unsigned NumASTLoaded{0u}; }; } // namespace cross_tu diff --git a/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.def b/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.def index 85896100487ec..70bd476b6c438 100644 --- a/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.def +++ b/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.def @@ -300,6 +300,14 @@ ANALYZER_OPTION(bool, ShouldTrackConditionsDebug, "track-conditions-debug", "Whether to place an event at each tracked condition.", false) +ANALYZER_OPTION(unsigned, CTUImportThreshold, "ctu-import-threshold", + "The maximal amount of translation units that is considered " + "for import when inlining functions during CTU analysis. " + "Lowering this threshold can alleviate the memory burder of " + "analysis with many interdependent definitions located in " + "various translation units.", + 100u) + //===----------------------------------------------------------------------===// // Unsinged analyzer options. //===----------------------------------------------------------------------===// diff --git a/clang/lib/CrossTU/CrossTranslationUnit.cpp b/clang/lib/CrossTU/CrossTranslationUnit.cpp index 4c00bda17f52a..2264ee0ab752f 100644 --- a/clang/lib/CrossTU/CrossTranslationUnit.cpp +++ b/clang/lib/CrossTU/CrossTranslationUnit.cpp @@ -43,6 +43,8 @@ STATISTIC(NumGetCTUSuccess, STATISTIC(NumTripleMismatch, "The # of triple mismatches"); STATISTIC(NumLangMismatch, "The # of language mismatches"); STATISTIC(NumLangDialectMismatch, "The # of language dialect mismatches"); +STATISTIC(NumASTLoadThresholdReached, + "The # of ASTs not loaded because of threshold"); // Same as Triple's equality operator, but we check a field only if that is // known in both instances. @@ -102,6 +104,8 @@ class IndexErrorCategory : public std::error_category { return "Language mismatch"; case index_error_code::lang_dialect_mismatch: return "Language dialect mismatch"; + case index_error_code::load_threshold_reached: + return "Load threshold reached"; } llvm_unreachable("Unrecognized index_error_code."); } @@ -180,7 +184,8 @@ template static bool hasBodyOrInit(const T *D) { } CrossTranslationUnitContext::CrossTranslationUnitContext(CompilerInstance &CI) - : CI(CI), Context(CI.getASTContext()) {} + : CI(CI), Context(CI.getASTContext()), + CTULoadThreshold(CI.getAnalyzerOpts()->CTUImportThreshold) {} CrossTranslationUnitContext::~CrossTranslationUnitContext() {} @@ -228,8 +233,8 @@ llvm::Expected CrossTranslationUnitContext::getCrossTUDefinitionImpl( if (LookupName.empty()) return llvm::make_error( index_error_code::failed_to_generate_usr); - llvm::Expected ASTUnitOrError = - loadExternalAST(LookupName, CrossTUDir, IndexName, DisplayCTUProgress); + llvm::Expected ASTUnitOrError = loadExternalAST( + LookupName, CrossTUDir, IndexName, DisplayCTUProgress); if (!ASTUnitOrError) return ASTUnitOrError.takeError(); ASTUnit *Unit = *ASTUnitOrError; @@ -338,6 +343,13 @@ llvm::Expected CrossTranslationUnitContext::loadExternalAST( // a lookup name from a single translation unit. If multiple // translation units contains decls with the same lookup name an // error will be returned. + + if (NumASTLoaded >= CTULoadThreshold) { + ++NumASTLoadThresholdReached; + return llvm::make_error( + index_error_code::load_threshold_reached); + } + ASTUnit *Unit = nullptr; auto NameUnitCacheEntry = NameASTUnitMap.find(LookupName); if (NameUnitCacheEntry == NameASTUnitMap.end()) { @@ -375,6 +387,7 @@ llvm::Expected CrossTranslationUnitContext::loadExternalAST( ASTUnit::LoadEverything, Diags, CI.getFileSystemOpts())); Unit = LoadedUnit.get(); FileASTUnitMap[ASTFileName] = std::move(LoadedUnit); + ++NumASTLoaded; if (DisplayCTUProgress) { llvm::errs() << "CTU loaded AST file: " << ASTFileName << "\n"; diff --git a/clang/test/Analysis/analyzer-config.c b/clang/test/Analysis/analyzer-config.c index 9ceb8e94727dd..26fa5d22068cf 100644 --- a/clang/test/Analysis/analyzer-config.c +++ b/clang/test/Analysis/analyzer-config.c @@ -27,6 +27,7 @@ // CHECK-NEXT: cplusplus.Move:WarnOn = KnownsAndLocals // CHECK-NEXT: crosscheck-with-z3 = false // CHECK-NEXT: ctu-dir = "" +// CHECK-NEXT: ctu-import-threshold = 100 // CHECK-NEXT: ctu-index-name = externalDefMap.txt // CHECK-NEXT: debug.AnalysisOrder:* = false // CHECK-NEXT: debug.AnalysisOrder:Bind = false @@ -90,4 +91,4 @@ // CHECK-NEXT: unroll-loops = false // CHECK-NEXT: widen-loops = false // CHECK-NEXT: [stats] -// CHECK-NEXT: num-entries = 87 +// CHECK-NEXT: num-entries = 88 diff --git a/clang/test/Analysis/ctu-import-threshold.c b/clang/test/Analysis/ctu-import-threshold.c new file mode 100644 index 0000000000000..82711b873d106 --- /dev/null +++ b/clang/test/Analysis/ctu-import-threshold.c @@ -0,0 +1,5 @@ +// Ensure analyzer option 'ctu-import-threshold' is a recognized option. +// +// RUN: %clang_cc1 -analyze -analyzer-config ctu-import-threshold=30 -verify %s +// +// expected-no-diagnostics diff --git a/clang/unittests/CrossTU/CrossTranslationUnitTest.cpp b/clang/unittests/CrossTU/CrossTranslationUnitTest.cpp index 43e0e75c31446..b3e7243ce1d22 100644 --- a/clang/unittests/CrossTU/CrossTranslationUnitTest.cpp +++ b/clang/unittests/CrossTU/CrossTranslationUnitTest.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "clang/CrossTU/CrossTranslationUnit.h" +#include "clang/Frontend/CompilerInstance.h" #include "clang/AST/ASTConsumer.h" #include "clang/Frontend/FrontendAction.h" #include "clang/Tooling/Tooling.h" @@ -70,12 +71,14 @@ class CTUASTConsumer : public clang::ASTConsumer { EXPECT_TRUE(llvm::sys::fs::exists(ASTFileName)); // Load the definition from the AST file. - llvm::Expected NewFDorError = - CTU.getCrossTUDefinition(FD, "", IndexFileName); - EXPECT_TRUE((bool)NewFDorError); - const FunctionDecl *NewFD = *NewFDorError; + llvm::Expected NewFDorError = handleExpected( + CTU.getCrossTUDefinition(FD, "", IndexFileName, false), + []() { return nullptr; }, [](IndexError &) {}); - *Success = NewFD && NewFD->hasBody() && !OrigFDHasBody; + if (NewFDorError) { + const FunctionDecl *NewFD = *NewFDorError; + *Success = NewFD && NewFD->hasBody() && !OrigFDHasBody; + } } private: @@ -85,26 +88,37 @@ class CTUASTConsumer : public clang::ASTConsumer { class CTUAction : public clang::ASTFrontendAction { public: - CTUAction(bool *Success) : Success(Success) {} + CTUAction(bool *Success, unsigned OverrideLimit) + : Success(Success), OverrideLimit(OverrideLimit) {} protected: std::unique_ptr CreateASTConsumer(clang::CompilerInstance &CI, StringRef) override { + CI.getAnalyzerOpts()->CTUImportThreshold = OverrideLimit; return llvm::make_unique(CI, Success); } private: bool *Success; + const unsigned OverrideLimit; }; } // end namespace TEST(CrossTranslationUnit, CanLoadFunctionDefinition) { bool Success = false; - EXPECT_TRUE(tooling::runToolOnCode(new CTUAction(&Success), "int f(int);")); + EXPECT_TRUE( + tooling::runToolOnCode(new CTUAction(&Success, 1u), "int f(int);")); EXPECT_TRUE(Success); } +TEST(CrossTranslationUnit, RespectsLoadThreshold) { + bool Success = false; + EXPECT_TRUE( + tooling::runToolOnCode(new CTUAction(&Success, 0u), "int f(int);")); + EXPECT_FALSE(Success); +} + TEST(CrossTranslationUnit, IndexFormatCanBeParsed) { llvm::StringMap Index; Index["a"] = "/b/f1"; From 25ab0b829b93a9a53a9843d3cd7595c317fee10c Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Mon, 8 Jul 2019 23:54:11 +0000 Subject: [PATCH 056/181] [analyzer] exploded-graph-rewriter: Implement a single-path mode. Instead of rewriting the whole graph, rewrite the leftmost path in the graph. Useful for trimmed graphs that are still too large to display due to multiple equivalent reports mixed into them. Differential Revision: https://reviews.llvm.org/D64263 llvm-svn: 365409 (cherry picked from commit 78566e454f68248ab18ed6b43cde64950096c98e) --- .../exploded-graph-rewriter/explorers.dot | 37 ++++++++++++++++ .../utils/analyzer/exploded-graph-rewriter.py | 44 +++++++++++++++++-- 2 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 clang/test/Analysis/exploded-graph-rewriter/explorers.dot diff --git a/clang/test/Analysis/exploded-graph-rewriter/explorers.dot b/clang/test/Analysis/exploded-graph-rewriter/explorers.dot new file mode 100644 index 0000000000000..226c63911c5a5 --- /dev/null +++ b/clang/test/Analysis/exploded-graph-rewriter/explorers.dot @@ -0,0 +1,37 @@ +// RUN: %exploded_graph_rewriter %s \ +// RUN: | FileCheck %s -check-prefixes=CHECK,BASIC +// RUN: %exploded_graph_rewriter -s %s \ +// RUN: | FileCheck %s -check-prefixes=CHECK,SINGLE + +// FIXME: Substitution doesn't seem to work on Windows. +// UNSUPPORTED: system-windows + +Node0x1 [shape=record,label= + "{{ "node_id": 1, "pointer": "0x1", "has_report": false, "is_sink": false, + "program_state": null, "program_points": []}\l}"]; + +Node0x2 [shape=record,label= + "{{ "node_id": 2, "pointer": "0x2", "has_report": false, "is_sink": false, + "program_state": null, "program_points": []}\l}"]; + +Node0x3 [shape=record,label= + "{{ "node_id": 3, "pointer": "0x3", "has_report": false, "is_sink": false, + "program_state": null, "program_points": []}\l}"]; + +Node0x4 [shape=record,label= + "{{ "node_id": 4, "pointer": "0x4", "has_report": false, "is_sink": false, + "program_state": null, "program_points": []}\l}"]; + +// CHECK: Node0x1 -> Node0x2; +Node0x1 -> Node0x2; + +// BASIC: Node0x1 -> Node0x3; +// SINGLE-NOT: Node0x1 -> Node0x3; +Node0x1 -> Node0x3; + +// CHECK: Node0x2 -> Node0x4; +Node0x2 -> Node0x4; + +// BASIC: Node0x3 -> Node0x4; +// SINGLE-NOT: Node0x3 -> Node0x4; +Node0x3 -> Node0x4; diff --git a/clang/utils/analyzer/exploded-graph-rewriter.py b/clang/utils/analyzer/exploded-graph-rewriter.py index b78789f0b647b..3ff9e5b93f9c4 100755 --- a/clang/utils/analyzer/exploded-graph-rewriter.py +++ b/clang/utils/analyzer/exploded-graph-rewriter.py @@ -804,8 +804,7 @@ def visit_end_of_graph(self): #===-----------------------------------------------------------------------===# -# A class that encapsulates traversal of the ExplodedGraph. Different explorer -# kinds could potentially traverse specific sub-graphs. +# BasicExplorer explores the whole graph in no particular order. class BasicExplorer(object): def __init__(self): super(BasicExplorer, self).__init__() @@ -821,6 +820,39 @@ def explore(self, graph, visitor): visitor.visit_end_of_graph() +# SinglePathExplorer traverses only a single path - the leftmost path +# from the root. Useful when the trimmed graph is still too large +# due to a large amount of equivalent reports. +class SinglePathExplorer(object): + def __init__(self): + super(SinglePathExplorer, self).__init__() + + def explore(self, graph, visitor): + visitor.visit_begin_graph(graph) + + # Keep track of visited nodes in order to avoid loops. + visited = set() + node_id = graph.root_id + while True: + visited.add(node_id) + node = graph.nodes[node_id] + logging.debug('Visiting ' + node_id) + visitor.visit_node(node) + if len(node.successors) == 0: + break + + succ_id = node.successors[0] + succ = graph.nodes[succ_id] + logging.debug('Visiting edge: %s -> %s ' % (node_id, succ_id)) + visitor.visit_edge(node, succ) + if succ_id in visited: + break + + node_id = succ_id + + visitor.visit_end_of_graph() + + #===-----------------------------------------------------------------------===# # The entry point to the script. #===-----------------------------------------------------------------------===# @@ -836,6 +868,11 @@ def main(): parser.add_argument('-d', '--diff', action='store_const', dest='diff', const=True, default=False, help='display differences between states') + parser.add_argument('-s', '--single-path', action='store_const', + dest='single_path', const=True, default=False, + help='only display the leftmost path in the graph ' + '(useful for trimmed graphs that still ' + 'branch too much)') parser.add_argument('--dark', action='store_const', dest='dark', const=True, default=False, help='dark mode') @@ -851,8 +888,9 @@ def main(): raw_line = raw_line.strip() graph.add_raw_line(raw_line) - explorer = BasicExplorer() + explorer = SinglePathExplorer() if args.single_path else BasicExplorer() visitor = DotDumpVisitor(args.diff, args.dark, args.gray) + explorer.explore(graph, visitor) From ee6a8d9aa45ab18d3dba556189ded085dd8a33b1 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Mon, 8 Jul 2019 23:54:14 +0000 Subject: [PATCH 057/181] [analyzer] exploded-graph-rewriter: Implement a topology-only mode. In this mode the rewriter will only rewrite program points and omit program states. Useful for understanding the rough topology of the graph. Differential Revision: https://reviews.llvm.org/D64264 llvm-svn: 365410 (cherry picked from commit c6b5c5b92545d47377e3bd7a15fbdeda4781018d) --- .../exploded-graph-rewriter/topology.dot | 32 +++++++++++++++++++ .../utils/analyzer/exploded-graph-rewriter.py | 21 +++++++----- 2 files changed, 45 insertions(+), 8 deletions(-) create mode 100644 clang/test/Analysis/exploded-graph-rewriter/topology.dot diff --git a/clang/test/Analysis/exploded-graph-rewriter/topology.dot b/clang/test/Analysis/exploded-graph-rewriter/topology.dot new file mode 100644 index 0000000000000..fa1b10f68b94f --- /dev/null +++ b/clang/test/Analysis/exploded-graph-rewriter/topology.dot @@ -0,0 +1,32 @@ +// RUN: %exploded_graph_rewriter %s \ +// RUN: | FileCheck -check-prefixes=NORMAL %s +// RUN: %exploded_graph_rewriter -t %s \ +// RUN: | FileCheck -check-prefixes=TOPOLOGY %s + +// FIXME: Substitution doesn't seem to work on Windows. +// UNSUPPORTED: system-windows + +// NORMAL: Program point +// TOPOLOGY-NOT: Program point +// NORMAL: Checker State +// TOPOLOGY-NOT: Checker State +Node0x1 [shape=record,label= + "{ + { "node_id": 1, + "pointer": "0x1", + "has_report": false, + "is_sink": false, + "state_id": 2, + "program_points": [], + "program_state": { + "environment": null, + "constraints": null, + "dynamic_types": null, + "constructing_objects": null, + "checker_messages": [ + { "checker": "foo", "messages": ["bar"] } + ], + "store": null + } + } +\l}"]; diff --git a/clang/utils/analyzer/exploded-graph-rewriter.py b/clang/utils/analyzer/exploded-graph-rewriter.py index 3ff9e5b93f9c4..99f5403189bf4 100755 --- a/clang/utils/analyzer/exploded-graph-rewriter.py +++ b/clang/utils/analyzer/exploded-graph-rewriter.py @@ -384,11 +384,12 @@ def add_raw_line(self, raw_line): # A visitor that dumps the ExplodedGraph into a DOT file with fancy HTML-based # syntax highlighing. class DotDumpVisitor(object): - def __init__(self, do_diffs, dark_mode, gray_mode): + def __init__(self, do_diffs, dark_mode, gray_mode, topo_mode): super(DotDumpVisitor, self).__init__() self._do_diffs = do_diffs self._dark_mode = dark_mode self._gray_mode = gray_mode + self._topo_mode = topo_mode @staticmethod def _dump_raw(s): @@ -766,18 +767,19 @@ def visit_node(self, node): if node.is_sink: self._dump('Sink Node' '') - self._dump('') - if len(node.points) > 1: - self._dump('Program points:') - else: - self._dump('Program point:') + if not self._topo_mode: + self._dump('') + if len(node.points) > 1: + self._dump('Program points:') + else: + self._dump('Program point:') self._dump('' '') for p in node.points: self.visit_program_point(p) self._dump('
') - if node.state is not None: + if node.state is not None and not self._topo_mode: prev_s = None # Do diffs only when we have a unique predecessor. # Don't do diffs on the leaf nodes because they're @@ -868,6 +870,9 @@ def main(): parser.add_argument('-d', '--diff', action='store_const', dest='diff', const=True, default=False, help='display differences between states') + parser.add_argument('-t', '--topology', action='store_const', + dest='topology', const=True, default=False, + help='only display program points, omit states') parser.add_argument('-s', '--single-path', action='store_const', dest='single_path', const=True, default=False, help='only display the leftmost path in the graph ' @@ -889,7 +894,7 @@ def main(): graph.add_raw_line(raw_line) explorer = SinglePathExplorer() if args.single_path else BasicExplorer() - visitor = DotDumpVisitor(args.diff, args.dark, args.gray) + visitor = DotDumpVisitor(args.diff, args.dark, args.gray, args.topology) explorer.explore(graph, visitor) From 9a4d4198dde734580f6b8e355b3048931d3e2e25 Mon Sep 17 00:00:00 2001 From: Csaba Dabis Date: Wed, 10 Jul 2019 00:20:03 +0000 Subject: [PATCH 058/181] [analyzer] CastValueChecker: Model casts Summary: It models the LLVM casts: - `cast<>` - `dyn_cast<>` - `cast_or_null<>` - `dyn_cast_or_null<>` It has a very basic support without checking the `classof()` function. (It reapplies the reverted 'llvm-svn: 365582' patch with proper test file.) Reviewed By: NoQ Tags: #clang Differential Revision: https://reviews.llvm.org/D64374 llvm-svn: 365585 (cherry picked from commit 693936ab8fe84488e9c888245890dc7936d857c3) --- .../clang/StaticAnalyzer/Checkers/Checkers.td | 17 +- .../Core/PathSensitive/CheckerContext.h | 11 + .../StaticAnalyzer/Checkers/CMakeLists.txt | 1 + .../Checkers/CastValueChecker.cpp | 191 ++++++++++++++++++ clang/test/Analysis/cast-value.cpp | 137 +++++++++++++ .../test/Analysis/return-value-guaranteed.cpp | 2 +- 6 files changed, 354 insertions(+), 5 deletions(-) create mode 100644 clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp create mode 100644 clang/test/Analysis/cast-value.cpp diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index 95ea2d5234ea5..2b29efba66a41 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -100,6 +100,7 @@ def LLVMAlpha : Package<"llvm">, ParentPackage; // intended for API modeling that is not controlled by the target triple. def APIModeling : Package<"apiModeling">, Hidden; def GoogleAPIModeling : Package<"google">, ParentPackage, Hidden; +def LLVMAPIModeling : Package<"llvm">, ParentPackage, Hidden; def Debug : Package<"debug">, Hidden; @@ -274,10 +275,6 @@ def NullableReturnedFromNonnullChecker : Checker<"NullableReturnedFromNonnull">, let ParentPackage = APIModeling in { -def ReturnValueChecker : Checker<"ReturnValue">, - HelpText<"Model the guaranteed boolean return value of function calls">, - Documentation; - def StdCLibraryFunctionsChecker : Checker<"StdCLibraryFunctions">, HelpText<"Improve modeling of the C standard library functions">, Documentation; @@ -1109,6 +1106,18 @@ def LLVMConventionsChecker : Checker<"Conventions">, } // end "llvm" +let ParentPackage = LLVMAPIModeling in { + +def CastValueChecker : Checker<"CastValue">, + HelpText<"Model implementation of custom RTTIs">, + Documentation; + +def ReturnValueChecker : Checker<"ReturnValue">, + HelpText<"Model the guaranteed boolean return value of function calls">, + Documentation; + +} // end "apiModeling.llvm" + //===----------------------------------------------------------------------===// // Checkers modeling Google APIs. //===----------------------------------------------------------------------===// diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h index 0c68078bb3541..981133e669775 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h @@ -247,6 +247,17 @@ class CheckerContext { IsPrunable); } + /// A shorthand version of getNoteTag that accepts a plain note. + /// + /// @param Note The note. + /// @param IsPrunable Whether the note is prunable. It allows BugReporter + /// to omit the note from the report if it would make the displayed + /// bug path significantly shorter. + const NoteTag *getNoteTag(StringRef Note, bool IsPrunable = false) { + return getNoteTag( + [Note](BugReporterContext &, BugReport &) { return Note; }, IsPrunable); + } + /// Returns the word that should be used to refer to the declaration /// in the report. StringRef getDeclDescription(const Decl *D); diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt index 63ff770531837..7cbd8c2a71389 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -16,6 +16,7 @@ add_clang_library(clangStaticAnalyzerCheckers CallAndMessageChecker.cpp CastSizeChecker.cpp CastToStructChecker.cpp + CastValueChecker.cpp CheckObjCDealloc.cpp CheckObjCInstMethSignature.cpp CheckSecuritySyntaxOnly.cpp diff --git a/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp new file mode 100644 index 0000000000000..0cdb67f11c904 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp @@ -0,0 +1,191 @@ +//===- CastValueChecker - Model implementation of custom RTTIs --*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This defines CastValueChecker which models casts of custom RTTIs. +// +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "llvm/ADT/Optional.h" + +using namespace clang; +using namespace ento; + +namespace { +class CastValueChecker : public Checker { + using CastCheck = + std::function; + +public: + // We have three cases to evaluate a cast: + // 1) The parameter is non-null, the return value is non-null + // 2) The parameter is non-null, the return value is null + // 3) The parameter is null, the return value is null + // + // cast: 1; dyn_cast: 1, 2; cast_or_null: 1, 3; dyn_cast_or_null: 1, 2, 3. + bool evalCall(const CallEvent &Call, CheckerContext &C) const; + +private: + // These are known in the LLVM project. + const CallDescriptionMap CDM = { + {{{"llvm", "cast"}, 1}, &CastValueChecker::evalCast}, + {{{"llvm", "dyn_cast"}, 1}, &CastValueChecker::evalDynCast}, + {{{"llvm", "cast_or_null"}, 1}, &CastValueChecker::evalCastOrNull}, + {{{"llvm", "dyn_cast_or_null"}, 1}, + &CastValueChecker::evalDynCastOrNull}}; + + void evalCast(const CallExpr *CE, DefinedOrUnknownSVal ParamDV, + CheckerContext &C) const; + void evalDynCast(const CallExpr *CE, DefinedOrUnknownSVal ParamDV, + CheckerContext &C) const; + void evalCastOrNull(const CallExpr *CE, DefinedOrUnknownSVal ParamDV, + CheckerContext &C) const; + void evalDynCastOrNull(const CallExpr *CE, DefinedOrUnknownSVal ParamDV, + CheckerContext &C) const; +}; +} // namespace + +static std::string getCastName(const Expr *Cast) { + return Cast->getType()->getPointeeCXXRecordDecl()->getNameAsString(); +} + +static void evalNonNullParamNonNullReturn(const CallExpr *CE, + DefinedOrUnknownSVal ParamDV, + CheckerContext &C) { + ProgramStateRef State = C.getState()->assume(ParamDV, true); + if (!State) + return; + + State = State->BindExpr(CE, C.getLocationContext(), ParamDV, false); + + std::string CastFromName = getCastName(CE->getArg(0)); + std::string CastToName = getCastName(CE); + + const NoteTag *CastTag = C.getNoteTag( + [CastFromName, CastToName](BugReport &) -> std::string { + SmallString<128> Msg; + llvm::raw_svector_ostream Out(Msg); + + Out << "Assuming dynamic cast from '" << CastFromName << "' to '" + << CastToName << "' succeeds"; + return Out.str(); + }, + /*IsPrunable=*/true); + + C.addTransition(State, CastTag); +} + +static void evalNonNullParamNullReturn(const CallExpr *CE, + DefinedOrUnknownSVal ParamDV, + CheckerContext &C) { + ProgramStateRef State = C.getState()->assume(ParamDV, true); + if (!State) + return; + + State = State->BindExpr(CE, C.getLocationContext(), + C.getSValBuilder().makeNull(), false); + + std::string CastFromName = getCastName(CE->getArg(0)); + std::string CastToName = getCastName(CE); + + const NoteTag *CastTag = C.getNoteTag( + [CastFromName, CastToName](BugReport &) -> std::string { + SmallString<128> Msg; + llvm::raw_svector_ostream Out(Msg); + + Out << "Assuming dynamic cast from '" << CastFromName << "' to '" + << CastToName << "' fails"; + return Out.str(); + }, + /*IsPrunable=*/true); + + C.addTransition(State, CastTag); +} + +static void evalNullParamNullReturn(const CallExpr *CE, + DefinedOrUnknownSVal ParamDV, + CheckerContext &C) { + ProgramStateRef State = C.getState()->assume(ParamDV, false); + if (!State) + return; + + State = State->BindExpr(CE, C.getLocationContext(), + C.getSValBuilder().makeNull(), false); + + const NoteTag *CastTag = + C.getNoteTag("Assuming null pointer is passed into cast", + /*IsPrunable=*/true); + + C.addTransition(State, CastTag); +} + +void CastValueChecker::evalCast(const CallExpr *CE, + DefinedOrUnknownSVal ParamDV, + CheckerContext &C) const { + evalNonNullParamNonNullReturn(CE, ParamDV, C); +} + +void CastValueChecker::evalDynCast(const CallExpr *CE, + DefinedOrUnknownSVal ParamDV, + CheckerContext &C) const { + evalNonNullParamNonNullReturn(CE, ParamDV, C); + evalNonNullParamNullReturn(CE, ParamDV, C); +} + +void CastValueChecker::evalCastOrNull(const CallExpr *CE, + DefinedOrUnknownSVal ParamDV, + CheckerContext &C) const { + evalNonNullParamNonNullReturn(CE, ParamDV, C); + evalNullParamNullReturn(CE, ParamDV, C); +} + +void CastValueChecker::evalDynCastOrNull(const CallExpr *CE, + DefinedOrUnknownSVal ParamDV, + CheckerContext &C) const { + evalNonNullParamNonNullReturn(CE, ParamDV, C); + evalNonNullParamNullReturn(CE, ParamDV, C); + evalNullParamNullReturn(CE, ParamDV, C); +} + +bool CastValueChecker::evalCall(const CallEvent &Call, + CheckerContext &C) const { + const CastCheck *Check = CDM.lookup(Call); + if (!Check) + return false; + + Call.getOriginExpr()->dump(); + const auto *CE = cast(Call.getOriginExpr()); + if (!CE) + return false; + + // If we cannot obtain both of the classes we cannot be sure how to model it. + if (!CE->getType()->getPointeeCXXRecordDecl() || + !CE->getArg(0)->getType()->getPointeeCXXRecordDecl()) + return false; + + SVal ParamV = Call.getArgSVal(0); + auto ParamDV = ParamV.getAs(); + if (!ParamDV) + return false; + + (*Check)(this, CE, *ParamDV, C); + return true; +} + +void ento::registerCastValueChecker(CheckerManager &Mgr) { + Mgr.registerChecker(); +} + +bool ento::shouldRegisterCastValueChecker(const LangOptions &LO) { + return true; +} diff --git a/clang/test/Analysis/cast-value.cpp b/clang/test/Analysis/cast-value.cpp new file mode 100644 index 0000000000000..609ee23fd64c0 --- /dev/null +++ b/clang/test/Analysis/cast-value.cpp @@ -0,0 +1,137 @@ +// RUN: %clang_analyze_cc1 \ +// RUN: -analyzer-checker=core,apiModeling.llvm.CastValue,debug.ExprInspection\ +// RUN: -verify=logic %s +// RUN: %clang_analyze_cc1 \ +// RUN: -analyzer-checker=core,apiModeling.llvm.CastValue \ +// RUN: -analyzer-output=text -verify %s + +void clang_analyzer_numTimesReached(); +void clang_analyzer_warnIfReached(); +void clang_analyzer_eval(bool); + +namespace llvm { +template +const X *cast(Y Value); + +template +const X *dyn_cast(Y Value); + +template +const X *cast_or_null(Y Value); + +template +const X *dyn_cast_or_null(Y Value); +} // namespace llvm + +using namespace llvm; + +class Shape {}; +class Triangle : public Shape {}; +class Circle : public Shape {}; + +namespace test_cast { +void evalLogic(const Shape *S) { + const Circle *C = cast(S); + clang_analyzer_numTimesReached(); // logic-warning {{1}} + + if (S && C) + clang_analyzer_eval(C == S); // logic-warning {{TRUE}} + + if (S && !C) + clang_analyzer_warnIfReached(); // no-warning + + if (!S) + clang_analyzer_warnIfReached(); // no-warning +} +} // namespace test_cast + +namespace test_dyn_cast { +void evalLogic(const Shape *S) { + const Circle *C = dyn_cast(S); + clang_analyzer_numTimesReached(); // logic-warning {{2}} + + if (S && C) + clang_analyzer_eval(C == S); // logic-warning {{TRUE}} + + if (S && !C) + clang_analyzer_warnIfReached(); // logic-warning {{REACHABLE}} + + if (!S) + clang_analyzer_warnIfReached(); // no-warning +} +} // namespace test_dyn_cast + +namespace test_cast_or_null { +void evalLogic(const Shape *S) { + const Circle *C = cast_or_null(S); + clang_analyzer_numTimesReached(); // logic-warning {{2}} + + if (S && C) + clang_analyzer_eval(C == S); // logic-warning {{TRUE}} + + if (S && !C) + clang_analyzer_warnIfReached(); // no-warning + + if (!S) + clang_analyzer_eval(!C); // logic-warning {{TRUE}} +} +} // namespace test_cast_or_null + +namespace test_dyn_cast_or_null { +void evalLogic(const Shape *S) { + const Circle *C = dyn_cast_or_null(S); + clang_analyzer_numTimesReached(); // logic-warning {{3}} + + if (S && C) + clang_analyzer_eval(C == S); // logic-warning {{TRUE}} + + if (S && !C) + clang_analyzer_warnIfReached(); // logic-warning {{REACHABLE}} + + if (!S) + clang_analyzer_eval(!C); // logic-warning {{TRUE}} +} + +void evalNonNullParamNonNullReturn(const Shape *S) { + const auto *C = dyn_cast_or_null(S); + // expected-note@-1 {{Assuming dynamic cast from 'Shape' to 'Circle' succeeds}} + // expected-note@-2 {{Assuming pointer value is null}} + // expected-note@-3 {{'C' initialized here}} + + (void)(1 / !(bool)C); + // expected-note@-1 {{'C' is non-null}} + // expected-note@-2 {{Division by zero}} + // expected-warning@-3 {{Division by zero}} + // logic-warning@-4 {{Division by zero}} +} + +void evalNonNullParamNullReturn(const Shape *S) { + const auto *C = dyn_cast_or_null(S); + // expected-note@-1 {{Assuming dynamic cast from 'Shape' to 'Circle' fails}} + // expected-note@-2 {{Assuming pointer value is null}} + + if (const auto *T = dyn_cast_or_null(S)) { + // expected-note@-1 {{Assuming dynamic cast from 'Shape' to 'Triangle' succeeds}} + // expected-note@-2 {{'T' initialized here}} + // expected-note@-3 {{'T' is non-null}} + // expected-note@-4 {{Taking true branch}} + + (void)(1 / !T); + // expected-note@-1 {{'T' is non-null}} + // expected-note@-2 {{Division by zero}} + // expected-warning@-3 {{Division by zero}} + // logic-warning@-4 {{Division by zero}} + } +} + +void evalNullParamNullReturn(const Shape *S) { + const auto *C = dyn_cast_or_null(S); + // expected-note@-1 {{Assuming null pointer is passed into cast}} + // expected-note@-2 {{'C' initialized to a null pointer value}} + + (void)(1 / (bool)C); + // expected-note@-1 {{Division by zero}} + // expected-warning@-2 {{Division by zero}} + // logic-warning@-3 {{Division by zero}} +} +} // namespace test_dyn_cast_or_null diff --git a/clang/test/Analysis/return-value-guaranteed.cpp b/clang/test/Analysis/return-value-guaranteed.cpp index 6461a98a7ade3..367a8e5906afc 100644 --- a/clang/test/Analysis/return-value-guaranteed.cpp +++ b/clang/test/Analysis/return-value-guaranteed.cpp @@ -1,5 +1,5 @@ // RUN: %clang_analyze_cc1 \ -// RUN: -analyzer-checker=core,apiModeling.ReturnValue \ +// RUN: -analyzer-checker=core,apiModeling.llvm.ReturnValue \ // RUN: -analyzer-output=text -verify=class %s struct Foo { int Field; }; From f06353d2ea3eae2e7674363444dea3da240fa800 Mon Sep 17 00:00:00 2001 From: Csaba Dabis Date: Tue, 9 Jul 2019 23:47:09 +0000 Subject: [PATCH 059/181] Revert "[analyzer] CastValueChecker: Model casts" This reverts commit 27cf6664437efd640bb6db5594bafcce68fa2854. llvm-svn: 365584 (cherry picked from commit 6a29680efb0b9866a0106e20c6682fd485ddeb4e) --- .../clang/StaticAnalyzer/Checkers/Checkers.td | 17 +- .../Core/PathSensitive/CheckerContext.h | 11 - .../StaticAnalyzer/Checkers/CMakeLists.txt | 1 - .../Checkers/CastValueChecker.cpp | 191 ------------------ clang/test/Analysis/cast-value.cpp | 137 ------------- .../test/Analysis/return-value-guaranteed.cpp | 2 +- 6 files changed, 5 insertions(+), 354 deletions(-) delete mode 100644 clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp delete mode 100644 clang/test/Analysis/cast-value.cpp diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index 2b29efba66a41..95ea2d5234ea5 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -100,7 +100,6 @@ def LLVMAlpha : Package<"llvm">, ParentPackage; // intended for API modeling that is not controlled by the target triple. def APIModeling : Package<"apiModeling">, Hidden; def GoogleAPIModeling : Package<"google">, ParentPackage, Hidden; -def LLVMAPIModeling : Package<"llvm">, ParentPackage, Hidden; def Debug : Package<"debug">, Hidden; @@ -275,6 +274,10 @@ def NullableReturnedFromNonnullChecker : Checker<"NullableReturnedFromNonnull">, let ParentPackage = APIModeling in { +def ReturnValueChecker : Checker<"ReturnValue">, + HelpText<"Model the guaranteed boolean return value of function calls">, + Documentation; + def StdCLibraryFunctionsChecker : Checker<"StdCLibraryFunctions">, HelpText<"Improve modeling of the C standard library functions">, Documentation; @@ -1106,18 +1109,6 @@ def LLVMConventionsChecker : Checker<"Conventions">, } // end "llvm" -let ParentPackage = LLVMAPIModeling in { - -def CastValueChecker : Checker<"CastValue">, - HelpText<"Model implementation of custom RTTIs">, - Documentation; - -def ReturnValueChecker : Checker<"ReturnValue">, - HelpText<"Model the guaranteed boolean return value of function calls">, - Documentation; - -} // end "apiModeling.llvm" - //===----------------------------------------------------------------------===// // Checkers modeling Google APIs. //===----------------------------------------------------------------------===// diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h index 981133e669775..0c68078bb3541 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h @@ -247,17 +247,6 @@ class CheckerContext { IsPrunable); } - /// A shorthand version of getNoteTag that accepts a plain note. - /// - /// @param Note The note. - /// @param IsPrunable Whether the note is prunable. It allows BugReporter - /// to omit the note from the report if it would make the displayed - /// bug path significantly shorter. - const NoteTag *getNoteTag(StringRef Note, bool IsPrunable = false) { - return getNoteTag( - [Note](BugReporterContext &, BugReport &) { return Note; }, IsPrunable); - } - /// Returns the word that should be used to refer to the declaration /// in the report. StringRef getDeclDescription(const Decl *D); diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt index 7cbd8c2a71389..63ff770531837 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -16,7 +16,6 @@ add_clang_library(clangStaticAnalyzerCheckers CallAndMessageChecker.cpp CastSizeChecker.cpp CastToStructChecker.cpp - CastValueChecker.cpp CheckObjCDealloc.cpp CheckObjCInstMethSignature.cpp CheckSecuritySyntaxOnly.cpp diff --git a/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp deleted file mode 100644 index 0cdb67f11c904..0000000000000 --- a/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp +++ /dev/null @@ -1,191 +0,0 @@ -//===- CastValueChecker - Model implementation of custom RTTIs --*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// This defines CastValueChecker which models casts of custom RTTIs. -// -//===----------------------------------------------------------------------===// - -#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" -#include "clang/StaticAnalyzer/Core/Checker.h" -#include "clang/StaticAnalyzer/Core/CheckerManager.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" -#include "llvm/ADT/Optional.h" - -using namespace clang; -using namespace ento; - -namespace { -class CastValueChecker : public Checker { - using CastCheck = - std::function; - -public: - // We have three cases to evaluate a cast: - // 1) The parameter is non-null, the return value is non-null - // 2) The parameter is non-null, the return value is null - // 3) The parameter is null, the return value is null - // - // cast: 1; dyn_cast: 1, 2; cast_or_null: 1, 3; dyn_cast_or_null: 1, 2, 3. - bool evalCall(const CallEvent &Call, CheckerContext &C) const; - -private: - // These are known in the LLVM project. - const CallDescriptionMap CDM = { - {{{"llvm", "cast"}, 1}, &CastValueChecker::evalCast}, - {{{"llvm", "dyn_cast"}, 1}, &CastValueChecker::evalDynCast}, - {{{"llvm", "cast_or_null"}, 1}, &CastValueChecker::evalCastOrNull}, - {{{"llvm", "dyn_cast_or_null"}, 1}, - &CastValueChecker::evalDynCastOrNull}}; - - void evalCast(const CallExpr *CE, DefinedOrUnknownSVal ParamDV, - CheckerContext &C) const; - void evalDynCast(const CallExpr *CE, DefinedOrUnknownSVal ParamDV, - CheckerContext &C) const; - void evalCastOrNull(const CallExpr *CE, DefinedOrUnknownSVal ParamDV, - CheckerContext &C) const; - void evalDynCastOrNull(const CallExpr *CE, DefinedOrUnknownSVal ParamDV, - CheckerContext &C) const; -}; -} // namespace - -static std::string getCastName(const Expr *Cast) { - return Cast->getType()->getPointeeCXXRecordDecl()->getNameAsString(); -} - -static void evalNonNullParamNonNullReturn(const CallExpr *CE, - DefinedOrUnknownSVal ParamDV, - CheckerContext &C) { - ProgramStateRef State = C.getState()->assume(ParamDV, true); - if (!State) - return; - - State = State->BindExpr(CE, C.getLocationContext(), ParamDV, false); - - std::string CastFromName = getCastName(CE->getArg(0)); - std::string CastToName = getCastName(CE); - - const NoteTag *CastTag = C.getNoteTag( - [CastFromName, CastToName](BugReport &) -> std::string { - SmallString<128> Msg; - llvm::raw_svector_ostream Out(Msg); - - Out << "Assuming dynamic cast from '" << CastFromName << "' to '" - << CastToName << "' succeeds"; - return Out.str(); - }, - /*IsPrunable=*/true); - - C.addTransition(State, CastTag); -} - -static void evalNonNullParamNullReturn(const CallExpr *CE, - DefinedOrUnknownSVal ParamDV, - CheckerContext &C) { - ProgramStateRef State = C.getState()->assume(ParamDV, true); - if (!State) - return; - - State = State->BindExpr(CE, C.getLocationContext(), - C.getSValBuilder().makeNull(), false); - - std::string CastFromName = getCastName(CE->getArg(0)); - std::string CastToName = getCastName(CE); - - const NoteTag *CastTag = C.getNoteTag( - [CastFromName, CastToName](BugReport &) -> std::string { - SmallString<128> Msg; - llvm::raw_svector_ostream Out(Msg); - - Out << "Assuming dynamic cast from '" << CastFromName << "' to '" - << CastToName << "' fails"; - return Out.str(); - }, - /*IsPrunable=*/true); - - C.addTransition(State, CastTag); -} - -static void evalNullParamNullReturn(const CallExpr *CE, - DefinedOrUnknownSVal ParamDV, - CheckerContext &C) { - ProgramStateRef State = C.getState()->assume(ParamDV, false); - if (!State) - return; - - State = State->BindExpr(CE, C.getLocationContext(), - C.getSValBuilder().makeNull(), false); - - const NoteTag *CastTag = - C.getNoteTag("Assuming null pointer is passed into cast", - /*IsPrunable=*/true); - - C.addTransition(State, CastTag); -} - -void CastValueChecker::evalCast(const CallExpr *CE, - DefinedOrUnknownSVal ParamDV, - CheckerContext &C) const { - evalNonNullParamNonNullReturn(CE, ParamDV, C); -} - -void CastValueChecker::evalDynCast(const CallExpr *CE, - DefinedOrUnknownSVal ParamDV, - CheckerContext &C) const { - evalNonNullParamNonNullReturn(CE, ParamDV, C); - evalNonNullParamNullReturn(CE, ParamDV, C); -} - -void CastValueChecker::evalCastOrNull(const CallExpr *CE, - DefinedOrUnknownSVal ParamDV, - CheckerContext &C) const { - evalNonNullParamNonNullReturn(CE, ParamDV, C); - evalNullParamNullReturn(CE, ParamDV, C); -} - -void CastValueChecker::evalDynCastOrNull(const CallExpr *CE, - DefinedOrUnknownSVal ParamDV, - CheckerContext &C) const { - evalNonNullParamNonNullReturn(CE, ParamDV, C); - evalNonNullParamNullReturn(CE, ParamDV, C); - evalNullParamNullReturn(CE, ParamDV, C); -} - -bool CastValueChecker::evalCall(const CallEvent &Call, - CheckerContext &C) const { - const CastCheck *Check = CDM.lookup(Call); - if (!Check) - return false; - - Call.getOriginExpr()->dump(); - const auto *CE = cast(Call.getOriginExpr()); - if (!CE) - return false; - - // If we cannot obtain both of the classes we cannot be sure how to model it. - if (!CE->getType()->getPointeeCXXRecordDecl() || - !CE->getArg(0)->getType()->getPointeeCXXRecordDecl()) - return false; - - SVal ParamV = Call.getArgSVal(0); - auto ParamDV = ParamV.getAs(); - if (!ParamDV) - return false; - - (*Check)(this, CE, *ParamDV, C); - return true; -} - -void ento::registerCastValueChecker(CheckerManager &Mgr) { - Mgr.registerChecker(); -} - -bool ento::shouldRegisterCastValueChecker(const LangOptions &LO) { - return true; -} diff --git a/clang/test/Analysis/cast-value.cpp b/clang/test/Analysis/cast-value.cpp deleted file mode 100644 index 609ee23fd64c0..0000000000000 --- a/clang/test/Analysis/cast-value.cpp +++ /dev/null @@ -1,137 +0,0 @@ -// RUN: %clang_analyze_cc1 \ -// RUN: -analyzer-checker=core,apiModeling.llvm.CastValue,debug.ExprInspection\ -// RUN: -verify=logic %s -// RUN: %clang_analyze_cc1 \ -// RUN: -analyzer-checker=core,apiModeling.llvm.CastValue \ -// RUN: -analyzer-output=text -verify %s - -void clang_analyzer_numTimesReached(); -void clang_analyzer_warnIfReached(); -void clang_analyzer_eval(bool); - -namespace llvm { -template -const X *cast(Y Value); - -template -const X *dyn_cast(Y Value); - -template -const X *cast_or_null(Y Value); - -template -const X *dyn_cast_or_null(Y Value); -} // namespace llvm - -using namespace llvm; - -class Shape {}; -class Triangle : public Shape {}; -class Circle : public Shape {}; - -namespace test_cast { -void evalLogic(const Shape *S) { - const Circle *C = cast(S); - clang_analyzer_numTimesReached(); // logic-warning {{1}} - - if (S && C) - clang_analyzer_eval(C == S); // logic-warning {{TRUE}} - - if (S && !C) - clang_analyzer_warnIfReached(); // no-warning - - if (!S) - clang_analyzer_warnIfReached(); // no-warning -} -} // namespace test_cast - -namespace test_dyn_cast { -void evalLogic(const Shape *S) { - const Circle *C = dyn_cast(S); - clang_analyzer_numTimesReached(); // logic-warning {{2}} - - if (S && C) - clang_analyzer_eval(C == S); // logic-warning {{TRUE}} - - if (S && !C) - clang_analyzer_warnIfReached(); // logic-warning {{REACHABLE}} - - if (!S) - clang_analyzer_warnIfReached(); // no-warning -} -} // namespace test_dyn_cast - -namespace test_cast_or_null { -void evalLogic(const Shape *S) { - const Circle *C = cast_or_null(S); - clang_analyzer_numTimesReached(); // logic-warning {{2}} - - if (S && C) - clang_analyzer_eval(C == S); // logic-warning {{TRUE}} - - if (S && !C) - clang_analyzer_warnIfReached(); // no-warning - - if (!S) - clang_analyzer_eval(!C); // logic-warning {{TRUE}} -} -} // namespace test_cast_or_null - -namespace test_dyn_cast_or_null { -void evalLogic(const Shape *S) { - const Circle *C = dyn_cast_or_null(S); - clang_analyzer_numTimesReached(); // logic-warning {{3}} - - if (S && C) - clang_analyzer_eval(C == S); // logic-warning {{TRUE}} - - if (S && !C) - clang_analyzer_warnIfReached(); // logic-warning {{REACHABLE}} - - if (!S) - clang_analyzer_eval(!C); // logic-warning {{TRUE}} -} - -void evalNonNullParamNonNullReturn(const Shape *S) { - const auto *C = dyn_cast_or_null(S); - // expected-note@-1 {{Assuming dynamic cast from 'Shape' to 'Circle' succeeds}} - // expected-note@-2 {{Assuming pointer value is null}} - // expected-note@-3 {{'C' initialized here}} - - (void)(1 / !(bool)C); - // expected-note@-1 {{'C' is non-null}} - // expected-note@-2 {{Division by zero}} - // expected-warning@-3 {{Division by zero}} - // logic-warning@-4 {{Division by zero}} -} - -void evalNonNullParamNullReturn(const Shape *S) { - const auto *C = dyn_cast_or_null(S); - // expected-note@-1 {{Assuming dynamic cast from 'Shape' to 'Circle' fails}} - // expected-note@-2 {{Assuming pointer value is null}} - - if (const auto *T = dyn_cast_or_null(S)) { - // expected-note@-1 {{Assuming dynamic cast from 'Shape' to 'Triangle' succeeds}} - // expected-note@-2 {{'T' initialized here}} - // expected-note@-3 {{'T' is non-null}} - // expected-note@-4 {{Taking true branch}} - - (void)(1 / !T); - // expected-note@-1 {{'T' is non-null}} - // expected-note@-2 {{Division by zero}} - // expected-warning@-3 {{Division by zero}} - // logic-warning@-4 {{Division by zero}} - } -} - -void evalNullParamNullReturn(const Shape *S) { - const auto *C = dyn_cast_or_null(S); - // expected-note@-1 {{Assuming null pointer is passed into cast}} - // expected-note@-2 {{'C' initialized to a null pointer value}} - - (void)(1 / (bool)C); - // expected-note@-1 {{Division by zero}} - // expected-warning@-2 {{Division by zero}} - // logic-warning@-3 {{Division by zero}} -} -} // namespace test_dyn_cast_or_null diff --git a/clang/test/Analysis/return-value-guaranteed.cpp b/clang/test/Analysis/return-value-guaranteed.cpp index 367a8e5906afc..6461a98a7ade3 100644 --- a/clang/test/Analysis/return-value-guaranteed.cpp +++ b/clang/test/Analysis/return-value-guaranteed.cpp @@ -1,5 +1,5 @@ // RUN: %clang_analyze_cc1 \ -// RUN: -analyzer-checker=core,apiModeling.llvm.ReturnValue \ +// RUN: -analyzer-checker=core,apiModeling.ReturnValue \ // RUN: -analyzer-output=text -verify=class %s struct Foo { int Field; }; From 52a7a12b2b093ea07a90c99185fa14e920da2a2e Mon Sep 17 00:00:00 2001 From: Csaba Dabis Date: Wed, 10 Jul 2019 00:20:03 +0000 Subject: [PATCH 060/181] [analyzer] CastValueChecker: Model casts Summary: It models the LLVM casts: - `cast<>` - `dyn_cast<>` - `cast_or_null<>` - `dyn_cast_or_null<>` It has a very basic support without checking the `classof()` function. (It reapplies the reverted 'llvm-svn: 365582' patch with proper test file.) Reviewed By: NoQ Tags: #clang Differential Revision: https://reviews.llvm.org/D64374 llvm-svn: 365585 (cherry picked from commit 693936ab8fe84488e9c888245890dc7936d857c3) --- .../clang/StaticAnalyzer/Checkers/Checkers.td | 17 +- .../Core/PathSensitive/CheckerContext.h | 11 + .../StaticAnalyzer/Checkers/CMakeLists.txt | 1 + .../Checkers/CastValueChecker.cpp | 191 ++++++++++++++++++ clang/test/Analysis/cast-value.cpp | 137 +++++++++++++ .../test/Analysis/return-value-guaranteed.cpp | 2 +- 6 files changed, 354 insertions(+), 5 deletions(-) create mode 100644 clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp create mode 100644 clang/test/Analysis/cast-value.cpp diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index 95ea2d5234ea5..2b29efba66a41 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -100,6 +100,7 @@ def LLVMAlpha : Package<"llvm">, ParentPackage; // intended for API modeling that is not controlled by the target triple. def APIModeling : Package<"apiModeling">, Hidden; def GoogleAPIModeling : Package<"google">, ParentPackage, Hidden; +def LLVMAPIModeling : Package<"llvm">, ParentPackage, Hidden; def Debug : Package<"debug">, Hidden; @@ -274,10 +275,6 @@ def NullableReturnedFromNonnullChecker : Checker<"NullableReturnedFromNonnull">, let ParentPackage = APIModeling in { -def ReturnValueChecker : Checker<"ReturnValue">, - HelpText<"Model the guaranteed boolean return value of function calls">, - Documentation; - def StdCLibraryFunctionsChecker : Checker<"StdCLibraryFunctions">, HelpText<"Improve modeling of the C standard library functions">, Documentation; @@ -1109,6 +1106,18 @@ def LLVMConventionsChecker : Checker<"Conventions">, } // end "llvm" +let ParentPackage = LLVMAPIModeling in { + +def CastValueChecker : Checker<"CastValue">, + HelpText<"Model implementation of custom RTTIs">, + Documentation; + +def ReturnValueChecker : Checker<"ReturnValue">, + HelpText<"Model the guaranteed boolean return value of function calls">, + Documentation; + +} // end "apiModeling.llvm" + //===----------------------------------------------------------------------===// // Checkers modeling Google APIs. //===----------------------------------------------------------------------===// diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h index 0c68078bb3541..981133e669775 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h @@ -247,6 +247,17 @@ class CheckerContext { IsPrunable); } + /// A shorthand version of getNoteTag that accepts a plain note. + /// + /// @param Note The note. + /// @param IsPrunable Whether the note is prunable. It allows BugReporter + /// to omit the note from the report if it would make the displayed + /// bug path significantly shorter. + const NoteTag *getNoteTag(StringRef Note, bool IsPrunable = false) { + return getNoteTag( + [Note](BugReporterContext &, BugReport &) { return Note; }, IsPrunable); + } + /// Returns the word that should be used to refer to the declaration /// in the report. StringRef getDeclDescription(const Decl *D); diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt index 63ff770531837..7cbd8c2a71389 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -16,6 +16,7 @@ add_clang_library(clangStaticAnalyzerCheckers CallAndMessageChecker.cpp CastSizeChecker.cpp CastToStructChecker.cpp + CastValueChecker.cpp CheckObjCDealloc.cpp CheckObjCInstMethSignature.cpp CheckSecuritySyntaxOnly.cpp diff --git a/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp new file mode 100644 index 0000000000000..0cdb67f11c904 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp @@ -0,0 +1,191 @@ +//===- CastValueChecker - Model implementation of custom RTTIs --*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This defines CastValueChecker which models casts of custom RTTIs. +// +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "llvm/ADT/Optional.h" + +using namespace clang; +using namespace ento; + +namespace { +class CastValueChecker : public Checker { + using CastCheck = + std::function; + +public: + // We have three cases to evaluate a cast: + // 1) The parameter is non-null, the return value is non-null + // 2) The parameter is non-null, the return value is null + // 3) The parameter is null, the return value is null + // + // cast: 1; dyn_cast: 1, 2; cast_or_null: 1, 3; dyn_cast_or_null: 1, 2, 3. + bool evalCall(const CallEvent &Call, CheckerContext &C) const; + +private: + // These are known in the LLVM project. + const CallDescriptionMap CDM = { + {{{"llvm", "cast"}, 1}, &CastValueChecker::evalCast}, + {{{"llvm", "dyn_cast"}, 1}, &CastValueChecker::evalDynCast}, + {{{"llvm", "cast_or_null"}, 1}, &CastValueChecker::evalCastOrNull}, + {{{"llvm", "dyn_cast_or_null"}, 1}, + &CastValueChecker::evalDynCastOrNull}}; + + void evalCast(const CallExpr *CE, DefinedOrUnknownSVal ParamDV, + CheckerContext &C) const; + void evalDynCast(const CallExpr *CE, DefinedOrUnknownSVal ParamDV, + CheckerContext &C) const; + void evalCastOrNull(const CallExpr *CE, DefinedOrUnknownSVal ParamDV, + CheckerContext &C) const; + void evalDynCastOrNull(const CallExpr *CE, DefinedOrUnknownSVal ParamDV, + CheckerContext &C) const; +}; +} // namespace + +static std::string getCastName(const Expr *Cast) { + return Cast->getType()->getPointeeCXXRecordDecl()->getNameAsString(); +} + +static void evalNonNullParamNonNullReturn(const CallExpr *CE, + DefinedOrUnknownSVal ParamDV, + CheckerContext &C) { + ProgramStateRef State = C.getState()->assume(ParamDV, true); + if (!State) + return; + + State = State->BindExpr(CE, C.getLocationContext(), ParamDV, false); + + std::string CastFromName = getCastName(CE->getArg(0)); + std::string CastToName = getCastName(CE); + + const NoteTag *CastTag = C.getNoteTag( + [CastFromName, CastToName](BugReport &) -> std::string { + SmallString<128> Msg; + llvm::raw_svector_ostream Out(Msg); + + Out << "Assuming dynamic cast from '" << CastFromName << "' to '" + << CastToName << "' succeeds"; + return Out.str(); + }, + /*IsPrunable=*/true); + + C.addTransition(State, CastTag); +} + +static void evalNonNullParamNullReturn(const CallExpr *CE, + DefinedOrUnknownSVal ParamDV, + CheckerContext &C) { + ProgramStateRef State = C.getState()->assume(ParamDV, true); + if (!State) + return; + + State = State->BindExpr(CE, C.getLocationContext(), + C.getSValBuilder().makeNull(), false); + + std::string CastFromName = getCastName(CE->getArg(0)); + std::string CastToName = getCastName(CE); + + const NoteTag *CastTag = C.getNoteTag( + [CastFromName, CastToName](BugReport &) -> std::string { + SmallString<128> Msg; + llvm::raw_svector_ostream Out(Msg); + + Out << "Assuming dynamic cast from '" << CastFromName << "' to '" + << CastToName << "' fails"; + return Out.str(); + }, + /*IsPrunable=*/true); + + C.addTransition(State, CastTag); +} + +static void evalNullParamNullReturn(const CallExpr *CE, + DefinedOrUnknownSVal ParamDV, + CheckerContext &C) { + ProgramStateRef State = C.getState()->assume(ParamDV, false); + if (!State) + return; + + State = State->BindExpr(CE, C.getLocationContext(), + C.getSValBuilder().makeNull(), false); + + const NoteTag *CastTag = + C.getNoteTag("Assuming null pointer is passed into cast", + /*IsPrunable=*/true); + + C.addTransition(State, CastTag); +} + +void CastValueChecker::evalCast(const CallExpr *CE, + DefinedOrUnknownSVal ParamDV, + CheckerContext &C) const { + evalNonNullParamNonNullReturn(CE, ParamDV, C); +} + +void CastValueChecker::evalDynCast(const CallExpr *CE, + DefinedOrUnknownSVal ParamDV, + CheckerContext &C) const { + evalNonNullParamNonNullReturn(CE, ParamDV, C); + evalNonNullParamNullReturn(CE, ParamDV, C); +} + +void CastValueChecker::evalCastOrNull(const CallExpr *CE, + DefinedOrUnknownSVal ParamDV, + CheckerContext &C) const { + evalNonNullParamNonNullReturn(CE, ParamDV, C); + evalNullParamNullReturn(CE, ParamDV, C); +} + +void CastValueChecker::evalDynCastOrNull(const CallExpr *CE, + DefinedOrUnknownSVal ParamDV, + CheckerContext &C) const { + evalNonNullParamNonNullReturn(CE, ParamDV, C); + evalNonNullParamNullReturn(CE, ParamDV, C); + evalNullParamNullReturn(CE, ParamDV, C); +} + +bool CastValueChecker::evalCall(const CallEvent &Call, + CheckerContext &C) const { + const CastCheck *Check = CDM.lookup(Call); + if (!Check) + return false; + + Call.getOriginExpr()->dump(); + const auto *CE = cast(Call.getOriginExpr()); + if (!CE) + return false; + + // If we cannot obtain both of the classes we cannot be sure how to model it. + if (!CE->getType()->getPointeeCXXRecordDecl() || + !CE->getArg(0)->getType()->getPointeeCXXRecordDecl()) + return false; + + SVal ParamV = Call.getArgSVal(0); + auto ParamDV = ParamV.getAs(); + if (!ParamDV) + return false; + + (*Check)(this, CE, *ParamDV, C); + return true; +} + +void ento::registerCastValueChecker(CheckerManager &Mgr) { + Mgr.registerChecker(); +} + +bool ento::shouldRegisterCastValueChecker(const LangOptions &LO) { + return true; +} diff --git a/clang/test/Analysis/cast-value.cpp b/clang/test/Analysis/cast-value.cpp new file mode 100644 index 0000000000000..609ee23fd64c0 --- /dev/null +++ b/clang/test/Analysis/cast-value.cpp @@ -0,0 +1,137 @@ +// RUN: %clang_analyze_cc1 \ +// RUN: -analyzer-checker=core,apiModeling.llvm.CastValue,debug.ExprInspection\ +// RUN: -verify=logic %s +// RUN: %clang_analyze_cc1 \ +// RUN: -analyzer-checker=core,apiModeling.llvm.CastValue \ +// RUN: -analyzer-output=text -verify %s + +void clang_analyzer_numTimesReached(); +void clang_analyzer_warnIfReached(); +void clang_analyzer_eval(bool); + +namespace llvm { +template +const X *cast(Y Value); + +template +const X *dyn_cast(Y Value); + +template +const X *cast_or_null(Y Value); + +template +const X *dyn_cast_or_null(Y Value); +} // namespace llvm + +using namespace llvm; + +class Shape {}; +class Triangle : public Shape {}; +class Circle : public Shape {}; + +namespace test_cast { +void evalLogic(const Shape *S) { + const Circle *C = cast(S); + clang_analyzer_numTimesReached(); // logic-warning {{1}} + + if (S && C) + clang_analyzer_eval(C == S); // logic-warning {{TRUE}} + + if (S && !C) + clang_analyzer_warnIfReached(); // no-warning + + if (!S) + clang_analyzer_warnIfReached(); // no-warning +} +} // namespace test_cast + +namespace test_dyn_cast { +void evalLogic(const Shape *S) { + const Circle *C = dyn_cast(S); + clang_analyzer_numTimesReached(); // logic-warning {{2}} + + if (S && C) + clang_analyzer_eval(C == S); // logic-warning {{TRUE}} + + if (S && !C) + clang_analyzer_warnIfReached(); // logic-warning {{REACHABLE}} + + if (!S) + clang_analyzer_warnIfReached(); // no-warning +} +} // namespace test_dyn_cast + +namespace test_cast_or_null { +void evalLogic(const Shape *S) { + const Circle *C = cast_or_null(S); + clang_analyzer_numTimesReached(); // logic-warning {{2}} + + if (S && C) + clang_analyzer_eval(C == S); // logic-warning {{TRUE}} + + if (S && !C) + clang_analyzer_warnIfReached(); // no-warning + + if (!S) + clang_analyzer_eval(!C); // logic-warning {{TRUE}} +} +} // namespace test_cast_or_null + +namespace test_dyn_cast_or_null { +void evalLogic(const Shape *S) { + const Circle *C = dyn_cast_or_null(S); + clang_analyzer_numTimesReached(); // logic-warning {{3}} + + if (S && C) + clang_analyzer_eval(C == S); // logic-warning {{TRUE}} + + if (S && !C) + clang_analyzer_warnIfReached(); // logic-warning {{REACHABLE}} + + if (!S) + clang_analyzer_eval(!C); // logic-warning {{TRUE}} +} + +void evalNonNullParamNonNullReturn(const Shape *S) { + const auto *C = dyn_cast_or_null(S); + // expected-note@-1 {{Assuming dynamic cast from 'Shape' to 'Circle' succeeds}} + // expected-note@-2 {{Assuming pointer value is null}} + // expected-note@-3 {{'C' initialized here}} + + (void)(1 / !(bool)C); + // expected-note@-1 {{'C' is non-null}} + // expected-note@-2 {{Division by zero}} + // expected-warning@-3 {{Division by zero}} + // logic-warning@-4 {{Division by zero}} +} + +void evalNonNullParamNullReturn(const Shape *S) { + const auto *C = dyn_cast_or_null(S); + // expected-note@-1 {{Assuming dynamic cast from 'Shape' to 'Circle' fails}} + // expected-note@-2 {{Assuming pointer value is null}} + + if (const auto *T = dyn_cast_or_null(S)) { + // expected-note@-1 {{Assuming dynamic cast from 'Shape' to 'Triangle' succeeds}} + // expected-note@-2 {{'T' initialized here}} + // expected-note@-3 {{'T' is non-null}} + // expected-note@-4 {{Taking true branch}} + + (void)(1 / !T); + // expected-note@-1 {{'T' is non-null}} + // expected-note@-2 {{Division by zero}} + // expected-warning@-3 {{Division by zero}} + // logic-warning@-4 {{Division by zero}} + } +} + +void evalNullParamNullReturn(const Shape *S) { + const auto *C = dyn_cast_or_null(S); + // expected-note@-1 {{Assuming null pointer is passed into cast}} + // expected-note@-2 {{'C' initialized to a null pointer value}} + + (void)(1 / (bool)C); + // expected-note@-1 {{Division by zero}} + // expected-warning@-2 {{Division by zero}} + // logic-warning@-3 {{Division by zero}} +} +} // namespace test_dyn_cast_or_null diff --git a/clang/test/Analysis/return-value-guaranteed.cpp b/clang/test/Analysis/return-value-guaranteed.cpp index 6461a98a7ade3..367a8e5906afc 100644 --- a/clang/test/Analysis/return-value-guaranteed.cpp +++ b/clang/test/Analysis/return-value-guaranteed.cpp @@ -1,5 +1,5 @@ // RUN: %clang_analyze_cc1 \ -// RUN: -analyzer-checker=core,apiModeling.ReturnValue \ +// RUN: -analyzer-checker=core,apiModeling.llvm.ReturnValue \ // RUN: -analyzer-output=text -verify=class %s struct Foo { int Field; }; From b153f8ed0b06bc404252c8b52a55836d4cb0990b Mon Sep 17 00:00:00 2001 From: Csaba Dabis Date: Wed, 10 Jul 2019 00:50:01 +0000 Subject: [PATCH 061/181] [analyzer] CastValueChecker: Remove a dump() Summary: Fix a nit. llvm-svn: 365590 (cherry picked from commit e856c0465d11bd38cc7a905e9f5016ca03c4114a) --- clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp index 0cdb67f11c904..ff5d12c27c69f 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp @@ -163,7 +163,6 @@ bool CastValueChecker::evalCall(const CallEvent &Call, if (!Check) return false; - Call.getOriginExpr()->dump(); const auto *CE = cast(Call.getOriginExpr()); if (!CE) return false; From bd14f180229ef2968bfce14770717dea225cd44b Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Thu, 11 Jul 2019 21:27:42 +0000 Subject: [PATCH 062/181] [analyzer] exploded-graph-rewriter: Fix filenames in program point. Fix a typo in JSON field name. llvm-svn: 365827 (cherry picked from commit fc6059e8b987489579b40f32ba50cfdb1cb0cb2d) --- .../test/Analysis/exploded-graph-rewriter/program_points.dot | 3 ++- clang/utils/analyzer/exploded-graph-rewriter.py | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/clang/test/Analysis/exploded-graph-rewriter/program_points.dot b/clang/test/Analysis/exploded-graph-rewriter/program_points.dot index 342a923725ec5..772fe4de80627 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/program_points.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/program_points.dot @@ -52,7 +52,7 @@ Node0x1 [shape=record,label= // CHECK-SAME: // CHECK-SAME: // CHECK-SAME: // CHECK-SAME: // CHECK-SAME: // CHECK-SAME: // CHECK-SAME: @@ -46,7 +50,16 @@ Node0x1 [shape=record,label= "location_context": "#0 Call", "lctx_id": 3, "calling": "foo", - "call_line": 4, + "location": { + "file": "environment.cpp", + "line": 4, + "column": 6, + "spelling": { + "file": "environment.h", + "line": 7, + "column": 8 + } + }, "items": [ { "stmt_id": 5, diff --git a/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot b/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot index c3ba88622f1f0..475247bb98917 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot @@ -25,7 +25,7 @@ Node0x1 [shape=record,label= "location_context": "#0 Call", "lctx_id": 3, "calling": "foo", - "call_line": 4, + "location": null, "items": [ { "stmt_id": 5, @@ -76,7 +76,7 @@ Node0x6 [shape=record,label= "location_context": "#0 Call", "lctx_id": 3, "calling": "foo", - "call_line": 4, + "location": null, "items": [ { "stmt_id": 9, @@ -121,7 +121,7 @@ Node0x9 [shape=record,label= "location_context": "#0 Call", "lctx_id": 3, "calling": "foo", - "call_line": 4, + "location": null, "items": [ { "stmt_id": 9, diff --git a/clang/test/Analysis/exploded-graph-rewriter/macros.c b/clang/test/Analysis/exploded-graph-rewriter/macros.c new file mode 100644 index 0000000000000..4dcbda95d225e --- /dev/null +++ b/clang/test/Analysis/exploded-graph-rewriter/macros.c @@ -0,0 +1,18 @@ +// FIXME: Figure out how to use %clang_analyze_cc1 with our lit.local.cfg. +// RUN: %clang_cc1 -analyze -triple x86_64-unknown-linux-gnu \ +// RUN: -analyzer-checker=core \ +// RUN: -analyzer-dump-egraph=%t.dot %s +// RUN: %exploded_graph_rewriter %t.dot | FileCheck %s +// REQUIRES: asserts + +// FIXME: Substitution doesn't seem to work on Windows. +// UNSUPPORTED: system-windows + +// CHECK: macros.c:17:10 +// CHECK-SAME: +// CHECK-SAME: (spelling at macros.c:15:14) +// CHECK-SAME: +#define NULL 0 +void *foo() { + return NULL; +} diff --git a/clang/test/Analysis/exploded-graph-rewriter/program_points.dot b/clang/test/Analysis/exploded-graph-rewriter/program_points.dot index 772fe4de80627..2f49d7f75ef01 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/program_points.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/program_points.dot @@ -57,10 +57,11 @@ Node0x1 [shape=record,label= // CHECK-SAME: +// CHECK-SAME: // CHECK-SAME: -// CHECK-SAME: +// CHECK-SAME: // CHECK-SAME: // CHECK-SAME: // CHECK-SAME: +// CHECK-SAME: Node0x3 [shape=record,label= "{ { "node_id": 3, "pointer": "0x3", "has_report": false, "is_sink": false, @@ -104,7 +105,7 @@ Node0x3 [shape=record,label= "kind": "Statement", "stmt_kind": "CompoundStmt", "stmt_point_kind": "PostStmt", - "stmd_id": 6, + "stmt_id": 6, "pointer": "0x6", "pretty": "{ very very very very very very long pretty print }", "location": { diff --git a/clang/test/Analysis/expr-inspection.c b/clang/test/Analysis/expr-inspection.c index 5d07b70965f43..c2bcafe068966 100644 --- a/clang/test/Analysis/expr-inspection.c +++ b/clang/test/Analysis/expr-inspection.c @@ -30,7 +30,7 @@ void foo(int x) { // CHECK-NEXT: ]} // CHECK-NEXT: ]}, // CHECK-NEXT: "environment": { "pointer": "{{0x[0-9a-f]+}}", "items": [ -// CHECK-NEXT: { "lctx_id": 1, "location_context": "#0 Call", "calling": "foo", "call_line": null, "items": [ +// CHECK-NEXT: { "lctx_id": 1, "location_context": "#0 Call", "calling": "foo", "location": null, "items": [ // CHECK-NEXT: { "stmt_id": {{[0-9]+}}, "pretty": "clang_analyzer_printState", "value": "&code{clang_analyzer_printState}" } // CHECK-NEXT: ]} // CHECK-NEXT: ]}, diff --git a/clang/utils/analyzer/exploded-graph-rewriter.py b/clang/utils/analyzer/exploded-graph-rewriter.py index baf1be8de06a4..5ce56d61c0dcf 100755 --- a/clang/utils/analyzer/exploded-graph-rewriter.py +++ b/clang/utils/analyzer/exploded-graph-rewriter.py @@ -49,10 +49,16 @@ def is_different(self, prev): class SourceLocation(object): def __init__(self, json_loc): super(SourceLocation, self).__init__() + logging.debug('json: %s' % json_loc) self.line = json_loc['line'] self.col = json_loc['column'] self.filename = os.path.basename(json_loc['file']) \ if 'file' in json_loc else '(main file)' + self.spelling = SourceLocation(json_loc['spelling']) \ + if 'spelling' in json_loc else None + + def is_macro(self): + return self.spelling is not None # A deserialized program point. @@ -65,8 +71,10 @@ def __init__(self, json_pp): self.src_id = json_pp['src_id'] self.dst_id = json_pp['dst_id'] elif self.kind == 'Statement': + logging.debug(json_pp) self.stmt_kind = json_pp['stmt_kind'] self.stmt_point_kind = json_pp['stmt_point_kind'] + self.stmt_id = json_pp['stmt_id'] self.pointer = json_pp['pointer'] self.pretty = json_pp['pretty'] self.loc = SourceLocation(json_pp['location']) \ @@ -102,7 +110,8 @@ def __init__(self, json_frame): self.lctx_id = json_frame['lctx_id'] self.caption = json_frame['location_context'] self.decl = json_frame['calling'] - self.line = json_frame['call_line'] + self.loc = SourceLocation(json_frame['location']) \ + if json_frame['location'] is not None else None def _key(self): return self.lctx_id @@ -432,6 +441,22 @@ def _short_pretty(s): return s return candidate + @staticmethod + def _make_sloc(loc): + if loc is None: + return 'Invalid Source Location' + + def make_plain_loc(loc): + return '%s:%s:%s' \ + % (loc.filename, loc.line, loc.col) + + if loc.is_macro(): + return '%s ' \ + '(spelling at %s)' \ + % (make_plain_loc(loc), make_plain_loc(loc.spelling)) + + return make_plain_loc(loc) + def visit_begin_graph(self, graph): self._graph = graph self._dump_raw('digraph "ExplodedGraph" {\n') @@ -457,29 +482,16 @@ def visit_program_point(self, p): # Such statements show up only at [Pre|Post]StmtPurgeDeadSymbols skip_pretty = 'PurgeDeadSymbols' in p.stmt_point_kind stmt_color = 'cyan3' - if p.loc is not None: - self._dump('' - '' - '' - '' - % (p.loc.filename, p.loc.line, - p.loc.col, color, p.stmt_kind, - stmt_color, p.stmt_point_kind, - self._short_pretty(p.pretty) - if not skip_pretty else '')) - else: - self._dump('' - '' - '' - '' - % (color, p.stmt_kind, - stmt_color, p.stmt_point_kind, - self._short_pretty(p.pretty) - if not skip_pretty else '')) + self._dump('' + '' + '' + '' + '' + % (self._make_sloc(p.loc), color, p.stmt_kind, + p.stmt_id, stmt_color, p.stmt_point_kind, + self._short_pretty(p.pretty) + if not skip_pretty else '')) elif p.kind == 'Edge': self._dump('' '' % (self._diff_plus_minus(is_added), lc.caption, lc.decl, - ('(line %s)' % lc.line) if lc.line is not None - else '')) + ('(%s)' % self._make_sloc(lc.loc)) + if lc.loc is not None else '')) def dump_binding(f, b, is_added=None): self._dump('' From 47c09e34227cb00478ea0875ec85845ed6a29d1c Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Fri, 12 Jul 2019 02:16:56 +0000 Subject: [PATCH 064/181] NFC: Unforget a colon in a few CHECK: directives. Differential Revision: https://reviews.llvm.org/D64526 llvm-svn: 365863 (cherry picked from commit 8bd441af8b84165ca904772b1e73eaaed4fbe71a) --- clang/test/Analysis/cfg-rich-constructors.cpp | 2 +- clang/test/CodeGenCXX/noescape.cpp | 2 +- clang/test/CodeGenObjC/externally-retained.m | 2 +- compiler-rt/test/profile/instrprof-merge.c | 4 ++-- llvm/test/tools/dsymutil/X86/modules.m | 1 + llvm/test/tools/dsymutil/X86/odr-fwd-declaration.cpp | 2 +- 6 files changed, 7 insertions(+), 6 deletions(-) diff --git a/clang/test/Analysis/cfg-rich-constructors.cpp b/clang/test/Analysis/cfg-rich-constructors.cpp index 0125c9bf52325..cb1ed8eb6b2b3 100644 --- a/clang/test/Analysis/cfg-rich-constructors.cpp +++ b/clang/test/Analysis/cfg-rich-constructors.cpp @@ -459,7 +459,7 @@ namespace temporary_object_expr_without_dtors { // TODO: Should provide construction context for the constructor, // even if there is no specific trigger statement here. // CHECK: void simpleTemporary() -// CHECK 1: C() (CXXConstructExpr, class C) +// CHECK: 1: C() (CXXConstructExpr, class C) void simpleTemporary() { C(); } diff --git a/clang/test/CodeGenCXX/noescape.cpp b/clang/test/CodeGenCXX/noescape.cpp index 40bc839788ac8..dbd97daa5e064 100644 --- a/clang/test/CodeGenCXX/noescape.cpp +++ b/clang/test/CodeGenCXX/noescape.cpp @@ -45,7 +45,7 @@ void *operator new(std::size_t, void * __attribute__((noescape)) p) { } // CHECK-LABEL: define i8* @_Z5test1Pv( -// CHECK : %call = call {{.*}} @_ZnwmPv({{.*}}, {{.*}} nocapture {{.*}}) +// CHECK: %call = call {{.*}} @_ZnwmPv({{.*}}, {{.*}} nocapture {{.*}}) void *test1(void *p0) { return ::operator new(16, p0); } diff --git a/clang/test/CodeGenObjC/externally-retained.m b/clang/test/CodeGenObjC/externally-retained.m index 0b4d0d648b440..f68696879768f 100644 --- a/clang/test/CodeGenObjC/externally-retained.m +++ b/clang/test/CodeGenObjC/externally-retained.m @@ -22,7 +22,7 @@ @interface ObjTy @end void param(ObjTy *p) EXT_RET { // CHECK-LABEL: define void @param // CHECK-NOT: llvm.objc. - // CHECK ret + // CHECK: ret } void local() { diff --git a/compiler-rt/test/profile/instrprof-merge.c b/compiler-rt/test/profile/instrprof-merge.c index 8f8d7f458c966..8e8f06874b094 100644 --- a/compiler-rt/test/profile/instrprof-merge.c +++ b/compiler-rt/test/profile/instrprof-merge.c @@ -89,8 +89,8 @@ int main(int argc, const char *argv[]) { // Not profiled // CHECK-LABEL: bar: // CHECK: Counters: 1 -// CHECK-NEXT Function count: 0 -// CHECK-NEXT Block counts: [] +// CHECK-NEXT: Function count: 0 +// CHECK-NEXT: Block counts: [] // Not profiled // CHECK-LABEL: main: diff --git a/llvm/test/tools/dsymutil/X86/modules.m b/llvm/test/tools/dsymutil/X86/modules.m index 1a5209e42bbd9..9e417861b9726 100644 --- a/llvm/test/tools/dsymutil/X86/modules.m +++ b/llvm/test/tools/dsymutil/X86/modules.m @@ -116,6 +116,7 @@ @interface Foo { // CHECK: DW_AT_type {{.*}}{0x{{0*}}[[PTR:.*]]} // // CHECK: 0x{{0*}}[[PTR]]: DW_TAG_pointer_type +// FIXME: The next line doesn't work. // CHECK-NEXT DW_AT_type [DW_FORM_ref_addr] {0x{{0*}}[[INTERFACE]] extern int odr_violation; diff --git a/llvm/test/tools/dsymutil/X86/odr-fwd-declaration.cpp b/llvm/test/tools/dsymutil/X86/odr-fwd-declaration.cpp index 8803b7c64b609..29b0ae2fb4e09 100644 --- a/llvm/test/tools/dsymutil/X86/odr-fwd-declaration.cpp +++ b/llvm/test/tools/dsymutil/X86/odr-fwd-declaration.cpp @@ -49,7 +49,7 @@ void foo() { // CHECK: AT_name{{.*}} "S" // CHECK-NOT: {{DW_TAG|NULL}} // CHECK: AT_declaration -// CHECK-NOT AT_byte_size +// CHECK-NOT: AT_byte_size #elif defined(FILE2) # 1 "Header.h" 1 From 47155f0837eb77ad0742d7a01eb87a99bd5ff826 Mon Sep 17 00:00:00 2001 From: Csaba Dabis Date: Thu, 18 Jul 2019 00:03:55 +0000 Subject: [PATCH 065/181] [analyzer] MallocChecker: Prevent Integer Set Library false positives Summary: Integer Set Library using retain-count based allocation which is not modeled in MallocChecker. Reviewed By: NoQ Tags: #clang Differential Revision: https://reviews.llvm.org/D64680 llvm-svn: 366391 (cherry picked from commit 68983321cc966018d2d0408f1abe920d332033df) --- .../StaticAnalyzer/Checkers/MallocChecker.cpp | 39 ++++++++++++++++++- clang/test/Analysis/retain-count-alloc.cpp | 37 ++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 clang/test/Analysis/retain-count-alloc.cpp diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index 03e779f3c52d8..b95481ae1e0ba 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -17,6 +17,7 @@ #include "clang/AST/ParentMap.h" #include "clang/Basic/SourceManager.h" #include "clang/Basic/TargetInfo.h" +#include "clang/Lex/Lexer.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h" #include "clang/StaticAnalyzer/Core/Checker.h" @@ -359,6 +360,11 @@ class MallocChecker : public CheckerisReleased()); } +bool MallocChecker::suppressDeallocationsInSuspiciousContexts( + const CallExpr *CE, CheckerContext &C) const { + if (CE->getNumArgs() == 0) + return false; + + StringRef FunctionStr = ""; + if (const auto *FD = dyn_cast(C.getStackFrame()->getDecl())) + if (const Stmt *Body = FD->getBody()) + if (Body->getBeginLoc().isValid()) + FunctionStr = + Lexer::getSourceText(CharSourceRange::getTokenRange( + {FD->getBeginLoc(), Body->getBeginLoc()}), + C.getSourceManager(), C.getLangOpts()); + + // We do not model the Integer Set Library's retain-count based allocation. + if (!FunctionStr.contains("__isl_")) + return false; + + ProgramStateRef State = C.getState(); + + for (const Expr *Arg : CE->arguments()) + if (SymbolRef Sym = C.getSVal(Arg).getAsSymbol()) + if (const RefState *RS = State->get(Sym)) + State = State->set(Sym, RefState::getEscaped(RS)); + + C.addTransition(State); + return true; +} + bool MallocChecker::checkUseAfterFree(SymbolRef Sym, CheckerContext &C, const Stmt *S) const { @@ -2833,7 +2871,6 @@ ProgramStateRef MallocChecker::checkPointerEscapeAux(ProgramStateRef State, if (const RefState *RS = State->get(sym)) { if ((RS->isAllocated() || RS->isAllocatedOfSizeZero()) && CheckRefState(RS)) { - State = State->remove(sym); State = State->set(sym, RefState::getEscaped(RS)); } } diff --git a/clang/test/Analysis/retain-count-alloc.cpp b/clang/test/Analysis/retain-count-alloc.cpp new file mode 100644 index 0000000000000..472cbbf0705e2 --- /dev/null +++ b/clang/test/Analysis/retain-count-alloc.cpp @@ -0,0 +1,37 @@ +// RUN: %clang_analyze_cc1 \ +// RUN: -analyzer-checker=core,unix.Malloc \ +// RUN: -verify %s + +// expected-no-diagnostics: We do not model Integer Set Library's retain-count +// based allocation. If any of the parameters has an +// '__isl_' prefixed macro definition we escape every +// of them when we are about to 'free()' something. + +#define __isl_take +#define __isl_keep + +struct Object { int Ref; }; +void free(void *); + +Object *copyObj(__isl_keep Object *O) { + O->Ref++; + return O; +} + +void freeObj(__isl_take Object *O) { + if (--O->Ref > 0) + return; + + free(O); // Here we notice that the parameter contains '__isl_', escape it. +} + +void useAfterFree(__isl_take Object *A) { + if (!A) + return; + + Object *B = copyObj(A); + freeObj(B); + + A->Ref = 13; + // no-warning: 'Use of memory after it is freed' was here. +} From c443faa2fb0c014f5a66e113ee81740188cf8681 Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Mon, 22 Jul 2019 04:14:09 +0000 Subject: [PATCH 066/181] [analyzer] Fix -Wunused-function in NDEBUG builds with #ifdef LLVM_DUMP_METHOD llvm-svn: 366663 (cherry picked from commit 1a1af4392ac142bc5246aa561abc3e8dad43b61f) --- clang/lib/StaticAnalyzer/Core/RegionStore.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp index a389619f84260..09bfbbe38116d 100644 --- a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp +++ b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp @@ -108,7 +108,7 @@ class BindingKey { Data == X.Data; } - void dump() const; + LLVM_DUMP_METHOD void dump() const; }; } // end anonymous namespace @@ -135,7 +135,9 @@ static inline raw_ostream &operator<<(raw_ostream &Out, BindingKey K) { } // namespace llvm -LLVM_DUMP_METHOD void BindingKey::dump() const { llvm::errs() << *this; } +#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP) +void BindingKey::dump() const { llvm::errs() << *this; } +#endif //===----------------------------------------------------------------------===// // Actual Store type. From 1bfc7a3eaa762aa8bb69a0d9f1832eb1e1c94779 Mon Sep 17 00:00:00 2001 From: Balazs Keri <1.int32@gmail.com> Date: Tue, 23 Jul 2019 07:04:20 +0000 Subject: [PATCH 067/181] [CrossTU] Added CTU argument to diagnostic consumer create fn. Summary: The PListDiagnosticConsumer needs a new CTU parameter that is passed through the create functions. Reviewers: NoQ, Szelethus, xazax.hun, martong Reviewed By: Szelethus Subscribers: rnkovacs, dkrupp, Szelethus, gamesh411, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D64635 llvm-svn: 366782 (cherry picked from commit 32f220c5fbe5f0f2d821c93f46de6cbb95877050) --- .../Core/PathDiagnosticConsumers.h | 12 ++--- .../StaticAnalyzer/Core/HTMLDiagnostics.cpp | 16 +++---- .../StaticAnalyzer/Core/PlistDiagnostics.cpp | 45 ++++++++++--------- .../StaticAnalyzer/Core/SarifDiagnostics.cpp | 8 ++-- .../Frontend/AnalysisConsumer.cpp | 22 ++++----- 5 files changed, 55 insertions(+), 48 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h b/clang/include/clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h index ef6e7e0f45d57..8601966c91e58 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h @@ -20,17 +20,19 @@ namespace clang { class AnalyzerOptions; class Preprocessor; +namespace cross_tu { +class CrossTranslationUnitContext; +} namespace ento { class PathDiagnosticConsumer; typedef std::vector PathDiagnosticConsumers; -#define ANALYSIS_DIAGNOSTICS(NAME, CMDFLAG, DESC, CREATEFN)\ -void CREATEFN(AnalyzerOptions &AnalyzerOpts,\ - PathDiagnosticConsumers &C,\ - const std::string &Prefix,\ - const Preprocessor &PP); +#define ANALYSIS_DIAGNOSTICS(NAME, CMDFLAG, DESC, CREATEFN) \ + void CREATEFN(AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, \ + const std::string &Prefix, const Preprocessor &PP, \ + const cross_tu::CrossTranslationUnitContext &CTU); #include "clang/StaticAnalyzer/Core/Analyses.def" } // end 'ento' namespace diff --git a/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp b/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp index 64c42699fcf3c..f781c949e4dd3 100644 --- a/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp +++ b/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp @@ -134,17 +134,17 @@ class HTMLDiagnostics : public PathDiagnosticConsumer { } // namespace -void ento::createHTMLDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts, - PathDiagnosticConsumers &C, - const std::string& prefix, - const Preprocessor &PP) { +void ento::createHTMLDiagnosticConsumer( + AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, + const std::string &prefix, const Preprocessor &PP, + const cross_tu::CrossTranslationUnitContext &) { C.push_back(new HTMLDiagnostics(AnalyzerOpts, prefix, PP, true)); } -void ento::createHTMLSingleFileDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts, - PathDiagnosticConsumers &C, - const std::string& prefix, - const Preprocessor &PP) { +void ento::createHTMLSingleFileDiagnosticConsumer( + AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, + const std::string &prefix, const Preprocessor &PP, + const cross_tu::CrossTranslationUnitContext &) { C.push_back(new HTMLDiagnostics(AnalyzerOpts, prefix, PP, false)); } diff --git a/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp b/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp index 8387512792974..893f594f83c2f 100644 --- a/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp +++ b/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp @@ -14,6 +14,7 @@ #include "clang/Basic/PlistSupport.h" #include "clang/Basic/SourceManager.h" #include "clang/Basic/Version.h" +#include "clang/CrossTU/CrossTranslationUnit.h" #include "clang/Lex/Preprocessor.h" #include "clang/Lex/TokenConcatenation.h" #include "clang/Rewrite/Core/HTMLRewrite.h" @@ -21,9 +22,9 @@ #include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" #include "clang/StaticAnalyzer/Core/IssueHash.h" #include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h" -#include "llvm/ADT/Statistic.h" #include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/Statistic.h" #include "llvm/Support/Casting.h" using namespace clang; @@ -39,12 +40,13 @@ namespace { class PlistDiagnostics : public PathDiagnosticConsumer { const std::string OutputFile; const Preprocessor &PP; + const cross_tu::CrossTranslationUnitContext &CTU; AnalyzerOptions &AnOpts; const bool SupportsCrossFileDiagnostics; public: - PlistDiagnostics(AnalyzerOptions &AnalyzerOpts, - const std::string& prefix, + PlistDiagnostics(AnalyzerOptions &AnalyzerOpts, const std::string &prefix, const Preprocessor &PP, + const cross_tu::CrossTranslationUnitContext &CTU, bool supportsMultipleFiles); ~PlistDiagnostics() override {} @@ -518,26 +520,29 @@ static void printBugPath(llvm::raw_ostream &o, const FIDMap& FM, // Methods of PlistDiagnostics. //===----------------------------------------------------------------------===// -PlistDiagnostics::PlistDiagnostics(AnalyzerOptions &AnalyzerOpts, - const std::string& output, - const Preprocessor &PP, - bool supportsMultipleFiles) - : OutputFile(output), PP(PP), AnOpts(AnalyzerOpts), - SupportsCrossFileDiagnostics(supportsMultipleFiles) {} - -void ento::createPlistDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts, - PathDiagnosticConsumers &C, - const std::string& s, - const Preprocessor &PP) { - C.push_back(new PlistDiagnostics(AnalyzerOpts, s, PP, +PlistDiagnostics::PlistDiagnostics( + AnalyzerOptions &AnalyzerOpts, const std::string &output, + const Preprocessor &PP, const cross_tu::CrossTranslationUnitContext &CTU, + bool supportsMultipleFiles) + : OutputFile(output), PP(PP), CTU(CTU), AnOpts(AnalyzerOpts), + SupportsCrossFileDiagnostics(supportsMultipleFiles) { + // FIXME: Will be used by a later planned change. + (void)CTU; +} + +void ento::createPlistDiagnosticConsumer( + AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, + const std::string &s, const Preprocessor &PP, + const cross_tu::CrossTranslationUnitContext &CTU) { + C.push_back(new PlistDiagnostics(AnalyzerOpts, s, PP, CTU, /*supportsMultipleFiles*/ false)); } -void ento::createPlistMultiFileDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts, - PathDiagnosticConsumers &C, - const std::string &s, - const Preprocessor &PP) { - C.push_back(new PlistDiagnostics(AnalyzerOpts, s, PP, +void ento::createPlistMultiFileDiagnosticConsumer( + AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, + const std::string &s, const Preprocessor &PP, + const cross_tu::CrossTranslationUnitContext &CTU) { + C.push_back(new PlistDiagnostics(AnalyzerOpts, s, PP, CTU, /*supportsMultipleFiles*/ true)); } void PlistDiagnostics::FlushDiagnosticsImpl( diff --git a/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp b/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp index 4233f25edb349..4fbbf908caa8c 100644 --- a/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp +++ b/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp @@ -43,10 +43,10 @@ class SarifDiagnostics : public PathDiagnosticConsumer { }; } // end anonymous namespace -void ento::createSarifDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts, - PathDiagnosticConsumers &C, - const std::string &Output, - const Preprocessor &) { +void ento::createSarifDiagnosticConsumer( + AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, + const std::string &Output, const Preprocessor &, + const cross_tu::CrossTranslationUnitContext &) { C.push_back(new SarifDiagnostics(AnalyzerOpts, Output)); } diff --git a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp index 454b61fd51a14..ce3deee7b2659 100644 --- a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp +++ b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp @@ -64,19 +64,19 @@ STATISTIC(MaxCFGSize, "The maximum number of basic blocks in a function."); // Special PathDiagnosticConsumers. //===----------------------------------------------------------------------===// -void ento::createPlistHTMLDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts, - PathDiagnosticConsumers &C, - const std::string &prefix, - const Preprocessor &PP) { +void ento::createPlistHTMLDiagnosticConsumer( + AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, + const std::string &prefix, const Preprocessor &PP, + const cross_tu::CrossTranslationUnitContext &CTU) { createHTMLDiagnosticConsumer(AnalyzerOpts, C, - llvm::sys::path::parent_path(prefix), PP); - createPlistMultiFileDiagnosticConsumer(AnalyzerOpts, C, prefix, PP); + llvm::sys::path::parent_path(prefix), PP, CTU); + createPlistMultiFileDiagnosticConsumer(AnalyzerOpts, C, prefix, PP, CTU); } -void ento::createTextPathDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts, - PathDiagnosticConsumers &C, - const std::string &Prefix, - const clang::Preprocessor &PP) { +void ento::createTextPathDiagnosticConsumer( + AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, + const std::string &Prefix, const clang::Preprocessor &PP, + const cross_tu::CrossTranslationUnitContext &CTU) { llvm_unreachable("'text' consumer should be enabled on ClangDiags"); } @@ -249,7 +249,7 @@ class AnalysisConsumer : public AnalysisASTConsumer, default: #define ANALYSIS_DIAGNOSTICS(NAME, CMDFLAG, DESC, CREATEFN) \ case PD_##NAME: \ - CREATEFN(*Opts.get(), PathConsumers, OutDir, PP); \ + CREATEFN(*Opts.get(), PathConsumers, OutDir, PP, CTU); \ break; #include "clang/StaticAnalyzer/Core/Analyses.def" } From c1187cc8820a85896517959d563af4e1da55b8eb Mon Sep 17 00:00:00 2001 From: Balazs Keri <1.int32@gmail.com> Date: Tue, 23 Jul 2019 10:21:42 +0000 Subject: [PATCH 068/181] PlistDiagnostics Fix for compile warning (NFC). llvm-svn: 366792 (cherry picked from commit 739a93558f8c4b3ed0aeee48bba62a989e9212bd) --- clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp b/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp index 893f594f83c2f..c7424916be50f 100644 --- a/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp +++ b/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp @@ -527,7 +527,7 @@ PlistDiagnostics::PlistDiagnostics( : OutputFile(output), PP(PP), CTU(CTU), AnOpts(AnalyzerOpts), SupportsCrossFileDiagnostics(supportsMultipleFiles) { // FIXME: Will be used by a later planned change. - (void)CTU; + (void)this->CTU; } void ento::createPlistDiagnosticConsumer( From c1b1f83f0a78b9b034223e1004d032c16ab1e27a Mon Sep 17 00:00:00 2001 From: Aaron Ballman Date: Wed, 24 Jul 2019 20:03:27 +0000 Subject: [PATCH 069/181] Fix exporting SARIF files from scan-build on Windows. In Perl, -z is defined as checking if a "file has zero size" and makes no mention what it does when given a directory. It looks like the behavior differs across platforms, which is why on Windows the SARIF file was always being deleted. Patch by Joe Ranieri. llvm-svn: 366941 (cherry picked from commit 70964d42ea413256b7aa763088a02ba08c6a9087) --- clang/tools/scan-build/libexec/ccc-analyzer | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clang/tools/scan-build/libexec/ccc-analyzer b/clang/tools/scan-build/libexec/ccc-analyzer index 9a4548f1671a5..277ed9f83af72 100755 --- a/clang/tools/scan-build/libexec/ccc-analyzer +++ b/clang/tools/scan-build/libexec/ccc-analyzer @@ -118,7 +118,7 @@ my $ResultFile; # Remove any stale files at exit. END { - if (defined $ResultFile && -z $ResultFile) { + if (defined $ResultFile && $ResultFile ne "") { unlink($ResultFile); } if (defined $CleanupFile) { @@ -752,7 +752,7 @@ if ($Action eq 'compile' or $Action eq 'link') { DIR => $HtmlDir); $ResultFile = $f; # If the HtmlDir is not set, we should clean up the plist files. - if (!defined $HtmlDir || -z $HtmlDir) { + if (!defined $HtmlDir || $HtmlDir eq "") { $CleanupFile = $f; } } From 34408f6bcee2c27bb6f3f44222239da03a93f7df Mon Sep 17 00:00:00 2001 From: Balazs Keri <1.int32@gmail.com> Date: Wed, 24 Jul 2019 10:16:37 +0000 Subject: [PATCH 070/181] [CrossTU] Add a function to retrieve original source location. Summary: A new function will be added to get the original SourceLocation for a SourceLocation that was imported as result of getCrossTUDefinition. The returned SourceLocation is in the context of the (original) SourceManager for the original source file. Additionally the ASTUnit object for that source file is returned. This is needed to get a SourceManager to operate on with the returned source location. The new function works if multiple different source files are loaded with the same CrossTU context. Reviewers: martong, shafik Reviewed By: martong Subscribers: rnkovacs, dkrupp, Szelethus, gamesh411, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D65064 llvm-svn: 366884 (cherry picked from commit d22f877356addf54b0b7a09e20b7f61a91ef49d9) --- clang/include/clang/AST/ASTImporter.h | 12 +++++ .../clang/CrossTU/CrossTranslationUnit.h | 33 +++++++++++-- clang/lib/AST/ASTImporter.cpp | 4 ++ clang/lib/CrossTU/CrossTranslationUnit.cpp | 46 +++++++++++++++---- .../CrossTU/CrossTranslationUnitTest.cpp | 39 +++++++++++++--- 5 files changed, 116 insertions(+), 18 deletions(-) diff --git a/clang/include/clang/AST/ASTImporter.h b/clang/include/clang/AST/ASTImporter.h index 25f45888800e1..83003a8d9ad8c 100644 --- a/clang/include/clang/AST/ASTImporter.h +++ b/clang/include/clang/AST/ASTImporter.h @@ -86,6 +86,8 @@ class TypeSourceInfo; using NonEquivalentDeclSet = llvm::DenseSet>; using ImportedCXXBaseSpecifierMap = llvm::DenseMap; + using FileIDImportHandlerType = + std::function; enum class ODRHandlingType { Conservative, Liberal }; @@ -211,6 +213,8 @@ class TypeSourceInfo; }; private: + FileIDImportHandlerType FileIDImportHandler; + std::shared_ptr SharedState = nullptr; /// The path which we go through during the import of a given AST node. @@ -313,6 +317,14 @@ class TypeSourceInfo; virtual ~ASTImporter(); + /// Set a callback function for FileID import handling. + /// The function is invoked when a FileID is imported from the From context. + /// The imported FileID in the To context and the original FileID in the + /// From context is passed to it. + void setFileIDImportHandler(FileIDImportHandlerType H) { + FileIDImportHandler = H; + } + /// Whether the importer will perform a minimal import, creating /// to-be-completed forward declarations when possible. bool isMinimalImport() const { return Minimal; } diff --git a/clang/include/clang/CrossTU/CrossTranslationUnit.h b/clang/include/clang/CrossTU/CrossTranslationUnit.h index d64329cdff3e6..3e3d2ba70b312 100644 --- a/clang/include/clang/CrossTU/CrossTranslationUnit.h +++ b/clang/include/clang/CrossTU/CrossTranslationUnit.h @@ -153,8 +153,10 @@ class CrossTranslationUnitContext { /// was passed to the constructor. /// /// \return Returns the resulting definition or an error. - llvm::Expected importDefinition(const FunctionDecl *FD); - llvm::Expected importDefinition(const VarDecl *VD); + llvm::Expected importDefinition(const FunctionDecl *FD, + ASTUnit *Unit); + llvm::Expected importDefinition(const VarDecl *VD, + ASTUnit *Unit); /// Get a name to identify a named decl. static std::string getLookupName(const NamedDecl *ND); @@ -162,9 +164,23 @@ class CrossTranslationUnitContext { /// Emit diagnostics for the user for potential configuration errors. void emitCrossTUDiagnostics(const IndexError &IE); + /// Determine the original source location in the original TU for an + /// imported source location. + /// \p ToLoc Source location in the imported-to AST. + /// \return Source location in the imported-from AST and the corresponding + /// ASTUnit object (the AST was loaded from a file using an internal ASTUnit + /// object that is returned here). + /// If any error happens (ToLoc is a non-imported source location) empty is + /// returned. + llvm::Optional> + getImportedFromSourceLocation(const clang::SourceLocation &ToLoc) const; + private: + using ImportedFileIDMap = + llvm::DenseMap>; + void lazyInitImporterSharedSt(TranslationUnitDecl *ToTU); - ASTImporter &getOrCreateASTImporter(ASTContext &From); + ASTImporter &getOrCreateASTImporter(ASTUnit *Unit); template llvm::Expected getCrossTUDefinitionImpl(const T *D, StringRef CrossTUDir, @@ -174,7 +190,7 @@ class CrossTranslationUnitContext { const T *findDefInDeclContext(const DeclContext *DC, StringRef LookupName); template - llvm::Expected importDefinitionImpl(const T *D); + llvm::Expected importDefinitionImpl(const T *D, ASTUnit *Unit); llvm::StringMap> FileASTUnitMap; llvm::StringMap NameASTUnitMap; @@ -184,6 +200,15 @@ class CrossTranslationUnitContext { CompilerInstance &CI; ASTContext &Context; std::shared_ptr ImporterSharedSt; + /// Map of imported FileID's (in "To" context) to FileID in "From" context + /// and the ASTUnit for the From context. + /// This map is used by getImportedFromSourceLocation to lookup a FileID and + /// its Preprocessor when knowing only the FileID in the 'To' context. The + /// FileID could be imported by any of multiple 'From' ASTImporter objects. + /// we do not want to loop over all ASTImporter's to find the one that + /// imported the FileID. + ImportedFileIDMap ImportedFileIDs; + /// \p CTULoadTreshold should serve as an upper limit to the number of TUs /// imported in order to reduce the memory footprint of CTU analysis. const unsigned CTULoadThreshold; diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp index e4bb0a6db361a..eef42033d5127 100644 --- a/clang/lib/AST/ASTImporter.cpp +++ b/clang/lib/AST/ASTImporter.cpp @@ -8496,6 +8496,10 @@ Expected ASTImporter::Import(FileID FromID, bool IsBuiltin) { assert(ToID.isValid() && "Unexpected invalid fileID was created."); ImportedFileIDs[FromID] = ToID; + + if (FileIDImportHandler) + FileIDImportHandler(ToID, FromID); + return ToID; } diff --git a/clang/lib/CrossTU/CrossTranslationUnit.cpp b/clang/lib/CrossTU/CrossTranslationUnit.cpp index 2264ee0ab752f..880129d23151a 100644 --- a/clang/lib/CrossTU/CrossTranslationUnit.cpp +++ b/clang/lib/CrossTU/CrossTranslationUnit.cpp @@ -291,7 +291,7 @@ llvm::Expected CrossTranslationUnitContext::getCrossTUDefinitionImpl( TranslationUnitDecl *TU = Unit->getASTContext().getTranslationUnitDecl(); if (const T *ResultDecl = findDefInDeclContext(TU, LookupName)) - return importDefinition(ResultDecl); + return importDefinition(ResultDecl, Unit); return llvm::make_error(index_error_code::failed_import); } @@ -407,10 +407,13 @@ llvm::Expected CrossTranslationUnitContext::loadExternalAST( template llvm::Expected -CrossTranslationUnitContext::importDefinitionImpl(const T *D) { +CrossTranslationUnitContext::importDefinitionImpl(const T *D, ASTUnit *Unit) { assert(hasBodyOrInit(D) && "Decls to be imported should have body or init."); - ASTImporter &Importer = getOrCreateASTImporter(D->getASTContext()); + assert(&D->getASTContext() == &Unit->getASTContext() && + "ASTContext of Decl and the unit should match."); + ASTImporter &Importer = getOrCreateASTImporter(Unit); + auto ToDeclOrError = Importer.Import(D); if (!ToDeclOrError) { handleAllErrors(ToDeclOrError.takeError(), @@ -437,13 +440,15 @@ CrossTranslationUnitContext::importDefinitionImpl(const T *D) { } llvm::Expected -CrossTranslationUnitContext::importDefinition(const FunctionDecl *FD) { - return importDefinitionImpl(FD); +CrossTranslationUnitContext::importDefinition(const FunctionDecl *FD, + ASTUnit *Unit) { + return importDefinitionImpl(FD, Unit); } llvm::Expected -CrossTranslationUnitContext::importDefinition(const VarDecl *VD) { - return importDefinitionImpl(VD); +CrossTranslationUnitContext::importDefinition(const VarDecl *VD, + ASTUnit *Unit) { + return importDefinitionImpl(VD, Unit); } void CrossTranslationUnitContext::lazyInitImporterSharedSt( @@ -453,7 +458,9 @@ void CrossTranslationUnitContext::lazyInitImporterSharedSt( } ASTImporter & -CrossTranslationUnitContext::getOrCreateASTImporter(ASTContext &From) { +CrossTranslationUnitContext::getOrCreateASTImporter(ASTUnit *Unit) { + ASTContext &From = Unit->getASTContext(); + auto I = ASTUnitImporterMap.find(From.getTranslationUnitDecl()); if (I != ASTUnitImporterMap.end()) return *I->second; @@ -461,9 +468,32 @@ CrossTranslationUnitContext::getOrCreateASTImporter(ASTContext &From) { ASTImporter *NewImporter = new ASTImporter( Context, Context.getSourceManager().getFileManager(), From, From.getSourceManager().getFileManager(), false, ImporterSharedSt); + NewImporter->setFileIDImportHandler([this, Unit](FileID ToID, FileID FromID) { + assert(ImportedFileIDs.find(ToID) == ImportedFileIDs.end() && + "FileID already imported, should not happen."); + ImportedFileIDs[ToID] = std::make_pair(FromID, Unit); + }); ASTUnitImporterMap[From.getTranslationUnitDecl()].reset(NewImporter); return *NewImporter; } +llvm::Optional> +CrossTranslationUnitContext::getImportedFromSourceLocation( + const clang::SourceLocation &ToLoc) const { + const SourceManager &SM = Context.getSourceManager(); + auto DecToLoc = SM.getDecomposedLoc(ToLoc); + + auto I = ImportedFileIDs.find(DecToLoc.first); + if (I == ImportedFileIDs.end()) + return {}; + + FileID FromID = I->second.first; + clang::ASTUnit *Unit = I->second.second; + SourceLocation FromLoc = + Unit->getSourceManager().getComposedLoc(FromID, DecToLoc.second); + + return std::make_pair(FromLoc, Unit); +} + } // namespace cross_tu } // namespace clang diff --git a/clang/unittests/CrossTU/CrossTranslationUnitTest.cpp b/clang/unittests/CrossTU/CrossTranslationUnitTest.cpp index b3e7243ce1d22..b4f22d5cb0324 100644 --- a/clang/unittests/CrossTU/CrossTranslationUnitTest.cpp +++ b/clang/unittests/CrossTU/CrossTranslationUnitTest.cpp @@ -28,13 +28,18 @@ class CTUASTConsumer : public clang::ASTConsumer { : CTU(CI), Success(Success) {} void HandleTranslationUnit(ASTContext &Ctx) { + auto FindFInTU = [](const TranslationUnitDecl *TU) { + const FunctionDecl *FD = nullptr; + for (const Decl *D : TU->decls()) { + FD = dyn_cast(D); + if (FD && FD->getName() == "f") + break; + } + return FD; + }; + const TranslationUnitDecl *TU = Ctx.getTranslationUnitDecl(); - const FunctionDecl *FD = nullptr; - for (const Decl *D : TU->decls()) { - FD = dyn_cast(D); - if (FD && FD->getName() == "f") - break; - } + const FunctionDecl *FD = FindFInTU(TU); assert(FD && FD->getName() == "f"); bool OrigFDHasBody = FD->hasBody(); @@ -78,6 +83,28 @@ class CTUASTConsumer : public clang::ASTConsumer { if (NewFDorError) { const FunctionDecl *NewFD = *NewFDorError; *Success = NewFD && NewFD->hasBody() && !OrigFDHasBody; + + if (NewFD) { + // Check GetImportedFromSourceLocation. + llvm::Optional> SLocResult = + CTU.getImportedFromSourceLocation(NewFD->getLocation()); + EXPECT_TRUE(SLocResult); + if (SLocResult) { + SourceLocation OrigSLoc = (*SLocResult).first; + ASTUnit *OrigUnit = (*SLocResult).second; + // OrigUnit is created internally by CTU (is not the + // ASTWithDefinition). + TranslationUnitDecl *OrigTU = + OrigUnit->getASTContext().getTranslationUnitDecl(); + const FunctionDecl *FDWithDefinition = FindFInTU(OrigTU); + EXPECT_TRUE(FDWithDefinition); + if (FDWithDefinition) { + EXPECT_EQ(FDWithDefinition->getName(), "f"); + EXPECT_TRUE(FDWithDefinition->isThisDeclarationADefinition()); + EXPECT_EQ(OrigSLoc, FDWithDefinition->getLocation()); + } + } + } } } From 83306e8c3c2827c062db5a81af898c8ad22ce367 Mon Sep 17 00:00:00 2001 From: Balazs Keri <1.int32@gmail.com> Date: Thu, 25 Jul 2019 10:53:22 +0000 Subject: [PATCH 071/181] [CrossTU] Fix plist macro expansion if macro in other file. Summary: When cross TU analysis is used it is possible that a macro expansion is generated for a macro that is defined (and used) in other than the main translation unit. To get the expansion for it the source location in the original source file and original preprocessor is needed. Reviewers: martong, xazax.hun, Szelethus, ilya-biryukov Reviewed By: Szelethus Subscribers: mgorny, NoQ, ilya-biryukov, rnkovacs, dkrupp, Szelethus, gamesh411, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D64638 llvm-svn: 367006 (cherry picked from commit aeac909329a76993314c26ec1a1973c07a79db81) --- clang/lib/StaticAnalyzer/Core/CMakeLists.txt | 1 + .../StaticAnalyzer/Core/PlistDiagnostics.cpp | 44 ++++++---- clang/test/Analysis/Inputs/plist-macros-ctu.c | 21 +++++ clang/test/Analysis/Inputs/plist-macros-ctu.h | 4 + ...os-with-expansion-ctu.c.externalDefMap.txt | 4 + .../plist-macros-with-expansion-ctu.c | 80 +++++++++++++++++++ 6 files changed, 137 insertions(+), 17 deletions(-) create mode 100644 clang/test/Analysis/Inputs/plist-macros-ctu.c create mode 100644 clang/test/Analysis/Inputs/plist-macros-ctu.h create mode 100644 clang/test/Analysis/Inputs/plist-macros-with-expansion-ctu.c.externalDefMap.txt create mode 100644 clang/test/Analysis/plist-macros-with-expansion-ctu.c diff --git a/clang/lib/StaticAnalyzer/Core/CMakeLists.txt b/clang/lib/StaticAnalyzer/Core/CMakeLists.txt index 942aedd3889c0..0affa8be5572d 100644 --- a/clang/lib/StaticAnalyzer/Core/CMakeLists.txt +++ b/clang/lib/StaticAnalyzer/Core/CMakeLists.txt @@ -53,6 +53,7 @@ add_clang_library(clangStaticAnalyzerCore clangAnalysis clangBasic clangCrossTU + clangFrontend clangLex clangRewrite ) diff --git a/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp b/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp index c7424916be50f..15b8e0c41c3fb 100644 --- a/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp +++ b/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp @@ -15,6 +15,7 @@ #include "clang/Basic/SourceManager.h" #include "clang/Basic/Version.h" #include "clang/CrossTU/CrossTranslationUnit.h" +#include "clang/Frontend/ASTUnit.h" #include "clang/Lex/Preprocessor.h" #include "clang/Lex/TokenConcatenation.h" #include "clang/Rewrite/Core/HTMLRewrite.h" @@ -75,12 +76,14 @@ class PlistPrinter { const FIDMap& FM; AnalyzerOptions &AnOpts; const Preprocessor &PP; + const cross_tu::CrossTranslationUnitContext &CTU; llvm::SmallVector MacroPieces; public: PlistPrinter(const FIDMap& FM, AnalyzerOptions &AnOpts, - const Preprocessor &PP) - : FM(FM), AnOpts(AnOpts), PP(PP) { + const Preprocessor &PP, + const cross_tu::CrossTranslationUnitContext &CTU) + : FM(FM), AnOpts(AnOpts), PP(PP), CTU(CTU) { } void ReportDiag(raw_ostream &o, const PathDiagnosticPiece& P) { @@ -162,8 +165,8 @@ struct ExpansionInfo { } // end of anonymous namespace static void printBugPath(llvm::raw_ostream &o, const FIDMap& FM, - AnalyzerOptions &AnOpts, - const Preprocessor &PP, + AnalyzerOptions &AnOpts, const Preprocessor &PP, + const cross_tu::CrossTranslationUnitContext &CTU, const PathPieces &Path); /// Print coverage information to output stream {@code o}. @@ -174,8 +177,9 @@ static void printCoverage(const PathDiagnostic *D, FIDMap &FM, llvm::raw_fd_ostream &o); -static ExpansionInfo getExpandedMacro(SourceLocation MacroLoc, - const Preprocessor &PP); +static ExpansionInfo +getExpandedMacro(SourceLocation MacroLoc, const Preprocessor &PP, + const cross_tu::CrossTranslationUnitContext &CTU); //===----------------------------------------------------------------------===// // Methods of PlistPrinter. @@ -349,7 +353,7 @@ void PlistPrinter::ReportMacroExpansions(raw_ostream &o, unsigned indent) { for (const PathDiagnosticMacroPiece *P : MacroPieces) { const SourceManager &SM = PP.getSourceManager(); - ExpansionInfo EI = getExpandedMacro(P->getLocation().asLocation(), PP); + ExpansionInfo EI = getExpandedMacro(P->getLocation().asLocation(), PP, CTU); Indent(o, indent) << "\n"; ++indent; @@ -471,10 +475,10 @@ static void printCoverage(const PathDiagnostic *D, } static void printBugPath(llvm::raw_ostream &o, const FIDMap& FM, - AnalyzerOptions &AnOpts, - const Preprocessor &PP, + AnalyzerOptions &AnOpts, const Preprocessor &PP, + const cross_tu::CrossTranslationUnitContext &CTU, const PathPieces &Path) { - PlistPrinter Printer(FM, AnOpts, PP); + PlistPrinter Printer(FM, AnOpts, PP, CTU); assert(std::is_partitioned( Path.begin(), Path.end(), [](const std::shared_ptr &E) @@ -619,7 +623,7 @@ void PlistDiagnostics::FlushDiagnosticsImpl( o << " \n"; const PathDiagnostic *D = *DI; - printBugPath(o, FM, AnOpts, PP, D->path); + printBugPath(o, FM, AnOpts, PP, CTU, D->path); // Output the bug type and bug category. o << " description"; @@ -872,17 +876,23 @@ static const MacroInfo *getMacroInfoForLocation(const Preprocessor &PP, // Definitions of helper functions and methods for expanding macros. //===----------------------------------------------------------------------===// -static ExpansionInfo getExpandedMacro(SourceLocation MacroLoc, - const Preprocessor &PP) { +static ExpansionInfo +getExpandedMacro(SourceLocation MacroLoc, const Preprocessor &PP, + const cross_tu::CrossTranslationUnitContext &CTU) { + + const Preprocessor *PPToUse = &PP; + if (auto LocAndUnit = CTU.getImportedFromSourceLocation(MacroLoc)) { + MacroLoc = LocAndUnit->first; + PPToUse = &LocAndUnit->second->getPreprocessor(); + } llvm::SmallString<200> ExpansionBuf; llvm::raw_svector_ostream OS(ExpansionBuf); - TokenPrinter Printer(OS, PP); + TokenPrinter Printer(OS, *PPToUse); llvm::SmallPtrSet AlreadyProcessedTokens; - std::string MacroName = - getMacroNameAndPrintExpansion(Printer, MacroLoc, PP, MacroArgMap{}, - AlreadyProcessedTokens); + std::string MacroName = getMacroNameAndPrintExpansion( + Printer, MacroLoc, *PPToUse, MacroArgMap{}, AlreadyProcessedTokens); return { MacroName, OS.str() }; } diff --git a/clang/test/Analysis/Inputs/plist-macros-ctu.c b/clang/test/Analysis/Inputs/plist-macros-ctu.c new file mode 100644 index 0000000000000..f3e374caae58b --- /dev/null +++ b/clang/test/Analysis/Inputs/plist-macros-ctu.c @@ -0,0 +1,21 @@ + +#include "plist-macros-ctu.h" + +#define M *X = (int *)0 + +void F1(int **X) { + M; +} + +#undef M +#define M *Y = (int *)0 + +void F2(int **Y) { + M; +} + +#define M1 *Z = (int *)0 + +void F3(int **Z) { + M1; +} diff --git a/clang/test/Analysis/Inputs/plist-macros-ctu.h b/clang/test/Analysis/Inputs/plist-macros-ctu.h new file mode 100644 index 0000000000000..0b1b9d7a4f533 --- /dev/null +++ b/clang/test/Analysis/Inputs/plist-macros-ctu.h @@ -0,0 +1,4 @@ +#define M_H *A = (int *)0 +void F_H(int **A) { + M_H; +} diff --git a/clang/test/Analysis/Inputs/plist-macros-with-expansion-ctu.c.externalDefMap.txt b/clang/test/Analysis/Inputs/plist-macros-with-expansion-ctu.c.externalDefMap.txt new file mode 100644 index 0000000000000..52214a41891ef --- /dev/null +++ b/clang/test/Analysis/Inputs/plist-macros-with-expansion-ctu.c.externalDefMap.txt @@ -0,0 +1,4 @@ +c:@F@F1 plist-macros-ctu.c.ast +c:@F@F2 plist-macros-ctu.c.ast +c:@F@F3 plist-macros-ctu.c.ast +c:@F@F_H plist-macros-ctu.c.ast diff --git a/clang/test/Analysis/plist-macros-with-expansion-ctu.c b/clang/test/Analysis/plist-macros-with-expansion-ctu.c new file mode 100644 index 0000000000000..ff99b619fd77c --- /dev/null +++ b/clang/test/Analysis/plist-macros-with-expansion-ctu.c @@ -0,0 +1,80 @@ +// RUN: rm -rf %t && mkdir %t +// RUN: mkdir -p %t/ctudir +// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu \ +// RUN: -emit-pch -o %t/ctudir/plist-macros-ctu.c.ast %S/Inputs/plist-macros-ctu.c +// RUN: cp %S/Inputs/plist-macros-with-expansion-ctu.c.externalDefMap.txt %t/ctudir/externalDefMap.txt + +// RUN: %clang_analyze_cc1 -analyzer-checker=core \ +// RUN: -analyzer-config experimental-enable-naive-ctu-analysis=true \ +// RUN: -analyzer-config ctu-dir=%t/ctudir \ +// RUN: -analyzer-config expand-macros=true \ +// RUN: -analyzer-output=plist-multi-file -o %t.plist -verify %s + +// Check the macro expansions from the plist output here, to make the test more +// understandable. +// RUN: FileCheck --input-file=%t.plist %s + +extern void F1(int **); +extern void F2(int **); +extern void F3(int **); +extern void F_H(int **); + +void test0() { + int *X; + F3(&X); + *X = 1; // expected-warning{{Dereference of null pointer}} +} +// CHECK: nameM1 +// CHECK-NEXT: expansion*Z = (int *)0 + + +void test1() { + int *X; + F1(&X); + *X = 1; // expected-warning{{Dereference of null pointer}} +} +// CHECK: nameM +// CHECK-NEXT: expansion*X = (int *)0 + +void test2() { + int *X; + F2(&X); + *X = 1; // expected-warning{{Dereference of null pointer}} +} +// CHECK: nameM +// CHECK-NEXT: expansion*Y = (int *)0 + +#define M F1(&X) + +void test3() { + int *X; + M; + *X = 1; // expected-warning{{Dereference of null pointer}} +} +// CHECK: nameM +// CHECK-NEXT: expansionF1(&X) +// CHECK: nameM +// CHECK-NEXT: expansion*X = (int *)0 + +#undef M +#define M F2(&X) + +void test4() { + int *X; + M; + *X = 1; // expected-warning{{Dereference of null pointer}} +} + +// CHECK: nameM +// CHECK-NEXT: expansionF2(&X) +// CHECK: nameM +// CHECK-NEXT: expansion*Y = (int *)0 + +void test_h() { + int *X; + F_H(&X); + *X = 1; // expected-warning{{Dereference of null pointer}} +} + +// CHECK: nameM_H +// CHECK-NEXT: expansion*A = (int *)0 From fb6a2e4278637be6a5510a3b54df3c55a3aa7566 Mon Sep 17 00:00:00 2001 From: Balazs Keri <1.int32@gmail.com> Date: Thu, 25 Jul 2019 12:46:42 +0000 Subject: [PATCH 072/181] Fix failing test plist-macros-with-expansion-ctu.c llvm-svn: 367013 (cherry picked from commit f34da181d27399473599ce722e07dbb053b5d54a) --- clang/test/Analysis/plist-macros-with-expansion-ctu.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/clang/test/Analysis/plist-macros-with-expansion-ctu.c b/clang/test/Analysis/plist-macros-with-expansion-ctu.c index ff99b619fd77c..193b8f37480e7 100644 --- a/clang/test/Analysis/plist-macros-with-expansion-ctu.c +++ b/clang/test/Analysis/plist-macros-with-expansion-ctu.c @@ -1,7 +1,6 @@ // RUN: rm -rf %t && mkdir %t // RUN: mkdir -p %t/ctudir -// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu \ -// RUN: -emit-pch -o %t/ctudir/plist-macros-ctu.c.ast %S/Inputs/plist-macros-ctu.c +// RUN: %clang_cc1 -emit-pch -o %t/ctudir/plist-macros-ctu.c.ast %S/Inputs/plist-macros-ctu.c // RUN: cp %S/Inputs/plist-macros-with-expansion-ctu.c.externalDefMap.txt %t/ctudir/externalDefMap.txt // RUN: %clang_analyze_cc1 -analyzer-checker=core \ From d385af7333a30a8616ee1045ba6c8fdfbadd066d Mon Sep 17 00:00:00 2001 From: Gabor Borsik Date: Sun, 28 Jul 2019 13:38:04 +0000 Subject: [PATCH 073/181] [analyzer] Add yaml parser to GenericTaintChecker While we implemented taint propagation rules for several builtin/standard functions, there's a natural desire for users to add such rules to custom functions. A series of patches will implement an option that allows users to annotate their functions with taint propagation rules through a YAML file. This one adds parsing of the configuration file, which may be specified in the commands line with the analyzer config: alpha.security.taint.TaintPropagation:Config. The configuration may contain propagation rules, filter functions (remove taint) and sink functions (give a warning if it gets a tainted value). I also added a new header for future checkers to conveniently read YAML files as checker options. Differential Revision: https://reviews.llvm.org/D59555 llvm-svn: 367190 (cherry picked from commit 4bde15fe1e4a320b6f198588af4c73316e4d3dc9) --- .../clang/StaticAnalyzer/Checkers/Checkers.td | 7 + .../Checkers/GenericTaintChecker.cpp | 178 +++++++++++++++--- clang/lib/StaticAnalyzer/Checkers/Yaml.h | 59 ++++++ .../taint-generic-config-ill-formed.yaml | 4 + .../taint-generic-config-invalid-arg.yaml | 3 + .../Analysis/Inputs/taint-generic-config.yaml | 50 +++++ clang/test/Analysis/analyzer-config.c | 3 +- clang/test/Analysis/taint-generic.c | 48 ++++- 8 files changed, 327 insertions(+), 25 deletions(-) create mode 100755 clang/lib/StaticAnalyzer/Checkers/Yaml.h create mode 100755 clang/test/Analysis/Inputs/taint-generic-config-ill-formed.yaml create mode 100755 clang/test/Analysis/Inputs/taint-generic-config-invalid-arg.yaml create mode 100755 clang/test/Analysis/Inputs/taint-generic-config.yaml diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index 2b29efba66a41..f1ba5b676f159 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -799,6 +799,13 @@ let ParentPackage = Taint in { def GenericTaintChecker : Checker<"TaintPropagation">, HelpText<"Generate taint information used by other checkers">, + CheckerOptions<[ + CmdLineOption, + ]>, Documentation; } // end "alpha.security.taint" diff --git a/clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp index d3ab980332365..20e70b65eac2e 100644 --- a/clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp @@ -15,16 +15,18 @@ //===----------------------------------------------------------------------===// #include "Taint.h" -#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "Yaml.h" #include "clang/AST/Attr.h" #include "clang/Basic/Builtins.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" -#include -#include +#include "llvm/ADT/StringMap.h" +#include "llvm/Support/YAMLTraits.h" +#include #include using namespace clang; @@ -44,14 +46,51 @@ class GenericTaintChecker void checkPreStmt(const CallExpr *CE, CheckerContext &C) const; - void printState(raw_ostream &Out, ProgramStateRef State, - const char *NL, const char *Sep) const override; + void printState(raw_ostream &Out, ProgramStateRef State, const char *NL, + const char *Sep) const override; -private: - static const unsigned InvalidArgIndex = UINT_MAX; + using ArgVector = SmallVector; + using SignedArgVector = SmallVector; + + enum class VariadicType { None, Src, Dst }; + + /// Used to parse the configuration file. + struct TaintConfiguration { + using NameArgsPair = std::pair; + + struct Propagation { + std::string Name; + ArgVector SrcArgs; + SignedArgVector DstArgs; + VariadicType VarType; + unsigned VarIndex; + }; + + std::vector Propagations; + std::vector Filters; + std::vector Sinks; + + TaintConfiguration() = default; + TaintConfiguration(const TaintConfiguration &) = delete; + TaintConfiguration(TaintConfiguration &&) = default; + TaintConfiguration &operator=(const TaintConfiguration &) = delete; + TaintConfiguration &operator=(TaintConfiguration &&) = default; + }; + + /// Convert SignedArgVector to ArgVector. + ArgVector convertToArgVector(CheckerManager &Mgr, const std::string &Option, + SignedArgVector Args); + + /// Parse the config. + void parseConfiguration(CheckerManager &Mgr, const std::string &Option, + TaintConfiguration &&Config); + + static const unsigned InvalidArgIndex{std::numeric_limits::max()}; /// Denotes the return vale. - static const unsigned ReturnValueIndex = UINT_MAX - 1; + static const unsigned ReturnValueIndex{std::numeric_limits::max() - + 1}; +private: mutable std::unique_ptr BT; void initBugType() const { if (!BT) @@ -97,8 +136,6 @@ class GenericTaintChecker bool generateReportIfTainted(const Expr *E, const char Msg[], CheckerContext &C) const; - using ArgVector = SmallVector; - /// A struct used to specify taint propagation rules for a function. /// /// If any of the possible taint source arguments is tainted, all of the @@ -109,8 +146,6 @@ class GenericTaintChecker /// ReturnValueIndex is added to the dst list, the return value will be /// tainted. struct TaintPropagationRule { - enum class VariadicType { None, Src, Dst }; - using PropagationFuncType = bool (*)(bool IsTainted, const CallExpr *, CheckerContext &C); @@ -131,8 +166,7 @@ class GenericTaintChecker : VariadicIndex(InvalidArgIndex), VarType(VariadicType::None), PropagationFunc(nullptr) {} - TaintPropagationRule(std::initializer_list &&Src, - std::initializer_list &&Dst, + TaintPropagationRule(ArgVector &&Src, ArgVector &&Dst, VariadicType Var = VariadicType::None, unsigned VarIndex = InvalidArgIndex, PropagationFuncType Func = nullptr) @@ -176,6 +210,19 @@ class GenericTaintChecker static bool postSocket(bool IsTainted, const CallExpr *CE, CheckerContext &C); }; + + using NameRuleMap = llvm::StringMap; + using NameArgMap = llvm::StringMap; + + /// Defines a map between the propagation function's name and + /// TaintPropagationRule. + NameRuleMap CustomPropagations; + + /// Defines a map between the filter function's name and filtering args. + NameArgMap CustomFilters; + + /// Defines a map between the sink function's name and sinking args. + NameArgMap CustomSinks; }; const unsigned GenericTaintChecker::ReturnValueIndex; @@ -193,15 +240,94 @@ const char GenericTaintChecker::MsgTaintedBufferSize[] = "Untrusted data is used to specify the buffer size " "(CERT/STR31-C. Guarantee that storage for strings has sufficient space " "for character data and the null terminator)"; - } // end of anonymous namespace +using TaintConfig = GenericTaintChecker::TaintConfiguration; + +LLVM_YAML_IS_SEQUENCE_VECTOR(TaintConfig::Propagation) +LLVM_YAML_IS_SEQUENCE_VECTOR(TaintConfig::NameArgsPair) + +namespace llvm { +namespace yaml { +template <> struct MappingTraits { + static void mapping(IO &IO, TaintConfig &Config) { + IO.mapOptional("Propagations", Config.Propagations); + IO.mapOptional("Filters", Config.Filters); + IO.mapOptional("Sinks", Config.Sinks); + } +}; + +template <> struct MappingTraits { + static void mapping(IO &IO, TaintConfig::Propagation &Propagation) { + IO.mapRequired("Name", Propagation.Name); + IO.mapOptional("SrcArgs", Propagation.SrcArgs); + IO.mapOptional("DstArgs", Propagation.DstArgs); + IO.mapOptional("VariadicType", Propagation.VarType, + GenericTaintChecker::VariadicType::None); + IO.mapOptional("VariadicIndex", Propagation.VarIndex, + GenericTaintChecker::InvalidArgIndex); + } +}; + +template <> struct ScalarEnumerationTraits { + static void enumeration(IO &IO, GenericTaintChecker::VariadicType &Value) { + IO.enumCase(Value, "None", GenericTaintChecker::VariadicType::None); + IO.enumCase(Value, "Src", GenericTaintChecker::VariadicType::Src); + IO.enumCase(Value, "Dst", GenericTaintChecker::VariadicType::Dst); + } +}; + +template <> struct MappingTraits { + static void mapping(IO &IO, TaintConfig::NameArgsPair &NameArg) { + IO.mapRequired("Name", NameArg.first); + IO.mapRequired("Args", NameArg.second); + } +}; +} // namespace yaml +} // namespace llvm + /// A set which is used to pass information from call pre-visit instruction /// to the call post-visit. The values are unsigned integers, which are either /// ReturnValueIndex, or indexes of the pointer/reference argument, which /// points to data, which should be tainted on return. REGISTER_SET_WITH_PROGRAMSTATE(TaintArgsOnPostVisit, unsigned) +GenericTaintChecker::ArgVector GenericTaintChecker::convertToArgVector( + CheckerManager &Mgr, const std::string &Option, SignedArgVector Args) { + ArgVector Result; + for (int Arg : Args) { + if (Arg == -1) + Result.push_back(ReturnValueIndex); + else if (Arg < -1) { + Result.push_back(InvalidArgIndex); + Mgr.reportInvalidCheckerOptionValue( + this, Option, + "an argument number for propagation rules greater or equal to -1"); + } else + Result.push_back(static_cast(Arg)); + } + return Result; +} + +void GenericTaintChecker::parseConfiguration(CheckerManager &Mgr, + const std::string &Option, + TaintConfiguration &&Config) { + for (auto &P : Config.Propagations) { + GenericTaintChecker::CustomPropagations.try_emplace( + P.Name, std::move(P.SrcArgs), + convertToArgVector(Mgr, Option, P.DstArgs), P.VarType, P.VarIndex); + } + + for (auto &F : Config.Filters) { + GenericTaintChecker::CustomFilters.try_emplace(F.first, + std::move(F.second)); + } + + for (auto &S : Config.Sinks) { + GenericTaintChecker::CustomSinks.try_emplace(S.first, std::move(S.second)); + } +} + GenericTaintChecker::TaintPropagationRule GenericTaintChecker::TaintPropagationRule::getTaintPropagationRule( const FunctionDecl *FDecl, StringRef Name, CheckerContext &C) { @@ -218,7 +344,8 @@ GenericTaintChecker::TaintPropagationRule::getTaintPropagationRule( .Case("freopen", TaintPropagationRule({}, {ReturnValueIndex})) .Case("getch", TaintPropagationRule({}, {ReturnValueIndex})) .Case("getchar", TaintPropagationRule({}, {ReturnValueIndex})) - .Case("getchar_unlocked", TaintPropagationRule({}, {ReturnValueIndex})) + .Case("getchar_unlocked", + TaintPropagationRule({}, {ReturnValueIndex})) .Case("getenv", TaintPropagationRule({}, {ReturnValueIndex})) .Case("gets", TaintPropagationRule({}, {0, ReturnValueIndex})) .Case("scanf", TaintPropagationRule({}, {}, VariadicType::Dst, 1)) @@ -454,7 +581,7 @@ GenericTaintChecker::TaintPropagationRule::process(const CallExpr *CE, // Check for taint in variadic arguments. if (!IsTainted && VariadicType::Src == VarType) { // Check if any of the arguments is tainted - for (unsigned int i = VariadicIndex; i < CE->getNumArgs(); ++i) { + for (unsigned i = VariadicIndex; i < CE->getNumArgs(); ++i) { if ((IsTainted = isTaintedOrPointsToTainted(CE->getArg(i), State, C))) break; } @@ -485,7 +612,7 @@ GenericTaintChecker::TaintPropagationRule::process(const CallExpr *CE, // If they are not pointing to const data, mark data as tainted. // TODO: So far we are just going one level down; ideally we'd need to // recurse here. - for (unsigned int i = VariadicIndex; i < CE->getNumArgs(); ++i) { + for (unsigned i = VariadicIndex; i < CE->getNumArgs(); ++i) { const Expr *Arg = CE->getArg(i); // Process pointer argument. const Type *ArgTy = Arg->getType().getTypePtr(); @@ -550,7 +677,7 @@ bool GenericTaintChecker::isStdin(const Expr *E, CheckerContext &C) { static bool getPrintfFormatArgumentNum(const CallExpr *CE, const CheckerContext &C, - unsigned int &ArgNum) { + unsigned &ArgNum) { // Find if the function contains a format string argument. // Handles: fprintf, printf, sprintf, snprintf, vfprintf, vprintf, vsprintf, // vsnprintf, syslog, custom annotated functions. @@ -603,7 +730,7 @@ bool GenericTaintChecker::generateReportIfTainted(const Expr *E, bool GenericTaintChecker::checkUncontrolledFormatString( const CallExpr *CE, CheckerContext &C) const { // Check if the function contains a format string argument. - unsigned int ArgNum = 0; + unsigned ArgNum = 0; if (!getPrintfFormatArgumentNum(CE, C, ArgNum)) return false; @@ -676,8 +803,15 @@ bool GenericTaintChecker::checkTaintedBufferSize(const CallExpr *CE, generateReportIfTainted(CE->getArg(ArgNum), MsgTaintedBufferSize, C); } -void ento::registerGenericTaintChecker(CheckerManager &mgr) { - mgr.registerChecker(); +void ento::registerGenericTaintChecker(CheckerManager &Mgr) { + auto *Checker = Mgr.registerChecker(); + std::string Option{"Config"}; + StringRef ConfigFile = + Mgr.getAnalyzerOptions().getCheckerStringOption(Checker, Option); + llvm::Optional Config = + getConfiguration(Mgr, Checker, Option, ConfigFile); + if (Config) + Checker->parseConfiguration(Mgr, Option, std::move(Config).getValue()); } bool ento::shouldRegisterGenericTaintChecker(const LangOptions &LO) { diff --git a/clang/lib/StaticAnalyzer/Checkers/Yaml.h b/clang/lib/StaticAnalyzer/Checkers/Yaml.h new file mode 100755 index 0000000000000..968c50e33f6d6 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/Yaml.h @@ -0,0 +1,59 @@ +//== Yaml.h ---------------------------------------------------- -*- C++ -*--=// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines convenience functions for handling YAML configuration files +// for checkers/packages. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKER_YAML_H +#define LLVM_CLANG_LIB_STATICANALYZER_CHECKER_YAML_H + +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "llvm/Support/YAMLTraits.h" + +namespace clang { +namespace ento { + +/// Read the given file from the filesystem and parse it as a yaml file. The +/// template parameter must have a yaml MappingTraits. +/// Emit diagnostic error in case of any failure. +template +llvm::Optional getConfiguration(CheckerManager &Mgr, Checker *Chk, + StringRef Option, StringRef ConfigFile) { + if (ConfigFile.trim().empty()) + return None; + + llvm::vfs::FileSystem *FS = llvm::vfs::getRealFileSystem().get(); + llvm::ErrorOr> Buffer = + FS->getBufferForFile(ConfigFile.str()); + + if (std::error_code ec = Buffer.getError()) { + Mgr.reportInvalidCheckerOptionValue(Chk, Option, + "a valid filename instead of '" + + std::string(ConfigFile) + "'"); + return None; + } + + llvm::yaml::Input Input(Buffer.get()->getBuffer()); + T Config; + Input >> Config; + + if (std::error_code ec = Input.error()) { + Mgr.reportInvalidCheckerOptionValue(Chk, Option, + "a valid yaml file: " + ec.message()); + return None; + } + + return Config; +} + +} // namespace ento +} // namespace clang + +#endif // LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_MOVE_H diff --git a/clang/test/Analysis/Inputs/taint-generic-config-ill-formed.yaml b/clang/test/Analysis/Inputs/taint-generic-config-ill-formed.yaml new file mode 100755 index 0000000000000..9b79b32027942 --- /dev/null +++ b/clang/test/Analysis/Inputs/taint-generic-config-ill-formed.yaml @@ -0,0 +1,4 @@ +Propagations: + - Name: mySource1 + DstArgs: [-1] + NotExist: 1 diff --git a/clang/test/Analysis/Inputs/taint-generic-config-invalid-arg.yaml b/clang/test/Analysis/Inputs/taint-generic-config-invalid-arg.yaml new file mode 100755 index 0000000000000..c06aef83de840 --- /dev/null +++ b/clang/test/Analysis/Inputs/taint-generic-config-invalid-arg.yaml @@ -0,0 +1,3 @@ +Propagations: + - Name: mySource1 + DstArgs: [-2] diff --git a/clang/test/Analysis/Inputs/taint-generic-config.yaml b/clang/test/Analysis/Inputs/taint-generic-config.yaml new file mode 100755 index 0000000000000..0a822d7750398 --- /dev/null +++ b/clang/test/Analysis/Inputs/taint-generic-config.yaml @@ -0,0 +1,50 @@ +# A list of source/propagation function +Propagations: + # int x = mySource1(); // x is tainted + - Name: mySource1 + DstArgs: [-1] # Index for return value + + # int x; + # mySource2(&x); // x is tainted + - Name: mySource2 + DstArgs: [0] + + # int x, y; + # myScanf("%d %d", &x, &y); // x and y are tainted + - Name: myScanf + VariadicType: Dst + VariadicIndex: 1 + + # int x; // x is tainted + # int y; + # myPropagator(x, &y); // y is tainted + - Name: myPropagator + SrcArgs: [0] + DstArgs: [1] + + # constexpr unsigned size = 100; + # char buf[size]; + # int x, y; + # int n = mySprintf(buf, size, "%d %d", x, y); // If size, x or y is tainted + # // the return value and the buf will be tainted + - Name: mySnprintf + SrcArgs: [1] + DstArgs: [0, -1] + VariadicType: Src + VariadicIndex: 3 + +# A list of filter functions +Filters: + # int x; // x is tainted + # myFilter(&x); // x is not tainted anymore + - Name: myFilter + Args: [0] + +# A list of sink functions +Sinks: + # int x, y; // x and y are tainted + # mySink(x, 0, 1); // It will warn + # mySink(0, 1, y); // It will warn + # mySink(0, x, 1); // It won't warn + - Name: mySink + Args: [0, 2] diff --git a/clang/test/Analysis/analyzer-config.c b/clang/test/Analysis/analyzer-config.c index 26fa5d22068cf..7cba52fdaba2a 100644 --- a/clang/test/Analysis/analyzer-config.c +++ b/clang/test/Analysis/analyzer-config.c @@ -9,6 +9,7 @@ // CHECK-NEXT: alpha.clone.CloneChecker:ReportNormalClones = true // CHECK-NEXT: alpha.security.MmapWriteExec:MmapProtExec = 0x04 // CHECK-NEXT: alpha.security.MmapWriteExec:MmapProtRead = 0x01 +// CHECK-NEXT: alpha.security.taint.TaintPropagation:Config = "" // CHECK-NEXT: avoid-suppressing-null-argument-paths = false // CHECK-NEXT: c++-allocator-inlining = true // CHECK-NEXT: c++-container-inlining = false @@ -91,4 +92,4 @@ // CHECK-NEXT: unroll-loops = false // CHECK-NEXT: widen-loops = false // CHECK-NEXT: [stats] -// CHECK-NEXT: num-entries = 88 +// CHECK-NEXT: num-entries = 89 diff --git a/clang/test/Analysis/taint-generic.c b/clang/test/Analysis/taint-generic.c index cdac02bf9e37b..4d7eee2547d2b 100644 --- a/clang/test/Analysis/taint-generic.c +++ b/clang/test/Analysis/taint-generic.c @@ -1,5 +1,49 @@ -// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.security.taint,core,alpha.security.ArrayBoundV2 -Wno-format-security -verify %s -// RUN: %clang_analyze_cc1 -DFILE_IS_STRUCT -analyzer-checker=alpha.security.taint,core,alpha.security.ArrayBoundV2 -Wno-format-security -verify %s +// RUN: %clang_analyze_cc1 -Wno-format-security -verify %s \ +// RUN: -analyzer-checker=alpha.security.taint \ +// RUN: -analyzer-checker=core \ +// RUN: -analyzer-checker=alpha.security.ArrayBoundV2 \ +// RUN: -analyzer-config \ +// RUN: alpha.security.taint.TaintPropagation:Config=%S/Inputs/taint-generic-config.yaml + +// RUN: %clang_analyze_cc1 -Wno-format-security -verify %s \ +// RUN: -DFILE_IS_STRUCT \ +// RUN: -analyzer-checker=alpha.security.taint \ +// RUN: -analyzer-checker=core \ +// RUN: -analyzer-checker=alpha.security.ArrayBoundV2 \ +// RUN: -analyzer-config \ +// RUN: alpha.security.taint.TaintPropagation:Config=%S/Inputs/taint-generic-config.yaml + +// RUN: not %clang_analyze_cc1 -verify %s \ +// RUN: -analyzer-checker=alpha.security.taint \ +// RUN: -analyzer-config \ +// RUN: alpha.security.taint.TaintPropagation:Config=justguessit \ +// RUN: 2>&1 | FileCheck %s -check-prefix=CHECK-INVALID-FILE + +// CHECK-INVALID-FILE: (frontend): invalid input for checker option +// CHECK-INVALID-FILE-SAME: 'alpha.security.taint.TaintPropagation:Config', +// CHECK-INVALID-FILE-SAME: that expects a valid filename instead of +// CHECK-INVALID-FILE-SAME: 'justguessit' + +// RUN: not %clang_analyze_cc1 -verify %s \ +// RUN: -analyzer-checker=alpha.security.taint \ +// RUN: -analyzer-config \ +// RUN: alpha.security.taint.TaintPropagation:Config=%S/Inputs/taint-generic-config-ill-formed.yaml \ +// RUN: 2>&1 | FileCheck %s -check-prefix=CHECK-ILL-FORMED + +// CHECK-ILL-FORMED: (frontend): invalid input for checker option +// CHECK-ILL-FORMED-SAME: 'alpha.security.taint.TaintPropagation:Config', +// CHECK-ILL-FORMED-SAME: that expects a valid yaml file: Invalid argument + +// RUN: not %clang_analyze_cc1 -verify %s \ +// RUN: -analyzer-checker=alpha.security.taint \ +// RUN: -analyzer-config \ +// RUN: alpha.security.taint.TaintPropagation:Config=%S/Inputs/taint-generic-config-invalid-arg.yaml \ +// RUN: 2>&1 | FileCheck %s -check-prefix=CHECK-INVALID-ARG + +// CHECK-INVALID-ARG: (frontend): invalid input for checker option +// CHECK-INVALID-ARG-SAME: 'alpha.security.taint.TaintPropagation:Config', +// CHECK-INVALID-ARG-SAME: that expects an argument number for propagation +// CHECK-INVALID-ARG-SAME: rules greater or equal to -1 int scanf(const char *restrict format, ...); char *gets(char *str); From 4f3e5661ff428564a5f1fd45b1ff63cd6c930e5d Mon Sep 17 00:00:00 2001 From: Gabor Borsik Date: Sun, 28 Jul 2019 14:57:41 +0000 Subject: [PATCH 074/181] Buildbot fix for r367190 llvm-svn: 367193 (cherry picked from commit 2c8098374b6baedf1007aaf82b161addfb507bf8) --- clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp index 20e70b65eac2e..b2172f6e9f544 100644 --- a/clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp @@ -811,7 +811,7 @@ void ento::registerGenericTaintChecker(CheckerManager &Mgr) { llvm::Optional Config = getConfiguration(Mgr, Checker, Option, ConfigFile); if (Config) - Checker->parseConfiguration(Mgr, Option, std::move(Config).getValue()); + Checker->parseConfiguration(Mgr, Option, std::move(Config.getValue())); } bool ento::shouldRegisterGenericTaintChecker(const LangOptions &LO) { From ac22da6dbbc2a3b86761c0cb094d68b8dd29dec0 Mon Sep 17 00:00:00 2001 From: Reid Kleckner Date: Mon, 29 Jul 2019 18:48:50 +0000 Subject: [PATCH 075/181] Fix taint-generic.c on Windows, handle case in OS error llvm-svn: 367249 (cherry picked from commit 2336c1b872a659ebb7e3c15c73dd0592cd751d57) --- clang/test/Analysis/taint-generic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/test/Analysis/taint-generic.c b/clang/test/Analysis/taint-generic.c index 4d7eee2547d2b..a6aae22a61f4b 100644 --- a/clang/test/Analysis/taint-generic.c +++ b/clang/test/Analysis/taint-generic.c @@ -32,7 +32,7 @@ // CHECK-ILL-FORMED: (frontend): invalid input for checker option // CHECK-ILL-FORMED-SAME: 'alpha.security.taint.TaintPropagation:Config', -// CHECK-ILL-FORMED-SAME: that expects a valid yaml file: Invalid argument +// CHECK-ILL-FORMED-SAME: that expects a valid yaml file: {{[Ii]}}nvalid argument // RUN: not %clang_analyze_cc1 -verify %s \ // RUN: -analyzer-checker=alpha.security.taint \ From 86d1d3085c72cac2c7a99a5e8cb901099bff4772 Mon Sep 17 00:00:00 2001 From: Csaba Dabis Date: Thu, 1 Aug 2019 20:41:13 +0000 Subject: [PATCH 076/181] [analyzer] StackFrameContext: Add NodeBuilderContext::blockCount() to its profile Summary: It allows discriminating between stack frames of the same call that is called multiple times in a loop. Thanks to Artem Dergachev for the great idea! Reviewed By: NoQ Tags: #clang Differential Revision: https://reviews.llvm.org/D65587 llvm-svn: 367608 (cherry picked from commit 7740c6d643765f390254706284824b090f985461) --- .../clang/Analysis/AnalysisDeclContext.h | 49 ++++++++++--------- .../Core/PathSensitive/CallEvent.h | 5 +- clang/lib/Analysis/AnalysisDeclContext.cpp | 21 ++++---- clang/lib/StaticAnalyzer/Core/CallEvent.cpp | 12 +++-- .../lib/StaticAnalyzer/Core/ExprEngineCXX.cpp | 5 +- .../Core/ExprEngineCallAndReturn.cpp | 5 +- clang/test/Analysis/loop-block-counts.c | 2 +- clang/test/Analysis/loop-unrolling.cpp | 10 ++-- .../Analysis/stack-frame-context-revision.cpp | 37 ++++++++++++++ 9 files changed, 95 insertions(+), 51 deletions(-) create mode 100644 clang/test/Analysis/stack-frame-context-revision.cpp diff --git a/clang/include/clang/Analysis/AnalysisDeclContext.h b/clang/include/clang/Analysis/AnalysisDeclContext.h index 1961d571e9e12..d5445d3ce5b6a 100644 --- a/clang/include/clang/Analysis/AnalysisDeclContext.h +++ b/clang/include/clang/Analysis/AnalysisDeclContext.h @@ -183,9 +183,8 @@ class AnalysisDeclContext { const ImplicitParamDecl *getSelfDecl() const; const StackFrameContext *getStackFrame(LocationContext const *Parent, - const Stmt *S, - const CFGBlock *Blk, - unsigned Idx); + const Stmt *S, const CFGBlock *Blk, + unsigned BlockCount, unsigned Idx); const BlockInvocationContext * getBlockInvocationContext(const LocationContext *parent, @@ -303,15 +302,19 @@ class StackFrameContext : public LocationContext { // The parent block of the callsite. const CFGBlock *Block; + // The number of times the 'Block' has been visited. + // It allows discriminating between stack frames of the same call that is + // called multiple times in a loop. + const unsigned BlockCount; + // The index of the callsite in the CFGBlock. - unsigned Index; + const unsigned Index; StackFrameContext(AnalysisDeclContext *ctx, const LocationContext *parent, - const Stmt *s, const CFGBlock *blk, - unsigned idx, - int64_t ID) - : LocationContext(StackFrame, ctx, parent, ID), CallSite(s), - Block(blk), Index(idx) {} + const Stmt *s, const CFGBlock *blk, unsigned blockCount, + unsigned idx, int64_t ID) + : LocationContext(StackFrame, ctx, parent, ID), CallSite(s), Block(blk), + BlockCount(blockCount), Index(idx) {} public: ~StackFrameContext() override = default; @@ -329,9 +332,10 @@ class StackFrameContext : public LocationContext { static void Profile(llvm::FoldingSetNodeID &ID, AnalysisDeclContext *ctx, const LocationContext *parent, const Stmt *s, - const CFGBlock *blk, unsigned idx) { + const CFGBlock *blk, unsigned blockCount, unsigned idx) { ProfileCommon(ID, StackFrame, ctx, parent, s); ID.AddPointer(blk); + ID.AddInteger(blockCount); ID.AddInteger(idx); } @@ -410,8 +414,8 @@ class LocationContextManager { const StackFrameContext *getStackFrame(AnalysisDeclContext *ctx, const LocationContext *parent, - const Stmt *s, - const CFGBlock *blk, unsigned idx); + const Stmt *s, const CFGBlock *blk, + unsigned blockCount, unsigned idx); const ScopeContext *getScope(AnalysisDeclContext *ctx, const LocationContext *parent, @@ -483,26 +487,25 @@ class AnalysisDeclContextManager { bool synthesizeBodies() const { return SynthesizeBodies; } const StackFrameContext *getStackFrame(AnalysisDeclContext *Ctx, - LocationContext const *Parent, - const Stmt *S, - const CFGBlock *Blk, - unsigned Idx) { - return LocContexts.getStackFrame(Ctx, Parent, S, Blk, Idx); + const LocationContext *Parent, + const Stmt *S, const CFGBlock *Blk, + unsigned BlockCount, unsigned Idx) { + return LocContexts.getStackFrame(Ctx, Parent, S, Blk, BlockCount, Idx); } // Get the top level stack frame. const StackFrameContext *getStackFrame(const Decl *D) { return LocContexts.getStackFrame(getContext(D), nullptr, nullptr, nullptr, - 0); + 0, 0); } // Get a stack frame with parent. StackFrameContext const *getStackFrame(const Decl *D, - LocationContext const *Parent, - const Stmt *S, - const CFGBlock *Blk, - unsigned Idx) { - return LocContexts.getStackFrame(getContext(D), Parent, S, Blk, Idx); + const LocationContext *Parent, + const Stmt *S, const CFGBlock *Blk, + unsigned BlockCount, unsigned Idx) { + return LocContexts.getStackFrame(getContext(D), Parent, S, Blk, BlockCount, + Idx); } /// Get a reference to {@code BodyFarm} instance. diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h index db84102983af1..b2826a81f406f 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h @@ -386,11 +386,12 @@ class CallEvent { /// during analysis if the call is inlined, but it may still be useful /// in intermediate calculations even if the call isn't inlined. /// May fail; returns null on failure. - const StackFrameContext *getCalleeStackFrame() const; + const StackFrameContext *getCalleeStackFrame(unsigned BlockCount) const; /// Returns memory location for a parameter variable within the callee stack /// frame. May fail; returns null on failure. - const VarRegion *getParameterLocation(unsigned Index) const; + const VarRegion *getParameterLocation(unsigned Index, + unsigned BlockCount) const; /// Returns true if on the current path, the argument was constructed by /// calling a C++ constructor over it. This is an internal detail of the diff --git a/clang/lib/Analysis/AnalysisDeclContext.cpp b/clang/lib/Analysis/AnalysisDeclContext.cpp index b6a429ff49ebc..5ecb3ff470ad4 100644 --- a/clang/lib/Analysis/AnalysisDeclContext.cpp +++ b/clang/lib/Analysis/AnalysisDeclContext.cpp @@ -310,8 +310,10 @@ BodyFarm &AnalysisDeclContextManager::getBodyFarm() { return FunctionBodyFarm; } const StackFrameContext * AnalysisDeclContext::getStackFrame(LocationContext const *Parent, const Stmt *S, - const CFGBlock *Blk, unsigned Idx) { - return getLocationContextManager().getStackFrame(this, Parent, S, Blk, Idx); + const CFGBlock *Blk, unsigned BlockCount, + unsigned Idx) { + return getLocationContextManager().getStackFrame(this, Parent, S, Blk, + BlockCount, Idx); } const BlockInvocationContext * @@ -359,7 +361,8 @@ void LocationContext::ProfileCommon(llvm::FoldingSetNodeID &ID, } void StackFrameContext::Profile(llvm::FoldingSetNodeID &ID) { - Profile(ID, getAnalysisDeclContext(), getParent(), CallSite, Block, Index); + Profile(ID, getAnalysisDeclContext(), getParent(), CallSite, Block, + BlockCount, Index); } void ScopeContext::Profile(llvm::FoldingSetNodeID &ID) { @@ -392,18 +395,16 @@ LocationContextManager::getLocationContext(AnalysisDeclContext *ctx, return L; } -const StackFrameContext* -LocationContextManager::getStackFrame(AnalysisDeclContext *ctx, - const LocationContext *parent, - const Stmt *s, - const CFGBlock *blk, unsigned idx) { +const StackFrameContext *LocationContextManager::getStackFrame( + AnalysisDeclContext *ctx, const LocationContext *parent, const Stmt *s, + const CFGBlock *blk, unsigned blockCount, unsigned idx) { llvm::FoldingSetNodeID ID; - StackFrameContext::Profile(ID, ctx, parent, s, blk, idx); + StackFrameContext::Profile(ID, ctx, parent, s, blk, blockCount, idx); void *InsertPos; auto *L = cast_or_null(Contexts.FindNodeOrInsertPos(ID, InsertPos)); if (!L) { - L = new StackFrameContext(ctx, parent, s, blk, idx, ++NewID); + L = new StackFrameContext(ctx, parent, s, blk, blockCount, idx, ++NewID); Contexts.InsertNode(L, InsertPos); } return L; diff --git a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp index a5f7500e63074..b6f551d4920bb 100644 --- a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp +++ b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp @@ -191,7 +191,8 @@ AnalysisDeclContext *CallEvent::getCalleeAnalysisDeclContext() const { return ADC; } -const StackFrameContext *CallEvent::getCalleeStackFrame() const { +const StackFrameContext * +CallEvent::getCalleeStackFrame(unsigned BlockCount) const { AnalysisDeclContext *ADC = getCalleeAnalysisDeclContext(); if (!ADC) return nullptr; @@ -217,11 +218,12 @@ const StackFrameContext *CallEvent::getCalleeStackFrame() const { break; assert(Idx < Sz); - return ADC->getManager()->getStackFrame(ADC, LCtx, E, B, Idx); + return ADC->getManager()->getStackFrame(ADC, LCtx, E, B, BlockCount, Idx); } -const VarRegion *CallEvent::getParameterLocation(unsigned Index) const { - const StackFrameContext *SFC = getCalleeStackFrame(); +const VarRegion *CallEvent::getParameterLocation(unsigned Index, + unsigned BlockCount) const { + const StackFrameContext *SFC = getCalleeStackFrame(BlockCount); // We cannot construct a VarRegion without a stack frame. if (!SFC) return nullptr; @@ -322,7 +324,7 @@ ProgramStateRef CallEvent::invalidateRegions(unsigned BlockCount, if (getKind() != CE_CXXAllocator) if (isArgumentConstructedDirectly(Idx)) if (auto AdjIdx = getAdjustedParameterIndex(Idx)) - if (const VarRegion *VR = getParameterLocation(*AdjIdx)) + if (const VarRegion *VR = getParameterLocation(*AdjIdx, BlockCount)) ValuesToInvalidate.push_back(loc::MemRegionVal(VR)); } diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp index 1cbd09ea57932..10c8cb1a9ee10 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp @@ -323,7 +323,8 @@ std::pair ExprEngine::prepareForObjectConstruction( CallEventManager &CEMgr = getStateManager().getCallEventManager(); SVal V = UnknownVal(); auto getArgLoc = [&](CallEventRef<> Caller) -> Optional { - const LocationContext *FutureSFC = Caller->getCalleeStackFrame(); + const LocationContext *FutureSFC = + Caller->getCalleeStackFrame(currBldrCtx->blockCount()); // Return early if we are unable to reliably foresee // the future stack frame. if (!FutureSFC) @@ -342,7 +343,7 @@ std::pair ExprEngine::prepareForObjectConstruction( // because this-argument is implemented as a normal argument in // operator call expressions but not in operator declarations. const VarRegion *VR = Caller->getParameterLocation( - *Caller->getAdjustedParameterIndex(Idx)); + *Caller->getAdjustedParameterIndex(Idx), currBldrCtx->blockCount()); if (!VR) return None; diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp index e00a08b2162c1..3bef471751d15 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp @@ -451,9 +451,8 @@ bool ExprEngine::inlineCall(const CallEvent &Call, const Decl *D, // Construct a new stack frame for the callee. AnalysisDeclContext *CalleeADC = AMgr.getAnalysisDeclContext(D); const StackFrameContext *CalleeSFC = - CalleeADC->getStackFrame(ParentOfCallee, CallE, - currBldrCtx->getBlock(), - currStmtIdx); + CalleeADC->getStackFrame(ParentOfCallee, CallE, currBldrCtx->getBlock(), + currBldrCtx->blockCount(), currStmtIdx); CallEnter Loc(CallE, CalleeSFC, CurLC); diff --git a/clang/test/Analysis/loop-block-counts.c b/clang/test/Analysis/loop-block-counts.c index 04a3f747c2ee9..66bb850780cb1 100644 --- a/clang/test/Analysis/loop-block-counts.c +++ b/clang/test/Analysis/loop-block-counts.c @@ -12,7 +12,7 @@ void loop() { for (int i = 0; i < 2; ++i) callee(&arr[i]); // FIXME: Should be UNKNOWN. - clang_analyzer_eval(arr[0] == arr[1]); // expected-warning{{TRUE}} + clang_analyzer_eval(arr[0] == arr[1]); // expected-warning{{FALSE}} } void loopWithCall() { diff --git a/clang/test/Analysis/loop-unrolling.cpp b/clang/test/Analysis/loop-unrolling.cpp index b7375df38b695..761bf5af6a8b0 100644 --- a/clang/test/Analysis/loop-unrolling.cpp +++ b/clang/test/Analysis/loop-unrolling.cpp @@ -347,9 +347,9 @@ int simple_known_bound_loop() { int simple_unknown_bound_loop() { for (int i = 2; i < getNum(); i++) { #ifdef DFS - clang_analyzer_numTimesReached(); // expected-warning {{10}} + clang_analyzer_numTimesReached(); // expected-warning {{16}} #else - clang_analyzer_numTimesReached(); // expected-warning {{13}} + clang_analyzer_numTimesReached(); // expected-warning {{8}} #endif } return 0; @@ -368,10 +368,10 @@ int nested_inlined_unroll1() { int nested_inlined_no_unroll1() { int k; for (int i = 0; i < 9; i++) { -#ifdef ANALYZER_CM_Z3 - clang_analyzer_numTimesReached(); // expected-warning {{13}} +#ifdef DFS + clang_analyzer_numTimesReached(); // expected-warning {{18}} #else - clang_analyzer_numTimesReached(); // expected-warning {{15}} + clang_analyzer_numTimesReached(); // expected-warning {{14}} #endif k = simple_unknown_bound_loop(); // reevaluation without inlining, splits the state as well } diff --git a/clang/test/Analysis/stack-frame-context-revision.cpp b/clang/test/Analysis/stack-frame-context-revision.cpp new file mode 100644 index 0000000000000..8c119f50c141e --- /dev/null +++ b/clang/test/Analysis/stack-frame-context-revision.cpp @@ -0,0 +1,37 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,cplusplus.NewDelete -verify %s + +// expected-no-diagnostics: +// From now the profile of the 'StackFrameContext' also contains the +// 'NodeBuilderContext::blockCount()'. With this addition we can distinguish +// between the 'StackArgumentsSpaceRegion' of the 'P' arguments being different +// on every iteration. + +typedef __INTPTR_TYPE__ intptr_t; + +template +struct SmarterPointer { + PointerTy getFromVoidPointer(void *P) const { + return static_cast(P); + } + + PointerTy getPointer() const { + return getFromVoidPointer(reinterpret_cast(Value)); + } + + intptr_t Value = 13; +}; + +struct Node { + SmarterPointer Pred; +}; + +void test(Node *N) { + while (N) { + SmarterPointer Next = N->Pred; + delete N; + + N = Next.getPointer(); + // no-warning: 'Use of memory after it is freed' was here as the same + // 'StackArgumentsSpaceRegion' purged out twice as 'P'. + } +} From 0cdd16807a970332ca588e4a695522f3c8b4d9e0 Mon Sep 17 00:00:00 2001 From: Adam Balogh Date: Mon, 5 Aug 2019 06:45:41 +0000 Subject: [PATCH 077/181] [Analyzer] Iterator Checkers - Fix for Crash on Iterator Differences Iterators differences were mistakenly handled as random decrements which causes an assertion. This patch fixes this. llvm-svn: 367802 (cherry picked from commit 8557f17d887ab7e70a44f7b674478a16c6eb0119) --- .../lib/StaticAnalyzer/Checkers/IteratorChecker.cpp | 12 ++++++++---- .../Analysis/Inputs/system-header-simulator-cxx.h | 3 +++ .../Analysis/diagnostics/explicit-suppression.cpp | 2 +- clang/test/Analysis/iterator-range.cpp | 5 +++++ 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp index 6f1060b5f26d9..600458a743ea1 100644 --- a/clang/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp @@ -406,13 +406,15 @@ void IteratorChecker::checkPreCall(const CallEvent &Call, } else if (isRandomIncrOrDecrOperator(Func->getOverloadedOperator())) { if (const auto *InstCall = dyn_cast(&Call)) { // Check for out-of-range incrementions and decrementions - if (Call.getNumArgs() >= 1) { + if (Call.getNumArgs() >= 1 && + Call.getArgExpr(0)->getType()->isIntegralOrEnumerationType()) { verifyRandomIncrOrDecr(C, Func->getOverloadedOperator(), InstCall->getCXXThisVal(), Call.getArgSVal(0)); } } else { - if (Call.getNumArgs() >= 2) { + if (Call.getNumArgs() >= 2 && + Call.getArgExpr(1)->getType()->isIntegralOrEnumerationType()) { verifyRandomIncrOrDecr(C, Func->getOverloadedOperator(), Call.getArgSVal(0), Call.getArgSVal(1)); } @@ -590,14 +592,16 @@ void IteratorChecker::checkPostCall(const CallEvent &Call, return; } else if (isRandomIncrOrDecrOperator(Func->getOverloadedOperator())) { if (const auto *InstCall = dyn_cast(&Call)) { - if (Call.getNumArgs() >= 1) { + if (Call.getNumArgs() >= 1 && + Call.getArgExpr(0)->getType()->isIntegralOrEnumerationType()) { handleRandomIncrOrDecr(C, Func->getOverloadedOperator(), Call.getReturnValue(), InstCall->getCXXThisVal(), Call.getArgSVal(0)); return; } } else { - if (Call.getNumArgs() >= 2) { + if (Call.getNumArgs() >= 2 && + Call.getArgExpr(1)->getType()->isIntegralOrEnumerationType()) { handleRandomIncrOrDecr(C, Func->getOverloadedOperator(), Call.getReturnValue(), Call.getArgSVal(0), Call.getArgSVal(1)); diff --git a/clang/test/Analysis/Inputs/system-header-simulator-cxx.h b/clang/test/Analysis/Inputs/system-header-simulator-cxx.h index 5b37e96f60277..30b25b85944b6 100644 --- a/clang/test/Analysis/Inputs/system-header-simulator-cxx.h +++ b/clang/test/Analysis/Inputs/system-header-simulator-cxx.h @@ -70,6 +70,9 @@ template struct __vector_iterator { return ptr -= n; } + template + difference_type operator-(const __vector_iterator &rhs); + Ref operator*() const { return *ptr; } Ptr operator->() const { return *ptr; } diff --git a/clang/test/Analysis/diagnostics/explicit-suppression.cpp b/clang/test/Analysis/diagnostics/explicit-suppression.cpp index 2bb969059ffbe..6bc01479b815b 100644 --- a/clang/test/Analysis/diagnostics/explicit-suppression.cpp +++ b/clang/test/Analysis/diagnostics/explicit-suppression.cpp @@ -19,6 +19,6 @@ class C { void testCopyNull(C *I, C *E) { std::copy(I, E, (C *)0); #ifndef SUPPRESSED - // expected-warning@../Inputs/system-header-simulator-cxx.h:677 {{Called C++ object pointer is null}} + // expected-warning@../Inputs/system-header-simulator-cxx.h:680 {{Called C++ object pointer is null}} #endif } diff --git a/clang/test/Analysis/iterator-range.cpp b/clang/test/Analysis/iterator-range.cpp index 6fc8939c8e846..bc7e08263ae21 100644 --- a/clang/test/Analysis/iterator-range.cpp +++ b/clang/test/Analysis/iterator-range.cpp @@ -236,3 +236,8 @@ void good_derived(simple_container c) { *i0; // no-warning } } + +void iter_diff(std::vector &V) { + auto i0 = V.begin(), i1 = V.end(); + ptrdiff_t len = i1 - i0; // no-crash +} From da029dbcd67043afcf6dda22b4933b999fd80ac4 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Thu, 8 Aug 2019 20:22:32 +0000 Subject: [PATCH 078/181] [analyzer] Fix scan-build's plist output in plist-html mode. r366941 accidentally made it delete all plist files as soon as they're produced. llvm-svn: 368338 (cherry picked from commit 8b49e0fd39f946d0d2e6e3979a893b89dec1d9e0) --- clang/tools/scan-build/libexec/ccc-analyzer | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/tools/scan-build/libexec/ccc-analyzer b/clang/tools/scan-build/libexec/ccc-analyzer index 277ed9f83af72..e1635e6c29b87 100755 --- a/clang/tools/scan-build/libexec/ccc-analyzer +++ b/clang/tools/scan-build/libexec/ccc-analyzer @@ -118,7 +118,7 @@ my $ResultFile; # Remove any stale files at exit. END { - if (defined $ResultFile && $ResultFile ne "") { + if (defined $ResultFile && -z $ResultFile) { unlink($ResultFile); } if (defined $CleanupFile) { From f92e410a218140e902a13bf9a0ddde4da5d88ef2 Mon Sep 17 00:00:00 2001 From: Csaba Dabis Date: Fri, 9 Aug 2019 02:20:44 +0000 Subject: [PATCH 079/181] [analyzer] ConditionBRVisitor: Fix HTML PathDiagnosticPopUpPieces Summary: A condition could be a multi-line expression where we create the highlight in separated chunks. PathDiagnosticPopUpPiece is not made for that purpose, it cannot be added to multiple lines because we have only one ending part which contains all the notes. So that it cannot have multiple endings and therefore this patch narrows down the ranges of the highlight to the given interesting variable of the condition. It prevents HTML-breaking injections. Reviewed By: NoQ Differential Revision: https://reviews.llvm.org/D65663 llvm-svn: 368382 (cherry picked from commit 124ef7fce40369a176a2d354a214bf62460b5446) --- .../Core/BugReporterVisitors.cpp | 51 ++++-- .../StaticAnalyzer/Core/HTMLDiagnostics.cpp | 9 +- .../expected-plists/cxx-for-range.cpp.plist | 4 +- .../Inputs/expected-plists/edges-new.mm.plist | 163 ++++++++++-------- .../expected-plists/inline-plist.c.plist | 2 +- .../objc-radar17039661.m.plist | 2 +- .../expected-plists/plist-output.m.plist | 8 +- 7 files changed, 140 insertions(+), 99 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp index 250793c4baff0..731545022cab0 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp @@ -2335,12 +2335,12 @@ std::shared_ptr ConditionBRVisitor::VisitTrueTest( // Check if the field name of the MemberExprs is ambiguous. Example: // " 'a.d' is equal to 'h.d' " in 'test/Analysis/null-deref-path-notes.cpp'. bool IsSameFieldName = false; - if (const auto *LhsME = - dyn_cast(BExpr->getLHS()->IgnoreParenCasts())) - if (const auto *RhsME = - dyn_cast(BExpr->getRHS()->IgnoreParenCasts())) - IsSameFieldName = LhsME->getMemberDecl()->getName() == - RhsME->getMemberDecl()->getName(); + const auto *LhsME = dyn_cast(BExpr->getLHS()->IgnoreParenCasts()); + const auto *RhsME = dyn_cast(BExpr->getRHS()->IgnoreParenCasts()); + + if (LhsME && RhsME) + IsSameFieldName = + LhsME->getMemberDecl()->getName() == RhsME->getMemberDecl()->getName(); SmallString<128> LhsString, RhsString; { @@ -2410,16 +2410,31 @@ std::shared_ptr ConditionBRVisitor::VisitTrueTest( Out << (shouldInvert ? LhsString : RhsString); const LocationContext *LCtx = N->getLocationContext(); - PathDiagnosticLocation Loc(Cond, BRC.getSourceManager(), LCtx); + const SourceManager &SM = BRC.getSourceManager(); // Convert 'field ...' to 'Field ...' if it is a MemberExpr. std::string Message = Out.str(); Message[0] = toupper(Message[0]); - // If we know the value create a pop-up note. - if (!IsAssuming) + // If we know the value create a pop-up note to the value part of 'BExpr'. + if (!IsAssuming) { + PathDiagnosticLocation Loc; + if (!shouldInvert) { + if (LhsME && LhsME->getMemberLoc().isValid()) + Loc = PathDiagnosticLocation(LhsME->getMemberLoc(), SM); + else + Loc = PathDiagnosticLocation(BExpr->getLHS(), SM, LCtx); + } else { + if (RhsME && RhsME->getMemberLoc().isValid()) + Loc = PathDiagnosticLocation(RhsME->getMemberLoc(), SM); + else + Loc = PathDiagnosticLocation(BExpr->getRHS(), SM, LCtx); + } + return std::make_shared(Loc, Message); + } + PathDiagnosticLocation Loc(Cond, SM, LCtx); auto event = std::make_shared(Loc, Message); if (shouldPrune.hasValue()) event->setPrunable(shouldPrune.getValue()); @@ -2472,12 +2487,14 @@ std::shared_ptr ConditionBRVisitor::VisitTrueTest( return nullptr; const LocationContext *LCtx = N->getLocationContext(); - PathDiagnosticLocation Loc(Cond, BRC.getSourceManager(), LCtx); - // If we know the value create a pop-up note. - if (!IsAssuming) + // If we know the value create a pop-up note to the 'DRE'. + if (!IsAssuming) { + PathDiagnosticLocation Loc(DRE, BRC.getSourceManager(), LCtx); return std::make_shared(Loc, Out.str()); + } + PathDiagnosticLocation Loc(Cond, BRC.getSourceManager(), LCtx); auto event = std::make_shared(Loc, Out.str()); const ProgramState *state = N->getState().get(); if (const MemRegion *R = state->getLValue(VD, LCtx).getAsRegion()) { @@ -2505,11 +2522,17 @@ std::shared_ptr ConditionBRVisitor::VisitTrueTest( return nullptr; const LocationContext *LCtx = N->getLocationContext(); - PathDiagnosticLocation Loc(Cond, BRC.getSourceManager(), LCtx); + PathDiagnosticLocation Loc; + + // If we know the value create a pop-up note to the member of the MemberExpr. + if (!IsAssuming && ME->getMemberLoc().isValid()) + Loc = PathDiagnosticLocation(ME->getMemberLoc(), BRC.getSourceManager()); + else + Loc = PathDiagnosticLocation(Cond, BRC.getSourceManager(), LCtx); + if (!Loc.isValid() || !Loc.asLocation().isValid()) return nullptr; - // If we know the value create a pop-up note. if (!IsAssuming) return std::make_shared(Loc, Out.str()); diff --git a/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp b/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp index f781c949e4dd3..a641695722500 100644 --- a/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp +++ b/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp @@ -612,7 +612,7 @@ HandlePopUpPieceStartTag(Rewriter &R, for (const auto &Range : PopUpRanges) { html::HighlightRange(R, Range.getBegin(), Range.getEnd(), "", "
-// CHECK-SAME: (main file):4:5: +// CHECK-SAME: main.cpp:4:5: // CHECK-SAME: // CHECK-SAME: DeclRefExpr @@ -83,6 +83,7 @@ Node0x2 [shape=record,label= "pointer": "0x3", "pretty": "x", "location": { + "file": "main.cpp", "line": 4, "column": 5 }, diff --git a/clang/utils/analyzer/exploded-graph-rewriter.py b/clang/utils/analyzer/exploded-graph-rewriter.py index 99f5403189bf4..baf1be8de06a4 100755 --- a/clang/utils/analyzer/exploded-graph-rewriter.py +++ b/clang/utils/analyzer/exploded-graph-rewriter.py @@ -16,6 +16,7 @@ import difflib import json import logging +import os import re @@ -50,8 +51,8 @@ def __init__(self, json_loc): super(SourceLocation, self).__init__() self.line = json_loc['line'] self.col = json_loc['column'] - self.filename = json_loc['filename'] \ - if 'filename' in json_loc else '(main file)' + self.filename = os.path.basename(json_loc['file']) \ + if 'file' in json_loc else '(main file)' # A deserialized program point. From a0b89c7568dcb54f10cfc7614b2ba2d97a6992f7 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Fri, 12 Jul 2019 02:10:33 +0000 Subject: [PATCH 063/181] [analyzer] exploded-graph-rewriter: Improve source location dumps. - Correctly display macro expansion and spelling locations. - Use the same procedure to display location context call site locations. - Display statement IDs for program points. llvm-svn: 365861 (cherry picked from commit ed035ff8264fcfd8ba380a6966c8f29097deb251) --- clang/include/clang/Basic/JsonSupport.h | 37 +++++++++++ clang/lib/Analysis/AnalysisDeclContext.cpp | 10 ++- clang/lib/Analysis/ProgramPoint.cpp | 32 ++++------ clang/test/Analysis/dump_egraph.cpp | 4 +- .../exploded-graph-rewriter/environment.dot | 17 ++++- .../environment_diff.dot | 6 +- .../Analysis/exploded-graph-rewriter/macros.c | 18 ++++++ .../program_points.dot | 9 +-- clang/test/Analysis/expr-inspection.c | 2 +- .../utils/analyzer/exploded-graph-rewriter.py | 64 +++++++++++-------- 10 files changed, 134 insertions(+), 65 deletions(-) create mode 100644 clang/test/Analysis/exploded-graph-rewriter/macros.c diff --git a/clang/include/clang/Basic/JsonSupport.h b/clang/include/clang/Basic/JsonSupport.h index f235daa1689e7..aed4c9a6ae3de 100644 --- a/clang/include/clang/Basic/JsonSupport.h +++ b/clang/include/clang/Basic/JsonSupport.h @@ -10,6 +10,7 @@ #define LLVM_CLANG_BASIC_JSONSUPPORT_H #include "clang/Basic/LLVM.h" +#include "clang/Basic/SourceManager.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/raw_ostream.h" @@ -58,6 +59,42 @@ inline std::string JsonFormat(StringRef RawSR, bool AddQuotes) { return '\"' + Str + '\"'; } +inline void printSourceLocationAsJson(raw_ostream &Out, SourceLocation Loc, + const SourceManager &SM, + bool AddBraces = true) { + // Mostly copy-pasted from SourceLocation::print. + if (!Loc.isValid()) { + Out << "null"; + return; + } + + if (Loc.isFileID()) { + PresumedLoc PLoc = SM.getPresumedLoc(Loc); + + if (PLoc.isInvalid()) { + Out << "null"; + return; + } + // The macro expansion and spelling pos is identical for file locs. + if (AddBraces) + Out << "{ "; + Out << "\"line\": " << PLoc.getLine() + << ", \"column\": " << PLoc.getColumn() + << ", \"file\": \"" << PLoc.getFilename() << "\""; + if (AddBraces) + Out << " }"; + return; + } + + // We want 'location: { ..., spelling: { ... }}' but not + // 'location: { ... }, spelling: { ... }', hence the dance + // with braces. + Out << "{ "; + printSourceLocationAsJson(Out, SM.getExpansionLoc(Loc), SM, false); + Out << ", \"spelling\": "; + printSourceLocationAsJson(Out, SM.getSpellingLoc(Loc), SM, true); + Out << " }"; +} } // namespace clang #endif // LLVM_CLANG_BASIC_JSONSUPPORT_H diff --git a/clang/lib/Analysis/AnalysisDeclContext.cpp b/clang/lib/Analysis/AnalysisDeclContext.cpp index 28d308132fdbd..b6a429ff49ebc 100644 --- a/clang/lib/Analysis/AnalysisDeclContext.cpp +++ b/clang/lib/Analysis/AnalysisDeclContext.cpp @@ -538,11 +538,9 @@ void LocationContext::printJson(raw_ostream &Out, const char *NL, else Out << "anonymous code"; - Out << "\", \"call_line\": "; + Out << "\", \"location\": "; if (const Stmt *S = cast(LCtx)->getCallSite()) { - Out << '\"'; - printLocation(Out, SM, S->getBeginLoc()); - Out << '\"'; + printSourceLocationAsJson(Out, S->getBeginLoc(), SM); } else { Out << "null"; } @@ -555,8 +553,8 @@ void LocationContext::printJson(raw_ostream &Out, const char *NL, case Block: Out << "Invoking block\" "; if (const Decl *D = cast(LCtx)->getDecl()) { - Out << ", \"decl_line\": "; - printLocation(Out, SM, D->getBeginLoc()); + Out << ", \"location\": "; + printSourceLocationAsJson(Out, D->getBeginLoc(), SM); Out << ' '; } break; diff --git a/clang/lib/Analysis/ProgramPoint.cpp b/clang/lib/Analysis/ProgramPoint.cpp index 0398251b5eab6..97e90965d007a 100644 --- a/clang/lib/Analysis/ProgramPoint.cpp +++ b/clang/lib/Analysis/ProgramPoint.cpp @@ -12,6 +12,7 @@ //===----------------------------------------------------------------------===// #include "clang/Analysis/ProgramPoint.h" +#include "clang/Basic/JsonSupport.h" using namespace clang; @@ -46,19 +47,6 @@ LLVM_DUMP_METHOD void ProgramPoint::dump() const { return printJson(llvm::errs()); } -static void printLocJson(raw_ostream &Out, SourceLocation Loc, - const SourceManager &SM) { - Out << "\"location\": "; - if (!Loc.isFileID()) { - Out << "null"; - return; - } - - Out << "{ \"line\": " << SM.getExpansionLineNumber(Loc) - << ", \"column\": " << SM.getExpansionColumnNumber(Loc) - << ", \"file\": \"" << SM.getFilename(Loc) << "\" }"; -} - void ProgramPoint::printJson(llvm::raw_ostream &Out, const char *NL) const { const ASTContext &Context = getLocationContext()->getAnalysisDeclContext()->getASTContext(); @@ -112,16 +100,18 @@ void ProgramPoint::printJson(llvm::raw_ostream &Out, const char *NL) const { case ProgramPoint::PreImplicitCallKind: { ImplicitCallPoint PC = castAs(); Out << "PreCall\", \"decl\": \"" - << PC.getDecl()->getAsFunction()->getQualifiedNameAsString() << "\", "; - printLocJson(Out, PC.getLocation(), SM); + << PC.getDecl()->getAsFunction()->getQualifiedNameAsString() + << "\", \"location\": "; + printSourceLocationAsJson(Out, PC.getLocation(), SM); break; } case ProgramPoint::PostImplicitCallKind: { ImplicitCallPoint PC = castAs(); Out << "PostCall\", \"decl\": \"" - << PC.getDecl()->getAsFunction()->getQualifiedNameAsString() << "\", "; - printLocJson(Out, PC.getLocation(), SM); + << PC.getDecl()->getAsFunction()->getQualifiedNameAsString() + << "\", \"location\": "; + printSourceLocationAsJson(Out, PC.getLocation(), SM); break; } @@ -153,8 +143,8 @@ void ProgramPoint::printJson(llvm::raw_ostream &Out, const char *NL) const { E.getSrc()->printTerminatorJson(Out, Context.getLangOpts(), /*AddQuotes=*/true); - Out << ", "; - printLocJson(Out, T->getBeginLoc(), SM); + Out << ", \"location\": "; + printSourceLocationAsJson(Out, T->getBeginLoc(), SM); Out << ", \"term_kind\": \""; if (isa(T)) { @@ -202,8 +192,8 @@ void ProgramPoint::printJson(llvm::raw_ostream &Out, const char *NL) const { S->printJson(Out, nullptr, PP, AddQuotes); - Out << ", "; - printLocJson(Out, S->getBeginLoc(), SM); + Out << ", \"location\": "; + printSourceLocationAsJson(Out, S->getBeginLoc(), SM); Out << ", \"stmt_point_kind\": \""; if (getAs()) diff --git a/clang/test/Analysis/dump_egraph.cpp b/clang/test/Analysis/dump_egraph.cpp index f9ad71b7abcb7..f5db3d142eed6 100644 --- a/clang/test/Analysis/dump_egraph.cpp +++ b/clang/test/Analysis/dump_egraph.cpp @@ -18,9 +18,9 @@ void foo() { new S; } -// CHECK: \"location_context\": \"#0 Call\", \"calling\": \"foo\", \"call_line\": null, \"items\": [\l        \{ \"stmt_id\": {{[0-9]+}}, \"kind\": \"construct into local variable\", \"argument_index\": null, \"pretty\": \"T t;\", \"value\": \"&t\" +// CHECK: \"location_context\": \"#0 Call\", \"calling\": \"foo\", \"location\": null, \"items\": [\l        \{ \"stmt_id\": {{[0-9]+}}, \"kind\": \"construct into local variable\", \"argument_index\": null, \"pretty\": \"T t;\", \"value\": \"&t\" -// CHECK: \"location_context\": \"#0 Call\", \"calling\": \"T::T\", \"call_line\": \"16\", \"items\": [\l        \{ \"init_id\": {{[0-9]+}}, \"kind\": \"construct into member variable\", \"argument_index\": null, \"pretty\": \"s\", \"value\": \"&t-\>s\" +// CHECK: \"location_context\": \"#0 Call\", \"calling\": \"T::T\", \"location\": \{ \"line\": 16, \"column\": 5, \"file\": \"{{.*}}dump_egraph.cpp\" \}, \"items\": [\l        \{ \"init_id\": {{[0-9]+}}, \"kind\": \"construct into member variable\", \"argument_index\": null, \"pretty\": \"s\", \"value\": \"&t-\>s\" // CHECK: \"cluster\": \"t\", \"pointer\": \"{{0x[0-9a-f]+}}\", \"items\": [\l        \{ \"kind\": \"Default\", \"offset\": 0, \"value\": \"conj_$2\{int, LC5, no stmt, #1\}\" diff --git a/clang/test/Analysis/exploded-graph-rewriter/environment.dot b/clang/test/Analysis/exploded-graph-rewriter/environment.dot index 2b8a402cd8eb6..399484a7eece2 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/environment.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/environment.dot @@ -10,7 +10,11 @@ // CHECK-SAME: #0 Call // CHECK-SAME: -// CHECK-SAME: foo (line 4) +// CHECK-SAME: foo +// CHECK-SAME: (environment.cpp:4:6 +// CHECK-SAME: +// CHECK-SAME: (spelling at environment.h:7:8) +// CHECK-SAME: ) // CHECK-SAME:
// CHECK-SAME: DeclRefExpr // CHECK-SAME: S3 // CHECK-SAME: PreStmt // CHECK-SAME: xx
@@ -79,7 +80,7 @@ Node0x2 [shape=record,label= "kind": "Statement", "stmt_kind": "DeclRefExpr", "stmt_point_kind": "PreStmt", - "stmd_id": 3, + "stmt_id": 3, "pointer": "0x3", "pretty": "x", "location": { @@ -95,7 +96,7 @@ Node0x2 [shape=record,label= // Test collapsing large pretty prints with braces. // CHECK-NEXT: Program point: -// CHECK-SAME: \{ ... \}\{ ... \}
' - '%s:%s:%s:' - '%s%s%s
' - 'Invalid Source Location:' - '%s%s%s
%s:' + '%s S%s%s%s
' @@ -516,8 +528,8 @@ def dump_location_context(lc, is_added=None): '%s
%s
", - /*IsTokenRange=*/false); + /*IsTokenRange=*/true); } } @@ -644,12 +644,11 @@ static void HandlePopUpPieceEndTag(Rewriter &R, Out << "
"; html::HighlightRange(R, Range.getBegin(), Range.getEnd(), "", Buf.c_str(), - /*IsTokenRange=*/false); - - // Otherwise inject just the new row at the end of the range. + /*IsTokenRange=*/true); } else { + // Otherwise inject just the new row at the end of the range. html::HighlightRange(R, Range.getBegin(), Range.getEnd(), "", Buf.c_str(), - /*IsTokenRange=*/false); + /*IsTokenRange=*/true); } } diff --git a/clang/test/Analysis/Inputs/expected-plists/cxx-for-range.cpp.plist b/clang/test/Analysis/Inputs/expected-plists/cxx-for-range.cpp.plist index e7f6dba2fa43f..edcaaf2cd44b8 100644 --- a/clang/test/Analysis/Inputs/expected-plists/cxx-for-range.cpp.plist +++ b/clang/test/Analysis/Inputs/expected-plists/cxx-for-range.cpp.plist @@ -191,7 +191,7 @@ line11 - col14 + col9 file0 @@ -515,7 +515,7 @@ line11 - col14 + col9 file0 diff --git a/clang/test/Analysis/Inputs/expected-plists/edges-new.mm.plist b/clang/test/Analysis/Inputs/expected-plists/edges-new.mm.plist index b4c79018c6665..ab04dadbf479c 100644 --- a/clang/test/Analysis/Inputs/expected-plists/edges-new.mm.plist +++ b/clang/test/Analysis/Inputs/expected-plists/edges-new.mm.plist @@ -2727,7 +2727,7 @@ line146 - col13 + col8 file0 @@ -2949,7 +2949,7 @@ line146 - col13 + col8 file0 @@ -3929,7 +3929,7 @@ line178 - col14 + col9 file0 @@ -4185,7 +4185,7 @@ line178 - col14 + col9 file0 @@ -4281,7 +4281,7 @@ line181 - col14 + col9 file0 @@ -8087,7 +8087,7 @@ location line267 - col18 + col19 file0 ranges @@ -8095,7 +8095,7 @@ line267 - col18 + col19 file0 @@ -8119,12 +8119,12 @@ line267 - col18 + col19 file0 line267 - col18 + col22 file0 @@ -11983,12 +11983,12 @@ line457 - col9 + col10 file0 line457 - col9 + col14 file0 @@ -12000,7 +12000,7 @@ location line457 - col9 + col10 file0 ranges @@ -12008,7 +12008,7 @@ line457 - col9 + col10 file0 @@ -12032,12 +12032,12 @@ line457 - col9 + col10 file0 line457 - col9 + col14 file0 @@ -12244,12 +12244,12 @@ line457 - col9 + col10 file0 line457 - col9 + col14 file0 @@ -12261,7 +12261,7 @@ location line457 - col9 + col10 file0 ranges @@ -12269,7 +12269,7 @@ line457 - col9 + col10 file0 @@ -12293,12 +12293,12 @@ line457 - col9 + col10 file0 line457 - col9 + col14 file0 @@ -12571,12 +12571,12 @@ line457 - col9 + col10 file0 line457 - col9 + col14 file0 @@ -12588,7 +12588,7 @@ location line457 - col9 + col10 file0 ranges @@ -12596,7 +12596,7 @@ line457 - col9 + col10 file0 @@ -12620,12 +12620,12 @@ line457 - col9 + col10 file0 line457 - col9 + col14 file0 @@ -13128,12 +13128,12 @@ line457 - col9 + col10 file0 line457 - col9 + col14 file0 @@ -13145,7 +13145,7 @@ location line457 - col9 + col10 file0 ranges @@ -13153,7 +13153,7 @@ line457 - col9 + col10 file0 @@ -13177,12 +13177,12 @@ line457 - col9 + col10 file0 line457 - col9 + col14 file0 @@ -13752,12 +13752,12 @@ line457 - col9 + col10 file0 line457 - col9 + col14 file0 @@ -13769,7 +13769,7 @@ location line457 - col9 + col10 file0 ranges @@ -13777,7 +13777,7 @@ line457 - col9 + col10 file0 @@ -13801,12 +13801,12 @@ line457 - col9 + col10 file0 line457 - col9 + col14 file0 @@ -15295,12 +15295,12 @@ line457 - col9 + col10 file0 line457 - col9 + col14 file0 @@ -15312,7 +15312,7 @@ location line457 - col9 + col10 file0 ranges @@ -15320,7 +15320,7 @@ line457 - col9 + col10 file0 @@ -15344,12 +15344,12 @@ line457 - col9 + col10 file0 line457 - col9 + col14 file0 @@ -16965,12 +16965,12 @@ line457 - col9 + col10 file0 line457 - col9 + col14 file0 @@ -16982,7 +16982,7 @@ location line457 - col9 + col10 file0 ranges @@ -16990,7 +16990,7 @@ line457 - col9 + col10 file0 @@ -17014,12 +17014,12 @@ line457 - col9 + col10 file0 line457 - col9 + col14 file0 @@ -18860,12 +18860,12 @@ line457 - col9 + col10 file0 line457 - col9 + col14 file0 @@ -18877,7 +18877,7 @@ location line457 - col9 + col10 file0 ranges @@ -18885,7 +18885,7 @@ line457 - col9 + col10 file0 @@ -18909,12 +18909,12 @@ line457 - col9 + col10 file0 line457 - col9 + col14 file0 @@ -22243,6 +22243,40 @@ + + kindcontrol + edges + + + start + + + line587 + col11 + file0 + + + line587 + col11 + file0 + + + end + + + line587 + col11 + file0 + + + line587 + col11 + file0 + + + + + kindpop-up location @@ -22251,21 +22285,6 @@ col11 file0 - ranges - - - - line587 - col11 - file0 - - - line587 - col16 - file0 - - - extended_message Field 'b' is equal to 2 message diff --git a/clang/test/Analysis/Inputs/expected-plists/inline-plist.c.plist b/clang/test/Analysis/Inputs/expected-plists/inline-plist.c.plist index db6b5af0d5684..1ee17de4b5fa9 100644 --- a/clang/test/Analysis/Inputs/expected-plists/inline-plist.c.plist +++ b/clang/test/Analysis/Inputs/expected-plists/inline-plist.c.plist @@ -548,7 +548,7 @@ line45 - col12 + col7 file0 diff --git a/clang/test/Analysis/Inputs/expected-plists/objc-radar17039661.m.plist b/clang/test/Analysis/Inputs/expected-plists/objc-radar17039661.m.plist index 926f827426499..3c87e3909bec5 100644 --- a/clang/test/Analysis/Inputs/expected-plists/objc-radar17039661.m.plist +++ b/clang/test/Analysis/Inputs/expected-plists/objc-radar17039661.m.plist @@ -836,7 +836,7 @@ line38 - col37 + col20 file0 diff --git a/clang/test/Analysis/Inputs/expected-plists/plist-output.m.plist b/clang/test/Analysis/Inputs/expected-plists/plist-output.m.plist index 5b1de9121f35c..ee8bf06e59fd9 100644 --- a/clang/test/Analysis/Inputs/expected-plists/plist-output.m.plist +++ b/clang/test/Analysis/Inputs/expected-plists/plist-output.m.plist @@ -2513,7 +2513,7 @@ line96 - col13 + col8 file0 @@ -2735,7 +2735,7 @@ line96 - col13 + col8 file0 @@ -3554,7 +3554,7 @@ line127 - col14 + col9 file0 @@ -3776,7 +3776,7 @@ line127 - col14 + col9 file0 From e4f5bee2a7cb1457cb9c83f739e92c00c241d75f Mon Sep 17 00:00:00 2001 From: Csaba Dabis Date: Fri, 9 Aug 2019 02:24:42 +0000 Subject: [PATCH 080/181] [analyzer] CastValueChecker: Model castAs(), getAs() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Thanks to Kristóf Umann for the great idea! Reviewed By: NoQ Differential Revision: https://reviews.llvm.org/D65889 llvm-svn: 368383 (cherry picked from commit cf229d575226ccae2742cda0708a9f981f71fdfb) --- .../Checkers/CastValueChecker.cpp | 252 +++++++++++------- clang/test/Analysis/cast-value.cpp | 118 +++++++- 2 files changed, 273 insertions(+), 97 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp index ff5d12c27c69f..9cf1b0a020a26 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp @@ -16,168 +16,238 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "llvm/ADT/Optional.h" +#include using namespace clang; using namespace ento; namespace { class CastValueChecker : public Checker { + enum class CastKind { Function, Method }; + using CastCheck = std::function; + using CheckKindPair = std::pair; + public: - // We have three cases to evaluate a cast: + // We have five cases to evaluate a cast: // 1) The parameter is non-null, the return value is non-null // 2) The parameter is non-null, the return value is null // 3) The parameter is null, the return value is null - // // cast: 1; dyn_cast: 1, 2; cast_or_null: 1, 3; dyn_cast_or_null: 1, 2, 3. + // + // 4) castAs: has no parameter, the return value is non-null. + // 5) getAs: has no parameter, the return value is null or non-null. bool evalCall(const CallEvent &Call, CheckerContext &C) const; private: - // These are known in the LLVM project. - const CallDescriptionMap CDM = { - {{{"llvm", "cast"}, 1}, &CastValueChecker::evalCast}, - {{{"llvm", "dyn_cast"}, 1}, &CastValueChecker::evalDynCast}, - {{{"llvm", "cast_or_null"}, 1}, &CastValueChecker::evalCastOrNull}, + // These are known in the LLVM project. The pairs are in the following form: + // {{{namespace, call}, argument-count}, {callback, kind}} + const CallDescriptionMap CDM = { + {{{"llvm", "cast"}, 1}, + {&CastValueChecker::evalCast, CastKind::Function}}, + {{{"llvm", "dyn_cast"}, 1}, + {&CastValueChecker::evalDynCast, CastKind::Function}}, + {{{"llvm", "cast_or_null"}, 1}, + {&CastValueChecker::evalCastOrNull, CastKind::Function}}, {{{"llvm", "dyn_cast_or_null"}, 1}, - &CastValueChecker::evalDynCastOrNull}}; + {&CastValueChecker::evalDynCastOrNull, CastKind::Function}}, + {{{"clang", "castAs"}, 0}, + {&CastValueChecker::evalCastAs, CastKind::Method}}, + {{{"clang", "getAs"}, 0}, + {&CastValueChecker::evalGetAs, CastKind::Method}}}; - void evalCast(const CallExpr *CE, DefinedOrUnknownSVal ParamDV, + void evalCast(const CallExpr *CE, DefinedOrUnknownSVal DV, CheckerContext &C) const; - void evalDynCast(const CallExpr *CE, DefinedOrUnknownSVal ParamDV, + void evalDynCast(const CallExpr *CE, DefinedOrUnknownSVal DV, CheckerContext &C) const; - void evalCastOrNull(const CallExpr *CE, DefinedOrUnknownSVal ParamDV, + void evalCastOrNull(const CallExpr *CE, DefinedOrUnknownSVal DV, CheckerContext &C) const; - void evalDynCastOrNull(const CallExpr *CE, DefinedOrUnknownSVal ParamDV, + void evalDynCastOrNull(const CallExpr *CE, DefinedOrUnknownSVal DV, CheckerContext &C) const; + void evalCastAs(const CallExpr *CE, DefinedOrUnknownSVal DV, + CheckerContext &C) const; + void evalGetAs(const CallExpr *CE, DefinedOrUnknownSVal DV, + CheckerContext &C) const; }; } // namespace static std::string getCastName(const Expr *Cast) { - return Cast->getType()->getPointeeCXXRecordDecl()->getNameAsString(); -} - -static void evalNonNullParamNonNullReturn(const CallExpr *CE, - DefinedOrUnknownSVal ParamDV, - CheckerContext &C) { - ProgramStateRef State = C.getState()->assume(ParamDV, true); - if (!State) - return; + QualType Ty = Cast->getType(); + if (const CXXRecordDecl *RD = Ty->getAsCXXRecordDecl()) + return RD->getNameAsString(); - State = State->BindExpr(CE, C.getLocationContext(), ParamDV, false); + return Ty->getPointeeCXXRecordDecl()->getNameAsString(); +} - std::string CastFromName = getCastName(CE->getArg(0)); +static const NoteTag *getCastTag(bool IsNullReturn, const CallExpr *CE, + CheckerContext &C, + bool IsCheckedCast = false) { + Optional CastFromName = (CE->getNumArgs() > 0) + ? getCastName(CE->getArg(0)) + : Optional(); std::string CastToName = getCastName(CE); - const NoteTag *CastTag = C.getNoteTag( - [CastFromName, CastToName](BugReport &) -> std::string { + return C.getNoteTag( + [CastFromName, CastToName, IsNullReturn, + IsCheckedCast](BugReport &) -> std::string { SmallString<128> Msg; llvm::raw_svector_ostream Out(Msg); - Out << "Assuming dynamic cast from '" << CastFromName << "' to '" - << CastToName << "' succeeds"; - return Out.str(); - }, - /*IsPrunable=*/true); + Out << (!IsCheckedCast ? "Assuming dynamic cast " : "Checked cast "); + if (CastFromName) + Out << "from '" << *CastFromName << "' "; - C.addTransition(State, CastTag); -} + Out << "to '" << CastToName << "' " + << (!IsNullReturn ? "succeeds" : "fails"); -static void evalNonNullParamNullReturn(const CallExpr *CE, - DefinedOrUnknownSVal ParamDV, - CheckerContext &C) { - ProgramStateRef State = C.getState()->assume(ParamDV, true); - if (!State) - return; - - State = State->BindExpr(CE, C.getLocationContext(), - C.getSValBuilder().makeNull(), false); - - std::string CastFromName = getCastName(CE->getArg(0)); - std::string CastToName = getCastName(CE); - - const NoteTag *CastTag = C.getNoteTag( - [CastFromName, CastToName](BugReport &) -> std::string { - SmallString<128> Msg; - llvm::raw_svector_ostream Out(Msg); - - Out << "Assuming dynamic cast from '" << CastFromName << "' to '" - << CastToName << "' fails"; return Out.str(); }, /*IsPrunable=*/true); +} - C.addTransition(State, CastTag); +static ProgramStateRef getState(bool IsNullReturn, + DefinedOrUnknownSVal ReturnDV, + const CallExpr *CE, ProgramStateRef State, + CheckerContext &C) { + return State->BindExpr( + CE, C.getLocationContext(), + IsNullReturn ? C.getSValBuilder().makeNull() : ReturnDV, false); } -static void evalNullParamNullReturn(const CallExpr *CE, - DefinedOrUnknownSVal ParamDV, - CheckerContext &C) { - ProgramStateRef State = C.getState()->assume(ParamDV, false); - if (!State) - return; +//===----------------------------------------------------------------------===// +// Evaluating cast, dyn_cast, cast_or_null, dyn_cast_or_null. +//===----------------------------------------------------------------------===// - State = State->BindExpr(CE, C.getLocationContext(), - C.getSValBuilder().makeNull(), false); +static void evalNonNullParamNonNullReturn(const CallExpr *CE, + DefinedOrUnknownSVal DV, + CheckerContext &C, + bool IsCheckedCast = false) { + bool IsNullReturn = false; + if (ProgramStateRef State = C.getState()->assume(DV, true)) + C.addTransition(getState(IsNullReturn, DV, CE, State, C), + getCastTag(IsNullReturn, CE, C, IsCheckedCast)); +} - const NoteTag *CastTag = - C.getNoteTag("Assuming null pointer is passed into cast", - /*IsPrunable=*/true); +static void evalNonNullParamNullReturn(const CallExpr *CE, + DefinedOrUnknownSVal DV, + CheckerContext &C) { + bool IsNullReturn = true; + if (ProgramStateRef State = C.getState()->assume(DV, true)) + C.addTransition(getState(IsNullReturn, DV, CE, State, C), + getCastTag(IsNullReturn, CE, C)); +} - C.addTransition(State, CastTag); +static void evalNullParamNullReturn(const CallExpr *CE, DefinedOrUnknownSVal DV, + CheckerContext &C) { + if (ProgramStateRef State = C.getState()->assume(DV, false)) + C.addTransition(getState(/*IsNullReturn=*/true, DV, CE, State, C), + C.getNoteTag("Assuming null pointer is passed into cast", + /*IsPrunable=*/true)); } -void CastValueChecker::evalCast(const CallExpr *CE, - DefinedOrUnknownSVal ParamDV, +void CastValueChecker::evalCast(const CallExpr *CE, DefinedOrUnknownSVal DV, CheckerContext &C) const { - evalNonNullParamNonNullReturn(CE, ParamDV, C); + evalNonNullParamNonNullReturn(CE, DV, C, /*IsCheckedCast=*/true); } -void CastValueChecker::evalDynCast(const CallExpr *CE, - DefinedOrUnknownSVal ParamDV, +void CastValueChecker::evalDynCast(const CallExpr *CE, DefinedOrUnknownSVal DV, CheckerContext &C) const { - evalNonNullParamNonNullReturn(CE, ParamDV, C); - evalNonNullParamNullReturn(CE, ParamDV, C); + evalNonNullParamNonNullReturn(CE, DV, C); + evalNonNullParamNullReturn(CE, DV, C); } void CastValueChecker::evalCastOrNull(const CallExpr *CE, - DefinedOrUnknownSVal ParamDV, + DefinedOrUnknownSVal DV, CheckerContext &C) const { - evalNonNullParamNonNullReturn(CE, ParamDV, C); - evalNullParamNullReturn(CE, ParamDV, C); + evalNonNullParamNonNullReturn(CE, DV, C); + evalNullParamNullReturn(CE, DV, C); } void CastValueChecker::evalDynCastOrNull(const CallExpr *CE, - DefinedOrUnknownSVal ParamDV, + DefinedOrUnknownSVal DV, CheckerContext &C) const { - evalNonNullParamNonNullReturn(CE, ParamDV, C); - evalNonNullParamNullReturn(CE, ParamDV, C); - evalNullParamNullReturn(CE, ParamDV, C); + evalNonNullParamNonNullReturn(CE, DV, C); + evalNonNullParamNullReturn(CE, DV, C); + evalNullParamNullReturn(CE, DV, C); +} + +//===----------------------------------------------------------------------===// +// Evaluating castAs, getAs. +//===----------------------------------------------------------------------===// + +static void evalZeroParamNonNullReturn(const CallExpr *CE, + DefinedOrUnknownSVal DV, + CheckerContext &C, + bool IsCheckedCast = false) { + bool IsNullReturn = false; + if (ProgramStateRef State = C.getState()->assume(DV, true)) + C.addTransition(getState(IsNullReturn, DV, CE, C.getState(), C), + getCastTag(IsNullReturn, CE, C, IsCheckedCast)); +} + +static void evalZeroParamNullReturn(const CallExpr *CE, DefinedOrUnknownSVal DV, + CheckerContext &C) { + bool IsNullReturn = true; + if (ProgramStateRef State = C.getState()->assume(DV, true)) + C.addTransition(getState(IsNullReturn, DV, CE, C.getState(), C), + getCastTag(IsNullReturn, CE, C)); +} + +void CastValueChecker::evalCastAs(const CallExpr *CE, DefinedOrUnknownSVal DV, + CheckerContext &C) const { + evalZeroParamNonNullReturn(CE, DV, C, /*IsCheckedCast=*/true); +} + +void CastValueChecker::evalGetAs(const CallExpr *CE, DefinedOrUnknownSVal DV, + CheckerContext &C) const { + evalZeroParamNonNullReturn(CE, DV, C); + evalZeroParamNullReturn(CE, DV, C); } bool CastValueChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { - const CastCheck *Check = CDM.lookup(Call); - if (!Check) + const auto *Lookup = CDM.lookup(Call); + if (!Lookup) return false; - const auto *CE = cast(Call.getOriginExpr()); - if (!CE) + // If we cannot obtain the call's class we cannot be sure how to model it. + QualType ResultTy = Call.getResultType(); + if (!ResultTy->getPointeeCXXRecordDecl()) return false; - // If we cannot obtain both of the classes we cannot be sure how to model it. - if (!CE->getType()->getPointeeCXXRecordDecl() || - !CE->getArg(0)->getType()->getPointeeCXXRecordDecl()) - return false; + const CastCheck &Check = Lookup->first; + CastKind Kind = Lookup->second; - SVal ParamV = Call.getArgSVal(0); - auto ParamDV = ParamV.getAs(); - if (!ParamDV) + const auto *CE = cast(Call.getOriginExpr()); + Optional DV; + + switch (Kind) { + case CastKind::Function: { + // If we cannot obtain the arg's class we cannot be sure how to model it. + QualType ArgTy = Call.parameters()[0]->getType(); + if (!ArgTy->getAsCXXRecordDecl() && !ArgTy->getPointeeCXXRecordDecl()) + return false; + + DV = Call.getArgSVal(0).getAs(); + break; + } + case CastKind::Method: + // If we cannot obtain the 'InstanceCall' we cannot be sure how to model it. + const auto *InstanceCall = dyn_cast(&Call); + if (!InstanceCall) + return false; + + DV = InstanceCall->getCXXThisVal().getAs(); + break; + } + + if (!DV) return false; - (*Check)(this, CE, *ParamDV, C); + Check(this, CE, *DV, C); return true; } diff --git a/clang/test/Analysis/cast-value.cpp b/clang/test/Analysis/cast-value.cpp index 609ee23fd64c0..a67ffe2c0833c 100644 --- a/clang/test/Analysis/cast-value.cpp +++ b/clang/test/Analysis/cast-value.cpp @@ -14,20 +14,33 @@ template const X *cast(Y Value); template -const X *dyn_cast(Y Value); +const X *dyn_cast(Y *Value); +template +const X &dyn_cast(Y &Value); template const X *cast_or_null(Y Value); template -const X *dyn_cast_or_null(Y Value); +const X *dyn_cast_or_null(Y *Value); +template +const X *dyn_cast_or_null(Y &Value); } // namespace llvm -using namespace llvm; +namespace clang { +struct Shape { + template + const T *castAs() const; -class Shape {}; + template + const T *getAs() const; +}; class Triangle : public Shape {}; class Circle : public Shape {}; +} // namespace clang + +using namespace llvm; +using namespace clang; namespace test_cast { void evalLogic(const Shape *S) { @@ -91,8 +104,52 @@ void evalLogic(const Shape *S) { if (!S) clang_analyzer_eval(!C); // logic-warning {{TRUE}} } +} // namespace test_dyn_cast_or_null -void evalNonNullParamNonNullReturn(const Shape *S) { +namespace test_cast_as { +void evalLogic(const Shape *S) { + const Circle *C = S->castAs(); + clang_analyzer_numTimesReached(); // logic-warning {{1}} + + if (S && C) + clang_analyzer_eval(C == S); + // logic-warning@-1 {{TRUE}} + + if (S && !C) + clang_analyzer_warnIfReached(); // no-warning + + if (!S) + clang_analyzer_warnIfReached(); // no-warning +} +} // namespace test_cast_as + +namespace test_get_as { +void evalLogic(const Shape *S) { + const Circle *C = S->getAs(); + clang_analyzer_numTimesReached(); // logic-warning {{2}} + + if (S && C) + clang_analyzer_eval(C == S); + // logic-warning@-1 {{TRUE}} + + if (S && !C) + clang_analyzer_warnIfReached(); // logic-warning {{REACHABLE}} + + if (!S) + clang_analyzer_warnIfReached(); // no-warning +} +} // namespace test_get_as + +namespace test_notes { +void evalReferences(const Shape &S) { + const auto &C = dyn_cast(S); + // expected-note@-1 {{Assuming dynamic cast from 'Shape' to 'Circle' fails}} + // expected-note@-2 {{Dereference of null pointer}} + // expected-warning@-3 {{Dereference of null pointer}} + // logic-warning@-4 {{Dereference of null pointer}} +} + +void evalNonNullParamNonNullReturnReference(const Shape &S) { const auto *C = dyn_cast_or_null(S); // expected-note@-1 {{Assuming dynamic cast from 'Shape' to 'Circle' succeeds}} // expected-note@-2 {{Assuming pointer value is null}} @@ -105,6 +162,19 @@ void evalNonNullParamNonNullReturn(const Shape *S) { // logic-warning@-4 {{Division by zero}} } +void evalNonNullParamNonNullReturn(const Shape *S) { + const auto *C = cast(S); + // expected-note@-1 {{Checked cast from 'Shape' to 'Circle' succeeds}} + // expected-note@-2 {{Assuming pointer value is null}} + // expected-note@-3 {{'C' initialized here}} + + (void)(1 / !(bool)C); + // expected-note@-1 {{'C' is non-null}} + // expected-note@-2 {{Division by zero}} + // expected-warning@-3 {{Division by zero}} + // logic-warning@-4 {{Division by zero}} +} + void evalNonNullParamNullReturn(const Shape *S) { const auto *C = dyn_cast_or_null(S); // expected-note@-1 {{Assuming dynamic cast from 'Shape' to 'Circle' fails}} @@ -134,4 +204,40 @@ void evalNullParamNullReturn(const Shape *S) { // expected-warning@-2 {{Division by zero}} // logic-warning@-3 {{Division by zero}} } -} // namespace test_dyn_cast_or_null + +void evalZeroParamNonNullReturnPointer(const Shape *S) { + const auto *C = S->castAs(); + // expected-note@-1 {{Assuming pointer value is null}} + // expected-note@-2 {{Checked cast to 'Circle' succeeds}} + // expected-note@-3 {{'C' initialized here}} + + (void)(1 / !(bool)C); + // expected-note@-1 {{'C' is non-null}} + // expected-note@-2 {{Division by zero}} + // expected-warning@-3 {{Division by zero}} + // logic-warning@-4 {{Division by zero}} +} + +void evalZeroParamNonNullReturn(const Shape &S) { + const auto *C = S.castAs(); + // expected-note@-1 {{Checked cast to 'Circle' succeeds}} + // expected-note@-2 {{'C' initialized here}} + + (void)(1 / !(bool)C); + // expected-note@-1 {{'C' is non-null}} + // expected-note@-2 {{Division by zero}} + // expected-warning@-3 {{Division by zero}} + // logic-warning@-4 {{Division by zero}} +} + +void evalZeroParamNullReturn(const Shape &S) { + const auto *C = S.getAs(); + // expected-note@-1 {{Assuming dynamic cast to 'Circle' fails}} + // expected-note@-2 {{'C' initialized to a null pointer value}} + + (void)(1 / (bool)C); + // expected-note@-1 {{Division by zero}} + // expected-warning@-2 {{Division by zero}} + // logic-warning@-3 {{Division by zero}} +} +} // namespace test_notes From ae739093de37167e7fcefacdde9d99646cba2575 Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Tue, 13 Aug 2019 13:09:48 +0000 Subject: [PATCH 081/181] [analyzer][NFC] Refactoring BugReporter.cpp P1.: Store interesting symbols/regions in a simple set The goal of this refactoring effort was to better understand how interestingness was propagated in BugReporter.cpp, which eventually turned out to be a dead end, but with such a twist, I wouldn't even want to spoil it ahead of time. However, I did get to learn a lot about how things are working in there. In these series of patches, as well as cleaning up the code big time, I invite you to study how BugReporter.cpp operates, and discuss how we could design this file to reduce the horrible mess that it is. This patch reverts a great part of rC162028, which holds the title "Allow multiple PathDiagnosticConsumers to be used with a BugReporter at the same time.". This, however doesn't imply that there's any need for multiple "layers" or stacks of interesting symbols and regions, quite the contrary, I would argue that we would like to generate the same amount of information for all output types, and only process them differently. Differential Revision: https://reviews.llvm.org/D65378 llvm-svn: 368689 (cherry picked from commit b9bd6ebe1dca7978b86f27c583e5b471b447108b) --- .../Core/BugReporter/BugReporter.h | 23 ++------ clang/lib/StaticAnalyzer/Core/BugReporter.cpp | 53 +++---------------- 2 files changed, 12 insertions(+), 64 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h index d30ad19b20f85..f99af2be2d4c8 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h @@ -107,22 +107,19 @@ class BugReport : public llvm::ilist_node { ExtraTextList ExtraText; NoteList Notes; - using Symbols = llvm::DenseSet; - using Regions = llvm::DenseSet; - /// A (stack of) a set of symbols that are registered with this /// report as being "interesting", and thus used to help decide which /// diagnostics to include when constructing the final path diagnostic. /// The stack is largely used by BugReporter when generating PathDiagnostics /// for multiple PathDiagnosticConsumers. - SmallVector interestingSymbols; + llvm::DenseSet InterestingSymbols; /// A (stack of) set of regions that are registered with this report as being /// "interesting", and thus used to help decide which diagnostics /// to include when constructing the final path diagnostic. /// The stack is largely used by BugReporter when generating PathDiagnostics /// for multiple PathDiagnosticConsumers. - SmallVector interestingRegions; + llvm::DenseSet InterestingRegions; /// A set of location contexts that correspoind to call sites which should be /// considered "interesting". @@ -156,15 +153,6 @@ class BugReport : public llvm::ilist_node { /// Conditions we're already tracking. llvm::SmallSet TrackedConditions; -private: - // Used internally by BugReporter. - Symbols &getInterestingSymbols(); - Regions &getInterestingRegions(); - - void lazyInitializeInterestingSets(); - void pushInterestingSymbolsAndRegions(); - void popInterestingSymbolsAndRegions(); - public: BugReport(const BugType& bt, StringRef desc, const ExplodedNode *errornode) : BT(bt), Description(desc), ErrorNode(errornode) {} @@ -189,7 +177,7 @@ class BugReport : public llvm::ilist_node { : BT(bt), Description(desc), UniqueingLocation(LocationToUnique), UniqueingDecl(DeclToUnique), ErrorNode(errornode) {} - virtual ~BugReport(); + virtual ~BugReport() = default; const BugType& getBugType() const { return BT; } //BugType& getBugType() { return BT; } @@ -381,7 +369,6 @@ class BugReportEquivClass : public llvm::FoldingSetNode { public: BugReportEquivClass(std::unique_ptr R) { AddReport(std::move(R)); } - ~BugReportEquivClass(); void Profile(llvm::FoldingSetNodeID& ID) const { assert(!Reports.empty()); @@ -404,7 +391,7 @@ class BugReportEquivClass : public llvm::FoldingSetNode { class BugReporterData { public: - virtual ~BugReporterData(); + virtual ~BugReporterData() = default; virtual DiagnosticsEngine& getDiagnostic() = 0; virtual ArrayRef getPathDiagnosticConsumers() = 0; @@ -526,7 +513,7 @@ class GRBugReporter : public BugReporter { GRBugReporter(BugReporterData& d, ExprEngine& eng) : BugReporter(d, GRBugReporterKind), Eng(eng) {} - ~GRBugReporter() override; + ~GRBugReporter() override = default;; /// getGraph - Get the exploded graph created by the analysis engine /// for the analyzed method or function. diff --git a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp index 6627633f39332..5bdd9ecef0b48 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp @@ -2058,12 +2058,6 @@ void BugReport::clearVisitors() { Callbacks.clear(); } -BugReport::~BugReport() { - while (!interestingSymbols.empty()) { - popInterestingSymbolsAndRegions(); - } -} - const Decl *BugReport::getDeclWithIssue() const { if (DeclWithIssue) return DeclWithIssue; @@ -2101,10 +2095,10 @@ void BugReport::markInteresting(SymbolRef sym) { if (!sym) return; - getInterestingSymbols().insert(sym); + InterestingSymbols.insert(sym); if (const auto *meta = dyn_cast(sym)) - getInterestingRegions().insert(meta->getRegion()); + InterestingRegions.insert(meta->getRegion()); } void BugReport::markInteresting(const MemRegion *R) { @@ -2112,10 +2106,10 @@ void BugReport::markInteresting(const MemRegion *R) { return; R = R->getBaseRegion(); - getInterestingRegions().insert(R); + InterestingRegions.insert(R); if (const auto *SR = dyn_cast(R)) - getInterestingSymbols().insert(SR->getSymbol()); + InterestingSymbols.insert(SR->getSymbol()); } void BugReport::markInteresting(SVal V) { @@ -2138,18 +2132,18 @@ bool BugReport::isInteresting(SymbolRef sym) { return false; // We don't currently consider metadata symbols to be interesting // even if we know their region is interesting. Is that correct behavior? - return getInterestingSymbols().count(sym); + return InterestingSymbols.count(sym); } bool BugReport::isInteresting(const MemRegion *R) { if (!R) return false; R = R->getBaseRegion(); - bool b = getInterestingRegions().count(R); + bool b = InterestingRegions.count(R); if (b) return true; if (const auto *SR = dyn_cast(R)) - return getInterestingSymbols().count(SR->getSymbol()); + return InterestingSymbols.count(SR->getSymbol()); return false; } @@ -2159,33 +2153,6 @@ bool BugReport::isInteresting(const LocationContext *LC) { return InterestingLocationContexts.count(LC); } -void BugReport::lazyInitializeInterestingSets() { - if (interestingSymbols.empty()) { - interestingSymbols.push_back(new Symbols()); - interestingRegions.push_back(new Regions()); - } -} - -BugReport::Symbols &BugReport::getInterestingSymbols() { - lazyInitializeInterestingSets(); - return *interestingSymbols.back(); -} - -BugReport::Regions &BugReport::getInterestingRegions() { - lazyInitializeInterestingSets(); - return *interestingRegions.back(); -} - -void BugReport::pushInterestingSymbolsAndRegions() { - interestingSymbols.push_back(new Symbols(getInterestingSymbols())); - interestingRegions.push_back(new Regions(getInterestingRegions())); -} - -void BugReport::popInterestingSymbolsAndRegions() { - delete interestingSymbols.pop_back_val(); - delete interestingRegions.pop_back_val(); -} - const Stmt *BugReport::getStmt() const { if (!ErrorNode) return nullptr; @@ -2236,12 +2203,6 @@ PathDiagnosticLocation BugReport::getLocation(const SourceManager &SM) const { // Methods for BugReporter and subclasses. //===----------------------------------------------------------------------===// -BugReportEquivClass::~BugReportEquivClass() = default; - -GRBugReporter::~GRBugReporter() = default; - -BugReporterData::~BugReporterData() = default; - ExplodedGraph &GRBugReporter::getGraph() { return Eng.getGraph(); } ProgramStateManager& From ceaf317f9e1ab74ae1caeba1ee815b960478cf17 Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Tue, 13 Aug 2019 13:56:12 +0000 Subject: [PATCH 082/181] [analyzer][NFC] Refactoring BugReporter.cpp P2.: Clean up the construction of bug paths and finding a valid report This patch refactors the utility functions and classes around the construction of a bug path. At a very high level, this consists of 3 steps: * For all BugReports in the same BugReportEquivClass, collect all their error nodes in a set. With that set, create a new, trimmed ExplodedGraph whose leafs are all error nodes. * Until a valid report is found, construct a bug path, which is yet another ExplodedGraph, that is linear from a given error node to the root of the graph. * Run all visitors on the constructed bug path. If in this process the report got invalidated, start over from step 2. Now, to the changes within this patch: * Do not allow the invalidation of BugReports up to the point where the trimmed graph is constructed. Checkers shouldn't add bug reports that are known to be invalid, and should use visitors and argue about the entirety of the bug path if needed. * Do not calculate indices. I may be biased, but I personally find code like this horrible. I'd like to point you to one of the comments in the original code: SmallVector errorNodes; for (const auto I : bugReports) { if (I->isValid()) { HasValid = true; errorNodes.push_back(I->getErrorNode()); } else { // Keep the errorNodes list in sync with the bugReports list. errorNodes.push_back(nullptr); } } Not on my watch. Instead, use a far easier to follow trick: store a pointer to the BugReport in question, not an index to it. * Add range iterators to ExplodedGraph's successors and predecessors, and a visitor range to BugReporter. * Rename TrimmedGraph to BugPathGetter. Because that is what it has always been: no sane graph type should store an iterator-like state, or have an interface not exposing a single graph-like functionalities. * Rename ReportGraph to BugPathInfo, because it is only a linear path with some other context. * Instead of having both and out and in parameter (which I think isn't ever excusable unless we use the out-param for caching), return a record object with descriptive getter methods. * Where descriptive names weren't sufficient, compliment the code with comments. Differential Revision: https://reviews.llvm.org/D65379 llvm-svn: 368694 (cherry picked from commit ed9cc4079452e827c8cd0f6d4f18d1bbc3258f03) --- .../Core/BugReporter/BugReporter.h | 2 + .../Core/PathSensitive/ExplodedGraph.h | 11 + clang/lib/StaticAnalyzer/Core/BugReporter.cpp | 211 ++++++++++-------- 3 files changed, 128 insertions(+), 96 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h index f99af2be2d4c8..9a16f23461ceb 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h @@ -87,6 +87,7 @@ class BugReport : public llvm::ilist_node { using ranges_iterator = const SourceRange *; using VisitorList = SmallVector, 8>; using visitor_iterator = VisitorList::iterator; + using visitor_range = llvm::iterator_range; using ExtraTextList = SmallVector; using NoteList = SmallVector, 4>; @@ -339,6 +340,7 @@ class BugReport : public llvm::ilist_node { /// Iterators through the custom diagnostic visitors. visitor_iterator visitor_begin() { return Callbacks.begin(); } visitor_iterator visitor_end() { return Callbacks.end(); } + visitor_range visitors() { return {visitor_begin(), visitor_end()}; } /// Notes that the condition of the CFGBlock associated with \p Cond is /// being tracked. diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h index 727d04cba2784..52ae92e70d3dd 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h @@ -219,12 +219,20 @@ class ExplodedNode : public llvm::FoldingSetNode { // Iterators over successor and predecessor vertices. using succ_iterator = ExplodedNode * const *; + using succ_range = llvm::iterator_range; + using const_succ_iterator = const ExplodedNode * const *; + using const_succ_range = llvm::iterator_range; + using pred_iterator = ExplodedNode * const *; + using pred_range = llvm::iterator_range; + using const_pred_iterator = const ExplodedNode * const *; + using const_pred_range = llvm::iterator_range; pred_iterator pred_begin() { return Preds.begin(); } pred_iterator pred_end() { return Preds.end(); } + pred_range preds() { return {Preds.begin(), Preds.end()}; } const_pred_iterator pred_begin() const { return const_cast(this)->pred_begin(); @@ -232,9 +240,11 @@ class ExplodedNode : public llvm::FoldingSetNode { const_pred_iterator pred_end() const { return const_cast(this)->pred_end(); } + const_pred_range preds() const { return {Preds.begin(), Preds.end()}; } succ_iterator succ_begin() { return Succs.begin(); } succ_iterator succ_end() { return Succs.end(); } + succ_range succs() { return {Succs.begin(), Succs.end()}; } const_succ_iterator succ_begin() const { return const_cast(this)->succ_begin(); @@ -242,6 +252,7 @@ class ExplodedNode : public llvm::FoldingSetNode { const_succ_iterator succ_end() const { return const_cast(this)->succ_end(); } + const_succ_range succs() const { return {Succs.begin(), Succs.end()}; } int64_t getID(ExplodedGraph *G) const; diff --git a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp index 5bdd9ecef0b48..9058fe7a13dd2 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp @@ -2241,29 +2241,36 @@ void BugReporter::FlushReports() { namespace { -/// A wrapper around a report graph, which contains only a single path, and its -/// node maps. -class ReportGraph { +/// A wrapper around an ExplodedGraph that contains a single path from the root +/// to the error node, and a map that maps the nodes in this path to the ones in +/// the original ExplodedGraph. +class BugPathInfo { public: - InterExplodedGraphMap BackMap; - std::unique_ptr Graph; + InterExplodedGraphMap MapToOriginNodes; + std::unique_ptr Path; + BugReport *Report; const ExplodedNode *ErrorNode; - size_t Index; }; -/// A wrapper around a trimmed graph and its node maps. -class TrimmedGraph { +/// A wrapper around an ExplodedGraph whose leafs are all error nodes. Can +/// conveniently retrieve bug paths from a single error node to the root. +class BugPathGetter { + std::unique_ptr TrimmedGraph; + + /// Map from the trimmed graph to the original. InterExplodedGraphMap InverseMap; using PriorityMapTy = llvm::DenseMap; + /// Assign each node with its distance from the root. PriorityMapTy PriorityMap; - using NodeIndexPair = std::pair; - - SmallVector ReportNodes; + /// Since the getErrorNode() or BugReport refers to the original ExplodedGraph, + /// we need to pair it to the error node of the constructed trimmed graph. + using ReportNewNodePair = std::pair; + SmallVector ReportNodes; - std::unique_ptr G; + BugPathInfo CurrentBugPath; /// A helper class for sorting ExplodedNodes by priority. template @@ -2287,37 +2294,48 @@ class TrimmedGraph { : LI->second < RI->second; } - bool operator()(const NodeIndexPair &LHS, const NodeIndexPair &RHS) const { - return (*this)(LHS.first, RHS.first); + bool operator()(const ReportNewNodePair &LHS, + const ReportNewNodePair &RHS) const { + return (*this)(LHS.second, RHS.second); } }; public: - TrimmedGraph(const ExplodedGraph *OriginalGraph, - ArrayRef Nodes); + BugPathGetter(const ExplodedGraph *OriginalGraph, + ArrayRef &bugReports); - bool popNextReportGraph(ReportGraph &GraphWrapper); + BugPathInfo *getNextBugPath(); }; } // namespace -TrimmedGraph::TrimmedGraph(const ExplodedGraph *OriginalGraph, - ArrayRef Nodes) { +BugPathGetter::BugPathGetter(const ExplodedGraph *OriginalGraph, + ArrayRef &bugReports) { + SmallVector Nodes; + for (const auto I : bugReports) { + assert(I->isValid() && + "We only allow BugReporterVisitors and BugReporter itself to " + "invalidate reports!"); + Nodes.emplace_back(I->getErrorNode()); + } + // The trimmed graph is created in the body of the constructor to ensure // that the DenseMaps have been initialized already. InterExplodedGraphMap ForwardMap; - G = OriginalGraph->trim(Nodes, &ForwardMap, &InverseMap); + TrimmedGraph = OriginalGraph->trim(Nodes, &ForwardMap, &InverseMap); // Find the (first) error node in the trimmed graph. We just need to consult // the node map which maps from nodes in the original graph to nodes // in the new graph. llvm::SmallPtrSet RemainingNodes; - for (unsigned i = 0, count = Nodes.size(); i < count; ++i) { - if (const ExplodedNode *NewNode = ForwardMap.lookup(Nodes[i])) { - ReportNodes.push_back(std::make_pair(NewNode, i)); - RemainingNodes.insert(NewNode); - } + for (BugReport *Report : bugReports) { + const ExplodedNode *NewNode = ForwardMap.lookup(Report->getErrorNode()); + assert(NewNode && + "Failed to construct a trimmed graph that contains this error " + "node!"); + ReportNodes.emplace_back(Report, NewNode); + RemainingNodes.insert(NewNode); } assert(!RemainingNodes.empty() && "No error node found in the trimmed graph"); @@ -2325,8 +2343,8 @@ TrimmedGraph::TrimmedGraph(const ExplodedGraph *OriginalGraph, // Perform a forward BFS to find all the shortest paths. std::queue WS; - assert(G->num_roots() == 1); - WS.push(*G->roots_begin()); + assert(TrimmedGraph->num_roots() == 1); + WS.push(*TrimmedGraph->roots_begin()); unsigned Priority = 0; while (!WS.empty()) { @@ -2335,8 +2353,7 @@ TrimmedGraph::TrimmedGraph(const ExplodedGraph *OriginalGraph, PriorityMapTy::iterator PriorityEntry; bool IsNew; - std::tie(PriorityEntry, IsNew) = - PriorityMap.insert(std::make_pair(Node, Priority)); + std::tie(PriorityEntry, IsNew) = PriorityMap.insert({Node, Priority}); ++Priority; if (!IsNew) { @@ -2348,29 +2365,27 @@ TrimmedGraph::TrimmedGraph(const ExplodedGraph *OriginalGraph, if (RemainingNodes.empty()) break; - for (ExplodedNode::const_pred_iterator I = Node->succ_begin(), - E = Node->succ_end(); - I != E; ++I) - WS.push(*I); + for (const ExplodedNode *Succ : Node->succs()) + WS.push(Succ); } // Sort the error paths from longest to shortest. llvm::sort(ReportNodes, PriorityCompare(PriorityMap)); } -bool TrimmedGraph::popNextReportGraph(ReportGraph &GraphWrapper) { +BugPathInfo *BugPathGetter::getNextBugPath() { if (ReportNodes.empty()) - return false; + return nullptr; const ExplodedNode *OrigN; - std::tie(OrigN, GraphWrapper.Index) = ReportNodes.pop_back_val(); + std::tie(CurrentBugPath.Report, OrigN) = ReportNodes.pop_back_val(); assert(PriorityMap.find(OrigN) != PriorityMap.end() && "error node not accessible from root"); - // Create a new graph with a single path. This is the graph - // that will be returned to the caller. + // Create a new graph with a single path. This is the graph that will be + // returned to the caller. auto GNew = llvm::make_unique(); - GraphWrapper.BackMap.clear(); + CurrentBugPath.MapToOriginNodes.clear(); // Now walk from the error node up the BFS path, always taking the // predeccessor with the lowest number. @@ -2378,19 +2393,19 @@ bool TrimmedGraph::popNextReportGraph(ReportGraph &GraphWrapper) { while (true) { // Create the equivalent node in the new graph with the same state // and location. - ExplodedNode *NewN = GNew->createUncachedNode(OrigN->getLocation(), OrigN->getState(), - OrigN->isSink()); + ExplodedNode *NewN = GNew->createUncachedNode( + OrigN->getLocation(), OrigN->getState(), OrigN->isSink()); // Store the mapping to the original node. InterExplodedGraphMap::const_iterator IMitr = InverseMap.find(OrigN); assert(IMitr != InverseMap.end() && "No mapping to original node."); - GraphWrapper.BackMap[NewN] = IMitr->second; + CurrentBugPath.MapToOriginNodes[NewN] = IMitr->second; // Link up the new node with the previous node. if (Succ) Succ->addPredecessor(NewN, *GNew); else - GraphWrapper.ErrorNode = NewN; + CurrentBugPath.ErrorNode = NewN; Succ = NewN; @@ -2403,12 +2418,12 @@ bool TrimmedGraph::popNextReportGraph(ReportGraph &GraphWrapper) { // Find the next predeccessor node. We choose the node that is marked // with the lowest BFS number. OrigN = *std::min_element(OrigN->pred_begin(), OrigN->pred_end(), - PriorityCompare(PriorityMap)); + PriorityCompare(PriorityMap)); } - GraphWrapper.Graph = std::move(GNew); + CurrentBugPath.Path = std::move(GNew); - return true; + return &CurrentBugPath; } /// CompactMacroExpandedPieces - This function postprocesses a PathDiagnostic @@ -2519,12 +2534,14 @@ generateVisitorsDiagnostics(BugReport *R, const ExplodedNode *ErrorNode, const ExplodedNode *NextNode = ErrorNode->getFirstPred(); while (NextNode) { - // At each iteration, move all visitors from report to visitor list. - for (BugReport::visitor_iterator I = R->visitor_begin(), - E = R->visitor_end(); - I != E; ++I) { - visitors.push_back(std::move(*I)); - } + // At each iteration, move all visitors from report to visitor list. This is + // important, because the Profile() functions of the visitors make sure that + // a visitor isn't added multiple times for the same node, but it's fine + // to add the a visitor with Profile() for different nodes (e.g. tracking + // a region at different points of the symbolic execution). + for (std::unique_ptr &Visitor : R->visitors()) + visitors.push_back(std::move(Visitor)); + R->clearVisitors(); const ExplodedNode *Pred = NextNode->getFirstPred(); @@ -2536,6 +2553,8 @@ generateVisitorsDiagnostics(BugReport *R, const ExplodedNode *ErrorNode, if (auto Piece = V->getEndPath(BRC, ErrorNode, *R)) { assert(!LastPiece && "There can only be one final piece in a diagnostic."); + assert(Piece->getKind() == PathDiagnosticPiece::Kind::Event && + "The final piece must contain a message!"); LastPiece = std::move(Piece); (*Notes)[ErrorNode].push_back(LastPiece); } @@ -2558,24 +2577,43 @@ generateVisitorsDiagnostics(BugReport *R, const ExplodedNode *ErrorNode, return Notes; } -/// Find a non-invalidated report for a given equivalence class, -/// and return together with a cache of visitors notes. -/// If none found, return a nullptr paired with an empty cache. -static -std::pair> findValidReport( - TrimmedGraph &TrimG, - ReportGraph &ErrorGraph, - ArrayRef &bugReports, - AnalyzerOptions &Opts, - GRBugReporter &Reporter) { +class ReportInfo { + BugPathInfo BugPath; + std::unique_ptr VisitorDiagnostics; + +public: + ReportInfo(BugPathInfo &&BugPath, std::unique_ptr V) + : BugPath(std::move(BugPath)), VisitorDiagnostics(std::move(V)) {} + + ReportInfo() = default; - while (TrimG.popNextReportGraph(ErrorGraph)) { + bool isValid() { return static_cast(VisitorDiagnostics); } + + BugReport *getBugReport() { return BugPath.Report; } + const ExplodedNode *getErrorNode() { return BugPath.ErrorNode; } + + InterExplodedGraphMap &getMapToOriginNodes() { + return BugPath.MapToOriginNodes; + } + + VisitorsDiagnosticsTy &getVisitorsDiagnostics() { + return *VisitorDiagnostics; + } +}; + +/// Find a non-invalidated report for a given equivalence class, and returns +/// the bug path associated with it together with a cache of visitors notes. +/// If none found, returns an isInvalid() object. +static ReportInfo findValidReport(ArrayRef &bugReports, + GRBugReporter &Reporter) { + BugPathGetter BugGraph(&Reporter.getGraph(), bugReports); + + while (BugPathInfo *BugPath = BugGraph.getNextBugPath()) { // Find the BugReport with the original location. - assert(ErrorGraph.Index < bugReports.size()); - BugReport *R = bugReports[ErrorGraph.Index]; + BugReport *R = BugPath->Report; assert(R && "No original report found for sliced graph."); assert(R->isValid() && "Report selected by trimmed graph marked invalid."); - const ExplodedNode *ErrorNode = ErrorGraph.ErrorNode; + const ExplodedNode *ErrorNode = BugPath->ErrorNode; // Register refutation visitors first, if they mark the bug invalid no // further analysis is required @@ -2586,14 +2624,14 @@ std::pair> findValidReport( R->addVisitor(llvm::make_unique()); R->addVisitor(llvm::make_unique()); - BugReporterContext BRC(Reporter, ErrorGraph.BackMap); + BugReporterContext BRC(Reporter, BugPath->MapToOriginNodes); // Run all visitors on a given graph, once. std::unique_ptr visitorNotes = generateVisitorsDiagnostics(R, ErrorNode, BRC); if (R->isValid()) { - if (Opts.ShouldCrosscheckWithZ3) { + if (Reporter.getAnalyzerOptions().ShouldCrosscheckWithZ3) { // If crosscheck is enabled, remove all visitors, add the refutation // visitor and check again R->clearVisitors(); @@ -2601,16 +2639,16 @@ std::pair> findValidReport( // We don't overrite the notes inserted by other visitors because the // refutation manager does not add any new note to the path - generateVisitorsDiagnostics(R, ErrorGraph.ErrorNode, BRC); + generateVisitorsDiagnostics(R, BugPath->ErrorNode, BRC); } // Check if the bug is still valid if (R->isValid()) - return std::make_pair(R, std::move(visitorNotes)); + return {std::move(*BugPath), std::move(visitorNotes)}; } } - return std::make_pair(nullptr, llvm::make_unique()); + return {}; } std::unique_ptr @@ -2620,35 +2658,16 @@ GRBugReporter::generatePathDiagnostics( assert(!bugReports.empty()); auto Out = llvm::make_unique(); - bool HasValid = false; - SmallVector errorNodes; - for (const auto I : bugReports) { - if (I->isValid()) { - HasValid = true; - errorNodes.push_back(I->getErrorNode()); - } else { - // Keep the errorNodes list in sync with the bugReports list. - errorNodes.push_back(nullptr); - } - } - - // If all the reports have been marked invalid by a previous path generation, - // we're done. - if (!HasValid) - return Out; - TrimmedGraph TrimG(&getGraph(), errorNodes); - ReportGraph ErrorGraph; - auto ReportInfo = findValidReport(TrimG, ErrorGraph, bugReports, - getAnalyzerOptions(), *this); - BugReport *R = ReportInfo.first; + ReportInfo Info = findValidReport(bugReports, *this); - if (R && R->isValid()) { - const ExplodedNode *ErrorNode = ErrorGraph.ErrorNode; + if (Info.isValid()) { for (PathDiagnosticConsumer *PC : consumers) { - PathDiagnosticBuilder PDB(*this, R, ErrorGraph.BackMap, PC); + PathDiagnosticBuilder PDB(*this, Info.getBugReport(), + Info.getMapToOriginNodes(), PC); std::unique_ptr PD = generatePathDiagnosticForConsumer( - PC->getGenerationScheme(), PDB, ErrorNode, *ReportInfo.second); + PC->getGenerationScheme(), PDB, Info.getErrorNode(), + Info.getVisitorsDiagnostics()); (*Out)[PC] = std::move(PD); } } From 830bb802e79c42ba7aba833e06f068659ca2f17a Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Tue, 13 Aug 2019 16:45:48 +0000 Subject: [PATCH 083/181] [analyzer][NFC] Refactoring BugReporter.cpp P3.: std::shared_pointer -> PathDiagnosticPieceRef find clang/ -type f -exec sed -i 's/std::shared_ptr/PathDiagnosticPieceRef/g' {} \; git diff -U3 --no-color HEAD^ | clang-format-diff-6.0 -p1 -i Just as C++ is meant to be refactored, right? Differential Revision: https://reviews.llvm.org/D65381 llvm-svn: 368717 (cherry picked from commit 6d716ef1814bea126dcdb761eb2482abb9c44f39) --- .../Core/BugReporter/BugReporterVisitors.h | 115 +++++++-------- .../Core/BugReporter/PathDiagnostic.h | 6 +- .../DeleteWithNonVirtualDtorChecker.cpp | 11 +- .../Checkers/DynamicTypeChecker.cpp | 12 +- .../Checkers/DynamicTypePropagation.cpp | 12 +- .../Checkers/InnerPointerChecker.cpp | 12 +- .../Checkers/LocalizationChecker.cpp | 10 +- .../Checkers/MPI-Checker/MPIBugReporter.cpp | 6 +- .../Checkers/MPI-Checker/MPIBugReporter.h | 6 +- .../Checkers/MacOSKeychainAPIChecker.cpp | 8 +- .../StaticAnalyzer/Checkers/MallocChecker.cpp | 22 +-- .../StaticAnalyzer/Checkers/MoveChecker.cpp | 11 +- .../Checkers/NullabilityChecker.cpp | 12 +- .../Checkers/ObjCSuperDeallocChecker.cpp | 11 +- .../RetainCountDiagnostics.cpp | 35 +++-- clang/lib/StaticAnalyzer/Checkers/Taint.cpp | 6 +- clang/lib/StaticAnalyzer/Checkers/Taint.h | 6 +- .../Checkers/TestAfterDivZeroChecker.cpp | 12 +- .../StaticAnalyzer/Checkers/ValistChecker.cpp | 20 +-- .../Checkers/VirtualCallChecker.cpp | 12 +- clang/lib/StaticAnalyzer/Core/BugReporter.cpp | 20 ++- .../Core/BugReporterVisitors.cpp | 131 ++++++++---------- .../StaticAnalyzer/Core/HTMLDiagnostics.cpp | 18 ++- .../StaticAnalyzer/Core/PlistDiagnostics.cpp | 14 +- 24 files changed, 251 insertions(+), 277 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h index ef5d327d39da5..ecd5ed0ca3927 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h @@ -38,6 +38,7 @@ class BugReporterContext; class ExplodedNode; class MemRegion; class PathDiagnosticPiece; +using PathDiagnosticPieceRef = std::shared_ptr; /// BugReporterVisitors are used to add custom diagnostics along a path. class BugReporterVisitor : public llvm::FoldingSetNode { @@ -57,9 +58,9 @@ class BugReporterVisitor : public llvm::FoldingSetNode { /// /// The last parameter can be used to register a new visitor with the given /// BugReport while processing a node. - virtual std::shared_ptr - VisitNode(const ExplodedNode *Succ, - BugReporterContext &BRC, BugReport &BR) = 0; + virtual PathDiagnosticPieceRef VisitNode(const ExplodedNode *Succ, + BugReporterContext &BRC, + BugReport &BR) = 0; /// Last function called on the visitor, no further calls to VisitNode /// would follow. @@ -72,15 +73,15 @@ class BugReporterVisitor : public llvm::FoldingSetNode { /// /// NOTE that this function can be implemented on at most one used visitor, /// and otherwise it crahes at runtime. - virtual std::shared_ptr + virtual PathDiagnosticPieceRef getEndPath(BugReporterContext &BRC, const ExplodedNode *N, BugReport &BR); virtual void Profile(llvm::FoldingSetNodeID &ID) const = 0; /// Generates the default final diagnostic piece. - static std::shared_ptr - getDefaultEndPath(BugReporterContext &BRC, const ExplodedNode *N, - BugReport &BR); + static PathDiagnosticPieceRef getDefaultEndPath(BugReporterContext &BRC, + const ExplodedNode *N, + BugReport &BR); }; /// Finds last store into the given region, @@ -106,9 +107,9 @@ class FindLastStoreBRVisitor final : public BugReporterVisitor { void Profile(llvm::FoldingSetNodeID &ID) const override; - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + BugReport &BR) override; }; class TrackConstraintBRVisitor final : public BugReporterVisitor { @@ -132,9 +133,9 @@ class TrackConstraintBRVisitor final : public BugReporterVisitor { /// to make all PathDiagnosticPieces created by this visitor. static const char *getTag(); - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + BugReport &BR) override; private: /// Checks if the constraint is valid in the current state. @@ -150,9 +151,9 @@ class NilReceiverBRVisitor final : public BugReporterVisitor { ID.AddPointer(&x); } - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + BugReport &BR) override; /// If the statement is a message send expression with nil receiver, returns /// the receiver expression. Returns NULL otherwise. @@ -175,39 +176,40 @@ class ConditionBRVisitor final : public BugReporterVisitor { /// to make all PathDiagnosticPieces created by this visitor. static const char *getTag(); - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + BugReport &BR) override; - std::shared_ptr VisitNodeImpl(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR); + PathDiagnosticPieceRef VisitNodeImpl(const ExplodedNode *N, + BugReporterContext &BRC, BugReport &BR); - std::shared_ptr - VisitTerminator(const Stmt *Term, const ExplodedNode *N, - const CFGBlock *srcBlk, const CFGBlock *dstBlk, BugReport &R, - BugReporterContext &BRC); + PathDiagnosticPieceRef VisitTerminator(const Stmt *Term, + const ExplodedNode *N, + const CFGBlock *SrcBlk, + const CFGBlock *DstBlk, BugReport &R, + BugReporterContext &BRC); - std::shared_ptr - VisitTrueTest(const Expr *Cond, BugReporterContext &BRC, BugReport &R, - const ExplodedNode *N, bool TookTrue); + PathDiagnosticPieceRef VisitTrueTest(const Expr *Cond, + BugReporterContext &BRC, BugReport &R, + const ExplodedNode *N, bool TookTrue); - std::shared_ptr - VisitTrueTest(const Expr *Cond, const DeclRefExpr *DR, - BugReporterContext &BRC, BugReport &R, const ExplodedNode *N, - bool TookTrue, bool IsAssuming); + PathDiagnosticPieceRef VisitTrueTest(const Expr *Cond, const DeclRefExpr *DR, + BugReporterContext &BRC, BugReport &R, + const ExplodedNode *N, bool TookTrue, + bool IsAssuming); - std::shared_ptr - VisitTrueTest(const Expr *Cond, const BinaryOperator *BExpr, - BugReporterContext &BRC, BugReport &R, const ExplodedNode *N, - bool TookTrue, bool IsAssuming); + PathDiagnosticPieceRef VisitTrueTest(const Expr *Cond, + const BinaryOperator *BExpr, + BugReporterContext &BRC, BugReport &R, + const ExplodedNode *N, bool TookTrue, + bool IsAssuming); - std::shared_ptr - VisitTrueTest(const Expr *Cond, const MemberExpr *ME, BugReporterContext &BRC, - BugReport &R, const ExplodedNode *N, bool TookTrue, - bool IsAssuming); + PathDiagnosticPieceRef VisitTrueTest(const Expr *Cond, const MemberExpr *ME, + BugReporterContext &BRC, BugReport &R, + const ExplodedNode *N, bool TookTrue, + bool IsAssuming); - std::shared_ptr + PathDiagnosticPieceRef VisitConditionVariable(StringRef LhsString, const Expr *CondVarExpr, BugReporterContext &BRC, BugReport &R, const ExplodedNode *N, bool TookTrue); @@ -251,9 +253,8 @@ class LikelyFalsePositiveSuppressionBRVisitor final ID.AddPointer(getTag()); } - std::shared_ptr VisitNode(const ExplodedNode *, - BugReporterContext &, - BugReport &) override { + PathDiagnosticPieceRef VisitNode(const ExplodedNode *, BugReporterContext &, + BugReport &) override { return nullptr; } @@ -279,9 +280,9 @@ class UndefOrNullArgVisitor final : public BugReporterVisitor { ID.AddPointer(R); } - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + BugReport &BR) override; }; class SuppressInlineDefensiveChecksVisitor final : public BugReporterVisitor { @@ -308,9 +309,9 @@ class SuppressInlineDefensiveChecksVisitor final : public BugReporterVisitor { /// to make all PathDiagnosticPieces created by this visitor. static const char *getTag(); - std::shared_ptr VisitNode(const ExplodedNode *Succ, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *Succ, + BugReporterContext &BRC, + BugReport &BR) override; }; /// The bug visitor will walk all the nodes in a path and collect all the @@ -326,9 +327,9 @@ class FalsePositiveRefutationBRVisitor final : public BugReporterVisitor { void Profile(llvm::FoldingSetNodeID &ID) const override; - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + BugReport &BR) override; void finalizeVisitor(BugReporterContext &BRC, const ExplodedNode *EndPathNode, BugReport &BR) override; @@ -340,9 +341,9 @@ class TagVisitor : public BugReporterVisitor { public: void Profile(llvm::FoldingSetNodeID &ID) const override; - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &R) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + BugReport &R) override; }; namespace bugreporter { diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h index 5230742a4aa43..f98259560030b 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h @@ -446,7 +446,9 @@ class PathDiagnosticPiece: public llvm::FoldingSetNode { virtual void dump() const = 0; }; -class PathPieces : public std::list> { +using PathDiagnosticPieceRef = std::shared_ptr; + +class PathPieces : public std::list { void flattenTo(PathPieces &Primary, PathPieces &Current, bool ShouldFlattenMacros) const; @@ -836,7 +838,7 @@ class PathDiagnostic : public llvm::FoldingSetNode { bool isWithinCall() const { return !pathStack.empty(); } - void setEndOfPath(std::shared_ptr EndPiece) { + void setEndOfPath(PathDiagnosticPieceRef EndPiece) { assert(!Loc.isValid() && "End location already set!"); Loc = EndPiece->getLocation(); assert(Loc.isValid() && "Invalid location for end-of-path piece"); diff --git a/clang/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp index 8bf77c109f8a6..2059ff6c0e42b 100644 --- a/clang/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp @@ -45,9 +45,9 @@ class DeleteWithNonVirtualDtorChecker static int X = 0; ID.AddPointer(&X); } - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + BugReport &BR) override; private: bool Satisfied; @@ -100,10 +100,9 @@ void DeleteWithNonVirtualDtorChecker::checkPreStmt(const CXXDeleteExpr *DE, C.emitReport(std::move(R)); } -std::shared_ptr +PathDiagnosticPieceRef DeleteWithNonVirtualDtorChecker::DeleteBugVisitor::VisitNode( - const ExplodedNode *N, BugReporterContext &BRC, - BugReport &BR) { + const ExplodedNode *N, BugReporterContext &BRC, BugReport &BR) { // Stop traversal after the first conversion was found on a path. if (Satisfied) return nullptr; diff --git a/clang/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp index 4d979dc9f2400..7dc4b93d2f88b 100644 --- a/clang/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp @@ -47,9 +47,9 @@ class DynamicTypeChecker : public Checker> { ID.AddPointer(Reg); } - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + BugReport &BR) override; private: // The tracked region. @@ -88,10 +88,8 @@ void DynamicTypeChecker::reportTypeError(QualType DynamicType, C.emitReport(std::move(R)); } -std::shared_ptr -DynamicTypeChecker::DynamicTypeBugVisitor::VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &) { +PathDiagnosticPieceRef DynamicTypeChecker::DynamicTypeBugVisitor::VisitNode( + const ExplodedNode *N, BugReporterContext &BRC, BugReport &) { ProgramStateRef State = N->getState(); ProgramStateRef StatePrev = N->getFirstPred()->getState(); diff --git a/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp b/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp index aede4e778a0ab..d32544a9ac58f 100644 --- a/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp @@ -83,9 +83,9 @@ class DynamicTypePropagation: ID.AddPointer(Sym); } - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + BugReport &BR) override; private: // The tracked symbol. @@ -928,10 +928,8 @@ void DynamicTypePropagation::reportGenericsBug( C.emitReport(std::move(R)); } -std::shared_ptr -DynamicTypePropagation::GenericsBugVisitor::VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) { +PathDiagnosticPieceRef DynamicTypePropagation::GenericsBugVisitor::VisitNode( + const ExplodedNode *N, BugReporterContext &BRC, BugReport &BR) { ProgramStateRef state = N->getState(); ProgramStateRef statePrev = N->getFirstPred()->getState(); diff --git a/clang/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp index e3270f1f7be27..3644fd87bd31f 100644 --- a/clang/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp @@ -54,9 +54,9 @@ class InnerPointerChecker ID.AddPointer(getTag()); } - virtual std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; + virtual PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + BugReport &BR) override; // FIXME: Scan the map once in the visitor's constructor and do a direct // lookup by region. @@ -278,10 +278,8 @@ const MemRegion *getContainerObjRegion(ProgramStateRef State, SymbolRef Sym) { } // end namespace ento } // end namespace clang -std::shared_ptr -InnerPointerChecker::InnerPointerBRVisitor::VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &) { +PathDiagnosticPieceRef InnerPointerChecker::InnerPointerBRVisitor::VisitNode( + const ExplodedNode *N, BugReporterContext &BRC, BugReport &) { if (!isSymbolTracked(N->getState(), PtrToBuf) || isSymbolTracked(N->getFirstPred()->getState(), PtrToBuf)) return nullptr; diff --git a/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp index 6927ba39c0a78..b2daa820ffa22 100644 --- a/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp @@ -120,12 +120,12 @@ class NonLocalizedStringBRVisitor final : public BugReporterVisitor { public: NonLocalizedStringBRVisitor(const MemRegion *NonLocalizedString) : NonLocalizedString(NonLocalizedString), Satisfied(false) { - assert(NonLocalizedString); + assert(NonLocalizedString); } - std::shared_ptr VisitNode(const ExplodedNode *Succ, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *Succ, + BugReporterContext &BRC, + BugReport &BR) override; void Profile(llvm::FoldingSetNodeID &ID) const override { ID.Add(NonLocalizedString); @@ -999,7 +999,7 @@ void NonLocalizedStringChecker::checkPostStmt(const ObjCStringLiteral *SL, setNonLocalizedState(sv, C); } -std::shared_ptr +PathDiagnosticPieceRef NonLocalizedStringBRVisitor::VisitNode(const ExplodedNode *Succ, BugReporterContext &BRC, BugReport &BR) { if (Satisfied) diff --git a/clang/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.cpp b/clang/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.cpp index b250d3f8795e7..b40ddacca5924 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.cpp @@ -84,10 +84,8 @@ void MPIBugReporter::reportUnmatchedWait( BReporter.emitReport(std::move(Report)); } -std::shared_ptr -MPIBugReporter::RequestNodeVisitor::VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) { +PathDiagnosticPieceRef MPIBugReporter::RequestNodeVisitor::VisitNode( + const ExplodedNode *N, BugReporterContext &BRC, BugReport &BR) { if (IsNodeFound) return nullptr; diff --git a/clang/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.h b/clang/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.h index 6fbc30288655c..66da02f76156e 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.h +++ b/clang/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.h @@ -89,9 +89,9 @@ class MPIBugReporter { ID.AddPointer(RequestRegion); } - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + BugReport &BR) override; private: const MemRegion *const RequestRegion; diff --git a/clang/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp index 32ba9bc8e2ef5..c3c5701328fcc 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp @@ -139,9 +139,9 @@ class MacOSKeychainAPIChecker : public Checker, ID.AddPointer(Sym); } - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + BugReport &BR) override; }; }; } @@ -613,7 +613,7 @@ ProgramStateRef MacOSKeychainAPIChecker::checkPointerEscape( return State; } -std::shared_ptr +PathDiagnosticPieceRef MacOSKeychainAPIChecker::SecKeychainBugVisitor::VisitNode( const ExplodedNode *N, BugReporterContext &BRC, BugReport &BR) { const AllocationState *AS = N->getState()->get(Sym); diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index b95481ae1e0ba..6b99dff7bbae8 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -516,19 +516,19 @@ class MallocChecker : public Checker allocated, it must be the realloc return value // check. If we have to handle more cases here, it might be cleaner just // to track this extra bit in the state itself. - return ((!Stmt || !isa(Stmt)) && - (S && (S->isAllocated() || S->isAllocatedOfSizeZero())) && - (SPrev && !(SPrev->isAllocated() || - SPrev->isAllocatedOfSizeZero()))); + return ( + (!Stmt || !isa(Stmt)) && + (S && (S->isAllocated() || S->isAllocatedOfSizeZero())) && + (SPrev && !(SPrev->isAllocated() || SPrev->isAllocatedOfSizeZero()))); } - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + BugReport &BR) override; - std::shared_ptr - getEndPath(BugReporterContext &BRC, const ExplodedNode *EndPathNode, - BugReport &BR) override { + PathDiagnosticPieceRef getEndPath(BugReporterContext &BRC, + const ExplodedNode *EndPathNode, + BugReport &BR) override { if (!IsLeak) return nullptr; @@ -2906,7 +2906,7 @@ static bool isReferenceCountingPointerDestructor(const CXXDestructorDecl *DD) { return false; } -std::shared_ptr MallocChecker::MallocBugVisitor::VisitNode( +PathDiagnosticPieceRef MallocChecker::MallocBugVisitor::VisitNode( const ExplodedNode *N, BugReporterContext &BRC, BugReport &BR) { ProgramStateRef state = N->getState(); diff --git a/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp index d8a9af78536a0..47fea7dcf5c42 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp @@ -169,9 +169,9 @@ class MoveChecker // in the first place. } - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + BugReport &BR) override; private: const MoveChecker &Chk; @@ -270,9 +270,8 @@ static const MemRegion *unwrapRValueReferenceIndirection(const MemRegion *MR) { return MR; } -std::shared_ptr -MoveChecker::MovedBugVisitor::VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, BugReport &BR) { +PathDiagnosticPieceRef MoveChecker::MovedBugVisitor::VisitNode( + const ExplodedNode *N, BugReporterContext &BRC, BugReport &BR) { // We need only the last move of the reported object's region. // The visitor walks the ExplodedGraph backwards. if (Found) diff --git a/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp index b7bf9f3db3ff6..ce2143137acba 100644 --- a/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp @@ -137,9 +137,9 @@ class NullabilityChecker ID.AddPointer(Region); } - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + BugReport &BR) override; private: // The tracked region. @@ -290,10 +290,8 @@ NullabilityChecker::getTrackRegion(SVal Val, bool CheckSuperRegion) const { return dyn_cast(Region); } -std::shared_ptr -NullabilityChecker::NullabilityBugVisitor::VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) { +PathDiagnosticPieceRef NullabilityChecker::NullabilityBugVisitor::VisitNode( + const ExplodedNode *N, BugReporterContext &BRC, BugReport &BR) { ProgramStateRef State = N->getState(); ProgramStateRef StatePrev = N->getFirstPred()->getState(); diff --git a/clang/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp index f435f00c08e79..dc2cb2ba9d556 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp @@ -67,12 +67,11 @@ class SuperDeallocBRVisitor final : public BugReporterVisitor { public: SuperDeallocBRVisitor(SymbolRef ReceiverSymbol) - : ReceiverSymbol(ReceiverSymbol), - Satisfied(false) {} + : ReceiverSymbol(ReceiverSymbol), Satisfied(false) {} - std::shared_ptr VisitNode(const ExplodedNode *Succ, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *Succ, + BugReporterContext &BRC, + BugReport &BR) override; void Profile(llvm::FoldingSetNodeID &ID) const override { ID.Add(ReceiverSymbol); @@ -243,7 +242,7 @@ ObjCSuperDeallocChecker::isSuperDeallocMessage(const ObjCMethodCall &M) const { return M.getSelector() == SELdealloc; } -std::shared_ptr +PathDiagnosticPieceRef SuperDeallocBRVisitor::VisitNode(const ExplodedNode *Succ, BugReporterContext &BRC, BugReport &) { if (Satisfied) diff --git a/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.cpp b/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.cpp index 927e9ae443609..69d8cda491313 100644 --- a/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.cpp @@ -325,22 +325,22 @@ class RefCountReportVisitor : public BugReporterVisitor { ID.AddPointer(Sym); } - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + BugReport &BR) override; - std::shared_ptr getEndPath(BugReporterContext &BRC, - const ExplodedNode *N, - BugReport &BR) override; + PathDiagnosticPieceRef getEndPath(BugReporterContext &BRC, + const ExplodedNode *N, + BugReport &BR) override; }; class RefLeakReportVisitor : public RefCountReportVisitor { public: RefLeakReportVisitor(SymbolRef sym) : RefCountReportVisitor(sym) {} - std::shared_ptr getEndPath(BugReporterContext &BRC, - const ExplodedNode *N, - BugReport &BR) override; + PathDiagnosticPieceRef getEndPath(BugReporterContext &BRC, + const ExplodedNode *N, + BugReport &BR) override; }; } // end namespace retaincountchecker @@ -448,9 +448,9 @@ annotateStartParameter(const ExplodedNode *N, SymbolRef Sym, return std::make_shared(L, os.str()); } -std::shared_ptr -RefCountReportVisitor::VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, BugReport &BR) { +PathDiagnosticPieceRef RefCountReportVisitor::VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + BugReport &BR) { const auto &BT = static_cast(BR.getBugType()); const auto *Checker = @@ -709,21 +709,20 @@ static AllocationInfo GetAllocationSite(ProgramStateManager &StateMgr, LeakContext) FirstBinding = nullptr; - return AllocationInfo(AllocationNodeInCurrentOrParentContext, - FirstBinding, + return AllocationInfo(AllocationNodeInCurrentOrParentContext, FirstBinding, InterestingMethodContext); } -std::shared_ptr +PathDiagnosticPieceRef RefCountReportVisitor::getEndPath(BugReporterContext &BRC, - const ExplodedNode *EndN, BugReport &BR) { + const ExplodedNode *EndN, BugReport &BR) { BR.markInteresting(Sym); return BugReporterVisitor::getDefaultEndPath(BRC, EndN, BR); } -std::shared_ptr +PathDiagnosticPieceRef RefLeakReportVisitor::getEndPath(BugReporterContext &BRC, - const ExplodedNode *EndN, BugReport &BR) { + const ExplodedNode *EndN, BugReport &BR) { // Tell the BugReporterContext to report cases when the tracked symbol is // assigned to different variables, etc. diff --git a/clang/lib/StaticAnalyzer/Checkers/Taint.cpp b/clang/lib/StaticAnalyzer/Checkers/Taint.cpp index bc120949ee4f1..cd8cab8f52e40 100644 --- a/clang/lib/StaticAnalyzer/Checkers/Taint.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/Taint.cpp @@ -204,9 +204,9 @@ bool taint::isTainted(ProgramStateRef State, SymbolRef Sym, TaintTagType Kind) { return false; } -std::shared_ptr -TaintBugVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &BRC, - BugReport &BR) { +PathDiagnosticPieceRef TaintBugVisitor::VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + BugReport &BR) { // Find the ExplodedNode where the taint was first introduced if (!isTainted(N->getState(), V) || diff --git a/clang/lib/StaticAnalyzer/Checkers/Taint.h b/clang/lib/StaticAnalyzer/Checkers/Taint.h index 72cf6a79d52c4..2c3b001b5f0a0 100644 --- a/clang/lib/StaticAnalyzer/Checkers/Taint.h +++ b/clang/lib/StaticAnalyzer/Checkers/Taint.h @@ -89,9 +89,9 @@ class TaintBugVisitor final : public BugReporterVisitor { TaintBugVisitor(const SVal V) : V(V) {} void Profile(llvm::FoldingSetNodeID &ID) const override { ID.Add(V); } - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + BugReport &BR) override; }; } // namespace taint diff --git a/clang/lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp index 7a33845a6a266..e95c86e23e021 100644 --- a/clang/lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp @@ -69,9 +69,9 @@ class DivisionBRVisitor : public BugReporterVisitor { ID.Add(SFC); } - std::shared_ptr VisitNode(const ExplodedNode *Succ, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *Succ, + BugReporterContext &BRC, + BugReport &BR) override; }; class TestAfterDivZeroChecker @@ -92,9 +92,9 @@ class TestAfterDivZeroChecker REGISTER_SET_WITH_PROGRAMSTATE(DivZeroMap, ZeroState) -std::shared_ptr -DivisionBRVisitor::VisitNode(const ExplodedNode *Succ, - BugReporterContext &BRC, BugReport &BR) { +PathDiagnosticPieceRef DivisionBRVisitor::VisitNode(const ExplodedNode *Succ, + BugReporterContext &BRC, + BugReport &BR) { if (Satisfied) return nullptr; diff --git a/clang/lib/StaticAnalyzer/Checkers/ValistChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ValistChecker.cpp index 13ad3d98e8ebb..0ae556c3669f3 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ValistChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ValistChecker.cpp @@ -77,20 +77,21 @@ class ValistChecker : public Checker, ID.AddPointer(&X); ID.AddPointer(Reg); } - std::shared_ptr - getEndPath(BugReporterContext &BRC, const ExplodedNode *EndPathNode, - BugReport &BR) override { + PathDiagnosticPieceRef getEndPath(BugReporterContext &BRC, + const ExplodedNode *EndPathNode, + BugReport &BR) override { if (!IsLeak) return nullptr; PathDiagnosticLocation L = PathDiagnosticLocation::createEndOfPath( EndPathNode, BRC.getSourceManager()); // Do not add the statement itself as a range in case of leak. - return std::make_shared(L, BR.getDescription(), false); + return std::make_shared(L, BR.getDescription(), + false); } - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + BugReport &BR) override; private: const MemRegion *Reg; @@ -373,9 +374,8 @@ void ValistChecker::checkVAListEndCall(const CallEvent &Call, C.addTransition(State); } -std::shared_ptr ValistChecker::ValistBugVisitor::VisitNode( - const ExplodedNode *N, BugReporterContext &BRC, - BugReport &) { +PathDiagnosticPieceRef ValistChecker::ValistBugVisitor::VisitNode( + const ExplodedNode *N, BugReporterContext &BRC, BugReport &) { ProgramStateRef State = N->getState(); ProgramStateRef StatePrev = N->getFirstPred()->getState(); diff --git a/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp index 762c9c1c8d7aa..0d76a098d4088 100644 --- a/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp @@ -70,9 +70,9 @@ class VirtualCallChecker ID.AddPointer(ObjectRegion); } - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + BugReport &BR) override; }; }; } // end namespace @@ -80,10 +80,8 @@ class VirtualCallChecker // GDM (generic data map) to the memregion of this for the ctor and dtor. REGISTER_MAP_WITH_PROGRAMSTATE(CtorDtorMap, const MemRegion *, ObjectState) -std::shared_ptr -VirtualCallChecker::VirtualBugVisitor::VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &) { +PathDiagnosticPieceRef VirtualCallChecker::VirtualBugVisitor::VisitNode( + const ExplodedNode *N, BugReporterContext &BRC, BugReport &) { // We need the last ctor/dtor which call the virtual function. // The visitor walks the ExplodedGraph backwards. if (Found) diff --git a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp index 9058fe7a13dd2..a9e955b9514ee 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp @@ -1881,12 +1881,11 @@ static void dropFunctionEntryEdge(PathPieces &Path, LocationContextMap &LCM, Path.pop_front(); } -using VisitorsDiagnosticsTy = llvm::DenseMap>>; +using VisitorsDiagnosticsTy = + llvm::DenseMap>; /// Populate executes lines with lines containing at least one diagnostics. -static void updateExecutedLinesWithDiagnosticPieces( - PathDiagnostic &PD) { +static void updateExecutedLinesWithDiagnosticPieces(PathDiagnostic &PD) { PathPieces path = PD.path.flatten(/*ShouldFlattenMacros=*/true); FilesToLineNumsMap &ExecutedLines = PD.getExecutedLines(); @@ -1930,7 +1929,7 @@ static std::unique_ptr generatePathDiagnosticForConsumer( if (GenerateDiagnostics) { auto EndNotes = VisitorsDiagnostics.find(ErrorNode); - std::shared_ptr LastPiece; + PathDiagnosticPieceRef LastPiece; if (EndNotes != VisitorsDiagnostics.end()) { assert(!EndNotes->second.empty()); LastPiece = EndNotes->second[0]; @@ -1957,7 +1956,7 @@ static std::unique_ptr generatePathDiagnosticForConsumer( std::set DeduplicationSet; // Add pieces from custom visitors. - for (const auto &Note : VisitorNotes->second) { + for (const PathDiagnosticPieceRef &Note : VisitorNotes->second) { llvm::FoldingSetNodeID ID; Note->Profile(ID); auto P = DeduplicationSet.insert(ID); @@ -2430,11 +2429,10 @@ BugPathInfo *BugPathGetter::getNextBugPath() { /// object and collapses PathDiagosticPieces that are expanded by macros. static void CompactMacroExpandedPieces(PathPieces &path, const SourceManager& SM) { - using MacroStackTy = - std::vector< - std::pair, SourceLocation>>; + using MacroStackTy = std::vector< + std::pair, SourceLocation>>; - using PiecesTy = std::vector>; + using PiecesTy = std::vector; MacroStackTy MacroStack; PiecesTy Pieces; @@ -2546,7 +2544,7 @@ generateVisitorsDiagnostics(BugReport *R, const ExplodedNode *ErrorNode, const ExplodedNode *Pred = NextNode->getFirstPred(); if (!Pred) { - std::shared_ptr LastPiece; + PathDiagnosticPieceRef LastPiece; for (auto &V : visitors) { V->finalizeVisitor(BRC, ErrorNode, *R); diff --git a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp index 731545022cab0..a71ac21fe693a 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp @@ -255,20 +255,19 @@ static bool wasRegionOfInterestModifiedAt(const SubRegion *RegionOfInterest, // Implementation of BugReporterVisitor. //===----------------------------------------------------------------------===// -std::shared_ptr -BugReporterVisitor::getEndPath(BugReporterContext &, - const ExplodedNode *, BugReport &) { +PathDiagnosticPieceRef BugReporterVisitor::getEndPath(BugReporterContext &, + const ExplodedNode *, + BugReport &) { return nullptr; } -void -BugReporterVisitor::finalizeVisitor(BugReporterContext &, - const ExplodedNode *, BugReport &) {} +void BugReporterVisitor::finalizeVisitor(BugReporterContext &, + const ExplodedNode *, BugReport &) {} -std::shared_ptr BugReporterVisitor::getDefaultEndPath( +PathDiagnosticPieceRef BugReporterVisitor::getDefaultEndPath( BugReporterContext &BRC, const ExplodedNode *EndPathNode, BugReport &BR) { - PathDiagnosticLocation L = - PathDiagnosticLocation::createEndOfPath(EndPathNode,BRC.getSourceManager()); + PathDiagnosticLocation L = PathDiagnosticLocation::createEndOfPath( + EndPathNode, BRC.getSourceManager()); const auto &Ranges = BR.getRanges(); @@ -333,9 +332,9 @@ class NoStoreFuncVisitor final : public BugReporterVisitor { return static_cast(&Tag); } - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BR, - BugReport &R) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BR, + BugReport &R) override; private: /// Attempts to find the region of interest in a given record decl, @@ -368,7 +367,7 @@ class NoStoreFuncVisitor final : public BugReporterVisitor { /// either emit a note or suppress the report enirely. /// \return Diagnostics piece for region not modified in the current function, /// if it decides to emit one. - std::shared_ptr + PathDiagnosticPieceRef maybeEmitNote(BugReport &R, const CallEvent &Call, const ExplodedNode *N, const RegionVector &FieldChain, const MemRegion *MatchedRegion, StringRef FirstElement, bool FirstIsReferenceType, @@ -501,9 +500,9 @@ NoStoreFuncVisitor::findRegionOfInterestInRecord( return None; } -std::shared_ptr -NoStoreFuncVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &BR, - BugReport &R) { +PathDiagnosticPieceRef NoStoreFuncVisitor::VisitNode(const ExplodedNode *N, + BugReporterContext &BR, + BugReport &R) { const LocationContext *Ctx = N->getLocationContext(); const StackFrameContext *SCtx = Ctx->getStackFrame(); @@ -611,7 +610,7 @@ void NoStoreFuncVisitor::findModifyingFrames(const ExplodedNode *N) { } while (N); } -std::shared_ptr NoStoreFuncVisitor::maybeEmitNote( +PathDiagnosticPieceRef NoStoreFuncVisitor::maybeEmitNote( BugReport &R, const CallEvent &Call, const ExplodedNode *N, const RegionVector &FieldChain, const MemRegion *MatchedRegion, StringRef FirstElement, bool FirstIsReferenceType, @@ -752,13 +751,12 @@ class MacroNullReturnSuppressionVisitor final : public BugReporterVisitor { bool WasModified = false; public: - MacroNullReturnSuppressionVisitor(const SubRegion *R, - const SVal V) : RegionOfInterest(R), - ValueAtDereference(V) {} + MacroNullReturnSuppressionVisitor(const SubRegion *R, const SVal V) + : RegionOfInterest(R), ValueAtDereference(V) {} - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override { + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + BugReport &BR) override { if (WasModified) return nullptr; @@ -956,9 +954,9 @@ class ReturnVisitor : public BugReporterVisitor { Options)); } - std::shared_ptr - visitNodeInitial(const ExplodedNode *N, - BugReporterContext &BRC, BugReport &BR) { + PathDiagnosticPieceRef visitNodeInitial(const ExplodedNode *N, + BugReporterContext &BRC, + BugReport &BR) { // Only print a message at the interesting return statement. if (N->getLocationContext() != StackFrame) return nullptr; @@ -1059,9 +1057,9 @@ class ReturnVisitor : public BugReporterVisitor { return std::make_shared(L, Out.str()); } - std::shared_ptr - visitNodeMaybeUnsuppress(const ExplodedNode *N, - BugReporterContext &BRC, BugReport &BR) { + PathDiagnosticPieceRef visitNodeMaybeUnsuppress(const ExplodedNode *N, + BugReporterContext &BRC, + BugReport &BR) { #ifndef NDEBUG assert(Options.ShouldAvoidSuppressingNullArgumentPaths); #endif @@ -1108,9 +1106,9 @@ class ReturnVisitor : public BugReporterVisitor { return nullptr; } - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override { + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + BugReport &BR) override { switch (Mode) { case Initial: return visitNodeInitial(N, BRC, BR); @@ -1291,7 +1289,7 @@ static void showBRDefaultDiagnostics(llvm::raw_svector_ostream& os, } } -std::shared_ptr +PathDiagnosticPieceRef FindLastStoreBRVisitor::VisitNode(const ExplodedNode *Succ, BugReporterContext &BRC, BugReport &BR) { if (Satisfied) @@ -1467,7 +1465,7 @@ bool TrackConstraintBRVisitor::isUnderconstrained(const ExplodedNode *N) const { return (bool)N->getState()->assume(Constraint, !Assumption); } -std::shared_ptr +PathDiagnosticPieceRef TrackConstraintBRVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &BRC, BugReport &) { const ExplodedNode *PrevN = N->getFirstPred(); @@ -1547,10 +1545,8 @@ const char *SuppressInlineDefensiveChecksVisitor::getTag() { return "IDCVisitor"; } -std::shared_ptr -SuppressInlineDefensiveChecksVisitor::VisitNode(const ExplodedNode *Succ, - BugReporterContext &BRC, - BugReport &BR) { +PathDiagnosticPieceRef SuppressInlineDefensiveChecksVisitor::VisitNode( + const ExplodedNode *Succ, BugReporterContext &BRC, BugReport &BR) { const ExplodedNode *Pred = Succ->getFirstPred(); if (IsSatisfied) return nullptr; @@ -1649,9 +1645,9 @@ class TrackControlDependencyCondBRVisitor final : public BugReporterVisitor { ID.AddPointer(&x); } - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + BugReport &BR) override; }; } // end of anonymous namespace @@ -1687,10 +1683,8 @@ constructDebugPieceForTrackedCondition(const Expr *Cond, (Twine() + "Tracking condition '" + ConditionText + "'").str()); } -std::shared_ptr -TrackControlDependencyCondBRVisitor::VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) { +PathDiagnosticPieceRef TrackControlDependencyCondBRVisitor::VisitNode( + const ExplodedNode *N, BugReporterContext &BRC, BugReport &BR) { // We can only reason about control dependencies within the same stack frame. if (Origin->getStackFrame() != N->getStackFrame()) return nullptr; @@ -1978,9 +1972,9 @@ const Expr *NilReceiverBRVisitor::getNilReceiver(const Stmt *S, return nullptr; } -std::shared_ptr -NilReceiverBRVisitor::VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, BugReport &BR) { +PathDiagnosticPieceRef NilReceiverBRVisitor::VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + BugReport &BR) { Optional P = N->getLocationAs(); if (!P) return nullptr; @@ -2059,13 +2053,11 @@ void FindLastStoreBRVisitor::registerStatementVarDecls(BugReport &BR, /// Return the tag associated with this visitor. This tag will be used /// to make all PathDiagnosticPieces created by this visitor. -const char *ConditionBRVisitor::getTag() { - return "ConditionBRVisitor"; -} +const char *ConditionBRVisitor::getTag() { return "ConditionBRVisitor"; } -std::shared_ptr -ConditionBRVisitor::VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, BugReport &BR) { +PathDiagnosticPieceRef ConditionBRVisitor::VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + BugReport &BR) { auto piece = VisitNodeImpl(N, BRC, BR); if (piece) { piece->setTag(getTag()); @@ -2075,7 +2067,7 @@ ConditionBRVisitor::VisitNode(const ExplodedNode *N, return piece; } -std::shared_ptr +PathDiagnosticPieceRef ConditionBRVisitor::VisitNodeImpl(const ExplodedNode *N, BugReporterContext &BRC, BugReport &BR) { ProgramPoint ProgPoint = N->getLocation(); @@ -2113,7 +2105,7 @@ ConditionBRVisitor::VisitNodeImpl(const ExplodedNode *N, return nullptr; } -std::shared_ptr ConditionBRVisitor::VisitTerminator( +PathDiagnosticPieceRef ConditionBRVisitor::VisitTerminator( const Stmt *Term, const ExplodedNode *N, const CFGBlock *srcBlk, const CFGBlock *dstBlk, BugReport &R, BugReporterContext &BRC) { const Expr *Cond = nullptr; @@ -2170,7 +2162,7 @@ std::shared_ptr ConditionBRVisitor::VisitTerminator( return VisitTrueTest(Cond, BRC, R, N, TookTrue); } -std::shared_ptr +PathDiagnosticPieceRef ConditionBRVisitor::VisitTrueTest(const Expr *Cond, BugReporterContext &BRC, BugReport &R, const ExplodedNode *N, bool TookTrue) { @@ -2326,7 +2318,7 @@ bool ConditionBRVisitor::patternMatch(const Expr *Ex, return false; } -std::shared_ptr ConditionBRVisitor::VisitTrueTest( +PathDiagnosticPieceRef ConditionBRVisitor::VisitTrueTest( const Expr *Cond, const BinaryOperator *BExpr, BugReporterContext &BRC, BugReport &R, const ExplodedNode *N, bool TookTrue, bool IsAssuming) { bool shouldInvert = false; @@ -2441,7 +2433,7 @@ std::shared_ptr ConditionBRVisitor::VisitTrueTest( return event; } -std::shared_ptr ConditionBRVisitor::VisitConditionVariable( +PathDiagnosticPieceRef ConditionBRVisitor::VisitConditionVariable( StringRef LhsString, const Expr *CondVarExpr, BugReporterContext &BRC, BugReport &report, const ExplodedNode *N, bool TookTrue) { // FIXME: If there's already a constraint tracker for this variable, @@ -2471,7 +2463,7 @@ std::shared_ptr ConditionBRVisitor::VisitConditionVariable( return event; } -std::shared_ptr ConditionBRVisitor::VisitTrueTest( +PathDiagnosticPieceRef ConditionBRVisitor::VisitTrueTest( const Expr *Cond, const DeclRefExpr *DRE, BugReporterContext &BRC, BugReport &report, const ExplodedNode *N, bool TookTrue, bool IsAssuming) { const auto *VD = dyn_cast(DRE->getDecl()); @@ -2509,7 +2501,7 @@ std::shared_ptr ConditionBRVisitor::VisitTrueTest( return std::move(event); } -std::shared_ptr ConditionBRVisitor::VisitTrueTest( +PathDiagnosticPieceRef ConditionBRVisitor::VisitTrueTest( const Expr *Cond, const MemberExpr *ME, BugReporterContext &BRC, BugReport &report, const ExplodedNode *N, bool TookTrue, bool IsAssuming) { SmallString<256> Buf; @@ -2677,9 +2669,9 @@ void LikelyFalsePositiveSuppressionBRVisitor::finalizeVisitor( // Implementation of UndefOrNullArgVisitor. //===----------------------------------------------------------------------===// -std::shared_ptr -UndefOrNullArgVisitor::VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, BugReport &BR) { +PathDiagnosticPieceRef UndefOrNullArgVisitor::VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + BugReport &BR) { ProgramStateRef State = N->getState(); ProgramPoint ProgLoc = N->getLocation(); @@ -2770,10 +2762,9 @@ void FalsePositiveRefutationBRVisitor::finalizeVisitor( BR.markInvalid("Infeasible constraints", EndPathNode->getLocationContext()); } -std::shared_ptr +PathDiagnosticPieceRef FalsePositiveRefutationBRVisitor::VisitNode(const ExplodedNode *N, - BugReporterContext &, - BugReport &) { + BugReporterContext &, BugReport &) { // Collect new constraints const ConstraintRangeTy &NewCs = N->getState()->get(); ConstraintRangeTy::Factory &CF = @@ -2807,9 +2798,9 @@ void TagVisitor::Profile(llvm::FoldingSetNodeID &ID) const { ID.AddPointer(&Tag); } -std::shared_ptr -TagVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &BRC, - BugReport &R) { +PathDiagnosticPieceRef TagVisitor::VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + BugReport &R) { ProgramPoint PP = N->getLocation(); const NoteTag *T = dyn_cast_or_null(PP.getTag()); if (!T) diff --git a/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp b/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp index a641695722500..39500a20fde3c 100644 --- a/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp +++ b/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp @@ -657,16 +657,14 @@ void HTMLDiagnostics::RewriteFile(Rewriter &R, // Process the path. // Maintain the counts of extra note pieces separately. unsigned TotalPieces = path.size(); - unsigned TotalNotePieces = - std::count_if(path.begin(), path.end(), - [](const std::shared_ptr &p) { - return isa(*p); - }); - unsigned PopUpPieceCount = - std::count_if(path.begin(), path.end(), - [](const std::shared_ptr &p) { - return isa(*p); - }); + unsigned TotalNotePieces = std::count_if( + path.begin(), path.end(), [](const PathDiagnosticPieceRef &p) { + return isa(*p); + }); + unsigned PopUpPieceCount = std::count_if( + path.begin(), path.end(), [](const PathDiagnosticPieceRef &p) { + return isa(*p); + }); unsigned TotalRegularPieces = TotalPieces - TotalNotePieces - PopUpPieceCount; unsigned NumRegularPieces = TotalRegularPieces; diff --git a/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp b/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp index 15b8e0c41c3fb..8956752a9b0da 100644 --- a/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp +++ b/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp @@ -479,16 +479,16 @@ static void printBugPath(llvm::raw_ostream &o, const FIDMap& FM, const cross_tu::CrossTranslationUnitContext &CTU, const PathPieces &Path) { PlistPrinter Printer(FM, AnOpts, PP, CTU); - assert(std::is_partitioned( - Path.begin(), Path.end(), - [](const std::shared_ptr &E) - { return E->getKind() == PathDiagnosticPiece::Note; }) && + assert(std::is_partitioned(Path.begin(), Path.end(), + [](const PathDiagnosticPieceRef &E) { + return E->getKind() == PathDiagnosticPiece::Note; + }) && "PathDiagnostic is not partitioned so that notes precede the rest"); PathPieces::const_iterator FirstNonNote = std::partition_point( - Path.begin(), Path.end(), - [](const std::shared_ptr &E) - { return E->getKind() == PathDiagnosticPiece::Note; }); + Path.begin(), Path.end(), [](const PathDiagnosticPieceRef &E) { + return E->getKind() == PathDiagnosticPiece::Note; + }); PathPieces::const_iterator I = Path.begin(); From 03ba03c0ded304dedc331064b72e68dbbb426c20 Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Tue, 13 Aug 2019 18:48:08 +0000 Subject: [PATCH 084/181] [analyzer][NFC] Refactoring BugReporter.cpp P4.: If it can be const, make it const When I'm new to a file/codebase, I personally find C++'s strong static type system to be a great aid. BugReporter.cpp is still painful to read however: function calls are made with mile long parameter lists, seemingly all of them taken with a non-const reference/pointer. This patch fixes nothing but this: make a few things const, and hammer it until it compiles. Differential Revision: https://reviews.llvm.org/D65382 llvm-svn: 368735 (cherry picked from commit fc76d8551f54e0a8b99306741d6d818b90334d6a) --- .../clang/Analysis/AnalysisDeclContext.h | 2 +- .../Core/BugReporter/BugReporter.h | 28 ++-- .../Core/BugReporter/BugReporterVisitors.h | 3 +- .../Core/BugReporter/PathDiagnostic.h | 1 - .../Core/PathSensitive/CheckerContext.h | 2 +- .../Core/PathSensitive/ExplodedGraph.h | 6 +- .../Core/PathSensitive/ProgramState.h | 4 + .../StaticAnalyzer/Checkers/MallocChecker.cpp | 2 +- .../Checkers/ObjCUnusedIVarsChecker.cpp | 4 +- .../RetainCountDiagnostics.cpp | 2 +- .../Checkers/UnreachableCodeChecker.cpp | 2 +- clang/lib/StaticAnalyzer/Core/BugReporter.cpp | 142 +++++++++--------- .../Core/BugReporterVisitors.cpp | 6 +- clang/lib/StaticAnalyzer/Core/CallEvent.cpp | 2 +- .../lib/StaticAnalyzer/Core/ExplodedGraph.cpp | 2 +- 15 files changed, 110 insertions(+), 98 deletions(-) diff --git a/clang/include/clang/Analysis/AnalysisDeclContext.h b/clang/include/clang/Analysis/AnalysisDeclContext.h index d5445d3ce5b6a..9faa78cde89c2 100644 --- a/clang/include/clang/Analysis/AnalysisDeclContext.h +++ b/clang/include/clang/Analysis/AnalysisDeclContext.h @@ -257,7 +257,7 @@ class LocationContext : public llvm::FoldingSetNode { return getAnalysisDeclContext()->getAnalysis(); } - ParentMap &getParentMap() const { + const ParentMap &getParentMap() const { return getAnalysisDeclContext()->getParentMap(); } diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h index 9a16f23461ceb..f73fd29805314 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h @@ -217,10 +217,10 @@ class BugReport : public llvm::ilist_node { void markInteresting(SVal V); void markInteresting(const LocationContext *LC); - bool isInteresting(SymbolRef sym); - bool isInteresting(const MemRegion *R); - bool isInteresting(SVal V); - bool isInteresting(const LocationContext *LC); + bool isInteresting(SymbolRef sym) const; + bool isInteresting(const MemRegion *R) const; + bool isInteresting(SVal V) const; + bool isInteresting(const LocationContext *LC) const; /// Returns whether or not this report should be considered valid. /// @@ -469,9 +469,9 @@ class BugReporter { ASTContext &getContext() { return D.getASTContext(); } - SourceManager &getSourceManager() { return D.getSourceManager(); } + const SourceManager &getSourceManager() { return D.getSourceManager(); } - AnalyzerOptions &getAnalyzerOptions() { return D.getAnalyzerOptions(); } + const AnalyzerOptions &getAnalyzerOptions() { return D.getAnalyzerOptions(); } virtual std::unique_ptr generatePathDiagnostics(ArrayRef consumers, @@ -519,12 +519,14 @@ class GRBugReporter : public BugReporter { /// getGraph - Get the exploded graph created by the analysis engine /// for the analyzed method or function. - ExplodedGraph &getGraph(); + const ExplodedGraph &getGraph() const; /// getStateManager - Return the state manager used by the analysis /// engine. ProgramStateManager &getStateManager(); + ProgramStateManager &getStateManager() const; + /// \p bugReports A set of bug reports within a *single* equivalence class /// /// \return A mapping from consumers to the corresponding diagnostics. @@ -566,25 +568,25 @@ class BugReporterContext { GRBugReporter& getBugReporter() { return BR; } - ExplodedGraph &getGraph() { return BR.getGraph(); } + const ExplodedGraph &getGraph() const { return BR.getGraph(); } - ProgramStateManager& getStateManager() { + ProgramStateManager& getStateManager() const { return BR.getStateManager(); } - SValBuilder &getSValBuilder() { + SValBuilder &getSValBuilder() const { return getStateManager().getSValBuilder(); } - ASTContext &getASTContext() { + ASTContext &getASTContext() const { return BR.getContext(); } - SourceManager& getSourceManager() { + const SourceManager& getSourceManager() const { return BR.getSourceManager(); } - AnalyzerOptions &getAnalyzerOptions() { + const AnalyzerOptions &getAnalyzerOptions() const { return BR.getAnalyzerOptions(); } diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h index ecd5ed0ca3927..0e2346174a9ca 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h @@ -74,7 +74,8 @@ class BugReporterVisitor : public llvm::FoldingSetNode { /// NOTE that this function can be implemented on at most one used visitor, /// and otherwise it crahes at runtime. virtual PathDiagnosticPieceRef - getEndPath(BugReporterContext &BRC, const ExplodedNode *N, BugReport &BR); + getEndPath(BugReporterContext &BRC, const ExplodedNode *N, + BugReport &BR); virtual void Profile(llvm::FoldingSetNodeID &ID) const = 0; diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h index f98259560030b..7ccf569a68fc0 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h @@ -919,7 +919,6 @@ class PathDiagnostic : public llvm::FoldingSetNode { }; } // namespace ento - } // namespace clang #endif // LLVM_CLANG_STATICANALYZER_CORE_BUGREPORTER_PATHDIAGNOSTIC_H diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h index 981133e669775..1d2e554ec2735 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h @@ -103,7 +103,7 @@ class CheckerContext { return Eng.getBugReporter(); } - SourceManager &getSourceManager() { + const SourceManager &getSourceManager() { return getBugReporter().getSourceManager(); } diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h index 52ae92e70d3dd..864266e77eed1 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h @@ -153,7 +153,11 @@ class ExplodedNode : public llvm::FoldingSetNode { CFG &getCFG() const { return *getLocationContext()->getCFG(); } - ParentMap &getParentMap() const {return getLocationContext()->getParentMap();} + const CFGBlock *getCFGBlock() const; + + const ParentMap &getParentMap() const { + return getLocationContext()->getParentMap(); + } template T &getAnalysis() const { diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h index d38058f9af56d..07920790c80aa 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h @@ -507,6 +507,10 @@ class ProgramStateManager { return *svalBuilder; } + const SValBuilder &getSValBuilder() const { + return *svalBuilder; + } + SymbolManager &getSymbolManager() { return svalBuilder->getSymbolManager(); } diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index 6b99dff7bbae8..12ccf6774333b 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -1086,7 +1086,7 @@ void MallocChecker::processNewAllocation(const CXXNewExpr *NE, if (!isStandardNewDelete(NE->getOperatorNew(), C.getASTContext())) return; - ParentMap &PM = C.getLocationContext()->getParentMap(); + const ParentMap &PM = C.getLocationContext()->getParentMap(); if (!PM.isConsumedExpr(NE) && treatUnusedNewEscaped(NE)) return; diff --git a/clang/lib/StaticAnalyzer/Checkers/ObjCUnusedIVarsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ObjCUnusedIVarsChecker.cpp index 4b39a97c7e8d1..45137d6cd04bf 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ObjCUnusedIVarsChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ObjCUnusedIVarsChecker.cpp @@ -94,7 +94,7 @@ static void Scan(IvarUsageMap& M, const ObjCContainerDecl *D) { } static void Scan(IvarUsageMap &M, const DeclContext *C, const FileID FID, - SourceManager &SM) { + const SourceManager &SM) { for (const auto *I : C->decls()) if (const auto *FD = dyn_cast(I)) { SourceLocation L = FD->getBeginLoc(); @@ -148,7 +148,7 @@ static void checkObjCUnusedIvar(const ObjCImplementationDecl *D, // FIXME: In the future hopefully we can just use the lexical DeclContext // to go from the ObjCImplementationDecl to the lexically "nested" // C functions. - SourceManager &SM = BR.getSourceManager(); + const SourceManager &SM = BR.getSourceManager(); Scan(M, D->getDeclContext(), SM.getFileID(D->getLocation()), SM); // Find ivars that are unused. diff --git a/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.cpp b/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.cpp index 69d8cda491313..798c0a1c9c11d 100644 --- a/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.cpp @@ -736,7 +736,7 @@ RefLeakReportVisitor::getEndPath(BugReporterContext &BRC, const MemRegion* FirstBinding = AllocI.R; BR.markInteresting(AllocI.InterestingMethodContext); - SourceManager& SM = BRC.getSourceManager(); + const SourceManager& SM = BRC.getSourceManager(); // Compute an actual location for the leak. Sometimes a leak doesn't // occur at an actual statement (e.g., transition between blocks; end diff --git a/clang/lib/StaticAnalyzer/Checkers/UnreachableCodeChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/UnreachableCodeChecker.cpp index 0b0bf8465c9dd..65dd82675df9b 100644 --- a/clang/lib/StaticAnalyzer/Checkers/UnreachableCodeChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/UnreachableCodeChecker.cpp @@ -55,7 +55,7 @@ void UnreachableCodeChecker::checkEndAnalysis(ExplodedGraph &G, const Decl *D = nullptr; CFG *C = nullptr; - ParentMap *PM = nullptr; + const ParentMap *PM = nullptr; const LocationContext *LC = nullptr; // Iterate over ExplodedGraph for (ExplodedGraph::node_iterator I = G.nodes_begin(), E = G.nodes_end(); diff --git a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp index a9e955b9514ee..0cf6d124fe4c8 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp @@ -190,8 +190,8 @@ using LocationContextMap = /// Recursively scan through a path and prune out calls and macros pieces /// that aren't needed. Return true if afterwards the path contains /// "interesting stuff" which means it shouldn't be pruned from the parent path. -static bool removeUnneededCalls(PathPieces &pieces, BugReport *R, - LocationContextMap &LCM, +static bool removeUnneededCalls(PathPieces &pieces, const BugReport *R, + const LocationContextMap &LCM, bool IsInteresting = false) { bool containsSomethingInteresting = IsInteresting; const unsigned N = pieces.size(); @@ -208,7 +208,7 @@ static bool removeUnneededCalls(PathPieces &pieces, BugReport *R, // Check if the location context is interesting. assert(LCM.count(&call.path)); if (!removeUnneededCalls(call.path, R, LCM, - R->isInteresting(LCM[&call.path]))) + R->isInteresting(LCM.lookup(&call.path)))) continue; containsSomethingInteresting = true; @@ -353,33 +353,33 @@ namespace { class PathDiagnosticBuilder : public BugReporterContext { BugReport *R; - PathDiagnosticConsumer *PDC; + const PathDiagnosticConsumer *PDC; public: const LocationContext *LC; PathDiagnosticBuilder(GRBugReporter &br, BugReport *r, InterExplodedGraphMap &Backmap, - PathDiagnosticConsumer *pdc) + const PathDiagnosticConsumer *pdc) : BugReporterContext(br, Backmap), R(r), PDC(pdc), LC(r->getErrorNode()->getLocationContext()) {} - PathDiagnosticLocation ExecutionContinues(const ExplodedNode *N); + PathDiagnosticLocation ExecutionContinues(const ExplodedNode *N) const; PathDiagnosticLocation ExecutionContinues(llvm::raw_string_ostream &os, - const ExplodedNode *N); + const ExplodedNode *N) const; BugReport *getBugReport() { return R; } - Decl const &getCodeDecl() { return R->getErrorNode()->getCodeDecl(); } + const Decl &getCodeDecl() const { return R->getErrorNode()->getCodeDecl(); } - ParentMap& getParentMap() { return LC->getParentMap(); } + const ParentMap& getParentMap() const { return LC->getParentMap(); } - const Stmt *getParent(const Stmt *S) { + const Stmt *getParent(const Stmt *S) const { return getParentMap().getParent(S); } - PathDiagnosticLocation getEnclosingStmtLocation(const Stmt *S); + PathDiagnosticLocation getEnclosingStmtLocation(const Stmt *S) const; PathDiagnosticConsumer::PathGenerationScheme getGenerationScheme() const { return PDC ? PDC->getGenerationScheme() : PathDiagnosticConsumer::Minimal; @@ -393,7 +393,7 @@ class PathDiagnosticBuilder : public BugReporterContext { } // namespace PathDiagnosticLocation -PathDiagnosticBuilder::ExecutionContinues(const ExplodedNode *N) { +PathDiagnosticBuilder::ExecutionContinues(const ExplodedNode *N) const { if (const Stmt *S = PathDiagnosticLocation::getNextStmt(N)) return PathDiagnosticLocation(S, getSourceManager(), LC); @@ -403,7 +403,7 @@ PathDiagnosticBuilder::ExecutionContinues(const ExplodedNode *N) { PathDiagnosticLocation PathDiagnosticBuilder::ExecutionContinues(llvm::raw_string_ostream &os, - const ExplodedNode *N) { + const ExplodedNode *N) const { // Slow, but probably doesn't matter. if (os.str().empty()) os << ' '; @@ -454,8 +454,9 @@ static const Stmt *getEnclosingParent(const Stmt *S, const ParentMap &PM) { } static PathDiagnosticLocation -getEnclosingStmtLocation(const Stmt *S, SourceManager &SMgr, const ParentMap &P, - const LocationContext *LC, bool allowNestedContexts) { +getEnclosingStmtLocation(const Stmt *S, const SourceManager &SMgr, + const ParentMap &P, const LocationContext *LC, + bool allowNestedContexts) { if (!S) return {}; @@ -521,7 +522,7 @@ getEnclosingStmtLocation(const Stmt *S, SourceManager &SMgr, const ParentMap &P, } PathDiagnosticLocation -PathDiagnosticBuilder::getEnclosingStmtLocation(const Stmt *S) { +PathDiagnosticBuilder::getEnclosingStmtLocation(const Stmt *S) const { assert(S && "Null Stmt passed to getEnclosingStmtLocation"); return ::getEnclosingStmtLocation(S, getSourceManager(), getParentMap(), LC, /*allowNestedContexts=*/false); @@ -535,7 +536,7 @@ using StackDiagPair = using StackDiagVector = SmallVector; static void updateStackPiecesWithMessage(PathDiagnosticPiece &P, - StackDiagVector &CallStack) { + const StackDiagVector &CallStack) { // If the piece contains a special message, add it to all the call // pieces on the active stack. if (auto *ep = dyn_cast(&P)) { @@ -563,7 +564,7 @@ std::shared_ptr generateDiagForSwitchOP( const CFGBlock *Dst, const SourceManager &SM, const LocationContext *LC, - PathDiagnosticBuilder &PDB, + const PathDiagnosticBuilder &PDB, PathDiagnosticLocation &Start ) { // Figure out what case arm we took. @@ -622,7 +623,7 @@ std::shared_ptr generateDiagForSwitchOP( std::shared_ptr generateDiagForGotoOP( const Stmt *S, - PathDiagnosticBuilder &PDB, + const PathDiagnosticBuilder &PDB, PathDiagnosticLocation &Start) { std::string sbuf; llvm::raw_string_ostream os(sbuf); @@ -633,13 +634,10 @@ std::shared_ptr generateDiagForGotoOP( } std::shared_ptr generateDiagForBinaryOP( - const ExplodedNode *N, - const Stmt *T, - const CFGBlock *Src, - const CFGBlock *Dst, - const SourceManager &SM, - PathDiagnosticBuilder &PDB, - const LocationContext *LC) { + const ExplodedNode *N, const Stmt *T, const CFGBlock *Src, + const CFGBlock *Dst, const SourceManager &SM, + const PathDiagnosticBuilder &PDB, const LocationContext *LC) { + const auto *B = cast(T); std::string sbuf; llvm::raw_string_ostream os(sbuf); @@ -682,7 +680,7 @@ std::shared_ptr generateDiagForBinaryOP( void generateMinimalDiagForBlockEdge(const ExplodedNode *N, BlockEdge BE, const SourceManager &SM, - PathDiagnosticBuilder &PDB, + const PathDiagnosticBuilder &PDB, PathDiagnostic &PD) { const LocationContext *LC = N->getLocationContext(); const CFGBlock *Src = BE.getSrc(); @@ -916,7 +914,8 @@ static bool isJumpToFalseBranch(const BlockEdge *BE) { return (*(Src->succ_begin()+1) == BE->getDst()); } -static bool isContainedByStmt(ParentMap &PM, const Stmt *S, const Stmt *SubS) { +static bool isContainedByStmt(const ParentMap &PM, const Stmt *S, + const Stmt *SubS) { while (SubS) { if (SubS == S) return true; @@ -925,7 +924,7 @@ static bool isContainedByStmt(ParentMap &PM, const Stmt *S, const Stmt *SubS) { return false; } -static const Stmt *getStmtBeforeCond(ParentMap &PM, const Stmt *Term, +static const Stmt *getStmtBeforeCond(const ParentMap &PM, const Stmt *Term, const ExplodedNode *N) { while (N) { Optional SP = N->getLocation().getAs(); @@ -939,7 +938,7 @@ static const Stmt *getStmtBeforeCond(ParentMap &PM, const Stmt *Term, return nullptr; } -static bool isInLoopBody(ParentMap &PM, const Stmt *S, const Stmt *Term) { +static bool isInLoopBody(const ParentMap &PM, const Stmt *S, const Stmt *Term) { const Stmt *LoopBody = nullptr; switch (Term->getStmtClass()) { case Stmt::CXXForRangeStmtClass: { @@ -1007,15 +1006,14 @@ static const Stmt *getTerminatorCondition(const CFGBlock *B) { return S; } -static const char StrEnteringLoop[] = "Entering loop body"; -static const char StrLoopBodyZero[] = "Loop body executed 0 times"; -static const char StrLoopRangeEmpty[] = - "Loop body skipped when range is empty"; -static const char StrLoopCollectionEmpty[] = - "Loop body skipped when collection is empty"; +llvm::StringLiteral StrEnteringLoop = "Entering loop body"; +llvm::StringLiteral StrLoopBodyZero = "Loop body executed 0 times"; +llvm::StringLiteral StrLoopRangeEmpty = "Loop body skipped when range is empty"; +llvm::StringLiteral StrLoopCollectionEmpty = + "Loop body skipped when collection is empty"; static std::unique_ptr -findExecutedLines(SourceManager &SM, const ExplodedNode *N); +findExecutedLines(const SourceManager &SM, const ExplodedNode *N); /// Generate diagnostics for the node \p N, /// and write it into \p PD. @@ -1210,7 +1208,7 @@ static void generatePathDiagnosticsForNode(const ExplodedNode *N, } const CFGBlock *BSrc = BE->getSrc(); - ParentMap &PM = PDB.getParentMap(); + const ParentMap &PM = PDB.getParentMap(); if (const Stmt *Term = BSrc->getTerminatorStmt()) { // Are we jumping past the loop body without ever executing the @@ -1220,7 +1218,7 @@ static void generatePathDiagnosticsForNode(const ExplodedNode *N, bool IsInLoopBody = isInLoopBody(PM, getStmtBeforeCond(PM, TermCond, N), Term); - const char *str = nullptr; + StringRef str; if (isJumpToFalseBranch(&*BE)) { if (!IsInLoopBody) { @@ -1236,7 +1234,7 @@ static void generatePathDiagnosticsForNode(const ExplodedNode *N, str = StrEnteringLoop; } - if (str) { + if (!str.empty()) { PathDiagnosticLocation L(TermCond ? TermCond : Term, SM, PDB.LC); auto PE = std::make_shared(L, str); PE->setPrunable(true); @@ -1254,7 +1252,7 @@ static void generatePathDiagnosticsForNode(const ExplodedNode *N, } static std::unique_ptr -generateEmptyDiagnosticForReport(BugReport *R, SourceManager &SM) { +generateEmptyDiagnosticForReport(const BugReport *R, const SourceManager &SM) { const BugType &BT = R->getBugType(); return llvm::make_unique( R->getBugType().getCheckName(), R->getDeclWithIssue(), @@ -1342,7 +1340,7 @@ using OptimizedCallsSet = llvm::DenseSet; /// This avoids a "swoosh" effect, where an edge from a top-level statement A /// points to a sub-expression B.1 that's not at the start of B. In these cases, /// we'd like to see an edge from A to B, then another one from B to B.1. -static void addContextEdges(PathPieces &pieces, SourceManager &SM, +static void addContextEdges(PathPieces &pieces, const SourceManager &SM, const ParentMap &PM, const LocationContext *LCtx) { PathPieces::iterator Prev = pieces.end(); for (PathPieces::iterator I = pieces.begin(), E = Prev; I != E; @@ -1493,7 +1491,7 @@ static void simplifySimpleBranches(PathPieces &pieces) { /// If the locations in the range are not on the same line, returns None. /// /// Note that this does not do a precise user-visible character or column count. -static Optional getLengthOnSingleLine(SourceManager &SM, +static Optional getLengthOnSingleLine(const SourceManager &SM, SourceRange Range) { SourceRange ExpansionRange(SM.getExpansionLoc(Range.getBegin()), SM.getExpansionRange(Range.getEnd()).getEnd()); @@ -1523,7 +1521,7 @@ static Optional getLengthOnSingleLine(SourceManager &SM, } /// \sa getLengthOnSingleLine(SourceManager, SourceRange) -static Optional getLengthOnSingleLine(SourceManager &SM, +static Optional getLengthOnSingleLine(const SourceManager &SM, const Stmt *S) { return getLengthOnSingleLine(SM, S->getSourceRange()); } @@ -1544,7 +1542,7 @@ static Optional getLengthOnSingleLine(SourceManager &SM, /// - if there is an inlined call between the edges instead of a single event. /// - if the whole statement is large enough that having subexpression arrows /// might be helpful. -static void removeContextCycles(PathPieces &Path, SourceManager &SM) { +static void removeContextCycles(PathPieces &Path, const SourceManager &SM) { for (PathPieces::iterator I = Path.begin(), E = Path.end(); I != E; ) { // Pattern match the current piece and its successor. const auto *PieceI = dyn_cast(I->get()); @@ -1599,7 +1597,7 @@ static void removeContextCycles(PathPieces &Path, SourceManager &SM) { } /// Return true if X is contained by Y. -static bool lexicalContains(ParentMap &PM, const Stmt *X, const Stmt *Y) { +static bool lexicalContains(const ParentMap &PM, const Stmt *X, const Stmt *Y) { while (X) { if (X == Y) return true; @@ -1609,8 +1607,8 @@ static bool lexicalContains(ParentMap &PM, const Stmt *X, const Stmt *Y) { } // Remove short edges on the same line less than 3 columns in difference. -static void removePunyEdges(PathPieces &path, SourceManager &SM, - ParentMap &PM) { +static void removePunyEdges(PathPieces &path, const SourceManager &SM, + const ParentMap &PM) { bool erased = false; for (PathPieces::iterator I = path.begin(), E = path.end(); I != E; @@ -1685,13 +1683,13 @@ static void removeIdenticalEvents(PathPieces &path) { } } -static bool optimizeEdges(PathPieces &path, SourceManager &SM, +static bool optimizeEdges(PathPieces &path, const SourceManager &SM, OptimizedCallsSet &OCS, - LocationContextMap &LCM) { + const LocationContextMap &LCM) { bool hasChanges = false; - const LocationContext *LC = LCM[&path]; + const LocationContext *LC = LCM.lookup(&path); assert(LC); - ParentMap &PM = LC->getParentMap(); + const ParentMap &PM = LC->getParentMap(); for (PathPieces::iterator I = path.begin(), E = path.end(); I != E; ) { // Optimize subpaths. @@ -1866,14 +1864,15 @@ static bool optimizeEdges(PathPieces &path, SourceManager &SM, /// statement had an invalid source location), this function does nothing. // FIXME: We should just generate invalid edges anyway and have the optimizer // deal with them. -static void dropFunctionEntryEdge(PathPieces &Path, LocationContextMap &LCM, - SourceManager &SM) { +static void dropFunctionEntryEdge(PathPieces &Path, + const LocationContextMap &LCM, + const SourceManager &SM) { const auto *FirstEdge = dyn_cast(Path.front().get()); if (!FirstEdge) return; - const Decl *D = LCM[&Path]->getDecl(); + const Decl *D = LCM.lookup(&Path)->getDecl(); PathDiagnosticLocation EntryLoc = PathDiagnosticLocation::createBegin(D, SM); if (FirstEdge->getStartLocation() != EntryLoc) return; @@ -1919,9 +1918,9 @@ static std::unique_ptr generatePathDiagnosticForConsumer( bool GenerateDiagnostics = (ActiveScheme != PathDiagnosticConsumer::None); bool AddPathEdges = (ActiveScheme == PathDiagnosticConsumer::Extensive); - SourceManager &SM = PDB.getSourceManager(); - BugReport *R = PDB.getBugReport(); - AnalyzerOptions &Opts = PDB.getBugReporter().getAnalyzerOptions(); + const SourceManager &SM = PDB.getSourceManager(); + const BugReport *R = PDB.getBugReport(); + const AnalyzerOptions &Opts = PDB.getBugReporter().getAnalyzerOptions(); StackDiagVector CallStack; InterestingExprs IE; LocationContextMap LCM; @@ -1934,7 +1933,8 @@ static std::unique_ptr generatePathDiagnosticForConsumer( assert(!EndNotes->second.empty()); LastPiece = EndNotes->second[0]; } else { - LastPiece = BugReporterVisitor::getDefaultEndPath(PDB, ErrorNode, *R); + LastPiece = BugReporterVisitor::getDefaultEndPath(PDB, ErrorNode, + *PDB.getBugReport()); } PD->setEndOfPath(LastPiece); } @@ -1959,8 +1959,7 @@ static std::unique_ptr generatePathDiagnosticForConsumer( for (const PathDiagnosticPieceRef &Note : VisitorNotes->second) { llvm::FoldingSetNodeID ID; Note->Profile(ID); - auto P = DeduplicationSet.insert(ID); - if (!P.second) + if (!DeduplicationSet.insert(ID).second) continue; if (AddPathEdges) @@ -2122,11 +2121,11 @@ void BugReport::markInteresting(const LocationContext *LC) { InterestingLocationContexts.insert(LC); } -bool BugReport::isInteresting(SVal V) { +bool BugReport::isInteresting(SVal V) const { return isInteresting(V.getAsRegion()) || isInteresting(V.getAsSymbol()); } -bool BugReport::isInteresting(SymbolRef sym) { +bool BugReport::isInteresting(SymbolRef sym) const { if (!sym) return false; // We don't currently consider metadata symbols to be interesting @@ -2134,7 +2133,7 @@ bool BugReport::isInteresting(SymbolRef sym) { return InterestingSymbols.count(sym); } -bool BugReport::isInteresting(const MemRegion *R) { +bool BugReport::isInteresting(const MemRegion *R) const { if (!R) return false; R = R->getBaseRegion(); @@ -2146,7 +2145,7 @@ bool BugReport::isInteresting(const MemRegion *R) { return false; } -bool BugReport::isInteresting(const LocationContext *LC) { +bool BugReport::isInteresting(const LocationContext *LC) const { if (!LC) return false; return InterestingLocationContexts.count(LC); @@ -2202,11 +2201,14 @@ PathDiagnosticLocation BugReport::getLocation(const SourceManager &SM) const { // Methods for BugReporter and subclasses. //===----------------------------------------------------------------------===// -ExplodedGraph &GRBugReporter::getGraph() { return Eng.getGraph(); } +const ExplodedGraph &GRBugReporter::getGraph() const { return Eng.getGraph(); } ProgramStateManager& GRBugReporter::getStateManager() { return Eng.getStateManager(); } +ProgramStateManager& +GRBugReporter::getStateManager() const { return Eng.getStateManager(); } + BugReporter::~BugReporter() { FlushReports(); @@ -2984,7 +2986,7 @@ void BugReporter::FlushReport(BugReportEquivClass& EQ) { /// Insert all lines participating in the function signature \p Signature /// into \p ExecutedLines. static void populateExecutedLinesWithFunctionSignature( - const Decl *Signature, SourceManager &SM, + const Decl *Signature, const SourceManager &SM, FilesToLineNumsMap &ExecutedLines) { SourceRange SignatureSourceRange; const Stmt* Body = Signature->getBody(); @@ -3009,7 +3011,7 @@ static void populateExecutedLinesWithFunctionSignature( } static void populateExecutedLinesWithStmt( - const Stmt *S, SourceManager &SM, + const Stmt *S, const SourceManager &SM, FilesToLineNumsMap &ExecutedLines) { SourceLocation Loc = S->getSourceRange().getBegin(); if (!Loc.isValid()) @@ -3023,7 +3025,7 @@ static void populateExecutedLinesWithStmt( /// \return all executed lines including function signatures on the path /// starting from \p N. static std::unique_ptr -findExecutedLines(SourceManager &SM, const ExplodedNode *N) { +findExecutedLines(const SourceManager &SM, const ExplodedNode *N) { auto ExecutedLines = llvm::make_unique(); while (N) { @@ -3087,7 +3089,7 @@ BugReporter::generateDiagnosticForConsumerMap( // Examine the report and see if the last piece is in a header. Reset the // report location to the last piece in the main source file. - AnalyzerOptions &Opts = getAnalyzerOptions(); + const AnalyzerOptions &Opts = getAnalyzerOptions(); for (auto const &P : *Out) if (Opts.ShouldReportIssuesInMainSourceFile && !Opts.AnalyzeAll) P.second->resetDiagnosticLocationToMainFile(); diff --git a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp index a71ac21fe693a..ddd69f1085b4f 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp @@ -2250,7 +2250,7 @@ bool ConditionBRVisitor::patternMatch(const Expr *Ex, SourceLocation BeginLoc = OriginalExpr->getBeginLoc(); SourceLocation EndLoc = OriginalExpr->getEndLoc(); if (BeginLoc.isMacroID() && EndLoc.isMacroID()) { - SourceManager &SM = BRC.getSourceManager(); + const SourceManager &SM = BRC.getSourceManager(); const LangOptions &LO = BRC.getASTContext().getLangOpts(); if (Lexer::isAtStartOfMacroExpansion(BeginLoc, SM, LO) && Lexer::isAtEndOfMacroExpansion(EndLoc, SM, LO)) { @@ -2587,7 +2587,7 @@ void LikelyFalsePositiveSuppressionBRVisitor::finalizeVisitor( BugReporterContext &BRC, const ExplodedNode *N, BugReport &BR) { // Here we suppress false positives coming from system headers. This list is // based on known issues. - AnalyzerOptions &Options = BRC.getAnalyzerOptions(); + const AnalyzerOptions &Options = BRC.getAnalyzerOptions(); const Decl *D = N->getLocationContext()->getDecl(); if (AnalysisDeclContext::isInStdNamespace(D)) { @@ -2654,7 +2654,7 @@ void LikelyFalsePositiveSuppressionBRVisitor::finalizeVisitor( // Skip reports within the sys/queue.h macros as we do not have the ability to // reason about data structure shapes. - SourceManager &SM = BRC.getSourceManager(); + const SourceManager &SM = BRC.getSourceManager(); FullSourceLoc Loc = BR.getLocation(SM).asLocation(); while (Loc.isMacroID()) { Loc = Loc.getSpellingLoc(); diff --git a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp index b6f551d4920bb..316146e2dd210 100644 --- a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp +++ b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp @@ -1035,7 +1035,7 @@ getSyntacticFromForPseudoObjectExpr(const PseudoObjectExpr *POE) { ObjCMessageKind ObjCMethodCall::getMessageKind() const { if (!Data) { // Find the parent, ignoring implicit casts. - ParentMap &PM = getLocationContext()->getParentMap(); + const ParentMap &PM = getLocationContext()->getParentMap(); const Stmt *S = PM.getParentIgnoreParenCasts(getOriginExpr()); // Check if parent is a PseudoObjectExpr. diff --git a/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp b/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp index c86b1436baabb..15e8d62a52cd0 100644 --- a/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp @@ -134,7 +134,7 @@ bool ExplodedGraph::shouldCollect(const ExplodedNode *node) { // Do not collect nodes for non-consumed Stmt or Expr to ensure precise // diagnostic generation; specifically, so that we could anchor arrows // pointing to the beginning of statements (as written in code). - ParentMap &PM = progPoint.getLocationContext()->getParentMap(); + const ParentMap &PM = progPoint.getLocationContext()->getParentMap(); if (!PM.isConsumedExpr(Ex)) return false; From 2bda66a9a410a5efa26c39ce04d3f455e74c9208 Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Tue, 13 Aug 2019 19:01:33 +0000 Subject: [PATCH 085/181] [analyzer][NFC] Refactoring BugReporter.cpp P5.: Compact mile long function invocations into objects In D65379, I briefly described the construction of bug paths from an ExplodedGraph. This patch is about refactoring the code processing the bug path into a bug report. A part of finding a valid bug report was running all visitors on the bug path, so we already have a (possibly empty) set of diagnostics for each ExplodedNode in it. Then, for each diagnostic consumer, we construct non-visitor diagnostic pieces. * We first construct the final diagnostic piece (the warning), then * We start ascending the bug path from the error node's predecessor (since the error node itself was used to construct the warning event). For each node * We check the location (whether its a CallEnter, CallExit) etc. We simultaneously keep track of where we are with the execution by pushing CallStack when we see a CallExit (keep in mind that everything is happening in reverse!), popping it when we find a CallEnter, compacting them into a single PathDiagnosticCallEvent. void f() { bar(); } void g() { f(); error(); // warning } === The bug path === (root) -> f's CallEnter -> bar() -> f's CallExit -> (error node) === Constructed report === f's CallEnter -> bar() -> f's CallExit ^ / \ V (root) ---> f's CallEvent --> (error node) * We also keep track of different PathPieces different location contexts * (CallEvent::path in the above example has f's LocationContext, while the CallEvent itself is in g's context) in a LocationContextMap object. Construct whatever piece, if any, is needed for the note. * If we need to generate edges (or arrows) do so. Make sure to also connect these pieces with the ones that visitors emitted. * Clean up the constructed PathDiagnostic by making arrows nicer, pruning function calls, etc. So I complained about mile long function invocations with seemingly the same parameters being passed around. This problem, as I see it, a natural candidate for creating classes and tying them all together. I tried very hard to make the implementation feel natural, like, rolling off the tongue. I introduced 2 new classes: PathDiagnosticBuilder (I mean, I kept the name but changed almost everything in it) contains every contextual information (owns the bug path, the diagnostics constructed but the visitors, the BugReport itself, etc) needed for constructing a PathDiagnostic object, and is pretty much completely immutable. BugReportContruct is the object containing every non-contextual information (the PathDiagnostic object we're constructing, the current location in the bug path, the location context map and the call stack I meantioned earlier), and is passed around all over the place as a single entity instead of who knows how many parameters. I tried to used constness, asserts, limiting visibility of fields to my advantage to clean up the code big time and dramatically improve safety. Also, whenever I found the code difficult to understand, I added comments and/or examples. Here's a complete list of changes and my design philosophy behind it: * Instead of construcing a ReportInfo object (added by D65379) after finding a valid bug report, simply return an optional PathDiagnosticBuilder object straight away. Move findValidReport into the class as a static method. I find GRBugReporter::generatePathDiagnostics a joy to look at now. * Rename generatePathDiagnosticForConsumer to generate (maybe not needed, but felt that way in the moment) and moved it to PathDiagnosticBuilder. If we don't need to generate diagnostics, bail out straight away, like we always should have. After that, construct a BugReportConstruct object, leaving the rest of the logic untouched. * Move all static methods that would use contextual information into PathDiagnosticBuilder, reduce their parameter count drastically by simply passing around a BugReportConstruct object. * Glance at the code I removed: Could you tell what the original PathDiagnosticBuilder::LC object was for? It took a gooood long while for me to realize that nothing really. It is always equal with the LocationContext associated with our current position in the bug path. Remove it completely. * The original code contains the following expression quite a bit: LCM[&PD.getActivePath()], so what does it mean? I said that we collect the contexts associated with different PathPieces, but why would we ever modify that, shouldn't it be set? Well, theoretically yes, but in the implementation, the address of PathDiagnostic::getActivePath doesn't change if we move to an outer, previously unexplored function. Add both descriptive method names and explanations to BugReportConstruct to help on this. * Add plenty of asserts, both for safety and as a poor man's documentation. Differential Revision: https://reviews.llvm.org/D65484 llvm-svn: 368737 (cherry picked from commit f9d75bede84e71eddf0f5e232b8408c947d9adaa) --- .../Core/BugReporter/BugReporterVisitors.h | 2 +- .../Core/BugReporter/PathDiagnostic.h | 7 + clang/lib/StaticAnalyzer/Core/BugReporter.cpp | 823 ++++++++++-------- .../Core/BugReporterVisitors.cpp | 6 +- 4 files changed, 459 insertions(+), 379 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h index 0e2346174a9ca..536ed757e6aba 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h @@ -80,7 +80,7 @@ class BugReporterVisitor : public llvm::FoldingSetNode { virtual void Profile(llvm::FoldingSetNodeID &ID) const = 0; /// Generates the default final diagnostic piece. - static PathDiagnosticPieceRef getDefaultEndPath(BugReporterContext &BRC, + static PathDiagnosticPieceRef getDefaultEndPath(const BugReporterContext &BRC, const ExplodedNode *N, BugReport &BR); }; diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h index 7ccf569a68fc0..bfec18b034b5e 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h @@ -125,6 +125,13 @@ class PathDiagnosticConsumer { }; virtual PathGenerationScheme getGenerationScheme() const { return Minimal; } + + bool shouldGenerateDiagnostics() const { + return getGenerationScheme() != None; + } + + bool shouldAddPathEdges() const { return getGenerationScheme() == Extensive; } + virtual bool supportsLogicalOpControlFlow() const { return false; } /// Return true if the PathDiagnosticConsumer supports individual diff --git a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp index 0cf6d124fe4c8..f2f7fddf86e6f 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp @@ -84,6 +84,185 @@ BugReporterVisitor::~BugReporterVisitor() = default; void BugReporterContext::anchor() {} +//===----------------------------------------------------------------------===// +// PathDiagnosticBuilder and its associated routines and helper objects. +//===----------------------------------------------------------------------===// + +namespace { + +using StackDiagPair = + std::pair; +using StackDiagVector = SmallVector; + +/// Map from each node to the diagnostic pieces visitors emit for them. +using VisitorsDiagnosticsTy = + llvm::DenseMap>; + +using InterestingExprs = llvm::DenseSet; + +/// A map from PathDiagnosticPiece to the LocationContext of the inlined +/// function call it represents. +using LocationContextMap = + llvm::DenseMap; + +/// A helper class that contains everything needed to construct a +/// PathDiagnostic object. It does no much more then providing convenient +/// getters and some well placed asserts for extra security. +class BugReportConstruct { + /// The consumer we're constructing the bug report for. + const PathDiagnosticConsumer *Consumer; + /// Our current position in the bug path, which is owned by + /// PathDiagnosticBuilder. + const ExplodedNode *CurrentNode; + /// A mapping from parts of the bug path (for example, a function call, which + /// would span backwards from a CallExit to a CallEnter with the nodes in + /// between them) with the location contexts it is associated with. + LocationContextMap LCM; + const SourceManager &SM; + +public: + /// We keep stack of calls to functions as we're ascending the bug path. + /// TODO: PathDiagnostic has a stack doing the same thing, shouldn't we use + /// that instead? + StackDiagVector CallStack; + InterestingExprs IE; + /// The bug report we're constructing. For ease of use, this field is kept + /// public, though some "shortcut" getters are provided for commonly used + /// methods of PathDiagnostic. + std::unique_ptr PD; + +public: + BugReportConstruct(const PathDiagnosticConsumer *PDC, + const ExplodedNode *ErrorNode, const BugReport *R); + + /// \returns the location context associated with the current position in the + /// bug path. + const LocationContext *getCurrLocationContext() const { + assert(CurrentNode && "Already reached the root!"); + return CurrentNode->getLocationContext(); + } + + /// Same as getCurrLocationContext (they should always return the same + /// location context), but works after reaching the root of the bug path as + /// well. + const LocationContext *getLocationContextForActivePath() const { + return LCM.find(&PD->getActivePath())->getSecond(); + } + + const ExplodedNode *getCurrentNode() const { return CurrentNode; } + + /// Steps the current node to its predecessor. + /// \returns whether we reached the root of the bug path. + bool ascendToPrevNode() { + CurrentNode = CurrentNode->getFirstPred(); + return static_cast(CurrentNode); + } + + const ParentMap &getParentMap() const { + return getCurrLocationContext()->getParentMap(); + } + + const SourceManager &getSourceManager() const { return SM; } + + const Stmt *getParent(const Stmt *S) const { + return getParentMap().getParent(S); + } + + void updateLocCtxMap(const PathPieces *Path, const LocationContext *LC) { + assert(Path && LC); + LCM[Path] = LC; + } + + const LocationContext *getLocationContextFor(const PathPieces *Path) const { + assert(LCM.count(Path) && + "Failed to find the context associated with these pieces!"); + return LCM.find(Path)->getSecond(); + } + + bool isInLocCtxMap(const PathPieces *Path) const { return LCM.count(Path); } + + PathPieces &getActivePath() { return PD->getActivePath(); } + PathPieces &getMutablePieces() { return PD->getMutablePieces(); } + + bool shouldAddPathEdges() const { return Consumer->shouldAddPathEdges(); } + bool shouldGenerateDiagnostics() const { + return Consumer->shouldGenerateDiagnostics(); + } + bool supportsLogicalOpControlFlow() const { + return Consumer->supportsLogicalOpControlFlow(); + } +}; + +/// Contains every contextual information needed for constructing a +/// PathDiagnostic object for a given bug report. This class (and aside from +/// some caching BugReport does in the background) and its fields are immutable, +/// and passes a BugReportConstruct object around during the construction. +class PathDiagnosticBuilder : public BugReporterContext { + /// A linear path from the error node to the root. + std::unique_ptr BugPath; + BugReport *R; + /// The leaf of the bug path. This isn't the same as the bug reports error + /// node, which refers to the *original* graph, not the bug path. + const ExplodedNode *const ErrorNode; + /// The diagnostic pieces visitors emitted, which is expected to be collected + /// by the time this builder is constructed. + std::unique_ptr VisitorsDiagnostics; + +public: + /// Find a non-invalidated report for a given equivalence class, and returns + /// a PathDiagnosticBuilder able to construct bug reports for different + /// consumers. Returns None if no valid report is found. + static Optional + findValidReport(ArrayRef &bugReports, GRBugReporter &Reporter); + + PathDiagnosticBuilder( + BugReporterContext BRC, std::unique_ptr BugPath, + BugReport *r, const ExplodedNode *ErrorNode, + std::unique_ptr VisitorsDiagnostics); + + /// This function is responsible for generating diagnostic pieces that are + /// *not* provided by bug report visitors. + /// These diagnostics may differ depending on the consumer's settings, + /// and are therefore constructed separately for each consumer. + /// + /// There are two path diagnostics generation modes: with adding edges (used + /// for plists) and without (used for HTML and text). When edges are added, + /// the path is modified to insert artificially generated edges. + /// Otherwise, more detailed diagnostics is emitted for block edges, + /// explaining the transitions in words. + std::unique_ptr + generate(const PathDiagnosticConsumer *PDC) const; + +private: + void generatePathDiagnosticsForNode(BugReportConstruct &C, + PathDiagnosticLocation &PrevLoc) const; + + void generateMinimalDiagForBlockEdge(BugReportConstruct &C, + BlockEdge BE) const; + + PathDiagnosticPieceRef + generateDiagForGotoOP(const BugReportConstruct &C, const Stmt *S, + PathDiagnosticLocation &Start) const; + + PathDiagnosticPieceRef + generateDiagForSwitchOP(const BugReportConstruct &C, const CFGBlock *Dst, + PathDiagnosticLocation &Start) const; + + PathDiagnosticPieceRef generateDiagForBinaryOP(const BugReportConstruct &C, + const Stmt *T, + const CFGBlock *Src, + const CFGBlock *DstC) const; + + PathDiagnosticLocation ExecutionContinues(const BugReportConstruct &C) const; + + PathDiagnosticLocation ExecutionContinues(llvm::raw_string_ostream &os, + const BugReportConstruct &C) const; + + BugReport *getBugReport() const { return R; } +}; + +} // namespace + //===----------------------------------------------------------------------===// // Helper routines for walking the ExplodedGraph and fetching statements. //===----------------------------------------------------------------------===// @@ -182,16 +361,11 @@ static void removeRedundantMsgs(PathPieces &path) { } } -/// A map from PathDiagnosticPiece to the LocationContext of the inlined -/// function call it represents. -using LocationContextMap = - llvm::DenseMap; - /// Recursively scan through a path and prune out calls and macros pieces /// that aren't needed. Return true if afterwards the path contains /// "interesting stuff" which means it shouldn't be pruned from the parent path. -static bool removeUnneededCalls(PathPieces &pieces, const BugReport *R, - const LocationContextMap &LCM, +static bool removeUnneededCalls(const BugReportConstruct &C, PathPieces &pieces, + const BugReport *R, bool IsInteresting = false) { bool containsSomethingInteresting = IsInteresting; const unsigned N = pieces.size(); @@ -206,9 +380,9 @@ static bool removeUnneededCalls(PathPieces &pieces, const BugReport *R, case PathDiagnosticPiece::Call: { auto &call = cast(*piece); // Check if the location context is interesting. - assert(LCM.count(&call.path)); - if (!removeUnneededCalls(call.path, R, LCM, - R->isInteresting(LCM.lookup(&call.path)))) + if (!removeUnneededCalls( + C, call.path, R, + R->isInteresting(C.getLocationContextFor(&call.path)))) continue; containsSomethingInteresting = true; @@ -216,7 +390,7 @@ static bool removeUnneededCalls(PathPieces &pieces, const BugReport *R, } case PathDiagnosticPiece::Macro: { auto ¯o = cast(*piece); - if (!removeUnneededCalls(macro.subPieces, R, LCM, IsInteresting)) + if (!removeUnneededCalls(C, macro.subPieces, R, IsInteresting)) continue; containsSomethingInteresting = true; break; @@ -345,70 +519,24 @@ static void removePiecesWithInvalidLocations(PathPieces &Pieces) { } } -//===----------------------------------------------------------------------===// -// PathDiagnosticBuilder and its associated routines and helper objects. -//===----------------------------------------------------------------------===// - -namespace { - -class PathDiagnosticBuilder : public BugReporterContext { - BugReport *R; - const PathDiagnosticConsumer *PDC; - -public: - const LocationContext *LC; - - PathDiagnosticBuilder(GRBugReporter &br, - BugReport *r, InterExplodedGraphMap &Backmap, - const PathDiagnosticConsumer *pdc) - : BugReporterContext(br, Backmap), R(r), PDC(pdc), - LC(r->getErrorNode()->getLocationContext()) {} - - PathDiagnosticLocation ExecutionContinues(const ExplodedNode *N) const; - - PathDiagnosticLocation ExecutionContinues(llvm::raw_string_ostream &os, - const ExplodedNode *N) const; - - BugReport *getBugReport() { return R; } - - const Decl &getCodeDecl() const { return R->getErrorNode()->getCodeDecl(); } - - const ParentMap& getParentMap() const { return LC->getParentMap(); } - - const Stmt *getParent(const Stmt *S) const { - return getParentMap().getParent(S); - } - - PathDiagnosticLocation getEnclosingStmtLocation(const Stmt *S) const; - - PathDiagnosticConsumer::PathGenerationScheme getGenerationScheme() const { - return PDC ? PDC->getGenerationScheme() : PathDiagnosticConsumer::Minimal; - } - - bool supportsLogicalOpControlFlow() const { - return PDC ? PDC->supportsLogicalOpControlFlow() : true; - } -}; - -} // namespace - PathDiagnosticLocation -PathDiagnosticBuilder::ExecutionContinues(const ExplodedNode *N) const { - if (const Stmt *S = PathDiagnosticLocation::getNextStmt(N)) - return PathDiagnosticLocation(S, getSourceManager(), LC); +PathDiagnosticBuilder::ExecutionContinues(const BugReportConstruct &C) const { + if (const Stmt *S = PathDiagnosticLocation::getNextStmt(C.getCurrentNode())) + return PathDiagnosticLocation(S, getSourceManager(), + C.getCurrLocationContext()); - return PathDiagnosticLocation::createDeclEnd(N->getLocationContext(), + return PathDiagnosticLocation::createDeclEnd(C.getCurrLocationContext(), getSourceManager()); } PathDiagnosticLocation PathDiagnosticBuilder::ExecutionContinues(llvm::raw_string_ostream &os, - const ExplodedNode *N) const { + const BugReportConstruct &C) const { // Slow, but probably doesn't matter. if (os.str().empty()) os << ' '; - const PathDiagnosticLocation &Loc = ExecutionContinues(N); + const PathDiagnosticLocation &Loc = ExecutionContinues(C); if (Loc.asStmt()) os << "Execution continues on line " @@ -416,7 +544,7 @@ PathDiagnosticBuilder::ExecutionContinues(llvm::raw_string_ostream &os, << '.'; else { os << "Execution jumps to the end of the "; - const Decl *D = N->getLocationContext()->getDecl(); + const Decl *D = C.getCurrLocationContext()->getDecl(); if (isa(D)) os << "method"; else if (isa(D)) @@ -454,13 +582,14 @@ static const Stmt *getEnclosingParent(const Stmt *S, const ParentMap &PM) { } static PathDiagnosticLocation -getEnclosingStmtLocation(const Stmt *S, const SourceManager &SMgr, - const ParentMap &P, const LocationContext *LC, - bool allowNestedContexts) { +getEnclosingStmtLocation(const Stmt *S, const LocationContext *LC, + bool allowNestedContexts = false) { if (!S) return {}; - while (const Stmt *Parent = getEnclosingParent(S, P)) { + const SourceManager &SMgr = LC->getDecl()->getASTContext().getSourceManager(); + + while (const Stmt *Parent = getEnclosingParent(S, LC->getParentMap())) { switch (Parent->getStmtClass()) { case Stmt::BinaryOperatorClass: { const auto *B = cast(Parent); @@ -521,24 +650,22 @@ getEnclosingStmtLocation(const Stmt *S, const SourceManager &SMgr, return PathDiagnosticLocation(S, SMgr, LC); } -PathDiagnosticLocation -PathDiagnosticBuilder::getEnclosingStmtLocation(const Stmt *S) const { - assert(S && "Null Stmt passed to getEnclosingStmtLocation"); - return ::getEnclosingStmtLocation(S, getSourceManager(), getParentMap(), LC, - /*allowNestedContexts=*/false); -} - //===----------------------------------------------------------------------===// // "Minimal" path diagnostic generation algorithm. //===----------------------------------------------------------------------===// -using StackDiagPair = - std::pair; -using StackDiagVector = SmallVector; +/// If the piece contains a special message, add it to all the call pieces on +/// the active stack. For exampler, my_malloc allocated memory, so MallocChecker +/// will construct an event at the call to malloc(), and add a stack hint that +/// an allocated memory was returned. We'll use this hint to construct a message +/// when returning from the call to my_malloc +/// +/// void *my_malloc() { return malloc(sizeof(int)); } +/// void fishy() { +/// void *ptr = my_malloc(); // returned allocated memory +/// } // leak static void updateStackPiecesWithMessage(PathDiagnosticPiece &P, const StackDiagVector &CallStack) { - // If the piece contains a special message, add it to all the call - // pieces on the active stack. if (auto *ep = dyn_cast(&P)) { if (ep->hasCallStackHint()) for (const auto &I : CallStack) { @@ -558,22 +685,18 @@ static void updateStackPiecesWithMessage(PathDiagnosticPiece &P, static void CompactMacroExpandedPieces(PathPieces &path, const SourceManager& SM); +PathDiagnosticPieceRef PathDiagnosticBuilder::generateDiagForSwitchOP( + const BugReportConstruct &C, const CFGBlock *Dst, + PathDiagnosticLocation &Start) const { -std::shared_ptr generateDiagForSwitchOP( - const ExplodedNode *N, - const CFGBlock *Dst, - const SourceManager &SM, - const LocationContext *LC, - const PathDiagnosticBuilder &PDB, - PathDiagnosticLocation &Start - ) { + const SourceManager &SM = getSourceManager(); // Figure out what case arm we took. std::string sbuf; llvm::raw_string_ostream os(sbuf); PathDiagnosticLocation End; if (const Stmt *S = Dst->getLabel()) { - End = PathDiagnosticLocation(S, SM, LC); + End = PathDiagnosticLocation(S, SM, C.getCurrLocationContext()); switch (S->getStmtClass()) { default: @@ -606,7 +729,7 @@ std::shared_ptr generateDiagForSwitchOP( } if (GetRawInt) - os << LHS->EvaluateKnownConstInt(PDB.getASTContext()); + os << LHS->EvaluateKnownConstInt(getASTContext()); os << ":' at line " << End.asLocation().getExpansionLineNumber(); break; @@ -614,29 +737,28 @@ std::shared_ptr generateDiagForSwitchOP( } } else { os << "'Default' branch taken. "; - End = PDB.ExecutionContinues(os, N); + End = ExecutionContinues(os, C); } return std::make_shared(Start, End, os.str()); } - -std::shared_ptr generateDiagForGotoOP( - const Stmt *S, - const PathDiagnosticBuilder &PDB, - PathDiagnosticLocation &Start) { - std::string sbuf; - llvm::raw_string_ostream os(sbuf); - const PathDiagnosticLocation &End = PDB.getEnclosingStmtLocation(S); - os << "Control jumps to line " << End.asLocation().getExpansionLineNumber(); - return std::make_shared(Start, End, os.str()); - +PathDiagnosticPieceRef PathDiagnosticBuilder::generateDiagForGotoOP( + const BugReportConstruct &C, const Stmt *S, + PathDiagnosticLocation &Start) const { + std::string sbuf; + llvm::raw_string_ostream os(sbuf); + const PathDiagnosticLocation &End = + getEnclosingStmtLocation(S, C.getCurrLocationContext()); + os << "Control jumps to line " << End.asLocation().getExpansionLineNumber(); + return std::make_shared(Start, End, os.str()); } -std::shared_ptr generateDiagForBinaryOP( - const ExplodedNode *N, const Stmt *T, const CFGBlock *Src, - const CFGBlock *Dst, const SourceManager &SM, - const PathDiagnosticBuilder &PDB, const LocationContext *LC) { +PathDiagnosticPieceRef PathDiagnosticBuilder::generateDiagForBinaryOP( + const BugReportConstruct &C, const Stmt *T, const CFGBlock *Src, + const CFGBlock *Dst) const { + + const SourceManager &SM = getSourceManager(); const auto *B = cast(T); std::string sbuf; @@ -650,13 +772,14 @@ std::shared_ptr generateDiagForBinaryOP( if (*(Src->succ_begin() + 1) == Dst) { os << "false"; - End = PathDiagnosticLocation(B->getLHS(), SM, LC); + End = PathDiagnosticLocation(B->getLHS(), SM, C.getCurrLocationContext()); Start = PathDiagnosticLocation::createOperatorLoc(B, SM); } else { os << "true"; - Start = PathDiagnosticLocation(B->getLHS(), SM, LC); - End = PDB.ExecutionContinues(N); + Start = + PathDiagnosticLocation(B->getLHS(), SM, C.getCurrLocationContext()); + End = ExecutionContinues(C); } } else { assert(B->getOpcode() == BO_LOr); @@ -665,11 +788,12 @@ std::shared_ptr generateDiagForBinaryOP( if (*(Src->succ_begin() + 1) == Dst) { os << "false"; - Start = PathDiagnosticLocation(B->getLHS(), SM, LC); - End = PDB.ExecutionContinues(N); + Start = + PathDiagnosticLocation(B->getLHS(), SM, C.getCurrLocationContext()); + End = ExecutionContinues(C); } else { os << "true"; - End = PathDiagnosticLocation(B->getLHS(), SM, LC); + End = PathDiagnosticLocation(B->getLHS(), SM, C.getCurrLocationContext()); Start = PathDiagnosticLocation::createOperatorLoc(B, SM); } @@ -678,11 +802,10 @@ std::shared_ptr generateDiagForBinaryOP( os.str()); } -void generateMinimalDiagForBlockEdge(const ExplodedNode *N, BlockEdge BE, - const SourceManager &SM, - const PathDiagnosticBuilder &PDB, - PathDiagnostic &PD) { - const LocationContext *LC = N->getLocationContext(); +void PathDiagnosticBuilder::generateMinimalDiagForBlockEdge( + BugReportConstruct &C, BlockEdge BE) const { + const SourceManager &SM = getSourceManager(); + const LocationContext *LC = C.getCurrLocationContext(); const CFGBlock *Src = BE.getSrc(); const CFGBlock *Dst = BE.getDst(); const Stmt *T = Src->getTerminatorStmt(); @@ -696,14 +819,13 @@ void generateMinimalDiagForBlockEdge(const ExplodedNode *N, BlockEdge BE, case Stmt::GotoStmtClass: case Stmt::IndirectGotoStmtClass: { - if (const Stmt *S = PathDiagnosticLocation::getNextStmt(N)) - PD.getActivePath().push_front(generateDiagForGotoOP(S, PDB, Start)); + if (const Stmt *S = PathDiagnosticLocation::getNextStmt(C.getCurrentNode())) + C.getActivePath().push_front(generateDiagForGotoOP(C, S, Start)); break; } case Stmt::SwitchStmtClass: { - PD.getActivePath().push_front( - generateDiagForSwitchOP(N, Dst, SM, LC, PDB, Start)); + C.getActivePath().push_front(generateDiagForSwitchOP(C, Dst, Start)); break; } @@ -711,8 +833,8 @@ void generateMinimalDiagForBlockEdge(const ExplodedNode *N, BlockEdge BE, case Stmt::ContinueStmtClass: { std::string sbuf; llvm::raw_string_ostream os(sbuf); - PathDiagnosticLocation End = PDB.ExecutionContinues(os, N); - PD.getActivePath().push_front( + PathDiagnosticLocation End = ExecutionContinues(os, C); + C.getActivePath().push_front( std::make_shared(Start, End, os.str())); break; } @@ -729,24 +851,22 @@ void generateMinimalDiagForBlockEdge(const ExplodedNode *N, BlockEdge BE, else os << "true"; - PathDiagnosticLocation End = PDB.ExecutionContinues(N); + PathDiagnosticLocation End = ExecutionContinues(C); if (const Stmt *S = End.asStmt()) - End = PDB.getEnclosingStmtLocation(S); + End = getEnclosingStmtLocation(S, C.getCurrLocationContext()); - PD.getActivePath().push_front( + C.getActivePath().push_front( std::make_shared(Start, End, os.str())); break; } // Determine control-flow for short-circuited '&&' and '||'. case Stmt::BinaryOperatorClass: { - if (!PDB.supportsLogicalOpControlFlow()) + if (!C.supportsLogicalOpControlFlow()) break; - std::shared_ptr Diag = - generateDiagForBinaryOP(N, T, Src, Dst, SM, PDB, LC); - PD.getActivePath().push_front(Diag); + C.getActivePath().push_front(generateDiagForBinaryOP(C, T, Src, Dst)); break; } @@ -756,21 +876,21 @@ void generateMinimalDiagForBlockEdge(const ExplodedNode *N, BlockEdge BE, llvm::raw_string_ostream os(sbuf); os << "Loop condition is true. "; - PathDiagnosticLocation End = PDB.ExecutionContinues(os, N); + PathDiagnosticLocation End = ExecutionContinues(os, C); if (const Stmt *S = End.asStmt()) - End = PDB.getEnclosingStmtLocation(S); + End = getEnclosingStmtLocation(S, C.getCurrLocationContext()); - PD.getActivePath().push_front( + C.getActivePath().push_front( std::make_shared(Start, End, os.str())); } else { - PathDiagnosticLocation End = PDB.ExecutionContinues(N); + PathDiagnosticLocation End = ExecutionContinues(C); if (const Stmt *S = End.asStmt()) - End = PDB.getEnclosingStmtLocation(S); + End = getEnclosingStmtLocation(S, C.getCurrLocationContext()); - PD.getActivePath().push_front( + C.getActivePath().push_front( std::make_shared( Start, End, "Loop condition is false. Exiting loop")); } @@ -783,19 +903,19 @@ void generateMinimalDiagForBlockEdge(const ExplodedNode *N, BlockEdge BE, llvm::raw_string_ostream os(sbuf); os << "Loop condition is false. "; - PathDiagnosticLocation End = PDB.ExecutionContinues(os, N); + PathDiagnosticLocation End = ExecutionContinues(os, C); if (const Stmt *S = End.asStmt()) - End = PDB.getEnclosingStmtLocation(S); + End = getEnclosingStmtLocation(S, C.getCurrLocationContext()); - PD.getActivePath().push_front( + C.getActivePath().push_front( std::make_shared(Start, End, os.str())); } else { - PathDiagnosticLocation End = PDB.ExecutionContinues(N); + PathDiagnosticLocation End = ExecutionContinues(C); if (const Stmt *S = End.asStmt()) - End = PDB.getEnclosingStmtLocation(S); + End = getEnclosingStmtLocation(S, C.getCurrLocationContext()); - PD.getActivePath().push_front( + C.getActivePath().push_front( std::make_shared( Start, End, "Loop condition is true. Entering loop body")); } @@ -803,17 +923,17 @@ void generateMinimalDiagForBlockEdge(const ExplodedNode *N, BlockEdge BE, break; case Stmt::IfStmtClass: { - PathDiagnosticLocation End = PDB.ExecutionContinues(N); + PathDiagnosticLocation End = ExecutionContinues(C); if (const Stmt *S = End.asStmt()) - End = PDB.getEnclosingStmtLocation(S); + End = getEnclosingStmtLocation(S, C.getCurrLocationContext()); if (*(Src->succ_begin() + 1) == Dst) - PD.getActivePath().push_front( + C.getActivePath().push_front( std::make_shared( Start, End, "Taking false branch")); else - PD.getActivePath().push_front( + C.getActivePath().push_front( std::make_shared( Start, End, "Taking true branch")); @@ -833,7 +953,6 @@ void generateMinimalDiagForBlockEdge(const ExplodedNode *N, BlockEdge BE, // because the constraint solver sometimes simplifies certain symbolic values // into constants when appropriate, and this complicates reasoning about // interesting values. -using InterestingExprs = llvm::DenseSet; static void reversePropagateIntererstingSymbols(BugReport &R, InterestingExprs &IE, @@ -1015,20 +1134,10 @@ llvm::StringLiteral StrLoopCollectionEmpty = static std::unique_ptr findExecutedLines(const SourceManager &SM, const ExplodedNode *N); -/// Generate diagnostics for the node \p N, -/// and write it into \p PD. -/// \p AddPathEdges Whether diagnostic consumer can generate path arrows -/// showing both row and column. -static void generatePathDiagnosticsForNode(const ExplodedNode *N, - PathDiagnostic &PD, - PathDiagnosticLocation &PrevLoc, - PathDiagnosticBuilder &PDB, - LocationContextMap &LCM, - StackDiagVector &CallStack, - InterestingExprs &IE, - bool AddPathEdges) { - ProgramPoint P = N->getLocation(); - const SourceManager& SM = PDB.getSourceManager(); +void PathDiagnosticBuilder::generatePathDiagnosticsForNode( + BugReportConstruct &C, PathDiagnosticLocation &PrevLoc) const { + ProgramPoint P = C.getCurrentNode()->getLocation(); + const SourceManager &SM = getSourceManager(); // Have we encountered an entrance to a call? It may be // the case that we have not encountered a matching @@ -1036,7 +1145,7 @@ static void generatePathDiagnosticsForNode(const ExplodedNode *N, // terminated within the call itself. if (auto CE = P.getAs()) { - if (AddPathEdges) { + if (C.shouldAddPathEdges()) { // Add an edge to the start of the function. const StackFrameContext *CalleeLC = CE->getCalleeContext(); const Decl *D = CalleeLC->getDecl(); @@ -1047,139 +1156,131 @@ static void generatePathDiagnosticsForNode(const ExplodedNode *N, // because the exit edge comes from a statement (i.e. return), // not from declaration. if (D->hasBody()) - addEdgeToPath(PD.getActivePath(), PrevLoc, - PathDiagnosticLocation::createBegin(D, SM)); + addEdgeToPath(C.getActivePath(), PrevLoc, + PathDiagnosticLocation::createBegin(D, SM)); } // Did we visit an entire call? - bool VisitedEntireCall = PD.isWithinCall(); - PD.popActivePath(); + bool VisitedEntireCall = C.PD->isWithinCall(); + C.PD->popActivePath(); - PathDiagnosticCallPiece *C; + PathDiagnosticCallPiece *Call; if (VisitedEntireCall) { - C = cast(PD.getActivePath().front().get()); + Call = cast(C.getActivePath().front().get()); } else { + // The path terminated within a nested location context, create a new + // call piece to encapsulate the rest of the path pieces. const Decl *Caller = CE->getLocationContext()->getDecl(); - C = PathDiagnosticCallPiece::construct(PD.getActivePath(), Caller); - - if (AddPathEdges) { - // Since we just transferred the path over to the call piece, - // reset the mapping from active to location context. - assert(PD.getActivePath().size() == 1 && - PD.getActivePath().front().get() == C); - LCM[&PD.getActivePath()] = nullptr; - } - - // Record the location context mapping for the path within - // the call. - assert(LCM[&C->path] == nullptr || - LCM[&C->path] == CE->getCalleeContext()); - LCM[&C->path] = CE->getCalleeContext(); - - // If this is the first item in the active path, record - // the new mapping from active path to location context. - const LocationContext *&NewLC = LCM[&PD.getActivePath()]; - if (!NewLC) - NewLC = N->getLocationContext(); - - PDB.LC = NewLC; - } - C->setCallee(*CE, SM); + Call = PathDiagnosticCallPiece::construct(C.getActivePath(), Caller); + assert(C.getActivePath().size() == 1 && + C.getActivePath().front().get() == Call); + + // Since we just transferred the path over to the call piece, reset the + // mapping of the active path to the current location context. + assert(C.isInLocCtxMap(&C.getActivePath()) && + "When we ascend to a previously unvisited call, the active path's " + "address shouldn't change, but rather should be compacted into " + "a single CallEvent!"); + C.updateLocCtxMap(&C.getActivePath(), C.getCurrLocationContext()); + + // Record the location context mapping for the path within the call. + assert(!C.isInLocCtxMap(&Call->path) && + "When we ascend to a previously unvisited call, this must be the " + "first time we encounter the caller context!"); + C.updateLocCtxMap(&Call->path, CE->getCalleeContext()); + } + Call->setCallee(*CE, SM); // Update the previous location in the active path. - PrevLoc = C->getLocation(); + PrevLoc = Call->getLocation(); - if (!CallStack.empty()) { - assert(CallStack.back().first == C); - CallStack.pop_back(); + if (!C.CallStack.empty()) { + assert(C.CallStack.back().first == Call); + C.CallStack.pop_back(); } return; } - - if (AddPathEdges) { - // Query the location context here and the previous location - // as processing CallEnter may change the active path. - PDB.LC = N->getLocationContext(); - - // Record the mapping from the active path to the location - // context. - assert(!LCM[&PD.getActivePath()] || LCM[&PD.getActivePath()] == PDB.LC); - LCM[&PD.getActivePath()] = PDB.LC; - } + assert(C.getCurrLocationContext() == C.getLocationContextForActivePath() && + "The current position in the bug path is out of sync with the " + "location context associated with the active path!"); // Have we encountered an exit from a function call? if (Optional CE = P.getAs()) { // We are descending into a call (backwards). Construct // a new call piece to contain the path pieces for that call. - auto C = PathDiagnosticCallPiece::construct(*CE, SM); + auto Call = PathDiagnosticCallPiece::construct(*CE, SM); // Record the mapping from call piece to LocationContext. - LCM[&C->path] = CE->getCalleeContext(); + assert(!C.isInLocCtxMap(&Call->path) && + "We just entered a call, this must've been the first time we " + "encounter its context!"); + C.updateLocCtxMap(&Call->path, CE->getCalleeContext()); - if (AddPathEdges) { + if (C.shouldAddPathEdges()) { const Stmt *S = CE->getCalleeContext()->getCallSite(); // Propagate the interesting symbols accordingly. if (const auto *Ex = dyn_cast_or_null(S)) { - reversePropagateIntererstingSymbols(*PDB.getBugReport(), IE, - N->getState().get(), Ex, - N->getLocationContext()); + reversePropagateIntererstingSymbols( + *getBugReport(), C.IE, C.getCurrentNode()->getState().get(), Ex, + C.getCurrLocationContext()); } // Add the edge to the return site. - addEdgeToPath(PD.getActivePath(), PrevLoc, C->callReturn); + addEdgeToPath(C.getActivePath(), PrevLoc, Call->callReturn); PrevLoc.invalidate(); } - auto *P = C.get(); - PD.getActivePath().push_front(std::move(C)); + auto *P = Call.get(); + C.getActivePath().push_front(std::move(Call)); // Make the contents of the call the active path for now. - PD.pushActivePath(&P->path); - CallStack.push_back(StackDiagPair(P, N)); + C.PD->pushActivePath(&P->path); + C.CallStack.push_back(StackDiagPair(P, C.getCurrentNode())); return; } if (auto PS = P.getAs()) { - if (!AddPathEdges) + if (!C.shouldAddPathEdges()) return; // For expressions, make sure we propagate the // interesting symbols correctly. if (const Expr *Ex = PS->getStmtAs()) - reversePropagateIntererstingSymbols(*PDB.getBugReport(), IE, - N->getState().get(), Ex, - N->getLocationContext()); + reversePropagateIntererstingSymbols(*getBugReport(), C.IE, + C.getCurrentNode()->getState().get(), + Ex, C.getCurrLocationContext()); // Add an edge. If this is an ObjCForCollectionStmt do // not add an edge here as it appears in the CFG both // as a terminator and as a terminator condition. if (!isa(PS->getStmt())) { PathDiagnosticLocation L = - PathDiagnosticLocation(PS->getStmt(), SM, PDB.LC); - addEdgeToPath(PD.getActivePath(), PrevLoc, L); + PathDiagnosticLocation(PS->getStmt(), SM, C.getCurrLocationContext()); + addEdgeToPath(C.getActivePath(), PrevLoc, L); } } else if (auto BE = P.getAs()) { - if (!AddPathEdges) { - generateMinimalDiagForBlockEdge(N, *BE, SM, PDB, PD); + if (!C.shouldAddPathEdges()) { + generateMinimalDiagForBlockEdge(C, *BE); return; } // Does this represent entering a call? If so, look at propagating // interesting symbols across call boundaries. - if (const ExplodedNode *NextNode = N->getFirstPred()) { + if (const ExplodedNode *NextNode = C.getCurrentNode()->getFirstPred()) { const LocationContext *CallerCtx = NextNode->getLocationContext(); - const LocationContext *CalleeCtx = PDB.LC; - if (CallerCtx != CalleeCtx && AddPathEdges) { - reversePropagateInterestingSymbols(*PDB.getBugReport(), IE, - N->getState().get(), CalleeCtx); + const LocationContext *CalleeCtx = C.getCurrLocationContext(); + if (CallerCtx != CalleeCtx && C.shouldAddPathEdges()) { + reversePropagateInterestingSymbols(*getBugReport(), C.IE, + C.getCurrentNode()->getState().get(), + CalleeCtx); } } // Are we jumping to the head of a loop? Add a special diagnostic. if (const Stmt *Loop = BE->getSrc()->getLoopTarget()) { - PathDiagnosticLocation L(Loop, SM, PDB.LC); + PathDiagnosticLocation L(Loop, SM, C.getCurrLocationContext()); const Stmt *Body = nullptr; if (const auto *FS = dyn_cast(Loop)) @@ -1198,25 +1299,25 @@ static void generatePathDiagnosticsForNode(const ExplodedNode *N, "of the loop"); p->setPrunable(true); - addEdgeToPath(PD.getActivePath(), PrevLoc, p->getLocation()); - PD.getActivePath().push_front(std::move(p)); + addEdgeToPath(C.getActivePath(), PrevLoc, p->getLocation()); + C.getActivePath().push_front(std::move(p)); if (const auto *CS = dyn_cast_or_null(Body)) { - addEdgeToPath(PD.getActivePath(), PrevLoc, - PathDiagnosticLocation::createEndBrace(CS, SM)); + addEdgeToPath(C.getActivePath(), PrevLoc, + PathDiagnosticLocation::createEndBrace(CS, SM)); } } const CFGBlock *BSrc = BE->getSrc(); - const ParentMap &PM = PDB.getParentMap(); + const ParentMap &PM = C.getParentMap(); if (const Stmt *Term = BSrc->getTerminatorStmt()) { // Are we jumping past the loop body without ever executing the // loop (because the condition was false)? if (isLoop(Term)) { const Stmt *TermCond = getTerminatorCondition(BSrc); - bool IsInLoopBody = - isInLoopBody(PM, getStmtBeforeCond(PM, TermCond, N), Term); + bool IsInLoopBody = isInLoopBody( + PM, getStmtBeforeCond(PM, TermCond, C.getCurrentNode()), Term); StringRef str; @@ -1235,17 +1336,17 @@ static void generatePathDiagnosticsForNode(const ExplodedNode *N, } if (!str.empty()) { - PathDiagnosticLocation L(TermCond ? TermCond : Term, SM, PDB.LC); + PathDiagnosticLocation L(TermCond ? TermCond : Term, SM, + C.getCurrLocationContext()); auto PE = std::make_shared(L, str); PE->setPrunable(true); - addEdgeToPath(PD.getActivePath(), PrevLoc, - PE->getLocation()); - PD.getActivePath().push_front(std::move(PE)); + addEdgeToPath(C.getActivePath(), PrevLoc, PE->getLocation()); + C.getActivePath().push_front(std::move(PE)); } } else if (isa(Term) || isa(Term) || isa(Term)) { - PathDiagnosticLocation L(Term, SM, PDB.LC); - addEdgeToPath(PD.getActivePath(), PrevLoc, L); + PathDiagnosticLocation L(Term, SM, C.getCurrLocationContext()); + addEdgeToPath(C.getActivePath(), PrevLoc, L); } } } @@ -1340,8 +1441,8 @@ using OptimizedCallsSet = llvm::DenseSet; /// This avoids a "swoosh" effect, where an edge from a top-level statement A /// points to a sub-expression B.1 that's not at the start of B. In these cases, /// we'd like to see an edge from A to B, then another one from B to B.1. -static void addContextEdges(PathPieces &pieces, const SourceManager &SM, - const ParentMap &PM, const LocationContext *LCtx) { +static void addContextEdges(PathPieces &pieces, const LocationContext *LC) { + const ParentMap &PM = LC->getParentMap(); PathPieces::iterator Prev = pieces.end(); for (PathPieces::iterator I = pieces.begin(), E = Prev; I != E; Prev = I, ++I) { @@ -1358,7 +1459,7 @@ static void addContextEdges(PathPieces &pieces, const SourceManager &SM, while (NextSrcContext.isValid() && NextSrcContext.asStmt() != InnerStmt) { SrcContexts.push_back(NextSrcContext); InnerStmt = NextSrcContext.asStmt(); - NextSrcContext = getEnclosingStmtLocation(InnerStmt, SM, PM, LCtx, + NextSrcContext = getEnclosingStmtLocation(InnerStmt, LC, /*allowNested=*/true); } @@ -1371,7 +1472,7 @@ static void addContextEdges(PathPieces &pieces, const SourceManager &SM, // We are looking at an edge. Is the destination within a larger // expression? PathDiagnosticLocation DstContext = - getEnclosingStmtLocation(Dst, SM, PM, LCtx, /*allowNested=*/true); + getEnclosingStmtLocation(Dst, LC, /*allowNested=*/true); if (!DstContext.isValid() || DstContext.asStmt() == Dst) break; @@ -1683,13 +1784,13 @@ static void removeIdenticalEvents(PathPieces &path) { } } -static bool optimizeEdges(PathPieces &path, const SourceManager &SM, - OptimizedCallsSet &OCS, - const LocationContextMap &LCM) { +static bool optimizeEdges(const BugReportConstruct &C, PathPieces &path, + OptimizedCallsSet &OCS) { bool hasChanges = false; - const LocationContext *LC = LCM.lookup(&path); + const LocationContext *LC = C.getLocationContextFor(&path); assert(LC); const ParentMap &PM = LC->getParentMap(); + const SourceManager &SM = C.getSourceManager(); for (PathPieces::iterator I = path.begin(), E = path.end(); I != E; ) { // Optimize subpaths. @@ -1697,7 +1798,8 @@ static bool optimizeEdges(PathPieces &path, const SourceManager &SM, // Record the fact that a call has been optimized so we only do the // effort once. if (!OCS.count(CallI)) { - while (optimizeEdges(CallI->path, SM, OCS, LCM)) {} + while (optimizeEdges(C, CallI->path, OCS)) { + } OCS.insert(CallI); } ++I; @@ -1843,7 +1945,7 @@ static bool optimizeEdges(PathPieces &path, const SourceManager &SM, if (!hasChanges) { // Adjust edges into subexpressions to make them more uniform // and aesthetically pleasing. - addContextEdges(path, SM, PM, LC); + addContextEdges(path, LC); // Remove "cyclical" edges that include one or more context edges. removeContextCycles(path, SM); // Hoist edges originating from branch conditions to branches @@ -1864,25 +1966,22 @@ static bool optimizeEdges(PathPieces &path, const SourceManager &SM, /// statement had an invalid source location), this function does nothing. // FIXME: We should just generate invalid edges anyway and have the optimizer // deal with them. -static void dropFunctionEntryEdge(PathPieces &Path, - const LocationContextMap &LCM, - const SourceManager &SM) { +static void dropFunctionEntryEdge(const BugReportConstruct &C, + PathPieces &Path) { const auto *FirstEdge = dyn_cast(Path.front().get()); if (!FirstEdge) return; - const Decl *D = LCM.lookup(&Path)->getDecl(); - PathDiagnosticLocation EntryLoc = PathDiagnosticLocation::createBegin(D, SM); + const Decl *D = C.getLocationContextFor(&Path)->getDecl(); + PathDiagnosticLocation EntryLoc = + PathDiagnosticLocation::createBegin(D, C.getSourceManager()); if (FirstEdge->getStartLocation() != EntryLoc) return; Path.pop_front(); } -using VisitorsDiagnosticsTy = - llvm::DenseMap>; - /// Populate executes lines with lines containing at least one diagnostics. static void updateExecutedLinesWithDiagnosticPieces(PathDiagnostic &PD) { @@ -1898,57 +1997,55 @@ static void updateExecutedLinesWithDiagnosticPieces(PathDiagnostic &PD) { } } -/// This function is responsible for generating diagnostic pieces that are -/// *not* provided by bug report visitors. -/// These diagnostics may differ depending on the consumer's settings, -/// and are therefore constructed separately for each consumer. -/// -/// There are two path diagnostics generation modes: with adding edges (used -/// for plists) and without (used for HTML and text). -/// When edges are added (\p ActiveScheme is Extensive), -/// the path is modified to insert artificially generated -/// edges. -/// Otherwise, more detailed diagnostics is emitted for block edges, explaining -/// the transitions in words. -static std::unique_ptr generatePathDiagnosticForConsumer( - PathDiagnosticConsumer::PathGenerationScheme ActiveScheme, - PathDiagnosticBuilder &PDB, - const ExplodedNode *ErrorNode, - const VisitorsDiagnosticsTy &VisitorsDiagnostics) { - - bool GenerateDiagnostics = (ActiveScheme != PathDiagnosticConsumer::None); - bool AddPathEdges = (ActiveScheme == PathDiagnosticConsumer::Extensive); - const SourceManager &SM = PDB.getSourceManager(); - const BugReport *R = PDB.getBugReport(); - const AnalyzerOptions &Opts = PDB.getBugReporter().getAnalyzerOptions(); - StackDiagVector CallStack; - InterestingExprs IE; - LocationContextMap LCM; - std::unique_ptr PD = generateEmptyDiagnosticForReport(R, SM); - - if (GenerateDiagnostics) { - auto EndNotes = VisitorsDiagnostics.find(ErrorNode); - PathDiagnosticPieceRef LastPiece; - if (EndNotes != VisitorsDiagnostics.end()) { - assert(!EndNotes->second.empty()); - LastPiece = EndNotes->second[0]; - } else { - LastPiece = BugReporterVisitor::getDefaultEndPath(PDB, ErrorNode, - *PDB.getBugReport()); - } - PD->setEndOfPath(LastPiece); +BugReportConstruct::BugReportConstruct(const PathDiagnosticConsumer *PDC, + const ExplodedNode *ErrorNode, + const BugReport *R) + : Consumer(PDC), CurrentNode(ErrorNode), + SM(CurrentNode->getCodeDecl().getASTContext().getSourceManager()), + PD(generateEmptyDiagnosticForReport(R, getSourceManager())) { + LCM[&PD->getActivePath()] = ErrorNode->getLocationContext(); +} + +PathDiagnosticBuilder::PathDiagnosticBuilder( + BugReporterContext BRC, std::unique_ptr BugPath, + BugReport *r, const ExplodedNode *ErrorNode, + std::unique_ptr VisitorsDiagnostics) + : BugReporterContext(BRC), BugPath(std::move(BugPath)), R(r), + ErrorNode(ErrorNode), + VisitorsDiagnostics(std::move(VisitorsDiagnostics)) {} + +std::unique_ptr +PathDiagnosticBuilder::generate(const PathDiagnosticConsumer *PDC) const { + + if (!PDC->shouldGenerateDiagnostics()) + return generateEmptyDiagnosticForReport(R, getSourceManager()); + + BugReportConstruct Construct(PDC, ErrorNode, R); + + const SourceManager &SM = getSourceManager(); + const BugReport *R = getBugReport(); + const AnalyzerOptions &Opts = getAnalyzerOptions(); + + // Construct the final (warning) event for the bug report. + auto EndNotes = VisitorsDiagnostics->find(ErrorNode); + PathDiagnosticPieceRef LastPiece; + if (EndNotes != VisitorsDiagnostics->end()) { + assert(!EndNotes->second.empty()); + LastPiece = EndNotes->second[0]; + } else { + LastPiece = BugReporterVisitor::getDefaultEndPath(*this, ErrorNode, + *getBugReport()); } + Construct.PD->setEndOfPath(LastPiece); - PathDiagnosticLocation PrevLoc = PD->getLocation(); - const ExplodedNode *NextNode = ErrorNode->getFirstPred(); - while (NextNode) { - if (GenerateDiagnostics) - generatePathDiagnosticsForNode( - NextNode, *PD, PrevLoc, PDB, LCM, CallStack, IE, AddPathEdges); + PathDiagnosticLocation PrevLoc = Construct.PD->getLocation(); + // From the error node to the root, ascend the bug path and construct the bug + // report. + while (Construct.ascendToPrevNode()) { + generatePathDiagnosticsForNode(Construct, PrevLoc); - auto VisitorNotes = VisitorsDiagnostics.find(NextNode); - NextNode = NextNode->getFirstPred(); - if (!GenerateDiagnostics || VisitorNotes == VisitorsDiagnostics.end()) + auto VisitorNotes = VisitorsDiagnostics->find(Construct.getCurrentNode()); + if (VisitorNotes == VisitorsDiagnostics->end()) continue; // This is a workaround due to inability to put shared PathDiagnosticPiece @@ -1962,64 +2059,66 @@ static std::unique_ptr generatePathDiagnosticForConsumer( if (!DeduplicationSet.insert(ID).second) continue; - if (AddPathEdges) - addEdgeToPath(PD->getActivePath(), PrevLoc, Note->getLocation()); - updateStackPiecesWithMessage(*Note, CallStack); - PD->getActivePath().push_front(Note); + if (PDC->shouldAddPathEdges()) + addEdgeToPath(Construct.getActivePath(), PrevLoc, Note->getLocation()); + updateStackPiecesWithMessage(*Note, Construct.CallStack); + Construct.getActivePath().push_front(Note); } } - if (AddPathEdges) { + if (PDC->shouldAddPathEdges()) { // Add an edge to the start of the function. // We'll prune it out later, but it helps make diagnostics more uniform. - const StackFrameContext *CalleeLC = PDB.LC->getStackFrame(); + const StackFrameContext *CalleeLC = + Construct.getLocationContextForActivePath()->getStackFrame(); const Decl *D = CalleeLC->getDecl(); - addEdgeToPath(PD->getActivePath(), PrevLoc, + addEdgeToPath(Construct.getActivePath(), PrevLoc, PathDiagnosticLocation::createBegin(D, SM)); } // Finally, prune the diagnostic path of uninteresting stuff. - if (!PD->path.empty()) { + if (!Construct.PD->path.empty()) { if (R->shouldPrunePath() && Opts.ShouldPrunePaths) { bool stillHasNotes = - removeUnneededCalls(PD->getMutablePieces(), R, LCM); + removeUnneededCalls(Construct, Construct.getMutablePieces(), R); assert(stillHasNotes); (void)stillHasNotes; } // Remove pop-up notes if needed. if (!Opts.ShouldAddPopUpNotes) - removePopUpNotes(PD->getMutablePieces()); + removePopUpNotes(Construct.getMutablePieces()); // Redirect all call pieces to have valid locations. - adjustCallLocations(PD->getMutablePieces()); - removePiecesWithInvalidLocations(PD->getMutablePieces()); + adjustCallLocations(Construct.getMutablePieces()); + removePiecesWithInvalidLocations(Construct.getMutablePieces()); - if (AddPathEdges) { + if (PDC->shouldAddPathEdges()) { // Reduce the number of edges from a very conservative set // to an aesthetically pleasing subset that conveys the // necessary information. OptimizedCallsSet OCS; - while (optimizeEdges(PD->getMutablePieces(), SM, OCS, LCM)) {} + while (optimizeEdges(Construct, Construct.getMutablePieces(), OCS)) { + } // Drop the very first function-entry edge. It's not really necessary // for top-level functions. - dropFunctionEntryEdge(PD->getMutablePieces(), LCM, SM); + dropFunctionEntryEdge(Construct, Construct.getMutablePieces()); } // Remove messages that are basically the same, and edges that may not // make sense. // We have to do this after edge optimization in the Extensive mode. - removeRedundantMsgs(PD->getMutablePieces()); - removeEdgesToDefaultInitializers(PD->getMutablePieces()); + removeRedundantMsgs(Construct.getMutablePieces()); + removeEdgesToDefaultInitializers(Construct.getMutablePieces()); } - if (GenerateDiagnostics && Opts.ShouldDisplayMacroExpansions) - CompactMacroExpandedPieces(PD->getMutablePieces(), SM); + if (Opts.ShouldDisplayMacroExpansions) + CompactMacroExpandedPieces(Construct.getMutablePieces(), SM); - return PD; + return std::move(Construct.PD); } @@ -2248,7 +2347,7 @@ namespace { class BugPathInfo { public: InterExplodedGraphMap MapToOriginNodes; - std::unique_ptr Path; + std::unique_ptr BugPath; BugReport *Report; const ExplodedNode *ErrorNode; }; @@ -2422,7 +2521,7 @@ BugPathInfo *BugPathGetter::getNextBugPath() { PriorityCompare(PriorityMap)); } - CurrentBugPath.Path = std::move(GNew); + CurrentBugPath.BugPath = std::move(GNew); return &CurrentBugPath; } @@ -2526,7 +2625,8 @@ static void CompactMacroExpandedPieces(PathPieces &path, static std::unique_ptr generateVisitorsDiagnostics(BugReport *R, const ExplodedNode *ErrorNode, BugReporterContext &BRC) { - auto Notes = llvm::make_unique(); + std::unique_ptr Notes = + llvm::make_unique(); BugReport::VisitorList visitors; // Run visitors on all nodes starting from the node *before* the last one. @@ -2577,35 +2677,10 @@ generateVisitorsDiagnostics(BugReport *R, const ExplodedNode *ErrorNode, return Notes; } -class ReportInfo { - BugPathInfo BugPath; - std::unique_ptr VisitorDiagnostics; - -public: - ReportInfo(BugPathInfo &&BugPath, std::unique_ptr V) - : BugPath(std::move(BugPath)), VisitorDiagnostics(std::move(V)) {} - - ReportInfo() = default; - - bool isValid() { return static_cast(VisitorDiagnostics); } - - BugReport *getBugReport() { return BugPath.Report; } - const ExplodedNode *getErrorNode() { return BugPath.ErrorNode; } - - InterExplodedGraphMap &getMapToOriginNodes() { - return BugPath.MapToOriginNodes; - } - - VisitorsDiagnosticsTy &getVisitorsDiagnostics() { - return *VisitorDiagnostics; - } -}; +Optional +PathDiagnosticBuilder::findValidReport(ArrayRef &bugReports, + GRBugReporter &Reporter) { -/// Find a non-invalidated report for a given equivalence class, and returns -/// the bug path associated with it together with a cache of visitors notes. -/// If none found, returns an isInvalid() object. -static ReportInfo findValidReport(ArrayRef &bugReports, - GRBugReporter &Reporter) { BugPathGetter BugGraph(&Reporter.getGraph(), bugReports); while (BugPathInfo *BugPath = BugGraph.getNextBugPath()) { @@ -2644,7 +2719,9 @@ static ReportInfo findValidReport(ArrayRef &bugReports, // Check if the bug is still valid if (R->isValid()) - return {std::move(*BugPath), std::move(visitorNotes)}; + return PathDiagnosticBuilder( + std::move(BRC), std::move(BugPath->BugPath), BugPath->Report, + BugPath->ErrorNode, std::move(visitorNotes)); } } @@ -2659,18 +2736,12 @@ GRBugReporter::generatePathDiagnostics( auto Out = llvm::make_unique(); - ReportInfo Info = findValidReport(bugReports, *this); + Optional PDB = + PathDiagnosticBuilder::findValidReport(bugReports, *this); - if (Info.isValid()) { - for (PathDiagnosticConsumer *PC : consumers) { - PathDiagnosticBuilder PDB(*this, Info.getBugReport(), - Info.getMapToOriginNodes(), PC); - std::unique_ptr PD = generatePathDiagnosticForConsumer( - PC->getGenerationScheme(), PDB, Info.getErrorNode(), - Info.getVisitorsDiagnostics()); - (*Out)[PC] = std::move(PD); - } - } + if (PDB) + for (PathDiagnosticConsumer *PC : consumers) + (*Out)[PC] = PDB->generate(PC); return Out; } diff --git a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp index ddd69f1085b4f..131678cb73e6d 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp @@ -264,8 +264,10 @@ PathDiagnosticPieceRef BugReporterVisitor::getEndPath(BugReporterContext &, void BugReporterVisitor::finalizeVisitor(BugReporterContext &, const ExplodedNode *, BugReport &) {} -PathDiagnosticPieceRef BugReporterVisitor::getDefaultEndPath( - BugReporterContext &BRC, const ExplodedNode *EndPathNode, BugReport &BR) { +PathDiagnosticPieceRef +BugReporterVisitor::getDefaultEndPath(const BugReporterContext &BRC, + const ExplodedNode *EndPathNode, + BugReport &BR) { PathDiagnosticLocation L = PathDiagnosticLocation::createEndOfPath( EndPathNode, BRC.getSourceManager()); From 6e8af13a52920f6f43f0351e84fab8f417454896 Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Tue, 13 Aug 2019 20:42:48 +0000 Subject: [PATCH 086/181] [analyzer][NFC] Address inlines of D65484 llvm-svn: 368745 (cherry picked from commit edb788592d561329d84f73d9ef0f15506faefe9e) --- clang/lib/StaticAnalyzer/Core/BugReporter.cpp | 75 ++++++++++--------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp index f2f7fddf86e6f..e0d4f805172e1 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp @@ -90,9 +90,10 @@ void BugReporterContext::anchor() {} namespace { -using StackDiagPair = +/// A (CallPiece, node assiciated with its CallEnter) pair. +using CallWithEntry = std::pair; -using StackDiagVector = SmallVector; +using CallWithEntryStack = SmallVector; /// Map from each node to the diagnostic pieces visitors emit for them. using VisitorsDiagnosticsTy = @@ -108,7 +109,7 @@ using LocationContextMap = /// A helper class that contains everything needed to construct a /// PathDiagnostic object. It does no much more then providing convenient /// getters and some well placed asserts for extra security. -class BugReportConstruct { +class PathDiagnosticConstruct { /// The consumer we're constructing the bug report for. const PathDiagnosticConsumer *Consumer; /// Our current position in the bug path, which is owned by @@ -124,7 +125,7 @@ class BugReportConstruct { /// We keep stack of calls to functions as we're ascending the bug path. /// TODO: PathDiagnostic has a stack doing the same thing, shouldn't we use /// that instead? - StackDiagVector CallStack; + CallWithEntryStack CallStack; InterestingExprs IE; /// The bug report we're constructing. For ease of use, this field is kept /// public, though some "shortcut" getters are provided for commonly used @@ -132,8 +133,8 @@ class BugReportConstruct { std::unique_ptr PD; public: - BugReportConstruct(const PathDiagnosticConsumer *PDC, - const ExplodedNode *ErrorNode, const BugReport *R); + PathDiagnosticConstruct(const PathDiagnosticConsumer *PDC, + const ExplodedNode *ErrorNode, const BugReport *R); /// \returns the location context associated with the current position in the /// bug path. @@ -234,29 +235,30 @@ class PathDiagnosticBuilder : public BugReporterContext { generate(const PathDiagnosticConsumer *PDC) const; private: - void generatePathDiagnosticsForNode(BugReportConstruct &C, + void generatePathDiagnosticsForNode(PathDiagnosticConstruct &C, PathDiagnosticLocation &PrevLoc) const; - void generateMinimalDiagForBlockEdge(BugReportConstruct &C, + void generateMinimalDiagForBlockEdge(PathDiagnosticConstruct &C, BlockEdge BE) const; PathDiagnosticPieceRef - generateDiagForGotoOP(const BugReportConstruct &C, const Stmt *S, + generateDiagForGotoOP(const PathDiagnosticConstruct &C, const Stmt *S, PathDiagnosticLocation &Start) const; PathDiagnosticPieceRef - generateDiagForSwitchOP(const BugReportConstruct &C, const CFGBlock *Dst, + generateDiagForSwitchOP(const PathDiagnosticConstruct &C, const CFGBlock *Dst, PathDiagnosticLocation &Start) const; - PathDiagnosticPieceRef generateDiagForBinaryOP(const BugReportConstruct &C, - const Stmt *T, - const CFGBlock *Src, - const CFGBlock *DstC) const; + PathDiagnosticPieceRef + generateDiagForBinaryOP(const PathDiagnosticConstruct &C, const Stmt *T, + const CFGBlock *Src, const CFGBlock *DstC) const; - PathDiagnosticLocation ExecutionContinues(const BugReportConstruct &C) const; + PathDiagnosticLocation + ExecutionContinues(const PathDiagnosticConstruct &C) const; - PathDiagnosticLocation ExecutionContinues(llvm::raw_string_ostream &os, - const BugReportConstruct &C) const; + PathDiagnosticLocation + ExecutionContinues(llvm::raw_string_ostream &os, + const PathDiagnosticConstruct &C) const; BugReport *getBugReport() const { return R; } }; @@ -364,8 +366,8 @@ static void removeRedundantMsgs(PathPieces &path) { /// Recursively scan through a path and prune out calls and macros pieces /// that aren't needed. Return true if afterwards the path contains /// "interesting stuff" which means it shouldn't be pruned from the parent path. -static bool removeUnneededCalls(const BugReportConstruct &C, PathPieces &pieces, - const BugReport *R, +static bool removeUnneededCalls(const PathDiagnosticConstruct &C, + PathPieces &pieces, const BugReport *R, bool IsInteresting = false) { bool containsSomethingInteresting = IsInteresting; const unsigned N = pieces.size(); @@ -519,8 +521,8 @@ static void removePiecesWithInvalidLocations(PathPieces &Pieces) { } } -PathDiagnosticLocation -PathDiagnosticBuilder::ExecutionContinues(const BugReportConstruct &C) const { +PathDiagnosticLocation PathDiagnosticBuilder::ExecutionContinues( + const PathDiagnosticConstruct &C) const { if (const Stmt *S = PathDiagnosticLocation::getNextStmt(C.getCurrentNode())) return PathDiagnosticLocation(S, getSourceManager(), C.getCurrLocationContext()); @@ -529,9 +531,8 @@ PathDiagnosticBuilder::ExecutionContinues(const BugReportConstruct &C) const { getSourceManager()); } -PathDiagnosticLocation -PathDiagnosticBuilder::ExecutionContinues(llvm::raw_string_ostream &os, - const BugReportConstruct &C) const { +PathDiagnosticLocation PathDiagnosticBuilder::ExecutionContinues( + llvm::raw_string_ostream &os, const PathDiagnosticConstruct &C) const { // Slow, but probably doesn't matter. if (os.str().empty()) os << ' '; @@ -665,7 +666,7 @@ getEnclosingStmtLocation(const Stmt *S, const LocationContext *LC, /// void *ptr = my_malloc(); // returned allocated memory /// } // leak static void updateStackPiecesWithMessage(PathDiagnosticPiece &P, - const StackDiagVector &CallStack) { + const CallWithEntryStack &CallStack) { if (auto *ep = dyn_cast(&P)) { if (ep->hasCallStackHint()) for (const auto &I : CallStack) { @@ -686,7 +687,7 @@ static void CompactMacroExpandedPieces(PathPieces &path, const SourceManager& SM); PathDiagnosticPieceRef PathDiagnosticBuilder::generateDiagForSwitchOP( - const BugReportConstruct &C, const CFGBlock *Dst, + const PathDiagnosticConstruct &C, const CFGBlock *Dst, PathDiagnosticLocation &Start) const { const SourceManager &SM = getSourceManager(); @@ -744,7 +745,7 @@ PathDiagnosticPieceRef PathDiagnosticBuilder::generateDiagForSwitchOP( } PathDiagnosticPieceRef PathDiagnosticBuilder::generateDiagForGotoOP( - const BugReportConstruct &C, const Stmt *S, + const PathDiagnosticConstruct &C, const Stmt *S, PathDiagnosticLocation &Start) const { std::string sbuf; llvm::raw_string_ostream os(sbuf); @@ -755,7 +756,7 @@ PathDiagnosticPieceRef PathDiagnosticBuilder::generateDiagForGotoOP( } PathDiagnosticPieceRef PathDiagnosticBuilder::generateDiagForBinaryOP( - const BugReportConstruct &C, const Stmt *T, const CFGBlock *Src, + const PathDiagnosticConstruct &C, const Stmt *T, const CFGBlock *Src, const CFGBlock *Dst) const { const SourceManager &SM = getSourceManager(); @@ -803,7 +804,7 @@ PathDiagnosticPieceRef PathDiagnosticBuilder::generateDiagForBinaryOP( } void PathDiagnosticBuilder::generateMinimalDiagForBlockEdge( - BugReportConstruct &C, BlockEdge BE) const { + PathDiagnosticConstruct &C, BlockEdge BE) const { const SourceManager &SM = getSourceManager(); const LocationContext *LC = C.getCurrLocationContext(); const CFGBlock *Src = BE.getSrc(); @@ -1135,7 +1136,7 @@ static std::unique_ptr findExecutedLines(const SourceManager &SM, const ExplodedNode *N); void PathDiagnosticBuilder::generatePathDiagnosticsForNode( - BugReportConstruct &C, PathDiagnosticLocation &PrevLoc) const { + PathDiagnosticConstruct &C, PathDiagnosticLocation &PrevLoc) const { ProgramPoint P = C.getCurrentNode()->getLocation(); const SourceManager &SM = getSourceManager(); @@ -1235,7 +1236,7 @@ void PathDiagnosticBuilder::generatePathDiagnosticsForNode( // Make the contents of the call the active path for now. C.PD->pushActivePath(&P->path); - C.CallStack.push_back(StackDiagPair(P, C.getCurrentNode())); + C.CallStack.push_back(CallWithEntry(P, C.getCurrentNode())); return; } @@ -1784,7 +1785,7 @@ static void removeIdenticalEvents(PathPieces &path) { } } -static bool optimizeEdges(const BugReportConstruct &C, PathPieces &path, +static bool optimizeEdges(const PathDiagnosticConstruct &C, PathPieces &path, OptimizedCallsSet &OCS) { bool hasChanges = false; const LocationContext *LC = C.getLocationContextFor(&path); @@ -1966,7 +1967,7 @@ static bool optimizeEdges(const BugReportConstruct &C, PathPieces &path, /// statement had an invalid source location), this function does nothing. // FIXME: We should just generate invalid edges anyway and have the optimizer // deal with them. -static void dropFunctionEntryEdge(const BugReportConstruct &C, +static void dropFunctionEntryEdge(const PathDiagnosticConstruct &C, PathPieces &Path) { const auto *FirstEdge = dyn_cast(Path.front().get()); @@ -1997,9 +1998,9 @@ static void updateExecutedLinesWithDiagnosticPieces(PathDiagnostic &PD) { } } -BugReportConstruct::BugReportConstruct(const PathDiagnosticConsumer *PDC, - const ExplodedNode *ErrorNode, - const BugReport *R) +PathDiagnosticConstruct::PathDiagnosticConstruct( + const PathDiagnosticConsumer *PDC, const ExplodedNode *ErrorNode, + const BugReport *R) : Consumer(PDC), CurrentNode(ErrorNode), SM(CurrentNode->getCodeDecl().getASTContext().getSourceManager()), PD(generateEmptyDiagnosticForReport(R, getSourceManager())) { @@ -2020,7 +2021,7 @@ PathDiagnosticBuilder::generate(const PathDiagnosticConsumer *PDC) const { if (!PDC->shouldGenerateDiagnostics()) return generateEmptyDiagnosticForReport(R, getSourceManager()); - BugReportConstruct Construct(PDC, ErrorNode, R); + PathDiagnosticConstruct Construct(PDC, ErrorNode, R); const SourceManager &SM = getSourceManager(); const BugReport *R = getBugReport(); From b68349ea7174f59e945e4db46a87d49b595dc523 Mon Sep 17 00:00:00 2001 From: Michael Liao Date: Tue, 13 Aug 2019 21:26:42 +0000 Subject: [PATCH 087/181] Remove the extra `;`. llvm-svn: 368748 (cherry picked from commit 44e6c6bd2f05daaa14388e4935d9a53e06a30d76) --- .../include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h index f73fd29805314..7fb82a58a9eab 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h @@ -515,7 +515,7 @@ class GRBugReporter : public BugReporter { GRBugReporter(BugReporterData& d, ExprEngine& eng) : BugReporter(d, GRBugReporterKind), Eng(eng) {} - ~GRBugReporter() override = default;; + ~GRBugReporter() override = default; /// getGraph - Get the exploded graph created by the analysis engine /// for the analyzed method or function. From d659255dfdbca9d9c327c1466fe32f1a51bed86a Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Tue, 13 Aug 2019 21:48:17 +0000 Subject: [PATCH 088/181] [analyzer][NFC] Refactoring BugReporter.cpp P6.: Completely get rid of interestingness propagation Apparently this does literally nothing. When you think about this, it makes sense. If something is really important, we're tracking it anyways, and that system is sophisticated enough to mark actually interesting statements as such. I wouldn't say that it's even likely that subexpressions are also interesting (array[10 - x + x]), so I guess even if this produced any effects, its probably undesirable. Differential Revision: https://reviews.llvm.org/D65487 llvm-svn: 368752 (cherry picked from commit 6c1b19ac9e85aff6d6cba14d9c1e29c11c1a0372) --- clang/lib/StaticAnalyzer/Core/BugReporter.cpp | 97 ------------------- 1 file changed, 97 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp index e0d4f805172e1..3e607594fda24 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp @@ -99,8 +99,6 @@ using CallWithEntryStack = SmallVector; using VisitorsDiagnosticsTy = llvm::DenseMap>; -using InterestingExprs = llvm::DenseSet; - /// A map from PathDiagnosticPiece to the LocationContext of the inlined /// function call it represents. using LocationContextMap = @@ -126,7 +124,6 @@ class PathDiagnosticConstruct { /// TODO: PathDiagnostic has a stack doing the same thing, shouldn't we use /// that instead? CallWithEntryStack CallStack; - InterestingExprs IE; /// The bug report we're constructing. For ease of use, this field is kept /// public, though some "shortcut" getters are provided for commonly used /// methods of PathDiagnostic. @@ -943,74 +940,6 @@ void PathDiagnosticBuilder::generateMinimalDiagForBlockEdge( } } -// Cone-of-influence: support the reverse propagation of "interesting" symbols -// and values by tracing interesting calculations backwards through evaluated -// expressions along a path. This is probably overly complicated, but the idea -// is that if an expression computed an "interesting" value, the child -// expressions are also likely to be "interesting" as well (which then -// propagates to the values they in turn compute). This reverse propagation -// is needed to track interesting correlations across function call boundaries, -// where formal arguments bind to actual arguments, etc. This is also needed -// because the constraint solver sometimes simplifies certain symbolic values -// into constants when appropriate, and this complicates reasoning about -// interesting values. - -static void reversePropagateIntererstingSymbols(BugReport &R, - InterestingExprs &IE, - const ProgramState *State, - const Expr *Ex, - const LocationContext *LCtx) { - SVal V = State->getSVal(Ex, LCtx); - if (!(R.isInteresting(V) || IE.count(Ex))) - return; - - switch (Ex->getStmtClass()) { - default: - if (!isa(Ex)) - break; - LLVM_FALLTHROUGH; - case Stmt::BinaryOperatorClass: - case Stmt::UnaryOperatorClass: { - for (const Stmt *SubStmt : Ex->children()) { - if (const auto *child = dyn_cast_or_null(SubStmt)) { - IE.insert(child); - SVal ChildV = State->getSVal(child, LCtx); - R.markInteresting(ChildV); - } - } - break; - } - } - - R.markInteresting(V); -} - -static void reversePropagateInterestingSymbols(BugReport &R, - InterestingExprs &IE, - const ProgramState *State, - const LocationContext *CalleeCtx) -{ - // FIXME: Handle non-CallExpr-based CallEvents. - const StackFrameContext *Callee = CalleeCtx->getStackFrame(); - const Stmt *CallSite = Callee->getCallSite(); - if (const auto *CE = dyn_cast_or_null(CallSite)) { - if (const auto *FD = dyn_cast(CalleeCtx->getDecl())) { - FunctionDecl::param_const_iterator PI = FD->param_begin(), - PE = FD->param_end(); - CallExpr::const_arg_iterator AI = CE->arg_begin(), AE = CE->arg_end(); - for (; AI != AE && PI != PE; ++AI, ++PI) { - if (const Expr *ArgE = *AI) { - if (const ParmVarDecl *PD = *PI) { - Loc LV = State->getLValue(PD, CalleeCtx); - if (R.isInteresting(LV) || R.isInteresting(State->getRawSVal(LV))) - IE.insert(ArgE); - } - } - } - } - } -} - //===----------------------------------------------------------------------===// // Functions for determining if a loop was executed 0 times. //===----------------------------------------------------------------------===// @@ -1219,13 +1148,6 @@ void PathDiagnosticBuilder::generatePathDiagnosticsForNode( C.updateLocCtxMap(&Call->path, CE->getCalleeContext()); if (C.shouldAddPathEdges()) { - const Stmt *S = CE->getCalleeContext()->getCallSite(); - // Propagate the interesting symbols accordingly. - if (const auto *Ex = dyn_cast_or_null(S)) { - reversePropagateIntererstingSymbols( - *getBugReport(), C.IE, C.getCurrentNode()->getState().get(), Ex, - C.getCurrLocationContext()); - } // Add the edge to the return site. addEdgeToPath(C.getActivePath(), PrevLoc, Call->callReturn); PrevLoc.invalidate(); @@ -1244,13 +1166,6 @@ void PathDiagnosticBuilder::generatePathDiagnosticsForNode( if (!C.shouldAddPathEdges()) return; - // For expressions, make sure we propagate the - // interesting symbols correctly. - if (const Expr *Ex = PS->getStmtAs()) - reversePropagateIntererstingSymbols(*getBugReport(), C.IE, - C.getCurrentNode()->getState().get(), - Ex, C.getCurrLocationContext()); - // Add an edge. If this is an ObjCForCollectionStmt do // not add an edge here as it appears in the CFG both // as a terminator and as a terminator condition. @@ -1267,18 +1182,6 @@ void PathDiagnosticBuilder::generatePathDiagnosticsForNode( return; } - // Does this represent entering a call? If so, look at propagating - // interesting symbols across call boundaries. - if (const ExplodedNode *NextNode = C.getCurrentNode()->getFirstPred()) { - const LocationContext *CallerCtx = NextNode->getLocationContext(); - const LocationContext *CalleeCtx = C.getCurrLocationContext(); - if (CallerCtx != CalleeCtx && C.shouldAddPathEdges()) { - reversePropagateInterestingSymbols(*getBugReport(), C.IE, - C.getCurrentNode()->getState().get(), - CalleeCtx); - } - } - // Are we jumping to the head of a loop? Add a special diagnostic. if (const Stmt *Loop = BE->getSrc()->getLoopTarget()) { PathDiagnosticLocation L(Loop, SM, C.getCurrLocationContext()); From 8e595b69e8e37b8041b282efcc1c9a2de6b0c8cf Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Tue, 13 Aug 2019 22:03:08 +0000 Subject: [PATCH 089/181] [analyzer][NFC] Make sure that the BugReport is not modified during the construction of non-visitor pieces I feel this is kinda important, because in a followup patch I'm adding different kinds of interestingness, and propagating the correct kind in BugReporter.cpp is just one less thing to worry about. Differential Revision: https://reviews.llvm.org/D65578 llvm-svn: 368755 (cherry picked from commit e1117addd664c3b52b4a8b7be8bdd678d42f264c) --- .../Core/BugReporter/BugReporter.h | 19 +++++++++++------ .../Core/BugReporter/BugReporterVisitors.h | 2 +- .../RetainCountDiagnostics.h | 2 +- clang/lib/StaticAnalyzer/Core/BugReporter.cpp | 21 +++++++++++-------- .../Core/BugReporterVisitors.cpp | 2 +- 5 files changed, 28 insertions(+), 18 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h index 7fb82a58a9eab..c3d7ba3120fdd 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h @@ -105,6 +105,7 @@ class BugReport : public llvm::ilist_node { const ExplodedNode *ErrorNode = nullptr; SmallVector Ranges; + const SourceRange ErrorNodeRange; ExtraTextList ExtraText; NoteList Notes; @@ -155,16 +156,22 @@ class BugReport : public llvm::ilist_node { llvm::SmallSet TrackedConditions; public: - BugReport(const BugType& bt, StringRef desc, const ExplodedNode *errornode) - : BT(bt), Description(desc), ErrorNode(errornode) {} + BugReport(const BugType &bt, StringRef desc, const ExplodedNode *errornode) + : BT(bt), Description(desc), ErrorNode(errornode), + ErrorNodeRange(getStmt() ? getStmt()->getSourceRange() + : SourceRange()) {} - BugReport(const BugType& bt, StringRef shortDesc, StringRef desc, + BugReport(const BugType &bt, StringRef shortDesc, StringRef desc, const ExplodedNode *errornode) : BT(bt), ShortDescription(shortDesc), Description(desc), - ErrorNode(errornode) {} + ErrorNode(errornode), + ErrorNodeRange(getStmt() ? getStmt()->getSourceRange() + : SourceRange()) {} BugReport(const BugType &bt, StringRef desc, PathDiagnosticLocation l) - : BT(bt), Description(desc), Location(l) {} + : BT(bt), Description(desc), Location(l), + ErrorNodeRange(getStmt() ? getStmt()->getSourceRange() + : SourceRange()) {} /// Create a BugReport with a custom uniqueing location. /// @@ -323,7 +330,7 @@ class BugReport : public llvm::ilist_node { } /// Get the SourceRanges associated with the report. - virtual llvm::iterator_range getRanges(); + virtual llvm::iterator_range getRanges() const; /// Add custom or predefined bug report visitors to this report. /// diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h index 536ed757e6aba..b2927a08b6145 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h @@ -82,7 +82,7 @@ class BugReporterVisitor : public llvm::FoldingSetNode { /// Generates the default final diagnostic piece. static PathDiagnosticPieceRef getDefaultEndPath(const BugReporterContext &BRC, const ExplodedNode *N, - BugReport &BR); + const BugReport &BR); }; /// Finds last store into the given region, diff --git a/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.h b/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.h index ef3c75f87af5d..6e2a613aed629 100644 --- a/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.h +++ b/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.h @@ -67,7 +67,7 @@ class RefCountReport : public BugReport { ExplodedNode *n, SymbolRef sym, StringRef endText); - llvm::iterator_range getRanges() override { + llvm::iterator_range getRanges() const override { if (!isLeak) return BugReport::getRanges(); return llvm::make_range(ranges_iterator(), ranges_iterator()); diff --git a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp index 3e607594fda24..86b688fa96dba 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp @@ -192,13 +192,17 @@ class PathDiagnosticConstruct { }; /// Contains every contextual information needed for constructing a -/// PathDiagnostic object for a given bug report. This class (and aside from -/// some caching BugReport does in the background) and its fields are immutable, -/// and passes a BugReportConstruct object around during the construction. +/// PathDiagnostic object for a given bug report. This class and its fields are +/// immutable, and passes a BugReportConstruct object around during the +/// construction. class PathDiagnosticBuilder : public BugReporterContext { /// A linear path from the error node to the root. std::unique_ptr BugPath; - BugReport *R; + /// The bug report we're describing. Visitors create their diagnostics with + /// them being the last entities being able to modify it (for example, + /// changing interestingness here would cause inconsistencies as to how this + /// file and visitors construct diagnostics), hence its const. + const BugReport *R; /// The leaf of the bug path. This isn't the same as the bug reports error /// node, which refers to the *original* graph, not the bug path. const ExplodedNode *const ErrorNode; @@ -257,7 +261,7 @@ class PathDiagnosticBuilder : public BugReporterContext { ExecutionContinues(llvm::raw_string_ostream &os, const PathDiagnosticConstruct &C) const; - BugReport *getBugReport() const { return R; } + const BugReport *getBugReport() const { return R; } }; } // namespace @@ -2172,14 +2176,13 @@ const Stmt *BugReport::getStmt() const { return S; } -llvm::iterator_range BugReport::getRanges() { +llvm::iterator_range BugReport::getRanges() const { // If no custom ranges, add the range of the statement corresponding to // the error node. if (Ranges.empty()) { if (const auto *E = dyn_cast_or_null(getStmt())) - addRange(E->getSourceRange()); - else - return llvm::make_range(ranges_iterator(), ranges_iterator()); + return llvm::make_range(&ErrorNodeRange, &ErrorNodeRange + 1); + return llvm::make_range(ranges_iterator(), ranges_iterator()); } // User-specified absence of range info. diff --git a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp index 131678cb73e6d..02bdf86f57ff2 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp @@ -267,7 +267,7 @@ void BugReporterVisitor::finalizeVisitor(BugReporterContext &, PathDiagnosticPieceRef BugReporterVisitor::getDefaultEndPath(const BugReporterContext &BRC, const ExplodedNode *EndPathNode, - BugReport &BR) { + const BugReport &BR) { PathDiagnosticLocation L = PathDiagnosticLocation::createEndOfPath( EndPathNode, BRC.getSourceManager()); From 5d52cad9675d56095ad5caa09408a3a747d87f97 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Tue, 13 Aug 2019 23:04:44 +0000 Subject: [PATCH 090/181] [analyzer] Disable the checker-plugins test on Darwin. Fixes a buildbot. llvm-svn: 368765 (cherry picked from commit f5a60e590f05981d24a50ff21f611c3ba711acaa) --- clang/test/Analysis/checker-plugins.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/clang/test/Analysis/checker-plugins.c b/clang/test/Analysis/checker-plugins.c index b5444fa6cbf7f..fbc9c9bd1c22b 100644 --- a/clang/test/Analysis/checker-plugins.c +++ b/clang/test/Analysis/checker-plugins.c @@ -1,5 +1,9 @@ // REQUIRES: plugins +// FIXME: This test fails on clang-stage2-cmake-RgSan, +// see also https://reviews.llvm.org/D62445#1613268 +// UNSUPPORTED: darwin + // RUN: %clang_analyze_cc1 -verify %s \ // RUN: -load %llvmshlibdir/SampleAnalyzerPlugin%pluginext \ // RUN: -analyzer-checker='example.MainCallChecker' From 082a04ed1a91f7a48a81eb3cda5cd2bf15953c51 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Tue, 13 Aug 2019 23:04:47 +0000 Subject: [PATCH 091/181] [analyzer] exploded-graph-rewriter: Open the converted graph immediately. Change the default behavior: the tool no longer dumps the rewritten .dot file to stdout, but instead it automatically converts it into an .html file (which essentially wraps an .svg file) and immediately opens it with the default web browser. This means that the tool should now be fairly easy to use: $ exploded-graph-rewriter.py /tmp/ExprEngine.dot The benefits of wrapping the .svg file into an .html file are: - It'll open in a web browser, which is the intended behavior. An .svg file would be open with an image viewer/editor instead. - It avoids the white background around the otherwise dark svg area in dark mode. The feature can be turned off by passing a flag '--rewrite-only'. The LIT substitution is updated to enforce the old mode because we don't want web browsers opening on our buildbots. Differential Revision: https://reviews.llvm.org/D65250 llvm-svn: 368766 (cherry picked from commit e9e36354531e0dde20fbd7dac9749244a52e61be) --- .../exploded-graph-rewriter/lit.local.cfg | 2 +- .../utils/analyzer/exploded-graph-rewriter.py | 69 +++++++++++++++++-- 2 files changed, 63 insertions(+), 8 deletions(-) diff --git a/clang/test/Analysis/exploded-graph-rewriter/lit.local.cfg b/clang/test/Analysis/exploded-graph-rewriter/lit.local.cfg index dfeb0a86c4f3c..87ce52cc53e0d 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/lit.local.cfg +++ b/clang/test/Analysis/exploded-graph-rewriter/lit.local.cfg @@ -8,7 +8,7 @@ use_lit_shell = os.environ.get("LIT_USE_INTERNAL_SHELL") config.test_format = lit.formats.ShTest(use_lit_shell == "0") config.substitutions.append(('%exploded_graph_rewriter', - '\'%s\' %s' % ( + '\'%s\' %s --dump-dot-only' % ( config.python_executable, lit.util.which('exploded-graph-rewriter.py', os.path.join( diff --git a/clang/utils/analyzer/exploded-graph-rewriter.py b/clang/utils/analyzer/exploded-graph-rewriter.py index 5ce56d61c0dcf..eee09f37bd285 100755 --- a/clang/utils/analyzer/exploded-graph-rewriter.py +++ b/clang/utils/analyzer/exploded-graph-rewriter.py @@ -394,16 +394,25 @@ def add_raw_line(self, raw_line): # A visitor that dumps the ExplodedGraph into a DOT file with fancy HTML-based # syntax highlighing. class DotDumpVisitor(object): - def __init__(self, do_diffs, dark_mode, gray_mode, topo_mode): + def __init__(self, do_diffs, dark_mode, gray_mode, + topo_mode, dump_dot_only): super(DotDumpVisitor, self).__init__() self._do_diffs = do_diffs self._dark_mode = dark_mode self._gray_mode = gray_mode self._topo_mode = topo_mode + self._dump_dot_only = dump_dot_only + self._output = [] - @staticmethod - def _dump_raw(s): - print(s, end='') + def _dump_raw(self, s): + if self._dump_dot_only: + print(s, end='') + else: + self._output.append(s) + + def output(self): + assert not self._dump_dot_only + return ''.join(self._output) def _dump(self, s): s = s.replace('&', '&') \ @@ -812,6 +821,44 @@ def visit_edge(self, pred, succ): def visit_end_of_graph(self): self._dump_raw('}\n') + if not self._dump_dot_only: + import sys + import tempfile + + def write_temp_file(suffix, data): + fd, filename = tempfile.mkstemp(suffix=suffix) + print('Writing "%s"...' % filename) + with os.fdopen(fd, 'w') as fp: + fp.write(data) + print('Done! Please remember to remove the file.') + return filename + + try: + import graphviz + except ImportError: + # The fallback behavior if graphviz is not installed! + print('Python graphviz not found. Please invoke') + print(' $ pip install graphviz') + print('in order to enable automatic conversion to HTML.') + print() + print('You may also convert DOT to SVG manually via') + print(' $ dot -Tsvg input.dot -o output.svg') + print() + write_temp_file('.dot', self.output()) + return + + svg = graphviz.pipe('dot', 'svg', self.output()) + + filename = write_temp_file( + '.html', '%s' % ( + '#1a1a1a' if self._dark_mode else 'white', svg)) + if sys.platform == 'win32': + os.startfile(filename) + elif sys.platform == 'darwin': + os.system('open "%s"' % filename) + else: + os.system('xdg-open "%s"' % filename) + #===-----------------------------------------------------------------------===# # Explorers know how to traverse the ExplodedGraph in a certain order. @@ -874,8 +921,10 @@ def explore(self, graph, visitor): def main(): - parser = argparse.ArgumentParser() - parser.add_argument('filename', type=str) + parser = argparse.ArgumentParser( + description='Display and manipulate Exploded Graph dumps.') + parser.add_argument('filename', type=str, + help='the .dot file produced by the Static Analyzer') parser.add_argument('-v', '--verbose', action='store_const', dest='loglevel', const=logging.DEBUG, default=logging.WARNING, @@ -897,6 +946,11 @@ def main(): parser.add_argument('--gray', action='store_const', dest='gray', const=True, default=False, help='black-and-white mode') + parser.add_argument('--dump-dot-only', action='store_const', + dest='dump_dot_only', const=True, default=False, + help='instead of writing an HTML file and immediately ' + 'displaying it, dump the rewritten dot file ' + 'to stdout') args = parser.parse_args() logging.basicConfig(level=args.loglevel) @@ -907,7 +961,8 @@ def main(): graph.add_raw_line(raw_line) explorer = SinglePathExplorer() if args.single_path else BasicExplorer() - visitor = DotDumpVisitor(args.diff, args.dark, args.gray, args.topology) + visitor = DotDumpVisitor(args.diff, args.dark, args.gray, args.topology, + args.dump_dot_only) explorer.explore(graph, visitor) From 6fb513cfecaa4bb527bd69ab57563553e1149ff6 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Tue, 13 Aug 2019 23:04:50 +0000 Subject: [PATCH 092/181] [analyzer] exploded-graph-rewriter: NFC: Refactor explorers into trimmers. Explorers aren't the right abstraction. For the purposes of displaying svg files we don't care in which order do we explore the nodes. We may care about this for other analyses, but we're not there yet. The function of cutting out chunks of the graph is performed poorly by the explorers, because querying predecessors/successors on the explored nodes yields original successors/predecessors even if they aren't being explored. Introduce a new entity, "trimmers", that do one thing but to it right: cut out chunks of the graph. Trimmers mutate the graph, so stale edges aren't even visible to their consumers in the pipeline. Additionally, trimmers are intrinsically composable: multiple trimmers can be applied to the graph sequentially. Refactor the single-path explorer into the single-path trimmer. Rename the test file for consistency. Differential Revision: https://reviews.llvm.org/D65344 llvm-svn: 368767 (cherry picked from commit 0b26891f3f2b75dcc8f1a0f3a34342ce27570a2c) --- .../{explorers.dot => trimmers.dot} | 0 .../utils/analyzer/exploded-graph-rewriter.py | 59 +++++++++++-------- 2 files changed, 33 insertions(+), 26 deletions(-) rename clang/test/Analysis/exploded-graph-rewriter/{explorers.dot => trimmers.dot} (100%) diff --git a/clang/test/Analysis/exploded-graph-rewriter/explorers.dot b/clang/test/Analysis/exploded-graph-rewriter/trimmers.dot similarity index 100% rename from clang/test/Analysis/exploded-graph-rewriter/explorers.dot rename to clang/test/Analysis/exploded-graph-rewriter/trimmers.dot diff --git a/clang/utils/analyzer/exploded-graph-rewriter.py b/clang/utils/analyzer/exploded-graph-rewriter.py index eee09f37bd285..3fa8ba0c9a2d4 100755 --- a/clang/utils/analyzer/exploded-graph-rewriter.py +++ b/clang/utils/analyzer/exploded-graph-rewriter.py @@ -882,37 +882,36 @@ def explore(self, graph, visitor): visitor.visit_end_of_graph() -# SinglePathExplorer traverses only a single path - the leftmost path -# from the root. Useful when the trimmed graph is still too large -# due to a large amount of equivalent reports. -class SinglePathExplorer(object): - def __init__(self): - super(SinglePathExplorer, self).__init__() +#===-----------------------------------------------------------------------===# +# Trimmers cut out parts of the ExplodedGraph so that to focus on other parts. +# Trimmers can be combined together by applying them sequentially. +#===-----------------------------------------------------------------------===# - def explore(self, graph, visitor): - visitor.visit_begin_graph(graph) - # Keep track of visited nodes in order to avoid loops. - visited = set() +# SinglePathTrimmer keeps only a single path - the leftmost path from the root. +# Useful when the trimmed graph is still too large. +class SinglePathTrimmer(object): + def __init__(self): + super(SinglePathTrimmer, self).__init__() + + def trim(self, graph): + visited_nodes = set() node_id = graph.root_id while True: - visited.add(node_id) + visited_nodes.add(node_id) node = graph.nodes[node_id] - logging.debug('Visiting ' + node_id) - visitor.visit_node(node) - if len(node.successors) == 0: - break - - succ_id = node.successors[0] - succ = graph.nodes[succ_id] - logging.debug('Visiting edge: %s -> %s ' % (node_id, succ_id)) - visitor.visit_edge(node, succ) - if succ_id in visited: + if len(node.successors) > 0: + succ_id = node.successors[0] + succ = graph.nodes[succ_id] + node.successors = [succ_id] + succ.predecessors = [node_id] + if succ_id in visited_nodes: + break + node_id = succ_id + else: break - - node_id = succ_id - - visitor.visit_end_of_graph() + graph.nodes = {node_id: graph.nodes[node_id] + for node_id in visited_nodes} #===-----------------------------------------------------------------------===# @@ -960,10 +959,18 @@ def main(): raw_line = raw_line.strip() graph.add_raw_line(raw_line) - explorer = SinglePathExplorer() if args.single_path else BasicExplorer() + trimmers = [] + if args.single_path: + trimmers.append(SinglePathTrimmer()) + + explorer = BasicExplorer() + visitor = DotDumpVisitor(args.diff, args.dark, args.gray, args.topology, args.dump_dot_only) + for trimmer in trimmers: + trimmer.trim(graph) + explorer.explore(graph, visitor) From 58304c8fab19ff8cdc91793e71bcaa41481efd1b Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Tue, 13 Aug 2019 23:04:53 +0000 Subject: [PATCH 093/181] [analyzer] exploded-graph-rewriter: Implement manual graph trimming. When -trim-egraph is unavailable (say, when you're debugging a crash on a real-world code that takes too long to reduce), it makes sense to view the untrimmed graph up to the crashing node's predecessor, then dump the ID (or a pointer) of the node in the attached debugger, and then trim the dumped graph in order to keep only paths from the root to the node. The newly added --to flag does exactly that: $ exploded-graph-rewriter.py ExprEngine.dot --to 0x12229acd0 Multiple nodes can be specified. Stable IDs of nodes can be used instead of pointers. Differential Revision: https://reviews.llvm.org/D65345 llvm-svn: 368768 (cherry picked from commit 9289681ea3d000e7d46b2c69bac26b45f960c4dd) --- .../exploded-graph-rewriter/trimmers.dot | 32 +++++++---- .../utils/analyzer/exploded-graph-rewriter.py | 54 +++++++++++++++++++ 2 files changed, 75 insertions(+), 11 deletions(-) diff --git a/clang/test/Analysis/exploded-graph-rewriter/trimmers.dot b/clang/test/Analysis/exploded-graph-rewriter/trimmers.dot index 226c63911c5a5..8bdef649e0df6 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/trimmers.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/trimmers.dot @@ -1,7 +1,17 @@ // RUN: %exploded_graph_rewriter %s \ -// RUN: | FileCheck %s -check-prefixes=CHECK,BASIC +// RUN: | FileCheck %s -check-prefixes=ONE,TWO,THREE,FOUR // RUN: %exploded_graph_rewriter -s %s \ -// RUN: | FileCheck %s -check-prefixes=CHECK,SINGLE +// RUN: | FileCheck %s -check-prefixes=ONE,TWO,NOTHREE,FOUR +// RUN: %exploded_graph_rewriter --to=0x2 %s \ +// RUN: | FileCheck %s -check-prefixes=ONE,TWO,NOTHREE,NOFOUR +// RUN: %exploded_graph_rewriter --to 2 %s \ +// RUN: | FileCheck %s -check-prefixes=ONE,TWO,NOTHREE,NOFOUR +// RUN: %exploded_graph_rewriter --to 2,3 %s \ +// RUN: | FileCheck %s -check-prefixes=ONE,TWO,THREE,NOFOUR +// RUN: %exploded_graph_rewriter --to 4 %s \ +// RUN: | FileCheck %s -check-prefixes=ONE,TWO,THREE,FOUR +// RUN: %exploded_graph_rewriter --to 4 -s %s \ +// RUN: | FileCheck %s -check-prefixes=ONE,TWO,NOTHREE,FOUR // FIXME: Substitution doesn't seem to work on Windows. // UNSUPPORTED: system-windows @@ -22,16 +32,16 @@ Node0x4 [shape=record,label= "{{ "node_id": 4, "pointer": "0x4", "has_report": false, "is_sink": false, "program_state": null, "program_points": []}\l}"]; -// CHECK: Node0x1 -> Node0x2; Node0x1 -> Node0x2; - -// BASIC: Node0x1 -> Node0x3; -// SINGLE-NOT: Node0x1 -> Node0x3; Node0x1 -> Node0x3; - -// CHECK: Node0x2 -> Node0x4; Node0x2 -> Node0x4; - -// BASIC: Node0x3 -> Node0x4; -// SINGLE-NOT: Node0x3 -> Node0x4; Node0x3 -> Node0x4; + +// ONE: Node0x1 +// NOTONE-NOT: Node0x1 +// TWO: Node0x2 +// NOTTWO-NOT: Node0x2 +// THREE: Node0x3 +// NOTTHREE-NOT: Node0x3 +// FOUR: Node0x4 +// NOTFOUR-NOT: Node0x4 diff --git a/clang/utils/analyzer/exploded-graph-rewriter.py b/clang/utils/analyzer/exploded-graph-rewriter.py index 3fa8ba0c9a2d4..d612cfc3ba73f 100755 --- a/clang/utils/analyzer/exploded-graph-rewriter.py +++ b/clang/utils/analyzer/exploded-graph-rewriter.py @@ -914,6 +914,52 @@ def trim(self, graph): for node_id in visited_nodes} +# TargetedTrimmer keeps paths that lead to specific nodes and discards all +# other paths. Useful when you cannot use -trim-egraph (e.g. when debugging +# a crash). +class TargetedTrimmer(object): + def __init__(self, target_nodes): + super(TargetedTrimmer, self).__init__() + self._target_nodes = target_nodes + + @staticmethod + def parse_target_node(node, graph): + if node.startswith('0x'): + ret = 'Node' + node + assert ret in graph.nodes + return ret + else: + for other_id in graph.nodes: + other = graph.nodes[other_id] + if other.node_id == int(node): + return other_id + + @staticmethod + def parse_target_nodes(target_nodes, graph): + return [TargetedTrimmer.parse_target_node(node, graph) + for node in target_nodes.split(',')] + + def trim(self, graph): + queue = self._target_nodes + visited_nodes = set() + + while len(queue) > 0: + node_id = queue.pop() + visited_nodes.add(node_id) + node = graph.nodes[node_id] + for pred_id in node.predecessors: + if pred_id not in visited_nodes: + queue.append(pred_id) + graph.nodes = {node_id: graph.nodes[node_id] + for node_id in visited_nodes} + for node_id in graph.nodes: + node = graph.nodes[node_id] + node.successors = [succ_id for succ_id in node.successors + if succ_id in visited_nodes] + node.predecessors = [succ_id for succ_id in node.predecessors + if succ_id in visited_nodes] + + #===-----------------------------------------------------------------------===# # The entry point to the script. #===-----------------------------------------------------------------------===# @@ -939,6 +985,11 @@ def main(): help='only display the leftmost path in the graph ' '(useful for trimmed graphs that still ' 'branch too much)') + parser.add_argument('--to', type=str, default=None, + help='only display execution paths from the root ' + 'to the given comma-separated list of nodes ' + 'identified by a pointer or a stable ID; ' + 'compatible with --single-path') parser.add_argument('--dark', action='store_const', dest='dark', const=True, default=False, help='dark mode') @@ -960,6 +1011,9 @@ def main(): graph.add_raw_line(raw_line) trimmers = [] + if args.to is not None: + trimmers.append(TargetedTrimmer( + TargetedTrimmer.parse_target_nodes(args.to, graph))) if args.single_path: trimmers.append(SinglePathTrimmer()) From 9daf5aae2a7bc19f25e29cabdf38e9664829dfcd Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Tue, 13 Aug 2019 23:04:56 +0000 Subject: [PATCH 094/181] [analyzer] exploded-graph-rewriter: Implement displaying Store pointers. They're useful when trying to understand what's going on inside your LazyCompoundValues. Differential Revision: https://reviews.llvm.org/D65427 llvm-svn: 368769 (cherry picked from commit daf41722bd54e03cc5a367d35b2208ff652f553d) --- clang/test/Analysis/exploded-graph-rewriter/escapes.c | 2 +- clang/test/Analysis/exploded-graph-rewriter/store.dot | 1 + clang/utils/analyzer/exploded-graph-rewriter.py | 4 ++++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/clang/test/Analysis/exploded-graph-rewriter/escapes.c b/clang/test/Analysis/exploded-graph-rewriter/escapes.c index d35e41ae28d4e..d93baedf42bc2 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/escapes.c +++ b/clang/test/Analysis/exploded-graph-rewriter/escapes.c @@ -8,7 +8,7 @@ // UNSUPPORTED: system-windows void escapes() { - // CHECK: Store: + // CHECK: Store: (0x{{[0-9a-f]*}}) // CHECK-SAME: foo0 // CHECK-SAME: &Element\{"foo",0 S64b,char\} // CHECK: Environment: diff --git a/clang/test/Analysis/exploded-graph-rewriter/store.dot b/clang/test/Analysis/exploded-graph-rewriter/store.dot index d47a02295e493..7267dd43e8984 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/store.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/store.dot @@ -4,6 +4,7 @@ // UNSUPPORTED: system-windows // CHECK: Store: +// CHECK-SAME: (0x2) // CHECK-SAME: // CHECK-SAME: // CHECK-SAME: -// LIGHT-SAME: diff --git a/clang/utils/analyzer/exploded-graph-rewriter.py b/clang/utils/analyzer/exploded-graph-rewriter.py index ced5c36bab5e2..05b01b3f9502c 100755 --- a/clang/utils/analyzer/exploded-graph-rewriter.py +++ b/clang/utils/analyzer/exploded-graph-rewriter.py @@ -784,7 +784,7 @@ def visit_node(self, node): self._dump('' - % ("gray20" if self._dark_mode else "gray", + % ("gray20" if self._dark_mode else "gray70", node.node_id, node.ptr, node.state.state_id if node.state is not None else 'Unspecified')) if node.has_report: From 0e07329b303e82d6bddd5a9a03699d7e0fd1783b Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Thu, 17 Oct 2019 23:10:05 +0000 Subject: [PATCH 170/181] [analyzer] Display cast kinds in program point dumps. Because cast expressions have their own hierarchy, it's extremely useful to have some information about what kind of casts are we dealing with. llvm-svn: 375185 (cherry picked from commit d325196f19bfecff59252f3d214278fb6ee4ad61) --- clang/lib/Analysis/ProgramPoint.cpp | 6 ++- .../program_points.dot | 48 +++++++++++++++++++ .../utils/analyzer/exploded-graph-rewriter.py | 6 ++- 3 files changed, 58 insertions(+), 2 deletions(-) diff --git a/clang/lib/Analysis/ProgramPoint.cpp b/clang/lib/Analysis/ProgramPoint.cpp index 97e90965d007a..0783fbed53155 100644 --- a/clang/lib/Analysis/ProgramPoint.cpp +++ b/clang/lib/Analysis/ProgramPoint.cpp @@ -188,7 +188,11 @@ void ProgramPoint::printJson(llvm::raw_ostream &Out, const char *NL) const { Out << "Statement\", \"stmt_kind\": \"" << S->getStmtClassName() << "\", \"stmt_id\": " << S->getID(Context) - << ", \"pointer\": \"" << (const void *)S << "\", \"pretty\": "; + << ", \"pointer\": \"" << (const void *)S << "\", "; + if (const auto *CS = dyn_cast(S)) + Out << "\"cast_kind\": \"" << CS->getCastKindName() << "\", "; + + Out << "\"pretty\": "; S->printJson(Out, nullptr, PP, AddQuotes); diff --git a/clang/test/Analysis/exploded-graph-rewriter/program_points.dot b/clang/test/Analysis/exploded-graph-rewriter/program_points.dot index 2f49d7f75ef01..c27c230ebfb37 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/program_points.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/program_points.dot @@ -116,3 +116,51 @@ Node0x3 [shape=record,label= } ]} \l}"]; + +// CHECK-NEXT: Program point: +// CHECK-SAME:
diff --git a/clang/utils/analyzer/exploded-graph-rewriter.py b/clang/utils/analyzer/exploded-graph-rewriter.py index d612cfc3ba73f..ced5c36bab5e2 100755 --- a/clang/utils/analyzer/exploded-graph-rewriter.py +++ b/clang/utils/analyzer/exploded-graph-rewriter.py @@ -644,6 +644,10 @@ def visit_store_in_state(self, s, prev_s=None): if st is None: self._dump(' Nothing!') else: + if self._dark_mode: + self._dump(' (%s)' % st.ptr) + else: + self._dump(' (%s)' % st.ptr) if prev_st is not None: if s.store.is_different(prev_st): self._dump('
') From 64a76016cf6bf75ced0ca0cbf829d43c0e189b7c Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Tue, 13 Aug 2019 23:22:33 +0000 Subject: [PATCH 095/181] [analyzer] Prune calls to functions with linear CFGs that return a non-zero constrained value During the evaluation of D62883, I noticed a bunch of totally meaningless notes with the pattern of "Calling 'A'" -> "Returning value" -> "Returning from 'A'", which added no value to the report at all. This patch (not only affecting tracked conditions mind you) prunes diagnostic messages to functions that return a value not constrained to be 0, and are also linear. Differential Revision: https://reviews.llvm.org/D64232 llvm-svn: 368771 (cherry picked from commit 46929df72333dee8ace9fbdaf05a5e03a882708b) --- .../Core/BugReporterVisitors.cpp | 50 +++++-- .../Analysis/diagnostics/find_last_store.c | 6 +- .../track-control-dependency-conditions.cpp | 141 +++++++++++++++--- clang/test/Analysis/uninit-vals.c | 12 +- 4 files changed, 160 insertions(+), 49 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp index 02bdf86f57ff2..215f327448d0f 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp @@ -841,7 +841,7 @@ namespace { /// This visitor is intended to be used when another visitor discovers that an /// interesting value comes from an inlined function call. class ReturnVisitor : public BugReporterVisitor { - const StackFrameContext *StackFrame; + const StackFrameContext *CalleeSFC; enum { Initial, MaybeUnsuppress, @@ -853,10 +853,9 @@ class ReturnVisitor : public BugReporterVisitor { AnalyzerOptions& Options; public: - ReturnVisitor(const StackFrameContext *Frame, - bool Suppressed, + ReturnVisitor(const StackFrameContext *Frame, bool Suppressed, AnalyzerOptions &Options) - : StackFrame(Frame), EnableNullFPSuppression(Suppressed), + : CalleeSFC(Frame), EnableNullFPSuppression(Suppressed), Options(Options) {} static void *getTag() { @@ -866,7 +865,7 @@ class ReturnVisitor : public BugReporterVisitor { void Profile(llvm::FoldingSetNodeID &ID) const override { ID.AddPointer(ReturnVisitor::getTag()); - ID.AddPointer(StackFrame); + ID.AddPointer(CalleeSFC); ID.AddBoolean(EnableNullFPSuppression); } @@ -950,7 +949,6 @@ class ReturnVisitor : public BugReporterVisitor { if (Optional RetLoc = RetVal.getAs()) EnableNullFPSuppression = State->isNull(*RetLoc).isConstrainedTrue(); - BR.markInteresting(CalleeContext); BR.addVisitor(llvm::make_unique(CalleeContext, EnableNullFPSuppression, Options)); @@ -960,7 +958,7 @@ class ReturnVisitor : public BugReporterVisitor { BugReporterContext &BRC, BugReport &BR) { // Only print a message at the interesting return statement. - if (N->getLocationContext() != StackFrame) + if (N->getLocationContext() != CalleeSFC) return nullptr; Optional SP = N->getLocationAs(); @@ -974,7 +972,7 @@ class ReturnVisitor : public BugReporterVisitor { // Okay, we're at the right return statement, but do we have the return // value available? ProgramStateRef State = N->getState(); - SVal V = State->getSVal(Ret, StackFrame); + SVal V = State->getSVal(Ret, CalleeSFC); if (V.isUnknownOrUndef()) return nullptr; @@ -1008,6 +1006,8 @@ class ReturnVisitor : public BugReporterVisitor { SmallString<64> Msg; llvm::raw_svector_ostream Out(Msg); + bool WouldEventBeMeaningless = false; + if (State->isNull(V).isConstrainedTrue()) { if (V.getAs()) { @@ -1030,10 +1030,19 @@ class ReturnVisitor : public BugReporterVisitor { } else { if (auto CI = V.getAs()) { Out << "Returning the value " << CI->getValue(); - } else if (V.getAs()) { - Out << "Returning pointer"; } else { - Out << "Returning value"; + // There is nothing interesting about returning a value, when it is + // plain value without any constraints, and the function is guaranteed + // to return that every time. We could use CFG::isLinear() here, but + // constexpr branches are obvious to the compiler, not necesserily to + // the programmer. + if (N->getCFG().size() == 3) + WouldEventBeMeaningless = true; + + if (V.getAs()) + Out << "Returning pointer"; + else + Out << "Returning value"; } } @@ -1052,11 +1061,20 @@ class ReturnVisitor : public BugReporterVisitor { Out << " (loaded from '" << *DD << "')"; } - PathDiagnosticLocation L(Ret, BRC.getSourceManager(), StackFrame); + PathDiagnosticLocation L(Ret, BRC.getSourceManager(), CalleeSFC); if (!L.isValid() || !L.asLocation().isValid()) return nullptr; - return std::make_shared(L, Out.str()); + auto EventPiece = std::make_shared(L, Out.str()); + + // If we determined that the note is meaningless, make it prunable, and + // don't mark the stackframe interesting. + if (WouldEventBeMeaningless) + EventPiece->setPrunable(true); + else + BR.markInteresting(CalleeSFC); + + return EventPiece; } PathDiagnosticPieceRef visitNodeMaybeUnsuppress(const ExplodedNode *N, @@ -1071,7 +1089,7 @@ class ReturnVisitor : public BugReporterVisitor { if (!CE) return nullptr; - if (CE->getCalleeContext() != StackFrame) + if (CE->getCalleeContext() != CalleeSFC) return nullptr; Mode = Satisfied; @@ -1083,7 +1101,7 @@ class ReturnVisitor : public BugReporterVisitor { CallEventManager &CallMgr = StateMgr.getCallEventManager(); ProgramStateRef State = N->getState(); - CallEventRef<> Call = CallMgr.getCaller(StackFrame, State); + CallEventRef<> Call = CallMgr.getCaller(CalleeSFC, State); for (unsigned I = 0, E = Call->getNumArgs(); I != E; ++I) { Optional ArgV = Call->getArgSVal(I).getAs(); if (!ArgV) @@ -1126,7 +1144,7 @@ class ReturnVisitor : public BugReporterVisitor { void finalizeVisitor(BugReporterContext &, const ExplodedNode *, BugReport &BR) override { if (EnableNullFPSuppression && ShouldInvalidate) - BR.markInvalid(ReturnVisitor::getTag(), StackFrame); + BR.markInvalid(ReturnVisitor::getTag(), CalleeSFC); } }; diff --git a/clang/test/Analysis/diagnostics/find_last_store.c b/clang/test/Analysis/diagnostics/find_last_store.c index 9bf601ea30e2a..486e4ec64d169 100644 --- a/clang/test/Analysis/diagnostics/find_last_store.c +++ b/clang/test/Analysis/diagnostics/find_last_store.c @@ -2,13 +2,11 @@ typedef struct { float b; } c; void *a(); void *d() { - return a(); // expected-note{{Returning pointer}} + return a(); } void no_find_last_store() { - c *e = d(); // expected-note{{Calling 'd'}} - // expected-note@-1{{Returning from 'd'}} - // expected-note@-2{{'e' initialized here}} + c *e = d(); // expected-note{{'e' initialized here}} (void)(e || e->b); // expected-note{{Assuming 'e' is null}} // expected-note@-1{{Left side of '||' is false}} diff --git a/clang/test/Analysis/track-control-dependency-conditions.cpp b/clang/test/Analysis/track-control-dependency-conditions.cpp index d4e59a7ad675e..93a5efb39db8a 100644 --- a/clang/test/Analysis/track-control-dependency-conditions.cpp +++ b/clang/test/Analysis/track-control-dependency-conditions.cpp @@ -119,7 +119,7 @@ namespace variable_declaration_in_condition { bool coin(); bool foo() { - return coin(); // tracking-note{{Returning value}} + return coin(); } int bar; @@ -127,12 +127,10 @@ int bar; void test() { int *x = 0; // expected-note{{'x' initialized to a null pointer value}} - if (int flag = foo()) // tracking-note{{Calling 'foo'}} - // tracking-note@-1{{Returning from 'foo'}} - // tracking-note@-2{{'flag' initialized here}} - // debug-note@-3{{Tracking condition 'flag'}} - // expected-note@-4{{Assuming 'flag' is not equal to 0}} - // expected-note@-5{{Taking true branch}} + if (int flag = foo()) // tracking-note{{'flag' initialized here}} + // debug-note@-1{{Tracking condition 'flag'}} + // expected-note@-2{{Assuming 'flag' is not equal to 0}} + // expected-note@-3{{Taking true branch}} *x = 5; // expected-warning{{Dereference of null pointer}} // expected-note@-1{{Dereference of null pointer}} @@ -143,39 +141,114 @@ namespace conversion_to_bool { bool coin(); struct ConvertsToBool { - operator bool() const { return coin(); } // tracking-note{{Returning value}} + operator bool() const { return coin(); } }; void test() { int *x = 0; // expected-note{{'x' initialized to a null pointer value}} if (ConvertsToBool()) - // tracking-note@-1 {{Calling 'ConvertsToBool::operator bool'}} - // tracking-note@-2{{Returning from 'ConvertsToBool::operator bool'}} - // debug-note@-3{{Tracking condition 'ConvertsToBool()'}} - // expected-note@-4{{Assuming the condition is true}} - // expected-note@-5{{Taking true branch}} + // debug-note@-1{{Tracking condition 'ConvertsToBool()'}} + // expected-note@-2{{Assuming the condition is true}} + // expected-note@-3{{Taking true branch}} *x = 5; // expected-warning{{Dereference of null pointer}} // expected-note@-1{{Dereference of null pointer}} } } // end of namespace variable_declaration_in_condition +namespace important_returning_pointer_loaded_from { +bool coin(); + +int *getIntPtr(); + +void storeValue(int **i) { + *i = getIntPtr(); // tracking-note{{Value assigned to 'i'}} +} + +int *conjurePointer() { + int *i; + storeValue(&i); // tracking-note{{Calling 'storeValue'}} + // tracking-note@-1{{Returning from 'storeValue'}} + return i; // tracking-note{{Returning pointer (loaded from 'i')}} +} + +void f(int *ptr) { + if (ptr) // expected-note{{Assuming 'ptr' is null}} + // expected-note@-1{{Taking false branch}} + ; + if (!conjurePointer()) + // tracking-note@-1{{Calling 'conjurePointer'}} + // tracking-note@-2{{Returning from 'conjurePointer'}} + // debug-note@-3{{Tracking condition '!conjurePointer()'}} + // expected-note@-4{{Assuming the condition is true}} + // expected-note@-5{{Taking true branch}} + *ptr = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} +} // end of namespace important_returning_pointer_loaded_from + +namespace unimportant_returning_pointer_loaded_from { +bool coin(); + +int *getIntPtr(); + +int *conjurePointer() { + int *i = getIntPtr(); // tracking-note{{'i' initialized here}} + return i; // tracking-note{{Returning pointer (loaded from 'i')}} +} + +void f(int *ptr) { + if (ptr) // expected-note{{Assuming 'ptr' is null}} + // expected-note@-1{{Taking false branch}} + ; + if (!conjurePointer()) + // tracking-note@-1{{Calling 'conjurePointer'}} + // tracking-note@-2{{Returning from 'conjurePointer'}} + // debug-note@-3{{Tracking condition '!conjurePointer()'}} + // expected-note@-4{{Assuming the condition is true}} + // expected-note@-5{{Taking true branch}} + *ptr = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} +} // end of namespace unimportant_returning_pointer_loaded_from + +namespace unimportant_returning_pointer_loaded_from_through_cast { + +void *conjure(); + +int *cast(void *P) { + return static_cast(P); +} + +void f() { + int *x = 0; // expected-note{{'x' initialized to a null pointer value}} + + if (cast(conjure())) + // tracking-note@-1{{Passing value via 1st parameter 'P'}} + // debug-note@-2{{Tracking condition 'cast(conjure())'}} + // expected-note@-3{{Assuming the condition is false}} + // expected-note@-4{{Taking false branch}} + return; + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} + +} // end of namespace unimportant_returning_pointer_loaded_from_through_cast + namespace unimportant_returning_value_note { bool coin(); -bool flipCoin() { return coin(); } // tracking-note{{Returning value}} +bool flipCoin() { return coin(); } void i(int *ptr) { if (ptr) // expected-note{{Assuming 'ptr' is null}} // expected-note@-1{{Taking false branch}} ; if (!flipCoin()) - // tracking-note@-1{{Calling 'flipCoin'}} - // tracking-note@-2{{Returning from 'flipCoin'}} - // debug-note@-3{{Tracking condition '!flipCoin()'}} - // expected-note@-4{{Assuming the condition is true}} - // expected-note@-5{{Taking true branch}} + // debug-note@-1{{Tracking condition '!flipCoin()'}} + // expected-note@-2{{Assuming the condition is true}} + // expected-note@-3{{Taking true branch}} *ptr = 5; // expected-warning{{Dereference of null pointer}} // expected-note@-1{{Dereference of null pointer}} } @@ -207,6 +280,36 @@ void i(int *ptr) { } } // end of namespace important_returning_value_note +namespace important_returning_value_note_in_linear_function { +bool coin(); + +struct super_complicated_template_hackery { + static constexpr bool value = false; +}; + +bool flipCoin() { + if (super_complicated_template_hackery::value) + // tracking-note@-1{{'value' is false}} + // tracking-note@-2{{Taking false branch}} + return true; + return coin(); // tracking-note{{Returning value}} +} + +void i(int *ptr) { + if (ptr) // expected-note{{Assuming 'ptr' is null}} + // expected-note@-1{{Taking false branch}} + ; + if (!flipCoin()) + // tracking-note@-1{{Calling 'flipCoin'}} + // tracking-note@-2{{Returning from 'flipCoin'}} + // debug-note@-3{{Tracking condition '!flipCoin()'}} + // expected-note@-4{{Assuming the condition is true}} + // expected-note@-5{{Taking true branch}} + *ptr = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} +} // end of namespace important_returning_value_note_in_linear_function + namespace tracked_condition_is_only_initialized { int getInt(); diff --git a/clang/test/Analysis/uninit-vals.c b/clang/test/Analysis/uninit-vals.c index d24b458747384..c0c6034e80328 100644 --- a/clang/test/Analysis/uninit-vals.c +++ b/clang/test/Analysis/uninit-vals.c @@ -149,8 +149,6 @@ int test_radar12278788_FP() { RetVoidFuncType f = foo_radar12278788_fp; return ((RetIntFuncType)f)(); //expected-warning {{Undefined or garbage value returned to caller}} //expected-note@-1 {{Undefined or garbage value returned to caller}} - //expected-note@-2 {{Calling 'foo_radar12278788_fp'}} - //expected-note@-3 {{Returning from 'foo_radar12278788_fp'}} } void rdar13665798() { @@ -164,8 +162,6 @@ void rdar13665798() { RetVoidFuncType f = foo_radar12278788_fp; return ((RetIntFuncType)f)(); //expected-warning {{Undefined or garbage value returned to caller}} //expected-note@-1 {{Undefined or garbage value returned to caller}} - //expected-note@-2 {{Calling 'foo_radar12278788_fp'}} - //expected-note@-3 {{Returning from 'foo_radar12278788_fp'}} }(); } @@ -182,18 +178,14 @@ struct Point getHalfPoint() { void use(struct Point p); void testUseHalfPoint() { - struct Point p = getHalfPoint(); // expected-note{{Calling 'getHalfPoint'}} - // expected-note@-1{{Returning from 'getHalfPoint'}} - // expected-note@-2{{'p' initialized here}} + struct Point p = getHalfPoint(); // expected-note{{'p' initialized here}} use(p); // expected-warning{{uninitialized}} // expected-note@-1{{uninitialized}} } void testUseHalfPoint2() { struct Point p; - p = getHalfPoint(); // expected-note{{Calling 'getHalfPoint'}} - // expected-note@-1{{Returning from 'getHalfPoint'}} - // expected-note@-2{{Value assigned to 'p'}} + p = getHalfPoint(); // expected-note{{Value assigned to 'p'}} use(p); // expected-warning{{uninitialized}} // expected-note@-1{{uninitialized}} } From c47b77d7324399e750df8bf7df1d6f3a9935edbe Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Tue, 13 Aug 2019 23:48:10 +0000 Subject: [PATCH 096/181] [analyzer] Track the right hand side of the last store regardless of its value Summary: The following code snippet taken from D64271#1572188 has an issue: namely, because `flag`'s value isn't undef or a concrete int, it isn't being tracked. int flag; bool coin(); void foo() { flag = coin(); } void test() { int *x = 0; int local_flag; flag = 1; foo(); local_flag = flag; if (local_flag) x = new int; foo(); local_flag = flag; if (local_flag) *x = 5; } This, in my opinion, makes no sense, other values may be interesting too. Originally added by rC185608. Differential Revision: https://reviews.llvm.org/D64287 llvm-svn: 368773 (cherry picked from commit 0df9c8c57802b0b1c75380c904b3cce0b902e2c5) --- .../Core/BugReporterVisitors.cpp | 23 +++++++--------- .../track-control-dependency-conditions.cpp | 24 +++++++++++++++++ clang/test/Analysis/uninit-const.c | 27 ++++++++++--------- clang/test/Analysis/uninit-const.cpp | 6 ++--- 4 files changed, 52 insertions(+), 28 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp index 215f327448d0f..bd3296f7eae17 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp @@ -1394,15 +1394,11 @@ FindLastStoreBRVisitor::VisitNode(const ExplodedNode *Succ, // If we have an expression that provided the value, try to track where it // came from. if (InitE) { - if (V.isUndef() || - V.getAs() || V.getAs()) { - if (!IsParam) - InitE = InitE->IgnoreParenCasts(); - bugreporter::trackExpressionValue(StoreSite, InitE, BR, - EnableNullFPSuppression); - } - ReturnVisitor::addVisitorIfNecessary(StoreSite, InitE->IgnoreParenCasts(), - BR, EnableNullFPSuppression); + if (!IsParam) + InitE = InitE->IgnoreParenCasts(); + + bugreporter::trackExpressionValue(StoreSite, InitE, BR, + EnableNullFPSuppression); } // Okay, we've found the binding. Emit an appropriate message. @@ -1945,19 +1941,20 @@ bool bugreporter::trackExpressionValue(const ExplodedNode *InputNode, // We should not try to dereference pointers at all when we don't care // what is written inside the pointer. bool CanDereference = true; - if (const auto *SR = dyn_cast(L->getRegion())) + if (const auto *SR = L->getRegionAs()) { if (SR->getSymbol()->getType()->getPointeeType()->isVoidType()) CanDereference = false; + } else if (const auto *AR = L->getRegionAs()) + CanDereference = false; // At this point we are dealing with the region's LValue. // However, if the rvalue is a symbolic region, we should track it as well. // Try to use the correct type when looking up the value. SVal RVal; - if (ExplodedGraph::isInterestingLValueExpr(Inner)) { + if (ExplodedGraph::isInterestingLValueExpr(Inner)) RVal = LVState->getRawSVal(L.getValue(), Inner->getType()); - } else if (CanDereference) { + else if (CanDereference) RVal = LVState->getSVal(L->getRegion()); - } if (CanDereference) if (auto KV = RVal.getAs()) diff --git a/clang/test/Analysis/track-control-dependency-conditions.cpp b/clang/test/Analysis/track-control-dependency-conditions.cpp index 93a5efb39db8a..644eb6296feec 100644 --- a/clang/test/Analysis/track-control-dependency-conditions.cpp +++ b/clang/test/Analysis/track-control-dependency-conditions.cpp @@ -366,6 +366,30 @@ void f(int y) { } } // end of namespace tracked_condition_written_in_nested_stackframe +namespace condition_written_in_nested_stackframe_before_assignment { +int flag = 0; +int getInt(); + +void foo() { + flag = getInt(); // tracking-note{{Value assigned to 'flag'}} +} + +void f() { + int *x = 0; // expected-note{{'x' initialized to a null pointer value}} + int y = 0; + + foo(); // tracking-note{{Calling 'foo'}} + // tracking-note@-1{{Returning from 'foo'}} + y = flag; // tracking-note{{Value assigned to 'y'}} + + if (y) // expected-note{{Assuming 'y' is not equal to 0}} + // expected-note@-1{{Taking true branch}} + // debug-note@-2{{Tracking condition 'y'}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} +} // end of namespace condition_written_in_nested_stackframe_before_assignment + namespace collapse_point_not_in_condition { [[noreturn]] void halt(); diff --git a/clang/test/Analysis/uninit-const.c b/clang/test/Analysis/uninit-const.c index 407c28a5e8bab..ec29c236069cb 100644 --- a/clang/test/Analysis/uninit-const.c +++ b/clang/test/Analysis/uninit-const.c @@ -24,15 +24,15 @@ void doStuff_constStaticSizedArray(const int a[static 10]) {} void doStuff_variadic(const int *u, ...){}; void f_1(void) { - int t; + int t; // expected-note {{'t' declared without an initial value}} int* tp = &t; // expected-note {{'tp' initialized here}} doStuff_pointerToConstInt(tp); // expected-warning {{1st function call argument is a pointer to uninitialized value}} // expected-note@-1 {{1st function call argument is a pointer to uninitialized value}} } void f_1_1(void) { - int t; - int* tp1 = &t; + int t; // expected-note {{'t' declared without an initial value}} + int *tp1 = &t; // expected-note {{'tp1' initialized here}} int* tp2 = tp1; // expected-note {{'tp2' initialized here}} doStuff_pointerToConstInt(tp2); // expected-warning {{1st function call argument is a pointer to uninitialized value}} // expected-note@-1 {{1st function call argument is a pointer to uninitialized value}} @@ -40,12 +40,15 @@ void f_1_1(void) { int *f_2_sub(int *p) { - return p; + return p; // expected-note {{Returning pointer (loaded from 'p')}} } void f_2(void) { - int t; - int* p = f_2_sub(&t); + int t; // expected-note {{'t' declared without an initial value}} + int *p = f_2_sub(&t); // expected-note {{Passing value via 1st parameter 'p'}} + // expected-note@-1{{Calling 'f_2_sub'}} + // expected-note@-2{{Returning from 'f_2_sub'}} + // expected-note@-3{{'p' initialized here}} int* tp = p; // expected-note {{'tp' initialized here}} doStuff_pointerToConstInt(tp); // expected-warning {{1st function call argument is a pointer to uninitialized value}} // expected-note@-1 {{1st function call argument is a pointer to uninitialized value}} @@ -62,7 +65,7 @@ void f_4(void) { } void f_5(void) { - int ta[5]; + int ta[5]; // expected-note {{'ta' initialized here}} int* tp = ta; // expected-note {{'tp' initialized here}} doStuff_pointerToConstInt(tp); // expected-warning {{1st function call argument is a pointer to uninitialized value}} // expected-note@-1 {{1st function call argument is a pointer to uninitialized value}} @@ -99,7 +102,7 @@ void f_8(void) { } void f_9(void) { - int a[6]; + int a[6]; // expected-note {{'a' initialized here}} int const *ptau = a; // expected-note {{'ptau' initialized here}} doStuff_arrayOfConstInt(ptau); // expected-warning {{1st function call argument is a pointer to uninitialized value}} // expected-note@-1 {{1st function call argument is a pointer to uninitialized value}} @@ -173,7 +176,7 @@ int f_malloc_2(void) { // uninit pointer, uninit val void f_variadic_unp_unv(void) { - int t; + int t; // expected-note {{'t' declared without an initial value}} int v; int* tp = &t; // expected-note {{'tp' initialized here}} doStuff_variadic(tp,v); // expected-warning {{1st function call argument is a pointer to uninitialized value}} @@ -181,7 +184,7 @@ void f_variadic_unp_unv(void) { } // uninit pointer, init val void f_variadic_unp_inv(void) { - int t; + int t; // expected-note {{'t' declared without an initial value}} int v = 3; int* tp = &t; // expected-note {{'tp' initialized here}} doStuff_variadic(tp,v); // expected-warning {{1st function call argument is a pointer to uninitialized value}} @@ -216,7 +219,7 @@ void f_variadic_inp_inp(void) { //uninit pointer, init pointer void f_variadic_unp_inp(void) { - int t; + int t; // expected-note {{'t' declared without an initial value}} int u=3; int *vp = &u ; int *tp = &t; // expected-note {{'tp' initialized here}} @@ -235,7 +238,7 @@ void f_variadic_inp_unp(void) { //uninit pointer, uninit pointer void f_variadic_unp_unp(void) { - int t; + int t; // expected-note {{'t' declared without an initial value}} int u; int *vp = &u ; int *tp = &t; // expected-note {{'tp' initialized here}} diff --git a/clang/test/Analysis/uninit-const.cpp b/clang/test/Analysis/uninit-const.cpp index b6430307dc520..3b7089cbcacd6 100644 --- a/clang/test/Analysis/uninit-const.cpp +++ b/clang/test/Analysis/uninit-const.cpp @@ -66,8 +66,8 @@ void f6_1(void) { void f6_2(void) { int t; //expected-note {{'t' declared without an initial value}} - int &p = t; - int &s = p; + int &p = t; //expected-note {{'p' initialized here}} + int &s = p; //expected-note {{'s' initialized here}} int &q = s; //expected-note {{'q' initialized here}} doStuff6(q); //expected-warning {{1st function call argument is an uninitialized value}} //expected-note@-1 {{1st function call argument is an uninitialized value}} @@ -96,7 +96,7 @@ void f6(void) { void f5(void) { - int t; + int t; // expected-note {{'t' declared without an initial value}} int* tp = &t; // expected-note {{'tp' initialized here}} doStuff_uninit(tp); // expected-warning {{1st function call argument is a pointer to uninitialized value}} // expected-note@-1 {{1st function call argument is a pointer to uninitialized value}} From 65cdff478442bbe1f699902cf3eb5e441836acf1 Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Wed, 14 Aug 2019 00:48:57 +0000 Subject: [PATCH 097/181] [analyzer][NFC] Prepare visitors for different tracking kinds When we're tracking a variable that is responsible for a null pointer dereference or some other sinister programming error, we of course would like to gather as much information why we think that the variable has that specific value as possible. However, the newly introduced condition tracking shows that tracking all values this thoroughly could easily cause an intolerable growth in the bug report's length. There are a variety of heuristics we discussed on the mailing list[1] to combat this, all of them requiring to differentiate in between tracking a "regular value" and a "condition". This patch introduces the new `bugreporter::TrackingKind` enum, adds it to several visitors as a non-optional argument, and moves some functions around to make the code a little more coherent. [1] http://lists.llvm.org/pipermail/cfe-dev/2019-June/062613.html Differential Revision: https://reviews.llvm.org/D64270 llvm-svn: 368777 (cherry picked from commit 3f7c66d551ef1210f1f48822e6bfef2e1b97881d) --- .../Core/BugReporter/BugReporterVisitors.h | 76 +++++++---- .../StaticAnalyzer/Checkers/MIGChecker.cpp | 3 +- .../Checkers/ObjCContainersChecker.cpp | 4 +- .../Checkers/UndefCapturedBlockVarChecker.cpp | 3 +- .../Core/BugReporterVisitors.cpp | 123 +++++++++--------- 5 files changed, 121 insertions(+), 88 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h index b2927a08b6145..4ca38079d0209 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h @@ -85,6 +85,40 @@ class BugReporterVisitor : public llvm::FoldingSetNode { const BugReport &BR); }; +namespace bugreporter { + +/// Specifies the type of tracking for an expression. +enum class TrackingKind { + /// Default tracking kind -- specifies that as much information should be + /// gathered about the tracked expression value as possible. + Thorough, + /// Specifies that a more moderate tracking should be used for the expression + /// value. This will essentially make sure that functions relevant to the it + /// aren't pruned, but otherwise relies on the user reading the code or + /// following the arrows. + Condition +}; + +/// Attempts to add visitors to track expression value back to its point of +/// origin. +/// +/// \param N A node "downstream" from the evaluation of the statement. +/// \param E The expression value which we are tracking +/// \param R The bug report to which visitors should be attached. +/// \param EnableNullFPSuppression Whether we should employ false positive +/// suppression (inlined defensive checks, returned null). +/// +/// \return Whether or not the function was able to add visitors for this +/// statement. Note that returning \c true does not actually imply +/// that any visitors were added. +bool trackExpressionValue(const ExplodedNode *N, const Expr *E, BugReport &R, + TrackingKind TKind = TrackingKind::Thorough, + bool EnableNullFPSuppression = true); + +const Expr *getDerefExpr(const Stmt *S); + +} // namespace bugreporter + /// Finds last store into the given region, /// which is different from a given symbolic value. class FindLastStoreBRVisitor final : public BugReporterVisitor { @@ -96,15 +130,28 @@ class FindLastStoreBRVisitor final : public BugReporterVisitor { /// bug, we are going to employ false positive suppression. bool EnableNullFPSuppression; + using TrackingKind = bugreporter::TrackingKind; + TrackingKind TKind; + public: /// Creates a visitor for every VarDecl inside a Stmt and registers it with /// the BugReport. static void registerStatementVarDecls(BugReport &BR, const Stmt *S, - bool EnableNullFPSuppression); - + bool EnableNullFPSuppression, + TrackingKind TKind); + + /// \param V We're searching for the store where \c R received this value. + /// \param R The region we're tracking. + /// \param EnableNullFPSuppression Whether we should employ false positive + /// suppression (inlined defensive checks, returned null). + /// \param TKind May limit the amount of notes added to the bug report. FindLastStoreBRVisitor(KnownSVal V, const MemRegion *R, - bool InEnableNullFPSuppression) - : R(R), V(V), EnableNullFPSuppression(InEnableNullFPSuppression) {} + bool InEnableNullFPSuppression, + TrackingKind TKind) + : R(R), V(V), EnableNullFPSuppression(InEnableNullFPSuppression), + TKind(TKind) { + assert(R); + } void Profile(llvm::FoldingSetNodeID &ID) const override; @@ -347,27 +394,6 @@ class TagVisitor : public BugReporterVisitor { BugReport &R) override; }; -namespace bugreporter { - -/// Attempts to add visitors to track expression value back to its point of -/// origin. -/// -/// \param N A node "downstream" from the evaluation of the statement. -/// \param E The expression value which we are tracking -/// \param R The bug report to which visitors should be attached. -/// \param EnableNullFPSuppression Whether we should employ false positive -/// suppression (inlined defensive checks, returned null). -/// -/// \return Whether or not the function was able to add visitors for this -/// statement. Note that returning \c true does not actually imply -/// that any visitors were added. -bool trackExpressionValue(const ExplodedNode *N, const Expr *E, BugReport &R, - bool EnableNullFPSuppression = true); - -const Expr *getDerefExpr(const Stmt *S); - -} // namespace bugreporter - } // namespace ento } // namespace clang diff --git a/clang/lib/StaticAnalyzer/Checkers/MIGChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MIGChecker.cpp index 6e7776bb484e5..9cdc7612b2999 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MIGChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MIGChecker.cpp @@ -282,7 +282,8 @@ void MIGChecker::checkReturnAux(const ReturnStmt *RS, CheckerContext &C) const { N); R->addRange(RS->getSourceRange()); - bugreporter::trackExpressionValue(N, RS->getRetValue(), *R, false); + bugreporter::trackExpressionValue(N, RS->getRetValue(), *R, + bugreporter::TrackingKind::Thorough, false); C.emitReport(std::move(R)); } diff --git a/clang/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp index f69a3944a56ca..98d2a9941da92 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp @@ -146,8 +146,8 @@ void ObjCContainersChecker::checkPreStmt(const CallExpr *CE, initBugType(); auto R = llvm::make_unique(*BT, "Index is out of bounds", N); R->addRange(IdxExpr->getSourceRange()); - bugreporter::trackExpressionValue(N, IdxExpr, *R, - /*EnableNullFPSuppression=*/false); + bugreporter::trackExpressionValue( + N, IdxExpr, *R, bugreporter::TrackingKind::Thorough, false); C.emitReport(std::move(R)); return; } diff --git a/clang/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp index c787ef58660ab..1b6f19c01c2c5 100644 --- a/clang/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp @@ -87,7 +87,8 @@ UndefCapturedBlockVarChecker::checkPostStmt(const BlockExpr *BE, if (const Expr *Ex = FindBlockDeclRefExpr(BE->getBody(), VD)) R->addRange(Ex->getSourceRange()); R->addVisitor(llvm::make_unique( - *V, VR, /*EnableNullFPSuppression*/ false)); + *V, VR, /*EnableNullFPSuppression*/ false, + bugreporter::TrackingKind::Thorough)); R->disablePathPruning(); // need location of block C.emitReport(std::move(R)); diff --git a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp index bd3296f7eae17..21e488ff85338 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp @@ -851,12 +851,13 @@ class ReturnVisitor : public BugReporterVisitor { bool EnableNullFPSuppression; bool ShouldInvalidate = true; AnalyzerOptions& Options; + bugreporter::TrackingKind TKind; public: ReturnVisitor(const StackFrameContext *Frame, bool Suppressed, - AnalyzerOptions &Options) + AnalyzerOptions &Options, bugreporter::TrackingKind TKind) : CalleeSFC(Frame), EnableNullFPSuppression(Suppressed), - Options(Options) {} + Options(Options), TKind(TKind) {} static void *getTag() { static int Tag = 0; @@ -878,7 +879,8 @@ class ReturnVisitor : public BugReporterVisitor { /// bug report, so it can print a note later. static void addVisitorIfNecessary(const ExplodedNode *Node, const Stmt *S, BugReport &BR, - bool InEnableNullFPSuppression) { + bool InEnableNullFPSuppression, + bugreporter::TrackingKind TKind) { if (!CallEvent::isCallStmt(S)) return; @@ -951,7 +953,7 @@ class ReturnVisitor : public BugReporterVisitor { BR.addVisitor(llvm::make_unique(CalleeContext, EnableNullFPSuppression, - Options)); + Options, TKind)); } PathDiagnosticPieceRef visitNodeInitial(const ExplodedNode *N, @@ -999,8 +1001,9 @@ class ReturnVisitor : public BugReporterVisitor { RetE = RetE->IgnoreParenCasts(); - // If we're returning 0, we should track where that 0 came from. - bugreporter::trackExpressionValue(N, RetE, BR, EnableNullFPSuppression); + // Let's track the return value. + bugreporter::trackExpressionValue( + N, RetE, BR, TKind, EnableNullFPSuppression); // Build an appropriate message based on the return value. SmallString<64> Msg; @@ -1115,7 +1118,7 @@ class ReturnVisitor : public BugReporterVisitor { if (!State->isNull(*ArgV).isConstrainedTrue()) continue; - if (bugreporter::trackExpressionValue(N, ArgE, BR, EnableNullFPSuppression)) + if (trackExpressionValue(N, ArgE, BR, TKind, EnableNullFPSuppression)) ShouldInvalidate = false; // If we /can't/ track the null pointer, we should err on the side of @@ -1159,9 +1162,45 @@ void FindLastStoreBRVisitor::Profile(llvm::FoldingSetNodeID &ID) const { ID.AddPointer(&tag); ID.AddPointer(R); ID.Add(V); + ID.AddInteger(static_cast(TKind)); ID.AddBoolean(EnableNullFPSuppression); } +void FindLastStoreBRVisitor::registerStatementVarDecls( + BugReport &BR, const Stmt *S, bool EnableNullFPSuppression, + TrackingKind TKind) { + + const ExplodedNode *N = BR.getErrorNode(); + std::deque WorkList; + WorkList.push_back(S); + + while (!WorkList.empty()) { + const Stmt *Head = WorkList.front(); + WorkList.pop_front(); + + ProgramStateManager &StateMgr = N->getState()->getStateManager(); + + if (const auto *DR = dyn_cast(Head)) { + if (const auto *VD = dyn_cast(DR->getDecl())) { + const VarRegion *R = + StateMgr.getRegionManager().getVarRegion(VD, N->getLocationContext()); + + // What did we load? + SVal V = N->getSVal(S); + + if (V.getAs() || V.getAs()) { + // Register a new visitor with the BugReport. + BR.addVisitor(llvm::make_unique( + V.castAs(), R, EnableNullFPSuppression, TKind)); + } + } + } + + for (const Stmt *SubStmt : Head->children()) + WorkList.push_back(SubStmt); + } +} + /// Returns true if \p N represents the DeclStmt declaring and initializing /// \p VR. static bool isInitializationOfVar(const ExplodedNode *N, const VarRegion *VR) { @@ -1332,7 +1371,7 @@ FindLastStoreBRVisitor::VisitNode(const ExplodedNode *Succ, // should track the initializer expression. if (Optional PIP = Pred->getLocationAs()) { const MemRegion *FieldReg = (const MemRegion *)PIP->getLocationValue(); - if (FieldReg && FieldReg == R) { + if (FieldReg == R) { StoreSite = Pred; InitE = PIP->getInitializer()->getInit(); } @@ -1397,8 +1436,8 @@ FindLastStoreBRVisitor::VisitNode(const ExplodedNode *Succ, if (!IsParam) InitE = InitE->IgnoreParenCasts(); - bugreporter::trackExpressionValue(StoreSite, InitE, BR, - EnableNullFPSuppression); + bugreporter::trackExpressionValue( + StoreSite, InitE, BR, TKind, EnableNullFPSuppression); } // Okay, we've found the binding. Emit an appropriate message. @@ -1426,7 +1465,7 @@ FindLastStoreBRVisitor::VisitNode(const ExplodedNode *Succ, if (const VarRegion *OriginalR = BDR->getOriginalRegion(VR)) { if (auto KV = State->getSVal(OriginalR).getAs()) BR.addVisitor(llvm::make_unique( - *KV, OriginalR, EnableNullFPSuppression)); + *KV, OriginalR, EnableNullFPSuppression, TKind)); } } } @@ -1724,7 +1763,8 @@ PathDiagnosticPieceRef TrackControlDependencyCondBRVisitor::VisitNode( // expression, hence the BugReport level set. if (BR.addTrackedCondition(N)) { bugreporter::trackExpressionValue( - N, Condition, BR, /*EnableNullFPSuppression=*/false); + N, Condition, BR, bugreporter::TrackingKind::Condition, + /*EnableNullFPSuppression=*/false); return constructDebugPieceForTrackedCondition(Condition, N, BRC); } } @@ -1838,7 +1878,9 @@ static const ExplodedNode* findNodeForExpression(const ExplodedNode *N, bool bugreporter::trackExpressionValue(const ExplodedNode *InputNode, const Expr *E, BugReport &report, + bugreporter::TrackingKind TKind, bool EnableNullFPSuppression) { + if (!E || !InputNode) return false; @@ -1862,12 +1904,13 @@ bool bugreporter::trackExpressionValue(const ExplodedNode *InputNode, // At this point in the path, the receiver should be live since we are at the // message send expr. If it is nil, start tracking it. if (const Expr *Receiver = NilReceiverBRVisitor::getNilReceiver(Inner, LVNode)) - trackExpressionValue(LVNode, Receiver, report, EnableNullFPSuppression); + trackExpressionValue( + LVNode, Receiver, report, TKind, EnableNullFPSuppression); // Track the index if this is an array subscript. if (const auto *Arr = dyn_cast(Inner)) trackExpressionValue( - LVNode, Arr->getIdx(), report, /*EnableNullFPSuppression*/ false); + LVNode, Arr->getIdx(), report, TKind, /*EnableNullFPSuppression*/false); // See if the expression we're interested refers to a variable. // If so, we can track both its contents and constraints on its value. @@ -1883,7 +1926,7 @@ bool bugreporter::trackExpressionValue(const ExplodedNode *InputNode, if (RR && !LVIsNull) if (auto KV = LVal.getAs()) report.addVisitor(llvm::make_unique( - *KV, RR, EnableNullFPSuppression)); + *KV, RR, EnableNullFPSuppression, TKind)); // In case of C++ references, we want to differentiate between a null // reference and reference to null pointer. @@ -1920,7 +1963,7 @@ bool bugreporter::trackExpressionValue(const ExplodedNode *InputNode, if (auto KV = V.getAs()) report.addVisitor(llvm::make_unique( - *KV, R, EnableNullFPSuppression)); + *KV, R, EnableNullFPSuppression, TKind)); return true; } } @@ -1930,7 +1973,7 @@ bool bugreporter::trackExpressionValue(const ExplodedNode *InputNode, SVal V = LVState->getSValAsScalarOrLoc(Inner, LVNode->getLocationContext()); ReturnVisitor::addVisitorIfNecessary( - LVNode, Inner, report, EnableNullFPSuppression); + LVNode, Inner, report, EnableNullFPSuppression, TKind); // Is it a symbolic value? if (auto L = V.getAs()) { @@ -1959,7 +2002,7 @@ bool bugreporter::trackExpressionValue(const ExplodedNode *InputNode, if (CanDereference) if (auto KV = RVal.getAs()) report.addVisitor(llvm::make_unique( - *KV, L->getRegion(), EnableNullFPSuppression)); + *KV, L->getRegion(), EnableNullFPSuppression, TKind)); const MemRegion *RegionRVal = RVal.getAsRegion(); if (RegionRVal && isa(RegionRVal)) { @@ -2017,53 +2060,15 @@ PathDiagnosticPieceRef NilReceiverBRVisitor::VisitNode(const ExplodedNode *N, // The receiver was nil, and hence the method was skipped. // Register a BugReporterVisitor to issue a message telling us how // the receiver was null. - bugreporter::trackExpressionValue(N, Receiver, BR, - /*EnableNullFPSuppression*/ false); + bugreporter::trackExpressionValue( + N, Receiver, BR, bugreporter::TrackingKind::Thorough, + /*EnableNullFPSuppression*/ false); // Issue a message saying that the method was skipped. PathDiagnosticLocation L(Receiver, BRC.getSourceManager(), N->getLocationContext()); return std::make_shared(L, OS.str()); } -//===----------------------------------------------------------------------===// -// Implementation of FindLastStoreBRVisitor. -//===----------------------------------------------------------------------===// - -// Registers every VarDecl inside a Stmt with a last store visitor. -void FindLastStoreBRVisitor::registerStatementVarDecls(BugReport &BR, - const Stmt *S, - bool EnableNullFPSuppression) { - const ExplodedNode *N = BR.getErrorNode(); - std::deque WorkList; - WorkList.push_back(S); - - while (!WorkList.empty()) { - const Stmt *Head = WorkList.front(); - WorkList.pop_front(); - - ProgramStateManager &StateMgr = N->getState()->getStateManager(); - - if (const auto *DR = dyn_cast(Head)) { - if (const auto *VD = dyn_cast(DR->getDecl())) { - const VarRegion *R = - StateMgr.getRegionManager().getVarRegion(VD, N->getLocationContext()); - - // What did we load? - SVal V = N->getSVal(S); - - if (V.getAs() || V.getAs()) { - // Register a new visitor with the BugReport. - BR.addVisitor(llvm::make_unique( - V.castAs(), R, EnableNullFPSuppression)); - } - } - } - - for (const Stmt *SubStmt : Head->children()) - WorkList.push_back(SubStmt); - } -} - //===----------------------------------------------------------------------===// // Visitor that tries to report interesting diagnostics from conditions. //===----------------------------------------------------------------------===// From 2bc5f14c99b5db7cf7aa771435523cc02bc04d77 Mon Sep 17 00:00:00 2001 From: Alex Langford Date: Wed, 14 Aug 2019 01:09:07 +0000 Subject: [PATCH 098/181] [analyzer] Don't delete TaintConfig copy constructor Summary: Explicitly deleting the copy constructor makes compiling the function `ento::registerGenericTaintChecker` difficult with some compilers. When we construct an `llvm::Optional`, the optional is constructed with a const TaintConfig reference which it then uses to invoke the deleted TaintConfig copy constructor. I've observered this failing with clang 3.8 on Ubuntu 16.04. Reviewers: compnerd, Szelethus, boga95, NoQ, alexshap Subscribers: xazax.hun, baloghadamsoftware, szepet, a.sidorin, mikhail.ramalho, donat.nagy, dkrupp, Charusso, llvm-commits, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D66192 llvm-svn: 368779 (cherry picked from commit 21872bc9bf374db7f7f340758ac9bf262b892a53) --- clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp index b2172f6e9f544..42b35acd31e5a 100644 --- a/clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp @@ -71,9 +71,9 @@ class GenericTaintChecker std::vector Sinks; TaintConfiguration() = default; - TaintConfiguration(const TaintConfiguration &) = delete; + TaintConfiguration(const TaintConfiguration &) = default; TaintConfiguration(TaintConfiguration &&) = default; - TaintConfiguration &operator=(const TaintConfiguration &) = delete; + TaintConfiguration &operator=(const TaintConfiguration &) = default; TaintConfiguration &operator=(TaintConfiguration &&) = default; }; From 46c51f4f04488130f5d3bacf92cbf833c66bd2b7 Mon Sep 17 00:00:00 2001 From: Haojian Wu Date: Wed, 14 Aug 2019 08:20:42 +0000 Subject: [PATCH 099/181] Fix the -Wunused-variable warning. llvm-svn: 368808 (cherry picked from commit ec25edc17a45f838c2e76431f5c345d9c6e6bf89) --- clang/lib/StaticAnalyzer/Core/BugReporter.cpp | 2 +- clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp index 86b688fa96dba..9f61ecccbc193 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp @@ -2180,7 +2180,7 @@ llvm::iterator_range BugReport::getRanges() const { // If no custom ranges, add the range of the statement corresponding to // the error node. if (Ranges.empty()) { - if (const auto *E = dyn_cast_or_null(getStmt())) + if (dyn_cast_or_null(getStmt())) return llvm::make_range(&ErrorNodeRange, &ErrorNodeRange + 1); return llvm::make_range(ranges_iterator(), ranges_iterator()); } diff --git a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp index 21e488ff85338..f0c8de26bc171 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp @@ -1987,7 +1987,7 @@ bool bugreporter::trackExpressionValue(const ExplodedNode *InputNode, if (const auto *SR = L->getRegionAs()) { if (SR->getSymbol()->getType()->getPointeeType()->isVoidType()) CanDereference = false; - } else if (const auto *AR = L->getRegionAs()) + } else if (L->getRegionAs()) CanDereference = false; // At this point we are dealing with the region's LValue. From 915553addfeb6a4e261a5aae7ab83cf783928e82 Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Wed, 14 Aug 2019 09:39:38 +0000 Subject: [PATCH 100/181] [analyzer] Note last writes to a condition only in a nested stackframe Exactly what it says on the tin! The comments in the code detail this a little more too. Differential Revision: https://reviews.llvm.org/D64272 llvm-svn: 368817 (cherry picked from commit 967583bc087c035fdc4422d05be1030eecf2b241) --- .../Core/BugReporter/BugReporterVisitors.h | 14 +++- .../Core/BugReporterVisitors.cpp | 13 ++-- .../track-control-dependency-conditions.cpp | 65 ++++++++++++------- 3 files changed, 61 insertions(+), 31 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h index 4ca38079d0209..9069270a38e19 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h @@ -132,6 +132,7 @@ class FindLastStoreBRVisitor final : public BugReporterVisitor { using TrackingKind = bugreporter::TrackingKind; TrackingKind TKind; + const StackFrameContext *OriginSFC; public: /// Creates a visitor for every VarDecl inside a Stmt and registers it with @@ -145,11 +146,18 @@ class FindLastStoreBRVisitor final : public BugReporterVisitor { /// \param EnableNullFPSuppression Whether we should employ false positive /// suppression (inlined defensive checks, returned null). /// \param TKind May limit the amount of notes added to the bug report. + /// \param OriginSFC Only adds notes when the last store happened in a + /// different stackframe to this one. Disregarded if the tracking kind + /// is thorough. + /// This is useful, because for non-tracked regions, notes about + /// changes to its value in a nested stackframe could be pruned, and + /// this visitor can prevent that without polluting the bugpath too + /// much. FindLastStoreBRVisitor(KnownSVal V, const MemRegion *R, - bool InEnableNullFPSuppression, - TrackingKind TKind) + bool InEnableNullFPSuppression, TrackingKind TKind, + const StackFrameContext *OriginSFC = nullptr) : R(R), V(V), EnableNullFPSuppression(InEnableNullFPSuppression), - TKind(TKind) { + TKind(TKind), OriginSFC(OriginSFC) { assert(R); } diff --git a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp index f0c8de26bc171..8c17a367b8a58 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp @@ -1440,6 +1440,10 @@ FindLastStoreBRVisitor::VisitNode(const ExplodedNode *Succ, StoreSite, InitE, BR, TKind, EnableNullFPSuppression); } + if (TKind == TrackingKind::Condition && + !OriginSFC->isParentOf(StoreSite->getStackFrame())) + return nullptr; + // Okay, we've found the binding. Emit an appropriate message. SmallString<256> sbuf; llvm::raw_svector_ostream os(sbuf); @@ -1465,7 +1469,7 @@ FindLastStoreBRVisitor::VisitNode(const ExplodedNode *Succ, if (const VarRegion *OriginalR = BDR->getOriginalRegion(VR)) { if (auto KV = State->getSVal(OriginalR).getAs()) BR.addVisitor(llvm::make_unique( - *KV, OriginalR, EnableNullFPSuppression, TKind)); + *KV, OriginalR, EnableNullFPSuppression, TKind, OriginSFC)); } } } @@ -1890,6 +1894,7 @@ bool bugreporter::trackExpressionValue(const ExplodedNode *InputNode, return false; ProgramStateRef LVState = LVNode->getState(); + const StackFrameContext *SFC = LVNode->getStackFrame(); // We only track expressions if we believe that they are important. Chances // are good that control dependencies to the tracking point are also improtant @@ -1926,7 +1931,7 @@ bool bugreporter::trackExpressionValue(const ExplodedNode *InputNode, if (RR && !LVIsNull) if (auto KV = LVal.getAs()) report.addVisitor(llvm::make_unique( - *KV, RR, EnableNullFPSuppression, TKind)); + *KV, RR, EnableNullFPSuppression, TKind, SFC)); // In case of C++ references, we want to differentiate between a null // reference and reference to null pointer. @@ -1963,7 +1968,7 @@ bool bugreporter::trackExpressionValue(const ExplodedNode *InputNode, if (auto KV = V.getAs()) report.addVisitor(llvm::make_unique( - *KV, R, EnableNullFPSuppression, TKind)); + *KV, R, EnableNullFPSuppression, TKind, SFC)); return true; } } @@ -2002,7 +2007,7 @@ bool bugreporter::trackExpressionValue(const ExplodedNode *InputNode, if (CanDereference) if (auto KV = RVal.getAs()) report.addVisitor(llvm::make_unique( - *KV, L->getRegion(), EnableNullFPSuppression, TKind)); + *KV, L->getRegion(), EnableNullFPSuppression, TKind, SFC)); const MemRegion *RegionRVal = RVal.getAsRegion(); if (RegionRVal && isa(RegionRVal)) { diff --git a/clang/test/Analysis/track-control-dependency-conditions.cpp b/clang/test/Analysis/track-control-dependency-conditions.cpp index 644eb6296feec..35769f24739fc 100644 --- a/clang/test/Analysis/track-control-dependency-conditions.cpp +++ b/clang/test/Analysis/track-control-dependency-conditions.cpp @@ -127,10 +127,9 @@ int bar; void test() { int *x = 0; // expected-note{{'x' initialized to a null pointer value}} - if (int flag = foo()) // tracking-note{{'flag' initialized here}} - // debug-note@-1{{Tracking condition 'flag'}} - // expected-note@-2{{Assuming 'flag' is not equal to 0}} - // expected-note@-3{{Taking true branch}} + if (int flag = foo()) // debug-note{{Tracking condition 'flag'}} + // expected-note@-1{{Assuming 'flag' is not equal to 0}} + // expected-note@-2{{Taking true branch}} *x = 5; // expected-warning{{Dereference of null pointer}} // expected-note@-1{{Dereference of null pointer}} @@ -157,6 +156,28 @@ void test() { } // end of namespace variable_declaration_in_condition +namespace note_from_different_but_not_nested_stackframe { + +void nullptrDeref(int *ptr, bool True) { + if (True) // expected-note{{'True' is true}} + // expected-note@-1{{Taking true branch}} + // debug-note@-2{{Tracking condition 'True}} + *ptr = 5; + // expected-note@-1{{Dereference of null pointer (loaded from variable 'ptr')}} + // expected-warning@-2{{Dereference of null pointer (loaded from variable 'ptr')}} +} + +void f() { + int *ptr = nullptr; + // expected-note@-1{{'ptr' initialized to a null pointer value}} + bool True = true; + nullptrDeref(ptr, True); + // expected-note@-1{{Passing null pointer value via 1st parameter 'ptr'}} + // expected-note@-2{{Calling 'nullptrDeref'}} +} + +} // end of namespace note_from_different_but_not_nested_stackframe + namespace important_returning_pointer_loaded_from { bool coin(); @@ -194,8 +215,8 @@ bool coin(); int *getIntPtr(); int *conjurePointer() { - int *i = getIntPtr(); // tracking-note{{'i' initialized here}} - return i; // tracking-note{{Returning pointer (loaded from 'i')}} + int *i = getIntPtr(); + return i; } void f(int *ptr) { @@ -203,11 +224,9 @@ void f(int *ptr) { // expected-note@-1{{Taking false branch}} ; if (!conjurePointer()) - // tracking-note@-1{{Calling 'conjurePointer'}} - // tracking-note@-2{{Returning from 'conjurePointer'}} - // debug-note@-3{{Tracking condition '!conjurePointer()'}} - // expected-note@-4{{Assuming the condition is true}} - // expected-note@-5{{Taking true branch}} + // debug-note@-1{{Tracking condition '!conjurePointer()'}} + // expected-note@-2{{Assuming the condition is true}} + // expected-note@-3{{Taking true branch}} *ptr = 5; // expected-warning{{Dereference of null pointer}} // expected-note@-1{{Dereference of null pointer}} } @@ -225,10 +244,9 @@ void f() { int *x = 0; // expected-note{{'x' initialized to a null pointer value}} if (cast(conjure())) - // tracking-note@-1{{Passing value via 1st parameter 'P'}} - // debug-note@-2{{Tracking condition 'cast(conjure())'}} - // expected-note@-3{{Assuming the condition is false}} - // expected-note@-4{{Taking false branch}} + // debug-note@-1{{Tracking condition 'cast(conjure())'}} + // expected-note@-2{{Assuming the condition is false}} + // expected-note@-3{{Taking false branch}} return; *x = 5; // expected-warning{{Dereference of null pointer}} // expected-note@-1{{Dereference of null pointer}} @@ -314,7 +332,7 @@ namespace tracked_condition_is_only_initialized { int getInt(); void f() { - int flag = getInt(); // tracking-note{{'flag' initialized here}} + int flag = getInt(); int *x = 0; // expected-note{{'x' initialized to a null pointer value}} if (flag) // expected-note{{Assuming 'flag' is not equal to 0}} // expected-note@-1{{Taking true branch}} @@ -329,8 +347,8 @@ int flag; int getInt(); void f(int y) { - y = 1; // tracking-note{{The value 1 is assigned to 'y'}} - flag = y; // tracking-note{{The value 1 is assigned to 'flag'}} + y = 1; + flag = y; int *x = 0; // expected-note{{'x' initialized to a null pointer value}} if (flag) // expected-note{{'flag' is 1}} @@ -347,9 +365,8 @@ int getInt(); void foo() { int y; - y = 1; // tracking-note{{The value 1 is assigned to 'y'}} + y = 1; flag = y; // tracking-note{{The value 1 is assigned to 'flag'}} - } void f(int y) { @@ -378,9 +395,9 @@ void f() { int *x = 0; // expected-note{{'x' initialized to a null pointer value}} int y = 0; - foo(); // tracking-note{{Calling 'foo'}} - // tracking-note@-1{{Returning from 'foo'}} - y = flag; // tracking-note{{Value assigned to 'y'}} + foo(); // tracking-note{{Calling 'foo'}} + // tracking-note@-1{{Returning from 'foo'}} + y = flag; if (y) // expected-note{{Assuming 'y' is not equal to 0}} // expected-note@-1{{Taking true branch}} @@ -429,7 +446,7 @@ int getInt(); void f(int flag) { int *x = 0; // expected-note{{'x' initialized to a null pointer value}} - flag = getInt(); // tracking-note{{Value assigned to 'flag'}} + flag = getInt(); assert(flag); // tracking-note{{Calling 'assert'}} // tracking-note@-1{{Returning from 'assert'}} From 38af1e5c5b21880c1c553a0a4ccb4b99740eaedb Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Wed, 14 Aug 2019 12:20:08 +0000 Subject: [PATCH 101/181] [analyzer][CFG] Don't track the condition of asserts Well, what is says on the tin I guess! Some more changes: * Move isInevitablySinking() from BugReporter.cpp to CFGBlock's interface * Rename and move findBlockForNode() from BugReporter.cpp to ExplodedNode::getCFGBlock() Differential Revision: https://reviews.llvm.org/D65287 llvm-svn: 368836 (cherry picked from commit dd53bdbfdec7a179e8fb38008ab2b397572b3a69) --- clang/include/clang/Analysis/CFG.h | 4 + clang/lib/Analysis/CFG.cpp | 65 +++++ clang/lib/StaticAnalyzer/Core/BugReporter.cpp | 89 +------ .../Core/BugReporterVisitors.cpp | 52 +++- .../lib/StaticAnalyzer/Core/ExplodedGraph.cpp | 16 ++ .../track-control-dependency-conditions.cpp | 239 ++++++++++++++++++ 6 files changed, 365 insertions(+), 100 deletions(-) diff --git a/clang/include/clang/Analysis/CFG.h b/clang/include/clang/Analysis/CFG.h index 277b2292e5eac..2c38d51083393 100644 --- a/clang/include/clang/Analysis/CFG.h +++ b/clang/include/clang/Analysis/CFG.h @@ -855,6 +855,10 @@ class CFGBlock { void setLoopTarget(const Stmt *loopTarget) { LoopTarget = loopTarget; } void setHasNoReturnElement() { HasNoReturnElement = true; } + /// Returns true if the block would eventually end with a sink (a noreturn + /// node). + bool isInevitablySinking() const; + CFGTerminator getTerminator() const { return Terminator; } Stmt *getTerminatorStmt() { return Terminator.getStmt(); } diff --git a/clang/lib/Analysis/CFG.cpp b/clang/lib/Analysis/CFG.cpp index 80f5a46ceab47..3fe9892e73750 100644 --- a/clang/lib/Analysis/CFG.cpp +++ b/clang/lib/Analysis/CFG.cpp @@ -5615,6 +5615,71 @@ void CFGBlock::printTerminatorJson(raw_ostream &Out, const LangOptions &LO, Out << JsonFormat(TempOut.str(), AddQuotes); } +// Returns true if by simply looking at the block, we can be sure that it +// results in a sink during analysis. This is useful to know when the analysis +// was interrupted, and we try to figure out if it would sink eventually. +// There may be many more reasons why a sink would appear during analysis +// (eg. checkers may generate sinks arbitrarily), but here we only consider +// sinks that would be obvious by looking at the CFG. +static bool isImmediateSinkBlock(const CFGBlock *Blk) { + if (Blk->hasNoReturnElement()) + return true; + + // FIXME: Throw-expressions are currently generating sinks during analysis: + // they're not supported yet, and also often used for actually terminating + // the program. So we should treat them as sinks in this analysis as well, + // at least for now, but once we have better support for exceptions, + // we'd need to carefully handle the case when the throw is being + // immediately caught. + if (std::any_of(Blk->begin(), Blk->end(), [](const CFGElement &Elm) { + if (Optional StmtElm = Elm.getAs()) + if (isa(StmtElm->getStmt())) + return true; + return false; + })) + return true; + + return false; +} + +bool CFGBlock::isInevitablySinking() const { + const CFG &Cfg = *getParent(); + + const CFGBlock *StartBlk = this; + if (isImmediateSinkBlock(StartBlk)) + return true; + + llvm::SmallVector DFSWorkList; + llvm::SmallPtrSet Visited; + + DFSWorkList.push_back(StartBlk); + while (!DFSWorkList.empty()) { + const CFGBlock *Blk = DFSWorkList.back(); + DFSWorkList.pop_back(); + Visited.insert(Blk); + + // If at least one path reaches the CFG exit, it means that control is + // returned to the caller. For now, say that we are not sure what + // happens next. If necessary, this can be improved to analyze + // the parent StackFrameContext's call site in a similar manner. + if (Blk == &Cfg.getExit()) + return false; + + for (const auto &Succ : Blk->succs()) { + if (const CFGBlock *SuccBlk = Succ.getReachableBlock()) { + if (!isImmediateSinkBlock(SuccBlk) && !Visited.count(SuccBlk)) { + // If the block has reachable child blocks that aren't no-return, + // add them to the worklist. + DFSWorkList.push_back(SuccBlk); + } + } + } + } + + // Nothing reached the exit. It can only mean one thing: there's no return. + return true; +} + const Expr *CFGBlock::getLastCondition() const { // If the terminator is a temporary dtor or a virtual base, etc, we can't // retrieve a meaningful condition, bail out. diff --git a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp index 9f61ecccbc193..7a471dc0e883e 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp @@ -2716,90 +2716,6 @@ struct FRIEC_WLItem { } // namespace -static const CFGBlock *findBlockForNode(const ExplodedNode *N) { - ProgramPoint P = N->getLocation(); - if (auto BEP = P.getAs()) - return BEP->getBlock(); - - // Find the node's current statement in the CFG. - if (const Stmt *S = PathDiagnosticLocation::getStmt(N)) - return N->getLocationContext()->getAnalysisDeclContext() - ->getCFGStmtMap()->getBlock(S); - - return nullptr; -} - -// Returns true if by simply looking at the block, we can be sure that it -// results in a sink during analysis. This is useful to know when the analysis -// was interrupted, and we try to figure out if it would sink eventually. -// There may be many more reasons why a sink would appear during analysis -// (eg. checkers may generate sinks arbitrarily), but here we only consider -// sinks that would be obvious by looking at the CFG. -static bool isImmediateSinkBlock(const CFGBlock *Blk) { - if (Blk->hasNoReturnElement()) - return true; - - // FIXME: Throw-expressions are currently generating sinks during analysis: - // they're not supported yet, and also often used for actually terminating - // the program. So we should treat them as sinks in this analysis as well, - // at least for now, but once we have better support for exceptions, - // we'd need to carefully handle the case when the throw is being - // immediately caught. - if (std::any_of(Blk->begin(), Blk->end(), [](const CFGElement &Elm) { - if (Optional StmtElm = Elm.getAs()) - if (isa(StmtElm->getStmt())) - return true; - return false; - })) - return true; - - return false; -} - -// Returns true if by looking at the CFG surrounding the node's program -// point, we can be sure that any analysis starting from this point would -// eventually end with a sink. We scan the child CFG blocks in a depth-first -// manner and see if all paths eventually end up in an immediate sink block. -static bool isInevitablySinking(const ExplodedNode *N) { - const CFG &Cfg = N->getCFG(); - - const CFGBlock *StartBlk = findBlockForNode(N); - if (!StartBlk) - return false; - if (isImmediateSinkBlock(StartBlk)) - return true; - - llvm::SmallVector DFSWorkList; - llvm::SmallPtrSet Visited; - - DFSWorkList.push_back(StartBlk); - while (!DFSWorkList.empty()) { - const CFGBlock *Blk = DFSWorkList.back(); - DFSWorkList.pop_back(); - Visited.insert(Blk); - - // If at least one path reaches the CFG exit, it means that control is - // returned to the caller. For now, say that we are not sure what - // happens next. If necessary, this can be improved to analyze - // the parent StackFrameContext's call site in a similar manner. - if (Blk == &Cfg.getExit()) - return false; - - for (const auto &Succ : Blk->succs()) { - if (const CFGBlock *SuccBlk = Succ.getReachableBlock()) { - if (!isImmediateSinkBlock(SuccBlk) && !Visited.count(SuccBlk)) { - // If the block has reachable child blocks that aren't no-return, - // add them to the worklist. - DFSWorkList.push_back(SuccBlk); - } - } - } - } - - // Nothing reached the exit. It can only mean one thing: there's no return. - return true; -} - static BugReport * FindReportInEquivalenceClass(BugReportEquivClass& EQ, SmallVectorImpl &bugReports) { @@ -2851,8 +2767,9 @@ FindReportInEquivalenceClass(BugReportEquivClass& EQ, // to being post-dominated by a sink. This works better when the analysis // is incomplete and we have never reached the no-return function call(s) // that we'd inevitably bump into on this path. - if (isInevitablySinking(errorNode)) - continue; + if (const CFGBlock *ErrorB = errorNode->getCFGBlock()) + if (ErrorB->isInevitablySinking()) + continue; // At this point we know that 'N' is not a sink and it has at least one // successor. Use a DFS worklist to find a non-sink end-of-path node. diff --git a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp index 8c17a367b8a58..43347ed247a04 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp @@ -1710,18 +1710,6 @@ class TrackControlDependencyCondBRVisitor final : public BugReporterVisitor { }; } // end of anonymous namespace -static CFGBlock *GetRelevantBlock(const ExplodedNode *Node) { - if (auto SP = Node->getLocationAs()) { - const Stmt *S = SP->getStmt(); - assert(S); - - return const_cast(Node->getLocationContext() - ->getAnalysisDeclContext()->getCFGStmtMap()->getBlock(S)); - } - - return nullptr; -} - static std::shared_ptr constructDebugPieceForTrackedCondition(const Expr *Cond, const ExplodedNode *N, @@ -1742,24 +1730,60 @@ constructDebugPieceForTrackedCondition(const Expr *Cond, (Twine() + "Tracking condition '" + ConditionText + "'").str()); } +static bool isAssertlikeBlock(const CFGBlock *B, ASTContext &Context) { + if (B->succ_size() != 2) + return false; + + const CFGBlock *Then = B->succ_begin()->getReachableBlock(); + const CFGBlock *Else = (B->succ_begin() + 1)->getReachableBlock(); + + if (!Then || !Else) + return false; + + if (Then->isInevitablySinking() != Else->isInevitablySinking()) + return true; + + // For the following condition the following CFG would be built: + // + // -------------> + // / \ + // [B1] -> [B2] -> [B3] -> [sink] + // assert(A && B || C); \ \ + // -----------> [go on with the execution] + // + // It so happens that CFGBlock::getTerminatorCondition returns 'A' for block + // B1, 'A && B' for B2, and 'A && B || C' for B3. Let's check whether we + // reached the end of the condition! + if (const Stmt *ElseCond = Else->getTerminatorCondition()) + if (isa(ElseCond)) { + assert(cast(ElseCond)->isLogicalOp()); + return isAssertlikeBlock(Else, Context); + } + + return false; +} + PathDiagnosticPieceRef TrackControlDependencyCondBRVisitor::VisitNode( const ExplodedNode *N, BugReporterContext &BRC, BugReport &BR) { // We can only reason about control dependencies within the same stack frame. if (Origin->getStackFrame() != N->getStackFrame()) return nullptr; - CFGBlock *NB = GetRelevantBlock(N); + CFGBlock *NB = const_cast(N->getCFGBlock()); // Skip if we already inspected this block. if (!VisitedBlocks.insert(NB).second) return nullptr; - CFGBlock *OriginB = GetRelevantBlock(Origin); + CFGBlock *OriginB = const_cast(Origin->getCFGBlock()); // TODO: Cache CFGBlocks for each ExplodedNode. if (!OriginB || !NB) return nullptr; + if (isAssertlikeBlock(NB, BRC.getASTContext())) + return nullptr; + if (ControlDeps.isControlDependent(OriginB, NB)) { if (const Expr *Condition = NB->getLastCondition()) { // Keeping track of the already tracked conditions on a visitor level diff --git a/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp b/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp index 15e8d62a52cd0..03e813e1e676b 100644 --- a/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp @@ -16,6 +16,7 @@ #include "clang/AST/ExprObjC.h" #include "clang/AST/ParentMap.h" #include "clang/AST/Stmt.h" +#include "clang/Analysis/CFGStmtMap.h" #include "clang/Analysis/ProgramPoint.h" #include "clang/Analysis/Support/BumpVector.h" #include "clang/Basic/LLVM.h" @@ -292,6 +293,21 @@ bool ExplodedNode::isTrivial() const { getFirstPred()->succ_size() == 1; } +const CFGBlock *ExplodedNode::getCFGBlock() const { + ProgramPoint P = getLocation(); + if (auto BEP = P.getAs()) + return BEP->getBlock(); + + // Find the node's current statement in the CFG. + if (const Stmt *S = PathDiagnosticLocation::getStmt(this)) + return getLocationContext() + ->getAnalysisDeclContext() + ->getCFGStmtMap() + ->getBlock(S); + + return nullptr; +} + ExplodedNode *ExplodedGraph::getNode(const ProgramPoint &L, ProgramStateRef State, bool IsSink, diff --git a/clang/test/Analysis/track-control-dependency-conditions.cpp b/clang/test/Analysis/track-control-dependency-conditions.cpp index 35769f24739fc..b91448660299b 100644 --- a/clang/test/Analysis/track-control-dependency-conditions.cpp +++ b/clang/test/Analysis/track-control-dependency-conditions.cpp @@ -458,3 +458,242 @@ void f(int flag) { } } // end of namespace unimportant_write_before_collapse_point + +namespace dont_track_assertlike_conditions { + +extern void __assert_fail(__const char *__assertion, __const char *__file, + unsigned int __line, __const char *__function) + __attribute__((__noreturn__)); +#define assert(expr) \ + ((expr) ? (void)(0) : __assert_fail(#expr, __FILE__, __LINE__, __func__)) + +int getInt(); + +int cond1; + +void bar() { + cond1 = getInt(); +} + +void f(int flag) { + int *x = 0; // expected-note{{'x' initialized to a null pointer value}} + + flag = getInt(); + + bar(); + assert(cond1); // expected-note{{Assuming 'cond1' is not equal to 0}} + // expected-note@-1{{'?' condition is true}} + + if (flag) // expected-note{{'flag' is not equal to 0}} + // expected-note@-1{{Taking true branch}} + // debug-note@-2{{Tracking condition 'flag'}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} + +#undef assert +} // end of namespace dont_track_assertlike_conditions + +namespace dont_track_assertlike_and_conditions { + +extern void __assert_fail(__const char *__assertion, __const char *__file, + unsigned int __line, __const char *__function) + __attribute__((__noreturn__)); +#define assert(expr) \ + ((expr) ? (void)(0) : __assert_fail(#expr, __FILE__, __LINE__, __func__)) + +int getInt(); + +int cond1; +int cond2; + +void bar() { + cond1 = getInt(); + cond2 = getInt(); +} + +void f(int flag) { + int *x = 0; // expected-note{{'x' initialized to a null pointer value}} + + flag = getInt(); + + bar(); + assert(cond1 && cond2); + // expected-note@-1{{Assuming 'cond1' is not equal to 0}} + // expected-note@-2{{Assuming 'cond2' is not equal to 0}} + // expected-note@-3{{'?' condition is true}} + // expected-note@-4{{Left side of '&&' is true}} + + if (flag) // expected-note{{'flag' is not equal to 0}} + // expected-note@-1{{Taking true branch}} + // debug-note@-2{{Tracking condition 'flag'}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} + +#undef assert +} // end of namespace dont_track_assertlike_and_conditions + +namespace dont_track_assertlike_or_conditions { + +extern void __assert_fail(__const char *__assertion, __const char *__file, + unsigned int __line, __const char *__function) + __attribute__((__noreturn__)); +#define assert(expr) \ + ((expr) ? (void)(0) : __assert_fail(#expr, __FILE__, __LINE__, __func__)) + +int getInt(); + +int cond1; +int cond2; + +void bar() { + cond1 = getInt(); + cond2 = getInt(); +} + +void f(int flag) { + int *x = 0; // expected-note{{'x' initialized to a null pointer value}} + + flag = getInt(); + + bar(); + assert(cond1 || cond2); + // expected-note@-1{{Assuming 'cond1' is not equal to 0}} + // expected-note@-2{{Left side of '||' is true}} + + if (flag) // expected-note{{'flag' is not equal to 0}} + // expected-note@-1{{Taking true branch}} + // debug-note@-2{{Tracking condition 'flag'}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} + +#undef assert +} // end of namespace dont_track_assertlike_or_conditions + +namespace dont_track_assert2like_conditions { + +extern void __assert_fail(__const char *__assertion, __const char *__file, + unsigned int __line, __const char *__function) + __attribute__((__noreturn__)); +#define assert(expr) \ + do { \ + if (!(expr)) \ + __assert_fail(#expr, __FILE__, __LINE__, __func__); \ + } while (0) + +int getInt(); + +int cond1; + +void bar() { + cond1 = getInt(); +} + +void f(int flag) { + int *x = 0; // expected-note{{'x' initialized to a null pointer value}} + + flag = getInt(); + + bar(); + assert(cond1); // expected-note{{Assuming 'cond1' is not equal to 0}} + // expected-note@-1{{Taking false branch}} + // expected-note@-2{{Loop condition is false. Exiting loop}} + + if (flag) // expected-note{{'flag' is not equal to 0}} + // expected-note@-1{{Taking true branch}} + // debug-note@-2{{Tracking condition 'flag'}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} + +#undef assert +} // end of namespace dont_track_assert2like_conditions + +namespace dont_track_assert2like_and_conditions { + +extern void __assert_fail(__const char *__assertion, __const char *__file, + unsigned int __line, __const char *__function) + __attribute__((__noreturn__)); +#define assert(expr) \ + do { \ + if (!(expr)) \ + __assert_fail(#expr, __FILE__, __LINE__, __func__); \ + } while (0) + +int getInt(); + +int cond1; +int cond2; + +void bar() { + cond1 = getInt(); + cond2 = getInt(); +} + +void f(int flag) { + int *x = 0; // expected-note{{'x' initialized to a null pointer value}} + + flag = getInt(); + + bar(); + assert(cond1 && cond2); + // expected-note@-1{{Assuming 'cond1' is not equal to 0}} + // expected-note@-2{{Left side of '&&' is true}} + // expected-note@-3{{Assuming the condition is false}} + // expected-note@-4{{Taking false branch}} + // expected-note@-5{{Loop condition is false. Exiting loop}} + + if (flag) // expected-note{{'flag' is not equal to 0}} + // expected-note@-1{{Taking true branch}} + // debug-note@-2{{Tracking condition 'flag'}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} + +#undef assert +} // end of namespace dont_track_assert2like_and_conditions + +namespace dont_track_assert2like_or_conditions { + +extern void __assert_fail(__const char *__assertion, __const char *__file, + unsigned int __line, __const char *__function) + __attribute__((__noreturn__)); +#define assert(expr) \ + do { \ + if (!(expr)) \ + __assert_fail(#expr, __FILE__, __LINE__, __func__); \ + } while (0) + +int getInt(); + +int cond1; +int cond2; + +void bar() { + cond1 = getInt(); + cond2 = getInt(); +} + +void f(int flag) { + int *x = 0; // expected-note{{'x' initialized to a null pointer value}} + + flag = getInt(); + + bar(); + assert(cond1 || cond2); + // expected-note@-1{{Assuming 'cond1' is not equal to 0}} + // expected-note@-2{{Left side of '||' is true}} + // expected-note@-3{{Taking false branch}} + // expected-note@-4{{Loop condition is false. Exiting loop}} + + if (flag) // expected-note{{'flag' is not equal to 0}} + // expected-note@-1{{Taking true branch}} + // debug-note@-2{{Tracking condition 'flag'}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} + +#undef assert +} // end of namespace dont_track_assert2like_or_conditions From 76e9a3254632a1625a50f22df5d269b30ac2f214 Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Wed, 14 Aug 2019 13:51:52 +0000 Subject: [PATCH 102/181] [analyzer][NFC] Prove that we only track the evaluated part of the condition ...because we're working with a BugReporterVisitor, and the non-evaluated part of the condition isn't in the bugpath. Differential Revision: https://reviews.llvm.org/D65290 llvm-svn: 368853 (cherry picked from commit 571c52af58d91662f2a7f2c7570bab63dee71a53) --- .../track-control-dependency-conditions.cpp | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/clang/test/Analysis/track-control-dependency-conditions.cpp b/clang/test/Analysis/track-control-dependency-conditions.cpp index b91448660299b..016ba3c5dca30 100644 --- a/clang/test/Analysis/track-control-dependency-conditions.cpp +++ b/clang/test/Analysis/track-control-dependency-conditions.cpp @@ -532,6 +532,7 @@ void f(int flag) { } #undef assert + } // end of namespace dont_track_assertlike_and_conditions namespace dont_track_assertlike_or_conditions { @@ -697,3 +698,37 @@ void f(int flag) { #undef assert } // end of namespace dont_track_assert2like_or_conditions + +namespace only_track_the_evaluated_condition { + +bool coin(); + +void bar(int &flag) { + flag = coin(); // tracking-note{{Value assigned to 'flag'}} +} + +void bar2(int &flag2) { + flag2 = coin(); +} + +void f(int *x) { + if (x) // expected-note{{Assuming 'x' is null}} + // debug-note@-1{{Tracking condition 'x'}} + // expected-note@-2{{Taking false branch}} + return; + + int flag, flag2; + bar(flag); // tracking-note{{Calling 'bar'}} + // tracking-note@-1{{Returning from 'bar'}} + bar2(flag2); + + if (flag && flag2) // expected-note {{Assuming 'flag' is 0}} + // expected-note@-1{{Left side of '&&' is false}} + // debug-note@-2{{Tracking condition 'flag'}} + return; + + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} + +} // end of namespace only_track_the_evaluated_condition From b61668fa604e50f2ceeb6a03785579d8455f1b26 Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Wed, 14 Aug 2019 17:05:55 +0000 Subject: [PATCH 103/181] [CFG] Introduce CFGElementRef, a wrapper that knows it's position in a CFGBlock Previously, collecting CFGElements in a set was practially impossible, because both CFGBlock::operator[] and both the iterators returned it by value. One workaround would be to collect the iterators instead, but they don't really capture the concept of an element, and elements from different iterator types are incomparable. This patch introduces CFGElementRef, a wrapper around a (CFGBlock, Index) pair, and a variety of new iterators and iterator ranges to solve this problem. I guess you could say that this patch took a couple iterations to get right :^) Differential Revision: https://reviews.llvm.org/D65196 llvm-svn: 368883 (cherry picked from commit 92541e359e4bdd2e9a149a2bb7d1519f77244655) --- clang/include/clang/Analysis/CFG.h | 175 ++++++++++++++++++++++++++- clang/lib/Analysis/CFG.cpp | 4 + clang/unittests/Analysis/CFGTest.cpp | 133 ++++++++++++++++++++ 3 files changed, 310 insertions(+), 2 deletions(-) diff --git a/clang/include/clang/Analysis/CFG.h b/clang/include/clang/Analysis/CFG.h index 2c38d51083393..4c375c7a069e0 100644 --- a/clang/include/clang/Analysis/CFG.h +++ b/clang/include/clang/Analysis/CFG.h @@ -610,6 +610,144 @@ class CFGBlock { bool empty() const { return Impl.empty(); } }; + /// A convenience class for comparing CFGElements, since methods of CFGBlock + /// like operator[] return CFGElements by value. This is practically a wrapper + /// around a (CFGBlock, Index) pair. + template class ElementRefImpl { + + template friend class ElementRefImpl; + + using CFGBlockPtr = + typename std::conditional::type; + + using CFGElementPtr = typename std::conditional::type; + + protected: + CFGBlockPtr Parent; + size_t Index; + + public: + ElementRefImpl(CFGBlockPtr Parent, size_t Index) + : Parent(Parent), Index(Index) {} + + template + ElementRefImpl(ElementRefImpl Other) + : ElementRefImpl(Other.Parent, Other.Index) {} + + size_t getIndexInBlock() const { return Index; } + + CFGBlockPtr getParent() { return Parent; } + CFGBlockPtr getParent() const { return Parent; } + + bool operator<(ElementRefImpl Other) const { + return std::make_pair(Parent, Index) < + std::make_pair(Other.Parent, Other.Index); + } + + bool operator==(ElementRefImpl Other) const { + return Parent == Other.Parent && Index == Other.Index; + } + + bool operator!=(ElementRefImpl Other) const { return !(*this == Other); } + CFGElement operator*() { return (*Parent)[Index]; } + CFGElementPtr operator->() { return &*(Parent->begin() + Index); } + }; + + template class ElementRefIterator { + + template + friend class ElementRefIterator; + + using CFGBlockRef = + typename std::conditional::type; + + using UnderlayingIteratorTy = typename std::conditional< + IsConst, + typename std::conditional::type, + typename std::conditional::type>::type; + + using IteratorTraits = typename std::iterator_traits; + using ElementRef = typename CFGBlock::ElementRefImpl; + + public: + using difference_type = typename IteratorTraits::difference_type; + using value_type = ElementRef; + using pointer = ElementRef *; + using iterator_category = typename IteratorTraits::iterator_category; + + private: + CFGBlockRef Parent; + UnderlayingIteratorTy Pos; + + public: + ElementRefIterator(CFGBlockRef Parent, UnderlayingIteratorTy Pos) + : Parent(Parent), Pos(Pos) {} + + template + ElementRefIterator(ElementRefIterator E) + : ElementRefIterator(E.Parent, E.Pos.base()) {} + + template + ElementRefIterator(ElementRefIterator E) + : ElementRefIterator(E.Parent, llvm::make_reverse_iterator(E.Pos)) {} + + bool operator<(ElementRefIterator Other) const { + assert(Parent == Other.Parent); + return Pos < Other.Pos; + } + + bool operator==(ElementRefIterator Other) const { + return Parent == Other.Parent && Pos == Other.Pos; + } + + bool operator!=(ElementRefIterator Other) const { + return !(*this == Other); + } + + private: + template + static size_t + getIndexInBlock(CFGBlock::ElementRefIterator E) { + return E.Parent->size() - (E.Pos - E.Parent->rbegin()) - 1; + } + + template + static size_t + getIndexInBlock(CFGBlock::ElementRefIterator E) { + return E.Pos - E.Parent->begin(); + } + + public: + value_type operator*() { return {Parent, getIndexInBlock(*this)}; } + + difference_type operator-(ElementRefIterator Other) const { + return Pos - Other.Pos; + } + + ElementRefIterator operator++() { + ++this->Pos; + return *this; + } + ElementRefIterator operator++(int) { + ElementRefIterator Ret = *this; + ++*this; + return Ret; + } + ElementRefIterator operator+(size_t count) { + this->Pos += count; + return *this; + } + ElementRefIterator operator-(size_t count) { + this->Pos -= count; + return *this; + } + }; + +public: /// The set of statements in the basic block. ElementList Elements; @@ -715,6 +853,8 @@ class CFGBlock { using reverse_iterator = ElementList::reverse_iterator; using const_reverse_iterator = ElementList::const_reverse_iterator; + size_t getIndexInCFG() const; + CFGElement front() const { return Elements.front(); } CFGElement back() const { return Elements.back(); } @@ -728,6 +868,38 @@ class CFGBlock { const_reverse_iterator rbegin() const { return Elements.rbegin(); } const_reverse_iterator rend() const { return Elements.rend(); } + using CFGElementRef = ElementRefImpl; + using ConstCFGElementRef = ElementRefImpl; + + using ref_iterator = ElementRefIterator; + using ref_iterator_range = llvm::iterator_range; + using const_ref_iterator = ElementRefIterator; + using const_ref_iterator_range = llvm::iterator_range; + + using reverse_ref_iterator = ElementRefIterator; + using reverse_ref_iterator_range = llvm::iterator_range; + + using const_reverse_ref_iterator = ElementRefIterator; + using const_reverse_ref_iterator_range = + llvm::iterator_range; + + ref_iterator ref_begin() { return {this, begin()}; } + ref_iterator ref_end() { return {this, end()}; } + const_ref_iterator ref_begin() const { return {this, begin()}; } + const_ref_iterator ref_end() const { return {this, end()}; } + + reverse_ref_iterator rref_begin() { return {this, rbegin()}; } + reverse_ref_iterator rref_end() { return {this, rend()}; } + const_reverse_ref_iterator rref_begin() const { return {this, rbegin()}; } + const_reverse_ref_iterator rref_end() const { return {this, rend()}; } + + ref_iterator_range refs() { return {ref_begin(), ref_end()}; } + const_ref_iterator_range refs() const { return {ref_begin(), ref_end()}; } + reverse_ref_iterator_range rrefs() { return {rref_begin(), rref_end()}; } + const_reverse_ref_iterator_range rrefs() const { + return {rref_begin(), rref_end()}; + } + unsigned size() const { return Elements.size(); } bool empty() const { return Elements.empty(); } @@ -898,7 +1070,7 @@ class CFGBlock { void printTerminator(raw_ostream &OS, const LangOptions &LO) const; void printTerminatorJson(raw_ostream &Out, const LangOptions &LO, bool AddQuotes) const; - + void printAsOperand(raw_ostream &OS, bool /*PrintType*/) { OS << "BB#" << getBlockID(); } @@ -1014,7 +1186,6 @@ class CFGBlock { *I = CFGScopeEnd(VD, S); return ++I; } - }; /// CFGCallback defines methods that should be called when a logical diff --git a/clang/lib/Analysis/CFG.cpp b/clang/lib/Analysis/CFG.cpp index 3fe9892e73750..0f813566007ab 100644 --- a/clang/lib/Analysis/CFG.cpp +++ b/clang/lib/Analysis/CFG.cpp @@ -5578,6 +5578,10 @@ void CFG::print(raw_ostream &OS, const LangOptions &LO, bool ShowColors) const { OS.flush(); } +size_t CFGBlock::getIndexInCFG() const { + return llvm::find(*getParent(), this) - getParent()->begin(); +} + /// dump - A simply pretty printer of a CFGBlock that outputs to stderr. void CFGBlock::dump(const CFG* cfg, const LangOptions &LO, bool ShowColors) const { diff --git a/clang/unittests/Analysis/CFGTest.cpp b/clang/unittests/Analysis/CFGTest.cpp index 7cd3da2db5fb1..27071dc5a5d99 100644 --- a/clang/unittests/Analysis/CFGTest.cpp +++ b/clang/unittests/Analysis/CFGTest.cpp @@ -67,6 +67,139 @@ TEST(CFG, IsLinear) { expectLinear(true, "void foo() { foo(); }"); // Recursion is not our problem. } +TEST(CFG, ElementRefIterator) { + const char *Code = R"(void f() { + int i; + int j; + i = 5; + i = 6; + j = 7; + })"; + + BuildResult B = BuildCFG(Code); + EXPECT_EQ(BuildResult::BuiltCFG, B.getStatus()); + CFG *Cfg = B.getCFG(); + + // [B2 (ENTRY)] + // Succs (1): B1 + + // [B1] + // 1: int i; + // 2: int j; + // 3: i = 5 + // 4: i = 6 + // 5: j = 7 + // Preds (1): B2 + // Succs (1): B0 + + // [B0 (EXIT)] + // Preds (1): B1 + CFGBlock *MainBlock = *(Cfg->begin() + 1); + + constexpr CFGBlock::ref_iterator::difference_type MainBlockSize = 4; + + // The rest of this test looks totally insane, but there iterators are + // templates under the hood, to it's important to instantiate everything for + // proper converage. + + // Non-reverse, non-const version + size_t Index = 0; + for (CFGBlock::CFGElementRef ElementRef : MainBlock->refs()) { + EXPECT_EQ(ElementRef.getParent(), MainBlock); + EXPECT_EQ(ElementRef.getIndexInBlock(), Index); + EXPECT_TRUE(ElementRef->getAs()); + EXPECT_TRUE((*ElementRef).getAs()); + EXPECT_EQ(ElementRef.getParent(), MainBlock); + ++Index; + } + EXPECT_TRUE(*MainBlock->ref_begin() < *(MainBlock->ref_begin() + 1)); + EXPECT_TRUE(*MainBlock->ref_begin() == *MainBlock->ref_begin()); + EXPECT_FALSE(*MainBlock->ref_begin() != *MainBlock->ref_begin()); + + EXPECT_TRUE(MainBlock->ref_begin() < MainBlock->ref_begin() + 1); + EXPECT_TRUE(MainBlock->ref_begin() == MainBlock->ref_begin()); + EXPECT_FALSE(MainBlock->ref_begin() != MainBlock->ref_begin()); + EXPECT_EQ(MainBlock->ref_end() - MainBlock->ref_begin(), MainBlockSize + 1); + EXPECT_EQ(MainBlock->ref_end() - MainBlockSize - 1, MainBlock->ref_begin()); + EXPECT_EQ(MainBlock->ref_begin() + MainBlockSize + 1, MainBlock->ref_end()); + EXPECT_EQ(MainBlock->ref_begin()++, MainBlock->ref_begin()); + EXPECT_EQ(++(MainBlock->ref_begin()), MainBlock->ref_begin() + 1); + + // Non-reverse, non-const version + const CFGBlock *CMainBlock = MainBlock; + Index = 0; + for (CFGBlock::ConstCFGElementRef ElementRef : CMainBlock->refs()) { + EXPECT_EQ(ElementRef.getParent(), CMainBlock); + EXPECT_EQ(ElementRef.getIndexInBlock(), Index); + EXPECT_TRUE(ElementRef->getAs()); + EXPECT_TRUE((*ElementRef).getAs()); + EXPECT_EQ(ElementRef.getParent(), MainBlock); + ++Index; + } + EXPECT_TRUE(*CMainBlock->ref_begin() < *(CMainBlock->ref_begin() + 1)); + EXPECT_TRUE(*CMainBlock->ref_begin() == *CMainBlock->ref_begin()); + EXPECT_FALSE(*CMainBlock->ref_begin() != *CMainBlock->ref_begin()); + + EXPECT_TRUE(CMainBlock->ref_begin() < CMainBlock->ref_begin() + 1); + EXPECT_TRUE(CMainBlock->ref_begin() == CMainBlock->ref_begin()); + EXPECT_FALSE(CMainBlock->ref_begin() != CMainBlock->ref_begin()); + EXPECT_EQ(CMainBlock->ref_end() - CMainBlock->ref_begin(), MainBlockSize + 1); + EXPECT_EQ(CMainBlock->ref_end() - MainBlockSize - 1, CMainBlock->ref_begin()); + EXPECT_EQ(CMainBlock->ref_begin() + MainBlockSize + 1, CMainBlock->ref_end()); + EXPECT_EQ(CMainBlock->ref_begin()++, CMainBlock->ref_begin()); + EXPECT_EQ(++(CMainBlock->ref_begin()), CMainBlock->ref_begin() + 1); + + // Reverse, non-const version + Index = MainBlockSize; + for (CFGBlock::CFGElementRef ElementRef : MainBlock->rrefs()) { + llvm::errs() << Index << '\n'; + EXPECT_EQ(ElementRef.getParent(), MainBlock); + EXPECT_EQ(ElementRef.getIndexInBlock(), Index); + EXPECT_TRUE(ElementRef->getAs()); + EXPECT_TRUE((*ElementRef).getAs()); + EXPECT_EQ(ElementRef.getParent(), MainBlock); + --Index; + } + EXPECT_FALSE(*MainBlock->rref_begin() < *(MainBlock->rref_begin() + 1)); + EXPECT_TRUE(*MainBlock->rref_begin() == *MainBlock->rref_begin()); + EXPECT_FALSE(*MainBlock->rref_begin() != *MainBlock->rref_begin()); + + EXPECT_TRUE(MainBlock->rref_begin() < MainBlock->rref_begin() + 1); + EXPECT_TRUE(MainBlock->rref_begin() == MainBlock->rref_begin()); + EXPECT_FALSE(MainBlock->rref_begin() != MainBlock->rref_begin()); + EXPECT_EQ(MainBlock->rref_end() - MainBlock->rref_begin(), MainBlockSize + 1); + EXPECT_EQ(MainBlock->rref_end() - MainBlockSize - 1, MainBlock->rref_begin()); + EXPECT_EQ(MainBlock->rref_begin() + MainBlockSize + 1, MainBlock->rref_end()); + EXPECT_EQ(MainBlock->rref_begin()++, MainBlock->rref_begin()); + EXPECT_EQ(++(MainBlock->rref_begin()), MainBlock->rref_begin() + 1); + + // Reverse, const version + Index = MainBlockSize; + for (CFGBlock::ConstCFGElementRef ElementRef : CMainBlock->rrefs()) { + EXPECT_EQ(ElementRef.getParent(), CMainBlock); + EXPECT_EQ(ElementRef.getIndexInBlock(), Index); + EXPECT_TRUE(ElementRef->getAs()); + EXPECT_TRUE((*ElementRef).getAs()); + EXPECT_EQ(ElementRef.getParent(), MainBlock); + --Index; + } + EXPECT_FALSE(*CMainBlock->rref_begin() < *(CMainBlock->rref_begin() + 1)); + EXPECT_TRUE(*CMainBlock->rref_begin() == *CMainBlock->rref_begin()); + EXPECT_FALSE(*CMainBlock->rref_begin() != *CMainBlock->rref_begin()); + + EXPECT_TRUE(CMainBlock->rref_begin() < CMainBlock->rref_begin() + 1); + EXPECT_TRUE(CMainBlock->rref_begin() == CMainBlock->rref_begin()); + EXPECT_FALSE(CMainBlock->rref_begin() != CMainBlock->rref_begin()); + EXPECT_EQ(CMainBlock->rref_end() - CMainBlock->rref_begin(), + MainBlockSize + 1); + EXPECT_EQ(CMainBlock->rref_end() - MainBlockSize - 1, + CMainBlock->rref_begin()); + EXPECT_EQ(CMainBlock->rref_begin() + MainBlockSize + 1, + CMainBlock->rref_end()); + EXPECT_EQ(CMainBlock->rref_begin()++, CMainBlock->rref_begin()); + EXPECT_EQ(++(CMainBlock->rref_begin()), CMainBlock->rref_begin() + 1); +} + } // namespace } // namespace analysis } // namespace clang From 80789dfda68dd0fdf552f075017aa8a9fea385e4 Mon Sep 17 00:00:00 2001 From: Alex Langford Date: Thu, 15 Aug 2019 00:58:51 +0000 Subject: [PATCH 104/181] [NFCI] Always initialize BugReport const fields Summary: Some compilers require that const fields of an object must be explicitly initialized by the constructor. I ran into this issue building with clang 3.8 on Ubuntu 16.04. Reviewers: compnerd, Szelethus, NoQ Subscribers: cfe-commits, llvm-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D66265 llvm-svn: 368950 (cherry picked from commit 0630bbc7fe030999cfe76f698bc571540fdbda66) --- .../clang/StaticAnalyzer/Core/BugReporter/BugReporter.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h index c3d7ba3120fdd..c2a5bb7f629fd 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h @@ -180,10 +180,12 @@ class BugReport : public llvm::ilist_node { /// to the user. This method allows to rest the location which should be used /// for uniquing reports. For example, memory leaks checker, could set this to /// the allocation site, rather then the location where the bug is reported. - BugReport(BugType& bt, StringRef desc, const ExplodedNode *errornode, + BugReport(BugType &bt, StringRef desc, const ExplodedNode *errornode, PathDiagnosticLocation LocationToUnique, const Decl *DeclToUnique) : BT(bt), Description(desc), UniqueingLocation(LocationToUnique), - UniqueingDecl(DeclToUnique), ErrorNode(errornode) {} + UniqueingDecl(DeclToUnique), ErrorNode(errornode), + ErrorNodeRange(getStmt() ? getStmt()->getSourceRange() + : SourceRange()) {} virtual ~BugReport() = default; From 23ab9d8aff3b52e37a9a1c945e0afaf1a419b829 Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Thu, 15 Aug 2019 08:53:16 +0000 Subject: [PATCH 105/181] [analyzer] Warn about -analyzer-configs being meant for development purposes only This is more of a temporary fix, long term, we should convert AnalyzerOptions.def into the universally beloved (*coughs*) TableGen format, where they can more easily be separated into developer-only, alpha, and user-facing configs. Differential Revision: https://reviews.llvm.org/D66261 llvm-svn: 368980 (cherry picked from commit a1aef90bdd9ae7febce70f96f4a05a0074c8b556) --- clang/include/clang/Driver/CC1Options.td | 3 ++- .../Frontend/CheckerRegistration.cpp | 21 ++++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/clang/include/clang/Driver/CC1Options.td b/clang/include/clang/Driver/CC1Options.td index 80f6d9e3b29bf..bd289ffef2839 100644 --- a/clang/include/clang/Driver/CC1Options.td +++ b/clang/include/clang/Driver/CC1Options.td @@ -140,7 +140,8 @@ def analyzer_checker_help_developer : Flag<["-"], "analyzer-checker-help-develop "and debug checkers">; def analyzer_config_help : Flag<["-"], "analyzer-config-help">, - HelpText<"Display the list of -analyzer-config options">; + HelpText<"Display the list of -analyzer-config options. These are meant for " + "development purposes only!">; def analyzer_list_enabled_checkers : Flag<["-"], "analyzer-list-enabled-checkers">, HelpText<"Display the list of enabled analyzer checkers">; diff --git a/clang/lib/StaticAnalyzer/Frontend/CheckerRegistration.cpp b/clang/lib/StaticAnalyzer/Frontend/CheckerRegistration.cpp index 1e45ee96145ab..27555537c89e4 100644 --- a/clang/lib/StaticAnalyzer/Frontend/CheckerRegistration.cpp +++ b/clang/lib/StaticAnalyzer/Frontend/CheckerRegistration.cpp @@ -74,11 +74,22 @@ void ento::printCheckerConfigList(raw_ostream &OS, } void ento::printAnalyzerConfigList(raw_ostream &out) { - out << "OVERVIEW: Clang Static Analyzer -analyzer-config Option List\n\n"; - out << "USAGE: -analyzer-config \n\n"; - out << " -analyzer-config OPTION1=VALUE, -analyzer-config " - "OPTION2=VALUE, ...\n\n"; - out << "OPTIONS:\n\n"; + // FIXME: This message sounds scary, should be scary, but incorrectly states + // that all configs are super dangerous. In reality, many of them should be + // accessible to the user. We should create a user-facing subset of config + // options under a different frontend flag. + out << R"( +OVERVIEW: Clang Static Analyzer -analyzer-config Option List + +The following list of configurations are meant for development purposes only, as +some of the variables they define are set to result in the most optimal +analysis. Setting them to other values may drastically change how the analyzer +behaves, and may even result in instabilities, crashes! + +USAGE: -analyzer-config + -analyzer-config OPTION1=VALUE, -analyzer-config OPTION2=VALUE, ... +OPTIONS: +)"; using OptionAndDescriptionTy = std::pair; OptionAndDescriptionTy PrintableOptions[] = { From b2b970ad94548ca774875c2212732a172b799ded Mon Sep 17 00:00:00 2001 From: Csaba Dabis Date: Fri, 16 Aug 2019 01:53:14 +0000 Subject: [PATCH 106/181] [analyzer] Analysis: Silence checkers Summary: This patch introduces a new `analyzer-config` configuration: `-analyzer-config silence-checkers` which could be used to silence the given checkers. It accepts a semicolon separated list, packed into quotation marks, e.g: `-analyzer-config silence-checkers="core.DivideZero;core.NullDereference"` It could be used to "disable" core checkers, so they model the analysis as before, just if some of them are too noisy it prevents to emit reports. This patch also adds support for that new option to the scan-build. Passing the option `-disable-checker core.DivideZero` to the scan-build will be transferred to `-analyzer-config silence-checkers=core.DivideZero`. Reviewed By: NoQ, Szelethus Differential Revision: https://reviews.llvm.org/D66042 llvm-svn: 369078 (cherry picked from commit a079a4270851667d61ecbaa20b34d0dea5bbfbfc) --- clang-tools-extra/clang-tidy/ClangTidy.cpp | 12 +++--- .../clang/Basic/DiagnosticCommonKinds.td | 2 +- .../StaticAnalyzer/Core/AnalyzerOptions.def | 18 +++++--- .../StaticAnalyzer/Core/AnalyzerOptions.h | 41 +++++++++++++++++-- clang/lib/Frontend/CompilerInvocation.cpp | 40 ++++++++++++++---- .../StaticAnalyzer/Core/AnalyzerOptions.cpp | 19 --------- clang/lib/StaticAnalyzer/Core/BugReporter.cpp | 26 ++++++++---- .../Frontend/CheckerRegistry.cpp | 22 ++++++---- clang/test/Analysis/analyzer-config.c | 3 +- ...silence-checkers-and-packages-core-all.cpp | 39 ++++++++++++++++++ ...checkers-and-packages-core-div-by-zero.cpp | 18 ++++++++ clang/tools/scan-build/bin/scan-build | 18 ++++++-- .../RegisterCustomCheckersTest.cpp | 2 +- 13 files changed, 196 insertions(+), 64 deletions(-) create mode 100644 clang/test/Analysis/silence-checkers-and-packages-core-all.cpp create mode 100644 clang/test/Analysis/silence-checkers-and-packages-core-div-by-zero.cpp diff --git a/clang-tools-extra/clang-tidy/ClangTidy.cpp b/clang-tools-extra/clang-tidy/ClangTidy.cpp index 69d758e9750ed..61878930c0424 100644 --- a/clang-tools-extra/clang-tidy/ClangTidy.cpp +++ b/clang-tools-extra/clang-tidy/ClangTidy.cpp @@ -335,8 +335,8 @@ static void setStaticAnalyzerCheckerOpts(const ClangTidyOptions &Opts, typedef std::vector> CheckersList; -static CheckersList getCheckersControlList(ClangTidyContext &Context, - bool IncludeExperimental) { +static CheckersList getAnalyzerCheckersAndPackages(ClangTidyContext &Context, + bool IncludeExperimental) { CheckersList List; const auto &RegisteredCheckers = @@ -420,9 +420,9 @@ ClangTidyASTConsumerFactory::CreateASTConsumer( #if CLANG_ENABLE_STATIC_ANALYZER AnalyzerOptionsRef AnalyzerOptions = Compiler.getAnalyzerOpts(); - AnalyzerOptions->CheckersControlList = - getCheckersControlList(Context, Context.canEnableAnalyzerAlphaCheckers()); - if (!AnalyzerOptions->CheckersControlList.empty()) { + AnalyzerOptions->CheckersAndPackages = getAnalyzerCheckersAndPackages( + Context, Context.canEnableAnalyzerAlphaCheckers()); + if (!AnalyzerOptions->CheckersAndPackages.empty()) { setStaticAnalyzerCheckerOpts(Context.getOptions(), AnalyzerOptions); AnalyzerOptions->AnalysisStoreOpt = RegionStoreModel; AnalyzerOptions->AnalysisDiagOpt = PD_NONE; @@ -448,7 +448,7 @@ std::vector ClangTidyASTConsumerFactory::getCheckNames() { } #if CLANG_ENABLE_STATIC_ANALYZER - for (const auto &AnalyzerCheck : getCheckersControlList( + for (const auto &AnalyzerCheck : getAnalyzerCheckersAndPackages( Context, Context.canEnableAnalyzerAlphaCheckers())) CheckNames.push_back(AnalyzerCheckNamePrefix + AnalyzerCheck.first); #endif // CLANG_ENABLE_STATIC_ANALYZER diff --git a/clang/include/clang/Basic/DiagnosticCommonKinds.td b/clang/include/clang/Basic/DiagnosticCommonKinds.td index 5713c3409b4f1..b22912c4ca2f2 100644 --- a/clang/include/clang/Basic/DiagnosticCommonKinds.td +++ b/clang/include/clang/Basic/DiagnosticCommonKinds.td @@ -311,7 +311,7 @@ def err_omp_more_one_clause : Error< "directive '#pragma omp %0' cannot contain more than one '%1' clause%select{| with '%3' name modifier| with 'source' dependence}2">; // Static Analyzer Core -def err_unknown_analyzer_checker : Error< +def err_unknown_analyzer_checker_or_package : Error< "no analyzer checkers or packages are associated with '%0'">; def note_suggest_disabling_all_checkers : Note< "use -analyzer-disable-all-checks to disable all static analyzer checkers">; diff --git a/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.def b/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.def index 70bd476b6c438..95dc3524ce390 100644 --- a/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.def +++ b/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.def @@ -380,12 +380,6 @@ ANALYZER_OPTION( "Value: \"constructors\", \"destructors\", \"methods\".", "destructors") -ANALYZER_OPTION_DEPENDS_ON_USER_MODE( - StringRef, IPAMode, "ipa", - "Controls the mode of inter-procedural analysis. Value: \"none\", " - "\"basic-inlining\", \"inlining\", \"dynamic\", \"dynamic-bifurcate\".", - /* SHALLOW_VAL */ "inlining", /* DEEP_VAL */ "dynamic-bifurcate") - ANALYZER_OPTION( StringRef, ExplorationStrategy, "exploration_strategy", "Value: \"dfs\", \"bfs\", \"unexplored_first\", " @@ -393,5 +387,17 @@ ANALYZER_OPTION( "\"bfs_block_dfs_contents\".", "unexplored_first_queue") +ANALYZER_OPTION( + StringRef, RawSilencedCheckersAndPackages, "silence-checkers", + "A semicolon separated list of checker and package names to silence. " + "Silenced checkers will not emit reports, but the modeling remain enabled.", + "") + +ANALYZER_OPTION_DEPENDS_ON_USER_MODE( + StringRef, IPAMode, "ipa", + "Controls the mode of inter-procedural analysis. Value: \"none\", " + "\"basic-inlining\", \"inlining\", \"dynamic\", \"dynamic-bifurcate\".", + /* SHALLOW_VAL */ "inlining", /* DEEP_VAL */ "dynamic-bifurcate") + #undef ANALYZER_OPTION_DEPENDS_ON_USER_MODE #undef ANALYZER_OPTION diff --git a/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.h b/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.h index 9630a229bd3bf..609932ad70e21 100644 --- a/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.h +++ b/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.h @@ -164,7 +164,40 @@ class AnalyzerOptions : public RefCountedBase { using ConfigTable = llvm::StringMap; static std::vector - getRegisteredCheckers(bool IncludeExperimental = false); + getRegisteredCheckers(bool IncludeExperimental = false) { + static const StringRef StaticAnalyzerChecks[] = { +#define GET_CHECKERS +#define CHECKER(FULLNAME, CLASS, HELPTEXT, DOC_URI, IS_HIDDEN) FULLNAME, +#include "clang/StaticAnalyzer/Checkers/Checkers.inc" +#undef CHECKER +#undef GET_CHECKERS + }; + std::vector Checkers; + for (StringRef CheckerName : StaticAnalyzerChecks) { + if (!CheckerName.startswith("debug.") && + (IncludeExperimental || !CheckerName.startswith("alpha."))) + Checkers.push_back(CheckerName); + } + return Checkers; + } + + static std::vector + getRegisteredPackages(bool IncludeExperimental = false) { + static const StringRef StaticAnalyzerPackages[] = { +#define GET_PACKAGES +#define PACKAGE(FULLNAME) FULLNAME, +#include "clang/StaticAnalyzer/Checkers/Checkers.inc" +#undef PACKAGE +#undef GET_PACKAGES + }; + std::vector Packages; + for (StringRef PackageName : StaticAnalyzerPackages) { + if (PackageName != "debug" && + (IncludeExperimental || PackageName != "alpha")) + Packages.push_back(PackageName); + } + return Packages; + } /// Convenience function for printing options or checkers and their /// description in a formatted manner. If \p MinLineWidth is set to 0, no line @@ -188,9 +221,11 @@ class AnalyzerOptions : public RefCountedBase { std::pair EntryDescPair, size_t EntryWidth, size_t InitialPad, size_t MinLineWidth = 0); + /// Pairs of checker/package name and enable/disable. + std::vector> CheckersAndPackages; - /// Pair of checker name and enable/disable. - std::vector> CheckersControlList; + /// Vector of checker/package names which will not emit warnings. + std::vector SilencedCheckersAndPackages; /// A key-value table of use-specified configuration values. // TODO: This shouldn't be public. diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp index 210f01bd10268..bbd0a585a8297 100644 --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -324,18 +324,18 @@ static bool ParseAnalyzerArgs(AnalyzerOptions &Opts, ArgList &Args, getLastArgIntValue(Args, OPT_analyzer_inline_max_stack_depth, Opts.InlineMaxStackDepth, Diags); - Opts.CheckersControlList.clear(); + Opts.CheckersAndPackages.clear(); for (const Arg *A : Args.filtered(OPT_analyzer_checker, OPT_analyzer_disable_checker)) { A->claim(); - bool enable = (A->getOption().getID() == OPT_analyzer_checker); + bool IsEnabled = A->getOption().getID() == OPT_analyzer_checker; // We can have a list of comma separated checker names, e.g: // '-analyzer-checker=cocoa,unix' - StringRef checkerList = A->getValue(); - SmallVector checkers; - checkerList.split(checkers, ","); - for (auto checker : checkers) - Opts.CheckersControlList.emplace_back(checker, enable); + StringRef CheckerAndPackageList = A->getValue(); + SmallVector CheckersAndPackages; + CheckerAndPackageList.split(CheckersAndPackages, ","); + for (const StringRef CheckerOrPackage : CheckersAndPackages) + Opts.CheckersAndPackages.emplace_back(CheckerOrPackage, IsEnabled); } // Go through the analyzer configuration options. @@ -479,6 +479,32 @@ static void parseAnalyzerConfigs(AnalyzerOptions &AnOpts, !llvm::sys::fs::is_directory(AnOpts.ModelPath)) Diags->Report(diag::err_analyzer_config_invalid_input) << "model-path" << "a filename"; + + // FIXME: Here we try to validate the silenced checkers or packages are valid. + // The current approach only validates the registered checkers which does not + // contain the runtime enabled checkers and optimally we would validate both. + if (!AnOpts.RawSilencedCheckersAndPackages.empty()) { + std::vector Checkers = + AnOpts.getRegisteredCheckers(/*IncludeExperimental=*/true); + std::vector Packages = + AnOpts.getRegisteredPackages(/*IncludeExperimental=*/true); + + SmallVector CheckersAndPackages; + AnOpts.RawSilencedCheckersAndPackages.split(CheckersAndPackages, ";"); + + for (const StringRef CheckerOrPackage : CheckersAndPackages) { + bool IsChecker = CheckerOrPackage.contains('.'); + bool IsValidName = + IsChecker ? llvm::find(Checkers, CheckerOrPackage) != Checkers.end() + : llvm::find(Packages, CheckerOrPackage) != Packages.end(); + + if (!IsValidName) + Diags->Report(diag::err_unknown_analyzer_checker_or_package) + << CheckerOrPackage; + + AnOpts.SilencedCheckersAndPackages.emplace_back(CheckerOrPackage); + } + } } static bool ParseMigratorArgs(MigratorOptions &Opts, ArgList &Args) { diff --git a/clang/lib/StaticAnalyzer/Core/AnalyzerOptions.cpp b/clang/lib/StaticAnalyzer/Core/AnalyzerOptions.cpp index 71abe2ae6c0e8..01ac2bc83bb6b 100644 --- a/clang/lib/StaticAnalyzer/Core/AnalyzerOptions.cpp +++ b/clang/lib/StaticAnalyzer/Core/AnalyzerOptions.cpp @@ -30,25 +30,6 @@ using namespace clang; using namespace ento; using namespace llvm; -std::vector -AnalyzerOptions::getRegisteredCheckers(bool IncludeExperimental /* = false */) { - static const StringRef StaticAnalyzerChecks[] = { -#define GET_CHECKERS -#define CHECKER(FULLNAME, CLASS, HELPTEXT, DOC_URI, IS_HIDDEN) \ - FULLNAME, -#include "clang/StaticAnalyzer/Checkers/Checkers.inc" -#undef CHECKER -#undef GET_CHECKERS - }; - std::vector Result; - for (StringRef CheckName : StaticAnalyzerChecks) { - if (!CheckName.startswith("debug.") && - (IncludeExperimental || !CheckName.startswith("alpha."))) - Result.push_back(CheckName); - } - return Result; -} - void AnalyzerOptions::printFormattedEntry( llvm::raw_ostream &Out, std::pair EntryDescPair, diff --git a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp index 7a471dc0e883e..aa564f7de826e 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp @@ -1924,15 +1924,22 @@ PathDiagnosticBuilder::PathDiagnosticBuilder( std::unique_ptr PathDiagnosticBuilder::generate(const PathDiagnosticConsumer *PDC) const { - - if (!PDC->shouldGenerateDiagnostics()) - return generateEmptyDiagnosticForReport(R, getSourceManager()); - PathDiagnosticConstruct Construct(PDC, ErrorNode, R); const SourceManager &SM = getSourceManager(); const BugReport *R = getBugReport(); const AnalyzerOptions &Opts = getAnalyzerOptions(); + StringRef ErrorTag = ErrorNode->getLocation().getTag()->getTagDescription(); + + // See whether we need to silence the checker/package. + // FIXME: This will not work if the report was emitted with an incorrect tag. + for (const std::string &CheckerOrPackage : Opts.SilencedCheckersAndPackages) { + if (ErrorTag.startswith(CheckerOrPackage)) + return nullptr; + } + + if (!PDC->shouldGenerateDiagnostics()) + return generateEmptyDiagnosticForReport(R, getSourceManager()); // Construct the final (warning) event for the bug report. auto EndNotes = VisitorsDiagnostics->find(ErrorNode); @@ -2029,7 +2036,6 @@ PathDiagnosticBuilder::generate(const PathDiagnosticConsumer *PDC) const { return std::move(Construct.PD); } - //===----------------------------------------------------------------------===// // Methods for BugType and subclasses. //===----------------------------------------------------------------------===// @@ -2646,9 +2652,13 @@ GRBugReporter::generatePathDiagnostics( Optional PDB = PathDiagnosticBuilder::findValidReport(bugReports, *this); - if (PDB) - for (PathDiagnosticConsumer *PC : consumers) - (*Out)[PC] = PDB->generate(PC); + if (PDB) { + for (PathDiagnosticConsumer *PC : consumers) { + if (std::unique_ptr PD = PDB->generate(PC)) { + (*Out)[PC] = std::move(PD); + } + } + } return Out; } diff --git a/clang/lib/StaticAnalyzer/Frontend/CheckerRegistry.cpp b/clang/lib/StaticAnalyzer/Frontend/CheckerRegistry.cpp index 3fd4c36947cbb..322304b0fb70e 100644 --- a/clang/lib/StaticAnalyzer/Frontend/CheckerRegistry.cpp +++ b/clang/lib/StaticAnalyzer/Frontend/CheckerRegistry.cpp @@ -200,12 +200,12 @@ CheckerRegistry::CheckerRegistry( // Parse '-analyzer-checker' and '-analyzer-disable-checker' options from the // command line. - for (const std::pair &Opt : AnOpts.CheckersControlList) { + for (const std::pair &Opt : AnOpts.CheckersAndPackages) { CheckerInfoListRange CheckerForCmdLineArg = getMutableCheckersForCmdLineArg(Opt.first); if (CheckerForCmdLineArg.begin() == CheckerForCmdLineArg.end()) { - Diags.Report(diag::err_unknown_analyzer_checker) << Opt.first; + Diags.Report(diag::err_unknown_analyzer_checker_or_package) << Opt.first; Diags.Report(diag::note_suggest_disabling_all_checkers); } @@ -468,9 +468,10 @@ isOptionContainedIn(const CheckerRegistry::CmdLineOptionList &OptionList, void CheckerRegistry::validateCheckerOptions() const { for (const auto &Config : AnOpts.Config) { - StringRef SuppliedChecker; + StringRef SuppliedCheckerOrPackage; StringRef SuppliedOption; - std::tie(SuppliedChecker, SuppliedOption) = Config.getKey().split(':'); + std::tie(SuppliedCheckerOrPackage, SuppliedOption) = + Config.getKey().split(':'); if (SuppliedOption.empty()) continue; @@ -483,21 +484,24 @@ void CheckerRegistry::validateCheckerOptions() const { // Since lower_bound would look for the first element *not less* than "cor", // it would return with an iterator to the first checker in the core, so we // we really have to use find here, which uses operator==. - auto CheckerIt = llvm::find(Checkers, CheckerInfo(SuppliedChecker)); + auto CheckerIt = + llvm::find(Checkers, CheckerInfo(SuppliedCheckerOrPackage)); if (CheckerIt != Checkers.end()) { - isOptionContainedIn(CheckerIt->CmdLineOptions, SuppliedChecker, + isOptionContainedIn(CheckerIt->CmdLineOptions, SuppliedCheckerOrPackage, SuppliedOption, AnOpts, Diags); continue; } - auto PackageIt = llvm::find(Packages, PackageInfo(SuppliedChecker)); + auto PackageIt = + llvm::find(Packages, PackageInfo(SuppliedCheckerOrPackage)); if (PackageIt != Packages.end()) { - isOptionContainedIn(PackageIt->CmdLineOptions, SuppliedChecker, + isOptionContainedIn(PackageIt->CmdLineOptions, SuppliedCheckerOrPackage, SuppliedOption, AnOpts, Diags); continue; } - Diags.Report(diag::err_unknown_analyzer_checker) << SuppliedChecker; + Diags.Report(diag::err_unknown_analyzer_checker_or_package) + << SuppliedCheckerOrPackage; } } diff --git a/clang/test/Analysis/analyzer-config.c b/clang/test/Analysis/analyzer-config.c index 7cba52fdaba2a..99e1173d5d8c1 100644 --- a/clang/test/Analysis/analyzer-config.c +++ b/clang/test/Analysis/analyzer-config.c @@ -82,6 +82,7 @@ // CHECK-NEXT: region-store-small-struct-limit = 2 // CHECK-NEXT: report-in-main-source-file = false // CHECK-NEXT: serialize-stats = false +// CHECK-NEXT: silence-checkers = "" // CHECK-NEXT: stable-report-filename = false // CHECK-NEXT: suppress-c++-stdlib = true // CHECK-NEXT: suppress-inlined-defensive-checks = true @@ -92,4 +93,4 @@ // CHECK-NEXT: unroll-loops = false // CHECK-NEXT: widen-loops = false // CHECK-NEXT: [stats] -// CHECK-NEXT: num-entries = 89 +// CHECK-NEXT: num-entries = 90 diff --git a/clang/test/Analysis/silence-checkers-and-packages-core-all.cpp b/clang/test/Analysis/silence-checkers-and-packages-core-all.cpp new file mode 100644 index 0000000000000..0805cfc877c28 --- /dev/null +++ b/clang/test/Analysis/silence-checkers-and-packages-core-all.cpp @@ -0,0 +1,39 @@ +// RUN: %clang_analyze_cc1 \ +// RUN: -analyzer-checker=core -analyzer-config \ +// RUN: silence-checkers=core \ +// RUN: -verify %s + +// RUN: %clang_analyze_cc1 \ +// RUN: -analyzer-checker=core -analyzer-config \ +// RUN: silence-checkers="core.DivideZero;core.NullDereference" \ +// RUN: -verify %s + +// RUN: not %clang_analyze_cc1 -verify %s \ +// RUN: -analyzer-checker=core -analyzer-config \ +// RUN: silence-checkers=core.NullDeref \ +// RUN: 2>&1 | FileCheck %s -check-prefix=CHECK-CHECKER-ERROR + +// CHECK-CHECKER-ERROR: (frontend): no analyzer checkers or packages +// CHECK-CHECKER-ERROR-SAME: are associated with 'core.NullDeref' + +// RUN: not %clang_analyze_cc1 -verify %s \ +// RUN: -analyzer-checker=core -analyzer-config \ +// RUN: silence-checkers=coreModeling \ +// RUN: 2>&1 | FileCheck %s -check-prefix=CHECK-PACKAGE-ERROR + +// CHECK-PACKAGE-ERROR: (frontend): no analyzer checkers or packages +// CHECK-PACKAGE-ERROR-SAME: are associated with 'coreModeling' + +void test_disable_core_div_by_zero() { + (void)(1 / 0); + // expected-warning@-1 {{division by zero is undefined}} + // no-warning: 'Division by zero' +} + +void test_disable_null_deref(int *p) { + if (p) + return; + + int x = p[0]; + // no-warning: Array access (from variable 'p') results in a null pointer dereference +} diff --git a/clang/test/Analysis/silence-checkers-and-packages-core-div-by-zero.cpp b/clang/test/Analysis/silence-checkers-and-packages-core-div-by-zero.cpp new file mode 100644 index 0000000000000..3930f5a602534 --- /dev/null +++ b/clang/test/Analysis/silence-checkers-and-packages-core-div-by-zero.cpp @@ -0,0 +1,18 @@ +// RUN: %clang_analyze_cc1 \ +// RUN: -analyzer-checker=core -analyzer-config \ +// RUN: silence-checkers=core.DivideZero \ +// RUN: -verify %s + +void test_disable_core_div_by_zero() { + (void)(1 / 0); + // expected-warning@-1 {{division by zero is undefined}} + // no-warning: 'Division by zero' +} + +void test_disable_null_deref(int *p) { + if (p) + return; + + int x = p[0]; + // expected-warning@-1 {{Array access (from variable 'p') results in a null pointer dereference}} +} diff --git a/clang/tools/scan-build/bin/scan-build b/clang/tools/scan-build/bin/scan-build index 903e19a2909a6..37c94d5ec36e5 100755 --- a/clang/tools/scan-build/bin/scan-build +++ b/clang/tools/scan-build/bin/scan-build @@ -57,6 +57,7 @@ my %Options = ( KeepEmpty => 0, # Don't remove output directory even with 0 results. EnableCheckers => {}, DisableCheckers => {}, + SilenceCheckers => {}, Excludes => [], UseCC => undef, # C compiler to use for compilation. UseCXX => undef, # C++ compiler to use for compilation. @@ -1742,9 +1743,15 @@ sub ProcessArgs { if ($arg eq "-disable-checker") { shift @$Args; my $Checker = shift @$Args; - # Store $NumArgs to preserve the order the checkers were disabled. - $Options{DisableCheckers}{$Checker} = $NumArgs; - delete $Options{EnableCheckers}{$Checker}; + # Store $NumArgs to preserve the order the checkers are disabled/silenced. + # See whether it is a core checker to disable. That means we do not want + # to emit a report from that checker so we have to silence it. + if (index($Checker, "core") == 0) { + $Options{SilenceCheckers}{$Checker} = $NumArgs; + } else { + $Options{DisableCheckers}{$Checker} = $NumArgs; + delete $Options{EnableCheckers}{$Checker}; + } next; } @@ -1882,6 +1889,11 @@ foreach (sort { $Options{DisableCheckers}{$a} <=> $Options{DisableCheckers}{$b} # Push checkers in order they were disabled. push @AnalysesToRun, "-analyzer-disable-checker", $_; } +foreach (sort { $Options{SilenceCheckers}{$a} <=> $Options{SilenceCheckers}{$b} } + keys %{$Options{SilenceCheckers}}) { + # Push checkers in order they were silenced. + push @AnalysesToRun, "-analyzer-config silence-checker", $_; +} if ($Options{AnalyzeHeaders}) { push @AnalysesToRun, "-analyzer-opt-analyze-headers"; } if ($Options{AnalyzerStats}) { push @AnalysesToRun, '-analyzer-checker=debug.Stats'; } if ($Options{MaxLoop} > 0) { push @AnalysesToRun, "-analyzer-max-loop $Options{MaxLoop}"; } diff --git a/clang/unittests/StaticAnalyzer/RegisterCustomCheckersTest.cpp b/clang/unittests/StaticAnalyzer/RegisterCustomCheckersTest.cpp index d8988a0ee30ef..109e762892636 100644 --- a/clang/unittests/StaticAnalyzer/RegisterCustomCheckersTest.cpp +++ b/clang/unittests/StaticAnalyzer/RegisterCustomCheckersTest.cpp @@ -46,7 +46,7 @@ class TestAction : public ASTFrontendAction { std::unique_ptr AnalysisConsumer = CreateAnalysisConsumer(Compiler); AnalysisConsumer->AddDiagnosticConsumer(new DiagConsumer(DiagsOutput)); - Compiler.getAnalyzerOpts()->CheckersControlList = { + Compiler.getAnalyzerOpts()->CheckersAndPackages = { {"custom.CustomChecker", true}}; AnalysisConsumer->AddCheckerRegistrationFn([](CheckerRegistry &Registry) { Registry.addChecker("custom.CustomChecker", "Description", ""); From 67b9d767fa038241232bf9ac163d81e40cfa1981 Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Sat, 17 Aug 2019 16:49:54 +0000 Subject: [PATCH 107/181] [analyzer] Turn an assert into an if condition Shocker, turns out that terminator conditions that are binary operators aren't always logical operators. llvm-svn: 369195 (cherry picked from commit 032e1fdcd47ef98936c88aa30571c6a713179a46) --- .../lib/StaticAnalyzer/Core/BugReporterVisitors.cpp | 7 +++---- .../track-control-dependency-conditions.cpp | 13 ++++++++++++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp index 43347ed247a04..afdd0c105305a 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp @@ -1755,10 +1755,9 @@ static bool isAssertlikeBlock(const CFGBlock *B, ASTContext &Context) { // B1, 'A && B' for B2, and 'A && B || C' for B3. Let's check whether we // reached the end of the condition! if (const Stmt *ElseCond = Else->getTerminatorCondition()) - if (isa(ElseCond)) { - assert(cast(ElseCond)->isLogicalOp()); - return isAssertlikeBlock(Else, Context); - } + if (const auto *BinOp = dyn_cast(ElseCond)) + if (BinOp->isLogicalOp()) + return isAssertlikeBlock(Else, Context); return false; } diff --git a/clang/test/Analysis/track-control-dependency-conditions.cpp b/clang/test/Analysis/track-control-dependency-conditions.cpp index 016ba3c5dca30..e02eb412a8097 100644 --- a/clang/test/Analysis/track-control-dependency-conditions.cpp +++ b/clang/test/Analysis/track-control-dependency-conditions.cpp @@ -459,6 +459,18 @@ void f(int flag) { } // end of namespace unimportant_write_before_collapse_point +namespace dont_crash_on_nonlogical_binary_operator { + +void f6(int x) { + int a[20]; + if (x == 25) {} // expected-note{{Assuming 'x' is equal to 25}} + // expected-note@-1{{Taking true branch}} + if (a[x] == 123) {} // expected-warning{{The left operand of '==' is a garbage value due to array index out of bounds}} + // expected-note@-1{{The left operand of '==' is a garbage value due to array index out of bounds}} +} + +} // end of namespace dont_crash_on_nonlogical_binary_operator + namespace dont_track_assertlike_conditions { extern void __assert_fail(__const char *__assertion, __const char *__file, @@ -532,7 +544,6 @@ void f(int flag) { } #undef assert - } // end of namespace dont_track_assertlike_and_conditions namespace dont_track_assertlike_or_conditions { From 1131e93c79d0979f14b473d1fb988b79a96773fe Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Tue, 20 Aug 2019 02:15:47 +0000 Subject: [PATCH 108/181] [analyzer] NFC: Drop support for extra text attached to bug reports. It was introduced in 2011 but never used since then. llvm-svn: 369319 (cherry picked from commit 48786cf8d3d5a84a966be2a8ab5c5e8097493731) --- .../StaticAnalyzer/Core/BugReporter/BugReporter.h | 13 ------------- clang/lib/StaticAnalyzer/Core/BugReporter.cpp | 5 ----- 2 files changed, 18 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h index c2a5bb7f629fd..77795cc0d8f64 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h @@ -88,7 +88,6 @@ class BugReport : public llvm::ilist_node { using VisitorList = SmallVector, 8>; using visitor_iterator = VisitorList::iterator; using visitor_range = llvm::iterator_range; - using ExtraTextList = SmallVector; using NoteList = SmallVector, 4>; protected: @@ -106,7 +105,6 @@ class BugReport : public llvm::ilist_node { const ExplodedNode *ErrorNode = nullptr; SmallVector Ranges; const SourceRange ErrorNodeRange; - ExtraTextList ExtraText; NoteList Notes; /// A (stack of) a set of symbols that are registered with this @@ -288,17 +286,6 @@ class BugReport : public llvm::ilist_node { return Notes; } - /// This allows for addition of meta data to the diagnostic. - /// - /// Currently, only the HTMLDiagnosticClient knows how to display it. - void addExtraText(StringRef S) { - ExtraText.push_back(S); - } - - virtual const ExtraTextList &getExtraText() { - return ExtraText; - } - /// Return the "definitive" location of the reported bug. /// /// While a bug can span an entire path, usually there is a specific diff --git a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp index aa564f7de826e..190e177ee6788 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp @@ -2878,11 +2878,6 @@ void BugReporter::FlushReport(BugReportEquivClass& EQ) { Pieces.push_front(*I); } - // Get the meta data. - const BugReport::ExtraTextList &Meta = report->getExtraText(); - for (const auto &i : Meta) - PD->addMeta(i); - updateExecutedLinesWithDiagnosticPieces(*PD); Consumer->HandlePathDiagnostic(std::move(PD)); } From d7d5a99a470ec0034bf869446c88d016cec34bcd Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Tue, 20 Aug 2019 02:15:50 +0000 Subject: [PATCH 109/181] [analyzer] NFC: Rename GRBugReporter to PathSensitiveBugReporter. The GR prefix is super ancient. llvm-svn: 369320 (cherry picked from commit ee92f12fd18e42dd417174bcbb6429da60f47afa) --- .../Core/BugReporter/BugReporter.h | 21 ++++++++++--------- .../Core/PathSensitive/ExprEngine.h | 6 +++--- clang/lib/StaticAnalyzer/Core/BugReporter.cpp | 21 ++++++++++++------- 3 files changed, 27 insertions(+), 21 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h index 77795cc0d8f64..36df6335611c4 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h @@ -405,7 +405,7 @@ class BugReporterData { /// The base class is used for generating path-insensitive class BugReporter { public: - enum Kind { BaseBRKind, GRBugReporterKind }; + enum Kind { BasicBRKind, PathSensitiveBRKind }; private: using BugTypesTy = llvm::ImmutableSet; @@ -437,7 +437,7 @@ class BugReporter { public: BugReporter(BugReporterData& d) - : BugTypes(F.getEmptySet()), kind(BaseBRKind), D(d) {} + : BugTypes(F.getEmptySet()), kind(BasicBRKind), D(d) {} virtual ~BugReporter(); /// Generate and flush diagnostics for all bug reports. @@ -504,14 +504,14 @@ class BugReporter { }; /// GRBugReporter is used for generating path-sensitive reports. -class GRBugReporter : public BugReporter { +class PathSensitiveBugReporter : public BugReporter { ExprEngine& Eng; public: - GRBugReporter(BugReporterData& d, ExprEngine& eng) - : BugReporter(d, GRBugReporterKind), Eng(eng) {} + PathSensitiveBugReporter(BugReporterData& d, ExprEngine& eng) + : BugReporter(d, PathSensitiveBRKind), Eng(eng) {} - ~GRBugReporter() override = default; + ~PathSensitiveBugReporter() override = default; /// getGraph - Get the exploded graph created by the analysis engine /// for the analyzed method or function. @@ -534,7 +534,7 @@ class GRBugReporter : public BugReporter { /// classof - Used by isa<>, cast<>, and dyn_cast<>. static bool classof(const BugReporter* R) { - return R->getKind() == GRBugReporterKind; + return R->getKind() == PathSensitiveBRKind; } }; @@ -551,18 +551,19 @@ class NodeMapClosure : public BugReport::NodeResolver { }; class BugReporterContext { - GRBugReporter &BR; + PathSensitiveBugReporter &BR; NodeMapClosure NMC; virtual void anchor(); public: - BugReporterContext(GRBugReporter &br, InterExplodedGraphMap &Backmap) + BugReporterContext(PathSensitiveBugReporter &br, + InterExplodedGraphMap &Backmap) : BR(br), NMC(Backmap) {} virtual ~BugReporterContext() = default; - GRBugReporter& getBugReporter() { return BR; } + PathSensitiveBugReporter& getBugReporter() { return BR; } const ExplodedGraph &getGraph() const { return BR.getGraph(); } diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h index 2629d7121de4c..eb20b7d6cc981 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h @@ -145,9 +145,9 @@ class ExprEngine : public SubEngine { ObjCNoReturn ObjCNoRet; /// The BugReporter associated with this engine. It is important that - /// this object be placed at the very end of member variables so that its - /// destructor is called before the rest of the ExprEngine is destroyed. - GRBugReporter BR; + /// this object be placed at the very end of member variables so that its + /// destructor is called before the rest of the ExprEngine is destroyed. + PathSensitiveBugReporter BR; /// The functions which have been analyzed through inlining. This is owned by /// AnalysisConsumer. It can be null. diff --git a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp index 190e177ee6788..9bc00deaa063f 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp @@ -215,7 +215,8 @@ class PathDiagnosticBuilder : public BugReporterContext { /// a PathDiagnosticBuilder able to construct bug reports for different /// consumers. Returns None if no valid report is found. static Optional - findValidReport(ArrayRef &bugReports, GRBugReporter &Reporter); + findValidReport(ArrayRef &bugReports, + PathSensitiveBugReporter &Reporter); PathDiagnosticBuilder( BugReporterContext BRC, std::unique_ptr BugPath, @@ -2213,13 +2214,17 @@ PathDiagnosticLocation BugReport::getLocation(const SourceManager &SM) const { // Methods for BugReporter and subclasses. //===----------------------------------------------------------------------===// -const ExplodedGraph &GRBugReporter::getGraph() const { return Eng.getGraph(); } +const ExplodedGraph &PathSensitiveBugReporter::getGraph() const { + return Eng.getGraph(); +} -ProgramStateManager& -GRBugReporter::getStateManager() { return Eng.getStateManager(); } +ProgramStateManager &PathSensitiveBugReporter::getStateManager() { + return Eng.getStateManager(); +} -ProgramStateManager& -GRBugReporter::getStateManager() const { return Eng.getStateManager(); } +ProgramStateManager &PathSensitiveBugReporter::getStateManager() const { + return Eng.getStateManager(); +} BugReporter::~BugReporter() { FlushReports(); @@ -2592,7 +2597,7 @@ generateVisitorsDiagnostics(BugReport *R, const ExplodedNode *ErrorNode, Optional PathDiagnosticBuilder::findValidReport(ArrayRef &bugReports, - GRBugReporter &Reporter) { + PathSensitiveBugReporter &Reporter) { BugPathGetter BugGraph(&Reporter.getGraph(), bugReports); @@ -2642,7 +2647,7 @@ PathDiagnosticBuilder::findValidReport(ArrayRef &bugReports, } std::unique_ptr -GRBugReporter::generatePathDiagnostics( +PathSensitiveBugReporter::generatePathDiagnostics( ArrayRef consumers, ArrayRef &bugReports) { assert(!bugReports.empty()); From 59d2c0751aeb3befe4cca7ed1b9ce682e0c3cfdf Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Tue, 20 Aug 2019 02:22:37 +0000 Subject: [PATCH 110/181] [CallGraph] Take into accound calls that aren't within any function bodies. This patch improves Clang call graph analysis by adding in expressions that are not found in regular function bodies, such as default arguments or member initializers. Patch by Joshua Cranmer! Differential Revision: https://reviews.llvm.org/D65453 llvm-svn: 369321 (cherry picked from commit 8cf3dfea54187769b9c3feeb032f084857e8c79c) --- clang/include/clang/Analysis/CallGraph.h | 1 + clang/lib/Analysis/CallGraph.cpp | 37 ++++++++++++++++++- clang/test/Analysis/cxx-callgraph.cpp | 29 +++++++++++++++ .../objects_under_construction.cpp | 1 + 4 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 clang/test/Analysis/cxx-callgraph.cpp diff --git a/clang/include/clang/Analysis/CallGraph.h b/clang/include/clang/Analysis/CallGraph.h index 49c04490fed2c..dae2b58ffc102 100644 --- a/clang/include/clang/Analysis/CallGraph.h +++ b/clang/include/clang/Analysis/CallGraph.h @@ -131,6 +131,7 @@ class CallGraph : public RecursiveASTVisitor { bool shouldWalkTypesOfTypeLocs() const { return false; } bool shouldVisitTemplateInstantiations() const { return true; } + bool shouldVisitImplicitCode() const { return true; } private: /// Add the given declaration to the call graph. diff --git a/clang/lib/Analysis/CallGraph.cpp b/clang/lib/Analysis/CallGraph.cpp index 7eda80ea0505e..b6542cad120e5 100644 --- a/clang/lib/Analysis/CallGraph.cpp +++ b/clang/lib/Analysis/CallGraph.cpp @@ -79,6 +79,34 @@ class CGBuilder : public StmtVisitor { VisitChildren(CE); } + void VisitLambdaExpr(LambdaExpr *LE) { + if (CXXMethodDecl *MD = LE->getCallOperator()) + G->VisitFunctionDecl(MD); + } + + void VisitCXXNewExpr(CXXNewExpr *E) { + if (FunctionDecl *FD = E->getOperatorNew()) + addCalledDecl(FD); + VisitChildren(E); + } + + void VisitCXXConstructExpr(CXXConstructExpr *E) { + CXXConstructorDecl *Ctor = E->getConstructor(); + if (FunctionDecl *Def = Ctor->getDefinition()) + addCalledDecl(Def); + VisitChildren(E); + } + + // Include the evaluation of the default argument. + void VisitCXXDefaultArgExpr(CXXDefaultArgExpr *E) { + Visit(E->getExpr()); + } + + // Include the evaluation of the default initializers in a class. + void VisitCXXDefaultInitExpr(CXXDefaultInitExpr *E) { + Visit(E->getExpr()); + } + // Adds may-call edges for the ObjC message sends. void VisitObjCMessageExpr(ObjCMessageExpr *ME) { if (ObjCInterfaceDecl *IDecl = ME->getReceiverInterface()) { @@ -143,13 +171,20 @@ bool CallGraph::includeInGraph(const Decl *D) { void CallGraph::addNodeForDecl(Decl* D, bool IsGlobal) { assert(D); - // Allocate a new node, mark it as root, and process it's calls. + // Allocate a new node, mark it as root, and process its calls. CallGraphNode *Node = getOrInsertNode(D); // Process all the calls by this function as well. CGBuilder builder(this, Node); if (Stmt *Body = D->getBody()) builder.Visit(Body); + + // Include C++ constructor member initializers. + if (auto constructor = dyn_cast(D)) { + for (CXXCtorInitializer *init : constructor->inits()) { + builder.Visit(init->getInit()); + } + } } CallGraphNode *CallGraph::getNode(const Decl *F) const { diff --git a/clang/test/Analysis/cxx-callgraph.cpp b/clang/test/Analysis/cxx-callgraph.cpp new file mode 100644 index 0000000000000..4a48e422ce08a --- /dev/null +++ b/clang/test/Analysis/cxx-callgraph.cpp @@ -0,0 +1,29 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=debug.DumpCallGraph %s 2>&1 | FileCheck %s + +static int aaa() { + return 0; +} + +static int bbb(int param=aaa()) { + return 1; +} + +int ddd(); + +struct c { + c(int param=2) : val(bbb(param)) {} + int val; + int val2 = ddd(); +}; + +int ddd() { + c c; + return bbb(); +} + +// CHECK:--- Call graph Dump --- +// CHECK-NEXT: {{Function: < root > calls: aaa bbb c::c ddd}} +// CHECK-NEXT: {{Function: c::c calls: bbb ddd $}} +// CHECK-NEXT: {{Function: ddd calls: c::c bbb aaa $}} +// CHECK-NEXT: {{Function: bbb calls: $}} +// CHECK-NEXT: {{Function: aaa calls: $}} diff --git a/clang/test/Analysis/exploded-graph-rewriter/objects_under_construction.cpp b/clang/test/Analysis/exploded-graph-rewriter/objects_under_construction.cpp index b3d4aef8bc8cf..e4d256247f98a 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/objects_under_construction.cpp +++ b/clang/test/Analysis/exploded-graph-rewriter/objects_under_construction.cpp @@ -1,5 +1,6 @@ // FIXME: Figure out how to use %clang_analyze_cc1 with our lit.local.cfg. // RUN: %clang_cc1 -analyze -triple x86_64-unknown-linux-gnu \ +// RUN: -analyze-function "test()" \ // RUN: -analyzer-checker=core \ // RUN: -analyzer-dump-egraph=%t.dot %s // RUN: %exploded_graph_rewriter %t.dot | FileCheck %s From 859e66006d9aa24458795be15601d95434a1622f Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Tue, 20 Aug 2019 21:41:14 +0000 Subject: [PATCH 111/181] [analyzer] Improve VirtualCallChecker and enable parts of it by default. Calling a pure virtual method during construction or destruction is undefined behavior. It's worth it to warn about it by default. That part is now known as the cplusplus.PureVirtualCall checker. Calling a normal virtual method during construction or destruction may be fine, but does behave unexpectedly, as it skips virtual dispatch. Do not warn about this by default, but let projects opt in into it by enabling the optin.cplusplus.VirtualCall checker manually. Give the two parts differentiated warning text: Before: Call to virtual function during construction or destruction: Call to pure virtual function during construction Call to virtual function during construction or destruction: Call to virtual function during destruction After: Pure virtual method call: Call to pure virtual method 'X::foo' during construction has undefined behavior Unexpected loss of virtual dispatch: Call to virtual method 'Y::bar' during construction bypasses virtual dispatch Also fix checker names in consumers that support them (eg., clang-tidy) because we now have different checker names for pure virtual calls and regular virtual calls. Also fix capitalization in the bug category. Differential Revision: https://reviews.llvm.org/D64274 llvm-svn: 369449 (cherry picked from commit d3971fe97b64785c079d64bf4c8c3e2b5e1f85a1) --- .../clang/StaticAnalyzer/Checkers/Checkers.td | 18 +- .../Core/BugReporter/CommonBugCategories.h | 1 + .../StaticAnalyzer/Core/CheckerManager.h | 1 + .../Checkers/VirtualCallChecker.cpp | 186 ++++++------------ .../Core/CommonBugCategories.cpp | 1 + clang/test/Analysis/virtualcall-plist.cpp | 23 +++ clang/test/Analysis/virtualcall.cpp | 176 +++++------------ clang/test/Analysis/virtualcall.h | 7 +- 8 files changed, 153 insertions(+), 260 deletions(-) create mode 100644 clang/test/Analysis/virtualcall-plist.cpp diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index f1ba5b676f159..da59b3af3cd77 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -504,6 +504,15 @@ def MoveChecker: Checker<"Move">, ]>, Documentation; +def VirtualCallModeling : Checker<"VirtualCallModeling">, + HelpText<"Auxiliary modeling for the virtual method call checkers">, + Documentation, + Hidden; + +def PureVirtualCallChecker : Checker<"PureVirtualCall">, + HelpText<"Check pure virtual function calls during construction/destruction">, + Dependencies<[VirtualCallModeling]>, + Documentation; } // end: "cplusplus" let ParentPackage = CplusplusOptIn in { @@ -552,14 +561,17 @@ def UninitializedObjectChecker: Checker<"UninitializedObject">, Documentation; def VirtualCallChecker : Checker<"VirtualCall">, - HelpText<"Check virtual function calls during construction or destruction">, + HelpText<"Check virtual function calls during construction/destruction">, CheckerOptions<[ CmdLineOption + InAlpha> ]>, + Dependencies<[VirtualCallModeling]>, Documentation; } // end: "optin.cplusplus" diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h index 85526eb49f0cb..22c1a7dd98ccb 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h @@ -18,6 +18,7 @@ namespace clang { extern const char * const MemoryRefCount; extern const char * const MemoryError; extern const char * const UnixAPI; + extern const char * const CXXObjectLifecycle; } } } diff --git a/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h b/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h index 6cc4baa1687fd..8eec7ed993759 100644 --- a/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h +++ b/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h @@ -105,6 +105,7 @@ class CheckName { CheckName() = default; StringRef getName() const { return Name; } + operator StringRef() const { return Name; } }; enum class ObjCMessageVisitKind { diff --git a/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp index 0d76a098d4088..b50ab4b19f5c6 100644 --- a/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp @@ -6,7 +6,7 @@ // //===----------------------------------------------------------------------===// // -// This file defines a checker that checks virtual function calls during +// This file defines a checker that checks virtual method calls during // construction or destruction of C++ objects. // //===----------------------------------------------------------------------===// @@ -40,11 +40,9 @@ template <> struct FoldingSetTrait { namespace { class VirtualCallChecker : public Checker { - mutable std::unique_ptr BT; - public: - // The flag to determine if pure virtual functions should be issued only. - DefaultBool IsPureOnly; + // These are going to be null if the respective check is disabled. + mutable std::unique_ptr BT_Pure, BT_Impure; void checkBeginFunction(CheckerContext &C) const; void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const; @@ -53,85 +51,13 @@ class VirtualCallChecker private: void registerCtorDtorCallInState(bool IsBeginFunction, CheckerContext &C) const; - void reportBug(StringRef Msg, bool PureError, const MemRegion *Reg, - CheckerContext &C) const; - - class VirtualBugVisitor : public BugReporterVisitor { - private: - const MemRegion *ObjectRegion; - bool Found; - - public: - VirtualBugVisitor(const MemRegion *R) : ObjectRegion(R), Found(false) {} - - void Profile(llvm::FoldingSetNodeID &ID) const override { - static int X = 0; - ID.AddPointer(&X); - ID.AddPointer(ObjectRegion); - } - - PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; - }; }; } // end namespace // GDM (generic data map) to the memregion of this for the ctor and dtor. REGISTER_MAP_WITH_PROGRAMSTATE(CtorDtorMap, const MemRegion *, ObjectState) -PathDiagnosticPieceRef VirtualCallChecker::VirtualBugVisitor::VisitNode( - const ExplodedNode *N, BugReporterContext &BRC, BugReport &) { - // We need the last ctor/dtor which call the virtual function. - // The visitor walks the ExplodedGraph backwards. - if (Found) - return nullptr; - - ProgramStateRef State = N->getState(); - const LocationContext *LCtx = N->getLocationContext(); - const CXXConstructorDecl *CD = - dyn_cast_or_null(LCtx->getDecl()); - const CXXDestructorDecl *DD = - dyn_cast_or_null(LCtx->getDecl()); - - if (!CD && !DD) - return nullptr; - - ProgramStateManager &PSM = State->getStateManager(); - auto &SVB = PSM.getSValBuilder(); - const auto *MD = dyn_cast(LCtx->getDecl()); - if (!MD) - return nullptr; - auto ThiSVal = - State->getSVal(SVB.getCXXThis(MD, LCtx->getStackFrame())); - const MemRegion *Reg = ThiSVal.castAs().getRegion(); - if (!Reg) - return nullptr; - if (Reg != ObjectRegion) - return nullptr; - - const Stmt *S = PathDiagnosticLocation::getStmt(N); - if (!S) - return nullptr; - Found = true; - - std::string InfoText; - if (CD) - InfoText = "This constructor of an object of type '" + - CD->getNameAsString() + - "' has not returned when the virtual method was called"; - else - InfoText = "This destructor of an object of type '" + - DD->getNameAsString() + - "' has not returned when the virtual method was called"; - - // Generate the extra diagnostic. - PathDiagnosticLocation Pos(S, BRC.getSourceManager(), - N->getLocationContext()); - return std::make_shared(Pos, InfoText, true); -} - -// The function to check if a callexpr is a virtual function. +// The function to check if a callexpr is a virtual method call. static bool isVirtualCall(const CallExpr *CE) { bool CallIsNonVirtual = false; @@ -176,11 +102,9 @@ void VirtualCallChecker::checkPreCall(const CallEvent &Call, const CXXMethodDecl *MD = dyn_cast_or_null(Call.getDecl()); if (!MD) return; + ProgramStateRef State = C.getState(); const CallExpr *CE = dyn_cast_or_null(Call.getOriginExpr()); - - if (IsPureOnly && !MD->isPure()) - return; if (!isVirtualCall(CE)) return; @@ -188,29 +112,40 @@ void VirtualCallChecker::checkPreCall(const CallEvent &Call, const ObjectState *ObState = State->get(Reg); if (!ObState) return; - // Check if a virtual method is called. - // The GDM of constructor and destructor should be true. - if (*ObState == ObjectState::CtorCalled) { - if (IsPureOnly && MD->isPure()) - reportBug("Call to pure virtual function during construction", true, Reg, - C); - else if (!MD->isPure()) - reportBug("Call to virtual function during construction", false, Reg, C); - else - reportBug("Call to pure virtual function during construction", false, Reg, - C); - } - if (*ObState == ObjectState::DtorCalled) { - if (IsPureOnly && MD->isPure()) - reportBug("Call to pure virtual function during destruction", true, Reg, - C); - else if (!MD->isPure()) - reportBug("Call to virtual function during destruction", false, Reg, C); - else - reportBug("Call to pure virtual function during construction", false, Reg, - C); + bool IsPure = MD->isPure(); + + // At this point we're sure that we're calling a virtual method + // during construction or destruction, so we'll emit a report. + SmallString<128> Msg; + llvm::raw_svector_ostream OS(Msg); + OS << "Call to "; + if (IsPure) + OS << "pure "; + OS << "virtual method '" << MD->getParent()->getNameAsString() + << "::" << MD->getNameAsString() << "' during "; + if (*ObState == ObjectState::CtorCalled) + OS << "construction "; + else + OS << "destruction "; + if (IsPure) + OS << "has undefined behavior"; + else + OS << "bypasses virtual dispatch"; + + ExplodedNode *N = + IsPure ? C.generateErrorNode() : C.generateNonFatalErrorNode(); + if (!N) + return; + + const std::unique_ptr &BT = IsPure ? BT_Pure : BT_Impure; + if (!BT) { + // The respective check is disabled. + return; } + + auto Report = std::make_unique(*BT, OS.str(), N); + C.emitReport(std::move(Report)); } void VirtualCallChecker::registerCtorDtorCallInState(bool IsBeginFunction, @@ -252,34 +187,35 @@ void VirtualCallChecker::registerCtorDtorCallInState(bool IsBeginFunction, } } -void VirtualCallChecker::reportBug(StringRef Msg, bool IsSink, - const MemRegion *Reg, - CheckerContext &C) const { - ExplodedNode *N; - if (IsSink) - N = C.generateErrorNode(); - else - N = C.generateNonFatalErrorNode(); +void ento::registerVirtualCallModeling(CheckerManager &Mgr) { + Mgr.registerChecker(); +} - if (!N) - return; - if (!BT) - BT.reset(new BugType( - this, "Call to virtual function during construction or destruction", - "C++ Object Lifecycle")); - - auto Reporter = llvm::make_unique(*BT, Msg, N); - Reporter->addVisitor(llvm::make_unique(Reg)); - C.emitReport(std::move(Reporter)); +void ento::registerPureVirtualCallChecker(CheckerManager &Mgr) { + auto *Chk = Mgr.getChecker(); + Chk->BT_Pure = llvm::make_unique( + Mgr.getCurrentCheckName(), "Pure virtual method call", + categories::CXXObjectLifecycle); } -void ento::registerVirtualCallChecker(CheckerManager &mgr) { - VirtualCallChecker *checker = mgr.registerChecker(); +void ento::registerVirtualCallChecker(CheckerManager &Mgr) { + auto *Chk = Mgr.getChecker(); + if (!Mgr.getAnalyzerOptions().getCheckerBooleanOption( + Mgr.getCurrentCheckName(), "PureOnly")) { + Chk->BT_Impure = llvm::make_unique( + Mgr.getCurrentCheckName(), "Unexpected loss of virtual dispatch", + categories::CXXObjectLifecycle); + } +} + +bool ento::shouldRegisterVirtualCallModeling(const LangOptions &LO) { + return LO.CPlusPlus; +} - checker->IsPureOnly = - mgr.getAnalyzerOptions().getCheckerBooleanOption(checker, "PureOnly"); +bool ento::shouldRegisterPureVirtualCallChecker(const LangOptions &LO) { + return LO.CPlusPlus; } bool ento::shouldRegisterVirtualCallChecker(const LangOptions &LO) { - return true; + return LO.CPlusPlus; } diff --git a/clang/lib/StaticAnalyzer/Core/CommonBugCategories.cpp b/clang/lib/StaticAnalyzer/Core/CommonBugCategories.cpp index 54501314386a9..bdae3e605efff 100644 --- a/clang/lib/StaticAnalyzer/Core/CommonBugCategories.cpp +++ b/clang/lib/StaticAnalyzer/Core/CommonBugCategories.cpp @@ -17,4 +17,5 @@ const char * const MemoryRefCount = "Memory (Core Foundation/Objective-C/OSObject)"; const char * const MemoryError = "Memory error"; const char * const UnixAPI = "Unix API"; +const char * const CXXObjectLifecycle = "C++ object lifecycle"; }}} diff --git a/clang/test/Analysis/virtualcall-plist.cpp b/clang/test/Analysis/virtualcall-plist.cpp new file mode 100644 index 0000000000000..a85dc4b6a23ea --- /dev/null +++ b/clang/test/Analysis/virtualcall-plist.cpp @@ -0,0 +1,23 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,cplusplus \ +// RUN: -analyzer-output=plist -o %t.plist -w -verify=pure %s +// RUN: cat %t.plist | FileCheck --check-prefixes=PURE %s +// RUN: %clang_analyze_cc1 -analyzer-checker=core,optin.cplusplus \ +// RUN: -analyzer-output=plist -o %t.plist -w -verify=impure %s +// RUN: cat %t.plist | FileCheck --check-prefixes=IMPURE %s +// RUN: %clang_analyze_cc1 -analyzer-checker=core,cplusplus,optin.cplusplus \ +// RUN: -analyzer-output=plist -o %t.plist -w -verify=pure,impure %s +// RUN: cat %t.plist | FileCheck --check-prefixes=PURE,IMPURE %s + +struct S { + virtual void foo(); + virtual void bar() = 0; + + S() { + // IMPURE: Call to virtual method 'S::foo' during construction bypasses virtual dispatch + // IMPURE: optin.cplusplus.VirtualCall + foo(); // impure-warning{{Call to virtual method 'S::foo' during construction bypasses virtual dispatch}} + // PURE: Call to pure virtual method 'S::bar' during construction has undefined behavior + // PURE: cplusplus.PureVirtualCall + bar(); // pure-warning{{Call to pure virtual method 'S::bar' during construction has undefined behavior}} + } +}; diff --git a/clang/test/Analysis/virtualcall.cpp b/clang/test/Analysis/virtualcall.cpp index 5847110c093eb..80f89d14ea97c 100644 --- a/clang/test/Analysis/virtualcall.cpp +++ b/clang/test/Analysis/virtualcall.cpp @@ -1,9 +1,38 @@ -// RUN: %clang_analyze_cc1 -analyzer-checker=optin.cplusplus.VirtualCall -analyzer-store region -analyzer-output=text -verify -std=c++11 %s +// RUN: %clang_analyze_cc1 -analyzer-checker=core,optin.cplusplus.VirtualCall \ +// RUN: -analyzer-checker=debug.ExprInspection \ +// RUN: -std=c++11 -verify=impure %s + +// RUN: %clang_analyze_cc1 -analyzer-checker=core,cplusplus.PureVirtualCall \ +// RUN: -analyzer-checker=debug.ExprInspection \ +// RUN: -std=c++11 -verify=pure -std=c++11 %s + +// RUN: %clang_analyze_cc1 -analyzer-checker=core,optin.cplusplus.VirtualCall \ +// RUN: -analyzer-config \ +// RUN: optin.cplusplus.VirtualCall:PureOnly=true \ +// RUN: -analyzer-checker=debug.ExprInspection \ +// RUN: -std=c++11 -verify=none %s + +// RUN: %clang_analyze_cc1 -analyzer-checker=core,cplusplus.PureVirtualCall \ +// RUN: -analyzer-checker=optin.cplusplus.VirtualCall \ +// RUN: -analyzer-checker=debug.ExprInspection \ +// RUN: -std=c++11 -verify=pure,impure -std=c++11 %s + +// RUN: %clang_analyze_cc1 -analyzer-checker=core,cplusplus.PureVirtualCall \ +// RUN: -analyzer-checker=optin.cplusplus.VirtualCall \ +// RUN: -analyzer-config \ +// RUN: optin.cplusplus.VirtualCall:PureOnly=true \ +// RUN: -analyzer-checker=debug.ExprInspection \ +// RUN: -std=c++11 -verify=pure %s + + +// We expect no diagnostics when all checks are disabled. +// none-no-diagnostics -// RUN: %clang_analyze_cc1 -analyzer-checker=optin.cplusplus.VirtualCall -analyzer-store region -analyzer-config optin.cplusplus.VirtualCall:PureOnly=true -DPUREONLY=1 -analyzer-output=text -verify -std=c++11 %s #include "virtualcall.h" +void clang_analyzer_warnIfReached(); + class A { public: A(); @@ -13,54 +42,32 @@ class A { virtual int foo() = 0; virtual void bar() = 0; void f() { - foo(); - // expected-warning-re@-1 {{{{^}}Call to pure virtual function during construction}} - // expected-note-re@-2 {{{{^}}Call to pure virtual function during construction}} + foo(); // pure-warning{{Call to pure virtual method 'A::foo' during construction has undefined behavior}} + clang_analyzer_warnIfReached(); // no-warning } }; -class B : public A { +A::A() { + f(); +} + +class B { public: - B() { // expected-note {{Calling default constructor for 'A'}} - foo(); -#if !PUREONLY - // expected-warning-re@-2 {{{{^}}Call to virtual function during construction}} - // expected-note-re@-3 {{{{^}}This constructor of an object of type 'B' has not returned when the virtual method was called}} - // expected-note-re@-4 {{{{^}}Call to virtual function during construction}} -#endif + B() { + foo(); // impure-warning {{Call to virtual method 'B::foo' during construction bypasses virtual dispatch}} } ~B(); virtual int foo(); virtual void bar() { - foo(); -#if !PUREONLY - // expected-warning-re@-2 {{{{^}}Call to virtual function during destruction}} - // expected-note-re@-3 {{{{^}}Call to virtual function during destruction}} -#endif - } + foo(); // impure-warning {{Call to virtual method 'B::foo' during destruction bypasses virtual dispatch}} + } }; -A::A() { - f(); -// expected-note-re@-1 {{{{^}}This constructor of an object of type 'A' has not returned when the virtual method was called}} -// expected-note-re@-2 {{{{^}}Calling 'A::f'}} -} - B::~B() { this->B::foo(); // no-warning this->B::bar(); -#if !PUREONLY - // expected-note-re@-2 {{{{^}}This destructor of an object of type '~B' has not returned when the virtual method was called}} - // expected-note-re@-3 {{{{^}}Calling 'B::bar'}} -#endif - this->foo(); -#if !PUREONLY - // expected-warning-re@-2 {{{{^}}Call to virtual function during destruction}} - // expected-note-re@-3 {{{{^}}This destructor of an object of type '~B' has not returned when the virtual method was called}} - // expected-note-re@-4 {{{{^}}Call to virtual function during destruction}} -#endif - + this->foo(); // impure-warning {{Call to virtual method 'B::foo' during destruction bypasses virtual dispatch}} } class C : public B { @@ -73,12 +80,7 @@ class C : public B { }; C::C() { - f(foo()); -#if !PUREONLY - // expected-warning-re@-2 {{{{^}}Call to virtual function during construction}} - // expected-note-re@-3 {{{{^}}This constructor of an object of type 'C' has not returned when the virtual method was called}} - // expected-note-re@-4 {{{{^}}Call to virtual function during construction}} -#endif + f(foo()); // impure-warning {{Call to virtual method 'C::foo' during construction bypasses virtual dispatch}} } class D : public B { @@ -97,9 +99,6 @@ class E final : public B { foo(); // no-warning } ~E() { bar(); } -#if !PUREONLY - // expected-note-re@-2 2{{{{^}}Calling '~B'}} -#endif int foo() override; }; @@ -135,52 +134,23 @@ class H { G g; g.foo(); g.bar(); // no warning - f(); -#if !PUREONLY - // expected-warning-re@-2 {{{{^}}Call to virtual function during construction}} - // expected-note-re@-3 {{{{^}}This constructor of an object of type 'H' has not returned when the virtual method was called}} - // expected-note-re@-4 {{{{^}}Call to virtual function during construction}} -#endif + f(); // impure-warning {{Call to virtual method 'H::f' during construction bypasses virtual dispatch}} H &h = *this; - h.f(); -#if !PUREONLY - // expected-warning-re@-2 {{{{^}}Call to virtual function during construction}} - // expected-note-re@-3 {{{{^}}This constructor of an object of type 'H' has not returned when the virtual method was called}} - // expected-note-re@-4 {{{{^}}Call to virtual function during construction}} -#endif + h.f(); // impure-warning {{Call to virtual method 'H::f' during construction bypasses virtual dispatch}} } }; class X { public: X() { - g(); -#if !PUREONLY - // expected-warning-re@-2 {{{{^}}Call to virtual function during construction}} - // expected-note-re@-3 {{{{^}}This constructor of an object of type 'X' has not returned when the virtual method was called}} - // expected-note-re@-4 {{{{^}}Call to virtual function during construction}} -#endif + g(); // impure-warning {{Call to virtual method 'X::g' during construction bypasses virtual dispatch}} } X(int i) { if (i > 0) { -#if !PUREONLY - // expected-note-re@-2 {{{{^}}'i' is > 0}} - // expected-note-re@-3 {{{{^}}Taking true branch}} - // expected-note-re@-4 {{{{^}}'i' is <= 0}} - // expected-note-re@-5 {{{{^}}Taking false branch}} -#endif X x(i - 1); -#if !PUREONLY - // expected-note-re@-2 {{{{^}}Calling constructor for 'X'}} -#endif x.g(); // no warning } - g(); -#if !PUREONLY - // expected-warning-re@-2 {{{{^}}Call to virtual function during construction}} - // expected-note-re@-3 {{{{^}}This constructor of an object of type 'X' has not returned when the virtual method was called}} - // expected-note-re@-4 {{{{^}}Call to virtual function during construction}} -#endif + g(); // impure-warning {{Call to virtual method 'X::g' during construction bypasses virtual dispatch}} } virtual void g(); }; @@ -197,19 +167,11 @@ class M { N n; n.virtualMethod(); // no warning n.callFooOfM(this); -#if !PUREONLY - // expected-note-re@-2 {{{{^}}This constructor of an object of type 'M' has not returned when the virtual method was called}} - // expected-note-re@-3 {{{{^}}Calling 'N::callFooOfM'}} -#endif } virtual void foo(); }; void N::callFooOfM(M *m) { - m->foo(); -#if !PUREONLY - // expected-warning-re@-2 {{{{^}}Call to virtual function during construction}} - // expected-note-re@-3 {{{{^}}Call to virtual function during construction}} -#endif + m->foo(); // impure-warning {{Call to virtual method 'M::foo' during construction bypasses virtual dispatch}} } class Y { @@ -217,65 +179,27 @@ class Y { virtual void foobar(); void fooY() { F f1; - foobar(); -#if !PUREONLY - // expected-warning-re@-2 {{{{^}}Call to virtual function during construction}} - // expected-note-re@-3 {{{{^}}Call to virtual function during construction}} -#endif + foobar(); // impure-warning {{Call to virtual method 'Y::foobar' during construction bypasses virtual dispatch}} } Y() { fooY(); } -#if !PUREONLY - // expected-note-re@-2 {{{{^}}This constructor of an object of type 'Y' has not returned when the virtual method was called}} - // expected-note-re@-3 {{{{^}}Calling 'Y::fooY'}} -#endif }; int main() { B b; -#if PUREONLY - //expected-note-re@-2 {{{{^}}Calling default constructor for 'B'}} -#else - //expected-note-re@-4 2{{{{^}}Calling default constructor for 'B'}} -#endif C c; -#if !PUREONLY - //expected-note-re@-2 {{{{^}}Calling default constructor for 'C'}} -#endif D d; E e; F f; G g; H h; H h1(1); -#if !PUREONLY - //expected-note-re@-2 {{{{^}}Calling constructor for 'H'}} - //expected-note-re@-3 {{{{^}}Calling constructor for 'H'}} -#endif X x; -#if !PUREONLY - //expected-note-re@-2 {{{{^}}Calling default constructor for 'X'}} -#endif X x1(1); -#if !PUREONLY - //expected-note-re@-2 {{{{^}}Calling constructor for 'X'}} -#endif M m; -#if !PUREONLY - //expected-note-re@-2 {{{{^}}Calling default constructor for 'M'}} -#endif Y *y = new Y; -#if !PUREONLY - //expected-note-re@-2 {{{{^}}Calling default constructor for 'Y'}} -#endif delete y; header::Z z; -#if !PUREONLY - // expected-note-re@-2 {{{{^}}Calling default constructor for 'Z'}} -#endif } -#if !PUREONLY - //expected-note-re@-2 2{{{{^}}Calling '~E'}} -#endif namespace PR34451 { struct a { diff --git a/clang/test/Analysis/virtualcall.h b/clang/test/Analysis/virtualcall.h index e2fde2415ec15..f591aab2cacfc 100644 --- a/clang/test/Analysis/virtualcall.h +++ b/clang/test/Analysis/virtualcall.h @@ -2,12 +2,7 @@ namespace header { class Z { public: Z() { - foo(); -#if !PUREONLY - // expected-warning-re@-2 {{{{^}}Call to virtual function during construction}} - // expected-note-re@-3 {{{{^}}This constructor of an object of type 'Z' has not returned when the virtual method was called}} - // expected-note-re@-4 {{{{^}}Call to virtual function during construction}} -#endif + foo(); // impure-warning {{Call to virtual method 'Z::foo' during construction bypasses virtual dispatch}} } virtual int foo(); }; From 37f9ef022a626523f92bdceeb9ebd3676d350160 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Tue, 20 Aug 2019 21:41:17 +0000 Subject: [PATCH 112/181] [analyzer] Fix a crash when destroying a non-region. Add defensive check that prevents a crash when we try to evaluate a destructor whose this-value is a concrete integer that isn't a null. Differential Revision: https://reviews.llvm.org/D65349 llvm-svn: 369450 (cherry picked from commit 8eb7a74b7808584e017673d5046db867fb873e60) --- .../Core/PathSensitive/ExprEngine.h | 2 +- clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 23 +++++++------- .../lib/StaticAnalyzer/Core/ExprEngineCXX.cpp | 23 ++++++++++++-- clang/test/Analysis/dtor.cpp | 30 +++++++++++++++++++ 4 files changed, 62 insertions(+), 16 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h index eb20b7d6cc981..eeb606ebfde5f 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h @@ -530,7 +530,7 @@ class ExprEngine : public SubEngine { void VisitCXXDestructor(QualType ObjectType, const MemRegion *Dest, const Stmt *S, bool IsBaseDtor, ExplodedNode *Pred, ExplodedNodeSet &Dst, - const EvalCallOptions &Options); + EvalCallOptions &Options); void VisitCXXNewAllocatorCall(const CXXNewExpr *CNE, ExplodedNode *Pred, diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp index 72bcd33438c44..c9cc8a648386a 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -977,8 +977,8 @@ void ExprEngine::ProcessAutomaticObjDtor(const CFGAutomaticObjDtor Dtor, Region = makeZeroElementRegion(state, loc::MemRegionVal(Region), varType, CallOpts.IsArrayCtorOrDtor).getAsRegion(); - VisitCXXDestructor(varType, Region, Dtor.getTriggerStmt(), /*IsBase=*/ false, - Pred, Dst, CallOpts); + VisitCXXDestructor(varType, Region, Dtor.getTriggerStmt(), + /*IsBase=*/false, Pred, Dst, CallOpts); } void ExprEngine::ProcessDeleteDtor(const CFGDeleteDtor Dtor, @@ -1036,8 +1036,9 @@ void ExprEngine::ProcessBaseDtor(const CFGBaseDtor D, SVal BaseVal = getStoreManager().evalDerivedToBase(ThisVal, BaseTy, Base->isVirtual()); - VisitCXXDestructor(BaseTy, BaseVal.castAs().getRegion(), - CurDtor->getBody(), /*IsBase=*/ true, Pred, Dst, {}); + EvalCallOptions CallOpts; + VisitCXXDestructor(BaseTy, BaseVal.getAsRegion(), CurDtor->getBody(), + /*IsBase=*/true, Pred, Dst, CallOpts); } void ExprEngine::ProcessMemberDtor(const CFGMemberDtor D, @@ -1048,10 +1049,10 @@ void ExprEngine::ProcessMemberDtor(const CFGMemberDtor D, const LocationContext *LCtx = Pred->getLocationContext(); const auto *CurDtor = cast(LCtx->getDecl()); - Loc ThisVal = getSValBuilder().getCXXThis(CurDtor, - LCtx->getStackFrame()); - SVal FieldVal = - State->getLValue(Member, State->getSVal(ThisVal).castAs()); + Loc ThisStorageLoc = + getSValBuilder().getCXXThis(CurDtor, LCtx->getStackFrame()); + Loc ThisLoc = State->getSVal(ThisStorageLoc).castAs(); + SVal FieldVal = State->getLValue(Member, ThisLoc); // FIXME: We need to run the same destructor on every element of the array. // This workaround will just run the first destructor (which will still @@ -1060,8 +1061,8 @@ void ExprEngine::ProcessMemberDtor(const CFGMemberDtor D, FieldVal = makeZeroElementRegion(State, FieldVal, T, CallOpts.IsArrayCtorOrDtor); - VisitCXXDestructor(T, FieldVal.castAs().getRegion(), - CurDtor->getBody(), /*IsBase=*/false, Pred, Dst, CallOpts); + VisitCXXDestructor(T, FieldVal.getAsRegion(), CurDtor->getBody(), + /*IsBase=*/false, Pred, Dst, CallOpts); } void ExprEngine::ProcessTemporaryDtor(const CFGTemporaryDtor D, @@ -1109,8 +1110,6 @@ void ExprEngine::ProcessTemporaryDtor(const CFGTemporaryDtor D, EvalCallOptions CallOpts; CallOpts.IsTemporaryCtorOrDtor = true; if (!MR) { - CallOpts.IsCtorOrDtorWithImproperlyModeledTargetRegion = true; - // If we have no MR, we still need to unwrap the array to avoid destroying // the whole array at once. Regardless, we'd eventually need to model array // destructors properly, element-by-element. diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp index 10c8cb1a9ee10..91afde603ca93 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp @@ -604,7 +604,7 @@ void ExprEngine::VisitCXXDestructor(QualType ObjectType, bool IsBaseDtor, ExplodedNode *Pred, ExplodedNodeSet &Dst, - const EvalCallOptions &CallOpts) { + EvalCallOptions &CallOpts) { assert(S && "A destructor without a trigger!"); const LocationContext *LCtx = Pred->getLocationContext(); ProgramStateRef State = Pred->getState(); @@ -612,7 +612,6 @@ void ExprEngine::VisitCXXDestructor(QualType ObjectType, const CXXRecordDecl *RecordDecl = ObjectType->getAsCXXRecordDecl(); assert(RecordDecl && "Only CXXRecordDecls should have destructors"); const CXXDestructorDecl *DtorDecl = RecordDecl->getDestructor(); - // FIXME: There should always be a Decl, otherwise the destructor call // shouldn't have been added to the CFG in the first place. if (!DtorDecl) { @@ -626,9 +625,27 @@ void ExprEngine::VisitCXXDestructor(QualType ObjectType, return; } + if (!Dest) { + // We're trying to destroy something that is not a region. This may happen + // for a variety of reasons (unknown target region, concrete integer instead + // of target region, etc.). The current code makes an attempt to recover. + // FIXME: We probably don't really need to recover when we're dealing + // with concrete integers specifically. + CallOpts.IsCtorOrDtorWithImproperlyModeledTargetRegion = true; + if (const Expr *E = dyn_cast_or_null(S)) { + Dest = MRMgr.getCXXTempObjectRegion(E, Pred->getLocationContext()); + } else { + static SimpleProgramPointTag T("ExprEngine", "SkipInvalidDestructor"); + NodeBuilder Bldr(Pred, Dst, *currBldrCtx); + Bldr.generateSink(Pred->getLocation().withTag(&T), + Pred->getState(), Pred); + return; + } + } + CallEventManager &CEMgr = getStateManager().getCallEventManager(); CallEventRef Call = - CEMgr.getCXXDestructorCall(DtorDecl, S, Dest, IsBaseDtor, State, LCtx); + CEMgr.getCXXDestructorCall(DtorDecl, S, Dest, IsBaseDtor, State, LCtx); PrettyStackTraceLoc CrashInfo(getContext().getSourceManager(), Call->getSourceRange().getBegin(), diff --git a/clang/test/Analysis/dtor.cpp b/clang/test/Analysis/dtor.cpp index d843f03aada7e..1c6251781545c 100644 --- a/clang/test/Analysis/dtor.cpp +++ b/clang/test/Analysis/dtor.cpp @@ -540,3 +540,33 @@ void f() { clang_analyzer_eval(__alignof(NonTrivial) > 0); // expected-warning{{TRUE}} } } + +namespace dtor_over_loc_concrete_int { +struct A { + ~A() {} +}; + +struct B { + A a; + ~B() {} +}; + +struct C : A { + ~C() {} +}; + +void testB() { + B *b = (B *)-1; + b->~B(); // no-crash +} + +void testC() { + C *c = (C *)-1; + c->~C(); // no-crash +} + +void testAutoDtor() { + const A &a = *(A *)-1; + // no-crash +} +} // namespace dtor_over_loc_concrete_int From 04abbac844f3ead98e5779d2151e15ce54a12db6 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Tue, 20 Aug 2019 21:41:20 +0000 Subject: [PATCH 113/181] [analyzer] NFC: Remove the BugTypes set from BugReporter. Its only purpose was to avoid a bug that's caused by making a virtual call in BugReporter's destructor. llvm-svn: 369451 (cherry picked from commit 3fdc427f0bc8a48bc56ac29d75ec53a68b6a25f2) --- .../Core/BugReporter/BugReporter.h | 19 ++----------------- clang/lib/StaticAnalyzer/Core/BugReporter.cpp | 16 +++------------- .../Frontend/AnalysisConsumer.cpp | 4 ++++ 3 files changed, 9 insertions(+), 30 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h index 36df6335611c4..e1eda7eaac633 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h @@ -408,11 +408,6 @@ class BugReporter { enum Kind { BasicBRKind, PathSensitiveBRKind }; private: - using BugTypesTy = llvm::ImmutableSet; - - BugTypesTy::Factory F; - BugTypesTy BugTypes; - const Kind kind; BugReporterData& D; @@ -433,11 +428,10 @@ class BugReporter { protected: BugReporter(BugReporterData& d, Kind k) - : BugTypes(F.getEmptySet()), kind(k), D(d) {} + : kind(k), D(d) {} public: - BugReporter(BugReporterData& d) - : BugTypes(F.getEmptySet()), kind(BasicBRKind), D(d) {} + BugReporter(BugReporterData &d) : kind(BasicBRKind), D(d) {} virtual ~BugReporter(); /// Generate and flush diagnostics for all bug reports. @@ -453,11 +447,6 @@ class BugReporter { return D.getPathDiagnosticConsumers(); } - /// Iterator over the set of BugTypes tracked by the BugReporter. - using iterator = BugTypesTy::iterator; - iterator begin() { return BugTypes.begin(); } - iterator end() { return BugTypes.end(); } - /// Iterator over the set of BugReports tracked by the BugReporter. using EQClasses_iterator = llvm::FoldingSet::iterator; EQClasses_iterator EQClasses_begin() { return EQClasses.begin(); } @@ -475,8 +464,6 @@ class BugReporter { return {}; } - void Register(const BugType *BT); - /// Add the given report to the set of reports tracked by BugReporter. /// /// The reports are usually generated by the checkers. Further, they are @@ -511,8 +498,6 @@ class PathSensitiveBugReporter : public BugReporter { PathSensitiveBugReporter(BugReporterData& d, ExprEngine& eng) : BugReporter(d, PathSensitiveBRKind), Eng(eng) {} - ~PathSensitiveBugReporter() override = default; - /// getGraph - Get the exploded graph created by the analysis engine /// for the analyzed method or function. const ExplodedGraph &getGraph() const; diff --git a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp index 9bc00deaa063f..bea83686ecf92 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp @@ -2227,7 +2227,9 @@ ProgramStateManager &PathSensitiveBugReporter::getStateManager() const { } BugReporter::~BugReporter() { - FlushReports(); + // Make sure reports are flushed. + assert(StrBugTypes.empty() && + "Destroying BugReporter before diagnostics are emitted!"); // Free the bug reports we are tracking. for (const auto I : EQClassesVector) @@ -2235,9 +2237,6 @@ BugReporter::~BugReporter() { } void BugReporter::FlushReports() { - if (BugTypes.isEmpty()) - return; - // We need to flush reports in deterministic order to ensure the order // of the reports is consistent between runs. for (const auto EQ : EQClassesVector) @@ -2248,9 +2247,6 @@ void BugReporter::FlushReports() { // FIXME: There are leaks from checkers that assume that the BugTypes they // create will be destroyed by the BugReporter. llvm::DeleteContainerSeconds(StrBugTypes); - - // Remove all references to the BugType objects. - BugTypes = F.getEmptySet(); } //===----------------------------------------------------------------------===// @@ -2668,10 +2664,6 @@ PathSensitiveBugReporter::generatePathDiagnostics( return Out; } -void BugReporter::Register(const BugType *BT) { - BugTypes = F.add(BugTypes, BT); -} - void BugReporter::emitReport(std::unique_ptr R) { if (const ExplodedNode *E = R->getErrorNode()) { // An error node must either be a sink or have a tag, otherwise @@ -2702,8 +2694,6 @@ void BugReporter::emitReport(std::unique_ptr R) { R->Profile(ID); // Lookup the equivance class. If there isn't one, create it. - const BugType& BT = R->getBugType(); - Register(&BT); void *InsertPos; BugReportEquivClass* EQ = EQClasses.FindNodeOrInsertPos(ID, InsertPos); diff --git a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp index ce3deee7b2659..2276f5ce874d6 100644 --- a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp +++ b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp @@ -609,6 +609,7 @@ void AnalysisConsumer::runAnalysisOnTranslationUnit(ASTContext &C) { // After all decls handled, run checkers on the entire TranslationUnit. checkerMgr->runCheckersOnEndOfTranslationUnit(TU, *Mgr, BR); + BR.FlushReports(); RecVisitorBR = nullptr; } @@ -766,6 +767,9 @@ void AnalysisConsumer::HandleCode(Decl *D, AnalysisMode Mode, if (SyntaxCheckTimer) SyntaxCheckTimer->stopTimer(); } + + BR.FlushReports(); + if ((Mode & AM_Path) && checkerMgr->hasPathSensitiveCheckers()) { RunPathSensitiveChecks(D, IMode, VisitedCallees); if (IMode != ExprEngine::Inline_Minimal) From e6b0eeda17df71dd42d840c8858a443094a61c5c Mon Sep 17 00:00:00 2001 From: Dmitri Gribenko Date: Wed, 21 Aug 2019 08:48:24 +0000 Subject: [PATCH 114/181] Removed some dead code in BugReporter and related files Subscribers: cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D66473 llvm-svn: 369504 (cherry picked from commit 6b9d7c9da591f5b9c322ca85841ffe1daa3cd7cc) --- .../Core/BugReporter/BugReporter.h | 60 +------------------ .../Core/BugReporter/BugReporterVisitors.h | 6 -- .../Core/BugReporter/PathDiagnostic.h | 2 - .../Core/PathSensitive/AnalysisManager.h | 7 +-- .../StaticAnalyzer/Core/AnalysisManager.cpp | 4 +- clang/lib/StaticAnalyzer/Core/BugReporter.cpp | 23 +------ .../Core/BugReporterVisitors.cpp | 35 ----------- .../StaticAnalyzer/Core/PathDiagnostic.cpp | 11 ---- .../Frontend/AnalysisConsumer.cpp | 4 +- clang/unittests/StaticAnalyzer/Reusables.h | 2 +- 10 files changed, 12 insertions(+), 142 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h index e1eda7eaac633..6b4085434458d 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h @@ -74,16 +74,6 @@ using DiagnosticForConsumerMapTy = /// individual bug reports. class BugReport : public llvm::ilist_node { public: - class NodeResolver { - virtual void anchor(); - - public: - virtual ~NodeResolver() = default; - - virtual const ExplodedNode* - getOriginalNode(const ExplodedNode *N) = 0; - }; - using ranges_iterator = const SourceRange *; using VisitorList = SmallVector, 8>; using visitor_iterator = VisitorList::iterator; @@ -391,7 +381,6 @@ class BugReporterData { public: virtual ~BugReporterData() = default; - virtual DiagnosticsEngine& getDiagnostic() = 0; virtual ArrayRef getPathDiagnosticConsumers() = 0; virtual ASTContext &getASTContext() = 0; virtual SourceManager &getSourceManager() = 0; @@ -404,11 +393,7 @@ class BugReporterData { /// /// The base class is used for generating path-insensitive class BugReporter { -public: - enum Kind { BasicBRKind, PathSensitiveBRKind }; - private: - const Kind kind; BugReporterData& D; /// Generate and flush the diagnostics for the given bug report. @@ -426,23 +411,13 @@ class BugReporter { /// A vector of BugReports for tracking the allocated pointers and cleanup. std::vector EQClassesVector; -protected: - BugReporter(BugReporterData& d, Kind k) - : kind(k), D(d) {} - public: - BugReporter(BugReporterData &d) : kind(BasicBRKind), D(d) {} + BugReporter(BugReporterData &d) : D(d) {} virtual ~BugReporter(); /// Generate and flush diagnostics for all bug reports. void FlushReports(); - Kind getKind() const { return kind; } - - DiagnosticsEngine& getDiagnostic() { - return D.getDiagnostic(); - } - ArrayRef getPathDiagnosticConsumers() { return D.getPathDiagnosticConsumers(); } @@ -496,7 +471,7 @@ class PathSensitiveBugReporter : public BugReporter { public: PathSensitiveBugReporter(BugReporterData& d, ExprEngine& eng) - : BugReporter(d, PathSensitiveBRKind), Eng(eng) {} + : BugReporter(d), Eng(eng) {} /// getGraph - Get the exploded graph created by the analysis engine /// for the analyzed method or function. @@ -504,8 +479,6 @@ class PathSensitiveBugReporter : public BugReporter { /// getStateManager - Return the state manager used by the analysis /// engine. - ProgramStateManager &getStateManager(); - ProgramStateManager &getStateManager() const; /// \p bugReports A set of bug reports within a *single* equivalence class @@ -516,50 +489,25 @@ class PathSensitiveBugReporter : public BugReporter { std::unique_ptr generatePathDiagnostics(ArrayRef consumers, ArrayRef &bugReports) override; - - /// classof - Used by isa<>, cast<>, and dyn_cast<>. - static bool classof(const BugReporter* R) { - return R->getKind() == PathSensitiveBRKind; - } }; -class NodeMapClosure : public BugReport::NodeResolver { - InterExplodedGraphMap &M; - -public: - NodeMapClosure(InterExplodedGraphMap &m) : M(m) {} - - const ExplodedNode *getOriginalNode(const ExplodedNode *N) override { - return M.lookup(N); - } -}; - class BugReporterContext { PathSensitiveBugReporter &BR; - NodeMapClosure NMC; virtual void anchor(); public: - BugReporterContext(PathSensitiveBugReporter &br, - InterExplodedGraphMap &Backmap) - : BR(br), NMC(Backmap) {} + BugReporterContext(PathSensitiveBugReporter &br) : BR(br) {} virtual ~BugReporterContext() = default; PathSensitiveBugReporter& getBugReporter() { return BR; } - const ExplodedGraph &getGraph() const { return BR.getGraph(); } - ProgramStateManager& getStateManager() const { return BR.getStateManager(); } - SValBuilder &getSValBuilder() const { - return getStateManager().getSValBuilder(); - } - ASTContext &getASTContext() const { return BR.getContext(); } @@ -571,8 +519,6 @@ class BugReporterContext { const AnalyzerOptions &getAnalyzerOptions() const { return BR.getAnalyzerOptions(); } - - NodeMapClosure& getNodeResolver() { return NMC; } }; diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h index 9069270a38e19..2daf5e88c2746 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h @@ -135,12 +135,6 @@ class FindLastStoreBRVisitor final : public BugReporterVisitor { const StackFrameContext *OriginSFC; public: - /// Creates a visitor for every VarDecl inside a Stmt and registers it with - /// the BugReport. - static void registerStatementVarDecls(BugReport &BR, const Stmt *S, - bool EnableNullFPSuppression, - TrackingKind TKind); - /// \param V We're searching for the store where \c R received this value. /// \param R The region we're tracking. /// \param EnableNullFPSuppression Whether we should employ false positive diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h index bfec18b034b5e..52d2584150e17 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h @@ -735,8 +735,6 @@ class PathDiagnosticMacroPiece : public PathDiagnosticSpotPiece { PathPieces subPieces; - bool containsEvent() const; - void flattenLocations() override { PathDiagnosticSpotPiece::flattenLocations(); for (const auto &I : subPieces) diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h index b0dda78a00a98..ffddffe439c60 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h @@ -32,7 +32,6 @@ class AnalysisManager : public BugReporterData { AnalysisDeclContextManager AnaCtxMgr; ASTContext &Ctx; - DiagnosticsEngine &Diags; const LangOptions &LangOpts; PathDiagnosticConsumers PathConsumers; @@ -45,7 +44,7 @@ class AnalysisManager : public BugReporterData { public: AnalyzerOptions &options; - AnalysisManager(ASTContext &ctx, DiagnosticsEngine &diags, + AnalysisManager(ASTContext &ctx, const PathDiagnosticConsumers &Consumers, StoreManagerCreator storemgr, ConstraintManagerCreator constraintmgr, @@ -84,10 +83,6 @@ class AnalysisManager : public BugReporterData { return getASTContext().getSourceManager(); } - DiagnosticsEngine &getDiagnostic() override { - return Diags; - } - const LangOptions &getLangOpts() const { return LangOpts; } diff --git a/clang/lib/StaticAnalyzer/Core/AnalysisManager.cpp b/clang/lib/StaticAnalyzer/Core/AnalysisManager.cpp index 95f2b703cdd65..87069e5d19ea0 100644 --- a/clang/lib/StaticAnalyzer/Core/AnalysisManager.cpp +++ b/clang/lib/StaticAnalyzer/Core/AnalysisManager.cpp @@ -13,7 +13,7 @@ using namespace ento; void AnalysisManager::anchor() { } -AnalysisManager::AnalysisManager(ASTContext &ASTCtx, DiagnosticsEngine &diags, +AnalysisManager::AnalysisManager(ASTContext &ASTCtx, const PathDiagnosticConsumers &PDC, StoreManagerCreator storemgr, ConstraintManagerCreator constraintmgr, @@ -38,7 +38,7 @@ AnalysisManager::AnalysisManager(ASTContext &ASTCtx, DiagnosticsEngine &diags, Options.ShouldElideConstructors, /*addVirtualBaseBranches=*/true, injector), - Ctx(ASTCtx), Diags(diags), LangOpts(ASTCtx.getLangOpts()), + Ctx(ASTCtx), LangOpts(ASTCtx.getLangOpts()), PathConsumers(PDC), CreateStoreMgr(storemgr), CreateConstraintMgr(constraintmgr), CheckerMgr(checkerMgr), options(Options) { diff --git a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp index bea83686ecf92..af2ee7427c8b4 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp @@ -2049,8 +2049,6 @@ void BuiltinBug::anchor() {} // Methods for BugReport and subclasses. //===----------------------------------------------------------------------===// -void BugReport::NodeResolver::anchor() {} - void BugReport::addVisitor(std::unique_ptr visitor) { if (!visitor) return; @@ -2218,10 +2216,6 @@ const ExplodedGraph &PathSensitiveBugReporter::getGraph() const { return Eng.getGraph(); } -ProgramStateManager &PathSensitiveBugReporter::getStateManager() { - return Eng.getStateManager(); -} - ProgramStateManager &PathSensitiveBugReporter::getStateManager() const { return Eng.getStateManager(); } @@ -2256,11 +2250,9 @@ void BugReporter::FlushReports() { namespace { /// A wrapper around an ExplodedGraph that contains a single path from the root -/// to the error node, and a map that maps the nodes in this path to the ones in -/// the original ExplodedGraph. +/// to the error node. class BugPathInfo { public: - InterExplodedGraphMap MapToOriginNodes; std::unique_ptr BugPath; BugReport *Report; const ExplodedNode *ErrorNode; @@ -2271,9 +2263,6 @@ class BugPathInfo { class BugPathGetter { std::unique_ptr TrimmedGraph; - /// Map from the trimmed graph to the original. - InterExplodedGraphMap InverseMap; - using PriorityMapTy = llvm::DenseMap; /// Assign each node with its distance from the root. @@ -2336,7 +2325,7 @@ BugPathGetter::BugPathGetter(const ExplodedGraph *OriginalGraph, // The trimmed graph is created in the body of the constructor to ensure // that the DenseMaps have been initialized already. InterExplodedGraphMap ForwardMap; - TrimmedGraph = OriginalGraph->trim(Nodes, &ForwardMap, &InverseMap); + TrimmedGraph = OriginalGraph->trim(Nodes, &ForwardMap); // Find the (first) error node in the trimmed graph. We just need to consult // the node map which maps from nodes in the original graph to nodes @@ -2399,7 +2388,6 @@ BugPathInfo *BugPathGetter::getNextBugPath() { // Create a new graph with a single path. This is the graph that will be // returned to the caller. auto GNew = llvm::make_unique(); - CurrentBugPath.MapToOriginNodes.clear(); // Now walk from the error node up the BFS path, always taking the // predeccessor with the lowest number. @@ -2410,11 +2398,6 @@ BugPathInfo *BugPathGetter::getNextBugPath() { ExplodedNode *NewN = GNew->createUncachedNode( OrigN->getLocation(), OrigN->getState(), OrigN->isSink()); - // Store the mapping to the original node. - InterExplodedGraphMap::const_iterator IMitr = InverseMap.find(OrigN); - assert(IMitr != InverseMap.end() && "No mapping to original node."); - CurrentBugPath.MapToOriginNodes[NewN] = IMitr->second; - // Link up the new node with the previous node. if (Succ) Succ->addPredecessor(NewN, *GNew); @@ -2613,7 +2596,7 @@ PathDiagnosticBuilder::findValidReport(ArrayRef &bugReports, R->addVisitor(llvm::make_unique()); R->addVisitor(llvm::make_unique()); - BugReporterContext BRC(Reporter, BugPath->MapToOriginNodes); + BugReporterContext BRC(Reporter); // Run all visitors on a given graph, once. std::unique_ptr visitorNotes = diff --git a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp index afdd0c105305a..030a18cd38f7f 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp @@ -1166,41 +1166,6 @@ void FindLastStoreBRVisitor::Profile(llvm::FoldingSetNodeID &ID) const { ID.AddBoolean(EnableNullFPSuppression); } -void FindLastStoreBRVisitor::registerStatementVarDecls( - BugReport &BR, const Stmt *S, bool EnableNullFPSuppression, - TrackingKind TKind) { - - const ExplodedNode *N = BR.getErrorNode(); - std::deque WorkList; - WorkList.push_back(S); - - while (!WorkList.empty()) { - const Stmt *Head = WorkList.front(); - WorkList.pop_front(); - - ProgramStateManager &StateMgr = N->getState()->getStateManager(); - - if (const auto *DR = dyn_cast(Head)) { - if (const auto *VD = dyn_cast(DR->getDecl())) { - const VarRegion *R = - StateMgr.getRegionManager().getVarRegion(VD, N->getLocationContext()); - - // What did we load? - SVal V = N->getSVal(S); - - if (V.getAs() || V.getAs()) { - // Register a new visitor with the BugReport. - BR.addVisitor(llvm::make_unique( - V.castAs(), R, EnableNullFPSuppression, TKind)); - } - } - } - - for (const Stmt *SubStmt : Head->children()) - WorkList.push_back(SubStmt); - } -} - /// Returns true if \p N represents the DeclStmt declaring and initializing /// \p VR. static bool isInitializationOfVar(const ExplodedNode *N, const VarRegion *VR) { diff --git a/clang/lib/StaticAnalyzer/Core/PathDiagnostic.cpp b/clang/lib/StaticAnalyzer/Core/PathDiagnostic.cpp index 54fbd6a5bc496..2d3b50082c7dc 100644 --- a/clang/lib/StaticAnalyzer/Core/PathDiagnostic.cpp +++ b/clang/lib/StaticAnalyzer/Core/PathDiagnostic.cpp @@ -53,17 +53,6 @@ using namespace clang; using namespace ento; -bool PathDiagnosticMacroPiece::containsEvent() const { - for (const auto &P : subPieces) { - if (isa(*P)) - return true; - if (const auto *MP = dyn_cast(P.get())) - if (MP->containsEvent()) - return true; - } - return false; -} - static StringRef StripTrailingDots(StringRef s) { for (StringRef::size_type i = s.size(); i != 0; --i) if (s[i - 1] != '.') diff --git a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp index 2276f5ce874d6..a184f6463d10c 100644 --- a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp +++ b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp @@ -312,8 +312,8 @@ class AnalysisConsumer : public AnalysisASTConsumer, *Ctx, *Opts, Plugins, CheckerRegistrationFns, PP.getDiagnostics()); Mgr = llvm::make_unique( - *Ctx, PP.getDiagnostics(), PathConsumers, CreateStoreMgr, - CreateConstraintMgr, checkerMgr.get(), *Opts, Injector); + *Ctx, PathConsumers, CreateStoreMgr, CreateConstraintMgr, + checkerMgr.get(), *Opts, Injector); } /// Store the top level decls in the set to be processed later on. diff --git a/clang/unittests/StaticAnalyzer/Reusables.h b/clang/unittests/StaticAnalyzer/Reusables.h index 49b96f63960a1..bac2808369c24 100644 --- a/clang/unittests/StaticAnalyzer/Reusables.h +++ b/clang/unittests/StaticAnalyzer/Reusables.h @@ -58,7 +58,7 @@ class ExprEngineConsumer : public ASTConsumer { ExprEngineConsumer(CompilerInstance &C) : C(C), ChkMgr(C.getASTContext(), *C.getAnalyzerOpts()), CTU(C), Consumers(), - AMgr(C.getASTContext(), C.getDiagnostics(), Consumers, + AMgr(C.getASTContext(), Consumers, CreateRegionStoreManager, CreateRangeConstraintManager, &ChkMgr, *C.getAnalyzerOpts()), VisitedCallees(), FS(), From 0f21765d80d265198f5542fa5422bfbb2fe70755 Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Wed, 21 Aug 2019 20:43:27 +0000 Subject: [PATCH 115/181] [analyzer] Mention whether an event is about a condition in a bug report part 1 Can't add much more to the title! This is part 1, the case where the collapse point isn't in the condition point is the responsibility of ConditionBRVisitor, which I'm addressing in part 2. Differential Revision: https://reviews.llvm.org/D65575 llvm-svn: 369574 (cherry picked from commit da648ab8de3638ff82d6b9349c603b854a0224d6) --- .../Core/BugReporterVisitors.cpp | 18 +- .../track-control-dependency-conditions.cpp | 366 +++++++++--------- 2 files changed, 198 insertions(+), 186 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp index 030a18cd38f7f..e12568705426f 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp @@ -298,6 +298,7 @@ class NoStoreFuncVisitor final : public BugReporterVisitor { MemRegionManager &MmrMgr; const SourceManager &SM; const PrintingPolicy &PP; + bugreporter::TrackingKind TKind; /// Recursion limit for dereferencing fields when looking for the /// region of interest. @@ -318,10 +319,10 @@ class NoStoreFuncVisitor final : public BugReporterVisitor { using RegionVector = SmallVector; public: - NoStoreFuncVisitor(const SubRegion *R) + NoStoreFuncVisitor(const SubRegion *R, bugreporter::TrackingKind TKind) : RegionOfInterest(R), MmrMgr(*R->getMemRegionManager()), SM(MmrMgr.getContext().getSourceManager()), - PP(MmrMgr.getContext().getPrintingPolicy()) {} + PP(MmrMgr.getContext().getPrintingPolicy()), TKind(TKind) {} void Profile(llvm::FoldingSetNodeID &ID) const override { static int Tag = 0; @@ -612,6 +613,9 @@ void NoStoreFuncVisitor::findModifyingFrames(const ExplodedNode *N) { } while (N); } +static llvm::StringLiteral WillBeUsedForACondition = + ", which participates in a condition later"; + PathDiagnosticPieceRef NoStoreFuncVisitor::maybeEmitNote( BugReport &R, const CallEvent &Call, const ExplodedNode *N, const RegionVector &FieldChain, const MemRegion *MatchedRegion, @@ -658,6 +662,8 @@ PathDiagnosticPieceRef NoStoreFuncVisitor::maybeEmitNote( return nullptr; os << "'"; + if (TKind == bugreporter::TrackingKind::Condition) + os << WillBeUsedForACondition; return std::make_shared(L, os.str()); } @@ -1068,6 +1074,9 @@ class ReturnVisitor : public BugReporterVisitor { if (!L.isValid() || !L.asLocation().isValid()) return nullptr; + if (TKind == bugreporter::TrackingKind::Condition) + Out << WillBeUsedForACondition; + auto EventPiece = std::make_shared(L, Out.str()); // If we determined that the note is meaningless, make it prunable, and @@ -1450,6 +1459,9 @@ FindLastStoreBRVisitor::VisitNode(const ExplodedNode *Succ, if (os.str().empty()) showBRDefaultDiagnostics(os, R, V); + if (TKind == bugreporter::TrackingKind::Condition) + os << WillBeUsedForACondition; + // Construct a new PathDiagnosticPiece. ProgramPoint P = StoreSite->getLocation(); PathDiagnosticLocation L; @@ -1933,7 +1945,7 @@ bool bugreporter::trackExpressionValue(const ExplodedNode *InputNode, // Mark both the variable region and its contents as interesting. SVal V = LVState->getRawSVal(loc::MemRegionVal(R)); report.addVisitor( - llvm::make_unique(cast(R))); + llvm::make_unique(cast(R), TKind)); MacroNullReturnSuppressionVisitor::addMacroVisitorIfNecessary( LVNode, R, EnableNullFPSuppression, report, V); diff --git a/clang/test/Analysis/track-control-dependency-conditions.cpp b/clang/test/Analysis/track-control-dependency-conditions.cpp index e02eb412a8097..5f089b213da93 100644 --- a/clang/test/Analysis/track-control-dependency-conditions.cpp +++ b/clang/test/Analysis/track-control-dependency-conditions.cpp @@ -29,24 +29,24 @@ int flag; bool coin(); void foo() { - flag = coin(); // tracking-note{{Value assigned to 'flag'}} + flag = coin(); // tracking-note-re{{{{^}}Value assigned to 'flag', which participates in a condition later{{$}}}} } void test() { - int *x = 0; // expected-note{{'x' initialized to a null pointer value}} + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} flag = 1; foo(); // TODO: Add nodes here about flag's value being invalidated. - if (flag) // expected-note {{Assuming 'flag' is 0}} - // expected-note@-1{{Taking false branch}} + if (flag) // expected-note-re {{{{^}}Assuming 'flag' is 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking false branch{{$}}}} x = new int; - foo(); // tracking-note{{Calling 'foo'}} - // tracking-note@-1{{Returning from 'foo'}} + foo(); // tracking-note-re{{{{^}}Calling 'foo'{{$}}}} + // tracking-note-re@-1{{{{^}}Returning from 'foo'{{$}}}} - if (flag) // expected-note {{Assuming 'flag' is not equal to 0}} - // expected-note@-1{{Taking true branch}} - // debug-note@-2{{Tracking condition 'flag'}} + if (flag) // expected-note-re {{{{^}}Assuming 'flag' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} *x = 5; // expected-warning{{Dereference of null pointer}} // expected-note@-1{{Dereference of null pointer}} @@ -58,7 +58,7 @@ int flag; bool coin(); void foo() { - flag = coin(); // tracking-note{{Value assigned to 'flag'}} + flag = coin(); // tracking-note-re{{{{^}}Value assigned to 'flag', which participates in a condition later{{$}}}} } void test() { @@ -66,18 +66,18 @@ void test() { flag = 1; foo(); - if (flag) // expected-note {{Assuming 'flag' is 0}} - // expected-note@-1{{Taking false branch}} + if (flag) // expected-note-re {{{{^}}Assuming 'flag' is 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking false branch{{$}}}} x = new int; - x = 0; // expected-note{{Null pointer value stored to 'x'}} + x = 0; // expected-note-re{{{{^}}Null pointer value stored to 'x'{{$}}}} - foo(); // tracking-note{{Calling 'foo'}} - // tracking-note@-1{{Returning from 'foo'}} + foo(); // tracking-note-re{{{{^}}Calling 'foo'{{$}}}} + // tracking-note-re@-1{{{{^}}Returning from 'foo'{{$}}}} - if (flag) // expected-note {{Assuming 'flag' is not equal to 0}} - // expected-note@-1{{Taking true branch}} - // debug-note@-2{{Tracking condition 'flag'}} + if (flag) // expected-note-re {{{{^}}Assuming 'flag' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} *x = 5; // expected-warning{{Dereference of null pointer}} // expected-note@-1{{Dereference of null pointer}} @@ -90,25 +90,25 @@ bool coin(); void foo() { // coin() could write bar, do it's invalidated. - flag = coin(); // tracking-note{{Value assigned to 'flag'}} - // tracking-note@-1{{Value assigned to 'bar'}} + flag = coin(); // tracking-note-re{{{{^}}Value assigned to 'flag', which participates in a condition later{{$}}}} + // tracking-note-re@-1{{{{^}}Value assigned to 'bar', which participates in a condition later{{$}}}} } int bar; void test() { - int *x = 0; // expected-note{{'x' initialized to a null pointer value}} + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} flag = 1; - foo(); // tracking-note{{Calling 'foo'}} - // tracking-note@-1{{Returning from 'foo'}} + foo(); // tracking-note-re{{{{^}}Calling 'foo'{{$}}}} + // tracking-note-re@-1{{{{^}}Returning from 'foo'{{$}}}} - if (bar) // expected-note {{Assuming 'bar' is not equal to 0}} - // expected-note@-1{{Taking true branch}} - // debug-note@-2{{Tracking condition 'bar'}} - if (flag) // expected-note {{Assuming 'flag' is not equal to 0}} - // expected-note@-1{{Taking true branch}} - // debug-note@-2{{Tracking condition 'flag'}} + if (bar) // expected-note-re {{{{^}}Assuming 'bar' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'bar'{{$}}}} + if (flag) // expected-note-re {{{{^}}Assuming 'flag' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} *x = 5; // expected-warning{{Dereference of null pointer}} // expected-note@-1{{Dereference of null pointer}} @@ -125,11 +125,11 @@ bool foo() { int bar; void test() { - int *x = 0; // expected-note{{'x' initialized to a null pointer value}} + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} - if (int flag = foo()) // debug-note{{Tracking condition 'flag'}} - // expected-note@-1{{Assuming 'flag' is not equal to 0}} - // expected-note@-2{{Taking true branch}} + if (int flag = foo()) // debug-note-re{{{{^}}Tracking condition 'flag'{{$}}}} + // expected-note-re@-1{{{{^}}Assuming 'flag' is not equal to 0{{$}}}} + // expected-note-re@-2{{{{^}}Taking true branch{{$}}}} *x = 5; // expected-warning{{Dereference of null pointer}} // expected-note@-1{{Dereference of null pointer}} @@ -144,12 +144,12 @@ struct ConvertsToBool { }; void test() { - int *x = 0; // expected-note{{'x' initialized to a null pointer value}} + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} if (ConvertsToBool()) - // debug-note@-1{{Tracking condition 'ConvertsToBool()'}} - // expected-note@-2{{Assuming the condition is true}} - // expected-note@-3{{Taking true branch}} + // debug-note-re@-1{{{{^}}Tracking condition 'ConvertsToBool()'{{$}}}} + // expected-note-re@-2{{{{^}}Assuming the condition is true{{$}}}} + // expected-note-re@-3{{{{^}}Taking true branch{{$}}}} *x = 5; // expected-warning{{Dereference of null pointer}} // expected-note@-1{{Dereference of null pointer}} } @@ -159,9 +159,9 @@ void test() { namespace note_from_different_but_not_nested_stackframe { void nullptrDeref(int *ptr, bool True) { - if (True) // expected-note{{'True' is true}} - // expected-note@-1{{Taking true branch}} - // debug-note@-2{{Tracking condition 'True}} + if (True) // expected-note-re{{{{^}}'True' is true{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'True'{{$}}}} *ptr = 5; // expected-note@-1{{Dereference of null pointer (loaded from variable 'ptr')}} // expected-warning@-2{{Dereference of null pointer (loaded from variable 'ptr')}} @@ -169,11 +169,11 @@ void nullptrDeref(int *ptr, bool True) { void f() { int *ptr = nullptr; - // expected-note@-1{{'ptr' initialized to a null pointer value}} + // expected-note-re@-1{{{{^}}'ptr' initialized to a null pointer value{{$}}}} bool True = true; nullptrDeref(ptr, True); - // expected-note@-1{{Passing null pointer value via 1st parameter 'ptr'}} - // expected-note@-2{{Calling 'nullptrDeref'}} + // expected-note-re@-1{{{{^}}Passing null pointer value via 1st parameter 'ptr'{{$}}}} + // expected-note-re@-2{{{{^}}Calling 'nullptrDeref'{{$}}}} } } // end of namespace note_from_different_but_not_nested_stackframe @@ -184,26 +184,26 @@ bool coin(); int *getIntPtr(); void storeValue(int **i) { - *i = getIntPtr(); // tracking-note{{Value assigned to 'i'}} + *i = getIntPtr(); // tracking-note-re{{{{^}}Value assigned to 'i', which participates in a condition later{{$}}}} } int *conjurePointer() { int *i; - storeValue(&i); // tracking-note{{Calling 'storeValue'}} - // tracking-note@-1{{Returning from 'storeValue'}} - return i; // tracking-note{{Returning pointer (loaded from 'i')}} + storeValue(&i); // tracking-note-re{{{{^}}Calling 'storeValue'{{$}}}} + // tracking-note-re@-1{{{{^}}Returning from 'storeValue'{{$}}}} + return i; // tracking-note-re{{{{^}}Returning pointer (loaded from 'i'), which participates in a condition later{{$}}}} } void f(int *ptr) { - if (ptr) // expected-note{{Assuming 'ptr' is null}} - // expected-note@-1{{Taking false branch}} + if (ptr) // expected-note-re{{{{^}}Assuming 'ptr' is null{{$}}}} + // expected-note-re@-1{{{{^}}Taking false branch{{$}}}} ; if (!conjurePointer()) - // tracking-note@-1{{Calling 'conjurePointer'}} - // tracking-note@-2{{Returning from 'conjurePointer'}} - // debug-note@-3{{Tracking condition '!conjurePointer()'}} - // expected-note@-4{{Assuming the condition is true}} - // expected-note@-5{{Taking true branch}} + // tracking-note-re@-1{{{{^}}Calling 'conjurePointer'{{$}}}} + // tracking-note-re@-2{{{{^}}Returning from 'conjurePointer'{{$}}}} + // debug-note-re@-3{{{{^}}Tracking condition '!conjurePointer()'{{$}}}} + // expected-note-re@-4{{{{^}}Assuming the condition is true{{$}}}} + // expected-note-re@-5{{{{^}}Taking true branch{{$}}}} *ptr = 5; // expected-warning{{Dereference of null pointer}} // expected-note@-1{{Dereference of null pointer}} } @@ -220,13 +220,13 @@ int *conjurePointer() { } void f(int *ptr) { - if (ptr) // expected-note{{Assuming 'ptr' is null}} - // expected-note@-1{{Taking false branch}} + if (ptr) // expected-note-re{{{{^}}Assuming 'ptr' is null{{$}}}} + // expected-note-re@-1{{{{^}}Taking false branch{{$}}}} ; if (!conjurePointer()) - // debug-note@-1{{Tracking condition '!conjurePointer()'}} - // expected-note@-2{{Assuming the condition is true}} - // expected-note@-3{{Taking true branch}} + // debug-note-re@-1{{{{^}}Tracking condition '!conjurePointer()'{{$}}}} + // expected-note-re@-2{{{{^}}Assuming the condition is true{{$}}}} + // expected-note-re@-3{{{{^}}Taking true branch{{$}}}} *ptr = 5; // expected-warning{{Dereference of null pointer}} // expected-note@-1{{Dereference of null pointer}} } @@ -241,12 +241,12 @@ int *cast(void *P) { } void f() { - int *x = 0; // expected-note{{'x' initialized to a null pointer value}} + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} if (cast(conjure())) - // debug-note@-1{{Tracking condition 'cast(conjure())'}} - // expected-note@-2{{Assuming the condition is false}} - // expected-note@-3{{Taking false branch}} + // debug-note-re@-1{{{{^}}Tracking condition 'cast(conjure())'{{$}}}} + // expected-note-re@-2{{{{^}}Assuming the condition is false{{$}}}} + // expected-note-re@-3{{{{^}}Taking false branch{{$}}}} return; *x = 5; // expected-warning{{Dereference of null pointer}} // expected-note@-1{{Dereference of null pointer}} @@ -260,13 +260,13 @@ bool coin(); bool flipCoin() { return coin(); } void i(int *ptr) { - if (ptr) // expected-note{{Assuming 'ptr' is null}} - // expected-note@-1{{Taking false branch}} + if (ptr) // expected-note-re{{{{^}}Assuming 'ptr' is null{{$}}}} + // expected-note-re@-1{{{{^}}Taking false branch{{$}}}} ; if (!flipCoin()) - // debug-note@-1{{Tracking condition '!flipCoin()'}} - // expected-note@-2{{Assuming the condition is true}} - // expected-note@-3{{Taking true branch}} + // debug-note-re@-1{{{{^}}Tracking condition '!flipCoin()'{{$}}}} + // expected-note-re@-2{{{{^}}Assuming the condition is true{{$}}}} + // expected-note-re@-3{{{{^}}Taking true branch{{$}}}} *ptr = 5; // expected-warning{{Dereference of null pointer}} // expected-note@-1{{Dereference of null pointer}} } @@ -276,23 +276,23 @@ namespace important_returning_value_note { bool coin(); bool flipCoin() { - if (coin()) // tracking-note{{Assuming the condition is false}} - // tracking-note@-1{{Taking false branch}} - // debug-note@-2{{Tracking condition 'coin()'}} + if (coin()) // tracking-note-re{{{{^}}Assuming the condition is false{{$}}}} + // tracking-note-re@-1{{{{^}}Taking false branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'coin()'{{$}}}} return true; - return coin(); // tracking-note{{Returning value}} + return coin(); // tracking-note-re{{{{^}}Returning value, which participates in a condition later{{$}}}} } void i(int *ptr) { - if (ptr) // expected-note{{Assuming 'ptr' is null}} - // expected-note@-1{{Taking false branch}} + if (ptr) // expected-note-re{{{{^}}Assuming 'ptr' is null{{$}}}} + // expected-note-re@-1{{{{^}}Taking false branch{{$}}}} ; if (!flipCoin()) - // tracking-note@-1{{Calling 'flipCoin'}} - // tracking-note@-2{{Returning from 'flipCoin'}} - // debug-note@-3{{Tracking condition '!flipCoin()'}} - // expected-note@-4{{Assuming the condition is true}} - // expected-note@-5{{Taking true branch}} + // tracking-note-re@-1{{{{^}}Calling 'flipCoin'{{$}}}} + // tracking-note-re@-2{{{{^}}Returning from 'flipCoin'{{$}}}} + // debug-note-re@-3{{{{^}}Tracking condition '!flipCoin()'{{$}}}} + // expected-note-re@-4{{{{^}}Assuming the condition is true{{$}}}} + // expected-note-re@-5{{{{^}}Taking true branch{{$}}}} *ptr = 5; // expected-warning{{Dereference of null pointer}} // expected-note@-1{{Dereference of null pointer}} } @@ -307,22 +307,22 @@ struct super_complicated_template_hackery { bool flipCoin() { if (super_complicated_template_hackery::value) - // tracking-note@-1{{'value' is false}} - // tracking-note@-2{{Taking false branch}} + // tracking-note-re@-1{{{{^}}'value' is false{{$}}}} + // tracking-note-re@-2{{{{^}}Taking false branch{{$}}}} return true; - return coin(); // tracking-note{{Returning value}} + return coin(); // tracking-note-re{{{{^}}Returning value, which participates in a condition later{{$}}}} } void i(int *ptr) { - if (ptr) // expected-note{{Assuming 'ptr' is null}} - // expected-note@-1{{Taking false branch}} + if (ptr) // expected-note-re{{{{^}}Assuming 'ptr' is null{{$}}}} + // expected-note-re@-1{{{{^}}Taking false branch{{$}}}} ; if (!flipCoin()) - // tracking-note@-1{{Calling 'flipCoin'}} - // tracking-note@-2{{Returning from 'flipCoin'}} - // debug-note@-3{{Tracking condition '!flipCoin()'}} - // expected-note@-4{{Assuming the condition is true}} - // expected-note@-5{{Taking true branch}} + // tracking-note-re@-1{{{{^}}Calling 'flipCoin'{{$}}}} + // tracking-note-re@-2{{{{^}}Returning from 'flipCoin'{{$}}}} + // debug-note-re@-3{{{{^}}Tracking condition '!flipCoin()'{{$}}}} + // expected-note-re@-4{{{{^}}Assuming the condition is true{{$}}}} + // expected-note-re@-5{{{{^}}Taking true branch{{$}}}} *ptr = 5; // expected-warning{{Dereference of null pointer}} // expected-note@-1{{Dereference of null pointer}} } @@ -333,10 +333,10 @@ int getInt(); void f() { int flag = getInt(); - int *x = 0; // expected-note{{'x' initialized to a null pointer value}} - if (flag) // expected-note{{Assuming 'flag' is not equal to 0}} - // expected-note@-1{{Taking true branch}} - // debug-note@-2{{Tracking condition 'flag'}} + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} + if (flag) // expected-note-re{{{{^}}Assuming 'flag' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} *x = 5; // expected-warning{{Dereference of null pointer}} // expected-note@-1{{Dereference of null pointer}} } @@ -350,10 +350,10 @@ void f(int y) { y = 1; flag = y; - int *x = 0; // expected-note{{'x' initialized to a null pointer value}} - if (flag) // expected-note{{'flag' is 1}} - // expected-note@-1{{Taking true branch}} - // debug-note@-2{{Tracking condition 'flag'}} + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} + if (flag) // expected-note-re{{{{^}}'flag' is 1{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} *x = 5; // expected-warning{{Dereference of null pointer}} // expected-note@-1{{Dereference of null pointer}} } @@ -366,18 +366,18 @@ int getInt(); void foo() { int y; y = 1; - flag = y; // tracking-note{{The value 1 is assigned to 'flag'}} + flag = y; // tracking-note-re{{{{^}}The value 1 is assigned to 'flag', which participates in a condition later{{$}}}} } void f(int y) { - int *x = 0; // expected-note{{'x' initialized to a null pointer value}} + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} - foo(); // tracking-note{{Calling 'foo'}} - // tracking-note@-1{{Returning from 'foo'}} + foo(); // tracking-note-re{{{{^}}Calling 'foo'{{$}}}} + // tracking-note-re@-1{{{{^}}Returning from 'foo'{{$}}}} - if (flag) // expected-note{{'flag' is 1}} - // expected-note@-1{{Taking true branch}} - // debug-note@-2{{Tracking condition 'flag'}} + if (flag) // expected-note-re{{{{^}}'flag' is 1{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} *x = 5; // expected-warning{{Dereference of null pointer}} // expected-note@-1{{Dereference of null pointer}} } @@ -388,20 +388,20 @@ int flag = 0; int getInt(); void foo() { - flag = getInt(); // tracking-note{{Value assigned to 'flag'}} + flag = getInt(); // tracking-note-re{{{{^}}Value assigned to 'flag', which participates in a condition later{{$}}}} } void f() { - int *x = 0; // expected-note{{'x' initialized to a null pointer value}} + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} int y = 0; - foo(); // tracking-note{{Calling 'foo'}} - // tracking-note@-1{{Returning from 'foo'}} + foo(); // tracking-note-re{{{{^}}Calling 'foo'{{$}}}} + // tracking-note-re@-1{{{{^}}Returning from 'foo'{{$}}}} y = flag; - if (y) // expected-note{{Assuming 'y' is not equal to 0}} - // expected-note@-1{{Taking true branch}} - // debug-note@-2{{Tracking condition 'y'}} + if (y) // expected-note-re{{{{^}}Assuming 'y' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'y'{{$}}}} *x = 5; // expected-warning{{Dereference of null pointer}} // expected-note@-1{{Dereference of null pointer}} } @@ -412,20 +412,20 @@ namespace collapse_point_not_in_condition { [[noreturn]] void halt(); void assert(int b) { - if (!b) // tracking-note{{Assuming 'b' is not equal to 0}} - // tracking-note@-1{{Taking false branch}} + if (!b) // tracking-note-re{{{{^}}Assuming 'b' is not equal to 0{{$}}}} + // tracking-note-re@-1{{{{^}}Taking false branch{{$}}}} halt(); } void f(int flag) { - int *x = 0; // expected-note{{'x' initialized to a null pointer value}} + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} - assert(flag); // tracking-note{{Calling 'assert'}} - // tracking-note@-1{{Returning from 'assert'}} + assert(flag); // tracking-note-re{{{{^}}Calling 'assert'{{$}}}} + // tracking-note-re@-1{{{{^}}Returning from 'assert'{{$}}}} - if (flag) // expected-note{{'flag' is not equal to 0}} - // expected-note@-1{{Taking true branch}} - // debug-note@-2{{Tracking condition 'flag'}} + if (flag) // expected-note-re{{{{^}}'flag' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} *x = 5; // expected-warning{{Dereference of null pointer}} // expected-note@-1{{Dereference of null pointer}} } @@ -437,22 +437,22 @@ namespace unimportant_write_before_collapse_point { [[noreturn]] void halt(); void assert(int b) { - if (!b) // tracking-note{{Assuming 'b' is not equal to 0}} - // tracking-note@-1{{Taking false branch}} + if (!b) // tracking-note-re{{{{^}}Assuming 'b' is not equal to 0{{$}}}} + // tracking-note-re@-1{{{{^}}Taking false branch{{$}}}} halt(); } int getInt(); void f(int flag) { - int *x = 0; // expected-note{{'x' initialized to a null pointer value}} + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} flag = getInt(); - assert(flag); // tracking-note{{Calling 'assert'}} - // tracking-note@-1{{Returning from 'assert'}} + assert(flag); // tracking-note-re{{{{^}}Calling 'assert'{{$}}}} + // tracking-note-re@-1{{{{^}}Returning from 'assert'{{$}}}} - if (flag) // expected-note{{'flag' is not equal to 0}} - // expected-note@-1{{Taking true branch}} - // debug-note@-2{{Tracking condition 'flag'}} + if (flag) // expected-note-re{{{{^}}'flag' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} *x = 5; // expected-warning{{Dereference of null pointer}} // expected-note@-1{{Dereference of null pointer}} } @@ -488,17 +488,17 @@ void bar() { } void f(int flag) { - int *x = 0; // expected-note{{'x' initialized to a null pointer value}} + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} flag = getInt(); bar(); - assert(cond1); // expected-note{{Assuming 'cond1' is not equal to 0}} - // expected-note@-1{{'?' condition is true}} + assert(cond1); // expected-note-re{{{{^}}Assuming 'cond1' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}'?' condition is true{{$}}}} - if (flag) // expected-note{{'flag' is not equal to 0}} - // expected-note@-1{{Taking true branch}} - // debug-note@-2{{Tracking condition 'flag'}} + if (flag) // expected-note-re{{{{^}}Assuming 'flag' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} *x = 5; // expected-warning{{Dereference of null pointer}} // expected-note@-1{{Dereference of null pointer}} } @@ -525,20 +525,20 @@ void bar() { } void f(int flag) { - int *x = 0; // expected-note{{'x' initialized to a null pointer value}} + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} flag = getInt(); bar(); assert(cond1 && cond2); - // expected-note@-1{{Assuming 'cond1' is not equal to 0}} - // expected-note@-2{{Assuming 'cond2' is not equal to 0}} - // expected-note@-3{{'?' condition is true}} - // expected-note@-4{{Left side of '&&' is true}} - - if (flag) // expected-note{{'flag' is not equal to 0}} - // expected-note@-1{{Taking true branch}} - // debug-note@-2{{Tracking condition 'flag'}} + // expected-note-re@-1{{{{^}}Assuming 'cond1' is not equal to 0{{$}}}} + // expected-note-re@-2{{{{^}}Assuming 'cond2' is not equal to 0{{$}}}} + // expected-note-re@-3{{{{^}}'?' condition is true{{$}}}} + // expected-note-re@-4{{{{^}}Left side of '&&' is true{{$}}}} + + if (flag) // expected-note-re{{{{^}}Assuming 'flag' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} *x = 5; // expected-warning{{Dereference of null pointer}} // expected-note@-1{{Dereference of null pointer}} } @@ -565,18 +565,18 @@ void bar() { } void f(int flag) { - int *x = 0; // expected-note{{'x' initialized to a null pointer value}} + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} flag = getInt(); bar(); assert(cond1 || cond2); - // expected-note@-1{{Assuming 'cond1' is not equal to 0}} - // expected-note@-2{{Left side of '||' is true}} + // expected-note-re@-1{{{{^}}Assuming 'cond1' is not equal to 0{{$}}}} + // expected-note-re@-2{{{{^}}Left side of '||' is true{{$}}}} - if (flag) // expected-note{{'flag' is not equal to 0}} - // expected-note@-1{{Taking true branch}} - // debug-note@-2{{Tracking condition 'flag'}} + if (flag) // expected-note-re{{{{^}}Assuming 'flag' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} *x = 5; // expected-warning{{Dereference of null pointer}} // expected-note@-1{{Dereference of null pointer}} } @@ -604,18 +604,18 @@ void bar() { } void f(int flag) { - int *x = 0; // expected-note{{'x' initialized to a null pointer value}} + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} flag = getInt(); bar(); - assert(cond1); // expected-note{{Assuming 'cond1' is not equal to 0}} - // expected-note@-1{{Taking false branch}} - // expected-note@-2{{Loop condition is false. Exiting loop}} + assert(cond1); // expected-note-re{{{{^}}Assuming 'cond1' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking false branch{{$}}}} + // expected-note-re@-2{{{{^}}Loop condition is false. Exiting loop{{$}}}} - if (flag) // expected-note{{'flag' is not equal to 0}} - // expected-note@-1{{Taking true branch}} - // debug-note@-2{{Tracking condition 'flag'}} + if (flag) // expected-note-re{{{{^}}Assuming 'flag' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} *x = 5; // expected-warning{{Dereference of null pointer}} // expected-note@-1{{Dereference of null pointer}} } @@ -645,21 +645,21 @@ void bar() { } void f(int flag) { - int *x = 0; // expected-note{{'x' initialized to a null pointer value}} + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} flag = getInt(); bar(); assert(cond1 && cond2); - // expected-note@-1{{Assuming 'cond1' is not equal to 0}} - // expected-note@-2{{Left side of '&&' is true}} - // expected-note@-3{{Assuming the condition is false}} - // expected-note@-4{{Taking false branch}} - // expected-note@-5{{Loop condition is false. Exiting loop}} - - if (flag) // expected-note{{'flag' is not equal to 0}} - // expected-note@-1{{Taking true branch}} - // debug-note@-2{{Tracking condition 'flag'}} + // expected-note-re@-1{{{{^}}Assuming 'cond1' is not equal to 0{{$}}}} + // expected-note-re@-2{{{{^}}Left side of '&&' is true{{$}}}} + // expected-note-re@-3{{{{^}}Assuming the condition is false{{$}}}} + // expected-note-re@-4{{{{^}}Taking false branch{{$}}}} + // expected-note-re@-5{{{{^}}Loop condition is false. Exiting loop{{$}}}} + + if (flag) // expected-note-re{{{{^}}Assuming 'flag' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} *x = 5; // expected-warning{{Dereference of null pointer}} // expected-note@-1{{Dereference of null pointer}} } @@ -689,20 +689,20 @@ void bar() { } void f(int flag) { - int *x = 0; // expected-note{{'x' initialized to a null pointer value}} + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} flag = getInt(); bar(); assert(cond1 || cond2); - // expected-note@-1{{Assuming 'cond1' is not equal to 0}} - // expected-note@-2{{Left side of '||' is true}} - // expected-note@-3{{Taking false branch}} - // expected-note@-4{{Loop condition is false. Exiting loop}} - - if (flag) // expected-note{{'flag' is not equal to 0}} - // expected-note@-1{{Taking true branch}} - // debug-note@-2{{Tracking condition 'flag'}} + // expected-note-re@-1{{{{^}}Assuming 'cond1' is not equal to 0{{$}}}} + // expected-note-re@-2{{{{^}}Left side of '||' is true{{$}}}} + // expected-note-re@-3{{{{^}}Taking false branch{{$}}}} + // expected-note-re@-4{{{{^}}Loop condition is false. Exiting loop{{$}}}} + + if (flag) // expected-note-re{{{{^}}Assuming 'flag' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} *x = 5; // expected-warning{{Dereference of null pointer}} // expected-note@-1{{Dereference of null pointer}} } @@ -715,7 +715,7 @@ namespace only_track_the_evaluated_condition { bool coin(); void bar(int &flag) { - flag = coin(); // tracking-note{{Value assigned to 'flag'}} + flag = coin(); // tracking-note-re{{{{^}}Value assigned to 'flag', which participates in a condition later{{$}}}} } void bar2(int &flag2) { @@ -723,19 +723,19 @@ void bar2(int &flag2) { } void f(int *x) { - if (x) // expected-note{{Assuming 'x' is null}} - // debug-note@-1{{Tracking condition 'x'}} - // expected-note@-2{{Taking false branch}} + if (x) // expected-note-re{{{{^}}Assuming 'x' is null{{$}}}} + // debug-note-re@-1{{{{^}}Tracking condition 'x'{{$}}}} + // expected-note-re@-2{{{{^}}Taking false branch{{$}}}} return; int flag, flag2; - bar(flag); // tracking-note{{Calling 'bar'}} - // tracking-note@-1{{Returning from 'bar'}} + bar(flag); // tracking-note-re{{{{^}}Calling 'bar'{{$}}}} + // tracking-note-re@-1{{{{^}}Returning from 'bar'{{$}}}} bar2(flag2); - if (flag && flag2) // expected-note {{Assuming 'flag' is 0}} - // expected-note@-1{{Left side of '&&' is false}} - // debug-note@-2{{Tracking condition 'flag'}} + if (flag && flag2) // expected-note-re {{{{^}}Assuming 'flag' is 0{{$}}}} + // expected-note-re@-1{{{{^}}Left side of '&&' is false{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} return; *x = 5; // expected-warning{{Dereference of null pointer}} From cac5f304096d38da2b5fd87838d44efef0523ddd Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Wed, 21 Aug 2019 21:33:25 +0000 Subject: [PATCH 116/181] [analyzer][NFC] Add different interestingness kinds We defined (on the mailing list and here on phabricator) 2 different cases where retrieving information about a control dependency condition is very important: * When the condition's last write happened in a different stack frame * When the collapse point of the condition (when we can constrain it to be true/false) didn't happen in the actual condition. It seems like we solved this problem with the help of expression value tracking, and have started working on better diagnostics notes about this process. Expression value tracking is nothing more than registering a variety of visitors to construct reports about it. Each of the registered visitors (ReturnVisitor, FindLastStoreVisitor, NoStoreFuncVisitor, etc) have something to go by: a MemRegion, an SVal, an ExplodedNode, etc. For this reason, better explaining a last write is super simple, we can always just pass on some more information to the visitor in question (as seen in D65575). ConditionBRVisitor is a different beast, as it was built for a different purpose. It is responsible for constructing events at, well, conditions, and is registered only once, and isn't a part of the "expression value tracking family". Unfortunately, it is also the visitor to tinker with for constructing better diagnostics about the collapse point problem. This creates a need for alternative way to communicate with ConditionBRVisitor that a specific condition is being tracked for for the reason of being a control dependency. Since at almost all PathDiagnosticEventPiece construction the visitor checks interestingness, it makes sense to pair interestingness with a reason as to why we marked an entity as such. Differential Revision: https://reviews.llvm.org/D65723 llvm-svn: 369583 (cherry picked from commit fff01c8ec2b030d723f724254ce4780bba475cbb) --- .../Core/BugReporter/BugReporter.h | 34 +++++- clang/lib/StaticAnalyzer/Core/BugReporter.cpp | 113 ++++++++++++++---- .../Core/BugReporterVisitors.cpp | 6 +- 3 files changed, 124 insertions(+), 29 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h index 6b4085434458d..e3d28472e8894 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h @@ -102,14 +102,15 @@ class BugReport : public llvm::ilist_node { /// diagnostics to include when constructing the final path diagnostic. /// The stack is largely used by BugReporter when generating PathDiagnostics /// for multiple PathDiagnosticConsumers. - llvm::DenseSet InterestingSymbols; + llvm::DenseMap InterestingSymbols; /// A (stack of) set of regions that are registered with this report as being /// "interesting", and thus used to help decide which diagnostics /// to include when constructing the final path diagnostic. /// The stack is largely used by BugReporter when generating PathDiagnostics /// for multiple PathDiagnosticConsumers. - llvm::DenseSet InterestingRegions; + llvm::DenseMap + InterestingRegions; /// A set of location contexts that correspoind to call sites which should be /// considered "interesting". @@ -209,9 +210,24 @@ class BugReport : public llvm::ilist_node { /// Disable all path pruning when generating a PathDiagnostic. void disablePathPruning() { DoNotPrunePath = true; } - void markInteresting(SymbolRef sym); - void markInteresting(const MemRegion *R); - void markInteresting(SVal V); + /// Marks a symbol as interesting. Different kinds of interestingness will + /// be processed differently by visitors (e.g. if the tracking kind is + /// condition, will append "will be used as a condition" to the message). + void markInteresting(SymbolRef sym, bugreporter::TrackingKind TKind = + bugreporter::TrackingKind::Thorough); + + /// Marks a region as interesting. Different kinds of interestingness will + /// be processed differently by visitors (e.g. if the tracking kind is + /// condition, will append "will be used as a condition" to the message). + void markInteresting( + const MemRegion *R, + bugreporter::TrackingKind TKind = bugreporter::TrackingKind::Thorough); + + /// Marks a symbolic value as interesting. Different kinds of interestingness + /// will be processed differently by visitors (e.g. if the tracking kind is + /// condition, will append "will be used as a condition" to the message). + void markInteresting(SVal V, bugreporter::TrackingKind TKind = + bugreporter::TrackingKind::Thorough); void markInteresting(const LocationContext *LC); bool isInteresting(SymbolRef sym) const; @@ -219,6 +235,14 @@ class BugReport : public llvm::ilist_node { bool isInteresting(SVal V) const; bool isInteresting(const LocationContext *LC) const; + Optional + getInterestingnessKind(SymbolRef sym) const; + + Optional + getInterestingnessKind(const MemRegion *R) const; + + Optional getInterestingnessKind(SVal V) const; + /// Returns whether or not this report should be considered valid. /// /// Invalid reports are those that have been classified as likely false diff --git a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp index af2ee7427c8b4..2ca30726d26a9 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp @@ -2101,30 +2101,61 @@ void BugReport::Profile(llvm::FoldingSetNodeID& hash) const { } } -void BugReport::markInteresting(SymbolRef sym) { +template +static void insertToInterestingnessMap( + llvm::DenseMap &InterestingnessMap, T Val, + bugreporter::TrackingKind TKind) { + auto Result = InterestingnessMap.insert({Val, TKind}); + + if (Result.second) + return; + + // Even if this symbol/region was already marked as interesting as a + // condition, if we later mark it as interesting again but with + // thorough tracking, overwrite it. Entities marked with thorough + // interestiness are the most important (or most interesting, if you will), + // and we wouldn't like to downplay their importance. + + switch (TKind) { + case bugreporter::TrackingKind::Thorough: + Result.first->getSecond() = bugreporter::TrackingKind::Thorough; + return; + case bugreporter::TrackingKind::Condition: + return; + } + + llvm_unreachable( + "BugReport::markInteresting currently can only handle 2 different " + "tracking kinds! Please define what tracking kind should this entitiy" + "have, if it was already marked as interesting with a different kind!"); +} + +void BugReport::markInteresting(SymbolRef sym, + bugreporter::TrackingKind TKind) { if (!sym) return; - InterestingSymbols.insert(sym); + insertToInterestingnessMap(InterestingSymbols, sym, TKind); if (const auto *meta = dyn_cast(sym)) - InterestingRegions.insert(meta->getRegion()); + markInteresting(meta->getRegion(), TKind); } -void BugReport::markInteresting(const MemRegion *R) { +void BugReport::markInteresting(const MemRegion *R, + bugreporter::TrackingKind TKind) { if (!R) return; R = R->getBaseRegion(); - InterestingRegions.insert(R); + insertToInterestingnessMap(InterestingRegions, R, TKind); if (const auto *SR = dyn_cast(R)) - InterestingSymbols.insert(SR->getSymbol()); + markInteresting(SR->getSymbol(), TKind); } -void BugReport::markInteresting(SVal V) { - markInteresting(V.getAsRegion()); - markInteresting(V.getAsSymbol()); +void BugReport::markInteresting(SVal V, bugreporter::TrackingKind TKind) { + markInteresting(V.getAsRegion(), TKind); + markInteresting(V.getAsSymbol(), TKind); } void BugReport::markInteresting(const LocationContext *LC) { @@ -2133,28 +2164,68 @@ void BugReport::markInteresting(const LocationContext *LC) { InterestingLocationContexts.insert(LC); } -bool BugReport::isInteresting(SVal V) const { - return isInteresting(V.getAsRegion()) || isInteresting(V.getAsSymbol()); +Optional +BugReport::getInterestingnessKind(SVal V) const { + auto RKind = getInterestingnessKind(V.getAsRegion()); + auto SKind = getInterestingnessKind(V.getAsSymbol()); + if (!RKind) + return SKind; + if (!SKind) + return RKind; + + // If either is marked with throrough tracking, return that, we wouldn't like + // to downplay a note's importance by 'only' mentioning it as a condition. + switch(*RKind) { + case bugreporter::TrackingKind::Thorough: + return RKind; + case bugreporter::TrackingKind::Condition: + return SKind; + } + + llvm_unreachable( + "BugReport::getInterestingnessKind currently can only handle 2 different " + "tracking kinds! Please define what tracking kind should we return here " + "when the kind of getAsRegion() and getAsSymbol() is different!"); + return None; } -bool BugReport::isInteresting(SymbolRef sym) const { +Optional +BugReport::getInterestingnessKind(SymbolRef sym) const { if (!sym) - return false; + return None; // We don't currently consider metadata symbols to be interesting // even if we know their region is interesting. Is that correct behavior? - return InterestingSymbols.count(sym); + auto It = InterestingSymbols.find(sym); + if (It == InterestingSymbols.end()) + return None; + return It->getSecond(); } -bool BugReport::isInteresting(const MemRegion *R) const { +Optional +BugReport::getInterestingnessKind(const MemRegion *R) const { if (!R) - return false; + return None; + R = R->getBaseRegion(); - bool b = InterestingRegions.count(R); - if (b) - return true; + auto It = InterestingRegions.find(R); + if (It != InterestingRegions.end()) + return It->getSecond(); + if (const auto *SR = dyn_cast(R)) - return InterestingSymbols.count(SR->getSymbol()); - return false; + return getInterestingnessKind(SR->getSymbol()); + return None; +} + +bool BugReport::isInteresting(SVal V) const { + return getInterestingnessKind(V).hasValue(); +} + +bool BugReport::isInteresting(SymbolRef sym) const { + return getInterestingnessKind(sym).hasValue(); +} + +bool BugReport::isInteresting(const MemRegion *R) const { + return getInterestingnessKind(R).hasValue(); } bool BugReport::isInteresting(const LocationContext *LC) const { diff --git a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp index e12568705426f..e5fcbf60edcb8 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp @@ -1950,7 +1950,7 @@ bool bugreporter::trackExpressionValue(const ExplodedNode *InputNode, MacroNullReturnSuppressionVisitor::addMacroVisitorIfNecessary( LVNode, R, EnableNullFPSuppression, report, V); - report.markInteresting(V); + report.markInteresting(V, TKind); report.addVisitor(llvm::make_unique(R)); // If the contents are symbolic, find out when they became null. @@ -2011,9 +2011,9 @@ bool bugreporter::trackExpressionValue(const ExplodedNode *InputNode, const MemRegion *RegionRVal = RVal.getAsRegion(); if (RegionRVal && isa(RegionRVal)) { - report.markInteresting(RegionRVal); + report.markInteresting(RegionRVal, TKind); report.addVisitor(llvm::make_unique( - loc::MemRegionVal(RegionRVal), /*assumption=*/false)); + loc::MemRegionVal(RegionRVal), /*assumption=*/false)); } } return true; From 280b7ed88319ec32663b91c12dd6e03e82d0d7dd Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Wed, 21 Aug 2019 21:59:22 +0000 Subject: [PATCH 117/181] [analyzer] Don't make ConditionBRVisitor events prunable when the condition is an interesting field Exactly what it says on the tin! Note that we're talking about interestingness in general, hence this isn't a control-dependency-tracking specific patch. Differential Revision: https://reviews.llvm.org/D65724 llvm-svn: 369589 (cherry picked from commit 49ac7ece163f6e5985ba07dbd4ddabcc5e3449e5) --- .../Core/BugReporter/BugReporterVisitors.h | 6 +- .../Core/BugReporterVisitors.cpp | 80 ++++++------ .../track-control-dependency-conditions.cpp | 114 ++++++++++++++++++ 3 files changed, 164 insertions(+), 36 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h index 2daf5e88c2746..71d687a243dfb 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h @@ -213,8 +213,10 @@ class NilReceiverBRVisitor final : public BugReporterVisitor { /// Visitor that tries to report interesting diagnostics from conditions. class ConditionBRVisitor final : public BugReporterVisitor { // FIXME: constexpr initialization isn't supported by MSVC2013. - static const char *const GenericTrueMessage; - static const char *const GenericFalseMessage; + constexpr static llvm::StringLiteral GenericTrueMessage = + "Assuming the condition is true"; + constexpr static llvm::StringLiteral GenericFalseMessage = + "Assuming the condition is false"; public: void Profile(llvm::FoldingSetNodeID &ID) const override { diff --git a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp index e5fcbf60edcb8..5aab69aa66e54 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp @@ -180,21 +180,44 @@ static bool hasVisibleUpdate(const ExplodedNode *LeftNode, SVal LeftVal, RLCV->getStore() == RightNode->getState()->getStore(); } -static Optional -getConcreteIntegerValue(const Expr *CondVarExpr, const ExplodedNode *N) { +static Optional getSValForVar(const Expr *CondVarExpr, + const ExplodedNode *N) { ProgramStateRef State = N->getState(); const LocationContext *LCtx = N->getLocationContext(); + assert(CondVarExpr); + CondVarExpr = CondVarExpr->IgnoreImpCasts(); + // The declaration of the value may rely on a pointer so take its l-value. - if (const auto *DRE = dyn_cast_or_null(CondVarExpr)) { - if (const auto *VD = dyn_cast_or_null(DRE->getDecl())) { - SVal DeclSVal = State->getSVal(State->getLValue(VD, LCtx)); - if (auto DeclCI = DeclSVal.getAs()) - return &DeclCI->getValue(); - } - } + // FIXME: As seen in VisitCommonDeclRefExpr, sometimes DeclRefExpr may + // evaluate to a FieldRegion when it refers to a declaration of a lambda + // capture variable. We most likely need to duplicate that logic here. + if (const auto *DRE = dyn_cast(CondVarExpr)) + if (const auto *VD = dyn_cast(DRE->getDecl())) + return State->getSVal(State->getLValue(VD, LCtx)); + + if (const auto *ME = dyn_cast(CondVarExpr)) + if (const auto *FD = dyn_cast(ME->getMemberDecl())) + if (auto FieldL = State->getSVal(ME, LCtx).getAs()) + return State->getRawSVal(*FieldL, FD->getType()); - return {}; + return None; +} + +static Optional +getConcreteIntegerValue(const Expr *CondVarExpr, const ExplodedNode *N) { + + if (Optional V = getSValForVar(CondVarExpr, N)) + if (auto CI = V->getAs()) + return &CI->getValue(); + return None; +} + +static bool isInterestingExpr(const Expr *E, const ExplodedNode *N, + const BugReport *B) { + if (Optional V = getSValForVar(E, N)) + return B->getInterestingnessKind(*V).hasValue(); + return false; } /// \return name of the macro inside the location \p Loc. @@ -2475,17 +2498,11 @@ PathDiagnosticPieceRef ConditionBRVisitor::VisitConditionVariable( const LocationContext *LCtx = N->getLocationContext(); PathDiagnosticLocation Loc(CondVarExpr, BRC.getSourceManager(), LCtx); + auto event = std::make_shared(Loc, Out.str()); - if (const auto *DR = dyn_cast(CondVarExpr)) { - if (const auto *VD = dyn_cast(DR->getDecl())) { - const ProgramState *state = N->getState().get(); - if (const MemRegion *R = state->getLValue(VD, LCtx).getAsRegion()) { - if (report.isInteresting(R)) - event->setPrunable(false); - } - } - } + if (isInterestingExpr(CondVarExpr, N, &report)) + event->setPrunable(false); return event; } @@ -2515,16 +2532,10 @@ PathDiagnosticPieceRef ConditionBRVisitor::VisitTrueTest( PathDiagnosticLocation Loc(Cond, BRC.getSourceManager(), LCtx); auto event = std::make_shared(Loc, Out.str()); - const ProgramState *state = N->getState().get(); - if (const MemRegion *R = state->getLValue(VD, LCtx).getAsRegion()) { - if (report.isInteresting(R)) - event->setPrunable(false); - else { - SVal V = state->getSVal(R); - if (report.isInteresting(V)) - event->setPrunable(false); - } - } + + if (isInterestingExpr(DRE, N, &report)) + event->setPrunable(false); + return std::move(event); } @@ -2555,7 +2566,10 @@ PathDiagnosticPieceRef ConditionBRVisitor::VisitTrueTest( if (!IsAssuming) return std::make_shared(Loc, Out.str()); - return std::make_shared(Loc, Out.str()); + auto event = std::make_shared(Loc, Out.str()); + if (isInterestingExpr(ME, N, &report)) + event->setPrunable(false); + return event; } bool ConditionBRVisitor::printValue(const Expr *CondVarExpr, raw_ostream &Out, @@ -2595,10 +2609,8 @@ bool ConditionBRVisitor::printValue(const Expr *CondVarExpr, raw_ostream &Out, return true; } -const char *const ConditionBRVisitor::GenericTrueMessage = - "Assuming the condition is true"; -const char *const ConditionBRVisitor::GenericFalseMessage = - "Assuming the condition is false"; +constexpr llvm::StringLiteral ConditionBRVisitor::GenericTrueMessage; +constexpr llvm::StringLiteral ConditionBRVisitor::GenericFalseMessage; bool ConditionBRVisitor::isPieceMessageGeneric( const PathDiagnosticPiece *Piece) { diff --git a/clang/test/Analysis/track-control-dependency-conditions.cpp b/clang/test/Analysis/track-control-dependency-conditions.cpp index 5f089b213da93..02c53418a2095 100644 --- a/clang/test/Analysis/track-control-dependency-conditions.cpp +++ b/clang/test/Analysis/track-control-dependency-conditions.cpp @@ -407,6 +407,91 @@ void f() { } } // end of namespace condition_written_in_nested_stackframe_before_assignment +namespace condition_lambda_capture_by_reference_last_write { +int getInt(); + +[[noreturn]] void halt(); + +void f(int flag) { + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} + + auto lambda = [&flag]() { + flag = getInt(); // tracking-note-re{{{{^}}Value assigned to 'flag', which participates in a condition later{{$}}}} + }; + + lambda(); // tracking-note-re{{{{^}}Calling 'operator()'{{$}}}} + // tracking-note-re@-1{{{{^}}Returning from 'operator()'{{$}}}} + + if (flag) // expected-note-re{{{{^}}Assuming 'flag' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} +} // end of namespace condition_lambda_capture_by_reference_last_write + +namespace condition_lambda_capture_by_value_assumption { +int getInt(); + +[[noreturn]] void halt(); + +void bar(int &flag) { + flag = getInt(); // tracking-note-re{{{{^}}Value assigned to 'flag', which participates in a condition later{{$}}}} +} + +void f(int flag) { + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} + + auto lambda = [flag]() { + if (!flag) // tracking-note-re{{{{^}}Assuming 'flag' is not equal to 0{{$}}}} + // tracking-note-re@-1{{{{^}}Taking false branch{{$}}}} + halt(); + }; + + bar(flag); // tracking-note-re{{{{^}}Calling 'bar'{{$}}}} + // tracking-note-re@-1{{{{^}}Returning from 'bar'{{$}}}} + lambda(); // tracking-note-re{{{{^}}Calling 'operator()'{{$}}}} + // tracking-note-re@-1{{{{^}}Returning from 'operator()'{{$}}}} + + if (flag) // expected-note-re{{{{^}}Assuming 'flag' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} +} // end of namespace condition_lambda_capture_by_value_assumption + +namespace condition_lambda_capture_by_reference_assumption { +int getInt(); + +[[noreturn]] void halt(); + +void bar(int &flag) { + flag = getInt(); // tracking-note-re{{{{^}}Value assigned to 'flag', which participates in a condition later{{$}}}} +} + +void f(int flag) { + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} + + auto lambda = [&flag]() { + if (!flag) // tracking-note-re{{{{^}}Assuming 'flag' is not equal to 0{{$}}}} + // tracking-note-re@-1{{{{^}}Taking false branch{{$}}}} + halt(); + }; + + bar(flag); // tracking-note-re{{{{^}}Calling 'bar'{{$}}}} + // tracking-note-re@-1{{{{^}}Returning from 'bar'{{$}}}} + lambda(); // tracking-note-re{{{{^}}Calling 'operator()'{{$}}}} + // tracking-note-re@-1{{{{^}}Returning from 'operator()'{{$}}}} + + if (flag) // expected-note-re{{{{^}}'flag' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} +} // end of namespace condition_lambda_capture_by_reference_assumption + namespace collapse_point_not_in_condition { [[noreturn]] void halt(); @@ -471,6 +556,35 @@ void f6(int x) { } // end of namespace dont_crash_on_nonlogical_binary_operator +namespace collapse_point_not_in_condition_as_field { + +[[noreturn]] void halt(); +struct IntWrapper { + int b; + IntWrapper(); + + void check() { + if (!b) // tracking-note-re{{{{^}}Assuming field 'b' is not equal to 0{{$}}}} + // tracking-note-re@-1{{{{^}}Taking false branch{{$}}}} + halt(); + return; + } +}; + +void f(IntWrapper i) { + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} + + i.check(); // tracking-note-re{{{{^}}Calling 'IntWrapper::check'{{$}}}} + // tracking-note-re@-1{{{{^}}Returning from 'IntWrapper::check'{{$}}}} + if (i.b) // expected-note-re{{{{^}}Field 'b' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'i.b'{{$}}}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} + +} // end of namespace collapse_point_not_in_condition_as_field + namespace dont_track_assertlike_conditions { extern void __assert_fail(__const char *__assertion, __const char *__file, From 29ce5ea47950549780ea732d20125347b339b4ef Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Wed, 21 Aug 2019 22:38:00 +0000 Subject: [PATCH 118/181] [analyzer] Mention whether an event is about a condition in a bug report part 2 In D65724, I do a pretty thorough explanation about how I'm solving this problem, I think that summary nails whats happening here ;) Differential Revision: https://reviews.llvm.org/D65725 llvm-svn: 369596 (cherry picked from commit d9a81ccf05925e25df17cf64f7636ae78cd99d3d) --- .../Core/BugReporterVisitors.cpp | 30 +++++ .../track-control-dependency-conditions.cpp | 120 +++++++++++++++++- 2 files changed, 144 insertions(+), 6 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp index 5aab69aa66e54..d03da2b801145 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp @@ -213,6 +213,22 @@ getConcreteIntegerValue(const Expr *CondVarExpr, const ExplodedNode *N) { return None; } +static bool isVarAnInterestingCondition(const Expr *CondVarExpr, + const ExplodedNode *N, + const BugReport *B) { + // Even if this condition is marked as interesting, it isn't *that* + // interesting if it didn't happen in a nested stackframe, the user could just + // follow the arrows. + if (!B->getErrorNode()->getStackFrame()->isParentOf(N->getStackFrame())) + return false; + + if (Optional V = getSValForVar(CondVarExpr, N)) + if (Optional K = B->getInterestingnessKind(*V)) + return *K == bugreporter::TrackingKind::Condition; + + return false; +} + static bool isInterestingExpr(const Expr *E, const ExplodedNode *N, const BugReport *B) { if (Optional V = getSValForVar(E, N)) @@ -2454,6 +2470,10 @@ PathDiagnosticPieceRef ConditionBRVisitor::VisitTrueTest( const LocationContext *LCtx = N->getLocationContext(); const SourceManager &SM = BRC.getSourceManager(); + if (isVarAnInterestingCondition(BExpr->getLHS(), N, &R) || + isVarAnInterestingCondition(BExpr->getRHS(), N, &R)) + Out << WillBeUsedForACondition; + // Convert 'field ...' to 'Field ...' if it is a MemberExpr. std::string Message = Out.str(); Message[0] = toupper(Message[0]); @@ -2499,6 +2519,9 @@ PathDiagnosticPieceRef ConditionBRVisitor::VisitConditionVariable( const LocationContext *LCtx = N->getLocationContext(); PathDiagnosticLocation Loc(CondVarExpr, BRC.getSourceManager(), LCtx); + if (isVarAnInterestingCondition(CondVarExpr, N, &report)) + Out << WillBeUsedForACondition; + auto event = std::make_shared(Loc, Out.str()); if (isInterestingExpr(CondVarExpr, N, &report)) @@ -2524,6 +2547,9 @@ PathDiagnosticPieceRef ConditionBRVisitor::VisitTrueTest( const LocationContext *LCtx = N->getLocationContext(); + if (isVarAnInterestingCondition(DRE, N, &report)) + Out << WillBeUsedForACondition; + // If we know the value create a pop-up note to the 'DRE'. if (!IsAssuming) { PathDiagnosticLocation Loc(DRE, BRC.getSourceManager(), LCtx); @@ -2563,6 +2589,10 @@ PathDiagnosticPieceRef ConditionBRVisitor::VisitTrueTest( if (!Loc.isValid() || !Loc.asLocation().isValid()) return nullptr; + if (isVarAnInterestingCondition(ME, N, &report)) + Out << WillBeUsedForACondition; + + // If we know the value create a pop-up note. if (!IsAssuming) return std::make_shared(Loc, Out.str()); diff --git a/clang/test/Analysis/track-control-dependency-conditions.cpp b/clang/test/Analysis/track-control-dependency-conditions.cpp index 02c53418a2095..e95cc7cbb1ec5 100644 --- a/clang/test/Analysis/track-control-dependency-conditions.cpp +++ b/clang/test/Analysis/track-control-dependency-conditions.cpp @@ -443,7 +443,7 @@ void f(int flag) { int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} auto lambda = [flag]() { - if (!flag) // tracking-note-re{{{{^}}Assuming 'flag' is not equal to 0{{$}}}} + if (!flag) // tracking-note-re{{{{^}}Assuming 'flag' is not equal to 0, which participates in a condition later{{$}}}} // tracking-note-re@-1{{{{^}}Taking false branch{{$}}}} halt(); }; @@ -474,7 +474,7 @@ void f(int flag) { int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} auto lambda = [&flag]() { - if (!flag) // tracking-note-re{{{{^}}Assuming 'flag' is not equal to 0{{$}}}} + if (!flag) // tracking-note-re{{{{^}}Assuming 'flag' is not equal to 0, which participates in a condition later{{$}}}} // tracking-note-re@-1{{{{^}}Taking false branch{{$}}}} halt(); }; @@ -486,18 +486,42 @@ void f(int flag) { if (flag) // expected-note-re{{{{^}}'flag' is not equal to 0{{$}}}} // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} - // debug-note-re@-2{{{{^}}Tracking condition 'flag'}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} *x = 5; // expected-warning{{Dereference of null pointer}} // expected-note@-1{{Dereference of null pointer}} } } // end of namespace condition_lambda_capture_by_reference_assumption +namespace collapse_point_not_in_condition_bool { + +[[noreturn]] void halt(); + +void check(bool b) { + if (!b) // tracking-note-re{{{{^}}Assuming 'b' is true, which participates in a condition later{{$}}}} + // tracking-note-re@-1{{{{^}}Taking false branch{{$}}}} + halt(); +} + +void f(bool flag) { + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} + + check(flag); // tracking-note-re{{{{^}}Calling 'check'{{$}}}} + // tracking-note-re@-1{{{{^}}Returning from 'check'{{$}}}} + + if (flag) // expected-note-re{{{{^}}'flag' is true{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} +} // end of namespace collapse_point_not_in_condition_bool + namespace collapse_point_not_in_condition { [[noreturn]] void halt(); void assert(int b) { - if (!b) // tracking-note-re{{{{^}}Assuming 'b' is not equal to 0{{$}}}} + if (!b) // tracking-note-re{{{{^}}Assuming 'b' is not equal to 0, which participates in a condition later{{$}}}} // tracking-note-re@-1{{{{^}}Taking false branch{{$}}}} halt(); } @@ -522,7 +546,7 @@ namespace unimportant_write_before_collapse_point { [[noreturn]] void halt(); void assert(int b) { - if (!b) // tracking-note-re{{{{^}}Assuming 'b' is not equal to 0{{$}}}} + if (!b) // tracking-note-re{{{{^}}Assuming 'b' is not equal to 0, which participates in a condition later{{$}}}} // tracking-note-re@-1{{{{^}}Taking false branch{{$}}}} halt(); } @@ -556,6 +580,31 @@ void f6(int x) { } // end of namespace dont_crash_on_nonlogical_binary_operator +namespace collapse_point_not_in_condition_binary_op { + +[[noreturn]] void halt(); + +void check(int b) { + if (b == 1) // tracking-note-re{{{{^}}Assuming 'b' is not equal to 1, which participates in a condition later{{$}}}} + // tracking-note-re@-1{{{{^}}Taking false branch{{$}}}} + halt(); +} + +void f(int flag) { + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} + + check(flag); // tracking-note-re{{{{^}}Calling 'check'{{$}}}} + // tracking-note-re@-1{{{{^}}Returning from 'check'{{$}}}} + + if (flag) // expected-note-re{{{{^}}Assuming 'flag' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} + +} // end of namespace collapse_point_not_in_condition_binary_op + namespace collapse_point_not_in_condition_as_field { [[noreturn]] void halt(); @@ -564,7 +613,7 @@ struct IntWrapper { IntWrapper(); void check() { - if (!b) // tracking-note-re{{{{^}}Assuming field 'b' is not equal to 0{{$}}}} + if (!b) // tracking-note-re{{{{^}}Assuming field 'b' is not equal to 0, which participates in a condition later{{$}}}} // tracking-note-re@-1{{{{^}}Taking false branch{{$}}}} halt(); return; @@ -585,6 +634,65 @@ void f(IntWrapper i) { } // end of namespace collapse_point_not_in_condition_as_field +namespace assignemnt_in_condition_in_nested_stackframe { +int flag; + +bool coin(); + +[[noreturn]] void halt(); + +void foo() { + if ((flag = coin())) + // tracking-note-re@-1{{{{^}}Value assigned to 'flag', which participates in a condition later{{$}}}} + // tracking-note-re@-2{{{{^}}Assuming 'flag' is not equal to 0, which participates in a condition later{{$}}}} + // tracking-note-re@-3{{{{^}}Taking true branch{{$}}}} + return; + halt(); + return; +} + +void f() { + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} + + foo(); // tracking-note-re{{{{^}}Calling 'foo'{{$}}}} + // tracking-note-re@-1{{{{^}}Returning from 'foo'{{$}}}} + if (flag) // expected-note-re{{{{^}}'flag' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} +} // end of namespace assignemnt_in_condition_in_nested_stackframe + +namespace condition_variable_less { +int flag; + +bool coin(); + +[[noreturn]] void halt(); + +void foo() { + if (flag > 0) + // tracking-note-re@-1{{{{^}}Assuming 'flag' is > 0, which participates in a condition later{{$}}}} + // tracking-note-re@-2{{{{^}}Taking true branch{{$}}}} + return; + halt(); + return; +} + +void f() { + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} + + foo(); // tracking-note-re{{{{^}}Calling 'foo'{{$}}}} + // tracking-note-re@-1{{{{^}}Returning from 'foo'{{$}}}} + if (flag) // expected-note-re{{{{^}}'flag' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} +} // end of namespace condition_variable_less + namespace dont_track_assertlike_conditions { extern void __assert_fail(__const char *__assertion, __const char *__file, From 2e39e0d8ecac82a3906d7976f61da51fdc025de5 Mon Sep 17 00:00:00 2001 From: Csaba Dabis Date: Thu, 22 Aug 2019 00:06:58 +0000 Subject: [PATCH 119/181] [analyzer] TrackConstraintBRVisitor: Do not track unknown values Summary: - Reviewers: NoQ, Szelethus Reviewed By: NoQ, Szelethus Differential Revision: https://reviews.llvm.org/D66267 llvm-svn: 369604 (cherry picked from commit b73a5711f63466d829f815ed0f1b14f03cef0b05) --- .../lib/StaticAnalyzer/Core/BugReporterVisitors.cpp | 7 ++++--- clang/test/Analysis/cast-value.cpp | 12 ++++-------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp index d03da2b801145..0634ed44165a9 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp @@ -1992,9 +1992,10 @@ bool bugreporter::trackExpressionValue(const ExplodedNode *InputNode, report.markInteresting(V, TKind); report.addVisitor(llvm::make_unique(R)); - // If the contents are symbolic, find out when they became null. - if (V.getAsLocSymbol(/*IncludeBaseRegions*/ true)) - report.addVisitor(llvm::make_unique( + // If the contents are symbolic and null, find out when they became null. + if (V.getAsLocSymbol(/*IncludeBaseRegions=*/true)) + if (LVState->isNull(V).isConstrainedTrue()) + report.addVisitor(llvm::make_unique( V.castAs(), false)); // Add visitor, which will suppress inline defensive checks. diff --git a/clang/test/Analysis/cast-value.cpp b/clang/test/Analysis/cast-value.cpp index a67ffe2c0833c..fcf145365ea45 100644 --- a/clang/test/Analysis/cast-value.cpp +++ b/clang/test/Analysis/cast-value.cpp @@ -152,8 +152,7 @@ void evalReferences(const Shape &S) { void evalNonNullParamNonNullReturnReference(const Shape &S) { const auto *C = dyn_cast_or_null(S); // expected-note@-1 {{Assuming dynamic cast from 'Shape' to 'Circle' succeeds}} - // expected-note@-2 {{Assuming pointer value is null}} - // expected-note@-3 {{'C' initialized here}} + // expected-note@-2 {{'C' initialized here}} (void)(1 / !(bool)C); // expected-note@-1 {{'C' is non-null}} @@ -165,8 +164,7 @@ void evalNonNullParamNonNullReturnReference(const Shape &S) { void evalNonNullParamNonNullReturn(const Shape *S) { const auto *C = cast(S); // expected-note@-1 {{Checked cast from 'Shape' to 'Circle' succeeds}} - // expected-note@-2 {{Assuming pointer value is null}} - // expected-note@-3 {{'C' initialized here}} + // expected-note@-2 {{'C' initialized here}} (void)(1 / !(bool)C); // expected-note@-1 {{'C' is non-null}} @@ -178,7 +176,6 @@ void evalNonNullParamNonNullReturn(const Shape *S) { void evalNonNullParamNullReturn(const Shape *S) { const auto *C = dyn_cast_or_null(S); // expected-note@-1 {{Assuming dynamic cast from 'Shape' to 'Circle' fails}} - // expected-note@-2 {{Assuming pointer value is null}} if (const auto *T = dyn_cast_or_null(S)) { // expected-note@-1 {{Assuming dynamic cast from 'Shape' to 'Triangle' succeeds}} @@ -207,9 +204,8 @@ void evalNullParamNullReturn(const Shape *S) { void evalZeroParamNonNullReturnPointer(const Shape *S) { const auto *C = S->castAs(); - // expected-note@-1 {{Assuming pointer value is null}} - // expected-note@-2 {{Checked cast to 'Circle' succeeds}} - // expected-note@-3 {{'C' initialized here}} + // expected-note@-1 {{Checked cast to 'Circle' succeeds}} + // expected-note@-2 {{'C' initialized here}} (void)(1 / !(bool)C); // expected-note@-1 {{'C' is non-null}} From 372a83518b98e4deeb7ab9daf859599dba8d449c Mon Sep 17 00:00:00 2001 From: Csaba Dabis Date: Thu, 22 Aug 2019 00:20:36 +0000 Subject: [PATCH 120/181] [analyzer] CastValueChecker: Store the dynamic types and casts Summary: This patch introduces `DynamicCastInfo` similar to `DynamicTypeInfo` which is stored in `CastSets` which are storing the dynamic cast informations of objects based on memory regions. It could be used to store and check the casts and prevent infeasible paths. Reviewed By: NoQ Differential Revision: https://reviews.llvm.org/D66325 llvm-svn: 369605 (cherry picked from commit 0202c3596c52d453d1e9e5a43d7533b83444df4e) --- clang/include/clang/AST/Type.h | 3 + .../Core/PathSensitive/CheckerContext.h | 15 +- .../Core/PathSensitive/DynamicCastInfo.h | 55 ++++ .../Core/PathSensitive/DynamicType.h | 73 +++++ .../Core/PathSensitive/DynamicTypeInfo.h | 46 +-- .../Core/PathSensitive/DynamicTypeMap.h | 63 ---- .../Checkers/CastValueChecker.cpp | 276 +++++++++++------- .../Checkers/DynamicTypePropagation.cpp | 15 +- clang/lib/StaticAnalyzer/Core/CMakeLists.txt | 2 +- clang/lib/StaticAnalyzer/Core/DynamicType.cpp | 223 ++++++++++++++ .../StaticAnalyzer/Core/DynamicTypeMap.cpp | 97 ------ .../lib/StaticAnalyzer/Core/ProgramState.cpp | 2 +- clang/test/Analysis/Inputs/llvm.h | 19 ++ clang/test/Analysis/cast-value-logic.cpp | 128 ++++++++ clang/test/Analysis/cast-value-notes.cpp | 144 +++++++++ clang/test/Analysis/cast-value-state-dump.cpp | 47 +++ clang/test/Analysis/cast-value.cpp | 239 --------------- clang/test/Analysis/dump_egraph.cpp | 3 +- clang/test/Analysis/expr-inspection.c | 1 + 19 files changed, 914 insertions(+), 537 deletions(-) create mode 100644 clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicCastInfo.h create mode 100644 clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h delete mode 100644 clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeMap.h create mode 100644 clang/lib/StaticAnalyzer/Core/DynamicType.cpp delete mode 100644 clang/lib/StaticAnalyzer/Core/DynamicTypeMap.cpp create mode 100644 clang/test/Analysis/Inputs/llvm.h create mode 100644 clang/test/Analysis/cast-value-logic.cpp create mode 100644 clang/test/Analysis/cast-value-notes.cpp create mode 100644 clang/test/Analysis/cast-value-state-dump.cpp delete mode 100644 clang/test/Analysis/cast-value.cpp diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h index e80dd1a4ec40d..dc1db0500f85b 100644 --- a/clang/include/clang/AST/Type.h +++ b/clang/include/clang/AST/Type.h @@ -972,6 +972,9 @@ class QualType { friend bool operator!=(const QualType &LHS, const QualType &RHS) { return LHS.Value != RHS.Value; } + friend bool operator<(const QualType &LHS, const QualType &RHS) { + return LHS.Value < RHS.Value; + } static std::string getAsString(SplitQualType split, const PrintingPolicy &Policy) { diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h index 1d2e554ec2735..7f4df0d88def6 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h @@ -234,7 +234,7 @@ class CheckerContext { } /// A shorthand version of getNoteTag that doesn't require you to accept - /// the BugReporterContext arguments when you don't need it. + /// the 'BugReporterContext' argument when you don't need it. /// /// @param Cb Callback only with 'BugReport &' parameter. /// @param IsPrunable Whether the note is prunable. It allows BugReporter @@ -247,6 +247,19 @@ class CheckerContext { IsPrunable); } + /// A shorthand version of getNoteTag that doesn't require you to accept + /// the arguments when you don't need it. + /// + /// @param Cb Callback without parameters. + /// @param IsPrunable Whether the note is prunable. It allows BugReporter + /// to omit the note from the report if it would make the displayed + /// bug path significantly shorter. + const NoteTag *getNoteTag(std::function &&Cb, + bool IsPrunable = false) { + return getNoteTag([Cb](BugReporterContext &, BugReport &) { return Cb(); }, + IsPrunable); + } + /// A shorthand version of getNoteTag that accepts a plain note. /// /// @param Note The note. diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicCastInfo.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicCastInfo.h new file mode 100644 index 0000000000000..f5a710c77a6ad --- /dev/null +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicCastInfo.h @@ -0,0 +1,55 @@ +//===- DynamicCastInfo.h - Runtime cast information -------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_DYNAMICCASTINFO_H +#define LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_DYNAMICCASTINFO_H + +#include "clang/AST/Type.h" + +namespace clang { +namespace ento { + +class DynamicCastInfo { +public: + enum CastResult { Success, Failure }; + + DynamicCastInfo(QualType from, QualType to, CastResult resultKind) + : From(from), To(to), ResultKind(resultKind) {} + + QualType from() const { return From; } + QualType to() const { return To; } + + bool equals(QualType from, QualType to) const { + return From == from && To == to; + } + + bool succeeds() const { return ResultKind == CastResult::Success; } + bool fails() const { return ResultKind == CastResult::Failure; } + + bool operator==(const DynamicCastInfo &RHS) const { + return From == RHS.From && To == RHS.To; + } + bool operator<(const DynamicCastInfo &RHS) const { + return From < RHS.From && To < RHS.To; + } + + void Profile(llvm::FoldingSetNodeID &ID) const { + ID.Add(From); + ID.Add(To); + ID.AddInteger(ResultKind); + } + +private: + QualType From, To; + CastResult ResultKind; +}; + +} // namespace ento +} // namespace clang + +#endif // LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_DYNAMICCASTINFO_H diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h new file mode 100644 index 0000000000000..f300af2c0b871 --- /dev/null +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h @@ -0,0 +1,73 @@ +//===- DynamicType.h - Dynamic type related APIs ----------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines APIs that track and query dynamic type information. This +// information can be used to devirtualize calls during the symbolic execution +// or do type checking. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_DYNAMICTYPE_H +#define LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_DYNAMICTYPE_H + +#include "clang/AST/Type.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicCastInfo.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeInfo.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState_Fwd.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" +#include "llvm/ADT/ImmutableMap.h" +#include "llvm/ADT/Optional.h" +#include + +namespace clang { +namespace ento { + +/// Get dynamic type information for the region \p MR. +DynamicTypeInfo getDynamicTypeInfo(ProgramStateRef State, const MemRegion *MR); + +/// Get raw dynamic type information for the region \p MR. +const DynamicTypeInfo *getRawDynamicTypeInfo(ProgramStateRef State, + const MemRegion *MR); + +/// Get dynamic cast information from \p CastFromTy to \p CastToTy of \p MR. +const DynamicCastInfo *getDynamicCastInfo(ProgramStateRef State, + const MemRegion *MR, + QualType CastFromTy, + QualType CastToTy); + +/// Set dynamic type information of the region; return the new state. +ProgramStateRef setDynamicTypeInfo(ProgramStateRef State, const MemRegion *MR, + DynamicTypeInfo NewTy); + +/// Set dynamic type information of the region; return the new state. +ProgramStateRef setDynamicTypeInfo(ProgramStateRef State, const MemRegion *MR, + QualType NewTy, bool CanBeSubClassed = true); + +/// Set dynamic type and cast information of the region; return the new state. +ProgramStateRef setDynamicTypeAndCastInfo(ProgramStateRef State, + const MemRegion *MR, + QualType CastFromTy, + QualType CastToTy, QualType ResultTy, + bool IsCastSucceeds); + +/// Removes the dead type informations from \p State. +ProgramStateRef removeDeadTypes(ProgramStateRef State, SymbolReaper &SR); + +/// Removes the dead cast informations from \p State. +ProgramStateRef removeDeadCasts(ProgramStateRef State, SymbolReaper &SR); + +void printDynamicTypeInfoJson(raw_ostream &Out, ProgramStateRef State, + const char *NL = "\n", unsigned int Space = 0, + bool IsDot = false); + +} // namespace ento +} // namespace clang + +#endif // LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_DYNAMICTYPE_H diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeInfo.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeInfo.h index 9bb1e21375666..6262c4a1ce378 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeInfo.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeInfo.h @@ -1,10 +1,11 @@ -//== DynamicTypeInfo.h - Runtime type information ----------------*- C++ -*--=// +//===- DynamicTypeInfo.h - Runtime type information -------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// + #ifndef LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_DYNAMICTYPEINFO_H #define LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_DYNAMICTYPEINFO_H @@ -16,36 +17,37 @@ namespace ento { /// Stores the currently inferred strictest bound on the runtime type /// of a region in a given state along the analysis path. class DynamicTypeInfo { -private: - QualType T; - bool CanBeASubClass; - public: + DynamicTypeInfo() : DynTy(QualType()) {} - DynamicTypeInfo() : T(QualType()) {} - DynamicTypeInfo(QualType WithType, bool CanBeSub = true) - : T(WithType), CanBeASubClass(CanBeSub) {} + DynamicTypeInfo(QualType Ty, bool CanBeSub = true) + : DynTy(Ty), CanBeASubClass(CanBeSub) {} + + /// Returns false if the type information is precise (the type 'DynTy' is + /// the only type in the lattice), true otherwise. + bool canBeASubClass() const { return CanBeASubClass; } - /// Return false if no dynamic type info is available. - bool isValid() const { return !T.isNull(); } + /// Returns true if the dynamic type info is available. + bool isValid() const { return !DynTy.isNull(); } /// Returns the currently inferred upper bound on the runtime type. - QualType getType() const { return T; } + QualType getType() const { return DynTy; } - /// Returns false if the type information is precise (the type T is - /// the only type in the lattice), true otherwise. - bool canBeASubClass() const { return CanBeASubClass; } + bool operator==(const DynamicTypeInfo &RHS) const { + return DynTy == RHS.DynTy && CanBeASubClass == RHS.CanBeASubClass; + } void Profile(llvm::FoldingSetNodeID &ID) const { - ID.Add(T); - ID.AddInteger((unsigned)CanBeASubClass); - } - bool operator==(const DynamicTypeInfo &X) const { - return T == X.T && CanBeASubClass == X.CanBeASubClass; + ID.Add(DynTy); + ID.AddBoolean(CanBeASubClass); } + +private: + QualType DynTy; + bool CanBeASubClass; }; -} // end ento -} // end clang +} // namespace ento +} // namespace clang -#endif +#endif // LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_DYNAMICTYPEINFO_H diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeMap.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeMap.h deleted file mode 100644 index a84b248720618..0000000000000 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeMap.h +++ /dev/null @@ -1,63 +0,0 @@ -//===- DynamicTypeMap.h - Dynamic type map ----------------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// This file provides APIs for tracking dynamic type information. -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_DYNAMICTYPEMAP_H -#define LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_DYNAMICTYPEMAP_H - -#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeInfo.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState_Fwd.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" -#include "llvm/ADT/ImmutableMap.h" -#include "clang/AST/Type.h" - -namespace clang { -namespace ento { - -class MemRegion; - -/// The GDM component containing the dynamic type info. This is a map from a -/// symbol to its most likely type. -struct DynamicTypeMap {}; - -using DynamicTypeMapTy = llvm::ImmutableMap; - -template <> -struct ProgramStateTrait - : public ProgramStatePartialTrait { - static void *GDMIndex(); -}; - -/// Get dynamic type information for a region. -DynamicTypeInfo getDynamicTypeInfo(ProgramStateRef State, - const MemRegion *Reg); - -/// Set dynamic type information of the region; return the new state. -ProgramStateRef setDynamicTypeInfo(ProgramStateRef State, const MemRegion *Reg, - DynamicTypeInfo NewTy); - -/// Set dynamic type information of the region; return the new state. -inline ProgramStateRef setDynamicTypeInfo(ProgramStateRef State, - const MemRegion *Reg, QualType NewTy, - bool CanBeSubClassed = true) { - return setDynamicTypeInfo(State, Reg, - DynamicTypeInfo(NewTy, CanBeSubClassed)); -} - -void printDynamicTypeInfoJson(raw_ostream &Out, ProgramStateRef State, - const char *NL = "\n", unsigned int Space = 0, - bool IsDot = false); - -} // namespace ento -} // namespace clang - -#endif // LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_DYNAMICTYPEMAP_H diff --git a/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp index 9cf1b0a020a26..e7472ab156b76 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp @@ -6,7 +6,13 @@ // //===----------------------------------------------------------------------===// // -// This defines CastValueChecker which models casts of custom RTTIs. +// This defines CastValueChecker which models casts of custom RTTIs. +// +// TODO list: +// - It only allows one succesful cast between two types however in the wild +// the object could be casted to multiple types. +// - It needs to check the most likely type information from the dynamic type +// map to increase precision of dynamic casting. // //===----------------------------------------------------------------------===// @@ -15,6 +21,7 @@ #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h" #include "llvm/ADT/Optional.h" #include @@ -23,219 +30,281 @@ using namespace ento; namespace { class CastValueChecker : public Checker { - enum class CastKind { Function, Method }; + enum class CallKind { Function, Method }; using CastCheck = - std::function; - using CheckKindPair = std::pair; - public: // We have five cases to evaluate a cast: - // 1) The parameter is non-null, the return value is non-null - // 2) The parameter is non-null, the return value is null - // 3) The parameter is null, the return value is null + // 1) The parameter is non-null, the return value is non-null. + // 2) The parameter is non-null, the return value is null. + // 3) The parameter is null, the return value is null. // cast: 1; dyn_cast: 1, 2; cast_or_null: 1, 3; dyn_cast_or_null: 1, 2, 3. // - // 4) castAs: has no parameter, the return value is non-null. - // 5) getAs: has no parameter, the return value is null or non-null. + // 4) castAs: Has no parameter, the return value is non-null. + // 5) getAs: Has no parameter, the return value is null or non-null. bool evalCall(const CallEvent &Call, CheckerContext &C) const; + void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const; private: // These are known in the LLVM project. The pairs are in the following form: // {{{namespace, call}, argument-count}, {callback, kind}} - const CallDescriptionMap CDM = { + const CallDescriptionMap> CDM = { {{{"llvm", "cast"}, 1}, - {&CastValueChecker::evalCast, CastKind::Function}}, + {&CastValueChecker::evalCast, CallKind::Function}}, {{{"llvm", "dyn_cast"}, 1}, - {&CastValueChecker::evalDynCast, CastKind::Function}}, + {&CastValueChecker::evalDynCast, CallKind::Function}}, {{{"llvm", "cast_or_null"}, 1}, - {&CastValueChecker::evalCastOrNull, CastKind::Function}}, + {&CastValueChecker::evalCastOrNull, CallKind::Function}}, {{{"llvm", "dyn_cast_or_null"}, 1}, - {&CastValueChecker::evalDynCastOrNull, CastKind::Function}}, + {&CastValueChecker::evalDynCastOrNull, CallKind::Function}}, {{{"clang", "castAs"}, 0}, - {&CastValueChecker::evalCastAs, CastKind::Method}}, + {&CastValueChecker::evalCastAs, CallKind::Method}}, {{{"clang", "getAs"}, 0}, - {&CastValueChecker::evalGetAs, CastKind::Method}}}; + {&CastValueChecker::evalGetAs, CallKind::Method}}}; - void evalCast(const CallExpr *CE, DefinedOrUnknownSVal DV, + void evalCast(const CallEvent &Call, DefinedOrUnknownSVal DV, CheckerContext &C) const; - void evalDynCast(const CallExpr *CE, DefinedOrUnknownSVal DV, + void evalDynCast(const CallEvent &Call, DefinedOrUnknownSVal DV, CheckerContext &C) const; - void evalCastOrNull(const CallExpr *CE, DefinedOrUnknownSVal DV, + void evalCastOrNull(const CallEvent &Call, DefinedOrUnknownSVal DV, CheckerContext &C) const; - void evalDynCastOrNull(const CallExpr *CE, DefinedOrUnknownSVal DV, + void evalDynCastOrNull(const CallEvent &Call, DefinedOrUnknownSVal DV, CheckerContext &C) const; - void evalCastAs(const CallExpr *CE, DefinedOrUnknownSVal DV, + void evalCastAs(const CallEvent &Call, DefinedOrUnknownSVal DV, CheckerContext &C) const; - void evalGetAs(const CallExpr *CE, DefinedOrUnknownSVal DV, + void evalGetAs(const CallEvent &Call, DefinedOrUnknownSVal DV, CheckerContext &C) const; }; } // namespace -static std::string getCastName(const Expr *Cast) { - QualType Ty = Cast->getType(); - if (const CXXRecordDecl *RD = Ty->getAsCXXRecordDecl()) - return RD->getNameAsString(); +static QualType getRecordType(QualType Ty) { + Ty = Ty.getCanonicalType(); + + if (Ty->isPointerType()) + Ty = Ty->getPointeeType(); + + if (Ty->isReferenceType()) + Ty = Ty.getNonReferenceType(); - return Ty->getPointeeCXXRecordDecl()->getNameAsString(); + return Ty.getUnqualifiedType(); } -static const NoteTag *getCastTag(bool IsNullReturn, const CallExpr *CE, - CheckerContext &C, - bool IsCheckedCast = false) { - Optional CastFromName = (CE->getNumArgs() > 0) - ? getCastName(CE->getArg(0)) - : Optional(); - std::string CastToName = getCastName(CE); +static bool isInfeasibleCast(const DynamicCastInfo *CastInfo, + bool CastSucceeds) { + if (!CastInfo) + return false; + + return CastSucceeds ? CastInfo->fails() : CastInfo->succeeds(); +} + +static const NoteTag *getNoteTag(CheckerContext &C, + const DynamicCastInfo *CastInfo, + QualType CastToTy, const Expr *Object, + bool CastSucceeds, bool IsKnownCast) { + std::string CastToName = + CastInfo ? CastInfo->to()->getAsCXXRecordDecl()->getNameAsString() + : CastToTy->getAsCXXRecordDecl()->getNameAsString(); + Object = Object->IgnoreParenImpCasts(); return C.getNoteTag( - [CastFromName, CastToName, IsNullReturn, - IsCheckedCast](BugReport &) -> std::string { + [=] { SmallString<128> Msg; llvm::raw_svector_ostream Out(Msg); - Out << (!IsCheckedCast ? "Assuming dynamic cast " : "Checked cast "); - if (CastFromName) - Out << "from '" << *CastFromName << "' "; + if (!IsKnownCast) + Out << "Assuming "; + + if (const auto *DRE = dyn_cast(Object)) { + Out << '\'' << DRE->getDecl()->getNameAsString() << '\''; + } else if (const auto *ME = dyn_cast(Object)) { + Out << (IsKnownCast ? "Field '" : "field '") + << ME->getMemberDecl()->getNameAsString() << '\''; + } else { + Out << (IsKnownCast ? "The object" : "the object"); + } - Out << "to '" << CastToName << "' " - << (!IsNullReturn ? "succeeds" : "fails"); + Out << ' ' << (CastSucceeds ? "is a" : "is not a") << " '" << CastToName + << '\''; return Out.str(); }, /*IsPrunable=*/true); } -static ProgramStateRef getState(bool IsNullReturn, - DefinedOrUnknownSVal ReturnDV, - const CallExpr *CE, ProgramStateRef State, - CheckerContext &C) { - return State->BindExpr( - CE, C.getLocationContext(), - IsNullReturn ? C.getSValBuilder().makeNull() : ReturnDV, false); +//===----------------------------------------------------------------------===// +// Main logic to evaluate a cast. +//===----------------------------------------------------------------------===// + +static void addCastTransition(const CallEvent &Call, DefinedOrUnknownSVal DV, + CheckerContext &C, bool IsNonNullParam, + bool IsNonNullReturn, + bool IsCheckedCast = false) { + ProgramStateRef State = C.getState()->assume(DV, IsNonNullParam); + if (!State) + return; + + const Expr *Object; + QualType CastFromTy; + QualType CastToTy = getRecordType(Call.getResultType()); + + if (Call.getNumArgs() > 0) { + Object = Call.getArgExpr(0); + CastFromTy = getRecordType(Call.parameters()[0]->getType()); + } else { + Object = cast(&Call)->getCXXThisExpr(); + CastFromTy = getRecordType(Object->getType()); + } + + const MemRegion *MR = DV.getAsRegion(); + const DynamicCastInfo *CastInfo = + getDynamicCastInfo(State, MR, CastFromTy, CastToTy); + + // We assume that every checked cast succeeds. + bool CastSucceeds = IsCheckedCast || CastFromTy == CastToTy; + if (!CastSucceeds) { + if (CastInfo) + CastSucceeds = IsNonNullReturn && CastInfo->succeeds(); + else + CastSucceeds = IsNonNullReturn; + } + + // Check for infeasible casts. + if (isInfeasibleCast(CastInfo, CastSucceeds)) { + C.generateSink(State, C.getPredecessor()); + return; + } + + // Store the type and the cast information. + bool IsKnownCast = CastInfo || IsCheckedCast || CastFromTy == CastToTy; + if (!IsKnownCast || IsCheckedCast) + State = setDynamicTypeAndCastInfo(State, MR, CastFromTy, CastToTy, + Call.getResultType(), CastSucceeds); + + SVal V = CastSucceeds ? DV : C.getSValBuilder().makeNull(); + C.addTransition( + State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), V, false), + getNoteTag(C, CastInfo, CastToTy, Object, CastSucceeds, IsKnownCast)); } //===----------------------------------------------------------------------===// // Evaluating cast, dyn_cast, cast_or_null, dyn_cast_or_null. //===----------------------------------------------------------------------===// -static void evalNonNullParamNonNullReturn(const CallExpr *CE, +static void evalNonNullParamNonNullReturn(const CallEvent &Call, DefinedOrUnknownSVal DV, CheckerContext &C, bool IsCheckedCast = false) { - bool IsNullReturn = false; - if (ProgramStateRef State = C.getState()->assume(DV, true)) - C.addTransition(getState(IsNullReturn, DV, CE, State, C), - getCastTag(IsNullReturn, CE, C, IsCheckedCast)); + addCastTransition(Call, DV, C, /*IsNonNullParam=*/true, + /*IsNonNullReturn=*/true, IsCheckedCast); } -static void evalNonNullParamNullReturn(const CallExpr *CE, +static void evalNonNullParamNullReturn(const CallEvent &Call, DefinedOrUnknownSVal DV, CheckerContext &C) { - bool IsNullReturn = true; - if (ProgramStateRef State = C.getState()->assume(DV, true)) - C.addTransition(getState(IsNullReturn, DV, CE, State, C), - getCastTag(IsNullReturn, CE, C)); + addCastTransition(Call, DV, C, /*IsNonNullParam=*/true, + /*IsNonNullReturn=*/false); } -static void evalNullParamNullReturn(const CallExpr *CE, DefinedOrUnknownSVal DV, +static void evalNullParamNullReturn(const CallEvent &Call, + DefinedOrUnknownSVal DV, CheckerContext &C) { if (ProgramStateRef State = C.getState()->assume(DV, false)) - C.addTransition(getState(/*IsNullReturn=*/true, DV, CE, State, C), + C.addTransition(State->BindExpr(Call.getOriginExpr(), + C.getLocationContext(), + C.getSValBuilder().makeNull(), false), C.getNoteTag("Assuming null pointer is passed into cast", /*IsPrunable=*/true)); } -void CastValueChecker::evalCast(const CallExpr *CE, DefinedOrUnknownSVal DV, +void CastValueChecker::evalCast(const CallEvent &Call, DefinedOrUnknownSVal DV, CheckerContext &C) const { - evalNonNullParamNonNullReturn(CE, DV, C, /*IsCheckedCast=*/true); + evalNonNullParamNonNullReturn(Call, DV, C, /*IsCheckedCast=*/true); } -void CastValueChecker::evalDynCast(const CallExpr *CE, DefinedOrUnknownSVal DV, +void CastValueChecker::evalDynCast(const CallEvent &Call, + DefinedOrUnknownSVal DV, CheckerContext &C) const { - evalNonNullParamNonNullReturn(CE, DV, C); - evalNonNullParamNullReturn(CE, DV, C); + evalNonNullParamNonNullReturn(Call, DV, C); + evalNonNullParamNullReturn(Call, DV, C); } -void CastValueChecker::evalCastOrNull(const CallExpr *CE, +void CastValueChecker::evalCastOrNull(const CallEvent &Call, DefinedOrUnknownSVal DV, CheckerContext &C) const { - evalNonNullParamNonNullReturn(CE, DV, C); - evalNullParamNullReturn(CE, DV, C); + evalNonNullParamNonNullReturn(Call, DV, C); + evalNullParamNullReturn(Call, DV, C); } -void CastValueChecker::evalDynCastOrNull(const CallExpr *CE, +void CastValueChecker::evalDynCastOrNull(const CallEvent &Call, DefinedOrUnknownSVal DV, CheckerContext &C) const { - evalNonNullParamNonNullReturn(CE, DV, C); - evalNonNullParamNullReturn(CE, DV, C); - evalNullParamNullReturn(CE, DV, C); + evalNonNullParamNonNullReturn(Call, DV, C); + evalNonNullParamNullReturn(Call, DV, C); + evalNullParamNullReturn(Call, DV, C); } //===----------------------------------------------------------------------===// // Evaluating castAs, getAs. //===----------------------------------------------------------------------===// -static void evalZeroParamNonNullReturn(const CallExpr *CE, +static void evalZeroParamNonNullReturn(const CallEvent &Call, DefinedOrUnknownSVal DV, CheckerContext &C, bool IsCheckedCast = false) { - bool IsNullReturn = false; - if (ProgramStateRef State = C.getState()->assume(DV, true)) - C.addTransition(getState(IsNullReturn, DV, CE, C.getState(), C), - getCastTag(IsNullReturn, CE, C, IsCheckedCast)); + addCastTransition(Call, DV, C, /*IsNonNullParam=*/true, + /*IsNonNullReturn=*/true, IsCheckedCast); } -static void evalZeroParamNullReturn(const CallExpr *CE, DefinedOrUnknownSVal DV, +static void evalZeroParamNullReturn(const CallEvent &Call, + DefinedOrUnknownSVal DV, CheckerContext &C) { - bool IsNullReturn = true; - if (ProgramStateRef State = C.getState()->assume(DV, true)) - C.addTransition(getState(IsNullReturn, DV, CE, C.getState(), C), - getCastTag(IsNullReturn, CE, C)); + addCastTransition(Call, DV, C, /*IsNonNullParam=*/true, + /*IsNonNullReturn=*/false); } -void CastValueChecker::evalCastAs(const CallExpr *CE, DefinedOrUnknownSVal DV, +void CastValueChecker::evalCastAs(const CallEvent &Call, + DefinedOrUnknownSVal DV, CheckerContext &C) const { - evalZeroParamNonNullReturn(CE, DV, C, /*IsCheckedCast=*/true); + evalZeroParamNonNullReturn(Call, DV, C, /*IsCheckedCast=*/true); } -void CastValueChecker::evalGetAs(const CallExpr *CE, DefinedOrUnknownSVal DV, +void CastValueChecker::evalGetAs(const CallEvent &Call, DefinedOrUnknownSVal DV, CheckerContext &C) const { - evalZeroParamNonNullReturn(CE, DV, C); - evalZeroParamNullReturn(CE, DV, C); + evalZeroParamNonNullReturn(Call, DV, C); + evalZeroParamNullReturn(Call, DV, C); } +//===----------------------------------------------------------------------===// +// Main logic to evaluate a call. +//===----------------------------------------------------------------------===// + bool CastValueChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { const auto *Lookup = CDM.lookup(Call); if (!Lookup) return false; - // If we cannot obtain the call's class we cannot be sure how to model it. - QualType ResultTy = Call.getResultType(); - if (!ResultTy->getPointeeCXXRecordDecl()) + // We need to obtain the record type of the call's result to model it. + if (!getRecordType(Call.getResultType())->isRecordType()) return false; const CastCheck &Check = Lookup->first; - CastKind Kind = Lookup->second; - - const auto *CE = cast(Call.getOriginExpr()); + CallKind Kind = Lookup->second; Optional DV; switch (Kind) { - case CastKind::Function: { - // If we cannot obtain the arg's class we cannot be sure how to model it. - QualType ArgTy = Call.parameters()[0]->getType(); - if (!ArgTy->getAsCXXRecordDecl() && !ArgTy->getPointeeCXXRecordDecl()) + case CallKind::Function: { + // We need to obtain the record type of the call's parameter to model it. + if (!getRecordType(Call.parameters()[0]->getType())->isRecordType()) return false; DV = Call.getArgSVal(0).getAs(); break; } - case CastKind::Method: - // If we cannot obtain the 'InstanceCall' we cannot be sure how to model it. + case CallKind::Method: const auto *InstanceCall = dyn_cast(&Call); if (!InstanceCall) return false; @@ -247,10 +316,15 @@ bool CastValueChecker::evalCall(const CallEvent &Call, if (!DV) return false; - Check(this, CE, *DV, C); + Check(this, Call, *DV, C); return true; } +void CastValueChecker::checkDeadSymbols(SymbolReaper &SR, + CheckerContext &C) const { + C.addTransition(removeDeadCasts(C.getState(), SR)); +} + void ento::registerCastValueChecker(CheckerManager &Mgr) { Mgr.registerChecker(); } diff --git a/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp b/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp index d32544a9ac58f..8b98c57e75587 100644 --- a/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp @@ -20,16 +20,16 @@ // //===----------------------------------------------------------------------===// -#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/ParentMap.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/Basic/Builtins.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeMap.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" using namespace clang; @@ -113,14 +113,7 @@ class DynamicTypePropagation: void DynamicTypePropagation::checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const { - ProgramStateRef State = C.getState(); - DynamicTypeMapTy TypeMap = State->get(); - for (DynamicTypeMapTy::iterator I = TypeMap.begin(), E = TypeMap.end(); - I != E; ++I) { - if (!SR.isLiveRegion(I->first)) { - State = State->remove(I->first); - } - } + ProgramStateRef State = removeDeadTypes(C.getState(), SR); MostSpecializedTypeArgsMapTy TyArgMap = State->get(); @@ -882,7 +875,7 @@ void DynamicTypePropagation::checkPostObjCMessage(const ObjCMethodCall &M, // When there is an entry available for the return symbol in DynamicTypeMap, // the call was inlined, and the information in the DynamicTypeMap is should // be precise. - if (RetRegion && !State->get(RetRegion)) { + if (RetRegion && !getRawDynamicTypeInfo(State, RetRegion)) { // TODO: we have duplicated information in DynamicTypeMap and // MostSpecializedTypeArgsMap. We should only store anything in the later if // the stored data differs from the one stored in the former. diff --git a/clang/lib/StaticAnalyzer/Core/CMakeLists.txt b/clang/lib/StaticAnalyzer/Core/CMakeLists.txt index 0affa8be5572d..05bc94c7c9842 100644 --- a/clang/lib/StaticAnalyzer/Core/CMakeLists.txt +++ b/clang/lib/StaticAnalyzer/Core/CMakeLists.txt @@ -16,7 +16,7 @@ add_clang_library(clangStaticAnalyzerCore CommonBugCategories.cpp ConstraintManager.cpp CoreEngine.cpp - DynamicTypeMap.cpp + DynamicType.cpp Environment.cpp ExplodedGraph.cpp ExprEngine.cpp diff --git a/clang/lib/StaticAnalyzer/Core/DynamicType.cpp b/clang/lib/StaticAnalyzer/Core/DynamicType.cpp new file mode 100644 index 0000000000000..a9e281ec9e589 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Core/DynamicType.cpp @@ -0,0 +1,223 @@ +//===- DynamicType.cpp - Dynamic type related APIs --------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines APIs that track and query dynamic type information. This +// information can be used to devirtualize calls during the symbolic execution +// or do type checking. +// +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h" +#include "clang/Basic/JsonSupport.h" +#include "clang/Basic/LLVM.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/raw_ostream.h" +#include + +/// The GDM component containing the dynamic type info. This is a map from a +/// symbol to its most likely type. +REGISTER_MAP_WITH_PROGRAMSTATE(DynamicTypeMap, const clang::ento::MemRegion *, + clang::ento::DynamicTypeInfo) + +/// A set factory of dynamic cast informations. +REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(CastSet, clang::ento::DynamicCastInfo) + +/// A map from symbols to cast informations. +REGISTER_MAP_WITH_PROGRAMSTATE(DynamicCastMap, const clang::ento::MemRegion *, + CastSet) + +namespace clang { +namespace ento { + +DynamicTypeInfo getDynamicTypeInfo(ProgramStateRef State, const MemRegion *MR) { + MR = MR->StripCasts(); + + // Look up the dynamic type in the GDM. + if (const DynamicTypeInfo *DTI = State->get(MR)) + return *DTI; + + // Otherwise, fall back to what we know about the region. + if (const auto *TR = dyn_cast(MR)) + return DynamicTypeInfo(TR->getLocationType(), /*CanBeSub=*/false); + + if (const auto *SR = dyn_cast(MR)) { + SymbolRef Sym = SR->getSymbol(); + return DynamicTypeInfo(Sym->getType()); + } + + return {}; +} + +const DynamicTypeInfo *getRawDynamicTypeInfo(ProgramStateRef State, + const MemRegion *MR) { + return State->get(MR); +} + +const DynamicCastInfo *getDynamicCastInfo(ProgramStateRef State, + const MemRegion *MR, + QualType CastFromTy, + QualType CastToTy) { + const auto *Lookup = State->get().lookup(MR); + if (!Lookup) + return nullptr; + + for (const DynamicCastInfo &Cast : *Lookup) + if (Cast.equals(CastFromTy, CastToTy)) + return &Cast; + + return nullptr; +} + +ProgramStateRef setDynamicTypeInfo(ProgramStateRef State, const MemRegion *MR, + DynamicTypeInfo NewTy) { + State = State->set(MR->StripCasts(), NewTy); + assert(State); + return State; +} + +ProgramStateRef setDynamicTypeInfo(ProgramStateRef State, const MemRegion *MR, + QualType NewTy, bool CanBeSubClassed) { + return setDynamicTypeInfo(State, MR, DynamicTypeInfo(NewTy, CanBeSubClassed)); +} + +ProgramStateRef setDynamicTypeAndCastInfo(ProgramStateRef State, + const MemRegion *MR, + QualType CastFromTy, + QualType CastToTy, QualType ResultTy, + bool CastSucceeds) { + if (CastSucceeds) + State = State->set(MR, ResultTy); + + DynamicCastInfo::CastResult ResultKind = + CastSucceeds ? DynamicCastInfo::CastResult::Success + : DynamicCastInfo::CastResult::Failure; + + CastSet::Factory &F = State->get_context(); + + const CastSet *TempSet = State->get(MR); + CastSet Set = TempSet ? *TempSet : F.getEmptySet(); + + Set = F.add(Set, {CastFromTy, CastToTy, ResultKind}); + State = State->set(MR, Set); + + assert(State); + return State; +} + +template +ProgramStateRef removeDead(ProgramStateRef State, const MapTy &Map, + SymbolReaper &SR) { + for (const auto &Elem : Map) + if (!SR.isLiveRegion(Elem.first)) + State = State->remove(Elem.first); + + return State; +} + +ProgramStateRef removeDeadTypes(ProgramStateRef State, SymbolReaper &SR) { + return removeDead(State, State->get(), SR); +} + +ProgramStateRef removeDeadCasts(ProgramStateRef State, SymbolReaper &SR) { + return removeDead(State, State->get(), SR); +} + +static void printDynamicTypesJson(raw_ostream &Out, ProgramStateRef State, + const char *NL, unsigned int Space, + bool IsDot) { + Indent(Out, Space, IsDot) << "\"dynamic_types\": "; + + const DynamicTypeMapTy &Map = State->get(); + if (Map.isEmpty()) { + Out << "null," << NL; + return; + } + + ++Space; + Out << '[' << NL; + for (DynamicTypeMapTy::iterator I = Map.begin(); I != Map.end(); ++I) { + const MemRegion *MR = I->first; + const DynamicTypeInfo &DTI = I->second; + Indent(Out, Space, IsDot) + << "{ \"region\": \"" << MR << "\", \"dyn_type\": "; + if (!DTI.isValid()) { + Out << "null"; + } else { + Out << '\"' << DTI.getType()->getPointeeType().getAsString() + << "\", \"sub_classable\": " + << (DTI.canBeASubClass() ? "true" : "false"); + } + Out << " }"; + + if (std::next(I) != Map.end()) + Out << ','; + Out << NL; + } + + --Space; + Indent(Out, Space, IsDot) << "]," << NL; +} + +static void printDynamicCastsJson(raw_ostream &Out, ProgramStateRef State, + const char *NL, unsigned int Space, + bool IsDot) { + Indent(Out, Space, IsDot) << "\"dynamic_casts\": "; + + const DynamicCastMapTy &Map = State->get(); + if (Map.isEmpty()) { + Out << "null," << NL; + return; + } + + ++Space; + Out << '[' << NL; + for (DynamicCastMapTy::iterator I = Map.begin(); I != Map.end(); ++I) { + const MemRegion *MR = I->first; + const CastSet &Set = I->second; + + Indent(Out, Space, IsDot) << "{ \"region\": \"" << MR << "\", \"casts\": "; + if (Set.isEmpty()) { + Out << "null "; + } else { + ++Space; + Out << '[' << NL; + for (CastSet::iterator SI = Set.begin(); SI != Set.end(); ++SI) { + Indent(Out, Space, IsDot) + << "{ \"from\": \"" << SI->from().getAsString() << "\", \"to\": \"" + << SI->to().getAsString() << "\", \"kind\": \"" + << (SI->succeeds() ? "success" : "fail") << "\" }"; + + if (std::next(SI) != Set.end()) + Out << ','; + Out << NL; + } + --Space; + Indent(Out, Space, IsDot) << ']'; + } + Out << '}'; + + if (std::next(I) != Map.end()) + Out << ','; + Out << NL; + } + + --Space; + Indent(Out, Space, IsDot) << "]," << NL; +} + +void printDynamicTypeInfoJson(raw_ostream &Out, ProgramStateRef State, + const char *NL, unsigned int Space, bool IsDot) { + printDynamicTypesJson(Out, State, NL, Space, IsDot); + printDynamicCastsJson(Out, State, NL, Space, IsDot); +} + +} // namespace ento +} // namespace clang diff --git a/clang/lib/StaticAnalyzer/Core/DynamicTypeMap.cpp b/clang/lib/StaticAnalyzer/Core/DynamicTypeMap.cpp deleted file mode 100644 index 75ae2606910ac..0000000000000 --- a/clang/lib/StaticAnalyzer/Core/DynamicTypeMap.cpp +++ /dev/null @@ -1,97 +0,0 @@ -//===- DynamicTypeMap.cpp - Dynamic Type Info related APIs ----------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// This file defines APIs that track and query dynamic type information. This -// information can be used to devirtualize calls during the symbolic execution -// or do type checking. -// -//===----------------------------------------------------------------------===// - -#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeMap.h" -#include "clang/Basic/JsonSupport.h" -#include "clang/Basic/LLVM.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h" -#include "llvm/Support/Casting.h" -#include "llvm/Support/raw_ostream.h" -#include - -namespace clang { -namespace ento { - -DynamicTypeInfo getDynamicTypeInfo(ProgramStateRef State, - const MemRegion *Reg) { - Reg = Reg->StripCasts(); - - // Look up the dynamic type in the GDM. - const DynamicTypeInfo *GDMType = State->get(Reg); - if (GDMType) - return *GDMType; - - // Otherwise, fall back to what we know about the region. - if (const auto *TR = dyn_cast(Reg)) - return DynamicTypeInfo(TR->getLocationType(), /*CanBeSubclass=*/false); - - if (const auto *SR = dyn_cast(Reg)) { - SymbolRef Sym = SR->getSymbol(); - return DynamicTypeInfo(Sym->getType()); - } - - return {}; -} - -ProgramStateRef setDynamicTypeInfo(ProgramStateRef State, const MemRegion *Reg, - DynamicTypeInfo NewTy) { - Reg = Reg->StripCasts(); - ProgramStateRef NewState = State->set(Reg, NewTy); - assert(NewState); - return NewState; -} - -void printDynamicTypeInfoJson(raw_ostream &Out, ProgramStateRef State, - const char *NL, unsigned int Space, bool IsDot) { - Indent(Out, Space, IsDot) << "\"dynamic_types\": "; - - const DynamicTypeMapTy &DTM = State->get(); - if (DTM.isEmpty()) { - Out << "null," << NL; - return; - } - - ++Space; - Out << '[' << NL; - for (DynamicTypeMapTy::iterator I = DTM.begin(); I != DTM.end(); ++I) { - const MemRegion *MR = I->first; - const DynamicTypeInfo &DTI = I->second; - Out << "{ \"region\": \"" << MR << "\", \"dyn_type\": "; - if (DTI.isValid()) { - Out << '\"' << DTI.getType()->getPointeeType().getAsString() - << "\", \"sub_classable\": " - << (DTI.canBeASubClass() ? "true" : "false"); - } else { - Out << "null"; // Invalid type info - } - Out << "}"; - - if (std::next(I) != DTM.end()) - Out << ','; - Out << NL; - } - - --Space; - Indent(Out, Space, IsDot) << "]," << NL; -} - -void *ProgramStateTrait::GDMIndex() { - static int index = 0; - return &index; -} - -} // namespace ento -} // namespace clang diff --git a/clang/lib/StaticAnalyzer/Core/ProgramState.cpp b/clang/lib/StaticAnalyzer/Core/ProgramState.cpp index a1ca0b1b84bfa..f50d82de3b28d 100644 --- a/clang/lib/StaticAnalyzer/Core/ProgramState.cpp +++ b/clang/lib/StaticAnalyzer/Core/ProgramState.cpp @@ -15,7 +15,7 @@ #include "clang/Basic/JsonSupport.h" #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeMap.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SubEngine.h" #include "llvm/Support/raw_ostream.h" diff --git a/clang/test/Analysis/Inputs/llvm.h b/clang/test/Analysis/Inputs/llvm.h new file mode 100644 index 0000000000000..9d465c65fda98 --- /dev/null +++ b/clang/test/Analysis/Inputs/llvm.h @@ -0,0 +1,19 @@ +#pragma clang system_header + +namespace llvm { +template +const X *cast(Y Value); + +template +const X *dyn_cast(Y *Value); +template +const X &dyn_cast(Y &Value); + +template +const X *cast_or_null(Y Value); + +template +const X *dyn_cast_or_null(Y *Value); +template +const X *dyn_cast_or_null(Y &Value); +} // namespace llvm diff --git a/clang/test/Analysis/cast-value-logic.cpp b/clang/test/Analysis/cast-value-logic.cpp new file mode 100644 index 0000000000000..32001d8548a22 --- /dev/null +++ b/clang/test/Analysis/cast-value-logic.cpp @@ -0,0 +1,128 @@ +// RUN: %clang_analyze_cc1 \ +// RUN: -analyzer-checker=core,apiModeling.llvm.CastValue,debug.ExprInspection\ +// RUN: -verify %s + +#include "Inputs/llvm.h" + +void clang_analyzer_numTimesReached(); +void clang_analyzer_warnIfReached(); +void clang_analyzer_eval(bool); + +namespace clang { +struct Shape { + template + const T *castAs() const; + + template + const T *getAs() const; +}; +class Triangle : public Shape {}; +class Circle : public Shape {}; +} // namespace clang + +using namespace llvm; +using namespace clang; + +void test_regions(const Shape *A, const Shape *B) { + if (dyn_cast(A) && !dyn_cast(B)) + clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}} +} + +namespace test_cast { +void evalLogic(const Shape *S) { + const Circle *C = cast(S); + clang_analyzer_numTimesReached(); // expected-warning {{1}} + + if (S && C) + clang_analyzer_eval(C == S); // expected-warning {{TRUE}} + + if (S && !C) + clang_analyzer_warnIfReached(); // no-warning + + if (!S) + clang_analyzer_warnIfReached(); // no-warning +} +} // namespace test_cast + +namespace test_dyn_cast { +void evalLogic(const Shape *S) { + const Circle *C = dyn_cast(S); + clang_analyzer_numTimesReached(); // expected-warning {{2}} + + if (S && C) + clang_analyzer_eval(C == S); // expected-warning {{TRUE}} + + if (S && !C) + clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}} + + if (!S) + clang_analyzer_warnIfReached(); // no-warning +} +} // namespace test_dyn_cast + +namespace test_cast_or_null { +void evalLogic(const Shape *S) { + const Circle *C = cast_or_null(S); + clang_analyzer_numTimesReached(); // expected-warning {{2}} + + if (S && C) + clang_analyzer_eval(C == S); // expected-warning {{TRUE}} + + if (S && !C) + clang_analyzer_warnIfReached(); // no-warning + + if (!S) + clang_analyzer_eval(!C); // expected-warning {{TRUE}} +} +} // namespace test_cast_or_null + +namespace test_dyn_cast_or_null { +void evalLogic(const Shape *S) { + const Circle *C = dyn_cast_or_null(S); + clang_analyzer_numTimesReached(); // expected-warning {{3}} + + if (S && C) + clang_analyzer_eval(C == S); // expected-warning {{TRUE}} + + if (S && !C) + clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}} + + if (!S) + clang_analyzer_eval(!C); // expected-warning {{TRUE}} +} +} // namespace test_dyn_cast_or_null + +namespace test_cast_as { +void evalLogic(const Shape *S) { + const Circle *C = S->castAs(); + clang_analyzer_numTimesReached(); // expected-warning {{1}} + + if (S && C) + clang_analyzer_eval(C == S); + // expected-warning@-1 {{TRUE}} + + if (S && !C) + clang_analyzer_warnIfReached(); // no-warning + + if (!S) + clang_analyzer_warnIfReached(); // no-warning +} +} // namespace test_cast_as + +namespace test_get_as { +void evalLogic(const Shape *S) { + const Circle *C = S->getAs(); + clang_analyzer_numTimesReached(); // expected-warning {{2}} + + if (S && C) + clang_analyzer_eval(C == S); + // expected-warning@-1 {{TRUE}} + + if (S && !C) + clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}} + + if (!S) + clang_analyzer_warnIfReached(); // no-warning +} +} // namespace test_get_as + diff --git a/clang/test/Analysis/cast-value-notes.cpp b/clang/test/Analysis/cast-value-notes.cpp new file mode 100644 index 0000000000000..77801d699350e --- /dev/null +++ b/clang/test/Analysis/cast-value-notes.cpp @@ -0,0 +1,144 @@ +// RUN: %clang_analyze_cc1 \ +// RUN: -analyzer-checker=core,apiModeling.llvm.CastValue \ +// RUN: -analyzer-output=text -verify %s + +#include "Inputs/llvm.h" + +namespace clang { +struct Shape { + template + const T *castAs() const; + + template + const T *getAs() const; +}; +class Triangle : public Shape {}; +class Circle : public Shape {}; +} // namespace clang + +using namespace llvm; +using namespace clang; + +void evalReferences(const Shape &S) { + const auto &C = dyn_cast(S); + // expected-note@-1 {{Assuming 'S' is not a 'Circle'}} + // expected-note@-2 {{Dereference of null pointer}} + // expected-warning@-3 {{Dereference of null pointer}} +} + +void evalNonNullParamNonNullReturnReference(const Shape &S) { + const auto *C = dyn_cast_or_null(S); + // expected-note@-1 {{Assuming 'S' is a 'Circle'}} + // expected-note@-2 {{'C' initialized here}} + + if (!dyn_cast_or_null(C)) { + // expected-note@-1 {{'C' is a 'Circle'}} + // expected-note@-2 {{Taking false branch}} + return; + } + + if (dyn_cast_or_null(C)) { + // expected-note@-1 {{Assuming 'C' is not a 'Triangle'}} + // expected-note@-2 {{Taking false branch}} + return; + } + + if (dyn_cast_or_null(C)) { + // expected-note@-1 {{'C' is not a 'Triangle'}} + // expected-note@-2 {{Taking false branch}} + return; + } + + (void)(1 / !C); + // expected-note@-1 {{'C' is non-null}} + // expected-note@-2 {{Division by zero}} + // expected-warning@-3 {{Division by zero}} +} + +void evalNonNullParamNonNullReturn(const Shape *S) { + const auto *C = cast(S); + // expected-note@-1 {{'S' is a 'Circle'}} + // expected-note@-2 {{'C' initialized here}} + + if (!cast(C)) { + // expected-note@-1 {{'C' is a 'Triangle'}} + // expected-note@-2 {{Taking false branch}} + return; + } + + (void)(1 / !C); + // expected-note@-1 {{'C' is non-null}} + // expected-note@-2 {{Division by zero}} + // expected-warning@-3 {{Division by zero}} +} + +void evalNonNullParamNullReturn(const Shape *S) { + const auto *C = dyn_cast_or_null(S); + // expected-note@-1 {{Assuming 'S' is not a 'Circle'}} + + if (const auto *T = dyn_cast_or_null(S)) { + // expected-note@-1 {{Assuming 'S' is a 'Triangle'}} + // expected-note@-2 {{'T' initialized here}} + // expected-note@-3 {{'T' is non-null}} + // expected-note@-4 {{Taking true branch}} + + (void)(1 / !T); + // expected-note@-1 {{'T' is non-null}} + // expected-note@-2 {{Division by zero}} + // expected-warning@-3 {{Division by zero}} + } +} + +void evalNullParamNullReturn(const Shape *S) { + const auto *C = dyn_cast_or_null(S); + // expected-note@-1 {{Assuming null pointer is passed into cast}} + // expected-note@-2 {{'C' initialized to a null pointer value}} + + (void)(1 / (bool)C); + // expected-note@-1 {{Division by zero}} + // expected-warning@-2 {{Division by zero}} +} + +void evalZeroParamNonNullReturnPointer(const Shape *S) { + const auto *C = S->castAs(); + // expected-note@-1 {{'S' is a 'Circle'}} + // expected-note@-2 {{'C' initialized here}} + + (void)(1 / !C); + // expected-note@-1 {{'C' is non-null}} + // expected-note@-2 {{Division by zero}} + // expected-warning@-3 {{Division by zero}} +} + +void evalZeroParamNonNullReturn(const Shape &S) { + const auto *C = S.castAs(); + // expected-note@-1 {{'S' is a 'Circle'}} + // expected-note@-2 {{'C' initialized here}} + + (void)(1 / !C); + // expected-note@-1 {{'C' is non-null}} + // expected-note@-2 {{Division by zero}} + // expected-warning@-3 {{Division by zero}} +} + +void evalZeroParamNullReturn(const Shape &S) { + const auto *C = S.getAs(); + // expected-note@-1 {{Assuming 'S' is not a 'Circle'}} + // expected-note@-2 {{'C' initialized to a null pointer value}} + + if (!dyn_cast_or_null(S)) { + // expected-note@-1 {{Assuming 'S' is a 'Triangle'}} + // expected-note@-2 {{Taking false branch}} + return; + } + + if (!dyn_cast_or_null(S)) { + // expected-note@-1 {{'S' is a 'Triangle'}} + // expected-note@-2 {{Taking false branch}} + return; + } + + (void)(1 / (bool)C); + // expected-note@-1 {{Division by zero}} + // expected-warning@-2 {{Division by zero}} +} diff --git a/clang/test/Analysis/cast-value-state-dump.cpp b/clang/test/Analysis/cast-value-state-dump.cpp new file mode 100644 index 0000000000000..fd679984d6b2b --- /dev/null +++ b/clang/test/Analysis/cast-value-state-dump.cpp @@ -0,0 +1,47 @@ +// RUN: %clang_analyze_cc1 \ +// RUN: -analyzer-checker=core,apiModeling.llvm.CastValue,debug.ExprInspection\ +// RUN: -analyzer-output=text -verify %s 2>&1 | FileCheck %s + +#include "Inputs/llvm.h" + +void clang_analyzer_printState(); + +namespace clang { +struct Shape {}; +class Triangle : public Shape {}; +class Circle : public Shape {}; +class Square : public Shape {}; +} // namespace clang + +using namespace llvm; +using namespace clang; + +void evalNonNullParamNonNullReturnReference(const Shape &S) { + const auto *C = dyn_cast_or_null(S); + // expected-note@-1 {{Assuming 'S' is a 'Circle'}} + // expected-note@-2 {{'C' initialized here}} + + // FIXME: We assumed that 'S' is a 'Circle' therefore it is not a 'Square'. + if (dyn_cast_or_null(S)) { + // expected-note@-1 {{Assuming 'S' is not a 'Square'}} + // expected-note@-2 {{Taking false branch}} + return; + } + + clang_analyzer_printState(); + + // CHECK: "dynamic_types": [ + // CHECK-NEXT: { "region": "SymRegion{reg_$0}", "dyn_type": "const class clang::Circle", "sub_classable": true } + // CHECK-NEXT: ], + // CHECK-NEXT: "dynamic_casts": [ + // CHECK: { "region": "SymRegion{reg_$0}", "casts": [ + // CHECK-NEXT: { "from": "struct clang::Shape", "to": "class clang::Circle", "kind": "success" }, + // CHECK-NEXT: { "from": "struct clang::Shape", "to": "class clang::Square", "kind": "fail" } + // CHECK-NEXT: ]} + + (void)(1 / !C); + // expected-note@-1 {{'C' is non-null}} + // expected-note@-2 {{Division by zero}} + // expected-warning@-3 {{Division by zero}} +} + diff --git a/clang/test/Analysis/cast-value.cpp b/clang/test/Analysis/cast-value.cpp deleted file mode 100644 index fcf145365ea45..0000000000000 --- a/clang/test/Analysis/cast-value.cpp +++ /dev/null @@ -1,239 +0,0 @@ -// RUN: %clang_analyze_cc1 \ -// RUN: -analyzer-checker=core,apiModeling.llvm.CastValue,debug.ExprInspection\ -// RUN: -verify=logic %s -// RUN: %clang_analyze_cc1 \ -// RUN: -analyzer-checker=core,apiModeling.llvm.CastValue \ -// RUN: -analyzer-output=text -verify %s - -void clang_analyzer_numTimesReached(); -void clang_analyzer_warnIfReached(); -void clang_analyzer_eval(bool); - -namespace llvm { -template -const X *cast(Y Value); - -template -const X *dyn_cast(Y *Value); -template -const X &dyn_cast(Y &Value); - -template -const X *cast_or_null(Y Value); - -template -const X *dyn_cast_or_null(Y *Value); -template -const X *dyn_cast_or_null(Y &Value); -} // namespace llvm - -namespace clang { -struct Shape { - template - const T *castAs() const; - - template - const T *getAs() const; -}; -class Triangle : public Shape {}; -class Circle : public Shape {}; -} // namespace clang - -using namespace llvm; -using namespace clang; - -namespace test_cast { -void evalLogic(const Shape *S) { - const Circle *C = cast(S); - clang_analyzer_numTimesReached(); // logic-warning {{1}} - - if (S && C) - clang_analyzer_eval(C == S); // logic-warning {{TRUE}} - - if (S && !C) - clang_analyzer_warnIfReached(); // no-warning - - if (!S) - clang_analyzer_warnIfReached(); // no-warning -} -} // namespace test_cast - -namespace test_dyn_cast { -void evalLogic(const Shape *S) { - const Circle *C = dyn_cast(S); - clang_analyzer_numTimesReached(); // logic-warning {{2}} - - if (S && C) - clang_analyzer_eval(C == S); // logic-warning {{TRUE}} - - if (S && !C) - clang_analyzer_warnIfReached(); // logic-warning {{REACHABLE}} - - if (!S) - clang_analyzer_warnIfReached(); // no-warning -} -} // namespace test_dyn_cast - -namespace test_cast_or_null { -void evalLogic(const Shape *S) { - const Circle *C = cast_or_null(S); - clang_analyzer_numTimesReached(); // logic-warning {{2}} - - if (S && C) - clang_analyzer_eval(C == S); // logic-warning {{TRUE}} - - if (S && !C) - clang_analyzer_warnIfReached(); // no-warning - - if (!S) - clang_analyzer_eval(!C); // logic-warning {{TRUE}} -} -} // namespace test_cast_or_null - -namespace test_dyn_cast_or_null { -void evalLogic(const Shape *S) { - const Circle *C = dyn_cast_or_null(S); - clang_analyzer_numTimesReached(); // logic-warning {{3}} - - if (S && C) - clang_analyzer_eval(C == S); // logic-warning {{TRUE}} - - if (S && !C) - clang_analyzer_warnIfReached(); // logic-warning {{REACHABLE}} - - if (!S) - clang_analyzer_eval(!C); // logic-warning {{TRUE}} -} -} // namespace test_dyn_cast_or_null - -namespace test_cast_as { -void evalLogic(const Shape *S) { - const Circle *C = S->castAs(); - clang_analyzer_numTimesReached(); // logic-warning {{1}} - - if (S && C) - clang_analyzer_eval(C == S); - // logic-warning@-1 {{TRUE}} - - if (S && !C) - clang_analyzer_warnIfReached(); // no-warning - - if (!S) - clang_analyzer_warnIfReached(); // no-warning -} -} // namespace test_cast_as - -namespace test_get_as { -void evalLogic(const Shape *S) { - const Circle *C = S->getAs(); - clang_analyzer_numTimesReached(); // logic-warning {{2}} - - if (S && C) - clang_analyzer_eval(C == S); - // logic-warning@-1 {{TRUE}} - - if (S && !C) - clang_analyzer_warnIfReached(); // logic-warning {{REACHABLE}} - - if (!S) - clang_analyzer_warnIfReached(); // no-warning -} -} // namespace test_get_as - -namespace test_notes { -void evalReferences(const Shape &S) { - const auto &C = dyn_cast(S); - // expected-note@-1 {{Assuming dynamic cast from 'Shape' to 'Circle' fails}} - // expected-note@-2 {{Dereference of null pointer}} - // expected-warning@-3 {{Dereference of null pointer}} - // logic-warning@-4 {{Dereference of null pointer}} -} - -void evalNonNullParamNonNullReturnReference(const Shape &S) { - const auto *C = dyn_cast_or_null(S); - // expected-note@-1 {{Assuming dynamic cast from 'Shape' to 'Circle' succeeds}} - // expected-note@-2 {{'C' initialized here}} - - (void)(1 / !(bool)C); - // expected-note@-1 {{'C' is non-null}} - // expected-note@-2 {{Division by zero}} - // expected-warning@-3 {{Division by zero}} - // logic-warning@-4 {{Division by zero}} -} - -void evalNonNullParamNonNullReturn(const Shape *S) { - const auto *C = cast(S); - // expected-note@-1 {{Checked cast from 'Shape' to 'Circle' succeeds}} - // expected-note@-2 {{'C' initialized here}} - - (void)(1 / !(bool)C); - // expected-note@-1 {{'C' is non-null}} - // expected-note@-2 {{Division by zero}} - // expected-warning@-3 {{Division by zero}} - // logic-warning@-4 {{Division by zero}} -} - -void evalNonNullParamNullReturn(const Shape *S) { - const auto *C = dyn_cast_or_null(S); - // expected-note@-1 {{Assuming dynamic cast from 'Shape' to 'Circle' fails}} - - if (const auto *T = dyn_cast_or_null(S)) { - // expected-note@-1 {{Assuming dynamic cast from 'Shape' to 'Triangle' succeeds}} - // expected-note@-2 {{'T' initialized here}} - // expected-note@-3 {{'T' is non-null}} - // expected-note@-4 {{Taking true branch}} - - (void)(1 / !T); - // expected-note@-1 {{'T' is non-null}} - // expected-note@-2 {{Division by zero}} - // expected-warning@-3 {{Division by zero}} - // logic-warning@-4 {{Division by zero}} - } -} - -void evalNullParamNullReturn(const Shape *S) { - const auto *C = dyn_cast_or_null(S); - // expected-note@-1 {{Assuming null pointer is passed into cast}} - // expected-note@-2 {{'C' initialized to a null pointer value}} - - (void)(1 / (bool)C); - // expected-note@-1 {{Division by zero}} - // expected-warning@-2 {{Division by zero}} - // logic-warning@-3 {{Division by zero}} -} - -void evalZeroParamNonNullReturnPointer(const Shape *S) { - const auto *C = S->castAs(); - // expected-note@-1 {{Checked cast to 'Circle' succeeds}} - // expected-note@-2 {{'C' initialized here}} - - (void)(1 / !(bool)C); - // expected-note@-1 {{'C' is non-null}} - // expected-note@-2 {{Division by zero}} - // expected-warning@-3 {{Division by zero}} - // logic-warning@-4 {{Division by zero}} -} - -void evalZeroParamNonNullReturn(const Shape &S) { - const auto *C = S.castAs(); - // expected-note@-1 {{Checked cast to 'Circle' succeeds}} - // expected-note@-2 {{'C' initialized here}} - - (void)(1 / !(bool)C); - // expected-note@-1 {{'C' is non-null}} - // expected-note@-2 {{Division by zero}} - // expected-warning@-3 {{Division by zero}} - // logic-warning@-4 {{Division by zero}} -} - -void evalZeroParamNullReturn(const Shape &S) { - const auto *C = S.getAs(); - // expected-note@-1 {{Assuming dynamic cast to 'Circle' fails}} - // expected-note@-2 {{'C' initialized to a null pointer value}} - - (void)(1 / (bool)C); - // expected-note@-1 {{Division by zero}} - // expected-warning@-2 {{Division by zero}} - // logic-warning@-3 {{Division by zero}} -} -} // namespace test_notes diff --git a/clang/test/Analysis/dump_egraph.cpp b/clang/test/Analysis/dump_egraph.cpp index f5db3d142eed6..a56f061949270 100644 --- a/clang/test/Analysis/dump_egraph.cpp +++ b/clang/test/Analysis/dump_egraph.cpp @@ -24,4 +24,5 @@ void foo() { // CHECK: \"cluster\": \"t\", \"pointer\": \"{{0x[0-9a-f]+}}\", \"items\": [\l        \{ \"kind\": \"Default\", \"offset\": 0, \"value\": \"conj_$2\{int, LC5, no stmt, #1\}\" -// CHECK: \"dynamic_types\": [\l\{ \"region\": \"HeapSymRegion\{conj_$1\{struct S *, LC1, S{{[0-9]+}}, #1\}\}\", \"dyn_type\": \"struct S\", \"sub_classable\": false\}\l +// CHECK: \"dynamic_types\": [\l      \{ \"region\": \"HeapSymRegion\{conj_$1\{struct S *, LC1, S{{[0-9]+}}, #1\}\}\", \"dyn_type\": \"struct S\", \"sub_classable\": false \}\l + diff --git a/clang/test/Analysis/expr-inspection.c b/clang/test/Analysis/expr-inspection.c index c2bcafe068966..6d9ea4bb26c1b 100644 --- a/clang/test/Analysis/expr-inspection.c +++ b/clang/test/Analysis/expr-inspection.c @@ -38,6 +38,7 @@ void foo(int x) { // CHECK-NEXT: { "symbol": "reg_$0", "range": "{ [-2147483648, 13] }" } // CHECK-NEXT: ], // CHECK-NEXT: "dynamic_types": null, +// CHECK-NEXT: "dynamic_casts": null, // CHECK-NEXT: "constructing_objects": null, // CHECK-NEXT: "checker_messages": null // CHECK-NEXT: } From 80c6a0d0bfed45ad165198f93d117be70be21fb3 Mon Sep 17 00:00:00 2001 From: Csaba Dabis Date: Thu, 22 Aug 2019 00:36:42 +0000 Subject: [PATCH 121/181] [analyzer] CastValueChecker: Rewrite dead header hotfix llvm-svn: 369607 (cherry picked from commit e4bf456fcef23b7c9ec9968cdf1df36b591755ec) --- .../StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp | 2 +- clang/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp | 2 +- clang/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp | 2 +- .../Checkers/UninitializedObject/UninitializedObjectChecker.cpp | 2 +- .../Checkers/UninitializedObject/UninitializedPointee.cpp | 2 +- clang/lib/StaticAnalyzer/Core/CallEvent.cpp | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp index 2059ff6c0e42b..3acc3ac362d78 100644 --- a/clang/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp @@ -27,7 +27,7 @@ #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeMap.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" using namespace clang; diff --git a/clang/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp index 7dc4b93d2f88b..4c360f8a300b1 100644 --- a/clang/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp @@ -21,7 +21,7 @@ #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeMap.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" diff --git a/clang/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp index 600458a743ea1..ed56a095a1c57 100644 --- a/clang/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp @@ -71,7 +71,7 @@ #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeMap.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h" #include diff --git a/clang/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedObjectChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedObjectChecker.cpp index 9d608c12d19be..ff02123f45146 100644 --- a/clang/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedObjectChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedObjectChecker.cpp @@ -24,7 +24,7 @@ #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeMap.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h" using namespace clang; using namespace clang::ento; diff --git a/clang/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedPointee.cpp b/clang/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedPointee.cpp index a5dc250104f32..54d4e48ab8d9d 100644 --- a/clang/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedPointee.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedPointee.cpp @@ -18,7 +18,7 @@ #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeMap.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h" using namespace clang; using namespace clang::ento; diff --git a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp index 316146e2dd210..7226ccb4d7533 100644 --- a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp +++ b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp @@ -37,7 +37,7 @@ #include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeInfo.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeMap.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h" #include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState_Fwd.h" From f33973931a07d149eeee2ea63093be1f53eb73c5 Mon Sep 17 00:00:00 2001 From: Csaba Dabis Date: Thu, 22 Aug 2019 01:41:06 +0000 Subject: [PATCH 122/181] [analyzer] CastValueChecker: Try to fix the buildbots llvm-svn: 369609 (cherry picked from commit 22dc44ff896a34a94c7ed6d3bf7b577b98e34fbd) --- clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp index e7472ab156b76..1580f7f31cee2 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp @@ -110,7 +110,7 @@ static const NoteTag *getNoteTag(CheckerContext &C, Object = Object->IgnoreParenImpCasts(); return C.getNoteTag( - [=] { + [=]() -> std::string { SmallString<128> Msg; llvm::raw_svector_ostream Out(Msg); From 03764056475fa87080463211ba065761e8298552 Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Thu, 22 Aug 2019 02:44:19 +0000 Subject: [PATCH 123/181] [analyzer] Don't track the condition of foreach loops As discussed on the mailing list, notes originating from the tracking of foreach loop conditions are always meaningless. Differential Revision: https://reviews.llvm.org/D66131 llvm-svn: 369613 (cherry picked from commit 58eb033a497747331477f8aac36408f24ca0f628) --- .../Core/BugReporterVisitors.cpp | 5 +++ .../track-control-dependency-conditions.cpp | 33 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp index 0634ed44165a9..e628aeae7a1a2 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp @@ -1800,6 +1800,11 @@ PathDiagnosticPieceRef TrackControlDependencyCondBRVisitor::VisitNode( return nullptr; if (ControlDeps.isControlDependent(OriginB, NB)) { + // We don't really want to explain for range loops. Evidence suggests that + // the only thing that leads to is the addition of calls to operator!=. + if (isa(NB->getTerminator())) + return nullptr; + if (const Expr *Condition = NB->getLastCondition()) { // Keeping track of the already tracked conditions on a visitor level // isn't sufficient, because a new visitor is created for each tracked diff --git a/clang/test/Analysis/track-control-dependency-conditions.cpp b/clang/test/Analysis/track-control-dependency-conditions.cpp index e95cc7cbb1ec5..bd391abdcc0c7 100644 --- a/clang/test/Analysis/track-control-dependency-conditions.cpp +++ b/clang/test/Analysis/track-control-dependency-conditions.cpp @@ -407,6 +407,39 @@ void f() { } } // end of namespace condition_written_in_nested_stackframe_before_assignment +namespace dont_explain_foreach_loops { + +struct Iterator { + int *pos; + bool operator!=(Iterator other) const { + return pos && other.pos && pos != other.pos; + } + int operator*(); + Iterator operator++(); +}; + +struct Container { + Iterator begin(); + Iterator end(); +}; + +void f(Container Cont) { + int flag = 0; + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} + for (int i : Cont) + if (i) // expected-note-re {{{{^}}Assuming 'i' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'i'{{$}}}} + flag = i; + + if (flag) // expected-note-re{{{{^}}'flag' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} +} // end of namespace dont_explain_foreach_loops + namespace condition_lambda_capture_by_reference_last_write { int getInt(); From 818dd65b4f9201d8cf2f5fd26060b979ce2b6f30 Mon Sep 17 00:00:00 2001 From: Csaba Dabis Date: Thu, 22 Aug 2019 02:57:59 +0000 Subject: [PATCH 124/181] [analyzer] CastValueChecker: Model isa(), isa_and_nonnull() Summary: - Reviewed By: NoQ Differential Revision: https://reviews.llvm.org/D66423 llvm-svn: 369615 (cherry picked from commit 4d71600c113243b0f3eb04c2308f0eda60bfccaa) --- .../Checkers/CastValueChecker.cpp | 105 +++++++++++++++++- clang/test/Analysis/Inputs/llvm.h | 6 + clang/test/Analysis/cast-value-logic.cpp | 7 +- clang/test/Analysis/cast-value-notes.cpp | 25 +++-- 4 files changed, 130 insertions(+), 13 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp index 1580f7f31cee2..5c4daa3e428be 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp @@ -16,6 +16,7 @@ // //===----------------------------------------------------------------------===// +#include "clang/AST/DeclTemplate.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" @@ -30,7 +31,7 @@ using namespace ento; namespace { class CastValueChecker : public Checker { - enum class CallKind { Function, Method }; + enum class CallKind { Function, Method, InstanceOf }; using CastCheck = std::function { // // 4) castAs: Has no parameter, the return value is non-null. // 5) getAs: Has no parameter, the return value is null or non-null. + // + // We have two cases to check the parameter is an instance of the given type. + // 1) isa: The parameter is non-null, returns boolean. + // 2) isa_and_nonnull: The parameter is null or non-null, returns boolean. bool evalCall(const CallEvent &Call, CheckerContext &C) const; void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const; @@ -63,7 +68,11 @@ class CastValueChecker : public Checker { {{{"clang", "castAs"}, 0}, {&CastValueChecker::evalCastAs, CallKind::Method}}, {{{"clang", "getAs"}, 0}, - {&CastValueChecker::evalGetAs, CallKind::Method}}}; + {&CastValueChecker::evalGetAs, CallKind::Method}}, + {{{"llvm", "isa"}, 1}, + {&CastValueChecker::evalIsa, CallKind::InstanceOf}}, + {{{"llvm", "isa_and_nonnull"}, 1}, + {&CastValueChecker::evalIsaAndNonNull, CallKind::InstanceOf}}}; void evalCast(const CallEvent &Call, DefinedOrUnknownSVal DV, CheckerContext &C) const; @@ -77,6 +86,10 @@ class CastValueChecker : public Checker { CheckerContext &C) const; void evalGetAs(const CallEvent &Call, DefinedOrUnknownSVal DV, CheckerContext &C) const; + void evalIsa(const CallEvent &Call, DefinedOrUnknownSVal DV, + CheckerContext &C) const; + void evalIsaAndNonNull(const CallEvent &Call, DefinedOrUnknownSVal DV, + CheckerContext &C) const; }; } // namespace @@ -189,6 +202,42 @@ static void addCastTransition(const CallEvent &Call, DefinedOrUnknownSVal DV, getNoteTag(C, CastInfo, CastToTy, Object, CastSucceeds, IsKnownCast)); } +static void addInstanceOfTransition(const CallEvent &Call, + DefinedOrUnknownSVal DV, + ProgramStateRef State, CheckerContext &C, + bool IsInstanceOf) { + const FunctionDecl *FD = Call.getDecl()->getAsFunction(); + QualType CastToTy = FD->getTemplateSpecializationArgs()->get(0).getAsType(); + QualType CastFromTy = getRecordType(Call.parameters()[0]->getType()); + + const MemRegion *MR = DV.getAsRegion(); + const DynamicCastInfo *CastInfo = + getDynamicCastInfo(State, MR, CastFromTy, CastToTy); + + bool CastSucceeds; + if (CastInfo) + CastSucceeds = IsInstanceOf && CastInfo->succeeds(); + else + CastSucceeds = IsInstanceOf || CastFromTy == CastToTy; + + if (isInfeasibleCast(CastInfo, CastSucceeds)) { + C.generateSink(State, C.getPredecessor()); + return; + } + + // Store the type and the cast information. + bool IsKnownCast = CastInfo || CastFromTy == CastToTy; + if (!IsKnownCast) + State = setDynamicTypeAndCastInfo(State, MR, CastFromTy, CastToTy, + Call.getResultType(), IsInstanceOf); + + C.addTransition( + State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), + C.getSValBuilder().makeTruthVal(CastSucceeds)), + getNoteTag(C, CastInfo, CastToTy, Call.getArgExpr(0), CastSucceeds, + IsKnownCast)); +} + //===----------------------------------------------------------------------===// // Evaluating cast, dyn_cast, cast_or_null, dyn_cast_or_null. //===----------------------------------------------------------------------===// @@ -277,6 +326,41 @@ void CastValueChecker::evalGetAs(const CallEvent &Call, DefinedOrUnknownSVal DV, evalZeroParamNullReturn(Call, DV, C); } +//===----------------------------------------------------------------------===// +// Evaluating isa, isa_and_nonnull. +//===----------------------------------------------------------------------===// + +void CastValueChecker::evalIsa(const CallEvent &Call, DefinedOrUnknownSVal DV, + CheckerContext &C) const { + ProgramStateRef NonNullState, NullState; + std::tie(NonNullState, NullState) = C.getState()->assume(DV); + + if (NonNullState) { + addInstanceOfTransition(Call, DV, NonNullState, C, /*IsInstanceOf=*/true); + addInstanceOfTransition(Call, DV, NonNullState, C, /*IsInstanceOf=*/false); + } + + if (NullState) { + C.generateSink(NullState, C.getPredecessor()); + } +} + +void CastValueChecker::evalIsaAndNonNull(const CallEvent &Call, + DefinedOrUnknownSVal DV, + CheckerContext &C) const { + ProgramStateRef NonNullState, NullState; + std::tie(NonNullState, NullState) = C.getState()->assume(DV); + + if (NonNullState) { + addInstanceOfTransition(Call, DV, NonNullState, C, /*IsInstanceOf=*/true); + addInstanceOfTransition(Call, DV, NonNullState, C, /*IsInstanceOf=*/false); + } + + if (NullState) { + addInstanceOfTransition(Call, DV, NullState, C, /*IsInstanceOf=*/false); + } +} + //===----------------------------------------------------------------------===// // Main logic to evaluate a call. //===----------------------------------------------------------------------===// @@ -287,12 +371,14 @@ bool CastValueChecker::evalCall(const CallEvent &Call, if (!Lookup) return false; + const CastCheck &Check = Lookup->first; + CallKind Kind = Lookup->second; + // We need to obtain the record type of the call's result to model it. - if (!getRecordType(Call.getResultType())->isRecordType()) + if (Kind != CallKind::InstanceOf && + !getRecordType(Call.getResultType())->isRecordType()) return false; - const CastCheck &Check = Lookup->first; - CallKind Kind = Lookup->second; Optional DV; switch (Kind) { @@ -304,6 +390,15 @@ bool CastValueChecker::evalCall(const CallEvent &Call, DV = Call.getArgSVal(0).getAs(); break; } + case CallKind::InstanceOf: { + // We need to obtain the only template argument to determinte the type. + const FunctionDecl *FD = Call.getDecl()->getAsFunction(); + if (!FD || !FD->getTemplateSpecializationArgs()) + return false; + + DV = Call.getArgSVal(0).getAs(); + break; + } case CallKind::Method: const auto *InstanceCall = dyn_cast(&Call); if (!InstanceCall) diff --git a/clang/test/Analysis/Inputs/llvm.h b/clang/test/Analysis/Inputs/llvm.h index 9d465c65fda98..d77b4648107d2 100644 --- a/clang/test/Analysis/Inputs/llvm.h +++ b/clang/test/Analysis/Inputs/llvm.h @@ -16,4 +16,10 @@ template const X *dyn_cast_or_null(Y *Value); template const X *dyn_cast_or_null(Y &Value); + +template +bool isa(Y Value); + +template +bool isa_and_nonnull(Y Value); } // namespace llvm diff --git a/clang/test/Analysis/cast-value-logic.cpp b/clang/test/Analysis/cast-value-logic.cpp index 32001d8548a22..e001e4a65e822 100644 --- a/clang/test/Analysis/cast-value-logic.cpp +++ b/clang/test/Analysis/cast-value-logic.cpp @@ -23,11 +23,16 @@ class Circle : public Shape {}; using namespace llvm; using namespace clang; -void test_regions(const Shape *A, const Shape *B) { +void test_regions_dyn_cast(const Shape *A, const Shape *B) { if (dyn_cast(A) && !dyn_cast(B)) clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}} } +void test_regions_isa(const Shape *A, const Shape *B) { + if (isa(A) && !isa(B)) + clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}} +} + namespace test_cast { void evalLogic(const Shape *S) { const Circle *C = cast(S); diff --git a/clang/test/Analysis/cast-value-notes.cpp b/clang/test/Analysis/cast-value-notes.cpp index 77801d699350e..6e7f6b01af7b7 100644 --- a/clang/test/Analysis/cast-value-notes.cpp +++ b/clang/test/Analysis/cast-value-notes.cpp @@ -1,5 +1,5 @@ // RUN: %clang_analyze_cc1 \ -// RUN: -analyzer-checker=core,apiModeling.llvm.CastValue \ +// RUN: -analyzer-checker=core,apiModeling.llvm.CastValue,debug.ExprInspection\ // RUN: -analyzer-output=text -verify %s #include "Inputs/llvm.h" @@ -43,16 +43,21 @@ void evalNonNullParamNonNullReturnReference(const Shape &S) { return; } - if (dyn_cast_or_null(C)) { + if (isa(C)) { // expected-note@-1 {{'C' is not a 'Triangle'}} // expected-note@-2 {{Taking false branch}} return; } - (void)(1 / !C); - // expected-note@-1 {{'C' is non-null}} - // expected-note@-2 {{Division by zero}} - // expected-warning@-3 {{Division by zero}} + if (isa(C)) { + // expected-note@-1 {{'C' is a 'Circle'}} + // expected-note@-2 {{Taking true branch}} + + (void)(1 / !C); + // expected-note@-1 {{'C' is non-null}} + // expected-note@-2 {{Division by zero}} + // expected-warning@-3 {{Division by zero}} + } } void evalNonNullParamNonNullReturn(const Shape *S) { @@ -60,7 +65,13 @@ void evalNonNullParamNonNullReturn(const Shape *S) { // expected-note@-1 {{'S' is a 'Circle'}} // expected-note@-2 {{'C' initialized here}} - if (!cast(C)) { + if (!isa(C)) { + // expected-note@-1 {{Assuming 'C' is a 'Triangle'}} + // expected-note@-2 {{Taking false branch}} + return; + } + + if (!isa(C)) { // expected-note@-1 {{'C' is a 'Triangle'}} // expected-note@-2 {{Taking false branch}} return; From aaa2160a54862b776730ea3c5b46a8523955e5d6 Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Thu, 22 Aug 2019 03:08:48 +0000 Subject: [PATCH 125/181] [analyzer] Enable control dependency condition tracking by default This patch concludes my GSoC'19 project by enabling track-conditions by default. Differential Revision: https://reviews.llvm.org/D66381 llvm-svn: 369616 (cherry picked from commit 0f9e530c0f4d2449b093f08b7d46bd2ef174578c) --- clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.def | 2 +- clang/test/Analysis/analyzer-config.c | 2 +- clang/test/Analysis/diagnostics/no-store-func-path-notes.m | 1 + clang/test/Analysis/return-value-guaranteed.cpp | 3 +++ clang/test/Analysis/track-control-dependency-conditions.cpp | 2 ++ 5 files changed, 8 insertions(+), 2 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.def b/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.def index 95dc3524ce390..9a0225e5a0810 100644 --- a/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.def +++ b/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.def @@ -294,7 +294,7 @@ ANALYZER_OPTION(bool, DisplayCTUProgress, "display-ctu-progress", ANALYZER_OPTION(bool, ShouldTrackConditions, "track-conditions", "Whether to track conditions that are a control dependency of " "an already tracked variable.", - false) + true) ANALYZER_OPTION(bool, ShouldTrackConditionsDebug, "track-conditions-debug", "Whether to place an event at each tracked condition.", diff --git a/clang/test/Analysis/analyzer-config.c b/clang/test/Analysis/analyzer-config.c index 99e1173d5d8c1..63d22596dddf2 100644 --- a/clang/test/Analysis/analyzer-config.c +++ b/clang/test/Analysis/analyzer-config.c @@ -87,7 +87,7 @@ // CHECK-NEXT: suppress-c++-stdlib = true // CHECK-NEXT: suppress-inlined-defensive-checks = true // CHECK-NEXT: suppress-null-return-paths = true -// CHECK-NEXT: track-conditions = false +// CHECK-NEXT: track-conditions = true // CHECK-NEXT: track-conditions-debug = false // CHECK-NEXT: unix.DynamicMemoryModeling:Optimistic = false // CHECK-NEXT: unroll-loops = false diff --git a/clang/test/Analysis/diagnostics/no-store-func-path-notes.m b/clang/test/Analysis/diagnostics/no-store-func-path-notes.m index 0136389efe617..6ef162e4ecd50 100644 --- a/clang/test/Analysis/diagnostics/no-store-func-path-notes.m +++ b/clang/test/Analysis/diagnostics/no-store-func-path-notes.m @@ -16,6 +16,7 @@ - (int)initVar:(int *)var param:(int)param { return 0; } return 1; // expected-note{{Returning without writing to '*var'}} + // expected-note@-1{{Returning the value 1, which participates in a condition later}} } @end diff --git a/clang/test/Analysis/return-value-guaranteed.cpp b/clang/test/Analysis/return-value-guaranteed.cpp index 367a8e5906afc..2d04a264ad813 100644 --- a/clang/test/Analysis/return-value-guaranteed.cpp +++ b/clang/test/Analysis/return-value-guaranteed.cpp @@ -24,6 +24,7 @@ bool parseFoo(Foo &F) { // class-note@-1 {{The value 0 is assigned to 'F.Field'}} return !MCAsmParser::Error(); // class-note@-1 {{'MCAsmParser::Error' returns true}} + // class-note@-2 {{Returning zero, which participates in a condition later}} } bool parseFile() { @@ -57,6 +58,7 @@ namespace test_break { struct MCAsmParser { static bool Error() { return false; // class-note {{'MCAsmParser::Error' returns false}} + // class-note@-1 {{Returning zero, which participates in a condition later}} } }; @@ -72,6 +74,7 @@ bool parseFoo(Foo &F) { return MCAsmParser::Error(); // class-note@-1 {{Calling 'MCAsmParser::Error'}} // class-note@-2 {{Returning from 'MCAsmParser::Error'}} + // class-note@-3 {{Returning zero, which participates in a condition later}} } bool parseFile() { diff --git a/clang/test/Analysis/track-control-dependency-conditions.cpp b/clang/test/Analysis/track-control-dependency-conditions.cpp index bd391abdcc0c7..737620f4c31e4 100644 --- a/clang/test/Analysis/track-control-dependency-conditions.cpp +++ b/clang/test/Analysis/track-control-dependency-conditions.cpp @@ -6,6 +6,7 @@ // RUN: not %clang_analyze_cc1 -verify %s \ // RUN: -analyzer-checker=core \ +// RUN: -analyzer-config track-conditions=false \ // RUN: -analyzer-config track-conditions-debug=true \ // RUN: 2>&1 | FileCheck %s -check-prefix=CHECK-INVALID-DEBUG @@ -22,6 +23,7 @@ // RUN: %clang_analyze_cc1 %s -verify \ // RUN: -analyzer-output=text \ +// RUN: -analyzer-config track-conditions=false \ // RUN: -analyzer-checker=core namespace example_1 { From 5af8cb0599b24b5e0b2f7f734727a86ad03021f2 Mon Sep 17 00:00:00 2001 From: Haojian Wu Date: Thu, 22 Aug 2019 08:49:41 +0000 Subject: [PATCH 126/181] Remove an unused function, suppress -Wunused-function warning. llvm-svn: 369629 (cherry picked from commit fcedc6a61bd152324bc6805bb8f0e0163f2af7d1) --- clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp index 5c4daa3e428be..8724e4acbc4ce 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp @@ -51,7 +51,6 @@ class CastValueChecker : public Checker { // 1) isa: The parameter is non-null, returns boolean. // 2) isa_and_nonnull: The parameter is null or non-null, returns boolean. bool evalCall(const CallEvent &Call, CheckerContext &C) const; - void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const; private: // These are known in the LLVM project. The pairs are in the following form: @@ -415,11 +414,6 @@ bool CastValueChecker::evalCall(const CallEvent &Call, return true; } -void CastValueChecker::checkDeadSymbols(SymbolReaper &SR, - CheckerContext &C) const { - C.addTransition(removeDeadCasts(C.getState(), SR)); -} - void ento::registerCastValueChecker(CheckerManager &Mgr) { Mgr.registerChecker(); } From cfe5b5ee68fac1491c73b721c79dc1c2d16c8214 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Fri, 23 Aug 2019 03:23:55 +0000 Subject: [PATCH 127/181] [analyzer] DynamicTypeInfo: Avoid putting null regions into dynamic typemap. Fixes a crash. llvm-svn: 369726 (cherry picked from commit 0900b77db27fdbc7cd16e6c778643a4db348d912) --- clang/lib/StaticAnalyzer/Core/DynamicType.cpp | 3 +++ clang/test/Analysis/cast-value-logic.cpp | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/clang/lib/StaticAnalyzer/Core/DynamicType.cpp b/clang/lib/StaticAnalyzer/Core/DynamicType.cpp index a9e281ec9e589..e4ff132c6ed9a 100644 --- a/clang/lib/StaticAnalyzer/Core/DynamicType.cpp +++ b/clang/lib/StaticAnalyzer/Core/DynamicType.cpp @@ -93,6 +93,9 @@ ProgramStateRef setDynamicTypeAndCastInfo(ProgramStateRef State, QualType CastFromTy, QualType CastToTy, QualType ResultTy, bool CastSucceeds) { + if (!MR) + return State; + if (CastSucceeds) State = State->set(MR, ResultTy); diff --git a/clang/test/Analysis/cast-value-logic.cpp b/clang/test/Analysis/cast-value-logic.cpp index e001e4a65e822..0d2255a3aba26 100644 --- a/clang/test/Analysis/cast-value-logic.cpp +++ b/clang/test/Analysis/cast-value-logic.cpp @@ -131,3 +131,8 @@ void evalLogic(const Shape *S) { } } // namespace test_get_as +namespace crashes { +void test_non_reference_null_region_crash(Shape s) { + cast(s); // no-crash +} +} // namespace crashes From 00e714cee8ec1a2d326b059ff4e6e52ced63f72d Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Fri, 23 Aug 2019 03:23:58 +0000 Subject: [PATCH 128/181] [analyzer] CastValueChecker: Avoid modeling casts between objects. Our method only works correctly when casting a pointer to a pointer or a reference to a reference. Fixes a crash. llvm-svn: 369727 (cherry picked from commit af992e6d0137d798e6b2cd27737b7a73300676a1) --- .../lib/StaticAnalyzer/Checkers/CastValueChecker.cpp | 9 +++++++-- clang/test/Analysis/Inputs/llvm.h | 5 +++++ clang/test/Analysis/cast-value-logic.cpp | 6 ++++++ clang/test/Analysis/cast-value-notes.cpp | 11 ++++++----- clang/test/Analysis/cast-value-state-dump.cpp | 6 +++--- 5 files changed, 27 insertions(+), 10 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp index 8724e4acbc4ce..cd3b70db9a5f4 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp @@ -382,8 +382,13 @@ bool CastValueChecker::evalCall(const CallEvent &Call, switch (Kind) { case CallKind::Function: { - // We need to obtain the record type of the call's parameter to model it. - if (!getRecordType(Call.parameters()[0]->getType())->isRecordType()) + // We only model casts from pointers to pointers or from references + // to references. Other casts are most likely specialized and we + // cannot model them. + QualType ParamT = Call.parameters()[0]->getType(); + QualType ResultT = Call.getResultType(); + if (!(ParamT->isPointerType() && ResultT->isPointerType()) && + !(ParamT->isReferenceType() && ResultT->isReferenceType())) return false; DV = Call.getArgSVal(0).getAs(); diff --git a/clang/test/Analysis/Inputs/llvm.h b/clang/test/Analysis/Inputs/llvm.h index d77b4648107d2..c9d66ba2374d3 100644 --- a/clang/test/Analysis/Inputs/llvm.h +++ b/clang/test/Analysis/Inputs/llvm.h @@ -1,5 +1,7 @@ #pragma clang system_header +#include "system-header-simulator-cxx.h" + namespace llvm { template const X *cast(Y Value); @@ -22,4 +24,7 @@ bool isa(Y Value); template bool isa_and_nonnull(Y Value); + +template +std::unique_ptr cast(std::unique_ptr &&Value); } // namespace llvm diff --git a/clang/test/Analysis/cast-value-logic.cpp b/clang/test/Analysis/cast-value-logic.cpp index 0d2255a3aba26..c5083ef57c9cf 100644 --- a/clang/test/Analysis/cast-value-logic.cpp +++ b/clang/test/Analysis/cast-value-logic.cpp @@ -135,4 +135,10 @@ namespace crashes { void test_non_reference_null_region_crash(Shape s) { cast(s); // no-crash } + +void test_non_reference_temporary_crash() { + extern std::unique_ptr foo(); + auto P = foo(); + auto Q = cast(std::move(P)); // no-crash +} } // namespace crashes diff --git a/clang/test/Analysis/cast-value-notes.cpp b/clang/test/Analysis/cast-value-notes.cpp index 6e7f6b01af7b7..f92ba90336a7a 100644 --- a/clang/test/Analysis/cast-value-notes.cpp +++ b/clang/test/Analysis/cast-value-notes.cpp @@ -27,9 +27,9 @@ void evalReferences(const Shape &S) { } void evalNonNullParamNonNullReturnReference(const Shape &S) { + // Unmodeled cast from reference to pointer. const auto *C = dyn_cast_or_null(S); - // expected-note@-1 {{Assuming 'S' is a 'Circle'}} - // expected-note@-2 {{'C' initialized here}} + // expected-note@-1 {{'C' initialized here}} if (!dyn_cast_or_null(C)) { // expected-note@-1 {{'C' is a 'Circle'}} @@ -132,10 +132,11 @@ void evalZeroParamNonNullReturn(const Shape &S) { // expected-warning@-3 {{Division by zero}} } -void evalZeroParamNullReturn(const Shape &S) { - const auto *C = S.getAs(); +void evalZeroParamNullReturn(const Shape *S) { + const auto &C = S->getAs(); // expected-note@-1 {{Assuming 'S' is not a 'Circle'}} - // expected-note@-2 {{'C' initialized to a null pointer value}} + // expected-note@-2 {{Storing null pointer value}} + // expected-note@-3 {{'C' initialized here}} if (!dyn_cast_or_null(S)) { // expected-note@-1 {{Assuming 'S' is a 'Triangle'}} diff --git a/clang/test/Analysis/cast-value-state-dump.cpp b/clang/test/Analysis/cast-value-state-dump.cpp index fd679984d6b2b..890fa18933561 100644 --- a/clang/test/Analysis/cast-value-state-dump.cpp +++ b/clang/test/Analysis/cast-value-state-dump.cpp @@ -16,7 +16,7 @@ class Square : public Shape {}; using namespace llvm; using namespace clang; -void evalNonNullParamNonNullReturnReference(const Shape &S) { +void evalNonNullParamNonNullReturn(const Shape *S) { const auto *C = dyn_cast_or_null(S); // expected-note@-1 {{Assuming 'S' is a 'Circle'}} // expected-note@-2 {{'C' initialized here}} @@ -31,10 +31,10 @@ void evalNonNullParamNonNullReturnReference(const Shape &S) { clang_analyzer_printState(); // CHECK: "dynamic_types": [ - // CHECK-NEXT: { "region": "SymRegion{reg_$0}", "dyn_type": "const class clang::Circle", "sub_classable": true } + // CHECK-NEXT: { "region": "SymRegion{reg_$0}", "dyn_type": "const class clang::Circle", "sub_classable": true } // CHECK-NEXT: ], // CHECK-NEXT: "dynamic_casts": [ - // CHECK: { "region": "SymRegion{reg_$0}", "casts": [ + // CHECK: { "region": "SymRegion{reg_$0}", "casts": [ // CHECK-NEXT: { "from": "struct clang::Shape", "to": "class clang::Circle", "kind": "success" }, // CHECK-NEXT: { "from": "struct clang::Shape", "to": "class clang::Square", "kind": "fail" } // CHECK-NEXT: ]} From fd6e1ad08d092cd336ea2bb5f9964ee16b30944e Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Fri, 23 Aug 2019 03:24:01 +0000 Subject: [PATCH 129/181] [analyzer] CastValueChecker: Provide DynamicTypeMap with pointer types only. The idea to drop this requirement is good, but for now every other user of DynamicTypeInfo expects pointer types. Fixes a crash. llvm-svn: 369728 (cherry picked from commit 62a76d0ae349f53d4ebd911412591ec1bb74f4e7) --- .../Core/PathSensitive/DynamicType.h | 2 +- .../Checkers/CastValueChecker.cpp | 45 +++++++++---------- clang/lib/StaticAnalyzer/Core/DynamicType.cpp | 9 ++-- clang/test/Analysis/cast-value-logic.cpp | 8 ++++ clang/test/Analysis/cast-value-state-dump.cpp | 4 +- 5 files changed, 37 insertions(+), 31 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h index f300af2c0b871..356401d77561c 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h @@ -54,7 +54,7 @@ ProgramStateRef setDynamicTypeInfo(ProgramStateRef State, const MemRegion *MR, ProgramStateRef setDynamicTypeAndCastInfo(ProgramStateRef State, const MemRegion *MR, QualType CastFromTy, - QualType CastToTy, QualType ResultTy, + QualType CastToTy, bool IsCastSucceeds); /// Removes the dead type informations from \p State. diff --git a/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp index cd3b70db9a5f4..a0cebd6ab8562 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp @@ -92,18 +92,6 @@ class CastValueChecker : public Checker { }; } // namespace -static QualType getRecordType(QualType Ty) { - Ty = Ty.getCanonicalType(); - - if (Ty->isPointerType()) - Ty = Ty->getPointeeType(); - - if (Ty->isReferenceType()) - Ty = Ty.getNonReferenceType(); - - return Ty.getUnqualifiedType(); -} - static bool isInfeasibleCast(const DynamicCastInfo *CastInfo, bool CastSucceeds) { if (!CastInfo) @@ -117,8 +105,8 @@ static const NoteTag *getNoteTag(CheckerContext &C, QualType CastToTy, const Expr *Object, bool CastSucceeds, bool IsKnownCast) { std::string CastToName = - CastInfo ? CastInfo->to()->getAsCXXRecordDecl()->getNameAsString() - : CastToTy->getAsCXXRecordDecl()->getNameAsString(); + CastInfo ? CastInfo->to()->getPointeeCXXRecordDecl()->getNameAsString() + : CastToTy->getPointeeCXXRecordDecl()->getNameAsString(); Object = Object->IgnoreParenImpCasts(); return C.getNoteTag( @@ -160,14 +148,14 @@ static void addCastTransition(const CallEvent &Call, DefinedOrUnknownSVal DV, const Expr *Object; QualType CastFromTy; - QualType CastToTy = getRecordType(Call.getResultType()); + QualType CastToTy = Call.getResultType(); if (Call.getNumArgs() > 0) { Object = Call.getArgExpr(0); - CastFromTy = getRecordType(Call.parameters()[0]->getType()); + CastFromTy = Call.parameters()[0]->getType(); } else { Object = cast(&Call)->getCXXThisExpr(); - CastFromTy = getRecordType(Object->getType()); + CastFromTy = Object->getType(); } const MemRegion *MR = DV.getAsRegion(); @@ -193,7 +181,7 @@ static void addCastTransition(const CallEvent &Call, DefinedOrUnknownSVal DV, bool IsKnownCast = CastInfo || IsCheckedCast || CastFromTy == CastToTy; if (!IsKnownCast || IsCheckedCast) State = setDynamicTypeAndCastInfo(State, MR, CastFromTy, CastToTy, - Call.getResultType(), CastSucceeds); + CastSucceeds); SVal V = CastSucceeds ? DV : C.getSValBuilder().makeNull(); C.addTransition( @@ -206,8 +194,20 @@ static void addInstanceOfTransition(const CallEvent &Call, ProgramStateRef State, CheckerContext &C, bool IsInstanceOf) { const FunctionDecl *FD = Call.getDecl()->getAsFunction(); + QualType CastFromTy = Call.parameters()[0]->getType(); QualType CastToTy = FD->getTemplateSpecializationArgs()->get(0).getAsType(); - QualType CastFromTy = getRecordType(Call.parameters()[0]->getType()); + if (CastFromTy->isPointerType()) + CastToTy = C.getASTContext().getPointerType(CastToTy); + else if (CastFromTy->isLValueReferenceType() && + CastFromTy.isConstQualified()) { + CastToTy.addConst(); + CastToTy = C.getASTContext().getLValueReferenceType(CastToTy); + } else if (CastFromTy->isLValueReferenceType()) + CastToTy = C.getASTContext().getLValueReferenceType(CastToTy); + else if (CastFromTy->isRValueReferenceType()) + CastToTy = C.getASTContext().getRValueReferenceType(CastToTy); + else + return; const MemRegion *MR = DV.getAsRegion(); const DynamicCastInfo *CastInfo = @@ -228,7 +228,7 @@ static void addInstanceOfTransition(const CallEvent &Call, bool IsKnownCast = CastInfo || CastFromTy == CastToTy; if (!IsKnownCast) State = setDynamicTypeAndCastInfo(State, MR, CastFromTy, CastToTy, - Call.getResultType(), IsInstanceOf); + IsInstanceOf); C.addTransition( State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), @@ -373,11 +373,6 @@ bool CastValueChecker::evalCall(const CallEvent &Call, const CastCheck &Check = Lookup->first; CallKind Kind = Lookup->second; - // We need to obtain the record type of the call's result to model it. - if (Kind != CallKind::InstanceOf && - !getRecordType(Call.getResultType())->isRecordType()) - return false; - Optional DV; switch (Kind) { diff --git a/clang/lib/StaticAnalyzer/Core/DynamicType.cpp b/clang/lib/StaticAnalyzer/Core/DynamicType.cpp index e4ff132c6ed9a..a78e0e05e903b 100644 --- a/clang/lib/StaticAnalyzer/Core/DynamicType.cpp +++ b/clang/lib/StaticAnalyzer/Core/DynamicType.cpp @@ -91,13 +91,16 @@ ProgramStateRef setDynamicTypeInfo(ProgramStateRef State, const MemRegion *MR, ProgramStateRef setDynamicTypeAndCastInfo(ProgramStateRef State, const MemRegion *MR, QualType CastFromTy, - QualType CastToTy, QualType ResultTy, + QualType CastToTy, bool CastSucceeds) { if (!MR) return State; - if (CastSucceeds) - State = State->set(MR, ResultTy); + if (CastSucceeds) { + assert((CastToTy->isAnyPointerType() || CastToTy->isReferenceType()) && + "DynamicTypeInfo should always be a pointer."); + State = State->set(MR, CastToTy); + } DynamicCastInfo::CastResult ResultKind = CastSucceeds ? DynamicCastInfo::CastResult::Success diff --git a/clang/test/Analysis/cast-value-logic.cpp b/clang/test/Analysis/cast-value-logic.cpp index c5083ef57c9cf..531772825f970 100644 --- a/clang/test/Analysis/cast-value-logic.cpp +++ b/clang/test/Analysis/cast-value-logic.cpp @@ -15,6 +15,8 @@ struct Shape { template const T *getAs() const; + + virtual double area(); }; class Triangle : public Shape {}; class Circle : public Shape {}; @@ -141,4 +143,10 @@ void test_non_reference_temporary_crash() { auto P = foo(); auto Q = cast(std::move(P)); // no-crash } + +double test_virtual_method_after_call(Shape *S) { + if (isa(S)) + return S->area(); + return S->area() / 2; +} } // namespace crashes diff --git a/clang/test/Analysis/cast-value-state-dump.cpp b/clang/test/Analysis/cast-value-state-dump.cpp index 890fa18933561..b8152d46da47d 100644 --- a/clang/test/Analysis/cast-value-state-dump.cpp +++ b/clang/test/Analysis/cast-value-state-dump.cpp @@ -35,8 +35,8 @@ void evalNonNullParamNonNullReturn(const Shape *S) { // CHECK-NEXT: ], // CHECK-NEXT: "dynamic_casts": [ // CHECK: { "region": "SymRegion{reg_$0}", "casts": [ - // CHECK-NEXT: { "from": "struct clang::Shape", "to": "class clang::Circle", "kind": "success" }, - // CHECK-NEXT: { "from": "struct clang::Shape", "to": "class clang::Square", "kind": "fail" } + // CHECK-NEXT: { "from": "const struct clang::Shape *", "to": "const class clang::Circle *", "kind": "success" }, + // CHECK-NEXT: { "from": "const struct clang::Shape *", "to": "const class clang::Square *", "kind": "fail" } // CHECK-NEXT: ]} (void)(1 / !C); From e1f0dc2b8ead2bf1cdcdd645af4d6ed35fce3549 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Fri, 23 Aug 2019 03:24:04 +0000 Subject: [PATCH 130/181] [analyzer] CastValueChecker: Correctly model results of based-to-derived casts. Our SVal hierarchy doesn't allow modeling pointer casts as no-op. The pointer type is instead encoded into the pointer object. Defer to our usual pointer casting facility, SValBuilder::evalBinOp(). Fixes a crash. llvm-svn: 369729 (cherry picked from commit 85f7294e5af0cb7ea8e2d0469b96d0369627c48c) --- .../Checkers/CastValueChecker.cpp | 36 ++++++++++++++----- clang/test/Analysis/cast-value-logic.cpp | 12 ++++++- clang/test/Analysis/cast-value-notes.cpp | 3 +- 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp index a0cebd6ab8562..cc1c9a66b90e2 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp @@ -138,6 +138,20 @@ static const NoteTag *getNoteTag(CheckerContext &C, // Main logic to evaluate a cast. //===----------------------------------------------------------------------===// +static QualType alignReferenceTypes(QualType toAlign, QualType alignTowards, + ASTContext &ACtx) { + if (alignTowards->isLValueReferenceType() && + alignTowards.isConstQualified()) { + toAlign.addConst(); + return ACtx.getLValueReferenceType(toAlign); + } else if (alignTowards->isLValueReferenceType()) + return ACtx.getLValueReferenceType(toAlign); + else if (alignTowards->isRValueReferenceType()) + return ACtx.getRValueReferenceType(toAlign); + + llvm_unreachable("Must align towards a reference type!"); +} + static void addCastTransition(const CallEvent &Call, DefinedOrUnknownSVal DV, CheckerContext &C, bool IsNonNullParam, bool IsNonNullReturn, @@ -156,6 +170,15 @@ static void addCastTransition(const CallEvent &Call, DefinedOrUnknownSVal DV, } else { Object = cast(&Call)->getCXXThisExpr(); CastFromTy = Object->getType(); + if (CastToTy->isPointerType()) { + if (!CastFromTy->isPointerType()) + return; + } else { + if (!CastFromTy->isReferenceType()) + return; + + CastFromTy = alignReferenceTypes(CastFromTy, CastToTy, C.getASTContext()); + } } const MemRegion *MR = DV.getAsRegion(); @@ -183,7 +206,8 @@ static void addCastTransition(const CallEvent &Call, DefinedOrUnknownSVal DV, State = setDynamicTypeAndCastInfo(State, MR, CastFromTy, CastToTy, CastSucceeds); - SVal V = CastSucceeds ? DV : C.getSValBuilder().makeNull(); + SVal V = CastSucceeds ? C.getSValBuilder().evalCast(DV, CastToTy, CastFromTy) + : C.getSValBuilder().makeNull(); C.addTransition( State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), V, false), getNoteTag(C, CastInfo, CastToTy, Object, CastSucceeds, IsKnownCast)); @@ -198,14 +222,8 @@ static void addInstanceOfTransition(const CallEvent &Call, QualType CastToTy = FD->getTemplateSpecializationArgs()->get(0).getAsType(); if (CastFromTy->isPointerType()) CastToTy = C.getASTContext().getPointerType(CastToTy); - else if (CastFromTy->isLValueReferenceType() && - CastFromTy.isConstQualified()) { - CastToTy.addConst(); - CastToTy = C.getASTContext().getLValueReferenceType(CastToTy); - } else if (CastFromTy->isLValueReferenceType()) - CastToTy = C.getASTContext().getLValueReferenceType(CastToTy); - else if (CastFromTy->isRValueReferenceType()) - CastToTy = C.getASTContext().getRValueReferenceType(CastToTy); + else if (CastFromTy->isReferenceType()) + CastToTy = alignReferenceTypes(CastToTy, CastFromTy, C.getASTContext()); else return; diff --git a/clang/test/Analysis/cast-value-logic.cpp b/clang/test/Analysis/cast-value-logic.cpp index 531772825f970..221ae7f9ae38b 100644 --- a/clang/test/Analysis/cast-value-logic.cpp +++ b/clang/test/Analysis/cast-value-logic.cpp @@ -19,7 +19,11 @@ struct Shape { virtual double area(); }; class Triangle : public Shape {}; -class Circle : public Shape {}; +class Circle : public Shape { +public: + ~Circle(); +}; +class SuspiciouslySpecificCircle : public Circle {}; } // namespace clang using namespace llvm; @@ -149,4 +153,10 @@ double test_virtual_method_after_call(Shape *S) { return S->area(); return S->area() / 2; } + +void test_delete_crash() { + extern Circle *makeCircle(); + Shape *S = makeCircle(); + delete cast(S); +} } // namespace crashes diff --git a/clang/test/Analysis/cast-value-notes.cpp b/clang/test/Analysis/cast-value-notes.cpp index f92ba90336a7a..a0eaeae8ba483 100644 --- a/clang/test/Analysis/cast-value-notes.cpp +++ b/clang/test/Analysis/cast-value-notes.cpp @@ -123,8 +123,7 @@ void evalZeroParamNonNullReturnPointer(const Shape *S) { void evalZeroParamNonNullReturn(const Shape &S) { const auto *C = S.castAs(); - // expected-note@-1 {{'S' is a 'Circle'}} - // expected-note@-2 {{'C' initialized here}} + // expected-note@-1 {{'C' initialized here}} (void)(1 / !C); // expected-note@-1 {{'C' is non-null}} From 70e388fc1000dfd80d9dd4bdb36652de1ec55ef6 Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Fri, 23 Aug 2019 14:21:13 +0000 Subject: [PATCH 131/181] [analyzer] Avoid unnecessary enum range check on LValueToRValue casts Summary: EnumCastOutOfRangeChecker should not perform enum range checks on LValueToRValue casts, since this type of cast does not actually change the underlying type. Performing the unnecessary check actually triggered an assertion failure deeper in EnumCastOutOfRange for certain input (which is captured in the accompanying test code). Reviewers: #clang, Szelethus, gamesh411, NoQ Reviewed By: Szelethus, gamesh411, NoQ Subscribers: NoQ, gamesh411, xazax.hun, baloghadamsoftware, szepet, a.sidorin, mikhail.ramalho, donat.nagy, dkrupp, Charusso, bjope, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D66014 llvm-svn: 369760 (cherry picked from commit 09ce8ec78a958e24a7e22009d6bce1a4c17b6cad) --- .../Checkers/EnumCastOutOfRangeChecker.cpp | 16 +++++++++ clang/test/Analysis/enum-cast-out-of-range.c | 34 +++++++++++++++++++ .../test/Analysis/enum-cast-out-of-range.cpp | 10 +++++- 3 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 clang/test/Analysis/enum-cast-out-of-range.c diff --git a/clang/lib/StaticAnalyzer/Checkers/EnumCastOutOfRangeChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/EnumCastOutOfRangeChecker.cpp index 736d80ef9ec7e..a6539098c89a3 100644 --- a/clang/lib/StaticAnalyzer/Checkers/EnumCastOutOfRangeChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/EnumCastOutOfRangeChecker.cpp @@ -91,6 +91,22 @@ void EnumCastOutOfRangeChecker::reportWarning(CheckerContext &C) const { void EnumCastOutOfRangeChecker::checkPreStmt(const CastExpr *CE, CheckerContext &C) const { + + // Only perform enum range check on casts where such checks are valid. For + // all other cast kinds (where enum range checks are unnecessary or invalid), + // just return immediately. TODO: The set of casts whitelisted for enum + // range checking may be incomplete. Better to add a missing cast kind to + // enable a missing check than to generate false negatives and have to remove + // those later. + switch (CE->getCastKind()) { + case CK_IntegralCast: + break; + + default: + return; + break; + } + // Get the value of the expression to cast. const llvm::Optional ValueToCast = C.getSVal(CE->getSubExpr()).getAs(); diff --git a/clang/test/Analysis/enum-cast-out-of-range.c b/clang/test/Analysis/enum-cast-out-of-range.c new file mode 100644 index 0000000000000..03e1100c38f49 --- /dev/null +++ b/clang/test/Analysis/enum-cast-out-of-range.c @@ -0,0 +1,34 @@ +// RUN: %clang_analyze_cc1 \ +// RUN: -analyzer-checker=core,alpha.cplusplus.EnumCastOutOfRange \ +// RUN: -verify %s + +enum En_t { + En_0 = -4, + En_1, + En_2 = 1, + En_3, + En_4 = 4 +}; + +void unscopedUnspecifiedCStyle() { + enum En_t Below = (enum En_t)(-5); // expected-warning {{not in the valid range}} + enum En_t NegVal1 = (enum En_t)(-4); // OK. + enum En_t NegVal2 = (enum En_t)(-3); // OK. + enum En_t InRange1 = (enum En_t)(-2); // expected-warning {{not in the valid range}} + enum En_t InRange2 = (enum En_t)(-1); // expected-warning {{not in the valid range}} + enum En_t InRange3 = (enum En_t)(0); // expected-warning {{not in the valid range}} + enum En_t PosVal1 = (enum En_t)(1); // OK. + enum En_t PosVal2 = (enum En_t)(2); // OK. + enum En_t InRange4 = (enum En_t)(3); // expected-warning {{not in the valid range}} + enum En_t PosVal3 = (enum En_t)(4); // OK. + enum En_t Above = (enum En_t)(5); // expected-warning {{not in the valid range}} +} + +enum En_t unused; +void unusedExpr() { + // Following line is not something that EnumCastOutOfRangeChecker should + // evaluate. Checker should either ignore this line or process it without + // producing any warnings. However, compilation will (and should) still + // generate a warning having nothing to do with this checker. + unused; // expected-warning {{expression result unused}} +} diff --git a/clang/test/Analysis/enum-cast-out-of-range.cpp b/clang/test/Analysis/enum-cast-out-of-range.cpp index e77339b551e0b..b600367f8c14a 100644 --- a/clang/test/Analysis/enum-cast-out-of-range.cpp +++ b/clang/test/Analysis/enum-cast-out-of-range.cpp @@ -150,7 +150,15 @@ void scopedSpecifiedCStyle() { scoped_specified_t InvalidAfterRangeEnd = (scoped_specified_t)(5); // expected-warning {{The value provided to the cast expression is not in the valid range of values for the enum}} } -void rangeContstrained1(int input) { +unscoped_unspecified_t unused; +void unusedExpr() { + // following line is not something that EnumCastOutOfRangeChecker should evaluate. checker should either ignore this line + // or process it without producing any warnings. However, compilation will (and should) still generate a warning having + // nothing to do with this checker. + unused; // expected-warning {{expression result unused}} +} + +void rangeConstrained1(int input) { if (input > -5 && input < 5) auto value = static_cast(input); // OK. Being conservative, this is a possibly good value. } From c867f49a0f3970de4597175f94b7f232b5261e8f Mon Sep 17 00:00:00 2001 From: Csaba Dabis Date: Sat, 24 Aug 2019 12:17:49 +0000 Subject: [PATCH 132/181] [analyzer] Analysis: Fix checker silencing llvm-svn: 369845 (cherry picked from commit 0d7252b783697c2737069ffd91f7195eaa1afdc3) --- clang/lib/Frontend/CompilerInvocation.cpp | 51 ++++++++++++----------- clang/tools/scan-build/bin/scan-build | 13 +++--- 2 files changed, 35 insertions(+), 29 deletions(-) diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp index bbd0a585a8297..0cb6db02e8675 100644 --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -464,22 +464,6 @@ static void parseAnalyzerConfigs(AnalyzerOptions &AnOpts, // At this point, AnalyzerOptions is configured. Let's validate some options. - if (!Diags) - return; - - if (AnOpts.ShouldTrackConditionsDebug && !AnOpts.ShouldTrackConditions) - Diags->Report(diag::err_analyzer_config_invalid_input) - << "track-conditions-debug" << "'track-conditions' to also be enabled"; - - if (!AnOpts.CTUDir.empty() && !llvm::sys::fs::is_directory(AnOpts.CTUDir)) - Diags->Report(diag::err_analyzer_config_invalid_input) << "ctu-dir" - << "a filename"; - - if (!AnOpts.ModelPath.empty() && - !llvm::sys::fs::is_directory(AnOpts.ModelPath)) - Diags->Report(diag::err_analyzer_config_invalid_input) << "model-path" - << "a filename"; - // FIXME: Here we try to validate the silenced checkers or packages are valid. // The current approach only validates the registered checkers which does not // contain the runtime enabled checkers and optimally we would validate both. @@ -493,18 +477,37 @@ static void parseAnalyzerConfigs(AnalyzerOptions &AnOpts, AnOpts.RawSilencedCheckersAndPackages.split(CheckersAndPackages, ";"); for (const StringRef CheckerOrPackage : CheckersAndPackages) { - bool IsChecker = CheckerOrPackage.contains('.'); - bool IsValidName = - IsChecker ? llvm::find(Checkers, CheckerOrPackage) != Checkers.end() - : llvm::find(Packages, CheckerOrPackage) != Packages.end(); - - if (!IsValidName) - Diags->Report(diag::err_unknown_analyzer_checker_or_package) - << CheckerOrPackage; + if (Diags) { + bool IsChecker = CheckerOrPackage.contains('.'); + bool IsValidName = + IsChecker + ? llvm::find(Checkers, CheckerOrPackage) != Checkers.end() + : llvm::find(Packages, CheckerOrPackage) != Packages.end(); + + if (!IsValidName) + Diags->Report(diag::err_unknown_analyzer_checker_or_package) + << CheckerOrPackage; + } AnOpts.SilencedCheckersAndPackages.emplace_back(CheckerOrPackage); } } + + if (!Diags) + return; + + if (AnOpts.ShouldTrackConditionsDebug && !AnOpts.ShouldTrackConditions) + Diags->Report(diag::err_analyzer_config_invalid_input) + << "track-conditions-debug" << "'track-conditions' to also be enabled"; + + if (!AnOpts.CTUDir.empty() && !llvm::sys::fs::is_directory(AnOpts.CTUDir)) + Diags->Report(diag::err_analyzer_config_invalid_input) << "ctu-dir" + << "a filename"; + + if (!AnOpts.ModelPath.empty() && + !llvm::sys::fs::is_directory(AnOpts.ModelPath)) + Diags->Report(diag::err_analyzer_config_invalid_input) << "model-path" + << "a filename"; } static bool ParseMigratorArgs(MigratorOptions &Opts, ArgList &Args) { diff --git a/clang/tools/scan-build/bin/scan-build b/clang/tools/scan-build/bin/scan-build index 37c94d5ec36e5..1574b10f20548 100755 --- a/clang/tools/scan-build/bin/scan-build +++ b/clang/tools/scan-build/bin/scan-build @@ -1889,11 +1889,6 @@ foreach (sort { $Options{DisableCheckers}{$a} <=> $Options{DisableCheckers}{$b} # Push checkers in order they were disabled. push @AnalysesToRun, "-analyzer-disable-checker", $_; } -foreach (sort { $Options{SilenceCheckers}{$a} <=> $Options{SilenceCheckers}{$b} } - keys %{$Options{SilenceCheckers}}) { - # Push checkers in order they were silenced. - push @AnalysesToRun, "-analyzer-config silence-checker", $_; -} if ($Options{AnalyzeHeaders}) { push @AnalysesToRun, "-analyzer-opt-analyze-headers"; } if ($Options{AnalyzerStats}) { push @AnalysesToRun, '-analyzer-checker=debug.Stats'; } if ($Options{MaxLoop} > 0) { push @AnalysesToRun, "-analyzer-max-loop $Options{MaxLoop}"; } @@ -1903,6 +1898,14 @@ if ($Options{MaxLoop} > 0) { push @AnalysesToRun, "-analyzer-max-loop $Options{M my $CCC_ANALYZER_ANALYSIS = join ' ', @AnalysesToRun; my $CCC_ANALYZER_PLUGINS = join ' ', map { "-load ".$_ } @{$Options{PluginsToLoad}}; my $CCC_ANALYZER_CONFIG = join ' ', map { "-analyzer-config ".$_ } @{$Options{ConfigOptions}}; + +foreach (sort { $Options{SilenceCheckers}{$a} <=> $Options{SilenceCheckers}{$b} } + keys %{$Options{SilenceCheckers}}) { + # Add checkers in order they were silenced. + $CCC_ANALYZER_CONFIG = + $CCC_ANALYZER_CONFIG." -analyzer-config silence-checkers=".$_; +} + my %EnvVars = ( 'CC' => $Cmd, 'CXX' => $CmdCXX, From a5881a20e5d2b29f8aba54c24bdccabbc20a7180 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Wed, 28 Aug 2019 18:44:32 +0000 Subject: [PATCH 133/181] [analyzer] Trust global initializers when analyzing main(). If the global variable has an initializer, we'll ignore it because we're usually not analyzing the program from the beginning, which means that the global variable may have changed before we start our analysis. However when we're analyzing main() as the top-level function, we can rely on global initializers to still be valid. At least in C; in C++ we have global constructors that can still break this logic. This patch allows the Static Analyzer to load constant initializers from global variables if the top-level function of the current analysis is main(). Differential Revision: https://reviews.llvm.org/D65361 llvm-svn: 370244 (cherry picked from commit 8b2a39e9377ea38f69c27e78964a11e992881d4f) --- clang/lib/StaticAnalyzer/Core/RegionStore.cpp | 74 +++++++++++++++---- clang/test/Analysis/main.c | 32 ++++++++ clang/test/Analysis/main.cpp | 22 ++++++ 3 files changed, 112 insertions(+), 16 deletions(-) create mode 100644 clang/test/Analysis/main.c create mode 100644 clang/test/Analysis/main.cpp diff --git a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp index 09bfbbe38116d..ab88ed1e7b193 100644 --- a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp +++ b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp @@ -155,28 +155,42 @@ class RegionBindingsRef : public llvm::ImmutableMapRef { ClusterBindings::Factory *CBFactory; + // This flag indicates whether the current bindings are within the analysis + // that has started from main(). It affects how we perform loads from + // global variables that have initializers: if we have observed the + // program execution from the start and we know that these variables + // have not been overwritten yet, we can be sure that their initializers + // are still relevant. This flag never gets changed when the bindings are + // updated, so it could potentially be moved into RegionStoreManager + // (as if it's the same bindings but a different loading procedure) + // however that would have made the manager needlessly stateful. + bool IsMainAnalysis; + public: typedef llvm::ImmutableMapRef ParentTy; RegionBindingsRef(ClusterBindings::Factory &CBFactory, const RegionBindings::TreeTy *T, - RegionBindings::TreeTy::Factory *F) + RegionBindings::TreeTy::Factory *F, + bool IsMainAnalysis) : llvm::ImmutableMapRef(T, F), - CBFactory(&CBFactory) {} + CBFactory(&CBFactory), IsMainAnalysis(IsMainAnalysis) {} - RegionBindingsRef(const ParentTy &P, ClusterBindings::Factory &CBFactory) + RegionBindingsRef(const ParentTy &P, + ClusterBindings::Factory &CBFactory, + bool IsMainAnalysis) : llvm::ImmutableMapRef(P), - CBFactory(&CBFactory) {} + CBFactory(&CBFactory), IsMainAnalysis(IsMainAnalysis) {} RegionBindingsRef add(key_type_ref K, data_type_ref D) const { return RegionBindingsRef(static_cast(this)->add(K, D), - *CBFactory); + *CBFactory, IsMainAnalysis); } RegionBindingsRef remove(key_type_ref K) const { return RegionBindingsRef(static_cast(this)->remove(K), - *CBFactory); + *CBFactory, IsMainAnalysis); } RegionBindingsRef addBinding(BindingKey K, SVal V) const; @@ -206,7 +220,13 @@ class RegionBindingsRef : public llvm::ImmutableMapRef Ptr = { + asImmutableMap().getRootWithoutRetain(), IsMainAnalysis}; + return reinterpret_cast(Ptr.getOpaqueValue()); + } + + bool isMainAnalysis() const { + return IsMainAnalysis; } void printJson(raw_ostream &Out, const char *NL = "\n", @@ -381,8 +401,15 @@ class RegionStoreManager : public StoreManager { /// casts from arrays to pointers. SVal ArrayToPointer(Loc Array, QualType ElementTy) override; + /// Creates the Store that correctly represents memory contents before + /// the beginning of the analysis of the given top-level stack frame. StoreRef getInitialStore(const LocationContext *InitLoc) override { - return StoreRef(RBFactory.getEmptyMap().getRootWithoutRetain(), *this); + bool IsMainAnalysis = false; + if (const auto *FD = dyn_cast(InitLoc->getDecl())) + IsMainAnalysis = FD->isMain() && !Ctx.getLangOpts().CPlusPlus; + return StoreRef(RegionBindingsRef( + RegionBindingsRef::ParentTy(RBFactory.getEmptyMap(), RBFactory), + CBFactory, IsMainAnalysis).asStore(), *this); } //===-------------------------------------------------------------------===// @@ -608,9 +635,13 @@ class RegionStoreManager : public StoreManager { //===------------------------------------------------------------------===// RegionBindingsRef getRegionBindings(Store store) const { - return RegionBindingsRef(CBFactory, - static_cast(store), - RBFactory.getTreeFactory()); + llvm::PointerIntPair Ptr; + Ptr.setFromOpaqueValue(const_cast(store)); + return RegionBindingsRef( + CBFactory, + static_cast(Ptr.getPointer()), + RBFactory.getTreeFactory(), + Ptr.getInt()); } void printJson(raw_ostream &Out, Store S, const char *NL = "\n", @@ -1671,10 +1702,12 @@ SVal RegionStoreManager::getBindingForElement(RegionBindingsConstRef B, return svalBuilder.makeIntVal(c, T); } } else if (const VarRegion *VR = dyn_cast(superR)) { - // Check if the containing array is const and has an initialized value. + // Check if the containing array has an initialized value that we can trust. + // We can trust a const value or a value of a global initializer in main(). const VarDecl *VD = VR->getDecl(); - // Either the array or the array element has to be const. - if (VD->getType().isConstQualified() || R->getElementType().isConstQualified()) { + if (VD->getType().isConstQualified() || + R->getElementType().isConstQualified() || + (B.isMainAnalysis() && VD->hasGlobalStorage())) { if (const Expr *Init = VD->getAnyInitializer()) { if (const auto *InitList = dyn_cast(Init)) { // The array index has to be known. @@ -1763,8 +1796,11 @@ SVal RegionStoreManager::getBindingForField(RegionBindingsConstRef B, const VarDecl *VD = VR->getDecl(); QualType RecordVarTy = VD->getType(); unsigned Index = FD->getFieldIndex(); - // Either the record variable or the field has to be const qualified. - if (RecordVarTy.isConstQualified() || Ty.isConstQualified()) + // Either the record variable or the field has an initializer that we can + // trust. We trust initializers of constants and, additionally, respect + // initializers of globals when analyzing main(). + if (RecordVarTy.isConstQualified() || Ty.isConstQualified() || + (B.isMainAnalysis() && VD->hasGlobalStorage())) if (const Expr *Init = VD->getAnyInitializer()) if (const auto *InitList = dyn_cast(Init)) { if (Index < InitList->getNumInits()) { @@ -1982,6 +2018,12 @@ SVal RegionStoreManager::getBindingForVar(RegionBindingsConstRef B, if (isa(MS)) { QualType T = VD->getType(); + // If we're in main(), then global initializers have not become stale yet. + if (B.isMainAnalysis()) + if (const Expr *Init = VD->getAnyInitializer()) + if (Optional V = svalBuilder.getConstantVal(Init)) + return *V; + // Function-scoped static variables are default-initialized to 0; if they // have an initializer, it would have been processed by now. // FIXME: This is only true when we're starting analysis from main(). diff --git a/clang/test/Analysis/main.c b/clang/test/Analysis/main.c new file mode 100644 index 0000000000000..aa4c506096002 --- /dev/null +++ b/clang/test/Analysis/main.c @@ -0,0 +1,32 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -verify %s + +int x = 1; + +struct { + int a, b; +} s = {2, 3}; + +int arr[] = {4, 5, 6}; + +void clang_analyzer_eval(int); + +int main() { + // In main() we know that the initial values are still valid. + clang_analyzer_eval(x == 1); // expected-warning{{TRUE}} + clang_analyzer_eval(s.a == 2); // expected-warning{{TRUE}} + clang_analyzer_eval(s.b == 3); // expected-warning{{TRUE}} + clang_analyzer_eval(arr[0] == 4); // expected-warning{{TRUE}} + clang_analyzer_eval(arr[1] == 5); // expected-warning{{TRUE}} + clang_analyzer_eval(arr[2] == 6); // expected-warning{{TRUE}} + return 0; +} + +void foo() { + // In other functions these values may already be overwritten. + clang_analyzer_eval(x == 1); // expected-warning{{TRUE}} // expected-warning{{FALSE}} + clang_analyzer_eval(s.a == 2); // expected-warning{{TRUE}} // expected-warning{{FALSE}} + clang_analyzer_eval(s.b == 3); // expected-warning{{TRUE}} // expected-warning{{FALSE}} + clang_analyzer_eval(arr[0] == 4); // expected-warning{{TRUE}} // expected-warning{{FALSE}} + clang_analyzer_eval(arr[1] == 5); // expected-warning{{TRUE}} // expected-warning{{FALSE}} + clang_analyzer_eval(arr[2] == 6); // expected-warning{{TRUE}} // expected-warning{{FALSE}} +} diff --git a/clang/test/Analysis/main.cpp b/clang/test/Analysis/main.cpp new file mode 100644 index 0000000000000..c230c389cadb3 --- /dev/null +++ b/clang/test/Analysis/main.cpp @@ -0,0 +1,22 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -verify %s + +int x = 1; + +struct { + int a, b; +} s = {2, 3}; + +int arr[] = {4, 5, 6}; + +void clang_analyzer_eval(int); + +int main() { + // Do not trust global initializers in C++. + clang_analyzer_eval(x == 1); // expected-warning{{TRUE}} // expected-warning{{FALSE}} + clang_analyzer_eval(s.a == 2); // expected-warning{{TRUE}} // expected-warning{{FALSE}} + clang_analyzer_eval(s.b == 3); // expected-warning{{TRUE}} // expected-warning{{FALSE}} + clang_analyzer_eval(arr[0] == 4); // expected-warning{{TRUE}} // expected-warning{{FALSE}} + clang_analyzer_eval(arr[1] == 5); // expected-warning{{TRUE}} // expected-warning{{FALSE}} + clang_analyzer_eval(arr[2] == 6); // expected-warning{{TRUE}} // expected-warning{{FALSE}} + return 0; +} From e4af1c1c1a3941d661dbbb807e47b12abcf83a65 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Wed, 28 Aug 2019 18:44:35 +0000 Subject: [PATCH 134/181] [analyzer] pr43036: Fix support for operator 'sizeof...'. It was known to be a compile-time constant so it wasn't evaluated during symbolic execution, but it wasn't evaluated as a compile-time constant either. Differential Revision: https://reviews.llvm.org/D66565 llvm-svn: 370245 (cherry picked from commit 0909a392f318872a1eacbdc9a6044cb437095a11) --- clang/lib/StaticAnalyzer/Core/Environment.cpp | 1 + clang/test/Analysis/sizeofpack.cpp | 15 +++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 clang/test/Analysis/sizeofpack.cpp diff --git a/clang/lib/StaticAnalyzer/Core/Environment.cpp b/clang/lib/StaticAnalyzer/Core/Environment.cpp index 551c89b04db4b..1ccf4c6104a65 100644 --- a/clang/lib/StaticAnalyzer/Core/Environment.cpp +++ b/clang/lib/StaticAnalyzer/Core/Environment.cpp @@ -108,6 +108,7 @@ SVal Environment::getSVal(const EnvironmentEntry &Entry, case Stmt::ObjCStringLiteralClass: case Stmt::StringLiteralClass: case Stmt::TypeTraitExprClass: + case Stmt::SizeOfPackExprClass: // Known constants; defer to SValBuilder. return svalBuilder.getConstantVal(cast(S)).getValue(); diff --git a/clang/test/Analysis/sizeofpack.cpp b/clang/test/Analysis/sizeofpack.cpp new file mode 100644 index 0000000000000..44c3bba3a8ae4 --- /dev/null +++ b/clang/test/Analysis/sizeofpack.cpp @@ -0,0 +1,15 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection \ +// RUN: -verify %s + +typedef __typeof(sizeof(int)) size_t; + +void clang_analyzer_eval(bool); + +template size_t foo() { + return sizeof...(N); +} + +void bar() { + clang_analyzer_eval(foo<>() == 0); // expected-warning{{TRUE}} + clang_analyzer_eval(foo<1, 2, 3>() == 3); // expected-warning{{TRUE}} +} From 4538b749e89b3395fa5b0a92ed47f72af13605e7 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Wed, 28 Aug 2019 18:44:38 +0000 Subject: [PATCH 135/181] [analyzer] Fix analyzer warnings on analyzer. Write tests for the actual crash that was found. Write comments and refactor code around 17 style bugs and suppress 3 false positives. Differential Revision: https://reviews.llvm.org/D66847 llvm-svn: 370246 (cherry picked from commit 630f7daf80fe36d3aa4a9ebe60e7abefae514bba) --- .../Core/PathSensitive/MemRegion.h | 6 +++++ .../Checkers/CStringSyntaxChecker.cpp | 27 ++++++++++++------- .../Checkers/CheckSecuritySyntaxOnly.cpp | 9 ++++--- .../Checkers/DynamicTypePropagation.cpp | 15 +++++------ .../Checkers/IteratorChecker.cpp | 3 ++- .../Checkers/LocalizationChecker.cpp | 23 ++++++++-------- .../Checkers/MPI-Checker/MPIBugReporter.cpp | 4 ++- .../StaticAnalyzer/Checkers/MallocChecker.cpp | 13 +++++---- .../Checkers/MallocSizeofChecker.cpp | 2 +- .../Checkers/PointerArithChecker.cpp | 10 +++---- .../Checkers/UndefinedAssignmentChecker.cpp | 2 +- .../UninitializedPointee.cpp | 9 ++++--- .../Checkers/VirtualCallChecker.cpp | 3 ++- .../StaticAnalyzer/Core/CheckerHelpers.cpp | 2 +- clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp | 3 +-- .../lib/StaticAnalyzer/Core/ExprEngineCXX.cpp | 5 ++-- clang/lib/StaticAnalyzer/Core/MemRegion.cpp | 3 ++- clang/lib/StaticAnalyzer/Core/RegionStore.cpp | 5 ++-- clang/test/Analysis/cstring-syntax-weird.c | 19 +++++++++++++ clang/test/Analysis/cstring-syntax-weird2.c | 17 ++++++++++++ clang/test/Analysis/cstring-syntax.c | 19 ++++++++++--- 21 files changed, 134 insertions(+), 65 deletions(-) create mode 100644 clang/test/Analysis/cstring-syntax-weird.c create mode 100644 clang/test/Analysis/cstring-syntax-weird2.c diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h index 071e35085a5f9..71cbbe28fc258 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h @@ -169,6 +169,7 @@ class MemRegion : public llvm::FoldingSetNode { Kind getKind() const { return kind; } template const RegionTy* getAs() const; + template const RegionTy* castAs() const; virtual bool isBoundable() const { return false; } @@ -1231,6 +1232,11 @@ const RegionTy* MemRegion::getAs() const { return nullptr; } +template +const RegionTy* MemRegion::castAs() const { + return cast(this); +} + //===----------------------------------------------------------------------===// // MemRegionManager - Factory object for creating regions. //===----------------------------------------------------------------------===// diff --git a/clang/lib/StaticAnalyzer/Checkers/CStringSyntaxChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CStringSyntaxChecker.cpp index b828ac0592363..d84fcc69a4925 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CStringSyntaxChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CStringSyntaxChecker.cpp @@ -156,14 +156,21 @@ bool WalkAST::containsBadStrlcpyStrlcatPattern(const CallExpr *CE) { const Expr *DstArg = CE->getArg(0); const Expr *LenArg = CE->getArg(2); - const auto *DstArgDecl = dyn_cast(DstArg->IgnoreParenImpCasts()); - const auto *LenArgDecl = dyn_cast(LenArg->IgnoreParenLValueCasts()); + const auto *DstArgDRE = dyn_cast(DstArg->IgnoreParenImpCasts()); + const auto *LenArgDRE = + dyn_cast(LenArg->IgnoreParenLValueCasts()); uint64_t DstOff = 0; if (isSizeof(LenArg, DstArg)) return false; + // - size_t dstlen = sizeof(dst) - if (LenArgDecl) { - const auto *LenArgVal = dyn_cast(LenArgDecl->getDecl()); + if (LenArgDRE) { + const auto *LenArgVal = dyn_cast(LenArgDRE->getDecl()); + // If it's an EnumConstantDecl instead, then we're missing out on something. + if (!LenArgVal) { + assert(isa(LenArgDRE->getDecl())); + return false; + } if (LenArgVal->getInit()) LenArg = LenArgVal->getInit(); } @@ -177,9 +184,10 @@ bool WalkAST::containsBadStrlcpyStrlcatPattern(const CallExpr *CE) { // Case when there is pointer arithmetic on the destination buffer // especially when we offset from the base decreasing the // buffer length accordingly. - if (!DstArgDecl) { - if (const auto *BE = dyn_cast(DstArg->IgnoreParenImpCasts())) { - DstArgDecl = dyn_cast(BE->getLHS()->IgnoreParenImpCasts()); + if (!DstArgDRE) { + if (const auto *BE = + dyn_cast(DstArg->IgnoreParenImpCasts())) { + DstArgDRE = dyn_cast(BE->getLHS()->IgnoreParenImpCasts()); if (BE->getOpcode() == BO_Add) { if ((IL = dyn_cast(BE->getRHS()->IgnoreParenImpCasts()))) { DstOff = IL->getValue().getZExtValue(); @@ -187,8 +195,9 @@ bool WalkAST::containsBadStrlcpyStrlcatPattern(const CallExpr *CE) { } } } - if (DstArgDecl) { - if (const auto *Buffer = dyn_cast(DstArgDecl->getType())) { + if (DstArgDRE) { + if (const auto *Buffer = + dyn_cast(DstArgDRE->getType())) { ASTContext &C = BR.getContext(); uint64_t BufferLen = C.getTypeSize(Buffer) / 8; auto RemainingBufferLen = BufferLen - DstOff; diff --git a/clang/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp b/clang/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp index 3f1c213a56478..7f32b15ae92f7 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp @@ -204,6 +204,8 @@ void WalkAST::VisitForStmt(ForStmt *FS) { // Implements: CERT security coding advisory FLP-30. //===----------------------------------------------------------------------===// +// Returns either 'x' or 'y', depending on which one of them is incremented +// in 'expr', or nullptr if none of them is incremented. static const DeclRefExpr* getIncrementedVar(const Expr *expr, const VarDecl *x, const VarDecl *y) { expr = expr->IgnoreParenCasts(); @@ -289,14 +291,15 @@ void WalkAST::checkLoopConditionForFloat(const ForStmt *FS) { // Does either variable appear in increment? const DeclRefExpr *drInc = getIncrementedVar(increment, vdLHS, vdRHS); - if (!drInc) return; + const VarDecl *vdInc = cast(drInc->getDecl()); + assert(vdInc && (vdInc == vdLHS || vdInc == vdRHS)); + // Emit the error. First figure out which DeclRefExpr in the condition // referenced the compared variable. - assert(drInc->getDecl()); - const DeclRefExpr *drCond = vdLHS == drInc->getDecl() ? drLHS : drRHS; + const DeclRefExpr *drCond = vdLHS == vdInc ? drLHS : drRHS; SmallVector ranges; SmallString<256> sbuf; diff --git a/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp b/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp index 8b98c57e75587..5ae466d426056 100644 --- a/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp @@ -394,11 +394,11 @@ static const ObjCObjectPointerType *getMostInformativeDerivedClassImpl( } const auto *SuperOfTo = - To->getObjectType()->getSuperClassType()->getAs(); + To->getObjectType()->getSuperClassType()->castAs(); assert(SuperOfTo); QualType SuperPtrOfToQual = C.getObjCObjectPointerType(QualType(SuperOfTo, 0)); - const auto *SuperPtrOfTo = SuperPtrOfToQual->getAs(); + const auto *SuperPtrOfTo = SuperPtrOfToQual->castAs(); if (To->isUnspecialized()) return getMostInformativeDerivedClassImpl(From, SuperPtrOfTo, SuperPtrOfTo, C); @@ -827,16 +827,15 @@ void DynamicTypePropagation::checkPostObjCMessage(const ObjCMethodCall &M, if (MessageExpr->getReceiverKind() == ObjCMessageExpr::Class && Sel.getAsString() == "class") { QualType ReceiverType = MessageExpr->getClassReceiver(); - const auto *ReceiverClassType = ReceiverType->getAs(); + const auto *ReceiverClassType = ReceiverType->castAs(); + if (!ReceiverClassType->isSpecialized()) + return; + QualType ReceiverClassPointerType = C.getASTContext().getObjCObjectPointerType( QualType(ReceiverClassType, 0)); - - if (!ReceiverClassType->isSpecialized()) - return; const auto *InferredType = - ReceiverClassPointerType->getAs(); - assert(InferredType); + ReceiverClassPointerType->castAs(); State = State->set(RetSym, InferredType); C.addTransition(State); diff --git a/clang/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp index ed56a095a1c57..2d103c1c6aa08 100644 --- a/clang/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp @@ -567,7 +567,8 @@ void IteratorChecker::checkPostCall(const CallEvent &Call, if (Func->isOverloadedOperator()) { const auto Op = Func->getOverloadedOperator(); if (isAssignmentOperator(Op)) { - const auto *InstCall = dyn_cast(&Call); + // Overloaded 'operator=' must be a non-static member function. + const auto *InstCall = cast(&Call); if (cast(Func)->isMoveAssignmentOperator()) { handleAssign(C, InstCall->getCXXThisVal(), Call.getOriginExpr(), Call.getArgSVal(0)); diff --git a/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp index b2daa820ffa22..bbecdcd79884f 100644 --- a/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp @@ -883,18 +883,17 @@ void NonLocalizedStringChecker::checkPreObjCMessage(const ObjCMethodCall &msg, void NonLocalizedStringChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { - const Decl *D = Call.getDecl(); - if (D && isa(D)) { - const FunctionDecl *FD = dyn_cast(D); - auto formals = FD->parameters(); - for (unsigned i = 0, - ei = std::min(unsigned(formals.size()), Call.getNumArgs()); - i != ei; ++i) { - if (isAnnotatedAsTakingLocalized(formals[i])) { - auto actual = Call.getArgSVal(i); - if (hasNonLocalizedState(actual, C)) { - reportLocalizationError(actual, Call, C, i + 1); - } + const auto *FD = dyn_cast_or_null(Call.getDecl()); + if (!FD) + return; + + auto formals = FD->parameters(); + for (unsigned i = 0, ei = std::min(static_cast(formals.size()), + Call.getNumArgs()); i != ei; ++i) { + if (isAnnotatedAsTakingLocalized(formals[i])) { + auto actual = Call.getArgSVal(i); + if (hasNonLocalizedState(actual, C)) { + reportLocalizationError(actual, Call, C, i + 1); } } } diff --git a/clang/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.cpp b/clang/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.cpp index b40ddacca5924..cc7d2c715a37d 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.cpp @@ -91,11 +91,13 @@ PathDiagnosticPieceRef MPIBugReporter::RequestNodeVisitor::VisitNode( return nullptr; const Request *const Req = N->getState()->get(RequestRegion); + assert(Req && "The region must be tracked and alive, given that we've " + "just emitted a report against it"); const Request *const PrevReq = N->getFirstPred()->getState()->get(RequestRegion); // Check if request was previously unused or in a different state. - if ((Req && !PrevReq) || (Req->CurrentState != PrevReq->CurrentState)) { + if (!PrevReq || (Req->CurrentState != PrevReq->CurrentState)) { IsNodeFound = true; ProgramPoint P = N->getFirstPred()->getLocation(); diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index 12ccf6774333b..2b36c1a0de843 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -1132,14 +1132,13 @@ ProgramStateRef MallocChecker::addExtentSize(CheckerContext &C, // Store the extent size for the (symbolic)region // containing the elements. Region = Target.getAsRegion() - ->getAs() + ->castAs() ->StripCasts() - ->getAs(); + ->castAs(); } else { ElementCount = svalBuilder.makeIntVal(1, true); - Region = Target.getAsRegion()->getAs(); + Region = Target.getAsRegion()->castAs(); } - assert(Region); // Set the region's extent equal to the Size in Bytes. QualType ElementType = NE->getAllocatedType(); @@ -3066,8 +3065,12 @@ PathDiagnosticPieceRef MallocChecker::MallocBugVisitor::VisitNode( } } - if (Msg.empty()) + if (Msg.empty()) { + // Silence a memory leak warning by MallocChecker in MallocChecker.cpp :) + assert(!StackHint && "Memory leak!"); return nullptr; + } + assert(StackHint); // Generate the extra diagnostic. diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp index 2eb4d7141e284..b5881a9e65338 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp @@ -183,7 +183,7 @@ class MallocSizeofChecker : public Checker { QualType CastedType = i->CastedExpr->getType(); if (!CastedType->isPointerType()) continue; - QualType PointeeType = CastedType->getAs()->getPointeeType(); + QualType PointeeType = CastedType->getPointeeType(); if (PointeeType->isVoidType()) continue; diff --git a/clang/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp index 03c3f4dd23570..1888449879bc0 100644 --- a/clang/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp @@ -119,12 +119,12 @@ const MemRegion *PointerArithChecker::getArrayRegion(const MemRegion *Region, AllocKind &AKind, CheckerContext &C) const { assert(Region); - while (Region->getKind() == MemRegion::Kind::CXXBaseObjectRegionKind) { - Region = Region->getAs()->getSuperRegion(); + while (const auto *BaseRegion = dyn_cast(Region)) { + Region = BaseRegion->getSuperRegion(); Polymorphic = true; } - if (Region->getKind() == MemRegion::Kind::ElementRegionKind) { - Region = Region->getAs()->getSuperRegion(); + if (const auto *ElemRegion = dyn_cast(Region)) { + Region = ElemRegion->getSuperRegion(); } ProgramStateRef State = C.getState(); @@ -137,7 +137,7 @@ const MemRegion *PointerArithChecker::getArrayRegion(const MemRegion *Region, } // When the region is symbolic and we do not have any information about it, // assume that this is an array to avoid false positives. - if (Region->getKind() == MemRegion::Kind::SymbolicRegionKind) + if (isa(Region)) return Region; // No AllocKind stored and not symbolic, assume that it points to a single diff --git a/clang/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp index d32d2a4042de7..0923b35a6ce2e 100644 --- a/clang/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp @@ -85,7 +85,7 @@ void UndefinedAssignmentChecker::checkBind(SVal location, SVal val, } if (const DeclStmt *DS = dyn_cast(StoreE)) { - const VarDecl *VD = dyn_cast(DS->getSingleDecl()); + const VarDecl *VD = cast(DS->getSingleDecl()); ex = VD->getInit(); } diff --git a/clang/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedPointee.cpp b/clang/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedPointee.cpp index 54d4e48ab8d9d..f0dd0bf813aff 100644 --- a/clang/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedPointee.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedPointee.cpp @@ -260,12 +260,13 @@ static llvm::Optional dereference(ProgramStateRef State, break; } - while (R->getAs()) { + while (isa(R)) { NeedsCastBack = true; - - if (!isa(R->getSuperRegion())) + const auto *SuperR = dyn_cast(R->getSuperRegion()); + if (!SuperR) break; - R = R->getSuperRegion()->getAs(); + + R = SuperR; } return DereferenceInfo{R, NeedsCastBack, /*IsCyclic*/ false}; diff --git a/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp index b50ab4b19f5c6..53ed7e1761b98 100644 --- a/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp @@ -104,7 +104,8 @@ void VirtualCallChecker::checkPreCall(const CallEvent &Call, return; ProgramStateRef State = C.getState(); - const CallExpr *CE = dyn_cast_or_null(Call.getOriginExpr()); + // Member calls are always represented by a call-expression. + const auto *CE = cast(Call.getOriginExpr()); if (!isVirtualCall(CE)) return; diff --git a/clang/lib/StaticAnalyzer/Core/CheckerHelpers.cpp b/clang/lib/StaticAnalyzer/Core/CheckerHelpers.cpp index 34cdc9db699db..11693132de687 100644 --- a/clang/lib/StaticAnalyzer/Core/CheckerHelpers.cpp +++ b/clang/lib/StaticAnalyzer/Core/CheckerHelpers.cpp @@ -91,7 +91,7 @@ parseAssignment(const Stmt *S) { } else if (auto PD = dyn_cast_or_null(S)) { // Initialization assert(PD->isSingleDecl() && "We process decls one by one"); - VD = dyn_cast_or_null(PD->getSingleDecl()); + VD = cast(PD->getSingleDecl()); RHS = VD->getAnyInitializer(); } diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp index 3b5a437e3c15d..ab747230594ac 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp @@ -849,8 +849,7 @@ VisitOffsetOfExpr(const OffsetOfExpr *OOE, if (OOE->EvaluateAsInt(Result, getContext())) { APSInt IV = Result.Val.getInt(); assert(IV.getBitWidth() == getContext().getTypeSize(OOE->getType())); - assert(OOE->getType()->isBuiltinType()); - assert(OOE->getType()->getAs()->isInteger()); + assert(OOE->getType()->castAs()->isInteger()); assert(IV.isSigned() == OOE->getType()->isSignedIntegerType()); SVal X = svalBuilder.makeIntVal(IV); B.generateNode(OOE, Pred, diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp index 91afde603ca93..83303d722f271 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp @@ -803,9 +803,8 @@ void ExprEngine::VisitCXXNewExpr(const CXXNewExpr *CNE, ExplodedNode *Pred, if (CNE->isArray()) { // FIXME: allocating an array requires simulating the constructors. // For now, just return a symbolicated region. - if (const SubRegion *NewReg = - dyn_cast_or_null(symVal.getAsRegion())) { - QualType ObjTy = CNE->getType()->getAs()->getPointeeType(); + if (const auto *NewReg = cast_or_null(symVal.getAsRegion())) { + QualType ObjTy = CNE->getType()->getPointeeType(); const ElementRegion *EleReg = getStoreManager().GetElementZeroRegion(NewReg, ObjTy); Result = loc::MemRegionVal(EleReg); diff --git a/clang/lib/StaticAnalyzer/Core/MemRegion.cpp b/clang/lib/StaticAnalyzer/Core/MemRegion.cpp index f763701af7fb1..c7b280adcd9be 100644 --- a/clang/lib/StaticAnalyzer/Core/MemRegion.cpp +++ b/clang/lib/StaticAnalyzer/Core/MemRegion.cpp @@ -1075,7 +1075,7 @@ MemRegionManager::getCXXBaseObjectRegion(const CXXRecordDecl *RD, const SubRegion *Super, bool IsVirtual) { if (isa(Super)) { - assert(isValidBaseClass(RD, dyn_cast(Super), IsVirtual)); + assert(isValidBaseClass(RD, cast(Super), IsVirtual)); (void)&isValidBaseClass; if (IsVirtual) { @@ -1426,6 +1426,7 @@ static RegionOffset calculateOffset(const MemRegion *R) { case MemRegion::FieldRegionKind: { const auto *FR = cast(R); R = FR->getSuperRegion(); + assert(R); const RecordDecl *RD = FR->getDecl()->getParent(); if (RD->isUnion() || !RD->isCompleteDefinition()) { diff --git a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp index ab88ed1e7b193..97b856ef04f15 100644 --- a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp +++ b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp @@ -2291,8 +2291,7 @@ RegionBindingsRef RegionStoreManager::bindVector(RegionBindingsConstRef B, const TypedValueRegion* R, SVal V) { QualType T = R->getValueType(); - assert(T->isVectorType()); - const VectorType *VT = T->getAs(); // Use getAs for typedefs. + const VectorType *VT = T->castAs(); // Use castAs for typedefs. // Handle lazy compound values and symbolic values. if (V.getAs() || V.getAs()) @@ -2377,7 +2376,7 @@ RegionBindingsRef RegionStoreManager::bindStruct(RegionBindingsConstRef B, QualType T = R->getValueType(); assert(T->isStructureOrClassType()); - const RecordType* RT = T->getAs(); + const RecordType* RT = T->castAs(); const RecordDecl *RD = RT->getDecl(); if (!RD->isCompleteDefinition()) diff --git a/clang/test/Analysis/cstring-syntax-weird.c b/clang/test/Analysis/cstring-syntax-weird.c new file mode 100644 index 0000000000000..9a58f1609f7ae --- /dev/null +++ b/clang/test/Analysis/cstring-syntax-weird.c @@ -0,0 +1,19 @@ +// RUN: %clang_analyze_cc1 -w -analyzer-checker=unix.cstring.BadSizeArg \ +// RUN: -verify %s + +// expected-no-diagnostics + +typedef __SIZE_TYPE__ size_t; +// The last parameter is normally size_t but the test is about the abnormal +// situation when it's not a size_t. +size_t strlcpy(char *, const char *, int); + +enum WeirdDecl { + AStrangeWayToSpecifyStringLengthCorrectly = 10UL, + AStrangeWayToSpecifyStringLengthIncorrectly = 5UL +}; +void testWeirdDecls(const char *src) { + char dst[10]; + strlcpy(dst, src, AStrangeWayToSpecifyStringLengthCorrectly); // no-crash + strlcpy(dst, src, AStrangeWayToSpecifyStringLengthIncorrectly); // no-crash // no-warning +} diff --git a/clang/test/Analysis/cstring-syntax-weird2.c b/clang/test/Analysis/cstring-syntax-weird2.c new file mode 100644 index 0000000000000..a0f28536d4a38 --- /dev/null +++ b/clang/test/Analysis/cstring-syntax-weird2.c @@ -0,0 +1,17 @@ +// RUN: %clang_analyze_cc1 -w -analyzer-checker=unix.cstring.BadSizeArg \ +// RUN: -verify %s + +// expected-no-diagnostics + +typedef __SIZE_TYPE__ size_t; +// The last parameter is normally size_t but the test is about the abnormal +// situation when it's not a size_t. +size_t strlcpy(char *, const char *, void (*)()); + +void foo(); + +void testWeirdDecls(const char *src) { + char dst[10]; + strlcpy(dst, src, foo); // no-crash + strlcpy(dst, src, &foo); // no-crash +} diff --git a/clang/test/Analysis/cstring-syntax.c b/clang/test/Analysis/cstring-syntax.c index f01de36c1afb8..8ac971fe24127 100644 --- a/clang/test/Analysis/cstring-syntax.c +++ b/clang/test/Analysis/cstring-syntax.c @@ -1,7 +1,18 @@ -// RUN: %clang_analyze_cc1 -analyzer-checker=unix.cstring.BadSizeArg -analyzer-store=region -Wno-strncat-size -Wno-strlcpy-strlcat-size -Wno-sizeof-array-argument -Wno-sizeof-pointer-memaccess -verify %s -// RUN: %clang_analyze_cc1 -triple armv7-a15-linux -analyzer-checker=unix.cstring.BadSizeArg -analyzer-store=region -Wno-strncat-size -Wno-strlcpy-strlcat-size -Wno-sizeof-array-argument -Wno-sizeof-pointer-memaccess -verify %s -// RUN: %clang_analyze_cc1 -triple aarch64_be-none-linux-gnu -analyzer-checker=unix.cstring.BadSizeArg -analyzer-store=region -Wno-strncat-size -Wno-strlcpy-strlcat-size -Wno-sizeof-array-argument -Wno-sizeof-pointer-memaccess -verify %s -// RUN: %clang_analyze_cc1 -triple i386-apple-darwin10 -analyzer-checker=unix.cstring.BadSizeArg -analyzer-store=region -Wno-strncat-size -Wno-strlcpy-strlcat-size -Wno-sizeof-array-argument -Wno-sizeof-pointer-memaccess -verify %s +// RUN: %clang_analyze_cc1 -analyzer-checker=unix.cstring.BadSizeArg -verify %s\ +// RUN: -Wno-strncat-size -Wno-sizeof-pointer-memaccess \ +// RUN: -Wno-strlcpy-strlcat-size -Wno-sizeof-array-argument +// RUN: %clang_analyze_cc1 -analyzer-checker=unix.cstring.BadSizeArg -verify %s\ +// RUN: -Wno-strncat-size -Wno-sizeof-pointer-memaccess \ +// RUN: -Wno-strlcpy-strlcat-size -Wno-sizeof-array-argument\ +// RUN: -triple armv7-a15-linux +// RUN: %clang_analyze_cc1 -analyzer-checker=unix.cstring.BadSizeArg -verify %s\ +// RUN: -Wno-strncat-size -Wno-sizeof-pointer-memaccess \ +// RUN: -Wno-strlcpy-strlcat-size -Wno-sizeof-array-argument\ +// RUN: -triple aarch64_be-none-linux-gnu +// RUN: %clang_analyze_cc1 -analyzer-checker=unix.cstring.BadSizeArg -verify %s\ +// RUN: -Wno-strncat-size -Wno-sizeof-pointer-memaccess \ +// RUN: -Wno-strlcpy-strlcat-size -Wno-sizeof-array-argument\ +// RUN: -triple i386-apple-darwin10 typedef __SIZE_TYPE__ size_t; char *strncat(char *, const char *, size_t); From fda60dd038373db8d96b8426630d6cbc9c0a5136 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Wed, 28 Aug 2019 18:44:42 +0000 Subject: [PATCH 136/181] [CFG] Make representation of destructor calls more accurate. Respect C++17 copy elision; previously it would generate destructor calls for elided temporaries, including in initialization and return statements. Don't generate duplicate destructor calls for statement expressions. Fix destructors in initialization lists and comma operators. Improve printing of implicit destructors. Patch by Nicholas Allegra! Differential Revision: https://reviews.llvm.org/D66404 llvm-svn: 370247 (cherry picked from commit ead98ea3eb4e207be7ca003b612b68b7dfbb8f7e) --- clang/lib/Analysis/CFG.cpp | 123 ++++--- .../Analysis/auto-obj-dtors-cfg-output.cpp | 4 +- clang/test/Analysis/cfg-rich-constructors.cpp | 33 +- clang/test/Analysis/cfg-rich-constructors.mm | 3 +- clang/test/Analysis/cfg.cpp | 4 +- .../test/Analysis/missing-bind-temporary.cpp | 6 +- clang/test/Analysis/more-dtors-cfg-output.cpp | 317 ++++++++++++++++++ clang/test/Analysis/scopes-cfg-output.cpp | 4 +- clang/test/Analysis/temporaries.cpp | 19 +- 9 files changed, 423 insertions(+), 90 deletions(-) create mode 100644 clang/test/Analysis/more-dtors-cfg-output.cpp diff --git a/clang/lib/Analysis/CFG.cpp b/clang/lib/Analysis/CFG.cpp index 0f813566007ab..8bfaeb6c1d1fe 100644 --- a/clang/lib/Analysis/CFG.cpp +++ b/clang/lib/Analysis/CFG.cpp @@ -525,7 +525,7 @@ class CFGBuilder { CFGBlock *VisitCallExpr(CallExpr *C, AddStmtChoice asc); CFGBlock *VisitCaseStmt(CaseStmt *C); CFGBlock *VisitChooseExpr(ChooseExpr *C, AddStmtChoice asc); - CFGBlock *VisitCompoundStmt(CompoundStmt *C); + CFGBlock *VisitCompoundStmt(CompoundStmt *C, bool ExternallyDestructed); CFGBlock *VisitConditionalOperator(AbstractConditionalOperator *C, AddStmtChoice asc); CFGBlock *VisitContinueStmt(ContinueStmt *C); @@ -546,7 +546,8 @@ class CFGBuilder { CFGBlock *VisitDeclSubExpr(DeclStmt *DS); CFGBlock *VisitDefaultStmt(DefaultStmt *D); CFGBlock *VisitDoStmt(DoStmt *D); - CFGBlock *VisitExprWithCleanups(ExprWithCleanups *E, AddStmtChoice asc); + CFGBlock *VisitExprWithCleanups(ExprWithCleanups *E, + AddStmtChoice asc, bool ExternallyDestructed); CFGBlock *VisitForStmt(ForStmt *F); CFGBlock *VisitGotoStmt(GotoStmt *G); CFGBlock *VisitGCCAsmStmt(GCCAsmStmt *G, AddStmtChoice asc); @@ -585,7 +586,8 @@ class CFGBuilder { CFGBlock *VisitUnaryOperator(UnaryOperator *U, AddStmtChoice asc); CFGBlock *VisitWhileStmt(WhileStmt *W); - CFGBlock *Visit(Stmt *S, AddStmtChoice asc = AddStmtChoice::NotAlwaysAdd); + CFGBlock *Visit(Stmt *S, AddStmtChoice asc = AddStmtChoice::NotAlwaysAdd, + bool ExternallyDestructed = false); CFGBlock *VisitStmt(Stmt *S, AddStmtChoice asc); CFGBlock *VisitChildren(Stmt *S); CFGBlock *VisitNoRecurse(Expr *E, AddStmtChoice asc); @@ -654,15 +656,17 @@ class CFGBuilder { // Visitors to walk an AST and generate destructors of temporaries in // full expression. - CFGBlock *VisitForTemporaryDtors(Stmt *E, bool BindToTemporary, + CFGBlock *VisitForTemporaryDtors(Stmt *E, bool ExternallyDestructed, TempDtorContext &Context); - CFGBlock *VisitChildrenForTemporaryDtors(Stmt *E, TempDtorContext &Context); + CFGBlock *VisitChildrenForTemporaryDtors(Stmt *E, bool ExternallyDestructed, + TempDtorContext &Context); CFGBlock *VisitBinaryOperatorForTemporaryDtors(BinaryOperator *E, + bool ExternallyDestructed, TempDtorContext &Context); CFGBlock *VisitCXXBindTemporaryExprForTemporaryDtors( - CXXBindTemporaryExpr *E, bool BindToTemporary, TempDtorContext &Context); + CXXBindTemporaryExpr *E, bool ExternallyDestructed, TempDtorContext &Context); CFGBlock *VisitConditionalOperatorForTemporaryDtors( - AbstractConditionalOperator *E, bool BindToTemporary, + AbstractConditionalOperator *E, bool ExternallyDestructed, TempDtorContext &Context); void InsertTempDtorDecisionBlock(const TempDtorContext &Context, CFGBlock *FalseSucc = nullptr); @@ -1573,7 +1577,7 @@ CFGBlock *CFGBuilder::addInitializer(CXXCtorInitializer *I) { // Generate destructors for temporaries in initialization expression. TempDtorContext Context; VisitForTemporaryDtors(cast(Init)->getSubExpr(), - /*BindToTemporary=*/false, Context); + /*ExternallyDestructed=*/false, Context); } } @@ -2049,7 +2053,8 @@ CFGBuilder::prependAutomaticObjScopeEndWithTerminator( /// Visit - Walk the subtree of a statement and add extra /// blocks for ternary operators, &&, and ||. We also process "," and /// DeclStmts (which may contain nested control-flow). -CFGBlock *CFGBuilder::Visit(Stmt * S, AddStmtChoice asc) { +CFGBlock *CFGBuilder::Visit(Stmt * S, AddStmtChoice asc, + bool ExternallyDestructed) { if (!S) { badCFG = true; return nullptr; @@ -2090,7 +2095,7 @@ CFGBlock *CFGBuilder::Visit(Stmt * S, AddStmtChoice asc) { return VisitChooseExpr(cast(S), asc); case Stmt::CompoundStmtClass: - return VisitCompoundStmt(cast(S)); + return VisitCompoundStmt(cast(S), ExternallyDestructed); case Stmt::ConditionalOperatorClass: return VisitConditionalOperator(cast(S), asc); @@ -2102,7 +2107,8 @@ CFGBlock *CFGBuilder::Visit(Stmt * S, AddStmtChoice asc) { return VisitCXXCatchStmt(cast(S)); case Stmt::ExprWithCleanupsClass: - return VisitExprWithCleanups(cast(S), asc); + return VisitExprWithCleanups(cast(S), + asc, ExternallyDestructed); case Stmt::CXXDefaultArgExprClass: case Stmt::CXXDefaultInitExprClass: @@ -2597,7 +2603,7 @@ CFGBlock *CFGBuilder::VisitChooseExpr(ChooseExpr *C, return addStmt(C->getCond()); } -CFGBlock *CFGBuilder::VisitCompoundStmt(CompoundStmt *C) { +CFGBlock *CFGBuilder::VisitCompoundStmt(CompoundStmt *C, bool ExternallyDestructed) { LocalScope::const_iterator scopeBeginPos = ScopePos; addLocalScopeForStmt(C); @@ -2613,11 +2619,16 @@ CFGBlock *CFGBuilder::VisitCompoundStmt(CompoundStmt *C) { I != E; ++I ) { // If we hit a segment of code just containing ';' (NullStmts), we can // get a null block back. In such cases, just use the LastBlock - if (CFGBlock *newBlock = addStmt(*I)) + CFGBlock *newBlock = Visit(*I, AddStmtChoice::AlwaysAdd, + ExternallyDestructed); + + if (newBlock) LastBlock = newBlock; if (badCFG) return nullptr; + + ExternallyDestructed = false; } return LastBlock; @@ -2760,7 +2771,7 @@ CFGBlock *CFGBuilder::VisitDeclSubExpr(DeclStmt *DS) { // Generate destructors for temporaries in initialization expression. TempDtorContext Context; VisitForTemporaryDtors(cast(Init)->getSubExpr(), - /*BindToTemporary=*/false, Context); + /*ExternallyDestructed=*/true, Context); } } @@ -2974,9 +2985,18 @@ CFGBlock *CFGBuilder::VisitReturnStmt(Stmt *S) { if (!Block->hasNoReturnElement()) addSuccessor(Block, &cfg->getExit()); - // Add the return statement to the block. This may create new blocks if R - // contains control-flow (short-circuit operations). - return VisitStmt(S, AddStmtChoice::AlwaysAdd); + // Add the return statement to the block. + appendStmt(Block, S); + + // Visit children + if (ReturnStmt *RS = dyn_cast(S)) { + Expr *O = RS->getRetValue(); + if (O) + Visit(O, AddStmtChoice::AlwaysAdd, /*ExternallyDestructed=*/true); + return Block; + } else { // co_return + return VisitChildren(S); + } } CFGBlock *CFGBuilder::VisitSEHExceptStmt(SEHExceptStmt *ES) { @@ -3008,7 +3028,7 @@ CFGBlock *CFGBuilder::VisitSEHExceptStmt(SEHExceptStmt *ES) { } CFGBlock *CFGBuilder::VisitSEHFinallyStmt(SEHFinallyStmt *FS) { - return VisitCompoundStmt(FS->getBlock()); + return VisitCompoundStmt(FS->getBlock(), /*ExternallyDestructed=*/false); } CFGBlock *CFGBuilder::VisitSEHLeaveStmt(SEHLeaveStmt *LS) { @@ -3892,7 +3912,7 @@ CFGBlock *CFGBuilder::VisitStmtExpr(StmtExpr *SE, AddStmtChoice asc) { autoCreateBlock(); appendStmt(Block, SE); } - return VisitCompoundStmt(SE->getSubStmt()); + return VisitCompoundStmt(SE->getSubStmt(), /*ExternallyDestructed=*/true); } CFGBlock *CFGBuilder::VisitSwitchStmt(SwitchStmt *Terminator) { @@ -4357,12 +4377,12 @@ CFGBlock *CFGBuilder::VisitCXXForRangeStmt(CXXForRangeStmt *S) { } CFGBlock *CFGBuilder::VisitExprWithCleanups(ExprWithCleanups *E, - AddStmtChoice asc) { + AddStmtChoice asc, bool ExternallyDestructed) { if (BuildOpts.AddTemporaryDtors) { // If adding implicit destructors visit the full expression for adding // destructors of temporaries. TempDtorContext Context; - VisitForTemporaryDtors(E->getSubExpr(), false, Context); + VisitForTemporaryDtors(E->getSubExpr(), ExternallyDestructed, Context); // Full expression has to be added as CFGStmt so it will be sequenced // before destructors of it's temporaries. @@ -4498,7 +4518,7 @@ CFGBlock *CFGBuilder::VisitIndirectGotoStmt(IndirectGotoStmt *I) { return addStmt(I->getTarget()); } -CFGBlock *CFGBuilder::VisitForTemporaryDtors(Stmt *E, bool BindToTemporary, +CFGBlock *CFGBuilder::VisitForTemporaryDtors(Stmt *E, bool ExternallyDestructed, TempDtorContext &Context) { assert(BuildOpts.AddImplicitDtors && BuildOpts.AddTemporaryDtors); @@ -4509,28 +4529,32 @@ CFGBlock *CFGBuilder::VisitForTemporaryDtors(Stmt *E, bool BindToTemporary, } switch (E->getStmtClass()) { default: - return VisitChildrenForTemporaryDtors(E, Context); + return VisitChildrenForTemporaryDtors(E, false, Context); + + case Stmt::InitListExprClass: + return VisitChildrenForTemporaryDtors(E, ExternallyDestructed, Context); case Stmt::BinaryOperatorClass: return VisitBinaryOperatorForTemporaryDtors(cast(E), + ExternallyDestructed, Context); case Stmt::CXXBindTemporaryExprClass: return VisitCXXBindTemporaryExprForTemporaryDtors( - cast(E), BindToTemporary, Context); + cast(E), ExternallyDestructed, Context); case Stmt::BinaryConditionalOperatorClass: case Stmt::ConditionalOperatorClass: return VisitConditionalOperatorForTemporaryDtors( - cast(E), BindToTemporary, Context); + cast(E), ExternallyDestructed, Context); case Stmt::ImplicitCastExprClass: - // For implicit cast we want BindToTemporary to be passed further. + // For implicit cast we want ExternallyDestructed to be passed further. E = cast(E)->getSubExpr(); goto tryAgain; case Stmt::CXXFunctionalCastExprClass: - // For functional cast we want BindToTemporary to be passed further. + // For functional cast we want ExternallyDestructed to be passed further. E = cast(E)->getSubExpr(); goto tryAgain; @@ -4544,7 +4568,7 @@ CFGBlock *CFGBuilder::VisitForTemporaryDtors(Stmt *E, bool BindToTemporary, case Stmt::MaterializeTemporaryExprClass: { const MaterializeTemporaryExpr* MTE = cast(E); - BindToTemporary = (MTE->getStorageDuration() != SD_FullExpression); + ExternallyDestructed = (MTE->getStorageDuration() != SD_FullExpression); SmallVector CommaLHSs; SmallVector Adjustments; // Find the expression whose lifetime needs to be extended. @@ -4555,7 +4579,7 @@ CFGBlock *CFGBuilder::VisitForTemporaryDtors(Stmt *E, bool BindToTemporary, // Visit the skipped comma operator left-hand sides for other temporaries. for (const Expr *CommaLHS : CommaLHSs) { VisitForTemporaryDtors(const_cast(CommaLHS), - /*BindToTemporary=*/false, Context); + /*ExternallyDestructed=*/false, Context); } goto tryAgain; } @@ -4573,13 +4597,18 @@ CFGBlock *CFGBuilder::VisitForTemporaryDtors(Stmt *E, bool BindToTemporary, for (Expr *Init : LE->capture_inits()) { if (Init) { if (CFGBlock *R = VisitForTemporaryDtors( - Init, /*BindToTemporary=*/false, Context)) + Init, /*ExternallyDestructed=*/true, Context)) B = R; } } return B; } + case Stmt::StmtExprClass: + // Don't recurse into statement expressions; any cleanups inside them + // will be wrapped in their own ExprWithCleanups. + return Block; + case Stmt::CXXDefaultArgExprClass: E = cast(E)->getExpr(); goto tryAgain; @@ -4591,6 +4620,7 @@ CFGBlock *CFGBuilder::VisitForTemporaryDtors(Stmt *E, bool BindToTemporary, } CFGBlock *CFGBuilder::VisitChildrenForTemporaryDtors(Stmt *E, + bool ExternallyDestructed, TempDtorContext &Context) { if (isa(E)) { // Do not visit the children of lambdas; they have their own CFGs. @@ -4604,14 +4634,22 @@ CFGBlock *CFGBuilder::VisitChildrenForTemporaryDtors(Stmt *E, CFGBlock *B = Block; for (Stmt *Child : E->children()) if (Child) - if (CFGBlock *R = VisitForTemporaryDtors(Child, false, Context)) + if (CFGBlock *R = VisitForTemporaryDtors(Child, ExternallyDestructed, Context)) B = R; return B; } CFGBlock *CFGBuilder::VisitBinaryOperatorForTemporaryDtors( - BinaryOperator *E, TempDtorContext &Context) { + BinaryOperator *E, bool ExternallyDestructed, TempDtorContext &Context) { + if (E->isCommaOp()) { + // For comma operator LHS expression is visited + // before RHS expression. For destructors visit them in reverse order. + CFGBlock *RHSBlock = VisitForTemporaryDtors(E->getRHS(), ExternallyDestructed, Context); + CFGBlock *LHSBlock = VisitForTemporaryDtors(E->getLHS(), false, Context); + return LHSBlock ? LHSBlock : RHSBlock; + } + if (E->isLogicalOp()) { VisitForTemporaryDtors(E->getLHS(), false, Context); TryResult RHSExecuted = tryEvaluateBool(E->getLHS()); @@ -4646,10 +4684,11 @@ CFGBlock *CFGBuilder::VisitBinaryOperatorForTemporaryDtors( } CFGBlock *CFGBuilder::VisitCXXBindTemporaryExprForTemporaryDtors( - CXXBindTemporaryExpr *E, bool BindToTemporary, TempDtorContext &Context) { + CXXBindTemporaryExpr *E, bool ExternallyDestructed, TempDtorContext &Context) { // First add destructors for temporaries in subexpression. - CFGBlock *B = VisitForTemporaryDtors(E->getSubExpr(), false, Context); - if (!BindToTemporary) { + // Because VisitCXXBindTemporaryExpr calls setDestructed: + CFGBlock *B = VisitForTemporaryDtors(E->getSubExpr(), true, Context); + if (!ExternallyDestructed) { // If lifetime of temporary is not prolonged (by assigning to constant // reference) add destructor for it. @@ -4697,7 +4736,7 @@ void CFGBuilder::InsertTempDtorDecisionBlock(const TempDtorContext &Context, } CFGBlock *CFGBuilder::VisitConditionalOperatorForTemporaryDtors( - AbstractConditionalOperator *E, bool BindToTemporary, + AbstractConditionalOperator *E, bool ExternallyDestructed, TempDtorContext &Context) { VisitForTemporaryDtors(E->getCond(), false, Context); CFGBlock *ConditionBlock = Block; @@ -4708,14 +4747,14 @@ CFGBlock *CFGBuilder::VisitConditionalOperatorForTemporaryDtors( TempDtorContext TrueContext( bothKnownTrue(Context.KnownExecuted, ConditionVal)); - VisitForTemporaryDtors(E->getTrueExpr(), BindToTemporary, TrueContext); + VisitForTemporaryDtors(E->getTrueExpr(), ExternallyDestructed, TrueContext); CFGBlock *TrueBlock = Block; Block = ConditionBlock; Succ = ConditionSucc; TempDtorContext FalseContext( bothKnownTrue(Context.KnownExecuted, NegatedVal)); - VisitForTemporaryDtors(E->getFalseExpr(), BindToTemporary, FalseContext); + VisitForTemporaryDtors(E->getFalseExpr(), ExternallyDestructed, FalseContext); if (TrueContext.TerminatorExpr && FalseContext.TerminatorExpr) { InsertTempDtorDecisionBlock(FalseContext, TrueBlock); @@ -5312,15 +5351,13 @@ static void print_elem(raw_ostream &OS, StmtPrinterHelper &Helper, const VarDecl *VD = DE->getVarDecl(); Helper.handleDecl(VD, OS); - ASTContext &ACtx = VD->getASTContext(); QualType T = VD->getType(); if (T->isReferenceType()) T = getReferenceInitTemporaryType(VD->getInit(), nullptr); - if (const ArrayType *AT = ACtx.getAsArrayType(T)) - T = ACtx.getBaseElementType(AT); - OS << ".~" << T->getAsCXXRecordDecl()->getName().str() << "()"; - OS << " (Implicit destructor)\n"; + OS << ".~"; + T.getUnqualifiedType().print(OS, PrintingPolicy(Helper.getLangOpts())); + OS << "() (Implicit destructor)\n"; } else if (Optional DE = E.getAs()) { const VarDecl *VD = DE->getVarDecl(); Helper.handleDecl(VD, OS); diff --git a/clang/test/Analysis/auto-obj-dtors-cfg-output.cpp b/clang/test/Analysis/auto-obj-dtors-cfg-output.cpp index 7e678a1ec78d4..716eaf3c0fb44 100644 --- a/clang/test/Analysis/auto-obj-dtors-cfg-output.cpp +++ b/clang/test/Analysis/auto-obj-dtors-cfg-output.cpp @@ -334,7 +334,7 @@ void test_aggregate_array_lifetime_extension() { // CHECK-NEXT: 61: ~A() (Temporary object destructor) // CHECK-NEXT: 62: ~A() (Temporary object destructor) // CHECK-NEXT: 63: ~A() (Temporary object destructor) -// CHECK-NEXT: 64: [B1.57].~D() (Implicit destructor) +// CHECK-NEXT: 64: [B1.57].~D [2]() (Implicit destructor) // CHECK-NEXT: 65: [B1.18].~D() (Implicit destructor) // CHECK-NEXT: Preds (1): B2 // CHECK-NEXT: Succs (1): B0 @@ -363,7 +363,7 @@ void test_aggregate_with_nontrivial_own_destructor() { // WARNINGS-NEXT: 3: (CXXConstructExpr, class A [0]) // ANALYZER-NEXT: 3: (CXXConstructExpr, [B1.4], class A [0]) // CHECK-NEXT: 4: A b[0]; -// CHECK-NEXT: 5: [B1.2].~A() (Implicit destructor) +// CHECK-NEXT: 5: [B1.2].~A [2]() (Implicit destructor) // CHECK-NEXT: Preds (1): B2 // CHECK-NEXT: Succs (1): B0 // CHECK: [B0 (EXIT)] diff --git a/clang/test/Analysis/cfg-rich-constructors.cpp b/clang/test/Analysis/cfg-rich-constructors.cpp index cb1ed8eb6b2b3..e33e57597d9e4 100644 --- a/clang/test/Analysis/cfg-rich-constructors.cpp +++ b/clang/test/Analysis/cfg-rich-constructors.cpp @@ -412,7 +412,6 @@ class D { ~D(); }; -// FIXME: There should be no temporary destructor in C++17. // CHECK: return_stmt_with_dtor::D returnTemporary() // CXX11-ELIDE: 1: return_stmt_with_dtor::D() (CXXConstructExpr, [B1.2], [B1.4], [B1.5], class return_stmt_with_dtor::D) // CXX11-NOELIDE: 1: return_stmt_with_dtor::D() (CXXConstructExpr, [B1.2], [B1.4], class return_stmt_with_dtor::D) @@ -422,15 +421,13 @@ class D { // CXX11-NEXT: 5: [B1.4] (CXXConstructExpr, [B1.7], class return_stmt_with_dtor::D) // CXX11-NEXT: 6: ~return_stmt_with_dtor::D() (Temporary object destructor) // CXX11-NEXT: 7: return [B1.5]; -// CXX17: 1: return_stmt_with_dtor::D() (CXXConstructExpr, [B1.4], [B1.2], class return_stmt_w +// CXX17: 1: return_stmt_with_dtor::D() (CXXConstructExpr, [B1.3], [B1.2], class return_stmt_w // CXX17-NEXT: 2: [B1.1] (BindTemporary) -// CXX17-NEXT: 3: ~return_stmt_with_dtor::D() (Temporary object destructor) -// CXX17-NEXT: 4: return [B1.2]; +// CXX17-NEXT: 3: return [B1.2]; D returnTemporary() { return D(); } -// FIXME: There should be no temporary destructor in C++17. // CHECK: void returnByValueIntoVariable() // CHECK: 1: returnTemporary // CHECK-NEXT: 2: [B1.1] (ImplicitCastExpr, FunctionToPointerDecay, class return_stmt_with_dtor::D (*)(void)) @@ -442,12 +439,10 @@ D returnTemporary() { // CXX11-NEXT: 7: [B1.6] (CXXConstructExpr, [B1.8], class return_stmt_with_dtor::D) // CXX11-NEXT: 8: return_stmt_with_dtor::D d = returnTemporary(); // CXX11-NEXT: 9: ~return_stmt_with_dtor::D() (Temporary object destructor) -// CXX11-NEXT: 10: [B1.8].~D() (Implicit destructor) +// CXX11-NEXT: 10: [B1.8].~return_stmt_with_dtor::D() (Implicit destructor) // CXX17-NEXT: 3: [B1.2]() (CXXRecordTypedCall, [B1.5], [B1.4]) // CXX17-NEXT: 4: [B1.3] (BindTemporary) // CXX17-NEXT: 5: return_stmt_with_dtor::D d = returnTemporary(); -// CXX17-NEXT: 6: ~return_stmt_with_dtor::D() (Temporary object destructor) -// CXX17-NEXT: 7: [B1.5].~D() (Implicit destructor) void returnByValueIntoVariable() { D d = returnTemporary(); } @@ -602,7 +597,7 @@ void temporaryInCondition() { // CHECK-NEXT: 3: [B1.2] (BindTemporary) // CHECK-NEXT: 4: [B1.3] // CHECK-NEXT: 5: const temporary_object_expr_with_dtors::D &d(0); -// CHECK-NEXT: 6: [B1.5].~D() (Implicit destructor) +// CHECK-NEXT: 6: [B1.5].~temporary_object_expr_with_dtors::D() (Implicit destructor) void referenceVariableWithConstructor() { const D &d(0); } @@ -613,14 +608,14 @@ void referenceVariableWithConstructor() { // CHECK-NEXT: 3: [B1.2] (ImplicitCastExpr, NoOp, const class temporary_object_expr_with_dtors::D) // CHECK-NEXT: 4: [B1.3] // CHECK-NEXT: 5: const temporary_object_expr_with_dtors::D &d = temporary_object_expr_with_dtors::D(); -// CHECK-NEXT: 6: [B1.5].~D() (Implicit destructor) +// CHECK-NEXT: 6: [B1.5].~temporary_object_expr_with_dtors::D() (Implicit destructor) void referenceVariableWithInitializer() { const D &d = D(); } // CHECK: void referenceVariableWithTernaryOperator(bool coin) // CXX11: [B1] -// CXX11-NEXT: 1: [B4.4].~D() (Implicit destructor) +// CXX11-NEXT: 1: [B4.4].~temporary_object_expr_with_dtors::D() (Implicit destructor) // CXX11: [B2] // CXX11-NEXT: 1: ~temporary_object_expr_with_dtors::D() (Temporary object destructor) // CXX11: [B3] @@ -660,7 +655,7 @@ void referenceVariableWithInitializer() { // CXX17-NEXT: 2: [B1.1] (ImplicitCastExpr, NoOp, const class temporary_object_expr_with_dtors::D) // CXX17-NEXT: 3: [B1.2] // CXX17-NEXT: 4: const temporary_object_expr_with_dtors::D &d = coin ? D::get() : temporary_object_expr_with_dtors::D(0); -// CXX17-NEXT: 5: [B1.4].~D() (Implicit destructor) +// CXX17-NEXT: 5: [B1.4].~temporary_object_expr_with_dtors::D() (Implicit destructor) // CXX17: [B2] // CXX17-NEXT: 1: D::get // CXX17-NEXT: 2: [B2.1] (ImplicitCastExpr, FunctionToPointerDecay, class temporary_object_expr_with_dtors::D (*)(void)) @@ -686,7 +681,7 @@ void referenceVariableWithTernaryOperator(bool coin) { // CHECK-NEXT: 4: temporary_object_expr_with_dtors::D([B1.3]) (CXXFunctionalCastExpr, ConstructorCon // CHECK-NEXT: 5: [B1.4] // CHECK-NEXT: 6: temporary_object_expr_with_dtors::D &&d = temporary_object_expr_with_dtors::D(1); -// CHECK-NEXT: 7: [B1.6].~D() (Implicit destructor) +// CHECK-NEXT: 7: [B1.6].~temporary_object_expr_with_dtors::D() (Implicit destructor) void referenceWithFunctionalCast() { D &&d = D(1); } @@ -743,13 +738,13 @@ class B { // CXX11-NEXT: 9: [B1.8] (CXXConstructExpr, [B1.10], class implicit_constructor_conversion::B) // CXX11-NEXT: 10: implicit_constructor_conversion::B b = implicit_constructor_conversion::A(); // CXX11-NEXT: 11: ~implicit_constructor_conversion::B() (Temporary object destructor) -// CXX11-NEXT: 12: [B1.10].~B() (Implicit destructor) +// CXX11-NEXT: 12: [B1.10].~implicit_constructor_conversion::B() (Implicit destructor) // CXX17-NEXT: 2: [B1.1] (ImplicitCastExpr, NoOp, const class implicit_constructor_conversion::A) // CXX17-NEXT: 3: [B1.2] // CXX17-NEXT: 4: [B1.3] (CXXConstructExpr, [B1.6], class implicit_constructor_conversion::B) // CXX17-NEXT: 5: [B1.4] (ImplicitCastExpr, ConstructorConversion, class implicit_constructor_conversion::B) // CXX17-NEXT: 6: implicit_constructor_conversion::B b = implicit_constructor_conversion::A(); -// CXX17-NEXT: 7: [B1.6].~B() (Implicit destructor) +// CXX17-NEXT: 7: [B1.6].~implicit_constructor_conversion::B() (Implicit destructor) void implicitConstructionConversionFromTemporary() { B b = A(); } @@ -769,11 +764,11 @@ void implicitConstructionConversionFromTemporary() { // CXX11-NEXT: 11: [B1.10] (CXXConstructExpr, [B1.12], class implicit_constructor_conversion::B) // CXX11-NEXT: 12: implicit_constructor_conversion::B b = get(); // CXX11-NEXT: 13: ~implicit_constructor_conversion::B() (Temporary object destructor) -// CXX11-NEXT: 14: [B1.12].~B() (Implicit destructor) +// CXX11-NEXT: 14: [B1.12].~implicit_constructor_conversion::B() (Implicit destructor) // CXX17-NEXT: 6: [B1.5] (CXXConstructExpr, [B1.8], class implicit_constructor_conversion::B) // CXX17-NEXT: 7: [B1.6] (ImplicitCastExpr, ConstructorConversion, class implicit_constructor_conversion::B) // CXX17-NEXT: 8: implicit_constructor_conversion::B b = get(); -// CXX17-NEXT: 9: [B1.8].~B() (Implicit destructor) +// CXX17-NEXT: 9: [B1.8].~implicit_constructor_conversion::B() (Implicit destructor) void implicitConstructionConversionFromFunctionValue() { B b = get(); } @@ -787,7 +782,7 @@ void implicitConstructionConversionFromFunctionValue() { // CHECK-NEXT: 6: [B1.5] (ImplicitCastExpr, NoOp, const class implicit_constructor_conversion::B) // CHECK-NEXT: 7: [B1.6] // CHECK-NEXT: 8: const implicit_constructor_conversion::B &b = implicit_constructor_conversion::A(); -// CHECK-NEXT: 9: [B1.8].~B() (Implicit destructor) +// CHECK-NEXT: 9: [B1.8].~implicit_constructor_conversion::B() (Implicit destructor) void implicitConstructionConversionFromTemporaryWithLifetimeExtension() { const B &b = A(); } @@ -803,7 +798,7 @@ void implicitConstructionConversionFromTemporaryWithLifetimeExtension() { // CHECK-NEXT: 8: [B1.7] (ImplicitCastExpr, NoOp, const class implicit_constructor_conversion::B) // CHECK-NEXT: 9: [B1.8] // CHECK-NEXT: 10: const implicit_constructor_conversion::B &b = get(); -// CHECK-NEXT: 11: [B1.10].~B() (Implicit destructor) +// CHECK-NEXT: 11: [B1.10].~implicit_constructor_conversion::B() (Implicit destructor) void implicitConstructionConversionFromFunctionValueWithLifetimeExtension() { const B &b = get(); // no-crash } diff --git a/clang/test/Analysis/cfg-rich-constructors.mm b/clang/test/Analysis/cfg-rich-constructors.mm index 289094293eb5c..e55928d4c51fa 100644 --- a/clang/test/Analysis/cfg-rich-constructors.mm +++ b/clang/test/Analysis/cfg-rich-constructors.mm @@ -59,8 +59,7 @@ void passArgumentIntoMessage(E *e) { // CXX17-NEXT: 3: {{\[}}[B1.2] bar] (CXXRecordTypedCall, [B1.5], [B1.4]) // CXX17-NEXT: 4: [B1.3] (BindTemporary) // CXX17-NEXT: 5: D d = [e bar]; -// CXX17-NEXT: 6: ~D() (Temporary object destructor) -// CXX17-NEXT: 7: [B1.5].~D() (Implicit destructor) +// CXX17-NEXT: 6: [B1.5].~D() (Implicit destructor) void returnObjectFromMessage(E *e) { D d = [e bar]; } diff --git a/clang/test/Analysis/cfg.cpp b/clang/test/Analysis/cfg.cpp index ea028e06f3471..74f4d50f25dfc 100644 --- a/clang/test/Analysis/cfg.cpp +++ b/clang/test/Analysis/cfg.cpp @@ -203,7 +203,7 @@ namespace NoReturnSingleSuccessor { // CHECK-LABEL: int test1(int *x) // CHECK: 1: 1 // CHECK-NEXT: 2: return -// CHECK-NEXT: ~B() (Implicit destructor) +// CHECK-NEXT: ~NoReturnSingleSuccessor::B() (Implicit destructor) // CHECK-NEXT: Preds (1) // CHECK-NEXT: Succs (1): B0 int test1(int *x) { @@ -477,7 +477,7 @@ void test_lifetime_extended_temporaries() { // WARNINGS-NEXT: 1: (CXXConstructExpr, struct pr37688_deleted_union_destructor::A) // ANALYZER-NEXT: 1: (CXXConstructExpr, [B1.2], struct pr37688_deleted_union_destructor::A) // CHECK-NEXT: 2: pr37688_deleted_union_destructor::A a; -// CHECK-NEXT: 3: [B1.2].~A() (Implicit destructor) +// CHECK-NEXT: 3: [B1.2].~pr37688_deleted_union_destructor::A() (Implicit destructor) // CHECK-NEXT: Preds (1): B2 // CHECK-NEXT: Succs (1): B0 // CHECK: [B0 (EXIT)] diff --git a/clang/test/Analysis/missing-bind-temporary.cpp b/clang/test/Analysis/missing-bind-temporary.cpp index 7be4e2ddf3007..18204929bcbe0 100644 --- a/clang/test/Analysis/missing-bind-temporary.cpp +++ b/clang/test/Analysis/missing-bind-temporary.cpp @@ -31,7 +31,7 @@ class B { // CHECK-NEXT: 8: [B1.7] // CHECK-NEXT: 9: [B1.5] = [B1.8] (OperatorCall) // CHECK-NEXT: 10: ~variant_0::B() (Temporary object destructor) -// CHECK-NEXT: 11: [B1.2].~B() (Implicit destructor) +// CHECK-NEXT: 11: [B1.2].~variant_0::B() (Implicit destructor) void foo(int) { B i; i = {}; @@ -71,7 +71,7 @@ class B { // CHECK-NEXT: 6: {} (CXXConstructExpr, class variant_1::B) // CHECK-NEXT: 7: [B1.6] // CHECK-NEXT: 8: [B1.5] = [B1.7] (OperatorCall) -// CHECK-NEXT: 9: [B1.2].~B() (Implicit destructor) +// CHECK-NEXT: 9: [B1.2].~variant_1::B() (Implicit destructor) template void foo(T) { B i; i = {}; @@ -114,7 +114,7 @@ class B { // CHECK-NEXT: 9: [B1.8] // CHECK-NEXT: 10: [B1.5] = [B1.9] (OperatorCall) // CHECK-NEXT: 11: ~variant_2::B() (Temporary object destructor) -// CHECK-NEXT: 12: [B1.2].~B() (Implicit destructor) +// CHECK-NEXT: 12: [B1.2].~variant_2::B() (Implicit destructor) template void foo(T) { B i; i = {}; diff --git a/clang/test/Analysis/more-dtors-cfg-output.cpp b/clang/test/Analysis/more-dtors-cfg-output.cpp new file mode 100644 index 0000000000000..c0df5953aa6e1 --- /dev/null +++ b/clang/test/Analysis/more-dtors-cfg-output.cpp @@ -0,0 +1,317 @@ +// RUN: rm -f %t.14 %t.2a +// RUN: %clang_analyze_cc1 -analyzer-checker=debug.DumpCFG -std=c++14 -DCXX2A=0 -fblocks -Wall -Wno-unused -Werror %s > %t.14 2>&1 +// RUN: %clang_analyze_cc1 -analyzer-checker=debug.DumpCFG -std=c++2a -DCXX2A=1 -fblocks -Wall -Wno-unused -Werror %s > %t.2a 2>&1 +// RUN: FileCheck --input-file=%t.14 -check-prefixes=CHECK,CXX14 -implicit-check-not=destructor %s +// RUN: FileCheck --input-file=%t.2a -check-prefixes=CHECK,CXX2A -implicit-check-not=destructor %s + +int puts(const char *); + +struct Foo { + Foo() = delete; +#if CXX2A + // Guarantee that the elided examples are actually elided by deleting the + // copy constructor. + Foo(const Foo &) = delete; +#else + // No elision support, so we need a copy constructor. + Foo(const Foo &); +#endif + ~Foo(); +}; + +struct TwoFoos { + Foo foo1, foo2; + ~TwoFoos(); +}; + +Foo get_foo(); + +struct Bar { + Bar(); + Bar(const Bar &); + ~Bar(); + Bar &operator=(const Bar &); +}; + +Bar get_bar(); + +struct TwoBars { + Bar foo1, foo2; + ~TwoBars(); +}; + +// Start of tests: + +void elided_assign() { + Foo x = get_foo(); +} +// CHECK: void elided_assign() +// CXX14: (CXXConstructExpr{{.*}}, struct Foo) +// CXX14: ~Foo() (Temporary object destructor) +// CHECK: ~Foo() (Implicit destructor) + +void nonelided_assign() { + Bar x = (const Bar &)get_bar(); +} +// CHECK: void nonelided_assign() +// CHECK: (CXXConstructExpr{{.*}}, struct Bar) +// CHECK: ~Bar() (Temporary object destructor) +// CHECK: ~Bar() (Implicit destructor) + +void elided_paren_init() { + Foo x(get_foo()); +} +// CHECK: void elided_paren_init() +// CXX14: (CXXConstructExpr{{.*}}, struct Foo) +// CXX14: ~Foo() (Temporary object destructor) +// CHECK: ~Foo() (Implicit destructor) + +void nonelided_paren_init() { + Bar x((const Bar &)get_bar()); +} +// CHECK: void nonelided_paren_init() +// CHECK: (CXXConstructExpr{{.*}}, struct Bar) +// CHECK: ~Bar() (Temporary object destructor) +// CHECK: ~Bar() (Implicit destructor) + +void elided_brace_init() { + Foo x{get_foo()}; +} +// CHECK: void elided_brace_init() +// CXX14: (CXXConstructExpr{{.*}}, struct Foo) +// CXX14: ~Foo() (Temporary object destructor) +// CHECK: ~Foo() (Implicit destructor) + +void nonelided_brace_init() { + Bar x{(const Bar &)get_bar()}; +} +// CHECK: void nonelided_brace_init() +// CHECK: (CXXConstructExpr{{.*}}, struct Bar) +// CHECK: ~Bar() (Temporary object destructor) +// CHECK: ~Bar() (Implicit destructor) + +void elided_lambda_capture_init() { + // The copy from get_foo() into the lambda should be elided. Should call + // the lambda's destructor, but not ~Foo() separately. + // (This syntax is C++14 'generalized lambda captures'.) + auto z = [x=get_foo()]() {}; +} +// CHECK: void elided_lambda_capture_init() +// CXX14: (CXXConstructExpr{{.*}}, struct Foo) +// CXX14: ~(lambda at {{.*}})() (Temporary object destructor) +// CXX14: ~Foo() (Temporary object destructor) +// CHECK: ~(lambda at {{.*}})() (Implicit destructor) + +void nonelided_lambda_capture_init() { + // Should call the lambda's destructor as well as ~Bar() for the temporary. + auto z = [x((const Bar &)get_bar())]() {}; +} +// CHECK: void nonelided_lambda_capture_init() +// CHECK: (CXXConstructExpr{{.*}}, struct Bar) +// CXX14: ~(lambda at {{.*}})() (Temporary object destructor) +// CHECK: ~Bar() (Temporary object destructor) +// CHECK: ~(lambda at {{.*}})() (Implicit destructor) + +Foo elided_return_stmt_expr() { + // Two copies, both elided in C++17. + return ({ get_foo(); }); +} +// CHECK: Foo elided_return_stmt_expr() +// CXX14: (CXXConstructExpr{{.*}}, struct Foo) +// CXX14: ~Foo() (Temporary object destructor) +// CXX14: (CXXConstructExpr{{.*}}, struct Foo) +// CXX14: ~Foo() (Temporary object destructor) + +void elided_stmt_expr() { + // One copy, elided in C++17. + ({ get_foo(); }); +} +// CHECK: void elided_stmt_expr() +// CXX14: (CXXConstructExpr{{.*}}, struct Foo) +// CXX14: ~Foo() (Temporary object destructor) +// CHECK: ~Foo() (Temporary object destructor) + + +void elided_stmt_expr_multiple_stmts() { + // Make sure that only the value returned out of a statement expression is + // elided. + ({ get_bar(); get_foo(); }); +} +// CHECK: void elided_stmt_expr_multiple_stmts() +// CHECK: ~Bar() (Temporary object destructor) +// CXX14: (CXXConstructExpr{{.*}}, struct Foo) +// CXX14: ~Foo() (Temporary object destructor) +// CHECK: ~Foo() (Temporary object destructor) + + +void unelided_stmt_expr() { + ({ (const Bar &)get_bar(); }); +} +// CHECK: void unelided_stmt_expr() +// CHECK: (CXXConstructExpr{{.*}}, struct Bar) +// CHECK: ~Bar() (Temporary object destructor) +// CHECK: ~Bar() (Temporary object destructor) + +void elided_aggregate_init() { + TwoFoos x{get_foo(), get_foo()}; +} +// CHECK: void elided_aggregate_init() +// CXX14: (CXXConstructExpr{{.*}}, struct Foo) +// CXX14: (CXXConstructExpr{{.*}}, struct Foo) +// CXX14: ~Foo() (Temporary object destructor) +// CXX14: ~Foo() (Temporary object destructor) +// CHECK: ~TwoFoos() (Implicit destructor) + +void nonelided_aggregate_init() { + TwoBars x{(const Bar &)get_bar(), (const Bar &)get_bar()}; +} +// CHECK: void nonelided_aggregate_init() +// CHECK: (CXXConstructExpr{{.*}}, struct Bar) +// CHECK: (CXXConstructExpr{{.*}}, struct Bar) +// CHECK: ~Bar() (Temporary object destructor) +// CHECK: ~Bar() (Temporary object destructor) +// CHECK: ~TwoBars() (Implicit destructor) + +TwoFoos return_aggregate_init() { + return TwoFoos{get_foo(), get_foo()}; +} +// CHECK: TwoFoos return_aggregate_init() +// CXX14: (CXXConstructExpr{{.*}}, struct Foo) +// CXX14: (CXXConstructExpr{{.*}}, struct Foo) +// CXX14: ~TwoFoos() (Temporary object destructor) +// CXX14: ~Foo() (Temporary object destructor) +// CXX14: ~Foo() (Temporary object destructor) + +void lifetime_extended() { + const Foo &x = (get_foo(), get_foo()); + puts("one destroyed before, one destroyed after"); +} +// CHECK: void lifetime_extended() +// CHECK: ~Foo() (Temporary object destructor) +// CHECK: one destroyed before, one destroyed after +// CHECK: ~Foo() (Implicit destructor) + +void not_lifetime_extended() { + Foo x = (get_foo(), get_foo()); + puts("one destroyed before, one destroyed after"); +} +// CHECK: void not_lifetime_extended() +// CXX14: (CXXConstructExpr{{.*}}, struct Foo) +// CHECK: ~Foo() (Temporary object destructor) +// CXX14: ~Foo() (Temporary object destructor) +// CHECK: one destroyed before, one destroyed after +// CHECK: ~Foo() (Implicit destructor) + +void compound_literal() { + (void)(Bar[]){{}, {}}; +} +// CHECK: void compound_literal() +// CHECK: (CXXConstructExpr, struct Bar) +// CHECK: (CXXConstructExpr, struct Bar) +// CHECK: ~Bar [2]() (Temporary object destructor) + +Foo elided_return() { + return get_foo(); +} +// CHECK: Foo elided_return() +// CXX14: (CXXConstructExpr{{.*}}, struct Foo) +// CXX14: ~Foo() (Temporary object destructor) + +auto elided_return_lambda() { + return [x=get_foo()]() {}; +} +// CHECK: (lambda at {{.*}}) elided_return_lambda() +// CXX14: (CXXConstructExpr{{.*}}, class (lambda at {{.*}})) +// CXX14: ~(lambda at {{.*}})() (Temporary object destructor) +// CXX14: ~Foo() (Temporary object destructor) + +void const_auto_obj() { + const Bar bar; +} +// CHECK: void const_auto_obj() +// CHECK: .~Bar() (Implicit destructor) + +void has_default_arg(Foo foo = get_foo()); +void test_default_arg() { + // FIXME: This emits a destructor but no constructor. Search CFG.cpp for + // 'PR13385' for details. + has_default_arg(); +} +// CHECK: void test_default_arg() +// CXX14: ~Foo() (Temporary object destructor) +// CHECK: ~Foo() (Temporary object destructor) + +struct DefaultArgInCtor { + DefaultArgInCtor(Foo foo = get_foo()); + ~DefaultArgInCtor(); +}; + +void default_ctor_with_default_arg() { + // FIXME: Default arguments are mishandled in two ways: + // - The constructor is not emitted at all (not specific to arrays; see fixme + // in CFG.cpp that mentions PR13385). + // - The destructor is emitted once, even though the default argument will be + // constructed and destructed once per array element. + // Ideally, the CFG would expand array constructions into a loop that + // constructs each array element, allowing default argument + // constructor/destructor calls to be correctly placed inside the loop. + DefaultArgInCtor qux[3]; +} +// CHECK: void default_ctor_with_default_arg() +// CHECK: CXXConstructExpr, {{.*}}, struct DefaultArgInCtor [3] +// CXX14: ~Foo() (Temporary object destructor) +// CHECK: ~Foo() (Temporary object destructor) +// CHECK: .~DefaultArgInCtor [3]() (Implicit destructor) + +void new_default_ctor_with_default_arg(long count) { + // Same problems as above. + new DefaultArgInCtor[count]; +} +// CHECK: void new_default_ctor_with_default_arg(long count) +// CHECK: CXXConstructExpr, {{.*}}, struct DefaultArgInCtor [] +// CXX14: ~Foo() (Temporary object destructor) +// CHECK: ~Foo() (Temporary object destructor) + +#if CXX2A +// Boilerplate needed to test co_return: + +namespace std::experimental { + template + struct coroutine_handle { + static coroutine_handle from_address(void *); + }; +} + +struct TestPromise { + TestPromise initial_suspend(); + TestPromise final_suspend(); + bool await_ready(); + void await_suspend(const std::experimental::coroutine_handle &); + void await_resume(); + Foo return_value(const Bar &); + Bar get_return_object(); + void unhandled_exception(); +}; + +namespace std::experimental { + template + struct coroutine_traits; + template <> + struct coroutine_traits { + using promise_type = TestPromise; + }; +} + +Bar coreturn() { + co_return get_bar(); + // This expands to something like: + // promise.return_value(get_bar()); + // get_bar() is passed by reference to return_value() and is then destroyed; + // there is no equivalent of RVO. TestPromise::return_value also returns a + // Foo, which should be immediately destroyed. + // FIXME: The generated CFG completely ignores get_return_object(). +} +// CXX2A: Bar coreturn() +// CXX2A: ~Foo() (Temporary object destructor) +// CXX2A: ~Bar() (Temporary object destructor) +#endif diff --git a/clang/test/Analysis/scopes-cfg-output.cpp b/clang/test/Analysis/scopes-cfg-output.cpp index f8d84b60deb1d..4b6e2a92c53ad 100644 --- a/clang/test/Analysis/scopes-cfg-output.cpp +++ b/clang/test/Analysis/scopes-cfg-output.cpp @@ -38,7 +38,7 @@ extern const bool UV; // CHECK-NEXT: 3: A a[2]; // CHECK-NEXT: 4: (CXXConstructExpr, [B1.5], class A [0]) // CHECK-NEXT: 5: A b[0]; -// CHECK-NEXT: 6: [B1.3].~A() (Implicit destructor) +// CHECK-NEXT: 6: [B1.3].~A [2]() (Implicit destructor) // CHECK-NEXT: 7: CFGScopeEnd(a) // CHECK-NEXT: Preds (1): B2 // CHECK-NEXT: Succs (1): B0 @@ -810,7 +810,7 @@ void test_for_compound_and_break() { // CHECK-NEXT: 1: CFGScopeEnd(__end1) // CHECK-NEXT: 2: CFGScopeEnd(__begin1) // CHECK-NEXT: 3: CFGScopeEnd(__range1) -// CHECK-NEXT: 4: [B5.3].~A() (Implicit destructor) +// CHECK-NEXT: 4: [B5.3].~A [10]() (Implicit destructor) // CHECK-NEXT: 5: CFGScopeEnd(a) // CHECK-NEXT: Preds (1): B2 // CHECK-NEXT: Succs (1): B0 diff --git a/clang/test/Analysis/temporaries.cpp b/clang/test/Analysis/temporaries.cpp index 6191abfb4d2ea..012cef52f14e3 100644 --- a/clang/test/Analysis/temporaries.cpp +++ b/clang/test/Analysis/temporaries.cpp @@ -830,12 +830,7 @@ void test_ternary_temporary_with_copy(int coin) { // On each branch the variable is constructed directly. if (coin) { clang_analyzer_eval(x == 1); // expected-warning{{TRUE}} -#if __cplusplus < 201703L clang_analyzer_eval(y == 1); // expected-warning{{TRUE}} -#else - // FIXME: Destructor called twice in C++17? - clang_analyzer_eval(y == 2); // expected-warning{{TRUE}} -#endif clang_analyzer_eval(z == 0); // expected-warning{{TRUE}} clang_analyzer_eval(w == 0); // expected-warning{{TRUE}} @@ -843,12 +838,7 @@ void test_ternary_temporary_with_copy(int coin) { clang_analyzer_eval(x == 0); // expected-warning{{TRUE}} clang_analyzer_eval(y == 0); // expected-warning{{TRUE}} clang_analyzer_eval(z == 1); // expected-warning{{TRUE}} -#if __cplusplus < 201703L clang_analyzer_eval(w == 1); // expected-warning{{TRUE}} -#else - // FIXME: Destructor called twice in C++17? - clang_analyzer_eval(w == 2); // expected-warning{{TRUE}} -#endif } } } // namespace test_match_constructors_and_destructors @@ -1055,16 +1045,11 @@ void foo(void (*bar4)(S)) { #endif bar2(S(2)); - // FIXME: Why are we losing information in C++17? clang_analyzer_eval(glob == 2); #ifdef TEMPORARY_DTORS -#if __cplusplus < 201703L - // expected-warning@-3{{TRUE}} -#else - // expected-warning@-5{{UNKNOWN}} -#endif + // expected-warning@-2{{TRUE}} #else - // expected-warning@-8{{UNKNOWN}} + // expected-warning@-4{{UNKNOWN}} #endif C *c = new D(); From 035b74e4c76a6f34113a40ddfc735a18d418520c Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Wed, 28 Aug 2019 21:19:58 +0000 Subject: [PATCH 137/181] [analyzer] Fix more analyzer warnings on analyzer and libAnalysis. llvm-svn: 370263 (cherry picked from commit 3517d10575e29b57b5894aced3539e0ab92fba05) --- clang/lib/Analysis/BodyFarm.cpp | 4 ++-- clang/lib/Analysis/CFG.cpp | 22 +++++++++------------ clang/lib/Analysis/CocoaConventions.cpp | 4 ++-- clang/lib/Analysis/RetainSummaryManager.cpp | 2 +- 4 files changed, 14 insertions(+), 18 deletions(-) diff --git a/clang/lib/Analysis/BodyFarm.cpp b/clang/lib/Analysis/BodyFarm.cpp index 7e636ed1f5889..a0ad130b09f0f 100644 --- a/clang/lib/Analysis/BodyFarm.cpp +++ b/clang/lib/Analysis/BodyFarm.cpp @@ -408,8 +408,8 @@ static Stmt *create_call_once(ASTContext &C, const FunctionDecl *D) { // reference. for (unsigned int ParamIdx = 2; ParamIdx < D->getNumParams(); ParamIdx++) { const ParmVarDecl *PDecl = D->getParamDecl(ParamIdx); - if (PDecl && - CallbackFunctionType->getParamType(ParamIdx - 2) + assert(PDecl); + if (CallbackFunctionType->getParamType(ParamIdx - 2) .getNonReferenceType() .getCanonicalType() != PDecl->getType().getNonReferenceType().getCanonicalType()) { diff --git a/clang/lib/Analysis/CFG.cpp b/clang/lib/Analysis/CFG.cpp index 8bfaeb6c1d1fe..3de18488d50a6 100644 --- a/clang/lib/Analysis/CFG.cpp +++ b/clang/lib/Analysis/CFG.cpp @@ -2474,10 +2474,8 @@ CFGBlock *CFGBuilder::VisitBreakStmt(BreakStmt *B) { static bool CanThrow(Expr *E, ASTContext &Ctx) { QualType Ty = E->getType(); - if (Ty->isFunctionPointerType()) - Ty = Ty->getAs()->getPointeeType(); - else if (Ty->isBlockPointerType()) - Ty = Ty->getAs()->getPointeeType(); + if (Ty->isFunctionPointerType() || Ty->isBlockPointerType()) + Ty = Ty->getPointeeType(); const FunctionType *FT = Ty->getAs(); if (FT) { @@ -4869,9 +4867,13 @@ CFGImplicitDtor::getDestructorDecl(ASTContext &astContext) const { while (const ArrayType *arrayType = astContext.getAsArrayType(ty)) { ty = arrayType->getElementType(); } - const RecordType *recordType = ty->getAs(); - const CXXRecordDecl *classDecl = - cast(recordType->getDecl()); + + // The situation when the type of the lifetime-extending reference + // does not correspond to the type of the object is supposed + // to be handled by now. In particular, 'ty' is now the unwrapped + // record type. + const CXXRecordDecl *classDecl = ty->getAsCXXRecordDecl(); + assert(classDecl); return classDecl->getDestructor(); } case CFGElement::DeleteDtor: { @@ -4896,12 +4898,6 @@ CFGImplicitDtor::getDestructorDecl(ASTContext &astContext) const { llvm_unreachable("getKind() returned bogus value"); } -bool CFGImplicitDtor::isNoReturn(ASTContext &astContext) const { - if (const CXXDestructorDecl *DD = getDestructorDecl(astContext)) - return DD->isNoReturn(); - return false; -} - //===----------------------------------------------------------------------===// // CFGBlock operations. //===----------------------------------------------------------------------===// diff --git a/clang/lib/Analysis/CocoaConventions.cpp b/clang/lib/Analysis/CocoaConventions.cpp index b2ef426dead28..571d72e1a8416 100644 --- a/clang/lib/Analysis/CocoaConventions.cpp +++ b/clang/lib/Analysis/CocoaConventions.cpp @@ -38,8 +38,8 @@ bool cocoa::isRefType(QualType RetTy, StringRef Prefix, return false; // Is the type void*? - const PointerType* PT = RetTy->getAs(); - if (!(PT->getPointeeType().getUnqualifiedType()->isVoidType())) + const PointerType* PT = RetTy->castAs(); + if (!PT || !PT->getPointeeType().getUnqualifiedType()->isVoidType()) return false; // Does the name start with the prefix? diff --git a/clang/lib/Analysis/RetainSummaryManager.cpp b/clang/lib/Analysis/RetainSummaryManager.cpp index 132053fd2c244..6f46917b2dfc6 100644 --- a/clang/lib/Analysis/RetainSummaryManager.cpp +++ b/clang/lib/Analysis/RetainSummaryManager.cpp @@ -504,7 +504,7 @@ RetainSummaryManager::generateSummary(const FunctionDecl *FD, FName = FName.substr(FName.find_first_not_of('_')); // Inspect the result type. Strip away any typedefs. - const auto *FT = FD->getType()->getAs(); + const auto *FT = FD->getType()->castAs(); QualType RetTy = FT->getReturnType(); if (TrackOSObjects) From 807d0ec2d822b77f93a346f4351a9c950a56bc34 Mon Sep 17 00:00:00 2001 From: Adam Balogh Date: Thu, 29 Aug 2019 09:35:47 +0000 Subject: [PATCH 138/181] [Analyzer] Iterator Checkers - Make range errors and invalidated access fatal Range errors (dereferencing or incrementing the past-the-end iterator or decrementing the iterator of the first element of the range) and access of invalidated iterators lead to undefined behavior. There is no point to continue the analysis after such an error on the same execution path, but terminate it by a sink node (fatal error). This also improves the performance and helps avoiding double reports (e.g. in case of nested iterators). Differential Revision: https://reviews.llvm.org/D62893 llvm-svn: 370314 (cherry picked from commit 12f5c7f0c3b00c08c2f2a6cdfeb532ccb5cca6d7) --- .../Checkers/IteratorChecker.cpp | 14 +- .../Inputs/system-header-simulator-cxx.h | 10 +- .../diagnostics/explicit-suppression.cpp | 2 +- clang/test/Analysis/invalidated-iterator.cpp | 485 +++++++++++++++--- clang/test/Analysis/iterator-range.cpp | 1 + 5 files changed, 441 insertions(+), 71 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp index 2d103c1c6aa08..047194afcf5f3 100644 --- a/clang/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp @@ -356,14 +356,12 @@ bool isZero(ProgramStateRef State, const NonLoc &Val); IteratorChecker::IteratorChecker() { OutOfRangeBugType.reset( - new BugType(this, "Iterator out of range", "Misuse of STL APIs", - /*SuppressOnSink=*/true)); + new BugType(this, "Iterator out of range", "Misuse of STL APIs")); MismatchedBugType.reset( new BugType(this, "Iterator(s) mismatched", "Misuse of STL APIs", /*SuppressOnSink=*/true)); InvalidatedBugType.reset( - new BugType(this, "Iterator invalidated", "Misuse of STL APIs", - /*SuppressOnSink=*/true)); + new BugType(this, "Iterator invalidated", "Misuse of STL APIs")); } void IteratorChecker::checkPreCall(const CallEvent &Call, @@ -928,7 +926,7 @@ void IteratorChecker::verifyDereference(CheckerContext &C, auto State = C.getState(); const auto *Pos = getIteratorPosition(State, Val); if (Pos && isPastTheEnd(State, *Pos)) { - auto *N = C.generateNonFatalErrorNode(State); + auto *N = C.generateErrorNode(State); if (!N) return; reportOutOfRangeBug("Past-the-end iterator dereferenced.", Val, C, N); @@ -940,7 +938,7 @@ void IteratorChecker::verifyAccess(CheckerContext &C, const SVal &Val) const { auto State = C.getState(); const auto *Pos = getIteratorPosition(State, Val); if (Pos && !Pos->isValid()) { - auto *N = C.generateNonFatalErrorNode(State); + auto *N = C.generateErrorNode(State); if (!N) { return; } @@ -1048,14 +1046,14 @@ void IteratorChecker::verifyRandomIncrOrDecr(CheckerContext &C, // The result may be the past-end iterator of the container, but any other // out of range position is undefined behaviour if (isAheadOfRange(State, advancePosition(C, Op, *Pos, Value))) { - auto *N = C.generateNonFatalErrorNode(State); + auto *N = C.generateErrorNode(State); if (!N) return; reportOutOfRangeBug("Iterator decremented ahead of its valid range.", LHS, C, N); } if (isBehindPastTheEnd(State, advancePosition(C, Op, *Pos, Value))) { - auto *N = C.generateNonFatalErrorNode(State); + auto *N = C.generateErrorNode(State); if (!N) return; reportOutOfRangeBug("Iterator incremented behind the past-the-end " diff --git a/clang/test/Analysis/Inputs/system-header-simulator-cxx.h b/clang/test/Analysis/Inputs/system-header-simulator-cxx.h index 30b25b85944b6..26248a4d1f51a 100644 --- a/clang/test/Analysis/Inputs/system-header-simulator-cxx.h +++ b/clang/test/Analysis/Inputs/system-header-simulator-cxx.h @@ -150,7 +150,7 @@ template struct __list_iterator { typedef std::bidirectional_iterator_tag iterator_category; __list_iterator(T* it = 0) : item(it) {} - __list_iterator(const iterator &rhs): item(rhs.base()) {} + __list_iterator(const iterator &rhs): item(rhs.item) {} __list_iterator operator++() { item = item->next; return *this; } __list_iterator operator++(int) { auto tmp = *this; @@ -175,6 +175,9 @@ template struct __list_iterator { const T* &base() const { return item; } + template + friend struct __list_iterator; + private: T* item; }; @@ -190,7 +193,7 @@ template struct __fwdl_iterator { typedef std::forward_iterator_tag iterator_category; __fwdl_iterator(T* it = 0) : item(it) {} - __fwdl_iterator(const iterator &rhs): item(rhs.base()) {} + __fwdl_iterator(const iterator &rhs): item(rhs.item) {} __fwdl_iterator operator++() { item = item->next; return *this; } __fwdl_iterator operator++(int) { auto tmp = *this; @@ -208,6 +211,9 @@ template struct __fwdl_iterator { const T* &base() const { return item; } + template + friend struct __fwdl_iterator; + private: T* item; }; diff --git a/clang/test/Analysis/diagnostics/explicit-suppression.cpp b/clang/test/Analysis/diagnostics/explicit-suppression.cpp index 6bc01479b815b..c10aaa528e0d8 100644 --- a/clang/test/Analysis/diagnostics/explicit-suppression.cpp +++ b/clang/test/Analysis/diagnostics/explicit-suppression.cpp @@ -19,6 +19,6 @@ class C { void testCopyNull(C *I, C *E) { std::copy(I, E, (C *)0); #ifndef SUPPRESSED - // expected-warning@../Inputs/system-header-simulator-cxx.h:680 {{Called C++ object pointer is null}} + // expected-warning@../Inputs/system-header-simulator-cxx.h:686 {{Called C++ object pointer is null}} #endif } diff --git a/clang/test/Analysis/invalidated-iterator.cpp b/clang/test/Analysis/invalidated-iterator.cpp index 1151838bb8433..de37c9c055b97 100644 --- a/clang/test/Analysis/invalidated-iterator.cpp +++ b/clang/test/Analysis/invalidated-iterator.cpp @@ -3,397 +3,762 @@ #include "Inputs/system-header-simulator-cxx.h" -void bad_copy_assign_operator_list1(std::list &L1, +void clang_analyzer_warnIfReached(); + +void bad_copy_assign_operator1_list(std::list &L1, const std::list &L2) { auto i0 = L1.cbegin(); L1 = L2; *i0; // expected-warning{{Invalidated iterator accessed}} + clang_analyzer_warnIfReached(); } -void bad_copy_assign_operator_vector1(std::vector &V1, +void bad_copy_assign_operator1_vector(std::vector &V1, const std::vector &V2) { auto i0 = V1.cbegin(); V1 = V2; *i0; // expected-warning{{Invalidated iterator accessed}} } -void bad_copy_assign_operator_deque1(std::deque &D1, +void bad_copy_assign_operator1_deque(std::deque &D1, const std::deque &D2) { auto i0 = D1.cbegin(); D1 = D2; *i0; // expected-warning{{Invalidated iterator accessed}} } -void bad_copy_assign_operator_forward_list1(std::forward_list &FL1, +void bad_copy_assign_operator1_forward_list(std::forward_list &FL1, const std::forward_list &FL2) { auto i0 = FL1.cbegin(); FL1 = FL2; *i0; // expected-warning{{Invalidated iterator accessed}} } -void bad_assign_list1(std::list &L, int n) { +void bad_assign1_list(std::list &L, int n) { auto i0 = L.cbegin(); L.assign(10, n); *i0; // expected-warning{{Invalidated iterator accessed}} } -void bad_assign_vector1(std::vector &V, int n) { +void bad_assign1_vector(std::vector &V, int n) { auto i0 = V.cbegin(); V.assign(10, n); *i0; // expected-warning{{Invalidated iterator accessed}} } -void bad_assign_deque1(std::deque &D, int n) { +void bad_assign1_deque(std::deque &D, int n) { auto i0 = D.cbegin(); D.assign(10, n); *i0; // expected-warning{{Invalidated iterator accessed}} } -void bad_assign_forward_list1(std::forward_list &FL, int n) { +void bad_assign1_forward_list(std::forward_list &FL, int n) { auto i0 = FL.cbegin(); FL.assign(10, n); *i0; // expected-warning{{Invalidated iterator accessed}} } -void good_clear_list1(std::list &L) { +void good_clear1_list(std::list &L) { auto i0 = L.cend(); L.clear(); --i0; // no-warning } -void bad_clear_list1(std::list &L) { +void bad_clear1_list(std::list &L) { auto i0 = L.cbegin(), i1 = L.cend(); L.clear(); *i0; // expected-warning{{Invalidated iterator accessed}} } -void bad_clear_vector1(std::vector &V) { +void bad_clear1_vector(std::vector &V) { auto i0 = V.cbegin(), i1 = V.cend(); V.clear(); *i0; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_clear1_vector_decr(std::vector &V) { + auto i0 = V.cbegin(), i1 = V.cend(); + V.clear(); --i1; // expected-warning{{Invalidated iterator accessed}} } -void bad_clear_deque1(std::deque &D) { +void bad_clear1_deque(std::deque &D) { auto i0 = D.cbegin(), i1 = D.cend(); D.clear(); *i0; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_clear1_deque_decr(std::deque &D) { + auto i0 = D.cbegin(), i1 = D.cend(); + D.clear(); --i1; // expected-warning{{Invalidated iterator accessed}} } -void good_push_back_list1(std::list &L, int n) { +void good_push_back1_list(std::list &L, int n) { auto i0 = L.cbegin(), i1 = L.cend(); L.push_back(n); *i0; // no-warning --i1; // no-warning } -void good_push_back_vector1(std::vector &V, int n) { +void good_push_back1_vector(std::vector &V, int n) { auto i0 = V.cbegin(), i1 = V.cend(); V.push_back(n); *i0; // no-warning } -void bad_push_back_vector1(std::vector &V, int n) { +void bad_push_back1_vector(std::vector &V, int n) { auto i0 = V.cbegin(), i1 = V.cend(); V.push_back(n); --i1; // expected-warning{{Invalidated iterator accessed}} } -void bad_push_back_deque1(std::deque &D, int n) { +void bad_push_back1_deque(std::deque &D, int n) { auto i0 = D.cbegin(), i1 = D.cend(); D.push_back(n); *i0; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_push_back1_deque_decr(std::deque &D, int n) { + auto i0 = D.cbegin(), i1 = D.cend(); + D.push_back(n); --i1; // expected-warning{{Invalidated iterator accessed}} } -void good_emplace_back_list1(std::list &L, int n) { +void good_emplace_back1_list(std::list &L, int n) { auto i0 = L.cbegin(), i1 = L.cend(); L.emplace_back(n); *i0; // no-warning --i1; // no-warning } -void good_emplace_back_vector1(std::vector &V, int n) { +void good_emplace_back1_vector(std::vector &V, int n) { auto i0 = V.cbegin(), i1 = V.cend(); V.emplace_back(n); *i0; // no-warning } -void bad_emplace_back_vector1(std::vector &V, int n) { +void bad_emplace_back1_vector(std::vector &V, int n) { auto i0 = V.cbegin(), i1 = V.cend(); V.emplace_back(n); --i1; // expected-warning{{Invalidated iterator accessed}} } -void bad_emplace_back_deque1(std::deque &D, int n) { +void bad_emplace_back1_deque(std::deque &D, int n) { auto i0 = D.cbegin(), i1 = D.cend(); D.emplace_back(n); *i0; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_emplace_back1_deque_decr(std::deque &D, int n) { + auto i0 = D.cbegin(), i1 = D.cend(); + D.emplace_back(n); --i1; // expected-warning{{Invalidated iterator accessed}} } -void good_pop_back_list1(std::list &L, int n) { +void good_pop_back1_list(std::list &L, int n) { auto i0 = L.cbegin(), i1 = L.cend(), i2 = i1--; L.pop_back(); *i0; // no-warning *i2; // no-warning } -void bad_pop_back_list1(std::list &L, int n) { +void bad_pop_back1_list(std::list &L, int n) { auto i0 = L.cbegin(), i1 = L.cend(), i2 = i1--; L.pop_back(); *i1; // expected-warning{{Invalidated iterator accessed}} } -void good_pop_back_vector1(std::vector &V, int n) { +void good_pop_back1_vector(std::vector &V, int n) { auto i0 = V.cbegin(), i1 = V.cend(), i2 = i1--; V.pop_back(); *i0; // no-warning } -void bad_pop_back_vector1(std::vector &V, int n) { +void bad_pop_back1_vector(std::vector &V, int n) { auto i0 = V.cbegin(), i1 = V.cend(), i2 = i1--; V.pop_back(); *i1; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_pop_back1_vector_decr(std::vector &V, int n) { + auto i0 = V.cbegin(), i1 = V.cend(), i2 = i1--; + V.pop_back(); --i2; // expected-warning{{Invalidated iterator accessed}} } -void good_pop_back_deque1(std::deque &D, int n) { +void good_pop_back1_deque(std::deque &D, int n) { auto i0 = D.cbegin(), i1 = D.cend(), i2 = i1--; D.pop_back(); *i0; // no-warning } -void bad_pop_back_deque1(std::deque &D, int n) { +void bad_pop_back1_deque(std::deque &D, int n) { auto i0 = D.cbegin(), i1 = D.cend(), i2 = i1--; D.pop_back(); *i1; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_pop_back1_deque_decr(std::deque &D, int n) { + auto i0 = D.cbegin(), i1 = D.cend(), i2 = i1--; + D.pop_back(); --i2; // expected-warning{{Invalidated iterator accessed}} } -void good_push_front_list1(std::list &L, int n) { +void good_push_front1_list(std::list &L, int n) { auto i0 = L.cbegin(), i1 = L.cend(); L.push_front(n); *i0; // no-warning --i1; // no-warning } -void bad_push_front_deque1(std::deque &D, int n) { +void bad_push_front1_deque(std::deque &D, int n) { auto i0 = D.cbegin(), i1 = D.cend(); D.push_front(n); *i0; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_push_front1_deque_decr(std::deque &D, int n) { + auto i0 = D.cbegin(), i1 = D.cend(); + D.push_front(n); --i1; // expected-warning{{Invalidated iterator accessed}} } -void good_push_front_forward_list1(std::forward_list &FL, int n) { +void good_push_front1_forward_list(std::forward_list &FL, int n) { auto i0 = FL.cbegin(), i1 = FL.cend(); FL.push_front(n); *i0; // no-warning } -void good_emplace_front_list1(std::list &L, int n) { +void good_emplace_front1_list(std::list &L, int n) { auto i0 = L.cbegin(), i1 = L.cend(); L.emplace_front(n); *i0; // no-warning --i1; // no-warning } -void bad_emplace_front_deque1(std::deque &D, int n) { +void bad_emplace_front1_deque(std::deque &D, int n) { auto i0 = D.cbegin(), i1 = D.cend(); D.emplace_front(n); *i0; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_emplace_front1_deque_decr(std::deque &D, int n) { + auto i0 = D.cbegin(), i1 = D.cend(); + D.emplace_front(n); --i1; // expected-warning{{Invalidated iterator accessed}} } -void good_emplace_front_forward_list1(std::forward_list &FL, int n) { +void good_emplace_front1_forward_list(std::forward_list &FL, int n) { auto i0 = FL.cbegin(), i1 = FL.cend(); FL.emplace_front(n); *i0; // no-warning } -void good_pop_front_list1(std::list &L, int n) { +void good_pop_front1_list(std::list &L, int n) { auto i1 = L.cbegin(), i0 = i1++; L.pop_front(); *i1; // no-warning } -void bad_pop_front_list1(std::list &L, int n) { +void bad_pop_front1_list(std::list &L, int n) { auto i1 = L.cbegin(), i0 = i1++; L.pop_front(); *i0; // expected-warning{{Invalidated iterator accessed}} } -void good_pop_front_deque1(std::deque &D, int n) { +void good_pop_front1_deque(std::deque &D, int n) { auto i1 = D.cbegin(), i0 = i1++; D.pop_front(); *i1; // no-warning } -void bad_pop_front_deque1(std::deque &D, int n) { +void bad_pop_front1_deque(std::deque &D, int n) { auto i1 = D.cbegin(), i0 = i1++; D.pop_front(); *i0; // expected-warning{{Invalidated iterator accessed}} } -void good_pop_front_forward_list1(std::forward_list &FL, int n) { +void good_pop_front1_forward_list(std::forward_list &FL, int n) { auto i1 = FL.cbegin(), i0 = i1++; FL.pop_front(); *i1; // no-warning } -void bad_pop_front_forward_list1(std::forward_list &FL, int n) { +void bad_pop_front1_forward_list(std::forward_list &FL, int n) { auto i1 = FL.cbegin(), i0 = i1++; FL.pop_front(); *i0; // expected-warning{{Invalidated iterator accessed}} } -void good_insert_list1(std::list &L, int n) { +void good_insert1_list1(std::list &L, int n) { auto i1 = L.cbegin(), i0 = i1++; L.insert(i1, n); *i0; // no-warning *i1; // no-warning } -void good_insert_vector1(std::vector &V, int n) { +void good_insert1_list2(std::list &L, int n) { + auto i1 = L.cbegin(), i0 = i1++; + i1 = L.insert(i1, n); + *i1; // no-warning +} + +void good_insert1_vector1(std::vector &V, int n) { auto i1 = V.cbegin(), i0 = i1++; V.insert(i1, n); *i0; // no-warning } -void bad_insert_vector1(std::vector &V, int n) { +void good_insert1_vector2(std::vector &V, int n) { + auto i1 = V.cbegin(), i0 = i1++; + i1 = V.insert(i1, n); + *i1; // no-warning +} + +void bad_insert1_vector(std::vector &V, int n) { auto i1 = V.cbegin(), i0 = i1++; V.insert(i1, n); *i1; // expected-warning{{Invalidated iterator accessed}} } -void bad_insert_deque1(std::deque &D, int n) { +void good_insert1_deque(std::deque &D, int n) { + auto i1 = D.cbegin(), i0 = i1++; + i0 = D.insert(i1, n); + *i0; // no-warning +} + +void bad_insert1_deque1(std::deque &D, int n) { auto i1 = D.cbegin(), i0 = i1++; D.insert(i1, n); *i0; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_insert1_deque2(std::deque &D, int n) { + auto i1 = D.cbegin(), i0 = i1++; + D.insert(i1, n); *i1; // expected-warning{{Invalidated iterator accessed}} } -void good_emplace_list1(std::list &L, int n) { +void good_insert2_list1(std::list &L, int n) { + auto i1 = L.cbegin(), i0 = i1++; + L.insert(i1, std::move(n)); + *i0; // no-warning + *i1; // no-warning +} + +void good_insert2_list2(std::list &L, int n) { + auto i1 = L.cbegin(), i0 = i1++; + i1 = L.insert(i1, std::move(n)); + *i1; // no-warning +} + +void good_insert2_vector1(std::vector &V, int n) { + auto i1 = V.cbegin(), i0 = i1++; + V.insert(i1, std::move(n)); + *i0; // no-warning +} + +void good_insert2_vector2(std::vector &V, int n) { + auto i1 = V.cbegin(), i0 = i1++; + i1 = V.insert(i1, std::move(n)); + *i1; // no-warning +} + +void bad_insert2_vector(std::vector &V, int n) { + auto i1 = V.cbegin(), i0 = i1++; + V.insert(i1, std::move(n)); + *i1; // expected-warning{{Invalidated iterator accessed}} +} + +void good_insert2_deque(std::deque &D, int n) { + auto i1 = D.cbegin(), i0 = i1++; + i1 = D.insert(i1, std::move(n)); + *i1; // no-warning +} + +void bad_insert2_deque1(std::deque &D, int n) { + auto i1 = D.cbegin(), i0 = i1++; + D.insert(i1, std::move(n)); + *i0; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_insert2_deque2(std::deque &D, int n) { + auto i1 = D.cbegin(), i0 = i1++; + D.insert(i1, std::move(n)); + *i1; // expected-warning{{Invalidated iterator accessed}} +} + +void good_insert3_list1(std::list &L, int n) { + auto i1 = L.cbegin(), i0 = i1++; + L.insert(i1, 10, n); + *i0; // no-warning + *i1; // no-warning +} + +void good_insert3_list2(std::list &L, int n) { + auto i1 = L.cbegin(), i0 = i1++; + i1 = L.insert(i1, 10, n); + *i1; // no-warning +} + +void good_insert3_vector1(std::vector &V, int n) { + auto i1 = V.cbegin(), i0 = i1++; + V.insert(i1, 10, n); + *i0; // no-warning +} + +void good_insert3_vector2(std::vector &V, int n) { + auto i1 = V.cbegin(), i0 = i1++; + i1 = V.insert(i1, 10, n); + *i1; // no-warning +} + +void bad_insert3_vector(std::vector &V, int n) { + auto i1 = V.cbegin(), i0 = i1++; + V.insert(i1, 10, n); + *i1; // expected-warning{{Invalidated iterator accessed}} +} + +void good_insert3_deque(std::deque &D, int n) { + auto i1 = D.cbegin(), i0 = i1++; + i1 = D.insert(i1, 10, std::move(n)); + *i1; // no-warning +} + +void bad_insert3_deque1(std::deque &D, int n) { + auto i1 = D.cbegin(), i0 = i1++; + D.insert(i1, 10, std::move(n)); + *i0; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_insert3_deque2(std::deque &D, int n) { + auto i1 = D.cbegin(), i0 = i1++; + D.insert(i1, 10, std::move(n)); + *i1; // expected-warning{{Invalidated iterator accessed}} +} + +void good_insert4_list1(std::list &L1, std::list &L2, int n) { + auto i1 = L1.cbegin(), i0 = i1++; + L1.insert(i1, L2.cbegin(), L2.cend()); + *i0; // no-warning + *i1; // no-warning +} + +void good_insert4_list2(std::list &L1, std::list &L2, int n) { + auto i1 = L1.cbegin(), i0 = i1++; + i1 = L1.insert(i1, L2.cbegin(), L2.cend()); + *i1; // no-warning +} + +void good_insert4_vector1(std::vector &V1, std::vector &V2, int n) { + auto i1 = V1.cbegin(), i0 = i1++; + V1.insert(i1, V2.cbegin(), V2.cend()); + *i0; // no-warning +} + +void good_insert4_vector2(std::vector &V1, std::vector &V2, int n) { + auto i1 = V1.cbegin(), i0 = i1++; + i1 = V1.insert(i1, V2.cbegin(), V2.cend()); + *i1; // no-warning +} + +void bad_insert4_vector(std::vector &V1, std::vector &V2, int n) { + auto i1 = V1.cbegin(), i0 = i1++; + V1.insert(i1, V2.cbegin(), V2.cend()); + *i1; // expected-warning{{Invalidated iterator accessed}} +} + +void good_insert4_deque(std::deque &D1, std::deque &D2, int n) { + auto i1 = D1.cbegin(), i0 = i1++; + i1 = D1.insert(i1, D2.cbegin(), D2.cend()); + *i1; // no-warning +} + +void bad_insert4_deque1(std::deque &D1, std::deque &D2, int n) { + auto i1 = D1.cbegin(), i0 = i1++; + D1.insert(i1, D2.cbegin(), D2.cend()); + *i0; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_insert4_deque2(std::deque &D1, std::deque &D2, int n) { + auto i1 = D1.cbegin(), i0 = i1++; + D1.insert(i1, D2.cbegin(), D2.cend()); + *i1; // expected-warning{{Invalidated iterator accessed}} +} + +void good_insert5_list1(std::list &L) { + auto i1 = L.cbegin(), i0 = i1++; + L.insert(i1, {1, 2, 3, 4}); + *i0; // no-warning + *i1; // no-warning +} + +void good_insert5_list2(std::list &L) { + auto i1 = L.cbegin(), i0 = i1++; + i1 = L.insert(i1, {1, 2, 3, 4}); + *i1; // no-warning +} + +void good_insert5_vector1(std::vector &V) { + auto i1 = V.cbegin(), i0 = i1++; + V.insert(i1, {1, 2, 3, 4}); + *i0; // no-warning +} + +void good_insert5_vector2(std::vector &V) { + auto i1 = V.cbegin(), i0 = i1++; + i1 = V.insert(i1, {1, 2, 3, 4}); + *i1; // no-warning +} + +void bad_insert5_vector(std::vector &V) { + auto i1 = V.cbegin(), i0 = i1++; + V.insert(i1, {1, 2, 3, 4}); + *i1; // expected-warning{{Invalidated iterator accessed}} +} + +void good_insert5_deque(std::deque &D) { + auto i1 = D.cbegin(), i0 = i1++; + i1 = D.insert(i1, {1, 2, 3, 4}); + *i1; // no-warning +} + +void bad_insert5_deque1(std::deque &D) { + auto i1 = D.cbegin(), i0 = i1++; + D.insert(i1, {1, 2, 3, 4}); + *i0; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_insert5_deque2(std::deque &D) { + auto i1 = D.cbegin(), i0 = i1++; + D.insert(i1, {1, 2, 3, 4}); + *i1; // expected-warning{{Invalidated iterator accessed}} +} + +void good_emplace1_list(std::list &L, int n) { auto i1 = L.cbegin(), i0 = i1++; L.emplace(i1, n); *i0; // no-warning *i1; // no-warning } -void good_emplace_vector1(std::vector &V, int n) { +void good_emplace1_vector(std::vector &V, int n) { auto i1 = V.cbegin(), i0 = i1++; V.emplace(i1, n); *i0; // no-warning } -void bad_emplace_vector1(std::vector &V, int n) { +void bad_emplace1_vector(std::vector &V, int n) { auto i1 = V.cbegin(), i0 = i1++; V.emplace(i1, n); *i1; // expected-warning{{Invalidated iterator accessed}} } -void bad_emplace_deque1(std::deque &D, int n) { +void bad_emplace1_deque1(std::deque &D, int n) { auto i1 = D.cbegin(), i0 = i1++; D.emplace(i1, n); *i0; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_emplace1_deque2(std::deque &D, int n) { + auto i1 = D.cbegin(), i0 = i1++; + D.emplace(i1, n); *i1; // expected-warning{{Invalidated iterator accessed}} } -void good_erase_list1(std::list &L) { +void good_erase1_list1(std::list &L) { auto i2 = L.cbegin(), i0 = i2++, i1 = i2++; L.erase(i1); *i0; // no-warning *i2; // no-warning } -void bad_erase_list1(std::list &L) { +void good_erase1_list2(std::list &L) { + auto i0 = L.cbegin(); + i0 = L.erase(i0); + *i0; // no-warning +} + +void bad_erase1_list(std::list &L) { auto i0 = L.cbegin(); L.erase(i0); *i0; // expected-warning{{Invalidated iterator accessed}} } -void good_erase_vector1(std::vector &V) { +void good_erase1_vector1(std::vector &V) { auto i2 = V.cbegin(), i0 = i2++, i1 = i2++; V.erase(i1); *i0; // no-warning } -void bad_erase_vector1(std::vector &V) { +void good_erase1_vector2(std::vector &V) { + auto i0 = V.cbegin(); + i0 = V.erase(i0); + *i0; // no-warning +} + +void bad_erase1_vector1(std::vector &V) { auto i1 = V.cbegin(), i0 = i1++; V.erase(i0); *i0; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_erase1_vector2(std::vector &V) { + auto i1 = V.cbegin(), i0 = i1++; + V.erase(i0); *i1; // expected-warning{{Invalidated iterator accessed}} } -void bad_erase_deque1(std::deque &D) { +void good_erase1_deque(std::deque &D) { + auto i0 = D.cbegin(); + i0 = D.erase(i0); + *i0; // no-warning +} + +void bad_erase1_deque1(std::deque &D) { auto i2 = D.cbegin(), i0 = i2++, i1 = i2++; D.erase(i1); *i0; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_erase1_deque2(std::deque &D) { + auto i2 = D.cbegin(), i0 = i2++, i1 = i2++; + D.erase(i1); *i1; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_erase1_deque3(std::deque &D) { + auto i2 = D.cbegin(), i0 = i2++, i1 = i2++; + D.erase(i1); *i2; // expected-warning{{Invalidated iterator accessed}} } -void good_erase_list2(std::list &L) { +void good_erase2_list1(std::list &L) { auto i3 = L.cbegin(), i0 = i3++, i1 = i3++, i2 = i3++; L.erase(i1, i3); *i0; // no-warning *i3; // no-warning } -void bad_erase_list2(std::list &L) { +void good_erase2_list2(std::list &L) { + auto i2 = L.cbegin(), i0 = i2++, i1 = i2++; + i0 = L.erase(i0, i2); + *i0; // no-warning +} + +void bad_erase2_list1(std::list &L) { auto i2 = L.cbegin(), i0 = i2++, i1 = i2++; L.erase(i0, i2); *i0; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_erase2_list2(std::list &L) { + auto i2 = L.cbegin(), i0 = i2++, i1 = i2++; + L.erase(i0, i2); *i1; // expected-warning{{Invalidated iterator accessed}} } -void good_erase_vector2(std::vector &V) { +void good_erase2_vector1(std::vector &V) { auto i3 = V.cbegin(), i0 = i3++, i1 = i3++, i2 = i3++;; V.erase(i1, i3); *i0; // no-warning } -void bad_erase_vector2(std::vector &V) { +void good_erase2_vector2(std::vector &V) { + auto i2 = V.cbegin(), i0 = i2++, i1 = i2++; + i0 = V.erase(i0, i2); + *i0; // no-warning +} + +void bad_erase2_vector1(std::vector &V) { auto i2 = V.cbegin(), i0 = i2++, i1 = i2++; V.erase(i0, i2); *i0; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_erase2_vector2(std::vector &V) { + auto i2 = V.cbegin(), i0 = i2++, i1 = i2++; + V.erase(i0, i2); *i1; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_erase2_vector3(std::vector &V) { + auto i2 = V.cbegin(), i0 = i2++, i1 = i2++; + V.erase(i0, i2); *i2; // expected-warning{{Invalidated iterator accessed}} } -void bad_erase_deque2(std::deque &D) { +void good_erase2_deque(std::deque &D) { + auto i2 = D.cbegin(), i0 = i2++, i1 = i2++; + i0 = D.erase(i0, i2); + *i0; // no-warning +} + +void bad_erase2_deque1(std::deque &D) { auto i3 = D.cbegin(), i0 = i3++, i1 = i3++, i2 = i3++; D.erase(i1, i3); *i0; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_erase2_deque2(std::deque &D) { + auto i3 = D.cbegin(), i0 = i3++, i1 = i3++, i2 = i3++; + D.erase(i1, i3); *i1; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_erase2_deque3(std::deque &D) { + auto i3 = D.cbegin(), i0 = i3++, i1 = i3++, i2 = i3++; + D.erase(i1, i3); *i2; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_erase2_deque4(std::deque &D) { + auto i3 = D.cbegin(), i0 = i3++, i1 = i3++, i2 = i3++; + D.erase(i1, i3); *i3; // expected-warning{{Invalidated iterator accessed}} } -void good_erase_after_forward_list1(std::forward_list &FL) { +void good_erase_after1_forward_list1(std::forward_list &FL) { auto i2 = FL.cbegin(), i0 = i2++, i1 = i2++; FL.erase_after(i0); *i0; // no-warning *i2; // no-warning } -void bad_erase_after_forward_list1(std::forward_list &FL) { +void good_erase_after1_forward_lis2(std::forward_list &FL) { + auto i1 = FL.cbegin(), i0 = i1++; + i1 = FL.erase_after(i0); + *i1; // no-warning +} + +void bad_erase_after1_forward_list(std::forward_list &FL) { auto i1 = FL.cbegin(), i0 = i1++; FL.erase_after(i0); *i1; // expected-warning{{Invalidated iterator accessed}} } -void good_erase_after_forward_list2(std::forward_list &FL) { +void good_erase_after2_forward_list1(std::forward_list &FL) { auto i3 = FL.cbegin(), i0 = i3++, i1 = i3++, i2 = i3++; FL.erase_after(i0, i3); *i0; // no-warning *i3; // no-warning } -void bad_erase_after_forward_list2(std::forward_list &FL) { +void good_erase_after2_forward_list2(std::forward_list &FL) { + auto i3 = FL.cbegin(), i0 = i3++, i1 = i3++, i2 = i3++; + i2 = FL.erase_after(i0, i3); + *i2; // no-warning +} + +void bad_erase_after2_forward_list1(std::forward_list &FL) { auto i3 = FL.cbegin(), i0 = i3++, i1 = i3++, i2 = i3++; FL.erase_after(i0, i3); *i1; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_erase_after2_forward_list2(std::forward_list &FL) { + auto i3 = FL.cbegin(), i0 = i3++, i1 = i3++, i2 = i3++; + FL.erase_after(i0, i3); *i2; // expected-warning{{Invalidated iterator accessed}} } diff --git a/clang/test/Analysis/iterator-range.cpp b/clang/test/Analysis/iterator-range.cpp index bc7e08263ae21..93f69e47f563b 100644 --- a/clang/test/Analysis/iterator-range.cpp +++ b/clang/test/Analysis/iterator-range.cpp @@ -24,6 +24,7 @@ void simple_good_end_negated(const std::vector &v) { void simple_bad_end(const std::vector &v) { auto i = v.end(); *i; // expected-warning{{Past-the-end iterator dereferenced}} + clang_analyzer_warnIfReached(); } void copy(const std::vector &v) { From 2a829ba08d0b6fa5b8730abb1424563ab9a21b7c Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Thu, 29 Aug 2019 20:37:28 +0000 Subject: [PATCH 139/181] [CFG] Fix CFG for statement-expressions in return values. We're building the CFG from bottom to top, so when the return-value expression has a non-trivial CFG on its own, we need to continue building from the entry to the return-value expression CFG rather than from the block to which we've just appended the return statement. Fixes a false positive warning "control may reach end of non-void function". llvm-svn: 370406 (cherry picked from commit e5c0994ddfcf64bbc7e65ebb68890e98141b84d9) --- clang/lib/Analysis/CFG.cpp | 5 ++-- clang/test/Analysis/cfg.cpp | 49 ++++++++++++++++++++++++++++++++++++- clang/test/Sema/return.c | 11 +++++++++ 3 files changed, 61 insertions(+), 4 deletions(-) diff --git a/clang/lib/Analysis/CFG.cpp b/clang/lib/Analysis/CFG.cpp index 3de18488d50a6..2e51cb8894d68 100644 --- a/clang/lib/Analysis/CFG.cpp +++ b/clang/lib/Analysis/CFG.cpp @@ -2988,9 +2988,8 @@ CFGBlock *CFGBuilder::VisitReturnStmt(Stmt *S) { // Visit children if (ReturnStmt *RS = dyn_cast(S)) { - Expr *O = RS->getRetValue(); - if (O) - Visit(O, AddStmtChoice::AlwaysAdd, /*ExternallyDestructed=*/true); + if (Expr *O = RS->getRetValue()) + return Visit(O, AddStmtChoice::AlwaysAdd, /*ExternallyDestructed=*/true); return Block; } else { // co_return return VisitChildren(S); diff --git a/clang/test/Analysis/cfg.cpp b/clang/test/Analysis/cfg.cpp index 74f4d50f25dfc..9b0203e99efe9 100644 --- a/clang/test/Analysis/cfg.cpp +++ b/clang/test/Analysis/cfg.cpp @@ -499,6 +499,54 @@ void foo() { } // end namespace pr37688_deleted_union_destructor +namespace return_statement_expression { +int unknown(); + +// CHECK-LABEL: int foo() +// CHECK: [B6 (ENTRY)] +// CHECK-NEXT: Succs (1): B5 +// CHECK: [B1] +// CHECK-NEXT: 1: 0 +// CHECK-NEXT: 2: return [B1.1]; +// CHECK-NEXT: Preds (1): B5 +// CHECK-NEXT: Succs (1): B0 +// CHECK: [B2] +// CHECK-NEXT: 1: 0 +// CHECK-NEXT: 2: ({ ... ; [B2.1] }) +// CHECK-NEXT: 3: return [B2.2]; +// CHECK-NEXT: Preds (1): B4 +// CHECK-NEXT: Succs (1): B0 +// FIXME: Why do we have [B3] at all? +// CHECK: [B3] +// CHECK-NEXT: Succs (1): B4 +// CHECK: [B4] +// CHECK-NEXT: 1: 0 +// CHECK-NEXT: 2: [B4.1] (ImplicitCastExpr, IntegralToBoolean, _Bool) +// CHECK-NEXT: T: while [B4.2] +// CHECK-NEXT: Preds (2): B3 B5 +// CHECK-NEXT: Succs (2): NULL B2 +// CHECK: [B5] +// CHECK-NEXT: 1: unknown +// CHECK-NEXT: 2: [B5.1] (ImplicitCastExpr, FunctionToPointerDecay, int (*)(void)) +// CHECK-NEXT: 3: [B5.2]() +// CHECK-NEXT: 4: [B5.3] (ImplicitCastExpr, IntegralToBoolean, _Bool) +// CHECK-NEXT: T: if [B5.4] +// CHECK-NEXT: Preds (1): B6 +// CHECK-NEXT: Succs (2): B4 B1 +// CHECK: [B0 (EXIT)] +// CHECK-NEXT: Preds (2): B1 B2 +int foo() { + if (unknown()) + return ({ + while (0) + ; + 0; + }); + else + return 0; +} +} // namespace statement_expression_in_return + // CHECK-LABEL: template<> int *PR18472() // CHECK: [B2 (ENTRY)] // CHECK-NEXT: Succs (1): B1 @@ -522,4 +570,3 @@ template T *PR18472() { void PR18472_helper() { PR18472(); } - diff --git a/clang/test/Sema/return.c b/clang/test/Sema/return.c index debf5ab55f5b5..68c2251c46330 100644 --- a/clang/test/Sema/return.c +++ b/clang/test/Sema/return.c @@ -328,3 +328,14 @@ int sizeof_long() { if (sizeof(long) == 8) return 2; } // no-warning + +int return_statement_expression() { + if (unknown()) + return ({ + while (0) + ; + 0; + }); + else + return 0; +} // no-warning (used to be "control may reach end of non-void function") From 0332d8490fdc9efcdaf4532aff58833b643ce31e Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Tue, 3 Sep 2019 15:22:43 +0000 Subject: [PATCH 140/181] [analyzer] Add a checker option to detect nested dead stores MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enables the users to specify an optional flag which would warn for more dead stores. Previously it ignored if the dead store happened e.g. in an if condition. if ((X = generate())) { // dead store to X } This patch introduces the `WarnForDeadNestedAssignments` option to the checker, which is `false` by default - so this change would not affect any previous users. I have updated the code, tests and the docs as well. If I missed something, tell me. I also ran the analysis on Clang which generated 14 more reports compared to the unmodified version. All of them seemed reasonable for me. Related previous patches: rGf224820b45c6847b91071da8d7ade59f373b96f3 Reviewers: NoQ, krememek, Szelethus, baloghadamsoftware Reviewed By: Szelethus Patch by Balázs Benics! Differential Revision: https://reviews.llvm.org/D66733 llvm-svn: 370767 (cherry picked from commit 3b18b050b8fc132ee09e2457f0bb98386fe181f8) --- clang/docs/analyzer/checkers.rst | 9 + .../clang/StaticAnalyzer/Checkers/Checkers.td | 8 + .../Checkers/DeadStoresChecker.cpp | 33 +- clang/test/Analysis/analyzer-config.c | 3 +- clang/test/Analysis/dead-stores.c | 297 ++++++++++-------- clang/test/Analysis/dead-stores.cpp | 73 +++-- clang/test/Analysis/dead-stores.m | 4 +- 7 files changed, 262 insertions(+), 165 deletions(-) diff --git a/clang/docs/analyzer/checkers.rst b/clang/docs/analyzer/checkers.rst index 93b8f0cbf5c43..5c4f0dde4f049 100644 --- a/clang/docs/analyzer/checkers.rst +++ b/clang/docs/analyzer/checkers.rst @@ -260,6 +260,15 @@ Check for values stored to variables that are never read afterwards. x = 1; // warn } +The ``WarnForDeadNestedAssignments`` option enables the checker to emit +warnings for nested dead assignments. You can disable with the +``-analyzer-config deadcode.DeadStores:WarnForDeadNestedAssignments=false``. +*Defaults to true*. + +Would warn for this e.g.: +if ((y = make_int())) { +} + .. _nullability-checkers: nullability diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index da59b3af3cd77..0626d29c3d604 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -648,6 +648,14 @@ let ParentPackage = DeadCode in { def DeadStoresChecker : Checker<"DeadStores">, HelpText<"Check for values stored to variables that are never read " "afterwards">, + CheckerOptions<[ + CmdLineOption + ]>, Documentation; } // end DeadCode diff --git a/clang/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp index d5baa2bcba6fc..e4889c1cc14df 100644 --- a/clang/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp @@ -130,6 +130,7 @@ class DeadStoreObs : public LiveVariables::Observer { std::unique_ptr reachableCode; const CFGBlock *currentBlock; std::unique_ptr> InEH; + const bool WarnForDeadNestedAssignments; enum DeadStoreKind { Standard, Enclosing, DeadIncrement, DeadInit }; @@ -137,9 +138,11 @@ class DeadStoreObs : public LiveVariables::Observer { DeadStoreObs(const CFG &cfg, ASTContext &ctx, BugReporter &br, const CheckerBase *checker, AnalysisDeclContext *ac, ParentMap &parents, - llvm::SmallPtrSet &escaped) + llvm::SmallPtrSet &escaped, + bool warnForDeadNestedAssignments) : cfg(cfg), Ctx(ctx), BR(br), Checker(checker), AC(ac), Parents(parents), - Escaped(escaped), currentBlock(nullptr) {} + Escaped(escaped), currentBlock(nullptr), + WarnForDeadNestedAssignments(warnForDeadNestedAssignments) {} ~DeadStoreObs() override {} @@ -217,11 +220,16 @@ class DeadStoreObs : public LiveVariables::Observer { os << "Value stored to '" << *V << "' is never read"; break; + // eg.: f((x = foo())) case Enclosing: - // Don't report issues in this case, e.g.: "if (x = foo())", - // where 'x' is unused later. We have yet to see a case where - // this is a real bug. - return; + if (!WarnForDeadNestedAssignments) + return; + BugType = "Dead nested assignment"; + os << "Although the value stored to '" << *V + << "' is used in the enclosing expression, the value is never " + "actually read from '" + << *V << "'"; + break; } BR.EmitBasicReport(AC->getDecl(), Checker, BugType, "Dead store", os.str(), @@ -474,6 +482,8 @@ class FindEscaped { namespace { class DeadStoresChecker : public Checker { public: + bool WarnForDeadNestedAssignments = true; + void checkASTCodeBody(const Decl *D, AnalysisManager& mgr, BugReporter &BR) const { @@ -491,15 +501,20 @@ class DeadStoresChecker : public Checker { ParentMap &pmap = mgr.getParentMap(D); FindEscaped FS; cfg.VisitBlockStmts(FS); - DeadStoreObs A(cfg, BR.getContext(), BR, this, AC, pmap, FS.Escaped); + DeadStoreObs A(cfg, BR.getContext(), BR, this, AC, pmap, FS.Escaped, + WarnForDeadNestedAssignments); L->runOnAllBlocks(A); } } }; } -void ento::registerDeadStoresChecker(CheckerManager &mgr) { - mgr.registerChecker(); +void ento::registerDeadStoresChecker(CheckerManager &Mgr) { + auto Chk = Mgr.registerChecker(); + + const AnalyzerOptions &AnOpts = Mgr.getAnalyzerOptions(); + Chk->WarnForDeadNestedAssignments = + AnOpts.getCheckerBooleanOption(Chk, "WarnForDeadNestedAssignments"); } bool ento::shouldRegisterDeadStoresChecker(const LangOptions &LO) { diff --git a/clang/test/Analysis/analyzer-config.c b/clang/test/Analysis/analyzer-config.c index 63d22596dddf2..c8a24eea50175 100644 --- a/clang/test/Analysis/analyzer-config.c +++ b/clang/test/Analysis/analyzer-config.c @@ -30,6 +30,7 @@ // CHECK-NEXT: ctu-dir = "" // CHECK-NEXT: ctu-import-threshold = 100 // CHECK-NEXT: ctu-index-name = externalDefMap.txt +// CHECK-NEXT: deadcode.DeadStores:WarnForDeadNestedAssignments = true // CHECK-NEXT: debug.AnalysisOrder:* = false // CHECK-NEXT: debug.AnalysisOrder:Bind = false // CHECK-NEXT: debug.AnalysisOrder:EndFunction = false @@ -93,4 +94,4 @@ // CHECK-NEXT: unroll-loops = false // CHECK-NEXT: widen-loops = false // CHECK-NEXT: [stats] -// CHECK-NEXT: num-entries = 90 +// CHECK-NEXT: num-entries = 91 diff --git a/clang/test/Analysis/dead-stores.c b/clang/test/Analysis/dead-stores.c index 84217a286a551..26377f7617ee5 100644 --- a/clang/test/Analysis/dead-stores.c +++ b/clang/test/Analysis/dead-stores.c @@ -1,102 +1,110 @@ -// RUN: %clang_analyze_cc1 -Wunused-variable -analyzer-checker=core,deadcode.DeadStores -fblocks -verify -Wno-unreachable-code -analyzer-opt-analyze-nested-blocks %s -// RUN: %clang_analyze_cc1 -Wunused-variable -analyzer-checker=core,deadcode.DeadStores -analyzer-store=region -fblocks -verify -Wno-unreachable-code -analyzer-opt-analyze-nested-blocks %s +// RUN: %clang_analyze_cc1 -Wunused-variable -fblocks -Wno-unreachable-code \ +// RUN: -analyzer-checker=core,deadcode.DeadStores \ +// RUN: -analyzer-config deadcode.DeadStores:WarnForDeadNestedAssignments=false\ +// RUN: -analyzer-opt-analyze-nested-blocks -verify=non-nested %s +// +// RUN: %clang_analyze_cc1 -Wunused-variable -fblocks -Wno-unreachable-code \ +// RUN: -analyzer-checker=core,deadcode.DeadStores \ +// RUN: -analyzer-config deadcode.DeadStores:WarnForDeadNestedAssignments=false\ +// RUN: -analyzer-opt-analyze-nested-blocks -verify=non-nested \ +// RUN: -analyzer-store=region %s +// +// RUN: %clang_analyze_cc1 -Wunused-variable -fblocks -Wno-unreachable-code \ +// RUN: -analyzer-checker=core,deadcode.DeadStores \ +// RUN: -analyzer-opt-analyze-nested-blocks -verify=non-nested,nested %s void f1() { - int k, y; // expected-warning{{unused variable 'k'}} expected-warning{{unused variable 'y'}} - int abc=1; - long idx=abc+3*5; // expected-warning {{never read}} expected-warning{{unused variable 'idx'}} + int k, y; // non-nested-warning {{unused variable 'k'}} + // non-nested-warning@-1 {{unused variable 'y'}} + int abc = 1; + long idx = abc + 3 * 5; // non-nested-warning {{never read}} + // non-nested-warning@-1 {{unused variable 'idx'}} } void f2(void *b) { - char *c = (char*)b; // no-warning - char *d = b+1; // expected-warning {{never read}} expected-warning{{unused variable 'd'}} - printf("%s", c); // expected-warning{{implicitly declaring library function 'printf' with type 'int (const char *, ...)'}} \ - // expected-note{{include the header or explicitly provide a declaration for 'printf'}} + char *c = (char *)b; // no-warning + char *d = b + 1; // non-nested-warning {{never read}} + // non-nested-warning@-1 {{unused variable 'd'}} + printf("%s", c); + // non-nested-warning@-1 {{implicitly declaring library function 'printf' with type 'int (const char *, ...)'}} + // non-nested-note@-2 {{include the header or explicitly provide a declaration for 'printf'}} } int f(); - void f3() { int r; if ((r = f()) != 0) { // no-warning - int y = r; // no-warning + int y = r; // no-warning printf("the error is: %d\n", y); } } void f4(int k) { - k = 1; - if (k) f1(); - - k = 2; // expected-warning {{never read}} + k = 2; // non-nested-warning {{never read}} } - -void f5() { - - int x = 4; // no-warning - int *p = &x; // expected-warning{{never read}} expected-warning{{unused variable 'p'}} +void f5() { + int x = 4; // no-warning + int *p = &x; // non-nested-warning {{never read}} + // non-nested-warning@-1 {{unused variable 'p'}} } -// int f6() { - int x = 4; ++x; // no-warning return 1; } -int f7(int *p) { +int f7(int *p) { // This is allowed for defensive programming. - p = 0; // no-warning + p = 0; // no-warning return 1; } -int f7b(int *p) { +int f7b(int *p) { // This is allowed for defensive programming. - p = (0); // no-warning + p = (0); // no-warning return 1; } -int f7c(int *p) { +int f7c(int *p) { // This is allowed for defensive programming. - p = (void*) 0; // no-warning + p = (void *)0; // no-warning return 1; } -int f7d(int *p) { +int f7d(int *p) { // This is allowed for defensive programming. - p = (void*) (0); // no-warning + p = (void *)(0); // no-warning return 1; } -// Don't warn for dead stores in nested expressions. We have yet -// to see a real bug in this scenario. +// Warn for dead stores in nested expressions. int f8(int *p) { extern int *baz(); - if ((p = baz())) // no-warning + if ((p = baz())) // nested-warning {{Although the value stored}} return 1; return 0; } int f9() { int x = 4; - x = x + 10; // expected-warning{{never read}} + x = x + 10; // non-nested-warning {{never read}} return 1; } int f10() { int x = 4; - x = 10 + x; // expected-warning{{never read}} + x = 10 + x; // non-nested-warning {{never read}} return 1; } int f11() { int x = 4; - return x++; // expected-warning{{never read}} + return x++; // non-nested-warning {{never read}} } int f11b() { @@ -105,38 +113,38 @@ int f11b() { } int f12a(int y) { - int x = y; // expected-warning{{unused variable 'x'}} + int x = y; // non-nested-warning {{unused variable 'x'}} return 1; } + int f12b(int y) { - int x __attribute__((unused)) = y; // no-warning + int x __attribute__((unused)) = y; // no-warning return 1; } + int f12c(int y) { // Allow initialiation of scalar variables by parameters as a form of // defensive programming. - int x = y; // no-warning + int x = y; // no-warning x = 1; return x; } // Filed with PR 2630. This code should produce no warnings. -int f13(void) -{ +int f13(void) { int a = 1; int b, c = b = a + a; if (b > 0) return (0); - return (a + b + c); } // Filed with PR 2763. int f14(int count) { int index, nextLineIndex; - for (index = 0; index < count; index = nextLineIndex+1) { - nextLineIndex = index+1; // no-warning + for (index = 0; index < count; index = nextLineIndex + 1) { + nextLineIndex = index + 1; // no-warning continue; } return index; @@ -144,16 +152,15 @@ int f14(int count) { // Test case for void f15(unsigned x, unsigned y) { - int count = x * y; // no-warning - int z[count]; // expected-warning{{unused variable 'z'}} + int count = x * y; // no-warning + int z[count]; // non-nested-warning {{unused variable 'z'}} } -// Don't warn for dead stores in nested expressions. We have yet -// to see a real bug in this scenario. +// Warn for dead stores in nested expressions. int f16(int x) { x = x * 2; - x = sizeof(int [x = (x || x + 1) * 2]) - ? 5 : 8; + x = sizeof(int[x = (x || x + 1) * 2]) ? 5 : 8; + // nested-warning@-1 {{Although the value stored}} return x; } @@ -168,39 +175,39 @@ void f17() { // what that value is actually used. In other words, don't say "Although the // value stored to 'x' is used...". int f18() { - int x = 0; // no-warning - if (1) - x = 10; // expected-warning{{Value stored to 'x' is never read}} - while (1) - x = 10; // expected-warning{{Value stored to 'x' is never read}} - // unreachable. - do - x = 10; // no-warning - while (1); - return (x = 10); // no-warning + int x = 0; // no-warning + if (1) + x = 10; // non-nested-warning {{Value stored to 'x' is never read}} + while (1) + x = 10; // non-nested-warning {{Value stored to 'x' is never read}} + // unreachable. + do + x = 10; // no-warning + while (1); + return (x = 10); // no-warning } int f18_a() { - int x = 0; // no-warning - return (x = 10); // no-warning + int x = 0; // no-warning + return (x = 10); // nested-warning {{Although the value stored}} } void f18_b() { - int x = 0; // no-warning - if (1) - x = 10; // expected-warning{{Value stored to 'x' is never read}} + int x = 0; // no-warning + if (1) + x = 10; // non-nested-warning {{Value stored to 'x' is never read}} } void f18_c() { int x = 0; while (1) - x = 10; // expected-warning{{Value stored to 'x' is never read}} + x = 10; // non-nested-warning {{Value stored to 'x' is never read}} } void f18_d() { int x = 0; // no-warning do - x = 10; // expected-warning{{Value stored to 'x' is never read}} + x = 10; // non-nested-warning {{Value stored to 'x' is never read}} while (1); } @@ -208,7 +215,7 @@ void f18_d() { // http://llvm.org/bugs/show_bug.cgi?id=3514 extern const int MyConstant; int f19(void) { - int x = MyConstant; // no-warning + int x = MyConstant; // no-warning x = 1; return x; } @@ -217,7 +224,7 @@ int f19b(void) { // This case is the same as f19. const int MyConstant = 0; int x = MyConstant; // no-warning x = 1; - return x; + return x; } void f20(void) { @@ -228,8 +235,7 @@ void f20(void) { void halt() __attribute__((noreturn)); int f21() { int x = 4; - - x = x + 1; // expected-warning{{never read}} + x = x + 1; // non-nested-warning {{never read}} if (1) { halt(); (void)x; @@ -261,7 +267,7 @@ void f22() { int y19 = 4; int y20 = 4; - x = x + 1; // expected-warning{{never read}} + x = x + 1; // non-nested-warning {{never read}} ++y1; ++y2; ++y3; @@ -309,13 +315,13 @@ void f22() { } else (void)x; (void)x; - break; + break; case 4: - 0 ? : ((void)y4, ({ return; })); + 0 ?: ((void)y4, ({ return; })); (void)x; break; case 5: - 1 ? : (void)x; + 1 ?: (void)x; 0 ? (void)x : ((void)y5, ({ return; })); (void)x; break; @@ -326,11 +332,13 @@ void f22() { case 7: (void)(0 && x); (void)y7; - (void)(0 || (y8, ({ return; }), 1)); // expected-warning {{expression result unused}} + (void)(0 || (y8, ({ return; }), 1)); + // non-nested-warning@-1 {{expression result unused}} (void)x; break; case 8: - (void)(1 && (y9, ({ return; }), 1)); // expected-warning {{expression result unused}} + (void)(1 && (y9, ({ return; }), 1)); + // non-nested-warning@-1 {{expression result unused}} (void)x; break; case 9: @@ -365,16 +373,16 @@ void f22() { for (;;) { (void)y16; } - (void)x; + (void)x; break; case 15: - for (;1;) { + for (; 1;) { (void)y17; } (void)x; break; case 16: - for (;0;) { + for (; 0;) { (void)x; } (void)y18; @@ -390,28 +398,34 @@ void f22() { } } -void f23_aux(const char* s); +void f23_aux(const char *s); void f23(int argc, char **argv) { int shouldLog = (argc > 1); // no-warning - ^{ - if (shouldLog) f23_aux("I did too use it!\n"); - else f23_aux("I shouldn't log. Wait.. d'oh!\n"); + ^{ + if (shouldLog) + f23_aux("I did too use it!\n"); + else + f23_aux("I shouldn't log. Wait.. d'oh!\n"); }(); } void f23_pos(int argc, char **argv) { - int shouldLog = (argc > 1); // expected-warning{{Value stored to 'shouldLog' during its initialization is never read}} expected-warning{{unused variable 'shouldLog'}} - ^{ - f23_aux("I did too use it!\n"); - }(); + int shouldLog = (argc > 1); + // non-nested-warning@-1 {{Value stored to 'shouldLog' during its initialization is never read}} + // non-nested-warning@-2 {{unused variable 'shouldLog'}} + ^{ + f23_aux("I did too use it!\n"); + }(); } void f24_A(int y) { // FIXME: One day this should be reported as dead since 'z = x + y' is dead. int x = (y > 2); // no-warning - ^ { - int z = x + y; // expected-warning{{Value stored to 'z' during its initialization is never read}} expected-warning{{unused variable 'z'}} - }(); + ^{ + int z = x + y; + // non-nested-warning@-1 {{Value stored to 'z' during its initialization is never read}} + // non-nested-warning@-2 {{unused variable 'z'}} + }(); } void f24_B(int y) { @@ -426,7 +440,7 @@ void f24_B(int y) { int f24_C(int y) { // FIXME: One day this should be reported as dead since 'x' is just overwritten. __block int x = (y > 2); // no-warning - ^{ + ^{ x = 5; // no-warning }(); return x; @@ -434,32 +448,35 @@ int f24_C(int y) { int f24_D(int y) { __block int x = (y > 2); // no-warning - ^{ + ^{ if (y > 4) x = 5; // no-warning }(); return x; } -// This example shows that writing to a variable captured by a block means that it might -// not be dead. +// This example shows that writing to a variable captured by a block means that +// it might not be dead. int f25(int y) { __block int x = (y > 2); __block int z = 0; - void (^foo)() = ^{ z = x + y; }; + void (^foo)() = ^{ + z = x + y; + }; x = 4; // no-warning foo(); - return z; + return z; } -// This test is mostly the same as 'f25', but shows that the heuristic of pruning out dead -// stores for variables that are just marked '__block' is overly conservative. +// This test is mostly the same as 'f25', but shows that the heuristic of +// pruning out dead stores for variables that are just marked '__block' is +// overly conservative. int f25_b(int y) { // FIXME: we should eventually report a dead store here. __block int x = (y > 2); __block int z = 0; x = 4; // no-warning - return z; + return z; } int f26_nestedblocks() { @@ -468,10 +485,10 @@ int f26_nestedblocks() { __block int y = 0; ^{ int k; - k = 1; // expected-warning{{Value stored to 'k' is never read}} + k = 1; // non-nested-warning {{Value stored to 'k' is never read}} ^{ - y = z + 1; - }(); + y = z + 1; + }(); }(); return y; } @@ -480,11 +497,13 @@ int f26_nestedblocks() { // placed within the increment code of for loops. void rdar8014335() { for (int i = 0 ; i != 10 ; ({ break; })) { - for ( ; ; ({ ++i; break; })) ; // expected-warning {{'break' is bound to current loop, GCC binds it to the enclosing loop}} + for (;; ({ ++i; break; })) + ; + // non-nested-warning@-2 {{'break' is bound to current loop, GCC binds it to the enclosing loop}} // Note that the next value stored to 'i' is never executed // because the next statement to be executed is the 'break' // in the increment code of the first loop. - i = i * 3; // expected-warning{{Value stored to 'i' is never read}} + i = i * 3; // non-nested-warning {{Value stored to 'i' is never read}} } } @@ -517,10 +536,8 @@ void rdar8405222_aux(int i); void rdar8405222() { const int show = 0; int i = 0; - if (show) - i = 5; // no-warning - + i = 5; // no-warning if (show) rdar8405222_aux(i); } @@ -529,13 +546,13 @@ void rdar8405222() { // silencing heuristics. int radar11185138_foo() { int x, y; - x = y = 0; // expected-warning {{never read}} + x = y = 0; // non-nested-warning {{never read}} return y; } int rdar11185138_bar() { int y; - int x = y = 0; // no-warning + int x = y = 0; // nested-warning {{Although the value stored}} x = 2; y = 2; return x + y; @@ -550,26 +567,58 @@ int *radar11185138_baz() { int getInt(); int *getPtr(); void testBOComma() { - int x0 = (getInt(), 0); // expected-warning{{unused variable 'x0'}} - int x1 = (getInt(), getInt()); // expected-warning {{Value stored to 'x1' during its initialization is never read}} // expected-warning{{unused variable 'x1'}} - int x2 = (getInt(), getInt(), getInt()); //expected-warning{{Value stored to 'x2' during its initialization is never read}} // expected-warning{{unused variable 'x2'}} + int x0 = (getInt(), 0); // non-nested-warning {{unused variable 'x0'}} + int x1 = (getInt(), getInt()); + // non-nested-warning@-1 {{Value stored to 'x1' during its initialization is never read}} + // non-nested-warning@-2 {{unused variable 'x1'}} + + int x2 = (getInt(), getInt(), getInt()); + // non-nested-warning@-1 {{Value stored to 'x2' during its initialization is never read}} + // non-nested-warning@-2 {{unused variable 'x2'}} + int x3; - x3 = (getInt(), getInt(), 0); // expected-warning{{Value stored to 'x3' is never read}} - int x4 = (getInt(), (getInt(), 0)); // expected-warning{{unused variable 'x4'}} + x3 = (getInt(), getInt(), 0); + // non-nested-warning@-1 {{Value stored to 'x3' is never read}} + + int x4 = (getInt(), (getInt(), 0)); + // non-nested-warning@-1 {{unused variable 'x4'}} + int y; - int x5 = (getInt(), (y = 0)); // expected-warning{{unused variable 'x5'}} - int x6 = (getInt(), (y = getInt())); //expected-warning {{Value stored to 'x6' during its initialization is never read}} // expected-warning{{unused variable 'x6'}} - int x7 = 0, x8 = getInt(); //expected-warning {{Value stored to 'x8' during its initialization is never read}} // expected-warning{{unused variable 'x8'}} // expected-warning{{unused variable 'x7'}} - int x9 = getInt(), x10 = 0; //expected-warning {{Value stored to 'x9' during its initialization is never read}} // expected-warning{{unused variable 'x9'}} // expected-warning{{unused variable 'x10'}} - int m = getInt(), mm, mmm; //expected-warning {{Value stored to 'm' during its initialization is never read}} // expected-warning{{unused variable 'm'}} // expected-warning{{unused variable 'mm'}} // expected-warning{{unused variable 'mmm'}} - int n, nn = getInt(); //expected-warning {{Value stored to 'nn' during its initialization is never read}} // expected-warning{{unused variable 'n'}} // expected-warning{{unused variable 'nn'}} + int x5 = (getInt(), (y = 0)); + // non-nested-warning@-1 {{unused variable 'x5'}} + // nested-warning@-2 {{Although the value stored}} + + int x6 = (getInt(), (y = getInt())); + // non-nested-warning@-1 {{Value stored to 'x6' during its initialization is never read}} + // non-nested-warning@-2 {{unused variable 'x6'}} + // nested-warning@-3 {{Although the value stored}} + + int x7 = 0, x8 = getInt(); + // non-nested-warning@-1 {{Value stored to 'x8' during its initialization is never read}} + // non-nested-warning@-2 {{unused variable 'x8'}} + // non-nested-warning@-3 {{unused variable 'x7'}} + + int x9 = getInt(), x10 = 0; + // non-nested-warning@-1 {{Value stored to 'x9' during its initialization is never read}} + // non-nested-warning@-2 {{unused variable 'x9'}} + // non-nested-warning@-3 {{unused variable 'x10'}} + + int m = getInt(), mm, mmm; + // non-nested-warning@-1 {{Value stored to 'm' during its initialization is never read}} + // non-nested-warning@-2 {{unused variable 'm'}} + // non-nested-warning@-3 {{unused variable 'mm'}} + // non-nested-warning@-4 {{unused variable 'mmm'}} + + int n, nn = getInt(); + // non-nested-warning@-1 {{Value stored to 'nn' during its initialization is never read}} + // non-nested-warning@-2 {{unused variable 'n'}} + // non-nested-warning@-3 {{unused variable 'nn'}} int *p; p = (getPtr(), (int *)0); // no warning - } void testVolatile() { - volatile int v; - v = 0; // no warning + volatile int v; + v = 0; // no warning } diff --git a/clang/test/Analysis/dead-stores.cpp b/clang/test/Analysis/dead-stores.cpp index d926ccf5ecf69..94865b36a9084 100644 --- a/clang/test/Analysis/dead-stores.cpp +++ b/clang/test/Analysis/dead-stores.cpp @@ -1,15 +1,26 @@ -// RUN: %clang_analyze_cc1 -fcxx-exceptions -fexceptions -fblocks -std=c++11 -analyzer-checker=deadcode.DeadStores -verify -Wno-unreachable-code %s -// RUN: %clang_analyze_cc1 -fcxx-exceptions -fexceptions -fblocks -std=c++11 -analyzer-store=region -analyzer-checker=deadcode.DeadStores -verify -Wno-unreachable-code %s +// RUN: %clang_analyze_cc1 -fcxx-exceptions -fexceptions -fblocks -std=c++11 \ +// RUN: -analyzer-checker=deadcode.DeadStores -Wno-unreachable-code \ +// RUN: -analyzer-config deadcode.DeadStores:WarnForDeadNestedAssignments=false\ +// RUN: -verify=non-nested %s +// +// RUN: %clang_analyze_cc1 -fcxx-exceptions -fexceptions -fblocks -std=c++11 \ +// RUN: -analyzer-store=region -analyzer-checker=deadcode.DeadStores \ +// RUN: -analyzer-config deadcode.DeadStores:WarnForDeadNestedAssignments=false\ +// RUN: -Wno-unreachable-code -verify=non-nested %s +// +// RUN: %clang_analyze_cc1 -fcxx-exceptions -fexceptions -fblocks -std=c++11 \ +// RUN: -analyzer-checker=deadcode.DeadStores -Wno-unreachable-code \ +// RUN: -verify=non-nested,nested %s //===----------------------------------------------------------------------===// // Basic dead store checking (but in C++ mode). //===----------------------------------------------------------------------===// int j; +int make_int(); void test1() { int x = 4; - - x = x + 1; // expected-warning{{never read}} + x = x + 1; // non-nested-warning {{never read}} switch (j) { case 1: @@ -17,6 +28,11 @@ void test1() { (void)x; break; } + + int y; + (void)y; + if ((y = make_int())) // nested-warning {{Although the value stored}} + return; } //===----------------------------------------------------------------------===// @@ -25,6 +41,7 @@ void test1() { class Test2 { int &x; + public: Test2(int &y) : x(y) {} ~Test2() { ++x; } @@ -66,17 +83,17 @@ void test2_b() { //===----------------------------------------------------------------------===// void test3_a(int x) { - x = x + 1; // expected-warning{{never read}} + x = x + 1; // non-nested-warning {{never read}} } void test3_b(int &x) { - x = x + 1; // no-warninge + x = x + 1; // no-warning } void test3_c(int x) { int &y = x; - // Shows the limitation of dead stores tracking. The write is really - // dead since the value cannot escape the function. + // Shows the limitation of dead stores tracking. The write is really dead + // since the value cannot escape the function. ++y; // no-warning } @@ -94,7 +111,7 @@ void test3_e(int &x) { //===----------------------------------------------------------------------===// static void test_new(unsigned n) { - char **p = new char* [n]; // expected-warning{{never read}} + char **p = new char *[n]; // non-nested-warning {{never read}} } //===----------------------------------------------------------------------===// @@ -102,11 +119,11 @@ static void test_new(unsigned n) { //===----------------------------------------------------------------------===// namespace foo { - int test_4(int x) { - x = 2; // expected-warning{{Value stored to 'x' is never read}} - x = 2; - return x; - } +int test_4(int x) { + x = 2; // non-nested-warning {{Value stored to 'x' is never read}} + x = 2; + return x; +} } //===----------------------------------------------------------------------===// @@ -119,42 +136,39 @@ int test_5() { try { x = 2; // no-warning test_5_Aux(); - } - catch (int z) { + } catch (int z) { return x + z; } return 1; } - int test_6_aux(unsigned x); - void test_6() { - unsigned currDestLen = 0; // no-warning + unsigned currDestLen = 0; // no-warning try { while (test_6_aux(currDestLen)) { currDestLen += 2; // no-warning - } + } + } catch (void *) { } - catch (void *) {} } void test_6b() { - unsigned currDestLen = 0; // no-warning + unsigned currDestLen = 0; // no-warning try { while (test_6_aux(currDestLen)) { - currDestLen += 2; // expected-warning {{Value stored to 'currDestLen' is never read}} + currDestLen += 2; + // non-nested-warning@-1 {{Value stored to 'currDestLen' is never read}} break; - } + } + } catch (void *) { } - catch (void *) {} } - void testCXX11Using() { using Int = int; Int value; - value = 1; // expected-warning {{never read}} + value = 1; // non-nested-warning {{never read}} } //===----------------------------------------------------------------------===// @@ -177,13 +191,14 @@ int radar_13213575() { template void test_block_in_dependent_context(typename T::some_t someArray) { ^{ - int i = someArray[0]; // no-warning + int i = someArray[0]; // no-warning }(); } void test_block_in_non_dependent_context(int *someArray) { ^{ - int i = someArray[0]; // expected-warning {{Value stored to 'i' during its initialization is never read}} + int i = someArray[0]; + // non-nested-warning@-1 {{Value stored to 'i' during its initialization is never read}} }(); } diff --git a/clang/test/Analysis/dead-stores.m b/clang/test/Analysis/dead-stores.m index 9f91f393a1164..27543ab38139b 100644 --- a/clang/test/Analysis/dead-stores.m +++ b/clang/test/Analysis/dead-stores.m @@ -1,5 +1,4 @@ // RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.core -analyzer-checker=deadcode.DeadStores,osx.cocoa.RetainCount -fblocks -verify -Wno-objc-root-class %s -// expected-no-diagnostics typedef signed char BOOL; typedef unsigned int NSUInteger; @@ -72,7 +71,8 @@ - (id) init; @implementation Rdar7947686_B - (id) init { - id x = (self = [super init]); // no-warning + id x = (self = [super init]); + // expected-warning@-1 {{Although the value stored to 'self'}} return x; } @end From d0af8139132ba251e1f1462a457aa47d422c877f Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Tue, 3 Sep 2019 17:57:01 +0000 Subject: [PATCH 141/181] [analyzer] NonNullParamChecker and CStringChecker parameter number in checker message There are some functions which can't be given a null pointer as parameter either because it has a nonnull attribute or it is declared to have undefined behavior (e.g. strcmp()). Sometimes it is hard to determine from the checker message which parameter is null at the invocation, so now this information is included in the message. This commit fixes https://bugs.llvm.org/show_bug.cgi?id=39358 Reviewed By: NoQ, Szelethus, whisperity Patch by Tibor Brunner! Differential Revision: https://reviews.llvm.org/D66333 llvm-svn: 370798 (cherry picked from commit 1b439659a8407f469dd932814df15244dee254d2) --- .../Checkers/CStringChecker.cpp | 40 ++++++++++--------- .../Checkers/NonNullParamChecker.cpp | 19 ++++++--- .../Inputs/expected-plists/edges-new.mm.plist | 6 +-- .../expected-plists/plist-output.m.plist | 6 +-- clang/test/Analysis/misc-ps-region-store.m | 2 +- clang/test/Analysis/null-deref-ps.c | 6 +-- 6 files changed, 45 insertions(+), 34 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp index 44f4530781a8e..b3aa17b03fd5c 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp @@ -198,7 +198,8 @@ class CStringChecker : public Checker< eval::Call, ProgramStateRef checkNonNull(CheckerContext &C, ProgramStateRef state, const Expr *S, - SVal l) const; + SVal l, + unsigned IdxOfArg) const; ProgramStateRef CheckLocation(CheckerContext &C, ProgramStateRef state, const Expr *S, @@ -277,7 +278,8 @@ CStringChecker::assumeZero(CheckerContext &C, ProgramStateRef state, SVal V, ProgramStateRef CStringChecker::checkNonNull(CheckerContext &C, ProgramStateRef state, - const Expr *S, SVal l) const { + const Expr *S, SVal l, + unsigned IdxOfArg) const { // If a previous check has failed, propagate the failure. if (!state) return nullptr; @@ -288,11 +290,13 @@ ProgramStateRef CStringChecker::checkNonNull(CheckerContext &C, if (stateNull && !stateNonNull) { if (Filter.CheckCStringNullArg) { SmallString<80> buf; - llvm::raw_svector_ostream os(buf); + llvm::raw_svector_ostream OS(buf); assert(CurrentFunctionDescription); - os << "Null pointer argument in call to " << CurrentFunctionDescription; + OS << "Null pointer argument in call to " << CurrentFunctionDescription + << ' ' << IdxOfArg << llvm::getOrdinalSuffix(IdxOfArg) + << " parameter"; - emitNullArgBug(C, stateNull, S, os.str()); + emitNullArgBug(C, stateNull, S, OS.str()); } return nullptr; } @@ -384,7 +388,7 @@ ProgramStateRef CStringChecker::CheckBufferAccess(CheckerContext &C, // Check that the first buffer is non-null. SVal BufVal = C.getSVal(FirstBuf); - state = checkNonNull(C, state, FirstBuf, BufVal); + state = checkNonNull(C, state, FirstBuf, BufVal, 1); if (!state) return nullptr; @@ -424,7 +428,7 @@ ProgramStateRef CStringChecker::CheckBufferAccess(CheckerContext &C, // If there's a second buffer, check it as well. if (SecondBuf) { BufVal = state->getSVal(SecondBuf, LCtx); - state = checkNonNull(C, state, SecondBuf, BufVal); + state = checkNonNull(C, state, SecondBuf, BufVal, 2); if (!state) return nullptr; @@ -1163,7 +1167,7 @@ void CStringChecker::evalCopyCommon(CheckerContext &C, // Ensure the destination is not null. If it is NULL there will be a // NULL pointer dereference. - state = checkNonNull(C, state, Dest, destVal); + state = checkNonNull(C, state, Dest, destVal, 1); if (!state) return; @@ -1172,7 +1176,7 @@ void CStringChecker::evalCopyCommon(CheckerContext &C, // Ensure the source is not null. If it is NULL there will be a // NULL pointer dereference. - state = checkNonNull(C, state, Source, srcVal); + state = checkNonNull(C, state, Source, srcVal, 2); if (!state) return; @@ -1383,7 +1387,7 @@ void CStringChecker::evalstrLengthCommon(CheckerContext &C, const CallExpr *CE, const Expr *Arg = CE->getArg(0); SVal ArgVal = state->getSVal(Arg, LCtx); - state = checkNonNull(C, state, Arg, ArgVal); + state = checkNonNull(C, state, Arg, ArgVal, 1); if (!state) return; @@ -1541,14 +1545,14 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, const Expr *Dst = CE->getArg(0); SVal DstVal = state->getSVal(Dst, LCtx); - state = checkNonNull(C, state, Dst, DstVal); + state = checkNonNull(C, state, Dst, DstVal, 1); if (!state) return; // Check that the source is non-null. const Expr *srcExpr = CE->getArg(1); SVal srcVal = state->getSVal(srcExpr, LCtx); - state = checkNonNull(C, state, srcExpr, srcVal); + state = checkNonNull(C, state, srcExpr, srcVal, 2); if (!state) return; @@ -1904,14 +1908,14 @@ void CStringChecker::evalStrcmpCommon(CheckerContext &C, const CallExpr *CE, // Check that the first string is non-null const Expr *s1 = CE->getArg(0); SVal s1Val = state->getSVal(s1, LCtx); - state = checkNonNull(C, state, s1, s1Val); + state = checkNonNull(C, state, s1, s1Val, 1); if (!state) return; // Check that the second string is non-null. const Expr *s2 = CE->getArg(1); SVal s2Val = state->getSVal(s2, LCtx); - state = checkNonNull(C, state, s2, s2Val); + state = checkNonNull(C, state, s2, s2Val, 2); if (!state) return; @@ -2038,14 +2042,14 @@ void CStringChecker::evalStrsep(CheckerContext &C, const CallExpr *CE) const { // Check that the search string pointer is non-null (though it may point to // a null string). SVal SearchStrVal = State->getSVal(SearchStrPtr, LCtx); - State = checkNonNull(C, State, SearchStrPtr, SearchStrVal); + State = checkNonNull(C, State, SearchStrPtr, SearchStrVal, 1); if (!State) return; // Check that the delimiter string is non-null. const Expr *DelimStr = CE->getArg(1); SVal DelimStrVal = State->getSVal(DelimStr, LCtx); - State = checkNonNull(C, State, DelimStr, DelimStrVal); + State = checkNonNull(C, State, DelimStr, DelimStrVal, 2); if (!State) return; @@ -2148,7 +2152,7 @@ void CStringChecker::evalMemset(CheckerContext &C, const CallExpr *CE) const { // Ensure the memory area is not null. // If it is NULL there will be a NULL pointer dereference. - State = checkNonNull(C, StateNonZeroSize, Mem, MemVal); + State = checkNonNull(C, StateNonZeroSize, Mem, MemVal, 1); if (!State) return; @@ -2195,7 +2199,7 @@ void CStringChecker::evalBzero(CheckerContext &C, const CallExpr *CE) const { // Ensure the memory area is not null. // If it is NULL there will be a NULL pointer dereference. - State = checkNonNull(C, StateNonZeroSize, Mem, MemVal); + State = checkNonNull(C, StateNonZeroSize, Mem, MemVal, 1); if (!State) return; diff --git a/clang/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp index bf6b3e3e87cf8..bde3df52ba112 100644 --- a/clang/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp @@ -36,7 +36,9 @@ class NonNullParamChecker void checkPreCall(const CallEvent &Call, CheckerContext &C) const; std::unique_ptr - genReportNullAttrNonNull(const ExplodedNode *ErrorN, const Expr *ArgE) const; + genReportNullAttrNonNull(const ExplodedNode *ErrorN, + const Expr *ArgE, + unsigned IdxOfArg) const; std::unique_ptr genReportReferenceToNullPointer(const ExplodedNode *ErrorN, const Expr *ArgE) const; @@ -143,7 +145,7 @@ void NonNullParamChecker::checkPreCall(const CallEvent &Call, std::unique_ptr R; if (haveAttrNonNull) - R = genReportNullAttrNonNull(errorNode, ArgE); + R = genReportNullAttrNonNull(errorNode, ArgE, idx + 1); else if (haveRefTypeParam) R = genReportReferenceToNullPointer(errorNode, ArgE); @@ -179,7 +181,8 @@ void NonNullParamChecker::checkPreCall(const CallEvent &Call, std::unique_ptr NonNullParamChecker::genReportNullAttrNonNull(const ExplodedNode *ErrorNode, - const Expr *ArgE) const { + const Expr *ArgE, + unsigned IdxOfArg) const { // Lazily allocate the BugType object if it hasn't already been // created. Ownership is transferred to the BugReporter object once // the BugReport is passed to 'EmitWarning'. @@ -187,9 +190,13 @@ NonNullParamChecker::genReportNullAttrNonNull(const ExplodedNode *ErrorNode, BTAttrNonNull.reset(new BugType( this, "Argument with 'nonnull' attribute passed null", "API")); - auto R = llvm::make_unique( - *BTAttrNonNull, - "Null pointer passed as an argument to a 'nonnull' parameter", ErrorNode); + llvm::SmallString<256> SBuf; + llvm::raw_svector_ostream OS(SBuf); + OS << "Null pointer passed to " + << IdxOfArg << llvm::getOrdinalSuffix(IdxOfArg) + << " parameter expecting 'nonnull'"; + + auto R = llvm::make_unique(*BTAttrNonNull, SBuf, ErrorNode); if (ArgE) bugreporter::trackExpressionValue(ErrorNode, ArgE, *R); diff --git a/clang/test/Analysis/Inputs/expected-plists/edges-new.mm.plist b/clang/test/Analysis/Inputs/expected-plists/edges-new.mm.plist index ab04dadbf479c..b949e20ebbe86 100644 --- a/clang/test/Analysis/Inputs/expected-plists/edges-new.mm.plist +++ b/clang/test/Analysis/Inputs/expected-plists/edges-new.mm.plist @@ -10853,12 +10853,12 @@ depth0 extended_message - Null pointer passed as an argument to a 'nonnull' parameter + Null pointer passed to 1st parameter expecting 'nonnull' message - Null pointer passed as an argument to a 'nonnull' parameter + Null pointer passed to 1st parameter expecting 'nonnull' - descriptionNull pointer passed as an argument to a 'nonnull' parameter + descriptionNull pointer passed to 1st parameter expecting 'nonnull' categoryAPI typeArgument with 'nonnull' attribute passed null check_namecore.NonNullParamChecker diff --git a/clang/test/Analysis/Inputs/expected-plists/plist-output.m.plist b/clang/test/Analysis/Inputs/expected-plists/plist-output.m.plist index ee8bf06e59fd9..9203e48c46835 100644 --- a/clang/test/Analysis/Inputs/expected-plists/plist-output.m.plist +++ b/clang/test/Analysis/Inputs/expected-plists/plist-output.m.plist @@ -6141,12 +6141,12 @@ depth0 extended_message - Null pointer passed as an argument to a 'nonnull' parameter + Null pointer passed to 1st parameter expecting 'nonnull' message - Null pointer passed as an argument to a 'nonnull' parameter + Null pointer passed to 1st parameter expecting 'nonnull' - descriptionNull pointer passed as an argument to a 'nonnull' parameter + descriptionNull pointer passed to 1st parameter expecting 'nonnull' categoryAPI typeArgument with 'nonnull' attribute passed null check_namecore.NonNullParamChecker diff --git a/clang/test/Analysis/misc-ps-region-store.m b/clang/test/Analysis/misc-ps-region-store.m index 1ef1005631263..d1011bda1615e 100644 --- a/clang/test/Analysis/misc-ps-region-store.m +++ b/clang/test/Analysis/misc-ps-region-store.m @@ -1205,7 +1205,7 @@ static void RDar8424269_B(RDar8424269_A *p, unsigned char *RDar8424269_D, void rdar_8642434_funcB(struct rdar_8642434_typeA *x, struct rdar_8642434_typeA *y) { rdar_8642434_funcA(x); if (!y) - rdar_8642434_funcA(y); // expected-warning{{Null pointer passed as an argument to a 'nonnull' parameter}} + rdar_8642434_funcA(y); // expected-warning{{Null pointer passed to 1st parameter expecting 'nonnull'}} } // - Handle loads and stores from a symbolic index diff --git a/clang/test/Analysis/null-deref-ps.c b/clang/test/Analysis/null-deref-ps.c index d0e1f9f5cc330..d1c19e533deb4 100644 --- a/clang/test/Analysis/null-deref-ps.c +++ b/clang/test/Analysis/null-deref-ps.c @@ -88,21 +88,21 @@ int f5() { int bar(int* p, int q) __attribute__((nonnull)); int f6(int *p) { - return !p ? bar(p, 1) // expected-warning {{Null pointer passed as an argument to a 'nonnull' parameter}} + return !p ? bar(p, 1) // expected-warning {{Null pointer passed to 1st parameter expecting 'nonnull'}} : bar(p, 0); // no-warning } int bar2(int* p, int q) __attribute__((nonnull(1))); int f6b(int *p) { - return !p ? bar2(p, 1) // expected-warning {{Null pointer passed as an argument to a 'nonnull' parameter}} + return !p ? bar2(p, 1) // expected-warning {{Null pointer passed to 1st parameter expecting 'nonnull'}} : bar2(p, 0); // no-warning } int bar3(int*p, int q, int *r) __attribute__((nonnull(1,3))); int f6c(int *p, int *q) { - return !p ? bar3(q, 2, p) // expected-warning {{Null pointer passed as an argument to a 'nonnull' parameter}} + return !p ? bar3(q, 2, p) // expected-warning {{Null pointer passed to 3rd parameter expecting 'nonnull'}} : bar3(p, 2, q); // no-warning } From c1a51bd71594c1a775748437b7c4c9da9a725524 Mon Sep 17 00:00:00 2001 From: Benjamin Kramer Date: Tue, 3 Sep 2019 18:24:56 +0000 Subject: [PATCH 142/181] Unbreak the build after r370798 llvm-svn: 370807 (cherry picked from commit 0581a44e02e4d7170d3205d6109e84b101cbb71a) --- clang/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp index bde3df52ba112..5a35677949248 100644 --- a/clang/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp @@ -196,7 +196,7 @@ NonNullParamChecker::genReportNullAttrNonNull(const ExplodedNode *ErrorNode, << IdxOfArg << llvm::getOrdinalSuffix(IdxOfArg) << " parameter expecting 'nonnull'"; - auto R = llvm::make_unique(*BTAttrNonNull, SBuf, ErrorNode); + auto R = std::make_unique(*BTAttrNonNull, SBuf, ErrorNode); if (ArgE) bugreporter::trackExpressionValue(ErrorNode, ArgE, *R); From 8f90df1012dc4d58e4dd763233f5576a8ff2cbd2 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Thu, 5 Sep 2019 00:44:56 +0000 Subject: [PATCH 143/181] [analyzer] scan-build: handle --sysroot=/path in addition to --sysroot /path. Current code assumes flags in CompilerLinkerOptionMap don't use =, which isn't always true. Patch by Chris Laplante! Differential Revision: https://reviews.llvm.org/D66569 llvm-svn: 371002 (cherry picked from commit 473d0d7f569c84446d9880727403524d4a9838eb) --- clang/tools/scan-build/libexec/ccc-analyzer | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/clang/tools/scan-build/libexec/ccc-analyzer b/clang/tools/scan-build/libexec/ccc-analyzer index e1635e6c29b87..6d24a1af45393 100755 --- a/clang/tools/scan-build/libexec/ccc-analyzer +++ b/clang/tools/scan-build/libexec/ccc-analyzer @@ -498,7 +498,8 @@ my $HasSDK = 0; # Process the arguments. foreach (my $i = 0; $i < scalar(@ARGV); ++$i) { my $Arg = $ARGV[$i]; - my ($ArgKey) = split /=/,$Arg,2; + my @ArgParts = split /=/,$Arg,2; + my $ArgKey = @ArgParts[0]; # Be friendly to "" in the argument list. if (!defined($ArgKey)) { @@ -566,10 +567,12 @@ foreach (my $i = 0; $i < scalar(@ARGV); ++$i) { push @CompileOpts,$Arg; push @LinkOpts,$Arg; - while ($Cnt > 0) { - ++$i; --$Cnt; - push @CompileOpts, $ARGV[$i]; - push @LinkOpts, $ARGV[$i]; + if (scalar @ArgParts == 1) { + while ($Cnt > 0) { + ++$i; --$Cnt; + push @CompileOpts, $ARGV[$i]; + push @LinkOpts, $ARGV[$i]; + } } next; } From 69ca3dac2f288f3442cb8da4dff68c8323c3d96a Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Fri, 6 Sep 2019 20:55:24 +0000 Subject: [PATCH 144/181] [analyzer] pr43179: Make CallDescription defensive against C variadic functions. Most functions that our checkers react upon are not C-style variadic functions, and therefore they have as many actual arguments as they have formal parameters. However, it's not impossible to define a variadic function with the same name. This will crash any checker that relies on CallDescription to check the number of arguments but silently assumes that the number of parameters is the same. Change CallDescription to check both the number of arguments and the number of parameters by default. If we're intentionally trying to match variadic functions, allow specifying arguments and parameters separately (possibly omitting any of them). For now we only have one CallDescription which would make use of those, namely __builtin_va_start itself. Differential Revision: https://reviews.llvm.org/D67019 llvm-svn: 371256 (cherry picked from commit 2b1b4cab9605a33a6c90079b44bdddf048104e08) --- .../Core/PathSensitive/CallEvent.h | 20 ++++++++++++++++--- .../StaticAnalyzer/Checkers/ValistChecker.cpp | 4 +++- clang/lib/StaticAnalyzer/Core/CallEvent.cpp | 6 ++++-- clang/test/Analysis/cast-value-weird.cpp | 9 +++++++++ 4 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 clang/test/Analysis/cast-value-weird.cpp diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h index b2826a81f406f..8d1a672bc6129 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h @@ -1064,8 +1064,19 @@ class CallDescription { // e.g. "{a, b}" represent the qualified names, like "a::b". std::vector QualifiedName; Optional RequiredArgs; + Optional RequiredParams; int Flags; + // A constructor helper. + static Optional readRequiredParams(Optional RequiredArgs, + Optional RequiredParams) { + if (RequiredParams) + return RequiredParams; + if (RequiredArgs) + return static_cast(*RequiredArgs); + return None; + } + public: /// Constructs a CallDescription object. /// @@ -1078,14 +1089,17 @@ class CallDescription { /// call. Omit this parameter to match every occurrence of call with a given /// name regardless the number of arguments. CallDescription(int Flags, ArrayRef QualifiedName, - Optional RequiredArgs = None) + Optional RequiredArgs = None, + Optional RequiredParams = None) : QualifiedName(QualifiedName), RequiredArgs(RequiredArgs), + RequiredParams(readRequiredParams(RequiredArgs, RequiredParams)), Flags(Flags) {} /// Construct a CallDescription with default flags. CallDescription(ArrayRef QualifiedName, - Optional RequiredArgs = None) - : CallDescription(0, QualifiedName, RequiredArgs) {} + Optional RequiredArgs = None, + Optional RequiredParams = None) + : CallDescription(0, QualifiedName, RequiredArgs, RequiredParams) {} /// Get the name of the function that this object matches. StringRef getFunctionName() const { return QualifiedName.back(); } diff --git a/clang/lib/StaticAnalyzer/Checkers/ValistChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ValistChecker.cpp index 0ae556c3669f3..98794de2eb932 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ValistChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ValistChecker.cpp @@ -116,7 +116,9 @@ const SmallVector // vswprintf is the wide version of vsnprintf, // vsprintf has no wide version {{"vswscanf", 3}, 2}}; -const CallDescription ValistChecker::VaStart("__builtin_va_start", 2), + +const CallDescription + ValistChecker::VaStart("__builtin_va_start", /*Args=*/2, /*Params=*/1), ValistChecker::VaCopy("__builtin_va_copy", 2), ValistChecker::VaEnd("__builtin_va_end", 1); } // end anonymous namespace diff --git a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp index 7226ccb4d7533..b5d8599012acb 100644 --- a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp +++ b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp @@ -368,7 +368,8 @@ bool CallEvent::isCalled(const CallDescription &CD) const { if (CD.Flags & CDF_MaybeBuiltin) { return CheckerContext::isCLibraryFunction(FD, CD.getFunctionName()) && - (!CD.RequiredArgs || CD.RequiredArgs <= getNumArgs()); + (!CD.RequiredArgs || CD.RequiredArgs <= getNumArgs()) && + (!CD.RequiredParams || CD.RequiredParams <= parameters().size()); } if (!CD.IsLookupDone) { @@ -407,7 +408,8 @@ bool CallEvent::isCalled(const CallDescription &CD) const { return false; } - return (!CD.RequiredArgs || CD.RequiredArgs == getNumArgs()); + return (!CD.RequiredArgs || CD.RequiredArgs == getNumArgs()) && + (!CD.RequiredParams || CD.RequiredParams == parameters().size()); } SVal CallEvent::getArgSVal(unsigned Index) const { diff --git a/clang/test/Analysis/cast-value-weird.cpp b/clang/test/Analysis/cast-value-weird.cpp new file mode 100644 index 0000000000000..f15cc19c4477f --- /dev/null +++ b/clang/test/Analysis/cast-value-weird.cpp @@ -0,0 +1,9 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,apiModeling -verify %s + +// expected-no-diagnostics + +namespace llvm { +template +void cast(...); +void a() { cast(int()); } // no-crash +} // namespace llvm From 373f8bc6d74f19c233f5fdbb1760b07c9b539d29 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Fri, 6 Sep 2019 20:55:29 +0000 Subject: [PATCH 145/181] [analyzer] Add minimal support for fix-it hints. Allow attaching fixit hints to Static Analyzer BugReports. Fixits are attached either to the bug report itself or to its notes (path-sensitive event notes or path-insensitive extra notes). Add support for fixits in text output (including the default text output that goes without notes, as long as the fixit "belongs" to the warning). Add support for fixits in the plist output mode. Implement a fixit for the path-insensitive DeadStores checker. Only dead initialization warning is currently covered. Implement a fixit for the path-sensitive VirtualCall checker when the virtual method is not pure virtual (in this case the "fix" is to suppress the warning by qualifying the call). Both fixits are under an off-by-default flag for now, because they require more careful testing. Differential Revision: https://reviews.llvm.org/D65182 llvm-svn: 371257 (cherry picked from commit 6cee434ed10ead6b7416ca5ee9592b2b207eeb0f) --- .../clang/StaticAnalyzer/Checkers/Checkers.td | 12 ++- .../StaticAnalyzer/Core/AnalyzerOptions.def | 12 ++- .../Core/BugReporter/BugReporter.h | 36 +++++--- .../Core/BugReporter/PathDiagnostic.h | 8 ++ .../Checkers/DeadStoresChecker.cpp | 91 ++++++++++++------- .../Checkers/VirtualCallChecker.cpp | 14 +++ clang/lib/StaticAnalyzer/Core/BugReporter.cpp | 24 +++-- .../StaticAnalyzer/Core/PlistDiagnostics.cpp | 46 ++++++++++ .../Frontend/AnalysisConsumer.cpp | 60 ++++++++---- clang/test/Analysis/analyzer-config.c | 5 +- clang/test/Analysis/dead-stores.c | 32 ++++--- clang/test/Analysis/edges-new.mm | 2 +- clang/test/Analysis/objc-arc.m | 2 +- clang/test/Analysis/plist-output.m | 2 +- clang/test/Analysis/virtualcall-fixits.cpp | 45 +++++++++ 15 files changed, 296 insertions(+), 95 deletions(-) create mode 100644 clang/test/Analysis/virtualcall-fixits.cpp diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index 0626d29c3d604..4d52655045b3b 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -563,6 +563,11 @@ def UninitializedObjectChecker: Checker<"UninitializedObject">, def VirtualCallChecker : Checker<"VirtualCall">, HelpText<"Check virtual function calls during construction/destruction">, CheckerOptions<[ + CmdLineOption, CmdLineOption, "Warns for deadstores in nested assignments." "E.g.: if ((P = f())) where P is unused.", "true", - Released> + Released>, + CmdLineOption ]>, Documentation; diff --git a/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.def b/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.def index 9a0225e5a0810..d853fb74f9c77 100644 --- a/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.def +++ b/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.def @@ -300,6 +300,14 @@ ANALYZER_OPTION(bool, ShouldTrackConditionsDebug, "track-conditions-debug", "Whether to place an event at each tracked condition.", false) +ANALYZER_OPTION(bool, ShouldEmitFixItHintsAsRemarks, "fixits-as-remarks", + "Emit fix-it hints as remarks for testing purposes", + false) + +//===----------------------------------------------------------------------===// +// Unsigned analyzer options. +//===----------------------------------------------------------------------===// + ANALYZER_OPTION(unsigned, CTUImportThreshold, "ctu-import-threshold", "The maximal amount of translation units that is considered " "for import when inlining functions during CTU analysis. " @@ -308,10 +316,6 @@ ANALYZER_OPTION(unsigned, CTUImportThreshold, "ctu-import-threshold", "various translation units.", 100u) -//===----------------------------------------------------------------------===// -// Unsinged analyzer options. -//===----------------------------------------------------------------------===// - ANALYZER_OPTION( unsigned, AlwaysInlineSize, "ipa-always-inline-size", "The size of the functions (in basic blocks), which should be considered " diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h index e3d28472e8894..9be3b166c20d4 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h @@ -96,6 +96,7 @@ class BugReport : public llvm::ilist_node { SmallVector Ranges; const SourceRange ErrorNodeRange; NoteList Notes; + SmallVector Fixits; /// A (stack of) a set of symbols that are registered with this /// report as being "interesting", and thus used to help decide which @@ -280,20 +281,17 @@ class BugReport : public llvm::ilist_node { /// allows you to specify where exactly in the auto-generated path diagnostic /// the extra note should appear. void addNote(StringRef Msg, const PathDiagnosticLocation &Pos, - ArrayRef Ranges) { + ArrayRef Ranges = {}, + ArrayRef Fixits = {}) { auto P = std::make_shared(Pos, Msg); for (const auto &R : Ranges) P->addRange(R); - Notes.push_back(std::move(P)); - } + for (const auto &F : Fixits) + P->addFixit(F); - // FIXME: Instead of making an override, we could have default-initialized - // Ranges with {}, however it crashes the MSVC 2013 compiler. - void addNote(StringRef Msg, const PathDiagnosticLocation &Pos) { - std::vector Ranges; - addNote(Msg, Pos, Ranges); + Notes.push_back(std::move(P)); } virtual const NoteList &getNotes() { @@ -319,7 +317,7 @@ class BugReport : public llvm::ilist_node { const Stmt *getStmt() const; - /// Add a range to a bug report. + /// Add a range to the bug report. /// /// Ranges are used to highlight regions of interest in the source code. /// They should be at the same source code line as the BugReport location. @@ -335,6 +333,20 @@ class BugReport : public llvm::ilist_node { /// Get the SourceRanges associated with the report. virtual llvm::iterator_range getRanges() const; + /// Add a fix-it hint to the warning message of the bug report. + /// + /// Fix-it hints are the suggested edits to the code that would resolve + /// the problem explained by the bug report. Fix-it hints should be + /// as conservative as possible because it is not uncommon for the user + /// to blindly apply all fixits to their project. It usually is very hard + /// to produce a good fix-it hint for most path-sensitive warnings. + /// Fix-it hints can also be added to notes through the addNote() interface. + void addFixItHint(const FixItHint &F) { + Fixits.push_back(F); + } + + ArrayRef getFixits() const { return Fixits; } + /// Add custom or predefined bug report visitors to this report. /// /// The visitors should be used when the default trace is not sufficient. @@ -473,12 +485,14 @@ class BugReporter { void EmitBasicReport(const Decl *DeclWithIssue, const CheckerBase *Checker, StringRef BugName, StringRef BugCategory, StringRef BugStr, PathDiagnosticLocation Loc, - ArrayRef Ranges = None); + ArrayRef Ranges = None, + ArrayRef Fixits = None); void EmitBasicReport(const Decl *DeclWithIssue, CheckName CheckName, StringRef BugName, StringRef BugCategory, StringRef BugStr, PathDiagnosticLocation Loc, - ArrayRef Ranges = None); + ArrayRef Ranges = None, + ArrayRef Fixits = None); private: llvm::StringMap StrBugTypes; diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h index 52d2584150e17..6b5a2eb5a7e00 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h @@ -393,6 +393,7 @@ class PathDiagnosticPiece: public llvm::FoldingSetNode { StringRef Tag; std::vector ranges; + std::vector fixits; protected: PathDiagnosticPiece(StringRef s, Kind k, DisplayHint hint = Below); @@ -437,9 +438,16 @@ class PathDiagnosticPiece: public llvm::FoldingSetNode { ranges.push_back(SourceRange(B,E)); } + void addFixit(FixItHint F) { + fixits.push_back(F); + } + /// Return the SourceRanges associated with this PathDiagnosticPiece. ArrayRef getRanges() const { return ranges; } + /// Return the fix-it hints associated with this PathDiagnosticPiece. + ArrayRef getFixits() const { return fixits; } + virtual void Profile(llvm::FoldingSetNodeID &ID) const; void setAsLastInMainSourceFile() { diff --git a/clang/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp index e4889c1cc14df..61441889fc649 100644 --- a/clang/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp @@ -11,6 +11,7 @@ // //===----------------------------------------------------------------------===// +#include "clang/Lex/Lexer.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/ASTContext.h" #include "clang/AST/Attr.h" @@ -119,30 +120,37 @@ LookThroughTransitiveAssignmentsAndCommaOperators(const Expr *Ex) { } namespace { +class DeadStoresChecker : public Checker { +public: + bool ShowFixIts = false; + bool WarnForDeadNestedAssignments = true; + + void checkASTCodeBody(const Decl *D, AnalysisManager &Mgr, + BugReporter &BR) const; +}; + class DeadStoreObs : public LiveVariables::Observer { const CFG &cfg; ASTContext &Ctx; BugReporter& BR; - const CheckerBase *Checker; + const DeadStoresChecker *Checker; AnalysisDeclContext* AC; ParentMap& Parents; llvm::SmallPtrSet Escaped; std::unique_ptr reachableCode; const CFGBlock *currentBlock; std::unique_ptr> InEH; - const bool WarnForDeadNestedAssignments; enum DeadStoreKind { Standard, Enclosing, DeadIncrement, DeadInit }; public: DeadStoreObs(const CFG &cfg, ASTContext &ctx, BugReporter &br, - const CheckerBase *checker, AnalysisDeclContext *ac, + const DeadStoresChecker *checker, AnalysisDeclContext *ac, ParentMap &parents, llvm::SmallPtrSet &escaped, bool warnForDeadNestedAssignments) : cfg(cfg), Ctx(ctx), BR(br), Checker(checker), AC(ac), Parents(parents), - Escaped(escaped), currentBlock(nullptr), - WarnForDeadNestedAssignments(warnForDeadNestedAssignments) {} + Escaped(escaped), currentBlock(nullptr) {} ~DeadStoreObs() override {} @@ -205,12 +213,32 @@ class DeadStoreObs : public LiveVariables::Observer { llvm::raw_svector_ostream os(buf); const char *BugType = nullptr; + SmallVector Fixits; + switch (dsk) { - case DeadInit: + case DeadInit: { BugType = "Dead initialization"; os << "Value stored to '" << *V << "' during its initialization is never read"; + + ASTContext &ACtx = V->getASTContext(); + if (Checker->ShowFixIts) { + if (V->getInit()->HasSideEffects(ACtx, + /*IncludePossibleEffects=*/true)) { + break; + } + SourceManager &SM = ACtx.getSourceManager(); + const LangOptions &LO = ACtx.getLangOpts(); + SourceLocation L1 = + Lexer::findNextToken( + V->getTypeSourceInfo()->getTypeLoc().getEndLoc(), + SM, LO)->getEndLoc(); + SourceLocation L2 = + Lexer::getLocForEndOfToken(V->getInit()->getEndLoc(), 1, SM, LO); + Fixits.push_back(FixItHint::CreateRemoval({L1, L2})); + } break; + } case DeadIncrement: BugType = "Dead increment"; @@ -222,7 +250,7 @@ class DeadStoreObs : public LiveVariables::Observer { // eg.: f((x = foo())) case Enclosing: - if (!WarnForDeadNestedAssignments) + if (!Checker->WarnForDeadNestedAssignments) return; BugType = "Dead nested assignment"; os << "Although the value stored to '" << *V @@ -233,7 +261,7 @@ class DeadStoreObs : public LiveVariables::Observer { } BR.EmitBasicReport(AC->getDecl(), Checker, BugType, "Dead store", os.str(), - L, R); + L, R, Fixits); } void CheckVarDecl(const VarDecl *VD, const Expr *Ex, const Expr *Val, @@ -479,42 +507,37 @@ class FindEscaped { // DeadStoresChecker //===----------------------------------------------------------------------===// -namespace { -class DeadStoresChecker : public Checker { -public: - bool WarnForDeadNestedAssignments = true; - - void checkASTCodeBody(const Decl *D, AnalysisManager& mgr, - BugReporter &BR) const { +void DeadStoresChecker::checkASTCodeBody(const Decl *D, AnalysisManager &mgr, + BugReporter &BR) const { - // Don't do anything for template instantiations. - // Proving that code in a template instantiation is "dead" - // means proving that it is dead in all instantiations. - // This same problem exists with -Wunreachable-code. - if (const FunctionDecl *FD = dyn_cast(D)) - if (FD->isTemplateInstantiation()) - return; + // Don't do anything for template instantiations. + // Proving that code in a template instantiation is "dead" + // means proving that it is dead in all instantiations. + // This same problem exists with -Wunreachable-code. + if (const FunctionDecl *FD = dyn_cast(D)) + if (FD->isTemplateInstantiation()) + return; - if (LiveVariables *L = mgr.getAnalysis(D)) { - CFG &cfg = *mgr.getCFG(D); - AnalysisDeclContext *AC = mgr.getAnalysisDeclContext(D); - ParentMap &pmap = mgr.getParentMap(D); - FindEscaped FS; - cfg.VisitBlockStmts(FS); - DeadStoreObs A(cfg, BR.getContext(), BR, this, AC, pmap, FS.Escaped, - WarnForDeadNestedAssignments); - L->runOnAllBlocks(A); - } + if (LiveVariables *L = mgr.getAnalysis(D)) { + CFG &cfg = *mgr.getCFG(D); + AnalysisDeclContext *AC = mgr.getAnalysisDeclContext(D); + ParentMap &pmap = mgr.getParentMap(D); + FindEscaped FS; + cfg.VisitBlockStmts(FS); + DeadStoreObs A(cfg, BR.getContext(), BR, this, AC, pmap, FS.Escaped, + WarnForDeadNestedAssignments); + L->runOnAllBlocks(A); } -}; } void ento::registerDeadStoresChecker(CheckerManager &Mgr) { - auto Chk = Mgr.registerChecker(); + auto *Chk = Mgr.registerChecker(); const AnalyzerOptions &AnOpts = Mgr.getAnalyzerOptions(); Chk->WarnForDeadNestedAssignments = AnOpts.getCheckerBooleanOption(Chk, "WarnForDeadNestedAssignments"); + Chk->ShowFixIts = + AnOpts.getCheckerBooleanOption(Chk, "ShowFixIts"); } bool ento::shouldRegisterDeadStoresChecker(const LangOptions &LO) { diff --git a/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp index 53ed7e1761b98..5aa4ff4e57ee2 100644 --- a/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp @@ -43,6 +43,7 @@ class VirtualCallChecker public: // These are going to be null if the respective check is disabled. mutable std::unique_ptr BT_Pure, BT_Impure; + bool ShowFixIts = false; void checkBeginFunction(CheckerContext &C) const; void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const; @@ -146,6 +147,17 @@ void VirtualCallChecker::checkPreCall(const CallEvent &Call, } auto Report = std::make_unique(*BT, OS.str(), N); + + if (ShowFixIts && !IsPure) { + // FIXME: These hints are valid only when the virtual call is made + // directly from the constructor/destructor. Otherwise the dispatch + // will work just fine from other callees, and the fix may break + // the otherwise correct program. + FixItHint Fixit = FixItHint::CreateInsertion( + CE->getBeginLoc(), MD->getParent()->getNameAsString() + "::"); + Report->addFixItHint(Fixit); + } + C.emitReport(std::move(Report)); } @@ -206,6 +218,8 @@ void ento::registerVirtualCallChecker(CheckerManager &Mgr) { Chk->BT_Impure = llvm::make_unique( Mgr.getCurrentCheckName(), "Unexpected loss of virtual dispatch", categories::CXXObjectLifecycle); + Chk->ShowFixIts = Mgr.getAnalyzerOptions().getCheckerBooleanOption( + Mgr.getCurrentCheckName(), "ShowFixIts"); } } diff --git a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp index 2ca30726d26a9..9827b67849c86 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp @@ -2927,6 +2927,9 @@ void BugReporter::FlushReport(BugReportEquivClass& EQ) { Pieces.push_front(*I); } + for (const auto &I : report->getFixits()) + Pieces.back()->addFixit(I); + updateExecutedLinesWithDiagnosticPieces(*PD); Consumer->HandlePathDiagnostic(std::move(PD)); } @@ -3047,26 +3050,29 @@ BugReporter::generateDiagnosticForConsumerMap( } void BugReporter::EmitBasicReport(const Decl *DeclWithIssue, - const CheckerBase *Checker, - StringRef Name, StringRef Category, - StringRef Str, PathDiagnosticLocation Loc, - ArrayRef Ranges) { + const CheckerBase *Checker, StringRef Name, + StringRef Category, StringRef Str, + PathDiagnosticLocation Loc, + ArrayRef Ranges, + ArrayRef Fixits) { EmitBasicReport(DeclWithIssue, Checker->getCheckName(), Name, Category, Str, - Loc, Ranges); + Loc, Ranges, Fixits); } void BugReporter::EmitBasicReport(const Decl *DeclWithIssue, CheckName CheckName, StringRef name, StringRef category, StringRef str, PathDiagnosticLocation Loc, - ArrayRef Ranges) { + ArrayRef Ranges, + ArrayRef Fixits) { // 'BT' is owned by BugReporter. BugType *BT = getBugTypeForName(CheckName, name, category); auto R = llvm::make_unique(*BT, str, Loc); R->setDeclWithIssue(DeclWithIssue); - for (ArrayRef::iterator I = Ranges.begin(), E = Ranges.end(); - I != E; ++I) - R->addRange(*I); + for (const auto &SR : Ranges) + R->addRange(SR); + for (const auto &FH : Fixits) + R->addFixItHint(FH); emitReport(std::move(R)); } diff --git a/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp b/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp index 8956752a9b0da..2a4076838c6f8 100644 --- a/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp +++ b/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp @@ -134,6 +134,7 @@ class PlistPrinter { void EmitRanges(raw_ostream &o, const ArrayRef Ranges, unsigned indent); void EmitMessage(raw_ostream &o, StringRef Message, unsigned indent); + void EmitFixits(raw_ostream &o, ArrayRef fixits, unsigned indent); void ReportControlFlow(raw_ostream &o, const PathDiagnosticControlFlowPiece& P, @@ -222,6 +223,33 @@ void PlistPrinter::EmitMessage(raw_ostream &o, StringRef Message, EmitString(o, Message) << '\n'; } +void PlistPrinter::EmitFixits(raw_ostream &o, ArrayRef fixits, + unsigned indent) { + if (fixits.size() == 0) + return; + + const SourceManager &SM = PP.getSourceManager(); + const LangOptions &LangOpts = PP.getLangOpts(); + + Indent(o, indent) << "fixits\n"; + Indent(o, indent) << "\n"; + for (const auto &fixit : fixits) { + assert(!fixit.isNull()); + // FIXME: Add support for InsertFromRange and BeforePreviousInsertion. + assert(!fixit.InsertFromRange.isValid() && "Not implemented yet!"); + assert(!fixit.BeforePreviousInsertions && "Not implemented yet!"); + Indent(o, indent) << " \n"; + Indent(o, indent) << " remove_range\n"; + EmitRange(o, SM, Lexer::getAsCharRange(fixit.RemoveRange, SM, LangOpts), + FM, indent + 2); + Indent(o, indent) << " insert_string"; + EmitString(o, fixit.CodeToInsert); + o << "\n"; + Indent(o, indent) << " \n"; + } + Indent(o, indent) << "\n"; +} + void PlistPrinter::ReportControlFlow(raw_ostream &o, const PathDiagnosticControlFlowPiece& P, unsigned indent) { @@ -272,6 +300,9 @@ void PlistPrinter::ReportControlFlow(raw_ostream &o, EmitString(o, s) << '\n'; } + assert(P.getFixits().size() == 0 && + "Fixits on constrol flow pieces are not implemented yet!"); + --indent; Indent(o, indent) << "\n"; } @@ -308,6 +339,9 @@ void PlistPrinter::ReportEvent(raw_ostream &o, const PathDiagnosticEventPiece& P // Output the text. EmitMessage(o, P.getString(), indent); + // Output the fixits. + EmitFixits(o, P.getFixits(), indent); + // Finish up. --indent; Indent(o, indent); o << "\n"; @@ -335,6 +369,9 @@ void PlistPrinter::ReportCall(raw_ostream &o, const PathDiagnosticCallPiece &P, if (auto callExit = P.getCallExitEvent()) ReportPiece(o, *callExit, indent, depth, /*includeControlFlow*/ true); + + assert(P.getFixits().size() == 0 && + "Fixits on call pieces are not implemented yet!"); } void PlistPrinter::ReportMacroSubPieces(raw_ostream &o, @@ -347,6 +384,9 @@ void PlistPrinter::ReportMacroSubPieces(raw_ostream &o, I != E; ++I) { ReportPiece(o, **I, indent, depth, /*includeControlFlow*/ false); } + + assert(P.getFixits().size() == 0 && + "Fixits on constrol flow pieces are not implemented yet!"); } void PlistPrinter::ReportMacroExpansions(raw_ostream &o, unsigned indent) { @@ -404,6 +444,9 @@ void PlistPrinter::ReportNote(raw_ostream &o, const PathDiagnosticNotePiece& P, // Output the text. EmitMessage(o, P.getString(), indent); + // Output the fixits. + EmitFixits(o, P.getFixits(), indent); + // Finish up. --indent; Indent(o, indent); o << "\n"; @@ -432,6 +475,9 @@ void PlistPrinter::ReportPopUp(raw_ostream &o, // Output the text. EmitMessage(o, P.getString(), indent); + assert(P.getFixits().size() == 0 && + "Fixits on pop-up pieces are not implemented yet!"); + // Finish up. --indent; Indent(o, indent) << "\n"; diff --git a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp index a184f6463d10c..5acacc1decf55 100644 --- a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp +++ b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp @@ -83,11 +83,11 @@ void ento::createTextPathDiagnosticConsumer( namespace { class ClangDiagPathDiagConsumer : public PathDiagnosticConsumer { DiagnosticsEngine &Diag; - bool IncludePath, ShouldEmitAsError; + bool IncludePath = false, ShouldEmitAsError = false, FixitsAsRemarks = false; public: ClangDiagPathDiagConsumer(DiagnosticsEngine &Diag) - : Diag(Diag), IncludePath(false), ShouldEmitAsError(false) {} + : Diag(Diag) {} ~ClangDiagPathDiagConsumer() override {} StringRef getName() const override { return "ClangDiags"; } @@ -98,11 +98,9 @@ class ClangDiagPathDiagConsumer : public PathDiagnosticConsumer { return IncludePath ? Minimal : None; } - void enablePaths() { - IncludePath = true; - } - + void enablePaths() { IncludePath = true; } void enableWerror() { ShouldEmitAsError = true; } + void enableFixitsAsRemarks() { FixitsAsRemarks = true; } void FlushDiagnosticsImpl(std::vector &Diags, FilesMade *filesMade) override { @@ -111,22 +109,46 @@ class ClangDiagPathDiagConsumer : public PathDiagnosticConsumer { ? Diag.getCustomDiagID(DiagnosticsEngine::Error, "%0") : Diag.getCustomDiagID(DiagnosticsEngine::Warning, "%0"); unsigned NoteID = Diag.getCustomDiagID(DiagnosticsEngine::Note, "%0"); - - for (std::vector::iterator I = Diags.begin(), - E = Diags.end(); I != E; ++I) { + unsigned RemarkID = Diag.getCustomDiagID(DiagnosticsEngine::Remark, "%0"); + + auto reportPiece = + [&](unsigned ID, SourceLocation Loc, StringRef String, + ArrayRef Ranges, ArrayRef Fixits) { + if (!FixitsAsRemarks) { + Diag.Report(Loc, ID) << String << Ranges << Fixits; + } else { + Diag.Report(Loc, ID) << String << Ranges; + for (const FixItHint &Hint : Fixits) { + SourceManager &SM = Diag.getSourceManager(); + llvm::SmallString<128> Str; + llvm::raw_svector_ostream OS(Str); + // FIXME: Add support for InsertFromRange and + // BeforePreviousInsertion. + assert(!Hint.InsertFromRange.isValid() && "Not implemented yet!"); + assert(!Hint.BeforePreviousInsertions && "Not implemented yet!"); + OS << SM.getSpellingColumnNumber(Hint.RemoveRange.getBegin()) + << "-" << SM.getSpellingColumnNumber(Hint.RemoveRange.getEnd()) + << ": '" << Hint.CodeToInsert << "'"; + Diag.Report(Loc, RemarkID) << OS.str(); + } + } + }; + + for (std::vector::iterator I = Diags.begin(), + E = Diags.end(); + I != E; ++I) { const PathDiagnostic *PD = *I; - SourceLocation WarnLoc = PD->getLocation().asLocation(); - Diag.Report(WarnLoc, WarnID) << PD->getShortDescription() - << PD->path.back()->getRanges(); + reportPiece(WarnID, PD->getLocation().asLocation(), + PD->getShortDescription(), PD->path.back()->getRanges(), + PD->path.back()->getFixits()); // First, add extra notes, even if paths should not be included. for (const auto &Piece : PD->path) { if (!isa(Piece.get())) continue; - SourceLocation NoteLoc = Piece->getLocation().asLocation(); - Diag.Report(NoteLoc, NoteID) << Piece->getString() - << Piece->getRanges(); + reportPiece(NoteID, Piece->getLocation().asLocation(), + Piece->getString(), Piece->getRanges(), Piece->getFixits()); } if (!IncludePath) @@ -138,9 +160,8 @@ class ClangDiagPathDiagConsumer : public PathDiagnosticConsumer { if (isa(Piece.get())) continue; - SourceLocation NoteLoc = Piece->getLocation().asLocation(); - Diag.Report(NoteLoc, NoteID) << Piece->getString() - << Piece->getRanges(); + reportPiece(NoteID, Piece->getLocation().asLocation(), + Piece->getString(), Piece->getRanges(), Piece->getFixits()); } } } @@ -241,6 +262,9 @@ class AnalysisConsumer : public AnalysisASTConsumer, if (Opts->AnalyzerWerror) clangDiags->enableWerror(); + if (Opts->ShouldEmitFixItHintsAsRemarks) + clangDiags->enableFixitsAsRemarks(); + if (Opts->AnalysisDiagOpt == PD_TEXT) { clangDiags->enablePaths(); diff --git a/clang/test/Analysis/analyzer-config.c b/clang/test/Analysis/analyzer-config.c index c8a24eea50175..6c6883d88d69a 100644 --- a/clang/test/Analysis/analyzer-config.c +++ b/clang/test/Analysis/analyzer-config.c @@ -30,6 +30,7 @@ // CHECK-NEXT: ctu-dir = "" // CHECK-NEXT: ctu-import-threshold = 100 // CHECK-NEXT: ctu-index-name = externalDefMap.txt +// CHECK-NEXT: deadcode.DeadStores:ShowFixIts = false // CHECK-NEXT: deadcode.DeadStores:WarnForDeadNestedAssignments = true // CHECK-NEXT: debug.AnalysisOrder:* = false // CHECK-NEXT: debug.AnalysisOrder:Bind = false @@ -54,6 +55,7 @@ // CHECK-NEXT: experimental-enable-naive-ctu-analysis = false // CHECK-NEXT: exploration_strategy = unexplored_first_queue // CHECK-NEXT: faux-bodies = true +// CHECK-NEXT: fixits-as-remarks = false // CHECK-NEXT: graph-trim-interval = 1000 // CHECK-NEXT: inline-lambdas = true // CHECK-NEXT: ipa = dynamic-bifurcate @@ -74,6 +76,7 @@ // CHECK-NEXT: optin.cplusplus.UninitializedObject:NotesAsWarnings = false // CHECK-NEXT: optin.cplusplus.UninitializedObject:Pedantic = false // CHECK-NEXT: optin.cplusplus.VirtualCall:PureOnly = false +// CHECK-NEXT: optin.cplusplus.VirtualCall:ShowFixIts = false // CHECK-NEXT: optin.osx.cocoa.localizability.NonLocalizedStringChecker:AggressiveReport = false // CHECK-NEXT: optin.performance.Padding:AllowedPad = 24 // CHECK-NEXT: osx.NumberObjectConversion:Pedantic = false @@ -94,4 +97,4 @@ // CHECK-NEXT: unroll-loops = false // CHECK-NEXT: widen-loops = false // CHECK-NEXT: [stats] -// CHECK-NEXT: num-entries = 91 +// CHECK-NEXT: num-entries = 94 diff --git a/clang/test/Analysis/dead-stores.c b/clang/test/Analysis/dead-stores.c index 26377f7617ee5..cbfdabdcec632 100644 --- a/clang/test/Analysis/dead-stores.c +++ b/clang/test/Analysis/dead-stores.c @@ -1,17 +1,16 @@ -// RUN: %clang_analyze_cc1 -Wunused-variable -fblocks -Wno-unreachable-code \ -// RUN: -analyzer-checker=core,deadcode.DeadStores \ -// RUN: -analyzer-config deadcode.DeadStores:WarnForDeadNestedAssignments=false\ -// RUN: -analyzer-opt-analyze-nested-blocks -verify=non-nested %s -// -// RUN: %clang_analyze_cc1 -Wunused-variable -fblocks -Wno-unreachable-code \ -// RUN: -analyzer-checker=core,deadcode.DeadStores \ -// RUN: -analyzer-config deadcode.DeadStores:WarnForDeadNestedAssignments=false\ -// RUN: -analyzer-opt-analyze-nested-blocks -verify=non-nested \ -// RUN: -analyzer-store=region %s -// -// RUN: %clang_analyze_cc1 -Wunused-variable -fblocks -Wno-unreachable-code \ -// RUN: -analyzer-checker=core,deadcode.DeadStores \ -// RUN: -analyzer-opt-analyze-nested-blocks -verify=non-nested,nested %s +// RUN: %clang_analyze_cc1 -Wunused-variable -fblocks -Wno-unreachable-code \ +// RUN: -analyzer-checker=core,deadcode.DeadStores \ +// RUN: -analyzer-config deadcode.DeadStores:ShowFixIts=true \ +// RUN: -analyzer-config fixits-as-remarks=true \ +// RUN: -analyzer-config \ +// RUN: deadcode.DeadStores:WarnForDeadNestedAssignments=false \ +// RUN: -verify=non-nested %s + +// RUN: %clang_analyze_cc1 -Wunused-variable -fblocks -Wno-unreachable-code \ +// RUN: -analyzer-checker=core,deadcode.DeadStores \ +// RUN: -analyzer-config deadcode.DeadStores:ShowFixIts=true \ +// RUN: -analyzer-config fixits-as-remarks=true \ +// RUN: -verify=non-nested,nested %s void f1() { int k, y; // non-nested-warning {{unused variable 'k'}} @@ -19,12 +18,14 @@ void f1() { int abc = 1; long idx = abc + 3 * 5; // non-nested-warning {{never read}} // non-nested-warning@-1 {{unused variable 'idx'}} + // non-nested-remark@-2 {{11-24: ''}} } void f2(void *b) { char *c = (char *)b; // no-warning char *d = b + 1; // non-nested-warning {{never read}} // non-nested-warning@-1 {{unused variable 'd'}} + // non-nested-remark@-2 {{10-17: ''}} printf("%s", c); // non-nested-warning@-1 {{implicitly declaring library function 'printf' with type 'int (const char *, ...)'}} // non-nested-note@-2 {{include the header or explicitly provide a declaration for 'printf'}} @@ -50,6 +51,7 @@ void f5() { int x = 4; // no-warning int *p = &x; // non-nested-warning {{never read}} // non-nested-warning@-1 {{unused variable 'p'}} + // non-nested-remark@-2 {{9-13: ''}} } int f6() { @@ -413,6 +415,7 @@ void f23_pos(int argc, char **argv) { int shouldLog = (argc > 1); // non-nested-warning@-1 {{Value stored to 'shouldLog' during its initialization is never read}} // non-nested-warning@-2 {{unused variable 'shouldLog'}} + // non-nested-remark@-3 {{16-28: ''}} ^{ f23_aux("I did too use it!\n"); }(); @@ -425,6 +428,7 @@ void f24_A(int y) { int z = x + y; // non-nested-warning@-1 {{Value stored to 'z' during its initialization is never read}} // non-nested-warning@-2 {{unused variable 'z'}} + // non-nested-remark@-3 {{10-17: ''}} }(); } diff --git a/clang/test/Analysis/edges-new.mm b/clang/test/Analysis/edges-new.mm index dda1bfb31e745..6bddbef58f1db 100644 --- a/clang/test/Analysis/edges-new.mm +++ b/clang/test/Analysis/edges-new.mm @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 -triple x86_64-apple-darwin10 -analyzer-checker=core,deadcode.DeadStores,osx.cocoa.RetainCount,unix.Malloc,unix.MismatchedDeallocator -analyzer-output=plist -o %t -w %s +// RUN: %clang_analyze_cc1 -triple x86_64-apple-darwin10 -analyzer-checker=core,deadcode.DeadStores,osx.cocoa.RetainCount,unix.Malloc,unix.MismatchedDeallocator -analyzer-output=plist -analyzer-config deadcode.DeadStores:ShowFixIts=true -o %t -w %s // RUN: %normalize_plist <%t | diff -ub %S/Inputs/expected-plists/edges-new.mm.plist - //===----------------------------------------------------------------------===// diff --git a/clang/test/Analysis/objc-arc.m b/clang/test/Analysis/objc-arc.m index cab6618a4362a..7127232f0de5c 100644 --- a/clang/test/Analysis/objc-arc.m +++ b/clang/test/Analysis/objc-arc.m @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 -triple x86_64-apple-darwin10 -analyzer-checker=core,osx.cocoa.RetainCount,deadcode -verify -fblocks -analyzer-opt-analyze-nested-blocks -fobjc-arc -analyzer-output=plist-multi-file -o %t.plist %s +// RUN: %clang_analyze_cc1 -triple x86_64-apple-darwin10 -analyzer-checker=core,osx.cocoa.RetainCount,deadcode -verify -fblocks -analyzer-opt-analyze-nested-blocks -fobjc-arc -analyzer-output=plist-multi-file -analyzer-config deadcode.DeadStores:ShowFixIts=true -o %t.plist %s // RUN: %normalize_plist <%t.plist | diff -ub %S/Inputs/expected-plists/objc-arc.m.plist - typedef signed char BOOL; diff --git a/clang/test/Analysis/plist-output.m b/clang/test/Analysis/plist-output.m index 32916333cbfa0..553982b32fd84 100644 --- a/clang/test/Analysis/plist-output.m +++ b/clang/test/Analysis/plist-output.m @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 -analyzer-config eagerly-assume=false %s -analyzer-checker=osx.cocoa.RetainCount,deadcode.DeadStores,core -analyzer-output=plist -o %t.plist +// RUN: %clang_analyze_cc1 -analyzer-config eagerly-assume=false %s -analyzer-checker=osx.cocoa.RetainCount,deadcode.DeadStores,core -analyzer-output=plist -analyzer-config deadcode.DeadStores:ShowFixIts=true -o %t.plist // RUN: %normalize_plist <%t.plist | diff -ub %S/Inputs/expected-plists/plist-output.m.plist - void test_null_init(void) { diff --git a/clang/test/Analysis/virtualcall-fixits.cpp b/clang/test/Analysis/virtualcall-fixits.cpp new file mode 100644 index 0000000000000..ea149d52fcd21 --- /dev/null +++ b/clang/test/Analysis/virtualcall-fixits.cpp @@ -0,0 +1,45 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,optin.cplusplus.VirtualCall \ +// RUN: -analyzer-config optin.cplusplus.VirtualCall:ShowFixIts=true \ +// RUN: %s 2>&1 | FileCheck -check-prefix=TEXT %s +// RUN: %clang_analyze_cc1 -analyzer-checker=core,optin.cplusplus.VirtualCall \ +// RUN: -analyzer-config optin.cplusplus.VirtualCall:ShowFixIts=true \ +// RUN: -analyzer-config fixits-as-remarks=true \ +// RUN: -analyzer-output=plist -o %t.plist -verify %s +// RUN: cat %t.plist | FileCheck -check-prefix=PLIST %s + +struct S { + virtual void foo(); + S() { + foo(); + // expected-warning@-1{{Call to virtual method 'S::foo' during construction bypasses virtual dispatch}} + // expected-remark@-2{{5-5: 'S::'}} + } + ~S(); +}; + +// TEXT: warning: Call to virtual method 'S::foo' during construction +// TEXT-SAME: bypasses virtual dispatch +// TEXT-NEXT: foo(); +// TEXT-NEXT: ^~~~~ +// TEXT-NEXT: S:: +// TEXT-NEXT: 1 warning generated. + +// PLIST: fixits +// PLIST-NEXT: +// PLIST-NEXT: +// PLIST-NEXT: remove_range +// PLIST-NEXT: +// PLIST-NEXT: +// PLIST-NEXT: line13 +// PLIST-NEXT: col5 +// PLIST-NEXT: file0 +// PLIST-NEXT: +// PLIST-NEXT: +// PLIST-NEXT: line13 +// PLIST-NEXT: col4 +// PLIST-NEXT: file0 +// PLIST-NEXT: +// PLIST-NEXT: +// PLIST-NEXT: insert_stringS:: +// PLIST-NEXT: +// PLIST-NEXT: From 67419f1bc7be8a1b845da87f8f94f3be75acdfdc Mon Sep 17 00:00:00 2001 From: Gabor Borsik Date: Sun, 8 Sep 2019 19:23:43 +0000 Subject: [PATCH 146/181] Move prop-sink branch to monorepo. llvm-svn: 371342 (cherry picked from commit 080ecafdd8b3e990e5ad19202d089c91c9c9b164) --- .../Checkers/GenericTaintChecker.cpp | 94 +++++++++++++------ clang/test/Analysis/taint-generic.c | 42 +++++++++ 2 files changed, 107 insertions(+), 29 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp index 42b35acd31e5a..9c81e9f6a5a76 100644 --- a/clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp @@ -115,27 +115,44 @@ class GenericTaintChecker static Optional getPointedToSVal(CheckerContext &C, const Expr *Arg); /// Check for CWE-134: Uncontrolled Format String. - static const char MsgUncontrolledFormatString[]; + static constexpr llvm::StringLiteral MsgUncontrolledFormatString = + "Untrusted data is used as a format string " + "(CWE-134: Uncontrolled Format String)"; bool checkUncontrolledFormatString(const CallExpr *CE, CheckerContext &C) const; /// Check for: /// CERT/STR02-C. "Sanitize data passed to complex subsystems" /// CWE-78, "Failure to Sanitize Data into an OS Command" - static const char MsgSanitizeSystemArgs[]; + static constexpr llvm::StringLiteral MsgSanitizeSystemArgs = + "Untrusted data is passed to a system call " + "(CERT/STR02-C. Sanitize data passed to complex subsystems)"; bool checkSystemCall(const CallExpr *CE, StringRef Name, CheckerContext &C) const; /// Check if tainted data is used as a buffer size ins strn.. functions, /// and allocators. - static const char MsgTaintedBufferSize[]; + static constexpr llvm::StringLiteral MsgTaintedBufferSize = + "Untrusted data is used to specify the buffer size " + "(CERT/STR31-C. Guarantee that storage for strings has sufficient space " + "for character data and the null terminator)"; bool checkTaintedBufferSize(const CallExpr *CE, const FunctionDecl *FDecl, CheckerContext &C) const; + /// Check if tainted data is used as a custom sink's parameter. + static constexpr llvm::StringLiteral MsgCustomSink = + "Untrusted data is passed to a user-defined sink"; + bool checkCustomSinks(const CallExpr *CE, StringRef Name, + CheckerContext &C) const; + /// Generate a report if the expression is tainted or points to tainted data. - bool generateReportIfTainted(const Expr *E, const char Msg[], + bool generateReportIfTainted(const Expr *E, StringRef Msg, CheckerContext &C) const; + struct TaintPropagationRule; + using NameRuleMap = llvm::StringMap; + using NameArgMap = llvm::StringMap; + /// A struct used to specify taint propagation rules for a function. /// /// If any of the possible taint source arguments is tainted, all of the @@ -175,7 +192,8 @@ class GenericTaintChecker /// Get the propagation rule for a given function. static TaintPropagationRule - getTaintPropagationRule(const FunctionDecl *FDecl, StringRef Name, + getTaintPropagationRule(const NameRuleMap &CustomPropagations, + const FunctionDecl *FDecl, StringRef Name, CheckerContext &C); void addSrcArg(unsigned A) { SrcArgs.push_back(A); } @@ -211,9 +229,6 @@ class GenericTaintChecker CheckerContext &C); }; - using NameRuleMap = llvm::StringMap; - using NameArgMap = llvm::StringMap; - /// Defines a map between the propagation function's name and /// TaintPropagationRule. NameRuleMap CustomPropagations; @@ -228,18 +243,11 @@ class GenericTaintChecker const unsigned GenericTaintChecker::ReturnValueIndex; const unsigned GenericTaintChecker::InvalidArgIndex; -const char GenericTaintChecker::MsgUncontrolledFormatString[] = - "Untrusted data is used as a format string " - "(CWE-134: Uncontrolled Format String)"; - -const char GenericTaintChecker::MsgSanitizeSystemArgs[] = - "Untrusted data is passed to a system call " - "(CERT/STR02-C. Sanitize data passed to complex subsystems)"; - -const char GenericTaintChecker::MsgTaintedBufferSize[] = - "Untrusted data is used to specify the buffer size " - "(CERT/STR31-C. Guarantee that storage for strings has sufficient space " - "for character data and the null terminator)"; +// FIXME: these lines can be removed in C++17 +constexpr llvm::StringLiteral GenericTaintChecker::MsgUncontrolledFormatString; +constexpr llvm::StringLiteral GenericTaintChecker::MsgSanitizeSystemArgs; +constexpr llvm::StringLiteral GenericTaintChecker::MsgTaintedBufferSize; +constexpr llvm::StringLiteral GenericTaintChecker::MsgCustomSink; } // end of anonymous namespace using TaintConfig = GenericTaintChecker::TaintConfiguration; @@ -330,7 +338,8 @@ void GenericTaintChecker::parseConfiguration(CheckerManager &Mgr, GenericTaintChecker::TaintPropagationRule GenericTaintChecker::TaintPropagationRule::getTaintPropagationRule( - const FunctionDecl *FDecl, StringRef Name, CheckerContext &C) { + const NameRuleMap &CustomPropagations, const FunctionDecl *FDecl, + StringRef Name, CheckerContext &C) { // TODO: Currently, we might lose precision here: we always mark a return // value as tainted even if it's just a pointer, pointing to tainted data. @@ -424,6 +433,10 @@ GenericTaintChecker::TaintPropagationRule::getTaintPropagationRule( // or smart memory copy: // - memccpy - copying until hitting a special character. + auto It = CustomPropagations.find(Name); + if (It != CustomPropagations.end()) + return It->getValue(); + return TaintPropagationRule(); } @@ -463,8 +476,8 @@ void GenericTaintChecker::addSourcesPre(const CallExpr *CE, return; // First, try generating a propagation rule for this function. - TaintPropagationRule Rule = - TaintPropagationRule::getTaintPropagationRule(FDecl, Name, C); + TaintPropagationRule Rule = TaintPropagationRule::getTaintPropagationRule( + this->CustomPropagations, FDecl, Name, C); if (!Rule.isNull()) { State = Rule.process(CE, C); if (!State) @@ -536,6 +549,9 @@ bool GenericTaintChecker::checkPre(const CallExpr *CE, if (checkTaintedBufferSize(CE, FDecl, C)) return true; + if (checkCustomSinks(CE, Name, C)) + return true; + return false; } @@ -573,7 +589,8 @@ GenericTaintChecker::TaintPropagationRule::process(const CallExpr *CE, bool IsTainted = true; for (unsigned ArgNum : SrcArgs) { if (ArgNum >= CE->getNumArgs()) - return State; + continue; + if ((IsTainted = isTaintedOrPointsToTainted(CE->getArg(ArgNum), State, C))) break; } @@ -601,8 +618,10 @@ GenericTaintChecker::TaintPropagationRule::process(const CallExpr *CE, continue; } + if (ArgNum >= CE->getNumArgs()) + continue; + // Mark the given argument. - assert(ArgNum < CE->getNumArgs()); State = State->add(ArgNum); } @@ -699,8 +718,7 @@ static bool getPrintfFormatArgumentNum(const CallExpr *CE, return false; } -bool GenericTaintChecker::generateReportIfTainted(const Expr *E, - const char Msg[], +bool GenericTaintChecker::generateReportIfTainted(const Expr *E, StringRef Msg, CheckerContext &C) const { assert(E); @@ -756,9 +774,9 @@ bool GenericTaintChecker::checkSystemCall(const CallExpr *CE, StringRef Name, .Case("execvP", 0) .Case("execve", 0) .Case("dlopen", 0) - .Default(UINT_MAX); + .Default(InvalidArgIndex); - if (ArgNum == UINT_MAX || CE->getNumArgs() < (ArgNum + 1)) + if (ArgNum == InvalidArgIndex || CE->getNumArgs() < (ArgNum + 1)) return false; return generateReportIfTainted(CE->getArg(ArgNum), MsgSanitizeSystemArgs, C); @@ -803,6 +821,24 @@ bool GenericTaintChecker::checkTaintedBufferSize(const CallExpr *CE, generateReportIfTainted(CE->getArg(ArgNum), MsgTaintedBufferSize, C); } +bool GenericTaintChecker::checkCustomSinks(const CallExpr *CE, StringRef Name, + CheckerContext &C) const { + auto It = CustomSinks.find(Name); + if (It == CustomSinks.end()) + return false; + + const GenericTaintChecker::ArgVector &Args = It->getValue(); + for (unsigned ArgNum : Args) { + if (ArgNum >= CE->getNumArgs()) + continue; + + if (generateReportIfTainted(CE->getArg(ArgNum), MsgCustomSink, C)) + return true; + } + + return false; +} + void ento::registerGenericTaintChecker(CheckerManager &Mgr) { auto *Checker = Mgr.registerChecker(); std::string Option{"Config"}; diff --git a/clang/test/Analysis/taint-generic.c b/clang/test/Analysis/taint-generic.c index a6aae22a61f4b..4d933e7b75cc3 100644 --- a/clang/test/Analysis/taint-generic.c +++ b/clang/test/Analysis/taint-generic.c @@ -338,3 +338,45 @@ void constraintManagerShouldTreatAsOpaque(int rhs) { if (i < rhs) *(volatile int *) 0; // no-warning } + + +// Test configuration +int mySource1(); +void mySource2(int*); +void myScanf(const char*, ...); +int myPropagator(int, int*); +int mySnprintf(char*, size_t, const char*, ...); +void mySink(int, int, int); + +void testConfigurationSources1() { + int x = mySource1(); + Buffer[x] = 1; // expected-warning {{Out of bound memory access }} +} + +void testConfigurationSources2() { + int x; + mySource2(&x); + Buffer[x] = 1; // expected-warning {{Out of bound memory access }} +} + +void testConfigurationSources3() { + int x, y; + myScanf("%d %d", &x, &y); + Buffer[y] = 1; // expected-warning {{Out of bound memory access }} +} + +void testConfigurationPropagation() { + int x = mySource1(); + int y; + myPropagator(x, &y); + Buffer[y] = 1; // expected-warning {{Out of bound memory access }} +} + +void testConfigurationSinks() { + int x = mySource1(); + mySink(x, 1, 2); + // expected-warning@-1 {{Untrusted data is passed to a user-defined sink}} + mySink(1, x, 2); // no-warning + mySink(1, 2, x); + // expected-warning@-1 {{Untrusted data is passed to a user-defined sink}} +} From 9dbd22a938d5c4b8367f2a66add9df0eccb6660e Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Mon, 9 Sep 2019 20:34:40 +0000 Subject: [PATCH 147/181] [analyzer] NFC: Introduce sub-classes for path-sensitive and basic reports. Checkers are now required to specify whether they're creating a path-sensitive report or a path-insensitive report by constructing an object of the respective type. This makes BugReporter more independent from the rest of the Static Analyzer because all Analyzer-specific code is now in sub-classes. Differential Revision: https://reviews.llvm.org/D66572 llvm-svn: 371450 (cherry picked from commit 2f169e7cdd9973d2aa4cba6b0a09126a5e7268ec) --- .../Core/BugReporter/BugReporter.h | 404 +++++++++++------- .../Core/BugReporter/BugReporterVisitors.h | 77 ++-- .../Core/BugReporter/PathDiagnostic.h | 3 +- .../SampleAnalyzer/MainCallChecker.cpp | 4 +- .../Checkers/ArrayBoundChecker.cpp | 3 +- .../Checkers/ArrayBoundCheckerV2.cpp | 2 +- .../Checkers/BasicObjCFoundationChecks.cpp | 11 +- .../BlockInCriticalSectionChecker.cpp | 3 +- .../Checkers/BoolAssignmentChecker.cpp | 4 +- .../Checkers/CStringChecker.cpp | 12 +- .../Checkers/CallAndMessageChecker.cpp | 19 +- .../Checkers/CastSizeChecker.cpp | 3 +- .../Checkers/CheckObjCDealloc.cpp | 13 +- .../StaticAnalyzer/Checkers/ChrootChecker.cpp | 2 +- .../StaticAnalyzer/Checkers/CloneChecker.cpp | 6 +- .../Checkers/ConversionChecker.cpp | 2 +- .../StaticAnalyzer/Checkers/DebugCheckers.cpp | 3 +- .../DeleteWithNonVirtualDtorChecker.cpp | 7 +- .../Checkers/DereferenceChecker.cpp | 6 +- .../Checkers/DivZeroChecker.cpp | 2 +- .../Checkers/DynamicTypeChecker.cpp | 8 +- .../Checkers/DynamicTypePropagation.cpp | 9 +- .../Checkers/EnumCastOutOfRangeChecker.cpp | 2 +- .../Checkers/ExprInspectionChecker.cpp | 2 +- .../Checkers/FixedAddressChecker.cpp | 3 +- .../Checkers/GenericTaintChecker.cpp | 2 +- .../Checkers/InnerPointerChecker.cpp | 8 +- .../Checkers/IteratorChecker.cpp | 12 +- .../Checkers/LocalizationChecker.cpp | 9 +- .../StaticAnalyzer/Checkers/MIGChecker.cpp | 2 +- .../Checkers/MPI-Checker/MPIBugReporter.cpp | 18 +- .../Checkers/MPI-Checker/MPIBugReporter.h | 2 +- .../Checkers/MacOSKeychainAPIChecker.cpp | 21 +- .../Checkers/MacOSXAPIChecker.cpp | 3 +- .../StaticAnalyzer/Checkers/MallocChecker.cpp | 44 +- .../Checkers/MmapWriteExecChecker.cpp | 2 +- .../StaticAnalyzer/Checkers/MoveChecker.cpp | 14 +- .../Checkers/NSAutoreleasePoolChecker.cpp | 8 +- .../Checkers/NSErrorChecker.cpp | 3 +- .../Checkers/NonNullParamChecker.cpp | 5 +- .../Checkers/NullabilityChecker.cpp | 7 +- .../Checkers/ObjCAtSyncChecker.cpp | 8 +- .../Checkers/ObjCContainersChecker.cpp | 3 +- .../Checkers/ObjCSelfInitChecker.cpp | 2 +- .../Checkers/ObjCSuperDeallocChecker.cpp | 9 +- .../Checkers/PaddingChecker.cpp | 3 +- .../Checkers/PointerArithChecker.cpp | 8 +- .../Checkers/PointerSubChecker.cpp | 3 +- .../Checkers/PthreadLockChecker.cpp | 14 +- .../RetainCountDiagnostics.cpp | 30 +- .../RetainCountDiagnostics.h | 11 +- .../Checkers/ReturnPointerRangeChecker.cpp | 3 +- .../Checkers/ReturnUndefChecker.cpp | 3 +- .../Checkers/SimpleStreamChecker.cpp | 9 +- .../Checkers/StackAddrEscapeChecker.cpp | 14 +- .../StaticAnalyzer/Checkers/StreamChecker.cpp | 8 +- clang/lib/StaticAnalyzer/Checkers/Taint.cpp | 2 +- clang/lib/StaticAnalyzer/Checkers/Taint.h | 2 +- .../Checkers/TaintTesterChecker.cpp | 2 +- .../Checkers/TestAfterDivZeroChecker.cpp | 10 +- .../Checkers/UndefBranchChecker.cpp | 3 +- .../Checkers/UndefCapturedBlockVarChecker.cpp | 2 +- .../Checkers/UndefResultChecker.cpp | 2 +- .../UndefinedArraySubscriptChecker.cpp | 2 +- .../Checkers/UndefinedAssignmentChecker.cpp | 2 +- .../UninitializedObjectChecker.cpp | 4 +- .../Checkers/UnixAPIChecker.cpp | 8 +- .../Checkers/VLASizeChecker.cpp | 2 +- .../StaticAnalyzer/Checkers/ValistChecker.cpp | 14 +- .../StaticAnalyzer/Checkers/VforkChecker.cpp | 2 +- .../Checkers/VirtualCallChecker.cpp | 2 +- clang/lib/StaticAnalyzer/Core/BugReporter.cpp | 265 +++++++----- .../Core/BugReporterVisitors.cpp | 135 +++--- clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 17 +- .../StaticAnalyzer/Core/PathDiagnostic.cpp | 5 +- 75 files changed, 786 insertions(+), 593 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h index 9be3b166c20d4..0e114f32afdf1 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h @@ -74,29 +74,190 @@ using DiagnosticForConsumerMapTy = /// individual bug reports. class BugReport : public llvm::ilist_node { public: - using ranges_iterator = const SourceRange *; - using VisitorList = SmallVector, 8>; - using visitor_iterator = VisitorList::iterator; - using visitor_range = llvm::iterator_range; - using NoteList = SmallVector, 4>; + enum class Kind { Basic, PathSensitive }; protected: friend class BugReportEquivClass; friend class BugReporter; + Kind K; const BugType& BT; - const Decl *DeclWithIssue = nullptr; std::string ShortDescription; std::string Description; + + SmallVector Ranges; + SmallVector, 4> Notes; + SmallVector Fixits; + + BugReport(Kind kind, const BugType &bt, StringRef desc) + : K(kind), BT(bt), Description(desc) {} + + BugReport(Kind K, const BugType &BT, StringRef ShortDescription, + StringRef Description) + : K(K), BT(BT), ShortDescription(ShortDescription), + Description(Description) {} + +public: + virtual ~BugReport() = default; + + Kind getKind() const { return K; } + + const BugType& getBugType() const { return BT; } + + /// A verbose warning message that is appropriate for displaying next to + /// the source code that introduces the problem. The description should be + /// at least a full sentence starting with a capital letter. The period at + /// the end of the warning is traditionally omitted. If the description + /// consists of multiple sentences, periods between the sentences are + /// encouraged, but the period at the end of the description is still omitted. + StringRef getDescription() const { return Description; } + + /// A short general warning message that is appropriate for displaying in + /// the list of all reported bugs. It should describe what kind of bug is found + /// but does not need to try to go into details of that specific bug. + /// Grammatical conventions of getDescription() apply here as well. + StringRef getShortDescription(bool UseFallback = true) const { + if (ShortDescription.empty() && UseFallback) + return Description; + return ShortDescription; + } + + /// The primary location of the bug report that points at the undesirable + /// behavior in the code. UIs should attach the warning description to this + /// location. The warning description should describe the bad behavior + /// at this location. + virtual PathDiagnosticLocation getLocation() const = 0; + + /// The smallest declaration that contains the bug location. + /// This is purely cosmetic; the declaration can be displayed to the user + /// but it does not affect whether the report is emitted. + virtual const Decl *getDeclWithIssue() const = 0; + + /// Get the location on which the report should be uniqued. Two warnings are + /// considered to be equivalent whenever they have the same bug types, + /// descriptions, and uniqueing locations. Out of a class of equivalent + /// warnings only one gets displayed to the user. For most warnings the + /// uniqueing location coincides with their location, but sometimes + /// it makes sense to use different locations. For example, a leak + /// checker can place the warning at the location where the last reference + /// to the leaking resource is dropped but at the same time unique the warning + /// by where that resource is acquired (allocated). + virtual PathDiagnosticLocation getUniqueingLocation() const = 0; + + /// Get the declaration that corresponds to (usually contains) the uniqueing + /// location. This is not actively used for uniqueing, i.e. otherwise + /// identical reports that have different uniqueing decls will be considered + /// equivalent. + virtual const Decl *getUniqueingDecl() const = 0; + + /// Add new item to the list of additional notes that need to be attached to + /// this report. If the report is path-sensitive, these notes will not be + /// displayed as part of the execution path explanation, but will be displayed + /// separately. Use bug visitors if you need to add an extra path note. + void addNote(StringRef Msg, const PathDiagnosticLocation &Pos, + ArrayRef Ranges = {}) { + auto P = std::make_shared(Pos, Msg); + + for (const auto &R : Ranges) + P->addRange(R); + + Notes.push_back(std::move(P)); + } + + ArrayRef> getNotes() { + return Notes; + } + + /// Add a range to a bug report. + /// + /// Ranges are used to highlight regions of interest in the source code. + /// They should be at the same source code line as the BugReport location. + /// By default, the source range of the statement corresponding to the error + /// node will be used; add a single invalid range to specify absence of + /// ranges. + void addRange(SourceRange R) { + assert((R.isValid() || Ranges.empty()) && "Invalid range can only be used " + "to specify that the report does not have a range."); + Ranges.push_back(R); + } + + /// Get the SourceRanges associated with the report. + virtual ArrayRef getRanges() const { + return Ranges; + } + + /// Add a fix-it hint to the bug report. + /// + /// Fix-it hints are the suggested edits to the code that would resolve + /// the problem explained by the bug report. Fix-it hints should be + /// as conservative as possible because it is not uncommon for the user + /// to blindly apply all fixits to their project. Note that it is very hard + /// to produce a good fix-it hint for most path-sensitive warnings. + void addFixItHint(const FixItHint &F) { + Fixits.push_back(F); + } + + llvm::ArrayRef getFixits() const { return Fixits; } + + /// Reports are uniqued to ensure that we do not emit multiple diagnostics + /// for each bug. + virtual void Profile(llvm::FoldingSetNodeID& hash) const = 0; +}; + +class BasicBugReport : public BugReport { PathDiagnosticLocation Location; - PathDiagnosticLocation UniqueingLocation; - const Decl *UniqueingDecl; + const Decl *DeclWithIssue = nullptr; + +public: + BasicBugReport(const BugType &bt, StringRef desc, PathDiagnosticLocation l) + : BugReport(Kind::Basic, bt, desc), Location(l) {} + + static bool classof(const BugReport *R) { + return R->getKind() == Kind::Basic; + } + + PathDiagnosticLocation getLocation() const override { + assert(Location.isValid()); + return Location; + } + + const Decl *getDeclWithIssue() const override { + return DeclWithIssue; + } + + PathDiagnosticLocation getUniqueingLocation() const override { + return getLocation(); + } + + const Decl *getUniqueingDecl() const override { + return getDeclWithIssue(); + } + + /// Specifically set the Decl where an issue occurred. This isn't necessary + /// for BugReports that cover a path as it will be automatically inferred. + void setDeclWithIssue(const Decl *declWithIssue) { + DeclWithIssue = declWithIssue; + } + + void Profile(llvm::FoldingSetNodeID& hash) const override; +}; + +class PathSensitiveBugReport : public BugReport { +public: + using VisitorList = SmallVector, 8>; + using visitor_iterator = VisitorList::iterator; + using visitor_range = llvm::iterator_range; +protected: + /// The ExplodedGraph node against which the report was thrown. It corresponds + /// to the end of the execution path that demonstrates the bug. const ExplodedNode *ErrorNode = nullptr; - SmallVector Ranges; + + /// The range that corresponds to ErrorNode's program point. It is usually + /// highlighted in the report. const SourceRange ErrorNodeRange; - NoteList Notes; - SmallVector Fixits; + + /// Profile to identify equivalent bug reports for error report coalescing. /// A (stack of) a set of symbols that are registered with this /// report as being "interesting", and thus used to help decide which @@ -145,65 +306,50 @@ class BugReport : public llvm::ilist_node { /// Conditions we're already tracking. llvm::SmallSet TrackedConditions; -public: - BugReport(const BugType &bt, StringRef desc, const ExplodedNode *errornode) - : BT(bt), Description(desc), ErrorNode(errornode), - ErrorNodeRange(getStmt() ? getStmt()->getSourceRange() - : SourceRange()) {} + /// Reports with different uniqueing locations are considered to be different + /// for the purposes of deduplication. + PathDiagnosticLocation UniqueingLocation; + const Decl *UniqueingDecl; - BugReport(const BugType &bt, StringRef shortDesc, StringRef desc, - const ExplodedNode *errornode) - : BT(bt), ShortDescription(shortDesc), Description(desc), - ErrorNode(errornode), + const Stmt *getStmt() const; + +public: + PathSensitiveBugReport(const BugType &bt, StringRef desc, + const ExplodedNode *errorNode) + : BugReport(Kind::PathSensitive, bt, desc), ErrorNode(errorNode), ErrorNodeRange(getStmt() ? getStmt()->getSourceRange() : SourceRange()) {} - BugReport(const BugType &bt, StringRef desc, PathDiagnosticLocation l) - : BT(bt), Description(desc), Location(l), + PathSensitiveBugReport(const BugType &bt, StringRef shortDesc, StringRef desc, + const ExplodedNode *errorNode) + : BugReport(Kind::PathSensitive, bt, shortDesc, desc), + ErrorNode(errorNode), ErrorNodeRange(getStmt() ? getStmt()->getSourceRange() : SourceRange()) {} - /// Create a BugReport with a custom uniqueing location. + /// Create a PathSensitiveBugReport with a custom uniqueing location. /// /// The reports that have the same report location, description, bug type, and /// ranges are uniqued - only one of the equivalent reports will be presented /// to the user. This method allows to rest the location which should be used /// for uniquing reports. For example, memory leaks checker, could set this to /// the allocation site, rather then the location where the bug is reported. - BugReport(BugType &bt, StringRef desc, const ExplodedNode *errornode, - PathDiagnosticLocation LocationToUnique, const Decl *DeclToUnique) - : BT(bt), Description(desc), UniqueingLocation(LocationToUnique), - UniqueingDecl(DeclToUnique), ErrorNode(errornode), - ErrorNodeRange(getStmt() ? getStmt()->getSourceRange() - : SourceRange()) {} - - virtual ~BugReport() = default; - - const BugType& getBugType() const { return BT; } - //BugType& getBugType() { return BT; } + PathSensitiveBugReport(BugType &bt, StringRef desc, + const ExplodedNode *errorNode, + PathDiagnosticLocation LocationToUnique, + const Decl *DeclToUnique) + : BugReport(Kind::PathSensitive, bt, desc), ErrorNode(errorNode), + ErrorNodeRange(getStmt() ? getStmt()->getSourceRange() : SourceRange()), + UniqueingLocation(LocationToUnique), UniqueingDecl(DeclToUnique) { + assert(errorNode); + } - /// True when the report has an execution path associated with it. - /// - /// A report is said to be path-sensitive if it was thrown against a - /// particular exploded node in the path-sensitive analysis graph. - /// Path-sensitive reports have their intermediate path diagnostics - /// auto-generated, perhaps with the help of checker-defined visitors, - /// and may contain extra notes. - /// Path-insensitive reports consist only of a single warning message - /// in a specific location, and perhaps extra notes. - /// Path-sensitive checkers are allowed to throw path-insensitive reports. - bool isPathSensitive() const { return ErrorNode != nullptr; } + static bool classof(const BugReport *R) { + return R->getKind() == Kind::PathSensitive; + } const ExplodedNode *getErrorNode() const { return ErrorNode; } - StringRef getDescription() const { return Description; } - - StringRef getShortDescription(bool UseFallback = true) const { - if (ShortDescription.empty() && UseFallback) - return Description; - return ShortDescription; - } - /// Indicates whether or not any path pruning should take place /// when generating a PathDiagnostic from this BugReport. bool shouldPrunePath() const { return !DoNotPrunePath; } @@ -211,6 +357,22 @@ class BugReport : public llvm::ilist_node { /// Disable all path pruning when generating a PathDiagnostic. void disablePathPruning() { DoNotPrunePath = true; } + /// Get the location on which the report should be uniqued. + PathDiagnosticLocation getUniqueingLocation() const override { + return UniqueingLocation; + } + + /// Get the declaration containing the uniqueing location. + const Decl *getUniqueingDecl() const override { + return UniqueingDecl; + } + + const Decl *getDeclWithIssue() const override; + + ArrayRef getRanges() const override; + + PathDiagnosticLocation getLocation() const override; + /// Marks a symbol as interesting. Different kinds of interestingness will /// be processed differently by visitors (e.g. if the tracking kind is /// condition, will append "will be used as a condition" to the message). @@ -265,87 +427,10 @@ class BugReport : public llvm::ilist_node { Invalidations.insert(std::make_pair(Tag, Data)); } - /// Return the canonical declaration, be it a method or class, where - /// this issue semantically occurred. - const Decl *getDeclWithIssue() const; - - /// Specifically set the Decl where an issue occurred. This isn't necessary - /// for BugReports that cover a path as it will be automatically inferred. - void setDeclWithIssue(const Decl *declWithIssue) { - DeclWithIssue = declWithIssue; - } - - /// Add new item to the list of additional notes that need to be attached to - /// this path-insensitive report. If you want to add extra notes to a - /// path-sensitive report, you need to use a BugReporterVisitor because it - /// allows you to specify where exactly in the auto-generated path diagnostic - /// the extra note should appear. - void addNote(StringRef Msg, const PathDiagnosticLocation &Pos, - ArrayRef Ranges = {}, - ArrayRef Fixits = {}) { - auto P = std::make_shared(Pos, Msg); - - for (const auto &R : Ranges) - P->addRange(R); - - for (const auto &F : Fixits) - P->addFixit(F); - - Notes.push_back(std::move(P)); - } - - virtual const NoteList &getNotes() { - return Notes; - } - - /// Return the "definitive" location of the reported bug. - /// - /// While a bug can span an entire path, usually there is a specific - /// location that can be used to identify where the key issue occurred. - /// This location is used by clients rendering diagnostics. - virtual PathDiagnosticLocation getLocation(const SourceManager &SM) const; - - /// Get the location on which the report should be uniqued. - PathDiagnosticLocation getUniqueingLocation() const { - return UniqueingLocation; - } - - /// Get the declaration containing the uniqueing location. - const Decl *getUniqueingDecl() const { - return UniqueingDecl; - } - - const Stmt *getStmt() const; - - /// Add a range to the bug report. - /// - /// Ranges are used to highlight regions of interest in the source code. - /// They should be at the same source code line as the BugReport location. - /// By default, the source range of the statement corresponding to the error - /// node will be used; add a single invalid range to specify absence of - /// ranges. - void addRange(SourceRange R) { - assert((R.isValid() || Ranges.empty()) && "Invalid range can only be used " - "to specify that the report does not have a range."); - Ranges.push_back(R); - } - - /// Get the SourceRanges associated with the report. - virtual llvm::iterator_range getRanges() const; - - /// Add a fix-it hint to the warning message of the bug report. - /// - /// Fix-it hints are the suggested edits to the code that would resolve - /// the problem explained by the bug report. Fix-it hints should be - /// as conservative as possible because it is not uncommon for the user - /// to blindly apply all fixits to their project. It usually is very hard - /// to produce a good fix-it hint for most path-sensitive warnings. - /// Fix-it hints can also be added to notes through the addNote() interface. - void addFixItHint(const FixItHint &F) { - Fixits.push_back(F); - } - - ArrayRef getFixits() const { return Fixits; } + /// Profile to identify equivalent bug reports for error report coalescing. + /// Reports are uniqued to ensure that we do not emit multiple diagnostics + /// for each bug. + void Profile(llvm::FoldingSetNodeID &hash) const override; /// Add custom or predefined bug report visitors to this report. /// @@ -370,11 +455,6 @@ class BugReport : public llvm::ilist_node { bool addTrackedCondition(const ExplodedNode *Cond) { return TrackedConditions.insert(Cond).second; } - - /// Profile to identify equivalent bug reports for error report coalescing. - /// Reports are uniqued to ensure that we do not emit multiple diagnostics - /// for each bug. - virtual void Profile(llvm::FoldingSetNodeID& hash) const; }; //===----------------------------------------------------------------------===// @@ -435,12 +515,6 @@ class BugReporter { /// Generate and flush the diagnostics for the given bug report. void FlushReport(BugReportEquivClass& EQ); - /// Generate the diagnostics for the given bug report. - std::unique_ptr - generateDiagnosticForConsumerMap(BugReport *exampleReport, - ArrayRef consumers, - ArrayRef bugReports); - /// The set of bug reports tracked by the BugReporter. llvm::FoldingSet EQClasses; @@ -469,18 +543,12 @@ class BugReporter { const AnalyzerOptions &getAnalyzerOptions() { return D.getAnalyzerOptions(); } - virtual std::unique_ptr - generatePathDiagnostics(ArrayRef consumers, - ArrayRef &bugReports) { - return {}; - } - /// Add the given report to the set of reports tracked by BugReporter. /// /// The reports are usually generated by the checkers. Further, they are /// folded based on the profile value, which is done to coalesce similar /// reports. - void emitReport(std::unique_ptr R); + virtual void emitReport(std::unique_ptr R); void EmitBasicReport(const Decl *DeclWithIssue, const CheckerBase *Checker, StringRef BugName, StringRef BugCategory, @@ -501,12 +569,34 @@ class BugReporter { /// category. BugType *getBugTypeForName(CheckName CheckName, StringRef name, StringRef category); + + virtual BugReport * + findReportInEquivalenceClass(BugReportEquivClass &eqClass, + SmallVectorImpl &bugReports) { + return &*eqClass.begin(); + } + +protected: + /// Generate the diagnostics for the given bug report. + virtual std::unique_ptr + generateDiagnosticForConsumerMap(BugReport *exampleReport, + ArrayRef consumers, + ArrayRef bugReports); }; /// GRBugReporter is used for generating path-sensitive reports. -class PathSensitiveBugReporter : public BugReporter { +class PathSensitiveBugReporter final : public BugReporter { ExprEngine& Eng; + BugReport *findReportInEquivalenceClass( + BugReportEquivClass &eqClass, + SmallVectorImpl &bugReports) override; + + /// Generate the diagnostics for the given bug report. + std::unique_ptr + generateDiagnosticForConsumerMap(BugReport *exampleReport, + ArrayRef consumers, + ArrayRef bugReports) override; public: PathSensitiveBugReporter(BugReporterData& d, ExprEngine& eng) : BugReporter(d), Eng(eng) {} @@ -524,9 +614,11 @@ class PathSensitiveBugReporter : public BugReporter { /// \return A mapping from consumers to the corresponding diagnostics. /// Iterates through the bug reports within a single equivalence class, /// stops at a first non-invalidated report. - std::unique_ptr - generatePathDiagnostics(ArrayRef consumers, - ArrayRef &bugReports) override; + std::unique_ptr generatePathDiagnostics( + ArrayRef consumers, + ArrayRef &bugReports); + + void emitReport(std::unique_ptr R) override; }; diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h index 71d687a243dfb..9117789b74faf 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h @@ -33,7 +33,7 @@ class Stmt; namespace ento { -class BugReport; +class PathSensitiveBugReport; class BugReporterContext; class ExplodedNode; class MemRegion; @@ -60,29 +60,29 @@ class BugReporterVisitor : public llvm::FoldingSetNode { /// BugReport while processing a node. virtual PathDiagnosticPieceRef VisitNode(const ExplodedNode *Succ, BugReporterContext &BRC, - BugReport &BR) = 0; + PathSensitiveBugReport &BR) = 0; /// Last function called on the visitor, no further calls to VisitNode /// would follow. virtual void finalizeVisitor(BugReporterContext &BRC, const ExplodedNode *EndPathNode, - BugReport &BR); + PathSensitiveBugReport &BR); /// Provide custom definition for the final diagnostic piece on the /// path - the piece, which is displayed before the path is expanded. /// /// NOTE that this function can be implemented on at most one used visitor, /// and otherwise it crahes at runtime. - virtual PathDiagnosticPieceRef - getEndPath(BugReporterContext &BRC, const ExplodedNode *N, - BugReport &BR); + virtual PathDiagnosticPieceRef getEndPath(BugReporterContext &BRC, + const ExplodedNode *N, + PathSensitiveBugReport &BR); virtual void Profile(llvm::FoldingSetNodeID &ID) const = 0; /// Generates the default final diagnostic piece. - static PathDiagnosticPieceRef getDefaultEndPath(const BugReporterContext &BRC, - const ExplodedNode *N, - const BugReport &BR); + static PathDiagnosticPieceRef + getDefaultEndPath(const BugReporterContext &BRC, const ExplodedNode *N, + const PathSensitiveBugReport &BR); }; namespace bugreporter { @@ -111,7 +111,8 @@ enum class TrackingKind { /// \return Whether or not the function was able to add visitors for this /// statement. Note that returning \c true does not actually imply /// that any visitors were added. -bool trackExpressionValue(const ExplodedNode *N, const Expr *E, BugReport &R, +bool trackExpressionValue(const ExplodedNode *N, const Expr *E, + PathSensitiveBugReport &R, TrackingKind TKind = TrackingKind::Thorough, bool EnableNullFPSuppression = true); @@ -159,7 +160,7 @@ class FindLastStoreBRVisitor final : public BugReporterVisitor { PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, BugReporterContext &BRC, - BugReport &BR) override; + PathSensitiveBugReport &BR) override; }; class TrackConstraintBRVisitor final : public BugReporterVisitor { @@ -185,7 +186,7 @@ class TrackConstraintBRVisitor final : public BugReporterVisitor { PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, BugReporterContext &BRC, - BugReport &BR) override; + PathSensitiveBugReport &BR) override; private: /// Checks if the constraint is valid in the current state. @@ -203,7 +204,7 @@ class NilReceiverBRVisitor final : public BugReporterVisitor { PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, BugReporterContext &BRC, - BugReport &BR) override; + PathSensitiveBugReport &BR) override; /// If the statement is a message send expression with nil receiver, returns /// the receiver expression. Returns NULL otherwise. @@ -230,40 +231,42 @@ class ConditionBRVisitor final : public BugReporterVisitor { PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, BugReporterContext &BRC, - BugReport &BR) override; + PathSensitiveBugReport &BR) override; PathDiagnosticPieceRef VisitNodeImpl(const ExplodedNode *N, - BugReporterContext &BRC, BugReport &BR); + BugReporterContext &BRC, + PathSensitiveBugReport &BR); - PathDiagnosticPieceRef VisitTerminator(const Stmt *Term, - const ExplodedNode *N, - const CFGBlock *SrcBlk, - const CFGBlock *DstBlk, BugReport &R, - BugReporterContext &BRC); + PathDiagnosticPieceRef + VisitTerminator(const Stmt *Term, const ExplodedNode *N, + const CFGBlock *SrcBlk, const CFGBlock *DstBlk, + PathSensitiveBugReport &R, BugReporterContext &BRC); PathDiagnosticPieceRef VisitTrueTest(const Expr *Cond, - BugReporterContext &BRC, BugReport &R, + BugReporterContext &BRC, + PathSensitiveBugReport &R, const ExplodedNode *N, bool TookTrue); PathDiagnosticPieceRef VisitTrueTest(const Expr *Cond, const DeclRefExpr *DR, - BugReporterContext &BRC, BugReport &R, + BugReporterContext &BRC, + PathSensitiveBugReport &R, const ExplodedNode *N, bool TookTrue, bool IsAssuming); - PathDiagnosticPieceRef VisitTrueTest(const Expr *Cond, - const BinaryOperator *BExpr, - BugReporterContext &BRC, BugReport &R, - const ExplodedNode *N, bool TookTrue, - bool IsAssuming); + PathDiagnosticPieceRef + VisitTrueTest(const Expr *Cond, const BinaryOperator *BExpr, + BugReporterContext &BRC, PathSensitiveBugReport &R, + const ExplodedNode *N, bool TookTrue, bool IsAssuming); PathDiagnosticPieceRef VisitTrueTest(const Expr *Cond, const MemberExpr *ME, - BugReporterContext &BRC, BugReport &R, + BugReporterContext &BRC, + PathSensitiveBugReport &R, const ExplodedNode *N, bool TookTrue, bool IsAssuming); PathDiagnosticPieceRef VisitConditionVariable(StringRef LhsString, const Expr *CondVarExpr, - BugReporterContext &BRC, BugReport &R, + BugReporterContext &BRC, PathSensitiveBugReport &R, const ExplodedNode *N, bool TookTrue); /// Tries to print the value of the given expression. @@ -282,7 +285,7 @@ class ConditionBRVisitor final : public BugReporterVisitor { const Expr *ParentEx, raw_ostream &Out, BugReporterContext &BRC, - BugReport &R, + PathSensitiveBugReport &R, const ExplodedNode *N, Optional &prunable, bool IsSameFieldName); @@ -306,12 +309,12 @@ class LikelyFalsePositiveSuppressionBRVisitor final } PathDiagnosticPieceRef VisitNode(const ExplodedNode *, BugReporterContext &, - BugReport &) override { + PathSensitiveBugReport &) override { return nullptr; } void finalizeVisitor(BugReporterContext &BRC, const ExplodedNode *N, - BugReport &BR) override; + PathSensitiveBugReport &BR) override; }; /// When a region containing undefined value or '0' value is passed @@ -334,7 +337,7 @@ class UndefOrNullArgVisitor final : public BugReporterVisitor { PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, BugReporterContext &BRC, - BugReport &BR) override; + PathSensitiveBugReport &BR) override; }; class SuppressInlineDefensiveChecksVisitor final : public BugReporterVisitor { @@ -363,7 +366,7 @@ class SuppressInlineDefensiveChecksVisitor final : public BugReporterVisitor { PathDiagnosticPieceRef VisitNode(const ExplodedNode *Succ, BugReporterContext &BRC, - BugReport &BR) override; + PathSensitiveBugReport &BR) override; }; /// The bug visitor will walk all the nodes in a path and collect all the @@ -381,10 +384,10 @@ class FalsePositiveRefutationBRVisitor final : public BugReporterVisitor { PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, BugReporterContext &BRC, - BugReport &BR) override; + PathSensitiveBugReport &BR) override; void finalizeVisitor(BugReporterContext &BRC, const ExplodedNode *EndPathNode, - BugReport &BR) override; + PathSensitiveBugReport &BR) override; }; @@ -395,7 +398,7 @@ class TagVisitor : public BugReporterVisitor { PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, BugReporterContext &BRC, - BugReport &R) override; + PathSensitiveBugReport &R) override; }; } // namespace ento diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h index 6b5a2eb5a7e00..289f6de97d215 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h @@ -282,8 +282,7 @@ class PathDiagnosticLocation { /// Create a location corresponding to the next valid ExplodedNode as end /// of path location. - static PathDiagnosticLocation createEndOfPath(const ExplodedNode* N, - const SourceManager &SM); + static PathDiagnosticLocation createEndOfPath(const ExplodedNode* N); /// Convert the given location into a single kind location. static PathDiagnosticLocation createSingleLocation( diff --git a/clang/lib/Analysis/plugins/SampleAnalyzer/MainCallChecker.cpp b/clang/lib/Analysis/plugins/SampleAnalyzer/MainCallChecker.cpp index 8bd4085108e9a..4c65d05e20e93 100644 --- a/clang/lib/Analysis/plugins/SampleAnalyzer/MainCallChecker.cpp +++ b/clang/lib/Analysis/plugins/SampleAnalyzer/MainCallChecker.cpp @@ -36,8 +36,8 @@ void MainCallChecker::checkPreStmt(const CallExpr *CE, if (!BT) BT.reset(new BugType(this, "call to main", "example analyzer plugin")); - std::unique_ptr report = - llvm::make_unique(*BT, BT->getName(), N); + auto report = + llvm::make_unique(*BT, BT->getName(), N); report->addRange(Callee->getSourceRange()); C.emitReport(std::move(report)); } diff --git a/clang/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp index 58017acb4a246..40baa279dee64 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp @@ -75,7 +75,8 @@ void ArrayBoundChecker::checkLocation(SVal l, bool isLoad, const Stmt* LoadS, // reference is outside the range. // Generate a report for this bug. - auto report = llvm::make_unique(*BT, BT->getDescription(), N); + auto report = + llvm::make_unique(*BT, BT->getDescription(), N); report->addRange(LoadS->getSourceRange()); C.emitReport(std::move(report)); diff --git a/clang/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp b/clang/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp index 3bf8a1836b19b..56b68689c6987 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp @@ -256,7 +256,7 @@ void ArrayBoundCheckerV2::reportOOB( break; } - auto BR = llvm::make_unique(*BT, os.str(), errorNode); + auto BR = llvm::make_unique(*BT, os.str(), errorNode); BR->addVisitor(std::move(Visitor)); checkerContext.emitReport(std::move(BR)); } diff --git a/clang/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp b/clang/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp index e3fb4c3eb523f..24b2b021657f2 100644 --- a/clang/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp @@ -211,7 +211,7 @@ void NilArgChecker::generateBugReport(ExplodedNode *N, if (!BT) BT.reset(new APIMisuse(this, "nil argument")); - auto R = llvm::make_unique(*BT, Msg, N); + auto R = llvm::make_unique(*BT, Msg, N); R->addRange(Range); bugreporter::trackExpressionValue(N, E, *R); C.emitReport(std::move(R)); @@ -520,7 +520,7 @@ void CFNumberChecker::checkPreStmt(const CallExpr *CE, if (!BT) BT.reset(new APIMisuse(this, "Bad use of CFNumber APIs")); - auto report = llvm::make_unique(*BT, os.str(), N); + auto report = llvm::make_unique(*BT, os.str(), N); report->addRange(CE->getArg(2)->getSourceRange()); C.emitReport(std::move(report)); } @@ -575,7 +575,7 @@ void CFRetainReleaseChecker::checkPreCall(const CallEvent &Call, OS << "Null pointer argument in call to " << cast(Call.getDecl())->getName(); - auto report = llvm::make_unique(BT, OS.str(), N); + auto report = llvm::make_unique(BT, OS.str(), N); report->addRange(Call.getArgSourceRange(0)); bugreporter::trackExpressionValue(N, Call.getArgExpr(0), *report); C.emitReport(std::move(report)); @@ -635,7 +635,7 @@ void ClassReleaseChecker::checkPreObjCMessage(const ObjCMethodCall &msg, "of class '" << Class->getName() << "' and not the class directly"; - auto report = llvm::make_unique(*BT, os.str(), N); + auto report = llvm::make_unique(*BT, os.str(), N); report->addRange(msg.getSourceRange()); C.emitReport(std::move(report)); } @@ -788,7 +788,8 @@ void VariadicMethodTypeChecker::checkPreObjCMessage(const ObjCMethodCall &msg, ArgTy.print(os, C.getLangOpts()); os << "'"; - auto R = llvm::make_unique(*BT, os.str(), errorNode.getValue()); + auto R = llvm::make_unique(*BT, os.str(), + errorNode.getValue()); R->addRange(msg.getArgSourceRange(I)); C.emitReport(std::move(R)); } diff --git a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp index 009160fc98157..067dafe6db649 100644 --- a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp @@ -173,7 +173,8 @@ void BlockInCriticalSectionChecker::reportBlockInCritSection( llvm::raw_string_ostream os(msg); os << "Call to blocking function '" << Call.getCalleeIdentifier()->getName() << "' inside of critical section"; - auto R = llvm::make_unique(*BlockInCritSectionBugType, os.str(), ErrNode); + auto R = llvm::make_unique(*BlockInCritSectionBugType, + os.str(), ErrNode); R->addRange(Call.getSourceRange()); R->markInteresting(BlockDescSym); C.emitReport(std::move(R)); diff --git a/clang/lib/StaticAnalyzer/Checkers/BoolAssignmentChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/BoolAssignmentChecker.cpp index de8763c1b7b57..6ccf4d51ae9b1 100644 --- a/clang/lib/StaticAnalyzer/Checkers/BoolAssignmentChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/BoolAssignmentChecker.cpp @@ -34,7 +34,9 @@ void BoolAssignmentChecker::emitReport(ProgramStateRef state, if (ExplodedNode *N = C.generateNonFatalErrorNode(state)) { if (!BT) BT.reset(new BuiltinBug(this, "Assignment of a non-Boolean value")); - C.emitReport(llvm::make_unique(*BT, BT->getDescription(), N)); + + C.emitReport( + llvm::make_unique(*BT, BT->getDescription(), N)); } } diff --git a/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp index b3aa17b03fd5c..13bab06e253bb 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp @@ -570,7 +570,7 @@ void CStringChecker::emitOverlapBug(CheckerContext &C, ProgramStateRef state, categories::UnixAPI, "Improper arguments")); // Generate a report for this bug. - auto report = llvm::make_unique( + auto report = llvm::make_unique( *BT_Overlap, "Arguments must not be overlapping buffers", N); report->addRange(First->getSourceRange()); report->addRange(Second->getSourceRange()); @@ -587,7 +587,7 @@ void CStringChecker::emitNullArgBug(CheckerContext &C, ProgramStateRef State, "Null pointer argument in call to byte string function")); BuiltinBug *BT = static_cast(BT_Null.get()); - auto Report = llvm::make_unique(*BT, WarningMsg, N); + auto Report = llvm::make_unique(*BT, WarningMsg, N); Report->addRange(S->getSourceRange()); if (const auto *Ex = dyn_cast(S)) bugreporter::trackExpressionValue(N, Ex, *Report); @@ -611,7 +611,7 @@ void CStringChecker::emitOutOfBoundsBug(CheckerContext &C, // FIXME: It would be nice to eventually make this diagnostic more clear, // e.g., by referencing the original declaration or by saying *why* this // reference is outside the range. - auto Report = llvm::make_unique(*BT, WarningMsg, N); + auto Report = llvm::make_unique(*BT, WarningMsg, N); Report->addRange(S->getSourceRange()); C.emitReport(std::move(Report)); } @@ -626,7 +626,8 @@ void CStringChecker::emitNotCStringBug(CheckerContext &C, ProgramStateRef State, Filter.CheckNameCStringNotNullTerm, categories::UnixAPI, "Argument is not a null-terminated string.")); - auto Report = llvm::make_unique(*BT_NotCString, WarningMsg, N); + auto Report = + llvm::make_unique(*BT_NotCString, WarningMsg, N); Report->addRange(S->getSourceRange()); C.emitReport(std::move(Report)); @@ -648,7 +649,8 @@ void CStringChecker::emitAdditionOverflowBug(CheckerContext &C, "This expression will create a string whose length is too big to " "be represented as a size_t"; - auto Report = llvm::make_unique(*BT_NotCString, WarningMsg, N); + auto Report = + llvm::make_unique(*BT_NotCString, WarningMsg, N); C.emitReport(std::move(Report)); } } diff --git a/clang/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp index 5a7eba0760fe5..e96ddc0338e78 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp @@ -95,7 +95,7 @@ void CallAndMessageChecker::emitBadCall(BugType *BT, CheckerContext &C, if (!N) return; - auto R = llvm::make_unique(*BT, BT->getName(), N); + auto R = llvm::make_unique(*BT, BT->getName(), N); if (BadE) { R->addRange(BadE->getSourceRange()); if (BadE->isGLValue()) @@ -175,7 +175,7 @@ bool CallAndMessageChecker::uninitRefOrPointer( if (PSV.isUndef()) { if (ExplodedNode *N = C.generateErrorNode()) { LazyInit_BT(BD, BT); - auto R = llvm::make_unique(*BT, Os.str(), N); + auto R = llvm::make_unique(*BT, Os.str(), N); R->addRange(ArgRange); if (ArgEx) bugreporter::trackExpressionValue(N, ArgEx, *R); @@ -252,7 +252,7 @@ bool CallAndMessageChecker::PreVisitProcessArg(CheckerContext &C, SmallString<200> Buf; llvm::raw_svector_ostream Os(Buf); describeUninitializedArgumentInCall(Call, ArgumentNumber, Os); - auto R = llvm::make_unique(*BT, Os.str(), N); + auto R = llvm::make_unique(*BT, Os.str(), N); R->addRange(ArgRange); if (ArgEx) @@ -295,7 +295,7 @@ bool CallAndMessageChecker::PreVisitProcessArg(CheckerContext &C, } // Generate a report for this bug. - auto R = llvm::make_unique(*BT, os.str(), N); + auto R = llvm::make_unique(*BT, os.str(), N); R->addRange(ArgRange); if (ArgEx) @@ -358,7 +358,7 @@ void CallAndMessageChecker::checkPreStmt(const CXXDeleteExpr *DE, else Desc = "Argument to 'delete' is uninitialized"; BugType *BT = BT_cxx_delete_undef.get(); - auto R = llvm::make_unique(*BT, Desc, N); + auto R = llvm::make_unique(*BT, Desc, N); bugreporter::trackExpressionValue(N, DE, *R); C.emitReport(std::move(R)); return; @@ -420,8 +420,8 @@ void CallAndMessageChecker::checkPreCall(const CallEvent &Call, << (Params == 1 ? "" : "s") << " is called with fewer (" << Call.getNumArgs() << ")"; - C.emitReport( - llvm::make_unique(*BT_call_few_args, os.str(), N)); + C.emitReport(llvm::make_unique(*BT_call_few_args, + os.str(), N)); } } @@ -482,7 +482,7 @@ void CallAndMessageChecker::checkPreObjCMessage(const ObjCMethodCall &msg, } assert(BT && "Unknown message kind."); - auto R = llvm::make_unique(*BT, BT->getName(), N); + auto R = llvm::make_unique(*BT, BT->getName(), N); const ObjCMessageExpr *ME = msg.getOriginExpr(); R->addRange(ME->getReceiverRange()); @@ -525,7 +525,8 @@ void CallAndMessageChecker::emitNilReceiverBug(CheckerContext &C, os << "' that will be garbage"; } - auto report = llvm::make_unique(*BT_msg_ret, os.str(), N); + auto report = + llvm::make_unique(*BT_msg_ret, os.str(), N); report->addRange(ME->getReceiverRange()); // FIXME: This won't track "self" in messages to super. if (const Expr *receiver = ME->getInstanceReceiver()) { diff --git a/clang/lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp index 05ece961467fb..9d823574c9f55 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp @@ -132,7 +132,8 @@ void CastSizeChecker::checkPreStmt(const CastExpr *CE,CheckerContext &C) const { BT.reset(new BuiltinBug(this, "Cast region with wrong size.", "Cast a region whose size is not a multiple" " of the destination type size.")); - auto R = llvm::make_unique(*BT, BT->getDescription(), errorNode); + auto R = llvm::make_unique(*BT, BT->getDescription(), + errorNode); R->addRange(CE->getSourceRange()); C.emitReport(std::move(R)); } diff --git a/clang/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp b/clang/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp index a7ca814c8f967..5f64e5a0dda3f 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp @@ -576,9 +576,8 @@ void ObjCDeallocChecker::diagnoseMissingReleases(CheckerContext &C) const { OS << " by a synthesized property but not released" " before '[super dealloc]'"; - std::unique_ptr BR( - new BugReport(*MissingReleaseBugType, OS.str(), ErrNode)); - + auto BR = llvm::make_unique(*MissingReleaseBugType, + OS.str(), ErrNode); C.emitReport(std::move(BR)); } @@ -699,8 +698,8 @@ bool ObjCDeallocChecker::diagnoseExtraRelease(SymbolRef ReleasedValue, OS << " property but was released in 'dealloc'"; } - std::unique_ptr BR( - new BugReport(*ExtraReleaseBugType, OS.str(), ErrNode)); + auto BR = llvm::make_unique(*ExtraReleaseBugType, + OS.str(), ErrNode); BR->addRange(M.getOriginExpr()->getSourceRange()); C.emitReport(std::move(BR)); @@ -741,8 +740,8 @@ bool ObjCDeallocChecker::diagnoseMistakenDealloc(SymbolRef DeallocedValue, OS << "'" << *PropImpl->getPropertyIvarDecl() << "' should be released rather than deallocated"; - std::unique_ptr BR( - new BugReport(*MistakenDeallocBugType, OS.str(), ErrNode)); + auto BR = llvm::make_unique(*MistakenDeallocBugType, + OS.str(), ErrNode); BR->addRange(M.getOriginExpr()->getSourceRange()); C.emitReport(std::move(BR)); diff --git a/clang/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp index 9fffedfccd871..0dc775f8df28e 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp @@ -127,7 +127,7 @@ void ChrootChecker::checkPreCall(const CallEvent &Call, BT_BreakJail.reset(new BuiltinBug( this, "Break out of jail", "No call of chdir(\"/\") immediately " "after chroot")); - C.emitReport(llvm::make_unique( + C.emitReport(llvm::make_unique( *BT_BreakJail, BT_BreakJail->getDescription(), N)); } } diff --git a/clang/lib/StaticAnalyzer/Checkers/CloneChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CloneChecker.cpp index 4fc225056d4c7..ebba31e4007e3 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CloneChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CloneChecker.cpp @@ -114,8 +114,8 @@ void CloneChecker::reportClones( for (const CloneDetector::CloneGroup &Group : CloneGroups) { // We group the clones by printing the first as a warning and all others // as a note. - auto R = llvm::make_unique(*BT_Exact, "Duplicate code detected", - makeLocation(Group.front(), Mgr)); + auto R = llvm::make_unique( + *BT_Exact, "Duplicate code detected", makeLocation(Group.front(), Mgr)); R->addRange(Group.front().getSourceRange()); for (unsigned i = 1; i < Group.size(); ++i) @@ -169,7 +169,7 @@ void CloneChecker::reportSuspiciousClones( // which may confuse the user. // Think how to perform more accurate suggestions? - auto R = llvm::make_unique( + auto R = llvm::make_unique( *BT_Suspicious, "Potential copy-paste error; did you really mean to use '" + Pair.FirstCloneInfo.Variable->getNameAsString() + "' here?", diff --git a/clang/lib/StaticAnalyzer/Checkers/ConversionChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ConversionChecker.cpp index 5058d101b8e59..fd2417cfe3c0b 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ConversionChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ConversionChecker.cpp @@ -121,7 +121,7 @@ void ConversionChecker::reportBug(ExplodedNode *N, CheckerContext &C, new BuiltinBug(this, "Conversion", "Possible loss of sign/precision.")); // Generate a report for this bug. - auto R = llvm::make_unique(*BT, Msg, N); + auto R = llvm::make_unique(*BT, Msg, N); C.emitReport(std::move(R)); } diff --git a/clang/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp b/clang/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp index bb9e8110b6479..ceb638fac95bd 100644 --- a/clang/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp @@ -333,7 +333,8 @@ class ReportStmts : public Checker> { if (!Node) return; - auto Report = llvm::make_unique(BT_stmtLoc, "Statement", Node); + auto Report = + llvm::make_unique(BT_stmtLoc, "Statement", Node); C.emitReport(std::move(Report)); } diff --git a/clang/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp index 3acc3ac362d78..3d92d68290a62 100644 --- a/clang/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp @@ -47,7 +47,7 @@ class DeleteWithNonVirtualDtorChecker } PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, BugReporterContext &BRC, - BugReport &BR) override; + PathSensitiveBugReport &BR) override; private: bool Satisfied; @@ -92,7 +92,7 @@ void DeleteWithNonVirtualDtorChecker::checkPreStmt(const CXXDeleteExpr *DE, "Logic error")); ExplodedNode *N = C.generateNonFatalErrorNode(); - auto R = llvm::make_unique(*BT, BT->getName(), N); + auto R = llvm::make_unique(*BT, BT->getName(), N); // Mark region of problematic base class for later use in the BugVisitor. R->markInteresting(BaseClassRegion); @@ -102,7 +102,8 @@ void DeleteWithNonVirtualDtorChecker::checkPreStmt(const CXXDeleteExpr *DE, PathDiagnosticPieceRef DeleteWithNonVirtualDtorChecker::DeleteBugVisitor::VisitNode( - const ExplodedNode *N, BugReporterContext &BRC, BugReport &BR) { + const ExplodedNode *N, BugReporterContext &BRC, + PathSensitiveBugReport &BR) { // Stop traversal after the first conversion was found on a path. if (Satisfied) return nullptr; diff --git a/clang/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp index 2c264833f2a9f..4b7d3998bca76 100644 --- a/clang/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp @@ -179,7 +179,7 @@ void DereferenceChecker::reportBug(ProgramStateRef State, const Stmt *S, break; } - auto report = llvm::make_unique( + auto report = llvm::make_unique( *BT_null, buf.empty() ? BT_null->getDescription() : StringRef(buf), N); bugreporter::trackExpressionValue(N, bugreporter::getDerefExpr(S), *report); @@ -200,8 +200,8 @@ void DereferenceChecker::checkLocation(SVal l, bool isLoad, const Stmt* S, BT_undef.reset( new BuiltinBug(this, "Dereference of undefined pointer value")); - auto report = - llvm::make_unique(*BT_undef, BT_undef->getDescription(), N); + auto report = llvm::make_unique( + *BT_undef, BT_undef->getDescription(), N); bugreporter::trackExpressionValue(N, bugreporter::getDerefExpr(S), *report); C.emitReport(std::move(report)); } diff --git a/clang/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp index 33e8fcd8af7bb..e153cbc60680a 100644 --- a/clang/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp @@ -47,7 +47,7 @@ void DivZeroChecker::reportBug( if (!BT) BT.reset(new BuiltinBug(this, "Division by zero")); - auto R = llvm::make_unique(*BT, Msg, N); + auto R = llvm::make_unique(*BT, Msg, N); R->addVisitor(std::move(Visitor)); bugreporter::trackExpressionValue(N, getDenomExpr(N), *R); C.emitReport(std::move(R)); diff --git a/clang/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp index 4c360f8a300b1..6a58f80c43f53 100644 --- a/clang/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp @@ -49,7 +49,7 @@ class DynamicTypeChecker : public Checker> { PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, BugReporterContext &BRC, - BugReport &BR) override; + PathSensitiveBugReport &BR) override; private: // The tracked region. @@ -80,8 +80,8 @@ void DynamicTypeChecker::reportTypeError(QualType DynamicType, QualType::print(StaticType.getTypePtr(), Qualifiers(), OS, C.getLangOpts(), llvm::Twine()); OS << "'"; - std::unique_ptr R( - new BugReport(*BT, OS.str(), C.generateNonFatalErrorNode())); + auto R = llvm::make_unique( + *BT, OS.str(), C.generateNonFatalErrorNode()); R->markInteresting(Reg); R->addVisitor(llvm::make_unique(Reg)); R->addRange(ReportedNode->getSourceRange()); @@ -89,7 +89,7 @@ void DynamicTypeChecker::reportTypeError(QualType DynamicType, } PathDiagnosticPieceRef DynamicTypeChecker::DynamicTypeBugVisitor::VisitNode( - const ExplodedNode *N, BugReporterContext &BRC, BugReport &) { + const ExplodedNode *N, BugReporterContext &BRC, PathSensitiveBugReport &) { ProgramStateRef State = N->getState(); ProgramStateRef StatePrev = N->getFirstPred()->getState(); diff --git a/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp b/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp index 5ae466d426056..e0a39da603857 100644 --- a/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp @@ -85,7 +85,7 @@ class DynamicTypePropagation: PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, BugReporterContext &BRC, - BugReport &BR) override; + PathSensitiveBugReport &BR) override; private: // The tracked symbol. @@ -911,8 +911,8 @@ void DynamicTypePropagation::reportGenericsBug( OS << "' to incompatible type '"; QualType::print(To, Qualifiers(), OS, C.getLangOpts(), llvm::Twine()); OS << "'"; - std::unique_ptr R( - new BugReport(*ObjCGenericsBugType, OS.str(), N)); + auto R = llvm::make_unique(*ObjCGenericsBugType, + OS.str(), N); R->markInteresting(Sym); R->addVisitor(llvm::make_unique(Sym)); if (ReportedNode) @@ -921,7 +921,8 @@ void DynamicTypePropagation::reportGenericsBug( } PathDiagnosticPieceRef DynamicTypePropagation::GenericsBugVisitor::VisitNode( - const ExplodedNode *N, BugReporterContext &BRC, BugReport &BR) { + const ExplodedNode *N, BugReporterContext &BRC, + PathSensitiveBugReport &BR) { ProgramStateRef state = N->getState(); ProgramStateRef statePrev = N->getFirstPred()->getState(); diff --git a/clang/lib/StaticAnalyzer/Checkers/EnumCastOutOfRangeChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/EnumCastOutOfRangeChecker.cpp index a6539098c89a3..79f49d064b9c7 100644 --- a/clang/lib/StaticAnalyzer/Checkers/EnumCastOutOfRangeChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/EnumCastOutOfRangeChecker.cpp @@ -83,7 +83,7 @@ void EnumCastOutOfRangeChecker::reportWarning(CheckerContext &C) const { new BuiltinBug(this, "Enum cast out of range", "The value provided to the cast expression is not in " "the valid range of values for the enum")); - C.emitReport(llvm::make_unique( + C.emitReport(llvm::make_unique( *EnumValueCastOutOfRange, EnumValueCastOutOfRange->getDescription(), N)); } diff --git a/clang/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp index f23f784016d88..5ba80e201cad4 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp @@ -148,7 +148,7 @@ ExplodedNode *ExprInspectionChecker::reportBug(llvm::StringRef Msg, if (!BT) BT.reset(new BugType(this, "Checking analyzer assumptions", "debug")); - BR.emitReport(llvm::make_unique(*BT, Msg, N)); + BR.emitReport(llvm::make_unique(*BT, Msg, N)); return N; } diff --git a/clang/lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp index 94542be7dd7ca..92e3ff24610b6 100644 --- a/clang/lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp @@ -55,7 +55,8 @@ void FixedAddressChecker::checkPreStmt(const BinaryOperator *B, "Using a fixed address is not portable because that " "address will probably not be valid in all " "environments or platforms.")); - auto R = llvm::make_unique(*BT, BT->getDescription(), N); + auto R = + llvm::make_unique(*BT, BT->getDescription(), N); R->addRange(B->getRHS()->getSourceRange()); C.emitReport(std::move(R)); } diff --git a/clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp index 9c81e9f6a5a76..3a004e2f7a675 100644 --- a/clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp @@ -736,7 +736,7 @@ bool GenericTaintChecker::generateReportIfTainted(const Expr *E, StringRef Msg, // Generate diagnostic. if (ExplodedNode *N = C.generateNonFatalErrorNode()) { initBugType(); - auto report = llvm::make_unique(*BT, Msg, N); + auto report = llvm::make_unique(*BT, Msg, N); report->addRange(E->getSourceRange()); report->addVisitor(llvm::make_unique(TaintedSVal)); C.emitReport(std::move(report)); diff --git a/clang/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp index 3644fd87bd31f..83fbdea809596 100644 --- a/clang/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp @@ -54,9 +54,9 @@ class InnerPointerChecker ID.AddPointer(getTag()); } - virtual PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; + virtual PathDiagnosticPieceRef + VisitNode(const ExplodedNode *N, BugReporterContext &BRC, + PathSensitiveBugReport &BR) override; // FIXME: Scan the map once in the visitor's constructor and do a direct // lookup by region. @@ -279,7 +279,7 @@ const MemRegion *getContainerObjRegion(ProgramStateRef State, SymbolRef Sym) { } // end namespace clang PathDiagnosticPieceRef InnerPointerChecker::InnerPointerBRVisitor::VisitNode( - const ExplodedNode *N, BugReporterContext &BRC, BugReport &) { + const ExplodedNode *N, BugReporterContext &BRC, PathSensitiveBugReport &) { if (!isSymbolTracked(N->getState(), PtrToBuf) || isSymbolTracked(N->getFirstPred()->getState(), PtrToBuf)) return nullptr; diff --git a/clang/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp index 047194afcf5f3..4b5250b3448ab 100644 --- a/clang/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp @@ -1590,7 +1590,8 @@ IteratorPosition IteratorChecker::advancePosition(CheckerContext &C, void IteratorChecker::reportOutOfRangeBug(const StringRef &Message, const SVal &Val, CheckerContext &C, ExplodedNode *ErrNode) const { - auto R = llvm::make_unique(*OutOfRangeBugType, Message, ErrNode); + auto R = llvm::make_unique(*OutOfRangeBugType, Message, + ErrNode); R->markInteresting(Val); C.emitReport(std::move(R)); } @@ -1599,7 +1600,8 @@ void IteratorChecker::reportMismatchedBug(const StringRef &Message, const SVal &Val1, const SVal &Val2, CheckerContext &C, ExplodedNode *ErrNode) const { - auto R = llvm::make_unique(*MismatchedBugType, Message, ErrNode); + auto R = llvm::make_unique(*MismatchedBugType, Message, + ErrNode); R->markInteresting(Val1); R->markInteresting(Val2); C.emitReport(std::move(R)); @@ -1609,7 +1611,8 @@ void IteratorChecker::reportMismatchedBug(const StringRef &Message, const SVal &Val, const MemRegion *Reg, CheckerContext &C, ExplodedNode *ErrNode) const { - auto R = llvm::make_unique(*MismatchedBugType, Message, ErrNode); + auto R = llvm::make_unique(*MismatchedBugType, Message, + ErrNode); R->markInteresting(Val); R->markInteresting(Reg); C.emitReport(std::move(R)); @@ -1618,7 +1621,8 @@ void IteratorChecker::reportMismatchedBug(const StringRef &Message, void IteratorChecker::reportInvalidatedBug(const StringRef &Message, const SVal &Val, CheckerContext &C, ExplodedNode *ErrNode) const { - auto R = llvm::make_unique(*InvalidatedBugType, Message, ErrNode); + auto R = llvm::make_unique(*InvalidatedBugType, + Message, ErrNode); R->markInteresting(Val); C.emitReport(std::move(R)); } diff --git a/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp index bbecdcd79884f..068f972eba6d7 100644 --- a/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp @@ -125,7 +125,7 @@ class NonLocalizedStringBRVisitor final : public BugReporterVisitor { PathDiagnosticPieceRef VisitNode(const ExplodedNode *Succ, BugReporterContext &BRC, - BugReport &BR) override; + PathSensitiveBugReport &BR) override; void Profile(llvm::FoldingSetNodeID &ID) const override { ID.Add(NonLocalizedString); @@ -762,8 +762,8 @@ void NonLocalizedStringChecker::reportLocalizationError( return; // Generate the bug report. - std::unique_ptr R(new BugReport( - *BT, "User-facing text should use localized string macro", ErrNode)); + auto R = llvm::make_unique( + *BT, "User-facing text should use localized string macro", ErrNode); if (argumentNumber) { R->addRange(M.getArgExpr(argumentNumber - 1)->getSourceRange()); } else { @@ -1000,7 +1000,8 @@ void NonLocalizedStringChecker::checkPostStmt(const ObjCStringLiteral *SL, PathDiagnosticPieceRef NonLocalizedStringBRVisitor::VisitNode(const ExplodedNode *Succ, - BugReporterContext &BRC, BugReport &BR) { + BugReporterContext &BRC, + PathSensitiveBugReport &BR) { if (Satisfied) return nullptr; diff --git a/clang/lib/StaticAnalyzer/Checkers/MIGChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MIGChecker.cpp index 9cdc7612b2999..b06490d37e36b 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MIGChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MIGChecker.cpp @@ -274,7 +274,7 @@ void MIGChecker::checkReturnAux(const ReturnStmt *RS, CheckerContext &C) const { if (!N) return; - auto R = llvm::make_unique( + auto R = llvm::make_unique( BT, "MIG callback fails with error after deallocating argument value. " "This is a use-after-free vulnerability because the caller will try to " diff --git a/clang/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.cpp b/clang/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.cpp index cc7d2c715a37d..212229db6e482 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.cpp @@ -30,8 +30,8 @@ void MPIBugReporter::reportDoubleNonblocking( ErrorText = "Double nonblocking on request " + RequestRegion->getDescriptiveName() + ". "; - auto Report = llvm::make_unique(*DoubleNonblockingBugType, - ErrorText, ExplNode); + auto Report = llvm::make_unique( + *DoubleNonblockingBugType, ErrorText, ExplNode); Report->addRange(MPICallEvent.getSourceRange()); SourceRange Range = RequestRegion->sourceRange(); @@ -53,8 +53,8 @@ void MPIBugReporter::reportMissingWait( std::string ErrorText{"Request " + RequestRegion->getDescriptiveName() + " has no matching wait. "}; - auto Report = - llvm::make_unique(*MissingWaitBugType, ErrorText, ExplNode); + auto Report = llvm::make_unique(*MissingWaitBugType, + ErrorText, ExplNode); SourceRange Range = RequestRegion->sourceRange(); if (Range.isValid()) @@ -73,8 +73,8 @@ void MPIBugReporter::reportUnmatchedWait( std::string ErrorText{"Request " + RequestRegion->getDescriptiveName() + " has no matching nonblocking call. "}; - auto Report = - llvm::make_unique(*UnmatchedWaitBugType, ErrorText, ExplNode); + auto Report = llvm::make_unique(*UnmatchedWaitBugType, + ErrorText, ExplNode); Report->addRange(CE.getSourceRange()); SourceRange Range = RequestRegion->sourceRange(); @@ -84,8 +84,10 @@ void MPIBugReporter::reportUnmatchedWait( BReporter.emitReport(std::move(Report)); } -PathDiagnosticPieceRef MPIBugReporter::RequestNodeVisitor::VisitNode( - const ExplodedNode *N, BugReporterContext &BRC, BugReport &BR) { +PathDiagnosticPieceRef +MPIBugReporter::RequestNodeVisitor::VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) { if (IsNodeFound) return nullptr; diff --git a/clang/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.h b/clang/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.h index 66da02f76156e..9871da026b042 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.h +++ b/clang/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.h @@ -91,7 +91,7 @@ class MPIBugReporter { PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, BugReporterContext &BRC, - BugReport &BR) override; + PathSensitiveBugReport &BR) override; private: const MemRegion *const RequestRegion; diff --git a/clang/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp index c3c5701328fcc..c37b89b72a2d6 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp @@ -117,7 +117,8 @@ class MacOSKeychainAPIChecker : public Checker, const AllocationPair &AP, ExplodedNode *N, CheckerContext &C) const; /// Mark an AllocationPair interesting for diagnostic reporting. - void markInteresting(BugReport *R, const AllocationPair &AP) const { + void markInteresting(PathSensitiveBugReport *R, + const AllocationPair &AP) const { R->markInteresting(AP.first); R->markInteresting(AP.second->Region); } @@ -141,7 +142,7 @@ class MacOSKeychainAPIChecker : public Checker, PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, BugReporterContext &BRC, - BugReport &BR) override; + PathSensitiveBugReport &BR) override; }; }; } @@ -236,7 +237,7 @@ void MacOSKeychainAPIChecker:: os << "Deallocator doesn't match the allocator: '" << FunctionsToTrack[PDeallocIdx].Name << "' should be used."; - auto Report = llvm::make_unique(*BT, os.str(), N); + auto Report = llvm::make_unique(*BT, os.str(), N); Report->addVisitor(llvm::make_unique(AP.first)); Report->addRange(ArgExpr->getSourceRange()); markInteresting(Report.get(), AP); @@ -280,7 +281,8 @@ void MacOSKeychainAPIChecker::checkPreStmt(const CallExpr *CE, << "the allocator: missing a call to '" << FunctionsToTrack[DIdx].Name << "'."; - auto Report = llvm::make_unique(*BT, os.str(), N); + auto Report = + llvm::make_unique(*BT, os.str(), N); Report->addVisitor(llvm::make_unique(V)); Report->addRange(ArgExpr->getSourceRange()); Report->markInteresting(AS->Region); @@ -334,7 +336,7 @@ void MacOSKeychainAPIChecker::checkPreStmt(const CallExpr *CE, if (!N) return; initBugType(); - auto Report = llvm::make_unique( + auto Report = llvm::make_unique( *BT, "Trying to free data which has not been allocated.", N); Report->addRange(ArgExpr->getSourceRange()); if (AS) @@ -487,9 +489,9 @@ MacOSKeychainAPIChecker::generateAllocatedDataNotReleasedReport( C.getSourceManager(), AllocNode->getLocationContext()); - auto Report = - llvm::make_unique(*BT, os.str(), N, LocUsedForUniqueing, - AllocNode->getLocationContext()->getDecl()); + auto Report = llvm::make_unique( + *BT, os.str(), N, LocUsedForUniqueing, + AllocNode->getLocationContext()->getDecl()); Report->addVisitor(llvm::make_unique(AP.first)); markInteresting(Report.get(), AP); @@ -615,7 +617,8 @@ ProgramStateRef MacOSKeychainAPIChecker::checkPointerEscape( PathDiagnosticPieceRef MacOSKeychainAPIChecker::SecKeychainBugVisitor::VisitNode( - const ExplodedNode *N, BugReporterContext &BRC, BugReport &BR) { + const ExplodedNode *N, BugReporterContext &BRC, + PathSensitiveBugReport &BR) { const AllocationState *AS = N->getState()->get(Sym); if (!AS) return nullptr; diff --git a/clang/lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp index 1c52d20d09914..2cfe89e0eadfc 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp @@ -139,7 +139,8 @@ void MacOSXAPIChecker::CheckDispatchOnce(CheckerContext &C, const CallExpr *CE, BT_dispatchOnce.reset(new BugType(this, "Improper use of 'dispatch_once'", "API Misuse (Apple)")); - auto report = llvm::make_unique(*BT_dispatchOnce, os.str(), N); + auto report = + llvm::make_unique(*BT_dispatchOnce, os.str(), N); report->addRange(CE->getArg(0)->getSourceRange()); C.emitReport(std::move(report)); } diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index 2b36c1a0de843..882ac1701b690 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -524,17 +524,16 @@ class MallocChecker : public Checker(L, BR.getDescription(), false); @@ -1822,7 +1821,8 @@ void MallocChecker::ReportBadFree(CheckerContext &C, SVal ArgVal, printExpectedAllocName(os, C, DeallocExpr); - auto R = llvm::make_unique(*BT_BadFree[*CheckKind], os.str(), N); + auto R = llvm::make_unique(*BT_BadFree[*CheckKind], + os.str(), N); R->markInteresting(MR); R->addRange(Range); C.emitReport(std::move(R)); @@ -1846,7 +1846,7 @@ void MallocChecker::ReportFreeAlloca(CheckerContext &C, SVal ArgVal, BT_FreeAlloca[*CheckKind].reset(new BugType( CheckNames[*CheckKind], "Free alloca()", categories::MemoryError)); - auto R = llvm::make_unique( + auto R = llvm::make_unique( *BT_FreeAlloca[*CheckKind], "Memory allocated by alloca() should not be deallocated", N); R->markInteresting(ArgVal.getAsRegion()); @@ -1902,7 +1902,8 @@ void MallocChecker::ReportMismatchedDealloc(CheckerContext &C, os << ", not " << DeallocOs.str(); } - auto R = llvm::make_unique(*BT_MismatchedDealloc, os.str(), N); + auto R = llvm::make_unique(*BT_MismatchedDealloc, + os.str(), N); R->markInteresting(Sym); R->addRange(Range); R->addVisitor(llvm::make_unique(Sym)); @@ -1961,7 +1962,8 @@ void MallocChecker::ReportOffsetFree(CheckerContext &C, SVal ArgVal, else os << "allocated memory"; - auto R = llvm::make_unique(*BT_OffsetFree[*CheckKind], os.str(), N); + auto R = llvm::make_unique(*BT_OffsetFree[*CheckKind], + os.str(), N); R->markInteresting(MR->getBaseRegion()); R->addRange(Range); C.emitReport(std::move(R)); @@ -1987,10 +1989,11 @@ void MallocChecker::ReportUseAfterFree(CheckerContext &C, SourceRange Range, AllocationFamily AF = C.getState()->get(Sym)->getAllocationFamily(); - auto R = llvm::make_unique(*BT_UseFree[*CheckKind], + auto R = llvm::make_unique( + *BT_UseFree[*CheckKind], AF == AF_InnerBuffer - ? "Inner pointer of container used after re/deallocation" - : "Use of memory after it is freed", + ? "Inner pointer of container used after re/deallocation" + : "Use of memory after it is freed", N); R->markInteresting(Sym); @@ -2021,7 +2024,7 @@ void MallocChecker::ReportDoubleFree(CheckerContext &C, SourceRange Range, BT_DoubleFree[*CheckKind].reset(new BugType( CheckNames[*CheckKind], "Double free", categories::MemoryError)); - auto R = llvm::make_unique( + auto R = llvm::make_unique( *BT_DoubleFree[*CheckKind], (Released ? "Attempt to free released memory" : "Attempt to free non-owned memory"), @@ -2050,7 +2053,7 @@ void MallocChecker::ReportDoubleDelete(CheckerContext &C, SymbolRef Sym) const { "Double delete", categories::MemoryError)); - auto R = llvm::make_unique( + auto R = llvm::make_unique( *BT_DoubleDelete, "Attempt to delete released memory", N); R->markInteresting(Sym); @@ -2078,8 +2081,8 @@ void MallocChecker::ReportUseZeroAllocated(CheckerContext &C, new BugType(CheckNames[*CheckKind], "Use of zero allocated", categories::MemoryError)); - auto R = llvm::make_unique(*BT_UseZerroAllocated[*CheckKind], - "Use of zero-allocated memory", N); + auto R = llvm::make_unique( + *BT_UseZerroAllocated[*CheckKind], "Use of zero-allocated memory", N); R->addRange(Range); if (Sym) { @@ -2118,7 +2121,8 @@ void MallocChecker::ReportFunctionPointerFree(CheckerContext &C, SVal ArgVal, Os << " is a function pointer"; - auto R = llvm::make_unique(*BT_BadFree[*CheckKind], Os.str(), N); + auto R = llvm::make_unique(*BT_BadFree[*CheckKind], + Os.str(), N); R->markInteresting(MR); R->addRange(Range); C.emitReport(std::move(R)); @@ -2343,7 +2347,7 @@ void MallocChecker::reportLeak(SymbolRef Sym, ExplodedNode *N, os << "Potential memory leak"; } - auto R = llvm::make_unique( + auto R = llvm::make_unique( *BT_Leak[*CheckKind], os.str(), N, LocUsedForUniqueing, AllocNode->getLocationContext()->getDecl()); R->markInteresting(Sym); @@ -2905,8 +2909,10 @@ static bool isReferenceCountingPointerDestructor(const CXXDestructorDecl *DD) { return false; } -PathDiagnosticPieceRef MallocChecker::MallocBugVisitor::VisitNode( - const ExplodedNode *N, BugReporterContext &BRC, BugReport &BR) { +PathDiagnosticPieceRef +MallocChecker::MallocBugVisitor::VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) { ProgramStateRef state = N->getState(); ProgramStateRef statePrev = N->getFirstPred()->getState(); diff --git a/clang/lib/StaticAnalyzer/Checkers/MmapWriteExecChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MmapWriteExecChecker.cpp index 270efede83858..604a2b39146fd 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MmapWriteExecChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MmapWriteExecChecker.cpp @@ -67,7 +67,7 @@ void MmapWriteExecChecker::checkPreCall(const CallEvent &Call, if (!N) return; - auto Report = llvm::make_unique( + auto Report = llvm::make_unique( *BT, "Both PROT_WRITE and PROT_EXEC flags are set. This can " "lead to exploitable memory regions, which could be overwritten " "with malicious code", N); diff --git a/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp index 47fea7dcf5c42..89abb4a629367 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp @@ -171,7 +171,7 @@ class MoveChecker PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, BugReporterContext &BRC, - BugReport &BR) override; + PathSensitiveBugReport &BR) override; private: const MoveChecker &Chk; @@ -270,8 +270,10 @@ static const MemRegion *unwrapRValueReferenceIndirection(const MemRegion *MR) { return MR; } -PathDiagnosticPieceRef MoveChecker::MovedBugVisitor::VisitNode( - const ExplodedNode *N, BugReporterContext &BRC, BugReport &BR) { +PathDiagnosticPieceRef +MoveChecker::MovedBugVisitor::VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) { // We need only the last move of the reported object's region. // The visitor walks the ExplodedGraph backwards. if (Found) @@ -427,9 +429,9 @@ ExplodedNode *MoveChecker::reportBug(const MemRegion *Region, break; } - auto R = - llvm::make_unique(*BT, OS.str(), N, LocUsedForUniqueing, - MoveNode->getLocationContext()->getDecl()); + auto R = llvm::make_unique( + *BT, OS.str(), N, LocUsedForUniqueing, + MoveNode->getLocationContext()->getDecl()); R->addVisitor(llvm::make_unique(*this, Region, RD, MK)); C.emitReport(std::move(R)); return N; diff --git a/clang/lib/StaticAnalyzer/Checkers/NSAutoreleasePoolChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/NSAutoreleasePoolChecker.cpp index 6fc7c17bc42fb..c42efbce47e06 100644 --- a/clang/lib/StaticAnalyzer/Checkers/NSAutoreleasePoolChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/NSAutoreleasePoolChecker.cpp @@ -67,9 +67,11 @@ void NSAutoreleasePoolChecker::checkPreObjCMessage(const ObjCMethodCall &msg, return; } - auto Report = llvm::make_unique( - *BT, "Use -drain instead of -release when using NSAutoreleasePool and " - "garbage collection", N); + auto Report = llvm::make_unique( + *BT, + "Use -drain instead of -release when using NSAutoreleasePool and " + "garbage collection", + N); Report->addRange(msg.getSourceRange()); C.emitReport(std::move(Report)); } diff --git a/clang/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp index 5cec012258c15..74f2cbefe9de7 100644 --- a/clang/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp @@ -273,7 +273,8 @@ void NSOrCFErrorDerefChecker::checkEvent(ImplicitNullDerefEvent event) const { CFBT.reset(new CFErrorDerefBug(this)); bug = CFBT.get(); } - BR.emitReport(llvm::make_unique(*bug, os.str(), event.SinkNode)); + BR.emitReport( + llvm::make_unique(*bug, os.str(), event.SinkNode)); } static bool IsNSError(QualType T, IdentifierInfo *II) { diff --git a/clang/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp index 5a35677949248..d91488b5e0b54 100644 --- a/clang/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp @@ -196,7 +196,8 @@ NonNullParamChecker::genReportNullAttrNonNull(const ExplodedNode *ErrorNode, << IdxOfArg << llvm::getOrdinalSuffix(IdxOfArg) << " parameter expecting 'nonnull'"; - auto R = std::make_unique(*BTAttrNonNull, SBuf, ErrorNode); + auto R = + llvm::make_unique(*BTAttrNonNull, SBuf, ErrorNode); if (ArgE) bugreporter::trackExpressionValue(ErrorNode, ArgE, *R); @@ -208,7 +209,7 @@ std::unique_ptr NonNullParamChecker::genReportReferenceToNullPointer( if (!BTNullRefArg) BTNullRefArg.reset(new BuiltinBug(this, "Dereference of null pointer")); - auto R = llvm::make_unique( + auto R = llvm::make_unique( *BTNullRefArg, "Forming reference to null pointer", ErrorNode); if (ArgE) { const Expr *ArgEDeref = bugreporter::getDerefExpr(ArgE); diff --git a/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp index ce2143137acba..59f5939cd7d88 100644 --- a/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp @@ -139,7 +139,7 @@ class NullabilityChecker PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, BugReporterContext &BRC, - BugReport &BR) override; + PathSensitiveBugReport &BR) override; private: // The tracked region. @@ -163,7 +163,7 @@ class NullabilityChecker if (!BT) BT.reset(new BugType(this, "Nullability", categories::MemoryError)); - auto R = llvm::make_unique(*BT, Msg, N); + auto R = llvm::make_unique(*BT, Msg, N); if (Region) { R->markInteresting(Region); R->addVisitor(llvm::make_unique(Region)); @@ -291,7 +291,8 @@ NullabilityChecker::getTrackRegion(SVal Val, bool CheckSuperRegion) const { } PathDiagnosticPieceRef NullabilityChecker::NullabilityBugVisitor::VisitNode( - const ExplodedNode *N, BugReporterContext &BRC, BugReport &BR) { + const ExplodedNode *N, BugReporterContext &BRC, + PathSensitiveBugReport &BR) { ProgramStateRef State = N->getState(); ProgramStateRef StatePrev = N->getFirstPred()->getState(); diff --git a/clang/lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp index bd8cfb14680fe..22e4d48e86b2d 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp @@ -46,8 +46,8 @@ void ObjCAtSyncChecker::checkPreStmt(const ObjCAtSynchronizedStmt *S, if (!BT_undef) BT_undef.reset(new BuiltinBug(this, "Uninitialized value used as mutex " "for @synchronized")); - auto report = - llvm::make_unique(*BT_undef, BT_undef->getDescription(), N); + auto report = llvm::make_unique( + *BT_undef, BT_undef->getDescription(), N); bugreporter::trackExpressionValue(N, Ex, *report); C.emitReport(std::move(report)); } @@ -70,8 +70,8 @@ void ObjCAtSyncChecker::checkPreStmt(const ObjCAtSynchronizedStmt *S, BT_null.reset(new BuiltinBug( this, "Nil value used as mutex for @synchronized() " "(no synchronization will occur)")); - auto report = - llvm::make_unique(*BT_null, BT_null->getDescription(), N); + auto report = llvm::make_unique( + *BT_null, BT_null->getDescription(), N); bugreporter::trackExpressionValue(N, Ex, *report); C.emitReport(std::move(report)); diff --git a/clang/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp index 98d2a9941da92..023beab367188 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp @@ -144,7 +144,8 @@ void ObjCContainersChecker::checkPreStmt(const CallExpr *CE, if (!N) return; initBugType(); - auto R = llvm::make_unique(*BT, "Index is out of bounds", N); + auto R = llvm::make_unique( + *BT, "Index is out of bounds", N); R->addRange(IdxExpr->getSourceRange()); bugreporter::trackExpressionValue( N, IdxExpr, *R, bugreporter::TrackingKind::Thorough, false); diff --git a/clang/lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp index 767b7bf4063c8..542266a505aea 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp @@ -159,7 +159,7 @@ void ObjCSelfInitChecker::checkForInvalidSelf(const Expr *E, CheckerContext &C, if (!BT) BT.reset(new BugType(this, "Missing \"self = [(super or self) init...]\"", categories::CoreFoundationObjectiveC)); - C.emitReport(llvm::make_unique(*BT, errorStr, N)); + C.emitReport(llvm::make_unique(*BT, errorStr, N)); } void ObjCSelfInitChecker::checkPostObjCMessage(const ObjCMethodCall &Msg, diff --git a/clang/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp index dc2cb2ba9d556..52f7d11e9038f 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp @@ -71,7 +71,7 @@ class SuperDeallocBRVisitor final : public BugReporterVisitor { PathDiagnosticPieceRef VisitNode(const ExplodedNode *Succ, BugReporterContext &BRC, - BugReport &BR) override; + PathSensitiveBugReport &BR) override; void Profile(llvm::FoldingSetNodeID &ID) const override { ID.Add(ReceiverSymbol); @@ -187,8 +187,8 @@ void ObjCSuperDeallocChecker::reportUseAfterDealloc(SymbolRef Sym, Desc = "Use of 'self' after it has been deallocated"; // Generate the report. - std::unique_ptr BR( - new BugReport(*DoubleSuperDeallocBugType, Desc, ErrNode)); + auto BR = llvm::make_unique(*DoubleSuperDeallocBugType, + Desc, ErrNode); BR->addRange(S->getSourceRange()); BR->addVisitor(llvm::make_unique(Sym)); C.emitReport(std::move(BR)); @@ -244,7 +244,8 @@ ObjCSuperDeallocChecker::isSuperDeallocMessage(const ObjCMethodCall &M) const { PathDiagnosticPieceRef SuperDeallocBRVisitor::VisitNode(const ExplodedNode *Succ, - BugReporterContext &BRC, BugReport &) { + BugReporterContext &BRC, + PathSensitiveBugReport &) { if (Satisfied) return nullptr; diff --git a/clang/lib/StaticAnalyzer/Checkers/PaddingChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PaddingChecker.cpp index 09f2fd635b6ca..81dbde46391d1 100644 --- a/clang/lib/StaticAnalyzer/Checkers/PaddingChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/PaddingChecker.cpp @@ -337,7 +337,8 @@ class PaddingChecker : public Checker> { PathDiagnosticLocation CELoc = PathDiagnosticLocation::create(RD, BR->getSourceManager()); - auto Report = llvm::make_unique(*PaddingBug, Os.str(), CELoc); + auto Report = + llvm::make_unique(*PaddingBug, Os.str(), CELoc); Report->setDeclWithIssue(RD); Report->addRange(RD->getSourceRange()); BR->emitReport(std::move(Report)); diff --git a/clang/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp index 1888449879bc0..2415b42dcb182 100644 --- a/clang/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp @@ -173,8 +173,8 @@ void PointerArithChecker::reportPointerArithMisuse(const Expr *E, this, "Dangerous pointer arithmetic", "Pointer arithmetic on a pointer to base class is dangerous " "because derived and base class may have different size.")); - auto R = llvm::make_unique(*BT_polyArray, - BT_polyArray->getDescription(), N); + auto R = llvm::make_unique( + *BT_polyArray, BT_polyArray->getDescription(), N); R->addRange(E->getSourceRange()); R->markInteresting(ArrayRegion); C.emitReport(std::move(R)); @@ -196,8 +196,8 @@ void PointerArithChecker::reportPointerArithMisuse(const Expr *E, "Pointer arithmetic on non-array " "variables relies on memory layout, " "which is dangerous.")); - auto R = llvm::make_unique(*BT_pointerArith, - BT_pointerArith->getDescription(), N); + auto R = llvm::make_unique( + *BT_pointerArith, BT_pointerArith->getDescription(), N); R->addRange(SR); R->markInteresting(Region); C.emitReport(std::move(R)); diff --git a/clang/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp index c9512f4fc42ff..321aa914e2dbf 100644 --- a/clang/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp @@ -63,7 +63,8 @@ void PointerSubChecker::checkPreStmt(const BinaryOperator *B, new BuiltinBug(this, "Pointer subtraction", "Subtraction of two pointers that do not point to " "the same memory chunk may cause incorrect result.")); - auto R = llvm::make_unique(*BT, BT->getDescription(), N); + auto R = + llvm::make_unique(*BT, BT->getDescription(), N); R->addRange(B->getSourceRange()); C.emitReport(std::move(R)); } diff --git a/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp index 33f677e1c2583..e1786bff8887e 100644 --- a/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp @@ -240,7 +240,7 @@ void PthreadLockChecker::AcquireLock(CheckerContext &C, const CallExpr *CE, ExplodedNode *N = C.generateErrorNode(); if (!N) return; - auto report = llvm::make_unique( + auto report = llvm::make_unique( *BT_doublelock, "This lock has already been acquired", N); report->addRange(CE->getArg(0)->getSourceRange()); C.emitReport(std::move(report)); @@ -305,7 +305,7 @@ void PthreadLockChecker::ReleaseLock(CheckerContext &C, const CallExpr *CE, ExplodedNode *N = C.generateErrorNode(); if (!N) return; - auto Report = llvm::make_unique( + auto Report = llvm::make_unique( *BT_doubleunlock, "This lock has already been unlocked", N); Report->addRange(CE->getArg(0)->getSourceRange()); C.emitReport(std::move(Report)); @@ -328,7 +328,7 @@ void PthreadLockChecker::ReleaseLock(CheckerContext &C, const CallExpr *CE, ExplodedNode *N = C.generateErrorNode(); if (!N) return; - auto report = llvm::make_unique( + auto report = llvm::make_unique( *BT_lor, "This was not the most recently acquired lock. Possible " "lock order reversal", N); report->addRange(CE->getArg(0)->getSourceRange()); @@ -399,7 +399,8 @@ void PthreadLockChecker::DestroyLock(CheckerContext &C, const CallExpr *CE, ExplodedNode *N = C.generateErrorNode(); if (!N) return; - auto Report = llvm::make_unique(*BT_destroylock, Message, N); + auto Report = + llvm::make_unique(*BT_destroylock, Message, N); Report->addRange(CE->getArg(0)->getSourceRange()); C.emitReport(std::move(Report)); } @@ -438,7 +439,8 @@ void PthreadLockChecker::InitLock(CheckerContext &C, const CallExpr *CE, ExplodedNode *N = C.generateErrorNode(); if (!N) return; - auto Report = llvm::make_unique(*BT_initlock, Message, N); + auto Report = + llvm::make_unique(*BT_initlock, Message, N); Report->addRange(CE->getArg(0)->getSourceRange()); C.emitReport(std::move(Report)); } @@ -451,7 +453,7 @@ void PthreadLockChecker::reportUseDestroyedBug(CheckerContext &C, ExplodedNode *N = C.generateErrorNode(); if (!N) return; - auto Report = llvm::make_unique( + auto Report = llvm::make_unique( *BT_destroylock, "This lock has already been destroyed", N); Report->addRange(CE->getArg(0)->getSourceRange()); C.emitReport(std::move(Report)); diff --git a/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.cpp b/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.cpp index 798c0a1c9c11d..121048574d27f 100644 --- a/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.cpp @@ -327,11 +327,11 @@ class RefCountReportVisitor : public BugReporterVisitor { PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, BugReporterContext &BRC, - BugReport &BR) override; + PathSensitiveBugReport &BR) override; PathDiagnosticPieceRef getEndPath(BugReporterContext &BRC, const ExplodedNode *N, - BugReport &BR) override; + PathSensitiveBugReport &BR) override; }; class RefLeakReportVisitor : public RefCountReportVisitor { @@ -340,7 +340,7 @@ class RefLeakReportVisitor : public RefCountReportVisitor { PathDiagnosticPieceRef getEndPath(BugReporterContext &BRC, const ExplodedNode *N, - BugReport &BR) override; + PathSensitiveBugReport &BR) override; }; } // end namespace retaincountchecker @@ -448,9 +448,9 @@ annotateStartParameter(const ExplodedNode *N, SymbolRef Sym, return std::make_shared(L, os.str()); } -PathDiagnosticPieceRef RefCountReportVisitor::VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) { +PathDiagnosticPieceRef +RefCountReportVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &BRC, + PathSensitiveBugReport &BR) { const auto &BT = static_cast(BR.getBugType()); const auto *Checker = @@ -715,14 +715,16 @@ static AllocationInfo GetAllocationSite(ProgramStateManager &StateMgr, PathDiagnosticPieceRef RefCountReportVisitor::getEndPath(BugReporterContext &BRC, - const ExplodedNode *EndN, BugReport &BR) { + const ExplodedNode *EndN, + PathSensitiveBugReport &BR) { BR.markInteresting(Sym); return BugReporterVisitor::getDefaultEndPath(BRC, EndN, BR); } PathDiagnosticPieceRef RefLeakReportVisitor::getEndPath(BugReporterContext &BRC, - const ExplodedNode *EndN, BugReport &BR) { + const ExplodedNode *EndN, + PathSensitiveBugReport &BR) { // Tell the BugReporterContext to report cases when the tracked symbol is // assigned to different variables, etc. @@ -736,13 +738,11 @@ RefLeakReportVisitor::getEndPath(BugReporterContext &BRC, const MemRegion* FirstBinding = AllocI.R; BR.markInteresting(AllocI.InterestingMethodContext); - const SourceManager& SM = BRC.getSourceManager(); - // Compute an actual location for the leak. Sometimes a leak doesn't // occur at an actual statement (e.g., transition between blocks; end // of function) so we need to walk the graph and compute a real location. const ExplodedNode *LeakN = EndN; - PathDiagnosticLocation L = PathDiagnosticLocation::createEndOfPath(LeakN, SM); + PathDiagnosticLocation L = PathDiagnosticLocation::createEndOfPath(LeakN); std::string sbuf; llvm::raw_string_ostream os(sbuf); @@ -813,9 +813,9 @@ RefLeakReportVisitor::getEndPath(BugReporterContext &BRC, } RefCountReport::RefCountReport(const RefCountBug &D, const LangOptions &LOpts, - ExplodedNode *n, SymbolRef sym, - bool isLeak) - : BugReport(D, D.getDescription(), n), Sym(sym), isLeak(isLeak) { + ExplodedNode *n, SymbolRef sym, bool isLeak) + : PathSensitiveBugReport(D, D.getDescription(), n), Sym(sym), + isLeak(isLeak) { if (!isLeak) addVisitor(llvm::make_unique(sym)); } @@ -823,7 +823,7 @@ RefCountReport::RefCountReport(const RefCountBug &D, const LangOptions &LOpts, RefCountReport::RefCountReport(const RefCountBug &D, const LangOptions &LOpts, ExplodedNode *n, SymbolRef sym, StringRef endText) - : BugReport(D, D.getDescription(), endText, n) { + : PathSensitiveBugReport(D, D.getDescription(), endText, n) { addVisitor(llvm::make_unique(sym)); } diff --git a/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.h b/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.h index 6e2a613aed629..323b50ad9f048 100644 --- a/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.h +++ b/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.h @@ -53,7 +53,7 @@ class RefCountBug : public BugType { static StringRef bugTypeToName(RefCountBugType BT); }; -class RefCountReport : public BugReport { +class RefCountReport : public PathSensitiveBugReport { protected: SymbolRef Sym; bool isLeak = false; @@ -67,16 +67,17 @@ class RefCountReport : public BugReport { ExplodedNode *n, SymbolRef sym, StringRef endText); - llvm::iterator_range getRanges() const override { + ArrayRef getRanges() const override { if (!isLeak) - return BugReport::getRanges(); - return llvm::make_range(ranges_iterator(), ranges_iterator()); + return PathSensitiveBugReport::getRanges(); + return {}; } }; class RefLeakReport : public RefCountReport { const MemRegion* AllocBinding; const Stmt *AllocStmt; + PathDiagnosticLocation Location; // Finds the function declaration where a leak warning for the parameter // 'sym' should be raised. @@ -90,7 +91,7 @@ class RefLeakReport : public RefCountReport { RefLeakReport(const RefCountBug &D, const LangOptions &LOpts, ExplodedNode *n, SymbolRef sym, CheckerContext &Ctx); - PathDiagnosticLocation getLocation(const SourceManager &SM) const override { + PathDiagnosticLocation getLocation() const override { assert(Location.isValid()); return Location; } diff --git a/clang/lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp index 9eb47e0526dc5..b4845f3e64f9e 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp @@ -79,7 +79,8 @@ void ReturnPointerRangeChecker::checkPreStmt(const ReturnStmt *RS, // reference is outside the range. // Generate a report for this bug. - auto report = llvm::make_unique(*BT, BT->getDescription(), N); + auto report = + llvm::make_unique(*BT, BT->getDescription(), N); report->addRange(RetE->getSourceRange()); C.emitReport(std::move(report)); diff --git a/clang/lib/StaticAnalyzer/Checkers/ReturnUndefChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ReturnUndefChecker.cpp index f55c369da67eb..97c5a8b7c153c 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ReturnUndefChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ReturnUndefChecker.cpp @@ -83,7 +83,8 @@ static void emitBug(CheckerContext &C, BuiltinBug &BT, const Expr *RetE, if (!N) return; - auto Report = llvm::make_unique(BT, BT.getDescription(), N); + auto Report = + llvm::make_unique(BT, BT.getDescription(), N); Report->addRange(RetE->getSourceRange()); bugreporter::trackExpressionValue(N, TrackingE ? TrackingE : RetE, *Report); diff --git a/clang/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp index ec5e9622c236f..e4485ec298e49 100644 --- a/clang/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp @@ -206,8 +206,8 @@ void SimpleStreamChecker::reportDoubleClose(SymbolRef FileDescSym, return; // Generate the report. - auto R = llvm::make_unique(*DoubleCloseBugType, - "Closing a previously closed file stream", ErrNode); + auto R = llvm::make_unique( + *DoubleCloseBugType, "Closing a previously closed file stream", ErrNode); R->addRange(Call.getSourceRange()); R->markInteresting(FileDescSym); C.emitReport(std::move(R)); @@ -219,8 +219,9 @@ void SimpleStreamChecker::reportLeaks(ArrayRef LeakedStreams, // Attach bug reports to the leak node. // TODO: Identify the leaked file descriptor. for (SymbolRef LeakedStream : LeakedStreams) { - auto R = llvm::make_unique(*LeakBugType, - "Opened file is never closed; potential resource leak", ErrNode); + auto R = llvm::make_unique( + *LeakBugType, "Opened file is never closed; potential resource leak", + ErrNode); R->markInteresting(LeakedStream); C.emitReport(std::move(R)); } diff --git a/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp index b93bed5c30973..95bfc008aedd4 100644 --- a/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp @@ -162,7 +162,8 @@ void StackAddrEscapeChecker::EmitStackError(CheckerContext &C, llvm::raw_svector_ostream os(buf); SourceRange range = genName(os, R, C.getASTContext()); os << " returned to caller"; - auto report = llvm::make_unique(*BT_returnstack, os.str(), N); + auto report = + llvm::make_unique(*BT_returnstack, os.str(), N); report->addRange(RetE->getSourceRange()); if (range.isValid()) report->addRange(range); @@ -199,8 +200,8 @@ void StackAddrEscapeChecker::checkAsyncExecutedBlockCaptures( llvm::raw_svector_ostream Out(Buf); SourceRange Range = genName(Out, Region, C.getASTContext()); Out << " is captured by an asynchronously-executed block"; - auto Report = - llvm::make_unique(*BT_capturedstackasync, Out.str(), N); + auto Report = llvm::make_unique( + *BT_capturedstackasync, Out.str(), N); if (Range.isValid()) Report->addRange(Range); C.emitReport(std::move(Report)); @@ -222,8 +223,8 @@ void StackAddrEscapeChecker::checkReturnedBlockCaptures( llvm::raw_svector_ostream Out(Buf); SourceRange Range = genName(Out, Region, C.getASTContext()); Out << " is captured by a returned block"; - auto Report = - llvm::make_unique(*BT_capturedstackret, Out.str(), N); + auto Report = llvm::make_unique(*BT_capturedstackret, + Out.str(), N); if (Range.isValid()) Report->addRange(Range); C.emitReport(std::move(Report)); @@ -351,7 +352,8 @@ void StackAddrEscapeChecker::checkEndFunction(const ReturnStmt *RS, const VarRegion *VR = cast(P.first->getBaseRegion()); Out << *VR->getDecl() << "' upon returning to the caller. This will be a dangling reference"; - auto Report = llvm::make_unique(*BT_stackleak, Out.str(), N); + auto Report = + llvm::make_unique(*BT_stackleak, Out.str(), N); if (Range.isValid()) Report->addRange(Range); diff --git a/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp index 1ea5e07695133..a9d9ab3ae8e76 100644 --- a/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp @@ -277,7 +277,7 @@ void StreamChecker::Fseek(CheckerContext &C, const CallExpr *CE) const { new BuiltinBug(this, "Illegal whence argument", "The whence argument to fseek() should be " "SEEK_SET, SEEK_END, or SEEK_CUR.")); - C.emitReport(llvm::make_unique( + C.emitReport(llvm::make_unique( *BT_illegalwhence, BT_illegalwhence->getDescription(), N)); } } @@ -345,7 +345,7 @@ ProgramStateRef StreamChecker::CheckNullStream(SVal SV, ProgramStateRef state, if (!BT_nullfp) BT_nullfp.reset(new BuiltinBug(this, "NULL stream pointer", "Stream pointer might be NULL.")); - C.emitReport(llvm::make_unique( + C.emitReport(llvm::make_unique( *BT_nullfp, BT_nullfp->getDescription(), N)); } return nullptr; @@ -375,7 +375,7 @@ ProgramStateRef StreamChecker::CheckDoubleClose(const CallExpr *CE, BT_doubleclose.reset(new BuiltinBug( this, "Double fclose", "Try to close a file Descriptor already" " closed. Cause undefined behaviour.")); - C.emitReport(llvm::make_unique( + C.emitReport(llvm::make_unique( *BT_doubleclose, BT_doubleclose->getDescription(), N)); } return nullptr; @@ -405,7 +405,7 @@ void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, BT_ResourceLeak.reset( new BuiltinBug(this, "Resource Leak", "Opened File never closed. Potential Resource leak.")); - C.emitReport(llvm::make_unique( + C.emitReport(llvm::make_unique( *BT_ResourceLeak, BT_ResourceLeak->getDescription(), N)); } } diff --git a/clang/lib/StaticAnalyzer/Checkers/Taint.cpp b/clang/lib/StaticAnalyzer/Checkers/Taint.cpp index cd8cab8f52e40..2a9456dd84d5b 100644 --- a/clang/lib/StaticAnalyzer/Checkers/Taint.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/Taint.cpp @@ -206,7 +206,7 @@ bool taint::isTainted(ProgramStateRef State, SymbolRef Sym, TaintTagType Kind) { PathDiagnosticPieceRef TaintBugVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &BRC, - BugReport &BR) { + PathSensitiveBugReport &BR) { // Find the ExplodedNode where the taint was first introduced if (!isTainted(N->getState(), V) || diff --git a/clang/lib/StaticAnalyzer/Checkers/Taint.h b/clang/lib/StaticAnalyzer/Checkers/Taint.h index 2c3b001b5f0a0..8940916c19331 100644 --- a/clang/lib/StaticAnalyzer/Checkers/Taint.h +++ b/clang/lib/StaticAnalyzer/Checkers/Taint.h @@ -91,7 +91,7 @@ class TaintBugVisitor final : public BugReporterVisitor { PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, BugReporterContext &BRC, - BugReport &BR) override; + PathSensitiveBugReport &BR) override; }; } // namespace taint diff --git a/clang/lib/StaticAnalyzer/Checkers/TaintTesterChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/TaintTesterChecker.cpp index 094762e2faf83..db104274d0942 100644 --- a/clang/lib/StaticAnalyzer/Checkers/TaintTesterChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/TaintTesterChecker.cpp @@ -52,7 +52,7 @@ void TaintTesterChecker::checkPostStmt(const Expr *E, if (isTainted(State, E, C.getLocationContext())) { if (ExplodedNode *N = C.generateNonFatalErrorNode()) { initBugType(); - auto report = llvm::make_unique(*BT, "tainted",N); + auto report = llvm::make_unique(*BT, "tainted", N); report->addRange(E->getSourceRange()); C.emitReport(std::move(report)); } diff --git a/clang/lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp index e95c86e23e021..3b40ec8e9631f 100644 --- a/clang/lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp @@ -71,7 +71,7 @@ class DivisionBRVisitor : public BugReporterVisitor { PathDiagnosticPieceRef VisitNode(const ExplodedNode *Succ, BugReporterContext &BRC, - BugReport &BR) override; + PathSensitiveBugReport &BR) override; }; class TestAfterDivZeroChecker @@ -92,9 +92,9 @@ class TestAfterDivZeroChecker REGISTER_SET_WITH_PROGRAMSTATE(DivZeroMap, ZeroState) -PathDiagnosticPieceRef DivisionBRVisitor::VisitNode(const ExplodedNode *Succ, - BugReporterContext &BRC, - BugReport &BR) { +PathDiagnosticPieceRef +DivisionBRVisitor::VisitNode(const ExplodedNode *Succ, BugReporterContext &BRC, + PathSensitiveBugReport &BR) { if (Satisfied) return nullptr; @@ -167,7 +167,7 @@ void TestAfterDivZeroChecker::reportBug(SVal Val, CheckerContext &C) const { if (!DivZeroBug) DivZeroBug.reset(new BuiltinBug(this, "Division by zero")); - auto R = llvm::make_unique( + auto R = llvm::make_unique( *DivZeroBug, "Value being compared against zero has already been used " "for division", N); diff --git a/clang/lib/StaticAnalyzer/Checkers/UndefBranchChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/UndefBranchChecker.cpp index 3a4a1dbf641bb..cb974b0b23116 100644 --- a/clang/lib/StaticAnalyzer/Checkers/UndefBranchChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/UndefBranchChecker.cpp @@ -96,7 +96,8 @@ void UndefBranchChecker::checkBranchCondition(const Stmt *Condition, Ex = FindIt.FindExpr(Ex); // Emit the bug report. - auto R = llvm::make_unique(*BT, BT->getDescription(), N); + auto R = llvm::make_unique( + *BT, BT->getDescription(), N); bugreporter::trackExpressionValue(N, Ex, *R); R->addRange(Ex->getSourceRange()); diff --git a/clang/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp index 1b6f19c01c2c5..0a4dc86ae34ac 100644 --- a/clang/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp @@ -83,7 +83,7 @@ UndefCapturedBlockVarChecker::checkPostStmt(const BlockExpr *BE, os << "Variable '" << VD->getName() << "' is uninitialized when captured by block"; - auto R = llvm::make_unique(*BT, os.str(), N); + auto R = llvm::make_unique(*BT, os.str(), N); if (const Expr *Ex = FindBlockDeclRefExpr(BE->getBody(), VD)) R->addRange(Ex->getSourceRange()); R->addVisitor(llvm::make_unique( diff --git a/clang/lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp index 1ae287d39f11a..fd31f537c5c4d 100644 --- a/clang/lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp @@ -170,7 +170,7 @@ void UndefResultChecker::checkPostStmt(const BinaryOperator *B, << "' expression is undefined"; } } - auto report = llvm::make_unique(*BT, OS.str(), N); + auto report = llvm::make_unique(*BT, OS.str(), N); if (Ex) { report->addRange(Ex->getSourceRange()); bugreporter::trackExpressionValue(N, Ex, *report); diff --git a/clang/lib/StaticAnalyzer/Checkers/UndefinedArraySubscriptChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/UndefinedArraySubscriptChecker.cpp index 4c517d6f05622..5bc39a7351a80 100644 --- a/clang/lib/StaticAnalyzer/Checkers/UndefinedArraySubscriptChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/UndefinedArraySubscriptChecker.cpp @@ -52,7 +52,7 @@ UndefinedArraySubscriptChecker::checkPreStmt(const ArraySubscriptExpr *A, BT.reset(new BuiltinBug(this, "Array subscript is undefined")); // Generate a report for this bug. - auto R = llvm::make_unique(*BT, BT->getName(), N); + auto R = llvm::make_unique(*BT, BT->getName(), N); R->addRange(A->getIdx()->getSourceRange()); bugreporter::trackExpressionValue(N, A->getIdx(), *R); C.emitReport(std::move(R)); diff --git a/clang/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp index 0923b35a6ce2e..347483f7f109b 100644 --- a/clang/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp @@ -108,7 +108,7 @@ void UndefinedAssignmentChecker::checkBind(SVal location, SVal val, if (OS.str().empty()) OS << DefaultMsg; - auto R = llvm::make_unique(*BT, OS.str(), N); + auto R = llvm::make_unique(*BT, OS.str(), N); if (ex) { R->addRange(ex->getSourceRange()); bugreporter::trackExpressionValue(N, ex, *R); diff --git a/clang/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedObjectChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedObjectChecker.cpp index ff02123f45146..9f0122a1da7a9 100644 --- a/clang/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedObjectChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedObjectChecker.cpp @@ -187,7 +187,7 @@ void UninitializedObjectChecker::checkEndFunction( if (Opts.ShouldConvertNotesToWarnings) { for (const auto &Pair : UninitFields) { - auto Report = llvm::make_unique( + auto Report = llvm::make_unique( *BT_uninitField, Pair.second, Node, LocUsedForUniqueing, Node->getLocationContext()->getDecl()); Context.emitReport(std::move(Report)); @@ -201,7 +201,7 @@ void UninitializedObjectChecker::checkEndFunction( << (UninitFields.size() == 1 ? "" : "s") << " at the end of the constructor call"; - auto Report = llvm::make_unique( + auto Report = llvm::make_unique( *BT_uninitField, WarningOS.str(), Node, LocUsedForUniqueing, Node->getLocationContext()->getDecl()); diff --git a/clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp index 2ccb519891f37..96ec8d90af50e 100644 --- a/clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp @@ -135,7 +135,7 @@ void UnixAPIMisuseChecker::ReportOpenBug(CheckerContext &C, LazyInitialize(this, BT_open, "Improper use of 'open'"); - auto Report = llvm::make_unique(*BT_open, Msg, N); + auto Report = llvm::make_unique(*BT_open, Msg, N); Report->addRange(SR); C.emitReport(std::move(Report)); } @@ -304,7 +304,8 @@ void UnixAPIMisuseChecker::CheckPthreadOnce(CheckerContext &C, LazyInitialize(this, BT_pthreadOnce, "Improper use of 'pthread_once'"); - auto report = llvm::make_unique(*BT_pthreadOnce, os.str(), N); + auto report = + llvm::make_unique(*BT_pthreadOnce, os.str(), N); report->addRange(CE->getArg(0)->getSourceRange()); C.emitReport(std::move(report)); } @@ -347,7 +348,8 @@ bool UnixAPIPortabilityChecker::ReportZeroByteAllocation( SmallString<256> S; llvm::raw_svector_ostream os(S); os << "Call to '" << fn_name << "' has an allocation size of 0 bytes"; - auto report = llvm::make_unique(*BT_mallocZero, os.str(), N); + auto report = + llvm::make_unique(*BT_mallocZero, os.str(), N); report->addRange(arg->getSourceRange()); bugreporter::trackExpressionValue(N, arg, *report); diff --git a/clang/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp index 1630896c3b607..632beb9feee41 100644 --- a/clang/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp @@ -72,7 +72,7 @@ void VLASizeChecker::reportBug( break; } - auto report = llvm::make_unique(*BT, os.str(), N); + auto report = llvm::make_unique(*BT, os.str(), N); report->addVisitor(std::move(Visitor)); report->addRange(SizeE->getSourceRange()); bugreporter::trackExpressionValue(N, SizeE, *report); diff --git a/clang/lib/StaticAnalyzer/Checkers/ValistChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ValistChecker.cpp index 98794de2eb932..04ec5843b07b4 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ValistChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ValistChecker.cpp @@ -79,19 +79,19 @@ class ValistChecker : public Checker, } PathDiagnosticPieceRef getEndPath(BugReporterContext &BRC, const ExplodedNode *EndPathNode, - BugReport &BR) override { + PathSensitiveBugReport &BR) override { if (!IsLeak) return nullptr; - PathDiagnosticLocation L = PathDiagnosticLocation::createEndOfPath( - EndPathNode, BRC.getSourceManager()); + PathDiagnosticLocation L = + PathDiagnosticLocation::createEndOfPath(EndPathNode); // Do not add the statement itself as a range in case of leak. return std::make_shared(L, BR.getDescription(), false); } PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, BugReporterContext &BRC, - BugReport &BR) override; + PathSensitiveBugReport &BR) override; private: const MemRegion *Reg; @@ -256,7 +256,7 @@ void ValistChecker::reportUninitializedAccess(const MemRegion *VAList, BT_uninitaccess.reset(new BugType(CheckNames[CK_Uninitialized], "Uninitialized va_list", categories::MemoryError)); - auto R = llvm::make_unique(*BT_uninitaccess, Msg, N); + auto R = llvm::make_unique(*BT_uninitaccess, Msg, N); R->markInteresting(VAList); R->addVisitor(llvm::make_unique(VAList)); C.emitReport(std::move(R)); @@ -297,7 +297,7 @@ void ValistChecker::reportLeakedVALists(const RegionVector &LeakedVALists, OS << " " << VariableName; OS << Msg2; - auto R = llvm::make_unique( + auto R = llvm::make_unique( *BT_leakedvalist, OS.str(), N, LocUsedForUniqueing, StartNode->getLocationContext()->getDecl()); R->markInteresting(Reg); @@ -377,7 +377,7 @@ void ValistChecker::checkVAListEndCall(const CallEvent &Call, } PathDiagnosticPieceRef ValistChecker::ValistBugVisitor::VisitNode( - const ExplodedNode *N, BugReporterContext &BRC, BugReport &) { + const ExplodedNode *N, BugReporterContext &BRC, PathSensitiveBugReport &) { ProgramStateRef State = N->getState(); ProgramStateRef StatePrev = N->getFirstPred()->getState(); diff --git a/clang/lib/StaticAnalyzer/Checkers/VforkChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/VforkChecker.cpp index 40d14aa5c7d44..3f8c040536944 100644 --- a/clang/lib/StaticAnalyzer/Checkers/VforkChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/VforkChecker.cpp @@ -132,7 +132,7 @@ void VforkChecker::reportBug(const char *What, CheckerContext &C, if (Details) os << "; " << Details; - auto Report = llvm::make_unique(*BT, os.str(), N); + auto Report = llvm::make_unique(*BT, os.str(), N); // TODO: mark vfork call in BugReportVisitor C.emitReport(std::move(Report)); } diff --git a/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp index 5aa4ff4e57ee2..45859d4b6a471 100644 --- a/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp @@ -146,7 +146,7 @@ void VirtualCallChecker::checkPreCall(const CallEvent &Call, return; } - auto Report = std::make_unique(*BT, OS.str(), N); + auto Report = llvm::make_unique(*BT, OS.str(), N); if (ShowFixIts && !IsPure) { // FIXME: These hints are valid only when the virtual call is made diff --git a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp index 9827b67849c86..2bcfe5d6fb219 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp @@ -71,6 +71,7 @@ using namespace clang; using namespace ento; +using namespace llvm; #define DEBUG_TYPE "BugReporter" @@ -131,7 +132,8 @@ class PathDiagnosticConstruct { public: PathDiagnosticConstruct(const PathDiagnosticConsumer *PDC, - const ExplodedNode *ErrorNode, const BugReport *R); + const ExplodedNode *ErrorNode, + const PathSensitiveBugReport *R); /// \returns the location context associated with the current position in the /// bug path. @@ -202,7 +204,7 @@ class PathDiagnosticBuilder : public BugReporterContext { /// them being the last entities being able to modify it (for example, /// changing interestingness here would cause inconsistencies as to how this /// file and visitors construct diagnostics), hence its const. - const BugReport *R; + const PathSensitiveBugReport *R; /// The leaf of the bug path. This isn't the same as the bug reports error /// node, which refers to the *original* graph, not the bug path. const ExplodedNode *const ErrorNode; @@ -215,12 +217,12 @@ class PathDiagnosticBuilder : public BugReporterContext { /// a PathDiagnosticBuilder able to construct bug reports for different /// consumers. Returns None if no valid report is found. static Optional - findValidReport(ArrayRef &bugReports, + findValidReport(ArrayRef &bugReports, PathSensitiveBugReporter &Reporter); PathDiagnosticBuilder( BugReporterContext BRC, std::unique_ptr BugPath, - BugReport *r, const ExplodedNode *ErrorNode, + PathSensitiveBugReport *r, const ExplodedNode *ErrorNode, std::unique_ptr VisitorsDiagnostics); /// This function is responsible for generating diagnostic pieces that are @@ -262,7 +264,7 @@ class PathDiagnosticBuilder : public BugReporterContext { ExecutionContinues(llvm::raw_string_ostream &os, const PathDiagnosticConstruct &C) const; - const BugReport *getBugReport() const { return R; } + const PathSensitiveBugReport *getBugReport() const { return R; } }; } // namespace @@ -369,7 +371,8 @@ static void removeRedundantMsgs(PathPieces &path) { /// that aren't needed. Return true if afterwards the path contains /// "interesting stuff" which means it shouldn't be pruned from the parent path. static bool removeUnneededCalls(const PathDiagnosticConstruct &C, - PathPieces &pieces, const BugReport *R, + PathPieces &pieces, + const PathSensitiveBugReport *R, bool IsInteresting = false) { bool containsSomethingInteresting = IsInteresting; const unsigned N = pieces.size(); @@ -1262,13 +1265,23 @@ void PathDiagnosticBuilder::generatePathDiagnosticsForNode( } static std::unique_ptr -generateEmptyDiagnosticForReport(const BugReport *R, const SourceManager &SM) { +generateDiagnosticForBasicReport(const BasicBugReport *R) { const BugType &BT = R->getBugType(); return llvm::make_unique( - R->getBugType().getCheckName(), R->getDeclWithIssue(), - R->getBugType().getName(), R->getDescription(), - R->getShortDescription(/*Fallback=*/false), BT.getCategory(), - R->getUniqueingLocation(), R->getUniqueingDecl(), + BT.getCheckName(), R->getDeclWithIssue(), BT.getName(), + R->getDescription(), R->getShortDescription(/*UseFallback=*/false), + BT.getCategory(), R->getUniqueingLocation(), R->getUniqueingDecl(), + llvm::make_unique()); +} + +static std::unique_ptr +generateEmptyDiagnosticForReport(const PathSensitiveBugReport *R, + const SourceManager &SM) { + const BugType &BT = R->getBugType(); + return llvm::make_unique( + BT.getCheckName(), R->getDeclWithIssue(), BT.getName(), + R->getDescription(), R->getShortDescription(/*UseFallback=*/false), + BT.getCategory(), R->getUniqueingLocation(), R->getUniqueingDecl(), findExecutedLines(SM, R->getErrorNode())); } @@ -1908,7 +1921,7 @@ static void updateExecutedLinesWithDiagnosticPieces(PathDiagnostic &PD) { PathDiagnosticConstruct::PathDiagnosticConstruct( const PathDiagnosticConsumer *PDC, const ExplodedNode *ErrorNode, - const BugReport *R) + const PathSensitiveBugReport *R) : Consumer(PDC), CurrentNode(ErrorNode), SM(CurrentNode->getCodeDecl().getASTContext().getSourceManager()), PD(generateEmptyDiagnosticForReport(R, getSourceManager())) { @@ -1917,7 +1930,7 @@ PathDiagnosticConstruct::PathDiagnosticConstruct( PathDiagnosticBuilder::PathDiagnosticBuilder( BugReporterContext BRC, std::unique_ptr BugPath, - BugReport *r, const ExplodedNode *ErrorNode, + PathSensitiveBugReport *r, const ExplodedNode *ErrorNode, std::unique_ptr VisitorsDiagnostics) : BugReporterContext(BRC), BugPath(std::move(BugPath)), R(r), ErrorNode(ErrorNode), @@ -1928,7 +1941,6 @@ PathDiagnosticBuilder::generate(const PathDiagnosticConsumer *PDC) const { PathDiagnosticConstruct Construct(PDC, ErrorNode, R); const SourceManager &SM = getSourceManager(); - const BugReport *R = getBugReport(); const AnalyzerOptions &Opts = getAnalyzerOptions(); StringRef ErrorTag = ErrorNode->getLocation().getTag()->getTagDescription(); @@ -2049,7 +2061,8 @@ void BuiltinBug::anchor() {} // Methods for BugReport and subclasses. //===----------------------------------------------------------------------===// -void BugReport::addVisitor(std::unique_ptr visitor) { +void PathSensitiveBugReport::addVisitor( + std::unique_ptr visitor) { if (!visitor) return; @@ -2064,14 +2077,11 @@ void BugReport::addVisitor(std::unique_ptr visitor) { Callbacks.push_back(std::move(visitor)); } -void BugReport::clearVisitors() { +void PathSensitiveBugReport::clearVisitors() { Callbacks.clear(); } -const Decl *BugReport::getDeclWithIssue() const { - if (DeclWithIssue) - return DeclWithIssue; - +const Decl *PathSensitiveBugReport::getDeclWithIssue() const { const ExplodedNode *N = getErrorNode(); if (!N) return nullptr; @@ -2080,14 +2090,28 @@ const Decl *BugReport::getDeclWithIssue() const { return LC->getStackFrame()->getDecl(); } -void BugReport::Profile(llvm::FoldingSetNodeID& hash) const { +void BasicBugReport::Profile(llvm::FoldingSetNodeID& hash) const { + hash.AddInteger(static_cast(getKind())); + hash.AddPointer(&BT); + hash.AddString(Description); + assert(Location.isValid()); + Location.Profile(hash); + + for (SourceRange range : Ranges) { + if (!range.isValid()) + continue; + hash.AddInteger(range.getBegin().getRawEncoding()); + hash.AddInteger(range.getEnd().getRawEncoding()); + } +} + +void PathSensitiveBugReport::Profile(llvm::FoldingSetNodeID &hash) const { + hash.AddInteger(static_cast(getKind())); hash.AddPointer(&BT); hash.AddString(Description); PathDiagnosticLocation UL = getUniqueingLocation(); if (UL.isValid()) { UL.Profile(hash); - } else if (Location.isValid()) { - Location.Profile(hash); } else { assert(ErrorNode); hash.AddPointer(GetCurrentOrPreviousStmt(ErrorNode)); @@ -2130,8 +2154,8 @@ static void insertToInterestingnessMap( "have, if it was already marked as interesting with a different kind!"); } -void BugReport::markInteresting(SymbolRef sym, - bugreporter::TrackingKind TKind) { +void PathSensitiveBugReport::markInteresting(SymbolRef sym, + bugreporter::TrackingKind TKind) { if (!sym) return; @@ -2141,8 +2165,8 @@ void BugReport::markInteresting(SymbolRef sym, markInteresting(meta->getRegion(), TKind); } -void BugReport::markInteresting(const MemRegion *R, - bugreporter::TrackingKind TKind) { +void PathSensitiveBugReport::markInteresting(const MemRegion *R, + bugreporter::TrackingKind TKind) { if (!R) return; @@ -2153,19 +2177,20 @@ void BugReport::markInteresting(const MemRegion *R, markInteresting(SR->getSymbol(), TKind); } -void BugReport::markInteresting(SVal V, bugreporter::TrackingKind TKind) { +void PathSensitiveBugReport::markInteresting(SVal V, + bugreporter::TrackingKind TKind) { markInteresting(V.getAsRegion(), TKind); markInteresting(V.getAsSymbol(), TKind); } -void BugReport::markInteresting(const LocationContext *LC) { +void PathSensitiveBugReport::markInteresting(const LocationContext *LC) { if (!LC) return; InterestingLocationContexts.insert(LC); } Optional -BugReport::getInterestingnessKind(SVal V) const { +PathSensitiveBugReport::getInterestingnessKind(SVal V) const { auto RKind = getInterestingnessKind(V.getAsRegion()); auto SKind = getInterestingnessKind(V.getAsSymbol()); if (!RKind) @@ -2190,7 +2215,7 @@ BugReport::getInterestingnessKind(SVal V) const { } Optional -BugReport::getInterestingnessKind(SymbolRef sym) const { +PathSensitiveBugReport::getInterestingnessKind(SymbolRef sym) const { if (!sym) return None; // We don't currently consider metadata symbols to be interesting @@ -2202,7 +2227,7 @@ BugReport::getInterestingnessKind(SymbolRef sym) const { } Optional -BugReport::getInterestingnessKind(const MemRegion *R) const { +PathSensitiveBugReport::getInterestingnessKind(const MemRegion *R) const { if (!R) return None; @@ -2216,25 +2241,25 @@ BugReport::getInterestingnessKind(const MemRegion *R) const { return None; } -bool BugReport::isInteresting(SVal V) const { +bool PathSensitiveBugReport::isInteresting(SVal V) const { return getInterestingnessKind(V).hasValue(); } -bool BugReport::isInteresting(SymbolRef sym) const { +bool PathSensitiveBugReport::isInteresting(SymbolRef sym) const { return getInterestingnessKind(sym).hasValue(); } -bool BugReport::isInteresting(const MemRegion *R) const { +bool PathSensitiveBugReport::isInteresting(const MemRegion *R) const { return getInterestingnessKind(R).hasValue(); } -bool BugReport::isInteresting(const LocationContext *LC) const { +bool PathSensitiveBugReport::isInteresting(const LocationContext *LC) const { if (!LC) return false; return InterestingLocationContexts.count(LC); } -const Stmt *BugReport::getStmt() const { +const Stmt *PathSensitiveBugReport::getStmt() const { if (!ErrorNode) return nullptr; @@ -2252,31 +2277,19 @@ const Stmt *BugReport::getStmt() const { return S; } -llvm::iterator_range BugReport::getRanges() const { +ArrayRef +PathSensitiveBugReport::getRanges() const { // If no custom ranges, add the range of the statement corresponding to // the error node. - if (Ranges.empty()) { - if (dyn_cast_or_null(getStmt())) - return llvm::make_range(&ErrorNodeRange, &ErrorNodeRange + 1); - return llvm::make_range(ranges_iterator(), ranges_iterator()); - } + if (Ranges.empty() && isa_and_nonnull(getStmt())) + return ErrorNodeRange; - // User-specified absence of range info. - if (Ranges.size() == 1 && !Ranges.begin()->isValid()) - return llvm::make_range(ranges_iterator(), ranges_iterator()); - - return llvm::make_range(Ranges.begin(), Ranges.end()); + return Ranges; } -PathDiagnosticLocation BugReport::getLocation(const SourceManager &SM) const { - if (ErrorNode) { - assert(!Location.isValid() && - "Either Location or ErrorNode should be specified but not both."); - return PathDiagnosticLocation::createEndOfPath(ErrorNode, SM); - } - - assert(Location.isValid()); - return Location; +PathDiagnosticLocation +PathSensitiveBugReport::getLocation() const { + return PathDiagnosticLocation::createEndOfPath(ErrorNode); } //===----------------------------------------------------------------------===// @@ -2325,7 +2338,7 @@ namespace { class BugPathInfo { public: std::unique_ptr BugPath; - BugReport *Report; + PathSensitiveBugReport *Report; const ExplodedNode *ErrorNode; }; @@ -2341,7 +2354,8 @@ class BugPathGetter { /// Since the getErrorNode() or BugReport refers to the original ExplodedGraph, /// we need to pair it to the error node of the constructed trimmed graph. - using ReportNewNodePair = std::pair; + using ReportNewNodePair = + std::pair; SmallVector ReportNodes; BugPathInfo CurrentBugPath; @@ -2376,7 +2390,7 @@ class BugPathGetter { public: BugPathGetter(const ExplodedGraph *OriginalGraph, - ArrayRef &bugReports); + ArrayRef &bugReports); BugPathInfo *getNextBugPath(); }; @@ -2384,7 +2398,7 @@ class BugPathGetter { } // namespace BugPathGetter::BugPathGetter(const ExplodedGraph *OriginalGraph, - ArrayRef &bugReports) { + ArrayRef &bugReports) { SmallVector Nodes; for (const auto I : bugReports) { assert(I->isValid() && @@ -2403,7 +2417,7 @@ BugPathGetter::BugPathGetter(const ExplodedGraph *OriginalGraph, // in the new graph. llvm::SmallPtrSet RemainingNodes; - for (BugReport *Report : bugReports) { + for (PathSensitiveBugReport *Report : bugReports) { const ExplodedNode *NewNode = ForwardMap.lookup(Report->getErrorNode()); assert(NewNode && "Failed to construct a trimmed graph that contains this error " @@ -2591,11 +2605,11 @@ static void CompactMacroExpandedPieces(PathPieces &path, /// Notes associated with {@code ErrorNode} are generated using /// {@code getEndPath}, and the rest are generated with {@code VisitNode}. static std::unique_ptr -generateVisitorsDiagnostics(BugReport *R, const ExplodedNode *ErrorNode, +generateVisitorsDiagnostics(PathSensitiveBugReport *R, + const ExplodedNode *ErrorNode, BugReporterContext &BRC) { - std::unique_ptr Notes = - llvm::make_unique(); - BugReport::VisitorList visitors; + auto Notes = llvm::make_unique(); + PathSensitiveBugReport::VisitorList visitors; // Run visitors on all nodes starting from the node *before* the last one. // The last node is reserved for notes generated with {@code getEndPath}. @@ -2645,15 +2659,15 @@ generateVisitorsDiagnostics(BugReport *R, const ExplodedNode *ErrorNode, return Notes; } -Optional -PathDiagnosticBuilder::findValidReport(ArrayRef &bugReports, - PathSensitiveBugReporter &Reporter) { +Optional PathDiagnosticBuilder::findValidReport( + ArrayRef &bugReports, + PathSensitiveBugReporter &Reporter) { BugPathGetter BugGraph(&Reporter.getGraph(), bugReports); while (BugPathInfo *BugPath = BugGraph.getNextBugPath()) { // Find the BugReport with the original location. - BugReport *R = BugPath->Report; + PathSensitiveBugReport *R = BugPath->Report; assert(R && "No original report found for sliced graph."); assert(R->isValid() && "Report selected by trimmed graph marked invalid."); const ExplodedNode *ErrorNode = BugPath->ErrorNode; @@ -2699,7 +2713,7 @@ PathDiagnosticBuilder::findValidReport(ArrayRef &bugReports, std::unique_ptr PathSensitiveBugReporter::generatePathDiagnostics( ArrayRef consumers, - ArrayRef &bugReports) { + ArrayRef &bugReports) { assert(!bugReports.empty()); auto Out = llvm::make_unique(); @@ -2719,24 +2733,7 @@ PathSensitiveBugReporter::generatePathDiagnostics( } void BugReporter::emitReport(std::unique_ptr R) { - if (const ExplodedNode *E = R->getErrorNode()) { - // An error node must either be a sink or have a tag, otherwise - // it could get reclaimed before the path diagnostic is created. - assert((E->isSink() || E->getLocation().getTag()) && - "Error node must either be a sink or have a tag"); - - const AnalysisDeclContext *DeclCtx = - E->getLocationContext()->getAnalysisDeclContext(); - // The source of autosynthesized body can be handcrafted AST or a model - // file. The locations from handcrafted ASTs have no valid source locations - // and have to be discarded. Locations from model files should be preserved - // for processing and reporting. - if (DeclCtx->isBodyAutosynthesized() && - !DeclCtx->isBodyAutosynthesizedFromModelFile()) - return; - } - - bool ValidSourceLoc = R->getLocation(getSourceManager()).isValid(); + bool ValidSourceLoc = R->getLocation().isValid(); assert(ValidSourceLoc); // If we mess up in a release build, we'd still prefer to just drop the bug // instead of trying to go on. @@ -2759,6 +2756,28 @@ void BugReporter::emitReport(std::unique_ptr R) { EQ->AddReport(std::move(R)); } +void PathSensitiveBugReporter::emitReport(std::unique_ptr R) { + if (auto PR = dyn_cast(R.get())) + if (const ExplodedNode *E = PR->getErrorNode()) { + // An error node must either be a sink or have a tag, otherwise + // it could get reclaimed before the path diagnostic is created. + assert((E->isSink() || E->getLocation().getTag()) && + "Error node must either be a sink or have a tag"); + + const AnalysisDeclContext *DeclCtx = + E->getLocationContext()->getAnalysisDeclContext(); + // The source of autosynthesized body can be handcrafted AST or a model + // file. The locations from handcrafted ASTs have no valid source + // locations and have to be discarded. Locations from model files should + // be preserved for processing and reporting. + if (DeclCtx->isBodyAutosynthesized() && + !DeclCtx->isBodyAutosynthesizedFromModelFile()) + return; + } + + BugReporter::emitReport(std::move(R)); +} + //===----------------------------------------------------------------------===// // Emitting reports in equivalence classes. //===----------------------------------------------------------------------===// @@ -2775,9 +2794,8 @@ struct FRIEC_WLItem { } // namespace -static BugReport * -FindReportInEquivalenceClass(BugReportEquivClass& EQ, - SmallVectorImpl &bugReports) { +BugReport *PathSensitiveBugReporter::findReportInEquivalenceClass( + BugReportEquivClass &EQ, SmallVectorImpl &bugReports) { BugReportEquivClass::iterator I = EQ.begin(), E = EQ.end(); assert(I != E); const BugType& BT = I->getBugType(); @@ -2788,10 +2806,9 @@ FindReportInEquivalenceClass(BugReportEquivClass& EQ, if (!BT.isSuppressOnSink()) { BugReport *R = &*I; for (auto &I : EQ) { - const ExplodedNode *N = I.getErrorNode(); - if (N) { - R = &I; - bugReports.push_back(R); + if (auto *PR = dyn_cast(&I)) { + R = PR; + bugReports.push_back(PR); } } return R; @@ -2806,19 +2823,20 @@ FindReportInEquivalenceClass(BugReportEquivClass& EQ, BugReport *exampleReport = nullptr; for (; I != E; ++I) { - const ExplodedNode *errorNode = I->getErrorNode(); - - if (!errorNode) + auto *R = dyn_cast(&*I); + if (!R) continue; + + const ExplodedNode *errorNode = R->getErrorNode(); if (errorNode->isSink()) { llvm_unreachable( "BugType::isSuppressSink() should not be 'true' for sink end nodes"); } // No successors? By definition this nodes isn't post-dominated by a sink. if (errorNode->succ_empty()) { - bugReports.push_back(&*I); + bugReports.push_back(R); if (!exampleReport) - exampleReport = &*I; + exampleReport = R; continue; } @@ -2851,9 +2869,9 @@ FindReportInEquivalenceClass(BugReportEquivClass& EQ, if (Succ->succ_empty()) { // If we found an end-of-path node that is not a sink. if (!Succ->isSink()) { - bugReports.push_back(&*I); + bugReports.push_back(R); if (!exampleReport) - exampleReport = &*I; + exampleReport = R; WL.clear(); break; } @@ -2884,7 +2902,7 @@ FindReportInEquivalenceClass(BugReportEquivClass& EQ, void BugReporter::FlushReport(BugReportEquivClass& EQ) { SmallVector bugReports; - BugReport *report = FindReportInEquivalenceClass(EQ, bugReports); + BugReport *report = findReportInEquivalenceClass(EQ, bugReports); if (!report) return; @@ -2899,9 +2917,9 @@ void BugReporter::FlushReport(BugReportEquivClass& EQ) { // If the path is empty, generate a single step path with the location // of the issue. if (PD->path.empty()) { - PathDiagnosticLocation L = report->getLocation(getSourceManager()); + PathDiagnosticLocation L = report->getLocation(); auto piece = llvm::make_unique( - L, report->getDescription()); + L, report->getDescription()); for (SourceRange Range : report->getRanges()) piece->addRange(Range); PD->setEndOfPath(std::move(piece)); @@ -3014,16 +3032,24 @@ findExecutedLines(const SourceManager &SM, const ExplodedNode *N) { std::unique_ptr BugReporter::generateDiagnosticForConsumerMap( - BugReport *report, ArrayRef consumers, + BugReport *exampleReport, ArrayRef consumers, ArrayRef bugReports) { + auto *basicReport = cast(exampleReport); + auto Out = llvm::make_unique(); + for (auto *Consumer : consumers) + (*Out)[Consumer] = generateDiagnosticForBasicReport(basicReport); + return Out; +} - if (!report->isPathSensitive()) { - auto Out = llvm::make_unique(); - for (auto *Consumer : consumers) - (*Out)[Consumer] = generateEmptyDiagnosticForReport(report, - getSourceManager()); - return Out; - } +std::unique_ptr +PathSensitiveBugReporter::generateDiagnosticForConsumerMap( + BugReport *exampleReport, ArrayRef consumers, + ArrayRef bugReports) { + std::vector BasicBugReports; + std::vector PathSensitiveBugReports; + if (isa(exampleReport)) + return BugReporter::generateDiagnosticForConsumerMap(exampleReport, + consumers, bugReports); // Generate the full path sensitive diagnostic, using the generation scheme // specified by the PathDiagnosticConsumer. Note that we have to generate @@ -3031,8 +3057,13 @@ BugReporter::generateDiagnosticForConsumerMap( // the BugReporterVisitors may mark this bug as a false positive. assert(!bugReports.empty()); MaxBugClassSize.updateMax(bugReports.size()); - std::unique_ptr Out = - generatePathDiagnostics(consumers, bugReports); + + // Avoid copying the whole array because there may be a lot of reports. + ArrayRef convertedArrayOfReports( + reinterpret_cast(&*bugReports.begin()), + reinterpret_cast(&*bugReports.end())); + std::unique_ptr Out = generatePathDiagnostics( + consumers, convertedArrayOfReports); if (Out->empty()) return Out; @@ -3067,7 +3098,7 @@ void BugReporter::EmitBasicReport(const Decl *DeclWithIssue, ArrayRef Fixits) { // 'BT' is owned by BugReporter. BugType *BT = getBugTypeForName(CheckName, name, category); - auto R = llvm::make_unique(*BT, str, Loc); + auto R = llvm::make_unique(*BT, str, Loc); R->setDeclWithIssue(DeclWithIssue); for (const auto &SR : Ranges) R->addRange(SR); diff --git a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp index e628aeae7a1a2..715771fb38ee1 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp @@ -215,7 +215,7 @@ getConcreteIntegerValue(const Expr *CondVarExpr, const ExplodedNode *N) { static bool isVarAnInterestingCondition(const Expr *CondVarExpr, const ExplodedNode *N, - const BugReport *B) { + const PathSensitiveBugReport *B) { // Even if this condition is marked as interesting, it isn't *that* // interesting if it didn't happen in a nested stackframe, the user could just // follow the arrows. @@ -230,7 +230,7 @@ static bool isVarAnInterestingCondition(const Expr *CondVarExpr, } static bool isInterestingExpr(const Expr *E, const ExplodedNode *N, - const BugReport *B) { + const PathSensitiveBugReport *B) { if (Optional V = getSValForVar(E, N)) return B->getInterestingnessKind(*V).hasValue(); return false; @@ -296,19 +296,20 @@ static bool wasRegionOfInterestModifiedAt(const SubRegion *RegionOfInterest, PathDiagnosticPieceRef BugReporterVisitor::getEndPath(BugReporterContext &, const ExplodedNode *, - BugReport &) { + PathSensitiveBugReport &) { return nullptr; } void BugReporterVisitor::finalizeVisitor(BugReporterContext &, - const ExplodedNode *, BugReport &) {} + const ExplodedNode *, + PathSensitiveBugReport &) {} PathDiagnosticPieceRef BugReporterVisitor::getDefaultEndPath(const BugReporterContext &BRC, const ExplodedNode *EndPathNode, - const BugReport &BR) { - PathDiagnosticLocation L = PathDiagnosticLocation::createEndOfPath( - EndPathNode, BRC.getSourceManager()); + const PathSensitiveBugReport &BR) { + PathDiagnosticLocation L = + PathDiagnosticLocation::createEndOfPath(EndPathNode); const auto &Ranges = BR.getRanges(); @@ -376,7 +377,7 @@ class NoStoreFuncVisitor final : public BugReporterVisitor { PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, BugReporterContext &BR, - BugReport &R) override; + PathSensitiveBugReport &R) override; private: /// Attempts to find the region of interest in a given record decl, @@ -410,10 +411,10 @@ class NoStoreFuncVisitor final : public BugReporterVisitor { /// \return Diagnostics piece for region not modified in the current function, /// if it decides to emit one. PathDiagnosticPieceRef - maybeEmitNote(BugReport &R, const CallEvent &Call, const ExplodedNode *N, - const RegionVector &FieldChain, const MemRegion *MatchedRegion, - StringRef FirstElement, bool FirstIsReferenceType, - unsigned IndirectionLevel); + maybeEmitNote(PathSensitiveBugReport &R, const CallEvent &Call, + const ExplodedNode *N, const RegionVector &FieldChain, + const MemRegion *MatchedRegion, StringRef FirstElement, + bool FirstIsReferenceType, unsigned IndirectionLevel); /// Pretty-print region \p MatchedRegion to \p os. /// \return Whether printing succeeded. @@ -542,9 +543,9 @@ NoStoreFuncVisitor::findRegionOfInterestInRecord( return None; } -PathDiagnosticPieceRef NoStoreFuncVisitor::VisitNode(const ExplodedNode *N, - BugReporterContext &BR, - BugReport &R) { +PathDiagnosticPieceRef +NoStoreFuncVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &BR, + PathSensitiveBugReport &R) { const LocationContext *Ctx = N->getLocationContext(); const StackFrameContext *SCtx = Ctx->getStackFrame(); @@ -656,7 +657,7 @@ static llvm::StringLiteral WillBeUsedForACondition = ", which participates in a condition later"; PathDiagnosticPieceRef NoStoreFuncVisitor::maybeEmitNote( - BugReport &R, const CallEvent &Call, const ExplodedNode *N, + PathSensitiveBugReport &R, const CallEvent &Call, const ExplodedNode *N, const RegionVector &FieldChain, const MemRegion *MatchedRegion, StringRef FirstElement, bool FirstIsReferenceType, unsigned IndirectionLevel) { @@ -803,7 +804,7 @@ class MacroNullReturnSuppressionVisitor final : public BugReporterVisitor { PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, BugReporterContext &BRC, - BugReport &BR) override { + PathSensitiveBugReport &BR) override { if (WasModified) return nullptr; @@ -829,7 +830,7 @@ class MacroNullReturnSuppressionVisitor final : public BugReporterVisitor { static void addMacroVisitorIfNecessary( const ExplodedNode *N, const MemRegion *R, - bool EnableNullFPSuppression, BugReport &BR, + bool EnableNullFPSuppression, PathSensitiveBugReport &BR, const SVal V) { AnalyzerOptions &Options = N->getState()->getAnalysisManager().options; if (EnableNullFPSuppression && @@ -923,7 +924,7 @@ class ReturnVisitor : public BugReporterVisitor { /// the statement is a call that was inlined, we add the visitor to the /// bug report, so it can print a note later. static void addVisitorIfNecessary(const ExplodedNode *Node, const Stmt *S, - BugReport &BR, + PathSensitiveBugReport &BR, bool InEnableNullFPSuppression, bugreporter::TrackingKind TKind) { if (!CallEvent::isCallStmt(S)) @@ -1003,7 +1004,7 @@ class ReturnVisitor : public BugReporterVisitor { PathDiagnosticPieceRef visitNodeInitial(const ExplodedNode *N, BugReporterContext &BRC, - BugReport &BR) { + PathSensitiveBugReport &BR) { // Only print a message at the interesting return statement. if (N->getLocationContext() != CalleeSFC) return nullptr; @@ -1130,10 +1131,8 @@ class ReturnVisitor : public BugReporterVisitor { PathDiagnosticPieceRef visitNodeMaybeUnsuppress(const ExplodedNode *N, BugReporterContext &BRC, - BugReport &BR) { -#ifndef NDEBUG + PathSensitiveBugReport &BR) { assert(Options.ShouldAvoidSuppressingNullArgumentPaths); -#endif // Are we at the entry node for this call? Optional CE = N->getLocationAs(); @@ -1179,7 +1178,7 @@ class ReturnVisitor : public BugReporterVisitor { PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, BugReporterContext &BRC, - BugReport &BR) override { + PathSensitiveBugReport &BR) override { switch (Mode) { case Initial: return visitNodeInitial(N, BRC, BR); @@ -1193,7 +1192,7 @@ class ReturnVisitor : public BugReporterVisitor { } void finalizeVisitor(BugReporterContext &, const ExplodedNode *, - BugReport &BR) override { + PathSensitiveBugReport &BR) override { if (EnableNullFPSuppression && ShouldInvalidate) BR.markInvalid(ReturnVisitor::getTag(), CalleeSFC); } @@ -1316,9 +1315,8 @@ static void showBRParamDiagnostics(llvm::raw_svector_ostream& os, } /// Show default diagnostics for storing bad region. -static void showBRDefaultDiagnostics(llvm::raw_svector_ostream& os, - const MemRegion *R, - SVal V) { +static void showBRDefaultDiagnostics(llvm::raw_svector_ostream &os, + const MemRegion *R, SVal V) { if (V.getAs()) { bool b = false; if (R->isBoundable()) { @@ -1363,7 +1361,8 @@ static void showBRDefaultDiagnostics(llvm::raw_svector_ostream& os, PathDiagnosticPieceRef FindLastStoreBRVisitor::VisitNode(const ExplodedNode *Succ, - BugReporterContext &BRC, BugReport &BR) { + BugReporterContext &BRC, + PathSensitiveBugReport &BR) { if (Satisfied) return nullptr; @@ -1540,9 +1539,8 @@ bool TrackConstraintBRVisitor::isUnderconstrained(const ExplodedNode *N) const { return (bool)N->getState()->assume(Constraint, !Assumption); } -PathDiagnosticPieceRef -TrackConstraintBRVisitor::VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, BugReport &) { +PathDiagnosticPieceRef TrackConstraintBRVisitor::VisitNode( + const ExplodedNode *N, BugReporterContext &BRC, PathSensitiveBugReport &) { const ExplodedNode *PrevN = N->getFirstPred(); if (IsSatisfied) return nullptr; @@ -1620,8 +1618,10 @@ const char *SuppressInlineDefensiveChecksVisitor::getTag() { return "IDCVisitor"; } -PathDiagnosticPieceRef SuppressInlineDefensiveChecksVisitor::VisitNode( - const ExplodedNode *Succ, BugReporterContext &BRC, BugReport &BR) { +PathDiagnosticPieceRef +SuppressInlineDefensiveChecksVisitor::VisitNode(const ExplodedNode *Succ, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) { const ExplodedNode *Pred = Succ->getFirstPred(); if (IsSatisfied) return nullptr; @@ -1722,7 +1722,7 @@ class TrackControlDependencyCondBRVisitor final : public BugReporterVisitor { PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, BugReporterContext &BRC, - BugReport &BR) override; + PathSensitiveBugReport &BR) override; }; } // end of anonymous namespace @@ -1778,8 +1778,10 @@ static bool isAssertlikeBlock(const CFGBlock *B, ASTContext &Context) { return false; } -PathDiagnosticPieceRef TrackControlDependencyCondBRVisitor::VisitNode( - const ExplodedNode *N, BugReporterContext &BRC, BugReport &BR) { +PathDiagnosticPieceRef +TrackControlDependencyCondBRVisitor::VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) { // We can only reason about control dependencies within the same stack frame. if (Origin->getStackFrame() != N->getStackFrame()) return nullptr; @@ -1925,7 +1927,8 @@ static const ExplodedNode* findNodeForExpression(const ExplodedNode *N, } bool bugreporter::trackExpressionValue(const ExplodedNode *InputNode, - const Expr *E, BugReport &report, + const Expr *E, + PathSensitiveBugReport &report, bugreporter::TrackingKind TKind, bool EnableNullFPSuppression) { @@ -2082,9 +2085,9 @@ const Expr *NilReceiverBRVisitor::getNilReceiver(const Stmt *S, return nullptr; } -PathDiagnosticPieceRef NilReceiverBRVisitor::VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) { +PathDiagnosticPieceRef +NilReceiverBRVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &BRC, + PathSensitiveBugReport &BR) { Optional P = N->getLocationAs(); if (!P) return nullptr; @@ -2127,9 +2130,9 @@ PathDiagnosticPieceRef NilReceiverBRVisitor::VisitNode(const ExplodedNode *N, /// to make all PathDiagnosticPieces created by this visitor. const char *ConditionBRVisitor::getTag() { return "ConditionBRVisitor"; } -PathDiagnosticPieceRef ConditionBRVisitor::VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) { +PathDiagnosticPieceRef +ConditionBRVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &BRC, + PathSensitiveBugReport &BR) { auto piece = VisitNodeImpl(N, BRC, BR); if (piece) { piece->setTag(getTag()); @@ -2141,7 +2144,8 @@ PathDiagnosticPieceRef ConditionBRVisitor::VisitNode(const ExplodedNode *N, PathDiagnosticPieceRef ConditionBRVisitor::VisitNodeImpl(const ExplodedNode *N, - BugReporterContext &BRC, BugReport &BR) { + BugReporterContext &BRC, + PathSensitiveBugReport &BR) { ProgramPoint ProgPoint = N->getLocation(); const std::pair &Tags = ExprEngine::geteagerlyAssumeBinOpBifurcationTags(); @@ -2179,7 +2183,8 @@ ConditionBRVisitor::VisitNodeImpl(const ExplodedNode *N, PathDiagnosticPieceRef ConditionBRVisitor::VisitTerminator( const Stmt *Term, const ExplodedNode *N, const CFGBlock *srcBlk, - const CFGBlock *dstBlk, BugReport &R, BugReporterContext &BRC) { + const CFGBlock *dstBlk, PathSensitiveBugReport &R, + BugReporterContext &BRC) { const Expr *Cond = nullptr; // In the code below, Term is a CFG terminator and Cond is a branch condition @@ -2236,8 +2241,8 @@ PathDiagnosticPieceRef ConditionBRVisitor::VisitTerminator( PathDiagnosticPieceRef ConditionBRVisitor::VisitTrueTest(const Expr *Cond, BugReporterContext &BRC, - BugReport &R, const ExplodedNode *N, - bool TookTrue) { + PathSensitiveBugReport &R, + const ExplodedNode *N, bool TookTrue) { ProgramStateRef CurrentState = N->getState(); ProgramStateRef PrevState = N->getFirstPred()->getState(); const LocationContext *LCtx = N->getLocationContext(); @@ -2307,7 +2312,7 @@ bool ConditionBRVisitor::patternMatch(const Expr *Ex, const Expr *ParentEx, raw_ostream &Out, BugReporterContext &BRC, - BugReport &report, + PathSensitiveBugReport &report, const ExplodedNode *N, Optional &prunable, bool IsSameFieldName) { @@ -2392,7 +2397,8 @@ bool ConditionBRVisitor::patternMatch(const Expr *Ex, PathDiagnosticPieceRef ConditionBRVisitor::VisitTrueTest( const Expr *Cond, const BinaryOperator *BExpr, BugReporterContext &BRC, - BugReport &R, const ExplodedNode *N, bool TookTrue, bool IsAssuming) { + PathSensitiveBugReport &R, const ExplodedNode *N, bool TookTrue, + bool IsAssuming) { bool shouldInvert = false; Optional shouldPrune; @@ -2511,7 +2517,7 @@ PathDiagnosticPieceRef ConditionBRVisitor::VisitTrueTest( PathDiagnosticPieceRef ConditionBRVisitor::VisitConditionVariable( StringRef LhsString, const Expr *CondVarExpr, BugReporterContext &BRC, - BugReport &report, const ExplodedNode *N, bool TookTrue) { + PathSensitiveBugReport &report, const ExplodedNode *N, bool TookTrue) { // FIXME: If there's already a constraint tracker for this variable, // we shouldn't emit anything here (c.f. the double note in // test/Analysis/inlining/path-notes.c) @@ -2538,7 +2544,8 @@ PathDiagnosticPieceRef ConditionBRVisitor::VisitConditionVariable( PathDiagnosticPieceRef ConditionBRVisitor::VisitTrueTest( const Expr *Cond, const DeclRefExpr *DRE, BugReporterContext &BRC, - BugReport &report, const ExplodedNode *N, bool TookTrue, bool IsAssuming) { + PathSensitiveBugReport &report, const ExplodedNode *N, bool TookTrue, + bool IsAssuming) { const auto *VD = dyn_cast(DRE->getDecl()); if (!VD) return nullptr; @@ -2573,7 +2580,8 @@ PathDiagnosticPieceRef ConditionBRVisitor::VisitTrueTest( PathDiagnosticPieceRef ConditionBRVisitor::VisitTrueTest( const Expr *Cond, const MemberExpr *ME, BugReporterContext &BRC, - BugReport &report, const ExplodedNode *N, bool TookTrue, bool IsAssuming) { + PathSensitiveBugReport &report, const ExplodedNode *N, bool TookTrue, + bool IsAssuming) { SmallString<256> Buf; llvm::raw_svector_ostream Out(Buf); @@ -2659,7 +2667,8 @@ bool ConditionBRVisitor::isPieceMessageGeneric( //===----------------------------------------------------------------------===// void LikelyFalsePositiveSuppressionBRVisitor::finalizeVisitor( - BugReporterContext &BRC, const ExplodedNode *N, BugReport &BR) { + BugReporterContext &BRC, const ExplodedNode *N, + PathSensitiveBugReport &BR) { // Here we suppress false positives coming from system headers. This list is // based on known issues. const AnalyzerOptions &Options = BRC.getAnalyzerOptions(); @@ -2730,7 +2739,7 @@ void LikelyFalsePositiveSuppressionBRVisitor::finalizeVisitor( // Skip reports within the sys/queue.h macros as we do not have the ability to // reason about data structure shapes. const SourceManager &SM = BRC.getSourceManager(); - FullSourceLoc Loc = BR.getLocation(SM).asLocation(); + FullSourceLoc Loc = BR.getLocation().asLocation(); while (Loc.isMacroID()) { Loc = Loc.getSpellingLoc(); if (SM.getFilename(Loc).endswith("sys/queue.h")) { @@ -2744,9 +2753,9 @@ void LikelyFalsePositiveSuppressionBRVisitor::finalizeVisitor( // Implementation of UndefOrNullArgVisitor. //===----------------------------------------------------------------------===// -PathDiagnosticPieceRef UndefOrNullArgVisitor::VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) { +PathDiagnosticPieceRef +UndefOrNullArgVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &BRC, + PathSensitiveBugReport &BR) { ProgramStateRef State = N->getState(); ProgramPoint ProgLoc = N->getLocation(); @@ -2802,7 +2811,8 @@ FalsePositiveRefutationBRVisitor::FalsePositiveRefutationBRVisitor() : Constraints(ConstraintRangeTy::Factory().getEmptyMap()) {} void FalsePositiveRefutationBRVisitor::finalizeVisitor( - BugReporterContext &BRC, const ExplodedNode *EndPathNode, BugReport &BR) { + BugReporterContext &BRC, const ExplodedNode *EndPathNode, + PathSensitiveBugReport &BR) { // Collect new constraints VisitNode(EndPathNode, BRC, BR); @@ -2837,9 +2847,8 @@ void FalsePositiveRefutationBRVisitor::finalizeVisitor( BR.markInvalid("Infeasible constraints", EndPathNode->getLocationContext()); } -PathDiagnosticPieceRef -FalsePositiveRefutationBRVisitor::VisitNode(const ExplodedNode *N, - BugReporterContext &, BugReport &) { +PathDiagnosticPieceRef FalsePositiveRefutationBRVisitor::VisitNode( + const ExplodedNode *N, BugReporterContext &, PathSensitiveBugReport &) { // Collect new constraints const ConstraintRangeTy &NewCs = N->getState()->get(); ConstraintRangeTy::Factory &CF = @@ -2875,7 +2884,7 @@ void TagVisitor::Profile(llvm::FoldingSetNodeID &ID) const { PathDiagnosticPieceRef TagVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &BRC, - BugReport &R) { + PathSensitiveBugReport &R) { ProgramPoint PP = N->getLocation(); const NoteTag *T = dyn_cast_or_null(PP.getTag()); if (!T) diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp index c9cc8a648386a..dd85e0db8b1e4 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -3003,9 +3003,13 @@ struct DOTGraphTraits : public DefaultDOTGraphTraits { llvm::make_range(BR.EQClasses_begin(), BR.EQClasses_end()); for (const auto &EQ : EQClasses) { - for (const BugReport &Report : EQ) { - if (Report.getErrorNode()->getState() == N->getState() && - Report.getErrorNode()->getLocation() == N->getLocation()) + for (const BugReport &R : EQ) { + const auto *PR = dyn_cast(&R); + if (!PR) + continue; + const ExplodedNode *EN = PR->getErrorNode(); + if (EN->getState() == N->getState() && + EN->getLocation() == N->getLocation()) return true; } } @@ -3130,8 +3134,11 @@ std::string ExprEngine::DumpGraph(bool trim, StringRef Filename) { // Iterate through the reports and get their nodes. for (BugReporter::EQClasses_iterator EI = BR.EQClasses_begin(), EE = BR.EQClasses_end(); EI != EE; ++EI) { - const auto *N = const_cast(EI->begin()->getErrorNode()); - if (N) Src.push_back(N); + const auto *R = dyn_cast(&*EI->begin()); + if (!R) + continue; + const auto *N = const_cast(R->getErrorNode()); + Src.push_back(N); } return DumpGraph(Src, Filename); } else { diff --git a/clang/lib/StaticAnalyzer/Core/PathDiagnostic.cpp b/clang/lib/StaticAnalyzer/Core/PathDiagnostic.cpp index 2d3b50082c7dc..f049a1c4120b4 100644 --- a/clang/lib/StaticAnalyzer/Core/PathDiagnostic.cpp +++ b/clang/lib/StaticAnalyzer/Core/PathDiagnostic.cpp @@ -850,11 +850,12 @@ const Stmt *PathDiagnosticLocation::getNextStmt(const ExplodedNode *N) { } PathDiagnosticLocation - PathDiagnosticLocation::createEndOfPath(const ExplodedNode *N, - const SourceManager &SM) { +PathDiagnosticLocation::createEndOfPath(const ExplodedNode *N) { assert(N && "Cannot create a location with a null node."); const Stmt *S = getStmt(N); const LocationContext *LC = N->getLocationContext(); + SourceManager &SM = + N->getState()->getStateManager().getContext().getSourceManager(); if (!S) { // If this is an implicit call, return the implicit call point location. From decdf639c12a19e782d7543762fd7ad2e26575b4 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Mon, 9 Sep 2019 20:34:44 +0000 Subject: [PATCH 148/181] [analyzer] NFC: Simplify bug report equivalence classes to not be ilists. Use a vector of unique pointers instead. Differential Revision: https://reviews.llvm.org/D67024 llvm-svn: 371451 (cherry picked from commit 589273bebd44c3fcc09d2ad87b259dcaff30d299) --- .../Core/BugReporter/BugReporter.h | 23 +++++++------------ clang/lib/StaticAnalyzer/Core/BugReporter.cpp | 16 ++++++------- clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 7 +++--- 3 files changed, 19 insertions(+), 27 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h index 0e114f32afdf1..dc811ff6c70ab 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h @@ -72,7 +72,7 @@ using DiagnosticForConsumerMapTy = /// This class provides an interface through which checkers can create /// individual bug reports. -class BugReport : public llvm::ilist_node { +class BugReport { public: enum class Kind { Basic, PathSensitive }; @@ -465,28 +465,21 @@ class BugReportEquivClass : public llvm::FoldingSetNode { friend class BugReporter; /// List of *owned* BugReport objects. - llvm::ilist Reports; + llvm::SmallVector, 4> Reports; - void AddReport(std::unique_ptr R) { - Reports.push_back(R.release()); + void AddReport(std::unique_ptr &&R) { + Reports.push_back(std::move(R)); } public: BugReportEquivClass(std::unique_ptr R) { AddReport(std::move(R)); } + ArrayRef> getReports() const { return Reports; } + void Profile(llvm::FoldingSetNodeID& ID) const { assert(!Reports.empty()); - Reports.front().Profile(ID); + Reports.front()->Profile(ID); } - - using iterator = llvm::ilist::iterator; - using const_iterator = llvm::ilist::const_iterator; - - iterator begin() { return Reports.begin(); } - iterator end() { return Reports.end(); } - - const_iterator begin() const { return Reports.begin(); } - const_iterator end() const { return Reports.end(); } }; //===----------------------------------------------------------------------===// @@ -573,7 +566,7 @@ class BugReporter { virtual BugReport * findReportInEquivalenceClass(BugReportEquivClass &eqClass, SmallVectorImpl &bugReports) { - return &*eqClass.begin(); + return eqClass.getReports()[0].get(); } protected: diff --git a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp index 2bcfe5d6fb219..2d51b7d2135d4 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp @@ -2796,17 +2796,15 @@ struct FRIEC_WLItem { BugReport *PathSensitiveBugReporter::findReportInEquivalenceClass( BugReportEquivClass &EQ, SmallVectorImpl &bugReports) { - BugReportEquivClass::iterator I = EQ.begin(), E = EQ.end(); - assert(I != E); - const BugType& BT = I->getBugType(); - // If we don't need to suppress any of the nodes because they are // post-dominated by a sink, simply add all the nodes in the equivalence class // to 'Nodes'. Any of the reports will serve as a "representative" report. + assert(EQ.getReports().size() > 0); + const BugType& BT = EQ.getReports()[0]->getBugType(); if (!BT.isSuppressOnSink()) { - BugReport *R = &*I; - for (auto &I : EQ) { - if (auto *PR = dyn_cast(&I)) { + BugReport *R = EQ.getReports()[0].get(); + for (auto &J : EQ.getReports()) { + if (auto *PR = dyn_cast(J.get())) { R = PR; bugReports.push_back(PR); } @@ -2822,8 +2820,8 @@ BugReport *PathSensitiveBugReporter::findReportInEquivalenceClass( // stack for very long paths. BugReport *exampleReport = nullptr; - for (; I != E; ++I) { - auto *R = dyn_cast(&*I); + for (const auto &I: EQ.getReports()) { + auto *R = dyn_cast(I.get()); if (!R) continue; diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp index dd85e0db8b1e4..e25401e240a2c 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -3003,8 +3003,8 @@ struct DOTGraphTraits : public DefaultDOTGraphTraits { llvm::make_range(BR.EQClasses_begin(), BR.EQClasses_end()); for (const auto &EQ : EQClasses) { - for (const BugReport &R : EQ) { - const auto *PR = dyn_cast(&R); + for (const auto &I : EQ.getReports()) { + const auto *PR = dyn_cast(I.get()); if (!PR) continue; const ExplodedNode *EN = PR->getErrorNode(); @@ -3134,7 +3134,8 @@ std::string ExprEngine::DumpGraph(bool trim, StringRef Filename) { // Iterate through the reports and get their nodes. for (BugReporter::EQClasses_iterator EI = BR.EQClasses_begin(), EE = BR.EQClasses_end(); EI != EE; ++EI) { - const auto *R = dyn_cast(&*EI->begin()); + const auto *R = + dyn_cast(EI->getReports()[0].get()); if (!R) continue; const auto *N = const_cast(R->getErrorNode()); From 707c3f531792e5600b84917587e2e13c0e0695d2 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Wed, 11 Sep 2019 20:54:17 +0000 Subject: [PATCH 149/181] [analyzer] NFC: Re-implement stack hints as a side map in BugReport. That's one of the few random entities in the PathDiagnostic interface that are specific to the Static Analyzer. By moving them out we could let everybody use path diagnostics without linking against Static Analyzer. Differential Revision: https://reviews.llvm.org/D67381 llvm-svn: 371658 (cherry picked from commit 8535b8ecf2913b1a53571624da04890174381afe) --- .../Core/BugReporter/BugReporter.h | 72 ++++++++++++++ .../Core/BugReporter/PathDiagnostic.h | 66 +------------ .../DeleteWithNonVirtualDtorChecker.cpp | 3 +- .../Checkers/DynamicTypeChecker.cpp | 3 +- .../Checkers/DynamicTypePropagation.cpp | 3 +- .../Checkers/InnerPointerChecker.cpp | 3 +- .../StaticAnalyzer/Checkers/MallocChecker.cpp | 35 +++---- .../Checkers/NullabilityChecker.cpp | 3 +- clang/lib/StaticAnalyzer/Core/BugReporter.cpp | 99 +++++++++++++++---- .../StaticAnalyzer/Core/PathDiagnostic.cpp | 65 ------------ 10 files changed, 178 insertions(+), 174 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h index dc811ff6c70ab..75f939e1be5e3 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h @@ -70,6 +70,50 @@ class SValBuilder; using DiagnosticForConsumerMapTy = llvm::DenseMap>; +/// Interface for classes constructing Stack hints. +/// +/// If a PathDiagnosticEvent occurs in a different frame than the final +/// diagnostic the hints can be used to summarize the effect of the call. +class StackHintGenerator { +public: + virtual ~StackHintGenerator() = 0; + + /// Construct the Diagnostic message for the given ExplodedNode. + virtual std::string getMessage(const ExplodedNode *N) = 0; +}; + +/// Constructs a Stack hint for the given symbol. +/// +/// The class knows how to construct the stack hint message based on +/// traversing the CallExpr associated with the call and checking if the given +/// symbol is returned or is one of the arguments. +/// The hint can be customized by redefining 'getMessageForX()' methods. +class StackHintGeneratorForSymbol : public StackHintGenerator { +private: + SymbolRef Sym; + std::string Msg; + +public: + StackHintGeneratorForSymbol(SymbolRef S, StringRef M) : Sym(S), Msg(M) {} + ~StackHintGeneratorForSymbol() override = default; + + /// Search the call expression for the symbol Sym and dispatch the + /// 'getMessageForX()' methods to construct a specific message. + std::string getMessage(const ExplodedNode *N) override; + + /// Produces the message of the following form: + /// 'Msg via Nth parameter' + virtual std::string getMessageForArg(const Expr *ArgE, unsigned ArgIndex); + + virtual std::string getMessageForReturn(const CallExpr *CallExpr) { + return Msg; + } + + virtual std::string getMessageForSymbolNotFound() { + return Msg; + } +}; + /// This class provides an interface through which checkers can create /// individual bug reports. class BugReport { @@ -313,6 +357,14 @@ class PathSensitiveBugReport : public BugReport { const Stmt *getStmt() const; + /// If an event occurs in a different frame than the final diagnostic, + /// supply a message that will be used to construct an extra hint on the + /// returns from all the calls on the stack from this event to the final + /// diagnostic. + // FIXME: Allow shared_ptr keys in DenseMap? + std::map> + StackHints; + public: PathSensitiveBugReport(const BugType &bt, StringRef desc, const ExplodedNode *errorNode) @@ -455,6 +507,26 @@ class PathSensitiveBugReport : public BugReport { bool addTrackedCondition(const ExplodedNode *Cond) { return TrackedConditions.insert(Cond).second; } + + void addCallStackHint(PathDiagnosticPieceRef Piece, + std::unique_ptr StackHint) { + StackHints[Piece] = std::move(StackHint); + } + + bool hasCallStackHint(PathDiagnosticPieceRef Piece) const { + return StackHints.count(Piece) > 0; + } + + /// Produce the hint for the given node. The node contains + /// information about the call for which the diagnostic can be generated. + std::string + getCallStackMessage(PathDiagnosticPieceRef Piece, + const ExplodedNode *N) const { + auto I = StackHints.find(Piece); + if (I != StackHints.end()) + return I->second->getMessage(N); + return ""; + } }; //===----------------------------------------------------------------------===// diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h index 289f6de97d215..dd65af452f3e7 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h @@ -502,65 +502,13 @@ class PathDiagnosticSpotPiece : public PathDiagnosticPiece { } }; -/// Interface for classes constructing Stack hints. -/// -/// If a PathDiagnosticEvent occurs in a different frame than the final -/// diagnostic the hints can be used to summarize the effect of the call. -class StackHintGenerator { -public: - virtual ~StackHintGenerator() = 0; - - /// Construct the Diagnostic message for the given ExplodedNode. - virtual std::string getMessage(const ExplodedNode *N) = 0; -}; - -/// Constructs a Stack hint for the given symbol. -/// -/// The class knows how to construct the stack hint message based on -/// traversing the CallExpr associated with the call and checking if the given -/// symbol is returned or is one of the arguments. -/// The hint can be customized by redefining 'getMessageForX()' methods. -class StackHintGeneratorForSymbol : public StackHintGenerator { -private: - SymbolRef Sym; - std::string Msg; - -public: - StackHintGeneratorForSymbol(SymbolRef S, StringRef M) : Sym(S), Msg(M) {} - ~StackHintGeneratorForSymbol() override = default; - - /// Search the call expression for the symbol Sym and dispatch the - /// 'getMessageForX()' methods to construct a specific message. - std::string getMessage(const ExplodedNode *N) override; - - /// Produces the message of the following form: - /// 'Msg via Nth parameter' - virtual std::string getMessageForArg(const Expr *ArgE, unsigned ArgIndex); - - virtual std::string getMessageForReturn(const CallExpr *CallExpr) { - return Msg; - } - - virtual std::string getMessageForSymbolNotFound() { - return Msg; - } -}; - class PathDiagnosticEventPiece : public PathDiagnosticSpotPiece { Optional IsPrunable; - /// If the event occurs in a different frame than the final diagnostic, - /// supply a message that will be used to construct an extra hint on the - /// returns from all the calls on the stack from this event to the final - /// diagnostic. - std::unique_ptr CallStackHint; - public: PathDiagnosticEventPiece(const PathDiagnosticLocation &pos, - StringRef s, bool addPosRange = true, - StackHintGenerator *stackHint = nullptr) - : PathDiagnosticSpotPiece(pos, s, Event, addPosRange), - CallStackHint(stackHint) {} + StringRef s, bool addPosRange = true) + : PathDiagnosticSpotPiece(pos, s, Event, addPosRange) {} ~PathDiagnosticEventPiece() override; /// Mark the diagnostic piece as being potentially prunable. This @@ -577,16 +525,6 @@ class PathDiagnosticEventPiece : public PathDiagnosticSpotPiece { return IsPrunable.hasValue() ? IsPrunable.getValue() : false; } - bool hasCallStackHint() { return (bool)CallStackHint; } - - /// Produce the hint for the given node. The node contains - /// information about the call for which the diagnostic can be generated. - std::string getCallStackMessage(const ExplodedNode *N) { - if (CallStackHint) - return CallStackHint->getMessage(N); - return {}; - } - void dump() const override; static bool classof(const PathDiagnosticPiece *P) { diff --git a/clang/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp index 3d92d68290a62..f189a8cdc0c73 100644 --- a/clang/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp @@ -140,8 +140,7 @@ DeleteWithNonVirtualDtorChecker::DeleteBugVisitor::VisitNode( OS << "Conversion from derived to base happened here"; PathDiagnosticLocation Pos(S, BRC.getSourceManager(), N->getLocationContext()); - return std::make_shared(Pos, OS.str(), true, - nullptr); + return std::make_shared(Pos, OS.str(), true); } void ento::registerDeleteWithNonVirtualDtorChecker(CheckerManager &mgr) { diff --git a/clang/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp index 6a58f80c43f53..f30a37fd725c1 100644 --- a/clang/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp @@ -139,8 +139,7 @@ PathDiagnosticPieceRef DynamicTypeChecker::DynamicTypeBugVisitor::VisitNode( // Generate the extra diagnostic. PathDiagnosticLocation Pos(S, BRC.getSourceManager(), N->getLocationContext()); - return std::make_shared(Pos, OS.str(), true, - nullptr); + return std::make_shared(Pos, OS.str(), true); } static bool hasDefinition(const ObjCObjectPointerType *ObjPtr) { diff --git a/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp b/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp index e0a39da603857..da51faf5182e1 100644 --- a/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp @@ -972,8 +972,7 @@ PathDiagnosticPieceRef DynamicTypePropagation::GenericsBugVisitor::VisitNode( // Generate the extra diagnostic. PathDiagnosticLocation Pos(S, BRC.getSourceManager(), N->getLocationContext()); - return std::make_shared(Pos, OS.str(), true, - nullptr); + return std::make_shared(Pos, OS.str(), true); } /// Register checkers. diff --git a/clang/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp index 83fbdea809596..966819587dfe6 100644 --- a/clang/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp @@ -299,8 +299,7 @@ PathDiagnosticPieceRef InnerPointerChecker::InnerPointerBRVisitor::VisitNode( << "' obtained here"; PathDiagnosticLocation Pos(S, BRC.getSourceManager(), N->getLocationContext()); - return std::make_shared(Pos, OS.str(), true, - nullptr); + return std::make_shared(Pos, OS.str(), true); } void ento::registerInnerPointerChecker(CheckerManager &Mgr) { diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index 882ac1701b690..39332155d1b30 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -2953,15 +2953,15 @@ MallocChecker::MallocBugVisitor::VisitNode(const ExplodedNode *N, // Find out if this is an interesting point and what is the kind. StringRef Msg; - StackHintGeneratorForSymbol *StackHint = nullptr; + std::unique_ptr StackHint = nullptr; SmallString<256> Buf; llvm::raw_svector_ostream OS(Buf); if (Mode == Normal) { if (isAllocated(RS, RSPrev, S)) { Msg = "Memory is allocated"; - StackHint = new StackHintGeneratorForSymbol(Sym, - "Returned allocated memory"); + StackHint = std::make_unique( + Sym, "Returned allocated memory"); } else if (isReleased(RS, RSPrev, S)) { const auto Family = RS->getAllocationFamily(); switch (Family) { @@ -2971,8 +2971,8 @@ MallocChecker::MallocBugVisitor::VisitNode(const ExplodedNode *N, case AF_CXXNewArray: case AF_IfNameIndex: Msg = "Memory is released"; - StackHint = new StackHintGeneratorForSymbol(Sym, - "Returning; memory was released"); + StackHint = std::make_unique( + Sym, "Returning; memory was released"); break; case AF_InnerBuffer: { const MemRegion *ObjRegion = @@ -2983,8 +2983,8 @@ MallocChecker::MallocBugVisitor::VisitNode(const ExplodedNode *N, if (N->getLocation().getKind() == ProgramPoint::PostImplicitCallKind) { OS << "deallocated by call to destructor"; - StackHint = new StackHintGeneratorForSymbol(Sym, - "Returning; inner buffer was deallocated"); + StackHint = std::make_unique( + Sym, "Returning; inner buffer was deallocated"); } else { OS << "reallocated by call to '"; const Stmt *S = RS->getStmt(); @@ -2999,8 +2999,8 @@ MallocChecker::MallocBugVisitor::VisitNode(const ExplodedNode *N, OS << (D ? D->getNameAsString() : "unknown"); } OS << "'"; - StackHint = new StackHintGeneratorForSymbol(Sym, - "Returning; inner buffer was reallocated"); + StackHint = std::make_unique( + Sym, "Returning; inner buffer was reallocated"); } Msg = OS.str(); break; @@ -3040,12 +3040,12 @@ MallocChecker::MallocBugVisitor::VisitNode(const ExplodedNode *N, } } else if (isRelinquished(RS, RSPrev, S)) { Msg = "Memory ownership is transferred"; - StackHint = new StackHintGeneratorForSymbol(Sym, ""); + StackHint = std::make_unique(Sym, ""); } else if (isReallocFailedCheck(RS, RSPrev, S)) { Mode = ReallocationFailed; Msg = "Reallocation failed"; - StackHint = new StackHintGeneratorForReallocationFailed(Sym, - "Reallocation failed"); + StackHint = std::make_unique( + Sym, "Reallocation failed"); if (SymbolRef sym = findFailedReallocSymbol(state, statePrev)) { // Is it possible to fail two reallocs WITHOUT testing in between? @@ -3064,16 +3064,15 @@ MallocChecker::MallocBugVisitor::VisitNode(const ExplodedNode *N, if (!statePrev->get(FailedReallocSymbol)) { // We're at the reallocation point. Msg = "Attempt to reallocate memory"; - StackHint = new StackHintGeneratorForSymbol(Sym, - "Returned reallocated memory"); + StackHint = std::make_unique( + Sym, "Returned reallocated memory"); FailedReallocSymbol = nullptr; Mode = Normal; } } if (Msg.empty()) { - // Silence a memory leak warning by MallocChecker in MallocChecker.cpp :) - assert(!StackHint && "Memory leak!"); + assert(!StackHint); return nullptr; } @@ -3093,7 +3092,9 @@ MallocChecker::MallocBugVisitor::VisitNode(const ExplodedNode *N, N->getLocationContext()); } - return std::make_shared(Pos, Msg, true, StackHint); + auto P = std::make_shared(Pos, Msg, true); + BR.addCallStackHint(P, std::move(StackHint)); + return P; } void MallocChecker::printState(raw_ostream &Out, ProgramStateRef State, diff --git a/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp index 59f5939cd7d88..424de4c21dacf 100644 --- a/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp @@ -323,8 +323,7 @@ PathDiagnosticPieceRef NullabilityChecker::NullabilityBugVisitor::VisitNode( // Generate the extra diagnostic. PathDiagnosticLocation Pos(S, BRC.getSourceManager(), N->getLocationContext()); - return std::make_shared(Pos, InfoText, true, - nullptr); + return std::make_shared(Pos, InfoText, true); } /// Returns true when the value stored at the given location has been diff --git a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp index 2d51b7d2135d4..6cf2bab791dad 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp @@ -239,6 +239,8 @@ class PathDiagnosticBuilder : public BugReporterContext { generate(const PathDiagnosticConsumer *PDC) const; private: + void updateStackPiecesWithMessage(PathDiagnosticPieceRef P, + const CallWithEntryStack &CallStack) const; void generatePathDiagnosticsForNode(PathDiagnosticConstruct &C, PathDiagnosticLocation &PrevLoc) const; @@ -269,6 +271,69 @@ class PathDiagnosticBuilder : public BugReporterContext { } // namespace +//===----------------------------------------------------------------------===// +// Base implementation of stack hint generators. +//===----------------------------------------------------------------------===// + +StackHintGenerator::~StackHintGenerator() = default; + +std::string StackHintGeneratorForSymbol::getMessage(const ExplodedNode *N){ + if (!N) + return getMessageForSymbolNotFound(); + + ProgramPoint P = N->getLocation(); + CallExitEnd CExit = P.castAs(); + + // FIXME: Use CallEvent to abstract this over all calls. + const Stmt *CallSite = CExit.getCalleeContext()->getCallSite(); + const auto *CE = dyn_cast_or_null(CallSite); + if (!CE) + return {}; + + // Check if one of the parameters are set to the interesting symbol. + unsigned ArgIndex = 0; + for (CallExpr::const_arg_iterator I = CE->arg_begin(), + E = CE->arg_end(); I != E; ++I, ++ArgIndex){ + SVal SV = N->getSVal(*I); + + // Check if the variable corresponding to the symbol is passed by value. + SymbolRef AS = SV.getAsLocSymbol(); + if (AS == Sym) { + return getMessageForArg(*I, ArgIndex); + } + + // Check if the parameter is a pointer to the symbol. + if (Optional Reg = SV.getAs()) { + // Do not attempt to dereference void*. + if ((*I)->getType()->isVoidPointerType()) + continue; + SVal PSV = N->getState()->getSVal(Reg->getRegion()); + SymbolRef AS = PSV.getAsLocSymbol(); + if (AS == Sym) { + return getMessageForArg(*I, ArgIndex); + } + } + } + + // Check if we are returning the interesting symbol. + SVal SV = N->getSVal(CE); + SymbolRef RetSym = SV.getAsLocSymbol(); + if (RetSym == Sym) { + return getMessageForReturn(CE); + } + + return getMessageForSymbolNotFound(); +} + +std::string StackHintGeneratorForSymbol::getMessageForArg(const Expr *ArgE, + unsigned ArgIndex) { + // Printed parameters start at 1, not 0. + ++ArgIndex; + + return (llvm::Twine(Msg) + " via " + std::to_string(ArgIndex) + + llvm::getOrdinalSuffix(ArgIndex) + " parameter").str(); +} + //===----------------------------------------------------------------------===// // Helper routines for walking the ExplodedGraph and fetching statements. //===----------------------------------------------------------------------===// @@ -661,7 +726,7 @@ getEnclosingStmtLocation(const Stmt *S, const LocationContext *LC, //===----------------------------------------------------------------------===// /// If the piece contains a special message, add it to all the call pieces on -/// the active stack. For exampler, my_malloc allocated memory, so MallocChecker +/// the active stack. For example, my_malloc allocated memory, so MallocChecker /// will construct an event at the call to malloc(), and add a stack hint that /// an allocated memory was returned. We'll use this hint to construct a message /// when returning from the call to my_malloc @@ -670,22 +735,20 @@ getEnclosingStmtLocation(const Stmt *S, const LocationContext *LC, /// void fishy() { /// void *ptr = my_malloc(); // returned allocated memory /// } // leak -static void updateStackPiecesWithMessage(PathDiagnosticPiece &P, - const CallWithEntryStack &CallStack) { - if (auto *ep = dyn_cast(&P)) { - if (ep->hasCallStackHint()) - for (const auto &I : CallStack) { - PathDiagnosticCallPiece *CP = I.first; - const ExplodedNode *N = I.second; - std::string stackMsg = ep->getCallStackMessage(N); - - // The last message on the path to final bug is the most important - // one. Since we traverse the path backwards, do not add the message - // if one has been previously added. - if (!CP->hasCallStackMessage()) - CP->setCallStackMessage(stackMsg); - } - } +void PathDiagnosticBuilder::updateStackPiecesWithMessage( + PathDiagnosticPieceRef P, const CallWithEntryStack &CallStack) const { + if (R->hasCallStackHint(P)) + for (const auto &I : CallStack) { + PathDiagnosticCallPiece *CP = I.first; + const ExplodedNode *N = I.second; + std::string stackMsg = R->getCallStackMessage(P, N); + + // The last message on the path to final bug is the most important + // one. Since we traverse the path backwards, do not add the message + // if one has been previously added. + if (!CP->hasCallStackMessage()) + CP->setCallStackMessage(stackMsg); + } } static void CompactMacroExpandedPieces(PathPieces &path, @@ -1989,7 +2052,7 @@ PathDiagnosticBuilder::generate(const PathDiagnosticConsumer *PDC) const { if (PDC->shouldAddPathEdges()) addEdgeToPath(Construct.getActivePath(), PrevLoc, Note->getLocation()); - updateStackPiecesWithMessage(*Note, Construct.CallStack); + updateStackPiecesWithMessage(Note, Construct.CallStack); Construct.getActivePath().push_front(Note); } } diff --git a/clang/lib/StaticAnalyzer/Core/PathDiagnostic.cpp b/clang/lib/StaticAnalyzer/Core/PathDiagnostic.cpp index f049a1c4120b4..94d1c095b8bfe 100644 --- a/clang/lib/StaticAnalyzer/Core/PathDiagnostic.cpp +++ b/clang/lib/StaticAnalyzer/Core/PathDiagnostic.cpp @@ -31,7 +31,6 @@ #include "clang/Basic/SourceManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/FoldingSet.h" #include "llvm/ADT/None.h" @@ -1303,70 +1302,6 @@ void PathDiagnostic::FullProfile(llvm::FoldingSetNodeID &ID) const { ID.AddString(*I); } -StackHintGenerator::~StackHintGenerator() = default; - -std::string StackHintGeneratorForSymbol::getMessage(const ExplodedNode *N){ - if (!N) - return getMessageForSymbolNotFound(); - - ProgramPoint P = N->getLocation(); - CallExitEnd CExit = P.castAs(); - - // FIXME: Use CallEvent to abstract this over all calls. - const Stmt *CallSite = CExit.getCalleeContext()->getCallSite(); - const auto *CE = dyn_cast_or_null(CallSite); - if (!CE) - return {}; - - // Check if one of the parameters are set to the interesting symbol. - unsigned ArgIndex = 0; - for (CallExpr::const_arg_iterator I = CE->arg_begin(), - E = CE->arg_end(); I != E; ++I, ++ArgIndex){ - SVal SV = N->getSVal(*I); - - // Check if the variable corresponding to the symbol is passed by value. - SymbolRef AS = SV.getAsLocSymbol(); - if (AS == Sym) { - return getMessageForArg(*I, ArgIndex); - } - - // Check if the parameter is a pointer to the symbol. - if (Optional Reg = SV.getAs()) { - // Do not attempt to dereference void*. - if ((*I)->getType()->isVoidPointerType()) - continue; - SVal PSV = N->getState()->getSVal(Reg->getRegion()); - SymbolRef AS = PSV.getAsLocSymbol(); - if (AS == Sym) { - return getMessageForArg(*I, ArgIndex); - } - } - } - - // Check if we are returning the interesting symbol. - SVal SV = N->getSVal(CE); - SymbolRef RetSym = SV.getAsLocSymbol(); - if (RetSym == Sym) { - return getMessageForReturn(CE); - } - - return getMessageForSymbolNotFound(); -} - -std::string StackHintGeneratorForSymbol::getMessageForArg(const Expr *ArgE, - unsigned ArgIndex) { - // Printed parameters start at 1, not 0. - ++ArgIndex; - - SmallString<200> buf; - llvm::raw_svector_ostream os(buf); - - os << Msg << " via " << ArgIndex << llvm::getOrdinalSuffix(ArgIndex) - << " parameter"; - - return os.str(); -} - LLVM_DUMP_METHOD void PathPieces::dump() const { unsigned index = 0; for (PathPieces::const_iterator I = begin(), E = end(); I != E; ++I) { From 4ed346d171fcf5e78ea86e784775c812cb1b4864 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Wed, 11 Sep 2019 20:54:21 +0000 Subject: [PATCH 150/181] [analyzer] NFC: Move getStmt() and createEndOfPath() out of PathDiagnostic. These static functions deal with ExplodedNodes which is something we don't want the PathDiagnostic interface to know anything about, as it's planned to be moved out of libStaticAnalyzerCore. Differential Revision: https://reviews.llvm.org/D67382 llvm-svn: 371659 (cherry picked from commit 6b85f8e99b38aed5bb4eeb4ca8d7ccdd5a1bf48c) --- .../Core/BugReporter/PathDiagnostic.h | 25 ++-- .../Core/PathSensitive/ExplodedGraph.h | 24 ++++ .../DeleteWithNonVirtualDtorChecker.cpp | 2 +- .../Checkers/DynamicTypeChecker.cpp | 2 +- .../Checkers/DynamicTypePropagation.cpp | 2 +- .../Checkers/InnerPointerChecker.cpp | 2 +- .../Checkers/MacOSKeychainAPIChecker.cpp | 2 +- .../StaticAnalyzer/Checkers/MallocChecker.cpp | 7 +- .../StaticAnalyzer/Checkers/MoveChecker.cpp | 4 +- .../Checkers/NullabilityChecker.cpp | 2 +- .../RetainCountDiagnostics.cpp | 8 +- .../RetainCountDiagnostics.h | 5 +- clang/lib/StaticAnalyzer/Checkers/Taint.cpp | 2 +- .../StaticAnalyzer/Checkers/ValistChecker.cpp | 7 +- clang/lib/StaticAnalyzer/Core/BugReporter.cpp | 77 +++++++---- .../Core/BugReporterVisitors.cpp | 8 +- .../lib/StaticAnalyzer/Core/ExplodedGraph.cpp | 90 ++++++++++++- .../lib/StaticAnalyzer/Core/LoopUnrolling.cpp | 4 +- .../StaticAnalyzer/Core/PathDiagnostic.cpp | 126 +----------------- 19 files changed, 204 insertions(+), 195 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h index dd65af452f3e7..aaeeb48cf2002 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h @@ -52,11 +52,6 @@ class SourceManager; namespace ento { -class ExplodedNode; -class SymExpr; - -using SymbolRef = const SymExpr *; - //===----------------------------------------------------------------------===// // High-level interface for handlers of path-sensitive diagnostics. //===----------------------------------------------------------------------===// @@ -276,18 +271,21 @@ class PathDiagnosticLocation { static PathDiagnosticLocation createDeclEnd(const LocationContext *LC, const SourceManager &SM); - /// Create a location corresponding to the given valid ExplodedNode. + /// Create a location corresponding to the given valid ProgramPoint. static PathDiagnosticLocation create(const ProgramPoint &P, const SourceManager &SMng); - /// Create a location corresponding to the next valid ExplodedNode as end - /// of path location. - static PathDiagnosticLocation createEndOfPath(const ExplodedNode* N); - /// Convert the given location into a single kind location. static PathDiagnosticLocation createSingleLocation( const PathDiagnosticLocation &PDL); + /// Construct a source location that corresponds to either the beginning + /// or the end of the given statement, or a nearby valid source location + /// if the statement does not have a valid source location of its own. + static SourceLocation + getValidSourceLocation(const Stmt *S, LocationOrAnalysisDeclContext LAC, + bool UseEndOfStatement = false); + bool operator==(const PathDiagnosticLocation &X) const { return K == X.K && Loc == X.Loc && Range == X.Range; } @@ -332,13 +330,6 @@ class PathDiagnosticLocation { void Profile(llvm::FoldingSetNodeID &ID) const; void dump() const; - - /// Given an exploded node, retrieve the statement that should be used - /// for the diagnostic location. - static const Stmt *getStmt(const ExplodedNode *N); - - /// Retrieve the statement corresponding to the successor node. - static const Stmt *getNextStmt(const ExplodedNode *N); }; class PathDiagnosticLocationPair { diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h index 864266e77eed1..c459b2aa299ec 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h @@ -267,6 +267,30 @@ class ExplodedNode : public llvm::FoldingSetNode { /// Trivial nodes may be skipped while printing exploded graph. bool isTrivial() const; + /// If the node's program point corresponds to a statement, retrieve that + /// statement. Useful for figuring out where to put a warning or a note. + /// If the statement belongs to a body-farmed definition, + /// retrieve the call site for that definition. + const Stmt *getStmtForDiagnostics() const; + + /// Find the next statement that was executed on this node's execution path. + /// Useful for explaining control flow that follows the current node. + /// If the statement belongs to a body-farmed definition, retrieve the + /// call site for that definition. + const Stmt *getNextStmtForDiagnostics() const; + + /// Find the statement that was executed immediately before this node. + /// Useful when the node corresponds to a CFG block entrance. + /// If the statement belongs to a body-farmed definition, retrieve the + /// call site for that definition. + const Stmt *getPreviousStmtForDiagnostics() const; + + /// Find the statement that was executed at or immediately before this node. + /// Useful when any nearby statement will do. + /// If the statement belongs to a body-farmed definition, retrieve the + /// call site for that definition. + const Stmt *getCurrentOrPreviousStmtForDiagnostics() const; + private: void replaceSuccessor(ExplodedNode *node) { Succs.replaceNode(node); } void replacePredecessor(ExplodedNode *node) { Preds.replaceNode(node); } diff --git a/clang/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp index f189a8cdc0c73..d5ce88236a7b7 100644 --- a/clang/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp @@ -108,7 +108,7 @@ DeleteWithNonVirtualDtorChecker::DeleteBugVisitor::VisitNode( if (Satisfied) return nullptr; - const Stmt *S = PathDiagnosticLocation::getStmt(N); + const Stmt *S = N->getStmtForDiagnostics(); if (!S) return nullptr; diff --git a/clang/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp index f30a37fd725c1..580579f2cbc35 100644 --- a/clang/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp @@ -103,7 +103,7 @@ PathDiagnosticPieceRef DynamicTypeChecker::DynamicTypeBugVisitor::VisitNode( return nullptr; // Retrieve the associated statement. - const Stmt *S = PathDiagnosticLocation::getStmt(N); + const Stmt *S = N->getStmtForDiagnostics(); if (!S) return nullptr; diff --git a/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp b/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp index da51faf5182e1..cfac1e0da1769 100644 --- a/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp @@ -937,7 +937,7 @@ PathDiagnosticPieceRef DynamicTypePropagation::GenericsBugVisitor::VisitNode( return nullptr; // Retrieve the associated statement. - const Stmt *S = PathDiagnosticLocation::getStmt(N); + const Stmt *S = N->getStmtForDiagnostics(); if (!S) return nullptr; diff --git a/clang/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp index 966819587dfe6..df9d7a92929a7 100644 --- a/clang/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp @@ -284,7 +284,7 @@ PathDiagnosticPieceRef InnerPointerChecker::InnerPointerBRVisitor::VisitNode( isSymbolTracked(N->getFirstPred()->getState(), PtrToBuf)) return nullptr; - const Stmt *S = PathDiagnosticLocation::getStmt(N); + const Stmt *S = N->getStmtForDiagnostics(); if (!S) return nullptr; diff --git a/clang/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp index c37b89b72a2d6..b3b09c1e45545 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp @@ -482,7 +482,7 @@ MacOSKeychainAPIChecker::generateAllocatedDataNotReleasedReport( // allocated, and only report a single path. PathDiagnosticLocation LocUsedForUniqueing; const ExplodedNode *AllocNode = getAllocationNode(N, AP.first, C); - const Stmt *AllocStmt = PathDiagnosticLocation::getStmt(AllocNode); + const Stmt *AllocStmt = AllocNode->getStmtForDiagnostics(); if (AllocStmt) LocUsedForUniqueing = PathDiagnosticLocation::createBegin(AllocStmt, diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index 39332155d1b30..d1e8b4a842c61 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -532,8 +532,7 @@ class MallocChecker : public Checker(L, BR.getDescription(), false); @@ -2332,7 +2331,7 @@ void MallocChecker::reportLeak(SymbolRef Sym, ExplodedNode *N, const MemRegion *Region = nullptr; std::tie(AllocNode, Region) = getAllocationSite(N, Sym, C); - const Stmt *AllocationStmt = PathDiagnosticLocation::getStmt(AllocNode); + const Stmt *AllocationStmt = AllocNode->getStmtForDiagnostics(); if (AllocationStmt) LocUsedForUniqueing = PathDiagnosticLocation::createBegin(AllocationStmt, C.getSourceManager(), @@ -2920,7 +2919,7 @@ MallocChecker::MallocBugVisitor::VisitNode(const ExplodedNode *N, const RefState *RS = state->get(Sym); const RefState *RSPrev = statePrev->get(Sym); - const Stmt *S = PathDiagnosticLocation::getStmt(N); + const Stmt *S = N->getStmtForDiagnostics(); // When dealing with containers, we sometimes want to give a note // even if the statement is missing. if (!S && (!RS || RS->getAllocationFamily() != AF_InnerBuffer)) diff --git a/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp index 89abb4a629367..8160b54fe7fdd 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp @@ -289,7 +289,7 @@ MoveChecker::MovedBugVisitor::VisitNode(const ExplodedNode *N, return nullptr; // Retrieve the associated statement. - const Stmt *S = PathDiagnosticLocation::getStmt(N); + const Stmt *S = N->getStmtForDiagnostics(); if (!S) return nullptr; Found = true; @@ -401,7 +401,7 @@ ExplodedNode *MoveChecker::reportBug(const MemRegion *Region, PathDiagnosticLocation LocUsedForUniqueing; const ExplodedNode *MoveNode = getMoveLocation(N, Region, C); - if (const Stmt *MoveStmt = PathDiagnosticLocation::getStmt(MoveNode)) + if (const Stmt *MoveStmt = MoveNode->getStmtForDiagnostics()) LocUsedForUniqueing = PathDiagnosticLocation::createBegin( MoveStmt, C.getSourceManager(), MoveNode->getLocationContext()); diff --git a/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp index 424de4c21dacf..562bddd10c856 100644 --- a/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp @@ -309,7 +309,7 @@ PathDiagnosticPieceRef NullabilityChecker::NullabilityBugVisitor::VisitNode( // Retrieve the associated statement. const Stmt *S = TrackedNullab->getNullabilitySource(); if (!S || S->getBeginLoc().isInvalid()) { - S = PathDiagnosticLocation::getStmt(N); + S = N->getStmtForDiagnostics(); } if (!S) diff --git a/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.cpp b/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.cpp index 121048574d27f..2de29918bd4af 100644 --- a/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.cpp @@ -738,11 +738,7 @@ RefLeakReportVisitor::getEndPath(BugReporterContext &BRC, const MemRegion* FirstBinding = AllocI.R; BR.markInteresting(AllocI.InterestingMethodContext); - // Compute an actual location for the leak. Sometimes a leak doesn't - // occur at an actual statement (e.g., transition between blocks; end - // of function) so we need to walk the graph and compute a real location. - const ExplodedNode *LeakN = EndN; - PathDiagnosticLocation L = PathDiagnosticLocation::createEndOfPath(LeakN); + PathDiagnosticLocation L = cast(BR).getEndOfPath(); std::string sbuf; llvm::raw_string_ostream os(sbuf); @@ -872,7 +868,7 @@ void RefLeakReport::deriveAllocLocation(CheckerContext &Ctx, // FIXME: This will crash the analyzer if an allocation comes from an // implicit call (ex: a destructor call). // (Currently there are no such allocations in Cocoa, though.) - AllocStmt = PathDiagnosticLocation::getStmt(AllocNode); + AllocStmt = AllocNode->getStmtForDiagnostics(); if (!AllocStmt) { AllocBinding = nullptr; diff --git a/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.h b/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.h index 323b50ad9f048..3b212127c9616 100644 --- a/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.h +++ b/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.h @@ -90,11 +90,14 @@ class RefLeakReport : public RefCountReport { public: RefLeakReport(const RefCountBug &D, const LangOptions &LOpts, ExplodedNode *n, SymbolRef sym, CheckerContext &Ctx); - PathDiagnosticLocation getLocation() const override { assert(Location.isValid()); return Location; } + + PathDiagnosticLocation getEndOfPath() const { + return PathSensitiveBugReport::getLocation(); + } }; } // end namespace retaincountchecker diff --git a/clang/lib/StaticAnalyzer/Checkers/Taint.cpp b/clang/lib/StaticAnalyzer/Checkers/Taint.cpp index 2a9456dd84d5b..574d4ed1e600c 100644 --- a/clang/lib/StaticAnalyzer/Checkers/Taint.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/Taint.cpp @@ -213,7 +213,7 @@ PathDiagnosticPieceRef TaintBugVisitor::VisitNode(const ExplodedNode *N, isTainted(N->getFirstPred()->getState(), V)) return nullptr; - const Stmt *S = PathDiagnosticLocation::getStmt(N); + const Stmt *S = N->getStmtForDiagnostics(); if (!S) return nullptr; diff --git a/clang/lib/StaticAnalyzer/Checkers/ValistChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ValistChecker.cpp index 04ec5843b07b4..add9537001457 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ValistChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ValistChecker.cpp @@ -83,8 +83,7 @@ class ValistChecker : public Checker, if (!IsLeak) return nullptr; - PathDiagnosticLocation L = - PathDiagnosticLocation::createEndOfPath(EndPathNode); + PathDiagnosticLocation L = BR.getLocation(); // Do not add the statement itself as a range in case of leak. return std::make_shared(L, BR.getDescription(), false); @@ -285,7 +284,7 @@ void ValistChecker::reportLeakedVALists(const RegionVector &LeakedVALists, const ExplodedNode *StartNode = getStartCallSite(N, Reg); PathDiagnosticLocation LocUsedForUniqueing; - if (const Stmt *StartCallStmt = PathDiagnosticLocation::getStmt(StartNode)) + if (const Stmt *StartCallStmt = StartNode->getStmtForDiagnostics()) LocUsedForUniqueing = PathDiagnosticLocation::createBegin( StartCallStmt, C.getSourceManager(), StartNode->getLocationContext()); @@ -381,7 +380,7 @@ PathDiagnosticPieceRef ValistChecker::ValistBugVisitor::VisitNode( ProgramStateRef State = N->getState(); ProgramStateRef StatePrev = N->getFirstPred()->getState(); - const Stmt *S = PathDiagnosticLocation::getStmt(N); + const Stmt *S = N->getStmtForDiagnostics(); if (!S) return nullptr; diff --git a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp index 6cf2bab791dad..7dbbe106a367a 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp @@ -334,26 +334,6 @@ std::string StackHintGeneratorForSymbol::getMessageForArg(const Expr *ArgE, llvm::getOrdinalSuffix(ArgIndex) + " parameter").str(); } -//===----------------------------------------------------------------------===// -// Helper routines for walking the ExplodedGraph and fetching statements. -//===----------------------------------------------------------------------===// - -static const Stmt *GetPreviousStmt(const ExplodedNode *N) { - for (N = N->getFirstPred(); N; N = N->getFirstPred()) - if (const Stmt *S = PathDiagnosticLocation::getStmt(N)) - return S; - - return nullptr; -} - -static inline const Stmt* -GetCurrentOrPreviousStmt(const ExplodedNode *N) { - if (const Stmt *S = PathDiagnosticLocation::getStmt(N)) - return S; - - return GetPreviousStmt(N); -} - //===----------------------------------------------------------------------===// // Diagnostic cleanup. //===----------------------------------------------------------------------===// @@ -593,7 +573,7 @@ static void removePiecesWithInvalidLocations(PathPieces &Pieces) { PathDiagnosticLocation PathDiagnosticBuilder::ExecutionContinues( const PathDiagnosticConstruct &C) const { - if (const Stmt *S = PathDiagnosticLocation::getNextStmt(C.getCurrentNode())) + if (const Stmt *S = C.getCurrentNode()->getNextStmtForDiagnostics()) return PathDiagnosticLocation(S, getSourceManager(), C.getCurrLocationContext()); @@ -888,7 +868,7 @@ void PathDiagnosticBuilder::generateMinimalDiagForBlockEdge( case Stmt::GotoStmtClass: case Stmt::IndirectGotoStmtClass: { - if (const Stmt *S = PathDiagnosticLocation::getNextStmt(C.getCurrentNode())) + if (const Stmt *S = C.getCurrentNode()->getNextStmtForDiagnostics()) C.getActivePath().push_front(generateDiagForGotoOP(C, S, Start)); break; } @@ -2176,8 +2156,11 @@ void PathSensitiveBugReport::Profile(llvm::FoldingSetNodeID &hash) const { if (UL.isValid()) { UL.Profile(hash); } else { - assert(ErrorNode); - hash.AddPointer(GetCurrentOrPreviousStmt(ErrorNode)); + // TODO: The statement may be null if the report was emitted before any + // statements were executed. In particular, some checkers by design + // occasionally emit their reports in empty functions (that have no + // statements in their body). Do we profile correctly in this case? + hash.AddPointer(ErrorNode->getCurrentOrPreviousStmtForDiagnostics()); } for (SourceRange range : Ranges) { @@ -2332,10 +2315,10 @@ const Stmt *PathSensitiveBugReport::getStmt() const { if (Optional BE = ProgP.getAs()) { CFGBlock &Exit = ProgP.getLocationContext()->getCFG()->getExit(); if (BE->getBlock() == &Exit) - S = GetPreviousStmt(ErrorNode); + S = ErrorNode->getPreviousStmtForDiagnostics(); } if (!S) - S = PathDiagnosticLocation::getStmt(ErrorNode); + S = ErrorNode->getStmtForDiagnostics(); return S; } @@ -2352,7 +2335,45 @@ PathSensitiveBugReport::getRanges() const { PathDiagnosticLocation PathSensitiveBugReport::getLocation() const { - return PathDiagnosticLocation::createEndOfPath(ErrorNode); + assert(ErrorNode && "Cannot create a location with a null node."); + const Stmt *S = ErrorNode->getStmtForDiagnostics(); + ProgramPoint P = ErrorNode->getLocation(); + const LocationContext *LC = P.getLocationContext(); + SourceManager &SM = + ErrorNode->getState()->getStateManager().getContext().getSourceManager(); + + if (!S) { + // If this is an implicit call, return the implicit call point location. + if (Optional PIE = P.getAs()) + return PathDiagnosticLocation(PIE->getLocation(), SM); + if (auto FE = P.getAs()) { + if (const ReturnStmt *RS = FE->getStmt()) + return PathDiagnosticLocation::createBegin(RS, SM, LC); + } + S = ErrorNode->getNextStmtForDiagnostics(); + } + + if (S) { + // For member expressions, return the location of the '.' or '->'. + if (const auto *ME = dyn_cast(S)) + return PathDiagnosticLocation::createMemberLoc(ME, SM); + + // For binary operators, return the location of the operator. + if (const auto *B = dyn_cast(S)) + return PathDiagnosticLocation::createOperatorLoc(B, SM); + + if (P.getAs()) + return PathDiagnosticLocation::createEnd(S, SM, LC); + + if (S->getBeginLoc().isValid()) + return PathDiagnosticLocation(S, SM, LC); + + return PathDiagnosticLocation( + PathDiagnosticLocation::getValidSourceLocation(S, LC), SM); + } + + return PathDiagnosticLocation::createDeclEnd(ErrorNode->getLocationContext(), + SM); } //===----------------------------------------------------------------------===// @@ -3068,7 +3089,7 @@ findExecutedLines(const SourceManager &SM, const ExplodedNode *N) { // Inlined function: show signature. const Decl* D = CE->getCalleeContext()->getDecl(); populateExecutedLinesWithFunctionSignature(D, SM, *ExecutedLines); - } else if (const Stmt *S = PathDiagnosticLocation::getStmt(N)) { + } else if (const Stmt *S = N->getStmtForDiagnostics()) { populateExecutedLinesWithStmt(S, SM, *ExecutedLines); // Show extra context for some parent kinds. diff --git a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp index 715771fb38ee1..34de736659b97 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp @@ -308,9 +308,7 @@ PathDiagnosticPieceRef BugReporterVisitor::getDefaultEndPath(const BugReporterContext &BRC, const ExplodedNode *EndPathNode, const PathSensitiveBugReport &BR) { - PathDiagnosticLocation L = - PathDiagnosticLocation::createEndOfPath(EndPathNode); - + PathDiagnosticLocation L = BR.getLocation(); const auto &Ranges = BR.getRanges(); // Only add the statement itself as a range if we didn't specify any @@ -852,7 +850,7 @@ class MacroNullReturnSuppressionVisitor final : public BugReporterVisitor { /// \return Source location of right hand side of an assignment /// into \c RegionOfInterest, empty optional if none found. Optional matchAssignment(const ExplodedNode *N) { - const Stmt *S = PathDiagnosticLocation::getStmt(N); + const Stmt *S = N->getStmtForDiagnostics(); ProgramStateRef State = N->getState(); auto *LCtx = N->getLocationContext(); if (!S) @@ -1919,7 +1917,7 @@ static const Expr *peelOffOuterExpr(const Expr *Ex, static const ExplodedNode* findNodeForExpression(const ExplodedNode *N, const Expr *Inner) { while (N) { - if (PathDiagnosticLocation::getStmt(N) == Inner) + if (N->getStmtForDiagnostics() == Inner) return N; N = N->getFirstPred(); } diff --git a/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp b/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp index 03e813e1e676b..a8fa7ad2d98c5 100644 --- a/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp @@ -299,7 +299,9 @@ const CFGBlock *ExplodedNode::getCFGBlock() const { return BEP->getBlock(); // Find the node's current statement in the CFG. - if (const Stmt *S = PathDiagnosticLocation::getStmt(this)) + // FIXME: getStmtForDiagnostics() does nasty things in order to provide + // a valid statement for body farms, do we need this behavior here? + if (const Stmt *S = getStmtForDiagnostics()) return getLocationContext() ->getAnalysisDeclContext() ->getCFGStmtMap() @@ -308,6 +310,92 @@ const CFGBlock *ExplodedNode::getCFGBlock() const { return nullptr; } +static const LocationContext * +findTopAutosynthesizedParentContext(const LocationContext *LC) { + assert(LC->getAnalysisDeclContext()->isBodyAutosynthesized()); + const LocationContext *ParentLC = LC->getParent(); + assert(ParentLC && "We don't start analysis from autosynthesized code"); + while (ParentLC->getAnalysisDeclContext()->isBodyAutosynthesized()) { + LC = ParentLC; + ParentLC = LC->getParent(); + assert(ParentLC && "We don't start analysis from autosynthesized code"); + } + return LC; +} + +const Stmt *ExplodedNode::getStmtForDiagnostics() const { + // We cannot place diagnostics on autosynthesized code. + // Put them onto the call site through which we jumped into autosynthesized + // code for the first time. + const LocationContext *LC = getLocationContext(); + if (LC->getAnalysisDeclContext()->isBodyAutosynthesized()) { + // It must be a stack frame because we only autosynthesize functions. + return cast(findTopAutosynthesizedParentContext(LC)) + ->getCallSite(); + } + // Otherwise, see if the node's program point directly points to a statement. + // FIXME: Refactor into a ProgramPoint method? + ProgramPoint P = getLocation(); + if (auto SP = P.getAs()) + return SP->getStmt(); + if (auto BE = P.getAs()) + return BE->getSrc()->getTerminatorStmt(); + if (auto CE = P.getAs()) + return CE->getCallExpr(); + if (auto CEE = P.getAs()) + return CEE->getCalleeContext()->getCallSite(); + if (auto PIPP = P.getAs()) + return PIPP->getInitializer()->getInit(); + if (auto CEB = P.getAs()) + return CEB->getReturnStmt(); + if (auto FEP = P.getAs()) + return FEP->getStmt(); + + return nullptr; +} + +const Stmt *ExplodedNode::getNextStmtForDiagnostics() const { + for (const ExplodedNode *N = getFirstSucc(); N; N = N->getFirstSucc()) { + if (const Stmt *S = N->getStmtForDiagnostics()) { + // Check if the statement is '?' or '&&'/'||'. These are "merges", + // not actual statement points. + switch (S->getStmtClass()) { + case Stmt::ChooseExprClass: + case Stmt::BinaryConditionalOperatorClass: + case Stmt::ConditionalOperatorClass: + continue; + case Stmt::BinaryOperatorClass: { + BinaryOperatorKind Op = cast(S)->getOpcode(); + if (Op == BO_LAnd || Op == BO_LOr) + continue; + break; + } + default: + break; + } + // We found the statement, so return it. + return S; + } + } + + return nullptr; +} + +const Stmt *ExplodedNode::getPreviousStmtForDiagnostics() const { + for (const ExplodedNode *N = getFirstPred(); N; N = N->getFirstPred()) + if (const Stmt *S = N->getStmtForDiagnostics()) + return S; + + return nullptr; +} + +const Stmt *ExplodedNode::getCurrentOrPreviousStmtForDiagnostics() const { + if (const Stmt *S = getStmtForDiagnostics()) + return S; + + return getPreviousStmtForDiagnostics(); +} + ExplodedNode *ExplodedGraph::getNode(const ProgramPoint &L, ProgramStateRef State, bool IsSink, diff --git a/clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp b/clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp index 9838249ae82ca..1a09a521f1167 100644 --- a/clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp +++ b/clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp @@ -165,7 +165,9 @@ static bool isPossiblyEscaped(const VarDecl *VD, ExplodedNode *N) { return true; while (!N->pred_empty()) { - const Stmt *S = PathDiagnosticLocation::getStmt(N); + // FIXME: getStmtForDiagnostics() does nasty things in order to provide + // a valid statement for body farms, do we need this behavior here? + const Stmt *S = N->getStmtForDiagnostics(); if (!S) { N = N->getFirstPred(); continue; diff --git a/clang/lib/StaticAnalyzer/Core/PathDiagnostic.cpp b/clang/lib/StaticAnalyzer/Core/PathDiagnostic.cpp index 94d1c095b8bfe..853029ac7b002 100644 --- a/clang/lib/StaticAnalyzer/Core/PathDiagnostic.cpp +++ b/clang/lib/StaticAnalyzer/Core/PathDiagnostic.cpp @@ -30,7 +30,6 @@ #include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/FoldingSet.h" #include "llvm/ADT/None.h" @@ -524,12 +523,12 @@ PathDiagnosticConsumer::FilesMade::getFiles(const PathDiagnostic &PD) { // PathDiagnosticLocation methods. //===----------------------------------------------------------------------===// -static SourceLocation getValidSourceLocation(const Stmt* S, - LocationOrAnalysisDeclContext LAC, - bool UseEnd = false) { - SourceLocation L = UseEnd ? S->getEndLoc() : S->getBeginLoc(); - assert(!LAC.isNull() && "A valid LocationContext or AnalysisDeclContext should " - "be passed to PathDiagnosticLocation upon creation."); +SourceLocation PathDiagnosticLocation::getValidSourceLocation( + const Stmt *S, LocationOrAnalysisDeclContext LAC, bool UseEndOfStatement) { + SourceLocation L = UseEndOfStatement ? S->getEndLoc() : S->getBeginLoc(); + assert(!LAC.isNull() && + "A valid LocationContext or AnalysisDeclContext should be passed to " + "PathDiagnosticLocation upon creation."); // S might be a temporary statement that does not have a location in the // source code, so find an enclosing statement and use its location. @@ -559,7 +558,7 @@ static SourceLocation getValidSourceLocation(const Stmt* S, break; } - L = UseEnd ? Parent->getEndLoc() : Parent->getBeginLoc(); + L = UseEndOfStatement ? Parent->getEndLoc() : Parent->getBeginLoc(); } while (!L.isValid()); } @@ -778,117 +777,6 @@ PathDiagnosticLocation::create(const ProgramPoint& P, return PathDiagnosticLocation(S, SMng, P.getLocationContext()); } -static const LocationContext * -findTopAutosynthesizedParentContext(const LocationContext *LC) { - assert(LC->getAnalysisDeclContext()->isBodyAutosynthesized()); - const LocationContext *ParentLC = LC->getParent(); - assert(ParentLC && "We don't start analysis from autosynthesized code"); - while (ParentLC->getAnalysisDeclContext()->isBodyAutosynthesized()) { - LC = ParentLC; - ParentLC = LC->getParent(); - assert(ParentLC && "We don't start analysis from autosynthesized code"); - } - return LC; -} - -const Stmt *PathDiagnosticLocation::getStmt(const ExplodedNode *N) { - // We cannot place diagnostics on autosynthesized code. - // Put them onto the call site through which we jumped into autosynthesized - // code for the first time. - const LocationContext *LC = N->getLocationContext(); - if (LC->getAnalysisDeclContext()->isBodyAutosynthesized()) { - // It must be a stack frame because we only autosynthesize functions. - return cast(findTopAutosynthesizedParentContext(LC)) - ->getCallSite(); - } - // Otherwise, see if the node's program point directly points to a statement. - ProgramPoint P = N->getLocation(); - if (auto SP = P.getAs()) - return SP->getStmt(); - if (auto BE = P.getAs()) - return BE->getSrc()->getTerminatorStmt(); - if (auto CE = P.getAs()) - return CE->getCallExpr(); - if (auto CEE = P.getAs()) - return CEE->getCalleeContext()->getCallSite(); - if (auto PIPP = P.getAs()) - return PIPP->getInitializer()->getInit(); - if (auto CEB = P.getAs()) - return CEB->getReturnStmt(); - if (auto FEP = P.getAs()) - return FEP->getStmt(); - - return nullptr; -} - -const Stmt *PathDiagnosticLocation::getNextStmt(const ExplodedNode *N) { - for (N = N->getFirstSucc(); N; N = N->getFirstSucc()) { - if (const Stmt *S = getStmt(N)) { - // Check if the statement is '?' or '&&'/'||'. These are "merges", - // not actual statement points. - switch (S->getStmtClass()) { - case Stmt::ChooseExprClass: - case Stmt::BinaryConditionalOperatorClass: - case Stmt::ConditionalOperatorClass: - continue; - case Stmt::BinaryOperatorClass: { - BinaryOperatorKind Op = cast(S)->getOpcode(); - if (Op == BO_LAnd || Op == BO_LOr) - continue; - break; - } - default: - break; - } - // We found the statement, so return it. - return S; - } - } - - return nullptr; -} - -PathDiagnosticLocation -PathDiagnosticLocation::createEndOfPath(const ExplodedNode *N) { - assert(N && "Cannot create a location with a null node."); - const Stmt *S = getStmt(N); - const LocationContext *LC = N->getLocationContext(); - SourceManager &SM = - N->getState()->getStateManager().getContext().getSourceManager(); - - if (!S) { - // If this is an implicit call, return the implicit call point location. - if (Optional PIE = N->getLocationAs()) - return PathDiagnosticLocation(PIE->getLocation(), SM); - if (auto FE = N->getLocationAs()) { - if (const ReturnStmt *RS = FE->getStmt()) - return PathDiagnosticLocation::createBegin(RS, SM, LC); - } - S = getNextStmt(N); - } - - if (S) { - ProgramPoint P = N->getLocation(); - - // For member expressions, return the location of the '.' or '->'. - if (const auto *ME = dyn_cast(S)) - return PathDiagnosticLocation::createMemberLoc(ME, SM); - - // For binary operators, return the location of the operator. - if (const auto *B = dyn_cast(S)) - return PathDiagnosticLocation::createOperatorLoc(B, SM); - - if (P.getAs()) - return PathDiagnosticLocation::createEnd(S, SM, LC); - - if (S->getBeginLoc().isValid()) - return PathDiagnosticLocation(S, SM, LC); - return PathDiagnosticLocation(getValidSourceLocation(S, LC), SM); - } - - return createDeclEnd(N->getLocationContext(), SM); -} - PathDiagnosticLocation PathDiagnosticLocation::createSingleLocation( const PathDiagnosticLocation &PDL) { FullSourceLoc L = PDL.asLocation(); From 1c5ee0cbd76f1c66fa4e9975f1e15def106f5041 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Wed, 11 Sep 2019 20:54:24 +0000 Subject: [PATCH 151/181] [analyzer] NFC: Move resetDiagnosticLocationToMainFile() to BugReporter. This method of PathDiagnostic is a part of Static Analyzer's particular path diagnostic construction scheme. As such, it doesn't belong to the PathDiagnostic class, but to the Analyzer. Differential Revision: https://reviews.llvm.org/D67418 llvm-svn: 371660 (cherry picked from commit 2bce23a4f8af8c208d97d6a3503e8659b1a4f71f) --- .../Core/BugReporter/PathDiagnostic.h | 23 ++++--- clang/lib/StaticAnalyzer/Core/BugReporter.cpp | 67 ++++++++++++++++++- .../StaticAnalyzer/Core/PathDiagnostic.cpp | 64 ------------------ 3 files changed, 79 insertions(+), 75 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h index aaeeb48cf2002..fdab18e7fe5a2 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h @@ -792,11 +792,6 @@ class PathDiagnostic : public llvm::FoldingSetNode { VerboseDesc += S; } - /// If the last piece of the report point to the header file, resets - /// the location of the report to be the last location in the main source - /// file. - void resetDiagnosticLocationToMainFile(); - StringRef getVerboseDescription() const { return VerboseDesc; } StringRef getShortDescription() const { @@ -807,11 +802,6 @@ class PathDiagnostic : public llvm::FoldingSetNode { StringRef getBugType() const { return BugType; } StringRef getCategory() const { return Category; } - /// Return the semantic context where an issue occurred. If the - /// issue occurs along a path, this represents the "central" area - /// where the bug manifests. - const Decl *getDeclWithIssue() const { return DeclWithIssue; } - using meta_iterator = std::deque::const_iterator; meta_iterator meta_begin() const { return OtherDesc.begin(); } @@ -826,10 +816,23 @@ class PathDiagnostic : public llvm::FoldingSetNode { return *ExecutedLines; } + /// Return the semantic context where an issue occurred. If the + /// issue occurs along a path, this represents the "central" area + /// where the bug manifests. + const Decl *getDeclWithIssue() const { return DeclWithIssue; } + + void setDeclWithIssue(const Decl *D) { + DeclWithIssue = D; + } + PathDiagnosticLocation getLocation() const { return Loc; } + void setLocation(PathDiagnosticLocation NewLoc) { + Loc = NewLoc; + } + /// Get the location on which the report should be uniqued. PathDiagnosticLocation getUniqueingLoc() const { return UniqueingLoc; diff --git a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp index 7dbbe106a367a..05c45f19f4e14 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp @@ -3123,6 +3123,71 @@ BugReporter::generateDiagnosticForConsumerMap( return Out; } +static PathDiagnosticCallPiece * +getFirstStackedCallToHeaderFile(PathDiagnosticCallPiece *CP, + const SourceManager &SMgr) { + SourceLocation CallLoc = CP->callEnter.asLocation(); + + // If the call is within a macro, don't do anything (for now). + if (CallLoc.isMacroID()) + return nullptr; + + assert(AnalysisManager::isInCodeFile(CallLoc, SMgr) && + "The call piece should not be in a header file."); + + // Check if CP represents a path through a function outside of the main file. + if (!AnalysisManager::isInCodeFile(CP->callEnterWithin.asLocation(), SMgr)) + return CP; + + const PathPieces &Path = CP->path; + if (Path.empty()) + return nullptr; + + // Check if the last piece in the callee path is a call to a function outside + // of the main file. + if (auto *CPInner = dyn_cast(Path.back().get())) + return getFirstStackedCallToHeaderFile(CPInner, SMgr); + + // Otherwise, the last piece is in the main file. + return nullptr; +} + +static void resetDiagnosticLocationToMainFile(PathDiagnostic &PD) { + if (PD.path.empty()) + return; + + PathDiagnosticPiece *LastP = PD.path.back().get(); + assert(LastP); + const SourceManager &SMgr = LastP->getLocation().getManager(); + + // We only need to check if the report ends inside headers, if the last piece + // is a call piece. + if (auto *CP = dyn_cast(LastP)) { + CP = getFirstStackedCallToHeaderFile(CP, SMgr); + if (CP) { + // Mark the piece. + CP->setAsLastInMainSourceFile(); + + // Update the path diagnostic message. + const auto *ND = dyn_cast(CP->getCallee()); + if (ND) { + SmallString<200> buf; + llvm::raw_svector_ostream os(buf); + os << " (within a call to '" << ND->getDeclName() << "')"; + PD.appendToDesc(os.str()); + } + + // Reset the report containing declaration and location. + PD.setDeclWithIssue(CP->getCaller()); + PD.setLocation(CP->getLocation()); + + return; + } + } +} + + + std::unique_ptr PathSensitiveBugReporter::generateDiagnosticForConsumerMap( BugReport *exampleReport, ArrayRef consumers, @@ -3157,7 +3222,7 @@ PathSensitiveBugReporter::generateDiagnosticForConsumerMap( const AnalyzerOptions &Opts = getAnalyzerOptions(); for (auto const &P : *Out) if (Opts.ShouldReportIssuesInMainSourceFile && !Opts.AnalyzeAll) - P.second->resetDiagnosticLocationToMainFile(); + resetDiagnosticLocationToMainFile(*P.second); return Out; } diff --git a/clang/lib/StaticAnalyzer/Core/PathDiagnostic.cpp b/clang/lib/StaticAnalyzer/Core/PathDiagnostic.cpp index 853029ac7b002..cc0be8b1b54df 100644 --- a/clang/lib/StaticAnalyzer/Core/PathDiagnostic.cpp +++ b/clang/lib/StaticAnalyzer/Core/PathDiagnostic.cpp @@ -29,7 +29,6 @@ #include "clang/Basic/LLVM.h" #include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/FoldingSet.h" #include "llvm/ADT/None.h" @@ -130,69 +129,6 @@ PathDiagnostic::PathDiagnostic( UniqueingDecl(DeclToUnique), ExecutedLines(std::move(ExecutedLines)), path(pathImpl) {} -static PathDiagnosticCallPiece * -getFirstStackedCallToHeaderFile(PathDiagnosticCallPiece *CP, - const SourceManager &SMgr) { - SourceLocation CallLoc = CP->callEnter.asLocation(); - - // If the call is within a macro, don't do anything (for now). - if (CallLoc.isMacroID()) - return nullptr; - - assert(AnalysisManager::isInCodeFile(CallLoc, SMgr) && - "The call piece should not be in a header file."); - - // Check if CP represents a path through a function outside of the main file. - if (!AnalysisManager::isInCodeFile(CP->callEnterWithin.asLocation(), SMgr)) - return CP; - - const PathPieces &Path = CP->path; - if (Path.empty()) - return nullptr; - - // Check if the last piece in the callee path is a call to a function outside - // of the main file. - if (auto *CPInner = dyn_cast(Path.back().get())) - return getFirstStackedCallToHeaderFile(CPInner, SMgr); - - // Otherwise, the last piece is in the main file. - return nullptr; -} - -void PathDiagnostic::resetDiagnosticLocationToMainFile() { - if (path.empty()) - return; - - PathDiagnosticPiece *LastP = path.back().get(); - assert(LastP); - const SourceManager &SMgr = LastP->getLocation().getManager(); - - // We only need to check if the report ends inside headers, if the last piece - // is a call piece. - if (auto *CP = dyn_cast(LastP)) { - CP = getFirstStackedCallToHeaderFile(CP, SMgr); - if (CP) { - // Mark the piece. - CP->setAsLastInMainSourceFile(); - - // Update the path diagnostic message. - const auto *ND = dyn_cast(CP->getCallee()); - if (ND) { - SmallString<200> buf; - llvm::raw_svector_ostream os(buf); - os << " (within a call to '" << ND->getDeclName() << "')"; - appendToDesc(os.str()); - } - - // Reset the report containing declaration and location. - DeclWithIssue = CP->getCaller(); - Loc = CP->getLocation(); - - return; - } - } -} - void PathDiagnosticConsumer::anchor() {} PathDiagnosticConsumer::~PathDiagnosticConsumer() { From fa82362ae5654da9473aa6142dc1d2eba63e43a5 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Wed, 11 Sep 2019 20:54:27 +0000 Subject: [PATCH 152/181] [analyzer] NFC: Move PathDiagnostic classes to libAnalysis. At this point the PathDiagnostic, PathDiagnosticLocation, PathDiagnosticPiece structures no longer rely on anything specific to Static Analyzer, so we can move them out of it for everybody to use. PathDiagnosticConsumers are still to be handed off. Differential Revision: https://reviews.llvm.org/D67419 llvm-svn: 371661 (cherry picked from commit f0bb45fac35c14edd09e3fbe603db75caaf3ef22) --- clang-tools-extra/clang-tidy/ClangTidy.cpp | 9 +++++---- .../Core/BugReporter => Analysis}/PathDiagnostic.h | 0 .../clang/StaticAnalyzer/Core/BugReporter/BugReporter.h | 2 +- .../StaticAnalyzer/Core/PathSensitive/AnalysisManager.h | 2 +- clang/lib/Analysis/CMakeLists.txt | 1 + .../{StaticAnalyzer/Core => Analysis}/PathDiagnostic.cpp | 2 +- clang/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp | 2 +- .../Checkers/CheckObjCInstMethSignature.cpp | 2 +- .../Checkers/ObjCMissingSuperCallChecker.cpp | 2 +- .../StaticAnalyzer/Checkers/ObjCUnusedIVarsChecker.cpp | 2 +- .../Checkers/RetainCountChecker/RetainCountChecker.h | 2 +- .../Checkers/RetainCountChecker/RetainCountDiagnostics.h | 2 +- clang/lib/StaticAnalyzer/Core/BugReporter.cpp | 2 +- clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp | 2 +- clang/lib/StaticAnalyzer/Core/CMakeLists.txt | 1 - clang/lib/StaticAnalyzer/Core/CallEvent.cpp | 2 +- clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp | 2 +- clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp | 2 +- clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp | 2 +- clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp | 2 +- 20 files changed, 22 insertions(+), 21 deletions(-) rename clang/include/clang/{StaticAnalyzer/Core/BugReporter => Analysis}/PathDiagnostic.h (100%) rename clang/lib/{StaticAnalyzer/Core => Analysis}/PathDiagnostic.cpp (99%) diff --git a/clang-tools-extra/clang-tidy/ClangTidy.cpp b/clang-tools-extra/clang-tidy/ClangTidy.cpp index 61878930c0424..39d2bdb0b84f1 100644 --- a/clang-tools-extra/clang-tidy/ClangTidy.cpp +++ b/clang-tools-extra/clang-tidy/ClangTidy.cpp @@ -37,10 +37,6 @@ #include "clang/Rewrite/Frontend/FixItRewriter.h" #include "clang/Rewrite/Frontend/FrontendActions.h" #include "clang/Tooling/Core/Diagnostic.h" -#if CLANG_ENABLE_STATIC_ANALYZER -#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" -#include "clang/StaticAnalyzer/Frontend/AnalysisConsumer.h" -#endif // CLANG_ENABLE_STATIC_ANALYZER #include "clang/Tooling/DiagnosticsYaml.h" #include "clang/Tooling/Refactoring.h" #include "clang/Tooling/ReplacementsYaml.h" @@ -50,6 +46,11 @@ #include #include +#if CLANG_ENABLE_STATIC_ANALYZER +#include "clang/Analysis/PathDiagnostic.h" +#include "clang/StaticAnalyzer/Frontend/AnalysisConsumer.h" +#endif // CLANG_ENABLE_STATIC_ANALYZER + using namespace clang::ast_matchers; using namespace clang::driver; using namespace clang::tooling; diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h b/clang/include/clang/Analysis/PathDiagnostic.h similarity index 100% rename from clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h rename to clang/include/clang/Analysis/PathDiagnostic.h diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h index 75f939e1be5e3..5d086c9e9cfc3 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h @@ -14,10 +14,10 @@ #ifndef LLVM_CLANG_STATICANALYZER_CORE_BUGREPORTER_BUGREPORTER_H #define LLVM_CLANG_STATICANALYZER_CORE_BUGREPORTER_BUGREPORTER_H +#include "clang/Analysis/PathDiagnostic.h" #include "clang/Basic/LLVM.h" #include "clang/Basic/SourceLocation.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h" -#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h index ffddffe439c60..d605a6a667f6b 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h @@ -15,9 +15,9 @@ #define LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_ANALYSISMANAGER_H #include "clang/Analysis/AnalysisDeclContext.h" +#include "clang/Analysis/PathDiagnostic.h" #include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" -#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" #include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h" namespace clang { diff --git a/clang/lib/Analysis/CMakeLists.txt b/clang/lib/Analysis/CMakeLists.txt index 92717143467d5..c1d0d6ed62c50 100644 --- a/clang/lib/Analysis/CMakeLists.txt +++ b/clang/lib/Analysis/CMakeLists.txt @@ -18,6 +18,7 @@ add_clang_library(clangAnalysis ExprMutationAnalyzer.cpp LiveVariables.cpp ObjCNoReturn.cpp + PathDiagnostic.cpp PostOrderCFGView.cpp ProgramPoint.cpp ReachableCode.cpp diff --git a/clang/lib/StaticAnalyzer/Core/PathDiagnostic.cpp b/clang/lib/Analysis/PathDiagnostic.cpp similarity index 99% rename from clang/lib/StaticAnalyzer/Core/PathDiagnostic.cpp rename to clang/lib/Analysis/PathDiagnostic.cpp index cc0be8b1b54df..33cb813b19584 100644 --- a/clang/lib/StaticAnalyzer/Core/PathDiagnostic.cpp +++ b/clang/lib/Analysis/PathDiagnostic.cpp @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" +#include "clang/Analysis/PathDiagnostic.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclBase.h" #include "clang/AST/DeclCXX.h" diff --git a/clang/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp b/clang/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp index 5f64e5a0dda3f..ed14cdd06206b 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp @@ -28,6 +28,7 @@ //===----------------------------------------------------------------------===// #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/Analysis/PathDiagnostic.h" #include "clang/AST/Attr.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/Expr.h" @@ -36,7 +37,6 @@ #include "clang/Basic/TargetInfo.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" -#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" diff --git a/clang/lib/StaticAnalyzer/Checkers/CheckObjCInstMethSignature.cpp b/clang/lib/StaticAnalyzer/Checkers/CheckObjCInstMethSignature.cpp index a020d33bfd95f..1694c237cda42 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CheckObjCInstMethSignature.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CheckObjCInstMethSignature.cpp @@ -13,11 +13,11 @@ //===----------------------------------------------------------------------===// #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/Analysis/PathDiagnostic.h" #include "clang/AST/ASTContext.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/Type.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" -#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "llvm/ADT/DenseMap.h" #include "llvm/Support/raw_ostream.h" diff --git a/clang/lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp index 33e4d2af000dd..1870c08432de3 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp @@ -13,12 +13,12 @@ //===----------------------------------------------------------------------===// #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/Analysis/PathDiagnostic.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/Expr.h" #include "clang/AST/ExprObjC.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" -#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" #include "llvm/ADT/SmallSet.h" diff --git a/clang/lib/StaticAnalyzer/Checkers/ObjCUnusedIVarsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ObjCUnusedIVarsChecker.cpp index 45137d6cd04bf..cb47704515723 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ObjCUnusedIVarsChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ObjCUnusedIVarsChecker.cpp @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/Analysis/PathDiagnostic.h" #include "clang/AST/Attr.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/Expr.h" @@ -20,7 +21,6 @@ #include "clang/Basic/LangOptions.h" #include "clang/Basic/SourceManager.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" -#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" #include "clang/StaticAnalyzer/Core/Checker.h" using namespace clang; diff --git a/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.h b/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.h index 124c0e5040b99..dd79bbef321c3 100644 --- a/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.h +++ b/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.h @@ -21,12 +21,12 @@ #include "clang/AST/DeclObjC.h" #include "clang/AST/ParentMap.h" #include "clang/Analysis/DomainSpecific/CocoaConventions.h" +#include "clang/Analysis/PathDiagnostic.h" #include "clang/Analysis/RetainSummaryManager.h" #include "clang/Basic/LangOptions.h" #include "clang/Basic/SourceManager.h" #include "clang/Analysis/SelectorExtras.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" -#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" diff --git a/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.h b/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.h index 3b212127c9616..e9e2777540548 100644 --- a/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.h +++ b/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.h @@ -14,10 +14,10 @@ #ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_RETAINCOUNTCHECKER_DIAGNOSTICS_H #define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_RETAINCOUNTCHECKER_DIAGNOSTICS_H +#include "clang/Analysis/PathDiagnostic.h" #include "clang/Analysis/RetainSummaryManager.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h" -#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" namespace clang { diff --git a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp index 05c45f19f4e14..16a217a09bc92 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp @@ -24,6 +24,7 @@ #include "clang/Analysis/AnalysisDeclContext.h" #include "clang/Analysis/CFG.h" #include "clang/Analysis/CFGStmtMap.h" +#include "clang/Analysis/PathDiagnostic.h" #include "clang/Analysis/ProgramPoint.h" #include "clang/Basic/LLVM.h" #include "clang/Basic/SourceLocation.h" @@ -31,7 +32,6 @@ #include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" -#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h" diff --git a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp index 34de736659b97..da619c9f3bb36 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp @@ -26,6 +26,7 @@ #include "clang/Analysis/AnalysisDeclContext.h" #include "clang/Analysis/CFG.h" #include "clang/Analysis/CFGStmtMap.h" +#include "clang/Analysis/PathDiagnostic.h" #include "clang/Analysis/ProgramPoint.h" #include "clang/Basic/IdentifierTable.h" #include "clang/Basic/LLVM.h" @@ -34,7 +35,6 @@ #include "clang/Lex/Lexer.h" #include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" -#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h" diff --git a/clang/lib/StaticAnalyzer/Core/CMakeLists.txt b/clang/lib/StaticAnalyzer/Core/CMakeLists.txt index 05bc94c7c9842..0207470ccf1e1 100644 --- a/clang/lib/StaticAnalyzer/Core/CMakeLists.txt +++ b/clang/lib/StaticAnalyzer/Core/CMakeLists.txt @@ -30,7 +30,6 @@ add_clang_library(clangStaticAnalyzerCore LoopUnrolling.cpp LoopWidening.cpp MemRegion.cpp - PathDiagnostic.cpp PlistDiagnostics.cpp ProgramState.cpp RangeConstraintManager.cpp diff --git a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp index b5d8599012acb..5f04a59ba0551 100644 --- a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp +++ b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp @@ -27,6 +27,7 @@ #include "clang/Analysis/AnalysisDeclContext.h" #include "clang/Analysis/CFG.h" #include "clang/Analysis/CFGStmtMap.h" +#include "clang/Analysis/PathDiagnostic.h" #include "clang/Analysis/ProgramPoint.h" #include "clang/CrossTU/CrossTranslationUnit.h" #include "clang/Basic/IdentifierTable.h" @@ -34,7 +35,6 @@ #include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" #include "clang/Basic/Specifiers.h" -#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeInfo.h" #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h" diff --git a/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp b/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp index 39500a20fde3c..d3df18ae76ae8 100644 --- a/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp +++ b/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +#include "clang/Analysis/PathDiagnostic.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclBase.h" #include "clang/AST/Stmt.h" @@ -23,7 +24,6 @@ #include "clang/Rewrite/Core/HTMLRewrite.h" #include "clang/Rewrite/Core/Rewriter.h" #include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" -#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" #include "clang/StaticAnalyzer/Core/IssueHash.h" #include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h" #include "llvm/ADT/ArrayRef.h" diff --git a/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp b/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp index 2a4076838c6f8..e85906b268a80 100644 --- a/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp +++ b/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +#include "clang/Analysis/PathDiagnostic.h" #include "clang/Basic/FileManager.h" #include "clang/Basic/PlistSupport.h" #include "clang/Basic/SourceManager.h" @@ -20,7 +21,6 @@ #include "clang/Lex/TokenConcatenation.h" #include "clang/Rewrite/Core/HTMLRewrite.h" #include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" -#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" #include "clang/StaticAnalyzer/Core/IssueHash.h" #include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h" #include "llvm/ADT/SmallPtrSet.h" diff --git a/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp b/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp index 4fbbf908caa8c..2f668875f08b5 100644 --- a/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp +++ b/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp @@ -10,10 +10,10 @@ // //===----------------------------------------------------------------------===// +#include "clang/Analysis/PathDiagnostic.h" #include "clang/Basic/Version.h" #include "clang/Lex/Preprocessor.h" #include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" -#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" #include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringMap.h" diff --git a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp index 5acacc1decf55..803c4f288f257 100644 --- a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp +++ b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp @@ -12,6 +12,7 @@ #include "clang/StaticAnalyzer/Frontend/AnalysisConsumer.h" #include "ModelInjector.h" +#include "clang/Analysis/PathDiagnostic.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclObjC.h" @@ -27,7 +28,6 @@ #include "clang/StaticAnalyzer/Checkers/LocalCheckers.h" #include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" -#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h" #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" From 5801e6383abd088f8a4d021d37ece9d87ab6699d Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Thu, 12 Sep 2019 18:53:48 +0000 Subject: [PATCH 153/181] [analyzer] Don't run the analyzer for -analyzer-list-enabled-checkers Short and sweet. Whenever I use -analyzer-list-enabled-checkers, I'm only interested about the configuration, not about the analysis. Differential Revision: https://reviews.llvm.org/D66714 llvm-svn: 371756 (cherry picked from commit d977b67ed617f4c1f7e0d1450f3d0adc41cb07b4) --- .../ExecuteCompilerInvocation.cpp | 1 + .../test/Analysis/analyzer-enabled-checkers.c | 71 ++++++++++++++----- 2 files changed, 54 insertions(+), 18 deletions(-) diff --git a/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp b/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp index c5225d1873360..25db5a67474c0 100644 --- a/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp +++ b/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp @@ -274,6 +274,7 @@ bool ExecuteCompilerInvocation(CompilerInstance *Clang) { AnOpts, Clang->getDiagnostics(), Clang->getLangOpts()); + return true; } // Honor -analyzer-config-help. diff --git a/clang/test/Analysis/analyzer-enabled-checkers.c b/clang/test/Analysis/analyzer-enabled-checkers.c index 0ea01a010af63..0618a68978190 100644 --- a/clang/test/Analysis/analyzer-enabled-checkers.c +++ b/clang/test/Analysis/analyzer-enabled-checkers.c @@ -1,20 +1,55 @@ -// RUN: %clang_analyze_cc1 -triple x86_64-apple-darwin10 %s -o /dev/null -analyzer-checker=core -analyzer-list-enabled-checkers > %t 2>&1 -// RUN: FileCheck --input-file=%t %s +// RUN: %clang --analyze %s \ +// RUN: -Xclang -triple -Xclang x86_64-pc-linux-gnu \ +// RUN: -Xclang -analyzer-list-enabled-checkers \ +// RUN: -Xclang -analyzer-display-progress \ +// RUN: 2>&1 | FileCheck %s --implicit-check-not=ANALYZE \ +// RUN: --implicit-check-not=\. -// CHECK: OVERVIEW: Clang Static Analyzer Enabled Checkers List -// CHECK: core.CallAndMessage -// CHECK: core.DivideZero -// CHECK: core.DynamicTypePropagation -// CHECK: core.NonNullParamChecker -// CHECK: core.NullDereference -// CHECK: core.StackAddressEscape -// CHECK: core.UndefinedBinaryOperatorResult -// CHECK: core.VLASize -// CHECK: core.builtin.BuiltinFunctions -// CHECK: core.builtin.NoReturnFunctions -// CHECK: core.uninitialized.ArraySubscript -// CHECK: core.uninitialized.Assign -// CHECK: core.uninitialized.Branch -// CHECK: core.uninitialized.CapturedBlockVariable -// CHECK: core.uninitialized.UndefReturn +// CHECK: OVERVIEW: Clang Static Analyzer Enabled Checkers List +// CHECK-EMPTY: +// CHECK-NEXT: apiModeling.StdCLibraryFunctions +// CHECK-NEXT: apiModeling.TrustNonnull +// CHECK-NEXT: apiModeling.llvm.CastValue +// CHECK-NEXT: apiModeling.llvm.ReturnValue +// CHECK-NEXT: core.CallAndMessage +// CHECK-NEXT: core.DivideZero +// CHECK-NEXT: core.DynamicTypePropagation +// CHECK-NEXT: core.NonNullParamChecker +// CHECK-NEXT: core.NonnilStringConstants +// CHECK-NEXT: core.NullDereference +// CHECK-NEXT: core.StackAddrEscapeBase +// CHECK-NEXT: core.StackAddressEscape +// CHECK-NEXT: core.UndefinedBinaryOperatorResult +// CHECK-NEXT: core.VLASize +// CHECK-NEXT: core.builtin.BuiltinFunctions +// CHECK-NEXT: core.builtin.NoReturnFunctions +// CHECK-NEXT: core.uninitialized.ArraySubscript +// CHECK-NEXT: core.uninitialized.Assign +// CHECK-NEXT: core.uninitialized.Branch +// CHECK-NEXT: core.uninitialized.CapturedBlockVariable +// CHECK-NEXT: core.uninitialized.UndefReturn +// CHECK-NEXT: deadcode.DeadStores +// CHECK-NEXT: nullability.NullabilityBase +// CHECK-NEXT: nullability.NullPassedToNonnull +// CHECK-NEXT: nullability.NullReturnedFromNonnull +// CHECK-NEXT: security.insecureAPI.SecuritySyntaxChecker +// CHECK-NEXT: security.insecureAPI.UncheckedReturn +// CHECK-NEXT: security.insecureAPI.getpw +// CHECK-NEXT: security.insecureAPI.gets +// CHECK-NEXT: security.insecureAPI.mkstemp +// CHECK-NEXT: security.insecureAPI.mktemp +// CHECK-NEXT: security.insecureAPI.vfork +// CHECK-NEXT: unix.API +// CHECK-NEXT: unix.cstring.CStringModeling +// CHECK-NEXT: unix.DynamicMemoryModeling +// CHECK-NEXT: unix.Malloc +// CHECK-NEXT: unix.MallocSizeof +// CHECK-NEXT: unix.MismatchedDeallocator +// CHECK-NEXT: unix.Vfork +// CHECK-NEXT: unix.cstring.BadSizeArg +// CHECK-NEXT: unix.cstring.NullArg +int main() { + int i; + (void)(10 / i); +} From 080f1567c591d13bd55c8698b3545b16a2967ead Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Thu, 12 Sep 2019 19:09:24 +0000 Subject: [PATCH 154/181] [analyzer][NFC] Fix inconsistent references to checkers as "checks" Traditionally, clang-tidy uses the term check, and the analyzer uses checker, but in the very early years, this wasn't the case, and code originating from the early 2010's still incorrectly refer to checkers as checks. This patch attempts to hunt down most of these, aiming to refer to checkers as checkers, but preserve references to callback functions (like checkPreCall) as checks. Differential Revision: https://reviews.llvm.org/D67140 llvm-svn: 371760 (cherry picked from commit 72649423c043341c770516cd73aecde281730260) --- clang/include/clang/Analysis/PathDiagnostic.h | 6 +- .../StaticAnalyzer/Core/AnalyzerOptions.h | 88 +++++++++++-------- .../Core/BugReporter/BugReporter.h | 4 +- .../StaticAnalyzer/Core/BugReporter/BugType.h | 40 ++++----- .../clang/StaticAnalyzer/Core/Checker.h | 4 +- .../StaticAnalyzer/Core/CheckerManager.h | 23 ++--- .../Core/PathSensitive/ExprEngine.h | 2 +- clang/lib/Analysis/PathDiagnostic.cpp | 4 +- .../SampleAnalyzer/MainCallChecker.cpp | 2 +- clang/lib/Frontend/CompilerInvocation.cpp | 2 +- .../Checkers/CStringChecker.cpp | 20 ++--- .../Checkers/CallAndMessageChecker.cpp | 10 ++- .../Checkers/CheckSecuritySyntaxOnly.cpp | 34 ++++--- .../DeleteWithNonVirtualDtorChecker.cpp | 3 +- .../Checkers/ExprInspectionChecker.cpp | 2 +- .../Checkers/IteratorChecker.cpp | 8 +- .../Checkers/IvarInvalidationChecker.cpp | 14 ++- .../StaticAnalyzer/Checkers/MallocChecker.cpp | 11 ++- .../Checkers/NullabilityChecker.cpp | 14 +-- .../UndefinedArraySubscriptChecker.cpp | 3 +- .../StaticAnalyzer/Checkers/ValistChecker.cpp | 5 +- .../Checkers/VirtualCallChecker.cpp | 12 +-- clang/lib/StaticAnalyzer/Core/BugReporter.cpp | 12 +-- clang/lib/StaticAnalyzer/Core/Checker.cpp | 8 +- .../StaticAnalyzer/Core/CheckerManager.cpp | 2 +- .../lib/StaticAnalyzer/Core/ExprEngineCXX.cpp | 3 +- .../StaticAnalyzer/Core/HTMLDiagnostics.cpp | 5 +- .../StaticAnalyzer/Core/PlistDiagnostics.cpp | 4 +- .../StaticAnalyzer/Core/SarifDiagnostics.cpp | 8 +- .../Frontend/AnalysisConsumer.cpp | 2 +- .../Frontend/CheckerRegistry.cpp | 2 +- .../RegisterCustomCheckersTest.cpp | 2 +- 32 files changed, 184 insertions(+), 175 deletions(-) diff --git a/clang/include/clang/Analysis/PathDiagnostic.h b/clang/include/clang/Analysis/PathDiagnostic.h index fdab18e7fe5a2..6730057cf0adc 100644 --- a/clang/include/clang/Analysis/PathDiagnostic.h +++ b/clang/include/clang/Analysis/PathDiagnostic.h @@ -725,7 +725,7 @@ using FilesToLineNumsMap = std::map>; /// diagnostic. It represents an ordered-collection of PathDiagnosticPieces, /// each which represent the pieces of the path. class PathDiagnostic : public llvm::FoldingSetNode { - std::string CheckName; + std::string CheckerName; const Decl *DeclWithIssue; std::string BugType; std::string VerboseDesc; @@ -749,7 +749,7 @@ class PathDiagnostic : public llvm::FoldingSetNode { public: PathDiagnostic() = delete; - PathDiagnostic(StringRef CheckName, const Decl *DeclWithIssue, + PathDiagnostic(StringRef CheckerName, const Decl *DeclWithIssue, StringRef bugtype, StringRef verboseDesc, StringRef shortDesc, StringRef category, PathDiagnosticLocation LocationToUnique, const Decl *DeclToUnique, @@ -798,7 +798,7 @@ class PathDiagnostic : public llvm::FoldingSetNode { return ShortDesc.empty() ? VerboseDesc : ShortDesc; } - StringRef getCheckName() const { return CheckName; } + StringRef getCheckerName() const { return CheckerName; } StringRef getBugType() const { return BugType; } StringRef getCategory() const { return Category; } diff --git a/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.h b/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.h index 609932ad70e21..04d611a94a7f5 100644 --- a/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.h +++ b/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.h @@ -163,41 +163,15 @@ class AnalyzerOptions : public RefCountedBase { public: using ConfigTable = llvm::StringMap; + /// Retrieves the list of checkers generated from Checkers.td. This doesn't + /// contain statically linked but non-generated checkers and plugin checkers! static std::vector - getRegisteredCheckers(bool IncludeExperimental = false) { - static const StringRef StaticAnalyzerChecks[] = { -#define GET_CHECKERS -#define CHECKER(FULLNAME, CLASS, HELPTEXT, DOC_URI, IS_HIDDEN) FULLNAME, -#include "clang/StaticAnalyzer/Checkers/Checkers.inc" -#undef CHECKER -#undef GET_CHECKERS - }; - std::vector Checkers; - for (StringRef CheckerName : StaticAnalyzerChecks) { - if (!CheckerName.startswith("debug.") && - (IncludeExperimental || !CheckerName.startswith("alpha."))) - Checkers.push_back(CheckerName); - } - return Checkers; - } + getRegisteredCheckers(bool IncludeExperimental = false); + /// Retrieves the list of packages generated from Checkers.td. This doesn't + /// contain statically linked but non-generated packages and plugin packages! static std::vector - getRegisteredPackages(bool IncludeExperimental = false) { - static const StringRef StaticAnalyzerPackages[] = { -#define GET_PACKAGES -#define PACKAGE(FULLNAME) FULLNAME, -#include "clang/StaticAnalyzer/Checkers/Checkers.inc" -#undef PACKAGE -#undef GET_PACKAGES - }; - std::vector Packages; - for (StringRef PackageName : StaticAnalyzerPackages) { - if (PackageName != "debug" && - (IncludeExperimental || PackageName != "alpha")) - Packages.push_back(PackageName); - } - return Packages; - } + getRegisteredPackages(bool IncludeExperimental = false); /// Convenience function for printing options or checkers and their /// description in a formatted manner. If \p MinLineWidth is set to 0, no line @@ -247,12 +221,12 @@ class AnalyzerOptions : public RefCountedBase { /// The maximum number of times the analyzer visits a block. unsigned maxBlockVisitOnPath; - /// Disable all analyzer checks. + /// Disable all analyzer checkers. /// - /// This flag allows one to disable analyzer checks on the code processed by + /// This flag allows one to disable analyzer checkers on the code processed by /// the given analysis consumer. Note, the code will get parsed and the /// command-line options will get checked. - unsigned DisableAllChecks : 1; + unsigned DisableAllCheckers : 1; unsigned ShowCheckerHelp : 1; unsigned ShowCheckerHelpAlpha : 1; @@ -327,7 +301,7 @@ class AnalyzerOptions : public RefCountedBase { } AnalyzerOptions() - : DisableAllChecks(false), ShowCheckerHelp(false), + : DisableAllCheckers(false), ShowCheckerHelp(false), ShowCheckerHelpAlpha(false), ShowCheckerHelpDeveloper(false), ShowCheckerOptionList(false), ShowCheckerOptionAlphaList(false), ShowCheckerOptionDeveloperList(false), ShowEnabledCheckerList(false), @@ -345,7 +319,7 @@ class AnalyzerOptions : public RefCountedBase { /// If an option value is not provided, returns the given \p DefaultVal. /// @param [in] CheckerName The *full name* of the checker. One may retrieve /// this from the checker object's field \c Name, or through \c - /// CheckerManager::getCurrentCheckName within the checker's registry + /// CheckerManager::getCurrentCheckerName within the checker's registry /// function. /// Checker options are retrieved in the following format: /// `-analyzer-config CheckerName:OptionName=Value. @@ -365,7 +339,7 @@ class AnalyzerOptions : public RefCountedBase { /// If an option value is not provided, returns the given \p DefaultVal. /// @param [in] CheckerName The *full name* of the checker. One may retrieve /// this from the checker object's field \c Name, or through \c - /// CheckerManager::getCurrentCheckName within the checker's registry + /// CheckerManager::getCurrentCheckerName within the checker's registry /// function. /// Checker options are retrieved in the following format: /// `-analyzer-config CheckerName:OptionName=Value. @@ -385,7 +359,7 @@ class AnalyzerOptions : public RefCountedBase { /// If an option value is not provided, returns the given \p DefaultVal. /// @param [in] CheckerName The *full name* of the checker. One may retrieve /// this from the checker object's field \c Name, or through \c - /// CheckerManager::getCurrentCheckName within the checker's registry + /// CheckerManager::getCurrentCheckerName within the checker's registry /// function. /// Checker options are retrieved in the following format: /// `-analyzer-config CheckerName:OptionName=Value. @@ -439,6 +413,42 @@ inline UserModeKind AnalyzerOptions::getUserMode() const { return K.getValue(); } +inline std::vector +AnalyzerOptions::getRegisteredCheckers(bool IncludeExperimental) { + static const StringRef StaticAnalyzerCheckerNames[] = { +#define GET_CHECKERS +#define CHECKER(FULLNAME, CLASS, HELPTEXT, DOC_URI, IS_HIDDEN) FULLNAME, +#include "clang/StaticAnalyzer/Checkers/Checkers.inc" +#undef CHECKER +#undef GET_CHECKERS + }; + std::vector Checkers; + for (StringRef CheckerName : StaticAnalyzerCheckerNames) { + if (!CheckerName.startswith("debug.") && + (IncludeExperimental || !CheckerName.startswith("alpha."))) + Checkers.push_back(CheckerName); + } + return Checkers; +} + +inline std::vector +AnalyzerOptions::getRegisteredPackages(bool IncludeExperimental) { + static const StringRef StaticAnalyzerPackageNames[] = { +#define GET_PACKAGES +#define PACKAGE(FULLNAME) FULLNAME, +#include "clang/StaticAnalyzer/Checkers/Checkers.inc" +#undef PACKAGE +#undef GET_PACKAGES + }; + std::vector Packages; + for (StringRef PackageName : StaticAnalyzerPackageNames) { + if (PackageName != "debug" && + (IncludeExperimental || PackageName != "alpha")) + Packages.push_back(PackageName); + } + return Packages; +} + } // namespace clang #endif // LLVM_CLANG_STATICANALYZER_CORE_ANALYZEROPTIONS_H diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h index 5d086c9e9cfc3..fae4770c9b858 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h @@ -621,7 +621,7 @@ class BugReporter { ArrayRef Ranges = None, ArrayRef Fixits = None); - void EmitBasicReport(const Decl *DeclWithIssue, CheckName CheckName, + void EmitBasicReport(const Decl *DeclWithIssue, CheckerNameRef CheckerName, StringRef BugName, StringRef BugCategory, StringRef BugStr, PathDiagnosticLocation Loc, ArrayRef Ranges = None, @@ -632,7 +632,7 @@ class BugReporter { /// Returns a BugType that is associated with the given name and /// category. - BugType *getBugTypeForName(CheckName CheckName, StringRef name, + BugType *getBugTypeForName(CheckerNameRef CheckerName, StringRef name, StringRef category); virtual BugReport * diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugType.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugType.h index 324b5312e790d..237053df7e442 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugType.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugType.h @@ -28,8 +28,8 @@ class ExprEngine; class BugType { private: - const CheckName Check; - const std::string Name; + const CheckerNameRef CheckerName; + const std::string Description; const std::string Category; const CheckerBase *Checker; bool SuppressOnSink; @@ -37,28 +37,27 @@ class BugType { virtual void anchor(); public: - BugType(CheckName Check, StringRef Name, StringRef Cat, - bool SuppressOnSink=false) - : Check(Check), Name(Name), Category(Cat), Checker(nullptr), - SuppressOnSink(SuppressOnSink) {} + BugType(CheckerNameRef CheckerName, StringRef Name, StringRef Cat, + bool SuppressOnSink = false) + : CheckerName(CheckerName), Description(Name), Category(Cat), + Checker(nullptr), SuppressOnSink(SuppressOnSink) {} BugType(const CheckerBase *Checker, StringRef Name, StringRef Cat, - bool SuppressOnSink=false) - : Check(Checker->getCheckName()), Name(Name), Category(Cat), - Checker(Checker), SuppressOnSink(SuppressOnSink) {} + bool SuppressOnSink = false) + : CheckerName(Checker->getCheckerName()), Description(Name), + Category(Cat), Checker(Checker), SuppressOnSink(SuppressOnSink) {} virtual ~BugType() = default; - StringRef getName() const { return Name; } + StringRef getDescription() const { return Description; } StringRef getCategory() const { return Category; } - StringRef getCheckName() const { - // FIXME: This is a workaround to ensure that the correct check name is used - // The check names are set after the constructors are run. + StringRef getCheckerName() const { + // FIXME: This is a workaround to ensure that the correct checerk name is + // used. The checker names are set after the constructors are run. // In case the BugType object is initialized in the checker's ctor - // the Check field will be empty. To circumvent this problem we use + // the CheckerName field will be empty. To circumvent this problem we use // CheckerBase whenever it is possible. - StringRef CheckName = - Checker ? Checker->getCheckName().getName() : Check.getName(); - assert(!CheckName.empty() && "Check name is not set properly."); - return CheckName; + StringRef Ret = Checker ? Checker->getCheckerName() : CheckerName; + assert(!Ret.empty() && "Checker name is not set properly."); + return Ret; } /// isSuppressOnSink - Returns true if bug reports associated with this bug @@ -71,8 +70,9 @@ class BuiltinBug : public BugType { const std::string desc; void anchor() override; public: - BuiltinBug(class CheckName check, const char *name, const char *description) - : BugType(check, name, categories::LogicError), desc(description) {} + BuiltinBug(class CheckerNameRef checker, const char *name, + const char *description) + : BugType(checker, name, categories::LogicError), desc(description) {} BuiltinBug(const CheckerBase *checker, const char *name, const char *description) diff --git a/clang/include/clang/StaticAnalyzer/Core/Checker.h b/clang/include/clang/StaticAnalyzer/Core/Checker.h index d0fe15f8b8963..0c7acdbc3a97e 100644 --- a/clang/include/clang/StaticAnalyzer/Core/Checker.h +++ b/clang/include/clang/StaticAnalyzer/Core/Checker.h @@ -490,12 +490,12 @@ class Call { } // end eval namespace class CheckerBase : public ProgramPointTag { - CheckName Name; + CheckerNameRef Name; friend class ::clang::ento::CheckerManager; public: StringRef getTagDescription() const override; - CheckName getCheckName() const; + CheckerNameRef getCheckerName() const; /// See CheckerManager::runCheckersForPrintState. virtual void printState(raw_ostream &Out, ProgramStateRef State, diff --git a/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h b/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h index 8eec7ed993759..38a9aaf72c27a 100644 --- a/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h +++ b/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h @@ -90,19 +90,20 @@ enum PointerEscapeKind { PSK_EscapeOther }; -// This wrapper is used to ensure that only StringRefs originating from the -// CheckerRegistry are used as check names. We want to make sure all check -// name strings have a lifetime that keeps them alive at least until the path -// diagnostics have been processed. -class CheckName { +/// This wrapper is used to ensure that only StringRefs originating from the +/// CheckerRegistry are used as check names. We want to make sure all checker +/// name strings have a lifetime that keeps them alive at least until the path +/// diagnostics have been processed, since they are expected to be constexpr +/// string literals (most likely generated by TblGen). +class CheckerNameRef { friend class ::clang::ento::CheckerRegistry; StringRef Name; - explicit CheckName(StringRef Name) : Name(Name) {} + explicit CheckerNameRef(StringRef Name) : Name(Name) {} public: - CheckName() = default; + CheckerNameRef() = default; StringRef getName() const { return Name; } operator StringRef() const { return Name; } @@ -118,7 +119,7 @@ class CheckerManager { ASTContext &Context; const LangOptions LangOpts; AnalyzerOptions &AOptions; - CheckName CurrentCheckName; + CheckerNameRef CurrentCheckerName; public: CheckerManager(ASTContext &Context, AnalyzerOptions &AOptions) @@ -126,8 +127,8 @@ class CheckerManager { ~CheckerManager(); - void setCurrentCheckName(CheckName name) { CurrentCheckName = name; } - CheckName getCurrentCheckName() const { return CurrentCheckName; } + void setCurrentCheckerName(CheckerNameRef name) { CurrentCheckerName = name; } + CheckerNameRef getCurrentCheckerName() const { return CurrentCheckerName; } bool hasPathSensitiveCheckers() const; @@ -163,7 +164,7 @@ class CheckerManager { assert(!ref && "Checker already registered, use getChecker!"); CHECKER *checker = new CHECKER(std::forward(Args)...); - checker->Name = CurrentCheckName; + checker->Name = CurrentCheckerName; CheckerDtors.push_back(CheckerDtor(checker, destruct)); CHECKER::_register(checker, *this); ref = checker; diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h index eeb606ebfde5f..2d0967616ff20 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h @@ -666,7 +666,7 @@ class ExprEngine : public SubEngine { const LocationContext *LCtx, ProgramStateRef State); - /// Evaluate a call, running pre- and post-call checks and allowing checkers + /// Evaluate a call, running pre- and post-call checkers and allowing checkers /// to be responsible for handling the evaluation of the call itself. void evalCall(ExplodedNodeSet &Dst, ExplodedNode *Pred, const CallEvent &Call); diff --git a/clang/lib/Analysis/PathDiagnostic.cpp b/clang/lib/Analysis/PathDiagnostic.cpp index 33cb813b19584..764cb8ed0e4f8 100644 --- a/clang/lib/Analysis/PathDiagnostic.cpp +++ b/clang/lib/Analysis/PathDiagnostic.cpp @@ -117,11 +117,11 @@ void PathPieces::flattenTo(PathPieces &Primary, PathPieces &Current, PathDiagnostic::~PathDiagnostic() = default; PathDiagnostic::PathDiagnostic( - StringRef CheckName, const Decl *declWithIssue, StringRef bugtype, + StringRef CheckerName, const Decl *declWithIssue, StringRef bugtype, StringRef verboseDesc, StringRef shortDesc, StringRef category, PathDiagnosticLocation LocationToUnique, const Decl *DeclToUnique, std::unique_ptr ExecutedLines) - : CheckName(CheckName), DeclWithIssue(declWithIssue), + : CheckerName(CheckerName), DeclWithIssue(declWithIssue), BugType(StripTrailingDots(bugtype)), VerboseDesc(StripTrailingDots(verboseDesc)), ShortDesc(StripTrailingDots(shortDesc)), diff --git a/clang/lib/Analysis/plugins/SampleAnalyzer/MainCallChecker.cpp b/clang/lib/Analysis/plugins/SampleAnalyzer/MainCallChecker.cpp index 4c65d05e20e93..57fa316a4db49 100644 --- a/clang/lib/Analysis/plugins/SampleAnalyzer/MainCallChecker.cpp +++ b/clang/lib/Analysis/plugins/SampleAnalyzer/MainCallChecker.cpp @@ -37,7 +37,7 @@ void MainCallChecker::checkPreStmt(const CallExpr *CE, BT.reset(new BugType(this, "call to main", "example analyzer plugin")); auto report = - llvm::make_unique(*BT, BT->getName(), N); + llvm::make_unique(*BT, BT->getDescription(), N); report->addRange(Callee->getSourceRange()); C.emitReport(std::move(report)); } diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp index 0cb6db02e8675..8241c3e906d12 100644 --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -303,7 +303,7 @@ static bool ParseAnalyzerArgs(AnalyzerOptions &Opts, ArgList &Args, .Case("true", true) .Case("false", false) .Default(false); - Opts.DisableAllChecks = Args.hasArg(OPT_analyzer_disable_all_checks); + Opts.DisableAllCheckers = Args.hasArg(OPT_analyzer_disable_all_checks); Opts.visualizeExplodedGraphWithGraphViz = Args.hasArg(OPT_analyzer_viz_egraph_graphviz); diff --git a/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp index 13bab06e253bb..fc461d2ea94ab 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp @@ -48,10 +48,10 @@ class CStringChecker : public Checker< eval::Call, DefaultBool CheckCStringBufferOverlap; DefaultBool CheckCStringNotNullTerm; - CheckName CheckNameCStringNullArg; - CheckName CheckNameCStringOutOfBounds; - CheckName CheckNameCStringBufferOverlap; - CheckName CheckNameCStringNotNullTerm; + CheckerNameRef CheckNameCStringNullArg; + CheckerNameRef CheckNameCStringOutOfBounds; + CheckerNameRef CheckNameCStringBufferOverlap; + CheckerNameRef CheckNameCStringNotNullTerm; }; CStringChecksFilter Filter; @@ -2409,14 +2409,12 @@ bool ento::shouldRegisterCStringModeling(const LangOptions &LO) { void ento::register##name(CheckerManager &mgr) { \ CStringChecker *checker = mgr.getChecker(); \ checker->Filter.Check##name = true; \ - checker->Filter.CheckName##name = mgr.getCurrentCheckName(); \ + checker->Filter.CheckName##name = mgr.getCurrentCheckerName(); \ } \ \ - bool ento::shouldRegister##name(const LangOptions &LO) { \ - return true; \ - } + bool ento::shouldRegister##name(const LangOptions &LO) { return true; } - REGISTER_CHECKER(CStringNullArg) - REGISTER_CHECKER(CStringOutOfBounds) - REGISTER_CHECKER(CStringBufferOverlap) +REGISTER_CHECKER(CStringNullArg) +REGISTER_CHECKER(CStringOutOfBounds) +REGISTER_CHECKER(CStringBufferOverlap) REGISTER_CHECKER(CStringNotNullTerm) diff --git a/clang/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp index e96ddc0338e78..e61c0664d521d 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp @@ -49,7 +49,7 @@ class CallAndMessageChecker public: DefaultBool Check_CallAndMessageUnInitRefArg; - CheckName CheckName_CallAndMessageUnInitRefArg; + CheckerNameRef CheckName_CallAndMessageUnInitRefArg; void checkPreStmt(const CallExpr *CE, CheckerContext &C) const; void checkPreStmt(const CXXDeleteExpr *DE, CheckerContext &C) const; @@ -95,7 +95,8 @@ void CallAndMessageChecker::emitBadCall(BugType *BT, CheckerContext &C, if (!N) return; - auto R = llvm::make_unique(*BT, BT->getName(), N); + auto R = + llvm::make_unique(*BT, BT->getDescription(), N); if (BadE) { R->addRange(BadE->getSourceRange()); if (BadE->isGLValue()) @@ -482,7 +483,8 @@ void CallAndMessageChecker::checkPreObjCMessage(const ObjCMethodCall &msg, } assert(BT && "Unknown message kind."); - auto R = llvm::make_unique(*BT, BT->getName(), N); + auto R = llvm::make_unique( + *BT, BT->getDescription(), N); const ObjCMessageExpr *ME = msg.getOriginExpr(); R->addRange(ME->getReceiverRange()); @@ -612,7 +614,7 @@ bool ento::shouldRegisterCallAndMessageChecker(const LangOptions &LO) { void ento::registerCallAndMessageUnInitRefArg(CheckerManager &mgr) { CallAndMessageChecker *Checker = mgr.getChecker(); Checker->Check_CallAndMessageUnInitRefArg = true; - Checker->CheckName_CallAndMessageUnInitRefArg = mgr.getCurrentCheckName(); + Checker->CheckName_CallAndMessageUnInitRefArg = mgr.getCurrentCheckerName(); } bool ento::shouldRegisterCallAndMessageUnInitRefArg(const LangOptions &LO) { diff --git a/clang/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp b/clang/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp index 7f32b15ae92f7..260a2896e78c8 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp @@ -50,19 +50,19 @@ struct ChecksFilter { DefaultBool check_FloatLoopCounter; DefaultBool check_UncheckedReturn; - CheckName checkName_bcmp; - CheckName checkName_bcopy; - CheckName checkName_bzero; - CheckName checkName_gets; - CheckName checkName_getpw; - CheckName checkName_mktemp; - CheckName checkName_mkstemp; - CheckName checkName_strcpy; - CheckName checkName_DeprecatedOrUnsafeBufferHandling; - CheckName checkName_rand; - CheckName checkName_vfork; - CheckName checkName_FloatLoopCounter; - CheckName checkName_UncheckedReturn; + CheckerNameRef checkName_bcmp; + CheckerNameRef checkName_bcopy; + CheckerNameRef checkName_bzero; + CheckerNameRef checkName_gets; + CheckerNameRef checkName_getpw; + CheckerNameRef checkName_mktemp; + CheckerNameRef checkName_mkstemp; + CheckerNameRef checkName_strcpy; + CheckerNameRef checkName_DeprecatedOrUnsafeBufferHandling; + CheckerNameRef checkName_rand; + CheckerNameRef checkName_vfork; + CheckerNameRef checkName_FloatLoopCounter; + CheckerNameRef checkName_UncheckedReturn; }; class WalkAST : public StmtVisitor { @@ -1015,14 +1015,12 @@ bool ento::shouldRegisterSecuritySyntaxChecker(const LangOptions &LO) { #define REGISTER_CHECKER(name) \ void ento::register##name(CheckerManager &mgr) { \ - SecuritySyntaxChecker *checker = mgr.getChecker(); \ + SecuritySyntaxChecker *checker = mgr.getChecker(); \ checker->filter.check_##name = true; \ - checker->filter.checkName_##name = mgr.getCurrentCheckName(); \ + checker->filter.checkName_##name = mgr.getCurrentCheckerName(); \ } \ \ - bool ento::shouldRegister##name(const LangOptions &LO) { \ - return true; \ - } + bool ento::shouldRegister##name(const LangOptions &LO) { return true; } REGISTER_CHECKER(bcmp) REGISTER_CHECKER(bcopy) diff --git a/clang/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp index d5ce88236a7b7..1ddafd7268d9c 100644 --- a/clang/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp @@ -92,7 +92,8 @@ void DeleteWithNonVirtualDtorChecker::checkPreStmt(const CXXDeleteExpr *DE, "Logic error")); ExplodedNode *N = C.generateNonFatalErrorNode(); - auto R = llvm::make_unique(*BT, BT->getName(), N); + auto R = + llvm::make_unique(*BT, BT->getDescription(), N); // Mark region of problematic base class for later use in the BugVisitor. R->markInteresting(BaseClassRegion); diff --git a/clang/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp index 5ba80e201cad4..3a771f783e756 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp @@ -305,7 +305,7 @@ void ExprInspectionChecker::analyzerHashDump(const CallExpr *CE, const SourceManager &SM = C.getSourceManager(); FullSourceLoc FL(CE->getArg(0)->getBeginLoc(), SM); std::string HashContent = - GetIssueString(SM, FL, getCheckName().getName(), "Category", + GetIssueString(SM, FL, getCheckerName().getName(), "Category", C.getLocationContext()->getDecl(), Opts); reportBug(HashContent, C); diff --git a/clang/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp index 4b5250b3448ab..883c4871402c4 100644 --- a/clang/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp @@ -248,7 +248,7 @@ class IteratorChecker }; DefaultBool ChecksEnabled[CK_NumCheckKinds]; - CheckName CheckNames[CK_NumCheckKinds]; + CheckerNameRef CheckNames[CK_NumCheckKinds]; void checkPreCall(const CallEvent &Call, CheckerContext &C) const; void checkPostCall(const CallEvent &Call, CheckerContext &C) const; @@ -2380,12 +2380,10 @@ bool ento::shouldRegisterIteratorModeling(const LangOptions &LO) { auto *checker = Mgr.getChecker(); \ checker->ChecksEnabled[IteratorChecker::CK_##name] = true; \ checker->CheckNames[IteratorChecker::CK_##name] = \ - Mgr.getCurrentCheckName(); \ + Mgr.getCurrentCheckerName(); \ } \ \ - bool ento::shouldRegister##name(const LangOptions &LO) { \ - return true; \ - } + bool ento::shouldRegister##name(const LangOptions &LO) { return true; } REGISTER_CHECKER(IteratorRangeChecker) REGISTER_CHECKER(MismatchedIteratorChecker) diff --git a/clang/lib/StaticAnalyzer/Checkers/IvarInvalidationChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/IvarInvalidationChecker.cpp index 2b75f3acc9228..0d64fbd6f62ec 100644 --- a/clang/lib/StaticAnalyzer/Checkers/IvarInvalidationChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/IvarInvalidationChecker.cpp @@ -48,8 +48,8 @@ struct ChecksFilter { /// Check that all ivars are invalidated. DefaultBool check_InstanceVariableInvalidation; - CheckName checkName_MissingInvalidationMethod; - CheckName checkName_InstanceVariableInvalidation; + CheckerNameRef checkName_MissingInvalidationMethod; + CheckerNameRef checkName_InstanceVariableInvalidation; }; class IvarInvalidationCheckerImpl { @@ -199,7 +199,7 @@ class IvarInvalidationCheckerImpl { const ObjCIvarDecl *IvarDecl, const IvarToPropMapTy &IvarToPopertyMap); - void reportNoInvalidationMethod(CheckName CheckName, + void reportNoInvalidationMethod(CheckerNameRef CheckName, const ObjCIvarDecl *FirstIvarDecl, const IvarToPropMapTy &IvarToPopertyMap, const ObjCInterfaceDecl *InterfaceD, @@ -526,7 +526,7 @@ visit(const ObjCImplementationDecl *ImplD) const { } void IvarInvalidationCheckerImpl::reportNoInvalidationMethod( - CheckName CheckName, const ObjCIvarDecl *FirstIvarDecl, + CheckerNameRef CheckName, const ObjCIvarDecl *FirstIvarDecl, const IvarToPropMapTy &IvarToPopertyMap, const ObjCInterfaceDecl *InterfaceD, bool MissingDeclaration) const { SmallString<128> sbuf; @@ -748,12 +748,10 @@ bool ento::shouldRegisterIvarInvalidationModeling(const LangOptions &LO) { IvarInvalidationChecker *checker = \ mgr.getChecker(); \ checker->Filter.check_##name = true; \ - checker->Filter.checkName_##name = mgr.getCurrentCheckName(); \ + checker->Filter.checkName_##name = mgr.getCurrentCheckerName(); \ } \ \ - bool ento::shouldRegister##name(const LangOptions &LO) { \ - return true; \ - } + bool ento::shouldRegister##name(const LangOptions &LO) { return true; } REGISTER_CHECKER(InstanceVariableInvalidation) REGISTER_CHECKER(MissingInvalidationMethod) diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index d1e8b4a842c61..92f64aa1d707e 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -208,7 +208,7 @@ class MallocChecker : public Checker(); checker->ChecksEnabled[MallocChecker::CK_InnerPointerChecker] = true; checker->CheckNames[MallocChecker::CK_InnerPointerChecker] = - mgr.getCurrentCheckName(); + mgr.getCurrentCheckerName(); } void ento::registerDynamicMemoryModeling(CheckerManager &mgr) { @@ -3157,12 +3157,11 @@ bool ento::shouldRegisterDynamicMemoryModeling(const LangOptions &LO) { void ento::register##name(CheckerManager &mgr) { \ MallocChecker *checker = mgr.getChecker(); \ checker->ChecksEnabled[MallocChecker::CK_##name] = true; \ - checker->CheckNames[MallocChecker::CK_##name] = mgr.getCurrentCheckName(); \ + checker->CheckNames[MallocChecker::CK_##name] = \ + mgr.getCurrentCheckerName(); \ } \ \ - bool ento::shouldRegister##name(const LangOptions &LO) { \ - return true; \ - } + bool ento::shouldRegister##name(const LangOptions &LO) { return true; } REGISTER_CHECKER(MallocChecker) REGISTER_CHECKER(NewDeleteChecker) diff --git a/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp index 562bddd10c856..cdf998762c5b8 100644 --- a/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp @@ -112,11 +112,11 @@ class NullabilityChecker DefaultBool CheckNullablePassedToNonnull; DefaultBool CheckNullableReturnedFromNonnull; - CheckName CheckNameNullPassedToNonnull; - CheckName CheckNameNullReturnedFromNonnull; - CheckName CheckNameNullableDereferenced; - CheckName CheckNameNullablePassedToNonnull; - CheckName CheckNameNullableReturnedFromNonnull; + CheckerNameRef CheckNameNullPassedToNonnull; + CheckerNameRef CheckNameNullReturnedFromNonnull; + CheckerNameRef CheckNameNullableDereferenced; + CheckerNameRef CheckNameNullablePassedToNonnull; + CheckerNameRef CheckNameNullableReturnedFromNonnull; }; NullabilityChecksFilter Filter; @@ -1201,12 +1201,12 @@ bool ento::shouldRegisterNullabilityBase(const LangOptions &LO) { void ento::register##name##Checker(CheckerManager &mgr) { \ NullabilityChecker *checker = mgr.getChecker(); \ checker->Filter.Check##name = true; \ - checker->Filter.CheckName##name = mgr.getCurrentCheckName(); \ + checker->Filter.CheckName##name = mgr.getCurrentCheckerName(); \ checker->NeedTracking = checker->NeedTracking || trackingRequired; \ checker->NoDiagnoseCallsToSystemHeaders = \ checker->NoDiagnoseCallsToSystemHeaders || \ mgr.getAnalyzerOptions().getCheckerBooleanOption( \ - checker, "NoDiagnoseCallsToSystemHeaders", true); \ + checker, "NoDiagnoseCallsToSystemHeaders", true); \ } \ \ bool ento::shouldRegister##name##Checker(const LangOptions &LO) { \ diff --git a/clang/lib/StaticAnalyzer/Checkers/UndefinedArraySubscriptChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/UndefinedArraySubscriptChecker.cpp index 5bc39a7351a80..c91e22b50fbc3 100644 --- a/clang/lib/StaticAnalyzer/Checkers/UndefinedArraySubscriptChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/UndefinedArraySubscriptChecker.cpp @@ -52,7 +52,8 @@ UndefinedArraySubscriptChecker::checkPreStmt(const ArraySubscriptExpr *A, BT.reset(new BuiltinBug(this, "Array subscript is undefined")); // Generate a report for this bug. - auto R = llvm::make_unique(*BT, BT->getName(), N); + auto R = + llvm::make_unique(*BT, BT->getDescription(), N); R->addRange(A->getIdx()->getSourceRange()); bugreporter::trackExpressionValue(N, A->getIdx(), *R); C.emitReport(std::move(R)); diff --git a/clang/lib/StaticAnalyzer/Checkers/ValistChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ValistChecker.cpp index add9537001457..7e78d52f3d223 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ValistChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ValistChecker.cpp @@ -46,7 +46,7 @@ class ValistChecker : public Checker, }; DefaultBool ChecksEnabled[CK_NumCheckKinds]; - CheckName CheckNames[CK_NumCheckKinds]; + CheckerNameRef CheckNames[CK_NumCheckKinds]; void checkPreStmt(const VAArgExpr *VAA, CheckerContext &C) const; void checkPreCall(const CallEvent &Call, CheckerContext &C) const; @@ -412,7 +412,8 @@ bool ento::shouldRegisterValistBase(const LangOptions &LO) { void ento::register##name##Checker(CheckerManager &mgr) { \ ValistChecker *checker = mgr.getChecker(); \ checker->ChecksEnabled[ValistChecker::CK_##name] = true; \ - checker->CheckNames[ValistChecker::CK_##name] = mgr.getCurrentCheckName(); \ + checker->CheckNames[ValistChecker::CK_##name] = \ + mgr.getCurrentCheckerName(); \ } \ \ bool ento::shouldRegister##name##Checker(const LangOptions &LO) { \ diff --git a/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp index 45859d4b6a471..cd3af0534a841 100644 --- a/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp @@ -206,20 +206,20 @@ void ento::registerVirtualCallModeling(CheckerManager &Mgr) { void ento::registerPureVirtualCallChecker(CheckerManager &Mgr) { auto *Chk = Mgr.getChecker(); - Chk->BT_Pure = llvm::make_unique( - Mgr.getCurrentCheckName(), "Pure virtual method call", - categories::CXXObjectLifecycle); + Chk->BT_Pure = llvm::make_unique(Mgr.getCurrentCheckerName(), + "Pure virtual method call", + categories::CXXObjectLifecycle); } void ento::registerVirtualCallChecker(CheckerManager &Mgr) { auto *Chk = Mgr.getChecker(); if (!Mgr.getAnalyzerOptions().getCheckerBooleanOption( - Mgr.getCurrentCheckName(), "PureOnly")) { + Mgr.getCurrentCheckerName(), "PureOnly")) { Chk->BT_Impure = llvm::make_unique( - Mgr.getCurrentCheckName(), "Unexpected loss of virtual dispatch", + Mgr.getCurrentCheckerName(), "Unexpected loss of virtual dispatch", categories::CXXObjectLifecycle); Chk->ShowFixIts = Mgr.getAnalyzerOptions().getCheckerBooleanOption( - Mgr.getCurrentCheckName(), "ShowFixIts"); + Mgr.getCurrentCheckerName(), "ShowFixIts"); } } diff --git a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp index 16a217a09bc92..1cfd3a8753041 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp @@ -1311,7 +1311,7 @@ static std::unique_ptr generateDiagnosticForBasicReport(const BasicBugReport *R) { const BugType &BT = R->getBugType(); return llvm::make_unique( - BT.getCheckName(), R->getDeclWithIssue(), BT.getName(), + BT.getCheckerName(), R->getDeclWithIssue(), BT.getDescription(), R->getDescription(), R->getShortDescription(/*UseFallback=*/false), BT.getCategory(), R->getUniqueingLocation(), R->getUniqueingDecl(), llvm::make_unique()); @@ -1322,7 +1322,7 @@ generateEmptyDiagnosticForReport(const PathSensitiveBugReport *R, const SourceManager &SM) { const BugType &BT = R->getBugType(); return llvm::make_unique( - BT.getCheckName(), R->getDeclWithIssue(), BT.getName(), + BT.getCheckerName(), R->getDeclWithIssue(), BT.getDescription(), R->getDescription(), R->getShortDescription(/*UseFallback=*/false), BT.getCategory(), R->getUniqueingLocation(), R->getUniqueingDecl(), findExecutedLines(SM, R->getErrorNode())); @@ -3233,12 +3233,12 @@ void BugReporter::EmitBasicReport(const Decl *DeclWithIssue, PathDiagnosticLocation Loc, ArrayRef Ranges, ArrayRef Fixits) { - EmitBasicReport(DeclWithIssue, Checker->getCheckName(), Name, Category, Str, + EmitBasicReport(DeclWithIssue, Checker->getCheckerName(), Name, Category, Str, Loc, Ranges, Fixits); } void BugReporter::EmitBasicReport(const Decl *DeclWithIssue, - CheckName CheckName, + CheckerNameRef CheckName, StringRef name, StringRef category, StringRef str, PathDiagnosticLocation Loc, ArrayRef Ranges, @@ -3254,8 +3254,8 @@ void BugReporter::EmitBasicReport(const Decl *DeclWithIssue, emitReport(std::move(R)); } -BugType *BugReporter::getBugTypeForName(CheckName CheckName, StringRef name, - StringRef category) { +BugType *BugReporter::getBugTypeForName(CheckerNameRef CheckName, + StringRef name, StringRef category) { SmallString<136> fullDesc; llvm::raw_svector_ostream(fullDesc) << CheckName.getName() << ":" << name << ":" << category; diff --git a/clang/lib/StaticAnalyzer/Core/Checker.cpp b/clang/lib/StaticAnalyzer/Core/Checker.cpp index f4e6f909d7644..bc1c8964b3ee4 100644 --- a/clang/lib/StaticAnalyzer/Core/Checker.cpp +++ b/clang/lib/StaticAnalyzer/Core/Checker.cpp @@ -19,10 +19,10 @@ using namespace ento; int ImplicitNullDerefEvent::Tag; StringRef CheckerBase::getTagDescription() const { - return getCheckName().getName(); + return getCheckerName().getName(); } -CheckName CheckerBase::getCheckName() const { return Name; } +CheckerNameRef CheckerBase::getCheckerName() const { return Name; } CheckerProgramPointTag::CheckerProgramPointTag(StringRef CheckerName, StringRef Msg) @@ -30,10 +30,10 @@ CheckerProgramPointTag::CheckerProgramPointTag(StringRef CheckerName, CheckerProgramPointTag::CheckerProgramPointTag(const CheckerBase *Checker, StringRef Msg) - : SimpleProgramPointTag(Checker->getCheckName().getName(), Msg) {} + : SimpleProgramPointTag(Checker->getCheckerName().getName(), Msg) {} raw_ostream& clang::ento::operator<<(raw_ostream &Out, const CheckerBase &Checker) { - Out << Checker.getCheckName().getName(); + Out << Checker.getCheckerName().getName(); return Out; } diff --git a/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp b/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp index 27d5797b4cbc9..f676bd8952836 100644 --- a/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp +++ b/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp @@ -748,7 +748,7 @@ void CheckerManager::runCheckersForPrintStateJson(raw_ostream &Out, continue; Indent(Out, Space, IsDot) - << "{ \"checker\": \"" << CT.second->getCheckName().getName() + << "{ \"checker\": \"" << CT.second->getCheckerName().getName() << "\", \"messages\": [" << NL; Indent(Out, InnerSpace, IsDot) << '\"' << TempBuf.str().trim() << '\"' << NL; diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp index 83303d722f271..058be985540d3 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp @@ -775,7 +775,8 @@ void ExprEngine::VisitCXXNewExpr(const CXXNewExpr *CNE, ExplodedNode *Pred, if (!AMgr.getAnalyzerOptions().MayInlineCXXAllocator) { // Invalidate placement args. // FIXME: Once we figure out how we want allocators to work, - // we should be using the usual pre-/(default-)eval-/post-call checks here. + // we should be using the usual pre-/(default-)eval-/post-call checkers + // here. State = Call->invalidateRegions(blockCount); if (!State) return; diff --git a/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp b/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp index d3df18ae76ae8..a4918d7179ff5 100644 --- a/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp +++ b/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp @@ -555,8 +555,9 @@ void HTMLDiagnostics::FinalizeHTML(const PathDiagnostic& D, Rewriter &R, os << "\n\n"; os << "\n\n"; + << GetIssueHash(SMgr, L, D.getCheckerName(), D.getBugType(), + DeclWithIssue, PP.getLangOpts()) + << " -->\n"; os << "\n\n"; o << " issue_hash_content_of_line_in_context"; @@ -689,7 +689,7 @@ void PlistDiagnostics::FlushDiagnosticsImpl( : D->getLocation().asLocation()), SM); const Decl *DeclWithIssue = D->getDeclWithIssue(); - EmitString(o, GetIssueHash(SM, L, D->getCheckName(), D->getBugType(), + EmitString(o, GetIssueHash(SM, L, D->getCheckerName(), D->getBugType(), DeclWithIssue, LangOpts)) << '\n'; diff --git a/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp b/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp index 2f668875f08b5..1e60966e7bbd0 100644 --- a/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp +++ b/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp @@ -240,7 +240,7 @@ static json::Object createResult(const PathDiagnostic &Diag, json::Array &Files, const PathPieces &Path = Diag.path.flatten(false); const SourceManager &SMgr = Path.front()->getLocation().getManager(); - auto Iter = RuleMapping.find(Diag.getCheckName()); + auto Iter = RuleMapping.find(Diag.getCheckerName()); assert(Iter != RuleMapping.end() && "Rule ID is not in the array index map?"); return json::Object{ @@ -251,7 +251,7 @@ static json::Object createResult(const PathDiagnostic &Diag, json::Array &Files, Diag.getLocation().asRange(), *Diag.getLocation().asLocation().getFileEntry(), SMgr, Files))}}, {"ruleIndex", Iter->getValue()}, - {"ruleId", Diag.getCheckName()}}; + {"ruleId", Diag.getCheckerName()}}; } static StringRef getRuleDescription(StringRef CheckName) { @@ -277,7 +277,7 @@ static StringRef getRuleHelpURIStr(StringRef CheckName) { } static json::Object createRule(const PathDiagnostic &Diag) { - StringRef CheckName = Diag.getCheckName(); + StringRef CheckName = Diag.getCheckerName(); json::Object Ret{ {"fullDescription", createMessage(getRuleDescription(CheckName))}, {"name", createMessage(CheckName)}, @@ -296,7 +296,7 @@ static json::Array createRules(std::vector &Diags, llvm::StringSet<> Seen; llvm::for_each(Diags, [&](const PathDiagnostic *D) { - StringRef RuleID = D->getCheckName(); + StringRef RuleID = D->getCheckerName(); std::pair::iterator, bool> P = Seen.insert(RuleID); if (P.second) { RuleMapping[RuleID] = Rules.size(); // Maps RuleID to an Array Index. diff --git a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp index 803c4f288f257..99f92bb81773e 100644 --- a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp +++ b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp @@ -651,7 +651,7 @@ void AnalysisConsumer::HandleTranslationUnit(ASTContext &C) { if (isBisonFile(C)) { reportAnalyzerProgress("Skipping bison-generated file\n"); - } else if (Opts->DisableAllChecks) { + } else if (Opts->DisableAllCheckers) { // Don't analyze if the user explicitly asked for no checks to be performed // on this file. diff --git a/clang/lib/StaticAnalyzer/Frontend/CheckerRegistry.cpp b/clang/lib/StaticAnalyzer/Frontend/CheckerRegistry.cpp index 322304b0fb70e..e00fd976f6b80 100644 --- a/clang/lib/StaticAnalyzer/Frontend/CheckerRegistry.cpp +++ b/clang/lib/StaticAnalyzer/Frontend/CheckerRegistry.cpp @@ -437,7 +437,7 @@ void CheckerRegistry::initializeManager(CheckerManager &CheckerMgr) const { // Initialize the CheckerManager with all enabled checkers. for (const auto *Checker : enabledCheckers) { - CheckerMgr.setCurrentCheckName(CheckName(Checker->FullName)); + CheckerMgr.setCurrentCheckerName(CheckerNameRef(Checker->FullName)); Checker->Initialize(CheckerMgr); } } diff --git a/clang/unittests/StaticAnalyzer/RegisterCustomCheckersTest.cpp b/clang/unittests/StaticAnalyzer/RegisterCustomCheckersTest.cpp index 109e762892636..940e9a8d8f0b6 100644 --- a/clang/unittests/StaticAnalyzer/RegisterCustomCheckersTest.cpp +++ b/clang/unittests/StaticAnalyzer/RegisterCustomCheckersTest.cpp @@ -30,7 +30,7 @@ class TestAction : public ASTFrontendAction { void FlushDiagnosticsImpl(std::vector &Diags, FilesMade *filesMade) override { for (const auto *PD : Diags) - Output << PD->getCheckName() << ":" << PD->getShortDescription(); + Output << PD->getCheckerName() << ":" << PD->getShortDescription(); } StringRef getName() const override { return "Test"; } From 786ed1e95abacfc3a7902338edf5c8d5c4143e98 Mon Sep 17 00:00:00 2001 From: Tim Shen Date: Thu, 12 Sep 2019 21:18:44 +0000 Subject: [PATCH 155/181] [ClangTidy] Adjust the name getCheckName to getCheckerName due to API change. llvm-svn: 371773 (cherry picked from commit 75e963ec6fae4ba36bae3c6712614847f8221754) --- clang-tools-extra/clang-tidy/ClangTidy.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang-tools-extra/clang-tidy/ClangTidy.cpp b/clang-tools-extra/clang-tidy/ClangTidy.cpp index 39d2bdb0b84f1..f623aa8ebb8f6 100644 --- a/clang-tools-extra/clang-tidy/ClangTidy.cpp +++ b/clang-tools-extra/clang-tidy/ClangTidy.cpp @@ -73,7 +73,7 @@ class AnalyzerDiagnosticConsumer : public ento::PathDiagnosticConsumer { FilesMade *filesMade) override { for (const ento::PathDiagnostic *PD : Diags) { SmallString<64> CheckName(AnalyzerCheckNamePrefix); - CheckName += PD->getCheckName(); + CheckName += PD->getCheckerName(); Context.diag(CheckName, PD->getLocation().asLocation(), PD->getShortDescription()) << PD->path.back()->getRanges(); From 3346f0b2bcf7740dca8ad6e833ce0c142ce37d62 Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Thu, 12 Sep 2019 19:52:34 +0000 Subject: [PATCH 156/181] [CFG] Add dumps for CFGElement and CFGElementRef Seems like we never had these, so here we go! I also did some refactoring as I was chasing a bug unrelated to this revision. Differential Revision: https://reviews.llvm.org/D66715 llvm-svn: 371765 (cherry picked from commit f174670efaa3cc20faf5ba8e40ceab7cee8f0f24) --- clang/include/clang/Analysis/CFG.h | 19 +++++- clang/lib/Analysis/CFG.cpp | 98 +++++++++++++++++++++--------- 2 files changed, 87 insertions(+), 30 deletions(-) diff --git a/clang/include/clang/Analysis/CFG.h b/clang/include/clang/Analysis/CFG.h index 4c375c7a069e0..b24e32c966777 100644 --- a/clang/include/clang/Analysis/CFG.h +++ b/clang/include/clang/Analysis/CFG.h @@ -121,6 +121,12 @@ class CFGElement { x |= Data1.getInt(); return (Kind) x; } + + void dumpToStream(llvm::raw_ostream &OS) const; + + void dump() const { + dumpToStream(llvm::errs()); + } }; class CFGStmt : public CFGElement { @@ -650,8 +656,17 @@ class CFGBlock { } bool operator!=(ElementRefImpl Other) const { return !(*this == Other); } - CFGElement operator*() { return (*Parent)[Index]; } - CFGElementPtr operator->() { return &*(Parent->begin() + Index); } + CFGElement operator*() const { return (*Parent)[Index]; } + CFGElementPtr operator->() const { return &*(Parent->begin() + Index); } + + void dumpToStream(llvm::raw_ostream &OS) const { + OS << getIndexInBlock() + 1 << ": "; + (*this)->dumpToStream(OS); + } + + void dump() const { + dumpToStream(llvm::errs()); + } }; template class ElementRefIterator { diff --git a/clang/lib/Analysis/CFG.cpp b/clang/lib/Analysis/CFG.cpp index 2e51cb8894d68..21fa9bee25ef3 100644 --- a/clang/lib/Analysis/CFG.cpp +++ b/clang/lib/Analysis/CFG.cpp @@ -4962,6 +4962,8 @@ class StmtPrinterHelper : public PrinterHelper { public: StmtPrinterHelper(const CFG* cfg, const LangOptions &LO) : LangOpts(LO) { + if (!cfg) + return; for (CFG::const_iterator I = cfg->begin(), E = cfg->end(); I != E; ++I ) { unsigned j = 1; for (CFGBlock::const_iterator BI = (*I)->begin(), BEnd = (*I)->end() ; @@ -5283,10 +5285,22 @@ static void print_construction_context(raw_ostream &OS, } } +static void print_elem(raw_ostream &OS, StmtPrinterHelper &Helper, + const CFGElement &E); + +void CFGElement::dumpToStream(llvm::raw_ostream &OS) const { + StmtPrinterHelper Helper(nullptr, {}); + print_elem(OS, Helper, *this); +} + static void print_elem(raw_ostream &OS, StmtPrinterHelper &Helper, const CFGElement &E) { - if (Optional CS = E.getAs()) { - const Stmt *S = CS->getStmt(); + switch (E.getKind()) { + case CFGElement::Kind::Statement: + case CFGElement::Kind::CXXRecordTypedCall: + case CFGElement::Kind::Constructor: { + CFGStmt CS = E.castAs(); + const Stmt *S = CS.getStmt(); assert(S != nullptr && "Expecting non-null Stmt"); // special printing for statement-expressions. @@ -5338,12 +5352,18 @@ static void print_elem(raw_ostream &OS, StmtPrinterHelper &Helper, // Expressions need a newline. if (isa(S)) OS << '\n'; - } else if (Optional IE = E.getAs()) { - print_initializer(OS, Helper, IE->getInitializer()); + + break; + } + + case CFGElement::Kind::Initializer: + print_initializer(OS, Helper, E.castAs().getInitializer()); OS << '\n'; - } else if (Optional DE = - E.getAs()) { - const VarDecl *VD = DE->getVarDecl(); + break; + + case CFGElement::Kind::AutomaticObjectDtor: { + CFGAutomaticObjDtor DE = E.castAs(); + const VarDecl *VD = DE.getVarDecl(); Helper.handleDecl(VD, OS); QualType T = VD->getType(); @@ -5353,53 +5373,75 @@ static void print_elem(raw_ostream &OS, StmtPrinterHelper &Helper, OS << ".~"; T.getUnqualifiedType().print(OS, PrintingPolicy(Helper.getLangOpts())); OS << "() (Implicit destructor)\n"; - } else if (Optional DE = E.getAs()) { - const VarDecl *VD = DE->getVarDecl(); - Helper.handleDecl(VD, OS); + break; + } + case CFGElement::Kind::LifetimeEnds: + Helper.handleDecl(E.castAs().getVarDecl(), OS); OS << " (Lifetime ends)\n"; - } else if (Optional LE = E.getAs()) { - const Stmt *LoopStmt = LE->getLoopStmt(); - OS << LoopStmt->getStmtClassName() << " (LoopExit)\n"; - } else if (Optional SB = E.getAs()) { + break; + + case CFGElement::Kind::LoopExit: + OS << E.castAs().getLoopStmt()->getStmtClassName() << " (LoopExit)\n"; + break; + + case CFGElement::Kind::ScopeBegin: OS << "CFGScopeBegin("; - if (const VarDecl *VD = SB->getVarDecl()) + if (const VarDecl *VD = E.castAs().getVarDecl()) OS << VD->getQualifiedNameAsString(); OS << ")\n"; - } else if (Optional SE = E.getAs()) { + break; + + case CFGElement::Kind::ScopeEnd: OS << "CFGScopeEnd("; - if (const VarDecl *VD = SE->getVarDecl()) + if (const VarDecl *VD = E.castAs().getVarDecl()) OS << VD->getQualifiedNameAsString(); OS << ")\n"; - } else if (Optional NE = E.getAs()) { + break; + + case CFGElement::Kind::NewAllocator: OS << "CFGNewAllocator("; - if (const CXXNewExpr *AllocExpr = NE->getAllocatorExpr()) + if (const CXXNewExpr *AllocExpr = E.castAs().getAllocatorExpr()) AllocExpr->getType().print(OS, PrintingPolicy(Helper.getLangOpts())); OS << ")\n"; - } else if (Optional DE = E.getAs()) { - const CXXRecordDecl *RD = DE->getCXXRecordDecl(); + break; + + case CFGElement::Kind::DeleteDtor: { + CFGDeleteDtor DE = E.castAs(); + const CXXRecordDecl *RD = DE.getCXXRecordDecl(); if (!RD) return; CXXDeleteExpr *DelExpr = - const_cast(DE->getDeleteExpr()); + const_cast(DE.getDeleteExpr()); Helper.handledStmt(cast(DelExpr->getArgument()), OS); OS << "->~" << RD->getName().str() << "()"; OS << " (Implicit destructor)\n"; - } else if (Optional BE = E.getAs()) { - const CXXBaseSpecifier *BS = BE->getBaseSpecifier(); + break; + } + + case CFGElement::Kind::BaseDtor: { + const CXXBaseSpecifier *BS = E.castAs().getBaseSpecifier(); OS << "~" << BS->getType()->getAsCXXRecordDecl()->getName() << "()"; OS << " (Base object destructor)\n"; - } else if (Optional ME = E.getAs()) { - const FieldDecl *FD = ME->getFieldDecl(); + break; + } + + case CFGElement::Kind::MemberDtor: { + const FieldDecl *FD = E.castAs().getFieldDecl(); const Type *T = FD->getType()->getBaseElementTypeUnsafe(); OS << "this->" << FD->getName(); OS << ".~" << T->getAsCXXRecordDecl()->getName() << "()"; OS << " (Member object destructor)\n"; - } else if (Optional TE = E.getAs()) { - const CXXBindTemporaryExpr *BT = TE->getBindTemporaryExpr(); + break; + } + + case CFGElement::Kind::TemporaryDtor: { + const CXXBindTemporaryExpr *BT = E.castAs().getBindTemporaryExpr(); OS << "~"; BT->getType().print(OS, PrintingPolicy(Helper.getLangOpts())); OS << "() (Temporary object destructor)\n"; + break; + } } } From 7d0f75901ec8f490615ed0dac0323a79c235b085 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Thu, 12 Sep 2019 22:11:15 +0000 Subject: [PATCH 157/181] [analyzer] Fix the 'analyzer-enabled-checkers.c' test on non-linux machines. '-Xclang -triple' doesn't seem to override the default target triple as reliably as '--target'. This leads to test failing due to platform-specific checks getting unexpectedly enabled. llvm-svn: 371781 (cherry picked from commit 851e95c1c12a1689bc7fe32c4737743bea3fc4ab) --- clang/test/Analysis/analyzer-enabled-checkers.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/clang/test/Analysis/analyzer-enabled-checkers.c b/clang/test/Analysis/analyzer-enabled-checkers.c index 0618a68978190..ba850ec6da48f 100644 --- a/clang/test/Analysis/analyzer-enabled-checkers.c +++ b/clang/test/Analysis/analyzer-enabled-checkers.c @@ -1,5 +1,4 @@ -// RUN: %clang --analyze %s \ -// RUN: -Xclang -triple -Xclang x86_64-pc-linux-gnu \ +// RUN: %clang --analyze %s --target=x86_64-pc-linux-gnu \ // RUN: -Xclang -analyzer-list-enabled-checkers \ // RUN: -Xclang -analyzer-display-progress \ // RUN: 2>&1 | FileCheck %s --implicit-check-not=ANALYZE \ From 804e170bd84765e07d02980112730ef1ad230811 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 13 Sep 2019 09:31:19 +0000 Subject: [PATCH 158/181] Fix a perl warning: Scalar value @ArgParts[0] better written as $ArgParts[0] at /usr/share/clang/scan-build-10/libexec/ccc-analyzer line 502. llvm-svn: 371832 (cherry picked from commit ea27b932b58cb6cf9d2f1ad1eddd187ee34b9a90) --- clang/tools/scan-build/libexec/ccc-analyzer | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/tools/scan-build/libexec/ccc-analyzer b/clang/tools/scan-build/libexec/ccc-analyzer index 6d24a1af45393..800f38b5ba241 100755 --- a/clang/tools/scan-build/libexec/ccc-analyzer +++ b/clang/tools/scan-build/libexec/ccc-analyzer @@ -499,7 +499,7 @@ my $HasSDK = 0; foreach (my $i = 0; $i < scalar(@ARGV); ++$i) { my $Arg = $ARGV[$i]; my @ArgParts = split /=/,$Arg,2; - my $ArgKey = @ArgParts[0]; + my $ArgKey = $ArgParts[0]; # Be friendly to "" in the argument list. if (!defined($ArgKey)) { From 25cbd184fd19c099a6f83c5762d0c139f5a9d559 Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Wed, 18 Sep 2019 22:24:26 +0000 Subject: [PATCH 159/181] [analyzer] PR43102: Fix an assertion and an out-of-bounds error for diagnostic location construction Summary: https://bugs.llvm.org/show_bug.cgi?id=43102 In today's edition of "Is this any better now that it isn't crashing?", I'd like to show you a very interesting test case with loop widening. Looking at the included test case, it's immediately obvious that this is not only a false positive, but also a very bad bug report in general. We can see how the analyzer mistakenly invalidated `b`, instead of its pointee, resulting in it reporting a null pointer dereference error. Not only that, the point at which this change of value is noted at is at the loop, rather then at the method call. It turns out that `FindLastStoreVisitor` works correctly, rather the supplied explodedgraph is faulty, because `BlockEdge` really is the `ProgramPoint` where this happens. {F9855739} So it's fair to say that this needs improving on multiple fronts. In any case, at least the crash is gone. Full ExplodedGraph: {F9855743} Reviewers: NoQ, xazax.hun, baloghadamsoftware, Charusso, dcoughlin, rnkovacs, TWeaver Subscribers: JesperAntonsson, uabelho, Ka-Ka, bjope, whisperity, szepet, a.sidorin, mikhail.ramalho, donat.nagy, dkrupp, gamesh411, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D66716 llvm-svn: 372269 (cherry picked from commit b8ac93c73b618dd9bec20dc2d94ec9afb0140780) --- clang/lib/Analysis/PathDiagnostic.cpp | 18 ++++++++----- .../Core/BugReporterVisitors.cpp | 3 ++- clang/test/Analysis/loop-widening.cpp | 27 +++++++++++++++++++ 3 files changed, 40 insertions(+), 8 deletions(-) create mode 100644 clang/test/Analysis/loop-widening.cpp diff --git a/clang/lib/Analysis/PathDiagnostic.cpp b/clang/lib/Analysis/PathDiagnostic.cpp index 764cb8ed0e4f8..53235ba076994 100644 --- a/clang/lib/Analysis/PathDiagnostic.cpp +++ b/clang/lib/Analysis/PathDiagnostic.cpp @@ -695,14 +695,18 @@ PathDiagnosticLocation::create(const ProgramPoint& P, return PathDiagnosticLocation( CEB->getLocationContext()->getDecl()->getSourceRange().getEnd(), SMng); } else if (Optional BE = P.getAs()) { - CFGElement BlockFront = BE->getBlock()->front(); - if (auto StmtElt = BlockFront.getAs()) { - return PathDiagnosticLocation(StmtElt->getStmt()->getBeginLoc(), SMng); - } else if (auto NewAllocElt = BlockFront.getAs()) { - return PathDiagnosticLocation( - NewAllocElt->getAllocatorExpr()->getBeginLoc(), SMng); + if (Optional BlockFront = BE->getFirstElement()) { + if (auto StmtElt = BlockFront->getAs()) { + return PathDiagnosticLocation(StmtElt->getStmt()->getBeginLoc(), SMng); + } else if (auto NewAllocElt = BlockFront->getAs()) { + return PathDiagnosticLocation( + NewAllocElt->getAllocatorExpr()->getBeginLoc(), SMng); + } + llvm_unreachable("Unexpected CFG element at front of block"); } - llvm_unreachable("Unexpected CFG element at front of block"); + + return PathDiagnosticLocation( + BE->getBlock()->getTerminatorStmt()->getBeginLoc(), SMng); } else if (Optional FE = P.getAs()) { return PathDiagnosticLocation(FE->getStmt(), SMng, FE->getLocationContext()); diff --git a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp index da619c9f3bb36..4e88fe6497e71 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp @@ -1438,6 +1438,7 @@ FindLastStoreBRVisitor::VisitNode(const ExplodedNode *Succ, if (!StoreSite) return nullptr; + Satisfied = true; // If we have an expression that provided the value, try to track where it @@ -1802,7 +1803,7 @@ TrackControlDependencyCondBRVisitor::VisitNode(const ExplodedNode *N, if (ControlDeps.isControlDependent(OriginB, NB)) { // We don't really want to explain for range loops. Evidence suggests that // the only thing that leads to is the addition of calls to operator!=. - if (isa(NB->getTerminator())) + if (llvm::isa_and_nonnull(NB->getTerminatorStmt())) return nullptr; if (const Expr *Condition = NB->getLastCondition()) { diff --git a/clang/test/Analysis/loop-widening.cpp b/clang/test/Analysis/loop-widening.cpp new file mode 100644 index 0000000000000..fbcb72dee160a --- /dev/null +++ b/clang/test/Analysis/loop-widening.cpp @@ -0,0 +1,27 @@ +// RUN: %clang_analyze_cc1 -verify %s \ +// RUN: -analyzer-checker=core \ +// RUN: -analyzer-config widen-loops=true \ +// RUN: -analyzer-config track-conditions=false \ +// RUN: -analyzer-max-loop 2 -analyzer-output=text + +namespace pr43102 { +class A { +public: + void m_fn1(); +}; +bool g; +void fn1() { + A a; + A *b = &a; + + for (;;) { // expected-note{{Loop condition is true. Entering loop body}} + // expected-note@-1{{Loop condition is true. Entering loop body}} + // expected-note@-2{{Value assigned to 'b'}} + // no crash during bug report construction + + g = !b; // expected-note{{Assuming 'b' is null}} + b->m_fn1(); // expected-warning{{Called C++ object pointer is null}} + // expected-note@-1{{Called C++ object pointer is null}} + } +} +} // end of namespace pr43102 From 60641f9fc01d180785789594d915ec574d4427b1 Mon Sep 17 00:00:00 2001 From: Benjamin Kramer Date: Fri, 20 Sep 2019 12:59:29 +0000 Subject: [PATCH 160/181] [StaticAnalyzer] Use llvm::StringLiteral instead of StringRef in few places StringRef's constexpr constructor seems to be extremely slow in MSVC 2017, so don't use it for generated tables. Should make PR43369 a bit better, no functionality change. llvm-svn: 372386 (cherry picked from commit 8599ffa4b1cda61e76bca973c65227867c8b0f05) --- .../clang/StaticAnalyzer/Core/AnalyzerOptions.h | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.h b/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.h index 04d611a94a7f5..ce16095e10c0b 100644 --- a/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.h +++ b/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.h @@ -278,13 +278,13 @@ class AnalyzerOptions : public RefCountedBase { // Create an array of all -analyzer-config command line options. Sort it in // the constructor. - std::vector AnalyzerConfigCmdFlags = { + std::vector AnalyzerConfigCmdFlags = { #define ANALYZER_OPTION_DEPENDS_ON_USER_MODE(TYPE, NAME, CMDFLAG, DESC, \ SHALLOW_VAL, DEEP_VAL) \ ANALYZER_OPTION(TYPE, NAME, CMDFLAG, DESC, SHALLOW_VAL) #define ANALYZER_OPTION(TYPE, NAME, CMDFLAG, DESC, DEFAULT_VAL) \ - CMDFLAG, + llvm::StringLiteral(CMDFLAG), #include "clang/StaticAnalyzer/Core/AnalyzerOptions.def" #undef ANALYZER_OPTION @@ -415,9 +415,10 @@ inline UserModeKind AnalyzerOptions::getUserMode() const { inline std::vector AnalyzerOptions::getRegisteredCheckers(bool IncludeExperimental) { - static const StringRef StaticAnalyzerCheckerNames[] = { + static constexpr llvm::StringLiteral StaticAnalyzerCheckerNames[] = { #define GET_CHECKERS -#define CHECKER(FULLNAME, CLASS, HELPTEXT, DOC_URI, IS_HIDDEN) FULLNAME, +#define CHECKER(FULLNAME, CLASS, HELPTEXT, DOC_URI, IS_HIDDEN) \ + llvm::StringLiteral(FULLNAME), #include "clang/StaticAnalyzer/Checkers/Checkers.inc" #undef CHECKER #undef GET_CHECKERS @@ -433,9 +434,9 @@ AnalyzerOptions::getRegisteredCheckers(bool IncludeExperimental) { inline std::vector AnalyzerOptions::getRegisteredPackages(bool IncludeExperimental) { - static const StringRef StaticAnalyzerPackageNames[] = { + static constexpr llvm::StringLiteral StaticAnalyzerPackageNames[] = { #define GET_PACKAGES -#define PACKAGE(FULLNAME) FULLNAME, +#define PACKAGE(FULLNAME) llvm::StringLiteral(FULLNAME), #include "clang/StaticAnalyzer/Checkers/Checkers.inc" #undef PACKAGE #undef GET_PACKAGES From d0c7973b536686dcc33291f8bbcf170985ca56c7 Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Fri, 20 Sep 2019 17:59:20 +0000 Subject: [PATCH 161/181] Reland '[analyzer][MallocChecker][NFC] Document and reorganize some functions' Differential Revision: https://reviews.llvm.org/D54823 llvm-svn: 372414 (cherry picked from commit 951cd32f4ba8e2cc08814a609853c11c69efc394) --- .../StaticAnalyzer/Checkers/MallocChecker.cpp | 1183 ++++++++++------- 1 file changed, 720 insertions(+), 463 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index 92f64aa1d707e..3885e3f2e5365 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -6,8 +6,41 @@ // //===----------------------------------------------------------------------===// // -// This file defines malloc/free checker, which checks for potential memory -// leaks, double free, and use-after-free problems. +// This file defines a variety of memory management related checkers, such as +// leak, double free, and use-after-free. +// +// The following checkers are defined here: +// +// * MallocChecker +// Despite its name, it models all sorts of memory allocations and +// de- or reallocation, including but not limited to malloc, free, +// relloc, new, delete. It also reports on a variety of memory misuse +// errors. +// Many other checkers interact very closely with this checker, in fact, +// most are merely options to this one. Other checkers may register +// MallocChecker, but do not enable MallocChecker's reports (more details +// to follow around its field, ChecksEnabled). +// It also has a boolean "Optimistic" checker option, which if set to true +// will cause the checker to model user defined memory management related +// functions annotated via the attribute ownership_takes, ownership_holds +// and ownership_returns. +// +// * NewDeleteChecker +// Enables the modeling of new, new[], delete, delete[] in MallocChecker, +// and checks for related double-free and use-after-free errors. +// +// * NewDeleteLeaksChecker +// Checks for leaks related to new, new[], delete, delete[]. +// Depends on NewDeleteChecker. +// +// * MismatchedDeallocatorChecker +// Enables checking whether memory is deallocated with the correspending +// allocation function in MallocChecker, such as malloc() allocated +// regions are only freed by free(), new by delete, new[] by delete[]. +// +// InnerPointerChecker interacts very closely with MallocChecker, but unlike +// the above checkers, it has it's own file, hence the many InnerPointerChecker +// related headers and non-static functions. // //===----------------------------------------------------------------------===// @@ -37,6 +70,10 @@ using namespace clang; using namespace ento; +//===----------------------------------------------------------------------===// +// The types of allocation we're modeling. +//===----------------------------------------------------------------------===// + namespace { // Used to check correspondence between allocators and deallocators. @@ -50,57 +87,88 @@ enum AllocationFamily { AF_InnerBuffer }; +struct MemFunctionInfoTy; + +} // end of anonymous namespace + +/// Determine family of a deallocation expression. +static AllocationFamily +getAllocationFamily(const MemFunctionInfoTy &MemFunctionInfo, CheckerContext &C, + const Stmt *S); + +/// Print names of allocators and deallocators. +/// +/// \returns true on success. +static bool printAllocDeallocName(raw_ostream &os, CheckerContext &C, + const Expr *E); + +/// Print expected name of an allocator based on the deallocator's +/// family derived from the DeallocExpr. +static void printExpectedAllocName(raw_ostream &os, + const MemFunctionInfoTy &MemFunctionInfo, + CheckerContext &C, const Expr *E); + +/// Print expected name of a deallocator based on the allocator's +/// family. +static void printExpectedDeallocName(raw_ostream &os, AllocationFamily Family); + +//===----------------------------------------------------------------------===// +// The state of a symbol, in terms of memory management. +//===----------------------------------------------------------------------===// + +namespace { + class RefState { - enum Kind { // Reference to allocated memory. - Allocated, - // Reference to zero-allocated memory. - AllocatedOfSizeZero, - // Reference to released/freed memory. - Released, - // The responsibility for freeing resources has transferred from - // this reference. A relinquished symbol should not be freed. - Relinquished, - // We are no longer guaranteed to have observed all manipulations - // of this pointer/memory. For example, it could have been - // passed as a parameter to an opaque function. - Escaped + enum Kind { + // Reference to allocated memory. + Allocated, + // Reference to zero-allocated memory. + AllocatedOfSizeZero, + // Reference to released/freed memory. + Released, + // The responsibility for freeing resources has transferred from + // this reference. A relinquished symbol should not be freed. + Relinquished, + // We are no longer guaranteed to have observed all manipulations + // of this pointer/memory. For example, it could have been + // passed as a parameter to an opaque function. + Escaped }; const Stmt *S; - unsigned K : 3; // Kind enum, but stored as a bitfield. - unsigned Family : 29; // Rest of 32-bit word, currently just an allocation - // family. - RefState(Kind k, const Stmt *s, unsigned family) - : S(s), K(k), Family(family) { + Kind K : 3; + AllocationFamily Family : 3; + + RefState(Kind k, const Stmt *s, AllocationFamily family) + : S(s), K(k), Family(family) { assert(family != AF_None); } + public: bool isAllocated() const { return K == Allocated; } bool isAllocatedOfSizeZero() const { return K == AllocatedOfSizeZero; } bool isReleased() const { return K == Released; } bool isRelinquished() const { return K == Relinquished; } bool isEscaped() const { return K == Escaped; } - AllocationFamily getAllocationFamily() const { - return (AllocationFamily)Family; - } + AllocationFamily getAllocationFamily() const { return Family; } const Stmt *getStmt() const { return S; } bool operator==(const RefState &X) const { return K == X.K && S == X.S && Family == X.Family; } - static RefState getAllocated(unsigned family, const Stmt *s) { + static RefState getAllocated(AllocationFamily family, const Stmt *s) { return RefState(Allocated, s, family); } static RefState getAllocatedOfSizeZero(const RefState *RS) { return RefState(AllocatedOfSizeZero, RS->getStmt(), RS->getAllocationFamily()); } - static RefState getReleased(unsigned family, const Stmt *s) { + static RefState getReleased(AllocationFamily family, const Stmt *s) { return RefState(Released, s, family); } - static RefState getRelinquished(unsigned family, const Stmt *s) { + static RefState getRelinquished(AllocationFamily family, const Stmt *s) { return RefState(Relinquished, s, family); } static RefState getEscaped(const RefState *RS) { @@ -113,8 +181,8 @@ class RefState { ID.AddInteger(Family); } - void dump(raw_ostream &OS) const { - switch (static_cast(K)) { + LLVM_DUMP_METHOD void dump(raw_ostream &OS) const { + switch (K) { #define CASE(ID) case ID: OS << #ID; break; CASE(Allocated) CASE(AllocatedOfSizeZero) @@ -127,24 +195,62 @@ class RefState { LLVM_DUMP_METHOD void dump() const { dump(llvm::errs()); } }; -enum ReallocPairKind { - RPToBeFreedAfterFailure, - // The symbol has been freed when reallocation failed. - RPIsFreeOnFailure, - // The symbol does not need to be freed after reallocation fails. - RPDoNotTrackAfterFailure +} // end of anonymous namespace + +REGISTER_MAP_WITH_PROGRAMSTATE(RegionState, SymbolRef, RefState) + +/// Check if the memory associated with this symbol was released. +static bool isReleased(SymbolRef Sym, CheckerContext &C); + +/// Update the RefState to reflect the new memory allocation. +/// The optional \p RetVal parameter specifies the newly allocated pointer +/// value; if unspecified, the value of expression \p E is used. +static ProgramStateRef MallocUpdateRefState(CheckerContext &C, const Expr *E, + ProgramStateRef State, + AllocationFamily Family = AF_Malloc, + Optional RetVal = None); + +//===----------------------------------------------------------------------===// +// The modeling of memory reallocation. +// +// The terminology 'toPtr' and 'fromPtr' will be used: +// toPtr = realloc(fromPtr, 20); +//===----------------------------------------------------------------------===// + +REGISTER_SET_WITH_PROGRAMSTATE(ReallocSizeZeroSymbols, SymbolRef) + +namespace { + +/// The state of 'fromPtr' after reallocation is known to have failed. +enum OwnershipAfterReallocKind { + // The symbol needs to be freed (e.g.: realloc) + OAR_ToBeFreedAfterFailure, + // The symbol has been freed (e.g.: reallocf) + OAR_FreeOnFailure, + // The symbol doesn't have to freed (e.g.: we aren't sure if, how and where + // 'fromPtr' was allocated: + // void Haha(int *ptr) { + // ptr = realloc(ptr, 67); + // // ... + // } + // ). + OAR_DoNotTrackAfterFailure }; -/// \class ReallocPair -/// Stores information about the symbol being reallocated by a call to -/// 'realloc' to allow modeling failed reallocation later in the path. +/// Stores information about the 'fromPtr' symbol after reallocation. +/// +/// This is important because realloc may fail, and that needs special modeling. +/// Whether reallocation failed or not will not be known until later, so we'll +/// store whether upon failure 'fromPtr' will be freed, or needs to be freed +/// later, etc. struct ReallocPair { - // The symbol which realloc reallocated. + + // The 'fromPtr'. SymbolRef ReallocatedSym; - ReallocPairKind Kind; + OwnershipAfterReallocKind Kind; - ReallocPair(SymbolRef S, ReallocPairKind K) : - ReallocatedSym(S), Kind(K) {} + ReallocPair(SymbolRef S, OwnershipAfterReallocKind K) + : ReallocatedSym(S), Kind(K) {} void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(Kind); ID.AddPointer(ReallocatedSym); @@ -155,42 +261,88 @@ struct ReallocPair { } }; -typedef std::pair LeakInfo; - -class MallocChecker : public Checker, - check::EndFunction, - check::PreCall, - check::PostStmt, - check::PostStmt, - check::NewAllocator, - check::PreStmt, - check::PostStmt, - check::PostObjCMessage, - check::Location, - eval::Assume> -{ -public: - MallocChecker() - : II_alloca(nullptr), II_win_alloca(nullptr), II_malloc(nullptr), - II_free(nullptr), II_realloc(nullptr), II_calloc(nullptr), - II_valloc(nullptr), II_reallocf(nullptr), II_strndup(nullptr), - II_strdup(nullptr), II_win_strdup(nullptr), II_kmalloc(nullptr), - II_kfree(nullptr), II_if_nameindex(nullptr), - II_if_freenameindex(nullptr), II_wcsdup(nullptr), - II_win_wcsdup(nullptr), II_g_malloc(nullptr), II_g_malloc0(nullptr), - II_g_realloc(nullptr), II_g_try_malloc(nullptr), - II_g_try_malloc0(nullptr), II_g_try_realloc(nullptr), - II_g_free(nullptr), II_g_memdup(nullptr), II_g_malloc_n(nullptr), - II_g_malloc0_n(nullptr), II_g_realloc_n(nullptr), - II_g_try_malloc_n(nullptr), II_g_try_malloc0_n(nullptr), - II_g_try_realloc_n(nullptr) {} +} // end of anonymous namespace + +REGISTER_MAP_WITH_PROGRAMSTATE(ReallocPairs, SymbolRef, ReallocPair) + +//===----------------------------------------------------------------------===// +// Kinds of memory operations, information about resource managing functions. +//===----------------------------------------------------------------------===// + +namespace { + +enum class MemoryOperationKind { MOK_Allocate, MOK_Free, MOK_Any }; +struct MemFunctionInfoTy { + /// The value of the MallocChecker:Optimistic is stored in this variable. + /// /// In pessimistic mode, the checker assumes that it does not know which /// functions might free the memory. + /// In optimistic mode, the checker assumes that all user-defined functions + /// which might free a pointer are annotated. + DefaultBool ShouldIncludeOwnershipAnnotatedFunctions; + + // TODO: Change these to CallDescription, and get rid of lazy initialization. + mutable IdentifierInfo *II_alloca = nullptr, *II_win_alloca = nullptr, + *II_malloc = nullptr, *II_free = nullptr, + *II_realloc = nullptr, *II_calloc = nullptr, + *II_valloc = nullptr, *II_reallocf = nullptr, + *II_strndup = nullptr, *II_strdup = nullptr, + *II_win_strdup = nullptr, *II_kmalloc = nullptr, + *II_if_nameindex = nullptr, + *II_if_freenameindex = nullptr, *II_wcsdup = nullptr, + *II_win_wcsdup = nullptr, *II_g_malloc = nullptr, + *II_g_malloc0 = nullptr, *II_g_realloc = nullptr, + *II_g_try_malloc = nullptr, + *II_g_try_malloc0 = nullptr, + *II_g_try_realloc = nullptr, *II_g_free = nullptr, + *II_g_memdup = nullptr, *II_g_malloc_n = nullptr, + *II_g_malloc0_n = nullptr, *II_g_realloc_n = nullptr, + *II_g_try_malloc_n = nullptr, + *II_g_try_malloc0_n = nullptr, *II_kfree = nullptr, + *II_g_try_realloc_n = nullptr; + + void initIdentifierInfo(ASTContext &C) const; + + ///@{ + /// Check if this is one of the functions which can allocate/reallocate + /// memory pointed to by one of its arguments. + bool isMemFunction(const FunctionDecl *FD, ASTContext &C) const; + bool isCMemFunction(const FunctionDecl *FD, ASTContext &C, + AllocationFamily Family, + MemoryOperationKind MemKind) const; + + /// Tells if the callee is one of the builtin new/delete operators, including + /// placement operators and other standard overloads. + bool isStandardNewDelete(const FunctionDecl *FD, ASTContext &C) const; + ///@} +}; + +} // end of anonymous namespace + +//===----------------------------------------------------------------------===// +// Definition of the MallocChecker class. +//===----------------------------------------------------------------------===// + +namespace { + +class MallocChecker + : public Checker, + check::EndFunction, check::PreCall, + check::PostStmt, check::PostStmt, + check::NewAllocator, check::PreStmt, + check::PostStmt, check::PostObjCMessage, + check::Location, eval::Assume> { +public: + MemFunctionInfoTy MemFunctionInfo; + + /// Many checkers are essentially built into this one, so enabling them will + /// make MallocChecker perform additional modeling and reporting. enum CheckKind { + /// When a subchecker is enabled but MallocChecker isn't, model memory + /// management but do not emit warnings emitted with MallocChecker only + /// enabled. CK_MallocChecker, CK_NewDeleteChecker, CK_NewDeleteLeaksChecker, @@ -199,13 +351,7 @@ class MallocChecker : public Checker; DefaultBool ChecksEnabled[CK_NumCheckKinds]; CheckerNameRef CheckNames[CK_NumCheckKinds]; @@ -248,47 +394,9 @@ class MallocChecker : public Checker BT_MismatchedDealloc; mutable std::unique_ptr BT_OffsetFree[CK_NumCheckKinds]; mutable std::unique_ptr BT_UseZerroAllocated[CK_NumCheckKinds]; - mutable IdentifierInfo *II_alloca, *II_win_alloca, *II_malloc, *II_free, - *II_realloc, *II_calloc, *II_valloc, *II_reallocf, - *II_strndup, *II_strdup, *II_win_strdup, *II_kmalloc, - *II_kfree, *II_if_nameindex, *II_if_freenameindex, - *II_wcsdup, *II_win_wcsdup, *II_g_malloc, - *II_g_malloc0, *II_g_realloc, *II_g_try_malloc, - *II_g_try_malloc0, *II_g_try_realloc, *II_g_free, - *II_g_memdup, *II_g_malloc_n, *II_g_malloc0_n, - *II_g_realloc_n, *II_g_try_malloc_n, - *II_g_try_malloc0_n, *II_g_try_realloc_n; - mutable Optional KernelZeroFlagVal; - void initIdentifierInfo(ASTContext &C) const; - - /// Determine family of a deallocation expression. - AllocationFamily getAllocationFamily(CheckerContext &C, const Stmt *S) const; - - /// Print names of allocators and deallocators. - /// - /// \returns true on success. - bool printAllocDeallocName(raw_ostream &os, CheckerContext &C, - const Expr *E) const; - - /// Print expected name of an allocator based on the deallocator's - /// family derived from the DeallocExpr. - void printExpectedAllocName(raw_ostream &os, CheckerContext &C, - const Expr *DeallocExpr) const; - /// Print expected name of a deallocator based on the allocator's - /// family. - void printExpectedDeallocName(raw_ostream &os, AllocationFamily Family) const; - - ///@{ - /// Check if this is one of the functions which can allocate/reallocate memory - /// pointed to by one of its arguments. - bool isMemFunction(const FunctionDecl *FD, ASTContext &C) const; - bool isCMemFunction(const FunctionDecl *FD, - ASTContext &C, - AllocationFamily Family, - MemoryOperationKind MemKind) const; - bool isStandardNewDelete(const FunctionDecl *FD, ASTContext &C) const; - ///@} + // TODO: Remove mutable by moving the initializtaion to the registry function. + mutable Optional KernelZeroFlagVal; /// Process C++ operator new()'s allocation, which is the part of C++ /// new-expression that goes before the constructor. @@ -296,23 +404,64 @@ class MallocChecker : public Checker RetVal = None) const; - + /// + /// \param [in] E The expression that allocates memory. + /// \param [in] IndexOfSizeArg Index of the argument that specifies the size + /// of the memory that needs to be allocated. E.g. for malloc, this would be + /// 0. + /// \param [in] RetVal Specifies the newly allocated pointer value; + /// if unspecified, the value of expression \p E is used. + static ProgramStateRef ProcessZeroAllocCheck(CheckerContext &C, const Expr *E, + const unsigned IndexOfSizeArg, + ProgramStateRef State, + Optional RetVal = None); + + /// Model functions with the ownership_returns attribute. + /// + /// User-defined function may have the ownership_returns attribute, which + /// annotates that the function returns with an object that was allocated on + /// the heap, and passes the ownertship to the callee. + /// + /// void __attribute((ownership_returns(malloc, 1))) *my_malloc(size_t); + /// + /// It has two parameters: + /// - first: name of the resource (e.g. 'malloc') + /// - (OPTIONAL) second: size of the allocated region + /// + /// \param [in] CE The expression that allocates memory. + /// \param [in] Att The ownership_returns attribute. + /// \param [in] State The \c ProgramState right before allocation. + /// \returns The ProgramState right after allocation. ProgramStateRef MallocMemReturnsAttr(CheckerContext &C, const CallExpr *CE, const OwnershipAttr* Att, ProgramStateRef State) const; + + /// Models memory allocation. + /// + /// \param [in] CE The expression that allocates memory. + /// \param [in] SizeEx Size of the memory that needs to be allocated. + /// \param [in] Init The value the allocated memory needs to be initialized. + /// with. For example, \c calloc initializes the allocated memory to 0, + /// malloc leaves it undefined. + /// \param [in] State The \c ProgramState right before allocation. + /// \returns The ProgramState right after allocation. static ProgramStateRef MallocMemAux(CheckerContext &C, const CallExpr *CE, const Expr *SizeEx, SVal Init, ProgramStateRef State, AllocationFamily Family = AF_Malloc); + + /// Models memory allocation. + /// + /// \param [in] CE The expression that allocates memory. + /// \param [in] Size Size of the memory that needs to be allocated. + /// \param [in] Init The value the allocated memory needs to be initialized. + /// with. For example, \c calloc initializes the allocated memory to 0, + /// malloc leaves it undefined. + /// \param [in] State The \c ProgramState right before allocation. + /// \returns The ProgramState right after allocation. static ProgramStateRef MallocMemAux(CheckerContext &C, const CallExpr *CE, - SVal SizeEx, SVal Init, + SVal Size, SVal Init, ProgramStateRef State, AllocationFamily Family = AF_Malloc); @@ -325,54 +474,125 @@ class MallocChecker : public Checker RetVal = None); - + /// Model functions with the ownership_takes and ownership_holds attributes. + /// + /// User-defined function may have the ownership_takes and/or ownership_holds + /// attributes, which annotates that the function frees the memory passed as a + /// parameter. + /// + /// void __attribute((ownership_takes(malloc, 1))) my_free(void *); + /// void __attribute((ownership_holds(malloc, 1))) my_hold(void *); + /// + /// They have two parameters: + /// - first: name of the resource (e.g. 'malloc') + /// - second: index of the parameter the attribute applies to + /// + /// \param [in] CE The expression that frees memory. + /// \param [in] Att The ownership_takes or ownership_holds attribute. + /// \param [in] State The \c ProgramState right before allocation. + /// \returns The ProgramState right after deallocation. ProgramStateRef FreeMemAttr(CheckerContext &C, const CallExpr *CE, const OwnershipAttr* Att, ProgramStateRef State) const; + + /// Models memory deallocation. + /// + /// \param [in] CE The expression that frees memory. + /// \param [in] State The \c ProgramState right before allocation. + /// \param [in] Num Index of the argument that needs to be freed. This is + /// normally 0, but for custom free functions it may be different. + /// \param [in] Hold Whether the parameter at \p Index has the ownership_holds + /// attribute. + /// \param [out] IsKnownToBeAllocated Whether the memory to be freed is known + /// to have been allocated, or in other words, the symbol to be freed was + /// registered as allocated by this checker. In the following case, \c ptr + /// isn't known to be allocated. + /// void Haha(int *ptr) { + /// ptr = realloc(ptr, 67); + /// // ... + /// } + /// \param [in] ReturnsNullOnFailure Whether the memory deallocation function + /// we're modeling returns with Null on failure. + /// \returns The ProgramState right after deallocation. ProgramStateRef FreeMemAux(CheckerContext &C, const CallExpr *CE, - ProgramStateRef state, unsigned Num, - bool Hold, - bool &ReleasedAllocated, + ProgramStateRef State, unsigned Num, bool Hold, + bool &IsKnownToBeAllocated, bool ReturnsNullOnFailure = false) const; - ProgramStateRef FreeMemAux(CheckerContext &C, const Expr *Arg, - const Expr *ParentExpr, - ProgramStateRef State, - bool Hold, - bool &ReleasedAllocated, + + /// Models memory deallocation. + /// + /// \param [in] ArgExpr The variable who's pointee needs to be freed. + /// \param [in] ParentExpr The expression that frees the memory. + /// \param [in] State The \c ProgramState right before allocation. + /// normally 0, but for custom free functions it may be different. + /// \param [in] Hold Whether the parameter at \p Index has the ownership_holds + /// attribute. + /// \param [out] IsKnownToBeAllocated Whether the memory to be freed is known + /// to have been allocated, or in other words, the symbol to be freed was + /// registered as allocated by this checker. In the following case, \c ptr + /// isn't known to be allocated. + /// void Haha(int *ptr) { + /// ptr = realloc(ptr, 67); + /// // ... + /// } + /// \param [in] ReturnsNullOnFailure Whether the memory deallocation function + /// we're modeling returns with Null on failure. + /// \returns The ProgramState right after deallocation. + ProgramStateRef FreeMemAux(CheckerContext &C, const Expr *ArgExpr, + const Expr *ParentExpr, ProgramStateRef State, + bool Hold, bool &IsKnownToBeAllocated, bool ReturnsNullOnFailure = false) const; + // TODO: Needs some refactoring, as all other deallocation modeling + // functions are suffering from out parameters and messy code due to how + // realloc is handled. + // + /// Models memory reallocation. + /// + /// \param [in] CE The expression that reallocated memory + /// \param [in] FreesMemOnFailure Whether if reallocation fails, the supplied + /// memory should be freed. + /// \param [in] State The \c ProgramState right before reallocation. + /// \param [in] SuffixWithN Whether the reallocation function we're modeling + /// has an '_n' suffix, such as g_realloc_n. + /// \returns The ProgramState right after reallocation. ProgramStateRef ReallocMemAux(CheckerContext &C, const CallExpr *CE, - bool FreesMemOnFailure, - ProgramStateRef State, + bool ShouldFreeOnFail, ProgramStateRef State, bool SuffixWithN = false) const; + + /// Evaluates the buffer size that needs to be allocated. + /// + /// \param [in] Blocks The amount of blocks that needs to be allocated. + /// \param [in] BlockBytes The size of a block. + /// \returns The symbolic value of \p Blocks * \p BlockBytes. static SVal evalMulForBufferSize(CheckerContext &C, const Expr *Blocks, const Expr *BlockBytes); + + /// Models zero initialized array allocation. + /// + /// \param [in] CE The expression that reallocated memory + /// \param [in] State The \c ProgramState right before reallocation. + /// \returns The ProgramState right after allocation. static ProgramStateRef CallocMem(CheckerContext &C, const CallExpr *CE, ProgramStateRef State); - /// Check if the memory associated with this symbol was released. - bool isReleased(SymbolRef Sym, CheckerContext &C) const; - /// See if deallocation happens in a suspicious context. If so, escape the /// pointers that otherwise would have been deallocated and return true. bool suppressDeallocationsInSuspiciousContexts(const CallExpr *CE, CheckerContext &C) const; + /// If in \p S \p Sym is used, check whether \p Sym was already freed. bool checkUseAfterFree(SymbolRef Sym, CheckerContext &C, const Stmt *S) const; + /// If in \p S \p Sym is used, check whether \p Sym was allocated as a zero + /// sized memory region. void checkUseZeroAllocated(SymbolRef Sym, CheckerContext &C, const Stmt *S) const; + /// If in \p S \p Sym is being freed, check whether \p Sym was already freed. bool checkDoubleDelete(SymbolRef Sym, CheckerContext &C) const; - /// Check if the function is known free memory, or if it is + /// Check if the function is known to free memory, or if it is /// "interesting" and should be modeled explicitly. /// /// \param [out] EscapingSymbol A function might not free memory in general, @@ -386,12 +606,12 @@ class MallocChecker : public Checker allocated. Other state (released) -> allocated. - return (Stmt && (isa(Stmt) || isa(Stmt)) && - (S && (S->isAllocated() || S->isAllocatedOfSizeZero())) && - (!SPrev || !(SPrev->isAllocated() || - SPrev->isAllocatedOfSizeZero()))); - } + // The allocated region symbol tracked by the main analysis. + SymbolRef Sym; - inline bool isReleased(const RefState *S, const RefState *SPrev, - const Stmt *Stmt) { - // Did not track -> released. Other state (allocated) -> released. - // The statement associated with the release might be missing. - bool IsReleased = (S && S->isReleased()) && - (!SPrev || !SPrev->isReleased()); - assert(!IsReleased || - (Stmt && (isa(Stmt) || isa(Stmt))) || - (!Stmt && S->getAllocationFamily() == AF_InnerBuffer)); - return IsReleased; - } + // The mode we are in, i.e. what kind of diagnostics will be emitted. + NotificationMode Mode; - inline bool isRelinquished(const RefState *S, const RefState *SPrev, - const Stmt *Stmt) { - // Did not track -> relinquished. Other state (allocated) -> relinquished. - return (Stmt && (isa(Stmt) || isa(Stmt) || - isa(Stmt)) && - (S && S->isRelinquished()) && - (!SPrev || !SPrev->isRelinquished())); - } + // A symbol from when the primary region should have been reallocated. + SymbolRef FailedReallocSymbol; - inline bool isReallocFailedCheck(const RefState *S, const RefState *SPrev, - const Stmt *Stmt) { - // If the expression is not a call, and the state change is - // released -> allocated, it must be the realloc return value - // check. If we have to handle more cases here, it might be cleaner just - // to track this extra bit in the state itself. - return ( - (!Stmt || !isa(Stmt)) && - (S && (S->isAllocated() || S->isAllocatedOfSizeZero())) && - (SPrev && !(SPrev->isAllocated() || SPrev->isAllocatedOfSizeZero()))); - } + // A C++ destructor stack frame in which memory was released. Used for + // miscellaneous false positive suppression. + const StackFrameContext *ReleaseDestructorLC; - PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - PathSensitiveBugReport &BR) override; + bool IsLeak; - PathDiagnosticPieceRef getEndPath(BugReporterContext &BRC, - const ExplodedNode *EndPathNode, - PathSensitiveBugReport &BR) override { - if (!IsLeak) - return nullptr; +public: + MallocBugVisitor(SymbolRef S, bool isLeak = false) + : Sym(S), Mode(Normal), FailedReallocSymbol(nullptr), + ReleaseDestructorLC(nullptr), IsLeak(isLeak) {} + + static void *getTag() { + static int Tag = 0; + return &Tag; + } + + void Profile(llvm::FoldingSetNodeID &ID) const override { + ID.AddPointer(getTag()); + ID.AddPointer(Sym); + } + + /// Did not track -> allocated. Other state (released) -> allocated. + static inline bool isAllocated(const RefState *RSCurr, const RefState *RSPrev, + const Stmt *Stmt) { + return (Stmt && (isa(Stmt) || isa(Stmt)) && + (RSCurr && + (RSCurr->isAllocated() || RSCurr->isAllocatedOfSizeZero())) && + (!RSPrev || + !(RSPrev->isAllocated() || RSPrev->isAllocatedOfSizeZero()))); + } + + /// Did not track -> released. Other state (allocated) -> released. + /// The statement associated with the release might be missing. + static inline bool isReleased(const RefState *RSCurr, const RefState *RSPrev, + const Stmt *Stmt) { + bool IsReleased = + (RSCurr && RSCurr->isReleased()) && (!RSPrev || !RSPrev->isReleased()); + assert(!IsReleased || + (Stmt && (isa(Stmt) || isa(Stmt))) || + (!Stmt && RSCurr->getAllocationFamily() == AF_InnerBuffer)); + return IsReleased; + } + + /// Did not track -> relinquished. Other state (allocated) -> relinquished. + static inline bool isRelinquished(const RefState *RSCurr, + const RefState *RSPrev, const Stmt *Stmt) { + return (Stmt && + (isa(Stmt) || isa(Stmt) || + isa(Stmt)) && + (RSCurr && RSCurr->isRelinquished()) && + (!RSPrev || !RSPrev->isRelinquished())); + } + + /// If the expression is not a call, and the state change is + /// released -> allocated, it must be the realloc return value + /// check. If we have to handle more cases here, it might be cleaner just + /// to track this extra bit in the state itself. + static inline bool hasReallocFailed(const RefState *RSCurr, + const RefState *RSPrev, + const Stmt *Stmt) { + return ((!Stmt || !isa(Stmt)) && + (RSCurr && + (RSCurr->isAllocated() || RSCurr->isAllocatedOfSizeZero())) && + (RSPrev && + !(RSPrev->isAllocated() || RSPrev->isAllocatedOfSizeZero()))); + } + + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) override; + + PathDiagnosticPieceRef getEndPath(BugReporterContext &BRC, + const ExplodedNode *EndPathNode, + PathSensitiveBugReport &BR) override { + if (!IsLeak) + return nullptr; - PathDiagnosticLocation L = BR.getLocation(); - // Do not add the statement itself as a range in case of leak. - return std::make_shared(L, BR.getDescription(), - false); - } + PathDiagnosticLocation L = BR.getLocation(); + // Do not add the statement itself as a range in case of leak. + return std::make_shared(L, BR.getDescription(), + false); + } - private: - class StackHintGeneratorForReallocationFailed - : public StackHintGeneratorForSymbol { - public: - StackHintGeneratorForReallocationFailed(SymbolRef S, StringRef M) +private: + class StackHintGeneratorForReallocationFailed + : public StackHintGeneratorForSymbol { + public: + StackHintGeneratorForReallocationFailed(SymbolRef S, StringRef M) : StackHintGeneratorForSymbol(S, M) {} - std::string getMessageForArg(const Expr *ArgE, - unsigned ArgIndex) override { - // Printed parameters start at 1, not 0. - ++ArgIndex; + std::string getMessageForArg(const Expr *ArgE, unsigned ArgIndex) override { + // Printed parameters start at 1, not 0. + ++ArgIndex; - SmallString<200> buf; - llvm::raw_svector_ostream os(buf); + SmallString<200> buf; + llvm::raw_svector_ostream os(buf); - os << "Reallocation of " << ArgIndex << llvm::getOrdinalSuffix(ArgIndex) - << " parameter failed"; + os << "Reallocation of " << ArgIndex << llvm::getOrdinalSuffix(ArgIndex) + << " parameter failed"; - return os.str(); - } + return os.str(); + } - std::string getMessageForReturn(const CallExpr *CallExpr) override { - return "Reallocation of returned value failed"; - } - }; + std::string getMessageForReturn(const CallExpr *CallExpr) override { + return "Reallocation of returned value failed"; + } }; }; -} // end anonymous namespace - -REGISTER_MAP_WITH_PROGRAMSTATE(RegionState, SymbolRef, RefState) -REGISTER_MAP_WITH_PROGRAMSTATE(ReallocPairs, SymbolRef, ReallocPair) -REGISTER_SET_WITH_PROGRAMSTATE(ReallocSizeZeroSymbols, SymbolRef) // A map from the freed symbol to the symbol representing the return value of // the free function. @@ -589,7 +811,11 @@ class StopTrackingCallback final : public SymbolVisitor { }; } // end anonymous namespace -void MallocChecker::initIdentifierInfo(ASTContext &Ctx) const { +//===----------------------------------------------------------------------===// +// Methods of MemFunctionInfoTy. +//===----------------------------------------------------------------------===// + +void MemFunctionInfoTy::initIdentifierInfo(ASTContext &Ctx) const { if (II_malloc) return; II_alloca = &Ctx.Idents.get("alloca"); @@ -629,7 +855,8 @@ void MallocChecker::initIdentifierInfo(ASTContext &Ctx) const { II_g_try_realloc_n = &Ctx.Idents.get("g_try_realloc_n"); } -bool MallocChecker::isMemFunction(const FunctionDecl *FD, ASTContext &C) const { +bool MemFunctionInfoTy::isMemFunction(const FunctionDecl *FD, + ASTContext &C) const { if (isCMemFunction(FD, C, AF_Malloc, MemoryOperationKind::MOK_Any)) return true; @@ -645,10 +872,9 @@ bool MallocChecker::isMemFunction(const FunctionDecl *FD, ASTContext &C) const { return false; } -bool MallocChecker::isCMemFunction(const FunctionDecl *FD, - ASTContext &C, - AllocationFamily Family, - MemoryOperationKind MemKind) const { +bool MemFunctionInfoTy::isCMemFunction(const FunctionDecl *FD, ASTContext &C, + AllocationFamily Family, + MemoryOperationKind MemKind) const { if (!FD) return false; @@ -701,7 +927,7 @@ bool MallocChecker::isCMemFunction(const FunctionDecl *FD, if (Family != AF_Malloc) return false; - if (IsOptimistic && FD->hasAttrs()) { + if (ShouldIncludeOwnershipAnnotatedFunctions && FD->hasAttrs()) { for (const auto *I : FD->specific_attrs()) { OwnershipAttr::OwnershipKind OwnKind = I->getOwnKind(); if(OwnKind == OwnershipAttr::Takes || OwnKind == OwnershipAttr::Holds) { @@ -716,11 +942,8 @@ bool MallocChecker::isCMemFunction(const FunctionDecl *FD, return false; } - -// Tells if the callee is one of the builtin new/delete operators, including -// placement operators and other standard overloads. -bool MallocChecker::isStandardNewDelete(const FunctionDecl *FD, - ASTContext &C) const { +bool MemFunctionInfoTy::isStandardNewDelete(const FunctionDecl *FD, + ASTContext &C) const { if (!FD) return false; @@ -736,6 +959,10 @@ bool MallocChecker::isStandardNewDelete(const FunctionDecl *FD, return !L.isValid() || C.getSourceManager().isInSystemHeader(L); } +//===----------------------------------------------------------------------===// +// Methods of MallocChecker and MallocBugVisitor. +//===----------------------------------------------------------------------===// + llvm::Optional MallocChecker::performKernelMalloc( const CallExpr *CE, CheckerContext &C, const ProgramStateRef &State) const { // 3-argument malloc(), as commonly used in {Free,Net,Open}BSD Kernels: @@ -834,28 +1061,35 @@ void MallocChecker::checkPostStmt(const CallExpr *CE, CheckerContext &C) const { return; ProgramStateRef State = C.getState(); - bool ReleasedAllocatedMemory = false; + bool IsKnownToBeAllocatedMemory = false; if (FD->getKind() == Decl::Function) { - initIdentifierInfo(C.getASTContext()); + MemFunctionInfo.initIdentifierInfo(C.getASTContext()); IdentifierInfo *FunI = FD->getIdentifier(); - if (FunI == II_malloc || FunI == II_g_malloc || FunI == II_g_try_malloc) { - if (CE->getNumArgs() < 1) + if (FunI == MemFunctionInfo.II_malloc || + FunI == MemFunctionInfo.II_g_malloc || + FunI == MemFunctionInfo.II_g_try_malloc) { + switch (CE->getNumArgs()) { + default: return; - if (CE->getNumArgs() < 3) { + case 1: + State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State); + State = ProcessZeroAllocCheck(C, CE, 0, State); + break; + case 2: State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State); - if (CE->getNumArgs() == 1) - State = ProcessZeroAllocation(C, CE, 0, State); - } else if (CE->getNumArgs() == 3) { + break; + case 3: llvm::Optional MaybeState = performKernelMalloc(CE, C, State); if (MaybeState.hasValue()) State = MaybeState.getValue(); else State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State); + break; } - } else if (FunI == II_kmalloc) { + } else if (FunI == MemFunctionInfo.II_kmalloc) { if (CE->getNumArgs() < 1) return; llvm::Optional MaybeState = @@ -864,100 +1098,116 @@ void MallocChecker::checkPostStmt(const CallExpr *CE, CheckerContext &C) const { State = MaybeState.getValue(); else State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State); - } else if (FunI == II_valloc) { + } else if (FunI == MemFunctionInfo.II_valloc) { if (CE->getNumArgs() < 1) return; State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State); - State = ProcessZeroAllocation(C, CE, 0, State); - } else if (FunI == II_realloc || FunI == II_g_realloc || - FunI == II_g_try_realloc) { - State = ReallocMemAux(C, CE, false, State); - State = ProcessZeroAllocation(C, CE, 1, State); - } else if (FunI == II_reallocf) { - State = ReallocMemAux(C, CE, true, State); - State = ProcessZeroAllocation(C, CE, 1, State); - } else if (FunI == II_calloc) { + State = ProcessZeroAllocCheck(C, CE, 0, State); + } else if (FunI == MemFunctionInfo.II_realloc || + FunI == MemFunctionInfo.II_g_realloc || + FunI == MemFunctionInfo.II_g_try_realloc) { + State = ReallocMemAux(C, CE, /*ShouldFreeOnFail*/ false, State); + State = ProcessZeroAllocCheck(C, CE, 1, State); + } else if (FunI == MemFunctionInfo.II_reallocf) { + State = ReallocMemAux(C, CE, /*ShouldFreeOnFail*/ true, State); + State = ProcessZeroAllocCheck(C, CE, 1, State); + } else if (FunI == MemFunctionInfo.II_calloc) { State = CallocMem(C, CE, State); - State = ProcessZeroAllocation(C, CE, 0, State); - State = ProcessZeroAllocation(C, CE, 1, State); - } else if (FunI == II_free || FunI == II_g_free || FunI == II_kfree) { + State = ProcessZeroAllocCheck(C, CE, 0, State); + State = ProcessZeroAllocCheck(C, CE, 1, State); + } else if (FunI == MemFunctionInfo.II_free || + FunI == MemFunctionInfo.II_g_free || + FunI == MemFunctionInfo.II_kfree) { if (suppressDeallocationsInSuspiciousContexts(CE, C)) return; - State = FreeMemAux(C, CE, State, 0, false, ReleasedAllocatedMemory); - } else if (FunI == II_strdup || FunI == II_win_strdup || - FunI == II_wcsdup || FunI == II_win_wcsdup) { + State = FreeMemAux(C, CE, State, 0, false, IsKnownToBeAllocatedMemory); + } else if (FunI == MemFunctionInfo.II_strdup || + FunI == MemFunctionInfo.II_win_strdup || + FunI == MemFunctionInfo.II_wcsdup || + FunI == MemFunctionInfo.II_win_wcsdup) { State = MallocUpdateRefState(C, CE, State); - } else if (FunI == II_strndup) { + } else if (FunI == MemFunctionInfo.II_strndup) { State = MallocUpdateRefState(C, CE, State); - } else if (FunI == II_alloca || FunI == II_win_alloca) { + } else if (FunI == MemFunctionInfo.II_alloca || + FunI == MemFunctionInfo.II_win_alloca) { if (CE->getNumArgs() < 1) return; State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State, AF_Alloca); - State = ProcessZeroAllocation(C, CE, 0, State); - } else if (isStandardNewDelete(FD, C.getASTContext())) { + State = ProcessZeroAllocCheck(C, CE, 0, State); + } else if (MemFunctionInfo.isStandardNewDelete(FD, C.getASTContext())) { // Process direct calls to operator new/new[]/delete/delete[] functions // as distinct from new/new[]/delete/delete[] expressions that are // processed by the checkPostStmt callbacks for CXXNewExpr and // CXXDeleteExpr. - OverloadedOperatorKind K = FD->getOverloadedOperator(); - if (K == OO_New) { + switch (FD->getOverloadedOperator()) { + case OO_New: State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State, AF_CXXNew); - State = ProcessZeroAllocation(C, CE, 0, State); - } - else if (K == OO_Array_New) { + State = ProcessZeroAllocCheck(C, CE, 0, State); + break; + case OO_Array_New: State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State, AF_CXXNewArray); - State = ProcessZeroAllocation(C, CE, 0, State); - } - else if (K == OO_Delete || K == OO_Array_Delete) - State = FreeMemAux(C, CE, State, 0, false, ReleasedAllocatedMemory); - else + State = ProcessZeroAllocCheck(C, CE, 0, State); + break; + case OO_Delete: + case OO_Array_Delete: + State = FreeMemAux(C, CE, State, 0, false, IsKnownToBeAllocatedMemory); + break; + default: llvm_unreachable("not a new/delete operator"); - } else if (FunI == II_if_nameindex) { + } + } else if (FunI == MemFunctionInfo.II_if_nameindex) { // Should we model this differently? We can allocate a fixed number of // elements with zeros in the last one. State = MallocMemAux(C, CE, UnknownVal(), UnknownVal(), State, AF_IfNameIndex); - } else if (FunI == II_if_freenameindex) { - State = FreeMemAux(C, CE, State, 0, false, ReleasedAllocatedMemory); - } else if (FunI == II_g_malloc0 || FunI == II_g_try_malloc0) { + } else if (FunI == MemFunctionInfo.II_if_freenameindex) { + State = FreeMemAux(C, CE, State, 0, false, IsKnownToBeAllocatedMemory); + } else if (FunI == MemFunctionInfo.II_g_malloc0 || + FunI == MemFunctionInfo.II_g_try_malloc0) { if (CE->getNumArgs() < 1) return; SValBuilder &svalBuilder = C.getSValBuilder(); SVal zeroVal = svalBuilder.makeZeroVal(svalBuilder.getContext().CharTy); State = MallocMemAux(C, CE, CE->getArg(0), zeroVal, State); - State = ProcessZeroAllocation(C, CE, 0, State); - } else if (FunI == II_g_memdup) { + State = ProcessZeroAllocCheck(C, CE, 0, State); + } else if (FunI == MemFunctionInfo.II_g_memdup) { if (CE->getNumArgs() < 2) return; State = MallocMemAux(C, CE, CE->getArg(1), UndefinedVal(), State); - State = ProcessZeroAllocation(C, CE, 1, State); - } else if (FunI == II_g_malloc_n || FunI == II_g_try_malloc_n || - FunI == II_g_malloc0_n || FunI == II_g_try_malloc0_n) { + State = ProcessZeroAllocCheck(C, CE, 1, State); + } else if (FunI == MemFunctionInfo.II_g_malloc_n || + FunI == MemFunctionInfo.II_g_try_malloc_n || + FunI == MemFunctionInfo.II_g_malloc0_n || + FunI == MemFunctionInfo.II_g_try_malloc0_n) { if (CE->getNumArgs() < 2) return; SVal Init = UndefinedVal(); - if (FunI == II_g_malloc0_n || FunI == II_g_try_malloc0_n) { + if (FunI == MemFunctionInfo.II_g_malloc0_n || + FunI == MemFunctionInfo.II_g_try_malloc0_n) { SValBuilder &SB = C.getSValBuilder(); Init = SB.makeZeroVal(SB.getContext().CharTy); } SVal TotalSize = evalMulForBufferSize(C, CE->getArg(0), CE->getArg(1)); State = MallocMemAux(C, CE, TotalSize, Init, State); - State = ProcessZeroAllocation(C, CE, 0, State); - State = ProcessZeroAllocation(C, CE, 1, State); - } else if (FunI == II_g_realloc_n || FunI == II_g_try_realloc_n) { + State = ProcessZeroAllocCheck(C, CE, 0, State); + State = ProcessZeroAllocCheck(C, CE, 1, State); + } else if (FunI == MemFunctionInfo.II_g_realloc_n || + FunI == MemFunctionInfo.II_g_try_realloc_n) { if (CE->getNumArgs() < 3) return; - State = ReallocMemAux(C, CE, false, State, true); - State = ProcessZeroAllocation(C, CE, 1, State); - State = ProcessZeroAllocation(C, CE, 2, State); + State = ReallocMemAux(C, CE, /*ShouldFreeOnFail*/ false, State, + /*SuffixWithN*/ true); + State = ProcessZeroAllocCheck(C, CE, 1, State); + State = ProcessZeroAllocCheck(C, CE, 2, State); } } - if (IsOptimistic || ChecksEnabled[CK_MismatchedDeallocatorChecker]) { + if (MemFunctionInfo.ShouldIncludeOwnershipAnnotatedFunctions || + ChecksEnabled[CK_MismatchedDeallocatorChecker]) { // Check all the attributes, if there are any. // There can be multiple of these attributes. if (FD->hasAttrs()) @@ -977,9 +1227,9 @@ void MallocChecker::checkPostStmt(const CallExpr *CE, CheckerContext &C) const { } // Performs a 0-sized allocations check. -ProgramStateRef MallocChecker::ProcessZeroAllocation( - CheckerContext &C, const Expr *E, const unsigned AllocationSizeArg, - ProgramStateRef State, Optional RetVal) const { +ProgramStateRef MallocChecker::ProcessZeroAllocCheck( + CheckerContext &C, const Expr *E, const unsigned IndexOfSizeArg, + ProgramStateRef State, Optional RetVal) { if (!State) return nullptr; @@ -989,7 +1239,7 @@ ProgramStateRef MallocChecker::ProcessZeroAllocation( const Expr *Arg = nullptr; if (const CallExpr *CE = dyn_cast(E)) { - Arg = CE->getArg(AllocationSizeArg); + Arg = CE->getArg(IndexOfSizeArg); } else if (const CXXNewExpr *NE = dyn_cast(E)) { if (NE->isArray()) @@ -1051,7 +1301,9 @@ static QualType getDeepPointeeType(QualType T) { return Result; } -static bool treatUnusedNewEscaped(const CXXNewExpr *NE) { +/// \returns true if the constructor invoked by \p NE has an argument of a +/// pointer/reference to a record type. +static bool hasNonTrivialConstructorCall(const CXXNewExpr *NE) { const CXXConstructExpr *ConstructE = NE->getConstructExpr(); if (!ConstructE) @@ -1081,11 +1333,17 @@ static bool treatUnusedNewEscaped(const CXXNewExpr *NE) { void MallocChecker::processNewAllocation(const CXXNewExpr *NE, CheckerContext &C, SVal Target) const { - if (!isStandardNewDelete(NE->getOperatorNew(), C.getASTContext())) + if (!MemFunctionInfo.isStandardNewDelete(NE->getOperatorNew(), + C.getASTContext())) return; const ParentMap &PM = C.getLocationContext()->getParentMap(); - if (!PM.isConsumedExpr(NE) && treatUnusedNewEscaped(NE)) + + // Non-trivial constructors have a chance to escape 'this', but marking all + // invocations of trivial constructors as escaped would cause too great of + // reduction of true positives, so let's just do that for constructors that + // have an argument of a pointer-to-record type. + if (!PM.isConsumedExpr(NE) && hasNonTrivialConstructorCall(NE)) return; ProgramStateRef State = C.getState(); @@ -1096,7 +1354,7 @@ void MallocChecker::processNewAllocation(const CXXNewExpr *NE, State = MallocUpdateRefState(C, NE, State, NE->isArray() ? AF_CXXNewArray : AF_CXXNew, Target); State = addExtentSize(C, NE, State, Target); - State = ProcessZeroAllocation(C, NE, 0, State, Target); + State = ProcessZeroAllocCheck(C, NE, 0, State, Target); C.addTransition(State); } @@ -1164,13 +1422,14 @@ void MallocChecker::checkPreStmt(const CXXDeleteExpr *DE, if (SymbolRef Sym = C.getSVal(DE->getArgument()).getAsSymbol()) checkUseAfterFree(Sym, C, DE->getArgument()); - if (!isStandardNewDelete(DE->getOperatorDelete(), C.getASTContext())) + if (!MemFunctionInfo.isStandardNewDelete(DE->getOperatorDelete(), + C.getASTContext())) return; ProgramStateRef State = C.getState(); - bool ReleasedAllocated; + bool IsKnownToBeAllocated; State = FreeMemAux(C, DE->getArgument(), DE, State, - /*Hold*/false, ReleasedAllocated); + /*Hold*/ false, IsKnownToBeAllocated); C.addTransition(State); } @@ -1210,11 +1469,11 @@ void MallocChecker::checkPostObjCMessage(const ObjCMethodCall &Call, if (!*FreeWhenDone) return; - bool ReleasedAllocatedMemory; - ProgramStateRef State = FreeMemAux(C, Call.getArgExpr(0), - Call.getOriginExpr(), C.getState(), - /*Hold=*/true, ReleasedAllocatedMemory, - /*RetNullOnFailure=*/true); + bool IsKnownToBeAllocatedMemory; + ProgramStateRef State = + FreeMemAux(C, Call.getArgExpr(0), Call.getOriginExpr(), C.getState(), + /*Hold=*/true, IsKnownToBeAllocatedMemory, + /*RetNullOnFailure=*/true); C.addTransition(State); } @@ -1226,7 +1485,7 @@ MallocChecker::MallocMemReturnsAttr(CheckerContext &C, const CallExpr *CE, if (!State) return nullptr; - if (Att->getModule() != II_malloc) + if (Att->getModule() != MemFunctionInfo.II_malloc) return nullptr; OwnershipAttr::args_iterator I = Att->args_begin(), E = Att->args_end(); @@ -1292,11 +1551,10 @@ ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C, return MallocUpdateRefState(C, CE, State, Family); } -ProgramStateRef MallocChecker::MallocUpdateRefState(CheckerContext &C, - const Expr *E, - ProgramStateRef State, - AllocationFamily Family, - Optional RetVal) { +static ProgramStateRef MallocUpdateRefState(CheckerContext &C, const Expr *E, + ProgramStateRef State, + AllocationFamily Family, + Optional RetVal) { if (!State) return nullptr; @@ -1324,27 +1582,24 @@ ProgramStateRef MallocChecker::FreeMemAttr(CheckerContext &C, if (!State) return nullptr; - if (Att->getModule() != II_malloc) + if (Att->getModule() != MemFunctionInfo.II_malloc) return nullptr; - bool ReleasedAllocated = false; + bool IsKnownToBeAllocated = false; for (const auto &Arg : Att->args()) { ProgramStateRef StateI = FreeMemAux( C, CE, State, Arg.getASTIndex(), - Att->getOwnKind() == OwnershipAttr::Holds, ReleasedAllocated); + Att->getOwnKind() == OwnershipAttr::Holds, IsKnownToBeAllocated); if (StateI) State = StateI; } return State; } -ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C, - const CallExpr *CE, - ProgramStateRef State, - unsigned Num, - bool Hold, - bool &ReleasedAllocated, +ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C, const CallExpr *CE, + ProgramStateRef State, unsigned Num, + bool Hold, bool &IsKnownToBeAllocated, bool ReturnsNullOnFailure) const { if (!State) return nullptr; @@ -1352,8 +1607,8 @@ ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C, if (CE->getNumArgs() < (Num + 1)) return nullptr; - return FreeMemAux(C, CE->getArg(Num), CE, State, Hold, - ReleasedAllocated, ReturnsNullOnFailure); + return FreeMemAux(C, CE->getArg(Num), CE, State, Hold, IsKnownToBeAllocated, + ReturnsNullOnFailure); } /// Checks if the previous call to free on the given symbol failed - if free @@ -1371,8 +1626,10 @@ static bool didPreviousFreeFail(ProgramStateRef State, return false; } -AllocationFamily MallocChecker::getAllocationFamily(CheckerContext &C, - const Stmt *S) const { +static AllocationFamily +getAllocationFamily(const MemFunctionInfoTy &MemFunctionInfo, CheckerContext &C, + const Stmt *S) { + if (!S) return AF_None; @@ -1384,10 +1641,11 @@ AllocationFamily MallocChecker::getAllocationFamily(CheckerContext &C, ASTContext &Ctx = C.getASTContext(); - if (isCMemFunction(FD, Ctx, AF_Malloc, MemoryOperationKind::MOK_Any)) + if (MemFunctionInfo.isCMemFunction(FD, Ctx, AF_Malloc, + MemoryOperationKind::MOK_Any)) return AF_Malloc; - if (isStandardNewDelete(FD, Ctx)) { + if (MemFunctionInfo.isStandardNewDelete(FD, Ctx)) { OverloadedOperatorKind Kind = FD->getOverloadedOperator(); if (Kind == OO_New || Kind == OO_Delete) return AF_CXXNew; @@ -1395,10 +1653,12 @@ AllocationFamily MallocChecker::getAllocationFamily(CheckerContext &C, return AF_CXXNewArray; } - if (isCMemFunction(FD, Ctx, AF_IfNameIndex, MemoryOperationKind::MOK_Any)) + if (MemFunctionInfo.isCMemFunction(FD, Ctx, AF_IfNameIndex, + MemoryOperationKind::MOK_Any)) return AF_IfNameIndex; - if (isCMemFunction(FD, Ctx, AF_Alloca, MemoryOperationKind::MOK_Any)) + if (MemFunctionInfo.isCMemFunction(FD, Ctx, AF_Alloca, + MemoryOperationKind::MOK_Any)) return AF_Alloca; return AF_None; @@ -1416,8 +1676,8 @@ AllocationFamily MallocChecker::getAllocationFamily(CheckerContext &C, return AF_None; } -bool MallocChecker::printAllocDeallocName(raw_ostream &os, CheckerContext &C, - const Expr *E) const { +static bool printAllocDeallocName(raw_ostream &os, CheckerContext &C, + const Expr *E) { if (const CallExpr *CE = dyn_cast(E)) { // FIXME: This doesn't handle indirect calls. const FunctionDecl *FD = CE->getDirectCallee(); @@ -1456,9 +1716,10 @@ bool MallocChecker::printAllocDeallocName(raw_ostream &os, CheckerContext &C, return false; } -void MallocChecker::printExpectedAllocName(raw_ostream &os, CheckerContext &C, - const Expr *E) const { - AllocationFamily Family = getAllocationFamily(C, E); +static void printExpectedAllocName(raw_ostream &os, + const MemFunctionInfoTy &MemFunctionInfo, + CheckerContext &C, const Expr *E) { + AllocationFamily Family = getAllocationFamily(MemFunctionInfo, C, E); switch(Family) { case AF_Malloc: os << "malloc()"; return; @@ -1471,8 +1732,7 @@ void MallocChecker::printExpectedAllocName(raw_ostream &os, CheckerContext &C, } } -void MallocChecker::printExpectedDeallocName(raw_ostream &os, - AllocationFamily Family) const { +static void printExpectedDeallocName(raw_ostream &os, AllocationFamily Family) { switch(Family) { case AF_Malloc: os << "free()"; return; case AF_CXXNew: os << "'delete'"; return; @@ -1487,9 +1747,8 @@ void MallocChecker::printExpectedDeallocName(raw_ostream &os, ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C, const Expr *ArgExpr, const Expr *ParentExpr, - ProgramStateRef State, - bool Hold, - bool &ReleasedAllocated, + ProgramStateRef State, bool Hold, + bool &IsKnownToBeAllocated, bool ReturnsNullOnFailure) const { if (!State) @@ -1563,6 +1822,9 @@ ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C, const RefState *RsBase = State->get(SymBase); SymbolRef PreviousRetStatusSymbol = nullptr; + IsKnownToBeAllocated = + RsBase && (RsBase->isAllocated() || RsBase->isAllocatedOfSizeZero()); + if (RsBase) { // Memory returned by alloca() shouldn't be freed. @@ -1585,7 +1847,8 @@ ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C, // Check if an expected deallocation function matches the real one. bool DeallocMatchesAlloc = - RsBase->getAllocationFamily() == getAllocationFamily(C, ParentExpr); + RsBase->getAllocationFamily() == + getAllocationFamily(MemFunctionInfo, C, ParentExpr); if (!DeallocMatchesAlloc) { ReportMismatchedDealloc(C, ArgExpr->getSourceRange(), ParentExpr, RsBase, SymBase, Hold); @@ -1611,9 +1874,6 @@ ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C, return nullptr; } - ReleasedAllocated = (RsBase != nullptr) && (RsBase->isAllocated() || - RsBase->isAllocatedOfSizeZero()); - // Clean out the info on previous call to free return info. State = State->remove(SymBase); @@ -1628,8 +1888,9 @@ ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C, } } - AllocationFamily Family = RsBase ? RsBase->getAllocationFamily() - : getAllocationFamily(C, ParentExpr); + AllocationFamily Family = + RsBase ? RsBase->getAllocationFamily() + : getAllocationFamily(MemFunctionInfo, C, ParentExpr); // Normal free. if (Hold) return State->set(SymBase, @@ -1679,8 +1940,8 @@ Optional MallocChecker::getCheckIfTracked(CheckerContext &C, const Stmt *AllocDeallocStmt, bool IsALeakCheck) const { - return getCheckIfTracked(getAllocationFamily(C, AllocDeallocStmt), - IsALeakCheck); + return getCheckIfTracked( + getAllocationFamily(MemFunctionInfo, C, AllocDeallocStmt), IsALeakCheck); } Optional @@ -1818,7 +2079,7 @@ void MallocChecker::ReportBadFree(CheckerContext &C, SVal ArgVal, else os << "not memory allocated by "; - printExpectedAllocName(os, C, DeallocExpr); + printExpectedAllocName(os, MemFunctionInfo, C, DeallocExpr); auto R = llvm::make_unique(*BT_BadFree[*CheckKind], os.str(), N); @@ -2130,7 +2391,7 @@ void MallocChecker::ReportFunctionPointerFree(CheckerContext &C, SVal ArgVal, ProgramStateRef MallocChecker::ReallocMemAux(CheckerContext &C, const CallExpr *CE, - bool FreesOnFail, + bool ShouldFreeOnFail, ProgramStateRef State, bool SuffixWithN) const { if (!State) @@ -2195,33 +2456,32 @@ ProgramStateRef MallocChecker::ReallocMemAux(CheckerContext &C, if (!FromPtr || !ToPtr) return nullptr; - bool ReleasedAllocated = false; + bool IsKnownToBeAllocated = false; // If the size is 0, free the memory. if (SizeIsZero) - if (ProgramStateRef stateFree = FreeMemAux(C, CE, StateSizeIsZero, 0, - false, ReleasedAllocated)){ - // The semantics of the return value are: - // If size was equal to 0, either NULL or a pointer suitable to be passed - // to free() is returned. We just free the input pointer and do not add - // any constrains on the output pointer. + // The semantics of the return value are: + // If size was equal to 0, either NULL or a pointer suitable to be passed + // to free() is returned. We just free the input pointer and do not add + // any constrains on the output pointer. + if (ProgramStateRef stateFree = + FreeMemAux(C, CE, StateSizeIsZero, 0, false, IsKnownToBeAllocated)) return stateFree; - } // Default behavior. if (ProgramStateRef stateFree = - FreeMemAux(C, CE, State, 0, false, ReleasedAllocated)) { + FreeMemAux(C, CE, State, 0, false, IsKnownToBeAllocated)) { ProgramStateRef stateRealloc = MallocMemAux(C, CE, TotalSize, UnknownVal(), stateFree); if (!stateRealloc) return nullptr; - ReallocPairKind Kind = RPToBeFreedAfterFailure; - if (FreesOnFail) - Kind = RPIsFreeOnFailure; - else if (!ReleasedAllocated) - Kind = RPDoNotTrackAfterFailure; + OwnershipAfterReallocKind Kind = OAR_ToBeFreedAfterFailure; + if (ShouldFreeOnFail) + Kind = OAR_FreeOnFailure; + else if (!IsKnownToBeAllocated) + Kind = OAR_DoNotTrackAfterFailure; // Record the info about the reallocated symbol so that we could properly // process failed reallocation. @@ -2249,9 +2509,9 @@ ProgramStateRef MallocChecker::CallocMem(CheckerContext &C, const CallExpr *CE, return MallocMemAux(C, CE, TotalSize, zeroVal, State); } -LeakInfo -MallocChecker::getAllocationSite(const ExplodedNode *N, SymbolRef Sym, - CheckerContext &C) const { +MallocChecker::LeakInfo MallocChecker::getAllocationSite(const ExplodedNode *N, + SymbolRef Sym, + CheckerContext &C) { const LocationContext *LeakContext = N->getLocationContext(); // Walk the ExplodedGraph backwards and find the first node that referred to // the tracked symbol. @@ -2432,9 +2692,10 @@ void MallocChecker::checkPreCall(const CallEvent &Call, ASTContext &Ctx = C.getASTContext(); if (ChecksEnabled[CK_MallocChecker] && - (isCMemFunction(FD, Ctx, AF_Malloc, MemoryOperationKind::MOK_Free) || - isCMemFunction(FD, Ctx, AF_IfNameIndex, - MemoryOperationKind::MOK_Free))) + (MemFunctionInfo.isCMemFunction(FD, Ctx, AF_Malloc, + MemoryOperationKind::MOK_Free) || + MemFunctionInfo.isCMemFunction(FD, Ctx, AF_IfNameIndex, + MemoryOperationKind::MOK_Free))) return; } @@ -2537,7 +2798,7 @@ void MallocChecker::checkPostStmt(const BlockExpr *BE, C.addTransition(state); } -bool MallocChecker::isReleased(SymbolRef Sym, CheckerContext &C) const { +static bool isReleased(SymbolRef Sym, CheckerContext &C) { assert(Sym); const RefState *RS = C.getState()->get(Sym); return (RS && RS->isReleased()); @@ -2642,13 +2903,17 @@ ProgramStateRef MallocChecker::evalAssume(ProgramStateRef state, SymbolRef ReallocSym = I.getData().ReallocatedSym; if (const RefState *RS = state->get(ReallocSym)) { if (RS->isReleased()) { - if (I.getData().Kind == RPToBeFreedAfterFailure) + switch (I.getData().Kind) { + case OAR_ToBeFreedAfterFailure: state = state->set(ReallocSym, RefState::getAllocated(RS->getAllocationFamily(), RS->getStmt())); - else if (I.getData().Kind == RPDoNotTrackAfterFailure) + break; + case OAR_DoNotTrackAfterFailure: state = state->remove(ReallocSym); - else - assert(I.getData().Kind == RPIsFreeOnFailure); + break; + default: + assert(I.getData().Kind == OAR_FreeOnFailure); + } } } state = state->remove(I.getKey()); @@ -2731,7 +2996,7 @@ bool MallocChecker::mayFreeAnyEscapedMemoryOrIsModeledExplicitly( // If it's one of the allocation functions we can reason about, we model // its behavior explicitly. - if (isMemFunction(FD, ASTC)) + if (MemFunctionInfo.isMemFunction(FD, ASTC)) return false; // If it's not a system call, assume it frees memory. @@ -2823,35 +3088,32 @@ bool MallocChecker::mayFreeAnyEscapedMemoryOrIsModeledExplicitly( return false; } -static bool retTrue(const RefState *RS) { - return true; -} - -static bool checkIfNewOrNewArrayFamily(const RefState *RS) { - return (RS->getAllocationFamily() == AF_CXXNewArray || - RS->getAllocationFamily() == AF_CXXNew); -} - ProgramStateRef MallocChecker::checkPointerEscape(ProgramStateRef State, const InvalidatedSymbols &Escaped, const CallEvent *Call, PointerEscapeKind Kind) const { - return checkPointerEscapeAux(State, Escaped, Call, Kind, &retTrue); + return checkPointerEscapeAux(State, Escaped, Call, Kind, + /*IsConstPointerEscape*/ false); } ProgramStateRef MallocChecker::checkConstPointerEscape(ProgramStateRef State, const InvalidatedSymbols &Escaped, const CallEvent *Call, PointerEscapeKind Kind) const { + // If a const pointer escapes, it may not be freed(), but it could be deleted. return checkPointerEscapeAux(State, Escaped, Call, Kind, - &checkIfNewOrNewArrayFamily); + /*IsConstPointerEscape*/ true); } -ProgramStateRef MallocChecker::checkPointerEscapeAux(ProgramStateRef State, - const InvalidatedSymbols &Escaped, - const CallEvent *Call, - PointerEscapeKind Kind, - bool(*CheckRefState)(const RefState*)) const { +static bool checkIfNewOrNewArrayFamily(const RefState *RS) { + return (RS->getAllocationFamily() == AF_CXXNewArray || + RS->getAllocationFamily() == AF_CXXNew); +} + +ProgramStateRef MallocChecker::checkPointerEscapeAux( + ProgramStateRef State, const InvalidatedSymbols &Escaped, + const CallEvent *Call, PointerEscapeKind Kind, + bool IsConstPointerEscape) const { // If we know that the call does not free memory, or we want to process the // call later, keep tracking the top level arguments. SymbolRef EscapingSymbol = nullptr; @@ -2870,12 +3132,10 @@ ProgramStateRef MallocChecker::checkPointerEscapeAux(ProgramStateRef State, if (EscapingSymbol && EscapingSymbol != sym) continue; - if (const RefState *RS = State->get(sym)) { - if ((RS->isAllocated() || RS->isAllocatedOfSizeZero()) && - CheckRefState(RS)) { - State = State->set(sym, RefState::getEscaped(RS)); - } - } + if (const RefState *RS = State->get(sym)) + if (RS->isAllocated() || RS->isAllocatedOfSizeZero()) + if (!IsConstPointerEscape || checkIfNewOrNewArrayFamily(RS)) + State = State->set(sym, RefState::getEscaped(RS)); } return State; } @@ -2885,9 +3145,8 @@ static SymbolRef findFailedReallocSymbol(ProgramStateRef currState, ReallocPairsTy currMap = currState->get(); ReallocPairsTy prevMap = prevState->get(); - for (ReallocPairsTy::iterator I = prevMap.begin(), E = prevMap.end(); - I != E; ++I) { - SymbolRef sym = I.getKey(); + for (const ReallocPairsTy::value_type &Pair : prevMap) { + SymbolRef sym = Pair.first; if (!currMap.lookup(sym)) return sym; } @@ -2908,21 +3167,19 @@ static bool isReferenceCountingPointerDestructor(const CXXDestructorDecl *DD) { return false; } -PathDiagnosticPieceRef -MallocChecker::MallocBugVisitor::VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - PathSensitiveBugReport &BR) { - +PathDiagnosticPieceRef MallocBugVisitor::VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) { ProgramStateRef state = N->getState(); ProgramStateRef statePrev = N->getFirstPred()->getState(); - const RefState *RS = state->get(Sym); + const RefState *RSCurr = state->get(Sym); const RefState *RSPrev = statePrev->get(Sym); const Stmt *S = N->getStmtForDiagnostics(); // When dealing with containers, we sometimes want to give a note // even if the statement is missing. - if (!S && (!RS || RS->getAllocationFamily() != AF_InnerBuffer)) + if (!S && (!RSCurr || RSCurr->getAllocationFamily() != AF_InnerBuffer)) return nullptr; const LocationContext *CurrentLC = N->getLocationContext(); @@ -2957,12 +3214,12 @@ MallocChecker::MallocBugVisitor::VisitNode(const ExplodedNode *N, llvm::raw_svector_ostream OS(Buf); if (Mode == Normal) { - if (isAllocated(RS, RSPrev, S)) { + if (isAllocated(RSCurr, RSPrev, S)) { Msg = "Memory is allocated"; StackHint = std::make_unique( Sym, "Returned allocated memory"); - } else if (isReleased(RS, RSPrev, S)) { - const auto Family = RS->getAllocationFamily(); + } else if (isReleased(RSCurr, RSPrev, S)) { + const auto Family = RSCurr->getAllocationFamily(); switch (Family) { case AF_Alloca: case AF_Malloc: @@ -2986,7 +3243,7 @@ MallocChecker::MallocBugVisitor::VisitNode(const ExplodedNode *N, Sym, "Returning; inner buffer was deallocated"); } else { OS << "reallocated by call to '"; - const Stmt *S = RS->getStmt(); + const Stmt *S = RSCurr->getStmt(); if (const auto *MemCallE = dyn_cast(S)) { OS << MemCallE->getMethodDecl()->getNameAsString(); } else if (const auto *OpCallE = dyn_cast(S)) { @@ -3037,10 +3294,10 @@ MallocChecker::MallocBugVisitor::VisitNode(const ExplodedNode *N, } } } - } else if (isRelinquished(RS, RSPrev, S)) { + } else if (isRelinquished(RSCurr, RSPrev, S)) { Msg = "Memory ownership is transferred"; StackHint = std::make_unique(Sym, ""); - } else if (isReallocFailedCheck(RS, RSPrev, S)) { + } else if (hasReallocFailed(RSCurr, RSPrev, S)) { Mode = ReallocationFailed; Msg = "Reallocation failed"; StackHint = std::make_unique( @@ -3080,7 +3337,7 @@ MallocChecker::MallocBugVisitor::VisitNode(const ExplodedNode *N, // Generate the extra diagnostic. PathDiagnosticLocation Pos; if (!S) { - assert(RS->getAllocationFamily() == AF_InnerBuffer); + assert(RSCurr->getAllocationFamily() == AF_InnerBuffer); auto PostImplCall = N->getLocation().getAs(); if (!PostImplCall) return nullptr; @@ -3145,8 +3402,8 @@ void ento::registerInnerPointerCheckerAux(CheckerManager &mgr) { void ento::registerDynamicMemoryModeling(CheckerManager &mgr) { auto *checker = mgr.registerChecker(); - checker->IsOptimistic = mgr.getAnalyzerOptions().getCheckerBooleanOption( - checker, "Optimistic"); + checker->MemFunctionInfo.ShouldIncludeOwnershipAnnotatedFunctions = + mgr.getAnalyzerOptions().getCheckerBooleanOption(checker, "Optimistic"); } bool ento::shouldRegisterDynamicMemoryModeling(const LangOptions &LO) { From adc75dc0158457824b5eec8a9ed9661e09f24e84 Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Fri, 20 Sep 2019 18:28:04 +0000 Subject: [PATCH 162/181] Fix a documentation error llvm-svn: 372419 (cherry picked from commit 96be6f485c733c990d01c60f0dc6ae11b3974eea) --- clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index 3885e3f2e5365..900e18487df75 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -550,7 +550,7 @@ class MallocChecker /// Models memory reallocation. /// /// \param [in] CE The expression that reallocated memory - /// \param [in] FreesMemOnFailure Whether if reallocation fails, the supplied + /// \param [in] ShouldFreeOnFail Whether if reallocation fails, the supplied /// memory should be freed. /// \param [in] State The \c ProgramState right before reallocation. /// \param [in] SuffixWithN Whether the reallocation function we're modeling From d7cbbf088ec1f10f1eb49400298b74a5417df833 Mon Sep 17 00:00:00 2001 From: Kristof Umann Date: Sat, 21 Sep 2019 07:56:40 +0000 Subject: [PATCH 163/181] Attempt to fix a windows buildbot failure llvm-svn: 372462 (cherry picked from commit c90fda6abe84127a99a8fe4341b6ed7c8abee47e) --- clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index 900e18487df75..41d2c83829814 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -137,8 +137,8 @@ class RefState { const Stmt *S; - Kind K : 3; - AllocationFamily Family : 3; + Kind K; + AllocationFamily Family; RefState(Kind k, const Stmt *s, AllocationFamily family) : S(s), K(k), Family(family) { From 40f5913b7aacfba630ddab6b4dd884d4928659c7 Mon Sep 17 00:00:00 2001 From: Alex Langford Date: Mon, 23 Sep 2019 22:24:47 +0000 Subject: [PATCH 164/181] [NFCI] Return PathSensitiveBugReport where appropriate Some compilers have trouble converting unique_ptr to unique_ptr causing some functions to fail to compile. Changing the return type of the functions that fail to compile does not appear to have any issues. I ran into this issue building with clang 3.8 on Ubuntu 16.04. llvm-svn: 372668 (cherry picked from commit 86c3af90296a893550203fc1a6558a24033c25d3) --- .../StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp | 8 +++++--- .../lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp | 9 +++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp index b3b09c1e45545..ab07ceeeca248 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp @@ -113,8 +113,10 @@ class MacOSKeychainAPIChecker : public Checker, const ExplodedNode *getAllocationNode(const ExplodedNode *N, SymbolRef Sym, CheckerContext &C) const; - std::unique_ptr generateAllocatedDataNotReleasedReport( - const AllocationPair &AP, ExplodedNode *N, CheckerContext &C) const; + std::unique_ptr + generateAllocatedDataNotReleasedReport(const AllocationPair &AP, + ExplodedNode *N, + CheckerContext &C) const; /// Mark an AllocationPair interesting for diagnostic reporting. void markInteresting(PathSensitiveBugReport *R, @@ -467,7 +469,7 @@ MacOSKeychainAPIChecker::getAllocationNode(const ExplodedNode *N, return AllocNode; } -std::unique_ptr +std::unique_ptr MacOSKeychainAPIChecker::generateAllocatedDataNotReleasedReport( const AllocationPair &AP, ExplodedNode *N, CheckerContext &C) const { const ADFunctionInfo &FI = FunctionsToTrack[AP.second->AllocatorIdx]; diff --git a/clang/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp index d91488b5e0b54..02bc3d2900126 100644 --- a/clang/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp @@ -35,11 +35,11 @@ class NonNullParamChecker void checkPreCall(const CallEvent &Call, CheckerContext &C) const; - std::unique_ptr + std::unique_ptr genReportNullAttrNonNull(const ExplodedNode *ErrorN, const Expr *ArgE, unsigned IdxOfArg) const; - std::unique_ptr + std::unique_ptr genReportReferenceToNullPointer(const ExplodedNode *ErrorN, const Expr *ArgE) const; }; @@ -179,7 +179,7 @@ void NonNullParamChecker::checkPreCall(const CallEvent &Call, C.addTransition(state); } -std::unique_ptr +std::unique_ptr NonNullParamChecker::genReportNullAttrNonNull(const ExplodedNode *ErrorNode, const Expr *ArgE, unsigned IdxOfArg) const { @@ -204,7 +204,8 @@ NonNullParamChecker::genReportNullAttrNonNull(const ExplodedNode *ErrorNode, return R; } -std::unique_ptr NonNullParamChecker::genReportReferenceToNullPointer( +std::unique_ptr +NonNullParamChecker::genReportReferenceToNullPointer( const ExplodedNode *ErrorNode, const Expr *ArgE) const { if (!BTNullRefArg) BTNullRefArg.reset(new BuiltinBug(this, "Dereference of null pointer")); From 34eb3ca6ac229d3159ab5161a7bfae6fc0c075b8 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Thu, 26 Sep 2019 06:33:21 +0000 Subject: [PATCH 165/181] [analyzer] Avoid small vectors of non-default-constructibles. Unconfuses certain compilers. llvm-svn: 372942 (cherry picked from commit 4ed9793f980fd6c07f8f55cc8463301c3521f679) --- .../include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h | 2 +- clang/lib/StaticAnalyzer/Core/Store.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h index 8d1a672bc6129..fc1cc91388266 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h @@ -347,7 +347,7 @@ class CallEvent { ProgramStateRef invalidateRegions(unsigned BlockCount, ProgramStateRef Orig = nullptr) const; - using FrameBindingTy = std::pair; + using FrameBindingTy = std::pair; using BindingsTy = SmallVectorImpl; /// Populates the given SmallVector with the bindings in the callee's stack diff --git a/clang/lib/StaticAnalyzer/Core/Store.cpp b/clang/lib/StaticAnalyzer/Core/Store.cpp index 3cf616161c669..b4ab6877726ce 100644 --- a/clang/lib/StaticAnalyzer/Core/Store.cpp +++ b/clang/lib/StaticAnalyzer/Core/Store.cpp @@ -52,7 +52,7 @@ StoreRef StoreManager::enterStackFrame(Store OldStore, Call.getInitialStackFrameContents(LCtx, InitialBindings); for (const auto &I : InitialBindings) - Store = Bind(Store.getStore(), I.first, I.second); + Store = Bind(Store.getStore(), I.first.castAs(), I.second); return Store; } From 8bb333a00f2f8dc90e7447497d49b77cb62be52a Mon Sep 17 00:00:00 2001 From: Aaron Puchert Date: Sat, 28 Sep 2019 13:28:50 +0000 Subject: [PATCH 166/181] Don't install example analyzer plugins Summary: Fixes PR43430. Reviewers: hintonda, NoQ, Szelethus, lebedev.ri Reviewed By: lebedev.ri Differential Revision: https://reviews.llvm.org/D68172 llvm-svn: 373148 (cherry picked from commit 243058fff1409baae54b5a361f8ba9d37f4f7b4e) --- .../Analysis/plugins/CheckerDependencyHandling/CMakeLists.txt | 2 +- clang/lib/Analysis/plugins/CheckerOptionHandling/CMakeLists.txt | 2 +- clang/lib/Analysis/plugins/SampleAnalyzer/CMakeLists.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/clang/lib/Analysis/plugins/CheckerDependencyHandling/CMakeLists.txt b/clang/lib/Analysis/plugins/CheckerDependencyHandling/CMakeLists.txt index 0a8ff48755f17..b8a04e32835c1 100644 --- a/clang/lib/Analysis/plugins/CheckerDependencyHandling/CMakeLists.txt +++ b/clang/lib/Analysis/plugins/CheckerDependencyHandling/CMakeLists.txt @@ -1,5 +1,5 @@ set(LLVM_EXPORTED_SYMBOL_FILE ${CMAKE_CURRENT_SOURCE_DIR}/CheckerDependencyHandlingAnalyzerPlugin.exports) -add_llvm_library(CheckerDependencyHandlingAnalyzerPlugin MODULE CheckerDependencyHandling.cpp PLUGIN_TOOL clang) +add_llvm_library(CheckerDependencyHandlingAnalyzerPlugin MODULE BUILDTREE_ONLY CheckerDependencyHandling.cpp PLUGIN_TOOL clang) target_link_libraries(CheckerDependencyHandlingAnalyzerPlugin PRIVATE clangAnalysis diff --git a/clang/lib/Analysis/plugins/CheckerOptionHandling/CMakeLists.txt b/clang/lib/Analysis/plugins/CheckerOptionHandling/CMakeLists.txt index 6e289933c2dd4..a7a8666f88d95 100644 --- a/clang/lib/Analysis/plugins/CheckerOptionHandling/CMakeLists.txt +++ b/clang/lib/Analysis/plugins/CheckerOptionHandling/CMakeLists.txt @@ -1,5 +1,5 @@ set(LLVM_EXPORTED_SYMBOL_FILE ${CMAKE_CURRENT_SOURCE_DIR}/CheckerOptionHandlingAnalyzerPlugin.exports) -add_llvm_library(CheckerOptionHandlingAnalyzerPlugin MODULE CheckerOptionHandling.cpp PLUGIN_TOOL clang) +add_llvm_library(CheckerOptionHandlingAnalyzerPlugin MODULE BUILDTREE_ONLY CheckerOptionHandling.cpp PLUGIN_TOOL clang) target_link_libraries(CheckerOptionHandlingAnalyzerPlugin PRIVATE clangAnalysis diff --git a/clang/lib/Analysis/plugins/SampleAnalyzer/CMakeLists.txt b/clang/lib/Analysis/plugins/SampleAnalyzer/CMakeLists.txt index 639a97f253112..0e97acfa646ae 100644 --- a/clang/lib/Analysis/plugins/SampleAnalyzer/CMakeLists.txt +++ b/clang/lib/Analysis/plugins/SampleAnalyzer/CMakeLists.txt @@ -1,5 +1,5 @@ set(LLVM_EXPORTED_SYMBOL_FILE ${CMAKE_CURRENT_SOURCE_DIR}/SampleAnalyzerPlugin.exports) -add_llvm_library(SampleAnalyzerPlugin MODULE MainCallChecker.cpp PLUGIN_TOOL clang) +add_llvm_library(SampleAnalyzerPlugin MODULE BUILDTREE_ONLY MainCallChecker.cpp PLUGIN_TOOL clang) target_link_libraries(SampleAnalyzerPlugin PRIVATE clangAnalysis From bee81d34a35ce65ef6645260c7289396a04bda48 Mon Sep 17 00:00:00 2001 From: Erich Keane Date: Mon, 30 Sep 2019 19:12:29 +0000 Subject: [PATCH 167/181] Teach CallGraph to look into Generic Lambdas. CallGraph visited LambdaExpr by getting the Call Operator from CXXRecordDecl (LambdaExpr::getCallOperator calls CXXRecordDecl::getLambdaCallOperator), which replaced generic lambda call operators with the non-instantiated FunctionDecl. The result was that the CallGraph would only pick up non-dependent calls. This patch does a few things: 1- Extend CXXRecordDecl to have a getDependentLambdaCallOperator, which will get the FunctionTemplateDecl, rather than immediately getting the TemplateDecl. 2- Define getLambdaCallOperator and getDependentLambdaCallOperator in terms of a common function. 3- Extend LambdaExpr with a getDependentCallOperator, which just calls the above function. 4- Changes CallGraph to handle Generic LambdaExprs. llvm-svn: 373247 (cherry picked from commit 5c2c60d2fc27e57c56674205faf95ecf01c1d4d9) --- clang/include/clang/AST/DeclCXX.h | 4 ++++ clang/include/clang/AST/ExprCXX.h | 4 ++++ clang/lib/AST/DeclCXX.cpp | 18 +++++++++++++----- clang/lib/AST/ExprCXX.cpp | 5 +++++ clang/lib/Analysis/CallGraph.cpp | 5 ++++- clang/test/Analysis/debug-CallGraph.cpp | 21 +++++++++++++++++++-- 6 files changed, 49 insertions(+), 8 deletions(-) diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h index 6133d2fce754c..c0a55c3c8eeaf 100644 --- a/clang/include/clang/AST/DeclCXX.h +++ b/clang/include/clang/AST/DeclCXX.h @@ -1212,6 +1212,10 @@ class CXXRecordDecl : public RecordDecl { /// if this is a closure type. CXXMethodDecl *getLambdaCallOperator() const; + /// Retrieve the dependent lambda call operator of the closure type + /// if this is a templated closure type. + FunctionTemplateDecl *getDependentLambdaCallOperator() const; + /// Retrieve the lambda static invoker, the address of which /// is returned by the conversion operator, and the body of which /// is forwarded to the lambda call operator. diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h index 551a677570ff9..b2ab834f30e9e 100644 --- a/clang/include/clang/AST/ExprCXX.h +++ b/clang/include/clang/AST/ExprCXX.h @@ -1902,6 +1902,10 @@ class LambdaExpr final : public Expr, /// lambda expression. CXXMethodDecl *getCallOperator() const; + /// Retrieve the function template call operator associated with this + /// lambda expression. + FunctionTemplateDecl *getDependentCallOperator() const; + /// If this is a generic lambda expression, retrieve the template /// parameter list associated with it, or else return null. TemplateParameterList *getTemplateParameterList() const; diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp index 48965966f091d..b8c9229230195 100644 --- a/clang/lib/AST/DeclCXX.cpp +++ b/clang/lib/AST/DeclCXX.cpp @@ -1369,17 +1369,25 @@ static bool allLookupResultsAreTheSame(const DeclContext::lookup_result &R) { } #endif -CXXMethodDecl* CXXRecordDecl::getLambdaCallOperator() const { - if (!isLambda()) return nullptr; +NamedDecl* getLambdaCallOperatorHelper(const CXXRecordDecl &RD) { + if (!RD.isLambda()) return nullptr; DeclarationName Name = - getASTContext().DeclarationNames.getCXXOperatorName(OO_Call); - DeclContext::lookup_result Calls = lookup(Name); + RD.getASTContext().DeclarationNames.getCXXOperatorName(OO_Call); + DeclContext::lookup_result Calls = RD.lookup(Name); assert(!Calls.empty() && "Missing lambda call operator!"); assert(allLookupResultsAreTheSame(Calls) && "More than one lambda call operator!"); + return Calls.front(); +} + +FunctionTemplateDecl* CXXRecordDecl::getDependentLambdaCallOperator() const { + NamedDecl *CallOp = getLambdaCallOperatorHelper(*this); + return dyn_cast(CallOp); +} - NamedDecl *CallOp = Calls.front(); +CXXMethodDecl *CXXRecordDecl::getLambdaCallOperator() const { + NamedDecl *CallOp = getLambdaCallOperatorHelper(*this); if (const auto *CallOpTmpl = dyn_cast(CallOp)) return cast(CallOpTmpl->getTemplatedDecl()); diff --git a/clang/lib/AST/ExprCXX.cpp b/clang/lib/AST/ExprCXX.cpp index b30f785ba8f54..66ae629ffec52 100644 --- a/clang/lib/AST/ExprCXX.cpp +++ b/clang/lib/AST/ExprCXX.cpp @@ -1205,6 +1205,11 @@ CXXMethodDecl *LambdaExpr::getCallOperator() const { return Record->getLambdaCallOperator(); } +FunctionTemplateDecl *LambdaExpr::getDependentCallOperator() const { + CXXRecordDecl *Record = getLambdaClass(); + return Record->getDependentLambdaCallOperator(); +} + TemplateParameterList *LambdaExpr::getTemplateParameterList() const { CXXRecordDecl *Record = getLambdaClass(); return Record->getGenericLambdaTemplateParameterList(); diff --git a/clang/lib/Analysis/CallGraph.cpp b/clang/lib/Analysis/CallGraph.cpp index b6542cad120e5..3b0546570180d 100644 --- a/clang/lib/Analysis/CallGraph.cpp +++ b/clang/lib/Analysis/CallGraph.cpp @@ -80,7 +80,10 @@ class CGBuilder : public StmtVisitor { } void VisitLambdaExpr(LambdaExpr *LE) { - if (CXXMethodDecl *MD = LE->getCallOperator()) + if (FunctionTemplateDecl *FTD = LE->getDependentCallOperator()) + for (FunctionDecl *FD : FTD->specializations()) + G->VisitFunctionDecl(FD); + else if (CXXMethodDecl *MD = LE->getCallOperator()) G->VisitFunctionDecl(MD); } diff --git a/clang/test/Analysis/debug-CallGraph.cpp b/clang/test/Analysis/debug-CallGraph.cpp index 1d6844fad94fc..0f5a83b268a01 100644 --- a/clang/test/Analysis/debug-CallGraph.cpp +++ b/clang/test/Analysis/debug-CallGraph.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 -analyzer-checker=debug.DumpCallGraph %s -fblocks 2>&1 | FileCheck %s +// RUN: %clang_analyze_cc1 -analyzer-checker=debug.DumpCallGraph %s -fblocks -std=c++14 2>&1 | FileCheck %s int get5() { return 5; @@ -68,8 +68,25 @@ void templUser() { } } +namespace Lambdas { + void Callee(){} + + void f1() { + [](int i) { + Callee(); + }(1); + [](auto i) { + Callee(); + }(1); + } +} + // CHECK:--- Call graph Dump --- -// CHECK-NEXT: {{Function: < root > calls: get5 add test_add mmm foo aaa < > bbb ddd ccc eee fff do_nothing test_single_call SomeNS::templ SomeNS::templ SomeNS::templUser $}} +// CHECK-NEXT: {{Function: < root > calls: get5 add test_add mmm foo aaa < > bbb ddd ccc eee fff do_nothing test_single_call SomeNS::templ SomeNS::templ SomeNS::templUser Lambdas::Callee Lambdas::f1 Lambdas::f1\(\)::\(anonymous class\)::operator\(\) Lambdas::f1\(\)::\(anonymous class\)::operator\(\) $}} +// CHECK-NEXT: {{Function: Lambdas::f1 calls: Lambdas::f1\(\)::\(anonymous class\)::operator\(\) Lambdas::f1\(\)::\(anonymous class\)::operator\(\) $}} +// CHECK-NEXT: {{Function: Lambdas::f1\(\)::\(anonymous class\)::operator\(\) calls: Lambdas::Callee $}} +// CHECK-NEXT: {{Function: Lambdas::f1\(\)::\(anonymous class\)::operator\(\) calls: Lambdas::Callee $}} +// CHECK-NEXT: {{Function: Lambdas::Callee calls: $}} // CHECK-NEXT: {{Function: SomeNS::templUser calls: SomeNS::templ SomeNS::templ $}} // CHECK-NEXT: {{Function: SomeNS::templ calls: eee $}} // CHECK-NEXT: {{Function: SomeNS::templ calls: ccc $}} From da072759205cf09e1d0fc7828ffdab6143c23cc1 Mon Sep 17 00:00:00 2001 From: Simon Pilgrim Date: Sun, 13 Oct 2019 11:30:06 +0000 Subject: [PATCH 168/181] BlockInCriticalSectionChecker - silence static analyzer dyn_cast null dereference warning. NFCI. The static analyzer is warning about a potential null dereference, but we should be able to use cast<> directly and if not assert will fire for us. llvm-svn: 374717 (cherry picked from commit 2c775709f6c23c691969cc900535b4ce339668c5) --- .../StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp index 067dafe6db649..2a20d86a6875e 100644 --- a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp @@ -126,7 +126,7 @@ bool BlockInCriticalSectionChecker::isLockFunction(const CallEvent &Call) const bool BlockInCriticalSectionChecker::isUnlockFunction(const CallEvent &Call) const { if (const auto *Dtor = dyn_cast(&Call)) { - const auto *DRecordDecl = dyn_cast(Dtor->getDecl()->getParent()); + const auto *DRecordDecl = cast(Dtor->getDecl()->getParent()); auto IdentifierInfo = DRecordDecl->getIdentifier(); if (IdentifierInfo == IILockGuard || IdentifierInfo == IIUniqueLock) return true; From 27bfa17c1f988026f6aa49c326e6d3480031b4eb Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Thu, 17 Oct 2019 23:10:02 +0000 Subject: [PATCH 169/181] [analyzer] exploded-graph-rewriter: Make node headers a bit lighter. The 50% grey color is too dark on some monitors. llvm-svn: 375184 (cherry picked from commit 73b67f0b1a3ca835a4c0481abee87a96a2107b7d) --- clang/test/Analysis/exploded-graph-rewriter/node_labels.dot | 2 +- clang/utils/analyzer/exploded-graph-rewriter.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clang/test/Analysis/exploded-graph-rewriter/node_labels.dot b/clang/test/Analysis/exploded-graph-rewriter/node_labels.dot index b8f69192a6c19..a434cd23071ee 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/node_labels.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/node_labels.dot @@ -13,7 +13,7 @@ // LIGHT: Node0x1 [shape=record,label=< // DARK: Node0x1 [shape=record,color="white",fontcolor="gray80",label=< // CHECK-SAME:
+// LIGHT-SAME: // DARK-SAME: // CHECK-SAME: Node 1 (0x1) - State Unspecified // CHECK-SAME:
Node %d (%s) - ' 'State %s
+// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME:
+// CHECK-SAME: main.cpp:8:9: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: ImplicitCastExpr (LValueToRValue) +// CHECK-SAME: +// CHECK-SAME: S5 +// CHECK-SAME: PreStmt +// CHECK-SAME: y
+// CHECK-SAME: +// CHECK-SAME: Tag: +// CHECK-SAME: ExprEngine : Clean Node +// CHECK-SAME:
+Node0x4 [shape=record,label= + "{ + { "node_id": 4, "pointer": "0x4", "has_report": false, "is_sink": false, + "program_state": null, "program_points": [ + { + "kind": "Statement", + "stmt_kind": "ImplicitCastExpr", + "cast_kind": "LValueToRValue", + "stmt_point_kind": "PreStmt", + "stmt_id": 5, + "pointer": "0x6", + "pretty": "y", + "location": { + "file": "main.cpp", + "line": 8, + "column": 9 + }, + "tag": "ExprEngine : Clean Node" + } + ]} +\l}"]; diff --git a/clang/utils/analyzer/exploded-graph-rewriter.py b/clang/utils/analyzer/exploded-graph-rewriter.py index 05b01b3f9502c..77da7392e3605 100755 --- a/clang/utils/analyzer/exploded-graph-rewriter.py +++ b/clang/utils/analyzer/exploded-graph-rewriter.py @@ -73,6 +73,8 @@ def __init__(self, json_pp): elif self.kind == 'Statement': logging.debug(json_pp) self.stmt_kind = json_pp['stmt_kind'] + self.cast_kind = json_pp['cast_kind'] \ + if 'cast_kind' in json_pp else None self.stmt_point_kind = json_pp['stmt_point_kind'] self.stmt_id = json_pp['stmt_id'] self.pointer = json_pp['pointer'] @@ -497,7 +499,9 @@ def visit_program_point(self, p): 'S%s' '%s' '%s' - % (self._make_sloc(p.loc), color, p.stmt_kind, + % (self._make_sloc(p.loc), color, + '%s (%s)' % (p.stmt_kind, p.cast_kind) + if p.cast_kind is not None else p.stmt_kind, p.stmt_id, stmt_color, p.stmt_point_kind, self._short_pretty(p.pretty) if not skip_pretty else '')) From e9b4862beabafb8f849201844a690e937ce59048 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Thu, 17 Oct 2019 23:10:09 +0000 Subject: [PATCH 171/181] [analyzer] Assign truly stable identifiers to exploded nodes. ExplodedGraph nodes will now have a numeric identifier stored in them which will keep track of the order in which the nodes were created and it will be fully deterministic both accross runs and across machines. This is extremely useful for debugging as it allows reliably setting conditional breakpoints by node IDs. llvm-svn: 375186 (cherry picked from commit 14e9eb3d7caed026a805033a9ce7b9e70d18bf04) --- .../Core/PathSensitive/ExplodedGraph.h | 11 +++-- clang/lib/StaticAnalyzer/Core/BugReporter.cpp | 3 +- .../lib/StaticAnalyzer/Core/ExplodedGraph.cpp | 14 +++--- clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 18 +++----- clang/test/Analysis/dump_egraph.c | 4 +- .../checker_messages.dot | 9 +++- .../checker_messages_diff.dot | 44 ++++++++++++------- .../exploded-graph-rewriter/constraints.dot | 14 +++--- .../constraints_diff.dot | 42 +++++++++++------- .../Analysis/exploded-graph-rewriter/edge.dot | 20 +++++++-- .../exploded-graph-rewriter/environment.dot | 11 ++++- .../environment_diff.dot | 42 +++++++++++------- .../exploded-graph-rewriter/node_labels.dot | 43 ++++++++++++------ .../program_points.dot | 42 ++++++++++++------ .../exploded-graph-rewriter/store.dot | 15 ++++--- .../exploded-graph-rewriter/store_diff.dot | 37 +++++++++++----- .../exploded-graph-rewriter/topology.dot | 14 +++--- .../exploded-graph-rewriter/trimmers.dot | 40 +++++++++++++---- .../utils/analyzer/exploded-graph-rewriter.py | 43 ++++++++++-------- 19 files changed, 305 insertions(+), 161 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h index c459b2aa299ec..2c9b0e5b9968e 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h @@ -131,10 +131,12 @@ class ExplodedNode : public llvm::FoldingSetNode { /// Succs - The successors of this node. NodeGroup Succs; + int64_t Id; + public: explicit ExplodedNode(const ProgramPoint &loc, ProgramStateRef state, - bool IsSink) - : Location(loc), State(std::move(state)), Succs(IsSink) { + int64_t Id, bool IsSink) + : Location(loc), State(std::move(state)), Succs(IsSink), Id(Id) { assert(isSink() == IsSink); } @@ -258,7 +260,7 @@ class ExplodedNode : public llvm::FoldingSetNode { } const_succ_range succs() const { return {Succs.begin(), Succs.end()}; } - int64_t getID(ExplodedGraph *G) const; + int64_t getID() const { return Id; } /// The node is trivial if it has only one successor, only one predecessor, /// it's predecessor has only one successor, @@ -324,7 +326,7 @@ class ExplodedGraph { BumpVectorContext BVC; /// NumNodes - The number of nodes in the graph. - unsigned NumNodes = 0; + int64_t NumNodes = 0; /// A list of recently allocated nodes that can potentially be recycled. NodeVector ChangedNodes; @@ -358,6 +360,7 @@ class ExplodedGraph { /// ExplodedGraph for further processing. ExplodedNode *createUncachedNode(const ProgramPoint &L, ProgramStateRef State, + int64_t Id, bool IsSink = false); std::unique_ptr MakeEmptyGraph() const { diff --git a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp index 1cfd3a8753041..9d7e942abe017 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp @@ -2565,7 +2565,8 @@ BugPathInfo *BugPathGetter::getNextBugPath() { // Create the equivalent node in the new graph with the same state // and location. ExplodedNode *NewN = GNew->createUncachedNode( - OrigN->getLocation(), OrigN->getState(), OrigN->isSink()); + OrigN->getLocation(), OrigN->getState(), + OrigN->getID(), OrigN->isSink()); // Link up the new node with the previous node. if (Succ) diff --git a/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp b/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp index a8fa7ad2d98c5..c4838492271cd 100644 --- a/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp @@ -283,10 +283,6 @@ ExplodedNode * const *ExplodedNode::NodeGroup::end() const { return Storage.getAddrOfPtr1() + 1; } -int64_t ExplodedNode::getID(ExplodedGraph *G) const { - return G->getAllocator().identifyKnownAlignedObject(this); -} - bool ExplodedNode::isTrivial() const { return pred_size() == 1 && succ_size() == 1 && getFirstPred()->getState()->getID() == getState()->getID() && @@ -417,14 +413,14 @@ ExplodedNode *ExplodedGraph::getNode(const ProgramPoint &L, V = (NodeTy*) getAllocator().Allocate(); } - new (V) NodeTy(L, State, IsSink); + ++NumNodes; + new (V) NodeTy(L, State, NumNodes, IsSink); if (ReclaimNodeInterval) ChangedNodes.push_back(V); // Insert the node into the node set and return it. Nodes.InsertNode(V, InsertPos); - ++NumNodes; if (IsNew) *IsNew = true; } @@ -436,9 +432,10 @@ ExplodedNode *ExplodedGraph::getNode(const ProgramPoint &L, ExplodedNode *ExplodedGraph::createUncachedNode(const ProgramPoint &L, ProgramStateRef State, + int64_t Id, bool IsSink) { NodeTy *V = (NodeTy *) getAllocator().Allocate(); - new (V) NodeTy(L, State, IsSink); + new (V) NodeTy(L, State, Id, IsSink); return V; } @@ -498,7 +495,8 @@ ExplodedGraph::trim(ArrayRef Sinks, // Create the corresponding node in the new graph and record the mapping // from the old node to the new node. - ExplodedNode *NewN = G->createUncachedNode(N->getLocation(), N->State, N->isSink()); + ExplodedNode *NewN = G->createUncachedNode(N->getLocation(), N->State, + N->getID(), N->isSink()); Pass2[N] = NewN; // Also record the reverse mapping from the new node to the old node. diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp index e25401e240a2c..c64a924484ba7 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -3057,16 +3057,7 @@ struct DOTGraphTraits : public DefaultDOTGraphTraits { const unsigned int Space = 1; ProgramStateRef State = N->getState(); - auto Noop = [](const ExplodedNode*){}; - bool HasReport = traverseHiddenNodes( - N, Noop, Noop, &nodeHasBugReport); - bool IsSink = traverseHiddenNodes( - N, Noop, Noop, [](const ExplodedNode *N) { return N->isSink(); }); - - Out << "{ \"node_id\": " << N->getID(G) << ", \"pointer\": \"" - << (const void *)N << "\", \"state_id\": " << State->getID() - << ", \"has_report\": " << (HasReport ? "true" : "false") - << ", \"is_sink\": " << (IsSink ? "true" : "false") + Out << "{ \"state_id\": " << State->getID() << ",\\l"; Indent(Out, Space, IsDot) << "\"program_points\": [\\l"; @@ -3079,9 +3070,12 @@ struct DOTGraphTraits : public DefaultDOTGraphTraits { OtherNode->getLocation().printJson(Out, /*NL=*/"\\l"); Out << ", \"tag\": "; if (const ProgramPointTag *Tag = OtherNode->getLocation().getTag()) - Out << '\"' << Tag->getTagDescription() << "\" }"; + Out << '\"' << Tag->getTagDescription() << "\""; else - Out << "null }"; + Out << "null"; + Out << ", \"node_id\": " << OtherNode->getID() << + ", \"is_sink\": " << OtherNode->isSink() << + ", \"has_report\": " << nodeHasBugReport(OtherNode) << " }"; }, // Adds a comma and a new-line between each program point. [&](const ExplodedNode *) { Out << ",\\l"; }, diff --git a/clang/test/Analysis/dump_egraph.c b/clang/test/Analysis/dump_egraph.c index 4b7352ff1840e..15e124edb1656 100644 --- a/clang/test/Analysis/dump_egraph.c +++ b/clang/test/Analysis/dump_egraph.c @@ -16,11 +16,11 @@ int foo() { return *x + *y; } -// CHECK: \"program_points\": [\l    \{ \"kind\": \"Edge\", \"src_id\": 2, \"dst_id\": 1, \"terminator\": null, \"term_kind\": null, \"tag\": null \}\l  ],\l  \"program_state\": null +// CHECK: \"program_points\": [\l    \{ \"kind\": \"Edge\", \"src_id\": 2, \"dst_id\": 1, \"terminator\": null, \"term_kind\": null, \"tag\": null, \"node_id\": 1, \"is_sink\":0, \"has_report\": 0 \}\l  ],\l  \"program_state\": null // CHECK: \"program_points\": [\l    \{ \"kind\": \"BlockEntrance\", \"block_id\": 1 // CHECK: \"pretty\": \"*x\", \"location\": \{ \"line\": 16, \"column\": 10, \"file\": \"{{(.+)}}dump_egraph.c\" \} -// CHECK: \"has_report\": true +// CHECK: \"has_report\": 1 diff --git a/clang/test/Analysis/exploded-graph-rewriter/checker_messages.dot b/clang/test/Analysis/exploded-graph-rewriter/checker_messages.dot index 84185db5af617..2d054a8d48bdb 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/checker_messages.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/checker_messages.dot @@ -14,7 +14,14 @@ Node0x1 [shape=record,label= "has_report": false, "is_sink": false, "state_id": 2, - "program_points": [], + "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ], "program_state": { "store": null, "constraints": null, diff --git a/clang/test/Analysis/exploded-graph-rewriter/checker_messages_diff.dot b/clang/test/Analysis/exploded-graph-rewriter/checker_messages_diff.dot index 2f0bcbd4e5f2f..898f79600b8ae 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/checker_messages_diff.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/checker_messages_diff.dot @@ -5,12 +5,15 @@ Node0x1 [shape=record,label= "{ - { "node_id": 1, - "pointer": "0x1", - "has_report": false, - "is_sink": false, - "state_id": 2, - "program_points": [], + { "state_id": 2, + "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ], "program_state": { "environment": null, "store": null, @@ -59,12 +62,16 @@ Node0x1 -> Node0x4; // CHECK-SAME: Node0x4 [shape=record,label= "{ - { "node_id": 4, - "pointer": "0x4", - "has_report": false, - "is_sink": false, + { "state_id": 5, - "program_points": [], + "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ], "program_state": { "environment": null, "store": null, @@ -88,12 +95,15 @@ Node0x4 -> Node0x6; Node0x6 [shape=record,label= "{ - { "node_id": 6, - "pointer": "0x6", - "has_report": false, - "is_sink": false, - "state_id": 7, - "program_points": [], + { "state_id": 7, + "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ], "program_state": null } \l}"]; diff --git a/clang/test/Analysis/exploded-graph-rewriter/constraints.dot b/clang/test/Analysis/exploded-graph-rewriter/constraints.dot index 075df98ce942e..f5ebcf1a606e2 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/constraints.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/constraints.dot @@ -12,12 +12,16 @@ // CHECK-SAME: Node0x1 [shape=record,label= "{ - { "node_id": 1, - "pointer": "0x1", - "has_report": false, - "is_sink": false, + { "state_id": 2, - "program_points": [], + "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ], "program_state": { "store": null, "environment": null, diff --git a/clang/test/Analysis/exploded-graph-rewriter/constraints_diff.dot b/clang/test/Analysis/exploded-graph-rewriter/constraints_diff.dot index 00b2f1456f313..53a87aa66755b 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/constraints_diff.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/constraints_diff.dot @@ -5,12 +5,16 @@ Node0x1 [shape=record,label= "{ - { "node_id": 1, - "pointer": "0x1", - "has_report": false, - "is_sink": false, + { "state_id": 2, - "program_points": [], + "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ], "program_state": { "store": null, "environment": null, @@ -39,12 +43,16 @@ Node0x1 -> Node0x3; // CHECK-SAME: Node0x3 [shape=record,label= "{ - { "node_id": 3, - "pointer": "0x3", - "has_report": false, - "is_sink": false, + { "state_id": 4, - "program_points": [], + "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ], "program_state": { "store": null, "environment": null, @@ -62,12 +70,16 @@ Node0x3 -> Node0x5; Node0x5 [shape=record,label= "{ - { "node_id": 5, - "pointer": "0x5", - "has_report": false, - "is_sink": false, + { "state_id": 6, - "program_points": [], + "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ], "program_state": { "store": null, "environment": null, diff --git a/clang/test/Analysis/exploded-graph-rewriter/edge.dot b/clang/test/Analysis/exploded-graph-rewriter/edge.dot index 15e55612b8037..3923f1f8ee5b5 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/edge.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/edge.dot @@ -5,13 +5,25 @@ // UNSUPPORTED: system-windows Node0x1 [shape=record,label= - "{{ "node_id": 1, "pointer": "0x1", "has_report": false, "is_sink": false, - "program_state": null, "program_points": []}\l}"]; + "{{ "program_state": null, "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ]}\l}"]; // LIGHT: Node0x1 -> Node0x2; // DARK: Node0x1 -> Node0x2 [color="white"]; Node0x1 -> Node0x2; Node0x2 [shape=record,label= - "{{ "node_id": 2, "pointer": "0x2", "has_report": false, "is_sink": false, - "program_state": null, "program_points": []}\l}"]; + "{{ "program_state": null, "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ]}\l}"]; diff --git a/clang/test/Analysis/exploded-graph-rewriter/environment.dot b/clang/test/Analysis/exploded-graph-rewriter/environment.dot index 399484a7eece2..733aae3036c88 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/environment.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/environment.dot @@ -11,7 +11,7 @@ // CHECK-SAME: // CHECK-SAME: // CHECK-SAME: foo -// CHECK-SAME: (environment.cpp:4:6 +// CHECK-SAME: (environment.cpp:4:6 // CHECK-SAME: // CHECK-SAME: (spelling at environment.h:7:8) // CHECK-SAME: ) @@ -36,7 +36,14 @@ Node0x1 [shape=record,label= "has_report": false, "is_sink": false, "state_id": 2, - "program_points": [], + "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ], "program_state": { "store": null, "constraints": null, diff --git a/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot b/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot index 475247bb98917..05e8d4eef50fd 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot @@ -6,12 +6,16 @@ // No diffs on the first node, nothing to check. Node0x1 [shape=record,label= "{ - { "node_id": 1, - "pointer": "0x1", - "has_report": false, - "is_sink": false, + { "state_id": 2, - "program_points": [], + "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ], "program_state": { "store": null, "constraints": null, @@ -57,12 +61,16 @@ Node0x1 -> Node0x6; // CHECK-SAME: Node0x6 [shape=record,label= "{ - { "node_id": 6, - "pointer": "0x6", - "has_report": false, - "is_sink": false, + { "state_id": 7, - "program_points": [], + "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ], "program_state": { "store": null, "constraints": null, @@ -102,12 +110,16 @@ Node0x6 -> Node0x9; // CHECK-SAME: Node0x9 [shape=record,label= "{ - { "node_id": 9, - "pointer": "0x9", - "has_report": false, - "is_sink": false, + { "state_id": 7, - "program_points": [], + "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ], "program_state": { "store": null, "constraints": null, diff --git a/clang/test/Analysis/exploded-graph-rewriter/node_labels.dot b/clang/test/Analysis/exploded-graph-rewriter/node_labels.dot index a434cd23071ee..a3d7420fed834 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/node_labels.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/node_labels.dot @@ -15,30 +15,47 @@ // CHECK-SAME: // LIGHT-SAME: // DARK-SAME: -// CHECK-SAME: Node 1 (0x1) - State Unspecified +// CHECK-SAME: State Unspecified // CHECK-SAME: // CHECK-SAME: Node0x1 [shape=record,label= "{ { "node_id": 1, "pointer": "0x1", "has_report": false, "is_sink": false, "program_state": null, - "program_points": [] + "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ] } \l}"]; // CHECK: Node0x2 [ -// CHECK-SAME: -// COLOR-SAME: Bug Report Attached -// GRAY-SAME: Bug Report Attached -// CHECK-SAME: -// CHECK-SAME: -// COLOR-SAME: Sink Node -// GRAY-SAME: Sink Node -// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// COLOR-SAME: Bug Report Attached +// GRAY-SAME: Bug Report Attached +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// COLOR-SAME: Sink Node +// GRAY-SAME: Sink Node +// CHECK-SAME: +// CHECK-SAME: Node0x2 [shape=record,label= "{ - { "node_id": 2, "pointer": "0x2", "has_report": true, "is_sink": true, - "program_state": null, - "program_points": [] + { "program_state": null, + "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 2, + "has_report": 1, "is_sink": 1 + } + ] } \l}"]; diff --git a/clang/test/Analysis/exploded-graph-rewriter/program_points.dot b/clang/test/Analysis/exploded-graph-rewriter/program_points.dot index c27c230ebfb37..c9492757c3aff 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/program_points.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/program_points.dot @@ -28,7 +28,7 @@ // CHECK-SAME: Node0x1 [shape=record,label= "{ - { "node_id": 1, "pointer": "0x1", "has_report": false, "is_sink": false, + { "program_state": null, "program_points": [ { "kind": "Edge", @@ -36,14 +36,20 @@ Node0x1 [shape=record,label= "dst_id": 1, "terminator": null, "term_kind": null, - "tag": null + "tag": null, + "node_id": 1, + "has_report": 0, + "is_sink": 0 }, { "kind": "BlockEntrance", "block_id": 1, "terminator": null, "term_kind": null, - "tag": null + "tag": null, + "node_id": 2, + "has_report": 0, + "is_sink": 0 } ]} \l}"]; @@ -72,10 +78,9 @@ Node0x1 [shape=record,label= // CHECK-SAME: // CHECK-SAME: // CHECK-SAME: -Node0x2 [shape=record,label= +Node0x3 [shape=record,label= "{ - { "node_id": 2, "pointer": "0x2", "has_report": false, "is_sink": false, - "program_state": null, "program_points": [ + { "program_state": null, "program_points": [ { "kind": "Statement", "stmt_kind": "DeclRefExpr", @@ -88,7 +93,11 @@ Node0x2 [shape=record,label= "line": 4, "column": 5 }, - "tag": "ExprEngine : Clean Node" + "tag": "ExprEngine : Clean Node", + "node_id": 3, + "pointer": "0x3", + "has_report": 0, + "is_sink": 0 } ]} \l}"]; @@ -97,9 +106,9 @@ Node0x2 [shape=record,label= // CHECK-NEXT: Program point: // CHECK-SAME: \{ ... \} -Node0x3 [shape=record,label= +Node0x4 [shape=record,label= "{ - { "node_id": 3, "pointer": "0x3", "has_report": false, "is_sink": false, + { "program_state": null, "program_points": [ { "kind": "Statement", @@ -112,7 +121,10 @@ Node0x3 [shape=record,label= "line": 7, "column": 8 }, - "tag": "ExprEngine : Clean Node" + "tag": "ExprEngine : Clean Node", + "node_id": 4, + "has_report": 0, + "is_sink": 0 } ]} \l}"]; @@ -143,10 +155,9 @@ Node0x3 [shape=record,label= // CHECK-SAME: // CHECK-SAME: // CHECK-SAME: -Node0x4 [shape=record,label= +Node0x5 [shape=record,label= "{ - { "node_id": 4, "pointer": "0x4", "has_report": false, "is_sink": false, - "program_state": null, "program_points": [ + { "program_state": null, "program_points": [ { "kind": "Statement", "stmt_kind": "ImplicitCastExpr", @@ -160,7 +171,10 @@ Node0x4 [shape=record,label= "line": 8, "column": 9 }, - "tag": "ExprEngine : Clean Node" + "tag": "ExprEngine : Clean Node", + "node_id": 5, + "has_report": 0, + "is_sink": 0 } ]} \l}"]; diff --git a/clang/test/Analysis/exploded-graph-rewriter/store.dot b/clang/test/Analysis/exploded-graph-rewriter/store.dot index 7267dd43e8984..c92901cbd7a34 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/store.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/store.dot @@ -23,12 +23,15 @@ // CHECK-SAME: Node0x1 [shape=record,label= "{ - { "node_id": 1, - "pointer": "0x1", - "has_report": false, - "is_sink": false, - "state_id": 2, - "program_points": [], + { "state_id": 2, + "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ], "program_state": { "environment": null, "constraints": null, diff --git a/clang/test/Analysis/exploded-graph-rewriter/store_diff.dot b/clang/test/Analysis/exploded-graph-rewriter/store_diff.dot index 94d1d8d9f1f5b..8dd5fb44cd747 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/store_diff.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/store_diff.dot @@ -10,7 +10,14 @@ Node0x1 [shape=record,label= "has_report": false, "is_sink": false, "state_id": 2, - "program_points": [], + "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ], "program_state": { "environment": null, "constraints": null, @@ -55,12 +62,16 @@ Node0x1 -> Node0x4; // CHECK-SAME: Node0x4 [shape=record,label= "{ - { "node_id": 4, - "pointer": "0x4", - "has_report": false, - "is_sink": false, + { "state_id": 5, - "program_points": [], + "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ], "program_state": { "environment": null, "constraints": null, @@ -91,12 +102,16 @@ Node0x4 -> Node0x6; Node0x6 [shape=record,label= "{ - { "node_id": 6, - "pointer": "0x6", - "has_report": false, - "is_sink": false, + { "state_id": 7, - "program_points": [], + "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ], "program_state": null } \l}"]; diff --git a/clang/test/Analysis/exploded-graph-rewriter/topology.dot b/clang/test/Analysis/exploded-graph-rewriter/topology.dot index fa1b10f68b94f..b85115ebeac72 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/topology.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/topology.dot @@ -12,12 +12,16 @@ // TOPOLOGY-NOT: Checker State Node0x1 [shape=record,label= "{ - { "node_id": 1, - "pointer": "0x1", - "has_report": false, - "is_sink": false, + { "state_id": 2, - "program_points": [], + "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ], "program_state": { "environment": null, "constraints": null, diff --git a/clang/test/Analysis/exploded-graph-rewriter/trimmers.dot b/clang/test/Analysis/exploded-graph-rewriter/trimmers.dot index 8bdef649e0df6..2c441e02c72d9 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/trimmers.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/trimmers.dot @@ -17,20 +17,44 @@ // UNSUPPORTED: system-windows Node0x1 [shape=record,label= - "{{ "node_id": 1, "pointer": "0x1", "has_report": false, "is_sink": false, - "program_state": null, "program_points": []}\l}"]; + "{{ "program_state": null, "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ]}\l}"]; Node0x2 [shape=record,label= - "{{ "node_id": 2, "pointer": "0x2", "has_report": false, "is_sink": false, - "program_state": null, "program_points": []}\l}"]; + "{{ "program_state": null, "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 2, + "has_report": 0, "is_sink": 0 + } + ]}\l}"]; Node0x3 [shape=record,label= - "{{ "node_id": 3, "pointer": "0x3", "has_report": false, "is_sink": false, - "program_state": null, "program_points": []}\l}"]; + "{{ "program_state": null, "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 3, + "has_report": 0, "is_sink": 0 + } + ]}\l}"]; Node0x4 [shape=record,label= - "{{ "node_id": 4, "pointer": "0x4", "has_report": false, "is_sink": false, - "program_state": null, "program_points": []}\l}"]; + "{{ "program_state": null, "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 4, + "has_report": 0, "is_sink": 0 + } + ]}\l}"]; Node0x1 -> Node0x2; Node0x1 -> Node0x3; diff --git a/clang/utils/analyzer/exploded-graph-rewriter.py b/clang/utils/analyzer/exploded-graph-rewriter.py index 77da7392e3605..79222bdbe95dd 100755 --- a/clang/utils/analyzer/exploded-graph-rewriter.py +++ b/clang/utils/analyzer/exploded-graph-rewriter.py @@ -67,6 +67,9 @@ def __init__(self, json_pp): super(ProgramPoint, self).__init__() self.kind = json_pp['kind'] self.tag = json_pp['tag'] + self.node_id = json_pp['node_id'] + self.is_sink = bool(json_pp['is_sink']) + self.has_report = bool(json_pp['has_report']) if self.kind == 'Edge': self.src_id = json_pp['src_id'] self.dst_id = json_pp['dst_id'] @@ -309,11 +312,9 @@ def __init__(self): def construct(self, node_id, json_node): logging.debug('Adding ' + node_id) - self.node_id = json_node['node_id'] - self.ptr = json_node['pointer'] - self.has_report = json_node['has_report'] - self.is_sink = json_node['is_sink'] + self.ptr = node_id[4:] self.points = [ProgramPoint(p) for p in json_node['program_points']] + self.node_id = self.points[-1].node_id self.state = ProgramState(json_node['state_id'], json_node['program_state']) \ if json_node['program_state'] is not None else None @@ -488,12 +489,14 @@ def visit_program_point(self, p): else: color = 'forestgreen' + self._dump('%s.' % p.node_id) + if p.kind == 'Statement': # This avoids pretty-printing huge statements such as CompoundStmt. # Such statements show up only at [Pre|Post]StmtPurgeDeadSymbols skip_pretty = 'PurgeDeadSymbols' in p.stmt_point_kind stmt_color = 'cyan3' - self._dump('%s:' + self._dump('%s:' '' '%s ' 'S%s' @@ -506,30 +509,41 @@ def visit_program_point(self, p): self._short_pretty(p.pretty) if not skip_pretty else '')) elif p.kind == 'Edge': - self._dump('' + self._dump('' '' '%s' '[B%d] -\\> [B%d]' % (color, 'BlockEdge', p.src_id, p.dst_id)) elif p.kind == 'BlockEntrance': - self._dump('' + self._dump('' '' '%s' '[B%d]' % (color, p.kind, p.block_id)) else: # TODO: Print more stuff for other kinds of points. - self._dump('' + self._dump('' '' '%s' % (color, p.kind)) if p.tag is not None: - self._dump('' + self._dump('' '' 'Tag: ' '%s' % p.tag) + if p.has_report: + self._dump('' + '' + 'Bug Report Attached' + '') + if p.is_sink: + self._dump('' + '' + 'Sink Node' + '') + def visit_environment(self, e, prev_e=None): self._dump('') @@ -786,17 +800,10 @@ def visit_node(self, node): self._dump('color="white",fontcolor="gray80",') self._dump('label=<
') - self._dump('' + self._dump('' % ("gray20" if self._dark_mode else "gray70", - node.node_id, node.ptr, node.state.state_id + node.state.state_id if node.state is not None else 'Unspecified')) - if node.has_report: - self._dump('') - if node.is_sink: - self._dump('') if not self._topo_mode: self._dump(' // LIGHT-SAME: // CHECK-SAME: Node0x1 [shape=record,label= "{ - { "node_id": 1, "pointer": "0x1", "has_report": false, "is_sink": false, - "program_state": null, + { "state_id": 0, "program_state": null, "program_points": [ { "kind": "BlockEntrance", "block_id": 1, @@ -48,7 +47,7 @@ Node0x1 [shape=record,label= // CHECK-SAME: Node0x2 [shape=record,label= "{ - { "program_state": null, + { "state_id": 0, "program_state": null, "program_points": [ { "kind": "BlockEntrance", "block_id": 1, diff --git a/clang/test/Analysis/exploded-graph-rewriter/program_points.dot b/clang/test/Analysis/exploded-graph-rewriter/program_points.dot index c9492757c3aff..bee48d0f49f8c 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/program_points.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/program_points.dot @@ -29,7 +29,7 @@ Node0x1 [shape=record,label= "{ { - "program_state": null, "program_points": [ + "state_id": 0, "program_state": null, "program_points": [ { "kind": "Edge", "src_id": 0, @@ -80,7 +80,7 @@ Node0x1 [shape=record,label= // CHECK-SAME:
Node %d (%s) - ' - 'State %s
State %s
Bug Report Attached' - '
Sink Node' - '
') if len(node.points) > 1: From 0ebcbd164eb6587c592c29d9decf1eeb82c194b4 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Thu, 17 Oct 2019 23:27:35 +0000 Subject: [PATCH 172/181] [analyzer] exploded-graph-rewriter: Fix typo in r375186. Unbreaks tests. llvm-svn: 375189 (cherry picked from commit 8b3ef1e45b06bfdc01cbc5e79df5c52ede5c88db) --- clang/test/Analysis/dump_egraph.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/test/Analysis/dump_egraph.c b/clang/test/Analysis/dump_egraph.c index 15e124edb1656..1672b79399256 100644 --- a/clang/test/Analysis/dump_egraph.c +++ b/clang/test/Analysis/dump_egraph.c @@ -16,7 +16,7 @@ int foo() { return *x + *y; } -// CHECK: \"program_points\": [\l    \{ \"kind\": \"Edge\", \"src_id\": 2, \"dst_id\": 1, \"terminator\": null, \"term_kind\": null, \"tag\": null, \"node_id\": 1, \"is_sink\":0, \"has_report\": 0 \}\l  ],\l  \"program_state\": null +// CHECK: \"program_points\": [\l    \{ \"kind\": \"Edge\", \"src_id\": 2, \"dst_id\": 1, \"terminator\": null, \"term_kind\": null, \"tag\": null, \"node_id\": 1, \"is_sink\": 0, \"has_report\": 0 \}\l  ],\l  \"program_state\": null // CHECK: \"program_points\": [\l    \{ \"kind\": \"BlockEntrance\", \"block_id\": 1 From 00e99fc240ba76996ed7592a2d84fccc36f413fc Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Fri, 18 Oct 2019 20:15:29 +0000 Subject: [PATCH 173/181] [analyzer] Fix hidden node traversal in exploded graph dumps. The joined nodes now actually have the same state. That was intended from the start but the original implementation turned out to be buggy. Differential Revision: https://reviews.llvm.org/D69150 llvm-svn: 375278 (cherry picked from commit 7a17f197093a6872d910b7486cfd43a53aba220b) --- clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 16 ++++------- clang/test/Analysis/dump_egraph.c | 28 +++++++++++++++++--- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp index c64a924484ba7..6fa92913cc5cb 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -3025,22 +3025,16 @@ struct DOTGraphTraits : public DefaultDOTGraphTraits { llvm::function_ref PreCallback, llvm::function_ref PostCallback, llvm::function_ref Stop) { - const ExplodedNode *FirstHiddenNode = N; - while (FirstHiddenNode->pred_size() == 1 && - isNodeHidden(*FirstHiddenNode->pred_begin())) { - FirstHiddenNode = *FirstHiddenNode->pred_begin(); - } - const ExplodedNode *OtherNode = FirstHiddenNode; while (true) { - PreCallback(OtherNode); - if (Stop(OtherNode)) + PreCallback(N); + if (Stop(N)) return true; - if (OtherNode == N) + if (N->succ_size() != 1 || !isNodeHidden(N->getFirstSucc())) break; - PostCallback(OtherNode); + PostCallback(N); - OtherNode = *OtherNode->succ_begin(); + N = N->getFirstSucc(); } return false; } diff --git a/clang/test/Analysis/dump_egraph.c b/clang/test/Analysis/dump_egraph.c index 1672b79399256..9f589ba17af5d 100644 --- a/clang/test/Analysis/dump_egraph.c +++ b/clang/test/Analysis/dump_egraph.c @@ -16,10 +16,30 @@ int foo() { return *x + *y; } -// CHECK: \"program_points\": [\l    \{ \"kind\": \"Edge\", \"src_id\": 2, \"dst_id\": 1, \"terminator\": null, \"term_kind\": null, \"tag\": null, \"node_id\": 1, \"is_sink\": 0, \"has_report\": 0 \}\l  ],\l  \"program_state\": null - -// CHECK: \"program_points\": [\l    \{ \"kind\": \"BlockEntrance\", \"block_id\": 1 - +// CHECK: \"program_points\": [\l +// CHECK-SAME: \{ \"kind\": \"Edge\", \"src_id\": 2, \"dst_id\": 1, +// CHECK-SAME: \"terminator\": null, \"term_kind\": null, \"tag\": null, +// CHECK-SAME: \"node_id\": 1, \"is_sink\": 0, \"has_report\": 0 +// CHECK-SAME: \}, +// CHECK-SAME: \{ \"kind\": \"BlockEntrance\", \"block_id\": 1, \"tag\": null, +// CHECK-SAME: \"node_id\": 2, \"is_sink\": 0, \"has_report\": 0 +// CHECK-SAME: \}, +// CHECK-SAME: \{ \"kind\": \"Statement\", \"stmt_kind\": \"IntegerLiteral\", +// CHECK-SAME: \"stmt_id\": 597, \"pointer\": \"0x{{[0-9a-f]*}}\", +// CHECK-SAME: \"pretty\": \"0\", \"location\": \{ +// CHECK-SAME: \"line\": 15, \"column\": 12, \"file\": +// CHECK-SAME: \}, \"stmt_point_kind\": \"PreStmtPurgeDeadSymbols\", +// CHECK-SAME: \"tag\": \"ExprEngine : Clean Node\", \"node_id\": 3, +// CHECK-SAME: \"is_sink\": 0, \"has_report\": 0 +// CHECK-SAME: \}, +// CHECK-SAME: \{ \"kind\": \"Statement\", \"stmt_kind\": \"IntegerLiteral\", +// CHECK-SAME: \"stmt_id\": 597, \"pointer\": \"0x{{[0-9a-f]*}}\", +// CHECK-SAME: \"pretty\": \"0\", \"location\": \{ +// CHECK-SAME: \"line\": 15, \"column\": 12, \"file\": +// CHECK-SAME: \}, \"stmt_point_kind\": \"PostStmt\", \"tag\": null, +// CHECK-SAME: \"node_id\": 4, \"is_sink\": 0, \"has_report\": 0 +// CHECK-SAME: \} +// CHECK-SAME: ] // CHECK: \"pretty\": \"*x\", \"location\": \{ \"line\": 16, \"column\": 10, \"file\": \"{{(.+)}}dump_egraph.c\" \} From 4db5dff0b51d22e8375480352a78726f68ad79f6 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Fri, 18 Oct 2019 20:15:32 +0000 Subject: [PATCH 174/181] [analyzer] exploded-graph-rewriter: Fix dump for state 0. It shouldn't say "unspecified" when the state is specified to be empty. llvm-svn: 375279 (cherry picked from commit d93b810cd673d37598ed05334a662e6878c32812) --- .../test/Analysis/exploded-graph-rewriter/edge.dot | 4 ++-- .../exploded-graph-rewriter/node_labels.dot | 7 +++---- .../exploded-graph-rewriter/program_points.dot | 8 ++++---- .../Analysis/exploded-graph-rewriter/trimmers.dot | 8 ++++---- clang/utils/analyzer/exploded-graph-rewriter.py | 14 ++++++++++++-- 5 files changed, 25 insertions(+), 16 deletions(-) diff --git a/clang/test/Analysis/exploded-graph-rewriter/edge.dot b/clang/test/Analysis/exploded-graph-rewriter/edge.dot index 3923f1f8ee5b5..43d6e3b8faf10 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/edge.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/edge.dot @@ -5,7 +5,7 @@ // UNSUPPORTED: system-windows Node0x1 [shape=record,label= - "{{ "program_state": null, "program_points": [ + "{{ "state_id": 0, "program_state": null, "program_points": [ { "kind": "BlockEntrance", "block_id": 1, "terminator": null, "term_kind": null, @@ -19,7 +19,7 @@ Node0x1 [shape=record,label= Node0x1 -> Node0x2; Node0x2 [shape=record,label= - "{{ "program_state": null, "program_points": [ + "{{ "state_id": 0, "program_state": null, "program_points": [ { "kind": "BlockEntrance", "block_id": 1, "terminator": null, "term_kind": null, diff --git a/clang/test/Analysis/exploded-graph-rewriter/node_labels.dot b/clang/test/Analysis/exploded-graph-rewriter/node_labels.dot index a3d7420fed834..89d5070deedc4 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/node_labels.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/node_labels.dot @@ -15,13 +15,12 @@ // CHECK-SAME:
// DARK-SAME: -// CHECK-SAME: State Unspecified +// CHECK-SAME: State 0 // CHECK-SAME:
Node0x3 [shape=record,label= "{ - { "program_state": null, "program_points": [ + { "state_id": 0, "program_state": null, "program_points": [ { "kind": "Statement", "stmt_kind": "DeclRefExpr", @@ -109,7 +109,7 @@ Node0x3 [shape=record,label= Node0x4 [shape=record,label= "{ { - "program_state": null, "program_points": [ + "state_id": 0, "program_state": null, "program_points": [ { "kind": "Statement", "stmt_kind": "CompoundStmt", @@ -157,7 +157,7 @@ Node0x4 [shape=record,label= // CHECK-SAME: Node0x5 [shape=record,label= "{ - { "program_state": null, "program_points": [ + { "state_id": 0, "program_state": null, "program_points": [ { "kind": "Statement", "stmt_kind": "ImplicitCastExpr", diff --git a/clang/test/Analysis/exploded-graph-rewriter/trimmers.dot b/clang/test/Analysis/exploded-graph-rewriter/trimmers.dot index 2c441e02c72d9..df6270d0ef1e2 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/trimmers.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/trimmers.dot @@ -17,7 +17,7 @@ // UNSUPPORTED: system-windows Node0x1 [shape=record,label= - "{{ "program_state": null, "program_points": [ + "{{ "state_id": 0, "program_state": null, "program_points": [ { "kind": "BlockEntrance", "block_id": 1, "terminator": null, "term_kind": null, @@ -27,7 +27,7 @@ Node0x1 [shape=record,label= ]}\l}"]; Node0x2 [shape=record,label= - "{{ "program_state": null, "program_points": [ + "{{ "state_id": 0, "program_state": null, "program_points": [ { "kind": "BlockEntrance", "block_id": 1, "terminator": null, "term_kind": null, @@ -37,7 +37,7 @@ Node0x2 [shape=record,label= ]}\l}"]; Node0x3 [shape=record,label= - "{{ "program_state": null, "program_points": [ + "{{ "state_id": 0, "program_state": null, "program_points": [ { "kind": "BlockEntrance", "block_id": 1, "terminator": null, "term_kind": null, @@ -47,7 +47,7 @@ Node0x3 [shape=record,label= ]}\l}"]; Node0x4 [shape=record,label= - "{{ "program_state": null, "program_points": [ + "{{ "state_id": 0, "program_state": null, "program_points": [ { "kind": "BlockEntrance", "block_id": 1, "terminator": null, "term_kind": null, diff --git a/clang/utils/analyzer/exploded-graph-rewriter.py b/clang/utils/analyzer/exploded-graph-rewriter.py index 79222bdbe95dd..46c0415826a0d 100755 --- a/clang/utils/analyzer/exploded-graph-rewriter.py +++ b/clang/utils/analyzer/exploded-graph-rewriter.py @@ -273,6 +273,16 @@ def __init__(self, state_id, json_ps): super(ProgramState, self).__init__() logging.debug('Adding ProgramState ' + str(state_id)) + if json_ps is None: + json_ps = { + 'store': None, + 'environment': None, + 'constraints': None, + 'dynamic_types': None, + 'constructing_objects': None, + 'checker_messages': None + } + self.state_id = state_id self.store = Store(json_ps['store']) \ @@ -316,8 +326,8 @@ def construct(self, node_id, json_node): self.points = [ProgramPoint(p) for p in json_node['program_points']] self.node_id = self.points[-1].node_id self.state = ProgramState(json_node['state_id'], - json_node['program_state']) \ - if json_node['program_state'] is not None else None + json_node['program_state'] + if json_node['program_state'] is not None else None); assert self.node_name() == node_id From a528b530860e87e9d2cb8782e24ad3924ccda8c0 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Fri, 18 Oct 2019 20:15:35 +0000 Subject: [PATCH 175/181] [analyzer] Drop the logic for collapsing the state if it's same as in preds. One of the first attempts to reduce the size of the exploded graph dumps was to skip the state dump as long as the state is the same as in all of the predecessor nodes. With all the new facilities in place (node joining, diff dumps), this feature doesn't do much, and when it does, it's more harmful than useful. Let's remove it. llvm-svn: 375280 (cherry picked from commit b3e34e043cae03e56f2a1b23eae1669e1c3be770) --- clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 11 +---------- clang/test/Analysis/dump_egraph.c | 2 ++ 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp index 6fa92913cc5cb..8f1b8e3ed65da 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -3078,16 +3078,7 @@ struct DOTGraphTraits : public DefaultDOTGraphTraits { Out << "\\l"; // Adds a new-line to the last program point. Indent(Out, Space, IsDot) << "],\\l"; - bool SameAsAllPredecessors = - std::all_of(N->pred_begin(), N->pred_end(), [&](const ExplodedNode *P) { - return P->getState() == State; - }); - - if (!SameAsAllPredecessors) { - State->printDOT(Out, N->getLocationContext(), Space); - } else { - Indent(Out, Space, IsDot) << "\"program_state\": null"; - } + State->printDOT(Out, N->getLocationContext(), Space); Out << "\\l}\\l"; return Out.str(); diff --git a/clang/test/Analysis/dump_egraph.c b/clang/test/Analysis/dump_egraph.c index 9f589ba17af5d..6198f66523e3f 100644 --- a/clang/test/Analysis/dump_egraph.c +++ b/clang/test/Analysis/dump_egraph.c @@ -44,3 +44,5 @@ int foo() { // CHECK: \"pretty\": \"*x\", \"location\": \{ \"line\": 16, \"column\": 10, \"file\": \"{{(.+)}}dump_egraph.c\" \} // CHECK: \"has_report\": 1 + +// CHECK-NOT: \"program_state\": null From 048cc7c7868291e1281d148200ecdf64e065d27b Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Fri, 18 Oct 2019 20:15:39 +0000 Subject: [PATCH 176/181] [analyzer] Fix FieldRegion dumps. The '->' thing has always been confusing; the actual operation '->' translates to a pointer dereference together with adding a FieldRegion, but FieldRegion on its own doesn't imply an additional pointer dereference. llvm-svn: 375281 (cherry picked from commit 2b4f6df91775a4f13233a04d3377f8233c7087f6) --- clang/lib/StaticAnalyzer/Core/MemRegion.cpp | 2 +- clang/test/Analysis/dump_egraph.cpp | 2 +- .../initializers_under_construction.cpp | 2 +- clang/test/Analysis/expr-inspection.c | 11 ++++++++++- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Core/MemRegion.cpp b/clang/lib/StaticAnalyzer/Core/MemRegion.cpp index c7b280adcd9be..a10d7e69ad7e7 100644 --- a/clang/lib/StaticAnalyzer/Core/MemRegion.cpp +++ b/clang/lib/StaticAnalyzer/Core/MemRegion.cpp @@ -506,7 +506,7 @@ void ElementRegion::dumpToStream(raw_ostream &os) const { } void FieldRegion::dumpToStream(raw_ostream &os) const { - os << superRegion << "->" << *getDecl(); + os << superRegion << "." << *getDecl(); } void ObjCIvarRegion::dumpToStream(raw_ostream &os) const { diff --git a/clang/test/Analysis/dump_egraph.cpp b/clang/test/Analysis/dump_egraph.cpp index a56f061949270..9b87d1a90fcaa 100644 --- a/clang/test/Analysis/dump_egraph.cpp +++ b/clang/test/Analysis/dump_egraph.cpp @@ -20,7 +20,7 @@ void foo() { // CHECK: \"location_context\": \"#0 Call\", \"calling\": \"foo\", \"location\": null, \"items\": [\l        \{ \"stmt_id\": {{[0-9]+}}, \"kind\": \"construct into local variable\", \"argument_index\": null, \"pretty\": \"T t;\", \"value\": \"&t\" -// CHECK: \"location_context\": \"#0 Call\", \"calling\": \"T::T\", \"location\": \{ \"line\": 16, \"column\": 5, \"file\": \"{{.*}}dump_egraph.cpp\" \}, \"items\": [\l        \{ \"init_id\": {{[0-9]+}}, \"kind\": \"construct into member variable\", \"argument_index\": null, \"pretty\": \"s\", \"value\": \"&t-\>s\" +// CHECK: \"location_context\": \"#0 Call\", \"calling\": \"T::T\", \"location\": \{ \"line\": 16, \"column\": 5, \"file\": \"{{.*}}dump_egraph.cpp\" \}, \"items\": [\l        \{ \"init_id\": {{[0-9]+}}, \"kind\": \"construct into member variable\", \"argument_index\": null, \"pretty\": \"s\", \"value\": \"&t.s\" // CHECK: \"cluster\": \"t\", \"pointer\": \"{{0x[0-9a-f]+}}\", \"items\": [\l        \{ \"kind\": \"Default\", \"offset\": 0, \"value\": \"conj_$2\{int, LC5, no stmt, #1\}\" diff --git a/clang/test/Analysis/exploded-graph-rewriter/initializers_under_construction.cpp b/clang/test/Analysis/exploded-graph-rewriter/initializers_under_construction.cpp index 472627ef61951..96df69f8577ac 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/initializers_under_construction.cpp +++ b/clang/test/Analysis/exploded-graph-rewriter/initializers_under_construction.cpp @@ -20,6 +20,6 @@ struct B { void test() { // CHECK: (construct into member variable) // CHECK-SAME: a - // CHECK-SAME: &b->a + // CHECK-SAME: &b.a B b; } diff --git a/clang/test/Analysis/expr-inspection.c b/clang/test/Analysis/expr-inspection.c index 6d9ea4bb26c1b..7c5d9e5c49d35 100644 --- a/clang/test/Analysis/expr-inspection.c +++ b/clang/test/Analysis/expr-inspection.c @@ -5,6 +5,7 @@ // Self-tests for the debug.ExprInspection checker. void clang_analyzer_dump(int x); +void clang_analyzer_dump_pointer(int *p); void clang_analyzer_printState(); void clang_analyzer_numTimesReached(); @@ -30,7 +31,7 @@ void foo(int x) { // CHECK-NEXT: ]} // CHECK-NEXT: ]}, // CHECK-NEXT: "environment": { "pointer": "{{0x[0-9a-f]+}}", "items": [ -// CHECK-NEXT: { "lctx_id": 1, "location_context": "#0 Call", "calling": "foo", "location": null, "items": [ +// CHECK-NEXT: { "lctx_id": {{[0-9]+}}, "location_context": "#0 Call", "calling": "foo", "location": null, "items": [ // CHECK-NEXT: { "stmt_id": {{[0-9]+}}, "pretty": "clang_analyzer_printState", "value": "&code{clang_analyzer_printState}" } // CHECK-NEXT: ]} // CHECK-NEXT: ]}, @@ -43,3 +44,11 @@ void foo(int x) { // CHECK-NEXT: "checker_messages": null // CHECK-NEXT: } +struct S { + int x, y; +}; + +void test_field_dumps(struct S s, struct S *p) { + clang_analyzer_dump_pointer(&s.x); // expected-warning{{&s.x}} + clang_analyzer_dump_pointer(&p->x); // expected-warning{{&SymRegion{reg_$0}.x}} +} From 8e66c8bbd47b4f8960af949568cef9fa1829425b Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Fri, 18 Oct 2019 20:15:41 +0000 Subject: [PATCH 177/181] [analyzer] exploded-graph-rewriter: Rename Environment to Expressions. It's less confusing for newcomers. llvm-svn: 375282 (cherry picked from commit c6921379f55ee566fb62ba5aa47b217cf7c5d960) --- clang/test/Analysis/exploded-graph-rewriter/environment.dot | 2 +- clang/test/Analysis/exploded-graph-rewriter/escapes.c | 2 +- clang/utils/analyzer/exploded-graph-rewriter.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/clang/test/Analysis/exploded-graph-rewriter/environment.dot b/clang/test/Analysis/exploded-graph-rewriter/environment.dot index 733aae3036c88..7b8fc05d3bb4a 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/environment.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/environment.dot @@ -3,7 +3,7 @@ // FIXME: Substitution doesn't seem to work on Windows. // UNSUPPORTED: system-windows -// CHECK: Environment: +// CHECK: Expressions: // CHECK-SAME: // CHECK-SAME: // CHECK-SAME: // CHECK-SAME: // CHECK-SAME: - // CHECK: + // CHECK: // CHECK-SAME: // CHECK-SAME: const char *const foo = "foo"; diff --git a/clang/utils/analyzer/exploded-graph-rewriter.py b/clang/utils/analyzer/exploded-graph-rewriter.py index 46c0415826a0d..79055b433e8de 100755 --- a/clang/utils/analyzer/exploded-graph-rewriter.py +++ b/clang/utils/analyzer/exploded-graph-rewriter.py @@ -792,7 +792,7 @@ def visit_checker_messages_in_state(self, s, prev_s=None): def visit_state(self, s, prev_s): self.visit_store_in_state(s, prev_s) - self.visit_environment_in_state('environment', 'Environment', + self.visit_environment_in_state('environment', 'Expressions', s, prev_s) self.visit_generic_map_in_state('constraints', 'Ranges', s, prev_s) From e2698a70763b584b5458f4a4e896bde05ef39c25 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Fri, 18 Oct 2019 20:48:21 +0000 Subject: [PATCH 178/181] [analyzer] exploded-graph-rewriter: Unforget to censor stmt_ids in the test. They're not stable across machines. Fixes buildbots after r375278. llvm-svn: 375286 (cherry picked from commit 3ff26e27d93ea4ca753bd4109b46e0ae4dcca293) --- clang/test/Analysis/dump_egraph.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clang/test/Analysis/dump_egraph.c b/clang/test/Analysis/dump_egraph.c index 6198f66523e3f..c5dd65de5fadf 100644 --- a/clang/test/Analysis/dump_egraph.c +++ b/clang/test/Analysis/dump_egraph.c @@ -25,7 +25,7 @@ int foo() { // CHECK-SAME: \"node_id\": 2, \"is_sink\": 0, \"has_report\": 0 // CHECK-SAME: \}, // CHECK-SAME: \{ \"kind\": \"Statement\", \"stmt_kind\": \"IntegerLiteral\", -// CHECK-SAME: \"stmt_id\": 597, \"pointer\": \"0x{{[0-9a-f]*}}\", +// CHECK-SAME: \"stmt_id\": {{[0-9]*}}, \"pointer\": \"0x{{[0-9a-f]*}}\", // CHECK-SAME: \"pretty\": \"0\", \"location\": \{ // CHECK-SAME: \"line\": 15, \"column\": 12, \"file\": // CHECK-SAME: \}, \"stmt_point_kind\": \"PreStmtPurgeDeadSymbols\", @@ -33,7 +33,7 @@ int foo() { // CHECK-SAME: \"is_sink\": 0, \"has_report\": 0 // CHECK-SAME: \}, // CHECK-SAME: \{ \"kind\": \"Statement\", \"stmt_kind\": \"IntegerLiteral\", -// CHECK-SAME: \"stmt_id\": 597, \"pointer\": \"0x{{[0-9a-f]*}}\", +// CHECK-SAME: \"stmt_id\": {{[0-9]*}}, \"pointer\": \"0x{{[0-9a-f]*}}\", // CHECK-SAME: \"pretty\": \"0\", \"location\": \{ // CHECK-SAME: \"line\": 15, \"column\": 12, \"file\": // CHECK-SAME: \}, \"stmt_point_kind\": \"PostStmt\", \"tag\": null, From 750990eabfd41bb93327c45025e35c3a9cf587be Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Sat, 19 Oct 2019 00:08:17 +0000 Subject: [PATCH 179/181] [analyzer] Specify the C++ standard in more tests. Makes life easier for downstream developers with different default standard. llvm-svn: 375308 (cherry picked from commit b0914e7276bf97cb57f84fecc3a95e0d3ceeaf3e) --- clang/test/Analysis/cast-value-logic.cpp | 2 +- clang/test/Analysis/cast-value-notes.cpp | 2 +- clang/test/Analysis/cast-value-state-dump.cpp | 2 +- clang/test/Analysis/ctu-different-triples.cpp | 4 ++-- clang/test/Analysis/ctu-main.cpp | 8 ++++---- clang/test/Analysis/ctu-unknown-parts-in-triples.cpp | 4 ++-- clang/test/Analysis/deadstores-driverkit.cpp | 2 +- clang/test/Analysis/diagnostics/dtors.cpp | 2 +- clang/test/Analysis/domtest.cpp | 2 +- clang/test/Analysis/explain-svals.cpp | 2 +- clang/test/Analysis/globals.cpp | 2 +- clang/test/Analysis/initialization.cpp | 2 +- .../Analysis/inlining/placement-new-fp-suppression.cpp | 4 ++-- clang/test/Analysis/inner-pointer.cpp | 3 ++- clang/test/Analysis/malloc.mm | 2 +- clang/test/Analysis/mig.mm | 2 +- clang/test/Analysis/new-ctor-null-throw.cpp | 4 ++-- clang/test/Analysis/new-ctor-null.cpp | 2 +- clang/test/Analysis/osobject-retain-release.cpp | 2 +- clang/test/Analysis/osobjectcstylecastchecker_test.cpp | 2 +- clang/test/Analysis/plist-macros-with-expansion.cpp | 4 ++-- clang/test/Analysis/ptr-iter.cpp | 2 +- clang/test/Analysis/ptr-sort.cpp | 2 +- clang/test/Analysis/rvo.cpp | 2 +- clang/test/Analysis/sizeofpack.cpp | 2 +- clang/test/Analysis/stack-frame-context-revision.cpp | 2 +- clang/test/Analysis/temporaries.mm | 2 +- clang/test/Analysis/test-separate-retaincount.cpp | 6 +++--- .../test/Analysis/track-control-dependency-conditions.cpp | 8 ++++---- clang/test/Analysis/unions.cpp | 2 +- 30 files changed, 44 insertions(+), 43 deletions(-) diff --git a/clang/test/Analysis/cast-value-logic.cpp b/clang/test/Analysis/cast-value-logic.cpp index 221ae7f9ae38b..1411ede92e366 100644 --- a/clang/test/Analysis/cast-value-logic.cpp +++ b/clang/test/Analysis/cast-value-logic.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 \ +// RUN: %clang_analyze_cc1 -std=c++14 \ // RUN: -analyzer-checker=core,apiModeling.llvm.CastValue,debug.ExprInspection\ // RUN: -verify %s diff --git a/clang/test/Analysis/cast-value-notes.cpp b/clang/test/Analysis/cast-value-notes.cpp index a0eaeae8ba483..eb5d1b3d3fe27 100644 --- a/clang/test/Analysis/cast-value-notes.cpp +++ b/clang/test/Analysis/cast-value-notes.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 \ +// RUN: %clang_analyze_cc1 -std=c++14 \ // RUN: -analyzer-checker=core,apiModeling.llvm.CastValue,debug.ExprInspection\ // RUN: -analyzer-output=text -verify %s diff --git a/clang/test/Analysis/cast-value-state-dump.cpp b/clang/test/Analysis/cast-value-state-dump.cpp index b8152d46da47d..9abdaae0d4592 100644 --- a/clang/test/Analysis/cast-value-state-dump.cpp +++ b/clang/test/Analysis/cast-value-state-dump.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 \ +// RUN: %clang_analyze_cc1 -std=c++14 \ // RUN: -analyzer-checker=core,apiModeling.llvm.CastValue,debug.ExprInspection\ // RUN: -analyzer-output=text -verify %s 2>&1 | FileCheck %s diff --git a/clang/test/Analysis/ctu-different-triples.cpp b/clang/test/Analysis/ctu-different-triples.cpp index dbfa82fb483d9..20acc318e2e72 100644 --- a/clang/test/Analysis/ctu-different-triples.cpp +++ b/clang/test/Analysis/ctu-different-triples.cpp @@ -1,9 +1,9 @@ // RUN: rm -rf %t && mkdir %t // RUN: mkdir -p %t/ctudir -// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu \ +// RUN: %clang_cc1 -std=c++14 -triple x86_64-pc-linux-gnu \ // RUN: -emit-pch -o %t/ctudir/ctu-other.cpp.ast %S/Inputs/ctu-other.cpp // RUN: cp %S/Inputs/ctu-other.cpp.externalDefMap.txt %t/ctudir/externalDefMap.txt -// RUN: %clang_analyze_cc1 -triple powerpc64-montavista-linux-gnu \ +// RUN: %clang_analyze_cc1 -std=c++14 -triple powerpc64-montavista-linux-gnu \ // RUN: -analyzer-checker=core,debug.ExprInspection \ // RUN: -analyzer-config experimental-enable-naive-ctu-analysis=true \ // RUN: -analyzer-config ctu-dir=%t/ctudir \ diff --git a/clang/test/Analysis/ctu-main.cpp b/clang/test/Analysis/ctu-main.cpp index 1cb0d4a9d774e..0bddeee81e677 100644 --- a/clang/test/Analysis/ctu-main.cpp +++ b/clang/test/Analysis/ctu-main.cpp @@ -1,16 +1,16 @@ // RUN: rm -rf %t && mkdir %t // RUN: mkdir -p %t/ctudir -// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu \ +// RUN: %clang_cc1 -std=c++14 -triple x86_64-pc-linux-gnu \ // RUN: -emit-pch -o %t/ctudir/ctu-other.cpp.ast %S/Inputs/ctu-other.cpp -// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu \ +// RUN: %clang_cc1 -std=c++14 -triple x86_64-pc-linux-gnu \ // RUN: -emit-pch -o %t/ctudir/ctu-chain.cpp.ast %S/Inputs/ctu-chain.cpp // RUN: cp %S/Inputs/ctu-other.cpp.externalDefMap.txt %t/ctudir/externalDefMap.txt -// RUN: %clang_analyze_cc1 -triple x86_64-pc-linux-gnu \ +// RUN: %clang_analyze_cc1 -std=c++14 -triple x86_64-pc-linux-gnu \ // RUN: -analyzer-checker=core,debug.ExprInspection \ // RUN: -analyzer-config experimental-enable-naive-ctu-analysis=true \ // RUN: -analyzer-config ctu-dir=%t/ctudir \ // RUN: -verify %s -// RUN: %clang_analyze_cc1 -triple x86_64-pc-linux-gnu \ +// RUN: %clang_analyze_cc1 -std=c++14 -triple x86_64-pc-linux-gnu \ // RUN: -analyzer-checker=core,debug.ExprInspection \ // RUN: -analyzer-config experimental-enable-naive-ctu-analysis=true \ // RUN: -analyzer-config ctu-dir=%t/ctudir \ diff --git a/clang/test/Analysis/ctu-unknown-parts-in-triples.cpp b/clang/test/Analysis/ctu-unknown-parts-in-triples.cpp index 5e643c164dd7d..6bcbd709b5ef7 100644 --- a/clang/test/Analysis/ctu-unknown-parts-in-triples.cpp +++ b/clang/test/Analysis/ctu-unknown-parts-in-triples.cpp @@ -3,10 +3,10 @@ // RUN: rm -rf %t && mkdir %t // RUN: mkdir -p %t/ctudir -// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu \ +// RUN: %clang_cc1 -std=c++14 -triple x86_64-pc-linux-gnu \ // RUN: -emit-pch -o %t/ctudir/ctu-other.cpp.ast %S/Inputs/ctu-other.cpp // RUN: cp %S/Inputs/ctu-other.cpp.externalDefMap.txt %t/ctudir/externalDefMap.txt -// RUN: %clang_analyze_cc1 -triple x86_64-unknown-linux-gnu \ +// RUN: %clang_analyze_cc1 -std=c++14 -triple x86_64-unknown-linux-gnu \ // RUN: -analyzer-checker=core,debug.ExprInspection \ // RUN: -analyzer-config experimental-enable-naive-ctu-analysis=true \ // RUN: -analyzer-config ctu-dir=%t/ctudir \ diff --git a/clang/test/Analysis/deadstores-driverkit.cpp b/clang/test/Analysis/deadstores-driverkit.cpp index 0885367b1b9bf..9c423fc6ff218 100644 --- a/clang/test/Analysis/deadstores-driverkit.cpp +++ b/clang/test/Analysis/deadstores-driverkit.cpp @@ -5,7 +5,7 @@ // It needs to be on the top. // Run-lines can wait. -// RUN: %clang_analyze_cc1 -w -triple x86_64-apple-driverkit19.0 \ +// RUN: %clang_analyze_cc1 -std=c++17 -w -triple x86_64-apple-driverkit19.0 \ // RUN: -analyzer-checker=deadcode -verify %s // expected-no-diagnostics diff --git a/clang/test/Analysis/diagnostics/dtors.cpp b/clang/test/Analysis/diagnostics/dtors.cpp index 18bedc61f98e8..6a8349da9d78c 100644 --- a/clang/test/Analysis/diagnostics/dtors.cpp +++ b/clang/test/Analysis/diagnostics/dtors.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 -w -analyzer-checker=core,cplusplus -analyzer-output=text -verify %s +// RUN: %clang_analyze_cc1 -std=c++14 -w -analyzer-checker=core,cplusplus -analyzer-output=text -verify %s namespace no_crash_on_delete_dtor { // We were crashing when producing diagnostics for this code, but not for the diff --git a/clang/test/Analysis/domtest.cpp b/clang/test/Analysis/domtest.cpp index 078117ef85dc1..2a2caed130d79 100644 --- a/clang/test/Analysis/domtest.cpp +++ b/clang/test/Analysis/domtest.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 %s \ +// RUN: %clang_analyze_cc1 -std=c++14 %s \ // RUN: -analyzer-checker=debug.DumpDominators \ // RUN: -analyzer-checker=debug.DumpPostDominators \ // RUN: -analyzer-checker=debug.DumpControlDependencies \ diff --git a/clang/test/Analysis/explain-svals.cpp b/clang/test/Analysis/explain-svals.cpp index c1b5200eb8e92..9c37642758bb9 100644 --- a/clang/test/Analysis/explain-svals.cpp +++ b/clang/test/Analysis/explain-svals.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 -triple i386-apple-darwin10 -analyzer-checker=core.builtin,debug.ExprInspection,unix.cstring -verify %s +// RUN: %clang_analyze_cc1 -std=c++14 -triple i386-apple-darwin10 -analyzer-checker=core.builtin,debug.ExprInspection,unix.cstring -verify %s typedef unsigned long size_t; diff --git a/clang/test/Analysis/globals.cpp b/clang/test/Analysis/globals.cpp index d3df6eb6d27c0..fc74161375f13 100644 --- a/clang/test/Analysis/globals.cpp +++ b/clang/test/Analysis/globals.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 -analyzer-checker=core -verify %s +// RUN: %clang_analyze_cc1 -analyzer-checker=core -verify -std=c++2a %s static const unsigned long long scull = 0; diff --git a/clang/test/Analysis/initialization.cpp b/clang/test/Analysis/initialization.cpp index db765930b6e5e..dd622e077e934 100644 --- a/clang/test/Analysis/initialization.cpp +++ b/clang/test/Analysis/initialization.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -triple i386-apple-darwin10 -analyze -analyzer-checker=core.builtin,debug.ExprInspection -verify %s +// RUN: %clang_cc1 -std=c++14 -triple i386-apple-darwin10 -analyze -analyzer-checker=core.builtin,debug.ExprInspection -verify %s void clang_analyzer_eval(int); diff --git a/clang/test/Analysis/inlining/placement-new-fp-suppression.cpp b/clang/test/Analysis/inlining/placement-new-fp-suppression.cpp index 5f75411716836..5a99ad11cc17e 100644 --- a/clang/test/Analysis/inlining/placement-new-fp-suppression.cpp +++ b/clang/test/Analysis/inlining/placement-new-fp-suppression.cpp @@ -1,8 +1,8 @@ -// RUN: %clang_analyze_cc1 \ +// RUN: %clang_analyze_cc1 -std=c++14 \ // RUN: -analyzer-checker=core.CallAndMessage \ // RUN: -analyzer-config suppress-null-return-paths=false \ // RUN: -verify %s -// RUN: %clang_analyze_cc1 \ +// RUN: %clang_analyze_cc1 -std=c++14 \ // RUN: -analyzer-checker=core.CallAndMessage \ // RUN: -DSUPPRESSED \ // RUN: -verify %s diff --git a/clang/test/Analysis/inner-pointer.cpp b/clang/test/Analysis/inner-pointer.cpp index f4646c20fc294..d8b011a7aa64e 100644 --- a/clang/test/Analysis/inner-pointer.cpp +++ b/clang/test/Analysis/inner-pointer.cpp @@ -1,4 +1,5 @@ -// RUN: %clang_analyze_cc1 -analyzer-checker=cplusplus.InnerPointer \ +// RUN: %clang_analyze_cc1 -std=c++14 -analyzer-checker=cplusplus.InnerPointer \ +// RUN: -Wno-dangling -Wno-dangling-field -Wno-return-stack-address \ // RUN: %s -analyzer-output=text -verify #include "Inputs/system-header-simulator-cxx.h" diff --git a/clang/test/Analysis/malloc.mm b/clang/test/Analysis/malloc.mm index d7bfbf3f34f33..e84644b9dd732 100644 --- a/clang/test/Analysis/malloc.mm +++ b/clang/test/Analysis/malloc.mm @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix.Malloc -analyzer-store=region -verify -fblocks %s +// RUN: %clang_analyze_cc1 -std=c++14 -analyzer-checker=core,unix.Malloc -analyzer-store=region -verify -fblocks %s #import "Inputs/system-header-simulator-objc.h" #import "Inputs/system-header-simulator-for-malloc.h" diff --git a/clang/test/Analysis/mig.mm b/clang/test/Analysis/mig.mm index 0c7d729e9375d..e8d08f355d3ea 100644 --- a/clang/test/Analysis/mig.mm +++ b/clang/test/Analysis/mig.mm @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 -w -analyzer-checker=core,osx.MIG\ +// RUN: %clang_analyze_cc1 -w -analyzer-checker=core,osx.MIG -std=c++14 \ // RUN: -analyzer-output=text -fblocks -verify %s typedef unsigned uint32_t; diff --git a/clang/test/Analysis/new-ctor-null-throw.cpp b/clang/test/Analysis/new-ctor-null-throw.cpp index dfa7cba763fcf..28922c0fad4b7 100644 --- a/clang/test/Analysis/new-ctor-null-throw.cpp +++ b/clang/test/Analysis/new-ctor-null-throw.cpp @@ -1,7 +1,7 @@ -// RUN: %clang_analyze_cc1 -analyzer-checker=core \ +// RUN: %clang_analyze_cc1 -std=c++14 -analyzer-checker=core \ // RUN: -analyzer-config suppress-null-return-paths=false \ // RUN: -verify %s -// RUN: %clang_analyze_cc1 -analyzer-checker=core \ +// RUN: %clang_analyze_cc1 -std=c++14 -analyzer-checker=core \ // RUN: -DSUPPRESSED \ // RUN: -verify %s diff --git a/clang/test/Analysis/new-ctor-null.cpp b/clang/test/Analysis/new-ctor-null.cpp index 32f2f9500cce3..f3c07e2123731 100644 --- a/clang/test/Analysis/new-ctor-null.cpp +++ b/clang/test/Analysis/new-ctor-null.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 \ +// RUN: %clang_analyze_cc1 -std=c++14 \ // RUN: -analyzer-checker=core,debug.ExprInspection \ // RUN: -verify %s diff --git a/clang/test/Analysis/osobject-retain-release.cpp b/clang/test/Analysis/osobject-retain-release.cpp index afcc242583588..42675fc70e785 100644 --- a/clang/test/Analysis/osobject-retain-release.cpp +++ b/clang/test/Analysis/osobject-retain-release.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 -fblocks -analyze -analyzer-output=text\ +// RUN: %clang_analyze_cc1 -std=c++14 -fblocks -analyze -analyzer-output=text\ // RUN: -analyzer-checker=core,osx,debug.ExprInspection -verify %s #include "os_object_base.h" diff --git a/clang/test/Analysis/osobjectcstylecastchecker_test.cpp b/clang/test/Analysis/osobjectcstylecastchecker_test.cpp index 07f878cd39d55..fabed7ee34b1b 100644 --- a/clang/test/Analysis/osobjectcstylecastchecker_test.cpp +++ b/clang/test/Analysis/osobjectcstylecastchecker_test.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 -analyzer-checker=optin.osx.OSObjectCStyleCast %s -verify +// RUN: %clang_analyze_cc1 -std=c++14 -analyzer-checker=optin.osx.OSObjectCStyleCast %s -verify #include "os_object_base.h" struct OSArray : public OSObject { diff --git a/clang/test/Analysis/plist-macros-with-expansion.cpp b/clang/test/Analysis/plist-macros-with-expansion.cpp index e836c78b4bb35..e07747eaec74d 100644 --- a/clang/test/Analysis/plist-macros-with-expansion.cpp +++ b/clang/test/Analysis/plist-macros-with-expansion.cpp @@ -1,6 +1,6 @@ -// RUN: %clang_analyze_cc1 -analyzer-checker=core -verify %s +// RUN: %clang_analyze_cc1 -std=c++14 -analyzer-checker=core -verify %s // -// RUN: %clang_analyze_cc1 -analyzer-checker=core %s \ +// RUN: %clang_analyze_cc1 -std=c++14 -analyzer-checker=core %s \ // RUN: -analyzer-output=plist -o %t.plist \ // RUN: -analyzer-config expand-macros=true // diff --git a/clang/test/Analysis/ptr-iter.cpp b/clang/test/Analysis/ptr-iter.cpp index a35fae470a7ef..a94288cd1c8cc 100644 --- a/clang/test/Analysis/ptr-iter.cpp +++ b/clang/test/Analysis/ptr-iter.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 %s -analyzer-output=text -verify \ +// RUN: %clang_analyze_cc1 %s -std=c++14 -analyzer-output=text -verify \ // RUN: -analyzer-checker=core,alpha.nondeterminism.PointerIteration #include "Inputs/system-header-simulator-cxx.h" diff --git a/clang/test/Analysis/ptr-sort.cpp b/clang/test/Analysis/ptr-sort.cpp index a4f94817f13b3..d238b390bdc23 100644 --- a/clang/test/Analysis/ptr-sort.cpp +++ b/clang/test/Analysis/ptr-sort.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 %s -analyzer-output=text -verify \ +// RUN: %clang_analyze_cc1 %s -std=c++14 -analyzer-output=text -verify \ // RUN: -analyzer-checker=core,alpha.nondeterminism.PointerSorting #include "Inputs/system-header-simulator-cxx.h" diff --git a/clang/test/Analysis/rvo.cpp b/clang/test/Analysis/rvo.cpp index cf06a9570b368..7215fbbded461 100644 --- a/clang/test/Analysis/rvo.cpp +++ b/clang/test/Analysis/rvo.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 -analyzer-checker core,cplusplus \ +// RUN: %clang_analyze_cc1 -analyzer-checker core,cplusplus -std=c++14 \ // RUN: -analyzer-checker debug.ExprInspection -verify %s void clang_analyzer_eval(bool); diff --git a/clang/test/Analysis/sizeofpack.cpp b/clang/test/Analysis/sizeofpack.cpp index 44c3bba3a8ae4..8c0ca02b0710a 100644 --- a/clang/test/Analysis/sizeofpack.cpp +++ b/clang/test/Analysis/sizeofpack.cpp @@ -1,5 +1,5 @@ // RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection \ -// RUN: -verify %s +// RUN: -std=c++14 -verify %s typedef __typeof(sizeof(int)) size_t; diff --git a/clang/test/Analysis/stack-frame-context-revision.cpp b/clang/test/Analysis/stack-frame-context-revision.cpp index 8c119f50c141e..51f86defe3154 100644 --- a/clang/test/Analysis/stack-frame-context-revision.cpp +++ b/clang/test/Analysis/stack-frame-context-revision.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 -analyzer-checker=core,cplusplus.NewDelete -verify %s +// RUN: %clang_analyze_cc1 -std=c++14 -analyzer-checker=core,cplusplus.NewDelete -verify %s // expected-no-diagnostics: // From now the profile of the 'StackFrameContext' also contains the diff --git a/clang/test/Analysis/temporaries.mm b/clang/test/Analysis/temporaries.mm index 43546ae3441d8..44d30d5d7d535 100644 --- a/clang/test/Analysis/temporaries.mm +++ b/clang/test/Analysis/temporaries.mm @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 -analyzer-checker core,cplusplus -verify %s +// RUN: %clang_analyze_cc1 -std=c++14 -analyzer-checker core,cplusplus -verify %s // expected-no-diagnostics diff --git a/clang/test/Analysis/test-separate-retaincount.cpp b/clang/test/Analysis/test-separate-retaincount.cpp index 5fda2b2e22112..621e1d120bbb2 100644 --- a/clang/test/Analysis/test-separate-retaincount.cpp +++ b/clang/test/Analysis/test-separate-retaincount.cpp @@ -1,12 +1,12 @@ -// RUN: %clang_analyze_cc1 -DNO_CF_OBJECT -verify %s \ +// RUN: %clang_analyze_cc1 -std=c++14 -DNO_CF_OBJECT -verify %s \ // RUN: -analyzer-checker=core,osx \ // RUN: -analyzer-disable-checker osx.cocoa.RetainCount // -// RUN: %clang_analyze_cc1 -DNO_OS_OBJECT -verify %s \ +// RUN: %clang_analyze_cc1 -std=c++14 -DNO_OS_OBJECT -verify %s \ // RUN: -analyzer-checker=core,osx \ // RUN: -analyzer-disable-checker osx.OSObjectRetainCount // -// RUN: %clang_analyze_cc1 -DNO_OS_OBJECT -verify %s \ +// RUN: %clang_analyze_cc1 -std=c++14 -DNO_OS_OBJECT -verify %s \ // RUN: -analyzer-checker=core,osx \ // RUN: -analyzer-config "osx.cocoa.RetainCount:CheckOSObject=false" diff --git a/clang/test/Analysis/track-control-dependency-conditions.cpp b/clang/test/Analysis/track-control-dependency-conditions.cpp index 737620f4c31e4..11eb1c56a0388 100644 --- a/clang/test/Analysis/track-control-dependency-conditions.cpp +++ b/clang/test/Analysis/track-control-dependency-conditions.cpp @@ -1,10 +1,10 @@ -// RUN: %clang_analyze_cc1 %s \ +// RUN: %clang_analyze_cc1 %s -std=c++14 \ // RUN: -verify=expected,tracking \ // RUN: -analyzer-config track-conditions=true \ // RUN: -analyzer-output=text \ // RUN: -analyzer-checker=core -// RUN: not %clang_analyze_cc1 -verify %s \ +// RUN: not %clang_analyze_cc1 -std=c++14 -verify %s \ // RUN: -analyzer-checker=core \ // RUN: -analyzer-config track-conditions=false \ // RUN: -analyzer-config track-conditions-debug=true \ @@ -14,14 +14,14 @@ // CHECK-INVALID-DEBUG-SAME: 'track-conditions-debug', that expects // CHECK-INVALID-DEBUG-SAME: 'track-conditions' to also be enabled // -// RUN: %clang_analyze_cc1 %s \ +// RUN: %clang_analyze_cc1 %s -std=c++14 \ // RUN: -verify=expected,tracking,debug \ // RUN: -analyzer-config track-conditions=true \ // RUN: -analyzer-config track-conditions-debug=true \ // RUN: -analyzer-output=text \ // RUN: -analyzer-checker=core -// RUN: %clang_analyze_cc1 %s -verify \ +// RUN: %clang_analyze_cc1 %s -std=c++14 -verify \ // RUN: -analyzer-output=text \ // RUN: -analyzer-config track-conditions=false \ // RUN: -analyzer-checker=core diff --git a/clang/test/Analysis/unions.cpp b/clang/test/Analysis/unions.cpp index 6fd35d1a43f4a..76eb20550fdb4 100644 --- a/clang/test/Analysis/unions.cpp +++ b/clang/test/Analysis/unions.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix.Malloc,debug.ExprInspection %s -analyzer-config eagerly-assume=false -verify +// RUN: %clang_analyze_cc1 -std=c++14 -analyzer-checker=core,unix.Malloc,debug.ExprInspection %s -analyzer-config eagerly-assume=false -verify extern void clang_analyzer_eval(bool); extern void clang_analyzer_warnIfReached(); From dcb65b622861bfb4f54910c3947ea74c896f7e7e Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Wed, 23 Oct 2019 08:10:19 -0700 Subject: [PATCH 180/181] [analyzer] Fix off-by-one in operator call parameter binding. Member operator declarations and member operator expressions have different numbering of parameters and arguments respectively: one of them includes "this", the other does not. Account for this inconsistency when figuring out whether the parameter needs to be manually rebound from the Environment to the Store when entering a stack frame of an operator call, as opposed to being constructed with a constructor and as such already having the necessary Store bindings. Differential Revision: https://reviews.llvm.org/D69155 (cherry picked from commit be86fdb86e1efd6921c81f25ac0c0a78903c0a2d) --- clang/lib/StaticAnalyzer/Core/CallEvent.cpp | 2 +- clang/test/Analysis/temporaries.cpp | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp index 5f04a59ba0551..d95f809bec1aa 100644 --- a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp +++ b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp @@ -519,7 +519,7 @@ static void addParameterValuesToBindings(const StackFrameContext *CalleeCtx, // TODO: Support allocator calls. if (Call.getKind() != CE_CXXAllocator) - if (Call.isArgumentConstructedDirectly(Idx)) + if (Call.isArgumentConstructedDirectly(Call.getASTArgumentIndex(Idx))) continue; // TODO: Allocators should receive the correct size and possibly alignment, diff --git a/clang/test/Analysis/temporaries.cpp b/clang/test/Analysis/temporaries.cpp index 012cef52f14e3..325b689c0debd 100644 --- a/clang/test/Analysis/temporaries.cpp +++ b/clang/test/Analysis/temporaries.cpp @@ -1231,3 +1231,19 @@ S bar3(int coin) { return coin ? S() : foo(); // no-warning } } // namespace return_from_top_frame + +#if __cplusplus >= 201103L +namespace arguments_of_operators { +struct S { + S() {} + S(const S &) {} +}; + +void test() { + int x = 0; + auto foo = [](S s, int &y) { y = 1; }; + foo(S(), x); + clang_analyzer_eval(x == 1); // expected-warning{{TRUE}} +} +} // namespace arguments_of_operators +#endif // __cplusplus >= 201103L From 7fb1a17a08030b338ea6cf3d54144adb96dfbff0 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Fri, 25 Oct 2019 16:31:27 -0700 Subject: [PATCH 181/181] Revert "Teach CallGraph to look into Generic Lambdas." This reverts commit bee81d34a35ce65ef6645260c7289396a04bda48. --- clang/include/clang/AST/DeclCXX.h | 4 ---- clang/include/clang/AST/ExprCXX.h | 4 ---- clang/lib/AST/DeclCXX.cpp | 18 +++++------------- clang/lib/AST/ExprCXX.cpp | 5 ----- clang/lib/Analysis/CallGraph.cpp | 5 +---- clang/test/Analysis/debug-CallGraph.cpp | 21 ++------------------- 6 files changed, 8 insertions(+), 49 deletions(-) diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h index c0a55c3c8eeaf..6133d2fce754c 100644 --- a/clang/include/clang/AST/DeclCXX.h +++ b/clang/include/clang/AST/DeclCXX.h @@ -1212,10 +1212,6 @@ class CXXRecordDecl : public RecordDecl { /// if this is a closure type. CXXMethodDecl *getLambdaCallOperator() const; - /// Retrieve the dependent lambda call operator of the closure type - /// if this is a templated closure type. - FunctionTemplateDecl *getDependentLambdaCallOperator() const; - /// Retrieve the lambda static invoker, the address of which /// is returned by the conversion operator, and the body of which /// is forwarded to the lambda call operator. diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h index b2ab834f30e9e..551a677570ff9 100644 --- a/clang/include/clang/AST/ExprCXX.h +++ b/clang/include/clang/AST/ExprCXX.h @@ -1902,10 +1902,6 @@ class LambdaExpr final : public Expr, /// lambda expression. CXXMethodDecl *getCallOperator() const; - /// Retrieve the function template call operator associated with this - /// lambda expression. - FunctionTemplateDecl *getDependentCallOperator() const; - /// If this is a generic lambda expression, retrieve the template /// parameter list associated with it, or else return null. TemplateParameterList *getTemplateParameterList() const; diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp index b8c9229230195..48965966f091d 100644 --- a/clang/lib/AST/DeclCXX.cpp +++ b/clang/lib/AST/DeclCXX.cpp @@ -1369,25 +1369,17 @@ static bool allLookupResultsAreTheSame(const DeclContext::lookup_result &R) { } #endif -NamedDecl* getLambdaCallOperatorHelper(const CXXRecordDecl &RD) { - if (!RD.isLambda()) return nullptr; +CXXMethodDecl* CXXRecordDecl::getLambdaCallOperator() const { + if (!isLambda()) return nullptr; DeclarationName Name = - RD.getASTContext().DeclarationNames.getCXXOperatorName(OO_Call); - DeclContext::lookup_result Calls = RD.lookup(Name); + getASTContext().DeclarationNames.getCXXOperatorName(OO_Call); + DeclContext::lookup_result Calls = lookup(Name); assert(!Calls.empty() && "Missing lambda call operator!"); assert(allLookupResultsAreTheSame(Calls) && "More than one lambda call operator!"); - return Calls.front(); -} - -FunctionTemplateDecl* CXXRecordDecl::getDependentLambdaCallOperator() const { - NamedDecl *CallOp = getLambdaCallOperatorHelper(*this); - return dyn_cast(CallOp); -} -CXXMethodDecl *CXXRecordDecl::getLambdaCallOperator() const { - NamedDecl *CallOp = getLambdaCallOperatorHelper(*this); + NamedDecl *CallOp = Calls.front(); if (const auto *CallOpTmpl = dyn_cast(CallOp)) return cast(CallOpTmpl->getTemplatedDecl()); diff --git a/clang/lib/AST/ExprCXX.cpp b/clang/lib/AST/ExprCXX.cpp index 66ae629ffec52..b30f785ba8f54 100644 --- a/clang/lib/AST/ExprCXX.cpp +++ b/clang/lib/AST/ExprCXX.cpp @@ -1205,11 +1205,6 @@ CXXMethodDecl *LambdaExpr::getCallOperator() const { return Record->getLambdaCallOperator(); } -FunctionTemplateDecl *LambdaExpr::getDependentCallOperator() const { - CXXRecordDecl *Record = getLambdaClass(); - return Record->getDependentLambdaCallOperator(); -} - TemplateParameterList *LambdaExpr::getTemplateParameterList() const { CXXRecordDecl *Record = getLambdaClass(); return Record->getGenericLambdaTemplateParameterList(); diff --git a/clang/lib/Analysis/CallGraph.cpp b/clang/lib/Analysis/CallGraph.cpp index 3b0546570180d..b6542cad120e5 100644 --- a/clang/lib/Analysis/CallGraph.cpp +++ b/clang/lib/Analysis/CallGraph.cpp @@ -80,10 +80,7 @@ class CGBuilder : public StmtVisitor { } void VisitLambdaExpr(LambdaExpr *LE) { - if (FunctionTemplateDecl *FTD = LE->getDependentCallOperator()) - for (FunctionDecl *FD : FTD->specializations()) - G->VisitFunctionDecl(FD); - else if (CXXMethodDecl *MD = LE->getCallOperator()) + if (CXXMethodDecl *MD = LE->getCallOperator()) G->VisitFunctionDecl(MD); } diff --git a/clang/test/Analysis/debug-CallGraph.cpp b/clang/test/Analysis/debug-CallGraph.cpp index 0f5a83b268a01..1d6844fad94fc 100644 --- a/clang/test/Analysis/debug-CallGraph.cpp +++ b/clang/test/Analysis/debug-CallGraph.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 -analyzer-checker=debug.DumpCallGraph %s -fblocks -std=c++14 2>&1 | FileCheck %s +// RUN: %clang_analyze_cc1 -analyzer-checker=debug.DumpCallGraph %s -fblocks 2>&1 | FileCheck %s int get5() { return 5; @@ -68,25 +68,8 @@ void templUser() { } } -namespace Lambdas { - void Callee(){} - - void f1() { - [](int i) { - Callee(); - }(1); - [](auto i) { - Callee(); - }(1); - } -} - // CHECK:--- Call graph Dump --- -// CHECK-NEXT: {{Function: < root > calls: get5 add test_add mmm foo aaa < > bbb ddd ccc eee fff do_nothing test_single_call SomeNS::templ SomeNS::templ SomeNS::templUser Lambdas::Callee Lambdas::f1 Lambdas::f1\(\)::\(anonymous class\)::operator\(\) Lambdas::f1\(\)::\(anonymous class\)::operator\(\) $}} -// CHECK-NEXT: {{Function: Lambdas::f1 calls: Lambdas::f1\(\)::\(anonymous class\)::operator\(\) Lambdas::f1\(\)::\(anonymous class\)::operator\(\) $}} -// CHECK-NEXT: {{Function: Lambdas::f1\(\)::\(anonymous class\)::operator\(\) calls: Lambdas::Callee $}} -// CHECK-NEXT: {{Function: Lambdas::f1\(\)::\(anonymous class\)::operator\(\) calls: Lambdas::Callee $}} -// CHECK-NEXT: {{Function: Lambdas::Callee calls: $}} +// CHECK-NEXT: {{Function: < root > calls: get5 add test_add mmm foo aaa < > bbb ddd ccc eee fff do_nothing test_single_call SomeNS::templ SomeNS::templ SomeNS::templUser $}} // CHECK-NEXT: {{Function: SomeNS::templUser calls: SomeNS::templ SomeNS::templ $}} // CHECK-NEXT: {{Function: SomeNS::templ calls: eee $}} // CHECK-NEXT: {{Function: SomeNS::templ calls: ccc $}}
diff --git a/clang/test/Analysis/exploded-graph-rewriter/escapes.c b/clang/test/Analysis/exploded-graph-rewriter/escapes.c index d93baedf42bc2..aebb74a7ae284 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/escapes.c +++ b/clang/test/Analysis/exploded-graph-rewriter/escapes.c @@ -11,7 +11,7 @@ void escapes() { // CHECK: Store: (0x{{[0-9a-f]*}})foo0&Element\{"foo",0 S64b,char\}Environment: Expressions: "foo"&Element\{"foo",0 S64b,char\}