[clang] [clang][CFG][NFC] Add some tests for guaranteed eval orders (PR #212116)

via cfe-commits cfe-commits at lists.llvm.org
Sun Jul 26 06:01:09 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

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

Author: Balázs Benics (steakhal)

<details>
<summary>Changes</summary>

rdar://183254267

Assisted-By: claude

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


2 Files Affected:

- (added) clang/test/Analysis/cfg-eval-order.c (+80) 
- (added) clang/test/Analysis/cfg-eval-order.cpp (+109) 


``````````diff
diff --git a/clang/test/Analysis/cfg-eval-order.c b/clang/test/Analysis/cfg-eval-order.c
new file mode 100644
index 0000000000000..79d586d252ebd
--- /dev/null
+++ b/clang/test/Analysis/cfg-eval-order.c
@@ -0,0 +1,80 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=debug.DumpCFG -std=c17 %s 2>&1 | FileCheck %s
+// RUN: %clang_analyze_cc1 -analyzer-checker=debug.DumpCFG -std=c99 %s 2>&1 | FileCheck %s
+
+int *getPtr(int);
+int getVal(int);
+int getL(void);
+int getR(void);
+int getIdx(void);
+int arr[10];
+int callee(int, int);
+
+// The RHS is emitted before the LHS, matching simple assignment below.
+void test_compound_assign(int a, int b) {
+  *getPtr(a) += getVal(b);
+}
+
+// CHECK-LABEL: void test_compound_assign(int a, int b)
+// CHECK:          1: getVal
+// CHECK-NEXT:     2: [B1.1] (ImplicitCastExpr, FunctionToPointerDecay, int (*)(int))
+// CHECK-NEXT:     3: b
+// CHECK-NEXT:     4: [B1.3] (ImplicitCastExpr, LValueToRValue, int)
+// CHECK-NEXT:     5: [B1.2]([B1.4])
+// CHECK-NEXT:     6: getPtr
+// CHECK-NEXT:     7: [B1.6] (ImplicitCastExpr, FunctionToPointerDecay, int *(*)(int))
+// CHECK-NEXT:     8: a
+// CHECK-NEXT:     9: [B1.8] (ImplicitCastExpr, LValueToRValue, int)
+// CHECK-NEXT:    10: [B1.7]([B1.9])
+// CHECK-NEXT:    11: *[B1.10]
+// CHECK-NEXT:    12: [B1.11] += [B1.5]
+
+// Simple assignment must agree with the compound form above.
+void test_simple_assign(int a, int b) {
+  *getPtr(a) = getVal(b);
+}
+
+// CHECK-LABEL: void test_simple_assign(int a, int b)
+// CHECK:          1: getVal
+// CHECK:          5: [B1.2]([B1.4])
+// CHECK-NEXT:     6: getPtr
+// CHECK:         10: [B1.7]([B1.9])
+// CHECK-NEXT:    11: *[B1.10]
+// CHECK-NEXT:    12: [B1.11] = [B1.5]
+
+// C17 6.5.17: the left operand of a comma is sequenced before the right one.
+void test_comma(void) {
+  getL(), getR();
+}
+
+// CHECK-LABEL: void test_comma(void)
+// CHECK:          1: getL
+// CHECK-NEXT:     2: [B1.1] (ImplicitCastExpr, FunctionToPointerDecay, int (*)(void))
+// CHECK-NEXT:     3: [B1.2]()
+// CHECK-NEXT:     4: getR
+// CHECK-NEXT:     5: [B1.4] (ImplicitCastExpr, FunctionToPointerDecay, int (*)(void))
+// CHECK-NEXT:     6: [B1.5]()
+// CHECK-NEXT:     7: ... , [B1.6]
+
+void test_subscript(void) {
+  int x = arr[getIdx()];
+}
+
+// CHECK-LABEL: void test_subscript(void)
+// CHECK:          1: arr
+// CHECK-NEXT:     2: [B1.1] (ImplicitCastExpr, ArrayToPointerDecay, int *)
+// CHECK-NEXT:     3: getIdx
+// CHECK-NEXT:     4: [B1.3] (ImplicitCastExpr, FunctionToPointerDecay, int (*)(void))
+// CHECK-NEXT:     5: [B1.4]()
+// CHECK-NEXT:     6: [B1.2]{{\[\[}}B1.5]]
+
+// The callee expression is emitted before the arguments. (The order among the
+// arguments themselves is unspecified and is not pinned here.)
+void test_call_callee_before_args(void) {
+  callee(getL(), getR());
+}
+
+// CHECK-LABEL: void test_call_callee_before_args(void)
+// CHECK:          1: callee
+// CHECK-NEXT:     2: [B1.1] (ImplicitCastExpr, FunctionToPointerDecay, int (*)(int, int))
+// CHECK-NEXT:     3: getL
+// CHECK:          9: [B1.2]([B1.5], [B1.8])
diff --git a/clang/test/Analysis/cfg-eval-order.cpp b/clang/test/Analysis/cfg-eval-order.cpp
new file mode 100644
index 0000000000000..e5cb7215149ff
--- /dev/null
+++ b/clang/test/Analysis/cfg-eval-order.cpp
@@ -0,0 +1,109 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=debug.DumpCFG -std=c++17 %s 2>&1 | FileCheck %s
+// RUN: %clang_analyze_cc1 -analyzer-checker=debug.DumpCFG -std=c++11 %s 2>&1 | FileCheck %s
+
+// The C++11 run intentionally reuses the same expectations.
+// Before C++17 these orders are unspecified, so these lines pin
+// a deliberate implementation choice.
+
+int getL();
+int getR();
+int getIdx();
+int arr[10];
+
+// [expr.shift]: the left operand is sequenced before the right operand.
+void test_shift() {
+  int x = getL() << getR();
+}
+
+// CHECK-LABEL: void test_shift()
+// CHECK:          1: getL
+// CHECK-NEXT:     2: [B1.1] (ImplicitCastExpr, FunctionToPointerDecay, int (*)(void))
+// CHECK-NEXT:     3: [B1.2]()
+// CHECK-NEXT:     4: getR
+// CHECK-NEXT:     5: [B1.4] (ImplicitCastExpr, FunctionToPointerDecay, int (*)(void))
+// CHECK-NEXT:     6: [B1.5]()
+// CHECK-NEXT:     7: [B1.3] << [B1.6]
+
+// [expr.sub] (C++17): the array operand is sequenced before the index operand.
+void test_subscript() {
+  int x = arr[getIdx()];
+}
+
+// CHECK-LABEL: void test_subscript()
+// CHECK:          1: arr
+// CHECK-NEXT:     2: [B1.1] (ImplicitCastExpr, ArrayToPointerDecay, int *)
+// CHECK-NEXT:     3: getIdx
+// CHECK-NEXT:     4: [B1.3] (ImplicitCastExpr, FunctionToPointerDecay, int (*)(void))
+// CHECK-NEXT:     5: [B1.4]()
+// CHECK-NEXT:     6: [B1.2]{{\[\[}}B1.5]]
+
+struct Obj {
+  int m;
+};
+int Obj::*getPMD();
+Obj *getObj();
+
+// [expr.mptr.oper]: the left operand is sequenced before the right operand.
+void test_ptr_to_member() {
+  int x = getObj()->*getPMD();
+}
+
+// CHECK-LABEL: void test_ptr_to_member()
+// CHECK:          1: getObj
+// CHECK-NEXT:     2: [B1.1] (ImplicitCastExpr, FunctionToPointerDecay, Obj *(*)(void))
+// CHECK-NEXT:     3: [B1.2]()
+// CHECK-NEXT:     4: getPMD
+// CHECK-NEXT:     5: [B1.4] (ImplicitCastExpr, FunctionToPointerDecay, int Obj::*(*)(void))
+// CHECK-NEXT:     6: [B1.5]()
+// CHECK-NEXT:     7: [B1.3] ->* [B1.6]
+
+// [expr.comma]: the left operand is sequenced before the right operand.
+void test_comma() {
+  getL(), getR();
+}
+
+// CHECK-LABEL: void test_comma()
+// CHECK:          1: getL
+// CHECK-NEXT:     2: [B1.1] (ImplicitCastExpr, FunctionToPointerDecay, int (*)(void))
+// CHECK-NEXT:     3: [B1.2]()
+// CHECK-NEXT:     4: getR
+// CHECK-NEXT:     5: [B1.4] (ImplicitCastExpr, FunctionToPointerDecay, int (*)(void))
+// CHECK-NEXT:     6: [B1.5]()
+// CHECK-NEXT:     7: ... , [B1.6]
+
+int callee(int, int);
+
+// [expr.call] (C++17): the callee (postfix-expression) is sequenced before the
+// arguments. (The order among the arguments themselves is unspecified and is
+// not pinned here.)
+void test_call_callee_before_args() {
+  callee(getL(), getR());
+}
+
+// CHECK-LABEL: void test_call_callee_before_args()
+// CHECK:          1: callee
+// CHECK-NEXT:     2: [B1.1] (ImplicitCastExpr, FunctionToPointerDecay, int (*)(int, int))
+// CHECK-NEXT:     3: getL
+// CHECK:          9: [B1.2]([B1.5], [B1.8])
+
+struct Stream {
+  Stream &operator<<(int);
+};
+Stream &getStream();
+
+// A chained overloaded oper<< is left-associative: the inner call (object and
+// its argument) is fully evaluated before the outer call's argument, matching
+// the built-in left-to-right shift order.
+void test_overloaded_shift() {
+  getStream() << getL() << getR();
+}
+
+// CHECK-LABEL: void test_overloaded_shift()
+// CHECK:          5: getStream
+// CHECK:          7: [B1.6]()
+// CHECK-NEXT:     8: getL
+// CHECK:         10: [B1.9]()
+// CHECK-NEXT:    11: [B1.7] << [B1.10] (OperatorCall)
+// CHECK-NEXT:    12: getR
+// CHECK:         14: [B1.13]()
+// CHECK-NEXT:    15: [B1.11] << [B1.14] (OperatorCall)

``````````

</details>


https://github.com/llvm/llvm-project/pull/212116


More information about the cfe-commits mailing list