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 1/4] [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 dd7eceef35b7f2..c40d536d7ddaff 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 4ef7a1fc4bf7a0..7dac3e945f3d25 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 6994917470ce84..e0f22112f9c39a 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 ce44fcd240ffad..faace7b38d23bc 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 b529f416999daf..f8267ef9ce026b 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 00000000000000..ff41955c20f4ac --- /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 00000000000000..c264ce0b566f9e --- /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 527b9410d833e0..6298eae2c3f0b6 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 3b5f9e71c25707..17714cfc06c265 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.}} >From 5a07ef7aa2970123907df8673f08a20dbfe6235c Mon Sep 17 00:00:00 2001 From: relaxcn <[email protected]> Date: Wed, 14 Oct 2026 14:01:42 +0800 Subject: [PATCH 2/4] [analyzer] Clarify CTU initializer lookup Keep declaration selection explicit and test statement initializer imports. --- clang/lib/CrossTU/CrossTranslationUnit.cpp | 2 +- clang/lib/StaticAnalyzer/Core/RegionStore.cpp | 38 +++++--------- .../Analysis/ctu/demanded-initializers.cpp | 49 +++++++++++++++++++ 3 files changed, 63 insertions(+), 26 deletions(-) diff --git a/clang/lib/CrossTU/CrossTranslationUnit.cpp b/clang/lib/CrossTU/CrossTranslationUnit.cpp index 7dac3e945f3d25..b7fdeeda334f0c 100644 --- a/clang/lib/CrossTU/CrossTranslationUnit.cpp +++ b/clang/lib/CrossTU/CrossTranslationUnit.cpp @@ -463,7 +463,7 @@ void CrossTranslationUnitContext::importReferencedInitializers( void VisitIfStmt(const IfStmt *If) { if (auto Selected = If->getNondiscardedCase(Context)) { - // The init-statement executes even when a body is discarded. + // Preserve the init-statement when skipping a discarded constexpr body. if (const Stmt *Init = If->getInit()) Visit(Init); if (const Stmt *ConditionVar = If->getConditionVariableDeclStmt()) diff --git a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp index e0f22112f9c39a..9bb73e1f88caa7 100644 --- a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp +++ b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp @@ -484,7 +484,7 @@ class RegionStoreManager : public StoreManager { SValListTy> LazyBindingsMapTy; LazyBindingsMapTy LazyBindingsMap; - const Expr *getInitializer(const VarDecl *&VD); + const Expr *getOrImportInitializer(const VarDecl *VD); /// The largest number of fields a struct can have and still be /// considered "small". @@ -539,11 +539,8 @@ class RegionStoreManager : public StoreManager { 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); - } + if (VD->getType().isConstQualified()) + getOrImportInitializer(VD); return StoreManager::getLValueVar(VD, SF); } @@ -1875,7 +1872,7 @@ convertOffsetsFromSvalToUnsigneds(const SmallVector<SVal, 2> &SrcOffsets, return std::nullopt; } -const Expr *RegionStoreManager::getInitializer(const VarDecl *&VD) { +const Expr *RegionStoreManager::getOrImportInitializer(const VarDecl *VD) { const Expr *Init = VD->getAnyInitializer(VD); const AnalyzerOptions &Opts = getOptions(); if (!Opts.IsNaiveCTUEnabled || !VD->hasGlobalStorage()) @@ -1926,24 +1923,15 @@ std::optional<SVal> RegionStoreManager::getConstantValFromConstArrayInitializer( (!B.isMainAnalysis() || !VD->hasGlobalStorage())) return std::nullopt; - // Array's declaration should have `ConstantArrayType` type, because only this - // type contains an array extent. It may happen that array type can be of - // `IncompleteArrayType` type. To get the declaration of `ConstantArrayType` - // type, we should find the declaration in the redeclarations chain that has - // the initialization expression. - // NOTE: `getAnyInitializer` has an out-parameter, which returns a new `VD` - // 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 = 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`. + const Expr *Init = getOrImportInitializer(VD); if (!Init) return std::nullopt; - // Array's declaration should have ConstantArrayType type, because only this - // type contains an array extent. - const ConstantArrayType *CAT = Ctx.getAsConstantArrayType(VD->getType()); + // The initializing declaration may have completed an incomplete array type. + const VarDecl *InitializingVD; + VD->getAnyInitializer(InitializingVD); + const ConstantArrayType *CAT = + Ctx.getAsConstantArrayType(InitializingVD->getType()); if (!CAT) return std::nullopt; @@ -2177,7 +2165,7 @@ RegionStoreManager::getConstantValFromInitializer(const FieldRegion *R, if (!TrustInit) return std::nullopt; - const Expr *Init = getInitializer(VD); + const Expr *Init = getOrImportInitializer(VD); if (!Init) return std::nullopt; @@ -2458,7 +2446,7 @@ SVal RegionStoreManager::getBindingForVar(RegionBindingsConstRef B, // from its initializer. if (VD->getType().isConstQualified() || VD->getType()->isFunctionReferenceType()) { - if (const Expr *Init = getInitializer(VD)) { + if (const Expr *Init = getOrImportInitializer(VD)) { if (std::optional<SVal> V = svalBuilder.getConstantVal(Init)) return *V; @@ -2478,7 +2466,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 = getInitializer(VD)) + if (const Expr *Init = getOrImportInitializer(VD)) if (std::optional<SVal> V = svalBuilder.getConstantVal(Init)) return *V; diff --git a/clang/test/Analysis/ctu/demanded-initializers.cpp b/clang/test/Analysis/ctu/demanded-initializers.cpp index c264ce0b566f9e..172be806eac28b 100644 --- a/clang/test/Analysis/ctu/demanded-initializers.cpp +++ b/clang/test/Analysis/ctu/demanded-initializers.cpp @@ -7,12 +7,27 @@ // 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 // +// DEFINE: %{analyze_syntax} = %clang_cc1 -analyze -triple x86_64-pc-linux-gnu -std=c++17 \ +// DEFINE: -analyzer-checker=deadcode.DeadStores \ +// DEFINE: -analyzer-config experimental-enable-naive-ctu-analysis=true,ctu-dir=%t,ctu-import-cpp-threshold=1,display-ctu-progress=true +// // 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 +// No path checkers: a Store read must not hide a missed initializer. +// RUN: %{analyze_syntax} -analyze-function="constexpr_if_initializer()" -verify=syntax %t/main.cpp 2>&1 | FileCheck %s --check-prefix=IMPORT +// RUN: %{analyze_syntax} -analyze-function="if_initializer()" -verify=syntax %t/main.cpp 2>&1 | FileCheck %s --check-prefix=IMPORT +// RUN: %{analyze_syntax} -analyze-function="for_initializer()" -verify=syntax %t/main.cpp 2>&1 | FileCheck %s --check-prefix=IMPORT +// RUN: %{analyze_syntax} -analyze-function="switch_initializer()" -verify=syntax %t/main.cpp 2>&1 | FileCheck %s --check-prefix=IMPORT +// RUN: %{analyze_syntax} -analyze-function="while_condition()" -verify=syntax %t/main.cpp 2>&1 | FileCheck %s --check-prefix=IMPORT +// IMPORT-NOT: CTU loaded AST file: +// IMPORT: CTU loaded AST file: {{.*}}dependency.ast +// IMPORT-NOT: CTU loaded AST file: + //--- main.cpp +// syntax-no-diagnostics void clang_analyzer_eval(bool); void observe(int); extern const int unused; @@ -41,6 +56,40 @@ void constexpr_if() { observe(value); } +void constexpr_if_initializer() { + if constexpr (int key = dependency; true) + observe(key); + else + observe(unused); +} + +void if_initializer() { + if (int key = dependency; key == 7) + observe(key); +} + +void for_initializer() { + for (int key = dependency; key == 7;) { + observe(key); + break; + } +} + +void switch_initializer() { + switch (int key = dependency; key) { + case 7: + observe(key); + break; + } +} + +void while_condition() { + while (int key = dependency) { + observe(key); + break; + } +} + void nested_body() { // An uncalled lambda body must not consume the slot for dependency. auto lambda = [] { return unused; }; >From 2e91d01794dbb20767d67a8ba4dceeb91bef86ba Mon Sep 17 00:00:00 2001 From: relaxcn <[email protected]> Date: Thu, 15 Oct 2026 10:44:19 +0800 Subject: [PATCH 3/4] [analyzer] Clarify CTU import comment --- clang/lib/StaticAnalyzer/Core/RegionStore.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp index 9bb73e1f88caa7..857d3a6fdcc8f3 100644 --- a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp +++ b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp @@ -537,8 +537,9 @@ class RegionStoreManager : public StoreManager { }()) {} 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. + // Consider importing from another translation unit before creating the region: + // the definition can complete an array type and becomes the declaration used + // to identify the region. if (VD->getType().isConstQualified()) getOrImportInitializer(VD); return StoreManager::getLValueVar(VD, SF); >From 6d37da3b2db7619b69c24ba0109ba4b4c3006c58 Mon Sep 17 00:00:00 2001 From: relaxcn <[email protected]> Date: Thu, 15 Oct 2026 10:58:31 +0800 Subject: [PATCH 4/4] [analyzer] Explain if constexpr handling --- clang/lib/CrossTU/CrossTranslationUnit.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clang/lib/CrossTU/CrossTranslationUnit.cpp b/clang/lib/CrossTU/CrossTranslationUnit.cpp index b7fdeeda334f0c..a9d8b25fab666c 100644 --- a/clang/lib/CrossTU/CrossTranslationUnit.cpp +++ b/clang/lib/CrossTU/CrossTranslationUnit.cpp @@ -461,9 +461,10 @@ void CrossTranslationUnitContext::importReferencedInitializers( Visit(E->getBase()); } + // Handle 'if constexpr' specially to skip its discarded branch while still + // visiting the init-statement and condition. void VisitIfStmt(const IfStmt *If) { if (auto Selected = If->getNondiscardedCase(Context)) { - // Preserve the init-statement when skipping a discarded constexpr body. if (const Stmt *Init = If->getInit()) Visit(Init); if (const Stmt *ConditionVar = If->getConditionVariableDeclStmt()) _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
