https://github.com/bozicrHT updated https://github.com/llvm/llvm-project/pull/217608
From 9ef09e0c2a6f72c2ed0ff683c28e610d5fc8ed79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radovan=20Bo=C5=BEi=C4=87?= <[email protected]> Date: Thu, 20 Aug 2026 14:50:13 +0200 Subject: [PATCH 1/5] [clang][analyzer] Model function addresses in constant initializers Teach `SValBuilder::getConstantVal()` to represent direct function addresses using the existing `FunctionCodeRegion` SVal. This allows the analyzer to resolve calls through constant function pointers and preserve caller constraints. --- clang/lib/StaticAnalyzer/Core/SValBuilder.cpp | 6 ++ .../test/Analysis/constant-function-pointer.c | 80 +++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 clang/test/Analysis/constant-function-pointer.c diff --git a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp index 57c97b2852445..54b4d9cd4dfed 100644 --- a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp +++ b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp @@ -326,6 +326,12 @@ loc::MemRegionVal SValBuilder::getCXXThis(const CXXRecordDecl *D, std::optional<SVal> SValBuilder::getConstantVal(const Expr *E) { E = E->IgnoreParens(); + if (E->getType()->isFunctionPointerType()) { + if (const auto *FD = + dyn_cast_or_null<FunctionDecl>(E->getReferencedDeclOfCallee())) + return getFunctionPointer(FD); + } + switch (E->getStmtClass()) { // Handle expressions that we treat differently from the AST's constant // evaluator. diff --git a/clang/test/Analysis/constant-function-pointer.c b/clang/test/Analysis/constant-function-pointer.c new file mode 100644 index 0000000000000..dcd12eb1122e9 --- /dev/null +++ b/clang/test/Analysis/constant-function-pointer.c @@ -0,0 +1,80 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection \ +// RUN: -analyze-function=test_function_pointer_forms -verify=forms %s +// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection \ +// RUN: -analyze-function=test_mutable_pointer -verify=mutable %s +// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection \ +// RUN: -analyze-function=test_no_false_positive -verify=fp %s + +void clang_analyzer_checkInlined(int); +void clang_analyzer_eval(int); + +typedef void (*callback)(unsigned); + +static void decay_target(unsigned value) { + clang_analyzer_checkInlined(value == 1); // forms-warning{{TRUE}} +} + +static callback const decayCallback = decay_target; + +static void address_target(unsigned value) { + clang_analyzer_checkInlined(value == 2); // forms-warning{{TRUE}} +} + +static callback const addressCallback = &address_target; + +static void cast_target(unsigned value) { + clang_analyzer_checkInlined(value == 3); // forms-warning{{TRUE}} +} + +static callback const castCallback = (callback)cast_target; + +void test_function_pointer_forms(void) { + clang_analyzer_eval(decayCallback == decay_target); // forms-warning{{TRUE}} + decay_target(1); + + clang_analyzer_eval(addressCallback == address_target); // forms-warning{{TRUE}} + address_target(2); + + clang_analyzer_eval(castCallback == cast_target); // forms-warning{{TRUE}} + castCallback(3); +} + +static callback mutableCallback = decay_target; + +void test_mutable_pointer(void) { + clang_analyzer_eval(mutableCallback == decay_target); // mutable-warning{{UNKNOWN}} +} + +struct St { + int f; +}; + +static struct St sts[4]; + +static void helper(unsigned id) { + struct St *s = 0; + + if (id < 1) + return; + if (id < 5) + s = &sts[id-1]; + else if (id == 5) + return; + + s->f = 60; +} + +static void notify(unsigned id) { + clang_analyzer_checkInlined(id >= 1 && id <= 4); // fp-warning{{TRUE}} + helper(id); +} + +static callback const callbackFn = notify; + +void test_no_false_positive(unsigned id) { + if (id < 1 || id > 4) + return; + + clang_analyzer_eval(callbackFn == notify); // fp-warning{{TRUE}} + callbackFn(id); +} From 7c98a3ca6537a8c2fb73d5aab9b531fcc2351002 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radovan=20Bo=C5=BEi=C4=87?= <[email protected]> Date: Mon, 24 Aug 2026 16:43:11 +0200 Subject: [PATCH 2/5] Fix function references in constant initializers --- clang/lib/StaticAnalyzer/Core/MemRegion.cpp | 4 +- clang/lib/StaticAnalyzer/Core/RegionStore.cpp | 12 +-- clang/lib/StaticAnalyzer/Core/SValBuilder.cpp | 5 +- .../test/Analysis/constant-function-pointer.c | 80 ------------------- .../Analysis/constant-function-pointer.cpp | 64 +++++++++++++++ 5 files changed, 78 insertions(+), 87 deletions(-) delete mode 100644 clang/test/Analysis/constant-function-pointer.c create mode 100644 clang/test/Analysis/constant-function-pointer.cpp diff --git a/clang/lib/StaticAnalyzer/Core/MemRegion.cpp b/clang/lib/StaticAnalyzer/Core/MemRegion.cpp index 9f27358381738..f57727b50d625 100644 --- a/clang/lib/StaticAnalyzer/Core/MemRegion.cpp +++ b/clang/lib/StaticAnalyzer/Core/MemRegion.cpp @@ -1102,7 +1102,9 @@ const VarRegion *MemRegionManager::getVarRegion(const VarDecl *D, if (D->hasGlobalStorage() && !D->isStaticLocal()) { QualType Ty = D->getType(); assert(!Ty.isNull()); - if (Ty.isConstQualified()) { + // A function reference's binding cannot be changed after initialization, + // even though reference types themselves are never const-qualified. + if (Ty.isConstQualified() || Ty->isFunctionReferenceType()) { sReg = getGlobalsRegion(MemRegion::GlobalImmutableSpaceRegionKind); } else { // Pointer value of C standard streams is usually not modified by calls diff --git a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp index 01c792a9011f9..72582a93da9d8 100644 --- a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp +++ b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp @@ -2411,15 +2411,17 @@ SVal RegionStoreManager::getBindingForVar(RegionBindingsConstRef B, if (isa<StackArgumentsSpaceRegion>(MS)) return svalBuilder.getRegionValueSymbolVal(R); - // Is 'VD' declared constant? If so, retrieve the constant value. - if (VD->getType().isConstQualified()) { + // Is 'VD' declared constant, or is it a global function reference whose + // binding is necessarily immutable? If so, retrieve the value from its + // initializer. + if (VD->getType().isConstQualified() || + (VD->hasGlobalStorage() && VD->getType()->isFunctionReferenceType())) { if (const Expr *Init = VD->getAnyInitializer()) { if (std::optional<SVal> V = svalBuilder.getConstantVal(Init)) return *V; - // If the variable is const qualified and has an initializer but - // we couldn't evaluate initializer to a value, treat the value as - // unknown. + // If the variable has an immutable binding and an initializer but we + // couldn't evaluate the initializer, treat the value as unknown. return UnknownVal(); } } diff --git a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp index 54b4d9cd4dfed..7366f1315c9fc 100644 --- a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp +++ b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp @@ -326,7 +326,10 @@ loc::MemRegionVal SValBuilder::getCXXThis(const CXXRecordDecl *D, std::optional<SVal> SValBuilder::getConstantVal(const Expr *E) { E = E->IgnoreParens(); - if (E->getType()->isFunctionPointerType()) { + // A function used as a constant initializer can either decay to a function + // pointer or bind directly to a function reference. + if (E->getType()->isFunctionPointerType() || + E->getType()->isFunctionType()) { if (const auto *FD = dyn_cast_or_null<FunctionDecl>(E->getReferencedDeclOfCallee())) return getFunctionPointer(FD); diff --git a/clang/test/Analysis/constant-function-pointer.c b/clang/test/Analysis/constant-function-pointer.c deleted file mode 100644 index dcd12eb1122e9..0000000000000 --- a/clang/test/Analysis/constant-function-pointer.c +++ /dev/null @@ -1,80 +0,0 @@ -// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection \ -// RUN: -analyze-function=test_function_pointer_forms -verify=forms %s -// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection \ -// RUN: -analyze-function=test_mutable_pointer -verify=mutable %s -// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection \ -// RUN: -analyze-function=test_no_false_positive -verify=fp %s - -void clang_analyzer_checkInlined(int); -void clang_analyzer_eval(int); - -typedef void (*callback)(unsigned); - -static void decay_target(unsigned value) { - clang_analyzer_checkInlined(value == 1); // forms-warning{{TRUE}} -} - -static callback const decayCallback = decay_target; - -static void address_target(unsigned value) { - clang_analyzer_checkInlined(value == 2); // forms-warning{{TRUE}} -} - -static callback const addressCallback = &address_target; - -static void cast_target(unsigned value) { - clang_analyzer_checkInlined(value == 3); // forms-warning{{TRUE}} -} - -static callback const castCallback = (callback)cast_target; - -void test_function_pointer_forms(void) { - clang_analyzer_eval(decayCallback == decay_target); // forms-warning{{TRUE}} - decay_target(1); - - clang_analyzer_eval(addressCallback == address_target); // forms-warning{{TRUE}} - address_target(2); - - clang_analyzer_eval(castCallback == cast_target); // forms-warning{{TRUE}} - castCallback(3); -} - -static callback mutableCallback = decay_target; - -void test_mutable_pointer(void) { - clang_analyzer_eval(mutableCallback == decay_target); // mutable-warning{{UNKNOWN}} -} - -struct St { - int f; -}; - -static struct St sts[4]; - -static void helper(unsigned id) { - struct St *s = 0; - - if (id < 1) - return; - if (id < 5) - s = &sts[id-1]; - else if (id == 5) - return; - - s->f = 60; -} - -static void notify(unsigned id) { - clang_analyzer_checkInlined(id >= 1 && id <= 4); // fp-warning{{TRUE}} - helper(id); -} - -static callback const callbackFn = notify; - -void test_no_false_positive(unsigned id) { - if (id < 1 || id > 4) - return; - - clang_analyzer_eval(callbackFn == notify); // fp-warning{{TRUE}} - callbackFn(id); -} diff --git a/clang/test/Analysis/constant-function-pointer.cpp b/clang/test/Analysis/constant-function-pointer.cpp new file mode 100644 index 0000000000000..603e04b1bd6c9 --- /dev/null +++ b/clang/test/Analysis/constant-function-pointer.cpp @@ -0,0 +1,64 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection \ +// RUN: -verify %s + +void clang_analyzer_checkInlined(bool); +void clang_analyzer_eval(bool); + +typedef void (*Callback)(unsigned); +typedef void (&CallbackRef)(unsigned); + +static int Storage; + +static void pointerTarget(unsigned Value) { + int *Ptr = nullptr; + + if (Value == 1) + Ptr = &Storage; + + clang_analyzer_checkInlined(Value == 1); // expected-warning{{TRUE}} + *Ptr = 0; +} + +static void referenceTarget(unsigned Value) { + int *Ptr = nullptr; + + if (Value == 1) + Ptr = &Storage; + + clang_analyzer_checkInlined(Value == 1); // expected-warning{{TRUE}} + *Ptr = 0; +} + +static Callback const ConstPointer = pointerTarget; +static Callback const AddressPointer = &pointerTarget; +static Callback const CastPointer = (Callback)pointerTarget; +static Callback MutablePointer = pointerTarget; +static CallbackRef Reference = referenceTarget; + +extern CallbackRef ExternalReference; + +void testPointers(unsigned Value) { + if (Value != 1) + return; + + clang_analyzer_eval(ConstPointer == pointerTarget); // expected-warning{{TRUE}} + ConstPointer(Value); + clang_analyzer_eval(AddressPointer == pointerTarget); // expected-warning{{TRUE}} + clang_analyzer_eval(CastPointer == pointerTarget); // expected-warning{{TRUE}} + clang_analyzer_eval(MutablePointer == pointerTarget); // expected-warning{{UNKNOWN}} +} + +void testReference(unsigned Value) { + if (Value != 1) + return; + + clang_analyzer_eval(Reference == referenceTarget); // expected-warning{{TRUE}} + Reference(Value); +} + +void testExternalReference() { + clang_analyzer_eval(ExternalReference == referenceTarget); // expected-warning{{UNKNOWN}} + + Callback Before = ExternalReference; + clang_analyzer_eval(ExternalReference == Before); // expected-warning{{TRUE}} +} From 9d63ff2e1da14095a7996987f3cd7b690aef20b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radovan=20Bo=C5=BEi=C4=87?= <[email protected]> Date: Mon, 24 Aug 2026 19:55:52 +0200 Subject: [PATCH 3/5] Fix formatting --- clang/lib/StaticAnalyzer/Core/SValBuilder.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp index 7366f1315c9fc..6e832f1d27e77 100644 --- a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp +++ b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp @@ -328,8 +328,7 @@ std::optional<SVal> SValBuilder::getConstantVal(const Expr *E) { // A function used as a constant initializer can either decay to a function // pointer or bind directly to a function reference. - if (E->getType()->isFunctionPointerType() || - E->getType()->isFunctionType()) { + if (E->getType()->isFunctionPointerType() || E->getType()->isFunctionType()) { if (const auto *FD = dyn_cast_or_null<FunctionDecl>(E->getReferencedDeclOfCallee())) return getFunctionPointer(FD); From 0ec2e3c277de387d03f3f6e37bdb67f8b2347f23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radovan=20Bo=C5=BEi=C4=87?= <[email protected]> Date: Tue, 25 Aug 2026 17:53:28 +0200 Subject: [PATCH 4/5] Fix code according to suggestions --- clang/lib/StaticAnalyzer/Core/RegionStore.cpp | 8 ++--- .../Analysis/constant-function-pointer.cpp | 32 ++++++++++++------- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp index 72582a93da9d8..6994917470ce8 100644 --- a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp +++ b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp @@ -2411,11 +2411,11 @@ SVal RegionStoreManager::getBindingForVar(RegionBindingsConstRef B, if (isa<StackArgumentsSpaceRegion>(MS)) return svalBuilder.getRegionValueSymbolVal(R); - // Is 'VD' declared constant, or is it a global function reference whose - // binding is necessarily immutable? If so, retrieve the value from its - // initializer. + // Is 'VD' declared constant, or is it a function reference whose + // binding is necessarily immutable? If so, retrieve the value + // from its initializer. if (VD->getType().isConstQualified() || - (VD->hasGlobalStorage() && VD->getType()->isFunctionReferenceType())) { + VD->getType()->isFunctionReferenceType()) { if (const Expr *Init = VD->getAnyInitializer()) { if (std::optional<SVal> V = svalBuilder.getConstantVal(Init)) return *V; diff --git a/clang/test/Analysis/constant-function-pointer.cpp b/clang/test/Analysis/constant-function-pointer.cpp index 603e04b1bd6c9..7d970ba0c9b43 100644 --- a/clang/test/Analysis/constant-function-pointer.cpp +++ b/clang/test/Analysis/constant-function-pointer.cpp @@ -1,31 +1,32 @@ // RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection \ // RUN: -verify %s -void clang_analyzer_checkInlined(bool); +void clang_analyzer_dump(const char *); +void clang_analyzer_dump(const bool); void clang_analyzer_eval(bool); -typedef void (*Callback)(unsigned); -typedef void (&CallbackRef)(unsigned); +using Callback = void (*)(const char *); +using CallbackRef = void (&)(const char *); static int Storage; -static void pointerTarget(unsigned Value) { +static void pointerTarget(const char *Value) { int *Ptr = nullptr; - if (Value == 1) + if (Value) Ptr = &Storage; - clang_analyzer_checkInlined(Value == 1); // expected-warning{{TRUE}} + clang_analyzer_dump(Value); // expected-warning{{"pointer"}} *Ptr = 0; } -static void referenceTarget(unsigned Value) { +static void referenceTarget(const char *Value) { int *Ptr = nullptr; - if (Value == 1) + if (Value) Ptr = &Storage; - clang_analyzer_checkInlined(Value == 1); // expected-warning{{TRUE}} + clang_analyzer_dump(Value); // expected-warning{{"reference"}} *Ptr = 0; } @@ -37,12 +38,15 @@ static CallbackRef Reference = referenceTarget; extern CallbackRef ExternalReference; +void myGlobalFn(); +static const bool Truthy = &myGlobalFn; + void testPointers(unsigned Value) { if (Value != 1) return; clang_analyzer_eval(ConstPointer == pointerTarget); // expected-warning{{TRUE}} - ConstPointer(Value); + ConstPointer("pointer"); clang_analyzer_eval(AddressPointer == pointerTarget); // expected-warning{{TRUE}} clang_analyzer_eval(CastPointer == pointerTarget); // expected-warning{{TRUE}} clang_analyzer_eval(MutablePointer == pointerTarget); // expected-warning{{UNKNOWN}} @@ -53,7 +57,7 @@ void testReference(unsigned Value) { return; clang_analyzer_eval(Reference == referenceTarget); // expected-warning{{TRUE}} - Reference(Value); + Reference("reference"); } void testExternalReference() { @@ -62,3 +66,9 @@ void testExternalReference() { Callback Before = ExternalReference; clang_analyzer_eval(ExternalReference == Before); // expected-warning{{TRUE}} } + +// Verify that a function pointer converted to ool is modeled as `true`, +// not as FunctionCodeRegion. +void testBoolInitializer() { + clang_analyzer_dump(Truthy); // expected-warning{{1 U1b}} +} From 22ab04377886b705255603cbf0e1f17efc360da8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radovan=20Bo=C5=BEi=C4=87?= <[email protected]> Date: Tue, 25 Aug 2026 17:58:24 +0200 Subject: [PATCH 5/5] Fix typo --- clang/test/Analysis/constant-function-pointer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clang/test/Analysis/constant-function-pointer.cpp b/clang/test/Analysis/constant-function-pointer.cpp index 7d970ba0c9b43..b7e401ced3724 100644 --- a/clang/test/Analysis/constant-function-pointer.cpp +++ b/clang/test/Analysis/constant-function-pointer.cpp @@ -67,8 +67,8 @@ void testExternalReference() { clang_analyzer_eval(ExternalReference == Before); // expected-warning{{TRUE}} } -// Verify that a function pointer converted to ool is modeled as `true`, -// not as FunctionCodeRegion. +// Verify that a function pointer converted to bool +// is modeled as `true`, not as FunctionCodeRegion. void testBoolInitializer() { clang_analyzer_dump(Truthy); // expected-warning{{1 U1b}} } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
