Skip to content

[clang][analyzer] Correctly handle lambda-converted function pointers #144906

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

flovent
Copy link
Contributor

@flovent flovent commented Jun 19, 2025

For lambdas that are converted to C function pointers,

int (*ret_zero)() = []() { return 0; };

clang will generate conversion method like:

CXXConversionDecl implicit used constexpr operator int (*)() 'int (*() const noexcept)()' inline
 -CompoundStmt
   -ReturnStmt
    -ImplicitCastExpr 'int (*)()' <FunctionToPointerDecay>
     -DeclRefExpr 'int ()' lvalue CXXMethod 0x5ddb6fe35b18 '__invoke' 'int ()'
-CXXMethodDecl implicit used __invoke 'int ()' static inline
 -CompoundStmt (empty)

Based on comment in Sema, __invoke's function body is left empty because it's will be filled in CodeGen, so in AST analysis phase we should get lambda's operator() directly instead of calling __invoke itself.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:static analyzer labels Jun 19, 2025
@llvmbot
Copy link
Member

llvmbot commented Jun 19, 2025

@llvm/pr-subscribers-clang

@llvm/pr-subscribers-clang-static-analyzer-1

Author: None (flovent)

Changes

For lambdas that are converted to C function pointers,

int (*ret_zero)() = []() { return 0; };

clang will generate conversion method like:

CXXConversionDecl implicit used constexpr operator int (*)() 'int (*() const noexcept)()' inline
 -CompoundStmt
   -ReturnStmt
    -ImplicitCastExpr 'int (*)()' &lt;FunctionToPointerDecay&gt;
     -DeclRefExpr 'int ()' lvalue CXXMethod 0x5ddb6fe35b18 '__invoke' 'int ()'
-CXXMethodDecl implicit used __invoke 'int ()' static inline
 -CompoundStmt (empty)

Based on comment in Sema, __invoke's function body is left empty because it's will be filled in CodeGen, so in AST analysis phase we should get lambda's operator() directly instead of calling __invoke itself.


Full diff: https://github.com/llvm/llvm-project/pull/144906.diff

3 Files Affected:

  • (modified) clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h (+2)
  • (modified) clang/lib/StaticAnalyzer/Core/CallEvent.cpp (+12)
  • (added) clang/test/Analysis/lambda-convert-to-func-ptr.cpp (+21)
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h
index f6a43bf5f493b..5dcf03f7a4648 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h
@@ -554,6 +554,8 @@ class SimpleFunctionCall : public AnyFunctionCall {
 
   const FunctionDecl *getDecl() const override;
 
+  RuntimeDefinition getRuntimeDefinition() const override;
+
   unsigned getNumArgs() const override { return getOriginExpr()->getNumArgs(); }
 
   const Expr *getArgExpr(unsigned Index) const override {
diff --git a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp
index f78b1b84f9df6..34fcb9b64d555 100644
--- a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp
+++ b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp
@@ -688,6 +688,18 @@ const FunctionDecl *SimpleFunctionCall::getDecl() const {
   return getSVal(getOriginExpr()->getCallee()).getAsFunctionDecl();
 }
 
+RuntimeDefinition SimpleFunctionCall::getRuntimeDefinition() const {
+  // Clang converts lambdas to function pointers using an implicit conversion
+  // operator, which returns the lambda's '__invoke' method. However, Sema
+  // leaves the body of '__invoke' empty (it is generated later in CodeGen), so
+  // we need to skip '__invoke' and access the lambda's operator() directly.
+  if (const auto *CMD = dyn_cast_if_present<CXXMethodDecl>(getDecl());
+      CMD && CMD->isLambdaStaticInvoker())
+    return RuntimeDefinition{CMD->getParent()->getLambdaCallOperator()};
+
+  return AnyFunctionCall::getRuntimeDefinition();
+}
+
 const FunctionDecl *CXXInstanceCall::getDecl() const {
   const auto *CE = cast_or_null<CallExpr>(getOriginExpr());
   if (!CE)
diff --git a/clang/test/Analysis/lambda-convert-to-func-ptr.cpp b/clang/test/Analysis/lambda-convert-to-func-ptr.cpp
new file mode 100644
index 0000000000000..c2ad7cd2de34a
--- /dev/null
+++ b/clang/test/Analysis/lambda-convert-to-func-ptr.cpp
@@ -0,0 +1,21 @@
+// RUN: %clang_analyze_cc1 -std=c++11 -analyzer-checker=core,debug.ExprInspection -analyzer-config inline-lambdas=true -verify %s
+
+void clang_analyzer_eval(bool);
+
+void basic() {
+  int (*ret_zero)() = []() { return 0; };
+  clang_analyzer_eval(ret_zero() == 0); // expected-warning{{TRUE}}
+}
+
+void withParam() {
+  int (*add_ten)(int) = [](int b) { return b + 10; };
+  clang_analyzer_eval(add_ten(1) == 11); // expected-warning{{TRUE}}
+}
+
+int callBack(int (*fp)(int), int x) {
+  return fp(x);
+}
+
+void passWithFunc() {
+  clang_analyzer_eval(callBack([](int x) { return x; }, 5) == 5); // expected-warning{{TRUE}}
+}

@steakhal
Copy link
Contributor

Hey, looks like a nice patch!

I only have one question. Have you considered overriding some other getRuntimeDefinition too? When I'm looking at the CallEvent inheritance graph, there could be a couple other options too.
Don't get me wrong, the SimpleFunctionCall::getRuntimeDefinition looks like the right place. I'm just curious.

@firewave
Copy link

The same occurs with assigning to auto: https://godbolt.org/z/nofG6cehf

Will this also be handled by this change?

@flovent
Copy link
Contributor Author

flovent commented Jun 20, 2025

I only have one question. Have you considered overriding some other getRuntimeDefinition too? When I'm looking at the CallEvent inheritance graph, there could be a couple other options too. Don't get me wrong, the SimpleFunctionCall::getRuntimeDefinition looks like the right place. I'm just curious.

As far as i know, __invoke will always be a normal static member method for lambda, and static member method will be modeled as SimpleFunctionCall in analyzer, other derived classes of same level seems to for memory-allocation related (CXXAllocatorCall, CXXDeallocatorCall), or class-instance related (AnyCXXConstructorCall, CXXInstanceCall)

@flovent
Copy link
Contributor Author

flovent commented Jun 20, 2025

The same occurs with assigning to auto: https://godbolt.org/z/nofG6cehf

Will this also be handled by this change?

If you mean directly calling this lambda, analyzer can already analyze operator() correctly before this patch, because neither CXXConversionDecl and __invoke is used here.

This patch fix the situation like this: https://godbolt.org/z/4YWPP6939

void f()
{
    auto f = []() { return 0; };
    int (*ptr)() = f;
    1 / ptr();
}

lambda f should be called indirecly from function pointer ptr and produce divzero.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:static analyzer clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants