Skip to content

Conversation

fmayer
Copy link
Contributor

@fmayer fmayer commented Oct 10, 2025

This builtin can be used by user code to communicate with static
analyis tools (e.g. clang-tidy or clang-static-analyzer). Because the
arguments are unevaluated, it is suitable for use in macros, where
evaluating the same expression multiple times can change program
semantics.

RFC: https://discourse.llvm.org/t/rfc-builtin-static-analysis-assume/88544

Created using spr 1.3.4
@steakhal
Copy link
Contributor

I'm not exactly sure how deep you want to go in the first PR, but I'd consider arming the Clang CFG to expect this new builtin.
That would bring (almost) out of the box support for it in clang-tidy and CSA.
At that point I'd also add a CFG dump test to ensure that the new builtin produces the same CFG as existing counterparts.

I was reluctant to directly push to your branch given that this seems like an spr branch, so here is the git diff.

diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h
index e1a4005d1a89..db91ac2403d3 100644
--- a/clang/include/clang/AST/Expr.h
+++ b/clang/include/clang/AST/Expr.h
@@ -3250,8 +3250,9 @@ private:
   }
 
 public:
-  /// Return true if this is a call to __assume() or __builtin_assume() with
-  /// a non-value-dependent constant parameter evaluating as false.
+  /// Return true if this is a call to __assume(), __builtin_assume() or
+  /// __builtin_static_analysis_assume() with a non-value-dependent constant
+  /// parameter evaluating as false.
   bool isBuiltinAssumeFalse(const ASTContext &Ctx) const;
 
   /// Used by Sema to implement MSVC-compatible delayed name lookup.
diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
index 922d67940e22..9e1dd7723814 100644
--- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp
+++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
@@ -2970,6 +2970,7 @@ bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const CallExpr *Call,
 
   case Builtin::BI__builtin_assume:
   case Builtin::BI__assume:
+  case Builtin::BI__builtin_static_analysis_assume:
     return interp__builtin_assume(S, OpPC, Frame, Call);
 
   case Builtin::BI__builtin_strcmp:
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index f899b3c4bb79..913ebd4d2e06 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -3557,7 +3557,8 @@ bool Expr::isConstantInitializer(ASTContext &Ctx, bool IsForRef,
 bool CallExpr::isBuiltinAssumeFalse(const ASTContext &Ctx) const {
   unsigned BuiltinID = getBuiltinCallee();
   if (BuiltinID != Builtin::BI__assume &&
-      BuiltinID != Builtin::BI__builtin_assume)
+      BuiltinID != Builtin::BI__builtin_assume &&
+      BuiltinID != Builtin::BI__builtin_static_analysis_assume)
     return false;
 
   const Expr* Arg = getArg(0);
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 35a866ea5010..909f31a7845a 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -17675,6 +17675,7 @@ public:
     switch (E->getBuiltinCallee()) {
     case Builtin::BI__assume:
     case Builtin::BI__builtin_assume:
+    case Builtin::BI__builtin_static_analysis_assume:
       // The argument is not evaluated!
       return true;
 
diff --git a/clang/lib/Analysis/CFG.cpp b/clang/lib/Analysis/CFG.cpp
index cdde849b0e02..5e8e5c529057 100644
--- a/clang/lib/Analysis/CFG.cpp
+++ b/clang/lib/Analysis/CFG.cpp
@@ -2793,7 +2793,8 @@ static bool isBuiltinAssumeWithSideEffects(const ASTContext &Ctx,
                                            const CallExpr *CE) {
   unsigned BuiltinID = CE->getBuiltinCallee();
   if (BuiltinID != Builtin::BI__assume &&
-      BuiltinID != Builtin::BI__builtin_assume)
+      BuiltinID != Builtin::BI__builtin_assume &&
+      BuiltinID != Builtin::BI__builtin_static_analysis_assume)
     return false;
 
   return CE->getArg(0)->HasSideEffects(Ctx);
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
index 4cfa91e09efb..654126673a11 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
@@ -221,6 +221,9 @@ RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl &gd, unsigned builtinID,
   case Builtin::BI__builtin_fabsf128:
     return emitUnaryMaybeConstrainedFPBuiltin<cir::FAbsOp>(*this, *e);
 
+  // FIXME: BI__builtin_static_analysis_assume should be handled carefully here.
+  // Ideally, we would emit CIR for this builtin, but only for analysis
+  // purposes, so lowering it to LLVM IR should not generate code,
   case Builtin::BI__assume:
   case Builtin::BI__builtin_assume: {
     if (e->getArg(0)->HasSideEffects(getContext()))
diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp
index 34e6b512d20c..34163fca6843 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -3561,6 +3561,8 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
     Builder.CreateDereferenceableAssumption(PtrValue, SizeValue);
     return RValue::get(nullptr);
   }
+  // Not handling BI__builtin_static_analysis_assume here because that should
+  // not emit LLVM IR.
   case Builtin::BI__assume:
   case Builtin::BI__builtin_assume: {
     if (E->getArg(0)->HasSideEffects(getContext()))
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 063db05665af..f46b478da9fe 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -2785,6 +2785,7 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
     break;
   case Builtin::BI__assume:
   case Builtin::BI__builtin_assume:
+  case Builtin::BI__builtin_static_analysis_assume:
     if (BuiltinAssume(TheCall))
       return ExprError();
     break;
diff --git a/clang/lib/StaticAnalyzer/Checkers/AssumeModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/AssumeModeling.cpp
index 789c7772d123..6fb21d67c61f 100644
--- a/clang/lib/StaticAnalyzer/Checkers/AssumeModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/AssumeModeling.cpp
@@ -58,7 +58,9 @@ bool AssumeModelingChecker::evalCall(const CallEvent &Call,
   if (!FD)
     return false;
 
-  if (!llvm::is_contained({Builtin::BI__builtin_assume, Builtin::BI__assume},
+  if (!llvm::is_contained({Builtin::BI__builtin_assume,
+                           Builtin::BI__builtin_static_analysis_assume,
+                           Builtin::BI__assume},
                           FD->getBuiltinID())) {
     return false;
   }
diff --git a/clang/test/Analysis/builtin-assume-cfgs.c b/clang/test/Analysis/builtin-assume-cfgs.c
new file mode 100644
index 000000000000..ca448bb16ecc
--- /dev/null
+++ b/clang/test/Analysis/builtin-assume-cfgs.c
@@ -0,0 +1,52 @@
+// RUN: %clang_analyze_cc1 -triple x86_64-unknown-unknown -verify %s \
+// RUN:   -analyzer-checker=core,debug.DumpCFG 2>&1 \
+// RUN: | FileCheck %s
+
+int intfn(void);
+
+int calling_builtin_static_analysis_assume(int x) {
+  __builtin_static_analysis_assume(++x >= intfn());
+  // expected-warning@-1 {{assumption is ignored because it contains (potential) side-effects}}
+  return x;
+}
+
+// CHECK:      int calling_builtin_static_analysis_assume(int x)
+// CHECK-NEXT:  [B2 (ENTRY)]
+// CHECK-NEXT:    Succs (1): B1
+// CHECK-EMPTY:
+// CHECK-NEXT:  [B1]
+// CHECK-NEXT:    1: __builtin_static_analysis_assume
+// CHECK-NEXT:    2: [B1.1] (ImplicitCastExpr, BuiltinFnToFnPtr, void (*)(_Bool))
+// CHECK-NEXT:    3: [B1.2](++x >= intfn())
+// CHECK-NEXT:    4: x
+// CHECK-NEXT:    5: [B1.4] (ImplicitCastExpr, LValueToRValue, int)
+// CHECK-NEXT:    6: return [B1.5];
+// CHECK-NEXT:    Preds (1): B2
+// CHECK-NEXT:    Succs (1): B0
+// CHECK-EMPTY:
+// CHECK-NEXT:  [B0 (EXIT)]
+// CHECK-NEXT:    Preds (1): B1
+
+
+int calling_builtin_assume(int x) {
+  __builtin_assume(++x >= intfn());
+  // expected-warning@-1 {{assumption is ignored because it contains (potential) side-effects}}
+  return x;
+}
+
+// CHECK:      int calling_builtin_assume(int x)
+// CHECK-NEXT:  [B2 (ENTRY)]
+// CHECK-NEXT:    Succs (1): B1
+// CHECK-EMPTY:
+// CHECK-NEXT:  [B1]
+// CHECK-NEXT:    1: __builtin_assume
+// CHECK-NEXT:    2: [B1.1] (ImplicitCastExpr, BuiltinFnToFnPtr, void (*)(_Bool))
+// CHECK-NEXT:    3: [B1.2](++x >= intfn())
+// CHECK-NEXT:    4: x
+// CHECK-NEXT:    5: [B1.4] (ImplicitCastExpr, LValueToRValue, int)
+// CHECK-NEXT:    6: return [B1.5];
+// CHECK-NEXT:    Preds (1): B2
+// CHECK-NEXT:    Succs (1): B0
+// CHECK-EMPTY:
+// CHECK-NEXT:  [B0 (EXIT)]
+// CHECK-NEXT:    Preds (1): B1

Comment on lines +3 to +11
void voidfn();

class Foo{};

int fun() {
int x = 0;
__builtin_static_analysis_assume(true);
__builtin_static_analysis_assume(x <= 0);
__builtin_static_analysis_assume(voidfn()); // expected-error{{cannot initialize a parameter of type 'bool' with an rvalue of type 'void}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's also have an example of intfn() too.

Suggested change
void voidfn();
class Foo{};
int fun() {
int x = 0;
__builtin_static_analysis_assume(true);
__builtin_static_analysis_assume(x <= 0);
__builtin_static_analysis_assume(voidfn()); // expected-error{{cannot initialize a parameter of type 'bool' with an rvalue of type 'void}}
void voidfn();
int intfn();
class Foo{};
int fun() {
int x = 0;
__builtin_static_analysis_assume(true);
__builtin_static_analysis_assume(x <= 0);
__builtin_static_analysis_assume(intfn());
__builtin_static_analysis_assume(voidfn()); // expected-error{{cannot initialize a parameter of type 'bool' with an rvalue of type 'void}}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants