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 <[email protected]> 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 <[email protected]> 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]) _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
