[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 24 07:30:15 PDT 2026
https://github.com/SLTozer updated https://github.com/llvm/llvm-project/pull/202800
>From dbcaf854f507db149d42c5b9be84c247ec271756 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/ExpectRewriter.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/ExpectRewriter.py b/cross-project-tests/debuginfo-tests/dexter/dex/evaluation/ExpectRewriter.py
index 7519d96a02a6d..19b394064cd38 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,23 @@ 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
+ 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[ExpectedValueRewriter]):
@@ -33,14 +49,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..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 5daf98676244e4104d5bd8a656caafe92735754e 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/ExpectRewriter.py | 3 ---
1 file changed, 3 deletions(-)
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 19b394064cd38..961ca9858b8f9 100644
--- a/cross-project-tests/debuginfo-tests/dexter/dex/evaluation/ExpectRewriter.py
+++ b/cross-project-tests/debuginfo-tests/dexter/dex/evaluation/ExpectRewriter.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 021663a1746fd2dadfc43f9f84c644cdf45b30ac 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