https://github.com/relaxcn updated https://github.com/llvm/llvm-project/pull/231393
>From 0be3938cf343dcd35f9a091679817481bbcf6654 Mon Sep 17 00:00:00 2001 From: relaxcn <[email protected]> Date: Sat, 10 Oct 2026 23:30:35 +0800 Subject: [PATCH] [analyzer] Import CTU constant initializers on demand AnalysisConsumer imports external constant initializers while walking the TU, even if the variables are never used. These imports can exhaust the shared CTU import budget before path analysis starts, preventing function definitions from being imported. Move initializer imports to RegionStore and prepare constants referenced by a function before building its CFG. The latter is needed for CFG pruning and dead-store checks, which cannot wait for a Store read. Also follow initializer dependencies and import array definitions before creating their regions, so constant evaluation and array extents still work with the later import. --- .../clang/CrossTU/CrossTranslationUnit.h | 7 ++ clang/lib/CrossTU/CrossTranslationUnit.cpp | 71 +++++++++++ clang/lib/StaticAnalyzer/Core/RegionStore.cpp | 50 +++++++- .../Frontend/AnalysisConsumer.cpp | 34 +----- .../Inputs/inherited-default-ctor-other.cpp | 1 + .../test/Analysis/ctu/demanded-initializers.c | 115 ++++++++++++++++++ .../Analysis/ctu/demanded-initializers.cpp | 64 ++++++++++ .../Analysis/ctu/inherited-default-ctor.cpp | 1 + .../test/Analysis/ctu/test-import-failure.cpp | 5 +- 9 files changed, 314 insertions(+), 34 deletions(-) create mode 100644 clang/test/Analysis/ctu/demanded-initializers.c create mode 100644 clang/test/Analysis/ctu/demanded-initializers.cpp diff --git a/clang/include/clang/CrossTU/CrossTranslationUnit.h b/clang/include/clang/CrossTU/CrossTranslationUnit.h index dd7eceef35b7f..c40d536d7ddaf 100644 --- a/clang/include/clang/CrossTU/CrossTranslationUnit.h +++ b/clang/include/clang/CrossTU/CrossTranslationUnit.h @@ -33,6 +33,7 @@ class DeclContext; class FunctionDecl; class VarDecl; class NamedDecl; +class Stmt; class TranslationUnitDecl; namespace cross_tu { @@ -153,6 +154,12 @@ class CrossTranslationUnitContext { getCrossTUDefinition(const VarDecl *VD, StringRef CrossTUDir, StringRef IndexName, bool DisplayCTUProgress = false); + /// Import initializers of constants referenced by potentially evaluated + /// expressions in \p S, including dependencies of those initializers. + void importReferencedInitializers(const Stmt *S, StringRef CrossTUDir, + StringRef IndexName, + bool DisplayCTUProgress = false); + /// This function loads a definition from an external AST file. /// /// A definition with the same declaration will be looked up in the diff --git a/clang/lib/CrossTU/CrossTranslationUnit.cpp b/clang/lib/CrossTU/CrossTranslationUnit.cpp index 4ef7a1fc4bf7a..7dac3e945f3d2 100644 --- a/clang/lib/CrossTU/CrossTranslationUnit.cpp +++ b/clang/lib/CrossTU/CrossTranslationUnit.cpp @@ -12,6 +12,7 @@ #include "clang/CrossTU/CrossTranslationUnit.h" #include "clang/AST/ASTImporter.h" #include "clang/AST/Decl.h" +#include "clang/AST/EvaluatedExprVisitor.h" #include "clang/AST/ParentMapContext.h" #include "clang/Basic/DiagnosticDriver.h" #include "clang/Basic/TargetInfo.h" @@ -22,6 +23,7 @@ #include "clang/Frontend/TextDiagnosticPrinter.h" #include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" #include "clang/UnifiedSymbolResolution/USRGeneration.h" +#include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/Statistic.h" #include "llvm/Option/ArgList.h" #include "llvm/Support/ErrorHandling.h" @@ -409,6 +411,75 @@ CrossTranslationUnitContext::getCrossTUDefinition(const VarDecl *VD, DisplayCTUProgress); } +void CrossTranslationUnitContext::importReferencedInitializers( + const Stmt *S, StringRef CrossTUDir, StringRef IndexName, + bool DisplayCTUProgress) { + class InitializerVisitor + : public ConstEvaluatedExprVisitor<InitializerVisitor> { + CrossTranslationUnitContext &CTU; + StringRef CrossTUDir; + StringRef IndexName; + bool DisplayCTUProgress; + llvm::SmallPtrSet<const VarDecl *, 8> Visited; + + void importInitializer(const VarDecl *VD) { + if (!VD || !VD->hasGlobalStorage() || !shouldImport(VD, Context) || + !Visited.insert(VD->getCanonicalDecl()).second) + return; + + const Expr *Init = VD->getAnyInitializer(); + if (!Init && (VD->hasExternalStorage() || VD->isStaticDataMember())) { + auto Imported = CTU.getCrossTUDefinition(VD, CrossTUDir, IndexName, + DisplayCTUProgress); + if (!Imported) { + llvm::handleAllErrors( + Imported.takeError(), [&](const IndexError &IE) { + CTU.emitCrossTUDiagnostics(IE, VD->getLocation()); + }); + return; + } + Init = (*Imported)->getAnyInitializer(); + } + if (Init) + Visit(Init); + } + + public: + InitializerVisitor(CrossTranslationUnitContext &CTU, ASTContext &Context, + StringRef CrossTUDir, StringRef IndexName, + bool DisplayCTUProgress) + : ConstEvaluatedExprVisitor<InitializerVisitor>(Context), CTU(CTU), + CrossTUDir(CrossTUDir), IndexName(IndexName), + DisplayCTUProgress(DisplayCTUProgress) {} + + void VisitDeclRefExpr(const DeclRefExpr *E) { + importInitializer(dyn_cast<VarDecl>(E->getDecl())); + } + + void VisitMemberExpr(const MemberExpr *E) { + importInitializer(dyn_cast<VarDecl>(E->getMemberDecl())); + Visit(E->getBase()); + } + + void VisitIfStmt(const IfStmt *If) { + if (auto Selected = If->getNondiscardedCase(Context)) { + // The init-statement executes even when a body is discarded. + if (const Stmt *Init = If->getInit()) + Visit(Init); + if (const Stmt *ConditionVar = If->getConditionVariableDeclStmt()) + Visit(ConditionVar); + Visit(If->getCond()); + if (*Selected) + Visit(*Selected); + return; + } + VisitStmt(If); + } + } Visitor(*this, Context, CrossTUDir, IndexName, DisplayCTUProgress); + if (S) + Visitor.Visit(S); +} + void CrossTranslationUnitContext::emitCrossTUDiagnostics(const IndexError &IE, SourceLocation Loc) { switch (IE.getCode()) { diff --git a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp index 6994917470ce8..e0f22112f9c39 100644 --- a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp +++ b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp @@ -20,6 +20,7 @@ #include "clang/Analysis/AnalysisDeclContext.h" #include "clang/Basic/JsonSupport.h" #include "clang/Basic/TargetInfo.h" +#include "clang/CrossTU/CrossTranslationUnit.h" #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" @@ -483,6 +484,8 @@ class RegionStoreManager : public StoreManager { SValListTy> LazyBindingsMapTy; LazyBindingsMapTy LazyBindingsMap; + const Expr *getInitializer(const VarDecl *&VD); + /// The largest number of fields a struct can have and still be /// considered "small". /// @@ -533,6 +536,17 @@ class RegionStoreManager : public StoreManager { return FanOut + 1 /*for the default binding*/; }()) {} + Loc getLValueVar(const VarDecl *VD, const StackFrame *SF) override { + // Import before creating the region: the definition can complete an array + // type and becomes the declaration used to identify the region. + if (getOptions().IsNaiveCTUEnabled && VD->hasGlobalStorage() && + VD->getType().isConstQualified()) { + const VarDecl *InitializingDecl = VD; + getInitializer(InitializingDecl); + } + return StoreManager::getLValueVar(VD, SF); + } + /// setImplicitDefaultValue - Set the default binding for the provided /// MemRegion to the value implicitly defined for compound literals when /// the value is not specified. @@ -1861,6 +1875,34 @@ convertOffsetsFromSvalToUnsigneds(const SmallVector<SVal, 2> &SrcOffsets, return std::nullopt; } +const Expr *RegionStoreManager::getInitializer(const VarDecl *&VD) { + const Expr *Init = VD->getAnyInitializer(VD); + const AnalyzerOptions &Opts = getOptions(); + if (!Opts.IsNaiveCTUEnabled || !VD->hasGlobalStorage()) + return Init; + + auto &CTU = *StateMgr.getOwningEngine().getCrossTranslationUnitContext(); + if (!Init && (VD->hasExternalStorage() || VD->isStaticDataMember()) && + cross_tu::shouldImport(VD, Ctx)) { + auto Imported = CTU.getCrossTUDefinition(VD, Opts.CTUDir, Opts.CTUIndexName, + Opts.DisplayCTUProgress); + if (!Imported) { + llvm::handleAllErrors(Imported.takeError(), + [&](const cross_tu::IndexError &IE) { + CTU.emitCrossTUDiagnostics(IE, VD->getLocation()); + }); + return nullptr; + } + VD = *Imported; + Init = VD->getAnyInitializer(VD); + } + // Constant evaluation reads dependencies directly from the AST. + if (Init) + CTU.importReferencedInitializers(Init, Opts.CTUDir, Opts.CTUIndexName, + Opts.DisplayCTUProgress); + return Init; +} + std::optional<SVal> RegionStoreManager::getConstantValFromConstArrayInitializer( RegionBindingsConstRef B, const ElementRegion *R) { assert(R && "ElementRegion should not be null"); @@ -1893,7 +1935,7 @@ std::optional<SVal> RegionStoreManager::getConstantValFromConstArrayInitializer( // from which an initializer is obtained. We replace current `VD` with the new // `VD`. If the return value of the function is null than `VD` won't be // replaced. - const Expr *Init = VD->getAnyInitializer(VD); + const Expr *Init = getInitializer(VD); // NOTE: If `Init` is non-null, then a new `VD` is non-null for sure. So check // `Init` for null only and don't worry about the replaced `VD`. if (!Init) @@ -2135,7 +2177,7 @@ RegionStoreManager::getConstantValFromInitializer(const FieldRegion *R, if (!TrustInit) return std::nullopt; - const Expr *Init = VD->getAnyInitializer(); + const Expr *Init = getInitializer(VD); if (!Init) return std::nullopt; @@ -2416,7 +2458,7 @@ SVal RegionStoreManager::getBindingForVar(RegionBindingsConstRef B, // from its initializer. if (VD->getType().isConstQualified() || VD->getType()->isFunctionReferenceType()) { - if (const Expr *Init = VD->getAnyInitializer()) { + if (const Expr *Init = getInitializer(VD)) { if (std::optional<SVal> V = svalBuilder.getConstantVal(Init)) return *V; @@ -2436,7 +2478,7 @@ SVal RegionStoreManager::getBindingForVar(RegionBindingsConstRef B, // If we're in main(), then global initializers have not become stale yet. if (B.isMainAnalysis()) - if (const Expr *Init = VD->getAnyInitializer()) + if (const Expr *Init = getInitializer(VD)) if (std::optional<SVal> V = svalBuilder.getConstantVal(Init)) return *V; diff --git a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp index ce44fcd240ffa..faace7b38d23b 100644 --- a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp +++ b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp @@ -287,35 +287,6 @@ class AnalysisConsumer : public AnalysisASTConsumer, return true; } - bool VisitVarDecl(VarDecl *VD) override { - if (!Opts.IsNaiveCTUEnabled) - return true; - - if (VD->hasExternalStorage() || VD->isStaticDataMember()) { - if (!cross_tu::shouldImport(VD, *Ctx)) - return true; - } else { - // Cannot be initialized in another TU. - return true; - } - - if (VD->getAnyInitializer()) - return true; - - llvm::Expected<const VarDecl *> CTUDeclOrError = - CTU.getCrossTUDefinition(VD, Opts.CTUDir, Opts.CTUIndexName, - Opts.DisplayCTUProgress); - - if (!CTUDeclOrError) { - handleAllErrors(CTUDeclOrError.takeError(), - [&](const cross_tu::IndexError &IE) { - CTU.emitCrossTUDiagnostics(IE, VD->getLocation()); - }); - } - - return true; - } - bool VisitFunctionDecl(FunctionDecl *FD) override { IdentifierInfo *II = FD->getIdentifier(); if (II && II->getName().starts_with("__inline")) @@ -731,6 +702,11 @@ void AnalysisConsumer::HandleCode(Decl *D, AnalysisMode Mode, if (Mgr->getAnalysisDeclContext(D)->isBodyAutosynthesized()) return; + // CFG pruning and AST-based checks need these values before Store reads. + if (Opts.IsNaiveCTUEnabled && (Mode & AM_Syntax)) + CTU.importReferencedInitializers( + D->getBody(), Opts.CTUDir, Opts.CTUIndexName, Opts.DisplayCTUProgress); + CFG *DeclCFG = Mgr->getCFG(D); if (DeclCFG) MaxCFGSize.updateMax(DeclCFG->size()); diff --git a/clang/test/Analysis/ctu/Inputs/inherited-default-ctor-other.cpp b/clang/test/Analysis/ctu/Inputs/inherited-default-ctor-other.cpp index b529f416999da..f8267ef9ce026 100644 --- a/clang/test/Analysis/ctu/Inputs/inherited-default-ctor-other.cpp +++ b/clang/test/Analysis/ctu/Inputs/inherited-default-ctor-other.cpp @@ -21,6 +21,7 @@ class fun { }; class DeclContextLookupResult { static int *const SingleElementDummyList; + static int *get() { return SingleElementDummyList; } }; } // namespace clang using namespace clang; diff --git a/clang/test/Analysis/ctu/demanded-initializers.c b/clang/test/Analysis/ctu/demanded-initializers.c new file mode 100644 index 0000000000000..ff41955c20f4a --- /dev/null +++ b/clang/test/Analysis/ctu/demanded-initializers.c @@ -0,0 +1,115 @@ +// RUN: split-file %s %t +// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -std=c11 -emit-pch -o %t/unused.ast %t/unused.c +// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -std=c11 -emit-pch -o %t/values.ast %t/values.c +// +// DEFINE: %{analyze} = %clang_analyze_cc1 -triple x86_64-pc-linux-gnu -std=c11 \ +// DEFINE: -analyzer-checker=core,debug.ExprInspection,deadcode.DeadStores \ +// DEFINE: -analyzer-config eagerly-assume=false,experimental-enable-naive-ctu-analysis=true,ctu-dir=%t,ctu-import-threshold=1,ctu-phase1-inlining=all +// +// Each entry runs in a fresh process, so earlier imports cannot hide a failure. +// RUN: %{analyze} -analyze-function=function_import -verify=callee %t/main.c +// RUN: %{analyze} -analyze-function=constants -verify=constants %t/main.c +// RUN: %{analyze} -analyze-function=array_extent -verify=extent %t/main.c +// RUN: %{analyze} -analyze-function=cfg_constants -verify=cfg %t/main.c +// RUN: %{analyze} -analyze-function=unevaluated -verify=unevaluated %t/main.c +// RUN: %{analyze} -analyze-function=through_callee -verify=limited %t/main.c +// RUN: %{analyze} -analyzer-config ctu-import-threshold=2 -analyze-function=through_callee -verify=dependency %t/main.c +// RUN: %{analyze} -analyzer-config ctu-import-threshold=0 -analyze-function=disabled -verify=disabled %t/main.c +// RUN: %{analyze} -analyzer-config experimental-enable-naive-ctu-analysis=false -analyze-function=disabled -verify=disabled %t/main.c + +//--- main.c +void clang_analyzer_eval(int); +__SIZE_TYPE__ clang_analyzer_getExtent(const void *); +void observe(int); + +// An unused header declaration must not consume the one available AST slot. +extern const int unused; +extern const int value; +extern const char bytes[]; +struct Record { + struct { int field; } nested; +}; +extern const struct Record record; +int zero(void); +int read_dependency(void); +__SIZE_TYPE__ imported_extent(void); + +int function_import(void) { + return 7 / zero(); // callee-warning{{Division by zero}} +} + +void constants(void) { + clang_analyzer_eval(value == 7); // constants-warning{{TRUE}} + clang_analyzer_eval(bytes[1] == 0); // constants-warning{{TRUE}} + clang_analyzer_eval(record.nested.field == 5); // constants-warning{{TRUE}} +} + +void array_extent(void) { + // The definition is needed even before the first element is loaded. + clang_analyzer_eval(clang_analyzer_getExtent(bytes) == 2); // extent-warning{{TRUE}} +} + +void cfg_constants(void) { + int scalar; + scalar = 0; // cfg-warning{{Value stored to 'scalar' is never read}} + if (value == 7) + scalar = 1; + observe(scalar); + + int field; + field = 0; // cfg-warning{{Value stored to 'field' is never read}} + if (record.nested.field == 5) + field = 1; + observe(field); +} + +const int object_size = sizeof(unused); +int unevaluated(void) { + observe(sizeof(unused)); + // Resolving initializer dependencies must also skip unevaluated expressions. + clang_analyzer_eval(object_size == sizeof(int)); // unevaluated-warning{{TRUE}} + return 7 / zero(); // unevaluated-warning{{Division by zero}} +} + +void through_callee(void) { + // The constant is only referenced in the imported function. Its definition + // needs a second AST, and must still obey the shared import threshold. + clang_analyzer_eval(read_dependency() == 7); // dependency-warning{{TRUE}} limited-warning{{UNKNOWN}} + clang_analyzer_eval(imported_extent() == 2); // dependency-warning{{TRUE}} limited-warning{{UNKNOWN}} +} + +void disabled(void) { + clang_analyzer_eval(value == 7); // disabled-warning{{UNKNOWN}} +} + +//--- unused.c +const int unused = 123; +const int dependency = 7; +const char hidden_bytes[] = "a"; + +//--- values.c +const int value = 7; +const char bytes[] = "a"; +struct Record { + struct { int field; } nested; +}; +const struct Record record = {{5}}; +int zero(void) { return 0; } +extern const int dependency; +int read_dependency(void) { return dependency; } +__SIZE_TYPE__ clang_analyzer_getExtent(const void *); +extern const char hidden_bytes[]; +__SIZE_TYPE__ imported_extent(void) { + return clang_analyzer_getExtent(hidden_bytes); +} + +//--- externalDefMap.txt +9:c:@unused unused.ast +13:c:@dependency unused.ast +15:c:@hidden_bytes unused.ast +8:c:@value values.ast +8:c:@bytes values.ast +9:c:@record values.ast +9:c:@F@zero values.ast +20:c:@F@read_dependency values.ast +20:c:@F@imported_extent values.ast diff --git a/clang/test/Analysis/ctu/demanded-initializers.cpp b/clang/test/Analysis/ctu/demanded-initializers.cpp new file mode 100644 index 0000000000000..c264ce0b566f9 --- /dev/null +++ b/clang/test/Analysis/ctu/demanded-initializers.cpp @@ -0,0 +1,64 @@ +// RUN: split-file %s %t +// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -std=c++17 -emit-pch -o %t/unused.ast %t/unused.cpp +// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -std=c++17 -emit-pch -o %t/dependency.ast %t/dependency.cpp +// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -std=c++17 -emit-pch -o %t/dependent.ast %t/dependent.cpp +// +// DEFINE: %{analyze} = %clang_analyze_cc1 -triple x86_64-pc-linux-gnu -std=c++17 \ +// DEFINE: -analyzer-checker=core,debug.ExprInspection,deadcode.DeadStores \ +// DEFINE: -analyzer-config eagerly-assume=false,experimental-enable-naive-ctu-analysis=true,ctu-dir=%t,ctu-import-cpp-threshold=2,ctu-phase1-inlining=all +// +// RUN: %{analyze} -analyze-function="local_initializer()" -verify=local %t/main.cpp +// RUN: %{analyze} -analyze-function="imported_initializer()" -verify=imported %t/main.cpp +// RUN: %{analyze} -analyzer-config ctu-import-cpp-threshold=1 -analyze-function="constexpr_if()" -verify=cfg %t/main.cpp +// RUN: %{analyze} -analyzer-config ctu-import-cpp-threshold=1 -analyze-function="nested_body()" -verify=nested %t/main.cpp + +//--- main.cpp +void clang_analyzer_eval(bool); +void observe(int); +extern const int unused; +extern const int dependency; +extern const int dependent; + +const int local_dependent = dependency + 1; + +void local_initializer() { + clang_analyzer_eval(local_dependent == 8); // local-warning{{TRUE}} +} + +void imported_initializer() { + clang_analyzer_eval(dependent == 8); // imported-warning{{TRUE}} +} + +void constexpr_if() { + int value; + value = 0; // cfg-warning{{Value stored to 'value' is never read}} + if constexpr (int key = dependency; true) { + if (dependency == 7) + value = key; + } else { + observe(unused); + } + observe(value); +} + +void nested_body() { + // An uncalled lambda body must not consume the slot for dependency. + auto lambda = [] { return unused; }; + (void)lambda; + clang_analyzer_eval(dependency == 7); // nested-warning{{TRUE}} +} + +//--- unused.cpp +extern const int unused = 123; + +//--- dependency.cpp +extern const int dependency = 7; + +//--- dependent.cpp +extern const int dependency; +extern const int dependent = dependency + 1; + +//--- externalDefMap.txt +9:c:@unused unused.ast +13:c:@dependency dependency.ast +12:c:@dependent dependent.ast diff --git a/clang/test/Analysis/ctu/inherited-default-ctor.cpp b/clang/test/Analysis/ctu/inherited-default-ctor.cpp index 527b9410d833e..6298eae2c3f0b 100644 --- a/clang/test/Analysis/ctu/inherited-default-ctor.cpp +++ b/clang/test/Analysis/ctu/inherited-default-ctor.cpp @@ -24,5 +24,6 @@ namespace llvm {} namespace clang { class DeclContextLookupResult { static int *const SingleElementDummyList; + static int *get() { return SingleElementDummyList; } }; } // namespace clang diff --git a/clang/test/Analysis/ctu/test-import-failure.cpp b/clang/test/Analysis/ctu/test-import-failure.cpp index 3b5f9e71c2570..17714cfc06c26 100644 --- a/clang/test/Analysis/ctu/test-import-failure.cpp +++ b/clang/test/Analysis/ctu/test-import-failure.cpp @@ -28,8 +28,11 @@ namespace CommandLine { extern const int RootExamples[]; } +const int *useExamples() { + return CommandLine::RootExamples; +} + // expected-warning@Inputs/test-import-failure-import.cpp:14{{incompatible definitions}} // expected-warning@Inputs/test-import-failure-import.cpp:14{{incompatible definitions}} // expected-note@Inputs/test-import-failure-import.cpp:14{{no corresponding field here}} // expected-note@Inputs/test-import-failure-import.cpp:14{{no corresponding field here}} -// expected-warning@Inputs/test-import-failure-import.cpp:44{{import of an external symbol for CTU failed: Failed to import the definition.}} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
