[llvm-branch-commits] [llvm] [Dexter] Add ability to rewrite scripts to fill-in unknown values (PR #202799)
Stephen Tozer via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Mon Jun 15 05:42:19 PDT 2026
https://github.com/SLTozer updated https://github.com/llvm/llvm-project/pull/202799
>From a40271fe2f46912f757cafcf9fb95724e3dc2b9f Mon Sep 17 00:00:00 2001
From: Stephen Tozer <stephen.tozer at sony.com>
Date: Tue, 9 Jun 2026 15:40:46 +0100
Subject: [PATCH 1/2] [Dexter] Add ability to rewrite scripts to fill-in
unknown values
This patch adds a feature to Dexter that allows scripts to be passed to
Dexter with missing expected values (`null` values in YAML), which Dexter
will attempt to "fill-in" with expected values that match the debugger's
actual output. The result is written to a file with the same name as the
original test file, in the directory given by --results-directory if one
is present; all content outside of the Dexter script itself is preserved
exactly as-is.
---
.../dexter/dex/evaluation/ExpectWriter.py | 201 ++++++++++++++++++
.../dexter/dex/test_script/Script.py | 26 +++
.../dexter/dex/tools/ToolBase.py | 3 +-
.../dexter/dex/tools/test/Tool.py | 40 +++-
.../Inputs/rewrite_expect_list_expected.cpp | 128 +++++++++++
.../Inputs/rewrite_expects_expected.cpp | 51 +++++
.../rewrite_multiple_scripts_expected.cpp | 46 ++++
.../scripts/rewriting/Inputs/simple_prog.cpp | 7 +
.../rewriting/Inputs/whole_file_test.dex | 2 +
.../Inputs/whole_file_test_expected.dex | 6 +
.../scripts/rewriting/rewrite_expect_list.cpp | 42 ++++
.../scripts/rewriting/rewrite_expects.cpp | 52 +++++
.../rewriting/rewrite_multiple_scripts.cpp | 46 ++++
.../scripts/rewriting/whole_file.test | 22 ++
14 files changed, 665 insertions(+), 7 deletions(-)
create mode 100644 cross-project-tests/debuginfo-tests/dexter/dex/evaluation/ExpectWriter.py
create mode 100644 cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/rewrite_expect_list_expected.cpp
create mode 100644 cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/rewrite_expects_expected.cpp
create mode 100644 cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/rewrite_multiple_scripts_expected.cpp
create mode 100644 cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/simple_prog.cpp
create mode 100644 cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/whole_file_test.dex
create mode 100644 cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/whole_file_test_expected.dex
create mode 100644 cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/rewrite_expect_list.cpp
create mode 100644 cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/rewrite_expects.cpp
create mode 100644 cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/rewrite_multiple_scripts.cpp
create mode 100644 cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/whole_file.test
diff --git a/cross-project-tests/debuginfo-tests/dexter/dex/evaluation/ExpectWriter.py b/cross-project-tests/debuginfo-tests/dexter/dex/evaluation/ExpectWriter.py
new file mode 100644
index 0000000000000..b943aadc9abfa
--- /dev/null
+++ b/cross-project-tests/debuginfo-tests/dexter/dex/evaluation/ExpectWriter.py
@@ -0,0 +1,201 @@
+# DExTer : Debugging Experience Tester
+# ~~~~~~ ~ ~~ ~ ~~
+#
+# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+# See https://llvm.org/LICENSE.txt for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+"""Utilities for using debugger output to generate expected values that match that output."""
+
+from collections import Counter, OrderedDict, defaultdict
+from copy import deepcopy
+from enum import Enum, IntEnum
+from typing import Any, Dict, List, Optional, Set, Tuple, Union
+
+from dex.dextIR import DextIR, StepIR, ValueIR
+from dex.evaluation.StateMatch import get_active_where_matches
+from dex.test_script.Nodes import Expect, Then, Value, Where
+from dex.test_script.Script import DexterScript, Scope
+from dex.tools.Main import Context
+
+
+class ExpectedValueWriter:
+ """Given a ValueIR for an Expect, generates a complete expected value that matches that value if one can be
+ provided."""
+
+ def __init__(self, expect: Expect, value: ValueIR):
+ self.expect = expect
+ self.root_value = value
+ self.expected_value = expect.get_variable_result(value)
+
+
+def unique_expected_values(elements: List[ExpectedValueWriter]):
+ """Given a list of ExpectedValueWriters, and returns either a list containing the unique set of non-None expected
+ 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."""
+
+ 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)
+ result.append(expected_value)
+ if not result:
+ return None
+ if len(result) == 1:
+ return result[0]
+ return result
+
+
+class StepExpectWriter:
+ """Processes all active, unknown expects at a given debugger step and produces ExpectedValueWriter results for
+ each."""
+
+ def __init__(self, step: StepIR, script: DexterScript):
+ self.step = step
+ self.script = script
+ self.state_match = get_active_where_matches(script, step)
+ active_expects = {
+ expect
+ for where_match in self.state_match.values()
+ for expect in where_match.active_expects
+ }
+ self.expect_matches: Dict[Expect, ExpectedValueWriter] = {}
+
+ def add_expected_values(expect: Expect, expected_value: Any, scope: Scope):
+ assert isinstance(expect, Value), "Non-Value expects currently unsupported"
+ if expect in active_expects and expected_value is None:
+ self.expect_matches[expect] = ExpectedValueWriter(
+ expect, step.watches[expect.get_watched_expr()]
+ )
+
+ script.visit_script(visit_expect=add_expected_values)
+
+
+class ScriptExpectWriter:
+ """Given the full output from a debugger run and a script with missing expected values, returns a script with
+ filled-in expected values that match the debugger output."""
+
+ def __init__(self, context: Context, dext_ir: DextIR):
+ self.context = context
+ self.dext_ir = dext_ir
+ self.unknown_expect_rewrites: Dict[
+ Expect, List[Tuple[int, ExpectedValueWriter]]
+ ] = {}
+ self.new_script: Optional[DexterScript] = None
+ self.new_expected_values: Dict[Expect, Any] = {}
+ self.missing_expect_rewrites: List[Expect] = []
+
+ def collect_unknown_expects(expect: Expect, expected_value: Any, scope: Scope):
+ assert isinstance(expect, Value), "Non-Value expects currently unsupported"
+ if expected_value is None:
+ self.unknown_expect_rewrites[expect] = []
+
+ script = dext_ir.script
+ assert (
+ script is not None
+ ), "Cannot use ScriptExpectWriter on a non-script Dexter test."
+ script.visit_script(visit_expect=collect_unknown_expects)
+
+ # If there are no expects to update, then there is no rewriting to be done - exit early.
+ if not self.unknown_expect_rewrites:
+ return
+
+ self.step_writers = [StepExpectWriter(step, script) for step in dext_ir.steps]
+ for step_writer in self.step_writers:
+ step_idx = step_writer.step.step_index
+ for expect, expected_value_writer in step_writer.expect_matches.items():
+ self.unknown_expect_rewrites[expect].append(
+ (step_idx, expected_value_writer)
+ )
+
+ self.new_expected_values = {
+ expect: expected_values
+ for expect, expect_writers in self.unknown_expect_rewrites.items()
+ if (
+ expected_values := unique_expected_values(
+ [writer for idx, writer in expect_writers]
+ )
+ )
+ is not None
+ }
+ self.new_script = rewrite_script(script, self.new_expected_values)
+ self.missing_expect_rewrites = [
+ expect
+ for expect in self.unknown_expect_rewrites
+ if expect not in self.new_expected_values
+ ]
+
+ @property
+ def num_successful_rewrites(self):
+ return len(self.new_expected_values)
+
+ @property
+ def num_unsuccessful_rewrites(self):
+ return len(self.missing_expect_rewrites)
+
+
+def rewrite_script(
+ script: DexterScript, add_expected_values: Dict[Expect, Any]
+) -> DexterScript:
+ """Given a set of updates to apply to a provided script, returns a copy of the script_obj with the updates
+ applied.
+ Does not deep copy, meaning the new script contains the same node objects as the old script; this is safe as we do
+ not modify these objects."""
+ # First build up a map describing the children of every node in the script, adding add_expected_values to the
+ # required expect nodes.
+ new_node_child_map = {}
+
+ def replace_where(where: Where, scope: Scope):
+ if scope.where:
+ scope_where_children = new_node_child_map.setdefault(scope.where, [])
+ assert isinstance(
+ scope_where_children, list
+ ), f"Unexpected child for !where node: {scope_where_children}"
+ scope_where_children.append(where)
+
+ def replace_then(then: Then, scope: Scope):
+ assert (
+ scope.where not in new_node_child_map
+ ), "!then must be the sole child of a state node."
+ new_node_child_map[scope.where] = then
+
+ def replace_expect(expect: Expect, expected_value, scope: Scope):
+ new_expected_value = add_expected_values.get(expect) or expected_value
+ new_node_child_map[expect] = new_expected_value
+ scope_where_children = new_node_child_map.setdefault(scope.where, [])
+ assert isinstance(
+ scope_where_children, list
+ ), f"Unexpected child for state node {scope.where}: {scope_where_children}"
+ scope_where_children.append(expect)
+
+ script.visit_script(
+ visit_where=replace_where, visit_expect=replace_expect, visit_then=replace_then
+ )
+
+ # Now rebuild the script object using the two maps.
+ def build_subscript(node):
+ """Returns the subset of the script object whose parent is the given node."""
+ assert isinstance(
+ node, (Expect, Where)
+ ), f"Unexpected script parent node: {node}"
+ if isinstance(node, Expect):
+ return new_node_child_map[node]
+ node_children = new_node_child_map[node]
+ if isinstance(node_children, Then):
+ return node_children
+ assert isinstance(
+ node_children, List
+ ), f"Unexpected child for state node {node}: {node_children}"
+ return {child: build_subscript(child) for child in node_children}
+
+ new_script_obj = {node: build_subscript(node) for node in script.script_obj}
+ return DexterScript(
+ script.context,
+ new_script_obj,
+ script.root_scope,
+ script.base_dir,
+ script.load_context,
+ )
diff --git a/cross-project-tests/debuginfo-tests/dexter/dex/test_script/Script.py b/cross-project-tests/debuginfo-tests/dexter/dex/test_script/Script.py
index 26a38a604dbe5..50a5cb48a200e 100644
--- a/cross-project-tests/debuginfo-tests/dexter/dex/test_script/Script.py
+++ b/cross-project-tests/debuginfo-tests/dexter/dex/test_script/Script.py
@@ -139,6 +139,16 @@ def get_known_file_for_where(self, where: Where) -> Optional[str]:
return next_scope.file
+class ScriptLoadContext:
+ """Contains information about the context that the script was loaded from."""
+
+ def __init__(self, file: str, lines: List[str], start_line: int, stop_line: int):
+ self.file = file
+ self.lines = lines
+ self.start_line = start_line
+ self.stop_line = stop_line
+
+
class DexterScript:
def __init__(
self,
@@ -146,10 +156,12 @@ def __init__(
script_obj,
scope: Scope,
source_root_dir: Optional[str],
+ load_context: ScriptLoadContext,
):
self.context = context
self.script_obj = script_obj
self.root_scope = scope
+ self.load_context = load_context
self.label_dict = LabelDict()
assert scope.file is not None
self.base_dir = (
@@ -271,6 +283,7 @@ def get_script(context, file, loader, source_root_dir: Optional[str]) -> DexterS
try_load_yaml("\n".join(lines), loader),
root_scope,
source_root_dir,
+ ScriptLoadContext(file, lines, start_line=0, stop_line=len(lines)),
)
except (Error, yaml.YAMLError) as e:
raise Error(f"File '{file}' was not a valid Dexter script:\n{e}")
@@ -293,6 +306,7 @@ def get_script(context, file, loader, source_root_dir: Optional[str]) -> DexterS
),
root_scope,
source_root_dir,
+ ScriptLoadContext(file, lines, start_line, stop_line),
)
except (Error, yaml.YAMLError) as e:
attempted_scripts.append((start_line, e))
@@ -330,3 +344,15 @@ def check_explicit_files(where: Where, _: Scope):
script.visit_script(visit_where=check_explicit_files)
return script, source_files
+
+
+def write_dexter_script_file(script: DexterScript) -> str:
+ load_context = script.load_context
+ script_lines = script.dump().splitlines(True)
+ write_lines = (
+ load_context.lines[: load_context.start_line]
+ + script_lines
+ + ["...\n"]
+ + load_context.lines[load_context.stop_line :]
+ )
+ return "".join(write_lines)
diff --git a/cross-project-tests/debuginfo-tests/dexter/dex/tools/ToolBase.py b/cross-project-tests/debuginfo-tests/dexter/dex/tools/ToolBase.py
index 4b09c134a1b6e..d54dd3924e407 100644
--- a/cross-project-tests/debuginfo-tests/dexter/dex/tools/ToolBase.py
+++ b/cross-project-tests/debuginfo-tests/dexter/dex/tools/ToolBase.py
@@ -11,6 +11,7 @@
import tempfile
from dex import __version__
+from dex.tools.Main import Context
from dex.utils import ExtArgParse
from dex.utils import PrettyOutput
from dex.utils.ReturnCode import ReturnCode
@@ -18,7 +19,7 @@
class ToolBase(object, metaclass=abc.ABCMeta):
def __init__(self, context):
- self.context = context
+ self.context: Context = context
self.parser = None
@abc.abstractproperty
diff --git a/cross-project-tests/debuginfo-tests/dexter/dex/tools/test/Tool.py b/cross-project-tests/debuginfo-tests/dexter/dex/tools/test/Tool.py
index 0c028773ec56c..ed14a95aece75 100644
--- a/cross-project-tests/debuginfo-tests/dexter/dex/tools/test/Tool.py
+++ b/cross-project-tests/debuginfo-tests/dexter/dex/tools/test/Tool.py
@@ -24,8 +24,13 @@
)
from dex.dextIR.DextIR import DextIR
from dex.evaluation import DebuggerRunMatch
+from dex.evaluation.ExpectWriter import ScriptExpectWriter
from dex.heuristic import Heuristic
-from dex.test_script.Script import get_dexter_script
+from dex.test_script.Script import (
+ DexterScript,
+ get_dexter_script,
+ write_dexter_script_file,
+)
from dex.tools import TestToolBase
from dex.utils.Exceptions import DebuggerException
from dex.utils.Exceptions import BuildScriptException, HeuristicException
@@ -155,10 +160,6 @@ def _init_debugger_controller(self):
self.context.options.source_files.extend(list(new_source_files))
- # If we are not running a debugger, return the DextIR instead of a DebuggerController.
- if self.context.options.skip_run:
- return step_collection
-
if self.context.options.use_script:
debugger_controller = ScriptDebuggerController(
self.context, step_collection
@@ -180,7 +181,8 @@ def _get_steps(self):
if self.context.options.skip_run:
self.context.logger.warning("Skipping run...")
- return debugger_controller
+ assert isinstance(debugger_controller.step_collection, DextIR)
+ return debugger_controller.step_collection
debugger_controller = run_debugger_subprocess(
debugger_controller, self.context.working_directory.path
)
@@ -254,6 +256,16 @@ def _record_structured_script_metric_results(
with open(output_json_path, "w") as fp:
json.dump(run_match.get_metric_json_output(), fp)
+ def _write_updated_structured_script(
+ self, test_name, rewritten_script: DexterScript
+ ):
+ """Write out the original script file, modified to replace any unknown expects with the actual observed
+ values."""
+ if self.context.options.results_directory:
+ output_text_path = self._get_results_path(test_name)
+ with open(output_text_path, "w", encoding="utf-8") as fp:
+ fp.write(write_dexter_script_file(rewritten_script))
+
def _record_test_and_display(self, test_case):
"""Output test case to o stream and record test case internally for
handling later.
@@ -313,6 +325,22 @@ def _run_test(self, test_name):
return
self._record_steps(test_name, steps)
if self.context.options.use_script:
+ # Before evaluating, the script may contain "unknown" expects; if they should be rewritten, then do so
+ # first, and then use the rewritten script to evaluate.
+ script_writer = ScriptExpectWriter(self.context, steps)
+ if script_writer.new_script:
+ self.context.logger.note(
+ f"Rewrote script to add {script_writer.num_successful_rewrites} expected values."
+ )
+ if script_writer.num_unsuccessful_rewrites:
+ self.context.logger.warning(
+ f"Failed to rewrite {script_writer.num_unsuccessful_rewrites} expected values."
+ )
+ self._write_updated_structured_script(
+ test_name, script_writer.new_script
+ )
+ steps.script = script_writer.new_script
+ # Then evaluate, using the new script if any was produced.
run_match = DebuggerRunMatch(self.context, steps)
self._record_structured_script_metric_results(test_name, run_match)
self._record_successful_test_match(test_name, steps, run_match)
diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/rewrite_expect_list_expected.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/rewrite_expect_list_expected.cpp
new file mode 100644
index 0000000000000..cec451fdd69c9
--- /dev/null
+++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/rewrite_expect_list_expected.cpp
@@ -0,0 +1,128 @@
+// 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_expect_list_expected.cpp
+
+/// Test that Dexter can write lists of expected values for simple scalar
+/// variables.
+
+/// 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 3 expected values.
+
+// CHECK: total_watched_steps: 90
+// CHECK: correct_steps: 90
+// CHECK: incorrect_steps: 0
+// CHECK: seen_values: 86
+// CHECK: missing_values: 0
+
+int main() {
+ int prev = 0;
+ int current = 0;
+ int next = 1;
+ for (int i = 0; i < 30; ++i) {
+ prev = current; // !dex_label loop
+ current = next;
+ next = prev + current;
+ }
+ return current;
+}
+
+/*
+---
+? !where {lines: !label 'loop'}
+: !value 'prev':
+ - '0'
+ - '1'
+ - '2'
+ - '3'
+ - '5'
+ - '8'
+ - '13'
+ - '21'
+ - '34'
+ - '55'
+ - '89'
+ - '144'
+ - '233'
+ - '377'
+ - '610'
+ - '987'
+ - '1597'
+ - '2584'
+ - '4181'
+ - '6765'
+ - '10946'
+ - '17711'
+ - '28657'
+ - '46368'
+ - '75025'
+ - '121393'
+ - '196418'
+ - '317811'
+ !value 'current':
+ - '0'
+ - '1'
+ - '2'
+ - '3'
+ - '5'
+ - '8'
+ - '13'
+ - '21'
+ - '34'
+ - '55'
+ - '89'
+ - '144'
+ - '233'
+ - '377'
+ - '610'
+ - '987'
+ - '1597'
+ - '2584'
+ - '4181'
+ - '6765'
+ - '10946'
+ - '17711'
+ - '28657'
+ - '46368'
+ - '75025'
+ - '121393'
+ - '196418'
+ - '317811'
+ - '514229'
+ !value 'next':
+ - '1'
+ - '2'
+ - '3'
+ - '5'
+ - '8'
+ - '13'
+ - '21'
+ - '34'
+ - '55'
+ - '89'
+ - '144'
+ - '233'
+ - '377'
+ - '610'
+ - '987'
+ - '1597'
+ - '2584'
+ - '4181'
+ - '6765'
+ - '10946'
+ - '17711'
+ - '28657'
+ - '46368'
+ - '75025'
+ - '121393'
+ - '196418'
+ - '317811'
+ - '514229'
+ - '832040'
+...
+*/
diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/rewrite_expects_expected.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/rewrite_expects_expected.cpp
new file mode 100644
index 0000000000000..2c04bcdc0fe0c
--- /dev/null
+++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/rewrite_expects_expected.cpp
@@ -0,0 +1,51 @@
+// 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_expects_expected.cpp
+
+/// Test that when we have a Dexter test with missing/unknown expected values,
+/// Dexter produces a modified test file that is identical except for a modified
+/// script section.
+
+/// 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 6 expected values.
+// CHECK: Failed to rewrite 2 expected values.
+
+// CHECK: total_watched_steps: 7
+// CHECK: correct_steps: 6
+// CHECK: incorrect_steps: 1
+// CHECK: seen_values: 6
+// CHECK: missing_values: 2
+
+int multiply(int b, int a) {
+ int result = a * b;
+ return result; // !dex_label mul_ret
+}
+
+int main() {
+ int a = 6;
+ int b = 7;
+ int c = multiply(a, b);
+ return c; // !dex_label main_ret
+}
+// !dex_label never_reached
+/*
+---
+? !where {lines: !label 'mul_ret'}
+: !value 'a': '7'
+ !value 'b': '6'
+ !value 'result': '42'
+? !where {lines: !label 'main_ret'}
+: !value 'a': '6'
+ !value 'b': '7'
+ !value 'c': '42'
+ !value 'not_real': null
+? !where {lines: !label 'never_reached'}
+: !value 'a': null
+...
+*/
diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/rewrite_multiple_scripts_expected.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/rewrite_multiple_scripts_expected.cpp
new file mode 100644
index 0000000000000..525889107895b
--- /dev/null
+++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/rewrite_multiple_scripts_expected.cpp
@@ -0,0 +1,46 @@
+// 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_multiple_scripts_expected.cpp
+
+/// Test that when a file contains more than one valid YAML script (but only one
+/// Dexter script), the existing YAML is printed correctly.
+
+/// 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.
+
+// CHECK: total_watched_steps: 1
+// CHECK: correct_steps: 1
+// CHECK: incorrect_steps: 0
+// CHECK: seen_values: 1
+// CHECK: missing_values: 0
+
+/*
+---
+hr: # 1998 hr ranking
+- Mark McGwire
+- Sammy Sosa
+# 1998 rbi ranking
+rbi:
+- Sammy Sosa
+- Ken Griffey
+...
+*/
+
+int main() {
+ int ret = 0;
+ return ret; // !dex_label ret
+}
+
+/*
+---
+? !where {lines: !label 'ret'}
+: !value 'ret': '0'
+...
+*/
diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/simple_prog.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/simple_prog.cpp
new file mode 100644
index 0000000000000..177289e1c3ff2
--- /dev/null
+++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/simple_prog.cpp
@@ -0,0 +1,7 @@
+
+int main() {
+ int i = 0;
+ i += 1; // !dex_label start
+ i += 1;
+ return i; // !dex_label end
+}
diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/whole_file_test.dex b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/whole_file_test.dex
new file mode 100644
index 0000000000000..4501532871cc9
--- /dev/null
+++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/whole_file_test.dex
@@ -0,0 +1,2 @@
+!where {file: 'simple_prog.cpp', lines: !range [!label start, !label end]}:
+ ? !value i
\ No newline at end of file
diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/whole_file_test_expected.dex b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/whole_file_test_expected.dex
new file mode 100644
index 0000000000000..c1cf5e87915a5
--- /dev/null
+++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/whole_file_test_expected.dex
@@ -0,0 +1,6 @@
+? !where {file: simple_prog.cpp, lines: !range [!label 'start', !label 'end']}
+: !value 'i':
+ - '0'
+ - '1'
+ - '2'
+...
diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/rewrite_expect_list.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/rewrite_expect_list.cpp
new file mode 100644
index 0000000000000..dee5c936522e5
--- /dev/null
+++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/rewrite_expect_list.cpp
@@ -0,0 +1,42 @@
+// 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_expect_list_expected.cpp
+
+/// Test that Dexter can write lists of expected values for simple scalar
+/// variables.
+
+/// 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 3 expected values.
+
+// CHECK: total_watched_steps: 90
+// CHECK: correct_steps: 90
+// CHECK: incorrect_steps: 0
+// CHECK: seen_values: 86
+// CHECK: missing_values: 0
+
+int main() {
+ int prev = 0;
+ int current = 0;
+ int next = 1;
+ for (int i = 0; i < 30; ++i) {
+ prev = current; // !dex_label loop
+ current = next;
+ next = prev + current;
+ }
+ return current;
+}
+
+/*
+---
+!where {lines: !label loop}:
+ ? !value prev
+ ? !value current
+ ? !value next
+...
+*/
diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/rewrite_expects.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/rewrite_expects.cpp
new file mode 100644
index 0000000000000..256804e114ad7
--- /dev/null
+++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/rewrite_expects.cpp
@@ -0,0 +1,52 @@
+// 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_expects_expected.cpp
+
+/// Test that when we have a Dexter test with missing/unknown expected values,
+/// Dexter produces a modified test file that is identical except for a modified
+/// script section.
+
+/// 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 6 expected values.
+// CHECK: Failed to rewrite 2 expected values.
+
+// CHECK: total_watched_steps: 7
+// CHECK: correct_steps: 6
+// CHECK: incorrect_steps: 1
+// CHECK: seen_values: 6
+// CHECK: missing_values: 2
+
+int multiply(int b, int a) {
+ int result = a * b;
+ return result; // !dex_label mul_ret
+}
+
+int main() {
+ int a = 6;
+ int b = 7;
+ int c = multiply(a, b);
+ return c; // !dex_label main_ret
+}
+// !dex_label never_reached
+/*
+---
+# Comments in the Dexter script are not preserved.
+!where {lines: !label mul_ret}:
+ ? !value a
+ ? !value b
+ ? !value result
+!where {lines: !label main_ret}:
+ ? !value a
+ ? !value b
+ ? !value c
+ ? !value not_real
+!where {lines: !label never_reached}:
+ ? !value a
+...
+*/
diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/rewrite_multiple_scripts.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/rewrite_multiple_scripts.cpp
new file mode 100644
index 0000000000000..f11a6d619e1ff
--- /dev/null
+++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/rewrite_multiple_scripts.cpp
@@ -0,0 +1,46 @@
+// 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_multiple_scripts_expected.cpp
+
+/// Test that when a file contains more than one valid YAML script (but only one
+/// Dexter script), the existing YAML is printed correctly.
+
+/// 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.
+
+// CHECK: total_watched_steps: 1
+// CHECK: correct_steps: 1
+// CHECK: incorrect_steps: 0
+// CHECK: seen_values: 1
+// CHECK: missing_values: 0
+
+/*
+---
+hr: # 1998 hr ranking
+- Mark McGwire
+- Sammy Sosa
+# 1998 rbi ranking
+rbi:
+- Sammy Sosa
+- Ken Griffey
+...
+*/
+
+int main() {
+ int ret = 0;
+ return ret; // !dex_label ret
+}
+
+/*
+---
+!where {lines: !label ret}:
+ ? !value ret
+...
+*/
diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/whole_file.test b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/whole_file.test
new file mode 100644
index 0000000000000..7168e4a7dd8ac
--- /dev/null
+++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/whole_file.test
@@ -0,0 +1,22 @@
+RUN: rm -rf %t
+RUN: mkdir %t
+RUN: %dexter_regression_test_cxx_build %S/Inputs/simple_prog.cpp -o %t/test
+RUN: %dexter_regression_test_run --use-script --binary %t/test \
+RUN: --results-directory %t/results --source-root-dir %S/Inputs -- \
+RUN: %S/Inputs/whole_file_test.dex 2>&1 | FileCheck %s
+RUN: diff %t/results/whole_file_test.dex %S/Inputs/whole_file_test_expected.dex
+
+Test that script rewriting still works when the test file is pure YAML, rather
+than a YAML Dexter script embedded in another file.
+
+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.
+
+CHECK: total_watched_steps: 3
+CHECK: correct_steps: 3
+CHECK: incorrect_steps: 0
+CHECK: seen_values: 3
+CHECK: missing_values: 0
\ No newline at end of file
>From 9d0538ac6347549a76e94879d0c9e384ec1baed9 Mon Sep 17 00:00:00 2001
From: Stephen Tozer <stephen.tozer at sony.com>
Date: Wed, 10 Jun 2026 13:23:51 +0100
Subject: [PATCH 2/2] format
---
.../Inputs/rewrite_multiple_scripts_expected.cpp | 4 ++--
.../scripts/rewriting/Inputs/simple_prog.cpp | 8 ++++----
.../scripts/rewriting/rewrite_multiple_scripts.cpp | 4 ++--
3 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/rewrite_multiple_scripts_expected.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/rewrite_multiple_scripts_expected.cpp
index 525889107895b..134d32f93a5a4 100644
--- a/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/rewrite_multiple_scripts_expected.cpp
+++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/rewrite_multiple_scripts_expected.cpp
@@ -34,8 +34,8 @@ hr: # 1998 hr ranking
*/
int main() {
- int ret = 0;
- return ret; // !dex_label ret
+ int ret = 0;
+ return ret; // !dex_label ret
}
/*
diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/simple_prog.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/simple_prog.cpp
index 177289e1c3ff2..72567b9db3e51 100644
--- a/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/simple_prog.cpp
+++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/simple_prog.cpp
@@ -1,7 +1,7 @@
int main() {
- int i = 0;
- i += 1; // !dex_label start
- i += 1;
- return i; // !dex_label end
+ int i = 0;
+ i += 1; // !dex_label start
+ i += 1;
+ return i; // !dex_label end
}
diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/rewrite_multiple_scripts.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/rewrite_multiple_scripts.cpp
index f11a6d619e1ff..ce3b8f6b2db38 100644
--- a/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/rewrite_multiple_scripts.cpp
+++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/rewrite_multiple_scripts.cpp
@@ -34,8 +34,8 @@ hr: # 1998 hr ranking
*/
int main() {
- int ret = 0;
- return ret; // !dex_label ret
+ int ret = 0;
+ return ret; // !dex_label ret
}
/*
More information about the llvm-branch-commits
mailing list