[llvm-branch-commits] [llvm] [Dexter] Add rewriting for aggregate variables (PR #202800)

Stephen Tozer via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Wed Jun 10 02:28:28 PDT 2026


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

>From 105ba05850c323f28cf16ee7533adb4c1e0b6be3 Mon Sep 17 00:00:00 2001
From: Stephen Tozer <stephen.tozer at sony.com>
Date: Tue, 9 Jun 2026 17:43:12 +0100
Subject: [PATCH 1/2] [Dexter] Add rewriting for aggregate variables

Following on from the previous patch, this patch allows Dexter to write
disaggregated expected values for aggregate variables. Dexter eagerly tries
to disaggregate whenever subvalues are available, but will fallback to the
root/parent value if all available subvalues are unavailable.
---
 .../dexter/dex/debugger/lldb/LLDB.py          |  1 +
 .../dexter/dex/evaluation/ExpectWriter.py     | 29 +++++++-
 .../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, 256 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/ExpectWriter.py b/cross-project-tests/debuginfo-tests/dexter/dex/evaluation/ExpectWriter.py
index b943aadc9abfa..5f4a5a5dc9bbf 100644
--- a/cross-project-tests/debuginfo-tests/dexter/dex/evaluation/ExpectWriter.py
+++ b/cross-project-tests/debuginfo-tests/dexter/dex/evaluation/ExpectWriter.py
@@ -25,7 +25,23 @@ class ExpectedValueWriter:
     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
+        print(value)
+        if sub_values := self.root_value.sub_values:
+            for sub_value in sub_values:
+                print(f"  {sub_value}")
+            self.expected_value = {
+                sub_value.expression: expected_value
+                for sub_value in sub_values
+                if (
+                    expected_value := ExpectedValueWriter(
+                        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[ExpectedValueWriter]):
@@ -33,14 +49,21 @@ def unique_expected_values(elements: List[ExpectedValueWriter]):
     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..cac4dd0cf8388
--- /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..16efa669e2b7c
--- /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..2419aae5876e7
--- /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..becf9d31d34d0
--- /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
+...
+*/

>From 4773921835e605614b476852e4fb37da69d55218 Mon Sep 17 00:00:00 2001
From: Stephen Tozer <stephen.tozer at sony.com>
Date: Wed, 10 Jun 2026 10:28:11 +0100
Subject: [PATCH 2/2] Remove debug print

---
 .../debuginfo-tests/dexter/dex/evaluation/ExpectWriter.py      | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/cross-project-tests/debuginfo-tests/dexter/dex/evaluation/ExpectWriter.py b/cross-project-tests/debuginfo-tests/dexter/dex/evaluation/ExpectWriter.py
index 5f4a5a5dc9bbf..80392c3ea68a3 100644
--- a/cross-project-tests/debuginfo-tests/dexter/dex/evaluation/ExpectWriter.py
+++ b/cross-project-tests/debuginfo-tests/dexter/dex/evaluation/ExpectWriter.py
@@ -26,10 +26,7 @@ def __init__(self, expect: Expect, value: ValueIR):
         self.expect = expect
         self.root_value = value
         self.expected_value: Union[Dict, str, None] = None
-        print(value)
         if sub_values := self.root_value.sub_values:
-            for sub_value in sub_values:
-                print(f"  {sub_value}")
             self.expected_value = {
                 sub_value.expression: expected_value
                 for sub_value in sub_values



More information about the llvm-branch-commits mailing list