[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