[llvm] Reapply "[Dexter] Add rewriting for aggregate variables (#202800)" (PR #206535)

Stephen Tozer via llvm-commits llvm-commits at lists.llvm.org
Tue Jun 30 08:38:27 PDT 2026


https://github.com/SLTozer updated https://github.com/llvm/llvm-project/pull/206535

>From a02f4e94a720f48309b8df0677706e762914f516 Mon Sep 17 00:00:00 2001
From: Stephen Tozer <stephen.tozer at sony.com>
Date: Mon, 29 Jun 2026 15:01:59 +0100
Subject: [PATCH] Reapply "[Dexter] Add rewriting for aggregate variables
 (#202800)"

This reverts commit 5bf495456b9898c45c437b8dadae21417d04a9a5.
---
 .../dexter/dex/debugger/lldb/LLDB.py          |  1 +
 .../dexter/dex/evaluation/ExpectRewriter.py   | 26 ++++++-
 .../Inputs/rewrite_aggregates_expected.cpp    | 70 +++++++++++++++++++
 .../rewrite_list_aggregates_expected.cpp      | 58 +++++++++++++++
 .../scripts/rewriting/rewrite_aggregates.cpp  | 53 ++++++++++++++
 .../rewriting/rewrite_list_aggregates.cpp     | 48 +++++++++++++
 6 files changed, 253 insertions(+), 3 deletions(-)
 create mode 100644 cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/rewrite_aggregates_expected.cpp
 create mode 100644 cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/rewrite_list_aggregates_expected.cpp
 create mode 100644 cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/rewrite_aggregates.cpp
 create mode 100644 cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/rewrite_list_aggregates.cpp

diff --git a/cross-project-tests/debuginfo-tests/dexter/dex/debugger/lldb/LLDB.py b/cross-project-tests/debuginfo-tests/dexter/dex/debugger/lldb/LLDB.py
index 8f90981f67e4c..4b92da9e0f38c 100644
--- a/cross-project-tests/debuginfo-tests/dexter/dex/debugger/lldb/LLDB.py
+++ b/cross-project-tests/debuginfo-tests/dexter/dex/debugger/lldb/LLDB.py
@@ -510,6 +510,7 @@ def _evaluate_result_value(
                 "couldn't read from memory",
                 "Cannot access memory at address",
                 "invalid address (fault address:",
+                "error: parent is NULL",
             ]
         )
 
diff --git a/cross-project-tests/debuginfo-tests/dexter/dex/evaluation/ExpectRewriter.py b/cross-project-tests/debuginfo-tests/dexter/dex/evaluation/ExpectRewriter.py
index 7519d96a02a6d..5f8829287efe4 100644
--- a/cross-project-tests/debuginfo-tests/dexter/dex/evaluation/ExpectRewriter.py
+++ b/cross-project-tests/debuginfo-tests/dexter/dex/evaluation/ExpectRewriter.py
@@ -25,7 +25,20 @@ class ExpectedValueRewriter:
     def __init__(self, expect: Expect, value: ValueIR):
         self.expect = expect
         self.root_value = value
-        self.expected_value = expect.get_variable_result(value)
+        self.expected_value: Union[Dict, str, None] = None
+        if sub_values := self.root_value.sub_values:
+            self.expected_value = {
+                sub_value.expression: expected_value
+                for sub_value in sub_values
+                if (
+                    expected_value := ExpectedValueRewriter(
+                        expect, sub_value
+                    ).expected_value
+                )
+                is not None
+            }
+        if not self.expected_value:
+            self.expected_value = expect.get_variable_result(value)
 
 
 def unique_expected_values(elements: List[ExpectedValueRewriter]):
@@ -33,14 +46,21 @@ def unique_expected_values(elements: List[ExpectedValueRewriter]):
     values, or a single item if there is only one non-duplicated expected value in the list, or None if there are no
     valid expected values."""
 
+    def freeze(input):
+        assert input is not None, "Unexpected 'None' in an expected_value"
+        if isinstance(input, dict):
+            return tuple(sorted((str(k), freeze(v)) for k, v in input.items()))
+        return input
+
     unique_set = set()
     result = []
     for element in elements:
         expected_value = element.expected_value
         if expected_value is None:
             continue
-        if expected_value not in unique_set:
-            unique_set.add(expected_value)
+        frozen_value = freeze(expected_value)
+        if frozen_value not in unique_set:
+            unique_set.add(frozen_value)
             result.append(expected_value)
     if not result:
         return None
diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/rewrite_aggregates_expected.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/rewrite_aggregates_expected.cpp
new file mode 100644
index 0000000000000..8f9c5206e6480
--- /dev/null
+++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/rewrite_aggregates_expected.cpp
@@ -0,0 +1,70 @@
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: %dexter_regression_test_cxx_build %s -o %t/test
+// RUN: %dexter_regression_test_run --use-script --binary %t/test \
+// RUN:   --results-directory %t/results -- %s 2>&1 | FileCheck %s
+// RUN: diff %t/results/%{s:basename} %S/Inputs/rewrite_aggregates_expected.cpp
+
+/// Test that Dexter can write disaggregated expected values for aggregates,
+/// including falling back to the parent value if sub_values contain errors,
+/// e.g. for pointers that are not dereferencable.
+
+/// NB: The exact contents of this file are compared against the expect file in
+///     the Inputs/ directory; any changes to this file, including comments,
+///     will require updating the corresponding expected file.
+
+// CHECK: Rewrote script to add 5 expected values.
+
+// CHECK: total_watched_steps: 5
+// CHECK: correct_steps: 5
+// CHECK: incorrect_steps: 0
+// CHECK: seen_values: 16
+// CHECK: missing_values: 0
+
+struct Point {
+  int X;
+  int Y;
+  int Z;
+};
+
+struct Rect {
+  Point TopLeft;
+  Point BottomRight;
+};
+
+int main() {
+  Point P{1, 2, 3};
+  int *I = &P.X;
+  Rect R{{1, 1, 1}, {2, 2, 2}};
+  int L[] = {0, 1, 2, 3, 4};
+  int *InvalidPtr = nullptr;
+  return 0; // !dex_label ret
+}
+
+/*
+---
+? !where {lines: !label 'ret'}
+: !value 'P':
+    X: '1'
+    Y: '2'
+    Z: '3'
+  !value 'I':
+    '*I': '1'
+  !value 'R':
+    BottomRight:
+      X: '2'
+      Y: '2'
+      Z: '2'
+    TopLeft:
+      X: '1'
+      Y: '1'
+      Z: '1'
+  !value 'L':
+    '[0]': '0'
+    '[1]': '1'
+    '[2]': '2'
+    '[3]': '3'
+    '[4]': '4'
+  !value 'InvalidPtr': '0x0000000000000000'
+...
+*/
diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/rewrite_list_aggregates_expected.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/rewrite_list_aggregates_expected.cpp
new file mode 100644
index 0000000000000..630ed6e887413
--- /dev/null
+++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/rewrite_list_aggregates_expected.cpp
@@ -0,0 +1,58 @@
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: %dexter_regression_test_cxx_build %s -o %t/test
+// RUN: %dexter_regression_test_run --use-script --binary %t/test \
+// RUN:   --results-directory %t/results -- %s 2>&1 | FileCheck %s
+// RUN: diff %t/results/%{s:basename} \
+// RUN:   %S/Inputs/rewrite_list_aggregates_expected.cpp
+
+/// Test that Dexter can write expects for variables that are aggregates and
+/// have more than one value, without writing any duplicate expected values.
+
+/// NB: The exact contents of this file are compared against the expect file in
+///     the Inputs/ directory; any changes to this file, including comments,
+///     will require updating the corresponding expected file.
+
+// CHECK: Rewrote script to add 1 expected values.
+
+struct Point {
+  int X;
+  int Y;
+};
+
+int main() {
+  Point P{1, 2};
+  P.X = 3; // !dex_label start
+  P.Y = 0;
+  P.X = 1;
+  P.Y = 2;
+  P = {0, 0};
+  return 0; // !dex_label end
+}
+
+// CHECK: total_watched_steps: 6
+// CHECK: correct_steps: 6
+// CHECK: incorrect_steps: 0
+// CHECK: partial_step_correctness: 6.0
+// CHECK: missing_var_steps: 0
+// CHECK: unexpected_value_steps: 0
+// CHECK: correct_step_coverage: 100.0% (6/6)
+// CHECK: seen_values: 10
+// CHECK: missing_values: 0
+
+/*
+---
+? !where {lines: !range [!label 'start', !label 'end']}
+: !value 'P':
+  - X: '1'
+    Y: '2'
+  - X: '3'
+    Y: '2'
+  - X: '3'
+    Y: '0'
+  - X: '1'
+    Y: '0'
+  - X: '0'
+    Y: '0'
+...
+*/
diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/rewrite_aggregates.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/rewrite_aggregates.cpp
new file mode 100644
index 0000000000000..da55a3345093c
--- /dev/null
+++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/rewrite_aggregates.cpp
@@ -0,0 +1,53 @@
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: %dexter_regression_test_cxx_build %s -o %t/test
+// RUN: %dexter_regression_test_run --use-script --binary %t/test \
+// RUN:   --results-directory %t/results -- %s 2>&1 | FileCheck %s
+// RUN: diff %t/results/%{s:basename} %S/Inputs/rewrite_aggregates_expected.cpp
+
+/// Test that Dexter can write disaggregated expected values for aggregates,
+/// including falling back to the parent value if sub_values contain errors,
+/// e.g. for pointers that are not dereferencable.
+
+/// NB: The exact contents of this file are compared against the expect file in
+///     the Inputs/ directory; any changes to this file, including comments,
+///     will require updating the corresponding expected file.
+
+// CHECK: Rewrote script to add 5 expected values.
+
+// CHECK: total_watched_steps: 5
+// CHECK: correct_steps: 5
+// CHECK: incorrect_steps: 0
+// CHECK: seen_values: 16
+// CHECK: missing_values: 0
+
+struct Point {
+  int X;
+  int Y;
+  int Z;
+};
+
+struct Rect {
+  Point TopLeft;
+  Point BottomRight;
+};
+
+int main() {
+  Point P{1, 2, 3};
+  int *I = &P.X;
+  Rect R{{1, 1, 1}, {2, 2, 2}};
+  int L[] = {0, 1, 2, 3, 4};
+  int *InvalidPtr = nullptr;
+  return 0; // !dex_label ret
+}
+
+/*
+---
+!where {lines: !label ret}:
+    ? !value P
+    ? !value I
+    ? !value R
+    ? !value L
+    ? !value InvalidPtr
+...
+*/
diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/rewrite_list_aggregates.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/rewrite_list_aggregates.cpp
new file mode 100644
index 0000000000000..ab517d1342b2f
--- /dev/null
+++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/rewrite_list_aggregates.cpp
@@ -0,0 +1,48 @@
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: %dexter_regression_test_cxx_build %s -o %t/test
+// RUN: %dexter_regression_test_run --use-script --binary %t/test \
+// RUN:   --results-directory %t/results -- %s 2>&1 | FileCheck %s
+// RUN: diff %t/results/%{s:basename} \
+// RUN:   %S/Inputs/rewrite_list_aggregates_expected.cpp
+
+/// Test that Dexter can write expects for variables that are aggregates and
+/// have more than one value, without writing any duplicate expected values.
+
+/// NB: The exact contents of this file are compared against the expect file in
+///     the Inputs/ directory; any changes to this file, including comments,
+///     will require updating the corresponding expected file.
+
+// CHECK: Rewrote script to add 1 expected values.
+
+struct Point {
+  int X;
+  int Y;
+};
+
+int main() {
+  Point P{1, 2};
+  P.X = 3; // !dex_label start
+  P.Y = 0;
+  P.X = 1;
+  P.Y = 2;
+  P = {0, 0};
+  return 0; // !dex_label end
+}
+
+// CHECK: total_watched_steps: 6
+// CHECK: correct_steps: 6
+// CHECK: incorrect_steps: 0
+// CHECK: partial_step_correctness: 6.0
+// CHECK: missing_var_steps: 0
+// CHECK: unexpected_value_steps: 0
+// CHECK: correct_step_coverage: 100.0% (6/6)
+// CHECK: seen_values: 10
+// CHECK: missing_values: 0
+
+/*
+---
+!where {lines: !range [!label start, !label end]}:
+    ? !value P
+...
+*/



More information about the llvm-commits mailing list