[clang] [clang][CFG][NFC] Add some tests for guaranteed eval orders (PR #212116)
Balázs Benics via cfe-commits
cfe-commits at lists.llvm.org
Sun Jul 26 06:48:34 PDT 2026
https://github.com/steakhal updated https://github.com/llvm/llvm-project/pull/212116
>From dcd5d75782300672edbb625e8e4009837d2ec006 Mon Sep 17 00:00:00 2001
From: Balazs Benics <benicsbalazs at gmail.com>
Date: Sun, 26 Jul 2026 13:13:29 +0100
Subject: [PATCH 1/2] [clang][CFG][NFC] Add regression tests for guaranteed
evaluation order
Add dedicated CFG dump tests pinning the operand evaluation order for C++
constructs whose order is guaranteed by the standard (mostly C++17 /
P0145R3): shift, built-in subscript, pointer-to-member access, comma,
callee-before-arguments, and chained overloaded operator<<.
Constructs with unspecified operand order (e.g. arithmetic operands,
inter-argument order) are intentionally omitted to avoid over-specifying
implementation details.
Assisted-By: claude
rdar://183254267
---
clang/test/Analysis/cfg-eval-order.cpp | 109 +++++++++++++++++++++++++
1 file changed, 109 insertions(+)
create mode 100644 clang/test/Analysis/cfg-eval-order.cpp
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)
>From 21bf332ca91d98457fc2d07583c5a89ac5805ecf Mon Sep 17 00:00:00 2001
From: Balazs Benics <benicsbalazs at gmail.com>
Date: Sun, 26 Jul 2026 13:24:12 +0100
Subject: [PATCH 2/2] [clang][CFG][NFC] Test evaluation order in pre-C++17 and
C modes
Assisted-By: claude
rdar://183254267
---
clang/test/Analysis/cfg-eval-order.c | 80 ++++++++++++++++++++++++++++
1 file changed, 80 insertions(+)
create mode 100644 clang/test/Analysis/cfg-eval-order.c
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])
More information about the cfe-commits
mailing list