Author: DonĂ¡t Nagy Date: 2026-06-01T11:10:43Z New Revision: 73ded458839c0a105b19476929f6206356f92991
URL: https://github.com/llvm/llvm-project/commit/73ded458839c0a105b19476929f6206356f92991 DIFF: https://github.com/llvm/llvm-project/commit/73ded458839c0a105b19476929f6206356f92991.diff LOG: [Liveness][analyzer] Fix handling of [[assume]] attributes (#198618) Before this commit, if the analyzer encountered code like ``` int f(int a, int b) { [[assume(a == 2), assume(b == 3)]]; return a + b; } ``` it performed the following steps: 1. It visited the expression `a == 2` with `ExprEngine::Visit` (after visiting its sub-expressions, within the regular visitation that visits each statement of the `CFGBlock`). This triggered the `EagerlyAssume` logic and separated two execution paths. 2. It discarded the result bound to `a == 2` from the `Environment` because `a == 2` is not a direct child of the `AttributedStmt`. 3. Analogously, it visited an evaluated `b == 3`. 4. Analogously, it discarded the result bound to `b == 3`. 5. On each execution path `VisitAttributedStmt` was reached, it ran the `PreStmt<AttributedStmt>` checkers and stored their results in `CheckerPreStmt`. As there are AFAIK no such checkers, after this step the only element of `CheckerPreStmt` was the node `Pred`. 6. It constructed a `NodeBuilder`, which was unused, but its constructor inserted the nodes in `CheckerPreStmt` into `EvalSet`. This was essentially a shortcut that skipped the handling of the assumptions -- but ultimately its effects were absorbed and negated by the tricky side effects of `ExprEngine::Visit`. 7. For each node `N` in `CheckerPreStmt` (i.e. for `N == Pred`) and for each assume attribute, it created a child node of `N` where the assumption of **that one attribute** was visited again with `ExprEngine::Visit`. (I.e. it separated execution paths and implicitly placed "OR" relationships between the assumptions.) 8. It **completely ignored** the value produced by the assumption expression: did not update the state and did not discard execution paths where the [[assume]] assumption contradicted earlier knowledge. This commit fixes all four logic errors: 1. The liveness analysis is updated to ensure that an `AttributedStmt` keeps alive the assumption expressions of its [[assume]] attributes. This ensured that their results remain in the `Environment`. 2. Now there is no `NodeBuilder` that skips the handling the assumptions. 3. The invocation of `ExprEngine::Visit` is removed from `VisitAttributedStmt`: there is no reason to revisit those statements (now that we don't lose the evaluation results). 4. The state is updated to record that the values of the assumption expressions are ALL assumed to be true. If the assumption leads to a contradiction, the execution path is discarded (by not generating a node). This commit also adds a few new testcases and resolves some FIXME comments in previously existing tests. Added: Modified: clang/lib/Analysis/LiveVariables.cpp clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp clang/test/Analysis/cxx23-assume-attribute.cpp Removed: ################################################################################ diff --git a/clang/lib/Analysis/LiveVariables.cpp b/clang/lib/Analysis/LiveVariables.cpp index ba382e766440f..af3055c3d5f8d 100644 --- a/clang/lib/Analysis/LiveVariables.cpp +++ b/clang/lib/Analysis/LiveVariables.cpp @@ -285,6 +285,15 @@ void TransferFunctions::Visit(Stmt *S) { } break; } + case Stmt::AttributedStmtClass: { + // In an attributed statement, include the assumptions of the + // [[assume(...)]] attributes as being live. + AttributedStmt *AS = cast<AttributedStmt>(S); + for (const auto *Attr : getSpecificAttrs<CXXAssumeAttr>(AS->getAttrs())) { + AddLiveExpr(val.liveExprs, LV.ESetFact, Attr->getAssumption()); + } + break; + } case Stmt::PseudoObjectExprClass: { // A pseudo-object operation only directly consumes its result // expression. diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp index 07bc5f6920293..291533d0c3289 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp @@ -1215,16 +1215,28 @@ void ExprEngine::VisitLambdaExpr(const LambdaExpr *LE, ExplodedNode *Pred, void ExprEngine::VisitAttributedStmt(const AttributedStmt *A, ExplodedNode *Pred, ExplodedNodeSet &Dst) { + const StackFrame *SF = Pred->getStackFrame(); ExplodedNodeSet CheckerPreStmt; getCheckerManager().runCheckersForPreStmt(CheckerPreStmt, Pred, A, *this); ExplodedNodeSet EvalSet; - NodeBuilder Bldr(CheckerPreStmt, EvalSet, *currBldrCtx); - for (const auto *Attr : getSpecificAttrs<CXXAssumeAttr>(A->getAttrs())) { - for (ExplodedNode *N : CheckerPreStmt) { - Visit(Attr->getAssumption()->IgnoreParens(), N, EvalSet); + for (ExplodedNode *N : CheckerPreStmt) { + ProgramStateRef State = N->getState(); + for (const auto *Attr : getSpecificAttrs<CXXAssumeAttr>(A->getAttrs())) { + SVal AssumedVal = State->getSVal(Attr->getAssumption(), SF); + // This code ignores assumptions that evaluate to UndefinedVal. + // Perhaps there should be a checker that reports this situation. + if (auto ValidAssumedVal = AssumedVal.getAs<DefinedOrUnknownSVal>()) { + State = State->assume(*ValidAssumedVal, true); + } + + if (!State) + break; } + + if (State) + EvalSet.insert(Engine.makePostStmtNode(A, State, N)); } getCheckerManager().runCheckersForPostStmt(Dst, EvalSet, A, *this); diff --git a/clang/test/Analysis/cxx23-assume-attribute.cpp b/clang/test/Analysis/cxx23-assume-attribute.cpp index 4cc16446572dc..ce9ad29e55c36 100644 --- a/clang/test/Analysis/cxx23-assume-attribute.cpp +++ b/clang/test/Analysis/cxx23-assume-attribute.cpp @@ -50,24 +50,23 @@ int ternary_in_assume(int a, int b) { int assume_and_fallthrough_at_the_same_attrstmt(int a, int b) { [[assume(a == 2)]]; clang_analyzer_dump(a); // expected-warning {{2 S32b}} - // expected-warning-re@-1 {{reg_${{[0-9]+}}<int a>}} FIXME: We shouldn't have this dump. switch (a) { case 2: [[fallthrough, assume(b == 30)]]; case 4: { clang_analyzer_dump(b); // expected-warning {{30 S32b}} - // expected-warning-re@-1 {{reg_${{[0-9]+}}<int b>}} FIXME: We shouldn't have this dump. return b; } } // This code should be unreachable. - clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}} + clang_analyzer_warnIfReached(); // no-warning + return 0; +} +void assume_false() { [[assume(false)]]; // This should definitely make it so. clang_analyzer_warnIfReached(); // no-warning - - return 0; } void assume_opaque_gh151854_no_crash() { @@ -75,3 +74,26 @@ void assume_opaque_gh151854_no_crash() { [[assume(opaque())]]; // no-crash // expected-warning@-1 {{assumption is ignored because it contains (potential) side-effects}} } + +int multiple_assumptions(int a, int b) { + [[assume(a == 2), assume(b == 3)]]; + clang_analyzer_dump(a); // expected-warning {{2 S32b}} + clang_analyzer_dump(b); // expected-warning {{3 S32b}} + clang_analyzer_dump(a+b); // expected-warning {{5 S32b}} + return a + b; +} + +int trivial_assumption(int a) { + [[assume(a == 2)]]; + clang_analyzer_dump(a); // expected-warning {{2 S32b}} + return a; +} + +int undefined_assumption() { + // Theoretically the analyzer should report that the assumption expression of + // the [[assume]] attribute has an undefined value; currently these + // attributes are ignored by the analyzer. + int a; + [[assume(a)]]; + return a; // expected-warning {{Undefined or garbage value returned to caller}} + } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
