diff options
Diffstat (limited to 'lib/StaticAnalyzer/Core')
41 files changed, 3964 insertions, 2362 deletions
diff --git a/lib/StaticAnalyzer/Core/AnalysisManager.cpp b/lib/StaticAnalyzer/Core/AnalysisManager.cpp index dc0d3ec8493a2..7fb1c09ca049c 100644 --- a/lib/StaticAnalyzer/Core/AnalysisManager.cpp +++ b/lib/StaticAnalyzer/Core/AnalysisManager.cpp @@ -14,28 +14,33 @@ using namespace ento; void AnalysisManager::anchor() { } -AnalysisManager::AnalysisManager( - ASTContext &ASTCtx, DiagnosticsEngine &diags, const LangOptions &lang, - const PathDiagnosticConsumers &PDC, StoreManagerCreator storemgr, - ConstraintManagerCreator constraintmgr, CheckerManager *checkerMgr, - AnalyzerOptions &Options, CodeInjector *injector) - : AnaCtxMgr(ASTCtx, Options.UnoptimizedCFG, - Options.includeImplicitDtorsInCFG(), - /*AddInitializers=*/true, Options.includeTemporaryDtorsInCFG(), - Options.includeLifetimeInCFG(), - // Adding LoopExit elements to the CFG is a requirement for loop - // unrolling. - Options.includeLoopExitInCFG() || Options.shouldUnrollLoops(), - Options.includeScopesInCFG(), - Options.shouldSynthesizeBodies(), - Options.shouldConditionalizeStaticInitializers(), - /*addCXXNewAllocator=*/true, - Options.includeRichConstructorsInCFG(), - Options.shouldElideConstructors(), - injector), - Ctx(ASTCtx), Diags(diags), LangOpts(lang), PathConsumers(PDC), - CreateStoreMgr(storemgr), CreateConstraintMgr(constraintmgr), - CheckerMgr(checkerMgr), options(Options) { +AnalysisManager::AnalysisManager(ASTContext &ASTCtx, DiagnosticsEngine &diags, + const PathDiagnosticConsumers &PDC, + StoreManagerCreator storemgr, + ConstraintManagerCreator constraintmgr, + CheckerManager *checkerMgr, + AnalyzerOptions &Options, + CodeInjector *injector) + : AnaCtxMgr( + ASTCtx, Options.UnoptimizedCFG, + Options.ShouldIncludeImplicitDtorsInCFG, + /*AddInitializers=*/true, + Options.ShouldIncludeTemporaryDtorsInCFG, + Options.ShouldIncludeLifetimeInCFG, + // Adding LoopExit elements to the CFG is a requirement for loop + // unrolling. + Options.ShouldIncludeLoopExitInCFG || + Options.ShouldUnrollLoops, + Options.ShouldIncludeScopesInCFG, + Options.ShouldSynthesizeBodies, + Options.ShouldConditionalizeStaticInitializers, + /*addCXXNewAllocator=*/true, + Options.ShouldIncludeRichConstructorsInCFG, + Options.ShouldElideConstructors, injector), + Ctx(ASTCtx), Diags(diags), LangOpts(ASTCtx.getLangOpts()), + PathConsumers(PDC), CreateStoreMgr(storemgr), + CreateConstraintMgr(constraintmgr), CheckerMgr(checkerMgr), + options(Options) { AnaCtxMgr.getCFGBuildOptions().setAllAlwaysAdd(); } diff --git a/lib/StaticAnalyzer/Core/AnalyzerOptions.cpp b/lib/StaticAnalyzer/Core/AnalyzerOptions.cpp index 9b2dc32e06004..0588c2bd3d35c 100644 --- a/lib/StaticAnalyzer/Core/AnalyzerOptions.cpp +++ b/lib/StaticAnalyzer/Core/AnalyzerOptions.cpp @@ -34,7 +34,7 @@ std::vector<StringRef> AnalyzerOptions::getRegisteredCheckers(bool IncludeExperimental /* = false */) { static const StringRef StaticAnalyzerChecks[] = { #define GET_CHECKERS -#define CHECKER(FULLNAME, CLASS, DESCFILE, HELPTEXT, GROUPINDEX, HIDDEN) \ +#define CHECKER(FULLNAME, CLASS, HELPTEXT, DOC_URI) \ FULLNAME, #include "clang/StaticAnalyzer/Checkers/Checkers.inc" #undef CHECKER @@ -49,114 +49,71 @@ AnalyzerOptions::getRegisteredCheckers(bool IncludeExperimental /* = false */) { return Result; } -AnalyzerOptions::UserModeKind AnalyzerOptions::getUserMode() { - if (UserMode == UMK_NotSet) { - StringRef ModeStr = - Config.insert(std::make_pair("mode", "deep")).first->second; - UserMode = llvm::StringSwitch<UserModeKind>(ModeStr) - .Case("shallow", UMK_Shallow) - .Case("deep", UMK_Deep) - .Default(UMK_NotSet); - assert(UserMode != UMK_NotSet && "User mode is invalid."); - } - return UserMode; -} - -AnalyzerOptions::ExplorationStrategyKind -AnalyzerOptions::getExplorationStrategy() { - if (ExplorationStrategy == ExplorationStrategyKind::NotSet) { - StringRef StratStr = - Config - .insert(std::make_pair("exploration_strategy", "unexplored_first_queue")) - .first->second; - ExplorationStrategy = - llvm::StringSwitch<ExplorationStrategyKind>(StratStr) - .Case("dfs", ExplorationStrategyKind::DFS) - .Case("bfs", ExplorationStrategyKind::BFS) - .Case("unexplored_first", - ExplorationStrategyKind::UnexploredFirst) - .Case("unexplored_first_queue", - ExplorationStrategyKind::UnexploredFirstQueue) - .Case("bfs_block_dfs_contents", - ExplorationStrategyKind::BFSBlockDFSContents) - .Default(ExplorationStrategyKind::NotSet); - assert(ExplorationStrategy != ExplorationStrategyKind::NotSet && - "User mode is invalid."); - } - return ExplorationStrategy; -} - -IPAKind AnalyzerOptions::getIPAMode() { - if (IPAMode == IPAK_NotSet) { - // Use the User Mode to set the default IPA value. - // Note, we have to add the string to the Config map for the ConfigDumper - // checker to function properly. - const char *DefaultIPA = nullptr; - UserModeKind HighLevelMode = getUserMode(); - if (HighLevelMode == UMK_Shallow) - DefaultIPA = "inlining"; - else if (HighLevelMode == UMK_Deep) - DefaultIPA = "dynamic-bifurcate"; - assert(DefaultIPA); - - // Lookup the ipa configuration option, use the default from User Mode. - StringRef ModeStr = - Config.insert(std::make_pair("ipa", DefaultIPA)).first->second; - IPAKind IPAConfig = llvm::StringSwitch<IPAKind>(ModeStr) - .Case("none", IPAK_None) - .Case("basic-inlining", IPAK_BasicInlining) - .Case("inlining", IPAK_Inlining) - .Case("dynamic", IPAK_DynamicDispatch) - .Case("dynamic-bifurcate", IPAK_DynamicDispatchBifurcate) - .Default(IPAK_NotSet); - assert(IPAConfig != IPAK_NotSet && "IPA Mode is invalid."); - - // Set the member variable. - IPAMode = IPAConfig; - } - - return IPAMode; +ExplorationStrategyKind +AnalyzerOptions::getExplorationStrategy() const { + auto K = + llvm::StringSwitch<llvm::Optional<ExplorationStrategyKind>>( + ExplorationStrategy) + .Case("dfs", ExplorationStrategyKind::DFS) + .Case("bfs", ExplorationStrategyKind::BFS) + .Case("unexplored_first", + ExplorationStrategyKind::UnexploredFirst) + .Case("unexplored_first_queue", + ExplorationStrategyKind::UnexploredFirstQueue) + .Case("unexplored_first_location_queue", + ExplorationStrategyKind::UnexploredFirstLocationQueue) + .Case("bfs_block_dfs_contents", + ExplorationStrategyKind::BFSBlockDFSContents) + .Default(None); + assert(K.hasValue() && "User mode is invalid."); + return K.getValue(); +} + +IPAKind AnalyzerOptions::getIPAMode() const { + auto K = llvm::StringSwitch<llvm::Optional<IPAKind>>(IPAMode) + .Case("none", IPAK_None) + .Case("basic-inlining", IPAK_BasicInlining) + .Case("inlining", IPAK_Inlining) + .Case("dynamic", IPAK_DynamicDispatch) + .Case("dynamic-bifurcate", IPAK_DynamicDispatchBifurcate) + .Default(None); + assert(K.hasValue() && "IPA Mode is invalid."); + + return K.getValue(); } bool -AnalyzerOptions::mayInlineCXXMemberFunction(CXXInlineableMemberKind K) { +AnalyzerOptions::mayInlineCXXMemberFunction( + CXXInlineableMemberKind Param) const { if (getIPAMode() < IPAK_Inlining) return false; - if (!CXXMemberInliningMode) { - static const char *ModeKey = "c++-inlining"; - - StringRef ModeStr = - Config.insert(std::make_pair(ModeKey, "destructors")).first->second; + auto K = + llvm::StringSwitch<llvm::Optional<CXXInlineableMemberKind>>( + CXXMemberInliningMode) + .Case("constructors", CIMK_Constructors) + .Case("destructors", CIMK_Destructors) + .Case("methods", CIMK_MemberFunctions) + .Case("none", CIMK_None) + .Default(None); - CXXInlineableMemberKind &MutableMode = - const_cast<CXXInlineableMemberKind &>(CXXMemberInliningMode); - - MutableMode = llvm::StringSwitch<CXXInlineableMemberKind>(ModeStr) - .Case("constructors", CIMK_Constructors) - .Case("destructors", CIMK_Destructors) - .Case("none", CIMK_None) - .Case("methods", CIMK_MemberFunctions) - .Default(CXXInlineableMemberKind()); - - if (!MutableMode) { - // FIXME: We should emit a warning here about an unknown inlining kind, - // but the AnalyzerOptions doesn't have access to a diagnostic engine. - MutableMode = CIMK_None; - } - } + assert(K.hasValue() && "Invalid c++ member function inlining mode."); - return CXXMemberInliningMode >= K; + return *K >= Param; } -static StringRef toString(bool b) { return b ? "true" : "false"; } - -StringRef AnalyzerOptions::getCheckerOption(StringRef CheckerName, - StringRef OptionName, - StringRef Default, - bool SearchInParents) { +StringRef AnalyzerOptions::getCheckerStringOption(StringRef OptionName, + StringRef DefaultVal, + const CheckerBase *C, + bool SearchInParents) const { + assert(C); // Search for a package option if the option for the checker is not specified // and search in parents is enabled. + StringRef CheckerName = C->getTagDescription(); + + assert(!CheckerName.empty() && + "Empty checker name! Make sure the checker object (including it's " + "bases!) if fully initialized before calling this function!"); ConfigTable::const_iterator E = Config.end(); do { ConfigTable::const_iterator I = @@ -165,331 +122,35 @@ StringRef AnalyzerOptions::getCheckerOption(StringRef CheckerName, return StringRef(I->getValue()); size_t Pos = CheckerName.rfind('.'); if (Pos == StringRef::npos) - return Default; + return DefaultVal; CheckerName = CheckerName.substr(0, Pos); } while (!CheckerName.empty() && SearchInParents); - return Default; + return DefaultVal; } -bool AnalyzerOptions::getBooleanOption(StringRef Name, bool DefaultVal, - const CheckerBase *C, - bool SearchInParents) { +bool AnalyzerOptions::getCheckerBooleanOption(StringRef Name, bool DefaultVal, + const CheckerBase *C, + bool SearchInParents) const { // FIXME: We should emit a warning here if the value is something other than // "true", "false", or the empty string (meaning the default value), // but the AnalyzerOptions doesn't have access to a diagnostic engine. - StringRef Default = toString(DefaultVal); - StringRef V = - C ? getCheckerOption(C->getTagDescription(), Name, Default, - SearchInParents) - : StringRef(Config.insert(std::make_pair(Name, Default)).first->second); - return llvm::StringSwitch<bool>(V) + assert(C); + return llvm::StringSwitch<bool>( + getCheckerStringOption(Name, DefaultVal ? "true" : "false", C, + SearchInParents)) .Case("true", true) .Case("false", false) .Default(DefaultVal); } -bool AnalyzerOptions::getBooleanOption(Optional<bool> &V, StringRef Name, - bool DefaultVal, const CheckerBase *C, - bool SearchInParents) { - if (!V.hasValue()) - V = getBooleanOption(Name, DefaultVal, C, SearchInParents); - return V.getValue(); -} - -bool AnalyzerOptions::includeTemporaryDtorsInCFG() { - return getBooleanOption(IncludeTemporaryDtorsInCFG, - "cfg-temporary-dtors", - /* Default = */ true); -} - -bool AnalyzerOptions::includeImplicitDtorsInCFG() { - return getBooleanOption(IncludeImplicitDtorsInCFG, - "cfg-implicit-dtors", - /* Default = */ true); -} - -bool AnalyzerOptions::includeLifetimeInCFG() { - return getBooleanOption(IncludeLifetimeInCFG, "cfg-lifetime", - /* Default = */ false); -} - -bool AnalyzerOptions::includeLoopExitInCFG() { - return getBooleanOption(IncludeLoopExitInCFG, "cfg-loopexit", - /* Default = */ false); -} - -bool AnalyzerOptions::includeRichConstructorsInCFG() { - return getBooleanOption(IncludeRichConstructorsInCFG, - "cfg-rich-constructors", - /* Default = */ true); -} - -bool AnalyzerOptions::includeScopesInCFG() { - return getBooleanOption(IncludeScopesInCFG, - "cfg-scopes", - /* Default = */ false); -} - -bool AnalyzerOptions::mayInlineCXXStandardLibrary() { - return getBooleanOption(InlineCXXStandardLibrary, - "c++-stdlib-inlining", - /*Default=*/true); -} - -bool AnalyzerOptions::mayInlineTemplateFunctions() { - return getBooleanOption(InlineTemplateFunctions, - "c++-template-inlining", - /*Default=*/true); -} - -bool AnalyzerOptions::mayInlineCXXAllocator() { - return getBooleanOption(InlineCXXAllocator, - "c++-allocator-inlining", - /*Default=*/true); -} - -bool AnalyzerOptions::mayInlineCXXContainerMethods() { - return getBooleanOption(InlineCXXContainerMethods, - "c++-container-inlining", - /*Default=*/false); -} - -bool AnalyzerOptions::mayInlineCXXSharedPtrDtor() { - return getBooleanOption(InlineCXXSharedPtrDtor, - "c++-shared_ptr-inlining", - /*Default=*/false); -} - -bool AnalyzerOptions::mayInlineCXXTemporaryDtors() { - return getBooleanOption(InlineCXXTemporaryDtors, - "c++-temp-dtor-inlining", - /*Default=*/true); -} - -bool AnalyzerOptions::mayInlineObjCMethod() { - return getBooleanOption(ObjCInliningMode, - "objc-inlining", - /* Default = */ true); -} - -bool AnalyzerOptions::shouldSuppressNullReturnPaths() { - return getBooleanOption(SuppressNullReturnPaths, - "suppress-null-return-paths", - /* Default = */ true); -} - -bool AnalyzerOptions::shouldAvoidSuppressingNullArgumentPaths() { - return getBooleanOption(AvoidSuppressingNullArgumentPaths, - "avoid-suppressing-null-argument-paths", - /* Default = */ false); -} - -bool AnalyzerOptions::shouldSuppressInlinedDefensiveChecks() { - return getBooleanOption(SuppressInlinedDefensiveChecks, - "suppress-inlined-defensive-checks", - /* Default = */ true); -} - -bool AnalyzerOptions::shouldSuppressFromCXXStandardLibrary() { - return getBooleanOption(SuppressFromCXXStandardLibrary, - "suppress-c++-stdlib", - /* Default = */ true); -} - -bool AnalyzerOptions::shouldCrosscheckWithZ3() { - return getBooleanOption(CrosscheckWithZ3, - "crosscheck-with-z3", - /* Default = */ false); -} - -bool AnalyzerOptions::shouldReportIssuesInMainSourceFile() { - return getBooleanOption(ReportIssuesInMainSourceFile, - "report-in-main-source-file", - /* Default = */ false); -} - - -bool AnalyzerOptions::shouldWriteStableReportFilename() { - return getBooleanOption(StableReportFilename, - "stable-report-filename", - /* Default = */ false); -} - -bool AnalyzerOptions::shouldSerializeStats() { - return getBooleanOption(SerializeStats, - "serialize-stats", - /* Default = */ false); -} - -bool AnalyzerOptions::shouldElideConstructors() { - return getBooleanOption(ElideConstructors, - "elide-constructors", - /* Default = */ true); -} - -int AnalyzerOptions::getOptionAsInteger(StringRef Name, int DefaultVal, +int AnalyzerOptions::getCheckerIntegerOption(StringRef Name, int DefaultVal, const CheckerBase *C, - bool SearchInParents) { - SmallString<10> StrBuf; - llvm::raw_svector_ostream OS(StrBuf); - OS << DefaultVal; - - StringRef V = C ? getCheckerOption(C->getTagDescription(), Name, OS.str(), - SearchInParents) - : StringRef(Config.insert(std::make_pair(Name, OS.str())) - .first->second); - - int Res = DefaultVal; - bool b = V.getAsInteger(10, Res); - assert(!b && "analyzer-config option should be numeric"); - (void)b; - return Res; -} - -StringRef AnalyzerOptions::getOptionAsString(StringRef Name, - StringRef DefaultVal, - const CheckerBase *C, - bool SearchInParents) { - return C ? getCheckerOption(C->getTagDescription(), Name, DefaultVal, - SearchInParents) - : StringRef( - Config.insert(std::make_pair(Name, DefaultVal)).first->second); -} - -unsigned AnalyzerOptions::getAlwaysInlineSize() { - if (!AlwaysInlineSize.hasValue()) - AlwaysInlineSize = getOptionAsInteger("ipa-always-inline-size", 3); - return AlwaysInlineSize.getValue(); -} - -unsigned AnalyzerOptions::getMaxInlinableSize() { - if (!MaxInlinableSize.hasValue()) { - int DefaultValue = 0; - UserModeKind HighLevelMode = getUserMode(); - switch (HighLevelMode) { - default: - llvm_unreachable("Invalid mode."); - case UMK_Shallow: - DefaultValue = 4; - break; - case UMK_Deep: - DefaultValue = 100; - break; - } - - MaxInlinableSize = getOptionAsInteger("max-inlinable-size", DefaultValue); - } - return MaxInlinableSize.getValue(); -} - -unsigned AnalyzerOptions::getGraphTrimInterval() { - if (!GraphTrimInterval.hasValue()) - GraphTrimInterval = getOptionAsInteger("graph-trim-interval", 1000); - return GraphTrimInterval.getValue(); -} - -unsigned AnalyzerOptions::getMaxSymbolComplexity() { - if (!MaxSymbolComplexity.hasValue()) - MaxSymbolComplexity = getOptionAsInteger("max-symbol-complexity", 35); - return MaxSymbolComplexity.getValue(); -} - -unsigned AnalyzerOptions::getMaxTimesInlineLarge() { - if (!MaxTimesInlineLarge.hasValue()) - MaxTimesInlineLarge = getOptionAsInteger("max-times-inline-large", 32); - return MaxTimesInlineLarge.getValue(); -} - -unsigned AnalyzerOptions::getMinCFGSizeTreatFunctionsAsLarge() { - if (!MinCFGSizeTreatFunctionsAsLarge.hasValue()) - MinCFGSizeTreatFunctionsAsLarge = getOptionAsInteger( - "min-cfg-size-treat-functions-as-large", 14); - return MinCFGSizeTreatFunctionsAsLarge.getValue(); -} - -unsigned AnalyzerOptions::getMaxNodesPerTopLevelFunction() { - if (!MaxNodesPerTopLevelFunction.hasValue()) { - int DefaultValue = 0; - UserModeKind HighLevelMode = getUserMode(); - switch (HighLevelMode) { - default: - llvm_unreachable("Invalid mode."); - case UMK_Shallow: - DefaultValue = 75000; - break; - case UMK_Deep: - DefaultValue = 225000; - break; - } - MaxNodesPerTopLevelFunction = getOptionAsInteger("max-nodes", DefaultValue); - } - return MaxNodesPerTopLevelFunction.getValue(); -} - -bool AnalyzerOptions::shouldSynthesizeBodies() { - return getBooleanOption("faux-bodies", true); -} - -bool AnalyzerOptions::shouldPrunePaths() { - return getBooleanOption("prune-paths", true); -} - -bool AnalyzerOptions::shouldConditionalizeStaticInitializers() { - return getBooleanOption("cfg-conditional-static-initializers", true); -} - -bool AnalyzerOptions::shouldInlineLambdas() { - if (!InlineLambdas.hasValue()) - InlineLambdas = getBooleanOption("inline-lambdas", /*Default=*/true); - return InlineLambdas.getValue(); -} - -bool AnalyzerOptions::shouldWidenLoops() { - if (!WidenLoops.hasValue()) - WidenLoops = getBooleanOption("widen-loops", /*Default=*/false); - return WidenLoops.getValue(); -} - -bool AnalyzerOptions::shouldUnrollLoops() { - if (!UnrollLoops.hasValue()) - UnrollLoops = getBooleanOption("unroll-loops", /*Default=*/false); - return UnrollLoops.getValue(); -} - -bool AnalyzerOptions::shouldDisplayNotesAsEvents() { - if (!DisplayNotesAsEvents.hasValue()) - DisplayNotesAsEvents = - getBooleanOption("notes-as-events", /*Default=*/false); - return DisplayNotesAsEvents.getValue(); -} - -bool AnalyzerOptions::shouldAggressivelySimplifyBinaryOperation() { - if (!AggressiveBinaryOperationSimplification.hasValue()) - AggressiveBinaryOperationSimplification = - getBooleanOption("aggressive-binary-operation-simplification", - /*Default=*/false); - return AggressiveBinaryOperationSimplification.getValue(); -} - -StringRef AnalyzerOptions::getCTUDir() { - if (!CTUDir.hasValue()) { - CTUDir = getOptionAsString("ctu-dir", ""); - if (!llvm::sys::fs::is_directory(*CTUDir)) - CTUDir = ""; - } - return CTUDir.getValue(); -} - -bool AnalyzerOptions::naiveCTUEnabled() { - if (!NaiveCTU.hasValue()) { - NaiveCTU = getBooleanOption("experimental-enable-naive-ctu-analysis", - /*Default=*/false); - } - return NaiveCTU.getValue(); -} - -StringRef AnalyzerOptions::getCTUIndexName() { - if (!CTUIndexName.hasValue()) - CTUIndexName = getOptionAsString("ctu-index-name", "externalFnMap.txt"); - return CTUIndexName.getValue(); + bool SearchInParents) const { + int Ret = DefaultVal; + bool HasFailed = getCheckerStringOption(Name, std::to_string(DefaultVal), C, + SearchInParents) + .getAsInteger(10, Ret); + assert(!HasFailed && "analyzer-config option should be numeric"); + (void)HasFailed; + return Ret; } diff --git a/lib/StaticAnalyzer/Core/BasicValueFactory.cpp b/lib/StaticAnalyzer/Core/BasicValueFactory.cpp index db4c1432ccc32..d8ed6942de81f 100644 --- a/lib/StaticAnalyzer/Core/BasicValueFactory.cpp +++ b/lib/StaticAnalyzer/Core/BasicValueFactory.cpp @@ -207,7 +207,7 @@ BasicValueFactory::evalAPSInt(BinaryOperator::Opcode Op, const llvm::APSInt& V1, const llvm::APSInt& V2) { switch (Op) { default: - assert(false && "Invalid Opcode."); + llvm_unreachable("Invalid Opcode."); case BO_Mul: return &getValue( V1 * V2 ); diff --git a/lib/StaticAnalyzer/Core/BugReporter.cpp b/lib/StaticAnalyzer/Core/BugReporter.cpp index f990eb6a058db..fd7f53210490c 100644 --- a/lib/StaticAnalyzer/Core/BugReporter.cpp +++ b/lib/StaticAnalyzer/Core/BugReporter.cpp @@ -546,7 +546,8 @@ static void updateStackPiecesWithMessage(PathDiagnosticPiece &P, } } -static void CompactPathDiagnostic(PathPieces &path, const SourceManager& SM); +static void CompactMacroExpandedPieces(PathPieces &path, + const SourceManager& SM); std::shared_ptr<PathDiagnosticControlFlowPiece> generateDiagForSwitchOP( @@ -819,7 +820,7 @@ void generateMinimalDiagForBlockEdge(const ExplodedNode *N, BlockEdge BE, // 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 are also likely to be "interesting" as well (which then +// 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 @@ -841,7 +842,7 @@ static void reversePropagateIntererstingSymbols(BugReport &R, default: if (!isa<CastExpr>(Ex)) break; - // Fall through. + LLVM_FALLTHROUGH; case Stmt::BinaryOperatorClass: case Stmt::UnaryOperatorClass: { for (const Stmt *SubStmt : Ex->children()) { @@ -861,8 +862,7 @@ static void reversePropagateIntererstingSymbols(BugReport &R, static void reversePropagateInterestingSymbols(BugReport &R, InterestingExprs &IE, const ProgramState *State, - const LocationContext *CalleeCtx, - const LocationContext *CallerCtx) + const LocationContext *CalleeCtx) { // FIXME: Handle non-CallExpr-based CallEvents. const StackFrameContext *Callee = CalleeCtx->getStackFrame(); @@ -967,8 +967,7 @@ static bool isInLoopBody(ParentMap &PM, const Stmt *S, const Stmt *Term) { /// Adds a sanitized control-flow diagnostic edge to a path. static void addEdgeToPath(PathPieces &path, PathDiagnosticLocation &PrevLoc, - PathDiagnosticLocation NewLoc, - const LocationContext *LC) { + PathDiagnosticLocation NewLoc) { if (!NewLoc.isValid()) return; @@ -1043,7 +1042,7 @@ static void generatePathDiagnosticsForNode(const ExplodedNode *N, // not from declaration. if (D->hasBody()) addEdgeToPath(PD.getActivePath(), PrevLoc, - PathDiagnosticLocation::createBegin(D, SM), CalleeLC); + PathDiagnosticLocation::createBegin(D, SM)); } // Did we visit an entire call? @@ -1108,7 +1107,7 @@ static void generatePathDiagnosticsForNode(const ExplodedNode *N, // We are descending into a call (backwards). Construct // a new call piece to contain the path pieces for that call. - auto C = PathDiagnosticCallPiece::construct(N, *CE, SM); + auto C = PathDiagnosticCallPiece::construct(*CE, SM); // Record the mapping from call piece to LocationContext. LCM[&C->path] = CE->getCalleeContext(); @@ -1121,7 +1120,7 @@ static void generatePathDiagnosticsForNode(const ExplodedNode *N, N->getLocationContext()); } // Add the edge to the return site. - addEdgeToPath(PD.getActivePath(), PrevLoc, C->callReturn, PDB.LC); + addEdgeToPath(PD.getActivePath(), PrevLoc, C->callReturn); PrevLoc.invalidate(); } @@ -1151,7 +1150,7 @@ static void generatePathDiagnosticsForNode(const ExplodedNode *N, if (!isa<ObjCForCollectionStmt>(PS->getStmt())) { PathDiagnosticLocation L = PathDiagnosticLocation(PS->getStmt(), SM, PDB.LC); - addEdgeToPath(PD.getActivePath(), PrevLoc, L, PDB.LC); + addEdgeToPath(PD.getActivePath(), PrevLoc, L); } } else if (auto BE = P.getAs<BlockEdge>()) { @@ -1168,8 +1167,7 @@ static void generatePathDiagnosticsForNode(const ExplodedNode *N, const LocationContext *CalleeCtx = PDB.LC; if (CallerCtx != CalleeCtx && AddPathEdges) { reversePropagateInterestingSymbols(*PDB.getBugReport(), IE, - N->getState().get(), - CalleeCtx, CallerCtx); + N->getState().get(), CalleeCtx); } } @@ -1194,13 +1192,12 @@ static void generatePathDiagnosticsForNode(const ExplodedNode *N, "of the loop"); p->setPrunable(true); - addEdgeToPath(PD.getActivePath(), PrevLoc, p->getLocation(), PDB.LC); + addEdgeToPath(PD.getActivePath(), PrevLoc, p->getLocation()); PD.getActivePath().push_front(std::move(p)); if (const auto *CS = dyn_cast_or_null<CompoundStmt>(Body)) { addEdgeToPath(PD.getActivePath(), PrevLoc, - PathDiagnosticLocation::createEndBrace(CS, SM), - PDB.LC); + PathDiagnosticLocation::createEndBrace(CS, SM)); } } @@ -1236,13 +1233,13 @@ static void generatePathDiagnosticsForNode(const ExplodedNode *N, auto PE = std::make_shared<PathDiagnosticEventPiece>(L, str); PE->setPrunable(true); addEdgeToPath(PD.getActivePath(), PrevLoc, - PE->getLocation(), PDB.LC); + PE->getLocation()); PD.getActivePath().push_front(std::move(PE)); } } else if (isa<BreakStmt>(Term) || isa<ContinueStmt>(Term) || isa<GotoStmt>(Term)) { PathDiagnosticLocation L(Term, SM, PDB.LC); - addEdgeToPath(PD.getActivePath(), PrevLoc, L, PDB.LC); + addEdgeToPath(PD.getActivePath(), PrevLoc, L); } } } @@ -1269,7 +1266,7 @@ static const Stmt *getStmtParent(const Stmt *S, const ParentMap &PM) { if (!S) break; - if (isa<ExprWithCleanups>(S) || + if (isa<FullExpr>(S) || isa<CXXBindTemporaryExpr>(S) || isa<SubstNonTypeTemplateParmExpr>(S)) continue; @@ -1540,8 +1537,7 @@ static Optional<size_t> 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, - ParentMap &PM) { +static void removeContextCycles(PathPieces &Path, 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<PathDiagnosticControlFlowPiece>(I->get()); @@ -1632,8 +1628,8 @@ static void removePunyEdges(PathPieces &path, SourceManager &SM, if (isConditionForTerminator(end, endParent)) continue; - SourceLocation FirstLoc = start->getLocStart(); - SourceLocation SecondLoc = end->getLocStart(); + SourceLocation FirstLoc = start->getBeginLoc(); + SourceLocation SecondLoc = end->getBeginLoc(); if (!SM.isWrittenInSameFile(FirstLoc, SecondLoc)) continue; @@ -1844,7 +1840,7 @@ static bool optimizeEdges(PathPieces &path, SourceManager &SM, // and aesthetically pleasing. addContextEdges(path, SM, PM, LC); // Remove "cyclical" edges that include one or more context edges. - removeContextCycles(path, SM, PM); + removeContextCycles(path, SM); // Hoist edges originating from branch conditions to branches // for simple branches. simplifySimpleBranches(path); @@ -1881,6 +1877,22 @@ static void dropFunctionEntryEdge(PathPieces &Path, LocationContextMap &LCM, using VisitorsDiagnosticsTy = llvm::DenseMap<const ExplodedNode *, std::vector<std::shared_ptr<PathDiagnosticPiece>>>; +/// Populate executes lines with lines containing at least one diagnostics. +static void updateExecutedLinesWithDiagnosticPieces( + PathDiagnostic &PD) { + + PathPieces path = PD.path.flatten(/*ShouldFlattenMacros=*/true); + FilesToLineNumsMap &ExecutedLines = PD.getExecutedLines(); + + for (const auto &P : path) { + FullSourceLoc Loc = P->getLocation().asLocation().getExpansionLoc(); + FileID FID = Loc.getFileID(); + unsigned LineNo = Loc.getLineNumber(); + assert(FID.isValid()); + ExecutedLines[FID].insert(LineNo); + } +} + /// 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, @@ -1946,8 +1958,7 @@ static std::unique_ptr<PathDiagnostic> generatePathDiagnosticForConsumer( continue; if (AddPathEdges) - addEdgeToPath(PD->getActivePath(), PrevLoc, Note->getLocation(), - PDB.LC); + addEdgeToPath(PD->getActivePath(), PrevLoc, Note->getLocation()); updateStackPiecesWithMessage(*Note, CallStack); PD->getActivePath().push_front(Note); } @@ -1959,15 +1970,13 @@ static std::unique_ptr<PathDiagnostic> generatePathDiagnosticForConsumer( const StackFrameContext *CalleeLC = PDB.LC->getStackFrame(); const Decl *D = CalleeLC->getDecl(); addEdgeToPath(PD->getActivePath(), PrevLoc, - PathDiagnosticLocation::createBegin(D, SM), CalleeLC); + PathDiagnosticLocation::createBegin(D, SM)); } - if (!AddPathEdges && GenerateDiagnostics) - CompactPathDiagnostic(PD->getMutablePieces(), SM); // Finally, prune the diagnostic path of uninteresting stuff. if (!PD->path.empty()) { - if (R->shouldPrunePath() && Opts.shouldPrunePaths()) { + if (R->shouldPrunePath() && Opts.ShouldPrunePaths) { bool stillHasNotes = removeUnneededCalls(PD->getMutablePieces(), R, LCM); assert(stillHasNotes); @@ -1997,6 +2006,10 @@ static std::unique_ptr<PathDiagnostic> generatePathDiagnosticForConsumer( removeRedundantMsgs(PD->getMutablePieces()); removeEdgesToDefaultInitializers(PD->getMutablePieces()); } + + if (GenerateDiagnostics && Opts.ShouldDisplayMacroExpansions) + CompactMacroExpandedPieces(PD->getMutablePieces(), SM); + return PD; } @@ -2007,8 +2020,6 @@ static std::unique_ptr<PathDiagnostic> generatePathDiagnosticForConsumer( void BugType::anchor() {} -void BugType::FlushReports(BugReporter &BR) {} - void BuiltinBug::anchor() {} //===----------------------------------------------------------------------===// @@ -2237,14 +2248,6 @@ void BugReporter::FlushReports() { if (BugTypes.isEmpty()) return; - // First flush the warnings for each BugType. This may end up creating new - // warnings and new BugTypes. - // FIXME: Only NSErrorChecker needs BugType's FlushReports. - // Turn NSErrorChecker into a proper checker and remove this. - SmallVector<const BugType *, 16> bugTypes(BugTypes.begin(), BugTypes.end()); - for (const auto I : bugTypes) - const_cast<BugType*>(I)->FlushReports(*this); - // We need to flush reports in deterministic order to ensure the order // of the reports is consistent between runs. for (const auto EQ : EQClassesVector) @@ -2380,8 +2383,7 @@ TrimmedGraph::TrimmedGraph(const ExplodedGraph *OriginalGraph, } // Sort the error paths from longest to shortest. - llvm::sort(ReportNodes.begin(), ReportNodes.end(), - PriorityCompare<true>(PriorityMap)); + llvm::sort(ReportNodes, PriorityCompare<true>(PriorityMap)); } bool TrimmedGraph::popNextReportGraph(ReportGraph &GraphWrapper) { @@ -2437,9 +2439,10 @@ bool TrimmedGraph::popNextReportGraph(ReportGraph &GraphWrapper) { return true; } -/// CompactPathDiagnostic - This function postprocesses a PathDiagnostic object -/// and collapses PathDiagosticPieces that are expanded by macros. -static void CompactPathDiagnostic(PathPieces &path, const SourceManager& SM) { +/// CompactMacroExpandedPieces - This function postprocesses a PathDiagnostic +/// object and collapses PathDiagosticPieces that are expanded by macros. +static void CompactMacroExpandedPieces(PathPieces &path, + const SourceManager& SM) { using MacroStackTy = std::vector< std::pair<std::shared_ptr<PathDiagnosticMacroPiece>, SourceLocation>>; @@ -2455,7 +2458,7 @@ static void CompactPathDiagnostic(PathPieces &path, const SourceManager& SM) { // Recursively compact calls. if (auto *call = dyn_cast<PathDiagnosticCallPiece>(&*piece)) { - CompactPathDiagnostic(call->path, SM); + CompactMacroExpandedPieces(call->path, SM); } // Get the location of the PathDiagnosticPiece. @@ -2569,7 +2572,7 @@ generateVisitorsDiagnostics(BugReport *R, const ExplodedNode *ErrorNode, } for (auto &V : visitors) { - auto P = V->VisitNode(NextNode, Pred, BRC, *R); + auto P = V->VisitNode(NextNode, BRC, *R); if (P) (*Notes)[NextNode].push_back(std::move(P)); } @@ -2618,7 +2621,7 @@ std::pair<BugReport*, std::unique_ptr<VisitorsDiagnosticsTy>> findValidReport( generateVisitorsDiagnostics(R, ErrorNode, BRC); if (R->isValid()) { - if (Opts.shouldCrosscheckWithZ3()) { + if (Opts.ShouldCrosscheckWithZ3) { // If crosscheck is enabled, remove all visitors, add the refutation // visitor and check again R->clearVisitors(); @@ -2806,16 +2809,15 @@ static bool isInevitablySinking(const ExplodedNode *N) { 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 (SuccBlk == &Cfg.getExit()) { - // 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. - return false; - } - if (!isImmediateSinkBlock(SuccBlk) && !Visited.count(SuccBlk)) { // If the block has reachable child blocks that aren't no-return, // add them to the worklist. @@ -2961,7 +2963,7 @@ void BugReporter::FlushReport(BugReportEquivClass& EQ) { } PathPieces &Pieces = PD->getMutablePieces(); - if (getAnalyzerOptions().shouldDisplayNotesAsEvents()) { + if (getAnalyzerOptions().ShouldDisplayNotesAsEvents) { // For path diagnostic consumers that don't support extra notes, // we may optionally convert those to path notes. for (auto I = report->getNotes().rbegin(), @@ -2985,6 +2987,7 @@ void BugReporter::FlushReport(BugReportEquivClass& EQ) { for (const auto &i : Meta) PD->addMeta(i); + updateExecutedLinesWithDiagnosticPieces(*PD); Consumer->HandlePathDiagnostic(std::move(PD)); } } @@ -2993,7 +2996,7 @@ void BugReporter::FlushReport(BugReportEquivClass& EQ) { /// into \p ExecutedLines. static void populateExecutedLinesWithFunctionSignature( const Decl *Signature, SourceManager &SM, - std::unique_ptr<FilesToLineNumsMap> &ExecutedLines) { + FilesToLineNumsMap &ExecutedLines) { SourceRange SignatureSourceRange; const Stmt* Body = Signature->getBody(); if (const auto FD = dyn_cast<FunctionDecl>(Signature)) { @@ -3006,22 +3009,26 @@ static void populateExecutedLinesWithFunctionSignature( SourceLocation Start = SignatureSourceRange.getBegin(); SourceLocation End = Body ? Body->getSourceRange().getBegin() : SignatureSourceRange.getEnd(); + if (!Start.isValid() || !End.isValid()) + return; unsigned StartLine = SM.getExpansionLineNumber(Start); unsigned EndLine = SM.getExpansionLineNumber(End); FileID FID = SM.getFileID(SM.getExpansionLoc(Start)); for (unsigned Line = StartLine; Line <= EndLine; Line++) - ExecutedLines->operator[](FID.getHashValue()).insert(Line); + ExecutedLines[FID].insert(Line); } static void populateExecutedLinesWithStmt( const Stmt *S, SourceManager &SM, - std::unique_ptr<FilesToLineNumsMap> &ExecutedLines) { + FilesToLineNumsMap &ExecutedLines) { SourceLocation Loc = S->getSourceRange().getBegin(); + if (!Loc.isValid()) + return; SourceLocation ExpansionLoc = SM.getExpansionLoc(Loc); FileID FID = SM.getFileID(ExpansionLoc); unsigned LineNo = SM.getExpansionLineNumber(ExpansionLoc); - ExecutedLines->operator[](FID.getHashValue()).insert(LineNo); + ExecutedLines[FID].insert(LineNo); } /// \return all executed lines including function signatures on the path @@ -3034,13 +3041,13 @@ findExecutedLines(SourceManager &SM, const ExplodedNode *N) { if (N->getFirstPred() == nullptr) { // First node: show signature of the entrance point. const Decl *D = N->getLocationContext()->getDecl(); - populateExecutedLinesWithFunctionSignature(D, SM, ExecutedLines); + populateExecutedLinesWithFunctionSignature(D, SM, *ExecutedLines); } else if (auto CE = N->getLocationAs<CallEnter>()) { // Inlined function: show signature. const Decl* D = CE->getCalleeContext()->getDecl(); - populateExecutedLinesWithFunctionSignature(D, SM, ExecutedLines); + populateExecutedLinesWithFunctionSignature(D, SM, *ExecutedLines); } else if (const Stmt *S = PathDiagnosticLocation::getStmt(N)) { - populateExecutedLinesWithStmt(S, SM, ExecutedLines); + populateExecutedLinesWithStmt(S, SM, *ExecutedLines); // Show extra context for some parent kinds. const Stmt *P = N->getParentMap().getParent(S); @@ -3049,12 +3056,12 @@ findExecutedLines(SourceManager &SM, const ExplodedNode *N) { // return statement is generated, but we do want to show the whole // return. if (const auto *RS = dyn_cast_or_null<ReturnStmt>(P)) { - populateExecutedLinesWithStmt(RS, SM, ExecutedLines); + populateExecutedLinesWithStmt(RS, SM, *ExecutedLines); P = N->getParentMap().getParent(RS); } if (P && (isa<SwitchCase>(P) || isa<LabelStmt>(P))) - populateExecutedLinesWithStmt(P, SM, ExecutedLines); + populateExecutedLinesWithStmt(P, SM, *ExecutedLines); } N = N->getFirstPred(); @@ -3093,7 +3100,7 @@ BugReporter::generateDiagnosticForConsumerMap( // report location to the last piece in the main source file. AnalyzerOptions &Opts = getAnalyzerOptions(); for (auto const &P : *Out) - if (Opts.shouldReportIssuesInMainSourceFile() && !Opts.AnalyzeAll) + if (Opts.ShouldReportIssuesInMainSourceFile && !Opts.AnalyzeAll) P.second->resetDiagnosticLocationToMainFile(); return Out; diff --git a/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp b/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp index d4d33c1746ced..da94b6eb21e9b 100644 --- a/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp +++ b/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp @@ -42,10 +42,10 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState_Fwd.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SMTConv.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SubEngine.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/SMTConstraintManager.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/None.h" #include "llvm/ADT/Optional.h" @@ -71,12 +71,6 @@ using namespace ento; // Utility functions. //===----------------------------------------------------------------------===// -bool bugreporter::isDeclRefExprToReference(const Expr *E) { - if (const auto *DRE = dyn_cast<DeclRefExpr>(E)) - return DRE->getDecl()->getType()->isReferenceType(); - return false; -} - static const Expr *peelOffPointerArithmetic(const BinaryOperator *B) { if (B->isAdditiveOp() && B->getType()->isPointerType()) { if (B->getLHS()->getType()->isPointerType()) { @@ -142,8 +136,8 @@ const Expr *bugreporter::getDerefExpr(const Stmt *S) { E = AE->getBase(); } else if (const auto *PE = dyn_cast<ParenExpr>(E)) { E = PE->getSubExpr(); - } else if (const auto *EWC = dyn_cast<ExprWithCleanups>(E)) { - E = EWC->getSubExpr(); + } else if (const auto *FE = dyn_cast<FullExpr>(E)) { + E = FE->getSubExpr(); } else { // Other arbitrary stuff. break; @@ -160,34 +154,19 @@ const Expr *bugreporter::getDerefExpr(const Stmt *S) { return E; } -const Stmt *bugreporter::GetDenomExpr(const ExplodedNode *N) { - const Stmt *S = N->getLocationAs<PreStmt>()->getStmt(); - if (const auto *BE = dyn_cast<BinaryOperator>(S)) - return BE->getRHS(); - return nullptr; -} - -const Stmt *bugreporter::GetRetValExpr(const ExplodedNode *N) { - const Stmt *S = N->getLocationAs<PostStmt>()->getStmt(); - if (const auto *RS = dyn_cast<ReturnStmt>(S)) - return RS->getRetValue(); - return nullptr; -} - //===----------------------------------------------------------------------===// // Definitions for bug reporter visitors. //===----------------------------------------------------------------------===// std::shared_ptr<PathDiagnosticPiece> -BugReporterVisitor::getEndPath(BugReporterContext &BRC, - const ExplodedNode *EndPathNode, BugReport &BR) { +BugReporterVisitor::getEndPath(BugReporterContext &, + const ExplodedNode *, BugReport &) { return nullptr; } void -BugReporterVisitor::finalizeVisitor(BugReporterContext &BRC, - const ExplodedNode *EndPathNode, - BugReport &BR) {} +BugReporterVisitor::finalizeVisitor(BugReporterContext &, + const ExplodedNode *, BugReport &) {} std::shared_ptr<PathDiagnosticPiece> BugReporterVisitor::getDefaultEndPath( BugReporterContext &BRC, const ExplodedNode *EndPathNode, BugReport &BR) { @@ -269,10 +248,14 @@ namespace { /// pointer dereference outside. class NoStoreFuncVisitor final : public BugReporterVisitor { const SubRegion *RegionOfInterest; + MemRegionManager &MmrMgr; const SourceManager &SM; const PrintingPolicy &PP; - static constexpr const char *DiagnosticsMsg = - "Returning without writing to '"; + + /// Recursion limit for dereferencing fields when looking for the + /// region of interest. + /// The limit of two indicates that we will dereference fields only once. + static const unsigned DEREFERENCE_LIMIT = 2; /// Frames writing into \c RegionOfInterest. /// This visitor generates a note only if a function does not write into @@ -285,21 +268,22 @@ class NoStoreFuncVisitor final : public BugReporterVisitor { llvm::SmallPtrSet<const StackFrameContext *, 32> FramesModifyingRegion; llvm::SmallPtrSet<const StackFrameContext *, 32> FramesModifyingCalculated; + using RegionVector = SmallVector<const MemRegion *, 5>; public: NoStoreFuncVisitor(const SubRegion *R) - : RegionOfInterest(R), - SM(R->getMemRegionManager()->getContext().getSourceManager()), - PP(R->getMemRegionManager()->getContext().getPrintingPolicy()) {} + : RegionOfInterest(R), MmrMgr(*R->getMemRegionManager()), + SM(MmrMgr.getContext().getSourceManager()), + PP(MmrMgr.getContext().getPrintingPolicy()) {} void Profile(llvm::FoldingSetNodeID &ID) const override { static int Tag = 0; ID.AddPointer(&Tag); + ID.AddPointer(RegionOfInterest); } std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N, - const ExplodedNode *PrevN, - BugReporterContext &BRC, - BugReport &BR) override { + BugReporterContext &BR, + BugReport &) override { const LocationContext *Ctx = N->getLocationContext(); const StackFrameContext *SCtx = Ctx->getStackFrame(); @@ -307,48 +291,66 @@ public: auto CallExitLoc = N->getLocationAs<CallExitBegin>(); // No diagnostic if region was modified inside the frame. - if (!CallExitLoc) + if (!CallExitLoc || isRegionOfInterestModifiedInFrame(N)) return nullptr; CallEventRef<> Call = - BRC.getStateManager().getCallEventManager().getCaller(SCtx, State); + BR.getStateManager().getCallEventManager().getCaller(SCtx, State); + + if (SM.isInSystemHeader(Call->getDecl()->getSourceRange().getBegin())) + return nullptr; // Region of interest corresponds to an IVar, exiting a method // which could have written into that IVar, but did not. - if (const auto *MC = dyn_cast<ObjCMethodCall>(Call)) - if (const auto *IvarR = dyn_cast<ObjCIvarRegion>(RegionOfInterest)) - if (potentiallyWritesIntoIvar(Call->getRuntimeDefinition().getDecl(), - IvarR->getDecl()) && - !isRegionOfInterestModifiedInFrame(N)) - return notModifiedMemberDiagnostics( - Ctx, *CallExitLoc, Call, MC->getReceiverSVal().getAsRegion()); + if (const auto *MC = dyn_cast<ObjCMethodCall>(Call)) { + if (const auto *IvarR = dyn_cast<ObjCIvarRegion>(RegionOfInterest)) { + const MemRegion *SelfRegion = MC->getReceiverSVal().getAsRegion(); + if (RegionOfInterest->isSubRegionOf(SelfRegion) && + potentiallyWritesIntoIvar(Call->getRuntimeDefinition().getDecl(), + IvarR->getDecl())) + return notModifiedDiagnostics(Ctx, *CallExitLoc, Call, {}, SelfRegion, + "self", /*FirstIsReferenceType=*/false, + 1); + } + } if (const auto *CCall = dyn_cast<CXXConstructorCall>(Call)) { const MemRegion *ThisR = CCall->getCXXThisVal().getAsRegion(); if (RegionOfInterest->isSubRegionOf(ThisR) - && !CCall->getDecl()->isImplicit() - && !isRegionOfInterestModifiedInFrame(N)) - return notModifiedMemberDiagnostics(Ctx, *CallExitLoc, Call, ThisR); + && !CCall->getDecl()->isImplicit()) + return notModifiedDiagnostics(Ctx, *CallExitLoc, Call, {}, ThisR, + "this", + /*FirstIsReferenceType=*/false, 1); + + // Do not generate diagnostics for not modified parameters in + // constructors. + return nullptr; } ArrayRef<ParmVarDecl *> parameters = getCallParameters(Call); for (unsigned I = 0; I < Call->getNumArgs() && I < parameters.size(); ++I) { const ParmVarDecl *PVD = parameters[I]; SVal S = Call->getArgSVal(I); - unsigned IndirectionLevel = 1; + bool ParamIsReferenceType = PVD->getType()->isReferenceType(); + std::string ParamName = PVD->getNameAsString(); + + int IndirectionLevel = 1; QualType T = PVD->getType(); while (const MemRegion *R = S.getAsRegion()) { - if (RegionOfInterest->isSubRegionOf(R) - && !isPointerToConst(PVD->getType())) { + if (RegionOfInterest->isSubRegionOf(R) && !isPointerToConst(T)) + return notModifiedDiagnostics(Ctx, *CallExitLoc, Call, {}, R, + ParamName, ParamIsReferenceType, + IndirectionLevel); - if (isRegionOfInterestModifiedInFrame(N)) - return nullptr; - - return notModifiedParameterDiagnostics( - Ctx, *CallExitLoc, Call, PVD, R, IndirectionLevel); - } QualType PT = T->getPointeeType(); if (PT.isNull() || PT->isVoidType()) break; + + if (const RecordDecl *RD = PT->getAsRecordDecl()) + if (auto P = findRegionOfInterestInRecord(RD, State, R)) + return notModifiedDiagnostics( + Ctx, *CallExitLoc, Call, *P, RegionOfInterest, ParamName, + ParamIsReferenceType, IndirectionLevel); + S = State->getSVal(R, PT); T = PT; IndirectionLevel++; @@ -359,20 +361,94 @@ public: } private: + /// Attempts to find the region of interest in a given CXX decl, + /// by either following the base classes or fields. + /// Dereferences fields up to a given recursion limit. + /// Note that \p Vec is passed by value, leading to quadratic copying cost, + /// but it's OK in practice since its length is limited to DEREFERENCE_LIMIT. + /// \return A chain fields leading to the region of interest or None. + const Optional<RegionVector> + findRegionOfInterestInRecord(const RecordDecl *RD, ProgramStateRef State, + const MemRegion *R, + const RegionVector &Vec = {}, + int depth = 0) { + + if (depth == DEREFERENCE_LIMIT) // Limit the recursion depth. + return None; + + if (const auto *RDX = dyn_cast<CXXRecordDecl>(RD)) + if (!RDX->hasDefinition()) + return None; + + // Recursively examine the base classes. + // Note that following base classes does not increase the recursion depth. + if (const auto *RDX = dyn_cast<CXXRecordDecl>(RD)) + for (const auto II : RDX->bases()) + if (const RecordDecl *RRD = II.getType()->getAsRecordDecl()) + if (auto Out = findRegionOfInterestInRecord(RRD, State, R, Vec, depth)) + return Out; + + for (const FieldDecl *I : RD->fields()) { + QualType FT = I->getType(); + const FieldRegion *FR = MmrMgr.getFieldRegion(I, cast<SubRegion>(R)); + const SVal V = State->getSVal(FR); + const MemRegion *VR = V.getAsRegion(); + + RegionVector VecF = Vec; + VecF.push_back(FR); + + if (RegionOfInterest == VR) + return VecF; + + if (const RecordDecl *RRD = FT->getAsRecordDecl()) + if (auto Out = + findRegionOfInterestInRecord(RRD, State, FR, VecF, depth + 1)) + return Out; + + QualType PT = FT->getPointeeType(); + if (PT.isNull() || PT->isVoidType() || !VR) continue; + + if (const RecordDecl *RRD = PT->getAsRecordDecl()) + if (auto Out = + findRegionOfInterestInRecord(RRD, State, VR, VecF, depth + 1)) + return Out; + + } + + return None; + } /// \return Whether the method declaration \p Parent /// syntactically has a binary operation writing into the ivar \p Ivar. bool potentiallyWritesIntoIvar(const Decl *Parent, const ObjCIvarDecl *Ivar) { using namespace ast_matchers; - if (!Parent || !Parent->getBody()) + const char * IvarBind = "Ivar"; + if (!Parent || !Parent->hasBody()) return false; StatementMatcher WriteIntoIvarM = binaryOperator( - hasOperatorName("="), hasLHS(ignoringParenImpCasts(objcIvarRefExpr( - hasDeclaration(equalsNode(Ivar)))))); + hasOperatorName("="), + hasLHS(ignoringParenImpCasts( + objcIvarRefExpr(hasDeclaration(equalsNode(Ivar))).bind(IvarBind)))); StatementMatcher ParentM = stmt(hasDescendant(WriteIntoIvarM)); auto Matches = match(ParentM, *Parent->getBody(), Parent->getASTContext()); - return !Matches.empty(); + for (BoundNodes &Match : Matches) { + auto IvarRef = Match.getNodeAs<ObjCIvarRefExpr>(IvarBind); + if (IvarRef->isFreeIvar()) + return true; + + const Expr *Base = IvarRef->getBase(); + if (const auto *ICE = dyn_cast<ImplicitCastExpr>(Base)) + Base = ICE->getSubExpr(); + + if (const auto *DRE = dyn_cast<DeclRefExpr>(Base)) + if (const auto *ID = dyn_cast<ImplicitParamDecl>(DRE->getDecl())) + if (ID->getParameterKind() == ImplicitParamDecl::ObjCSelf) + return true; + + return false; + } + return false; } /// Check and lazily calculate whether the region of interest is @@ -433,6 +509,8 @@ private: RuntimeDefinition RD = Call->getRuntimeDefinition(); if (const auto *FD = dyn_cast_or_null<FunctionDecl>(RD.getDecl())) return FD->parameters(); + if (const auto *MD = dyn_cast_or_null<ObjCMethodDecl>(RD.getDecl())) + return MD->parameters(); return Call->parameters(); } @@ -443,123 +521,112 @@ private: Ty->getPointeeType().getCanonicalType().isConstQualified(); } - /// \return Diagnostics piece for the member field not modified - /// in a given function. - std::shared_ptr<PathDiagnosticPiece> notModifiedMemberDiagnostics( - const LocationContext *Ctx, - CallExitBegin &CallExitLoc, - CallEventRef<> Call, - const MemRegion *ArgRegion) { - const char *TopRegionName = isa<ObjCMethodCall>(Call) ? "self" : "this"; + /// \return Diagnostics piece for region not modified in the current function. + std::shared_ptr<PathDiagnosticPiece> + notModifiedDiagnostics(const LocationContext *Ctx, CallExitBegin &CallExitLoc, + CallEventRef<> Call, const RegionVector &FieldChain, + const MemRegion *MatchedRegion, StringRef FirstElement, + bool FirstIsReferenceType, unsigned IndirectionLevel) { + + PathDiagnosticLocation L; + if (const ReturnStmt *RS = CallExitLoc.getReturnStmt()) { + L = PathDiagnosticLocation::createBegin(RS, SM, Ctx); + } else { + L = PathDiagnosticLocation( + Call->getRuntimeDefinition().getDecl()->getSourceRange().getEnd(), + SM); + } + SmallString<256> sbuf; llvm::raw_svector_ostream os(sbuf); - os << DiagnosticsMsg; - bool out = prettyPrintRegionName(TopRegionName, "->", /*IsReference=*/true, - /*IndirectionLevel=*/1, ArgRegion, os, PP); + os << "Returning without writing to '"; - // Return nothing if we have failed to pretty-print. - if (!out) + // Do not generate the note if failed to pretty-print. + if (!prettyPrintRegionName(FirstElement, FirstIsReferenceType, + MatchedRegion, FieldChain, IndirectionLevel, os)) return nullptr; os << "'"; - PathDiagnosticLocation L = - getPathDiagnosticLocation(CallExitLoc.getReturnStmt(), SM, Ctx, Call); return std::make_shared<PathDiagnosticEventPiece>(L, os.str()); } - /// \return Diagnostics piece for the parameter \p PVD not modified - /// in a given function. - /// \p IndirectionLevel How many times \c ArgRegion has to be dereferenced - /// before we get to the super region of \c RegionOfInterest - std::shared_ptr<PathDiagnosticPiece> - notModifiedParameterDiagnostics(const LocationContext *Ctx, - CallExitBegin &CallExitLoc, - CallEventRef<> Call, - const ParmVarDecl *PVD, - const MemRegion *ArgRegion, - unsigned IndirectionLevel) { - PathDiagnosticLocation L = getPathDiagnosticLocation( - CallExitLoc.getReturnStmt(), SM, Ctx, Call); - SmallString<256> sbuf; - llvm::raw_svector_ostream os(sbuf); - os << DiagnosticsMsg; - bool IsReference = PVD->getType()->isReferenceType(); - const char *Sep = IsReference && IndirectionLevel == 1 ? "." : "->"; - bool Success = prettyPrintRegionName( - PVD->getQualifiedNameAsString().c_str(), - Sep, IsReference, IndirectionLevel, ArgRegion, os, PP); - - // Print the parameter name if the pretty-printing has failed. - if (!Success) - PVD->printQualifiedName(os); - os << "'"; - return std::make_shared<PathDiagnosticEventPiece>(L, os.str()); - } + /// Pretty-print region \p MatchedRegion to \p os. + /// \return Whether printing succeeded. + bool prettyPrintRegionName(StringRef FirstElement, bool FirstIsReferenceType, + const MemRegion *MatchedRegion, + const RegionVector &FieldChain, + int IndirectionLevel, + llvm::raw_svector_ostream &os) { - /// \return a path diagnostic location for the optionally - /// present return statement \p RS. - PathDiagnosticLocation getPathDiagnosticLocation(const ReturnStmt *RS, - const SourceManager &SM, - const LocationContext *Ctx, - CallEventRef<> Call) { - if (RS) - return PathDiagnosticLocation::createBegin(RS, SM, Ctx); - return PathDiagnosticLocation( - Call->getRuntimeDefinition().getDecl()->getSourceRange().getEnd(), SM); - } + if (FirstIsReferenceType) + IndirectionLevel--; - /// Pretty-print region \p ArgRegion starting from parent to \p os. - /// \return whether printing has succeeded - bool prettyPrintRegionName(StringRef TopRegionName, - StringRef Sep, - bool IsReference, - int IndirectionLevel, - const MemRegion *ArgRegion, - llvm::raw_svector_ostream &os, - const PrintingPolicy &PP) { - SmallVector<const MemRegion *, 5> Subregions; + RegionVector RegionSequence; + + // Add the regions in the reverse order, then reverse the resulting array. + assert(RegionOfInterest->isSubRegionOf(MatchedRegion)); const MemRegion *R = RegionOfInterest; - while (R != ArgRegion) { - if (!(isa<FieldRegion>(R) || isa<CXXBaseObjectRegion>(R) || - isa<ObjCIvarRegion>(R))) - return false; // Pattern-matching failed. - Subregions.push_back(R); + while (R != MatchedRegion) { + RegionSequence.push_back(R); R = cast<SubRegion>(R)->getSuperRegion(); } - bool IndirectReference = !Subregions.empty(); + std::reverse(RegionSequence.begin(), RegionSequence.end()); + RegionSequence.append(FieldChain.begin(), FieldChain.end()); + + StringRef Sep; + for (const MemRegion *R : RegionSequence) { + + // Just keep going up to the base region. + // Element regions may appear due to casts. + if (isa<CXXBaseObjectRegion>(R) || isa<CXXTempObjectRegion>(R)) + continue; + + if (Sep.empty()) + Sep = prettyPrintFirstElement(FirstElement, + /*MoreItemsExpected=*/true, + IndirectionLevel, os); - if (IndirectReference) - IndirectionLevel--; // Due to "->" symbol. + os << Sep; - if (IsReference) - IndirectionLevel--; // Due to reference semantics. + // Can only reasonably pretty-print DeclRegions. + if (!isa<DeclRegion>(R)) + return false; - bool ShouldSurround = IndirectReference && IndirectionLevel > 0; + const auto *DR = cast<DeclRegion>(R); + Sep = DR->getValueType()->isAnyPointerType() ? "->" : "."; + DR->getDecl()->getDeclName().print(os, PP); + } - if (ShouldSurround) + if (Sep.empty()) + prettyPrintFirstElement(FirstElement, + /*MoreItemsExpected=*/false, IndirectionLevel, + os); + return true; + } + + /// Print first item in the chain, return new separator. + StringRef prettyPrintFirstElement(StringRef FirstElement, + bool MoreItemsExpected, + int IndirectionLevel, + llvm::raw_svector_ostream &os) { + StringRef Out = "."; + + if (IndirectionLevel > 0 && MoreItemsExpected) { + IndirectionLevel--; + Out = "->"; + } + + if (IndirectionLevel > 0 && MoreItemsExpected) os << "("; - for (int i = 0; i < IndirectionLevel; i++) + + for (int i=0; i<IndirectionLevel; i++) os << "*"; - os << TopRegionName; - if (ShouldSurround) + os << FirstElement; + + if (IndirectionLevel > 0 && MoreItemsExpected) os << ")"; - for (auto I = Subregions.rbegin(), E = Subregions.rend(); I != E; ++I) { - if (const auto *FR = dyn_cast<FieldRegion>(*I)) { - os << Sep; - FR->getDecl()->getDeclName().print(os, PP); - Sep = "."; - } else if (const auto *IR = dyn_cast<ObjCIvarRegion>(*I)) { - os << "->"; - IR->getDecl()->getDeclName().print(os, PP); - Sep = "."; - } else if (isa<CXXBaseObjectRegion>(*I)) { - continue; // Just keep going up to the base region. - } else { - llvm_unreachable("Previous check has missed an unexpected region"); - } - } - return true; + return Out; } }; @@ -579,7 +646,6 @@ public: ValueAtDereference(V) {} std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N, - const ExplodedNode *PrevN, BugReporterContext &BRC, BugReport &BR) override { if (WasModified) @@ -590,10 +656,10 @@ public: return nullptr; const SourceManager &SMgr = BRC.getSourceManager(); - if (auto Loc = matchAssignment(N, BRC)) { + if (auto Loc = matchAssignment(N)) { if (isFunctionMacroExpansion(*Loc, SMgr)) { std::string MacroName = getMacroName(*Loc, BRC); - SourceLocation BugLoc = BugPoint->getStmt()->getLocStart(); + SourceLocation BugLoc = BugPoint->getStmt()->getBeginLoc(); if (!BugLoc.isMacroID() || getMacroName(BugLoc, BRC) != MacroName) BR.markInvalid(getTag(), MacroName.c_str()); } @@ -610,8 +676,8 @@ public: bool EnableNullFPSuppression, BugReport &BR, const SVal V) { AnalyzerOptions &Options = N->getState()->getAnalysisManager().options; - if (EnableNullFPSuppression && Options.shouldSuppressNullReturnPaths() - && V.getAs<Loc>()) + if (EnableNullFPSuppression && + Options.ShouldSuppressNullReturnPaths && V.getAs<Loc>()) BR.addVisitor(llvm::make_unique<MacroNullReturnSuppressionVisitor>( R->getAs<SubRegion>(), V)); } @@ -628,8 +694,7 @@ public: private: /// \return Source location of right hand side of an assignment /// into \c RegionOfInterest, empty optional if none found. - Optional<SourceLocation> matchAssignment(const ExplodedNode *N, - BugReporterContext &BRC) { + Optional<SourceLocation> matchAssignment(const ExplodedNode *N) { const Stmt *S = PathDiagnosticLocation::getStmt(N); ProgramStateRef State = N->getState(); auto *LCtx = N->getLocationContext(); @@ -641,12 +706,12 @@ private: if (const Expr *RHS = VD->getInit()) if (RegionOfInterest->isSubRegionOf( State->getLValue(VD, LCtx).getAsRegion())) - return RHS->getLocStart(); + return RHS->getBeginLoc(); } else if (const auto *BO = dyn_cast<BinaryOperator>(S)) { const MemRegion *R = N->getSVal(BO->getLHS()).getAsRegion(); const Expr *RHS = BO->getRHS(); if (BO->isAssignmentOp() && RegionOfInterest->isSubRegionOf(R)) { - return RHS->getLocStart(); + return RHS->getBeginLoc(); } } return None; @@ -670,10 +735,14 @@ class ReturnVisitor : public BugReporterVisitor { bool EnableNullFPSuppression; bool ShouldInvalidate = true; + AnalyzerOptions& Options; public: - ReturnVisitor(const StackFrameContext *Frame, bool Suppressed) - : StackFrame(Frame), EnableNullFPSuppression(Suppressed) {} + ReturnVisitor(const StackFrameContext *Frame, + bool Suppressed, + AnalyzerOptions &Options) + : StackFrame(Frame), EnableNullFPSuppression(Suppressed), + Options(Options) {} static void *getTag() { static int Tag = 0; @@ -701,10 +770,10 @@ public: // First, find when we processed the statement. do { - if (Optional<CallExitEnd> CEE = Node->getLocationAs<CallExitEnd>()) + if (auto CEE = Node->getLocationAs<CallExitEnd>()) if (CEE->getCalleeContext()->getCallSite() == S) break; - if (Optional<StmtPoint> SP = Node->getLocationAs<StmtPoint>()) + if (auto SP = Node->getLocationAs<StmtPoint>()) if (SP->getStmt() == S) break; @@ -739,23 +808,19 @@ public: AnalyzerOptions &Options = State->getAnalysisManager().options; bool EnableNullFPSuppression = false; - if (InEnableNullFPSuppression && Options.shouldSuppressNullReturnPaths()) + if (InEnableNullFPSuppression && + Options.ShouldSuppressNullReturnPaths) if (Optional<Loc> RetLoc = RetVal.getAs<Loc>()) EnableNullFPSuppression = State->isNull(*RetLoc).isConstrainedTrue(); BR.markInteresting(CalleeContext); BR.addVisitor(llvm::make_unique<ReturnVisitor>(CalleeContext, - EnableNullFPSuppression)); - } - - /// Returns true if any counter-suppression heuristics are enabled for - /// ReturnVisitor. - static bool hasCounterSuppression(AnalyzerOptions &Options) { - return Options.shouldAvoidSuppressingNullArgumentPaths(); + EnableNullFPSuppression, + Options)); } std::shared_ptr<PathDiagnosticPiece> - visitNodeInitial(const ExplodedNode *N, const ExplodedNode *PrevN, + visitNodeInitial(const ExplodedNode *N, BugReporterContext &BRC, BugReport &BR) { // Only print a message at the interesting return statement. if (N->getLocationContext() != StackFrame) @@ -799,37 +864,40 @@ public: RetE = RetE->IgnoreParenCasts(); - // If we can't prove the return value is 0, just mark it interesting, and - // make sure to track it into any further inner functions. - if (!State->isNull(V).isConstrainedTrue()) { - BR.markInteresting(V); - ReturnVisitor::addVisitorIfNecessary(N, RetE, BR, - EnableNullFPSuppression); - return nullptr; - } - // If we're returning 0, we should track where that 0 came from. - bugreporter::trackNullOrUndefValue(N, RetE, BR, /*IsArg*/ false, - EnableNullFPSuppression); + bugreporter::trackExpressionValue(N, RetE, BR, EnableNullFPSuppression); // Build an appropriate message based on the return value. SmallString<64> Msg; llvm::raw_svector_ostream Out(Msg); - if (V.getAs<Loc>()) { - // If we have counter-suppression enabled, make sure we keep visiting - // future nodes. We want to emit a path note as well, in case - // the report is resurrected as valid later on. - AnalyzerOptions &Options = BRC.getAnalyzerOptions(); - if (EnableNullFPSuppression && hasCounterSuppression(Options)) - Mode = MaybeUnsuppress; + if (State->isNull(V).isConstrainedTrue()) { + if (V.getAs<Loc>()) { + + // If we have counter-suppression enabled, make sure we keep visiting + // future nodes. We want to emit a path note as well, in case + // the report is resurrected as valid later on. + if (EnableNullFPSuppression && + Options.ShouldAvoidSuppressingNullArgumentPaths) + Mode = MaybeUnsuppress; + + if (RetE->getType()->isObjCObjectPointerType()) { + Out << "Returning nil"; + } else { + Out << "Returning null pointer"; + } + } else { + Out << "Returning zero"; + } - if (RetE->getType()->isObjCObjectPointerType()) - Out << "Returning nil"; - else - Out << "Returning null pointer"; } else { - Out << "Returning zero"; + if (auto CI = V.getAs<nonloc::ConcreteInt>()) { + Out << "Returning the value " << CI->getValue(); + } else if (V.getAs<Loc>()) { + Out << "Returning pointer"; + } else { + Out << "Returning value"; + } } if (LValue) { @@ -855,11 +923,10 @@ public: } std::shared_ptr<PathDiagnosticPiece> - visitNodeMaybeUnsuppress(const ExplodedNode *N, const ExplodedNode *PrevN, + visitNodeMaybeUnsuppress(const ExplodedNode *N, BugReporterContext &BRC, BugReport &BR) { #ifndef NDEBUG - AnalyzerOptions &Options = BRC.getAnalyzerOptions(); - assert(hasCounterSuppression(Options)); + assert(Options.ShouldAvoidSuppressingNullArgumentPaths); #endif // Are we at the entry node for this call? @@ -893,8 +960,7 @@ public: if (!State->isNull(*ArgV).isConstrainedTrue()) continue; - if (bugreporter::trackNullOrUndefValue(N, ArgE, BR, /*IsArg=*/true, - EnableNullFPSuppression)) + if (bugreporter::trackExpressionValue(N, ArgE, BR, EnableNullFPSuppression)) ShouldInvalidate = false; // If we /can't/ track the null pointer, we should err on the side of @@ -906,14 +972,13 @@ public: } std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N, - const ExplodedNode *PrevN, BugReporterContext &BRC, BugReport &BR) override { switch (Mode) { case Initial: - return visitNodeInitial(N, PrevN, BRC, BR); + return visitNodeInitial(N, BRC, BR); case MaybeUnsuppress: - return visitNodeMaybeUnsuppress(N, PrevN, BRC, BR); + return visitNodeMaybeUnsuppress(N, BRC, BR); case Satisfied: return nullptr; } @@ -921,7 +986,7 @@ public: llvm_unreachable("Invalid visit mode!"); } - void finalizeVisitor(BugReporterContext &BRC, const ExplodedNode *N, + void finalizeVisitor(BugReporterContext &, const ExplodedNode *, BugReport &BR) override { if (EnableNullFPSuppression && ShouldInvalidate) BR.markInvalid(ReturnVisitor::getTag(), StackFrame); @@ -1087,12 +1152,12 @@ static void showBRDefaultDiagnostics(llvm::raw_svector_ostream& os, std::shared_ptr<PathDiagnosticPiece> FindLastStoreBRVisitor::VisitNode(const ExplodedNode *Succ, - const ExplodedNode *Pred, BugReporterContext &BRC, BugReport &BR) { if (Satisfied) return nullptr; const ExplodedNode *StoreSite = nullptr; + const ExplodedNode *Pred = Succ->getFirstPred(); const Expr *InitE = nullptr; bool IsParam = false; @@ -1173,12 +1238,11 @@ FindLastStoreBRVisitor::VisitNode(const ExplodedNode *Succ, V.getAs<loc::ConcreteInt>() || V.getAs<nonloc::ConcreteInt>()) { if (!IsParam) InitE = InitE->IgnoreParenCasts(); - bugreporter::trackNullOrUndefValue(StoreSite, InitE, BR, IsParam, - EnableNullFPSuppression); - } else { - ReturnVisitor::addVisitorIfNecessary(StoreSite, InitE->IgnoreParenCasts(), - BR, EnableNullFPSuppression); + bugreporter::trackExpressionValue(StoreSite, InitE, BR, + EnableNullFPSuppression); } + ReturnVisitor::addVisitorIfNecessary(StoreSite, InitE->IgnoreParenCasts(), + BR, EnableNullFPSuppression); } // Okay, we've found the binding. Emit an appropriate message. @@ -1204,8 +1268,7 @@ FindLastStoreBRVisitor::VisitNode(const ExplodedNode *Succ, if (const auto *BDR = dyn_cast_or_null<BlockDataRegion>(V.getAsRegion())) { if (const VarRegion *OriginalR = BDR->getOriginalRegion(VR)) { - if (Optional<KnownSVal> KV = - State->getSVal(OriginalR).getAs<KnownSVal>()) + if (auto KV = State->getSVal(OriginalR).getAs<KnownSVal>()) BR.addVisitor(llvm::make_unique<FindLastStoreBRVisitor>( *KV, OriginalR, EnableNullFPSuppression)); } @@ -1260,8 +1323,8 @@ bool TrackConstraintBRVisitor::isUnderconstrained(const ExplodedNode *N) const { std::shared_ptr<PathDiagnosticPiece> TrackConstraintBRVisitor::VisitNode(const ExplodedNode *N, - const ExplodedNode *PrevN, - BugReporterContext &BRC, BugReport &BR) { + BugReporterContext &BRC, BugReport &) { + const ExplodedNode *PrevN = N->getFirstPred(); if (IsSatisfied) return nullptr; @@ -1316,7 +1379,7 @@ SuppressInlineDefensiveChecksVisitor(DefinedSVal Value, const ExplodedNode *N) : V(Value) { // Check if the visitor is disabled. AnalyzerOptions &Options = N->getState()->getAnalysisManager().options; - if (!Options.shouldSuppressInlinedDefensiveChecks()) + if (!Options.ShouldSuppressInlinedDefensiveChecks) IsSatisfied = true; assert(N->getState()->isNull(V).isConstrainedTrue() && @@ -1336,9 +1399,9 @@ const char *SuppressInlineDefensiveChecksVisitor::getTag() { std::shared_ptr<PathDiagnosticPiece> SuppressInlineDefensiveChecksVisitor::VisitNode(const ExplodedNode *Succ, - const ExplodedNode *Pred, BugReporterContext &BRC, BugReport &BR) { + const ExplodedNode *Pred = Succ->getFirstPred(); if (IsSatisfied) return nullptr; @@ -1379,7 +1442,7 @@ SuppressInlineDefensiveChecksVisitor::VisitNode(const ExplodedNode *Succ, CurTerminatorStmt = BE->getSrc()->getTerminator().getStmt(); } else if (auto SP = CurPoint.getAs<StmtPoint>()) { const Stmt *CurStmt = SP->getStmt(); - if (!CurStmt->getLocStart().isMacroID()) + if (!CurStmt->getBeginLoc().isMacroID()) return nullptr; CFGStmtMap *Map = CurLC->getAnalysisDeclContext()->getCFGStmtMap(); @@ -1391,9 +1454,9 @@ SuppressInlineDefensiveChecksVisitor::VisitNode(const ExplodedNode *Succ, if (!CurTerminatorStmt) return nullptr; - SourceLocation TerminatorLoc = CurTerminatorStmt->getLocStart(); + SourceLocation TerminatorLoc = CurTerminatorStmt->getBeginLoc(); if (TerminatorLoc.isMacroID()) { - SourceLocation BugLoc = BugPoint->getStmt()->getLocStart(); + SourceLocation BugLoc = BugPoint->getStmt()->getBeginLoc(); // Suppress reports unless we are in that same macro. if (!BugLoc.isMacroID() || @@ -1427,11 +1490,13 @@ static const MemRegion *getLocationRegionIfReference(const Expr *E, return nullptr; } +/// \return A subexpression of {@code Ex} which represents the +/// expression-of-interest. static const Expr *peelOffOuterExpr(const Expr *Ex, const ExplodedNode *N) { Ex = Ex->IgnoreParenCasts(); - if (const auto *EWC = dyn_cast<ExprWithCleanups>(Ex)) - return peelOffOuterExpr(EWC->getSubExpr(), N); + if (const auto *FE = dyn_cast<FullExpr>(Ex)) + return peelOffOuterExpr(FE->getSubExpr(), N); if (const auto *OVE = dyn_cast<OpaqueValueExpr>(Ex)) return peelOffOuterExpr(OVE->getSourceExpr(), N); if (const auto *POE = dyn_cast<PseudoObjectExpr>(Ex)) { @@ -1471,121 +1536,72 @@ static const Expr *peelOffOuterExpr(const Expr *Ex, if (const Expr *SubEx = peelOffPointerArithmetic(BO)) return peelOffOuterExpr(SubEx, N); - return Ex; -} + if (auto *UO = dyn_cast<UnaryOperator>(Ex)) { + if (UO->getOpcode() == UO_LNot) + return peelOffOuterExpr(UO->getSubExpr(), N); -/// Walk through nodes until we get one that matches the statement exactly. -/// Alternately, if we hit a known lvalue for the statement, we know we've -/// gone too far (though we can likely track the lvalue better anyway). -static const ExplodedNode* findNodeForStatement(const ExplodedNode *N, - const Stmt *S, - const Expr *Inner) { - do { - const ProgramPoint &pp = N->getLocation(); - if (auto ps = pp.getAs<StmtPoint>()) { - if (ps->getStmt() == S || ps->getStmt() == Inner) - break; - } else if (auto CEE = pp.getAs<CallExitEnd>()) { - if (CEE->getCalleeContext()->getCallSite() == S || - CEE->getCalleeContext()->getCallSite() == Inner) - break; - } - N = N->getFirstPred(); - } while (N); - return N; + // FIXME: There's a hack in our Store implementation that always computes + // field offsets around null pointers as if they are always equal to 0. + // The idea here is to report accesses to fields as null dereferences + // even though the pointer value that's being dereferenced is actually + // the offset of the field rather than exactly 0. + // See the FIXME in StoreManager's getLValueFieldOrIvar() method. + // This code interacts heavily with this hack; otherwise the value + // would not be null at all for most fields, so we'd be unable to track it. + if (UO->getOpcode() == UO_AddrOf && UO->getSubExpr()->isLValue()) + if (const Expr *DerefEx = bugreporter::getDerefExpr(UO->getSubExpr())) + return peelOffOuterExpr(DerefEx, N); + } + + return Ex; } /// Find the ExplodedNode where the lvalue (the value of 'Ex') /// was computed. static const ExplodedNode* findNodeForExpression(const ExplodedNode *N, - const Expr *Inner) { + const Expr *Inner) { while (N) { - if (auto P = N->getLocation().getAs<PostStmt>()) { - if (P->getStmt() == Inner) - break; - } + if (PathDiagnosticLocation::getStmt(N) == Inner) + return N; N = N->getFirstPred(); } - assert(N && "Unable to find the lvalue node."); return N; } -/// Performing operator `&' on an lvalue expression is essentially a no-op. -/// Then, if we are taking addresses of fields or elements, these are also -/// unlikely to matter. -static const Expr* peelOfOuterAddrOf(const Expr* Ex) { - Ex = Ex->IgnoreParenCasts(); - - // FIXME: There's a hack in our Store implementation that always computes - // field offsets around null pointers as if they are always equal to 0. - // The idea here is to report accesses to fields as null dereferences - // even though the pointer value that's being dereferenced is actually - // the offset of the field rather than exactly 0. - // See the FIXME in StoreManager's getLValueFieldOrIvar() method. - // This code interacts heavily with this hack; otherwise the value - // would not be null at all for most fields, so we'd be unable to track it. - if (const auto *Op = dyn_cast<UnaryOperator>(Ex)) - if (Op->getOpcode() == UO_AddrOf && Op->getSubExpr()->isLValue()) - if (const Expr *DerefEx = bugreporter::getDerefExpr(Op->getSubExpr())) - return DerefEx; - return Ex; -} - -bool bugreporter::trackNullOrUndefValue(const ExplodedNode *N, - const Stmt *S, - BugReport &report, bool IsArg, - bool EnableNullFPSuppression) { - if (!S || !N) +bool bugreporter::trackExpressionValue(const ExplodedNode *InputNode, + const Expr *E, BugReport &report, + bool EnableNullFPSuppression) { + if (!E || !InputNode) return false; - if (const auto *Ex = dyn_cast<Expr>(S)) - S = peelOffOuterExpr(Ex, N); - - const Expr *Inner = nullptr; - if (const auto *Ex = dyn_cast<Expr>(S)) { - Ex = peelOfOuterAddrOf(Ex); - Ex = Ex->IgnoreParenCasts(); - - if (Ex && (ExplodedGraph::isInterestingLValueExpr(Ex) - || CallEvent::isCallStmt(Ex))) - Inner = Ex; - } - - if (IsArg && !Inner) { - assert(N->getLocation().getAs<CallEnter>() && "Tracking arg but not at call"); - } else { - N = findNodeForStatement(N, S, Inner); - if (!N) - return false; - } + const Expr *Inner = peelOffOuterExpr(E, InputNode); + const ExplodedNode *LVNode = findNodeForExpression(InputNode, Inner); + if (!LVNode) + return false; - ProgramStateRef state = N->getState(); + ProgramStateRef LVState = LVNode->getState(); // 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. - if (const Expr *Receiver = NilReceiverBRVisitor::getNilReceiver(S, N)) - trackNullOrUndefValue(N, Receiver, report, /* IsArg=*/ false, - EnableNullFPSuppression); + if (const Expr *Receiver = NilReceiverBRVisitor::getNilReceiver(Inner, LVNode)) + trackExpressionValue(LVNode, Receiver, report, EnableNullFPSuppression); // See if the expression we're interested refers to a variable. // If so, we can track both its contents and constraints on its value. - if (Inner && ExplodedGraph::isInterestingLValueExpr(Inner)) { - const ExplodedNode *LVNode = findNodeForExpression(N, Inner); - ProgramStateRef LVState = LVNode->getState(); + if (ExplodedGraph::isInterestingLValueExpr(Inner)) { SVal LVal = LVNode->getSVal(Inner); - const MemRegion *RR = getLocationRegionIfReference(Inner, N); + const MemRegion *RR = getLocationRegionIfReference(Inner, LVNode); bool LVIsNull = LVState->isNull(LVal).isConstrainedTrue(); // If this is a C++ reference to a null pointer, we are tracking the // pointer. In addition, we should find the store at which the reference // got initialized. - if (RR && !LVIsNull) { + if (RR && !LVIsNull) if (auto KV = LVal.getAs<KnownSVal>()) report.addVisitor(llvm::make_unique<FindLastStoreBRVisitor>( *KV, RR, EnableNullFPSuppression)); - } // In case of C++ references, we want to differentiate between a null // reference and reference to null pointer. @@ -1602,9 +1618,8 @@ bool bugreporter::trackNullOrUndefValue(const ExplodedNode *N, llvm::make_unique<NoStoreFuncVisitor>(cast<SubRegion>(R))); MacroNullReturnSuppressionVisitor::addMacroVisitorIfNecessary( - N, R, EnableNullFPSuppression, report, V); + LVNode, R, EnableNullFPSuppression, report, V); - report.markInteresting(R); report.markInteresting(V); report.addVisitor(llvm::make_unique<UndefOrNullArgVisitor>(R)); @@ -1614,14 +1629,12 @@ bool bugreporter::trackNullOrUndefValue(const ExplodedNode *N, V.castAs<DefinedSVal>(), false)); // Add visitor, which will suppress inline defensive checks. - if (auto DV = V.getAs<DefinedSVal>()) { + if (auto DV = V.getAs<DefinedSVal>()) if (!DV->isZeroConstant() && LVState->isNull(*DV).isConstrainedTrue() && - EnableNullFPSuppression) { + EnableNullFPSuppression) report.addVisitor( llvm::make_unique<SuppressInlineDefensiveChecksVisitor>(*DV, - LVNode)); - } - } + LVNode)); if (auto KV = V.getAs<KnownSVal>()) report.addVisitor(llvm::make_unique<FindLastStoreBRVisitor>( @@ -1632,40 +1645,44 @@ bool bugreporter::trackNullOrUndefValue(const ExplodedNode *N, // If the expression is not an "lvalue expression", we can still // track the constraints on its contents. - SVal V = state->getSValAsScalarOrLoc(S, N->getLocationContext()); + SVal V = LVState->getSValAsScalarOrLoc(Inner, LVNode->getLocationContext()); - // If the value came from an inlined function call, we should at least make - // sure that function isn't pruned in our output. - if (const auto *E = dyn_cast<Expr>(S)) - S = E->IgnoreParenCasts(); + ReturnVisitor::addVisitorIfNecessary( + LVNode, Inner, report, EnableNullFPSuppression); - ReturnVisitor::addVisitorIfNecessary(N, S, report, EnableNullFPSuppression); - - // Uncomment this to find cases where we aren't properly getting the - // base value that was dereferenced. - // assert(!V.isUnknownOrUndef()); // Is it a symbolic value? if (auto L = V.getAs<loc::MemRegionVal>()) { report.addVisitor(llvm::make_unique<UndefOrNullArgVisitor>(L->getRegion())); + // FIXME: this is a hack for fixing a later crash when attempting to + // dereference a void* pointer. + // 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<SymbolicRegion>(L->getRegion())) + if (SR->getSymbol()->getType()->getPointeeType()->isVoidType()) + 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 (const auto *E = dyn_cast<Expr>(S)) - RVal = state->getRawSVal(L.getValue(), E->getType()); - else - RVal = state->getSVal(L->getRegion()); + if (ExplodedGraph::isInterestingLValueExpr(Inner)) { + RVal = LVState->getRawSVal(L.getValue(), Inner->getType()); + } else if (CanDereference) { + RVal = LVState->getSVal(L->getRegion()); + } - if (auto KV = RVal.getAs<KnownSVal>()) - report.addVisitor(llvm::make_unique<FindLastStoreBRVisitor>( + if (CanDereference) + if (auto KV = RVal.getAs<KnownSVal>()) + report.addVisitor(llvm::make_unique<FindLastStoreBRVisitor>( *KV, L->getRegion(), EnableNullFPSuppression)); const MemRegion *RegionRVal = RVal.getAsRegion(); if (RegionRVal && isa<SymbolicRegion>(RegionRVal)) { report.markInteresting(RegionRVal); report.addVisitor(llvm::make_unique<TrackConstraintBRVisitor>( - loc::MemRegionVal(RegionRVal), false)); + loc::MemRegionVal(RegionRVal), /*assumption=*/false)); } } return true; @@ -1687,7 +1704,6 @@ const Expr *NilReceiverBRVisitor::getNilReceiver(const Stmt *S, std::shared_ptr<PathDiagnosticPiece> NilReceiverBRVisitor::VisitNode(const ExplodedNode *N, - const ExplodedNode *PrevN, BugReporterContext &BRC, BugReport &BR) { Optional<PreStmt> P = N->getLocationAs<PreStmt>(); if (!P) @@ -1714,8 +1730,8 @@ 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::trackNullOrUndefValue(N, Receiver, BR, /*IsArg*/ false, - /*EnableNullFPSuppression*/ false); + bugreporter::trackExpressionValue(N, Receiver, BR, + /*EnableNullFPSuppression*/ false); // Issue a message saying that the method was skipped. PathDiagnosticLocation L(Receiver, BRC.getSourceManager(), N->getLocationContext()); @@ -1768,9 +1784,9 @@ const char *ConditionBRVisitor::getTag() { } std::shared_ptr<PathDiagnosticPiece> -ConditionBRVisitor::VisitNode(const ExplodedNode *N, const ExplodedNode *Prev, +ConditionBRVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &BRC, BugReport &BR) { - auto piece = VisitNodeImpl(N, Prev, BRC, BR); + auto piece = VisitNodeImpl(N, BRC, BR); if (piece) { piece->setTag(getTag()); if (auto *ev = dyn_cast<PathDiagnosticEventPiece>(piece.get())) @@ -1781,11 +1797,10 @@ ConditionBRVisitor::VisitNode(const ExplodedNode *N, const ExplodedNode *Prev, std::shared_ptr<PathDiagnosticPiece> ConditionBRVisitor::VisitNodeImpl(const ExplodedNode *N, - const ExplodedNode *Prev, BugReporterContext &BRC, BugReport &BR) { ProgramPoint progPoint = N->getLocation(); ProgramStateRef CurrentState = N->getState(); - ProgramStateRef PrevState = Prev->getState(); + ProgramStateRef PrevState = N->getFirstPred()->getState(); // Compare the GDMs of the state, because that is where constraints // are managed. Note that ensure that we only look at nodes that @@ -1936,8 +1951,8 @@ bool ConditionBRVisitor::patternMatch(const Expr *Ex, // Use heuristics to determine if Ex is a macro expending to a literal and // if so, use the macro's name. - SourceLocation LocStart = Ex->getLocStart(); - SourceLocation LocEnd = Ex->getLocEnd(); + SourceLocation LocStart = Ex->getBeginLoc(); + SourceLocation LocEnd = Ex->getEndLoc(); if (LocStart.isMacroID() && LocEnd.isMacroID() && (isa<GNUNullExpr>(Ex) || isa<ObjCBoolLiteralExpr>(Ex) || @@ -1951,10 +1966,10 @@ bool ConditionBRVisitor::patternMatch(const Expr *Ex, bool beginAndEndAreTheSameMacro = StartName.equals(EndName); bool partOfParentMacro = false; - if (ParentEx->getLocStart().isMacroID()) { + if (ParentEx->getBeginLoc().isMacroID()) { StringRef PName = Lexer::getImmediateMacroNameForDiagnostics( - ParentEx->getLocStart(), BRC.getSourceManager(), - BRC.getASTContext().getLangOpts()); + ParentEx->getBeginLoc(), BRC.getSourceManager(), + BRC.getASTContext().getLangOpts()); partOfParentMacro = PName.equals(StartName); } @@ -2205,7 +2220,7 @@ void LikelyFalsePositiveSuppressionBRVisitor::finalizeVisitor( // the user's fault, we currently don't report them very well, and // Note that this will not help for any other data structure libraries, like // TR1, Boost, or llvm/ADT. - if (Options.shouldSuppressFromCXXStandardLibrary()) { + if (Options.ShouldSuppressFromCXXStandardLibrary) { BR.markInvalid(getTag(), nullptr); return; } else { @@ -2277,7 +2292,6 @@ void LikelyFalsePositiveSuppressionBRVisitor::finalizeVisitor( std::shared_ptr<PathDiagnosticPiece> UndefOrNullArgVisitor::VisitNode(const ExplodedNode *N, - const ExplodedNode *PrevN, BugReporterContext &BRC, BugReport &BR) { ProgramStateRef State = N->getState(); ProgramPoint ProgLoc = N->getLocation(); @@ -2328,8 +2342,7 @@ UndefOrNullArgVisitor::VisitNode(const ExplodedNode *N, std::shared_ptr<PathDiagnosticPiece> CXXSelfAssignmentBRVisitor::VisitNode(const ExplodedNode *Succ, - const ExplodedNode *Pred, - BugReporterContext &BRC, BugReport &BR) { + BugReporterContext &BRC, BugReport &) { if (Satisfied) return nullptr; @@ -2380,11 +2393,11 @@ CXXSelfAssignmentBRVisitor::VisitNode(const ExplodedNode *Succ, } std::shared_ptr<PathDiagnosticPiece> -TaintBugVisitor::VisitNode(const ExplodedNode *N, const ExplodedNode *PrevN, - BugReporterContext &BRC, BugReport &BR) { +TaintBugVisitor::VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, BugReport &) { // Find the ExplodedNode where the taint was first introduced - if (!N->getState()->isTainted(V) || PrevN->getState()->isTainted(V)) + if (!N->getState()->isTainted(V) || N->getFirstPred()->getState()->isTainted(V)) return nullptr; const Stmt *S = PathDiagnosticLocation::getStmt(N); @@ -2406,36 +2419,43 @@ FalsePositiveRefutationBRVisitor::FalsePositiveRefutationBRVisitor() void FalsePositiveRefutationBRVisitor::finalizeVisitor( BugReporterContext &BRC, const ExplodedNode *EndPathNode, BugReport &BR) { // Collect new constraints - VisitNode(EndPathNode, nullptr, BRC, BR); + VisitNode(EndPathNode, BRC, BR); // Create a refutation manager - std::unique_ptr<SMTSolver> RefutationSolver = CreateZ3Solver(); + SMTSolverRef RefutationSolver = CreateZ3Solver(); ASTContext &Ctx = BRC.getASTContext(); // Add constraints to the solver for (const auto &I : Constraints) { - SymbolRef Sym = I.first; + const SymbolRef Sym = I.first; + auto RangeIt = I.second.begin(); - SMTExprRef Constraints = RefutationSolver->fromBoolean(false); - for (const auto &Range : I.second) { + SMTExprRef Constraints = SMTConv::getRangeExpr( + RefutationSolver, Ctx, Sym, RangeIt->From(), RangeIt->To(), + /*InRange=*/true); + while ((++RangeIt) != I.second.end()) { Constraints = RefutationSolver->mkOr( - Constraints, - RefutationSolver->getRangeExpr(Ctx, Sym, Range.From(), Range.To(), - /*InRange=*/true)); + Constraints, SMTConv::getRangeExpr(RefutationSolver, Ctx, Sym, + RangeIt->From(), RangeIt->To(), + /*InRange=*/true)); } + RefutationSolver->addConstraint(Constraints); } // And check for satisfiability - if (RefutationSolver->check().isConstrainedFalse()) + Optional<bool> isSat = RefutationSolver->check(); + if (!isSat.hasValue()) + return; + + if (!isSat.getValue()) BR.markInvalid("Infeasible constraints", EndPathNode->getLocationContext()); } std::shared_ptr<PathDiagnosticPiece> FalsePositiveRefutationBRVisitor::VisitNode(const ExplodedNode *N, - const ExplodedNode *PrevN, - BugReporterContext &BRC, - BugReport &BR) { + BugReporterContext &, + BugReport &) { // Collect new constraints const ConstraintRangeTy &NewCs = N->getState()->get<ConstraintRange>(); ConstraintRangeTy::Factory &CF = diff --git a/lib/StaticAnalyzer/Core/CMakeLists.txt b/lib/StaticAnalyzer/Core/CMakeLists.txt index de994b598e597..167f78af62892 100644 --- a/lib/StaticAnalyzer/Core/CMakeLists.txt +++ b/lib/StaticAnalyzer/Core/CMakeLists.txt @@ -20,7 +20,6 @@ add_clang_library(clangStaticAnalyzerCore CheckerContext.cpp CheckerHelpers.cpp CheckerManager.cpp - CheckerRegistry.cpp CommonBugCategories.cpp ConstraintManager.cpp CoreEngine.cpp @@ -44,14 +43,16 @@ add_clang_library(clangStaticAnalyzerCore RangeConstraintManager.cpp RangedConstraintManager.cpp RegionStore.cpp - SValBuilder.cpp - SVals.cpp + RetainSummaryManager.cpp + SarifDiagnostics.cpp SimpleConstraintManager.cpp SimpleSValBuilder.cpp - SMTConstraintManager.cpp Store.cpp SubEngine.cpp + SValBuilder.cpp + SVals.cpp SymbolManager.cpp + TaintManager.cpp WorkList.cpp Z3ConstraintManager.cpp diff --git a/lib/StaticAnalyzer/Core/CallEvent.cpp b/lib/StaticAnalyzer/Core/CallEvent.cpp index fe9260e32dd8b..0e7f31502e81a 100644 --- a/lib/StaticAnalyzer/Core/CallEvent.cpp +++ b/lib/StaticAnalyzer/Core/CallEvent.cpp @@ -169,18 +169,27 @@ bool CallEvent::isGlobalCFunction(StringRef FunctionName) const { AnalysisDeclContext *CallEvent::getCalleeAnalysisDeclContext() const { const Decl *D = getDecl(); - - // If the callee is completely unknown, we cannot construct the stack frame. if (!D) return nullptr; - // FIXME: Skip virtual functions for now. There's no easy procedure to foresee - // the exact decl that should be used, especially when it's not a definition. - if (const Decl *RD = getRuntimeDefinition().getDecl()) - if (RD != D) - return nullptr; + // TODO: For now we skip functions without definitions, even if we have + // our own getDecl(), because it's hard to find out which re-declaration + // is going to be used, and usually clients don't really care about this + // situation because there's a loss of precision anyway because we cannot + // inline the call. + RuntimeDefinition RD = getRuntimeDefinition(); + if (!RD.getDecl()) + return nullptr; + + AnalysisDeclContext *ADC = + LCtx->getAnalysisDeclContext()->getManager()->getContext(D); + + // TODO: For now we skip virtual functions, because this also rises + // the problem of which decl to use, but now it's across different classes. + if (RD.mayHaveOtherDefinitions() || RD.getDecl() != ADC->getDecl()) + return nullptr; - return LCtx->getAnalysisDeclContext()->getManager()->getContext(D); + return ADC; } const StackFrameContext *CallEvent::getCalleeStackFrame() const { @@ -218,7 +227,24 @@ const VarRegion *CallEvent::getParameterLocation(unsigned Index) const { if (!SFC) return nullptr; - const ParmVarDecl *PVD = parameters()[Index]; + // Retrieve parameters of the definition, which are different from + // CallEvent's parameters() because getDecl() isn't necessarily + // the definition. SFC contains the definition that would be used + // during analysis. + const Decl *D = SFC->getDecl(); + + // TODO: Refactor into a virtual method of CallEvent, like parameters(). + const ParmVarDecl *PVD = nullptr; + if (const auto *FD = dyn_cast<FunctionDecl>(D)) + PVD = FD->parameters()[Index]; + else if (const auto *BD = dyn_cast<BlockDecl>(D)) + PVD = BD->parameters()[Index]; + else if (const auto *MD = dyn_cast<ObjCMethodDecl>(D)) + PVD = MD->parameters()[Index]; + else if (const auto *CD = dyn_cast<CXXConstructorDecl>(D)) + PVD = CD->parameters()[Index]; + assert(PVD && "Unexpected Decl kind!"); + const VarRegion *VR = State->getStateManager().getRegionManager().getVarRegion(PVD, SFC); @@ -285,6 +311,20 @@ ProgramStateRef CallEvent::invalidateRegions(unsigned BlockCount, // TODO: Factor this out + handle the lower level const pointers. ValuesToInvalidate.push_back(getArgSVal(Idx)); + + // If a function accepts an object by argument (which would of course be a + // temporary that isn't lifetime-extended), invalidate the object itself, + // not only other objects reachable from it. This is necessary because the + // destructor has access to the temporary object after the call. + // TODO: Support placement arguments once we start + // constructing them directly. + // TODO: This is unnecessary when there's no destructor, but that's + // currently hard to figure out. + if (getKind() != CE_CXXAllocator) + if (isArgumentConstructedDirectly(Idx)) + if (auto AdjIdx = getAdjustedParameterIndex(Idx)) + if (const VarRegion *VR = getParameterLocation(*AdjIdx)) + ValuesToInvalidate.push_back(loc::MemRegionVal(VR)); } // Invalidate designated regions using the batch invalidation API. @@ -319,11 +359,41 @@ bool CallEvent::isCalled(const CallDescription &CD) const { return false; if (!CD.IsLookupDone) { CD.IsLookupDone = true; - CD.II = &getState()->getStateManager().getContext().Idents.get(CD.FuncName); + CD.II = &getState()->getStateManager().getContext().Idents.get( + CD.getFunctionName()); } const IdentifierInfo *II = getCalleeIdentifier(); if (!II || 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(); + // See if we'll be able to match them all. + size_t NumUnmatched = CD.QualifiedName.size() - 1; + for (; Ctx && isa<NamedDecl>(Ctx); Ctx = Ctx->getParent()) { + if (NumUnmatched == 0) + break; + + if (const auto *ND = dyn_cast<NamespaceDecl>(Ctx)) { + if (ND->getName() == CD.QualifiedName[NumUnmatched - 1]) + --NumUnmatched; + continue; + } + + if (const auto *RD = dyn_cast<RecordDecl>(Ctx)) { + if (RD->getName() == CD.QualifiedName[NumUnmatched - 1]) + --NumUnmatched; + continue; + } + } + + if (NumUnmatched > 0) + return false; + } + return (CD.RequiredArgs == CallDescription::NoArgRequirement || CD.RequiredArgs == getNumArgs()); } @@ -433,6 +503,14 @@ static void addParameterValuesToBindings(const StackFrameContext *CalleeCtx, const ParmVarDecl *ParamDecl = *I; assert(ParamDecl && "Formal parameter has no decl?"); + // TODO: Support allocator calls. + if (Call.getKind() != CE_CXXAllocator) + if (Call.isArgumentConstructedDirectly(Idx)) + continue; + + // TODO: Allocators should receive the correct size and possibly alignment, + // determined in compile-time but not represented as arg-expressions, + // which makes getArgSVal() fail and return UnknownVal. SVal ArgVal = Call.getArgSVal(Idx); if (!ArgVal.isUnknown()) { Loc ParamLoc = SVB.makeLoc(MRMgr.getVarRegion(ParamDecl, CalleeCtx)); @@ -472,17 +550,18 @@ RuntimeDefinition AnyFunctionCall::getRuntimeDefinition() const { return RuntimeDefinition(Decl); } - SubEngine *Engine = getState()->getStateManager().getOwningEngine(); - AnalyzerOptions &Opts = Engine->getAnalysisManager().options; + SubEngine &Engine = getState()->getStateManager().getOwningEngine(); + AnalyzerOptions &Opts = Engine.getAnalysisManager().options; // Try to get CTU definition only if CTUDir is provided. - if (!Opts.naiveCTUEnabled()) + if (!Opts.IsNaiveCTUEnabled) return {}; cross_tu::CrossTranslationUnitContext &CTUCtx = - *Engine->getCrossTranslationUnitContext(); + *Engine.getCrossTranslationUnitContext(); llvm::Expected<const FunctionDecl *> CTUDeclOrError = - CTUCtx.getCrossTUDefinition(FD, Opts.getCTUDir(), Opts.getCTUIndexName()); + CTUCtx.getCrossTUDefinition(FD, Opts.CTUDir, Opts.CTUIndexName, + Opts.DisplayCTUProgress); if (!CTUDeclOrError) { handleAllErrors(CTUDeclOrError.takeError(), @@ -758,7 +837,7 @@ const BlockDataRegion *BlockCall::getBlockRegion() const { ArrayRef<ParmVarDecl*> BlockCall::parameters() const { const BlockDecl *D = getDecl(); if (!D) - return nullptr; + return None; return D->parameters(); } @@ -1008,7 +1087,7 @@ bool ObjCMethodCall::canBeOverridenInSubclass(ObjCInterfaceDecl *IDecl, Selector Sel) const { assert(IDecl); AnalysisManager &AMgr = - getState()->getStateManager().getOwningEngine()->getAnalysisManager(); + getState()->getStateManager().getOwningEngine().getAnalysisManager(); // If the class interface is declared inside the main file, assume it is not // subcassed. // TODO: It could actually be subclassed if the subclass is private as well. @@ -1290,28 +1369,20 @@ CallEventManager::getCaller(const StackFrameContext *CalleeCtx, const Stmt *CallSite = CalleeCtx->getCallSite(); if (CallSite) { - if (const CallExpr *CE = dyn_cast<CallExpr>(CallSite)) - return getSimpleCall(CE, State, CallerCtx); - - switch (CallSite->getStmtClass()) { - case Stmt::CXXConstructExprClass: - case Stmt::CXXTemporaryObjectExprClass: { - SValBuilder &SVB = State->getStateManager().getSValBuilder(); - const auto *Ctor = cast<CXXMethodDecl>(CalleeCtx->getDecl()); - Loc ThisPtr = SVB.getCXXThis(Ctor, CalleeCtx); - SVal ThisVal = State->getSVal(ThisPtr); - - return getCXXConstructorCall(cast<CXXConstructExpr>(CallSite), - ThisVal.getAsRegion(), State, CallerCtx); - } - case Stmt::CXXNewExprClass: - return getCXXAllocatorCall(cast<CXXNewExpr>(CallSite), State, CallerCtx); - case Stmt::ObjCMessageExprClass: - return getObjCMethodCall(cast<ObjCMessageExpr>(CallSite), - State, CallerCtx); - default: - llvm_unreachable("This is not an inlineable statement."); - } + if (CallEventRef<> Out = getCall(CallSite, State, CallerCtx)) + return Out; + + // All other cases are handled by getCall. + assert(isa<CXXConstructExpr>(CallSite) && + "This is not an inlineable statement"); + + SValBuilder &SVB = State->getStateManager().getSValBuilder(); + const auto *Ctor = cast<CXXMethodDecl>(CalleeCtx->getDecl()); + Loc ThisPtr = SVB.getCXXThis(Ctor, CalleeCtx); + SVal ThisVal = State->getSVal(ThisPtr); + + return getCXXConstructorCall(cast<CXXConstructExpr>(CallSite), + ThisVal.getAsRegion(), State, CallerCtx); } // Fall back to the CFG. The only thing we haven't handled yet is @@ -1338,3 +1409,16 @@ CallEventManager::getCaller(const StackFrameContext *CalleeCtx, E.getAs<CFGBaseDtor>().hasValue(), State, CallerCtx); } + +CallEventRef<> CallEventManager::getCall(const Stmt *S, ProgramStateRef State, + const LocationContext *LC) { + if (const auto *CE = dyn_cast<CallExpr>(S)) { + return getSimpleCall(CE, State, LC); + } else if (const auto *NE = dyn_cast<CXXNewExpr>(S)) { + return getCXXAllocatorCall(NE, State, LC); + } else if (const auto *ME = dyn_cast<ObjCMessageExpr>(S)) { + return getObjCMethodCall(ME, State, LC); + } else { + return nullptr; + } +} diff --git a/lib/StaticAnalyzer/Core/Checker.cpp b/lib/StaticAnalyzer/Core/Checker.cpp index b422a8871983b..72bfd84b40a33 100644 --- a/lib/StaticAnalyzer/Core/Checker.cpp +++ b/lib/StaticAnalyzer/Core/Checker.cpp @@ -17,6 +17,8 @@ using namespace clang; using namespace ento; +int ImplicitNullDerefEvent::Tag; + StringRef CheckerBase::getTagDescription() const { return getCheckName().getName(); } diff --git a/lib/StaticAnalyzer/Core/CheckerHelpers.cpp b/lib/StaticAnalyzer/Core/CheckerHelpers.cpp index b9facffcc8b51..e73a22ae39818 100644 --- a/lib/StaticAnalyzer/Core/CheckerHelpers.cpp +++ b/lib/StaticAnalyzer/Core/CheckerHelpers.cpp @@ -21,10 +21,10 @@ namespace ento { // Recursively find any substatements containing macros bool containsMacro(const Stmt *S) { - if (S->getLocStart().isMacroID()) + if (S->getBeginLoc().isMacroID()) return true; - if (S->getLocEnd().isMacroID()) + if (S->getEndLoc().isMacroID()) return true; for (const Stmt *Child : S->children()) @@ -103,9 +103,9 @@ Nullability getNullabilityAnnotation(QualType Type) { const auto *AttrType = Type->getAs<AttributedType>(); if (!AttrType) return Nullability::Unspecified; - if (AttrType->getAttrKind() == AttributedType::attr_nullable) + if (AttrType->getAttrKind() == attr::TypeNullable) return Nullability::Nullable; - else if (AttrType->getAttrKind() == AttributedType::attr_nonnull) + else if (AttrType->getAttrKind() == attr::TypeNonNull) return Nullability::Nonnull; return Nullability::Unspecified; } diff --git a/lib/StaticAnalyzer/Core/CheckerManager.cpp b/lib/StaticAnalyzer/Core/CheckerManager.cpp index 712872a15d8a0..688c47e984cce 100644 --- a/lib/StaticAnalyzer/Core/CheckerManager.cpp +++ b/lib/StaticAnalyzer/Core/CheckerManager.cpp @@ -441,14 +441,13 @@ void CheckerManager::runCheckersForEndFunction(NodeBuilderContext &BC, ExplodedNode *Pred, ExprEngine &Eng, const ReturnStmt *RS) { - // We define the builder outside of the loop bacause if at least one checkers - // creates a sucsessor for Pred, we do not need to generate an + // We define the builder outside of the loop because if at least one checker + // creates a successor for Pred, we do not need to generate an // autotransition for it. NodeBuilder Bldr(Pred, Dst, BC); for (const auto checkFn : EndFunctionCheckers) { - const ProgramPoint &L = BlockEntrance(BC.Block, - Pred->getLocationContext(), - checkFn.Checker); + const ProgramPoint &L = + FunctionExitPoint(RS, Pred->getLocationContext(), checkFn.Checker); CheckerContext C(Bldr, Eng, Pred, L); checkFn(RS, C); } diff --git a/lib/StaticAnalyzer/Core/CheckerRegistry.cpp b/lib/StaticAnalyzer/Core/CheckerRegistry.cpp deleted file mode 100644 index 645845ec2181f..0000000000000 --- a/lib/StaticAnalyzer/Core/CheckerRegistry.cpp +++ /dev/null @@ -1,190 +0,0 @@ -//===- CheckerRegistry.cpp - Maintains all available checkers -------------===// -// -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// - -#include "clang/StaticAnalyzer/Core/CheckerRegistry.h" -#include "clang/Basic/Diagnostic.h" -#include "clang/Basic/LLVM.h" -#include "clang/Frontend/FrontendDiagnostic.h" -#include "clang/StaticAnalyzer/Core/CheckerManager.h" -#include "clang/StaticAnalyzer/Core/CheckerOptInfo.h" -#include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" -#include "llvm/ADT/STLExtras.h" -#include "llvm/ADT/SetVector.h" -#include "llvm/ADT/StringMap.h" -#include "llvm/ADT/StringRef.h" -#include "llvm/Support/raw_ostream.h" -#include <algorithm> -#include <cstddef> -#include <tuple> - -using namespace clang; -using namespace ento; - -static const char PackageSeparator = '.'; - -using CheckerInfoSet = llvm::SetVector<const CheckerRegistry::CheckerInfo *>; - -static bool checkerNameLT(const CheckerRegistry::CheckerInfo &a, - const CheckerRegistry::CheckerInfo &b) { - return a.FullName < b.FullName; -} - -static bool isInPackage(const CheckerRegistry::CheckerInfo &checker, - StringRef packageName) { - // Does the checker's full name have the package as a prefix? - if (!checker.FullName.startswith(packageName)) - return false; - - // Is the package actually just the name of a specific checker? - if (checker.FullName.size() == packageName.size()) - return true; - - // Is the checker in the package (or a subpackage)? - if (checker.FullName[packageName.size()] == PackageSeparator) - return true; - - return false; -} - -static void collectCheckers(const CheckerRegistry::CheckerInfoList &checkers, - const llvm::StringMap<size_t> &packageSizes, - CheckerOptInfo &opt, CheckerInfoSet &collected) { - // Use a binary search to find the possible start of the package. - CheckerRegistry::CheckerInfo packageInfo(nullptr, opt.getName(), ""); - auto end = checkers.cend(); - auto i = std::lower_bound(checkers.cbegin(), end, packageInfo, checkerNameLT); - - // If we didn't even find a possible package, give up. - if (i == end) - return; - - // If what we found doesn't actually start the package, give up. - if (!isInPackage(*i, opt.getName())) - return; - - // There is at least one checker in the package; claim the option. - opt.claim(); - - // See how large the package is. - // If the package doesn't exist, assume the option refers to a single checker. - size_t size = 1; - llvm::StringMap<size_t>::const_iterator packageSize = - packageSizes.find(opt.getName()); - if (packageSize != packageSizes.end()) - size = packageSize->getValue(); - - // Step through all the checkers in the package. - for (auto checkEnd = i+size; i != checkEnd; ++i) - if (opt.isEnabled()) - collected.insert(&*i); - else - collected.remove(&*i); -} - -void CheckerRegistry::addChecker(InitializationFunction fn, StringRef name, - StringRef desc) { - Checkers.push_back(CheckerInfo(fn, name, desc)); - - // Record the presence of the checker in its packages. - StringRef packageName, leafName; - std::tie(packageName, leafName) = name.rsplit(PackageSeparator); - while (!leafName.empty()) { - Packages[packageName] += 1; - std::tie(packageName, leafName) = packageName.rsplit(PackageSeparator); - } -} - -void CheckerRegistry::initializeManager(CheckerManager &checkerMgr, - SmallVectorImpl<CheckerOptInfo> &opts) const { - // Sort checkers for efficient collection. - llvm::sort(Checkers.begin(), Checkers.end(), checkerNameLT); - - // Collect checkers enabled by the options. - CheckerInfoSet enabledCheckers; - for (auto &i : opts) - collectCheckers(Checkers, Packages, i, enabledCheckers); - - // Initialize the CheckerManager with all enabled checkers. - for (const auto *i :enabledCheckers) { - checkerMgr.setCurrentCheckName(CheckName(i->FullName)); - i->Initialize(checkerMgr); - } -} - -void CheckerRegistry::validateCheckerOptions(const AnalyzerOptions &opts, - DiagnosticsEngine &diags) const { - for (const auto &config : opts.Config) { - size_t pos = config.getKey().find(':'); - if (pos == StringRef::npos) - continue; - - bool hasChecker = false; - StringRef checkerName = config.getKey().substr(0, pos); - for (const auto &checker : Checkers) { - if (checker.FullName.startswith(checkerName) && - (checker.FullName.size() == pos || checker.FullName[pos] == '.')) { - hasChecker = true; - break; - } - } - if (!hasChecker) - diags.Report(diag::err_unknown_analyzer_checker) << checkerName; - } -} - -void CheckerRegistry::printHelp(raw_ostream &out, - size_t maxNameChars) const { - // FIXME: Alphabetical sort puts 'experimental' in the middle. - // Would it be better to name it '~experimental' or something else - // that's ASCIIbetically last? - llvm::sort(Checkers.begin(), Checkers.end(), checkerNameLT); - - // FIXME: Print available packages. - - out << "CHECKERS:\n"; - - // Find the maximum option length. - size_t optionFieldWidth = 0; - for (const auto &i : Checkers) { - // Limit the amount of padding we are willing to give up for alignment. - // Package.Name Description [Hidden] - size_t nameLength = i.FullName.size(); - if (nameLength <= maxNameChars) - optionFieldWidth = std::max(optionFieldWidth, nameLength); - } - - const size_t initialPad = 2; - for (const auto &i : Checkers) { - out.indent(initialPad) << i.FullName; - - int pad = optionFieldWidth - i.FullName.size(); - - // Break on long option names. - if (pad < 0) { - out << '\n'; - pad = optionFieldWidth + initialPad; - } - out.indent(pad + 2) << i.Desc; - - out << '\n'; - } -} - -void CheckerRegistry::printList( - raw_ostream &out, SmallVectorImpl<CheckerOptInfo> &opts) const { - llvm::sort(Checkers.begin(), Checkers.end(), checkerNameLT); - - // Collect checkers enabled by the options. - CheckerInfoSet enabledCheckers; - for (auto &i : opts) - collectCheckers(Checkers, Packages, i, enabledCheckers); - - for (const auto *i : enabledCheckers) - out << i->FullName << '\n'; -} diff --git a/lib/StaticAnalyzer/Core/CommonBugCategories.cpp b/lib/StaticAnalyzer/Core/CommonBugCategories.cpp index 421dfa48c97bd..cdae3ef0116a4 100644 --- a/lib/StaticAnalyzer/Core/CommonBugCategories.cpp +++ b/lib/StaticAnalyzer/Core/CommonBugCategories.cpp @@ -14,8 +14,8 @@ namespace clang { namespace ento { namespace categories { const char * const CoreFoundationObjectiveC = "Core Foundation/Objective-C"; const char * const LogicError = "Logic error"; -const char * const MemoryCoreFoundationObjectiveC = - "Memory (Core Foundation/Objective-C)"; +const char * const MemoryRefCount = + "Memory (Core Foundation/Objective-C/OSObject)"; const char * const MemoryError = "Memory error"; const char * const UnixAPI = "Unix API"; }}} diff --git a/lib/StaticAnalyzer/Core/CoreEngine.cpp b/lib/StaticAnalyzer/Core/CoreEngine.cpp index c17b6aae37e2f..196854cb09da7 100644 --- a/lib/StaticAnalyzer/Core/CoreEngine.cpp +++ b/lib/StaticAnalyzer/Core/CoreEngine.cpp @@ -53,26 +53,28 @@ STATISTIC(NumPathsExplored, // Core analysis engine. //===----------------------------------------------------------------------===// -static std::unique_ptr<WorkList> generateWorkList(AnalyzerOptions &Opts) { +static std::unique_ptr<WorkList> generateWorkList(AnalyzerOptions &Opts, + SubEngine &subengine) { switch (Opts.getExplorationStrategy()) { - case AnalyzerOptions::ExplorationStrategyKind::DFS: + case ExplorationStrategyKind::DFS: return WorkList::makeDFS(); - case AnalyzerOptions::ExplorationStrategyKind::BFS: + case ExplorationStrategyKind::BFS: return WorkList::makeBFS(); - case AnalyzerOptions::ExplorationStrategyKind::BFSBlockDFSContents: + case ExplorationStrategyKind::BFSBlockDFSContents: return WorkList::makeBFSBlockDFSContents(); - case AnalyzerOptions::ExplorationStrategyKind::UnexploredFirst: + case ExplorationStrategyKind::UnexploredFirst: return WorkList::makeUnexploredFirst(); - case AnalyzerOptions::ExplorationStrategyKind::UnexploredFirstQueue: + case ExplorationStrategyKind::UnexploredFirstQueue: return WorkList::makeUnexploredFirstPriorityQueue(); - default: - llvm_unreachable("Unexpected case"); + case ExplorationStrategyKind::UnexploredFirstLocationQueue: + return WorkList::makeUnexploredFirstPriorityLocationQueue(); } + llvm_unreachable("Unknown AnalyzerOptions::ExplorationStrategyKind"); } CoreEngine::CoreEngine(SubEngine &subengine, FunctionSummariesTy *FS, AnalyzerOptions &Opts) - : SubEng(subengine), WList(generateWorkList(Opts)), + : SubEng(subengine), WList(generateWorkList(Opts, subengine)), BCounterFactory(G.getAllocator()), FunctionSummaries(FS) {} /// ExecuteWorkList - Run the worklist algorithm for a maximum number of steps. @@ -146,7 +148,7 @@ bool CoreEngine::ExecuteWorkList(const LocationContext *L, unsigned Steps, dispatchWorkItem(Node, Node->getLocation(), WU); } - SubEng.processEndWorklist(hasWorkRemaining()); + SubEng.processEndWorklist(); return WList->hasWork(); } @@ -223,8 +225,12 @@ void CoreEngine::HandleBlockEdge(const BlockEdge &L, ExplodedNode *Pred) { // Get return statement.. const ReturnStmt *RS = nullptr; if (!L.getSrc()->empty()) { - if (Optional<CFGStmt> LastStmt = L.getSrc()->back().getAs<CFGStmt>()) { + CFGElement LastElement = L.getSrc()->back(); + if (Optional<CFGStmt> LastStmt = LastElement.getAs<CFGStmt>()) { RS = dyn_cast<ReturnStmt>(LastStmt->getStmt()); + } else if (Optional<CFGAutomaticObjDtor> AutoDtor = + LastElement.getAs<CFGAutomaticObjDtor>()) { + RS = dyn_cast<ReturnStmt>(AutoDtor->getTriggerStmt()); } } @@ -392,8 +398,8 @@ void CoreEngine::HandleBranch(const Stmt *Cond, const Stmt *Term, assert(B->succ_size() == 2); NodeBuilderContext Ctx(*this, B, Pred); ExplodedNodeSet Dst; - SubEng.processBranch(Cond, Term, Ctx, Pred, Dst, - *(B->succ_begin()), *(B->succ_begin()+1)); + SubEng.processBranch(Cond, Ctx, Pred, Dst, *(B->succ_begin()), + *(B->succ_begin() + 1)); // Enqueue the new frontier onto the worklist. enqueue(Dst); } diff --git a/lib/StaticAnalyzer/Core/DynamicTypeMap.cpp b/lib/StaticAnalyzer/Core/DynamicTypeMap.cpp index 530933916889c..da7854df1def0 100644 --- a/lib/StaticAnalyzer/Core/DynamicTypeMap.cpp +++ b/lib/StaticAnalyzer/Core/DynamicTypeMap.cpp @@ -77,5 +77,10 @@ void printDynamicTypeInfo(ProgramStateRef State, raw_ostream &Out, } } +void *ProgramStateTrait<DynamicTypeMap>::GDMIndex() { + static int index = 0; + return &index; +} + } // namespace ento } // namespace clang diff --git a/lib/StaticAnalyzer/Core/Environment.cpp b/lib/StaticAnalyzer/Core/Environment.cpp index eccaee292c403..b45f93b6dde83 100644 --- a/lib/StaticAnalyzer/Core/Environment.cpp +++ b/lib/StaticAnalyzer/Core/Environment.cpp @@ -44,6 +44,9 @@ static const Expr *ignoreTransparentExprs(const Expr *E) { case Stmt::ExprWithCleanupsClass: E = cast<ExprWithCleanups>(E)->getSubExpr(); break; + case Stmt::ConstantExprClass: + E = cast<ConstantExpr>(E)->getSubExpr(); + break; case Stmt::CXXBindTemporaryExprClass: E = cast<CXXBindTemporaryExpr>(E)->getSubExpr(); break; @@ -89,6 +92,7 @@ SVal Environment::getSVal(const EnvironmentEntry &Entry, case Stmt::ExprWithCleanupsClass: case Stmt::GenericSelectionExprClass: case Stmt::OpaqueValueExprClass: + case Stmt::ConstantExprClass: case Stmt::ParenExprClass: case Stmt::SubstNonTypeTemplateParmExprClass: llvm_unreachable("Should have been handled by ignoreTransparentExprs"); @@ -189,11 +193,6 @@ EnvironmentManager::removeDeadBindings(Environment Env, // Mark all symbols in the block expr's value live. RSScaner.scan(X); - continue; - } else { - SymExpr::symbol_iterator SI = X.symbol_begin(), SE = X.symbol_end(); - for (; SI != SE; ++SI) - SymReaper.maybeDead(*SI); } } @@ -202,7 +201,9 @@ EnvironmentManager::removeDeadBindings(Environment Env, } void Environment::print(raw_ostream &Out, const char *NL, - const char *Sep, const LocationContext *WithLC) const { + const char *Sep, + const ASTContext &Context, + const LocationContext *WithLC) const { if (ExprBindings.isEmpty()) return; @@ -222,10 +223,9 @@ void Environment::print(raw_ostream &Out, const char *NL, assert(WithLC); - LangOptions LO; // FIXME. - PrintingPolicy PP(LO); + PrintingPolicy PP = Context.getPrintingPolicy(); - Out << NL << NL << "Expressions by stack frame:" << NL; + Out << NL << "Expressions by stack frame:" << NL; WithLC->dumpStack(Out, "", NL, Sep, [&](const LocationContext *LC) { for (auto I : ExprBindings) { if (I.first.getLocationContext() != LC) @@ -234,8 +234,8 @@ void Environment::print(raw_ostream &Out, const char *NL, const Stmt *S = I.first.getStmt(); assert(S != nullptr && "Expected non-null Stmt"); - Out << "(" << (const void *)LC << ',' << (const void *)S << ") "; - S->printPretty(Out, nullptr, PP); + Out << "(LC" << LC->getID() << ", S" << S->getID(Context) << ") "; + S->printPretty(Out, /*Helper=*/nullptr, PP); Out << " : " << I.second << NL; } }); diff --git a/lib/StaticAnalyzer/Core/ExplodedGraph.cpp b/lib/StaticAnalyzer/Core/ExplodedGraph.cpp index ece103d9d09ad..d6bcbb96b55f1 100644 --- a/lib/StaticAnalyzer/Core/ExplodedGraph.cpp +++ b/lib/StaticAnalyzer/Core/ExplodedGraph.cpp @@ -36,23 +36,6 @@ using namespace clang; using namespace ento; //===----------------------------------------------------------------------===// -// Node auditing. -//===----------------------------------------------------------------------===// - -// An out of line virtual method to provide a home for the class vtable. -ExplodedNode::Auditor::~Auditor() = default; - -#ifndef NDEBUG -static ExplodedNode::Auditor* NodeAuditor = nullptr; -#endif - -void ExplodedNode::SetAuditor(ExplodedNode::Auditor* A) { -#ifndef NDEBUG - NodeAuditor = A; -#endif -} - -//===----------------------------------------------------------------------===// // Cleanup. //===----------------------------------------------------------------------===// @@ -224,9 +207,6 @@ void ExplodedNode::addPredecessor(ExplodedNode *V, ExplodedGraph &G) { assert(!V->isSink()); Preds.addNode(V, G); V->Succs.addNode(this, G); -#ifndef NDEBUG - if (NodeAuditor) NodeAuditor->AddEdge(V, this); -#endif } void ExplodedNode::NodeGroup::replaceNode(ExplodedNode *node) { @@ -303,6 +283,16 @@ ExplodedNode * const *ExplodedNode::NodeGroup::end() const { return Storage.getAddrOfPtr1() + 1; } +int64_t ExplodedNode::getID(ExplodedGraph *G) const { + return G->getAllocator().identifyKnownAlignedObject<ExplodedNode>(this); +} + +bool ExplodedNode::isTrivial() const { + return pred_size() == 1 && succ_size() == 1 && + getFirstPred()->getState()->getID() == getState()->getID() && + getFirstPred()->succ_size() == 1; +} + ExplodedNode *ExplodedGraph::getNode(const ProgramPoint &L, ProgramStateRef State, bool IsSink, diff --git a/lib/StaticAnalyzer/Core/ExprEngine.cpp b/lib/StaticAnalyzer/Core/ExprEngine.cpp index 2b4bdd754fdbc..151eef56fece8 100644 --- a/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -98,11 +98,12 @@ STATISTIC(NumMaxBlockCountReachedInInlined, STATISTIC(NumTimesRetriedWithoutInlining, "The # of times we re-evaluated a call without inlining"); - //===----------------------------------------------------------------------===// // Internal program state traits. //===----------------------------------------------------------------------===// +namespace { + // When modeling a C++ constructor, for a variety of reasons we need to track // the location of the object for the duration of its ConstructionContext. // ObjectsUnderConstruction maps statements within the construction context @@ -137,9 +138,17 @@ public: const ConstructionContextItem &getItem() const { return Impl.first; } const LocationContext *getLocationContext() const { return Impl.second; } + ASTContext &getASTContext() const { + return getLocationContext()->getDecl()->getASTContext(); + } + void print(llvm::raw_ostream &OS, PrinterHelper *Helper, PrintingPolicy &PP) { - OS << '(' << getLocationContext() << ',' << getAnyASTNodePtr() << ',' - << getItem().getKindAsString(); + OS << "(LC" << getLocationContext()->getID() << ','; + if (const Stmt *S = getItem().getStmtOrNull()) + OS << 'S' << S->getID(getASTContext()); + else + OS << 'I' << getItem().getCXXCtorInitializer()->getID(getASTContext()); + OS << ',' << getItem().getKindAsString(); if (getItem().getKind() == ConstructionContextItem::ArgumentKind) OS << " #" << getItem().getIndex(); OS << ") "; @@ -164,6 +173,7 @@ public: return Impl < RHS.Impl; } }; +} // namespace typedef llvm::ImmutableMap<ConstructedObjectKey, SVal> ObjectsUnderConstructionMap; @@ -177,7 +187,7 @@ REGISTER_TRAIT_WITH_PROGRAMSTATE(ObjectsUnderConstruction, static const char* TagProviderName = "ExprEngine"; ExprEngine::ExprEngine(cross_tu::CrossTranslationUnitContext &CTU, - AnalysisManager &mgr, bool gcEnabled, + AnalysisManager &mgr, SetOfConstDecls *VisitedCalleesIn, FunctionSummariesTy *FS, InliningModes HowToInlineIn) @@ -189,11 +199,11 @@ ExprEngine::ExprEngine(cross_tu::CrossTranslationUnitContext &CTU, this), SymMgr(StateMgr.getSymbolManager()), svalBuilder(StateMgr.getSValBuilder()), ObjCNoRet(mgr.getASTContext()), - ObjCGCEnabled(gcEnabled), BR(mgr, *this), + BR(mgr, *this), VisitedCallees(VisitedCalleesIn), HowToInline(HowToInlineIn) { - unsigned TrimInterval = mgr.options.getGraphTrimInterval(); + unsigned TrimInterval = mgr.options.GraphTrimInterval; if (TrimInterval != 0) { - // Enable eager node reclaimation when constructing the ExplodedGraph. + // Enable eager node reclamation when constructing the ExplodedGraph. G.enableNodeReclamation(TrimInterval); } } @@ -283,11 +293,10 @@ ProgramStateRef ExprEngine::getInitialState(const LocationContext *InitLoc) { return state; } -ProgramStateRef -ExprEngine::createTemporaryRegionIfNeeded(ProgramStateRef State, - const LocationContext *LC, - const Expr *InitWithAdjustments, - const Expr *Result) { +ProgramStateRef ExprEngine::createTemporaryRegionIfNeeded( + ProgramStateRef State, const LocationContext *LC, + const Expr *InitWithAdjustments, const Expr *Result, + const SubRegion **OutRegionWithAdjustments) { // FIXME: This function is a hack that works around the quirky AST // we're often having with respect to C++ temporaries. If only we modelled // the actual execution order of statements properly in the CFG, @@ -297,8 +306,11 @@ ExprEngine::createTemporaryRegionIfNeeded(ProgramStateRef State, if (!Result) { // If we don't have an explicit result expression, we're in "if needed" // mode. Only create a region if the current value is a NonLoc. - if (!InitValWithAdjustments.getAs<NonLoc>()) + if (!InitValWithAdjustments.getAs<NonLoc>()) { + if (OutRegionWithAdjustments) + *OutRegionWithAdjustments = nullptr; return State; + } Result = InitWithAdjustments; } else { // We need to create a region no matter what. For sanity, make sure we don't @@ -418,11 +430,17 @@ ExprEngine::createTemporaryRegionIfNeeded(ProgramStateRef State, // The result expression would now point to the correct sub-region of the // newly created temporary region. Do this last in order to getSVal of Init // correctly in case (Result == Init). - State = State->BindExpr(Result, LC, Reg); + if (Result->isGLValue()) { + State = State->BindExpr(Result, LC, Reg); + } else { + State = State->BindExpr(Result, LC, InitValWithAdjustments); + } // Notify checkers once for two bindLoc()s. State = processRegionChange(State, TR, LC); + if (OutRegionWithAdjustments) + *OutRegionWithAdjustments = cast<SubRegion>(Reg.getAsRegion()); return State; } @@ -523,7 +541,6 @@ ExprEngine::processRegionChanges(ProgramStateRef state, static void printObjectsUnderConstructionForContext(raw_ostream &Out, ProgramStateRef State, const char *NL, - const char *Sep, const LocationContext *LC) { PrintingPolicy PP = LC->getAnalysisDeclContext()->getASTContext().getPrintingPolicy(); @@ -545,7 +562,7 @@ void ExprEngine::printState(raw_ostream &Out, ProgramStateRef State, Out << Sep << "Objects under construction:" << NL; LCtx->dumpStack(Out, "", NL, Sep, [&](const LocationContext *LC) { - printObjectsUnderConstructionForContext(Out, State, NL, Sep, LC); + printObjectsUnderConstructionForContext(Out, State, NL, LC); }); } } @@ -553,7 +570,7 @@ void ExprEngine::printState(raw_ostream &Out, ProgramStateRef State, getCheckerManager().runCheckersForPrintState(Out, State, NL, Sep); } -void ExprEngine::processEndWorklist(bool hasWorkRemaining) { +void ExprEngine::processEndWorklist() { getCheckerManager().runCheckersForEndAnalysis(G, BR, *this); } @@ -666,44 +683,35 @@ void ExprEngine::removeDead(ExplodedNode *Pred, ExplodedNodeSet &Out, // Process any special transfer function for dead symbols. // A tag to track convenience transitions, which can be removed at cleanup. static SimpleProgramPointTag cleanupTag(TagProviderName, "Clean Node"); - if (!SymReaper.hasDeadSymbols()) { - // Generate a CleanedNode that has the environment and store cleaned - // up. Since no symbols are dead, we can optimize and not clean out - // the constraint manager. - StmtNodeBuilder Bldr(Pred, Out, *currBldrCtx); - Bldr.generateNode(DiagnosticStmt, Pred, CleanedState, &cleanupTag, K); - - } else { - // Call checkers with the non-cleaned state so that they could query the - // values of the soon to be dead symbols. - ExplodedNodeSet CheckedSet; - getCheckerManager().runCheckersForDeadSymbols(CheckedSet, Pred, SymReaper, - DiagnosticStmt, *this, K); - - // For each node in CheckedSet, generate CleanedNodes that have the - // environment, the store, and the constraints cleaned up but have the - // user-supplied states as the predecessors. - StmtNodeBuilder Bldr(CheckedSet, Out, *currBldrCtx); - for (const auto I : CheckedSet) { - ProgramStateRef CheckerState = I->getState(); - - // The constraint manager has not been cleaned up yet, so clean up now. - CheckerState = getConstraintManager().removeDeadBindings(CheckerState, - SymReaper); - - assert(StateMgr.haveEqualEnvironments(CheckerState, Pred->getState()) && - "Checkers are not allowed to modify the Environment as a part of " - "checkDeadSymbols processing."); - assert(StateMgr.haveEqualStores(CheckerState, Pred->getState()) && - "Checkers are not allowed to modify the Store as a part of " - "checkDeadSymbols processing."); - - // Create a state based on CleanedState with CheckerState GDM and - // generate a transition to that state. - ProgramStateRef CleanedCheckerSt = + // Call checkers with the non-cleaned state so that they could query the + // values of the soon to be dead symbols. + ExplodedNodeSet CheckedSet; + getCheckerManager().runCheckersForDeadSymbols(CheckedSet, Pred, SymReaper, + DiagnosticStmt, *this, K); + + // For each node in CheckedSet, generate CleanedNodes that have the + // environment, the store, and the constraints cleaned up but have the + // user-supplied states as the predecessors. + StmtNodeBuilder Bldr(CheckedSet, Out, *currBldrCtx); + for (const auto I : CheckedSet) { + ProgramStateRef CheckerState = I->getState(); + + // The constraint manager has not been cleaned up yet, so clean up now. + CheckerState = + getConstraintManager().removeDeadBindings(CheckerState, SymReaper); + + assert(StateMgr.haveEqualEnvironments(CheckerState, Pred->getState()) && + "Checkers are not allowed to modify the Environment as a part of " + "checkDeadSymbols processing."); + assert(StateMgr.haveEqualStores(CheckerState, Pred->getState()) && + "Checkers are not allowed to modify the Store as a part of " + "checkDeadSymbols processing."); + + // Create a state based on CleanedState with CheckerState GDM and + // generate a transition to that state. + ProgramStateRef CleanedCheckerSt = StateMgr.getPersistentStateWithGDM(CleanedState, CheckerState); - Bldr.generateNode(DiagnosticStmt, I, CleanedCheckerSt, &cleanupTag, K); - } + Bldr.generateNode(DiagnosticStmt, I, CleanedCheckerSt, &cleanupTag, K); } } @@ -712,7 +720,7 @@ void ExprEngine::ProcessStmt(const Stmt *currStmt, ExplodedNode *Pred) { G.reclaimRecentlyAllocatedNodes(); PrettyStackTraceLoc CrashInfo(getContext().getSourceManager(), - currStmt->getLocStart(), + currStmt->getBeginLoc(), "Error evaluating statement"); // Remove dead bindings and symbols. @@ -739,14 +747,14 @@ void ExprEngine::ProcessStmt(const Stmt *currStmt, ExplodedNode *Pred) { void ExprEngine::ProcessLoopExit(const Stmt* S, ExplodedNode *Pred) { PrettyStackTraceLoc CrashInfo(getContext().getSourceManager(), - S->getLocStart(), + S->getBeginLoc(), "Error evaluating end of the loop"); ExplodedNodeSet Dst; Dst.Add(Pred); NodeBuilder Bldr(Pred, Dst, *currBldrCtx); ProgramStateRef NewState = Pred->getState(); - if(AMgr.options.shouldUnrollLoops()) + if(AMgr.options.ShouldUnrollLoops) NewState = processLoopEnd(S, NewState); LoopExit PP(S, Pred->getLocationContext()); @@ -878,12 +886,12 @@ void ExprEngine::ProcessNewAllocator(const CXXNewExpr *NE, // TODO: We're not evaluating allocators for all cases just yet as // we're not handling the return value correctly, which causes false // positives when the alpha.cplusplus.NewDeleteLeaks check is on. - if (Opts.mayInlineCXXAllocator()) + if (Opts.MayInlineCXXAllocator) VisitCXXNewAllocatorCall(NE, Pred, Dst); else { NodeBuilder Bldr(Pred, Dst, *currBldrCtx); const LocationContext *LCtx = Pred->getLocationContext(); - PostImplicitCall PP(NE->getOperatorNew(), NE->getLocStart(), LCtx); + PostImplicitCall PP(NE->getOperatorNew(), NE->getBeginLoc(), LCtx); Bldr.generateNode(PP, Pred->getState(), Pred); } Engine.enqueue(Dst, currBldrCtx->getBlock(), currStmtIdx); @@ -940,7 +948,7 @@ void ExprEngine::ProcessDeleteDtor(const CFGDeleteDtor Dtor, const CXXRecordDecl *RD = BTy->getAsCXXRecordDecl(); const CXXDestructorDecl *Dtor = RD->getDestructor(); - PostImplicitCall PP(Dtor, DE->getLocStart(), LCtx); + PostImplicitCall PP(Dtor, DE->getBeginLoc(), LCtx); NodeBuilder Bldr(Pred, Dst, *currBldrCtx); Bldr.generateNode(PP, Pred->getState(), Pred); return; @@ -1025,13 +1033,13 @@ void ExprEngine::ProcessTemporaryDtor(const CFGTemporaryDtor D, MR = V->getAsRegion(); } - // If copy elision has occured, and the constructor corresponding to the + // If copy elision has occurred, and the constructor corresponding to the // destructor was elided, we need to skip the destructor as well. if (isDestructorElided(State, BTE, LC)) { State = cleanupElidedDestructor(State, BTE, LC); NodeBuilder Bldr(Pred, Dst, *currBldrCtx); PostImplicitCall PP(D.getDestructorDecl(getContext()), - D.getBindTemporaryExpr()->getLocStart(), + D.getBindTemporaryExpr()->getBeginLoc(), Pred->getLocationContext()); Bldr.generateNode(PP, State, Pred); return; @@ -1093,7 +1101,7 @@ void ExprEngine::VisitCXXBindTemporaryExpr(const CXXBindTemporaryExpr *BTE, // This is a fallback solution in case we didn't have a construction // context when we were constructing the temporary. Otherwise the map should // have been populated there. - if (!getAnalysisManager().options.includeTemporaryDtorsInCFG()) { + if (!getAnalysisManager().options.ShouldIncludeTemporaryDtorsInCFG) { // In case we don't have temporary destructors in the CFG, do not mark // the initialization - we would otherwise never clean it up. Dst = PreVisit; @@ -1120,7 +1128,7 @@ ProgramStateRef ExprEngine::escapeValue(ProgramStateRef State, SVal V, InvalidatedSymbols Symbols; public: - explicit CollectReachableSymbolsCallback(ProgramStateRef State) {} + explicit CollectReachableSymbolsCallback(ProgramStateRef) {} const InvalidatedSymbols &getSymbols() const { return Symbols; } @@ -1139,8 +1147,7 @@ ProgramStateRef ExprEngine::escapeValue(ProgramStateRef State, SVal V, void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, ExplodedNodeSet &DstTop) { PrettyStackTraceLoc CrashInfo(getContext().getSourceManager(), - S->getLocStart(), - "Error evaluating statement"); + S->getBeginLoc(), "Error evaluating statement"); ExplodedNodeSet Dst; StmtNodeBuilder Bldr(Pred, DstTop, *currBldrCtx); @@ -1274,6 +1281,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, Bldr.addNodes(Dst); break; + case Expr::ConstantExprClass: case Stmt::ExprWithCleanupsClass: // Handled due to fully linearised CFG. break; @@ -1454,7 +1462,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, break; case Stmt::LambdaExprClass: - if (AMgr.options.shouldInlineLambdas()) { + if (AMgr.options.ShouldInlineLambdas) { Bldr.takeNodes(Pred); VisitLambdaExpr(cast<LambdaExpr>(S), Pred, Dst); Bldr.addNodes(Dst); @@ -1483,7 +1491,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, Bldr.takeNodes(Pred); - if (AMgr.options.eagerlyAssumeBinOpBifurcation && + if (AMgr.options.ShouldEagerlyAssume && (B->isRelationalOp() || B->isEqualityOp())) { ExplodedNodeSet Tmp; VisitBinaryOperator(cast<BinaryOperator>(S), Pred, Tmp); @@ -1747,7 +1755,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::UnaryOperatorClass: { Bldr.takeNodes(Pred); const auto *U = cast<UnaryOperator>(S); - if (AMgr.options.eagerlyAssumeBinOpBifurcation && (U->getOpcode() == UO_LNot)) { + if (AMgr.options.ShouldEagerlyAssume && (U->getOpcode() == UO_LNot)) { ExplodedNodeSet Tmp; VisitUnaryOperator(U, Pred, Tmp); evalEagerlyAssumeBinOpBifurcation(Dst, Tmp, U); @@ -1848,7 +1856,7 @@ void ExprEngine::processCFGBlockEntrance(const BlockEdge &L, PrettyStackTraceLocationContext CrashInfo(Pred->getLocationContext()); // If we reach a loop which has a known bound (and meets // other constraints) then consider completely unrolling it. - if(AMgr.options.shouldUnrollLoops()) { + if(AMgr.options.ShouldUnrollLoops) { unsigned maxBlockVisitOnPath = AMgr.options.maxBlockVisitOnPath; const Stmt *Term = nodeBuilder.getContext().getBlock()->getTerminator(); if (Term) { @@ -1870,7 +1878,7 @@ void ExprEngine::processCFGBlockEntrance(const BlockEdge &L, // maximum number of times, widen the loop. unsigned int BlockCount = nodeBuilder.getContext().blockCount(); if (BlockCount == AMgr.options.maxBlockVisitOnPath - 1 && - AMgr.options.shouldWidenLoops()) { + AMgr.options.ShouldWidenLoops) { const Stmt *Term = nodeBuilder.getContext().getBlock()->getTerminator(); if (!(Term && (isa<ForStmt>(Term) || isa<WhileStmt>(Term) || isa<DoStmt>(Term)))) @@ -1923,8 +1931,7 @@ void ExprEngine::processCFGBlockEntrance(const BlockEdge &L, /// integers that promote their values (which are currently not tracked well). /// This function returns the SVal bound to Condition->IgnoreCasts if all the // cast(s) did was sign-extend the original value. -static SVal RecoverCastedSymbol(ProgramStateManager& StateMgr, - ProgramStateRef state, +static SVal RecoverCastedSymbol(ProgramStateRef state, const Stmt *Condition, const LocationContext *LCtx, ASTContext &Ctx) { @@ -2021,7 +2028,7 @@ static const Stmt *ResolveCondition(const Stmt *Condition, llvm_unreachable("could not resolve condition"); } -void ExprEngine::processBranch(const Stmt *Condition, const Stmt *Term, +void ExprEngine::processBranch(const Stmt *Condition, NodeBuilderContext& BldCtx, ExplodedNode *Pred, ExplodedNodeSet &Dst, @@ -2046,7 +2053,7 @@ void ExprEngine::processBranch(const Stmt *Condition, const Stmt *Term, Condition = ResolveCondition(Condition, BldCtx.getBlock()); PrettyStackTraceLoc CrashInfo(getContext().getSourceManager(), - Condition->getLocStart(), + Condition->getBeginLoc(), "Error evaluating branch"); ExplodedNodeSet CheckersOutSet; @@ -2072,8 +2079,7 @@ void ExprEngine::processBranch(const Stmt *Condition, const Stmt *Term, // integers that promote their values are currently not tracked well. // If 'Condition' is such an expression, try and recover the // underlying value and use that instead. - SVal recovered = RecoverCastedSymbol(getStateManager(), - PrevState, Condition, + SVal recovered = RecoverCastedSymbol(PrevState, Condition, PredI->getLocationContext(), getContext()); @@ -2200,17 +2206,21 @@ void ExprEngine::processBeginOfFunction(NodeBuilderContext &BC, void ExprEngine::processEndOfFunction(NodeBuilderContext& BC, ExplodedNode *Pred, const ReturnStmt *RS) { + ProgramStateRef State = Pred->getState(); + + if (!Pred->getStackFrame()->inTopFrame()) + State = finishArgumentConstruction( + State, *getStateManager().getCallEventManager().getCaller( + Pred->getStackFrame(), Pred->getState())); + // FIXME: We currently cannot assert that temporaries are clear, because // lifetime extended temporaries are not always modelled correctly. In some // cases when we materialize the temporary, we do // createTemporaryRegionIfNeeded(), and the region changes, and also the // respective destructor becomes automatic from temporary. So for now clean up - // the state manually before asserting. Ideally, the code above the assertion - // should go away, but the assertion should remain. + // the state manually before asserting. Ideally, this braced block of code + // should go away. { - ExplodedNodeSet CleanUpObjects; - NodeBuilder Bldr(Pred, CleanUpObjects, BC); - ProgramStateRef State = Pred->getState(); const LocationContext *FromLC = Pred->getLocationContext(); const LocationContext *ToLC = FromLC->getStackFrame()->getParent(); const LocationContext *LC = FromLC; @@ -2229,15 +2239,20 @@ void ExprEngine::processEndOfFunction(NodeBuilderContext& BC, } LC = LC->getParent(); } - if (State != Pred->getState()) { - Pred = Bldr.generateNode(Pred->getLocation(), State, Pred); - if (!Pred) { - // The node with clean temporaries already exists. We might have reached - // it on a path on which we initialize different temporaries. - return; - } + } + + // Perform the transition with cleanups. + if (State != Pred->getState()) { + ExplodedNodeSet PostCleanup; + NodeBuilder Bldr(Pred, PostCleanup, BC); + Pred = Bldr.generateNode(Pred->getLocation(), State, Pred); + if (!Pred) { + // The node with clean temporaries already exists. We might have reached + // it on a path on which we initialize different temporaries. + return; } } + assert(areAllObjectsFullyConstructed(Pred->getState(), Pred->getLocationContext(), Pred->getStackFrame()->getParent())); @@ -2364,7 +2379,7 @@ void ExprEngine::VisitCommonDeclRefExpr(const Expr *Ex, const NamedDecl *D, const auto *DeclRefEx = dyn_cast<DeclRefExpr>(Ex); Optional<std::pair<SVal, QualType>> VInfo; - if (AMgr.options.shouldInlineLambdas() && DeclRefEx && + if (AMgr.options.ShouldInlineLambdas && DeclRefEx && DeclRefEx->refersToEnclosingVariableOrCapture() && MD && MD->getParent()->isLambda()) { // Lookup the field of the lambda. @@ -2524,8 +2539,12 @@ void ExprEngine::VisitMemberExpr(const MemberExpr *M, ExplodedNode *Pred, } // Handle regular struct fields / member variables. - state = createTemporaryRegionIfNeeded(state, LCtx, BaseExpr); - SVal baseExprVal = state->getSVal(BaseExpr, LCtx); + const SubRegion *MR = nullptr; + state = createTemporaryRegionIfNeeded(state, LCtx, BaseExpr, + /*Result=*/nullptr, + /*OutRegionWithAdjustments=*/&MR); + SVal baseExprVal = + MR ? loc::MemRegionVal(MR) : state->getSVal(BaseExpr, LCtx); const auto *field = cast<FieldDecl>(Member); SVal L = state->getLValue(field, baseExprVal); @@ -2645,7 +2664,6 @@ ProgramStateRef ExprEngine::notifyCheckersOfPointerEscape(ProgramStateRef State, const InvalidatedSymbols *Invalidated, ArrayRef<const MemRegion *> ExplicitRegions, - ArrayRef<const MemRegion *> Regions, const CallEvent *Call, RegionAndSymbolInvalidationTraits &ITraits) { if (!Invalidated || Invalidated->empty()) @@ -2755,7 +2773,7 @@ void ExprEngine::evalStore(ExplodedNodeSet &Dst, const Expr *AssignE, // Evaluate the location (checks for bad dereferences). ExplodedNodeSet Tmp; - evalLocation(Tmp, AssignE, LocationE, Pred, state, location, tag, false); + evalLocation(Tmp, AssignE, LocationE, Pred, state, location, false); if (Tmp.empty()) return; @@ -2780,7 +2798,7 @@ void ExprEngine::evalLoad(ExplodedNodeSet &Dst, assert(BoundEx); // Evaluate the location (checks for bad dereferences). ExplodedNodeSet Tmp; - evalLocation(Tmp, NodeEx, BoundEx, Pred, state, location, tag, true); + evalLocation(Tmp, NodeEx, BoundEx, Pred, state, location, true); if (Tmp.empty()) return; @@ -2811,7 +2829,6 @@ void ExprEngine::evalLocation(ExplodedNodeSet &Dst, ExplodedNode *Pred, ProgramStateRef state, SVal location, - const ProgramPointTag *tag, bool isLoad) { StmtNodeBuilder BldrTop(Pred, Dst, *currBldrCtx); // Early checks for performance reason. @@ -2927,211 +2944,108 @@ void ExprEngine::VisitMSAsmStmt(const MSAsmStmt *A, ExplodedNode *Pred, //===----------------------------------------------------------------------===// #ifndef NDEBUG -static ExprEngine* GraphPrintCheckerState; -static SourceManager* GraphPrintSourceManager; - namespace llvm { template<> -struct DOTGraphTraits<ExplodedNode*> : public DefaultDOTGraphTraits { +struct DOTGraphTraits<ExplodedGraph*> : public DefaultDOTGraphTraits { DOTGraphTraits (bool isSimple = false) : DefaultDOTGraphTraits(isSimple) {} - // FIXME: Since we do not cache error nodes in ExprEngine now, this does not - // work. - static std::string getNodeAttributes(const ExplodedNode *N, void*) { - return {}; - } - - // De-duplicate some source location pretty-printing. - static void printLocation(raw_ostream &Out, SourceLocation SLoc) { - if (SLoc.isFileID()) { - Out << "\\lline=" - << GraphPrintSourceManager->getExpansionLineNumber(SLoc) - << " col=" - << GraphPrintSourceManager->getExpansionColumnNumber(SLoc) - << "\\l"; - } - } - - static std::string getNodeLabel(const ExplodedNode *N, void*){ - std::string sbuf; - llvm::raw_string_ostream Out(sbuf); - - // Program Location. - ProgramPoint Loc = N->getLocation(); - - switch (Loc.getKind()) { - case ProgramPoint::BlockEntranceKind: - Out << "Block Entrance: B" - << Loc.castAs<BlockEntrance>().getBlock()->getBlockID(); - break; - - case ProgramPoint::BlockExitKind: - assert(false); - break; - - case ProgramPoint::CallEnterKind: - Out << "CallEnter"; - break; - - case ProgramPoint::CallExitBeginKind: - Out << "CallExitBegin"; - break; + static bool nodeHasBugReport(const ExplodedNode *N) { + BugReporter &BR = static_cast<ExprEngine &>( + N->getState()->getStateManager().getOwningEngine()).getBugReporter(); - case ProgramPoint::CallExitEndKind: - Out << "CallExitEnd"; - break; - - case ProgramPoint::PostStmtPurgeDeadSymbolsKind: - Out << "PostStmtPurgeDeadSymbols"; - break; - - case ProgramPoint::PreStmtPurgeDeadSymbolsKind: - Out << "PreStmtPurgeDeadSymbols"; - break; - - case ProgramPoint::EpsilonKind: - Out << "Epsilon Point"; - break; + const auto EQClasses = + llvm::make_range(BR.EQClasses_begin(), BR.EQClasses_end()); - case ProgramPoint::LoopExitKind: { - LoopExit LE = Loc.castAs<LoopExit>(); - Out << "LoopExit: " << LE.getLoopStmt()->getStmtClassName(); - break; + for (const auto &EQ : EQClasses) { + for (const BugReport &Report : EQ) { + if (Report.getErrorNode() == N) + return true; } + } + return false; + } - case ProgramPoint::PreImplicitCallKind: { - ImplicitCallPoint PC = Loc.castAs<ImplicitCallPoint>(); - Out << "PreCall: "; - - // FIXME: Get proper printing options. - PC.getDecl()->print(Out, LangOptions()); - printLocation(Out, PC.getLocation()); - break; - } - - case ProgramPoint::PostImplicitCallKind: { - ImplicitCallPoint PC = Loc.castAs<ImplicitCallPoint>(); - Out << "PostCall: "; - - // FIXME: Get proper printing options. - PC.getDecl()->print(Out, LangOptions()); - printLocation(Out, PC.getLocation()); - break; - } - - case ProgramPoint::PostInitializerKind: { - Out << "PostInitializer: "; - const CXXCtorInitializer *Init = - Loc.castAs<PostInitializer>().getInitializer(); - if (const FieldDecl *FD = Init->getAnyMember()) - Out << *FD; - else { - QualType Ty = Init->getTypeSourceInfo()->getType(); - Ty = Ty.getLocalUnqualifiedType(); - LangOptions LO; // FIXME. - Ty.print(Out, LO); - } - break; - } - - case ProgramPoint::BlockEdgeKind: { - const BlockEdge &E = Loc.castAs<BlockEdge>(); - Out << "Edge: (B" << E.getSrc()->getBlockID() << ", B" - << E.getDst()->getBlockID() << ')'; - - if (const Stmt *T = E.getSrc()->getTerminator()) { - SourceLocation SLoc = T->getLocStart(); - - Out << "\\|Terminator: "; - LangOptions LO; // FIXME. - E.getSrc()->printTerminator(Out, LO); - - if (SLoc.isFileID()) { - Out << "\\lline=" - << GraphPrintSourceManager->getExpansionLineNumber(SLoc) - << " col=" - << GraphPrintSourceManager->getExpansionColumnNumber(SLoc); - } - - if (isa<SwitchStmt>(T)) { - const Stmt *Label = E.getDst()->getLabel(); - - if (Label) { - if (const auto *C = dyn_cast<CaseStmt>(Label)) { - Out << "\\lcase "; - LangOptions LO; // FIXME. - if (C->getLHS()) - C->getLHS()->printPretty(Out, nullptr, PrintingPolicy(LO)); - - if (const Stmt *RHS = C->getRHS()) { - Out << " .. "; - RHS->printPretty(Out, nullptr, PrintingPolicy(LO)); - } - - Out << ":"; - } - else { - assert(isa<DefaultStmt>(Label)); - Out << "\\ldefault:"; - } - } - else - Out << "\\l(implicit) default:"; - } - else if (isa<IndirectGotoStmt>(T)) { - // FIXME - } - else { - Out << "\\lCondition: "; - if (*E.getSrc()->succ_begin() == E.getDst()) - Out << "true"; - else - Out << "false"; - } - - Out << "\\l"; - } + /// \p PreCallback: callback before break. + /// \p PostCallback: callback after break. + /// \p Stop: stop iteration if returns {@code true} + /// \return Whether {@code Stop} ever returned {@code true}. + static bool traverseHiddenNodes( + const ExplodedNode *N, + llvm::function_ref<void(const ExplodedNode *)> PreCallback, + llvm::function_ref<void(const ExplodedNode *)> PostCallback, + llvm::function_ref<bool(const ExplodedNode *)> 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)) + return true; + if (OtherNode == N) break; - } + PostCallback(OtherNode); - default: { - const Stmt *S = Loc.castAs<StmtPoint>().getStmt(); - assert(S != nullptr && "Expecting non-null Stmt"); - - Out << S->getStmtClassName() << ' ' << (const void*) S << ' '; - LangOptions LO; // FIXME. - S->printPretty(Out, nullptr, PrintingPolicy(LO)); - printLocation(Out, S->getLocStart()); - - if (Loc.getAs<PreStmt>()) - Out << "\\lPreStmt\\l;"; - else if (Loc.getAs<PostLoad>()) - Out << "\\lPostLoad\\l;"; - else if (Loc.getAs<PostStore>()) - Out << "\\lPostStore\\l"; - else if (Loc.getAs<PostLValue>()) - Out << "\\lPostLValue\\l"; - else if (Loc.getAs<PostAllocatorCall>()) - Out << "\\lPostAllocatorCall\\l"; + OtherNode = *OtherNode->succ_begin(); + } + return false; + } - break; - } + static std::string getNodeAttributes(const ExplodedNode *N, + ExplodedGraph *) { + SmallVector<StringRef, 10> Out; + auto Noop = [](const ExplodedNode*){}; + if (traverseHiddenNodes(N, Noop, Noop, &nodeHasBugReport)) { + Out.push_back("style=filled"); + Out.push_back("fillcolor=red"); } - ProgramStateRef state = N->getState(); - Out << "\\|StateID: " << (const void*) state.get() - << " NodeID: " << (const void*) N << "\\|"; + if (traverseHiddenNodes(N, Noop, Noop, + [](const ExplodedNode *C) { return C->isSink(); })) + Out.push_back("color=blue"); + return llvm::join(Out, ","); + } - state->printDOT(Out, N->getLocationContext()); + static bool isNodeHidden(const ExplodedNode *N) { + return N->isTrivial(); + } - Out << "\\l"; + static std::string getNodeLabel(const ExplodedNode *N, ExplodedGraph *G){ + std::string sbuf; + llvm::raw_string_ostream Out(sbuf); - if (const ProgramPointTag *tag = Loc.getTag()) { - Out << "\\|Tag: " << tag->getTagDescription(); - Out << "\\l"; - } + ProgramStateRef State = N->getState(); + + // Dump program point for all the previously skipped nodes. + traverseHiddenNodes( + N, + [&](const ExplodedNode *OtherNode) { + OtherNode->getLocation().print(/*CR=*/"\\l", Out); + if (const ProgramPointTag *Tag = OtherNode->getLocation().getTag()) + Out << "\\lTag:" << Tag->getTagDescription(); + if (N->isSink()) + Out << "\\lNode is sink\\l"; + if (nodeHasBugReport(N)) + Out << "\\lBug report attached\\l"; + }, + [&](const ExplodedNode *) { Out << "\\l--------\\l"; }, + [&](const ExplodedNode *) { return false; }); + + Out << "\\l\\|"; + + Out << "StateID: ST" << State->getID() << ", NodeID: N" << N->getID(G) + << " <" << (const void *)N << ">\\|"; + + 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()); return Out.str(); } }; @@ -3141,48 +3055,61 @@ struct DOTGraphTraits<ExplodedNode*> : public DefaultDOTGraphTraits { void ExprEngine::ViewGraph(bool trim) { #ifndef NDEBUG + std::string Filename = DumpGraph(trim); + llvm::DisplayGraph(Filename, false, llvm::GraphProgram::DOT); +#endif + llvm::errs() << "Warning: viewing graph requires assertions" << "\n"; +} + + +void ExprEngine::ViewGraph(ArrayRef<const ExplodedNode*> Nodes) { +#ifndef NDEBUG + std::string Filename = DumpGraph(Nodes); + llvm::DisplayGraph(Filename, false, llvm::GraphProgram::DOT); +#endif + llvm::errs() << "Warning: viewing graph requires assertions" << "\n"; +} + +std::string ExprEngine::DumpGraph(bool trim, StringRef Filename) { +#ifndef NDEBUG if (trim) { std::vector<const ExplodedNode *> Src; - // Flush any outstanding reports to make sure we cover all the nodes. - // This does not cause them to get displayed. - for (const auto I : BR) - const_cast<BugType *>(I)->FlushReports(BR); - // 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<ExplodedNode *>(EI->begin()->getErrorNode()); if (N) Src.push_back(N); } - - ViewGraph(Src); - } - else { - GraphPrintCheckerState = this; - GraphPrintSourceManager = &getContext().getSourceManager(); - - llvm::ViewGraph(*G.roots_begin(), "ExprEngine"); - - GraphPrintCheckerState = nullptr; - GraphPrintSourceManager = nullptr; + return DumpGraph(Src, Filename); + } else { + return llvm::WriteGraph(&G, "ExprEngine", /*ShortNames=*/false, + /*Title=*/"Exploded Graph", /*Filename=*/Filename); } #endif + llvm::errs() << "Warning: dumping graph requires assertions" << "\n"; + return ""; } -void ExprEngine::ViewGraph(ArrayRef<const ExplodedNode*> Nodes) { +std::string ExprEngine::DumpGraph(ArrayRef<const ExplodedNode*> Nodes, + StringRef Filename) { #ifndef NDEBUG - GraphPrintCheckerState = this; - GraphPrintSourceManager = &getContext().getSourceManager(); - std::unique_ptr<ExplodedGraph> TrimmedG(G.trim(Nodes)); - if (!TrimmedG.get()) + if (!TrimmedG.get()) { llvm::errs() << "warning: Trimmed ExplodedGraph is empty.\n"; - else - llvm::ViewGraph(*TrimmedG->roots_begin(), "TrimmedExprEngine"); - - GraphPrintCheckerState = nullptr; - GraphPrintSourceManager = nullptr; + } else { + return llvm::WriteGraph(TrimmedG.get(), "TrimmedExprEngine", + /*ShortNames=*/false, + /*Title=*/"Trimmed Exploded Graph", + /*Filename=*/Filename); + } #endif + llvm::errs() << "Warning: dumping graph requires assertions" << "\n"; + return ""; +} + +void *ProgramStateTrait<ReplayWithoutInlining>::GDMIndex() { + static int index = 0; + return &index; } diff --git a/lib/StaticAnalyzer/Core/ExprEngineC.cpp b/lib/StaticAnalyzer/Core/ExprEngineC.cpp index 61b7a290e42a9..b980628878e99 100644 --- a/lib/StaticAnalyzer/Core/ExprEngineC.cpp +++ b/lib/StaticAnalyzer/Core/ExprEngineC.cpp @@ -412,10 +412,11 @@ void ExprEngine::VisitCast(const CastExpr *CastE, const Expr *Ex, case CK_BlockPointerToObjCPointerCast: case CK_AnyPointerToBlockPointerCast: case CK_ObjCObjectLValueCast: - case CK_ZeroToOCLEvent: - case CK_ZeroToOCLQueue: + case CK_ZeroToOCLOpaqueType: case CK_IntToOCLSampler: - case CK_LValueBitCast: { + case CK_LValueBitCast: + case CK_FixedPointCast: + case CK_FixedPointToBoolean: { state = handleLValueBitCast(state, Ex, LCtx, T, ExTy, CastE, Bldr, Pred); continue; @@ -809,8 +810,9 @@ void ExprEngine:: VisitOffsetOfExpr(const OffsetOfExpr *OOE, ExplodedNode *Pred, ExplodedNodeSet &Dst) { StmtNodeBuilder B(Pred, Dst, *currBldrCtx); - APSInt IV; - if (OOE->EvaluateAsInt(IV, getContext())) { + Expr::EvalResult Result; + 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<BuiltinType>()->isInteger()); @@ -956,7 +958,7 @@ void ExprEngine::VisitUnaryOperator(const UnaryOperator* U, ExplodedNode *Pred, } case UO_Plus: assert(!U->isGLValue()); - // FALL-THROUGH. + LLVM_FALLTHROUGH; case UO_Deref: case UO_Extension: { handleUOExtension(I, U, Bldr); @@ -1050,7 +1052,7 @@ void ExprEngine::VisitIncrementDecrementOperator(const UnaryOperator* U, // Perform the store, so that the uninitialized value detection happens. Bldr.takeNodes(*I); ExplodedNodeSet Dst3; - evalStore(Dst3, U, U, *I, state, loc, V2_untested); + evalStore(Dst3, U, Ex, *I, state, loc, V2_untested); Bldr.addNodes(Dst3); continue; @@ -1118,7 +1120,7 @@ void ExprEngine::VisitIncrementDecrementOperator(const UnaryOperator* U, // Perform the store. Bldr.takeNodes(*I); ExplodedNodeSet Dst3; - evalStore(Dst3, U, U, *I, state, loc, Result); + evalStore(Dst3, U, Ex, *I, state, loc, Result); Bldr.addNodes(Dst3); } Dst.insert(Dst2); diff --git a/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp b/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp index 4f1766a813c6d..6445b9df5a58d 100644 --- a/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp +++ b/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp @@ -113,7 +113,9 @@ SVal ExprEngine::makeZeroElementRegion(ProgramStateRef State, SVal LValue, std::pair<ProgramStateRef, SVal> ExprEngine::prepareForObjectConstruction( const Expr *E, ProgramStateRef State, const LocationContext *LCtx, const ConstructionContext *CC, EvalCallOptions &CallOpts) { - MemRegionManager &MRMgr = getSValBuilder().getRegionManager(); + SValBuilder &SVB = getSValBuilder(); + MemRegionManager &MRMgr = SVB.getRegionManager(); + ASTContext &ACtx = SVB.getContext(); // See if we're constructing an existing region by looking at the // current construction context. @@ -139,7 +141,7 @@ std::pair<ProgramStateRef, SVal> ExprEngine::prepareForObjectConstruction( assert(Init->isAnyMemberInitializer()); const CXXMethodDecl *CurCtor = cast<CXXMethodDecl>(LCtx->getDecl()); Loc ThisPtr = - getSValBuilder().getCXXThis(CurCtor, LCtx->getStackFrame()); + SVB.getCXXThis(CurCtor, LCtx->getStackFrame()); SVal ThisVal = State->getSVal(ThisPtr); const ValueDecl *Field; @@ -159,7 +161,7 @@ std::pair<ProgramStateRef, SVal> ExprEngine::prepareForObjectConstruction( return std::make_pair(State, FieldVal); } case ConstructionContext::NewAllocatedObjectKind: { - if (AMgr.getAnalyzerOptions().mayInlineCXXAllocator()) { + if (AMgr.getAnalyzerOptions().MayInlineCXXAllocator) { const auto *NECC = cast<NewAllocatedObjectConstructionContext>(CC); const auto *NE = NECC->getCXXNewExpr(); SVal V = *getObjectUnderConstruction(State, NE, LCtx); @@ -199,18 +201,31 @@ std::pair<ProgramStateRef, SVal> ExprEngine::prepareForObjectConstruction( cast<Expr>(SFC->getCallSite()), State, CallerLCtx, RTC->getConstructionContext(), CallOpts); } else { - // We are on the top frame of the analysis. - // TODO: What exactly happens when we are? Does the temporary object - // live long enough in the region store in this case? Would checkers - // think that this object immediately goes out of scope? - CallOpts.IsTemporaryCtorOrDtor = true; - SVal V = loc::MemRegionVal(MRMgr.getCXXTempObjectRegion(E, LCtx)); + // We are on the top frame of the analysis. We do not know where is the + // object returned to. Conjure a symbolic region for the return value. + // TODO: We probably need a new MemRegion kind to represent the storage + // of that SymbolicRegion, so that we cound produce a fancy symbol + // instead of an anonymous conjured symbol. + // TODO: Do we need to track the region to avoid having it dead + // too early? It does die too early, at least in C++17, but because + // putting anything into a SymbolicRegion causes an immediate escape, + // it doesn't cause any leak false positives. + const auto *RCC = cast<ReturnedValueConstructionContext>(CC); + // Make sure that this doesn't coincide with any other symbol + // conjured for the returned expression. + static const int TopLevelSymRegionTag = 0; + const Expr *RetE = RCC->getReturnStmt()->getRetValue(); + assert(RetE && "Void returns should not have a construction context"); + QualType ReturnTy = RetE->getType(); + QualType RegionTy = ACtx.getPointerType(ReturnTy); + SVal V = SVB.conjureSymbolVal(&TopLevelSymRegionTag, RetE, SFC, + RegionTy, currBldrCtx->blockCount()); return std::make_pair(State, V); } llvm_unreachable("Unhandled return value construction context!"); } case ConstructionContext::ElidedTemporaryObjectKind: { - assert(AMgr.getAnalyzerOptions().shouldElideConstructors()); + assert(AMgr.getAnalyzerOptions().ShouldElideConstructors); const auto *TCC = cast<ElidedTemporaryObjectConstructionContext>(CC); const CXXBindTemporaryExpr *BTE = TCC->getCXXBindTemporaryExpr(); const MaterializeTemporaryExpr *MTE = TCC->getMaterializedTemporaryExpr(); @@ -292,8 +307,75 @@ std::pair<ProgramStateRef, SVal> ExprEngine::prepareForObjectConstruction( return std::make_pair(State, V); } case ConstructionContext::ArgumentKind: { - // Function argument constructors. Not implemented yet. - break; + // Arguments are technically temporaries. + CallOpts.IsTemporaryCtorOrDtor = true; + + const auto *ACC = cast<ArgumentConstructionContext>(CC); + const Expr *E = ACC->getCallLikeExpr(); + unsigned Idx = ACC->getIndex(); + const CXXBindTemporaryExpr *BTE = ACC->getCXXBindTemporaryExpr(); + + CallEventManager &CEMgr = getStateManager().getCallEventManager(); + SVal V = UnknownVal(); + auto getArgLoc = [&](CallEventRef<> Caller) -> Optional<SVal> { + const LocationContext *FutureSFC = Caller->getCalleeStackFrame(); + // Return early if we are unable to reliably foresee + // the future stack frame. + if (!FutureSFC) + return None; + + // This should be equivalent to Caller->getDecl() for now, but + // FutureSFC->getDecl() is likely to support better stuff (like + // virtual functions) earlier. + const Decl *CalleeD = FutureSFC->getDecl(); + + // FIXME: Support for variadic arguments is not implemented here yet. + if (CallEvent::isVariadic(CalleeD)) + return None; + + // Operator arguments do not correspond to operator parameters + // 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)); + if (!VR) + return None; + + return loc::MemRegionVal(VR); + }; + + if (const auto *CE = dyn_cast<CallExpr>(E)) { + CallEventRef<> Caller = CEMgr.getSimpleCall(CE, State, LCtx); + if (auto OptV = getArgLoc(Caller)) + V = *OptV; + else + break; + State = addObjectUnderConstruction(State, {CE, Idx}, LCtx, V); + } else if (const auto *CCE = dyn_cast<CXXConstructExpr>(E)) { + // Don't bother figuring out the target region for the future + // constructor because we won't need it. + CallEventRef<> Caller = + CEMgr.getCXXConstructorCall(CCE, /*Target=*/nullptr, State, LCtx); + if (auto OptV = getArgLoc(Caller)) + V = *OptV; + else + break; + State = addObjectUnderConstruction(State, {CCE, Idx}, LCtx, V); + } else if (const auto *ME = dyn_cast<ObjCMessageExpr>(E)) { + CallEventRef<> Caller = CEMgr.getObjCMethodCall(ME, State, LCtx); + if (auto OptV = getArgLoc(Caller)) + V = *OptV; + else + break; + State = addObjectUnderConstruction(State, {ME, Idx}, LCtx, V); + } + + assert(!V.isUnknown()); + + if (BTE) + State = addObjectUnderConstruction(State, BTE, LCtx, V); + + return std::make_pair(State, V); } } } @@ -359,7 +441,7 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE, } } } - // FALLTHROUGH + LLVM_FALLTHROUGH; case CXXConstructExpr::CK_NonVirtualBase: // In C++17, classes with non-virtual bases may be aggregates, so they would // be initialized as aggregates without a constructor call, so we may have @@ -378,7 +460,7 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE, CallOpts.IsCtorOrDtorWithImproperlyModeledTargetRegion = true; break; } - // FALLTHROUGH + LLVM_FALLTHROUGH; case CXXConstructExpr::CK_Delegating: { const CXXMethodDecl *CurCtor = cast<CXXMethodDecl>(LCtx->getDecl()); Loc ThisPtr = getSValBuilder().getCXXThis(CurCtor, @@ -502,8 +584,15 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE, } } + ExplodedNodeSet DstPostArgumentCleanup; + for (auto I : DstEvaluated) + finishArgumentConstruction(DstPostArgumentCleanup, I, *Call); + + // If there were other constructors called for object-type arguments + // of this constructor, clean them up. ExplodedNodeSet DstPostCall; - getCheckerManager().runCheckersForPostCall(DstPostCall, DstEvaluated, + getCheckerManager().runCheckersForPostCall(DstPostCall, + DstPostArgumentCleanup, *Call, *this); getCheckerManager().runCheckersForPostStmt(destNodes, DstPostCall, CE, *this); } @@ -551,7 +640,7 @@ void ExprEngine::VisitCXXNewAllocatorCall(const CXXNewExpr *CNE, ProgramStateRef State = Pred->getState(); const LocationContext *LCtx = Pred->getLocationContext(); PrettyStackTraceLoc CrashInfo(getContext().getSourceManager(), - CNE->getStartLoc(), + CNE->getBeginLoc(), "Error evaluating New Allocator Call"); CallEventManager &CEMgr = getStateManager().getCallEventManager(); CallEventRef<CXXAllocatorCall> Call = @@ -632,7 +721,7 @@ void ExprEngine::VisitCXXNewExpr(const CXXNewExpr *CNE, ExplodedNode *Pred, ProgramStateRef State = Pred->getState(); // Retrieve the stored operator new() return value. - if (AMgr.getAnalyzerOptions().mayInlineCXXAllocator()) { + if (AMgr.getAnalyzerOptions().MayInlineCXXAllocator) { symVal = *getObjectUnderConstruction(State, CNE, LCtx); State = finishObjectConstruction(State, CNE, LCtx); } @@ -652,7 +741,7 @@ void ExprEngine::VisitCXXNewExpr(const CXXNewExpr *CNE, ExplodedNode *Pred, CallEventRef<CXXAllocatorCall> Call = CEMgr.getCXXAllocatorCall(CNE, State, LCtx); - if (!AMgr.getAnalyzerOptions().mayInlineCXXAllocator()) { + 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. diff --git a/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp b/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp index 3ee67f3d6882b..758195d8d911b 100644 --- a/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp +++ b/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp @@ -349,7 +349,7 @@ void ExprEngine::processCallExit(ExplodedNode *CEBNode) { /*WasInlined=*/true); } else if (CE && !(isa<CXXNewExpr>(CE) && // Called when visiting CXXNewExpr. - AMgr.getAnalyzerOptions().mayInlineCXXAllocator())) { + AMgr.getAnalyzerOptions().MayInlineCXXAllocator)) { getCheckerManager().runCheckersForPostStmt(Dst, DstPostCall, CE, *this, /*WasInlined=*/true); } else { @@ -386,7 +386,7 @@ void ExprEngine::examineStackFrames(const Decl *D, const LocationContext *LCtx, // Do not count the small functions when determining the stack depth. AnalysisDeclContext *CalleeADC = AMgr.getAnalysisDeclContext(DI); const CFG *CalleeCFG = CalleeADC->getCFG(); - if (CalleeCFG->getNumBlockIDs() > AMgr.options.getAlwaysInlineSize()) + if (CalleeCFG->getNumBlockIDs() > AMgr.options.AlwaysInlineSize) ++StackDepth; } LCtx = LCtx->getParent(); @@ -406,9 +406,8 @@ namespace { }; } // end anonymous namespace -REGISTER_TRAIT_WITH_PROGRAMSTATE(DynamicDispatchBifurcationMap, - CLANG_ENTO_PROGRAMSTATE_MAP(const MemRegion *, - unsigned)) +REGISTER_MAP_WITH_PROGRAMSTATE(DynamicDispatchBifurcationMap, + const MemRegion *, unsigned) bool ExprEngine::inlineCall(const CallEvent &Call, const Decl *D, NodeBuilder &Bldr, ExplodedNode *Pred, @@ -505,6 +504,50 @@ void ExprEngine::VisitCallExpr(const CallExpr *CE, ExplodedNode *Pred, *this); } +ProgramStateRef ExprEngine::finishArgumentConstruction(ProgramStateRef State, + const CallEvent &Call) { + const Expr *E = Call.getOriginExpr(); + // FIXME: Constructors to placement arguments of operator new + // are not supported yet. + if (!E || isa<CXXNewExpr>(E)) + return State; + + const LocationContext *LC = Call.getLocationContext(); + for (unsigned CallI = 0, CallN = Call.getNumArgs(); CallI != CallN; ++CallI) { + unsigned I = Call.getASTArgumentIndex(CallI); + if (Optional<SVal> V = + getObjectUnderConstruction(State, {E, I}, LC)) { + SVal VV = *V; + (void)VV; + assert(cast<VarRegion>(VV.castAs<loc::MemRegionVal>().getRegion()) + ->getStackFrame()->getParent() + ->getStackFrame() == LC->getStackFrame()); + State = finishObjectConstruction(State, {E, I}, LC); + } + } + + return State; +} + +void ExprEngine::finishArgumentConstruction(ExplodedNodeSet &Dst, + ExplodedNode *Pred, + const CallEvent &Call) { + ProgramStateRef State = Pred->getState(); + ProgramStateRef CleanedState = finishArgumentConstruction(State, Call); + if (CleanedState == State) { + Dst.insert(Pred); + return; + } + + const Expr *E = Call.getOriginExpr(); + const LocationContext *LC = Call.getLocationContext(); + NodeBuilder B(Pred, Dst, *currBldrCtx); + static SimpleProgramPointTag Tag("ExprEngine", + "Finish argument construction"); + PreStmt PP(E, LC, &Tag); + B.generateNode(PP, CleanedState, Pred); +} + void ExprEngine::evalCall(ExplodedNodeSet &Dst, ExplodedNode *Pred, const CallEvent &Call) { // WARNING: At this time, the state attached to 'Call' may be older than the @@ -516,7 +559,8 @@ void ExprEngine::evalCall(ExplodedNodeSet &Dst, ExplodedNode *Pred, // Run any pre-call checks using the generic call interface. ExplodedNodeSet dstPreVisit; - getCheckerManager().runCheckersForPreCall(dstPreVisit, Pred, Call, *this); + getCheckerManager().runCheckersForPreCall(dstPreVisit, Pred, + Call, *this); // Actually evaluate the function call. We try each of the checkers // to see if the can evaluate the function call, and get a callback at @@ -525,8 +569,14 @@ void ExprEngine::evalCall(ExplodedNodeSet &Dst, ExplodedNode *Pred, getCheckerManager().runCheckersForEvalCall(dstCallEvaluated, dstPreVisit, Call, *this); + // If there were other constructors called for object-type arguments + // of this call, clean them up. + ExplodedNodeSet dstArgumentCleanup; + for (auto I : dstCallEvaluated) + finishArgumentConstruction(dstArgumentCleanup, I, Call); + // Finally, run any post-call checks. - getCheckerManager().runCheckersForPostCall(Dst, dstCallEvaluated, + getCheckerManager().runCheckersForPostCall(Dst, dstArgumentCleanup, Call, *this); } @@ -633,7 +683,7 @@ ExprEngine::mayInlineCallKind(const CallEvent &Call, const ExplodedNode *Pred, : nullptr; if (CC && isa<NewAllocatedObjectConstructionContext>(CC) && - !Opts.mayInlineCXXAllocator()) + !Opts.MayInlineCXXAllocator) return CIP_DisallowedOnce; // FIXME: We don't handle constructors or destructors for arrays properly. @@ -662,7 +712,7 @@ ExprEngine::mayInlineCallKind(const CallEvent &Call, const ExplodedNode *Pred, // If we don't handle temporary destructors, we shouldn't inline // their constructors. if (CallOpts.IsTemporaryCtorOrDtor && - !Opts.includeTemporaryDtorsInCFG()) + !Opts.ShouldIncludeTemporaryDtorsInCFG) return CIP_DisallowedOnce; // If we did not find the correct this-region, it would be pointless @@ -693,7 +743,8 @@ ExprEngine::mayInlineCallKind(const CallEvent &Call, const ExplodedNode *Pred, return CIP_DisallowedOnce; // Allow disabling temporary destructor inlining with a separate option. - if (CallOpts.IsTemporaryCtorOrDtor && !Opts.mayInlineCXXTemporaryDtors()) + if (CallOpts.IsTemporaryCtorOrDtor && + !Opts.MayInlineCXXTemporaryDtors) return CIP_DisallowedOnce; // If we did not find the correct this-region, it would be pointless @@ -704,13 +755,13 @@ ExprEngine::mayInlineCallKind(const CallEvent &Call, const ExplodedNode *Pred, break; } case CE_CXXAllocator: - if (Opts.mayInlineCXXAllocator()) + if (Opts.MayInlineCXXAllocator) break; // Do not inline allocators until we model deallocators. // This is unfortunate, but basically necessary for smart pointers and such. return CIP_DisallowedAlways; case CE_ObjCMessage: - if (!Opts.mayInlineObjCMethod()) + if (!Opts.MayInlineObjCMethod) return CIP_DisallowedAlways; if (!(Opts.getIPAMode() == IPAK_DynamicDispatch || Opts.getIPAMode() == IPAK_DynamicDispatchBifurcate)) @@ -794,19 +845,19 @@ static bool mayInlineDecl(AnalysisManager &AMgr, if (Ctx.getLangOpts().CPlusPlus) { if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(CalleeADC->getDecl())) { // Conditionally control the inlining of template functions. - if (!Opts.mayInlineTemplateFunctions()) + if (!Opts.MayInlineTemplateFunctions) if (FD->getTemplatedKind() != FunctionDecl::TK_NonTemplate) return false; // Conditionally control the inlining of C++ standard library functions. - if (!Opts.mayInlineCXXStandardLibrary()) + if (!Opts.MayInlineCXXStandardLibrary) if (Ctx.getSourceManager().isInSystemHeader(FD->getLocation())) if (AnalysisDeclContext::isInStdNamespace(FD)) return false; // Conditionally control the inlining of methods on objects that look // like C++ containers. - if (!Opts.mayInlineCXXContainerMethods()) + if (!Opts.MayInlineCXXContainerMethods) if (!AMgr.isInCodeFile(FD->getLocation())) if (isContainerMethod(Ctx, FD)) return false; @@ -815,7 +866,7 @@ static bool mayInlineDecl(AnalysisManager &AMgr, // We don't currently do a good job modeling shared_ptr because we can't // see the reference count, so treating as opaque is probably the best // idea. - if (!Opts.mayInlineCXXSharedPtrDtor()) + if (!Opts.MayInlineCXXSharedPtrDtor) if (isCXXSharedPtrDtor(FD)) return false; } @@ -828,7 +879,7 @@ static bool mayInlineDecl(AnalysisManager &AMgr, return false; // Do not inline large functions. - if (CalleeCFG->getNumBlockIDs() > Opts.getMaxInlinableSize()) + if (CalleeCFG->getNumBlockIDs() > Opts.MaxInlinableSize) return false; // It is possible that the live variables analysis cannot be @@ -896,21 +947,21 @@ bool ExprEngine::shouldInlineCall(const CallEvent &Call, const Decl *D, unsigned StackDepth = 0; examineStackFrames(D, Pred->getLocationContext(), IsRecursive, StackDepth); if ((StackDepth >= Opts.InlineMaxStackDepth) && - ((CalleeCFG->getNumBlockIDs() > Opts.getAlwaysInlineSize()) + ((CalleeCFG->getNumBlockIDs() > Opts.AlwaysInlineSize) || IsRecursive)) return false; // Do not inline large functions too many times. if ((Engine.FunctionSummaries->getNumTimesInlined(D) > - Opts.getMaxTimesInlineLarge()) && + Opts.MaxTimesInlineLarge) && CalleeCFG->getNumBlockIDs() >= - Opts.getMinCFGSizeTreatFunctionsAsLarge()) { + Opts.MinCFGSizeTreatFunctionsAsLarge) { NumReachedInlineCountMax++; return false; } if (HowToInline == Inline_Minimal && - (CalleeCFG->getNumBlockIDs() > Opts.getAlwaysInlineSize() + (CalleeCFG->getNumBlockIDs() > Opts.AlwaysInlineSize || IsRecursive)) return false; diff --git a/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp b/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp index d76b9cbcfaca4..6b8402f621e0a 100644 --- a/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp +++ b/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp @@ -129,7 +129,7 @@ void ExprEngine::VisitObjCForCollectionStmt(const ObjCForCollectionStmt *S, bool isContainerNull = state->isNull(collectionV).isConstrainedTrue(); ExplodedNodeSet dstLocation; - evalLocation(dstLocation, S, elem, Pred, state, elementV, nullptr, false); + evalLocation(dstLocation, S, elem, Pred, state, elementV, false); ExplodedNodeSet Tmp; StmtNodeBuilder Bldr(Pred, Tmp, *currBldrCtx); @@ -197,7 +197,8 @@ void ExprEngine::VisitObjCMessage(const ObjCMessageExpr *ME, // Receiver is definitely nil, so run ObjCMessageNil callbacks and return. if (nilState && !notNilState) { - StmtNodeBuilder Bldr(Pred, Dst, *currBldrCtx); + ExplodedNodeSet dstNil; + StmtNodeBuilder Bldr(Pred, dstNil, *currBldrCtx); bool HasTag = Pred->getLocation().getTag(); Pred = Bldr.generateNode(ME, Pred, nilState, nullptr, ProgramPoint::PreStmtKind); @@ -205,8 +206,12 @@ void ExprEngine::VisitObjCMessage(const ObjCMessageExpr *ME, (void)HasTag; if (!Pred) return; - getCheckerManager().runCheckersForObjCMessageNil(Dst, Pred, + + ExplodedNodeSet dstPostCheckers; + getCheckerManager().runCheckersForObjCMessageNil(dstPostCheckers, Pred, *Msg, *this); + for (auto I : dstPostCheckers) + finishArgumentConstruction(Dst, I, *Msg); return; } @@ -267,8 +272,13 @@ void ExprEngine::VisitObjCMessage(const ObjCMessageExpr *ME, defaultEvalCall(Bldr, Pred, *UpdatedMsg); } + // If there were constructors called for object-type arguments, clean them up. + ExplodedNodeSet dstArgCleanup; + for (auto I : dstEval) + finishArgumentConstruction(dstArgCleanup, I, *Msg); + ExplodedNodeSet dstPostvisit; - getCheckerManager().runCheckersForPostCall(dstPostvisit, dstEval, + getCheckerManager().runCheckersForPostCall(dstPostvisit, dstArgCleanup, *Msg, *this); // Finally, perform the post-condition check of the ObjCMessageExpr and store diff --git a/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp b/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp index d5e5f96dee0f2..fc82f11769424 100644 --- a/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp +++ b/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp @@ -112,15 +112,24 @@ public: FileID FID, const FileEntry *Entry, const char *declName); // Rewrite the file specified by FID with HTML formatting. - void RewriteFile(Rewriter &R, const SourceManager& SMgr, - const PathPieces& path, FileID FID); + void RewriteFile(Rewriter &R, const PathPieces& path, FileID FID); - /// \return Javascript for navigating the HTML report using j/k keys. - std::string generateKeyboardNavigationJavascript(); private: /// \return Javascript for displaying shortcuts help; - std::string showHelpJavascript(); + StringRef showHelpJavascript(); + + /// \return Javascript for navigating the HTML report using j/k keys. + StringRef generateKeyboardNavigationJavascript(); + + /// \return JavaScript for an option to only show relevant lines. + std::string showRelevantLinesJavascript( + const PathDiagnostic &D, const PathPieces &path); + + /// Write executed lines from \p D in JSON format into \p os. + void dumpCoverageData(const PathDiagnostic &D, + const PathPieces &path, + llvm::raw_string_ostream &os); }; } // namespace @@ -194,7 +203,7 @@ void HTMLDiagnostics::ReportDiag(const PathDiagnostic& D, FullSourceLoc L( SMgr.getExpansionLoc(path.back()->getLocation().asLocation()), SMgr); - FullSourceLoc FunL(SMgr.getExpansionLoc(Body->getLocStart()), SMgr); + FullSourceLoc FunL(SMgr.getExpansionLoc(Body->getBeginLoc()), SMgr); offsetDecl = L.getExpansionLineNumber() - FunL.getExpansionLineNumber(); } } @@ -209,7 +218,7 @@ void HTMLDiagnostics::ReportDiag(const PathDiagnostic& D, int FD; SmallString<128> Model, ResultPath; - if (!AnalyzerOpts.shouldWriteStableReportFilename()) { + if (!AnalyzerOpts.ShouldWriteStableReportFilename) { llvm::sys::path::append(Model, Directory, "report-%%%%%%.html"); if (std::error_code EC = llvm::sys::fs::make_absolute(Model)) { @@ -269,7 +278,7 @@ std::string HTMLDiagnostics::GenerateHTML(const PathDiagnostic& D, Rewriter &R, continue; FileIDs.push_back(FID); - RewriteFile(R, SMgr, path, FID); + RewriteFile(R, path, FID); } if (SupportsCrossFileDiagnostics && FileIDs.size() > 1) { @@ -332,28 +341,12 @@ std::string HTMLDiagnostics::GenerateHTML(const PathDiagnostic& D, Rewriter &R, return os.str(); } -/// Write executed lines from \p D in JSON format into \p os. -static void serializeExecutedLines( +void HTMLDiagnostics::dumpCoverageData( const PathDiagnostic &D, const PathPieces &path, llvm::raw_string_ostream &os) { - // Copy executed lines from path diagnostics. - std::map<unsigned, std::set<unsigned>> ExecutedLines; - for (auto I = D.executedLines_begin(), - E = D.executedLines_end(); I != E; ++I) { - std::set<unsigned> &LinesInFile = ExecutedLines[I->first]; - for (unsigned LineNo : I->second) { - LinesInFile.insert(LineNo); - } - } - // We need to include all lines for which any kind of diagnostics appears. - for (const auto &P : path) { - FullSourceLoc Loc = P->getLocation().asLocation().getExpansionLoc(); - FileID FID = Loc.getFileID(); - unsigned LineNo = Loc.getLineNumber(); - ExecutedLines[FID.getHashValue()].insert(LineNo); - } + const FilesToLineNumsMap &ExecutedLines = D.getExecutedLines(); os << "var relevant_lines = {"; for (auto I = ExecutedLines.begin(), @@ -361,7 +354,7 @@ static void serializeExecutedLines( if (I != ExecutedLines.begin()) os << ", "; - os << "\"" << I->first << "\": {"; + os << "\"" << I->first.getHashValue() << "\": {"; for (unsigned LineNo : I->second) { if (LineNo != *(I->second.begin())) os << ", "; @@ -374,13 +367,12 @@ static void serializeExecutedLines( os << "};"; } -/// \return JavaScript for an option to only show relevant lines. -static std::string showRelevantLinesJavascript( +std::string HTMLDiagnostics::showRelevantLinesJavascript( const PathDiagnostic &D, const PathPieces &path) { std::string s; llvm::raw_string_ostream os(s); os << "<script type='text/javascript'>\n"; - serializeExecutedLines(D, path, os); + dumpCoverageData(D, path, os); os << R"<<<( var filterCounterexample = function (hide) { @@ -586,7 +578,7 @@ void HTMLDiagnostics::FinalizeHTML(const PathDiagnostic& D, Rewriter &R, html::AddHeaderFooterInternalBuiltinCSS(R, FID, Entry->getName()); } -std::string HTMLDiagnostics::showHelpJavascript() { +StringRef HTMLDiagnostics::showHelpJavascript() { return R"<<<( <script type='text/javascript'> @@ -614,8 +606,8 @@ window.addEventListener("keydown", function (event) { )<<<"; } -void HTMLDiagnostics::RewriteFile(Rewriter &R, const SourceManager& SMgr, - const PathPieces& path, FileID FID) { +void HTMLDiagnostics::RewriteFile(Rewriter &R, + const PathPieces& path, FileID FID) { // Process the path. // Maintain the counts of extra note pieces separately. unsigned TotalPieces = path.size(); @@ -944,7 +936,7 @@ void HTMLDiagnostics::HighlightRange(Rewriter& R, FileID BugFileID, html::HighlightRange(R, InstantiationStart, E, HighlightStart, HighlightEnd); } -std::string HTMLDiagnostics::generateKeyboardNavigationJavascript() { +StringRef HTMLDiagnostics::generateKeyboardNavigationJavascript() { return R"<<<( <script type='text/javascript'> var digitMatcher = new RegExp("[0-9]+"); @@ -997,7 +989,8 @@ var numToId = function(num) { }; var navigateTo = function(up) { - var numItems = document.querySelectorAll(".line > .msg").length; + var numItems = document.querySelectorAll( + ".line > .msgEvent, .line > .msgControl").length; var currentSelected = findNum(); var newSelected = move(currentSelected, up, numItems); var newEl = numToId(newSelected, numItems); diff --git a/lib/StaticAnalyzer/Core/LoopWidening.cpp b/lib/StaticAnalyzer/Core/LoopWidening.cpp index 9192f49eac6d2..8f6cb9a6b09ef 100644 --- a/lib/StaticAnalyzer/Core/LoopWidening.cpp +++ b/lib/StaticAnalyzer/Core/LoopWidening.cpp @@ -81,11 +81,12 @@ ProgramStateRef getWidenedLoopState(ProgramStateRef PrevState, // 'this' pointer is not an lvalue, we should not invalidate it. If the loop // is located in a method, constructor or destructor, the value of 'this' - // pointer shoule remain unchanged. - if (const CXXMethodDecl *CXXMD = dyn_cast<CXXMethodDecl>(STC->getDecl())) { - const CXXThisRegion *ThisR = MRMgr.getCXXThisRegion( - CXXMD->getThisType(STC->getAnalysisDeclContext()->getASTContext()), - STC); + // pointer should remain unchanged. Ignore static methods, since they do not + // have 'this' pointers. + const CXXMethodDecl *CXXMD = dyn_cast<CXXMethodDecl>(STC->getDecl()); + if (CXXMD && !CXXMD->isStatic()) { + const CXXThisRegion *ThisR = + MRMgr.getCXXThisRegion(CXXMD->getThisType(), STC); ITraits.setTrait(ThisR, RegionAndSymbolInvalidationTraits::TK_PreserveContents); } diff --git a/lib/StaticAnalyzer/Core/MemRegion.cpp b/lib/StaticAnalyzer/Core/MemRegion.cpp index cb2122c7749ef..9a1d4d73c20b1 100644 --- a/lib/StaticAnalyzer/Core/MemRegion.cpp +++ b/lib/StaticAnalyzer/Core/MemRegion.cpp @@ -225,6 +225,10 @@ QualType CXXBaseObjectRegion::getValueType() const { return QualType(getDecl()->getTypeForDecl(), 0); } +QualType CXXDerivedObjectRegion::getValueType() const { + return QualType(getDecl()->getTypeForDecl(), 0); +} + //===----------------------------------------------------------------------===// // FoldingSet profiling. //===----------------------------------------------------------------------===// @@ -404,6 +408,17 @@ void CXXBaseObjectRegion::Profile(llvm::FoldingSetNodeID &ID) const { ProfileRegion(ID, getDecl(), isVirtual(), superRegion); } +void CXXDerivedObjectRegion::ProfileRegion(llvm::FoldingSetNodeID &ID, + const CXXRecordDecl *RD, + const MemRegion *SReg) { + ID.AddPointer(RD); + ID.AddPointer(SReg); +} + +void CXXDerivedObjectRegion::Profile(llvm::FoldingSetNodeID &ID) const { + ProfileRegion(ID, getDecl(), superRegion); +} + //===----------------------------------------------------------------------===// // Region anchors. //===----------------------------------------------------------------------===// @@ -442,7 +457,7 @@ void MemRegion::dumpToStream(raw_ostream &os) const { } void AllocaRegion::dumpToStream(raw_ostream &os) const { - os << "alloca{" << static_cast<const void *>(Ex) << ',' << Cnt << '}'; + os << "alloca{S" << Ex->getID(getContext()) << ',' << Cnt << '}'; } void FunctionCodeRegion::dumpToStream(raw_ostream &os) const { @@ -466,16 +481,20 @@ void BlockDataRegion::dumpToStream(raw_ostream &os) const { void CompoundLiteralRegion::dumpToStream(raw_ostream &os) const { // FIXME: More elaborate pretty-printing. - os << "{ " << static_cast<const void *>(CL) << " }"; + os << "{ S" << CL->getID(getContext()) << " }"; } void CXXTempObjectRegion::dumpToStream(raw_ostream &os) const { - os << "temp_object{" << getValueType().getAsString() << ',' - << static_cast<const void *>(Ex) << '}'; + os << "temp_object{" << getValueType().getAsString() << ", " + << "S" << Ex->getID(getContext()) << '}'; } void CXXBaseObjectRegion::dumpToStream(raw_ostream &os) const { - os << "base{" << superRegion << ',' << getDecl()->getName() << '}'; + os << "Base{" << superRegion << ',' << getDecl()->getName() << '}'; +} + +void CXXDerivedObjectRegion::dumpToStream(raw_ostream &os) const { + os << "Derived{" << superRegion << ',' << getDecl()->getName() << '}'; } void CXXThisRegion::dumpToStream(raw_ostream &os) const { @@ -483,7 +502,7 @@ void CXXThisRegion::dumpToStream(raw_ostream &os) const { } void ElementRegion::dumpToStream(raw_ostream &os) const { - os << "element{" << superRegion << ',' + os << "Element{" << superRegion << ',' << Index << ',' << getElementType().getAsString() << '}'; } @@ -492,7 +511,7 @@ void FieldRegion::dumpToStream(raw_ostream &os) const { } void ObjCIvarRegion::dumpToStream(raw_ostream &os) const { - os << "ivar{" << superRegion << ',' << *getDecl() << '}'; + os << "Ivar{" << superRegion << ',' << *getDecl() << '}'; } void StringRegion::dumpToStream(raw_ostream &os) const { @@ -516,7 +535,7 @@ void VarRegion::dumpToStream(raw_ostream &os) const { if (const IdentifierInfo *ID = VD->getIdentifier()) os << ID->getName(); else - os << "VarRegion{" << static_cast<const void *>(this) << '}'; + os << "VarRegion{D" << VD->getID() << '}'; } LLVM_DUMP_METHOD void RegionRawOffset::dump() const { @@ -578,7 +597,7 @@ void MemRegion::printPretty(raw_ostream &os) const { os << "'"; } -void MemRegion::printPrettyAsExpr(raw_ostream &os) const { +void MemRegion::printPrettyAsExpr(raw_ostream &) const { llvm_unreachable("This region cannot be printed pretty."); } @@ -630,6 +649,14 @@ void CXXBaseObjectRegion::printPrettyAsExpr(raw_ostream &os) const { superRegion->printPrettyAsExpr(os); } +bool CXXDerivedObjectRegion::canPrintPrettyAsExpr() const { + return superRegion->canPrintPrettyAsExpr(); +} + +void CXXDerivedObjectRegion::printPrettyAsExpr(raw_ostream &os) const { + superRegion->printPrettyAsExpr(os); +} + std::string MemRegion::getDescriptiveName(bool UseQuotes) const { std::string VariableName; std::string ArrayIndices; @@ -1061,6 +1088,12 @@ MemRegionManager::getCXXBaseObjectRegion(const CXXRecordDecl *RD, return getSubRegion<CXXBaseObjectRegion>(RD, IsVirtual, Super); } +const CXXDerivedObjectRegion * +MemRegionManager::getCXXDerivedObjectRegion(const CXXRecordDecl *RD, + const SubRegion *Super) { + return getSubRegion<CXXDerivedObjectRegion>(RD, Super); +} + const CXXThisRegion* MemRegionManager::getCXXThisRegion(QualType thisPointerTy, const LocationContext *LC) { @@ -1072,9 +1105,8 @@ MemRegionManager::getCXXThisRegion(QualType thisPointerTy, // FIXME: when operator() of lambda is analyzed as a top level function and // 'this' refers to a this to the enclosing scope, there is no right region to // return. - while (!LC->inTopFrame() && - (!D || D->isStatic() || - PT != D->getThisType(getContext())->getAs<PointerType>())) { + while (!LC->inTopFrame() && (!D || D->isStatic() || + PT != D->getThisType()->getAs<PointerType>())) { LC = LC->getParent(); D = dyn_cast<CXXMethodDecl>(LC->getDecl()); } @@ -1131,6 +1163,7 @@ const MemRegion *MemRegion::getBaseRegion() const { case MemRegion::FieldRegionKind: case MemRegion::ObjCIvarRegionKind: case MemRegion::CXXBaseObjectRegionKind: + case MemRegion::CXXDerivedObjectRegionKind: R = cast<SubRegion>(R)->getSuperRegion(); continue; default: @@ -1141,7 +1174,16 @@ const MemRegion *MemRegion::getBaseRegion() const { return R; } -bool MemRegion::isSubRegionOf(const MemRegion *R) const { +// getgetMostDerivedObjectRegion gets the region of the root class of a C++ +// class hierarchy. +const MemRegion *MemRegion::getMostDerivedObjectRegion() const { + const MemRegion *R = this; + while (const auto *BR = dyn_cast<CXXBaseObjectRegion>(R)) + R = BR->getSuperRegion(); + return R; +} + +bool MemRegion::isSubRegionOf(const MemRegion *) const { return false; } @@ -1149,7 +1191,7 @@ bool MemRegion::isSubRegionOf(const MemRegion *R) const { // View handling. //===----------------------------------------------------------------------===// -const MemRegion *MemRegion::StripCasts(bool StripBaseCasts) const { +const MemRegion *MemRegion::StripCasts(bool StripBaseAndDerivedCasts) const { const MemRegion *R = this; while (true) { switch (R->getKind()) { @@ -1161,9 +1203,10 @@ const MemRegion *MemRegion::StripCasts(bool StripBaseCasts) const { break; } case CXXBaseObjectRegionKind: - if (!StripBaseCasts) + case CXXDerivedObjectRegionKind: + if (!StripBaseAndDerivedCasts) return R; - R = cast<CXXBaseObjectRegion>(R)->getSuperRegion(); + R = cast<TypedValueRegion>(R)->getSuperRegion(); break; default: return R; @@ -1344,6 +1387,12 @@ static RegionOffset calculateOffset(const MemRegion *R) { Offset += BaseOffset.getQuantity() * R->getContext().getCharWidth(); break; } + + case MemRegion::CXXDerivedObjectRegionKind: { + // TODO: Store the base type in the CXXDerivedObjectRegion and use it. + goto Finish; + } + case MemRegion::ElementRegionKind: { const auto *ER = cast<ElementRegion>(R); R = ER->getSuperRegion(); diff --git a/lib/StaticAnalyzer/Core/PathDiagnostic.cpp b/lib/StaticAnalyzer/Core/PathDiagnostic.cpp index 1b698ec5c0864..3e93bb6a7c4fc 100644 --- a/lib/StaticAnalyzer/Core/PathDiagnostic.cpp +++ b/lib/StaticAnalyzer/Core/PathDiagnostic.cpp @@ -536,7 +536,7 @@ PathDiagnosticConsumer::FilesMade::getFiles(const PathDiagnostic &PD) { static SourceLocation getValidSourceLocation(const Stmt* S, LocationOrAnalysisDeclContext LAC, bool UseEnd = false) { - SourceLocation L = UseEnd ? S->getLocEnd() : S->getLocStart(); + SourceLocation L = UseEnd ? S->getEndLoc() : S->getBeginLoc(); assert(!LAC.isNull() && "A valid LocationContext or AnalysisDeclContext should " "be passed to PathDiagnosticLocation upon creation."); @@ -562,13 +562,13 @@ static SourceLocation getValidSourceLocation(const Stmt* S, if (!Parent) { const Stmt *Body = ADC->getBody(); if (Body) - L = Body->getLocStart(); + L = Body->getBeginLoc(); else - L = ADC->getDecl()->getLocEnd(); + L = ADC->getDecl()->getEndLoc(); break; } - L = UseEnd ? Parent->getLocEnd() : Parent->getLocStart(); + L = UseEnd ? Parent->getEndLoc() : Parent->getBeginLoc(); } while (!L.isValid()); } @@ -635,7 +635,7 @@ getLocationForCaller(const StackFrameContext *SFC, PathDiagnosticLocation PathDiagnosticLocation::createBegin(const Decl *D, const SourceManager &SM) { - return PathDiagnosticLocation(D->getLocStart(), SM, SingleLocK); + return PathDiagnosticLocation(D->getBeginLoc(), SM, SingleLocK); } PathDiagnosticLocation @@ -695,7 +695,7 @@ PathDiagnosticLocation::createDeclBegin(const LocationContext *LC, // FIXME: Should handle CXXTryStmt if analyser starts supporting C++. if (const auto *CS = dyn_cast_or_null<CompoundStmt>(LC->getDecl()->getBody())) if (!CS->body_empty()) { - SourceLocation Loc = (*CS->body_begin())->getLocStart(); + SourceLocation Loc = (*CS->body_begin())->getBeginLoc(); return PathDiagnosticLocation(Loc, SM, SingleLocK); } @@ -723,6 +723,8 @@ PathDiagnosticLocation::create(const ProgramPoint& P, } else if (Optional<PostInitializer> PIP = P.getAs<PostInitializer>()) { return PathDiagnosticLocation(PIP->getInitializer()->getSourceLocation(), SMng); + } else if (Optional<PreImplicitCall> PIC = P.getAs<PreImplicitCall>()) { + return PathDiagnosticLocation(PIC->getLocation(), SMng); } else if (Optional<PostImplicitCall> PIE = P.getAs<PostImplicitCall>()) { return PathDiagnosticLocation(PIE->getLocation(), SMng); } else if (Optional<CallEnter> CE = P.getAs<CallEnter>()) { @@ -736,10 +738,10 @@ PathDiagnosticLocation::create(const ProgramPoint& P, } else if (Optional<BlockEntrance> BE = P.getAs<BlockEntrance>()) { CFGElement BlockFront = BE->getBlock()->front(); if (auto StmtElt = BlockFront.getAs<CFGStmt>()) { - return PathDiagnosticLocation(StmtElt->getStmt()->getLocStart(), SMng); + return PathDiagnosticLocation(StmtElt->getStmt()->getBeginLoc(), SMng); } else if (auto NewAllocElt = BlockFront.getAs<CFGNewAllocator>()) { return PathDiagnosticLocation( - NewAllocElt->getAllocatorExpr()->getLocStart(), SMng); + NewAllocElt->getAllocatorExpr()->getBeginLoc(), SMng); } llvm_unreachable("Unexpected CFG element at front of block"); } else { @@ -774,18 +776,20 @@ const Stmt *PathDiagnosticLocation::getStmt(const ExplodedNode *N) { } // Otherwise, see if the node's program point directly points to a statement. ProgramPoint P = N->getLocation(); - if (Optional<StmtPoint> SP = P.getAs<StmtPoint>()) + if (auto SP = P.getAs<StmtPoint>()) return SP->getStmt(); - if (Optional<BlockEdge> BE = P.getAs<BlockEdge>()) + if (auto BE = P.getAs<BlockEdge>()) return BE->getSrc()->getTerminator(); - if (Optional<CallEnter> CE = P.getAs<CallEnter>()) + if (auto CE = P.getAs<CallEnter>()) return CE->getCallExpr(); - if (Optional<CallExitEnd> CEE = P.getAs<CallExitEnd>()) + if (auto CEE = P.getAs<CallExitEnd>()) return CEE->getCalleeContext()->getCallSite(); - if (Optional<PostInitializer> PIPP = P.getAs<PostInitializer>()) + if (auto PIPP = P.getAs<PostInitializer>()) return PIPP->getInitializer()->getInit(); - if (Optional<CallExitBegin> CEB = P.getAs<CallExitBegin>()) + if (auto CEB = P.getAs<CallExitBegin>()) return CEB->getReturnStmt(); + if (auto FEP = P.getAs<FunctionExitPoint>()) + return FEP->getStmt(); return nullptr; } @@ -822,17 +826,21 @@ PathDiagnosticLocation const SourceManager &SM) { assert(N && "Cannot create a location with a null node."); const Stmt *S = getStmt(N); + const LocationContext *LC = N->getLocationContext(); if (!S) { // If this is an implicit call, return the implicit call point location. if (Optional<PreImplicitCall> PIE = N->getLocationAs<PreImplicitCall>()) return PathDiagnosticLocation(PIE->getLocation(), SM); + if (auto FE = N->getLocationAs<FunctionExitPoint>()) { + if (const ReturnStmt *RS = FE->getStmt()) + return PathDiagnosticLocation::createBegin(RS, SM, LC); + } S = getNextStmt(N); } if (S) { ProgramPoint P = N->getLocation(); - const LocationContext *LC = N->getLocationContext(); // For member expressions, return the location of the '.' or '->'. if (const auto *ME = dyn_cast<MemberExpr>(S)) @@ -845,7 +853,7 @@ PathDiagnosticLocation if (P.getAs<PostStmtPurgeDeadSymbols>()) return PathDiagnosticLocation::createEnd(S, SM, LC); - if (S->getLocStart().isValid()) + if (S->getBeginLoc().isValid()) return PathDiagnosticLocation(S, SM, LC); return PathDiagnosticLocation(getValidSourceLocation(S, LC), SM); } @@ -904,7 +912,7 @@ PathDiagnosticRange const auto *DS = cast<DeclStmt>(S); if (DS->isSingleDecl()) { // Should always be the case, but we'll be defensive. - return SourceRange(DS->getLocStart(), + return SourceRange(DS->getBeginLoc(), DS->getSingleDecl()->getLocation()); } break; @@ -964,7 +972,7 @@ void PathDiagnosticLocation::flatten() { //===----------------------------------------------------------------------===// std::shared_ptr<PathDiagnosticCallPiece> -PathDiagnosticCallPiece::construct(const ExplodedNode *N, const CallExitEnd &CE, +PathDiagnosticCallPiece::construct(const CallExitEnd &CE, const SourceManager &SM) { const Decl *caller = CE.getLocationContext()->getDecl(); PathDiagnosticLocation pos = getLocationForCaller(CE.getCalleeContext(), diff --git a/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp b/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp index cfe780db9ec9a..db4cf76578d8f 100644 --- a/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp +++ b/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp @@ -16,6 +16,7 @@ #include "clang/Basic/SourceManager.h" #include "clang/Basic/Version.h" #include "clang/Lex/Preprocessor.h" +#include "clang/Lex/TokenConcatenation.h" #include "clang/Rewrite/Core/HTMLRewrite.h" #include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" #include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" @@ -24,20 +25,26 @@ #include "llvm/ADT/Statistic.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Support/Casting.h" + using namespace clang; using namespace ento; using namespace markup; +//===----------------------------------------------------------------------===// +// Declarations of helper classes and functions for emitting bug reports in +// plist format. +//===----------------------------------------------------------------------===// + namespace { class PlistDiagnostics : public PathDiagnosticConsumer { const std::string OutputFile; - const LangOptions &LangOpts; + const Preprocessor &PP; + AnalyzerOptions &AnOpts; const bool SupportsCrossFileDiagnostics; - const bool SerializeStatistics; public: PlistDiagnostics(AnalyzerOptions &AnalyzerOpts, const std::string& prefix, - const LangOptions &LangOpts, + const Preprocessor &PP, bool supportsMultipleFiles); ~PlistDiagnostics() override {} @@ -59,37 +66,116 @@ namespace { }; } // end anonymous namespace -PlistDiagnostics::PlistDiagnostics(AnalyzerOptions &AnalyzerOpts, - const std::string& output, - const LangOptions &LO, - bool supportsMultipleFiles) - : OutputFile(output), - LangOpts(LO), - SupportsCrossFileDiagnostics(supportsMultipleFiles), - SerializeStatistics(AnalyzerOpts.shouldSerializeStats()) {} +namespace { -void ento::createPlistDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts, - PathDiagnosticConsumers &C, - const std::string& s, - const Preprocessor &PP) { - C.push_back(new PlistDiagnostics(AnalyzerOpts, s, - PP.getLangOpts(), false)); -} +/// A helper class for emitting a single report. +class PlistPrinter { + const FIDMap& FM; + AnalyzerOptions &AnOpts; + const Preprocessor &PP; + llvm::SmallVector<const PathDiagnosticMacroPiece *, 0> MacroPieces; + +public: + PlistPrinter(const FIDMap& FM, AnalyzerOptions &AnOpts, + const Preprocessor &PP) + : FM(FM), AnOpts(AnOpts), PP(PP) { + } -void ento::createPlistMultiFileDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts, - PathDiagnosticConsumers &C, - const std::string &s, - const Preprocessor &PP) { - C.push_back(new PlistDiagnostics(AnalyzerOpts, s, - PP.getLangOpts(), true)); -} + void ReportDiag(raw_ostream &o, const PathDiagnosticPiece& P) { + ReportPiece(o, P, /*indent*/ 4, /*depth*/ 0, /*includeControlFlow*/ true); + + // Don't emit a warning about an unused private field. + (void)AnOpts; + } + + /// Print the expansions of the collected macro pieces. + /// + /// Each time ReportDiag is called on a PathDiagnosticMacroPiece (or, if one + /// is found through a call piece, etc), it's subpieces are reported, and the + /// piece itself is collected. Call this function after the entire bugpath + /// was reported. + void ReportMacroExpansions(raw_ostream &o, unsigned indent); + +private: + void ReportPiece(raw_ostream &o, const PathDiagnosticPiece &P, + unsigned indent, unsigned depth, bool includeControlFlow, + bool isKeyEvent = false) { + switch (P.getKind()) { + case PathDiagnosticPiece::ControlFlow: + if (includeControlFlow) + ReportControlFlow(o, cast<PathDiagnosticControlFlowPiece>(P), indent); + break; + case PathDiagnosticPiece::Call: + ReportCall(o, cast<PathDiagnosticCallPiece>(P), indent, + depth); + break; + case PathDiagnosticPiece::Event: + ReportEvent(o, cast<PathDiagnosticEventPiece>(P), indent, depth, + isKeyEvent); + break; + case PathDiagnosticPiece::Macro: + ReportMacroSubPieces(o, cast<PathDiagnosticMacroPiece>(P), indent, + depth); + break; + case PathDiagnosticPiece::Note: + ReportNote(o, cast<PathDiagnosticNotePiece>(P), indent); + break; + } + } + + void EmitRanges(raw_ostream &o, const ArrayRef<SourceRange> Ranges, + unsigned indent); + void EmitMessage(raw_ostream &o, StringRef Message, unsigned indent); + + void ReportControlFlow(raw_ostream &o, + const PathDiagnosticControlFlowPiece& P, + unsigned indent); + void ReportEvent(raw_ostream &o, const PathDiagnosticEventPiece& P, + unsigned indent, unsigned depth, bool isKeyEvent = false); + void ReportCall(raw_ostream &o, const PathDiagnosticCallPiece &P, + unsigned indent, unsigned depth); + void ReportMacroSubPieces(raw_ostream &o, const PathDiagnosticMacroPiece& P, + unsigned indent, unsigned depth); + void ReportNote(raw_ostream &o, const PathDiagnosticNotePiece& P, + unsigned indent); +}; + +} // end of anonymous namespace -static void EmitRanges(raw_ostream &o, - const ArrayRef<SourceRange> Ranges, - const FIDMap& FM, - const SourceManager &SM, - const LangOptions &LangOpts, - unsigned indent) { +namespace { + +struct ExpansionInfo { + std::string MacroName; + std::string Expansion; + ExpansionInfo(std::string N, std::string E) + : MacroName(std::move(N)), Expansion(std::move(E)) {} +}; + +} // end of anonymous namespace + +static void printBugPath(llvm::raw_ostream &o, const FIDMap& FM, + AnalyzerOptions &AnOpts, + const Preprocessor &PP, + const PathPieces &Path); + +/// Print coverage information to output stream {@code o}. +/// May modify the used list of files {@code Fids} by inserting new ones. +static void printCoverage(const PathDiagnostic *D, + unsigned InputIndentLevel, + SmallVectorImpl<FileID> &Fids, + FIDMap &FM, + llvm::raw_fd_ostream &o); + +static ExpansionInfo getExpandedMacro(SourceLocation MacroLoc, + const Preprocessor &PP); + +//===----------------------------------------------------------------------===// +// Methods of PlistPrinter. +//===----------------------------------------------------------------------===// + +void PlistPrinter::EmitRanges(raw_ostream &o, + const ArrayRef<SourceRange> Ranges, + unsigned indent) { if (Ranges.empty()) return; @@ -97,6 +183,10 @@ static void EmitRanges(raw_ostream &o, Indent(o, indent) << "<key>ranges</key>\n"; Indent(o, indent) << "<array>\n"; ++indent; + + const SourceManager &SM = PP.getSourceManager(); + const LangOptions &LangOpts = PP.getLangOpts(); + for (auto &R : Ranges) EmitRange(o, SM, Lexer::getAsCharRange(SM.getExpansionRange(R), SM, LangOpts), @@ -105,7 +195,8 @@ static void EmitRanges(raw_ostream &o, Indent(o, indent) << "</array>\n"; } -static void EmitMessage(raw_ostream &o, StringRef Message, unsigned indent) { +void PlistPrinter::EmitMessage(raw_ostream &o, StringRef Message, + unsigned indent) { // Output the text. assert(!Message.empty()); Indent(o, indent) << "<key>extended_message</key>\n"; @@ -119,12 +210,12 @@ static void EmitMessage(raw_ostream &o, StringRef Message, unsigned indent) { EmitString(o, Message) << '\n'; } -static void ReportControlFlow(raw_ostream &o, - const PathDiagnosticControlFlowPiece& P, - const FIDMap& FM, - const SourceManager &SM, - const LangOptions &LangOpts, - unsigned indent) { +void PlistPrinter::ReportControlFlow(raw_ostream &o, + const PathDiagnosticControlFlowPiece& P, + unsigned indent) { + + const SourceManager &SM = PP.getSourceManager(); + const LangOptions &LangOpts = PP.getLangOpts(); Indent(o, indent) << "<dict>\n"; ++indent; @@ -173,13 +264,11 @@ static void ReportControlFlow(raw_ostream &o, Indent(o, indent) << "</dict>\n"; } -static void ReportEvent(raw_ostream &o, const PathDiagnosticEventPiece& P, - const FIDMap& FM, - const SourceManager &SM, - const LangOptions &LangOpts, - unsigned indent, - unsigned depth, - bool isKeyEvent = false) { +void PlistPrinter::ReportEvent(raw_ostream &o, const PathDiagnosticEventPiece& P, + unsigned indent, unsigned depth, + bool isKeyEvent) { + + const SourceManager &SM = PP.getSourceManager(); Indent(o, indent) << "<dict>\n"; ++indent; @@ -198,7 +287,7 @@ static void ReportEvent(raw_ostream &o, const PathDiagnosticEventPiece& P, // Output the ranges (if any). ArrayRef<SourceRange> Ranges = P.getRanges(); - EmitRanges(o, Ranges, FM, SM, LangOpts, indent); + EmitRanges(o, Ranges, indent); // Output the call depth. Indent(o, indent) << "<key>depth</key>"; @@ -212,61 +301,80 @@ static void ReportEvent(raw_ostream &o, const PathDiagnosticEventPiece& P, Indent(o, indent); o << "</dict>\n"; } -static void ReportPiece(raw_ostream &o, - const PathDiagnosticPiece &P, - const FIDMap& FM, const SourceManager &SM, - const LangOptions &LangOpts, - unsigned indent, - unsigned depth, - bool includeControlFlow, - bool isKeyEvent = false); - -static void ReportCall(raw_ostream &o, - const PathDiagnosticCallPiece &P, - const FIDMap& FM, const SourceManager &SM, - const LangOptions &LangOpts, - unsigned indent, - unsigned depth) { +void PlistPrinter::ReportCall(raw_ostream &o, const PathDiagnosticCallPiece &P, + unsigned indent, + unsigned depth) { if (auto callEnter = P.getCallEnterEvent()) - ReportPiece(o, *callEnter, FM, SM, LangOpts, indent, depth, true, + ReportPiece(o, *callEnter, indent, depth, /*includeControlFlow*/ true, P.isLastInMainSourceFile()); ++depth; if (auto callEnterWithinCaller = P.getCallEnterWithinCallerEvent()) - ReportPiece(o, *callEnterWithinCaller, FM, SM, LangOpts, - indent, depth, true); + ReportPiece(o, *callEnterWithinCaller, indent, depth, + /*includeControlFlow*/ true); for (PathPieces::const_iterator I = P.path.begin(), E = P.path.end();I!=E;++I) - ReportPiece(o, **I, FM, SM, LangOpts, indent, depth, true); + ReportPiece(o, **I, indent, depth, /*includeControlFlow*/ true); --depth; if (auto callExit = P.getCallExitEvent()) - ReportPiece(o, *callExit, FM, SM, LangOpts, indent, depth, true); + ReportPiece(o, *callExit, indent, depth, /*includeControlFlow*/ true); } -static void ReportMacro(raw_ostream &o, - const PathDiagnosticMacroPiece& P, - const FIDMap& FM, const SourceManager &SM, - const LangOptions &LangOpts, - unsigned indent, - unsigned depth) { +void PlistPrinter::ReportMacroSubPieces(raw_ostream &o, + const PathDiagnosticMacroPiece& P, + unsigned indent, unsigned depth) { + MacroPieces.push_back(&P); - for (PathPieces::const_iterator I = P.subPieces.begin(), E=P.subPieces.end(); - I!=E; ++I) { - ReportPiece(o, **I, FM, SM, LangOpts, indent, depth, false); + for (PathPieces::const_iterator I = P.subPieces.begin(), + E = P.subPieces.end(); + I != E; ++I) { + ReportPiece(o, **I, indent, depth, /*includeControlFlow*/ false); + } +} + +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); + + Indent(o, indent) << "<dict>\n"; + ++indent; + + // Output the location. + FullSourceLoc L = P->getLocation().asLocation(); + + Indent(o, indent) << "<key>location</key>\n"; + EmitLocation(o, SM, L, FM, indent); + + // Output the ranges (if any). + ArrayRef<SourceRange> Ranges = P->getRanges(); + EmitRanges(o, Ranges, indent); + + // Output the macro name. + Indent(o, indent) << "<key>name</key>"; + EmitString(o, EI.MacroName) << '\n'; + + // Output what it expands into. + Indent(o, indent) << "<key>expansion</key>"; + EmitString(o, EI.Expansion) << '\n'; + + // Finish up. + --indent; + Indent(o, indent); + o << "</dict>\n"; } } -static void ReportNote(raw_ostream &o, const PathDiagnosticNotePiece& P, - const FIDMap& FM, - const SourceManager &SM, - const LangOptions &LangOpts, - unsigned indent, - unsigned depth) { +void PlistPrinter::ReportNote(raw_ostream &o, const PathDiagnosticNotePiece& P, + unsigned indent) { + + const SourceManager &SM = PP.getSourceManager(); Indent(o, indent) << "<dict>\n"; ++indent; @@ -279,7 +387,7 @@ static void ReportNote(raw_ostream &o, const PathDiagnosticNotePiece& P, // Output the ranges (if any). ArrayRef<SourceRange> Ranges = P.getRanges(); - EmitRanges(o, Ranges, FM, SM, LangOpts, indent); + EmitRanges(o, Ranges, indent); // Output the text. EmitMessage(o, P.getString(), indent); @@ -289,45 +397,115 @@ static void ReportNote(raw_ostream &o, const PathDiagnosticNotePiece& P, Indent(o, indent); o << "</dict>\n"; } -static void ReportDiag(raw_ostream &o, const PathDiagnosticPiece& P, - const FIDMap& FM, const SourceManager &SM, - const LangOptions &LangOpts) { - ReportPiece(o, P, FM, SM, LangOpts, 4, 0, true); +//===----------------------------------------------------------------------===// +// Static function definitions. +//===----------------------------------------------------------------------===// + +/// Print coverage information to output stream {@code o}. +/// May modify the used list of files {@code Fids} by inserting new ones. +static void printCoverage(const PathDiagnostic *D, + unsigned InputIndentLevel, + SmallVectorImpl<FileID> &Fids, + FIDMap &FM, + llvm::raw_fd_ostream &o) { + unsigned IndentLevel = InputIndentLevel; + + Indent(o, IndentLevel) << "<key>ExecutedLines</key>\n"; + Indent(o, IndentLevel) << "<dict>\n"; + IndentLevel++; + + // Mapping from file IDs to executed lines. + const FilesToLineNumsMap &ExecutedLines = D->getExecutedLines(); + for (auto I = ExecutedLines.begin(), E = ExecutedLines.end(); I != E; ++I) { + unsigned FileKey = AddFID(FM, Fids, I->first); + Indent(o, IndentLevel) << "<key>" << FileKey << "</key>\n"; + Indent(o, IndentLevel) << "<array>\n"; + IndentLevel++; + for (unsigned LineNo : I->second) { + Indent(o, IndentLevel); + EmitInteger(o, LineNo) << "\n"; + } + IndentLevel--; + Indent(o, IndentLevel) << "</array>\n"; + } + IndentLevel--; + Indent(o, IndentLevel) << "</dict>\n"; + + assert(IndentLevel == InputIndentLevel); } -static void ReportPiece(raw_ostream &o, - const PathDiagnosticPiece &P, - const FIDMap& FM, const SourceManager &SM, - const LangOptions &LangOpts, - unsigned indent, - unsigned depth, - bool includeControlFlow, - bool isKeyEvent) { - switch (P.getKind()) { - case PathDiagnosticPiece::ControlFlow: - if (includeControlFlow) - ReportControlFlow(o, cast<PathDiagnosticControlFlowPiece>(P), FM, SM, - LangOpts, indent); - break; - case PathDiagnosticPiece::Call: - ReportCall(o, cast<PathDiagnosticCallPiece>(P), FM, SM, LangOpts, - indent, depth); - break; - case PathDiagnosticPiece::Event: - ReportEvent(o, cast<PathDiagnosticEventPiece>(P), FM, SM, LangOpts, - indent, depth, isKeyEvent); - break; - case PathDiagnosticPiece::Macro: - ReportMacro(o, cast<PathDiagnosticMacroPiece>(P), FM, SM, LangOpts, - indent, depth); - break; - case PathDiagnosticPiece::Note: - ReportNote(o, cast<PathDiagnosticNotePiece>(P), FM, SM, LangOpts, - indent, depth); - break; +static void printBugPath(llvm::raw_ostream &o, const FIDMap& FM, + AnalyzerOptions &AnOpts, + const Preprocessor &PP, + const PathPieces &Path) { + PlistPrinter Printer(FM, AnOpts, PP); + assert(std::is_partitioned( + Path.begin(), Path.end(), + [](const std::shared_ptr<PathDiagnosticPiece> &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<PathDiagnosticPiece> &E) + { return E->getKind() == PathDiagnosticPiece::Note; }); + + PathPieces::const_iterator I = Path.begin(); + + if (FirstNonNote != Path.begin()) { + o << " <key>notes</key>\n" + " <array>\n"; + + for (; I != FirstNonNote; ++I) + Printer.ReportDiag(o, **I); + + o << " </array>\n"; } + + o << " <key>path</key>\n"; + + o << " <array>\n"; + + for (PathPieces::const_iterator E = Path.end(); I != E; ++I) + Printer.ReportDiag(o, **I); + + o << " </array>\n"; + + if (!AnOpts.ShouldDisplayMacroExpansions) + return; + + o << " <key>macro_expansions</key>\n" + " <array>\n"; + Printer.ReportMacroExpansions(o, /* indent */ 4); + o << " </array>\n"; } +//===----------------------------------------------------------------------===// +// 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, + /*supportsMultipleFiles*/ false)); +} + +void ento::createPlistMultiFileDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts, + PathDiagnosticConsumers &C, + const std::string &s, + const Preprocessor &PP) { + C.push_back(new PlistDiagnostics(AnalyzerOpts, s, PP, + /*supportsMultipleFiles*/ true)); +} void PlistDiagnostics::FlushDiagnosticsImpl( std::vector<const PathDiagnostic *> &Diags, FilesMade *filesMade) { @@ -335,17 +513,15 @@ void PlistDiagnostics::FlushDiagnosticsImpl( // ranges of the diagnostics. FIDMap FM; SmallVector<FileID, 10> Fids; - const SourceManager* SM = nullptr; - - if (!Diags.empty()) - SM = &Diags.front()->path.front()->getLocation().getManager(); + const SourceManager& SM = PP.getSourceManager(); + const LangOptions &LangOpts = PP.getLangOpts(); - auto AddPieceFID = [&FM, &Fids, SM](const PathDiagnosticPiece &Piece) { - AddFID(FM, Fids, *SM, Piece.getLocation().asLocation()); + auto AddPieceFID = [&FM, &Fids, &SM](const PathDiagnosticPiece &Piece) { + AddFID(FM, Fids, SM, Piece.getLocation().asLocation()); ArrayRef<SourceRange> Ranges = Piece.getRanges(); for (const SourceRange &Range : Ranges) { - AddFID(FM, Fids, *SM, Range.getBegin()); - AddFID(FM, Fids, *SM, Range.getEnd()); + AddFID(FM, Fids, SM, Range.getBegin()); + AddFID(FM, Fids, SM, Range.getEnd()); } }; @@ -395,14 +571,7 @@ void PlistDiagnostics::FlushDiagnosticsImpl( o << "<dict>\n" << " <key>clang_version</key>\n"; EmitString(o, getClangFullVersion()) << '\n'; - o << " <key>files</key>\n" - " <array>\n"; - - for (FileID FID : Fids) - EmitString(o << " ", SM->getFileEntryForID(FID)->getName()) << '\n'; - - o << " </array>\n" - " <key>diagnostics</key>\n" + o << " <key>diagnostics</key>\n" " <array>\n"; for (std::vector<const PathDiagnostic*>::iterator DI=Diags.begin(), @@ -411,39 +580,7 @@ void PlistDiagnostics::FlushDiagnosticsImpl( o << " <dict>\n"; const PathDiagnostic *D = *DI; - const PathPieces &PP = D->path; - - assert(std::is_partitioned( - PP.begin(), PP.end(), - [](const std::shared_ptr<PathDiagnosticPiece> &E) - { return E->getKind() == PathDiagnosticPiece::Note; }) && - "PathDiagnostic is not partitioned so that notes precede the rest"); - - PathPieces::const_iterator FirstNonNote = std::partition_point( - PP.begin(), PP.end(), - [](const std::shared_ptr<PathDiagnosticPiece> &E) - { return E->getKind() == PathDiagnosticPiece::Note; }); - - PathPieces::const_iterator I = PP.begin(); - - if (FirstNonNote != PP.begin()) { - o << " <key>notes</key>\n" - " <array>\n"; - - for (; I != FirstNonNote; ++I) - ReportDiag(o, **I, FM, *SM, LangOpts); - - o << " </array>\n"; - } - - o << " <key>path</key>\n"; - - o << " <array>\n"; - - for (PathPieces::const_iterator E = PP.end(); I != E; ++I) - ReportDiag(o, **I, FM, *SM, LangOpts); - - o << " </array>\n"; + printBugPath(o, FM, AnOpts, PP, D->path); // Output the bug type and bug category. o << " <key>description</key>"; @@ -458,12 +595,12 @@ void PlistDiagnostics::FlushDiagnosticsImpl( o << " <!-- This hash is experimental and going to change! -->\n"; o << " <key>issue_hash_content_of_line_in_context</key>"; PathDiagnosticLocation UPDLoc = D->getUniqueingLoc(); - FullSourceLoc L(SM->getExpansionLoc(UPDLoc.isValid() + FullSourceLoc L(SM.getExpansionLoc(UPDLoc.isValid() ? UPDLoc.asLocation() : D->getLocation().asLocation()), - *SM); + SM); const Decl *DeclWithIssue = D->getDeclWithIssue(); - EmitString(o, GetIssueHash(*SM, L, D->getCheckName(), D->getBugType(), + EmitString(o, GetIssueHash(SM, L, D->getCheckName(), D->getBugType(), DeclWithIssue, LangOpts)) << '\n'; @@ -507,15 +644,17 @@ void PlistDiagnostics::FlushDiagnosticsImpl( // the leak location even after code is added between the allocation // site and the end of scope (leak report location). if (UPDLoc.isValid()) { - FullSourceLoc UFunL(SM->getExpansionLoc( - D->getUniqueingDecl()->getBody()->getLocStart()), *SM); + FullSourceLoc UFunL( + SM.getExpansionLoc( + D->getUniqueingDecl()->getBody()->getBeginLoc()), + SM); o << " <key>issue_hash_function_offset</key><string>" << L.getExpansionLineNumber() - UFunL.getExpansionLineNumber() << "</string>\n"; // Otherwise, use the location on which the bug is reported. } else { - FullSourceLoc FunL(SM->getExpansionLoc(Body->getLocStart()), *SM); + FullSourceLoc FunL(SM.getExpansionLoc(Body->getBeginLoc()), SM); o << " <key>issue_hash_function_offset</key><string>" << L.getExpansionLineNumber() - FunL.getExpansionLineNumber() << "</string>\n"; @@ -527,7 +666,7 @@ void PlistDiagnostics::FlushDiagnosticsImpl( // Output the location of the bug. o << " <key>location</key>\n"; - EmitLocation(o, *SM, D->getLocation().asLocation(), FM, 2); + EmitLocation(o, SM, D->getLocation().asLocation(), FM, 2); // Output the diagnostic to the sub-diagnostic client, if any. if (!filesMade->empty()) { @@ -551,13 +690,21 @@ void PlistDiagnostics::FlushDiagnosticsImpl( } } + printCoverage(D, /*IndentLevel=*/2, Fids, FM, o); + // Close up the entry. o << " </dict>\n"; } o << " </array>\n"; - if (llvm::AreStatisticsEnabled() && SerializeStatistics) { + o << " <key>files</key>\n" + " <array>\n"; + for (FileID FID : Fids) + EmitString(o << " ", SM.getFileEntryForID(FID)->getName()) << '\n'; + o << " </array>\n"; + + if (llvm::AreStatisticsEnabled() && AnOpts.ShouldSerializeStats) { o << " <key>statistics</key>\n"; std::string stats; llvm::raw_string_ostream os(stats); @@ -569,3 +716,402 @@ void PlistDiagnostics::FlushDiagnosticsImpl( // Finish. o << "</dict>\n</plist>"; } + +//===----------------------------------------------------------------------===// +// Declarations of helper functions and data structures for expanding macros. +//===----------------------------------------------------------------------===// + +namespace { + +using ExpArgTokens = llvm::SmallVector<Token, 2>; + +/// Maps unexpanded macro arguments to expanded arguments. A macro argument may +/// need to expanded further when it is nested inside another macro. +class MacroArgMap : public std::map<const IdentifierInfo *, ExpArgTokens> { +public: + void expandFromPrevMacro(const MacroArgMap &Super); +}; + +struct MacroNameAndArgs { + std::string Name; + const MacroInfo *MI = nullptr; + MacroArgMap Args; + + MacroNameAndArgs(std::string N, const MacroInfo *MI, MacroArgMap M) + : Name(std::move(N)), MI(MI), Args(std::move(M)) {} +}; + +class TokenPrinter { + llvm::raw_ostream &OS; + const Preprocessor &PP; + + Token PrevTok, PrevPrevTok; + TokenConcatenation ConcatInfo; + +public: + TokenPrinter(llvm::raw_ostream &OS, const Preprocessor &PP) + : OS(OS), PP(PP), ConcatInfo(PP) { + PrevTok.setKind(tok::unknown); + PrevPrevTok.setKind(tok::unknown); + } + + void printToken(const Token &Tok); +}; + +} // end of anonymous namespace + +/// The implementation method of getMacroExpansion: It prints the expansion of +/// a macro to \p Printer, and returns with the name of the macro. +/// +/// Since macros can be nested in one another, this function may call itself +/// recursively. +/// +/// Unfortunately, macro arguments have to expanded manually. To understand why, +/// observe the following example: +/// +/// #define PRINT(x) print(x) +/// #define DO_SOMETHING(str) PRINT(str) +/// +/// DO_SOMETHING("Cute panda cubs."); +/// +/// As we expand the last line, we'll immediately replace PRINT(str) with +/// print(x). The information that both 'str' and 'x' refers to the same string +/// is an information we have to forward, hence the argument \p PrevArgs. +static std::string getMacroNameAndPrintExpansion(TokenPrinter &Printer, + SourceLocation MacroLoc, + const Preprocessor &PP, + const MacroArgMap &PrevArgs); + +/// Retrieves the name of the macro and what it's arguments expand into +/// at \p ExpanLoc. +/// +/// For example, for the following macro expansion: +/// +/// #define SET_TO_NULL(x) x = 0 +/// #define NOT_SUSPICIOUS(a) \ +/// { \ +/// int b = 0; \ +/// } \ +/// SET_TO_NULL(a) +/// +/// int *ptr = new int(4); +/// NOT_SUSPICIOUS(&ptr); +/// *ptr = 5; +/// +/// When \p ExpanLoc references the last line, the macro name "NOT_SUSPICIOUS" +/// and the MacroArgMap map { (a, &ptr) } will be returned. +/// +/// When \p ExpanLoc references "SET_TO_NULL(a)" within the definition of +/// "NOT_SUSPICOUS", the macro name "SET_TO_NULL" and the MacroArgMap map +/// { (x, a) } will be returned. +static MacroNameAndArgs getMacroNameAndArgs(SourceLocation ExpanLoc, + const Preprocessor &PP); + +/// Retrieves the ')' token that matches '(' \p It points to. +static MacroInfo::tokens_iterator getMatchingRParen( + MacroInfo::tokens_iterator It, + MacroInfo::tokens_iterator End); + +/// Retrieves the macro info for \p II refers to at \p Loc. This is important +/// because macros can be redefined or undefined. +static const MacroInfo *getMacroInfoForLocation(const Preprocessor &PP, + const SourceManager &SM, + const IdentifierInfo *II, + SourceLocation Loc); + +//===----------------------------------------------------------------------===// +// Definitions of helper functions and methods for expanding macros. +//===----------------------------------------------------------------------===// + +static ExpansionInfo getExpandedMacro(SourceLocation MacroLoc, + const Preprocessor &PP) { + + llvm::SmallString<200> ExpansionBuf; + llvm::raw_svector_ostream OS(ExpansionBuf); + TokenPrinter Printer(OS, PP); + std::string MacroName = + getMacroNameAndPrintExpansion(Printer, MacroLoc, PP, MacroArgMap{}); + return { MacroName, OS.str() }; +} + +static std::string getMacroNameAndPrintExpansion(TokenPrinter &Printer, + SourceLocation MacroLoc, + const Preprocessor &PP, + const MacroArgMap &PrevArgs) { + + const SourceManager &SM = PP.getSourceManager(); + + MacroNameAndArgs Info = getMacroNameAndArgs(SM.getExpansionLoc(MacroLoc), PP); + + // Manually expand its arguments from the previous macro. + Info.Args.expandFromPrevMacro(PrevArgs); + + // Iterate over the macro's tokens and stringify them. + for (auto It = Info.MI->tokens_begin(), E = Info.MI->tokens_end(); It != E; + ++It) { + Token T = *It; + + // If this token is not an identifier, we only need to print it. + if (T.isNot(tok::identifier)) { + Printer.printToken(T); + continue; + } + + const auto *II = T.getIdentifierInfo(); + assert(II && + "This token is an identifier but has no IdentifierInfo!"); + + // If this token is a macro that should be expanded inside the current + // macro. + if (const MacroInfo *MI = + getMacroInfoForLocation(PP, SM, II, T.getLocation())) { + getMacroNameAndPrintExpansion(Printer, T.getLocation(), PP, Info.Args); + + // If this is a function-like macro, skip its arguments, as + // getExpandedMacro() already printed them. If this is the case, let's + // first jump to the '(' token. + if (MI->getNumParams() != 0) + It = getMatchingRParen(++It, E); + continue; + } + + // If this token is the current macro's argument, we should expand it. + auto ArgMapIt = Info.Args.find(II); + if (ArgMapIt != Info.Args.end()) { + for (MacroInfo::tokens_iterator ArgIt = ArgMapIt->second.begin(), + ArgEnd = ArgMapIt->second.end(); + ArgIt != ArgEnd; ++ArgIt) { + + // These tokens may still be macros, if that is the case, handle it the + // same way we did above. + const auto *ArgII = ArgIt->getIdentifierInfo(); + if (!ArgII) { + Printer.printToken(*ArgIt); + continue; + } + + const auto *MI = PP.getMacroInfo(ArgII); + if (!MI) { + Printer.printToken(*ArgIt); + continue; + } + + getMacroNameAndPrintExpansion(Printer, ArgIt->getLocation(), PP, + Info.Args); + if (MI->getNumParams() != 0) + ArgIt = getMatchingRParen(++ArgIt, ArgEnd); + } + continue; + } + + // If control reached here, then this token isn't a macro identifier, nor an + // unexpanded macro argument that we need to handle, print it. + Printer.printToken(T); + } + + return Info.Name; +} + +static MacroNameAndArgs getMacroNameAndArgs(SourceLocation ExpanLoc, + const Preprocessor &PP) { + + const SourceManager &SM = PP.getSourceManager(); + const LangOptions &LangOpts = PP.getLangOpts(); + + // First, we create a Lexer to lex *at the expansion location* the tokens + // referring to the macro's name and its arguments. + std::pair<FileID, unsigned> LocInfo = SM.getDecomposedLoc(ExpanLoc); + const llvm::MemoryBuffer *MB = SM.getBuffer(LocInfo.first); + const char *MacroNameTokenPos = MB->getBufferStart() + LocInfo.second; + + Lexer RawLexer(SM.getLocForStartOfFile(LocInfo.first), LangOpts, + MB->getBufferStart(), MacroNameTokenPos, MB->getBufferEnd()); + + // Acquire the macro's name. + Token TheTok; + RawLexer.LexFromRawLexer(TheTok); + + std::string MacroName = PP.getSpelling(TheTok); + + const auto *II = PP.getIdentifierInfo(MacroName); + assert(II && "Failed to acquire the IndetifierInfo for the macro!"); + + const MacroInfo *MI = getMacroInfoForLocation(PP, SM, II, ExpanLoc); + assert(MI && "The macro must've been defined at it's expansion location!"); + + // Acquire the macro's arguments. + // + // The rough idea here is to lex from the first left parentheses to the last + // right parentheses, and map the macro's unexpanded arguments to what they + // will be expanded to. An expanded macro argument may contain several tokens + // (like '3 + 4'), so we'll lex until we find a tok::comma or tok::r_paren, at + // which point we start lexing the next argument or finish. + ArrayRef<const IdentifierInfo *> MacroArgs = MI->params(); + if (MacroArgs.empty()) + return { MacroName, MI, {} }; + + RawLexer.LexFromRawLexer(TheTok); + assert(TheTok.is(tok::l_paren) && + "The token after the macro's identifier token should be '('!"); + + MacroArgMap Args; + + // When the macro's argument is a function call, like + // CALL_FN(someFunctionName(param1, param2)) + // we will find tok::l_paren, tok::r_paren, and tok::comma that do not divide + // actual macro arguments, or do not represent the macro argument's closing + // parentheses, so we'll count how many parentheses aren't closed yet. + // If ParanthesesDepth + // * = 0, then there are no more arguments to lex. + // * = 1, then if we find a tok::comma, we can start lexing the next arg. + // * > 1, then tok::comma is a part of the current arg. + int ParenthesesDepth = 1; + + // If we encounter __VA_ARGS__, we will lex until the closing tok::r_paren, + // even if we lex a tok::comma and ParanthesesDepth == 1. + const IdentifierInfo *__VA_ARGS__II = PP.getIdentifierInfo("__VA_ARGS__"); + + for (const IdentifierInfo *UnexpArgII : MacroArgs) { + MacroArgMap::mapped_type ExpandedArgTokens; + + // One could also simply not supply a single argument to __VA_ARGS__ -- this + // results in a preprocessor warning, but is not an error: + // #define VARIADIC(ptr, ...) \ + // someVariadicTemplateFunction(__VA_ARGS__) + // + // int *ptr; + // VARIADIC(ptr); // Note that there are no commas, this isn't just an + // // empty parameter -- there are no parameters for '...'. + // In any other case, ParenthesesDepth mustn't be 0 here. + if (ParenthesesDepth != 0) { + + // Lex the first token of the next macro parameter. + RawLexer.LexFromRawLexer(TheTok); + + while (!(ParenthesesDepth == 1 && + (UnexpArgII == __VA_ARGS__II ? false : TheTok.is(tok::comma)))) { + assert(TheTok.isNot(tok::eof) && + "EOF encountered while looking for expanded macro args!"); + + if (TheTok.is(tok::l_paren)) + ++ParenthesesDepth; + + if (TheTok.is(tok::r_paren)) + --ParenthesesDepth; + + if (ParenthesesDepth == 0) + break; + + if (TheTok.is(tok::raw_identifier)) + PP.LookUpIdentifierInfo(TheTok); + + ExpandedArgTokens.push_back(TheTok); + RawLexer.LexFromRawLexer(TheTok); + } + } else { + assert(UnexpArgII == __VA_ARGS__II); + } + + Args.emplace(UnexpArgII, std::move(ExpandedArgTokens)); + } + + assert(TheTok.is(tok::r_paren) && + "Expanded macro argument acquisition failed! After the end of the loop" + " this token should be ')'!"); + + return { MacroName, MI, Args }; +} + +static MacroInfo::tokens_iterator getMatchingRParen( + MacroInfo::tokens_iterator It, + MacroInfo::tokens_iterator End) { + + assert(It->is(tok::l_paren) && "This token should be '('!"); + + // Skip until we find the closing ')'. + int ParenthesesDepth = 1; + while (ParenthesesDepth != 0) { + ++It; + + assert(It->isNot(tok::eof) && + "Encountered EOF while attempting to skip macro arguments!"); + assert(It != End && + "End of the macro definition reached before finding ')'!"); + + if (It->is(tok::l_paren)) + ++ParenthesesDepth; + + if (It->is(tok::r_paren)) + --ParenthesesDepth; + } + return It; +} + +static const MacroInfo *getMacroInfoForLocation(const Preprocessor &PP, + const SourceManager &SM, + const IdentifierInfo *II, + SourceLocation Loc) { + + const MacroDirective *MD = PP.getLocalMacroDirectiveHistory(II); + if (!MD) + return nullptr; + + return MD->findDirectiveAtLoc(Loc, SM).getMacroInfo(); +} + +void MacroArgMap::expandFromPrevMacro(const MacroArgMap &Super) { + + for (value_type &Pair : *this) { + ExpArgTokens &CurrExpArgTokens = Pair.second; + + // For each token in the expanded macro argument. + auto It = CurrExpArgTokens.begin(); + while (It != CurrExpArgTokens.end()) { + if (It->isNot(tok::identifier)) { + ++It; + continue; + } + + const auto *II = It->getIdentifierInfo(); + assert(II); + + // Is this an argument that "Super" expands further? + if (!Super.count(II)) { + ++It; + continue; + } + + const ExpArgTokens &SuperExpArgTokens = Super.at(II); + + It = CurrExpArgTokens.insert( + It, SuperExpArgTokens.begin(), SuperExpArgTokens.end()); + std::advance(It, SuperExpArgTokens.size()); + It = CurrExpArgTokens.erase(It); + } + } +} + +void TokenPrinter::printToken(const Token &Tok) { + // If this is the first token to be printed, don't print space. + if (PrevTok.isNot(tok::unknown)) { + // If the tokens were already space separated, or if they must be to avoid + // them being implicitly pasted, add a space between them. + if(Tok.hasLeadingSpace() || ConcatInfo.AvoidConcat(PrevPrevTok, PrevTok, + Tok)) { + // AvoidConcat doesn't check for ##, don't print a space around it. + if (PrevTok.isNot(tok::hashhash) && Tok.isNot(tok::hashhash)) { + OS << ' '; + } + } + } + + if (!Tok.isOneOf(tok::hash, tok::hashhash)) { + if (PrevTok.is(tok::hash)) + OS << '\"' << PP.getSpelling(Tok) << '\"'; + else + OS << PP.getSpelling(Tok); + } + + PrevPrevTok = PrevTok; + PrevTok = Tok; +} diff --git a/lib/StaticAnalyzer/Core/ProgramState.cpp b/lib/StaticAnalyzer/Core/ProgramState.cpp index 94e2e00d8bbc5..2e2e2ec94f392 100644 --- a/lib/StaticAnalyzer/Core/ProgramState.cpp +++ b/lib/StaticAnalyzer/Core/ProgramState.cpp @@ -69,6 +69,10 @@ ProgramState::~ProgramState() { stateMgr->getStoreManager().decrementReferenceCount(store); } +int64_t ProgramState::getID() const { + return getStateManager().Alloc.identifyKnownAlignedObject<ProgramState>(this); +} + ProgramStateManager::ProgramStateManager(ASTContext &Ctx, StoreManagerCreator CreateSMgr, ConstraintManagerCreator CreateCMgr, @@ -121,8 +125,8 @@ ProgramStateRef ProgramState::bindLoc(Loc LV, ProgramStateRef newState = makeWithStore(Mgr.StoreMgr->Bind(getStore(), LV, V)); const MemRegion *MR = LV.getAsRegion(); - if (MR && Mgr.getOwningEngine() && notifyChanges) - return Mgr.getOwningEngine()->processRegionChange(newState, MR, LCtx); + if (MR && notifyChanges) + return Mgr.getOwningEngine().processRegionChange(newState, MR, LCtx); return newState; } @@ -134,9 +138,7 @@ ProgramState::bindDefaultInitial(SVal loc, SVal V, const MemRegion *R = loc.castAs<loc::MemRegionVal>().getRegion(); const StoreRef &newStore = Mgr.StoreMgr->BindDefaultInitial(getStore(), R, V); ProgramStateRef new_state = makeWithStore(newStore); - return Mgr.getOwningEngine() - ? Mgr.getOwningEngine()->processRegionChange(new_state, R, LCtx) - : new_state; + return Mgr.getOwningEngine().processRegionChange(new_state, R, LCtx); } ProgramStateRef @@ -145,9 +147,7 @@ ProgramState::bindDefaultZero(SVal loc, const LocationContext *LCtx) const { const MemRegion *R = loc.castAs<loc::MemRegionVal>().getRegion(); const StoreRef &newStore = Mgr.StoreMgr->BindDefaultZero(getStore(), R); ProgramStateRef new_state = makeWithStore(newStore); - return Mgr.getOwningEngine() - ? Mgr.getOwningEngine()->processRegionChange(new_state, R, LCtx) - : new_state; + return Mgr.getOwningEngine().processRegionChange(new_state, R, LCtx); } typedef ArrayRef<const MemRegion *> RegionList; @@ -192,41 +192,34 @@ ProgramState::invalidateRegionsImpl(ValueList Values, RegionAndSymbolInvalidationTraits *ITraits, const CallEvent *Call) const { ProgramStateManager &Mgr = getStateManager(); - SubEngine* Eng = Mgr.getOwningEngine(); + SubEngine &Eng = Mgr.getOwningEngine(); - InvalidatedSymbols Invalidated; + InvalidatedSymbols InvalidatedSyms; if (!IS) - IS = &Invalidated; + IS = &InvalidatedSyms; RegionAndSymbolInvalidationTraits ITraitsLocal; if (!ITraits) ITraits = &ITraitsLocal; - if (Eng) { - StoreManager::InvalidatedRegions TopLevelInvalidated; - StoreManager::InvalidatedRegions Invalidated; - const StoreRef &newStore - = Mgr.StoreMgr->invalidateRegions(getStore(), Values, E, Count, LCtx, Call, - *IS, *ITraits, &TopLevelInvalidated, - &Invalidated); - - ProgramStateRef newState = makeWithStore(newStore); - - if (CausedByPointerEscape) { - newState = Eng->notifyCheckersOfPointerEscape(newState, IS, - TopLevelInvalidated, - Invalidated, Call, - *ITraits); - } + StoreManager::InvalidatedRegions TopLevelInvalidated; + StoreManager::InvalidatedRegions Invalidated; + const StoreRef &newStore + = Mgr.StoreMgr->invalidateRegions(getStore(), Values, E, Count, LCtx, Call, + *IS, *ITraits, &TopLevelInvalidated, + &Invalidated); + + ProgramStateRef newState = makeWithStore(newStore); - return Eng->processRegionChanges(newState, IS, TopLevelInvalidated, - Invalidated, LCtx, Call); + if (CausedByPointerEscape) { + newState = Eng.notifyCheckersOfPointerEscape(newState, IS, + TopLevelInvalidated, + Call, + *ITraits); } - const StoreRef &newStore = - Mgr.StoreMgr->invalidateRegions(getStore(), Values, E, Count, LCtx, Call, - *IS, *ITraits, nullptr, nullptr); - return makeWithStore(newStore); + return Eng.processRegionChanges(newState, IS, TopLevelInvalidated, + Invalidated, LCtx, Call); } ProgramStateRef ProgramState::killBinding(Loc LV) const { @@ -449,14 +442,16 @@ void ProgramState::setStore(const StoreRef &newStore) { // State pretty-printing. //===----------------------------------------------------------------------===// -void ProgramState::print(raw_ostream &Out, const char *NL, const char *Sep, +void ProgramState::print(raw_ostream &Out, + const char *NL, const char *Sep, const LocationContext *LC) const { // Print the store. ProgramStateManager &Mgr = getStateManager(); - Mgr.getStoreManager().print(getStore(), Out, NL, Sep); + const ASTContext &Context = getStateManager().getContext(); + Mgr.getStoreManager().print(getStore(), Out, NL); // Print out the environment. - Env.print(Out, NL, Sep, LC); + Env.print(Out, NL, Sep, Context, LC); // Print out the constraints. Mgr.getConstraintManager().print(this, Out, NL, Sep); @@ -465,13 +460,14 @@ void ProgramState::print(raw_ostream &Out, const char *NL, const char *Sep, printDynamicTypeInfo(this, Out, NL, Sep); // Print out tainted symbols. - printTaint(Out, NL, Sep); + printTaint(Out, NL); // Print checker-specific data. - Mgr.getOwningEngine()->printState(Out, this, NL, Sep, LC); + Mgr.getOwningEngine().printState(Out, this, NL, Sep, LC); } -void ProgramState::printDOT(raw_ostream &Out, const LocationContext *LC) const { +void ProgramState::printDOT(raw_ostream &Out, + const LocationContext *LC) const { print(Out, "\\l", "\\|", LC); } @@ -480,7 +476,7 @@ LLVM_DUMP_METHOD void ProgramState::dump() const { } void ProgramState::printTaint(raw_ostream &Out, - const char *NL, const char *Sep) const { + const char *NL) const { TaintMapImpl TM = get<TaintMap>(); if (!TM.isEmpty()) @@ -496,7 +492,7 @@ void ProgramState::dumpTaint() const { } AnalysisManager& ProgramState::getAnalysisManager() const { - return stateMgr->getOwningEngine()->getAnalysisManager(); + return stateMgr->getOwningEngine().getAnalysisManager(); } //===----------------------------------------------------------------------===// @@ -652,22 +648,12 @@ bool ProgramState::scanReachableSymbols(SVal val, SymbolVisitor& visitor) const return S.scan(val); } -bool ProgramState::scanReachableSymbols(const SVal *I, const SVal *E, - SymbolVisitor &visitor) const { +bool ProgramState::scanReachableSymbols( + llvm::iterator_range<region_iterator> Reachable, + SymbolVisitor &visitor) const { ScanReachableSymbols S(this, visitor); - for ( ; I != E; ++I) { - if (!S.scan(*I)) - return false; - } - return true; -} - -bool ProgramState::scanReachableSymbols(const MemRegion * const *I, - const MemRegion * const *E, - SymbolVisitor &visitor) const { - ScanReachableSymbols S(this, visitor); - for ( ; I != E; ++I) { - if (!S.scan(*I)) + for (const MemRegion *R : Reachable) { + if (!S.scan(R)) return false; } return true; @@ -835,4 +821,3 @@ bool ProgramState::isTainted(SymbolRef Sym, TaintTagType Kind) const { return false; } - diff --git a/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp b/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp index e8c7bdbde385d..d9b58d0f5185b 100644 --- a/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp +++ b/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp @@ -399,7 +399,7 @@ RangeConstraintManager::removeDeadBindings(ProgramStateRef State, for (ConstraintRangeTy::iterator I = CR.begin(), E = CR.end(); I != E; ++I) { SymbolRef Sym = I.getKey(); - if (SymReaper.maybeDead(Sym)) { + if (SymReaper.isDead(Sym)) { Changed = true; CR = CRFactory.remove(CR, Sym); } diff --git a/lib/StaticAnalyzer/Core/RangedConstraintManager.cpp b/lib/StaticAnalyzer/Core/RangedConstraintManager.cpp index f99853f070731..146dc20ad0210 100644 --- a/lib/StaticAnalyzer/Core/RangedConstraintManager.cpp +++ b/lib/StaticAnalyzer/Core/RangedConstraintManager.cpp @@ -200,6 +200,11 @@ void RangedConstraintManager::computeAdjustment(SymbolRef &Sym, } } +void *ProgramStateTrait<ConstraintRange>::GDMIndex() { + static int Index; + return &Index; +} + } // end of namespace ento } // end of namespace clang diff --git a/lib/StaticAnalyzer/Core/RegionStore.cpp b/lib/StaticAnalyzer/Core/RegionStore.cpp index db6449e6d5f34..b2339be4f263e 100644 --- a/lib/StaticAnalyzer/Core/RegionStore.cpp +++ b/lib/StaticAnalyzer/Core/RegionStore.cpp @@ -17,6 +17,7 @@ #include "clang/AST/Attr.h" #include "clang/AST/CharUnits.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/Analysis/Analyses/LiveVariables.h" #include "clang/Analysis/AnalysisDeclContext.h" #include "clang/Basic/TargetInfo.h" @@ -61,7 +62,9 @@ private: : P(r, k), Data(offset) { assert(r && "Must have known regions."); assert(getOffset() == offset && "Failed to store offset"); - assert((r == r->getBaseRegion() || isa<ObjCIvarRegion>(r)) && "Not a base"); + assert((r == r->getBaseRegion() || isa<ObjCIvarRegion>(r) || + isa <CXXDerivedObjectRegion>(r)) && + "Not a base"); } public: @@ -308,7 +311,7 @@ public: //===----------------------------------------------------------------------===// namespace { -class invalidateRegionsWorker; +class InvalidateRegionsWorker; class RegionStoreManager : public StoreManager { public: @@ -335,7 +338,7 @@ private: /// A helper used to populate the work list with the given set of /// regions. - void populateWorkList(invalidateRegionsWorker &W, + void populateWorkList(InvalidateRegionsWorker &W, ArrayRef<SVal> Values, InvalidatedRegions *TopLevelRegions); @@ -344,11 +347,9 @@ public: : StoreManager(mgr), Features(f), RBFactory(mgr.getAllocator()), CBFactory(mgr.getAllocator()), SmallStructLimit(0) { - if (SubEngine *Eng = StateMgr.getOwningEngine()) { - AnalyzerOptions &Options = Eng->getAnalysisManager().options; - SmallStructLimit = - Options.getOptionAsInteger("region-store-small-struct-limit", 2); - } + SubEngine &Eng = StateMgr.getOwningEngine(); + AnalyzerOptions &Options = Eng.getAnalysisManager().options; + SmallStructLimit = Options.RegionStoreSmallStructLimit; } @@ -598,8 +599,7 @@ public: // Part of public interface to class. RBFactory.getTreeFactory()); } - void print(Store store, raw_ostream &Out, const char* nl, - const char *sep) override; + void print(Store store, raw_ostream &Out, const char* nl) override; void iterBindings(Store store, BindingsHandler& f) override { RegionBindingsRef B = getRegionBindings(store); @@ -945,7 +945,7 @@ RegionStoreManager::removeSubRegionBindings(RegionBindingsConstRef B, } namespace { -class invalidateRegionsWorker : public ClusterAnalysis<invalidateRegionsWorker> +class InvalidateRegionsWorker : public ClusterAnalysis<InvalidateRegionsWorker> { const Expr *Ex; unsigned Count; @@ -955,7 +955,7 @@ class invalidateRegionsWorker : public ClusterAnalysis<invalidateRegionsWorker> StoreManager::InvalidatedRegions *Regions; GlobalsFilterKind GlobalsFilter; public: - invalidateRegionsWorker(RegionStoreManager &rm, + InvalidateRegionsWorker(RegionStoreManager &rm, ProgramStateManager &stateMgr, RegionBindingsRef b, const Expr *ex, unsigned count, @@ -964,7 +964,7 @@ public: RegionAndSymbolInvalidationTraits &ITraitsIn, StoreManager::InvalidatedRegions *r, GlobalsFilterKind GFK) - : ClusterAnalysis<invalidateRegionsWorker>(rm, stateMgr, b), + : ClusterAnalysis<InvalidateRegionsWorker>(rm, stateMgr, b), Ex(ex), Count(count), LCtx(lctx), IS(is), ITraits(ITraitsIn), Regions(r), GlobalsFilter(GFK) {} @@ -985,14 +985,14 @@ public: }; } -bool invalidateRegionsWorker::AddToWorkList(const MemRegion *R) { +bool InvalidateRegionsWorker::AddToWorkList(const MemRegion *R) { bool doNotInvalidateSuperRegion = ITraits.hasTrait( R, RegionAndSymbolInvalidationTraits::TK_DoNotInvalidateSuperRegion); const MemRegion *BaseR = doNotInvalidateSuperRegion ? R : R->getBaseRegion(); return AddToWorkList(WorkListElement(BaseR), getCluster(BaseR)); } -void invalidateRegionsWorker::VisitBinding(SVal V) { +void InvalidateRegionsWorker::VisitBinding(SVal V) { // A symbol? Mark it touched by the invalidation. if (SymbolRef Sym = V.getAsSymbol()) IS.insert(Sym); @@ -1017,7 +1017,7 @@ void invalidateRegionsWorker::VisitBinding(SVal V) { } } -void invalidateRegionsWorker::VisitCluster(const MemRegion *baseR, +void InvalidateRegionsWorker::VisitCluster(const MemRegion *baseR, const ClusterBindings *C) { bool PreserveRegionsContents = @@ -1033,6 +1033,32 @@ void invalidateRegionsWorker::VisitCluster(const MemRegion *baseR, B = B.remove(baseR); } + if (const auto *TO = dyn_cast<TypedValueRegion>(baseR)) { + if (const auto *RD = TO->getValueType()->getAsCXXRecordDecl()) { + + // Lambdas can affect all static local variables without explicitly + // capturing those. + // We invalidate all static locals referenced inside the lambda body. + if (RD->isLambda() && RD->getLambdaCallOperator()->getBody()) { + using namespace ast_matchers; + + const char *DeclBind = "DeclBind"; + StatementMatcher RefToStatic = stmt(hasDescendant(declRefExpr( + to(varDecl(hasStaticStorageDuration()).bind(DeclBind))))); + auto Matches = + match(RefToStatic, *RD->getLambdaCallOperator()->getBody(), + RD->getASTContext()); + + for (BoundNodes &Match : Matches) { + auto *VD = Match.getNodeAs<VarDecl>(DeclBind); + const VarRegion *ToInvalidate = + RM.getRegionManager().getVarRegion(VD, LCtx); + AddToWorkList(ToInvalidate); + } + } + } + } + // BlockDataRegion? If so, invalidate captured variables that are passed // by reference. if (const BlockDataRegion *BR = dyn_cast<BlockDataRegion>(baseR)) { @@ -1181,7 +1207,7 @@ void invalidateRegionsWorker::VisitCluster(const MemRegion *baseR, B = B.addBinding(baseR, BindingKey::Direct, V); } -bool invalidateRegionsWorker::isInitiallyIncludedGlobalRegion( +bool InvalidateRegionsWorker::isInitiallyIncludedGlobalRegion( const MemRegion *R) { switch (GlobalsFilter) { case GFK_None: @@ -1195,7 +1221,7 @@ bool invalidateRegionsWorker::isInitiallyIncludedGlobalRegion( llvm_unreachable("unknown globals filter"); } -bool invalidateRegionsWorker::includeEntireMemorySpace(const MemRegion *Base) { +bool InvalidateRegionsWorker::includeEntireMemorySpace(const MemRegion *Base) { if (isInitiallyIncludedGlobalRegion(Base)) return true; @@ -1229,7 +1255,7 @@ RegionStoreManager::invalidateGlobalRegion(MemRegion::Kind K, return B; } -void RegionStoreManager::populateWorkList(invalidateRegionsWorker &W, +void RegionStoreManager::populateWorkList(InvalidateRegionsWorker &W, ArrayRef<SVal> Values, InvalidatedRegions *TopLevelRegions) { for (ArrayRef<SVal>::iterator I = Values.begin(), @@ -1280,7 +1306,7 @@ RegionStoreManager::invalidateRegions(Store store, } RegionBindingsRef B = getRegionBindings(store); - invalidateRegionsWorker W(*this, StateMgr, B, Ex, Count, LCtx, IS, ITraits, + InvalidateRegionsWorker W(*this, StateMgr, B, Ex, Count, LCtx, IS, ITraits, Invalidated, GlobalsFilter); // Scan the bindings and generate the clusters. @@ -1302,11 +1328,11 @@ RegionStoreManager::invalidateRegions(Store store, case GFK_All: B = invalidateGlobalRegion(MemRegion::GlobalInternalSpaceRegionKind, Ex, Count, LCtx, B, Invalidated); - // FALLTHROUGH + LLVM_FALLTHROUGH; case GFK_SystemOnly: B = invalidateGlobalRegion(MemRegion::GlobalSystemSpaceRegionKind, Ex, Count, LCtx, B, Invalidated); - // FALLTHROUGH + LLVM_FALLTHROUGH; case GFK_None: break; } @@ -2363,40 +2389,45 @@ RegionStoreManager::bindAggregate(RegionBindingsConstRef B, //===----------------------------------------------------------------------===// namespace { -class removeDeadBindingsWorker : - public ClusterAnalysis<removeDeadBindingsWorker> { - SmallVector<const SymbolicRegion*, 12> Postponed; +class RemoveDeadBindingsWorker + : public ClusterAnalysis<RemoveDeadBindingsWorker> { + using ChildrenListTy = SmallVector<const SymbolDerived *, 4>; + using MapParentsToDerivedTy = llvm::DenseMap<SymbolRef, ChildrenListTy>; + + MapParentsToDerivedTy ParentsToDerived; SymbolReaper &SymReaper; const StackFrameContext *CurrentLCtx; public: - removeDeadBindingsWorker(RegionStoreManager &rm, + RemoveDeadBindingsWorker(RegionStoreManager &rm, ProgramStateManager &stateMgr, RegionBindingsRef b, SymbolReaper &symReaper, const StackFrameContext *LCtx) - : ClusterAnalysis<removeDeadBindingsWorker>(rm, stateMgr, b), + : ClusterAnalysis<RemoveDeadBindingsWorker>(rm, stateMgr, b), SymReaper(symReaper), CurrentLCtx(LCtx) {} // Called by ClusterAnalysis. void VisitAddedToCluster(const MemRegion *baseR, const ClusterBindings &C); void VisitCluster(const MemRegion *baseR, const ClusterBindings *C); - using ClusterAnalysis<removeDeadBindingsWorker>::VisitCluster; + using ClusterAnalysis<RemoveDeadBindingsWorker>::VisitCluster; using ClusterAnalysis::AddToWorkList; bool AddToWorkList(const MemRegion *R); - bool UpdatePostponed(); void VisitBinding(SVal V); + +private: + void populateWorklistFromSymbol(SymbolRef s); }; } -bool removeDeadBindingsWorker::AddToWorkList(const MemRegion *R) { +bool RemoveDeadBindingsWorker::AddToWorkList(const MemRegion *R) { const MemRegion *BaseR = R->getBaseRegion(); return AddToWorkList(WorkListElement(BaseR), getCluster(BaseR)); } -void removeDeadBindingsWorker::VisitAddedToCluster(const MemRegion *baseR, +void RemoveDeadBindingsWorker::VisitAddedToCluster(const MemRegion *baseR, const ClusterBindings &C) { if (const VarRegion *VR = dyn_cast<VarRegion>(baseR)) { @@ -2407,10 +2438,11 @@ void removeDeadBindingsWorker::VisitAddedToCluster(const MemRegion *baseR, } if (const SymbolicRegion *SR = dyn_cast<SymbolicRegion>(baseR)) { - if (SymReaper.isLive(SR->getSymbol())) + if (SymReaper.isLive(SR->getSymbol())) { AddToWorkList(SR, &C); - else - Postponed.push_back(SR); + } else if (const auto *SD = dyn_cast<SymbolDerived>(SR->getSymbol())) { + ParentsToDerived[SD->getParentSymbol()].push_back(SD); + } return; } @@ -2422,7 +2454,7 @@ void removeDeadBindingsWorker::VisitAddedToCluster(const MemRegion *baseR, // CXXThisRegion in the current or parent location context is live. if (const CXXThisRegion *TR = dyn_cast<CXXThisRegion>(baseR)) { - const StackArgumentsSpaceRegion *StackReg = + const auto *StackReg = cast<StackArgumentsSpaceRegion>(TR->getSuperRegion()); const StackFrameContext *RegCtx = StackReg->getStackFrame(); if (CurrentLCtx && @@ -2431,7 +2463,7 @@ void removeDeadBindingsWorker::VisitAddedToCluster(const MemRegion *baseR, } } -void removeDeadBindingsWorker::VisitCluster(const MemRegion *baseR, +void RemoveDeadBindingsWorker::VisitCluster(const MemRegion *baseR, const ClusterBindings *C) { if (!C) return; @@ -2449,7 +2481,7 @@ void removeDeadBindingsWorker::VisitCluster(const MemRegion *baseR, } } -void removeDeadBindingsWorker::VisitBinding(SVal V) { +void RemoveDeadBindingsWorker::VisitBinding(SVal V) { // Is it a LazyCompoundVal? All referenced regions are live as well. if (Optional<nonloc::LazyCompoundVal> LCS = V.getAs<nonloc::LazyCompoundVal>()) { @@ -2467,6 +2499,15 @@ void removeDeadBindingsWorker::VisitBinding(SVal V) { // If V is a region, then add it to the worklist. if (const MemRegion *R = V.getAsRegion()) { AddToWorkList(R); + + if (const auto *TVR = dyn_cast<TypedValueRegion>(R)) { + DefinedOrUnknownSVal RVS = + RM.getSValBuilder().getRegionValueSymbolVal(TVR); + if (const MemRegion *SR = RVS.getAsRegion()) { + AddToWorkList(SR); + } + } + SymReaper.markLive(R); // All regions captured by a block are also live. @@ -2480,34 +2521,37 @@ void removeDeadBindingsWorker::VisitBinding(SVal V) { // Update the set of live symbols. - for (SymExpr::symbol_iterator SI = V.symbol_begin(), SE = V.symbol_end(); - SI!=SE; ++SI) + for (auto SI = V.symbol_begin(), SE = V.symbol_end(); SI != SE; ++SI) { + populateWorklistFromSymbol(*SI); + + for (const auto *SD : ParentsToDerived[*SI]) + populateWorklistFromSymbol(SD); + SymReaper.markLive(*SI); + } } -bool removeDeadBindingsWorker::UpdatePostponed() { - // See if any postponed SymbolicRegions are actually live now, after - // having done a scan. - bool changed = false; +void RemoveDeadBindingsWorker::populateWorklistFromSymbol(SymbolRef S) { + if (const auto *SD = dyn_cast<SymbolData>(S)) { + if (Loc::isLocType(SD->getType()) && !SymReaper.isLive(SD)) { + const SymbolicRegion *SR = RM.getRegionManager().getSymbolicRegion(SD); - for (SmallVectorImpl<const SymbolicRegion*>::iterator - I = Postponed.begin(), E = Postponed.end() ; I != E ; ++I) { - if (const SymbolicRegion *SR = *I) { - if (SymReaper.isLive(SR->getSymbol())) { - changed |= AddToWorkList(SR); - *I = nullptr; - } + if (B.contains(SR)) + AddToWorkList(SR); + + const SymbolicRegion *SHR = + RM.getRegionManager().getSymbolicHeapRegion(SD); + if (B.contains(SHR)) + AddToWorkList(SHR); } } - - return changed; } StoreRef RegionStoreManager::removeDeadBindings(Store store, const StackFrameContext *LCtx, SymbolReaper& SymReaper) { RegionBindingsRef B = getRegionBindings(store); - removeDeadBindingsWorker W(*this, StateMgr, B, SymReaper, LCtx); + RemoveDeadBindingsWorker W(*this, StateMgr, B, SymReaper, LCtx); W.GenerateClusters(); // Enqueue the region roots onto the worklist. @@ -2516,7 +2560,7 @@ StoreRef RegionStoreManager::removeDeadBindings(Store store, W.AddToWorkList(*I); } - do W.RunWorkList(); while (W.UpdatePostponed()); + W.RunWorkList(); // We have now scanned the store, marking reachable regions and symbols // as live. We now remove all the regions that are dead from the store @@ -2525,24 +2569,9 @@ StoreRef RegionStoreManager::removeDeadBindings(Store store, const MemRegion *Base = I.getKey(); // If the cluster has been visited, we know the region has been marked. - if (W.isVisited(Base)) - continue; - - // Remove the dead entry. - B = B.remove(Base); - - if (const SymbolicRegion *SymR = dyn_cast<SymbolicRegion>(Base)) - SymReaper.maybeDead(SymR->getSymbol()); - - // Mark all non-live symbols that this binding references as dead. - const ClusterBindings &Cluster = I.getData(); - for (ClusterBindings::iterator CI = Cluster.begin(), CE = Cluster.end(); - CI != CE; ++CI) { - SVal X = CI.getData(); - SymExpr::symbol_iterator SI = X.symbol_begin(), SE = X.symbol_end(); - for (; SI != SE; ++SI) - SymReaper.maybeDead(*SI); - } + // Otherwise, remove the dead entry. + if (!W.isVisited(Base)) + B = B.remove(Base); } return StoreRef(B.asStore(), *this); @@ -2553,7 +2582,7 @@ StoreRef RegionStoreManager::removeDeadBindings(Store store, //===----------------------------------------------------------------------===// void RegionStoreManager::print(Store store, raw_ostream &OS, - const char* nl, const char *sep) { + const char* nl) { RegionBindingsRef B = getRegionBindings(store); OS << "Store (direct and default bindings), " << B.asStore() diff --git a/lib/StaticAnalyzer/Core/RetainSummaryManager.cpp b/lib/StaticAnalyzer/Core/RetainSummaryManager.cpp new file mode 100644 index 0000000000000..2e40cc33381c1 --- /dev/null +++ b/lib/StaticAnalyzer/Core/RetainSummaryManager.cpp @@ -0,0 +1,1229 @@ +//== RetainSummaryManager.cpp - Summaries for reference counting --*- C++ -*--// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines summaries implementation for retain counting, which +// implements a reference count checker for Core Foundation, Cocoa +// and OSObject (on Mac OS X). +// +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Core/RetainSummaryManager.h" +#include "clang/Analysis/DomainSpecific/CocoaConventions.h" +#include "clang/AST/Attr.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/DeclObjC.h" +#include "clang/AST/ParentMap.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang; +using namespace ento; + +template <class T> +constexpr static bool isOneOf() { + return false; +} + +/// Helper function to check whether the class is one of the +/// rest of varargs. +template <class T, class P, class... ToCompare> +constexpr static bool isOneOf() { + return std::is_same<T, P>::value || isOneOf<T, ToCompare...>(); +} + +namespace { + +/// Fake attribute class for RC* attributes. +struct GeneralizedReturnsRetainedAttr { + static bool classof(const Attr *A) { + if (auto AA = dyn_cast<AnnotateAttr>(A)) + return AA->getAnnotation() == "rc_ownership_returns_retained"; + return false; + } +}; + +struct GeneralizedReturnsNotRetainedAttr { + static bool classof(const Attr *A) { + if (auto AA = dyn_cast<AnnotateAttr>(A)) + return AA->getAnnotation() == "rc_ownership_returns_not_retained"; + return false; + } +}; + +struct GeneralizedConsumedAttr { + static bool classof(const Attr *A) { + if (auto AA = dyn_cast<AnnotateAttr>(A)) + return AA->getAnnotation() == "rc_ownership_consumed"; + return false; + } +}; + +} + +template <class T> +Optional<ObjKind> RetainSummaryManager::hasAnyEnabledAttrOf(const Decl *D, + QualType QT) { + ObjKind K; + if (isOneOf<T, CFConsumedAttr, CFReturnsRetainedAttr, + CFReturnsNotRetainedAttr>()) { + if (!TrackObjCAndCFObjects) + return None; + + K = ObjKind::CF; + } else if (isOneOf<T, NSConsumedAttr, NSConsumesSelfAttr, + NSReturnsAutoreleasedAttr, NSReturnsRetainedAttr, + NSReturnsNotRetainedAttr, NSConsumesSelfAttr>()) { + + if (!TrackObjCAndCFObjects) + return None; + + if (isOneOf<T, NSReturnsRetainedAttr, NSReturnsAutoreleasedAttr, + NSReturnsNotRetainedAttr>() && + !cocoa::isCocoaObjectRef(QT)) + return None; + K = ObjKind::ObjC; + } else if (isOneOf<T, OSConsumedAttr, OSConsumesThisAttr, + OSReturnsNotRetainedAttr, OSReturnsRetainedAttr, + OSReturnsRetainedOnZeroAttr, + OSReturnsRetainedOnNonZeroAttr>()) { + if (!TrackOSObjects) + return None; + K = ObjKind::OS; + } else if (isOneOf<T, GeneralizedReturnsNotRetainedAttr, + GeneralizedReturnsRetainedAttr, + GeneralizedConsumedAttr>()) { + K = ObjKind::Generalized; + } else { + llvm_unreachable("Unexpected attribute"); + } + if (D->hasAttr<T>()) + return K; + return None; +} + +template <class T1, class T2, class... Others> +Optional<ObjKind> RetainSummaryManager::hasAnyEnabledAttrOf(const Decl *D, + QualType QT) { + if (auto Out = hasAnyEnabledAttrOf<T1>(D, QT)) + return Out; + return hasAnyEnabledAttrOf<T2, Others...>(D, QT); +} + +const RetainSummary * +RetainSummaryManager::getPersistentSummary(const RetainSummary &OldSumm) { + // Unique "simple" summaries -- those without ArgEffects. + if (OldSumm.isSimple()) { + ::llvm::FoldingSetNodeID ID; + OldSumm.Profile(ID); + + void *Pos; + CachedSummaryNode *N = SimpleSummaries.FindNodeOrInsertPos(ID, Pos); + + if (!N) { + N = (CachedSummaryNode *) BPAlloc.Allocate<CachedSummaryNode>(); + new (N) CachedSummaryNode(OldSumm); + SimpleSummaries.InsertNode(N, Pos); + } + + return &N->getValue(); + } + + RetainSummary *Summ = (RetainSummary *) BPAlloc.Allocate<RetainSummary>(); + new (Summ) RetainSummary(OldSumm); + return Summ; +} + +static bool isSubclass(const Decl *D, + StringRef ClassName) { + using namespace ast_matchers; + DeclarationMatcher SubclassM = cxxRecordDecl(isSameOrDerivedFrom(ClassName)); + return !(match(SubclassM, *D, D->getASTContext()).empty()); +} + +static bool isOSObjectSubclass(const Decl *D) { + return isSubclass(D, "OSObject"); +} + +static bool isOSObjectDynamicCast(StringRef S) { + return S == "safeMetaCast"; +} + +static bool isOSIteratorSubclass(const Decl *D) { + return isSubclass(D, "OSIterator"); +} + +static bool hasRCAnnotation(const Decl *D, StringRef rcAnnotation) { + for (const auto *Ann : D->specific_attrs<AnnotateAttr>()) { + if (Ann->getAnnotation() == rcAnnotation) + return true; + } + return false; +} + +static bool isRetain(const FunctionDecl *FD, StringRef FName) { + return FName.startswith_lower("retain") || FName.endswith_lower("retain"); +} + +static bool isRelease(const FunctionDecl *FD, StringRef FName) { + return FName.startswith_lower("release") || FName.endswith_lower("release"); +} + +static bool isAutorelease(const FunctionDecl *FD, StringRef FName) { + return FName.startswith_lower("autorelease") || + FName.endswith_lower("autorelease"); +} + +static bool isMakeCollectable(StringRef FName) { + return FName.contains_lower("MakeCollectable"); +} + +/// A function is OSObject related if it is declared on a subclass +/// of OSObject, or any of the parameters is a subclass of an OSObject. +static bool isOSObjectRelated(const CXXMethodDecl *MD) { + if (isOSObjectSubclass(MD->getParent())) + return true; + + for (ParmVarDecl *Param : MD->parameters()) { + QualType PT = Param->getType()->getPointeeType(); + if (!PT.isNull()) + if (CXXRecordDecl *RD = PT->getAsCXXRecordDecl()) + if (isOSObjectSubclass(RD)) + return true; + } + + return false; +} + +const RetainSummary * +RetainSummaryManager::getSummaryForOSObject(const FunctionDecl *FD, + StringRef FName, QualType RetTy) { + if (RetTy->isPointerType()) { + const CXXRecordDecl *PD = RetTy->getPointeeType()->getAsCXXRecordDecl(); + if (PD && isOSObjectSubclass(PD)) { + if (const IdentifierInfo *II = FD->getIdentifier()) { + if (isOSObjectDynamicCast(II->getName())) + return getDefaultSummary(); + + // All objects returned with functions *not* starting with + // get, or iterators, are returned at +1. + if ((!II->getName().startswith("get") && + !II->getName().startswith("Get")) || + isOSIteratorSubclass(PD)) { + return getOSSummaryCreateRule(FD); + } else { + return getOSSummaryGetRule(FD); + } + } + } + } + + if (const auto *MD = dyn_cast<CXXMethodDecl>(FD)) { + const CXXRecordDecl *Parent = MD->getParent(); + if (TrackOSObjects && Parent && isOSObjectSubclass(Parent)) { + if (FName == "release") + return getOSSummaryReleaseRule(FD); + + if (FName == "retain") + return getOSSummaryRetainRule(FD); + + if (FName == "free") + return getOSSummaryFreeRule(FD); + + if (MD->getOverloadedOperator() == OO_New) + return getOSSummaryCreateRule(MD); + } + } + + return nullptr; +} + +const RetainSummary *RetainSummaryManager::getSummaryForObjCOrCFObject( + const FunctionDecl *FD, + StringRef FName, + QualType RetTy, + const FunctionType *FT, + bool &AllowAnnotations) { + + ArgEffects ScratchArgs(AF.getEmptyMap()); + + std::string RetTyName = RetTy.getAsString(); + if (FName == "pthread_create" || FName == "pthread_setspecific") { + // Part of: <rdar://problem/7299394> and <rdar://problem/11282706>. + // This will be addressed better with IPA. + return getPersistentStopSummary(); + } else if(FName == "NSMakeCollectable") { + // Handle: id NSMakeCollectable(CFTypeRef) + AllowAnnotations = false; + return RetTy->isObjCIdType() ? getUnarySummary(FT, DoNothing) + : getPersistentStopSummary(); + } else if (FName == "CMBufferQueueDequeueAndRetain" || + FName == "CMBufferQueueDequeueIfDataReadyAndRetain") { + // Part of: <rdar://problem/39390714>. + return getPersistentSummary(RetEffect::MakeOwned(ObjKind::CF), + ScratchArgs, + ArgEffect(DoNothing), + ArgEffect(DoNothing)); + } else if (FName == "CFPlugInInstanceCreate") { + return getPersistentSummary(RetEffect::MakeNoRet(), ScratchArgs); + } else if (FName == "IORegistryEntrySearchCFProperty" || + (RetTyName == "CFMutableDictionaryRef" && + (FName == "IOBSDNameMatching" || FName == "IOServiceMatching" || + FName == "IOServiceNameMatching" || + FName == "IORegistryEntryIDMatching" || + FName == "IOOpenFirmwarePathMatching"))) { + // Part of <rdar://problem/6961230>. (IOKit) + // This should be addressed using a API table. + return getPersistentSummary(RetEffect::MakeOwned(ObjKind::CF), ScratchArgs, + ArgEffect(DoNothing), ArgEffect(DoNothing)); + } else if (FName == "IOServiceGetMatchingService" || + FName == "IOServiceGetMatchingServices") { + // FIXES: <rdar://problem/6326900> + // This should be addressed using a API table. This strcmp is also + // a little gross, but there is no need to super optimize here. + ScratchArgs = AF.add(ScratchArgs, 1, ArgEffect(DecRef, ObjKind::CF)); + return getPersistentSummary(RetEffect::MakeNoRet(), + ScratchArgs, + ArgEffect(DoNothing), ArgEffect(DoNothing)); + } else if (FName == "IOServiceAddNotification" || + FName == "IOServiceAddMatchingNotification") { + // Part of <rdar://problem/6961230>. (IOKit) + // This should be addressed using a API table. + ScratchArgs = AF.add(ScratchArgs, 2, ArgEffect(DecRef, ObjKind::CF)); + return getPersistentSummary(RetEffect::MakeNoRet(), + ScratchArgs, + ArgEffect(DoNothing), ArgEffect(DoNothing)); + } else if (FName == "CVPixelBufferCreateWithBytes") { + // FIXES: <rdar://problem/7283567> + // Eventually this can be improved by recognizing that the pixel + // buffer passed to CVPixelBufferCreateWithBytes is released via + // a callback and doing full IPA to make sure this is done correctly. + // FIXME: This function has an out parameter that returns an + // allocated object. + ScratchArgs = AF.add(ScratchArgs, 7, ArgEffect(StopTracking)); + return getPersistentSummary(RetEffect::MakeNoRet(), + ScratchArgs, + ArgEffect(DoNothing), ArgEffect(DoNothing)); + } else if (FName == "CGBitmapContextCreateWithData") { + // FIXES: <rdar://problem/7358899> + // Eventually this can be improved by recognizing that 'releaseInfo' + // passed to CGBitmapContextCreateWithData is released via + // a callback and doing full IPA to make sure this is done correctly. + ScratchArgs = AF.add(ScratchArgs, 8, ArgEffect(ArgEffect(StopTracking))); + return getPersistentSummary(RetEffect::MakeOwned(ObjKind::CF), ScratchArgs, + ArgEffect(DoNothing), ArgEffect(DoNothing)); + } else if (FName == "CVPixelBufferCreateWithPlanarBytes") { + // FIXES: <rdar://problem/7283567> + // Eventually this can be improved by recognizing that the pixel + // buffer passed to CVPixelBufferCreateWithPlanarBytes is released + // via a callback and doing full IPA to make sure this is done + // correctly. + ScratchArgs = AF.add(ScratchArgs, 12, ArgEffect(StopTracking)); + return getPersistentSummary(RetEffect::MakeNoRet(), + ScratchArgs, + ArgEffect(DoNothing), ArgEffect(DoNothing)); + } else if (FName == "VTCompressionSessionEncodeFrame") { + // The context argument passed to VTCompressionSessionEncodeFrame() + // is passed to the callback specified when creating the session + // (e.g. with VTCompressionSessionCreate()) which can release it. + // To account for this possibility, conservatively stop tracking + // the context. + ScratchArgs = AF.add(ScratchArgs, 5, ArgEffect(StopTracking)); + return getPersistentSummary(RetEffect::MakeNoRet(), + ScratchArgs, + ArgEffect(DoNothing), ArgEffect(DoNothing)); + } else if (FName == "dispatch_set_context" || + FName == "xpc_connection_set_context") { + // <rdar://problem/11059275> - The analyzer currently doesn't have + // a good way to reason about the finalizer function for libdispatch. + // If we pass a context object that is memory managed, stop tracking it. + // <rdar://problem/13783514> - Same problem, but for XPC. + // FIXME: this hack should possibly go away once we can handle + // libdispatch and XPC finalizers. + ScratchArgs = AF.add(ScratchArgs, 1, ArgEffect(StopTracking)); + return getPersistentSummary(RetEffect::MakeNoRet(), + ScratchArgs, + ArgEffect(DoNothing), ArgEffect(DoNothing)); + } else if (FName.startswith("NSLog")) { + return getDoNothingSummary(); + } else if (FName.startswith("NS") && + (FName.find("Insert") != StringRef::npos)) { + // Whitelist NSXXInsertXX, for example NSMapInsertIfAbsent, since they can + // be deallocated by NSMapRemove. (radar://11152419) + ScratchArgs = AF.add(ScratchArgs, 1, ArgEffect(StopTracking)); + ScratchArgs = AF.add(ScratchArgs, 2, ArgEffect(StopTracking)); + return getPersistentSummary(RetEffect::MakeNoRet(), + ScratchArgs, ArgEffect(DoNothing), + ArgEffect(DoNothing)); + } + + if (RetTy->isPointerType()) { + + // For CoreFoundation ('CF') types. + if (cocoa::isRefType(RetTy, "CF", FName)) { + if (isRetain(FD, FName)) { + // CFRetain isn't supposed to be annotated. However, this may as + // well be a user-made "safe" CFRetain function that is incorrectly + // annotated as cf_returns_retained due to lack of better options. + // We want to ignore such annotation. + AllowAnnotations = false; + + return getUnarySummary(FT, IncRef); + } else if (isAutorelease(FD, FName)) { + // The headers use cf_consumed, but we can fully model CFAutorelease + // ourselves. + AllowAnnotations = false; + + return getUnarySummary(FT, Autorelease); + } else if (isMakeCollectable(FName)) { + AllowAnnotations = false; + return getUnarySummary(FT, DoNothing); + } else { + return getCFCreateGetRuleSummary(FD); + } + } + + // For CoreGraphics ('CG') and CoreVideo ('CV') types. + if (cocoa::isRefType(RetTy, "CG", FName) || + cocoa::isRefType(RetTy, "CV", FName)) { + if (isRetain(FD, FName)) + return getUnarySummary(FT, IncRef); + else + return getCFCreateGetRuleSummary(FD); + } + + // For all other CF-style types, use the Create/Get + // rule for summaries but don't support Retain functions + // with framework-specific prefixes. + if (coreFoundation::isCFObjectRef(RetTy)) { + return getCFCreateGetRuleSummary(FD); + } + + if (FD->hasAttr<CFAuditedTransferAttr>()) { + return getCFCreateGetRuleSummary(FD); + } + } + + // Check for release functions, the only kind of functions that we care + // about that don't return a pointer type. + if (FName.startswith("CG") || FName.startswith("CF")) { + // Test for 'CGCF'. + FName = FName.substr(FName.startswith("CGCF") ? 4 : 2); + + if (isRelease(FD, FName)) + return getUnarySummary(FT, DecRef); + else { + assert(ScratchArgs.isEmpty()); + // Remaining CoreFoundation and CoreGraphics functions. + // We use to assume that they all strictly followed the ownership idiom + // and that ownership cannot be transferred. While this is technically + // correct, many methods allow a tracked object to escape. For example: + // + // CFMutableDictionaryRef x = CFDictionaryCreateMutable(...); + // CFDictionaryAddValue(y, key, x); + // CFRelease(x); + // ... it is okay to use 'x' since 'y' has a reference to it + // + // We handle this and similar cases with the follow heuristic. If the + // function name contains "InsertValue", "SetValue", "AddValue", + // "AppendValue", or "SetAttribute", then we assume that arguments may + // "escape." This means that something else holds on to the object, + // allowing it be used even after its local retain count drops to 0. + ArgEffectKind E = + (StrInStrNoCase(FName, "InsertValue") != StringRef::npos || + StrInStrNoCase(FName, "AddValue") != StringRef::npos || + StrInStrNoCase(FName, "SetValue") != StringRef::npos || + StrInStrNoCase(FName, "AppendValue") != StringRef::npos || + StrInStrNoCase(FName, "SetAttribute") != StringRef::npos) + ? MayEscape + : DoNothing; + + return getPersistentSummary(RetEffect::MakeNoRet(), ScratchArgs, + ArgEffect(DoNothing), ArgEffect(E, ObjKind::CF)); + } + } + + return nullptr; +} + +const RetainSummary * +RetainSummaryManager::generateSummary(const FunctionDecl *FD, + bool &AllowAnnotations) { + // We generate "stop" summaries for implicitly defined functions. + if (FD->isImplicit()) + return getPersistentStopSummary(); + + const IdentifierInfo *II = FD->getIdentifier(); + + StringRef FName = II ? II->getName() : ""; + + // Strip away preceding '_'. Doing this here will effect all the checks + // down below. + FName = FName.substr(FName.find_first_not_of('_')); + + // Inspect the result type. Strip away any typedefs. + const auto *FT = FD->getType()->getAs<FunctionType>(); + QualType RetTy = FT->getReturnType(); + + if (TrackOSObjects) + if (const RetainSummary *S = getSummaryForOSObject(FD, FName, RetTy)) + return S; + + if (TrackObjCAndCFObjects) + if (const RetainSummary *S = + getSummaryForObjCOrCFObject(FD, FName, RetTy, FT, AllowAnnotations)) + return S; + + if (const auto *MD = dyn_cast<CXXMethodDecl>(FD)) + if (!(TrackOSObjects && isOSObjectRelated(MD))) + return getPersistentSummary(RetEffect::MakeNoRet(), + ArgEffects(AF.getEmptyMap()), + ArgEffect(DoNothing), + ArgEffect(StopTracking), + ArgEffect(DoNothing)); + + return getDefaultSummary(); +} + +const RetainSummary * +RetainSummaryManager::getFunctionSummary(const FunctionDecl *FD) { + // If we don't know what function we're calling, use our default summary. + if (!FD) + return getDefaultSummary(); + + // Look up a summary in our cache of FunctionDecls -> Summaries. + FuncSummariesTy::iterator I = FuncSummaries.find(FD); + if (I != FuncSummaries.end()) + return I->second; + + // No summary? Generate one. + bool AllowAnnotations = true; + const RetainSummary *S = generateSummary(FD, AllowAnnotations); + + // Annotations override defaults. + if (AllowAnnotations) + updateSummaryFromAnnotations(S, FD); + + FuncSummaries[FD] = S; + return S; +} + +//===----------------------------------------------------------------------===// +// Summary creation for functions (largely uses of Core Foundation). +//===----------------------------------------------------------------------===// + +static ArgEffect getStopTrackingHardEquivalent(ArgEffect E) { + switch (E.getKind()) { + case DoNothing: + case Autorelease: + case DecRefBridgedTransferred: + case IncRef: + case UnretainedOutParameter: + case RetainedOutParameter: + case RetainedOutParameterOnZero: + case RetainedOutParameterOnNonZero: + case MayEscape: + case StopTracking: + case StopTrackingHard: + return E.withKind(StopTrackingHard); + case DecRef: + case DecRefAndStopTrackingHard: + return E.withKind(DecRefAndStopTrackingHard); + case Dealloc: + return E.withKind(Dealloc); + } + + llvm_unreachable("Unknown ArgEffect kind"); +} + +void RetainSummaryManager::updateSummaryForCall(const RetainSummary *&S, + const CallEvent &Call) { + if (Call.hasNonZeroCallbackArg()) { + ArgEffect RecEffect = + getStopTrackingHardEquivalent(S->getReceiverEffect()); + ArgEffect DefEffect = + getStopTrackingHardEquivalent(S->getDefaultArgEffect()); + + ArgEffects ScratchArgs(AF.getEmptyMap()); + ArgEffects CustomArgEffects = S->getArgEffects(); + for (ArgEffects::iterator I = CustomArgEffects.begin(), + E = CustomArgEffects.end(); + I != E; ++I) { + ArgEffect Translated = getStopTrackingHardEquivalent(I->second); + if (Translated.getKind() != DefEffect.getKind()) + ScratchArgs = AF.add(ScratchArgs, I->first, Translated); + } + + RetEffect RE = RetEffect::MakeNoRetHard(); + + // Special cases where the callback argument CANNOT free the return value. + // This can generally only happen if we know that the callback will only be + // called when the return value is already being deallocated. + if (const SimpleFunctionCall *FC = dyn_cast<SimpleFunctionCall>(&Call)) { + if (IdentifierInfo *Name = FC->getDecl()->getIdentifier()) { + // When the CGBitmapContext is deallocated, the callback here will free + // the associated data buffer. + // The callback in dispatch_data_create frees the buffer, but not + // the data object. + if (Name->isStr("CGBitmapContextCreateWithData") || + Name->isStr("dispatch_data_create")) + RE = S->getRetEffect(); + } + } + + S = getPersistentSummary(RE, ScratchArgs, RecEffect, DefEffect); + } + + // Special case '[super init];' and '[self init];' + // + // Even though calling '[super init]' without assigning the result to self + // and checking if the parent returns 'nil' is a bad pattern, it is common. + // Additionally, our Self Init checker already warns about it. To avoid + // overwhelming the user with messages from both checkers, we model the case + // of '[super init]' in cases when it is not consumed by another expression + // as if the call preserves the value of 'self'; essentially, assuming it can + // never fail and return 'nil'. + // Note, we don't want to just stop tracking the value since we want the + // RetainCount checker to report leaks and use-after-free if SelfInit checker + // is turned off. + if (const ObjCMethodCall *MC = dyn_cast<ObjCMethodCall>(&Call)) { + if (MC->getMethodFamily() == OMF_init && MC->isReceiverSelfOrSuper()) { + + // Check if the message is not consumed, we know it will not be used in + // an assignment, ex: "self = [super init]". + const Expr *ME = MC->getOriginExpr(); + const LocationContext *LCtx = MC->getLocationContext(); + ParentMap &PM = LCtx->getAnalysisDeclContext()->getParentMap(); + if (!PM.isConsumedExpr(ME)) { + RetainSummaryTemplate ModifiableSummaryTemplate(S, *this); + ModifiableSummaryTemplate->setReceiverEffect(ArgEffect(DoNothing)); + ModifiableSummaryTemplate->setRetEffect(RetEffect::MakeNoRet()); + } + } + } +} + +const RetainSummary * +RetainSummaryManager::getSummary(const CallEvent &Call, + QualType ReceiverType) { + const RetainSummary *Summ; + switch (Call.getKind()) { + case CE_Function: + case CE_CXXMember: + case CE_CXXMemberOperator: + case CE_CXXConstructor: + case CE_CXXAllocator: + Summ = getFunctionSummary(cast_or_null<FunctionDecl>(Call.getDecl())); + break; + case CE_Block: + case CE_CXXDestructor: + // FIXME: These calls are currently unsupported. + return getPersistentStopSummary(); + case CE_ObjCMessage: { + const ObjCMethodCall &Msg = cast<ObjCMethodCall>(Call); + if (Msg.isInstanceMessage()) + Summ = getInstanceMethodSummary(Msg, ReceiverType); + else + Summ = getClassMethodSummary(Msg); + break; + } + } + + updateSummaryForCall(Summ, Call); + + assert(Summ && "Unknown call type?"); + return Summ; +} + + +const RetainSummary * +RetainSummaryManager::getCFCreateGetRuleSummary(const FunctionDecl *FD) { + if (coreFoundation::followsCreateRule(FD)) + return getCFSummaryCreateRule(FD); + + return getCFSummaryGetRule(FD); +} + +bool RetainSummaryManager::isTrustedReferenceCountImplementation( + const FunctionDecl *FD) { + return hasRCAnnotation(FD, "rc_ownership_trusted_implementation"); +} + +Optional<RetainSummaryManager::BehaviorSummary> +RetainSummaryManager::canEval(const CallExpr *CE, const FunctionDecl *FD, + bool &hasTrustedImplementationAnnotation) { + + IdentifierInfo *II = FD->getIdentifier(); + if (!II) + return None; + + StringRef FName = II->getName(); + FName = FName.substr(FName.find_first_not_of('_')); + + QualType ResultTy = CE->getCallReturnType(Ctx); + if (ResultTy->isObjCIdType()) { + if (II->isStr("NSMakeCollectable")) + return BehaviorSummary::Identity; + } else if (ResultTy->isPointerType()) { + // Handle: (CF|CG|CV)Retain + // CFAutorelease + // It's okay to be a little sloppy here. + if (FName == "CMBufferQueueDequeueAndRetain" || + FName == "CMBufferQueueDequeueIfDataReadyAndRetain") { + // Part of: <rdar://problem/39390714>. + // These are not retain. They just return something and retain it. + return None; + } + if (cocoa::isRefType(ResultTy, "CF", FName) || + cocoa::isRefType(ResultTy, "CG", FName) || + cocoa::isRefType(ResultTy, "CV", FName)) + if (isRetain(FD, FName) || isAutorelease(FD, FName) || + isMakeCollectable(FName)) + return BehaviorSummary::Identity; + + // safeMetaCast is called by OSDynamicCast. + // We assume that OSDynamicCast is either an identity (cast is OK, + // the input was non-zero), + // or that it returns zero (when the cast failed, or the input + // was zero). + if (TrackOSObjects && isOSObjectDynamicCast(FName)) { + return BehaviorSummary::IdentityOrZero; + } + + const FunctionDecl* FDD = FD->getDefinition(); + if (FDD && isTrustedReferenceCountImplementation(FDD)) { + hasTrustedImplementationAnnotation = true; + return BehaviorSummary::Identity; + } + } + + if (const auto *MD = dyn_cast<CXXMethodDecl>(FD)) { + const CXXRecordDecl *Parent = MD->getParent(); + if (TrackOSObjects && Parent && isOSObjectSubclass(Parent)) + if (FName == "release" || FName == "retain") + return BehaviorSummary::NoOp; + } + + return None; +} + +const RetainSummary * +RetainSummaryManager::getUnarySummary(const FunctionType* FT, + ArgEffectKind AE) { + + // Unary functions have no arg effects by definition. + ArgEffects ScratchArgs(AF.getEmptyMap()); + + // Sanity check that this is *really* a unary function. This can + // happen if people do weird things. + const FunctionProtoType* FTP = dyn_cast<FunctionProtoType>(FT); + if (!FTP || FTP->getNumParams() != 1) + return getPersistentStopSummary(); + + ArgEffect Effect(AE, ObjKind::CF); + + ScratchArgs = AF.add(ScratchArgs, 0, Effect); + return getPersistentSummary(RetEffect::MakeNoRet(), + ScratchArgs, + ArgEffect(DoNothing), ArgEffect(DoNothing)); +} + +const RetainSummary * +RetainSummaryManager::getOSSummaryRetainRule(const FunctionDecl *FD) { + return getPersistentSummary(RetEffect::MakeNoRet(), + AF.getEmptyMap(), + /*ReceiverEff=*/ArgEffect(DoNothing), + /*DefaultEff=*/ArgEffect(DoNothing), + /*ThisEff=*/ArgEffect(IncRef, ObjKind::OS)); +} + +const RetainSummary * +RetainSummaryManager::getOSSummaryReleaseRule(const FunctionDecl *FD) { + return getPersistentSummary(RetEffect::MakeNoRet(), + AF.getEmptyMap(), + /*ReceiverEff=*/ArgEffect(DoNothing), + /*DefaultEff=*/ArgEffect(DoNothing), + /*ThisEff=*/ArgEffect(DecRef, ObjKind::OS)); +} + +const RetainSummary * +RetainSummaryManager::getOSSummaryFreeRule(const FunctionDecl *FD) { + return getPersistentSummary(RetEffect::MakeNoRet(), + AF.getEmptyMap(), + /*ReceiverEff=*/ArgEffect(DoNothing), + /*DefaultEff=*/ArgEffect(DoNothing), + /*ThisEff=*/ArgEffect(Dealloc, ObjKind::OS)); +} + +const RetainSummary * +RetainSummaryManager::getOSSummaryCreateRule(const FunctionDecl *FD) { + return getPersistentSummary(RetEffect::MakeOwned(ObjKind::OS), + AF.getEmptyMap()); +} + +const RetainSummary * +RetainSummaryManager::getOSSummaryGetRule(const FunctionDecl *FD) { + return getPersistentSummary(RetEffect::MakeNotOwned(ObjKind::OS), + AF.getEmptyMap()); +} + +const RetainSummary * +RetainSummaryManager::getCFSummaryCreateRule(const FunctionDecl *FD) { + return getPersistentSummary(RetEffect::MakeOwned(ObjKind::CF), + ArgEffects(AF.getEmptyMap())); +} + +const RetainSummary * +RetainSummaryManager::getCFSummaryGetRule(const FunctionDecl *FD) { + return getPersistentSummary(RetEffect::MakeNotOwned(ObjKind::CF), + ArgEffects(AF.getEmptyMap()), + ArgEffect(DoNothing), ArgEffect(DoNothing)); +} + + + + +//===----------------------------------------------------------------------===// +// Summary creation for Selectors. +//===----------------------------------------------------------------------===// + +Optional<RetEffect> +RetainSummaryManager::getRetEffectFromAnnotations(QualType RetTy, + const Decl *D) { + if (hasAnyEnabledAttrOf<NSReturnsRetainedAttr>(D, RetTy)) + return ObjCAllocRetE; + + if (auto K = hasAnyEnabledAttrOf<CFReturnsRetainedAttr, OSReturnsRetainedAttr, + GeneralizedReturnsRetainedAttr>(D, RetTy)) + return RetEffect::MakeOwned(*K); + + if (auto K = hasAnyEnabledAttrOf< + CFReturnsNotRetainedAttr, OSReturnsNotRetainedAttr, + GeneralizedReturnsNotRetainedAttr, NSReturnsNotRetainedAttr, + NSReturnsAutoreleasedAttr>(D, RetTy)) + return RetEffect::MakeNotOwned(*K); + + if (const auto *MD = dyn_cast<CXXMethodDecl>(D)) + for (const auto *PD : MD->overridden_methods()) + if (auto RE = getRetEffectFromAnnotations(RetTy, PD)) + return RE; + + return None; +} + +/// \return Whether the chain of typedefs starting from {@code QT} +/// has a typedef with a given name {@code Name}. +static bool hasTypedefNamed(QualType QT, + StringRef Name) { + while (auto *T = dyn_cast<TypedefType>(QT)) { + const auto &Context = T->getDecl()->getASTContext(); + if (T->getDecl()->getIdentifier() == &Context.Idents.get(Name)) + return true; + QT = T->getDecl()->getUnderlyingType(); + } + return false; +} + +static QualType getCallableReturnType(const NamedDecl *ND) { + if (const auto *FD = dyn_cast<FunctionDecl>(ND)) { + return FD->getReturnType(); + } else if (const auto *MD = dyn_cast<ObjCMethodDecl>(ND)) { + return MD->getReturnType(); + } else { + llvm_unreachable("Unexpected decl"); + } +} + +bool RetainSummaryManager::applyParamAnnotationEffect( + const ParmVarDecl *pd, unsigned parm_idx, const NamedDecl *FD, + RetainSummaryTemplate &Template) { + QualType QT = pd->getType(); + if (auto K = + hasAnyEnabledAttrOf<NSConsumedAttr, CFConsumedAttr, OSConsumedAttr, + GeneralizedConsumedAttr>(pd, QT)) { + Template->addArg(AF, parm_idx, ArgEffect(DecRef, *K)); + return true; + } else if (auto K = hasAnyEnabledAttrOf< + CFReturnsRetainedAttr, OSReturnsRetainedAttr, + OSReturnsRetainedOnNonZeroAttr, OSReturnsRetainedOnZeroAttr, + GeneralizedReturnsRetainedAttr>(pd, QT)) { + + // For OSObjects, we try to guess whether the object is created based + // on the return value. + if (K == ObjKind::OS) { + QualType QT = getCallableReturnType(FD); + + bool HasRetainedOnZero = pd->hasAttr<OSReturnsRetainedOnZeroAttr>(); + bool HasRetainedOnNonZero = pd->hasAttr<OSReturnsRetainedOnNonZeroAttr>(); + + // The usual convention is to create an object on non-zero return, but + // it's reverted if the typedef chain has a typedef kern_return_t, + // because kReturnSuccess constant is defined as zero. + // The convention can be overwritten by custom attributes. + bool SuccessOnZero = + HasRetainedOnZero || + (hasTypedefNamed(QT, "kern_return_t") && !HasRetainedOnNonZero); + bool ShouldSplit = !QT.isNull() && !QT->isVoidType(); + ArgEffectKind AK = RetainedOutParameter; + if (ShouldSplit && SuccessOnZero) { + AK = RetainedOutParameterOnZero; + } else if (ShouldSplit && (!SuccessOnZero || HasRetainedOnNonZero)) { + AK = RetainedOutParameterOnNonZero; + } + Template->addArg(AF, parm_idx, ArgEffect(AK, ObjKind::OS)); + } + + // For others: + // Do nothing. Retained out parameters will either point to a +1 reference + // or NULL, but the way you check for failure differs depending on the + // API. Consequently, we don't have a good way to track them yet. + return true; + } else if (auto K = hasAnyEnabledAttrOf<CFReturnsNotRetainedAttr, + OSReturnsNotRetainedAttr, + GeneralizedReturnsNotRetainedAttr>( + pd, QT)) { + Template->addArg(AF, parm_idx, ArgEffect(UnretainedOutParameter, *K)); + return true; + } + + if (const auto *MD = dyn_cast<CXXMethodDecl>(FD)) { + for (const auto *OD : MD->overridden_methods()) { + const ParmVarDecl *OP = OD->parameters()[parm_idx]; + if (applyParamAnnotationEffect(OP, parm_idx, OD, Template)) + return true; + } + } + + return false; +} + +void +RetainSummaryManager::updateSummaryFromAnnotations(const RetainSummary *&Summ, + const FunctionDecl *FD) { + if (!FD) + return; + + assert(Summ && "Must have a summary to add annotations to."); + RetainSummaryTemplate Template(Summ, *this); + + // Effects on the parameters. + unsigned parm_idx = 0; + for (auto pi = FD->param_begin(), + pe = FD->param_end(); pi != pe; ++pi, ++parm_idx) + applyParamAnnotationEffect(*pi, parm_idx, FD, Template); + + QualType RetTy = FD->getReturnType(); + if (Optional<RetEffect> RetE = getRetEffectFromAnnotations(RetTy, FD)) + Template->setRetEffect(*RetE); + + if (hasAnyEnabledAttrOf<OSConsumesThisAttr>(FD, RetTy)) + Template->setThisEffect(ArgEffect(DecRef, ObjKind::OS)); +} + +void +RetainSummaryManager::updateSummaryFromAnnotations(const RetainSummary *&Summ, + const ObjCMethodDecl *MD) { + if (!MD) + return; + + assert(Summ && "Must have a valid summary to add annotations to"); + RetainSummaryTemplate Template(Summ, *this); + + // Effects on the receiver. + if (hasAnyEnabledAttrOf<NSConsumesSelfAttr>(MD, MD->getReturnType())) + Template->setReceiverEffect(ArgEffect(DecRef, ObjKind::ObjC)); + + // Effects on the parameters. + unsigned parm_idx = 0; + for (auto pi = MD->param_begin(), pe = MD->param_end(); pi != pe; + ++pi, ++parm_idx) + applyParamAnnotationEffect(*pi, parm_idx, MD, Template); + + QualType RetTy = MD->getReturnType(); + if (Optional<RetEffect> RetE = getRetEffectFromAnnotations(RetTy, MD)) + Template->setRetEffect(*RetE); +} + +const RetainSummary * +RetainSummaryManager::getStandardMethodSummary(const ObjCMethodDecl *MD, + Selector S, QualType RetTy) { + // Any special effects? + ArgEffect ReceiverEff = ArgEffect(DoNothing, ObjKind::ObjC); + RetEffect ResultEff = RetEffect::MakeNoRet(); + + // Check the method family, and apply any default annotations. + switch (MD ? MD->getMethodFamily() : S.getMethodFamily()) { + case OMF_None: + case OMF_initialize: + case OMF_performSelector: + // Assume all Objective-C methods follow Cocoa Memory Management rules. + // FIXME: Does the non-threaded performSelector family really belong here? + // The selector could be, say, @selector(copy). + if (cocoa::isCocoaObjectRef(RetTy)) + ResultEff = RetEffect::MakeNotOwned(ObjKind::ObjC); + else if (coreFoundation::isCFObjectRef(RetTy)) { + // ObjCMethodDecl currently doesn't consider CF objects as valid return + // values for alloc, new, copy, or mutableCopy, so we have to + // double-check with the selector. This is ugly, but there aren't that + // many Objective-C methods that return CF objects, right? + if (MD) { + switch (S.getMethodFamily()) { + case OMF_alloc: + case OMF_new: + case OMF_copy: + case OMF_mutableCopy: + ResultEff = RetEffect::MakeOwned(ObjKind::CF); + break; + default: + ResultEff = RetEffect::MakeNotOwned(ObjKind::CF); + break; + } + } else { + ResultEff = RetEffect::MakeNotOwned(ObjKind::CF); + } + } + break; + case OMF_init: + ResultEff = ObjCInitRetE; + ReceiverEff = ArgEffect(DecRef, ObjKind::ObjC); + break; + case OMF_alloc: + case OMF_new: + case OMF_copy: + case OMF_mutableCopy: + if (cocoa::isCocoaObjectRef(RetTy)) + ResultEff = ObjCAllocRetE; + else if (coreFoundation::isCFObjectRef(RetTy)) + ResultEff = RetEffect::MakeOwned(ObjKind::CF); + break; + case OMF_autorelease: + ReceiverEff = ArgEffect(Autorelease, ObjKind::ObjC); + break; + case OMF_retain: + ReceiverEff = ArgEffect(IncRef, ObjKind::ObjC); + break; + case OMF_release: + ReceiverEff = ArgEffect(DecRef, ObjKind::ObjC); + break; + case OMF_dealloc: + ReceiverEff = ArgEffect(Dealloc, ObjKind::ObjC); + break; + case OMF_self: + // -self is handled specially by the ExprEngine to propagate the receiver. + break; + case OMF_retainCount: + case OMF_finalize: + // These methods don't return objects. + break; + } + + // If one of the arguments in the selector has the keyword 'delegate' we + // should stop tracking the reference count for the receiver. This is + // because the reference count is quite possibly handled by a delegate + // method. + if (S.isKeywordSelector()) { + for (unsigned i = 0, e = S.getNumArgs(); i != e; ++i) { + StringRef Slot = S.getNameForSlot(i); + if (Slot.substr(Slot.size() - 8).equals_lower("delegate")) { + if (ResultEff == ObjCInitRetE) + ResultEff = RetEffect::MakeNoRetHard(); + else + ReceiverEff = ArgEffect(StopTrackingHard, ObjKind::ObjC); + } + } + } + + if (ReceiverEff.getKind() == DoNothing && + ResultEff.getKind() == RetEffect::NoRet) + return getDefaultSummary(); + + return getPersistentSummary(ResultEff, ArgEffects(AF.getEmptyMap()), + ArgEffect(ReceiverEff), ArgEffect(MayEscape)); +} + +const RetainSummary *RetainSummaryManager::getInstanceMethodSummary( + const ObjCMethodCall &Msg, + QualType ReceiverType) { + const ObjCInterfaceDecl *ReceiverClass = nullptr; + + // We do better tracking of the type of the object than the core ExprEngine. + // See if we have its type in our private state. + if (!ReceiverType.isNull()) + if (const auto *PT = ReceiverType->getAs<ObjCObjectPointerType>()) + ReceiverClass = PT->getInterfaceDecl(); + + // If we don't know what kind of object this is, fall back to its static type. + if (!ReceiverClass) + ReceiverClass = Msg.getReceiverInterface(); + + // FIXME: The receiver could be a reference to a class, meaning that + // we should use the class method. + // id x = [NSObject class]; + // [x performSelector:... withObject:... afterDelay:...]; + Selector S = Msg.getSelector(); + const ObjCMethodDecl *Method = Msg.getDecl(); + if (!Method && ReceiverClass) + Method = ReceiverClass->getInstanceMethod(S); + + return getMethodSummary(S, ReceiverClass, Method, Msg.getResultType(), + ObjCMethodSummaries); +} + +const RetainSummary * +RetainSummaryManager::getMethodSummary(Selector S, + const ObjCInterfaceDecl *ID, + const ObjCMethodDecl *MD, QualType RetTy, + ObjCMethodSummariesTy &CachedSummaries) { + + // Objective-C method summaries are only applicable to ObjC and CF objects. + if (!TrackObjCAndCFObjects) + return getDefaultSummary(); + + // Look up a summary in our summary cache. + const RetainSummary *Summ = CachedSummaries.find(ID, S); + + if (!Summ) { + Summ = getStandardMethodSummary(MD, S, RetTy); + + // Annotations override defaults. + updateSummaryFromAnnotations(Summ, MD); + + // Memoize the summary. + CachedSummaries[ObjCSummaryKey(ID, S)] = Summ; + } + + return Summ; +} + +void RetainSummaryManager::InitializeClassMethodSummaries() { + ArgEffects ScratchArgs = AF.getEmptyMap(); + + // Create the [NSAssertionHandler currentHander] summary. + addClassMethSummary("NSAssertionHandler", "currentHandler", + getPersistentSummary(RetEffect::MakeNotOwned(ObjKind::ObjC), + ScratchArgs)); + + // Create the [NSAutoreleasePool addObject:] summary. + ScratchArgs = AF.add(ScratchArgs, 0, ArgEffect(Autorelease)); + addClassMethSummary("NSAutoreleasePool", "addObject", + getPersistentSummary(RetEffect::MakeNoRet(), ScratchArgs, + ArgEffect(DoNothing), + ArgEffect(Autorelease))); +} + +void RetainSummaryManager::InitializeMethodSummaries() { + + ArgEffects ScratchArgs = AF.getEmptyMap(); + // Create the "init" selector. It just acts as a pass-through for the + // receiver. + const RetainSummary *InitSumm = getPersistentSummary( + ObjCInitRetE, ScratchArgs, ArgEffect(DecRef, ObjKind::ObjC)); + addNSObjectMethSummary(GetNullarySelector("init", Ctx), InitSumm); + + // awakeAfterUsingCoder: behaves basically like an 'init' method. It + // claims the receiver and returns a retained object. + addNSObjectMethSummary(GetUnarySelector("awakeAfterUsingCoder", Ctx), + InitSumm); + + // The next methods are allocators. + const RetainSummary *AllocSumm = getPersistentSummary(ObjCAllocRetE, + ScratchArgs); + const RetainSummary *CFAllocSumm = + getPersistentSummary(RetEffect::MakeOwned(ObjKind::CF), ScratchArgs); + + // Create the "retain" selector. + RetEffect NoRet = RetEffect::MakeNoRet(); + const RetainSummary *Summ = getPersistentSummary( + NoRet, ScratchArgs, ArgEffect(IncRef, ObjKind::ObjC)); + addNSObjectMethSummary(GetNullarySelector("retain", Ctx), Summ); + + // Create the "release" selector. + Summ = getPersistentSummary(NoRet, ScratchArgs, + ArgEffect(DecRef, ObjKind::ObjC)); + addNSObjectMethSummary(GetNullarySelector("release", Ctx), Summ); + + // Create the -dealloc summary. + Summ = getPersistentSummary(NoRet, ScratchArgs, ArgEffect(Dealloc, + ObjKind::ObjC)); + addNSObjectMethSummary(GetNullarySelector("dealloc", Ctx), Summ); + + // Create the "autorelease" selector. + Summ = getPersistentSummary(NoRet, ScratchArgs, ArgEffect(Autorelease, + ObjKind::ObjC)); + addNSObjectMethSummary(GetNullarySelector("autorelease", Ctx), Summ); + + // For NSWindow, allocated objects are (initially) self-owned. + // FIXME: For now we opt for false negatives with NSWindow, as these objects + // self-own themselves. However, they only do this once they are displayed. + // Thus, we need to track an NSWindow's display status. + // This is tracked in <rdar://problem/6062711>. + // See also http://llvm.org/bugs/show_bug.cgi?id=3714. + const RetainSummary *NoTrackYet = + getPersistentSummary(RetEffect::MakeNoRet(), ScratchArgs, + ArgEffect(StopTracking), ArgEffect(StopTracking)); + + addClassMethSummary("NSWindow", "alloc", NoTrackYet); + + // For NSPanel (which subclasses NSWindow), allocated objects are not + // self-owned. + // FIXME: For now we don't track NSPanels. object for the same reason + // as for NSWindow objects. + addClassMethSummary("NSPanel", "alloc", NoTrackYet); + + // For NSNull, objects returned by +null are singletons that ignore + // retain/release semantics. Just don't track them. + // <rdar://problem/12858915> + addClassMethSummary("NSNull", "null", NoTrackYet); + + // Don't track allocated autorelease pools, as it is okay to prematurely + // exit a method. + addClassMethSummary("NSAutoreleasePool", "alloc", NoTrackYet); + addClassMethSummary("NSAutoreleasePool", "allocWithZone", NoTrackYet, false); + addClassMethSummary("NSAutoreleasePool", "new", NoTrackYet); + + // Create summaries QCRenderer/QCView -createSnapShotImageOfType: + addInstMethSummary("QCRenderer", AllocSumm, "createSnapshotImageOfType"); + addInstMethSummary("QCView", AllocSumm, "createSnapshotImageOfType"); + + // Create summaries for CIContext, 'createCGImage' and + // 'createCGLayerWithSize'. These objects are CF objects, and are not + // automatically garbage collected. + addInstMethSummary("CIContext", CFAllocSumm, "createCGImage", "fromRect"); + addInstMethSummary("CIContext", CFAllocSumm, "createCGImage", "fromRect", + "format", "colorSpace"); + addInstMethSummary("CIContext", CFAllocSumm, "createCGLayerWithSize", "info"); +} + +CallEffects CallEffects::getEffect(const ObjCMethodDecl *MD) { + ASTContext &Ctx = MD->getASTContext(); + LangOptions L = Ctx.getLangOpts(); + RetainSummaryManager M(Ctx, L.ObjCAutoRefCount, + /*TrackNSAndCFObjects=*/true, + /*TrackOSObjects=*/false); + const RetainSummary *S = M.getMethodSummary(MD); + CallEffects CE(S->getRetEffect(), S->getReceiverEffect()); + unsigned N = MD->param_size(); + for (unsigned i = 0; i < N; ++i) { + CE.Args.push_back(S->getArg(i)); + } + return CE; +} + +CallEffects CallEffects::getEffect(const FunctionDecl *FD) { + ASTContext &Ctx = FD->getASTContext(); + LangOptions L = Ctx.getLangOpts(); + RetainSummaryManager M(Ctx, L.ObjCAutoRefCount, + /*TrackNSAndCFObjects=*/true, + /*TrackOSObjects=*/false); + const RetainSummary *S = M.getFunctionSummary(FD); + CallEffects CE(S->getRetEffect()); + unsigned N = FD->param_size(); + for (unsigned i = 0; i < N; ++i) { + CE.Args.push_back(S->getArg(i)); + } + return CE; +} diff --git a/lib/StaticAnalyzer/Core/SMTConstraintManager.cpp b/lib/StaticAnalyzer/Core/SMTConstraintManager.cpp deleted file mode 100644 index d379562bf325e..0000000000000 --- a/lib/StaticAnalyzer/Core/SMTConstraintManager.cpp +++ /dev/null @@ -1,181 +0,0 @@ -//== SMTConstraintManager.cpp -----------------------------------*- C++ -*--==// -// -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// - -#include "clang/StaticAnalyzer/Core/PathSensitive/SMTConstraintManager.h" -#include "clang/Basic/TargetInfo.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" - -using namespace clang; -using namespace ento; - -ProgramStateRef SMTConstraintManager::assumeSym(ProgramStateRef State, - SymbolRef Sym, - bool Assumption) { - ASTContext &Ctx = getBasicVals().getContext(); - - QualType RetTy; - bool hasComparison; - - SMTExprRef Exp = Solver->getExpr(Ctx, Sym, &RetTy, &hasComparison); - - // Create zero comparison for implicit boolean cast, with reversed assumption - if (!hasComparison && !RetTy->isBooleanType()) - return assumeExpr(State, Sym, - Solver->getZeroExpr(Ctx, Exp, RetTy, !Assumption)); - - return assumeExpr(State, Sym, Assumption ? Exp : Solver->mkNot(Exp)); -} - -ProgramStateRef SMTConstraintManager::assumeSymInclusiveRange( - ProgramStateRef State, SymbolRef Sym, const llvm::APSInt &From, - const llvm::APSInt &To, bool InRange) { - ASTContext &Ctx = getBasicVals().getContext(); - return assumeExpr(State, Sym, - Solver->getRangeExpr(Ctx, Sym, From, To, InRange)); -} - -ProgramStateRef -SMTConstraintManager::assumeSymUnsupported(ProgramStateRef State, SymbolRef Sym, - bool Assumption) { - // Skip anything that is unsupported - return State; -} - -ConditionTruthVal SMTConstraintManager::checkNull(ProgramStateRef State, - SymbolRef Sym) { - ASTContext &Ctx = getBasicVals().getContext(); - - QualType RetTy; - // The expression may be casted, so we cannot call getZ3DataExpr() directly - SMTExprRef VarExp = Solver->getExpr(Ctx, Sym, &RetTy); - SMTExprRef Exp = Solver->getZeroExpr(Ctx, VarExp, RetTy, /*Assumption=*/true); - - // Negate the constraint - SMTExprRef NotExp = - Solver->getZeroExpr(Ctx, VarExp, RetTy, /*Assumption=*/false); - - Solver->reset(); - addStateConstraints(State); - - Solver->push(); - Solver->addConstraint(Exp); - ConditionTruthVal isSat = Solver->check(); - - Solver->pop(); - Solver->addConstraint(NotExp); - ConditionTruthVal isNotSat = Solver->check(); - - // Zero is the only possible solution - if (isSat.isConstrainedTrue() && isNotSat.isConstrainedFalse()) - return true; - - // Zero is not a solution - if (isSat.isConstrainedFalse() && isNotSat.isConstrainedTrue()) - return false; - - // Zero may be a solution - return ConditionTruthVal(); -} - -const llvm::APSInt *SMTConstraintManager::getSymVal(ProgramStateRef State, - SymbolRef Sym) const { - BasicValueFactory &BVF = getBasicVals(); - ASTContext &Ctx = BVF.getContext(); - - if (const SymbolData *SD = dyn_cast<SymbolData>(Sym)) { - QualType Ty = Sym->getType(); - assert(!Ty->isRealFloatingType()); - llvm::APSInt Value(Ctx.getTypeSize(Ty), - !Ty->isSignedIntegerOrEnumerationType()); - - SMTExprRef Exp = - Solver->fromData(SD->getSymbolID(), Ty, Ctx.getTypeSize(Ty)); - - Solver->reset(); - addStateConstraints(State); - - // Constraints are unsatisfiable - ConditionTruthVal isSat = Solver->check(); - if (!isSat.isConstrainedTrue()) - return nullptr; - - // Model does not assign interpretation - if (!Solver->getInterpretation(Exp, Value)) - return nullptr; - - // A value has been obtained, check if it is the only value - SMTExprRef NotExp = Solver->fromBinOp( - Exp, BO_NE, - Ty->isBooleanType() ? Solver->fromBoolean(Value.getBoolValue()) - : Solver->fromAPSInt(Value), - false); - - Solver->addConstraint(NotExp); - - ConditionTruthVal isNotSat = Solver->check(); - if (isNotSat.isConstrainedTrue()) - return nullptr; - - // This is the only solution, store it - return &BVF.getValue(Value); - } - - if (const SymbolCast *SC = dyn_cast<SymbolCast>(Sym)) { - SymbolRef CastSym = SC->getOperand(); - QualType CastTy = SC->getType(); - // Skip the void type - if (CastTy->isVoidType()) - return nullptr; - - const llvm::APSInt *Value; - if (!(Value = getSymVal(State, CastSym))) - return nullptr; - return &BVF.Convert(SC->getType(), *Value); - } - - if (const BinarySymExpr *BSE = dyn_cast<BinarySymExpr>(Sym)) { - const llvm::APSInt *LHS, *RHS; - if (const SymIntExpr *SIE = dyn_cast<SymIntExpr>(BSE)) { - LHS = getSymVal(State, SIE->getLHS()); - RHS = &SIE->getRHS(); - } else if (const IntSymExpr *ISE = dyn_cast<IntSymExpr>(BSE)) { - LHS = &ISE->getLHS(); - RHS = getSymVal(State, ISE->getRHS()); - } else if (const SymSymExpr *SSM = dyn_cast<SymSymExpr>(BSE)) { - // Early termination to avoid expensive call - LHS = getSymVal(State, SSM->getLHS()); - RHS = LHS ? getSymVal(State, SSM->getRHS()) : nullptr; - } else { - llvm_unreachable("Unsupported binary expression to get symbol value!"); - } - - if (!LHS || !RHS) - return nullptr; - - llvm::APSInt ConvertedLHS, ConvertedRHS; - QualType LTy, RTy; - std::tie(ConvertedLHS, LTy) = Solver->fixAPSInt(Ctx, *LHS); - std::tie(ConvertedRHS, RTy) = Solver->fixAPSInt(Ctx, *RHS); - Solver->doIntTypeConversion<llvm::APSInt, &SMTSolver::castAPSInt>( - Ctx, ConvertedLHS, LTy, ConvertedRHS, RTy); - return BVF.evalAPSInt(BSE->getOpcode(), ConvertedLHS, ConvertedRHS); - } - - llvm_unreachable("Unsupported expression to get symbol value!"); -} - -ConditionTruthVal -SMTConstraintManager::checkModel(ProgramStateRef State, - const SMTExprRef &Exp) const { - Solver->reset(); - Solver->addConstraint(Exp); - addStateConstraints(State); - return Solver->check(); -} diff --git a/lib/StaticAnalyzer/Core/SValBuilder.cpp b/lib/StaticAnalyzer/Core/SValBuilder.cpp index f292dca8e99fd..6c0d487c8a872 100644 --- a/lib/StaticAnalyzer/Core/SValBuilder.cpp +++ b/lib/StaticAnalyzer/Core/SValBuilder.cpp @@ -271,8 +271,8 @@ DefinedSVal SValBuilder::getBlockPointer(const BlockDecl *block, /// Return a memory region for the 'this' object reference. loc::MemRegionVal SValBuilder::getCXXThis(const CXXMethodDecl *D, const StackFrameContext *SFC) { - return loc::MemRegionVal(getRegionManager(). - getCXXThisRegion(D->getThisType(getContext()), SFC)); + return loc::MemRegionVal( + getRegionManager().getCXXThisRegion(D->getThisType(), SFC)); } /// Return a memory region for the 'this' object reference. @@ -362,9 +362,9 @@ Optional<SVal> SValBuilder::getConstantVal(const Expr *E) { return None; ASTContext &Ctx = getContext(); - llvm::APSInt Result; + Expr::EvalResult Result; if (E->EvaluateAsInt(Result, Ctx)) - return makeIntVal(Result); + return makeIntVal(Result.Val.getInt()); if (Loc::isLocType(E->getType())) if (E->isNullPointerConstant(Ctx, Expr::NPC_ValueDependentIsNotNull)) @@ -375,8 +375,7 @@ Optional<SVal> SValBuilder::getConstantVal(const Expr *E) { } } -SVal SValBuilder::makeSymExprValNN(ProgramStateRef State, - BinaryOperator::Opcode Op, +SVal SValBuilder::makeSymExprValNN(BinaryOperator::Opcode Op, NonLoc LHS, NonLoc RHS, QualType ResultTy) { const SymExpr *symLHS = LHS.getAsSymExpr(); @@ -385,8 +384,8 @@ SVal SValBuilder::makeSymExprValNN(ProgramStateRef State, // TODO: When the Max Complexity is reached, we should conjure a symbol // instead of generating an Unknown value and propagate the taint info to it. const unsigned MaxComp = StateMgr.getOwningEngine() - ->getAnalysisManager() - .options.getMaxSymbolComplexity(); + .getAnalysisManager() + .options.MaxSymbolComplexity; if (symLHS && symRHS && (symLHS->computeComplexity() + symRHS->computeComplexity()) < MaxComp) diff --git a/lib/StaticAnalyzer/Core/SVals.cpp b/lib/StaticAnalyzer/Core/SVals.cpp index 559ca2c9840d3..933c5c3300723 100644 --- a/lib/StaticAnalyzer/Core/SVals.cpp +++ b/lib/StaticAnalyzer/Core/SVals.cpp @@ -85,7 +85,7 @@ const FunctionDecl *SVal::getAsFunctionDecl() const { SymbolRef SVal::getAsLocSymbol(bool IncludeBaseRegions) const { // FIXME: should we consider SymbolRef wrapped in CodeTextRegion? if (Optional<nonloc::LocAsInteger> X = getAs<nonloc::LocAsInteger>()) - return X->getLoc().getAsLocSymbol(); + return X->getLoc().getAsLocSymbol(IncludeBaseRegions); if (Optional<loc::MemRegionVal> X = getAs<loc::MemRegionVal>()) { const MemRegion *R = X->getRegion(); @@ -171,6 +171,10 @@ const TypedValueRegion *nonloc::LazyCompoundVal::getRegion() const { return static_cast<const LazyCompoundValData*>(Data)->getRegion(); } +bool nonloc::PointerToMember::isNullMemberPointer() const { + return getPTMData().isNull(); +} + const DeclaratorDecl *nonloc::PointerToMember::getDecl() const { const auto PTMD = this->getPTMData(); if (PTMD.isNull()) diff --git a/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp b/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp new file mode 100644 index 0000000000000..fecbc00010793 --- /dev/null +++ b/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp @@ -0,0 +1,349 @@ +//===--- SarifDiagnostics.cpp - Sarif Diagnostics for Paths -----*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines the SarifDiagnostics object. +// +//===----------------------------------------------------------------------===// + +#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" +#include "llvm/Support/JSON.h" +#include "llvm/Support/Path.h" + +using namespace llvm; +using namespace clang; +using namespace ento; + +namespace { +class SarifDiagnostics : public PathDiagnosticConsumer { + std::string OutputFile; + +public: + SarifDiagnostics(AnalyzerOptions &, const std::string &Output) + : OutputFile(Output) {} + ~SarifDiagnostics() override = default; + + void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags, + FilesMade *FM) override; + + StringRef getName() const override { return "SarifDiagnostics"; } + PathGenerationScheme getGenerationScheme() const override { return Minimal; } + bool supportsLogicalOpControlFlow() const override { return true; } + bool supportsCrossFileDiagnostics() const override { return true; } +}; +} // end anonymous namespace + +void ento::createSarifDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts, + PathDiagnosticConsumers &C, + const std::string &Output, + const Preprocessor &) { + C.push_back(new SarifDiagnostics(AnalyzerOpts, Output)); +} + +static StringRef getFileName(const FileEntry &FE) { + StringRef Filename = FE.tryGetRealPathName(); + if (Filename.empty()) + Filename = FE.getName(); + return Filename; +} + +static std::string percentEncodeURICharacter(char C) { + // RFC 3986 claims alpha, numeric, and this handful of + // characters are not reserved for the path component and + // should be written out directly. Otherwise, percent + // encode the character and write that out instead of the + // reserved character. + if (llvm::isAlnum(C) || + StringRef::npos != StringRef("-._~:@!$&'()*+,;=").find(C)) + return std::string(&C, 1); + return "%" + llvm::toHex(StringRef(&C, 1)); +} + +static std::string fileNameToURI(StringRef Filename) { + llvm::SmallString<32> Ret = StringRef("file://"); + + // Get the root name to see if it has a URI authority. + StringRef Root = sys::path::root_name(Filename); + if (Root.startswith("//")) { + // There is an authority, so add it to the URI. + Ret += Root.drop_front(2).str(); + } else if (!Root.empty()) { + // There is no authority, so end the component and add the root to the URI. + Ret += Twine("/" + Root).str(); + } + + auto Iter = sys::path::begin(Filename), End = sys::path::end(Filename); + assert(Iter != End && "Expected there to be a non-root path component."); + // Add the rest of the path components, encoding any reserved characters; + // we skip past the first path component, as it was handled it above. + std::for_each(++Iter, End, [&Ret](StringRef Component) { + // For reasons unknown to me, we may get a backslash with Windows native + // paths for the initial backslash following the drive component, which + // we need to ignore as a URI path part. + if (Component == "\\") + return; + + // Add the separator between the previous path part and the one being + // currently processed. + Ret += "/"; + + // URI encode the part. + for (char C : Component) { + Ret += percentEncodeURICharacter(C); + } + }); + + return Ret.str().str(); +} + +static json::Object createFileLocation(const FileEntry &FE) { + return json::Object{{"uri", fileNameToURI(getFileName(FE))}}; +} + +static json::Object createFile(const FileEntry &FE) { + return json::Object{{"fileLocation", createFileLocation(FE)}, + {"roles", json::Array{"resultFile"}}, + {"length", FE.getSize()}, + {"mimeType", "text/plain"}}; +} + +static json::Object createFileLocation(const FileEntry &FE, + json::Array &Files) { + std::string FileURI = fileNameToURI(getFileName(FE)); + + // See if the Files array contains this URI already. If it does not, create + // a new file object to add to the array. + auto I = llvm::find_if(Files, [&](const json::Value &File) { + if (const json::Object *Obj = File.getAsObject()) { + if (const json::Object *FileLoc = Obj->getObject("fileLocation")) { + Optional<StringRef> URI = FileLoc->getString("uri"); + return URI && URI->equals(FileURI); + } + } + return false; + }); + + // Calculate the index within the file location array so it can be stored in + // the JSON object. + auto Index = static_cast<unsigned>(std::distance(Files.begin(), I)); + if (I == Files.end()) + Files.push_back(createFile(FE)); + + return json::Object{{"uri", FileURI}, {"fileIndex", Index}}; +} + +static json::Object createTextRegion(SourceRange R, const SourceManager &SM) { + return json::Object{ + {"startLine", SM.getExpansionLineNumber(R.getBegin())}, + {"endLine", SM.getExpansionLineNumber(R.getEnd())}, + {"startColumn", SM.getExpansionColumnNumber(R.getBegin())}, + {"endColumn", SM.getExpansionColumnNumber(R.getEnd())}}; +} + +static json::Object createPhysicalLocation(SourceRange R, const FileEntry &FE, + const SourceManager &SMgr, + json::Array &Files) { + return json::Object{{{"fileLocation", createFileLocation(FE, Files)}, + {"region", createTextRegion(R, SMgr)}}}; +} + +enum class Importance { Important, Essential, Unimportant }; + +static StringRef importanceToStr(Importance I) { + switch (I) { + case Importance::Important: + return "important"; + case Importance::Essential: + return "essential"; + case Importance::Unimportant: + return "unimportant"; + } + llvm_unreachable("Fully covered switch is not so fully covered"); +} + +static json::Object createThreadFlowLocation(json::Object &&Location, + Importance I) { + return json::Object{{"location", std::move(Location)}, + {"importance", importanceToStr(I)}}; +} + +static json::Object createMessage(StringRef Text) { + return json::Object{{"text", Text.str()}}; +} + +static json::Object createLocation(json::Object &&PhysicalLocation, + StringRef Message = "") { + json::Object Ret{{"physicalLocation", std::move(PhysicalLocation)}}; + if (!Message.empty()) + Ret.insert({"message", createMessage(Message)}); + return Ret; +} + +static Importance calculateImportance(const PathDiagnosticPiece &Piece) { + switch (Piece.getKind()) { + case PathDiagnosticPiece::Kind::Call: + case PathDiagnosticPiece::Kind::Macro: + case PathDiagnosticPiece::Kind::Note: + // FIXME: What should be reported here? + break; + case PathDiagnosticPiece::Kind::Event: + return Piece.getTagStr() == "ConditionBRVisitor" ? Importance::Important + : Importance::Essential; + case PathDiagnosticPiece::Kind::ControlFlow: + return Importance::Unimportant; + } + return Importance::Unimportant; +} + +static json::Object createThreadFlow(const PathPieces &Pieces, + json::Array &Files) { + const SourceManager &SMgr = Pieces.front()->getLocation().getManager(); + json::Array Locations; + for (const auto &Piece : Pieces) { + const PathDiagnosticLocation &P = Piece->getLocation(); + Locations.push_back(createThreadFlowLocation( + createLocation(createPhysicalLocation(P.asRange(), + *P.asLocation().getFileEntry(), + SMgr, Files), + Piece->getString()), + calculateImportance(*Piece))); + } + return json::Object{{"locations", std::move(Locations)}}; +} + +static json::Object createCodeFlow(const PathPieces &Pieces, + json::Array &Files) { + return json::Object{ + {"threadFlows", json::Array{createThreadFlow(Pieces, Files)}}}; +} + +static json::Object createTool() { + return json::Object{{"name", "clang"}, + {"fullName", "clang static analyzer"}, + {"language", "en-US"}, + {"version", getClangFullVersion()}}; +} + +static json::Object createResult(const PathDiagnostic &Diag, json::Array &Files, + const StringMap<unsigned> &RuleMapping) { + const PathPieces &Path = Diag.path.flatten(false); + const SourceManager &SMgr = Path.front()->getLocation().getManager(); + + auto Iter = RuleMapping.find(Diag.getCheckName()); + assert(Iter != RuleMapping.end() && "Rule ID is not in the array index map?"); + + return json::Object{ + {"message", createMessage(Diag.getVerboseDescription())}, + {"codeFlows", json::Array{createCodeFlow(Path, Files)}}, + {"locations", + json::Array{createLocation(createPhysicalLocation( + Diag.getLocation().asRange(), + *Diag.getLocation().asLocation().getFileEntry(), SMgr, Files))}}, + {"ruleIndex", Iter->getValue()}, + {"ruleId", Diag.getCheckName()}}; +} + +static StringRef getRuleDescription(StringRef CheckName) { + return llvm::StringSwitch<StringRef>(CheckName) +#define GET_CHECKERS +#define CHECKER(FULLNAME, CLASS, HELPTEXT, DOC_URI) \ + .Case(FULLNAME, HELPTEXT) +#include "clang/StaticAnalyzer/Checkers/Checkers.inc" +#undef CHECKER +#undef GET_CHECKERS + ; +} + +static StringRef getRuleHelpURIStr(StringRef CheckName) { + return llvm::StringSwitch<StringRef>(CheckName) +#define GET_CHECKERS +#define CHECKER(FULLNAME, CLASS, HELPTEXT, DOC_URI) \ + .Case(FULLNAME, DOC_URI) +#include "clang/StaticAnalyzer/Checkers/Checkers.inc" +#undef CHECKER +#undef GET_CHECKERS + ; +} + +static json::Object createRule(const PathDiagnostic &Diag) { + StringRef CheckName = Diag.getCheckName(); + json::Object Ret{ + {"fullDescription", createMessage(getRuleDescription(CheckName))}, + {"name", createMessage(CheckName)}, + {"id", CheckName}}; + + std::string RuleURI = getRuleHelpURIStr(CheckName); + if (!RuleURI.empty()) + Ret["helpUri"] = RuleURI; + + return Ret; +} + +static json::Array createRules(std::vector<const PathDiagnostic *> &Diags, + StringMap<unsigned> &RuleMapping) { + json::Array Rules; + llvm::StringSet<> Seen; + + llvm::for_each(Diags, [&](const PathDiagnostic *D) { + StringRef RuleID = D->getCheckName(); + std::pair<llvm::StringSet<>::iterator, bool> P = Seen.insert(RuleID); + if (P.second) { + RuleMapping[RuleID] = Rules.size(); // Maps RuleID to an Array Index. + Rules.push_back(createRule(*D)); + } + }); + + return Rules; +} + +static json::Object createResources(std::vector<const PathDiagnostic *> &Diags, + StringMap<unsigned> &RuleMapping) { + return json::Object{{"rules", createRules(Diags, RuleMapping)}}; +} + +static json::Object createRun(std::vector<const PathDiagnostic *> &Diags) { + json::Array Results, Files; + StringMap<unsigned> RuleMapping; + json::Object Resources = createResources(Diags, RuleMapping); + + llvm::for_each(Diags, [&](const PathDiagnostic *D) { + Results.push_back(createResult(*D, Files, RuleMapping)); + }); + + return json::Object{{"tool", createTool()}, + {"resources", std::move(Resources)}, + {"results", std::move(Results)}, + {"files", std::move(Files)}}; +} + +void SarifDiagnostics::FlushDiagnosticsImpl( + std::vector<const PathDiagnostic *> &Diags, FilesMade *) { + // We currently overwrite the file if it already exists. However, it may be + // useful to add a feature someday that allows the user to append a run to an + // existing SARIF file. One danger from that approach is that the size of the + // file can become large very quickly, so decoding into JSON to append a run + // may be an expensive operation. + std::error_code EC; + llvm::raw_fd_ostream OS(OutputFile, EC, llvm::sys::fs::F_Text); + if (EC) { + llvm::errs() << "warning: could not create file: " << EC.message() << '\n'; + return; + } + json::Object Sarif{ + {"$schema", + "http://json.schemastore.org/sarif-2.0.0-csd.2.beta.2018-11-28"}, + {"version", "2.0.0-csd.2.beta.2018-11-28"}, + {"runs", json::Array{createRun(Diags)}}}; + OS << llvm::formatv("{0:2}", json::Value(std::move(Sarif))); +} diff --git a/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp b/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp index 62c54fc956a96..fc57cecac9cbf 100644 --- a/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp +++ b/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp @@ -454,12 +454,12 @@ static Optional<NonLoc> tryRearrange(ProgramStateRef State, QualType SingleTy; auto &Opts = - StateMgr.getOwningEngine()->getAnalysisManager().getAnalyzerOptions(); + StateMgr.getOwningEngine().getAnalysisManager().getAnalyzerOptions(); // FIXME: After putting complexity threshold to the symbols we can always // rearrange additive operations but rearrange comparisons only if // option is set. - if(!Opts.shouldAggressivelySimplifyBinaryOperation()) + if(!Opts.ShouldAggressivelySimplifyBinaryOperation) return None; SymbolRef LSym = Lhs.getAsSymbol(); @@ -475,9 +475,6 @@ static Optional<NonLoc> tryRearrange(ProgramStateRef State, SingleTy = ResultTy; if (LSym->getType() != SingleTy) return None; - // Substracting unsigned integers is a nightmare. - if (!SingleTy->isSignedIntegerOrEnumerationType()) - return None; } else { // Don't rearrange other operations. return None; @@ -485,6 +482,10 @@ static Optional<NonLoc> tryRearrange(ProgramStateRef State, assert(!SingleTy.isNull() && "We should have figured out the type by now!"); + // Rearrange signed symbolic expressions only + if (!SingleTy->isSignedIntegerOrEnumerationType()) + return None; + SymbolRef RSym = Rhs.getAsSymbol(); if (!RSym || RSym->getType() != SingleTy) return None; @@ -534,7 +535,7 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state, while (1) { switch (lhs.getSubKind()) { default: - return makeSymExprValNN(state, op, lhs, rhs, resultTy); + return makeSymExprValNN(op, lhs, rhs, resultTy); case nonloc::PointerToMemberKind: { assert(rhs.getSubKind() == nonloc::PointerToMemberKind && "Both SVals should have pointer-to-member-type"); @@ -582,7 +583,7 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state, return makeTruthVal(true, resultTy); default: // This case also handles pointer arithmetic. - return makeSymExprValNN(state, op, InputLHS, InputRHS, resultTy); + return makeSymExprValNN(op, InputLHS, InputRHS, resultTy); } } } @@ -624,7 +625,7 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state, case BO_LE: case BO_GE: op = BinaryOperator::reverseComparisonOp(op); - // FALL-THROUGH + LLVM_FALLTHROUGH; case BO_EQ: case BO_NE: case BO_Add: @@ -638,14 +639,14 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state, // (~0)>>a if (LHSValue.isAllOnesValue() && LHSValue.isSigned()) return evalCastFromNonLoc(lhs, resultTy); - // FALL-THROUGH + LLVM_FALLTHROUGH; case BO_Shl: // 0<<a and 0>>a if (LHSValue == 0) return evalCastFromNonLoc(lhs, resultTy); - return makeSymExprValNN(state, op, InputLHS, InputRHS, resultTy); + return makeSymExprValNN(op, InputLHS, InputRHS, resultTy); default: - return makeSymExprValNN(state, op, InputLHS, InputRHS, resultTy); + return makeSymExprValNN(op, InputLHS, InputRHS, resultTy); } } case nonloc::SymbolValKind: { @@ -757,7 +758,7 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state, return *V; // Give up -- this is not a symbolic expression we can handle. - return makeSymExprValNN(state, op, InputLHS, InputRHS, resultTy); + return makeSymExprValNN(op, InputLHS, InputRHS, resultTy); } } } @@ -1201,6 +1202,7 @@ SVal SimpleSValBuilder::evalBinOpLN(ProgramStateRef state, const llvm::APSInt *SimpleSValBuilder::getKnownValue(ProgramStateRef state, SVal V) { + V = simplifySVal(state, V); if (V.isUnknownOrUndef()) return nullptr; diff --git a/lib/StaticAnalyzer/Core/Store.cpp b/lib/StaticAnalyzer/Core/Store.cpp index 94188a9ef698f..4fa937d9658dd 100644 --- a/lib/StaticAnalyzer/Core/Store.cpp +++ b/lib/StaticAnalyzer/Core/Store.cpp @@ -88,7 +88,7 @@ const MemRegion *StoreManager::castRegion(const MemRegion *R, QualType CastToTy) return R; // We don't know what to make of it. Return a NULL region, which - // will be interpretted as UnknownVal. + // will be interpreted as UnknownVal. return nullptr; } @@ -138,6 +138,7 @@ const MemRegion *StoreManager::castRegion(const MemRegion *R, QualType CastToTy) case MemRegion::VarRegionKind: case MemRegion::CXXTempObjectRegionKind: case MemRegion::CXXBaseObjectRegionKind: + case MemRegion::CXXDerivedObjectRegionKind: return MakeElementRegion(cast<SubRegion>(R), PointeeTy); case MemRegion::ElementRegionKind: { @@ -272,9 +273,8 @@ SVal StoreManager::evalDerivedToBase(SVal Derived, const CXXBasePath &Path) { SVal StoreManager::evalDerivedToBase(SVal Derived, QualType BaseType, bool IsVirtual) { - Optional<loc::MemRegionVal> DerivedRegVal = - Derived.getAs<loc::MemRegionVal>(); - if (!DerivedRegVal) + const MemRegion *DerivedReg = Derived.getAsRegion(); + if (!DerivedReg) return Derived; const CXXRecordDecl *BaseDecl = BaseType->getPointeeCXXRecordDecl(); @@ -282,8 +282,18 @@ SVal StoreManager::evalDerivedToBase(SVal Derived, QualType BaseType, BaseDecl = BaseType->getAsCXXRecordDecl(); assert(BaseDecl && "not a C++ object?"); + if (const auto *AlreadyDerivedReg = + dyn_cast<CXXDerivedObjectRegion>(DerivedReg)) { + if (const auto *SR = + dyn_cast<SymbolicRegion>(AlreadyDerivedReg->getSuperRegion())) + if (SR->getSymbol()->getType()->getPointeeCXXRecordDecl() == BaseDecl) + return loc::MemRegionVal(SR); + + DerivedReg = AlreadyDerivedReg->getSuperRegion(); + } + const MemRegion *BaseReg = MRMgr.getCXXBaseObjectRegion( - BaseDecl, cast<SubRegion>(DerivedRegVal->getRegion()), IsVirtual); + BaseDecl, cast<SubRegion>(DerivedReg), IsVirtual); return loc::MemRegionVal(BaseReg); } @@ -365,6 +375,20 @@ SVal StoreManager::attemptDownCast(SVal Base, QualType TargetType, MR = Uncasted; } + // If we're casting a symbolic base pointer to a derived class, use + // CXXDerivedObjectRegion to represent the cast. If it's a pointer to an + // unrelated type, it must be a weird reinterpret_cast and we have to + // be fine with ElementRegion. TODO: Should we instead make + // Derived{TargetClass, Element{SourceClass, SR}}? + if (const auto *SR = dyn_cast<SymbolicRegion>(MR)) { + QualType T = SR->getSymbol()->getType(); + const CXXRecordDecl *SourceClass = T->getPointeeCXXRecordDecl(); + if (TargetClass && SourceClass && TargetClass->isDerivedFrom(SourceClass)) + return loc::MemRegionVal( + MRMgr.getCXXDerivedObjectRegion(TargetClass, SR)); + return loc::MemRegionVal(GetElementZeroRegion(SR, TargetType)); + } + // We failed if the region we ended up with has perfect type info. Failed = isa<TypedValueRegion>(MR); return UnknownVal(); @@ -378,6 +402,17 @@ SVal StoreManager::CastRetrievedVal(SVal V, const TypedValueRegion *R, if (castTy.isNull() || V.isUnknownOrUndef()) return V; + // The dispatchCast() call below would convert the int into a float. + // What we want, however, is a bit-by-bit reinterpretation of the int + // as a float, which usually yields nothing garbage. For now skip casts + // from ints to floats. + // TODO: What other combinations of types are affected? + if (castTy->isFloatingType()) { + SymbolRef Sym = V.getAsSymbol(); + if (Sym && !Sym->getType()->isFloatingType()) + return UnknownVal(); + } + // When retrieving symbolic pointer and expecting a non-void pointer, // wrap them into element regions of the expected type if necessary. // SValBuilder::dispatchCast() doesn't do that, but it is necessary to diff --git a/lib/StaticAnalyzer/Core/SymbolManager.cpp b/lib/StaticAnalyzer/Core/SymbolManager.cpp index ed197010ebb7e..66273f099a38e 100644 --- a/lib/StaticAnalyzer/Core/SymbolManager.cpp +++ b/lib/StaticAnalyzer/Core/SymbolManager.cpp @@ -83,7 +83,13 @@ void SymbolCast::dumpToStream(raw_ostream &os) const { } void SymbolConjured::dumpToStream(raw_ostream &os) const { - os << "conj_$" << getSymbolID() << '{' << T.getAsString() << '}'; + os << "conj_$" << getSymbolID() << '{' << T.getAsString() << ", LC" + << LCtx->getID(); + if (S) + os << ", S" << S->getID(LCtx->getDecl()->getASTContext()); + else + os << ", no stmt"; + os << ", #" << Count << '}'; } void SymbolDerived::dumpToStream(raw_ostream &os) const { @@ -395,7 +401,6 @@ void SymbolReaper::markDependentsLive(SymbolRef sym) { void SymbolReaper::markLive(SymbolRef sym) { TheLiving[sym] = NotProcessed; - TheDead.erase(sym); markDependentsLive(sym); } @@ -420,14 +425,6 @@ void SymbolReaper::markInUse(SymbolRef sym) { MetadataInUse.insert(sym); } -bool SymbolReaper::maybeDead(SymbolRef sym) { - if (isLive(sym)) - return false; - - TheDead.insert(sym); - return true; -} - bool SymbolReaper::isLiveRegion(const MemRegion *MR) { if (RegionRoots.count(MR)) return true; diff --git a/lib/StaticAnalyzer/Core/TaintManager.cpp b/lib/StaticAnalyzer/Core/TaintManager.cpp new file mode 100644 index 0000000000000..c34b0ca1839de --- /dev/null +++ b/lib/StaticAnalyzer/Core/TaintManager.cpp @@ -0,0 +1,23 @@ +//== TaintManager.cpp ------------------------------------------ -*- C++ -*--=// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Core/PathSensitive/TaintManager.h" + +using namespace clang; +using namespace ento; + +void *ProgramStateTrait<TaintMap>::GDMIndex() { + static int index = 0; + return &index; +} + +void *ProgramStateTrait<DerivedSymTaint>::GDMIndex() { + static int index; + return &index; +} diff --git a/lib/StaticAnalyzer/Core/WorkList.cpp b/lib/StaticAnalyzer/Core/WorkList.cpp index 4b227375da9b2..e705393cb83ad 100644 --- a/lib/StaticAnalyzer/Core/WorkList.cpp +++ b/lib/StaticAnalyzer/Core/WorkList.cpp @@ -152,7 +152,7 @@ public: auto BE = N->getLocation().getAs<BlockEntrance>(); if (!BE) { - // Assume the choice of the order of the preceeding block entrance was + // Assume the choice of the order of the preceding block entrance was // correct. StackUnexplored.push_back(U); } else { @@ -252,3 +252,63 @@ public: std::unique_ptr<WorkList> WorkList::makeUnexploredFirstPriorityQueue() { return llvm::make_unique<UnexploredFirstPriorityQueue>(); } + +namespace { +class UnexploredFirstPriorityLocationQueue : public WorkList { + using LocIdentifier = const CFGBlock *; + + // How many times each location was visited. + // Is signed because we negate it later in order to have a reversed + // comparison. + using VisitedTimesMap = llvm::DenseMap<LocIdentifier, int>; + + // Compare by number of times the location was visited first (negated + // to prefer less often visited locations), then by insertion time (prefer + // expanding nodes inserted sooner first). + using QueuePriority = std::pair<int, unsigned long>; + using QueueItem = std::pair<WorkListUnit, QueuePriority>; + + struct ExplorationComparator { + bool operator() (const QueueItem &LHS, const QueueItem &RHS) { + return LHS.second < RHS.second; + } + }; + + // Number of inserted nodes, used to emulate DFS ordering in the priority + // queue when insertions are equal. + unsigned long Counter = 0; + + // Number of times a current location was reached. + VisitedTimesMap NumReached; + + // The top item is the largest one. + llvm::PriorityQueue<QueueItem, std::vector<QueueItem>, ExplorationComparator> + queue; + +public: + bool hasWork() const override { + return !queue.empty(); + } + + void enqueue(const WorkListUnit &U) override { + const ExplodedNode *N = U.getNode(); + unsigned NumVisited = 0; + if (auto BE = N->getLocation().getAs<BlockEntrance>()) + NumVisited = NumReached[BE->getBlock()]++; + + queue.push(std::make_pair(U, std::make_pair(-NumVisited, ++Counter))); + } + + WorkListUnit dequeue() override { + QueueItem U = queue.top(); + queue.pop(); + return U.first; + } + +}; + +} + +std::unique_ptr<WorkList> WorkList::makeUnexploredFirstPriorityLocationQueue() { + return llvm::make_unique<UnexploredFirstPriorityLocationQueue>(); +} diff --git a/lib/StaticAnalyzer/Core/Z3ConstraintManager.cpp b/lib/StaticAnalyzer/Core/Z3ConstraintManager.cpp index 7379ded49c803..c4729f969f333 100644 --- a/lib/StaticAnalyzer/Core/Z3ConstraintManager.cpp +++ b/lib/StaticAnalyzer/Core/Z3ConstraintManager.cpp @@ -11,10 +11,7 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SMTConstraintManager.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/SMTContext.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/SMTExpr.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/SMTSolver.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/SMTSort.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SMTConv.h" #include "clang/Config/config.h" @@ -49,15 +46,15 @@ public: // Function used to report errors void Z3ErrorHandler(Z3_context Context, Z3_error_code Error) { llvm::report_fatal_error("Z3 error: " + - llvm::Twine(Z3_get_error_msg_ex(Context, Error))); + llvm::Twine(Z3_get_error_msg(Context, Error))); } /// Wrapper for Z3 context -class Z3Context : public SMTContext { +class Z3Context { public: Z3_context Context; - Z3Context() : SMTContext() { + Z3Context() { Context = Z3_mk_context_rc(Z3Config().Config); // The error function is set here because the context is the first object // created by the backend @@ -80,32 +77,27 @@ class Z3Sort : public SMTSort { public: /// Default constructor, mainly used by make_shared - Z3Sort(Z3Context &C, Z3_sort ZS) : SMTSort(), Context(C), Sort(ZS) { + Z3Sort(Z3Context &C, Z3_sort ZS) : Context(C), Sort(ZS) { Z3_inc_ref(Context.Context, reinterpret_cast<Z3_ast>(Sort)); } /// Override implicit copy constructor for correct reference counting. - Z3Sort(const Z3Sort &Copy) - : SMTSort(), Context(Copy.Context), Sort(Copy.Sort) { + Z3Sort(const Z3Sort &Other) : Context(Other.Context), Sort(Other.Sort) { Z3_inc_ref(Context.Context, reinterpret_cast<Z3_ast>(Sort)); } - /// Provide move constructor - Z3Sort(Z3Sort &&Move) : SMTSort(), Context(Move.Context), Sort(nullptr) { - *this = std::move(Move); - } - - /// Provide move assignment constructor - Z3Sort &operator=(Z3Sort &&Move) { - if (this != &Move) { - if (Sort) - Z3_dec_ref(Context.Context, reinterpret_cast<Z3_ast>(Sort)); - Sort = Move.Sort; - Move.Sort = nullptr; - } + /// Override implicit copy assignment constructor for correct reference + /// counting. + Z3Sort &operator=(const Z3Sort &Other) { + Z3_inc_ref(Context.Context, reinterpret_cast<Z3_ast>(Other.Sort)); + Z3_dec_ref(Context.Context, reinterpret_cast<Z3_ast>(Sort)); + Sort = Other.Sort; return *this; } + Z3Sort(Z3Sort &&Other) = delete; + Z3Sort &operator=(Z3Sort &&Other) = delete; + ~Z3Sort() { if (Sort) Z3_dec_ref(Context.Context, reinterpret_cast<Z3_ast>(Sort)); @@ -137,13 +129,6 @@ public: static_cast<const Z3Sort &>(Other).Sort); } - Z3Sort &operator=(const Z3Sort &Move) { - Z3_inc_ref(Context.Context, reinterpret_cast<Z3_ast>(Move.Sort)); - Z3_dec_ref(Context.Context, reinterpret_cast<Z3_ast>(Sort)); - Sort = Move.Sort; - return *this; - } - void print(raw_ostream &OS) const override { OS << Z3_sort_to_string(Context.Context, Sort); } @@ -170,22 +155,18 @@ public: Z3_inc_ref(Context.Context, AST); } - /// Provide move constructor - Z3Expr(Z3Expr &&Move) : SMTExpr(), Context(Move.Context), AST(nullptr) { - *this = std::move(Move); - } - - /// Provide move assignment constructor - Z3Expr &operator=(Z3Expr &&Move) { - if (this != &Move) { - if (AST) - Z3_dec_ref(Context.Context, AST); - AST = Move.AST; - Move.AST = nullptr; - } + /// Override implicit copy assignment constructor for correct reference + /// counting. + Z3Expr &operator=(const Z3Expr &Other) { + Z3_inc_ref(Context.Context, Other.AST); + Z3_dec_ref(Context.Context, AST); + AST = Other.AST; return *this; } + Z3Expr(Z3Expr &&Other) = delete; + Z3Expr &operator=(Z3Expr &&Other) = delete; + ~Z3Expr() { if (AST) Z3_dec_ref(Context.Context, AST); @@ -205,14 +186,6 @@ public: static_cast<const Z3Expr &>(Other).AST); } - /// Override implicit move constructor for correct reference counting. - Z3Expr &operator=(const Z3Expr &Move) { - Z3_inc_ref(Context.Context, Move.AST); - Z3_dec_ref(Context.Context, AST); - AST = Move.AST; - return *this; - } - void print(raw_ostream &OS) const override { OS << Z3_ast_to_string(Context.Context, AST); } @@ -231,30 +204,13 @@ class Z3Model { public: Z3Model(Z3Context &C, Z3_model ZM) : Context(C), Model(ZM) { - assert(C.Context != nullptr); Z3_model_inc_ref(Context.Context, Model); } - /// Override implicit copy constructor for correct reference counting. - Z3Model(const Z3Model &Copy) : Context(Copy.Context), Model(Copy.Model) { - Z3_model_inc_ref(Context.Context, Model); - } - - /// Provide move constructor - Z3Model(Z3Model &&Move) : Context(Move.Context), Model(nullptr) { - *this = std::move(Move); - } - - /// Provide move assignment constructor - Z3Model &operator=(Z3Model &&Move) { - if (this != &Move) { - if (Model) - Z3_model_dec_ref(Context.Context, Model); - Model = Move.Model; - Move.Model = nullptr; - } - return *this; - } + Z3Model(const Z3Model &Other) = delete; + Z3Model(Z3Model &&Other) = delete; + Z3Model &operator=(Z3Model &Other) = delete; + Z3Model &operator=(Z3Model &&Other) = delete; ~Z3Model() { if (Model) @@ -313,32 +269,14 @@ class Z3Solver : public SMTSolver { Z3_solver Solver; public: - Z3Solver() : SMTSolver(), Solver(Z3_mk_simple_solver(Context.Context)) { - Z3_solver_inc_ref(Context.Context, Solver); - } - - /// Override implicit copy constructor for correct reference counting. - Z3Solver(const Z3Solver &Copy) - : SMTSolver(), Context(Copy.Context), Solver(Copy.Solver) { + Z3Solver() : Solver(Z3_mk_simple_solver(Context.Context)) { Z3_solver_inc_ref(Context.Context, Solver); } - /// Provide move constructor - Z3Solver(Z3Solver &&Move) - : SMTSolver(), Context(Move.Context), Solver(nullptr) { - *this = std::move(Move); - } - - /// Provide move assignment constructor - Z3Solver &operator=(Z3Solver &&Move) { - if (this != &Move) { - if (Solver) - Z3_solver_dec_ref(Context.Context, Solver); - Solver = Move.Solver; - Move.Solver = nullptr; - } - return *this; - } + Z3Solver(const Z3Solver &Other) = delete; + Z3Solver(Z3Solver &&Other) = delete; + Z3Solver &operator=(Z3Solver &Other) = delete; + Z3Solver &operator=(Z3Solver &&Other) = delete; ~Z3Solver() { if (Solver) @@ -674,7 +612,7 @@ public: toZ3Expr(*From).AST, toZ3Sort(*To).Sort))); } - SMTExprRef mkFPtoSBV(const SMTExprRef &From, const SMTSortRef &To) override { + SMTExprRef mkSBVtoFP(const SMTExprRef &From, const SMTSortRef &To) override { SMTExprRef RoundingMode = getFloatRoundingMode(); return newExprRef(Z3Expr( Context, @@ -682,7 +620,7 @@ public: toZ3Expr(*From).AST, toZ3Sort(*To).Sort))); } - SMTExprRef mkFPtoUBV(const SMTExprRef &From, const SMTSortRef &To) override { + SMTExprRef mkUBVtoFP(const SMTExprRef &From, const SMTSortRef &To) override { SMTExprRef RoundingMode = getFloatRoundingMode(); return newExprRef(Z3Expr( Context, @@ -690,14 +628,14 @@ public: toZ3Expr(*From).AST, toZ3Sort(*To).Sort))); } - SMTExprRef mkSBVtoFP(const SMTExprRef &From, unsigned ToWidth) override { + SMTExprRef mkFPtoSBV(const SMTExprRef &From, unsigned ToWidth) override { SMTExprRef RoundingMode = getFloatRoundingMode(); return newExprRef(Z3Expr( Context, Z3_mk_fpa_to_sbv(Context.Context, toZ3Expr(*RoundingMode).AST, toZ3Expr(*From).AST, ToWidth))); } - SMTExprRef mkUBVtoFP(const SMTExprRef &From, unsigned ToWidth) override { + SMTExprRef mkFPtoUBV(const SMTExprRef &From, unsigned ToWidth) override { SMTExprRef RoundingMode = getFloatRoundingMode(); return newExprRef(Z3Expr( Context, Z3_mk_fpa_to_ubv(Context.Context, toZ3Expr(*RoundingMode).AST, @@ -736,9 +674,11 @@ public: llvm::APSInt getBitvector(const SMTExprRef &Exp, unsigned BitWidth, bool isUnsigned) override { - return llvm::APSInt(llvm::APInt( - BitWidth, Z3_get_numeral_string(Context.Context, toZ3Expr(*Exp).AST), - 10)); + return llvm::APSInt( + llvm::APInt(BitWidth, + Z3_get_numeral_string(Context.Context, toZ3Expr(*Exp).AST), + 10), + isUnsigned); } bool getBoolean(const SMTExprRef &Exp) override { @@ -750,42 +690,6 @@ public: return newExprRef(Z3Expr(Context, Z3_mk_fpa_rne(Context.Context))); } - SMTExprRef fromData(const SymbolID ID, const QualType &Ty, - uint64_t BitWidth) override { - llvm::Twine Name = "$" + llvm::Twine(ID); - return mkSymbol(Name.str().c_str(), mkSort(Ty, BitWidth)); - } - - SMTExprRef fromBoolean(const bool Bool) override { - Z3_ast AST = - Bool ? Z3_mk_true(Context.Context) : Z3_mk_false(Context.Context); - return newExprRef(Z3Expr(Context, AST)); - } - - SMTExprRef fromAPFloat(const llvm::APFloat &Float) override { - SMTSortRef Sort = - getFloatSort(llvm::APFloat::semanticsSizeInBits(Float.getSemantics())); - - llvm::APSInt Int = llvm::APSInt(Float.bitcastToAPInt(), false); - SMTExprRef Z3Int = fromAPSInt(Int); - return newExprRef(Z3Expr( - Context, Z3_mk_fpa_to_fp_bv(Context.Context, toZ3Expr(*Z3Int).AST, - toZ3Sort(*Sort).Sort))); - } - - SMTExprRef fromAPSInt(const llvm::APSInt &Int) override { - SMTSortRef Sort = getBitvectorSort(Int.getBitWidth()); - Z3_ast AST = Z3_mk_numeral(Context.Context, Int.toString(10).c_str(), - toZ3Sort(*Sort).Sort); - return newExprRef(Z3Expr(Context, AST)); - } - - SMTExprRef fromInt(const char *Int, uint64_t BitWidth) override { - SMTSortRef Sort = getBitvectorSort(BitWidth); - Z3_ast AST = Z3_mk_numeral(Context.Context, Int, toZ3Sort(*Sort).Sort); - return newExprRef(Z3Expr(Context, AST)); - } - bool toAPFloat(const SMTSortRef &Sort, const SMTExprRef &AST, llvm::APFloat &Float, bool useSemantics) { assert(Sort->isFloatSort() && "Unsupported sort to floating-point!"); @@ -846,7 +750,7 @@ public: } bool getInterpretation(const SMTExprRef &Exp, llvm::APSInt &Int) override { - Z3Model Model = getModel(); + Z3Model Model(Context, Z3_solver_get_model(Context.Context, Solver)); Z3_func_decl Func = Z3_get_app_decl( Context.Context, Z3_to_app(Context.Context, toZ3Expr(*Exp).AST)); if (Z3_model_has_interp(Context.Context, Model.Model, Func) != Z3_L_TRUE) @@ -860,7 +764,7 @@ public: } bool getInterpretation(const SMTExprRef &Exp, llvm::APFloat &Float) override { - Z3Model Model = getModel(); + Z3Model Model(Context, Z3_solver_get_model(Context.Context, Solver)); Z3_func_decl Func = Z3_get_app_decl( Context.Context, Z3_to_app(Context.Context, toZ3Expr(*Exp).AST)); if (Z3_model_has_interp(Context.Context, Model.Model, Func) != Z3_L_TRUE) @@ -873,7 +777,7 @@ public: return toAPFloat(Sort, Assign, Float, true); } - ConditionTruthVal check() const override { + Optional<bool> check() const override { Z3_lbool res = Z3_solver_check(Context.Context, Solver); if (res == Z3_L_TRUE) return true; @@ -881,7 +785,7 @@ public: if (res == Z3_L_FALSE) return false; - return ConditionTruthVal(); + return Optional<bool>(); } void push() override { return Z3_solver_push(Context.Context, Solver); } @@ -891,138 +795,34 @@ public: return Z3_solver_pop(Context.Context, Solver, NumStates); } - /// Get a model from the solver. Caller should check the model is - /// satisfiable. - Z3Model getModel() { - return Z3Model(Context, Z3_solver_get_model(Context.Context, Solver)); - } + bool isFPSupported() override { return true; } /// Reset the solver and remove all constraints. - void reset() const override { Z3_solver_reset(Context.Context, Solver); } + void reset() override { Z3_solver_reset(Context.Context, Solver); } void print(raw_ostream &OS) const override { OS << Z3_solver_to_string(Context.Context, Solver); } }; // end class Z3Solver -class Z3ConstraintManager : public SMTConstraintManager { +class Z3ConstraintManager : public SMTConstraintManager<ConstraintZ3, Z3Expr> { SMTSolverRef Solver = CreateZ3Solver(); public: Z3ConstraintManager(SubEngine *SE, SValBuilder &SB) : SMTConstraintManager(SE, SB, Solver) {} - - void addStateConstraints(ProgramStateRef State) const override { - // TODO: Don't add all the constraints, only the relevant ones - ConstraintZ3Ty CZ = State->get<ConstraintZ3>(); - ConstraintZ3Ty::iterator I = CZ.begin(), IE = CZ.end(); - - // Construct the logical AND of all the constraints - if (I != IE) { - std::vector<SMTExprRef> ASTs; - - SMTExprRef Constraint = Solver->newExprRef(I++->second); - while (I != IE) { - Constraint = Solver->mkAnd(Constraint, Solver->newExprRef(I++->second)); - } - - Solver->addConstraint(Constraint); - } - } - - bool canReasonAbout(SVal X) const override { - const TargetInfo &TI = getBasicVals().getContext().getTargetInfo(); - - Optional<nonloc::SymbolVal> SymVal = X.getAs<nonloc::SymbolVal>(); - if (!SymVal) - return true; - - const SymExpr *Sym = SymVal->getSymbol(); - QualType Ty = Sym->getType(); - - // Complex types are not modeled - if (Ty->isComplexType() || Ty->isComplexIntegerType()) - return false; - - // Non-IEEE 754 floating-point types are not modeled - if ((Ty->isSpecificBuiltinType(BuiltinType::LongDouble) && - (&TI.getLongDoubleFormat() == &llvm::APFloat::x87DoubleExtended() || - &TI.getLongDoubleFormat() == &llvm::APFloat::PPCDoubleDouble()))) - return false; - - if (isa<SymbolData>(Sym)) - return true; - - SValBuilder &SVB = getSValBuilder(); - - if (const SymbolCast *SC = dyn_cast<SymbolCast>(Sym)) - return canReasonAbout(SVB.makeSymbolVal(SC->getOperand())); - - if (const BinarySymExpr *BSE = dyn_cast<BinarySymExpr>(Sym)) { - if (const SymIntExpr *SIE = dyn_cast<SymIntExpr>(BSE)) - return canReasonAbout(SVB.makeSymbolVal(SIE->getLHS())); - - if (const IntSymExpr *ISE = dyn_cast<IntSymExpr>(BSE)) - return canReasonAbout(SVB.makeSymbolVal(ISE->getRHS())); - - if (const SymSymExpr *SSE = dyn_cast<SymSymExpr>(BSE)) - return canReasonAbout(SVB.makeSymbolVal(SSE->getLHS())) && - canReasonAbout(SVB.makeSymbolVal(SSE->getRHS())); - } - - llvm_unreachable("Unsupported expression to reason about!"); - } - - ProgramStateRef removeDeadBindings(ProgramStateRef State, - SymbolReaper &SymReaper) override { - ConstraintZ3Ty CZ = State->get<ConstraintZ3>(); - ConstraintZ3Ty::Factory &CZFactory = State->get_context<ConstraintZ3>(); - - for (ConstraintZ3Ty::iterator I = CZ.begin(), E = CZ.end(); I != E; ++I) { - if (SymReaper.maybeDead(I->first)) - CZ = CZFactory.remove(CZ, *I); - } - - return State->set<ConstraintZ3>(CZ); - } - - ProgramStateRef assumeExpr(ProgramStateRef State, SymbolRef Sym, - const SMTExprRef &Exp) override { - // Check the model, avoid simplifying AST to save time - if (checkModel(State, Exp).isConstrainedTrue()) - return State->add<ConstraintZ3>(std::make_pair(Sym, toZ3Expr(*Exp))); - - return nullptr; - } - - //==------------------------------------------------------------------------==/ - // Pretty-printing. - //==------------------------------------------------------------------------==/ - - void print(ProgramStateRef St, raw_ostream &OS, const char *nl, - const char *sep) override { - - ConstraintZ3Ty CZ = St->get<ConstraintZ3>(); - - OS << nl << sep << "Constraints:"; - for (ConstraintZ3Ty::iterator I = CZ.begin(), E = CZ.end(); I != E; ++I) { - OS << nl << ' ' << I->first << " : "; - I->second.print(OS); - } - OS << nl; - } }; // end class Z3ConstraintManager } // end anonymous namespace #endif -std::unique_ptr<SMTSolver> clang::ento::CreateZ3Solver() { +SMTSolverRef clang::ento::CreateZ3Solver() { #if CLANG_ANALYZER_WITH_Z3 return llvm::make_unique<Z3Solver>(); #else llvm::report_fatal_error("Clang was not compiled with Z3 support, rebuild " - "with -DCLANG_ANALYZER_BUILD_Z3=ON", + "with -DCLANG_ANALYZER_ENABLE_Z3_SOLVER=ON", false); return nullptr; #endif @@ -1034,7 +834,7 @@ ento::CreateZ3ConstraintManager(ProgramStateManager &StMgr, SubEngine *Eng) { return llvm::make_unique<Z3ConstraintManager>(Eng, StMgr.getSValBuilder()); #else llvm::report_fatal_error("Clang was not compiled with Z3 support, rebuild " - "with -DCLANG_ANALYZER_BUILD_Z3=ON", + "with -DCLANG_ANALYZER_ENABLE_Z3_SOLVER=ON", false); return nullptr; #endif |