[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 17 07:59:40 PDT 2026


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

>From 6b811f9dcb7c4da23f5788aa3174f005a4e6d2bc 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/3] [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 6d976c967037dc3bd97aee6be9fe281f63f3ae86 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/3] 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

>From fc6a4766ed8966f842f984ee021f2e578963c674 Mon Sep 17 00:00:00 2001
From: Stephen Tozer <stephen.tozer at sony.com>
Date: Wed, 10 Jun 2026 13:24:57 +0100
Subject: [PATCH 3/3] format

---
 .../Inputs/rewrite_aggregates_expected.cpp       | 16 ++++++++--------
 .../Inputs/rewrite_list_aggregates_expected.cpp  |  6 +++---
 .../scripts/rewriting/rewrite_aggregates.cpp     | 16 ++++++++--------
 .../rewriting/rewrite_list_aggregates.cpp        |  6 +++---
 4 files changed, 22 insertions(+), 22 deletions(-)

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
index cac4dd0cf8388..8f9c5206e6480 100644
--- 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
@@ -22,21 +22,21 @@
 // CHECK: missing_values: 0
 
 struct Point {
-    int X;
-    int Y;
-    int Z;
+  int X;
+  int Y;
+  int Z;
 };
 
 struct Rect {
-    Point TopLeft;
-    Point BottomRight;
+  Point TopLeft;
+  Point BottomRight;
 };
 
 int main() {
-  Point P { 1, 2, 3 };
+  Point P{1, 2, 3};
   int *I = &P.X;
-  Rect R { { 1, 1, 1 }, { 2, 2, 2 } };
-  int L[] = { 0, 1, 2, 3, 4 };
+  Rect R{{1, 1, 1}, {2, 2, 2}};
+  int L[] = {0, 1, 2, 3, 4};
   int *InvalidPtr = nullptr;
   return 0; // !dex_label ret
 }
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
index 16efa669e2b7c..630ed6e887413 100644
--- 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
@@ -16,12 +16,12 @@
 // CHECK: Rewrote script to add 1 expected values.
 
 struct Point {
-    int X;
-    int Y;
+  int X;
+  int Y;
 };
 
 int main() {
-  Point P { 1, 2 };
+  Point P{1, 2};
   P.X = 3; // !dex_label start
   P.Y = 0;
   P.X = 1;
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
index 2419aae5876e7..da55a3345093c 100644
--- 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
@@ -22,21 +22,21 @@
 // CHECK: missing_values: 0
 
 struct Point {
-    int X;
-    int Y;
-    int Z;
+  int X;
+  int Y;
+  int Z;
 };
 
 struct Rect {
-    Point TopLeft;
-    Point BottomRight;
+  Point TopLeft;
+  Point BottomRight;
 };
 
 int main() {
-  Point P { 1, 2, 3 };
+  Point P{1, 2, 3};
   int *I = &P.X;
-  Rect R { { 1, 1, 1 }, { 2, 2, 2 } };
-  int L[] = { 0, 1, 2, 3, 4 };
+  Rect R{{1, 1, 1}, {2, 2, 2}};
+  int L[] = {0, 1, 2, 3, 4};
   int *InvalidPtr = nullptr;
   return 0; // !dex_label ret
 }
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
index becf9d31d34d0..ab517d1342b2f 100644
--- 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
@@ -16,12 +16,12 @@
 // CHECK: Rewrote script to add 1 expected values.
 
 struct Point {
-    int X;
-    int Y;
+  int X;
+  int Y;
 };
 
 int main() {
-  Point P { 1, 2 };
+  Point P{1, 2};
   P.X = 3; // !dex_label start
   P.Y = 0;
   P.X = 1;



More information about the llvm-branch-commits mailing list