llvmorg-github-actions[bot] wrote:

<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang

Author: kmehltretter82

<details>
<summary>Changes</summary>

The analyzer ends a path at a block whose terminator is an asm goto
("TODO: Handle jumping to labels" in CoreEngine::HandleBlockExit()).

Linux kernel static keys compile to an asm goto with CONFIG_JUMP_LABEL.
Every tracepoint has one, and with allocation profiling every kmalloc()
has one too, so kernel paths end at the first of them.

An asm goto is a block terminator and never reaches VisitGCCAsmStmt().
Invalidate its operands the same way, through a shared helper, and
continue at every successor.

The three FIXMEs in egraph-asm-goto-no-crash.cpp become expected
warnings. asm-goto-paths.c is new.

370 files of an x86_64 allmodconfig kernel (drivers/leds, sound/isa,
fs/ext4, net/ipv4), core checkers:

                                         before   after
    analysis time, summed over files      858 s   973 s
    wall clock, 4 jobs                    236 s   265 s
    reports                                  54      77
    step limit reached                      320     396

25 reports in 10 files are new, all in code that was not reached before.
24 are existing false positives, one is a harmless uninitialized read
(fs/ext4/extents.c:3158). None comes from the asm goto handling itself.

2 reports are gone. One function is now inlined into its caller and no
longer analyzed alone, and one path no longer fits the node budget.

Assisted-by: LLM

Fixes #<!-- -->229302

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


5 Files Affected:

- (modified) clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h 
(+15) 
- (modified) clang/lib/StaticAnalyzer/Core/CoreEngine.cpp (+8-3) 
- (modified) clang/lib/StaticAnalyzer/Core/ExprEngine.cpp (+49-15) 
- (added) clang/test/Analysis/asm-goto-paths.c (+110) 
- (modified) clang/test/Analysis/egraph-asm-goto-no-crash.cpp (+3-8) 


``````````diff
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h 
b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
index af813714e6831..ce98616549c56 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
@@ -387,6 +387,12 @@ class ExprEngine {
   void processIndirectGoto(ExplodedNodeSet &Dst, const Expr *Tgt,
                            const CFGBlock *Dispatch, ExplodedNode *Pred);
 
+  /// processAsmGoto - Called by CoreEngine. Used to generate successor
+  ///  nodes for an asm goto, which may fall through or jump to any of its
+  ///  labels. The operands are invalidated as for a plain asm statement.
+  void processAsmGoto(const GCCAsmStmt *A, const CFGBlock *B,
+                      ExplodedNode *Pred, ExplodedNodeSet &Dst);
+
   /// ProcessSwitch - Called by CoreEngine. Used to generate successor
   ///  nodes by processing the 'effects' of a switch statement.
   void processSwitch(const SwitchStmt *Switch, ExplodedNode *Pred,
@@ -489,6 +495,15 @@ class ExprEngine {
   void VisitGCCAsmStmt(const GCCAsmStmt *A, ExplodedNode *Pred,
                        ExplodedNodeSet &Dst);
 
+  /// Invalidate the regions that the outputs and inputs of an inline asm
+  /// statement refer to. Nothing is known about what the assembly does with
+  /// them.
+  ProgramStateRef invalidateAsmOperands(const GCCAsmStmt *A,
+                                        ProgramStateRef State,
+                                        ConstCFGElementRef Elem,
+                                        unsigned BlockCount,
+                                        const StackFrame *SF);
+
   /// VisitMSAsmStmt - Transfer function logic for MS inline asm.
   void VisitMSAsmStmt(const MSAsmStmt *A, ExplodedNode *Pred,
                       ExplodedNodeSet &Dst);
diff --git a/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp 
b/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp
index 04b700726fbc6..9ad83b76f1554 100644
--- a/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp
@@ -460,10 +460,15 @@ void CoreEngine::HandleBlockExit(const CFGBlock * B, 
ExplodedNode *Pred) {
         HandleBranch(cast<WhileStmt>(Term)->getCond(), Term, B, Pred);
         return;
 
-      case Stmt::GCCAsmStmtClass:
-        assert(cast<GCCAsmStmt>(Term)->isAsmGoto() && "Encountered GCCAsmStmt 
without labels");
-        // TODO: Handle jumping to labels
+      case Stmt::GCCAsmStmtClass: {
+        // An asm goto may fall through or jump to any of its labels.
+        assert(cast<GCCAsmStmt>(Term)->isAsmGoto() &&
+               "Encountered GCCAsmStmt without labels");
+        ExplodedNodeSet Dst;
+        ExprEng.processAsmGoto(cast<GCCAsmStmt>(Term), B, Pred, Dst);
+        enqueue(Dst);
         return;
+      }
     }
   }
 
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp 
b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
index 059bc4770da91..6d010291e8406 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
@@ -66,6 +66,7 @@
 #include "llvm/ADT/ImmutableMap.h"
 #include "llvm/ADT/ImmutableSet.h"
 #include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallPtrSet.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/Support/Casting.h"
 #include "llvm/Support/Compiler.h"
@@ -3926,40 +3927,73 @@ bool 
ExprEngine::didEagerlyAssumeBifurcateAt(ProgramStateRef State,
   return Ex && State->get<LastEagerlyAssumeExprIfSuccessful>() == Ex;
 }
 
-void ExprEngine::VisitGCCAsmStmt(const GCCAsmStmt *A, ExplodedNode *Pred,
-                                 ExplodedNodeSet &Dst) {
-  // We have processed both the inputs and the outputs.  All of the outputs
-  // should evaluate to Locs.  Nuke all of their values.
+ProgramStateRef ExprEngine::invalidateAsmOperands(const GCCAsmStmt *A,
+                                                  ProgramStateRef State,
+                                                  ConstCFGElementRef Elem,
+                                                  unsigned BlockCount,
+                                                  const StackFrame *SF) {
+  // All of the outputs should evaluate to Locs.  Nuke all of their values.
 
   // FIXME: Some day in the future it would be nice to allow a "plug-in"
   // which interprets the inline asm and stores proper results in the
   // outputs.
 
-  ProgramStateRef state = Pred->getState();
-
   for (const Expr *O : A->outputs()) {
-    SVal X = state->getSVal(O, Pred->getStackFrame());
+    SVal X = State->getSVal(O, SF);
     assert(!isa<NonLoc>(X)); // Should be an Lval, or unknown, undef.
 
     if (std::optional<Loc> LV = X.getAs<Loc>())
-      state = state->invalidateRegions(*LV, getCFGElementRef(),
-                                       getNumVisitedCurrent(),
-                                       Pred->getStackFrame(),
+      State = State->invalidateRegions(*LV, Elem, BlockCount, SF,
                                        /*CausedByPointerEscape=*/true);
   }
 
   // Do not reason about locations passed inside inline assembly.
   for (const Expr *I : A->inputs()) {
-    SVal X = state->getSVal(I, Pred->getStackFrame());
+    SVal X = State->getSVal(I, SF);
 
     if (std::optional<Loc> LV = X.getAs<Loc>())
-      state = state->invalidateRegions(*LV, getCFGElementRef(),
-                                       getNumVisitedCurrent(),
-                                       Pred->getStackFrame(),
+      State = State->invalidateRegions(*LV, Elem, BlockCount, SF,
                                        /*CausedByPointerEscape=*/true);
   }
 
-  Dst.insert(Engine.makePostStmtNode(A, state, Pred));
+  return State;
+}
+
+void ExprEngine::VisitGCCAsmStmt(const GCCAsmStmt *A, ExplodedNode *Pred,
+                                 ExplodedNodeSet &Dst) {
+  // We have processed both the inputs and the outputs.
+  ProgramStateRef State =
+      invalidateAsmOperands(A, Pred->getState(), getCFGElementRef(),
+                            getNumVisitedCurrent(), Pred->getStackFrame());
+
+  Dst.insert(Engine.makePostStmtNode(A, State, Pred));
+}
+
+void ExprEngine::processAsmGoto(const GCCAsmStmt *A, const CFGBlock *B,
+                                ExplodedNode *Pred, ExplodedNodeSet &Dst) {
+  ProgramStateRef State = Pred->getState();
+  const StackFrame *SF = Pred->getStackFrame();
+
+  // An asm goto is the terminator of its block, not one of its elements, so
+  // its operands do not pass through VisitGCCAsmStmt. Invalidate them here.
+  // A terminator has no CFG element of its own, so the new values are
+  // conjured at the last element of its block. Parts of an operand, such as
+  // the arms of a conditional, may have been evaluated in earlier blocks.
+  // Without operands the block can be empty, and nothing is invalidated.
+  if (!B->empty())
+    State =
+        invalidateAsmOperands(A, State, ConstCFGElementRef(B, B->size() - 1),
+                              getNumVisited(SF, B), SF);
+
+  // Nothing is known about what the assembly does, so it may fall through or
+  // jump to any of its labels. Follow every successor once.
+  llvm::SmallPtrSet<const CFGBlock *, 4> Followed;
+  for (const CFGBlock::AdjacentBlock &Succ : B->succs()) {
+    const CFGBlock *Next = Succ.getReachableBlock();
+    if (!Next || !Followed.insert(Next).second)
+      continue;
+    Dst.insert(Engine.makeNode(BlockEdge(B, Next, SF), State, Pred));
+  }
 }
 
 void ExprEngine::VisitMSAsmStmt(const MSAsmStmt *A, ExplodedNode *Pred,
diff --git a/clang/test/Analysis/asm-goto-paths.c 
b/clang/test/Analysis/asm-goto-paths.c
new file mode 100644
index 0000000000000..e863d5f115724
--- /dev/null
+++ b/clang/test/Analysis/asm-goto-paths.c
@@ -0,0 +1,110 @@
+// RUN: %clang_analyze_cc1 -triple x86_64-pc-linux-gnu \
+// RUN:   -analyzer-checker=core,debug.ExprInspection \
+// RUN:   -analyzer-config eagerly-assume=false -verify %s
+
+void clang_analyzer_eval(int);
+void clang_analyzer_warnIfReached(void);
+
+// The shape of a Linux kernel static branch.
+static inline int static_branch(const int *key) {
+  asm goto("jmp %l[l_yes]" : : "i"(key) : : l_yes);
+  return 0;
+l_yes:
+  return 1;
+}
+
+int key;
+
+void both_ways(void) {
+  if (static_branch(&key))
+    clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
+  else
+    clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
+  clang_analyzer_warnIfReached();   // expected-warning {{REACHABLE}}
+}
+
+int null_after_asm_goto(int *p) {
+  if (static_branch(&key))
+    p = 0;
+  return *p; // expected-warning {{Dereference of null pointer (loaded from 
variable 'p')}}
+}
+
+void outputs_are_forgotten(void) {
+  int x = 1;
+
+  asm goto("" : "=r"(x) : : : out);
+  clang_analyzer_eval(x == 1); // expected-warning {{UNKNOWN}}
+  return;
+out:
+  clang_analyzer_eval(x == 1); // expected-warning {{UNKNOWN}}
+}
+
+struct S { int a; };
+
+void struct_outputs_are_forgotten(void) {
+  struct S s = {1};
+
+  asm goto("" : "=m"(s) : : : out);
+  clang_analyzer_eval(s.a == 1); // expected-warning {{UNKNOWN}}
+  return;
+out:
+  clang_analyzer_eval(s.a == 1); // expected-warning {{UNKNOWN}}
+}
+
+// Memory that an input points to may have been written, as for a plain asm.
+void input_pointees_are_forgotten(int *p) {
+  *p = 1;
+
+  asm goto("" : : "r"(p) : "memory" : out);
+  clang_analyzer_eval(*p == 1); // expected-warning {{UNKNOWN}}
+  return;
+out:
+  clang_analyzer_eval(*p == 1); // expected-warning {{UNKNOWN}}
+}
+
+// The arms of a conditional operand are evaluated in earlier blocks. Only
+// the memory behind the arm that was taken is forgotten.
+void conditional_operand(int c, int *p, int *q) {
+  *p = 1;
+  *q = 1;
+
+  asm goto("" : : "r"(c ? p : q) : "memory" : out);
+  clang_analyzer_eval(*p == 1); // expected-warning {{TRUE}}
+                                // expected-warning@-1 {{UNKNOWN}}
+  clang_analyzer_eval(*q == 1); // expected-warning {{TRUE}}
+                                // expected-warning@-1 {{UNKNOWN}}
+  return;
+out:
+  clang_analyzer_eval(*p == 1); // expected-warning {{TRUE}}
+                                // expected-warning@-1 {{UNKNOWN}}
+  clang_analyzer_eval(*q == 1); // expected-warning {{TRUE}}
+                                // expected-warning@-1 {{UNKNOWN}}
+}
+
+// A label before the asm goto makes a loop. The checks only run on the
+// second time round: the backward edge is followed, the output gets a new
+// value each time, and the path goes on behind the loop.
+void backward_jump(void) {
+  int x = 0;
+  int prev;
+  int count = 0;
+
+again:
+  prev = x;
+  count++;
+  asm goto("" : "=r"(x) : : : again);
+  if (count == 2) {
+    clang_analyzer_eval(prev == x); // expected-warning {{UNKNOWN}}
+    clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
+  }
+}
+
+// No operands, so the block has no elements and there is nothing to
+// invalidate. Both ways are still reachable.
+void no_operands(void) {
+  asm goto("jmp %l[out]" : : : : out);
+  clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
+  return;
+out:
+  clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
+}
diff --git a/clang/test/Analysis/egraph-asm-goto-no-crash.cpp 
b/clang/test/Analysis/egraph-asm-goto-no-crash.cpp
index 37f8fc533abe3..a5b5729c7c83f 100644
--- a/clang/test/Analysis/egraph-asm-goto-no-crash.cpp
+++ b/clang/test/Analysis/egraph-asm-goto-no-crash.cpp
@@ -1,7 +1,5 @@
 // RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -verify 
%s
 
-// expected-no-diagnostics
-
 void clang_analyzer_warnIfReached();
 
 void testAsmGoto() {
@@ -11,16 +9,13 @@ void testAsmGoto() {
            : /* clobbers */
            : label1, label2 /* any labels used */);
 
-  // FIXME: Should be reachable.
-  clang_analyzer_warnIfReached();
+  clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
 
   label1:
-  // FIXME: Should be reachable.
-  clang_analyzer_warnIfReached();
+  clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
   return;
 
   label2:
-  // FIXME: Should be reachable.
-  clang_analyzer_warnIfReached();
+  clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
   return;
 }

``````````

</details>


https://github.com/llvm/llvm-project/pull/229316
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to