[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