diff --git a/clang/include/clang/AST/ASTImporterSharedState.h b/clang/include/clang/AST/ASTImporterSharedState.h --- a/clang/include/clang/AST/ASTImporterSharedState.h +++ b/clang/include/clang/AST/ASTImporterSharedState.h @@ -39,6 +39,9 @@ /// never cleared (like ImportedFromDecls). llvm::DenseMap ImportErrors; + /// Set of the newly created declarations. + llvm::DenseSet NewDecls; + // FIXME put ImportedFromDecls here! // And from that point we can better encapsulate the lookup table. @@ -74,6 +77,10 @@ void setImportDeclError(Decl *To, ImportError Error) { ImportErrors[To] = Error; } + + bool isNewDecl(const Decl *ToD) const { return NewDecls.count(ToD); } + + void setNewDecl(Decl *ToD) { NewDecls.insert(ToD); } }; } // namespace clang diff --git a/clang/include/clang/CrossTU/CrossTranslationUnit.h b/clang/include/clang/CrossTU/CrossTranslationUnit.h --- a/clang/include/clang/CrossTU/CrossTranslationUnit.h +++ b/clang/include/clang/CrossTU/CrossTranslationUnit.h @@ -197,6 +197,14 @@ getMacroExpansionContextForSourceLocation( const clang::SourceLocation &ToLoc) const; + /// Returns true if the given Decl is newly created during the import. + bool isImportedAsNew(const Decl *ToDecl) const; + + /// Returns true if the given Decl is mapped (or created) during an import + /// but there was an unrecoverable error (the AST node cannot be erased, it + /// is marked with an Error object in this case). + bool hasError(const Decl *ToDecl) const; + private: void lazyInitImporterSharedSt(TranslationUnitDecl *ToTU); ASTImporter &getOrCreateASTImporter(ASTUnit *Unit); diff --git a/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.h b/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.h --- a/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.h +++ b/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.h @@ -138,6 +138,8 @@ UMK_Deep = 2 }; +enum class CTUPhase1InliningKind { None, Small, All }; + /// Stores options for the analyzer from the command line. /// /// Some options are frontend flags (e.g.: -analyzer-output), but some are @@ -379,6 +381,7 @@ UserModeKind getUserMode() const; ExplorationStrategyKind getExplorationStrategy() const; + CTUPhase1InliningKind getCTUPhase1Inlining() const; /// Returns the inter-procedural analysis mode. IPAKind getIPAMode() const; diff --git a/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.def b/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.def --- a/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.def +++ b/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.def @@ -408,6 +408,39 @@ "top level function (for each exploded graph). 0 means no limit.", /* SHALLOW_VAL */ 75000, /* DEEP_VAL */ 225000) +ANALYZER_OPTION( + unsigned, CTUMaxNodesMultiplier, "ctu-max-nodes-mul", + "We count the nodes for a normal single tu analysis. We multiply that " + "number with this config value then we divide the result by 100 to get " + "the maximum number of nodes that the analyzer can generate while " + "exploring a top level function in CTU mode (for each exploded graph)." + "For example, 100 means that CTU will explore maximum as much nodes that " + "we explored during the single tu mode, 200 means it will explore twice as " + "much, 50 means it will explore maximum 50% more.", 100) + +ANALYZER_OPTION( + unsigned, CTUMaxNodesMin, "ctu-max-nodes-min", + "The maximum number of nodes in CTU mode is determinded by " + "'ctu-max-nodes-mul'. However, if the number of nodes in single tu analysis " + "is too low, it is meaningful to provide a minimum value that serves as an " + "upper bound instead.", 1000) + +ANALYZER_OPTION( + StringRef, CTUPhase1InliningMode, "ctu-phase1-inlining", + "Controls which functions will be inlined during the first phase of the ctu " + "analysis. " + "If the value is set to 'all' then all foreign functions are inlinied " + "immediately during the first phase, thus rendering the second phase a noop. " + "The 'ctu-max-nodes-*' budge has no effect in this case. " + "If the value is 'small' then only functions with a linear CFG and with a " + "limited number of statements would be inlined during the first phase. The " + "long and/or nontrivial functions are handled in the second phase and are " + "controlled by the 'ctu-max-nodes-*' budge. " + "The value 'none' means that all foreign functions are inlined only in the " + "second phase, 'ctu-max-nodes-*' budge limits the second phase. " + "Value: \"none\", \"small\", \"all\".", + "small") + ANALYZER_OPTION( unsigned, RegionStoreSmallStructLimit, "region-store-small-struct-limit", "The largest number of fields a struct can have and still be considered " diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h @@ -113,12 +113,18 @@ /// precise. const MemRegion *R = nullptr; + /// A definition is foreign if it has been imported and newly created by the + /// ASTImporter. This can be true only if CTU is enabled. + const bool Foreign = false; + public: RuntimeDefinition() = default; RuntimeDefinition(const Decl *InD): D(InD) {} + RuntimeDefinition(const Decl *InD, bool Foreign) : D(InD), Foreign(Foreign) {} RuntimeDefinition(const Decl *InD, const MemRegion *InR): D(InD), R(InR) {} const Decl *getDecl() { return D; } + bool isForeign() const { return Foreign; } /// Check if the definition we have is precise. /// If not, it is possible that the call dispatches to another definition at @@ -147,6 +153,7 @@ ProgramStateRef State; const LocationContext *LCtx; llvm::PointerUnion Origin; + mutable Optional Foreign; // From CTU. protected: // This is user data for subclasses. @@ -208,6 +215,12 @@ return Origin.dyn_cast(); } + bool isForeign() const { + assert(Foreign.hasValue() && "Foreign must be set before querying"); + return *Foreign; + } + void setForeign(bool B) const { Foreign = B; } + /// The state in which the call is being evaluated. const ProgramStateRef &getState() const { return State; diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CoreEngine.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CoreEngine.h --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CoreEngine.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CoreEngine.h @@ -78,6 +78,7 @@ /// worklist algorithm. It is up to the implementation of WList to decide /// the order that nodes are processed. std::unique_ptr WList; + std::unique_ptr CTUWList; /// BCounterFactory - A factory object for created BlockCounter objects. /// These are used to record for key nodes in the ExplodedGraph the @@ -101,6 +102,8 @@ /// tags. DataTag::Factory DataTags; + void setBlockCounter(BlockCounter C); + void generateNode(const ProgramPoint &Loc, ProgramStateRef State, ExplodedNode *Pred); @@ -170,6 +173,7 @@ } WorkList *getWorkList() const { return WList.get(); } + WorkList *getCTUWorkList() const { return CTUWList.get(); } BlocksExhausted::const_iterator blocks_exhausted_begin() const { return blocksExhausted.begin(); diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h @@ -805,8 +805,15 @@ const ExplodedNode *Pred, const EvalCallOptions &CallOpts = {}); - bool inlineCall(const CallEvent &Call, const Decl *D, NodeBuilder &Bldr, - ExplodedNode *Pred, ProgramStateRef State); + void inlineCall(WorkList *WList, const CallEvent &Call, const Decl *D, + NodeBuilder &Bldr, ExplodedNode *Pred, ProgramStateRef State); + + void ctuBifurcate(const CallEvent &Call, const Decl *D, NodeBuilder &Bldr, + ExplodedNode *Pred, ProgramStateRef State); + + /// Returns true if the CTU analysis is running its first phase. + /// Returns true in single TU (non-CTU) mode! + bool isCTUInFirtstPhase() { return Engine.getCTUWorkList(); } /// Conservatively evaluate call by invalidating regions and binding /// a conjured return value. diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp --- a/clang/lib/AST/ASTImporter.cpp +++ b/clang/lib/AST/ASTImporter.cpp @@ -285,6 +285,7 @@ ToD = CreateFun(std::forward(args)...); // Keep track of imported Decls. Importer.RegisterImportedDecl(FromD, ToD); + Importer.SharedState->setNewDecl(ToD); InitializeImportedDecl(FromD, ToD); return false; // A new Decl is created. } diff --git a/clang/lib/CrossTU/CrossTranslationUnit.cpp b/clang/lib/CrossTU/CrossTranslationUnit.cpp --- a/clang/lib/CrossTU/CrossTranslationUnit.cpp +++ b/clang/lib/CrossTU/CrossTranslationUnit.cpp @@ -801,5 +801,18 @@ return llvm::None; } +bool CrossTranslationUnitContext::isImportedAsNew(const Decl *ToDecl) const { + if (!ImporterSharedSt) + return false; + return ImporterSharedSt->isNewDecl(const_cast(ToDecl)); +} + +bool CrossTranslationUnitContext::hasError(const Decl *ToDecl) const { + if (!ImporterSharedSt) + return false; + return static_cast( + ImporterSharedSt->getImportDeclErrorIfAny(const_cast(ToDecl))); +} + } // namespace cross_tu } // namespace clang diff --git a/clang/lib/StaticAnalyzer/Core/AnalyzerOptions.cpp b/clang/lib/StaticAnalyzer/Core/AnalyzerOptions.cpp --- a/clang/lib/StaticAnalyzer/Core/AnalyzerOptions.cpp +++ b/clang/lib/StaticAnalyzer/Core/AnalyzerOptions.cpp @@ -81,6 +81,17 @@ return K.getValue(); } +CTUPhase1InliningKind AnalyzerOptions::getCTUPhase1Inlining() const { + auto K = llvm::StringSwitch>( + CTUPhase1InliningMode) + .Case("none", CTUPhase1InliningKind::None) + .Case("small", CTUPhase1InliningKind::Small) + .Case("all", CTUPhase1InliningKind::All) + .Default(None); + assert(K.hasValue() && "CTU inlining mode is invalid."); + return K.getValue(); +} + IPAKind AnalyzerOptions::getIPAMode() const { auto K = llvm::StringSwitch>(IPAMode) .Case("none", IPAK_None) diff --git a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp --- a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp +++ b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp @@ -515,20 +515,28 @@ llvm::dbgs() << "Using autosynthesized body for " << FD->getName() << "\n"; }); + + ExprEngine &Engine = getState()->getStateManager().getOwningEngine(); + cross_tu::CrossTranslationUnitContext &CTUCtx = + *Engine.getCrossTranslationUnitContext(); + if (Body) { const Decl* Decl = AD->getDecl(); - return RuntimeDefinition(Decl); + if (CTUCtx.isImportedAsNew(Decl)) { + // A newly created definition, but we had error(s) during the import. + if (CTUCtx.hasError(Decl)) + return {}; + return RuntimeDefinition(Decl, /*Foreign=*/true); + } + return RuntimeDefinition(Decl, /*Foreign=*/false); } - ExprEngine &Engine = getState()->getStateManager().getOwningEngine(); AnalyzerOptions &Opts = Engine.getAnalysisManager().options; // Try to get CTU definition only if CTUDir is provided. if (!Opts.IsNaiveCTUEnabled) return {}; - cross_tu::CrossTranslationUnitContext &CTUCtx = - *Engine.getCrossTranslationUnitContext(); llvm::Expected CTUDeclOrError = CTUCtx.getCrossTUDefinition(FD, Opts.CTUDir, Opts.CTUIndexName, Opts.DisplayCTUProgress); @@ -541,7 +549,7 @@ return {}; } - return RuntimeDefinition(*CTUDeclOrError); + return RuntimeDefinition(*CTUDeclOrError, /*Foreign=*/true); } void AnyFunctionCall::getInitialStackFrameContents( diff --git a/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp b/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp --- a/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp @@ -43,6 +43,8 @@ STATISTIC(NumSteps, "The # of steps executed."); +STATISTIC(NumSTUSteps, "The # of STU steps executed."); +STATISTIC(NumCTUSteps, "The # of CTU steps executed."); STATISTIC(NumReachedMaxSteps, "The # of times we reached the max number of steps."); STATISTIC(NumPathsExplored, @@ -73,11 +75,18 @@ CoreEngine::CoreEngine(ExprEngine &exprengine, FunctionSummariesTy *FS, AnalyzerOptions &Opts) : ExprEng(exprengine), WList(generateWorkList(Opts)), - BCounterFactory(G.getAllocator()), FunctionSummaries(FS) {} + CTUWList(generateWorkList(Opts)), BCounterFactory(G.getAllocator()), + FunctionSummaries(FS) {} + +void CoreEngine::setBlockCounter(BlockCounter C) { + WList->setBlockCounter(C); + if (CTUWList) + CTUWList->setBlockCounter(C); +} /// ExecuteWorkList - Run the worklist algorithm for a maximum number of steps. -bool CoreEngine::ExecuteWorkList(const LocationContext *L, unsigned Steps, - ProgramStateRef InitState) { +bool CoreEngine::ExecuteWorkList(const LocationContext *L, unsigned MaxSteps, + ProgramStateRef InitState) { if (G.num_roots() == 0) { // Initialize the analysis by constructing // the root if none exists. @@ -100,7 +109,7 @@ BlockEdge StartLoc(Entry, Succ, L); // Set the current block counter to being empty. - WList->setBlockCounter(BCounterFactory.GetEmptyCounter()); + setBlockCounter(BCounterFactory.GetEmptyCounter()); if (!InitState) InitState = ExprEng.getInitialState(L); @@ -118,34 +127,52 @@ } // Check if we have a steps limit - bool UnlimitedSteps = Steps == 0; + bool UnlimitedSteps = MaxSteps == 0; + // Cap our pre-reservation in the event that the user specifies // a very large number of maximum steps. const unsigned PreReservationCap = 4000000; if(!UnlimitedSteps) - G.reserve(std::min(Steps,PreReservationCap)); - - while (WList->hasWork()) { - if (!UnlimitedSteps) { - if (Steps == 0) { - NumReachedMaxSteps++; - break; + G.reserve(std::min(MaxSteps, PreReservationCap)); + + auto ProcessWList = [this, UnlimitedSteps](unsigned MaxSteps) { + unsigned Steps = MaxSteps; + while (WList->hasWork()) { + if (!UnlimitedSteps) { + if (Steps == 0) { + NumReachedMaxSteps++; + break; + } + --Steps; } - --Steps; - } - NumSteps++; + NumSteps++; - const WorkListUnit& WU = WList->dequeue(); + const WorkListUnit &WU = WList->dequeue(); - // Set the current block counter. - WList->setBlockCounter(WU.getBlockCounter()); + // Set the current block counter. + setBlockCounter(WU.getBlockCounter()); - // Retrieve the node. - ExplodedNode *Node = WU.getNode(); + // Retrieve the node. + ExplodedNode *Node = WU.getNode(); + + dispatchWorkItem(Node, Node->getLocation(), WU); + } + return MaxSteps - Steps; + }; + const unsigned STUSteps = ProcessWList(MaxSteps); + NumSTUSteps += STUSteps; + + const unsigned MinCTUSteps = + this->ExprEng.getAnalysisManager().options.CTUMaxNodesMin; + const unsigned Mul = + this->ExprEng.getAnalysisManager().options.CTUMaxNodesMultiplier; + unsigned MaxCTUSteps = std::max(STUSteps * Mul / 100, MinCTUSteps); + + WList = std::move(CTUWList); + const unsigned CTUSteps = ProcessWList(MaxCTUSteps); + NumCTUSteps += CTUSteps; - dispatchWorkItem(Node, Node->getLocation(), WU); - } ExprEng.processEndWorklist(); return WList->hasWork(); } @@ -282,7 +309,7 @@ BlockCounter Counter = WList->getBlockCounter(); Counter = BCounterFactory.IncrementCount(Counter, LC->getStackFrame(), BlockId); - WList->setBlockCounter(Counter); + setBlockCounter(Counter); // Process the entrance of the block. if (Optional E = L.getFirstElement()) { diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp --- a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp @@ -427,10 +427,39 @@ REGISTER_MAP_WITH_PROGRAMSTATE(DynamicDispatchBifurcationMap, const MemRegion *, unsigned) +REGISTER_SET_WITH_PROGRAMSTATE(CTUDispatchBifurcationSet, const Decl *) + +void ExprEngine::ctuBifurcate(const CallEvent &Call, const Decl *D, + NodeBuilder &Bldr, ExplodedNode *Pred, + ProgramStateRef State) { + ProgramStateRef ConservativeEvalState = nullptr; + if (Call.isForeign() && isCTUInFirtstPhase()) { + const auto IK = AMgr.options.getCTUPhase1Inlining(); + const bool DoInline = IK == CTUPhase1InliningKind::All || + (IK == CTUPhase1InliningKind::Small && + isSmall(AMgr.getAnalysisDeclContext(D))); + if (DoInline) { + inlineCall(Engine.getWorkList(), Call, D, Bldr, Pred, State); + return; + } + const bool BState = State->contains(D); + if (!BState) { // This is the first time we see this foreign function. + // Enqueue it to be analyzed in the second (ctu) phase. + inlineCall(Engine.getCTUWorkList(), Call, D, Bldr, Pred, State); + // Conservatively evaluate in the first phase. + ConservativeEvalState = State->add(D); + conservativeEvalCall(Call, Bldr, Pred, ConservativeEvalState); + } else { + conservativeEvalCall(Call, Bldr, Pred, State); + } + return; + } + inlineCall(Engine.getWorkList(), Call, D, Bldr, Pred, State); +} -bool ExprEngine::inlineCall(const CallEvent &Call, const Decl *D, - NodeBuilder &Bldr, ExplodedNode *Pred, - ProgramStateRef State) { +void ExprEngine::inlineCall(WorkList *WList, const CallEvent &Call, + const Decl *D, NodeBuilder &Bldr, + ExplodedNode *Pred, ProgramStateRef State) { assert(D); const LocationContext *CurLC = Pred->getLocationContext(); @@ -465,7 +494,7 @@ if (ExplodedNode *N = G.getNode(Loc, State, false, &isNew)) { N->addPredecessor(Pred, G); if (isNew) - Engine.getWorkList()->enqueue(N); + WList->enqueue(N); } // If we decided to inline the call, the successor has been manually @@ -475,11 +504,17 @@ NumInlinedCalls++; Engine.FunctionSummaries->bumpNumTimesInlined(D); - // Mark the decl as visited. - if (VisitedCallees) - VisitedCallees->insert(D); - - return true; + // Do not mark as visited in the 2nd run (CTUWList), so the function will + // be visited as top-level, this way we won't loose reports in non-ctu + // mode. Considering the case when a function in a foreign TU calls back + // into the main TU. + // Note, during the 1st run, it doesn't matter if we mark the foreign + // functions as visited (or not) because they can never appear as a top level + // function in the main TU. + if (isCTUInFirtstPhase()) + // Mark the decl as visited. + if (VisitedCallees) + VisitedCallees->insert(D); } static ProgramStateRef getInlineFailedState(ProgramStateRef State, @@ -1068,6 +1103,7 @@ State = InlinedFailedState; } else { RuntimeDefinition RD = Call->getRuntimeDefinition(); + Call->setForeign(RD.isForeign()); const Decl *D = RD.getDecl(); if (shouldInlineCall(*Call, D, Pred, CallOpts)) { if (RD.mayHaveOtherDefinitions()) { @@ -1085,10 +1121,8 @@ return; } } - - // We are not bifurcating and we do have a Decl, so just inline. - if (inlineCall(*Call, D, Bldr, Pred, State)) - return; + ctuBifurcate(*Call, D, Bldr, Pred, State); + return; } } @@ -1110,8 +1144,7 @@ if (BState) { // If we are on "inline path", keep inlining if possible. if (*BState == DynamicDispatchModeInlined) - if (inlineCall(Call, D, Bldr, Pred, State)) - return; + ctuBifurcate(Call, D, Bldr, Pred, State); // If inline failed, or we are on the path where we assume we // don't have enough info about the receiver to inline, conjure the // return value and invalidate the regions. @@ -1124,7 +1157,7 @@ ProgramStateRef IState = State->set(BifurReg, DynamicDispatchModeInlined); - inlineCall(Call, D, Bldr, Pred, IState); + ctuBifurcate(Call, D, Bldr, Pred, IState); ProgramStateRef NoIState = State->set(BifurReg, diff --git a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp --- a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp +++ b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp @@ -476,6 +476,18 @@ if (shouldSkipFunction(D, Visited, VisitedAsTopLevel)) continue; + // The CallGraph might have declarations as callees. However, during CTU + // the declaration might form a declaration chain with the newly imported + // definition from another TU. In this case we don't want to analyze the + // function definition as toplevel. + if (const auto *FD = dyn_cast(D)) { + // Calling 'hasBody' replaces 'FD' in place with the FunctionDecl + // that has the body. + FD->hasBody(FD); + if (CTU.isImportedAsNew(FD)) + continue; + } + // Analyze the function. SetOfConstDecls VisitedCallees; diff --git a/clang/test/Analysis/Inputs/ctu-onego-existingdef-other.cpp b/clang/test/Analysis/Inputs/ctu-onego-existingdef-other.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/Inputs/ctu-onego-existingdef-other.cpp @@ -0,0 +1,7 @@ +int bar() { + return 0; +} + +void other() { + bar(); +} diff --git a/clang/test/Analysis/Inputs/ctu-onego-existingdef-other.cpp.externalDefMap.ast-dump.txt b/clang/test/Analysis/Inputs/ctu-onego-existingdef-other.cpp.externalDefMap.ast-dump.txt new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/Inputs/ctu-onego-existingdef-other.cpp.externalDefMap.ast-dump.txt @@ -0,0 +1 @@ +11:c:@F@other# ctu-onego-other.cpp.ast diff --git a/clang/test/Analysis/Inputs/ctu-onego-indirect-other.cpp b/clang/test/Analysis/Inputs/ctu-onego-indirect-other.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/Inputs/ctu-onego-indirect-other.cpp @@ -0,0 +1,7 @@ +int bar() { + return 0; +} + +void other() { + bar(); +} diff --git a/clang/test/Analysis/Inputs/ctu-onego-indirect-other.cpp.externalDefMap.ast-dump.txt b/clang/test/Analysis/Inputs/ctu-onego-indirect-other.cpp.externalDefMap.ast-dump.txt new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/Inputs/ctu-onego-indirect-other.cpp.externalDefMap.ast-dump.txt @@ -0,0 +1,2 @@ +11:c:@F@other# ctu-onego-indirect-other.cpp.ast +9:c:@F@bar# ctu-onego-indirect-other.cpp.ast diff --git a/clang/test/Analysis/Inputs/ctu-onego-small-other.cpp b/clang/test/Analysis/Inputs/ctu-onego-small-other.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/Inputs/ctu-onego-small-other.cpp @@ -0,0 +1,3 @@ +int bar() { + return 0; +} diff --git a/clang/test/Analysis/Inputs/ctu-onego-small-other.cpp.externalDefMap.ast-dump.txt b/clang/test/Analysis/Inputs/ctu-onego-small-other.cpp.externalDefMap.ast-dump.txt new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/Inputs/ctu-onego-small-other.cpp.externalDefMap.ast-dump.txt @@ -0,0 +1 @@ +9:c:@F@bar# ctu-onego-small-other.cpp.ast diff --git a/clang/test/Analysis/Inputs/ctu-onego-toplevel-other.cpp b/clang/test/Analysis/Inputs/ctu-onego-toplevel-other.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/Inputs/ctu-onego-toplevel-other.cpp @@ -0,0 +1,4 @@ +void b(int x); +void other(int y) { + b(1); +} diff --git a/clang/test/Analysis/Inputs/ctu-onego-toplevel-other.cpp.externalDefMap.ast-dump.txt b/clang/test/Analysis/Inputs/ctu-onego-toplevel-other.cpp.externalDefMap.ast-dump.txt new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/Inputs/ctu-onego-toplevel-other.cpp.externalDefMap.ast-dump.txt @@ -0,0 +1 @@ +13:c:@F@other#I# ctu-onego-toplevel-other.cpp.ast diff --git a/clang/test/Analysis/analyzer-config.c b/clang/test/Analysis/analyzer-config.c --- a/clang/test/Analysis/analyzer-config.c +++ b/clang/test/Analysis/analyzer-config.c @@ -49,6 +49,9 @@ // CHECK-NEXT: ctu-import-threshold = 24 // CHECK-NEXT: ctu-index-name = externalDefMap.txt // CHECK-NEXT: ctu-invocation-list = invocations.yaml +// CHECK-NEXT: ctu-max-nodes-min = 1000 +// CHECK-NEXT: ctu-max-nodes-mul = 100 +// CHECK-NEXT: ctu-phase1-inlining = small // CHECK-NEXT: deadcode.DeadStores:ShowFixIts = false // CHECK-NEXT: deadcode.DeadStores:WarnForDeadNestedAssignments = true // CHECK-NEXT: debug.AnalysisOrder:* = false diff --git a/clang/test/Analysis/ctu-implicit.c b/clang/test/Analysis/ctu-implicit.c --- a/clang/test/Analysis/ctu-implicit.c +++ b/clang/test/Analysis/ctu-implicit.c @@ -5,6 +5,7 @@ // RUN: cp %S/Inputs/ctu-import.c.externalDefMap.ast-dump.txt %t/ctudir2/externalDefMap.txt // RUN: %clang_cc1 -analyze \ // RUN: -analyzer-checker=core,debug.ExprInspection \ +// RUN: -analyzer-config eagerly-assume=false \ // RUN: -analyzer-config experimental-enable-naive-ctu-analysis=true \ // RUN: -analyzer-config display-ctu-progress=true \ // RUN: -analyzer-config ctu-dir=%t/ctudir2 \ @@ -15,6 +16,7 @@ int testStaticImplicit(void); int func(void) { int ret = testStaticImplicit(); - clang_analyzer_eval(ret == 4); // expected-warning{{TRUE}} + clang_analyzer_eval(ret == 4); // expected-warning{{TRUE}} ctu + // expected-warning@-1{{UNKNOWN}} stu return testStaticImplicit(); } diff --git a/clang/test/Analysis/ctu-main.c b/clang/test/Analysis/ctu-main.c --- a/clang/test/Analysis/ctu-main.c +++ b/clang/test/Analysis/ctu-main.c @@ -3,14 +3,36 @@ // RUN: %clang_cc1 -triple x86_64-pc-linux-gnu \ // RUN: -emit-pch -o %t/ctudir2/ctu-other.c.ast %S/Inputs/ctu-other.c // RUN: cp %S/Inputs/ctu-other.c.externalDefMap.ast-dump.txt %t/ctudir2/externalDefMap.txt + +// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fsyntax-only -std=c89 -analyze \ +// RUN: -analyzer-checker=core,debug.ExprInspection \ +// RUN: -analyzer-config eagerly-assume=false \ +// RUN: -analyzer-config experimental-enable-naive-ctu-analysis=true \ +// RUN: -analyzer-config ctu-dir=%t/ctudir2 \ +// RUN: -analyzer-config ctu-phase1-inlining=none \ +// RUN: -verify=newctu %s + +// Simulate the behavior of the previous CTU implementation by inlining all +// functions during the first phase. This way, the second phase is a noop. // RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fsyntax-only -std=c89 -analyze \ // RUN: -analyzer-checker=core,debug.ExprInspection \ +// RUN: -analyzer-config eagerly-assume=false \ // RUN: -analyzer-config experimental-enable-naive-ctu-analysis=true \ // RUN: -analyzer-config ctu-dir=%t/ctudir2 \ -// RUN: -verify %s +// RUN: -analyzer-config ctu-phase1-inlining=all \ +// RUN: -verify=oldctu %s void clang_analyzer_eval(int); +// A function that's definition is unknown both for single-tu (stu) and ctu +// mode. +int unknown(int); +void test_unknown() { + int res = unknown(6); + clang_analyzer_eval(res == 6); // newctu-warning{{UNKNOWN}} + // oldctu-warning@-1{{UNKNOWN}} +} + // Test typedef and global variable in function. typedef struct { int a; @@ -18,8 +40,10 @@ } FooBar; extern FooBar fb; int f(int); -void testGlobalVariable(void) { - clang_analyzer_eval(f(5) == 1); // expected-warning{{TRUE}} +void testGlobalVariable() { + clang_analyzer_eval(f(5) == 1); // newctu-warning{{TRUE}} ctu + // newctu-warning@-1{{UNKNOWN}} stu + // oldctu-warning@-2{{TRUE}} } // Test enums. @@ -28,8 +52,11 @@ y, z }; void testEnum(void) { - clang_analyzer_eval(x == 0); // expected-warning{{TRUE}} - clang_analyzer_eval(enumCheck() == 42); // expected-warning{{TRUE}} + clang_analyzer_eval(x == 0); // newctu-warning{{TRUE}} + // oldctu-warning@-1{{TRUE}} + clang_analyzer_eval(enumCheck() == 42); // newctu-warning{{TRUE}} ctu + // newctu-warning@-1{{UNKNOWN}} stu + // oldctu-warning@-2{{TRUE}} } // Test that asm import does not fail. @@ -42,18 +69,22 @@ struct S; int g(struct S *); void testMacro(void) { - g(0); // expected-warning@Inputs/ctu-other.c:29 {{Access to field 'a' results in a dereference of a null pointer (loaded from variable 'ctx')}} + g(0); // newctu-warning@Inputs/ctu-other.c:29 {{Access to field 'a' results in a dereference of a null pointer (loaded from variable 'ctx')}} + // oldctu-warning@Inputs/ctu-other.c:29 {{Access to field 'a' results in a dereference of a null pointer (loaded from variable 'ctx')}} } // The external function prototype is incomplete. // warning:implicit functions are prohibited by c99 void testImplicit(void) { int res = identImplicit(6); // external implicit functions are not inlined - clang_analyzer_eval(res == 6); // expected-warning{{TRUE}} + clang_analyzer_eval(res == 6); // newctu-warning{{TRUE}} ctu + // newctu-warning@-1{{UNKNOWN}} stu + // oldctu-warning@-2{{TRUE}} // Call something with uninitialized from the same function in which the implicit was called. // This is necessary to reproduce a special bug in NoStoreFuncVisitor. int uninitialized; - h(uninitialized); // expected-warning{{1st function call argument is an uninitialized value}} + h(uninitialized); // newctu-warning{{1st function call argument is an uninitialized value}} + // oldctu-warning@-1{{1st function call argument is an uninitialized value}} } // Tests the import of functions that have a struct parameter @@ -67,7 +98,9 @@ struct DataType d; d.a = 1; d.b = 0; - clang_analyzer_eval(structInProto(&d) == 0); // expected-warning{{TRUE}} expected-warning{{FALSE}} + // Not imported, thus remains unknown both in stu and ctu. + clang_analyzer_eval(structInProto(&d) == 0); // newctu-warning{{UNKNOWN}} + // oldctu-warning@-1{{UNKNOWN}} } int switchWithoutCases(int); diff --git a/clang/test/Analysis/ctu-main.cpp b/clang/test/Analysis/ctu-main.cpp --- a/clang/test/Analysis/ctu-main.cpp +++ b/clang/test/Analysis/ctu-main.cpp @@ -5,11 +5,25 @@ // RUN: %clang_cc1 -std=c++14 -triple x86_64-pc-linux-gnu \ // RUN: -emit-pch -o %t/ctudir/ctu-chain.cpp.ast %S/Inputs/ctu-chain.cpp // RUN: cp %S/Inputs/ctu-other.cpp.externalDefMap.ast-dump.txt %t/ctudir/externalDefMap.txt + +// RUN: %clang_analyze_cc1 -std=c++14 -triple x86_64-pc-linux-gnu \ +// RUN: -analyzer-checker=core,debug.ExprInspection \ +// RUN: -analyzer-config eagerly-assume=false \ +// RUN: -analyzer-config experimental-enable-naive-ctu-analysis=true \ +// RUN: -analyzer-config ctu-dir=%t/ctudir \ +// RUN: -analyzer-config ctu-phase1-inlining=none \ +// RUN: -verify=newctu %s + +// Simulate the behavior of the previous CTU implementation by inlining all +// functions during the first phase. This way, the second phase is a noop. // RUN: %clang_analyze_cc1 -std=c++14 -triple x86_64-pc-linux-gnu \ // RUN: -analyzer-checker=core,debug.ExprInspection \ +// RUN: -analyzer-config eagerly-assume=false \ // RUN: -analyzer-config experimental-enable-naive-ctu-analysis=true \ // RUN: -analyzer-config ctu-dir=%t/ctudir \ -// RUN: -verify %s +// RUN: -analyzer-config ctu-phase1-inlining=all \ +// RUN: -verify=oldctu %s + // RUN: %clang_analyze_cc1 -std=c++14 -triple x86_64-pc-linux-gnu \ // RUN: -analyzer-checker=core,debug.ExprInspection \ // RUN: -analyzer-config experimental-enable-naive-ctu-analysis=true \ @@ -113,10 +127,17 @@ void test_virtual_functions(mycls* obj) { // The dynamic type is known. - clang_analyzer_eval(mycls().fvcl(1) == 8); // expected-warning{{TRUE}} - clang_analyzer_eval(derived().fvcl(1) == 9); // expected-warning{{TRUE}} + clang_analyzer_eval(mycls().fvcl(1) == 8); // newctu-warning{{TRUE}} ctu + // newctu-warning@-1{{UNKNOWN}} stu + // oldctu-warning@-2{{TRUE}} + clang_analyzer_eval(derived().fvcl(1) == 9); // newctu-warning{{TRUE}} ctu + // newctu-warning@-1{{UNKNOWN}} stu + // oldctu-warning@-2{{TRUE}} // We cannot decide about the dynamic type. - clang_analyzer_eval(obj->fvcl(1) == 8); // expected-warning{{FALSE}} expected-warning{{TRUE}} + clang_analyzer_eval(obj->fvcl(1) == 8); // newctu-warning{{TRUE}} ctu + // newctu-warning@-1{{UNKNOWN}} ctu, stu + // oldctu-warning@-2{{TRUE}} + // oldctu-warning@-3{{UNKNOWN}} } class TestAnonUnionUSR { @@ -137,44 +158,92 @@ extern int testImportOfDelegateConstructor(int); int main() { - clang_analyzer_eval(f(3) == 2); // expected-warning{{TRUE}} - clang_analyzer_eval(f(4) == 3); // expected-warning{{TRUE}} - clang_analyzer_eval(f(5) == 3); // expected-warning{{FALSE}} - clang_analyzer_eval(g(4) == 6); // expected-warning{{TRUE}} - clang_analyzer_eval(h(2) == 8); // expected-warning{{TRUE}} - - clang_analyzer_eval(myns::fns(2) == 9); // expected-warning{{TRUE}} - clang_analyzer_eval(myns::embed_ns::fens(2) == -1); // expected-warning{{TRUE}} - clang_analyzer_eval(mycls().fcl(1) == 6); // expected-warning{{TRUE}} - clang_analyzer_eval(mycls::fscl(1) == 7); // expected-warning{{TRUE}} - clang_analyzer_eval(myns::embed_cls().fecl(1) == -6); // expected-warning{{TRUE}} - clang_analyzer_eval(mycls::embed_cls2().fecl2(0) == -11); // expected-warning{{TRUE}} - - clang_analyzer_eval(chns::chf1(4) == 12); // expected-warning{{TRUE}} - clang_analyzer_eval(fun_using_anon_struct(8) == 8); // expected-warning{{TRUE}} - - clang_analyzer_eval(other_macro_diag(1) == 1); // expected-warning{{TRUE}} - // expected-warning@Inputs/ctu-other.cpp:93{{REACHABLE}} - MACRODIAG(); // expected-warning{{REACHABLE}} - - clang_analyzer_eval(extInt == 2); // expected-warning{{TRUE}} - clang_analyzer_eval(intns::extInt == 3); // expected-warning{{TRUE}} - clang_analyzer_eval(extS.a == 4); // expected-warning{{TRUE}} - clang_analyzer_eval(extNonConstS.a == 4); // expected-warning{{TRUE}} expected-warning{{FALSE}} + clang_analyzer_eval(f(3) == 2); // newctu-warning{{TRUE}} ctu + // newctu-warning@-1{{UNKNOWN}} stu + // oldctu-warning@-2{{TRUE}} + clang_analyzer_eval(f(4) == 3); // newctu-warning{{TRUE}} ctu + // newctu-warning@-1{{UNKNOWN}} stu + // oldctu-warning@-2{{TRUE}} + clang_analyzer_eval(f(5) == 3); // newctu-warning{{FALSE}} ctu + // newctu-warning@-1{{UNKNOWN}} stu + // oldctu-warning@-2{{FALSE}} + clang_analyzer_eval(g(4) == 6); // newctu-warning{{TRUE}} ctu + // newctu-warning@-1{{UNKNOWN}} stu + // oldctu-warning@-2{{TRUE}} + clang_analyzer_eval(h(2) == 8); // newctu-warning{{TRUE}} ctu + // newctu-warning@-1{{UNKNOWN}} stu + // oldctu-warning@-2{{TRUE}} + + clang_analyzer_eval(myns::fns(2) == 9); // newctu-warning{{TRUE}} ctu + // newctu-warning@-1{{UNKNOWN}} stu + // oldctu-warning@-2{{TRUE}} + clang_analyzer_eval(myns::embed_ns::fens(2) == -1); // newctu-warning{{TRUE}} ctu + // newctu-warning@-1{{UNKNOWN}} stu + // oldctu-warning@-2{{TRUE}} + clang_analyzer_eval(mycls().fcl(1) == 6); // newctu-warning{{TRUE}} ctu + // newctu-warning@-1{{UNKNOWN}} stu + // oldctu-warning@-2{{TRUE}} + clang_analyzer_eval(mycls::fscl(1) == 7); // newctu-warning{{TRUE}} ctu + // newctu-warning@-1{{UNKNOWN}} stu + // oldctu-warning@-2{{TRUE}} + clang_analyzer_eval(myns::embed_cls().fecl(1) == -6); // newctu-warning{{TRUE}} ctu + // newctu-warning@-1{{UNKNOWN}} stu + // oldctu-warning@-2{{TRUE}} + clang_analyzer_eval(mycls::embed_cls2().fecl2(0) == -11); // newctu-warning{{TRUE}} ctu + // newctu-warning@-1{{UNKNOWN}} stu + // oldctu-warning@-2{{TRUE}} + + clang_analyzer_eval(chns::chf1(4) == 12); // newctu-warning{{TRUE}} ctu + // newctu-warning@-1{{UNKNOWN}} stu + // oldctu-warning@-2{{TRUE}} + clang_analyzer_eval(fun_using_anon_struct(8) == 8); // newctu-warning{{TRUE}} ctu + // newctu-warning@-1{{UNKNOWN}} stu + // oldctu-warning@-2{{TRUE}} + + clang_analyzer_eval(other_macro_diag(1) == 1); // newctu-warning{{TRUE}} ctu + // newctu-warning@-1{{UNKNOWN}} stu + // oldctu-warning@-2{{TRUE}} + // newctu-warning@Inputs/ctu-other.cpp:93{{REACHABLE}} + // oldctu-warning@Inputs/ctu-other.cpp:93{{REACHABLE}} + MACRODIAG(); // newctu-warning{{REACHABLE}} + // oldctu-warning@-1{{REACHABLE}} + + // FIXME we should report an UNKNOWN as well for all external variables! + clang_analyzer_eval(extInt == 2); // newctu-warning{{TRUE}} + // oldctu-warning@-1{{TRUE}} + clang_analyzer_eval(intns::extInt == 3); // newctu-warning{{TRUE}} + // oldctu-warning@-1{{TRUE}} + clang_analyzer_eval(extS.a == 4); // newctu-warning{{TRUE}} + // oldctu-warning@-1{{TRUE}} + clang_analyzer_eval(extNonConstS.a == 4); // newctu-warning{{UNKNOWN}} + // oldctu-warning@-1{{UNKNOWN}} // Do not import non-trivial classes' initializers. - clang_analyzer_eval(extNTS.a == 4); // expected-warning{{TRUE}} expected-warning{{FALSE}} - clang_analyzer_eval(extHere == 6); // expected-warning{{TRUE}} - clang_analyzer_eval(A::a == 3); // expected-warning{{TRUE}} - clang_analyzer_eval(extSC.a == 8); // expected-warning{{TRUE}} - clang_analyzer_eval(ST::sc.a == 2); // expected-warning{{TRUE}} + clang_analyzer_eval(extNTS.a == 4); // newctu-warning{{UNKNOWN}} + // oldctu-warning@-1{{UNKNOWN}} + clang_analyzer_eval(extHere == 6); // newctu-warning{{TRUE}} + // oldctu-warning@-1{{TRUE}} + clang_analyzer_eval(A::a == 3); // newctu-warning{{TRUE}} + // oldctu-warning@-1{{TRUE}} + clang_analyzer_eval(extSC.a == 8); // newctu-warning{{TRUE}} + // oldctu-warning@-1{{TRUE}} + clang_analyzer_eval(ST::sc.a == 2); // newctu-warning{{TRUE}} + // oldctu-warning@-1{{TRUE}} // clang_analyzer_eval(extSCN.scn.a == 9); // TODO - clang_analyzer_eval(extSubSCN.a == 1); // expected-warning{{TRUE}} + clang_analyzer_eval(extSubSCN.a == 1); // newctu-warning{{TRUE}} + // oldctu-warning@-1{{TRUE}} // clang_analyzer_eval(extSCC.a == 7); // TODO - clang_analyzer_eval(extU.a == 4); // expected-warning{{TRUE}} - - clang_analyzer_eval(TestAnonUnionUSR::Test == 5); // expected-warning{{TRUE}} - - clang_analyzer_eval(testImportOfIncompleteDefaultParmDuringImport(9) == 9); // expected-warning{{TRUE}} - - clang_analyzer_eval(testImportOfDelegateConstructor(10) == 10); // expected-warning{{TRUE}} + clang_analyzer_eval(extU.a == 4); // newctu-warning{{TRUE}} + // oldctu-warning@-1{{TRUE}} + clang_analyzer_eval(TestAnonUnionUSR::Test == 5); // newctu-warning{{TRUE}} + // oldctu-warning@-1{{TRUE}} + + clang_analyzer_eval(testImportOfIncompleteDefaultParmDuringImport(9) == 9); + // newctu-warning@-1{{TRUE}} ctu + // newctu-warning@-2{{UNKNOWN}} stu + // oldctu-warning@-3{{TRUE}} + + clang_analyzer_eval(testImportOfDelegateConstructor(10) == 10); + // newctu-warning@-1{{TRUE}} ctu + // newctu-warning@-2{{UNKNOWN}} stu + // oldctu-warning@-3{{TRUE}} } diff --git a/clang/test/Analysis/ctu-on-demand-parsing.c b/clang/test/Analysis/ctu-on-demand-parsing.c --- a/clang/test/Analysis/ctu-on-demand-parsing.c +++ b/clang/test/Analysis/ctu-on-demand-parsing.c @@ -16,8 +16,12 @@ // RUN: -analyzer-config experimental-enable-naive-ctu-analysis=true \ // RUN: -analyzer-config ctu-dir=. \ // RUN: -analyzer-config ctu-invocation-list=invocations.yaml \ +// RUN: -analyzer-config ctu-phase1-inlining=all \ // RUN: -verify ctu-on-demand-parsing.c // +// FIXME On-demand ctu should be tested in the same file that we have for the +// PCH version, but with a different verify prefix (e.g. -verfiy=on-demand-ctu) +// // FIXME: Path handling should work on all platforms. // REQUIRES: system-linux diff --git a/clang/test/Analysis/ctu-on-demand-parsing.cpp b/clang/test/Analysis/ctu-on-demand-parsing.cpp --- a/clang/test/Analysis/ctu-on-demand-parsing.cpp +++ b/clang/test/Analysis/ctu-on-demand-parsing.cpp @@ -18,6 +18,7 @@ // RUN: -analyzer-config experimental-enable-naive-ctu-analysis=true \ // RUN: -analyzer-config ctu-dir=. \ // RUN: -analyzer-config ctu-invocation-list=invocations.yaml \ +// RUN: -analyzer-config ctu-phase1-inlining=all \ // RUN: -verify ctu-on-demand-parsing.cpp // RUN: cd "%t" && %clang_analyze_cc1 \ // RUN: -analyzer-checker=core,debug.ExprInspection \ @@ -28,6 +29,9 @@ // // CHECK: CTU loaded AST file: {{.*}}ctu-other.cpp // CHECK: CTU loaded AST file: {{.*}}ctu-chain.cpp + +// FIXME On-demand ctu should be tested in the same file that we have for the +// PCH version, but with a different verify prefix (e.g. -verfiy=on-demand-ctu) // // FIXME: Path handling should work on all platforms. // REQUIRES: system-linux diff --git a/clang/test/Analysis/ctu-onego-existingdef.cpp b/clang/test/Analysis/ctu-onego-existingdef.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/ctu-onego-existingdef.cpp @@ -0,0 +1,56 @@ +// RUN: %clang_analyze_cc1 -std=c++14 -triple x86_64-pc-linux-gnu \ +// RUN: -analyzer-checker=core,debug.ExprInspection \ +// RUN: -analyzer-config eagerly-assume=false \ +// RUN: -analyze-function='baruser(int)' -x c++ \ +// RUN: -verify=nonctu %s + +// RUN: rm -rf %t && mkdir %t +// RUN: mkdir -p %t/ctudir +// RUN: %clang_cc1 -std=c++14 -triple x86_64-pc-linux-gnu \ +// RUN: -emit-pch -o %t/ctudir/ctu-onego-existingdef-other.cpp.ast %S/Inputs/ctu-onego-existingdef-other.cpp +// RUN: cp %S/Inputs/ctu-onego-existingdef-other.cpp.externalDefMap.ast-dump.txt %t/ctudir/externalDefMap.txt + +// Existing and equal function definition in both TU. `other` calls `bar` thus +// `bar` will be indirectly imported. During the import we recognize that there +// is an existing definition in the main TU, so we don't create a new Decl. +// Thus, ctu should not bifurcate on the call of `bar` it should directly +// inlinie that as in the case of nonctu. +// Note, we would not get a warning below, if `bar` is conservatively evaluated. +int bar() { + return 0; +} + +//Here we completely supress the CTU work list execution. We should not +//bifurcate on the call of `bar`. (We do not load the foreign AST at all.) +// RUN: %clang_analyze_cc1 -std=c++14 -triple x86_64-pc-linux-gnu \ +// RUN: -analyzer-checker=core,debug.ExprInspection \ +// RUN: -analyzer-config eagerly-assume=false \ +// RUN: -analyzer-config experimental-enable-naive-ctu-analysis=true \ +// RUN: -analyzer-config ctu-dir=%t/ctudir \ +// RUN: -verify=stu %s \ +// RUN: -analyze-function='baruser(int)' -x c++ \ +// RUN: -analyzer-config ctu-max-nodes-mul=0 \ +// RUN: -analyzer-config ctu-max-nodes-min=0 + +//Here we enable the CTU work list execution. We should not bifurcate on the +//call of `bar`. +// RUN: %clang_analyze_cc1 -std=c++14 -triple x86_64-pc-linux-gnu \ +// RUN: -analyzer-checker=core,debug.ExprInspection \ +// RUN: -analyzer-config eagerly-assume=false \ +// RUN: -analyzer-config experimental-enable-naive-ctu-analysis=true \ +// RUN: -analyzer-config ctu-dir=%t/ctudir \ +// RUN: -verify=ctu %s \ +// RUN: -analyze-function='baruser(int)' -x c++ \ +// RUN: -analyzer-config ctu-max-nodes-mul=100 \ +// RUN: -analyzer-config ctu-max-nodes-min=1000 + +void other(); // Defined in the other TU. + +void baruser(int) { + other(); + int x = bar(); + (void)(1 / x); + // ctu-warning@-1{{Division by zero}} + // stu-warning@-2{{Division by zero}} + // nonctu-warning@-3{{Division by zero}} +} diff --git a/clang/test/Analysis/ctu-onego-indirect.cpp b/clang/test/Analysis/ctu-onego-indirect.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/ctu-onego-indirect.cpp @@ -0,0 +1,58 @@ +// RUN: rm -rf %t && mkdir %t +// RUN: mkdir -p %t/ctudir +// RUN: %clang_cc1 -std=c++14 -triple x86_64-pc-linux-gnu \ +// RUN: -emit-pch -o %t/ctudir/ctu-onego-indirect-other.cpp.ast %S/Inputs/ctu-onego-indirect-other.cpp +// RUN: cp %S/Inputs/ctu-onego-indirect-other.cpp.externalDefMap.ast-dump.txt %t/ctudir/externalDefMap.txt + +int bar(); + +// Here we have a foreign function `bar` that is imported when we analyze +// `adirectbaruser`. During the subsequent toplevel analysis of `baruser` we +// should bifurcate on the call of `bar`. + +//Ensure the order of the toplevel analyzed functions. +// RUN: %clang_analyze_cc1 -std=c++14 -triple x86_64-pc-linux-gnu \ +// RUN: -analyzer-checker=core,debug.ExprInspection \ +// RUN: -analyzer-config eagerly-assume=false \ +// RUN: -analyzer-config experimental-enable-naive-ctu-analysis=true \ +// RUN: -analyzer-config ctu-dir=%t/ctudir \ +// RUN: -analyzer-display-progress \ +// RUN: -analyzer-inlining-mode=all \ +// RUN: -analyzer-config ctu-phase1-inlining=none \ +// RUN: -analyzer-config ctu-max-nodes-mul=100 \ +// RUN: -analyzer-config ctu-max-nodes-min=1000 2>&1 %s | FileCheck %s +// CHECK: ANALYZE (Path, Inline_Regular):{{.*}}adirectbaruser(int) +// CHECK: ANALYZE (Path, Inline_Regular):{{.*}}baruser(int) + +// RUN: %clang_analyze_cc1 -std=c++14 -triple x86_64-pc-linux-gnu \ +// RUN: -analyzer-checker=core,debug.ExprInspection \ +// RUN: -analyzer-config eagerly-assume=false \ +// RUN: -analyzer-config experimental-enable-naive-ctu-analysis=true \ +// RUN: -analyzer-config ctu-dir=%t/ctudir \ +// RUN: -analyzer-display-progress \ +// RUN: -analyzer-inlining-mode=all \ +// RUN: -analyzer-config ctu-phase1-inlining=none \ +// RUN: -verify %s \ +// RUN: -analyzer-config ctu-max-nodes-mul=100 \ +// RUN: -analyzer-config ctu-max-nodes-min=1000 + + +void other(); // Defined in the other TU. + +void clang_analyzer_eval(int); + +void baruser(int x) { + if (x == 1) + return; + int y = bar(); + clang_analyzer_eval(y == 0); // expected-warning{{TRUE}} + // expected-warning@-1{{UNKNOWN}} + other(); +} + +void adirectbaruser(int) { + int y = bar(); + (void)y; + baruser(1); +} + diff --git a/clang/test/Analysis/ctu-onego-small.cpp b/clang/test/Analysis/ctu-onego-small.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/ctu-onego-small.cpp @@ -0,0 +1,51 @@ +// RUN: rm -rf %t && mkdir %t +// RUN: mkdir -p %t/ctudir +// RUN: %clang_cc1 -std=c++14 -triple x86_64-pc-linux-gnu \ +// RUN: -emit-pch -o %t/ctudir/ctu-onego-small-other.cpp.ast %S/Inputs/ctu-onego-small-other.cpp +// RUN: cp %S/Inputs/ctu-onego-small-other.cpp.externalDefMap.ast-dump.txt %t/ctudir/externalDefMap.txt + +// Small function defined in another TU. +int bar(); + +// Here we limit the ctu analysis to the first phase only (via the +// ctu-max-nodes config options). And we check whether the small foreign +// function `bar` is inlined. + +// RUN: %clang_analyze_cc1 -std=c++14 -triple x86_64-pc-linux-gnu \ +// RUN: -analyzer-checker=core,debug.ExprInspection \ +// RUN: -analyzer-config eagerly-assume=false \ +// RUN: -analyzer-config experimental-enable-naive-ctu-analysis=true \ +// RUN: -analyzer-config ctu-dir=%t/ctudir \ +// RUN: -analyzer-config display-ctu-progress=true \ +// RUN: -analyzer-display-progress \ +// RUN: -analyzer-config ctu-max-nodes-mul=0 \ +// RUN: -analyzer-config ctu-max-nodes-min=0 2>&1 %s | FileCheck %s +// CHECK: ANALYZE (Path, Inline_Regular): {{.*}} baruser(int){{.*}}CTU loaded AST file + +// RUN: %clang_analyze_cc1 -std=c++14 -triple x86_64-pc-linux-gnu \ +// RUN: -analyzer-checker=core,debug.ExprInspection \ +// RUN: -analyzer-config eagerly-assume=false \ +// RUN: -analyzer-config experimental-enable-naive-ctu-analysis=true \ +// RUN: -analyzer-config ctu-dir=%t/ctudir \ +// RUN: -analyzer-config ctu-max-nodes-mul=0 \ +// RUN: -analyzer-config ctu-phase1-inlining=none \ +// RUN: -analyzer-config ctu-max-nodes-min=0 -verify=inline-none %s + +// RUN: %clang_analyze_cc1 -std=c++14 -triple x86_64-pc-linux-gnu \ +// RUN: -analyzer-checker=core,debug.ExprInspection \ +// RUN: -analyzer-config eagerly-assume=false \ +// RUN: -analyzer-config experimental-enable-naive-ctu-analysis=true \ +// RUN: -analyzer-config ctu-dir=%t/ctudir \ +// RUN: -analyzer-config ctu-max-nodes-mul=0 \ +// RUN: -analyzer-config ctu-phase1-inlining=small \ +// RUN: -analyzer-config ctu-max-nodes-min=0 -verify=inline-small %s + + +void clang_analyzer_eval(int); + +void baruser(int x) { + int y = bar(); + // inline-none-warning@+2{{UNKNOWN}} + // inline-small-warning@+1{{TRUE}} + clang_analyzer_eval(y == 0); +} diff --git a/clang/test/Analysis/ctu-onego-toplevel.cpp b/clang/test/Analysis/ctu-onego-toplevel.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/ctu-onego-toplevel.cpp @@ -0,0 +1,54 @@ +// RUN: rm -rf %t && mkdir %t +// RUN: mkdir -p %t/ctudir +// RUN: %clang_cc1 -std=c++14 -triple x86_64-pc-linux-gnu \ +// RUN: -emit-pch -o %t/ctudir/ctu-onego-toplevel-other.cpp.ast %S/Inputs/ctu-onego-toplevel-other.cpp +// RUN: cp %S/Inputs/ctu-onego-toplevel-other.cpp.externalDefMap.ast-dump.txt %t/ctudir/externalDefMap.txt + +// RUN: %clang_analyze_cc1 -std=c++14 -triple x86_64-pc-linux-gnu \ +// RUN: -analyzer-checker=core,debug.ExprInspection \ +// RUN: -analyzer-config eagerly-assume=false \ +// RUN: -analyzer-config experimental-enable-naive-ctu-analysis=true \ +// RUN: -analyzer-config ctu-dir=%t/ctudir \ +// RUN: -analyzer-config ctu-phase1-inlining=none \ +// RUN: -verify=ctu %s + +// RUN: %clang_analyze_cc1 -std=c++14 -triple x86_64-pc-linux-gnu \ +// RUN: -analyzer-checker=core,debug.ExprInspection \ +// RUN: -analyzer-config eagerly-assume=false \ +// RUN: -analyzer-config experimental-enable-naive-ctu-analysis=true \ +// RUN: -analyzer-config ctu-dir=%t/ctudir \ +// RUN: -analyzer-config ctu-phase1-inlining=none \ +// RUN: -analyzer-config display-ctu-progress=true \ +// RUN: -analyzer-display-progress \ +// RUN: -verify=ctu %s 2>&1 | FileCheck %s +// +// CallGraph: c->b +// topological sort: c, b +// Note that `other` calls into `b` but that is not visible in the CallGraph +// b/c that happens in another TU. + +// During the onego CTU analysis, we start with c() as top level function. +// Then we visit b() as non-toplevel during the processing of the FWList, thus +// that would not be visited as toplevel without special care. + +// `c` is analyzed as toplevel and during that the other TU is loaded: +// CHECK: ANALYZE (Path, Inline_Regular): {{.*}} c(int){{.*}}CTU loaded AST file +// next, `b` is analyzed as toplevel: +// CHECK: ANALYZE (Path, Inline_Regular): {{.*}} b(int) + +void b(int x); +void other(int y); +void c(int y) { + other(y); + return; + // The below call is here to form the proper CallGraph, but will not be + // analyzed. + b(1); +} + +void b(int x) { + if (x == 0) + (void)(1 / x); + // ctu-warning@-1{{Division by zero}} + // We receive the above warning only if `b` is analyzed as top-level. +} diff --git a/clang/unittests/AST/ASTImporterTest.cpp b/clang/unittests/AST/ASTImporterTest.cpp --- a/clang/unittests/AST/ASTImporterTest.cpp +++ b/clang/unittests/AST/ASTImporterTest.cpp @@ -7686,6 +7686,38 @@ EXPECT_TRUE(ToX->getInClassInitializer()); } +TEST_P(ASTImporterOptionSpecificTestBase, isNewDecl) { + Decl *FromTU = getTuDecl( + R"( + int bar() { + return 0; + } + void other() { + bar(); + } + )", + Lang_CXX11); + Decl *ToTU = getToTuDecl( + R"( + int bar() { + return 0; + } + )", + Lang_CXX11); + auto *FromOther = FirstDeclMatcher().match( + FromTU, functionDecl(hasName("other"))); + ASSERT_TRUE(FromOther); + + auto *ToOther = Import(FromOther, Lang_CXX11); + ASSERT_TRUE(ToOther); + + auto *ToBar = FirstDeclMatcher().match( + ToTU, functionDecl(hasName("bar"))); + + EXPECT_TRUE(SharedStatePtr->isNewDecl(ToOther)); + EXPECT_FALSE(SharedStatePtr->isNewDecl(ToBar)); +} + INSTANTIATE_TEST_SUITE_P(ParameterizedTests, ASTImporterLookupTableTest, DefaultTestValuesForRunOptions);