https://github.com/SLTozer updated https://github.com/llvm/llvm-project/pull/203505
>From 205a3ba50840de03c482c2ac2e771031a882c1bb Mon Sep 17 00:00:00 2001 From: Stephen Tozer <[email protected]> Date: Fri, 12 Jun 2026 11:57:05 +0100 Subject: [PATCH] [Dexter] Add at_frame_idx to check values in frames above current This patch adds a new attribute for !and nodes, `at_frame_idx`, which matches against frames above its parent node; for example, in the script: ``` !where {function: foo}: !where {function: bar}: !and {at_frame_idx: 1}: !value x: 0 ``` The `!value x` node checks the value of 'x' in 'foo' while the debugger is inside 'bar'. Use of this attribute comes with some restrictions: a !where node can never be nested under a !and{at_frame_idx} node, and neither can another !and{at_frame_idx} node. --- .../dexter/dex/debugger/DAP.py | 15 +++-- .../dexter/dex/debugger/DebuggerBase.py | 2 +- .../ScriptDebuggerController.py | 31 ++++++---- .../dexter/dex/debugger/dbgeng/dbgeng.py | 2 +- .../dexter/dex/debugger/lldb/LLDB.py | 2 +- .../dex/debugger/visualstudio/VisualStudio.py | 2 +- .../dexter/dex/dextIR/FrameIR.py | 3 + .../dexter/dex/dextIR/StepIR.py | 41 ++++++++----- .../dexter/dex/evaluation/ExpectRewriter.py | 13 ++-- .../dexter/dex/evaluation/RunMatch.py | 5 +- .../dexter/dex/evaluation/StateMatch.py | 24 +++++--- .../dexter/dex/test_script/Nodes.py | 6 +- .../dexter/dex/test_script/Script.py | 25 +++++++- .../scripts/debugging/then_at_frame.cpp | 60 ++++++++++++++++++ .../scripts/evaluation/eval_at_frame.cpp | 46 ++++++++++++++ .../parser/reject-bad-at_frame_idx.test | 33 ++++++++++ .../Inputs/rewrite_at_frame_expected.cpp | 61 +++++++++++++++++++ .../scripts/rewriting/rewrite_at_frame.cpp | 49 +++++++++++++++ 18 files changed, 367 insertions(+), 53 deletions(-) create mode 100644 cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/debugging/then_at_frame.cpp create mode 100644 cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/evaluation/eval_at_frame.cpp create mode 100644 cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/parser/reject-bad-at_frame_idx.test create mode 100644 cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/rewrite_at_frame_expected.cpp create mode 100644 cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/rewrite_at_frame.cpp diff --git a/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DAP.py b/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DAP.py index b1e2538ea8891..04675f7409002 100644 --- a/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DAP.py +++ b/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DAP.py @@ -1016,10 +1016,9 @@ def get_stack_frames(self, step_index: int) -> StepIR: ) def collect_watches( - self, step: StepIR, watches: List[str], scope_watches: List[str] + self, step: StepIR, frame_idx: int, watches: List[str], scope_watches: List[str] ): """Evaluates the provided watches and stores their evaluation results (ValueIR) in the provided step.""" - frame_idx = 0 if not watches and not scope_watches: return active_exprs = set(watches) @@ -1055,15 +1054,19 @@ def collect_watches( ) self._evaluate_subvariables(value, var["variablesReference"]) scope_var_values[value.expression] = value - step.scope_watches[scope_name] = list(scope_var_values.keys()) - for var_name in sorted(step.scope_watches[scope_name]): - step.watches[var_name] = scope_var_values[var_name] + step.frames[frame_idx].scope_watches[scope_name] = sorted( + scope_var_values.keys() + ) + for var_name in sorted(step.frames[frame_idx].scope_watches[scope_name]): + step.frames[frame_idx].watches[var_name] = scope_var_values[var_name] for expr in list(active_exprs): if expr in scope_var_values: active_exprs.remove(expr) for expr in active_exprs: - step.watches[expr] = self.evaluate_expression(expr, frame_idx) + step.frames[frame_idx].watches[expr] = self.evaluate_expression( + expr, frame_idx + ) @property def is_running(self): diff --git a/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DebuggerBase.py b/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DebuggerBase.py index af8e69e75c212..8e1a1487b4d49 100644 --- a/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DebuggerBase.py +++ b/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DebuggerBase.py @@ -224,7 +224,7 @@ def get_stack_frames(self, step_index: int) -> StepIR: @abc.abstractmethod def collect_watches( - self, step: StepIR, watches: List[str], scope_watches: List[str] + self, step: StepIR, frame_idx: int, watches: List[str], scope_watches: List[str] ): pass diff --git a/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/ScriptDebuggerController.py b/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/ScriptDebuggerController.py index fc052b5274089..e80dd5405a3a0 100644 --- a/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/ScriptDebuggerController.py +++ b/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/ScriptDebuggerController.py @@ -137,19 +137,24 @@ def _run_debugger_custom(self, cmdline): script, step_info, state_match_context ) - watches = [ - watch - for where_match in active_where_matches.values() - for expect in where_match.active_expects - if (watch := expect.get_watched_expr()) - ] - scope_watches = [ - scope_watch - for where_match in active_where_matches.values() - for expect in where_match.active_expects - if (scope_watch := expect.get_watched_scope()) - ] - self.debugger.collect_watches(step_info, watches, scope_watches) + watches = defaultdict(list) + scope_watches = defaultdict(list) + for where, where_match in active_where_matches.items(): + watches[where_match.frame_idx].extend( + watch + for expect in where_match.active_expects + if (watch := expect.get_watched_expr()) + ) + scope_watches[where_match.frame_idx].extend( + watch + for expect in where_match.active_expects + if (watch := expect.get_watched_scope()) + ) + + for frame_idx in watches: + self.debugger.collect_watches( + step_info, frame_idx, watches[frame_idx], scope_watches[frame_idx] + ) active_thens = [ then diff --git a/cross-project-tests/debuginfo-tests/dexter/dex/debugger/dbgeng/dbgeng.py b/cross-project-tests/debuginfo-tests/dexter/dex/debugger/dbgeng/dbgeng.py index 86d5f78f4c556..c32e6df3d0094 100644 --- a/cross-project-tests/debuginfo-tests/dexter/dex/debugger/dbgeng/dbgeng.py +++ b/cross-project-tests/debuginfo-tests/dexter/dex/debugger/dbgeng/dbgeng.py @@ -172,7 +172,7 @@ def get_stack_frames(self, step_index: int) -> StepIR: raise NotImplementedError("--use-script debugging not supported in dbgeng yet.") def collect_watches( - self, step: StepIR, watches: List[str], scope_watches: List[str] + self, step: StepIR, frame_idx: int, watches: List[str], scope_watches: List[str] ): raise NotImplementedError("--use-script debugging not supported in dbgeng yet.") 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 d328498553938..6ab06d066dfe6 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 @@ -323,7 +323,7 @@ def get_stack_frames(self, step_index: int) -> StepIR: raise NotImplementedError("--use-script debugging not supported in lldb yet.") def collect_watches( - self, step: StepIR, watches: List[str], scope_watches: List[str] + self, step: StepIR, frame_idx: int, watches: List[str], scope_watches: List[str] ): raise NotImplementedError("--use-script debugging not supported in lldb yet.") diff --git a/cross-project-tests/debuginfo-tests/dexter/dex/debugger/visualstudio/VisualStudio.py b/cross-project-tests/debuginfo-tests/dexter/dex/debugger/visualstudio/VisualStudio.py index 606e08502fae3..63b0abf681932 100644 --- a/cross-project-tests/debuginfo-tests/dexter/dex/debugger/visualstudio/VisualStudio.py +++ b/cross-project-tests/debuginfo-tests/dexter/dex/debugger/visualstudio/VisualStudio.py @@ -399,7 +399,7 @@ def get_stack_frames(self, step_index: int) -> StepIR: ) def collect_watches( - self, step: StepIR, watches: List[str], scope_watches: List[str] + self, step: StepIR, frame_idx: int, watches: List[str], scope_watches: List[str] ): raise NotImplementedError( "--use-script debugging not supported in visual studio yet." diff --git a/cross-project-tests/debuginfo-tests/dexter/dex/dextIR/FrameIR.py b/cross-project-tests/debuginfo-tests/dexter/dex/dextIR/FrameIR.py index 4dd9a8b63ccc7..a34b26a4d0b07 100644 --- a/cross-project-tests/debuginfo-tests/dexter/dex/dextIR/FrameIR.py +++ b/cross-project-tests/debuginfo-tests/dexter/dex/dextIR/FrameIR.py @@ -4,6 +4,7 @@ # 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 +from collections import OrderedDict from typing import Optional from dex.dextIR.LocIR import LocIR @@ -23,3 +24,5 @@ def __init__( self.is_inlined = is_inlined self.loc = loc self.instruction_addr = instruction_addr + self.watches = {} + self.scope_watches = {} diff --git a/cross-project-tests/debuginfo-tests/dexter/dex/dextIR/StepIR.py b/cross-project-tests/debuginfo-tests/dexter/dex/dextIR/StepIR.py index 639e334f4aa20..67ade9342fac8 100644 --- a/cross-project-tests/debuginfo-tests/dexter/dex/dextIR/StepIR.py +++ b/cross-project-tests/debuginfo-tests/dexter/dex/dextIR/StepIR.py @@ -51,7 +51,6 @@ def __init__( frames: List[FrameIR], step_kind: StepKind = None, watches: OrderedDict = None, - scope_watches: Optional[Dict[str, List[str]]] = None, program_state: ProgramState = None, ): self.step_index = step_index @@ -66,7 +65,6 @@ def __init__( if watches is None: watches = {} self.watches = watches - self.scope_watches = scope_watches or OrderedDict() self.hit_where_bps: List[Where] = [] def __str__(self): @@ -128,18 +126,33 @@ def detailed_print(self) -> List[str]: lines.append(f" {frame.loc}") lines.append(f" $pc = {frame.instruction_addr}") - if self.scope_watches: + frame_scope_watches = { + frame_idx: frame.scope_watches + for frame_idx, frame in enumerate(self.frames) + } + if frame_scope_watches: lines.append(f"Variable Scopes:") - for scope_name in sorted(self.scope_watches): - lines.append( - f" {scope_name}: [{', '.join(self.scope_watches[scope_name])}]" - ) - - if self.watches: + for frame_idx, scope_watches in sorted(frame_scope_watches.items()): + frame_str = "" if frame_idx == 0 else f"(Frame {frame_idx}) " + for scope_name, scope_vars in sorted(scope_watches.items()): + lines.append( + f" {frame_str}{scope_name}: [{', '.join(scope_vars)}]" + ) + + frame_watches = { + frame_idx: frame.watches for frame_idx, frame in enumerate(self.frames) + } + if frame_watches: lines.append(f"Variables:") - for value in sorted(self.watches.values(), key=lambda v: v.expression): - if value.sub_values: - value.dump_nested(lines, 1) - else: - lines.append(f" {value}") + for frame_idx, watches in frame_watches.items(): + indent = 1 + if frame_idx != 0: + lines.append(f" Frame {frame_idx}:") + indent = 2 + for value in sorted(watches.values(), key=lambda v: v.expression): + if value.sub_values: + value.dump_nested(lines, indent) + else: + lines.append(f"{' ' * indent}{value}") + return lines 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 0a9b530c44bff..2d42062a70fff 100644 --- a/cross-project-tests/debuginfo-tests/dexter/dex/evaluation/ExpectRewriter.py +++ b/cross-project-tests/debuginfo-tests/dexter/dex/evaluation/ExpectRewriter.py @@ -167,7 +167,7 @@ def __init__( self.script = script self.state_match = get_active_where_matches(script, step, state_match_context) active_expects = { - expect + expect: where_match.frame_idx for where_match in self.state_match.values() for expect in where_match.active_expects } @@ -177,14 +177,19 @@ def __init__( def add_expected_values(expect: Expect, expected_value: Any, scope: Scope): if expect not in active_expects or expected_value is not None: return + expect_frame_idx = active_expects[expect] if (expr := expect.get_watched_expr()) is not None: self.expect_value_matches[expect] = ExpectedValueRewriter( - expect, step.watches[expr] + expect, step.frames[expect_frame_idx].watches[expr] ) elif (scope_name := expect.get_watched_scope()) is not None: - scope_vars = step.scope_watches.get(scope_name, []) + scope_vars = step.frames[expect_frame_idx].scope_watches.get( + scope_name, [] + ) self.expect_scope_matches[expect] = ExpectedScopeRewriter( - expect, step, [step.watches[var] for var in scope_vars] + expect, + step, + [step.frames[expect_frame_idx].watches[var] for var in scope_vars], ) else: raise Error( diff --git a/cross-project-tests/debuginfo-tests/dexter/dex/evaluation/RunMatch.py b/cross-project-tests/debuginfo-tests/dexter/dex/evaluation/RunMatch.py index e09cce69e6c28..75f6e6fa81a0b 100644 --- a/cross-project-tests/debuginfo-tests/dexter/dex/evaluation/RunMatch.py +++ b/cross-project-tests/debuginfo-tests/dexter/dex/evaluation/RunMatch.py @@ -46,7 +46,7 @@ def __init__( self.match_context = match_context self.state_match = get_active_where_matches(script, step, state_match_context) expects_to_match = { - expect + expect: where_match.frame_idx for where_match in self.state_match.values() for expect in where_match.active_expects } @@ -55,10 +55,11 @@ def __init__( def add_expected_values(expect: Expect, expected_value: Any, scope: Scope): assert isinstance(expect, Value), "Non-Value expects currently unsupported" if expect in expects_to_match: + expect_frame_idx = expects_to_match[expect] self.expect_matches[expect] = get_expect_match( expect, expected_value, - step.watches[expect.get_watched_expr()], + step.frames[expect_frame_idx].watches[expect.get_watched_expr()], self.match_context, ) diff --git a/cross-project-tests/debuginfo-tests/dexter/dex/evaluation/StateMatch.py b/cross-project-tests/debuginfo-tests/dexter/dex/evaluation/StateMatch.py index 563ec798e6c83..c7b2803ea0a86 100644 --- a/cross-project-tests/debuginfo-tests/dexter/dex/evaluation/StateMatch.py +++ b/cross-project-tests/debuginfo-tests/dexter/dex/evaluation/StateMatch.py @@ -149,6 +149,14 @@ def get_active_wheres(where: Where, scope: Scope): # If the target frame is -1, we can't match the !where yet, but we should prepare to step into it. active_where_expects[scope.where].pending_wheres.append(where) return + if where.at_frame_idx is not None: + # !and {at_frame_idx} is a special case: it cannot contain !where nodes, so there's no point checking it + # when the parent !where is not in the current frame (frame_idx=0), and we match its other conditions + # against the requested frame index. + assert where.is_and, "illegal `at_frame_idx` property for !where" + if target_frame_idx != 0 or where.at_frame_idx >= len(step_info.frames): + return + target_frame_idx = where.at_frame_idx labels = script.get_labels( expected_file or step_info.frames[target_frame_idx].loc.path ) @@ -175,17 +183,17 @@ def get_active_wheres(where: Where, scope: Scope): # As we visit the script nodes in pre-order traversal, we can always assume that an expect's parent !where # has already been visited, and thus should have an entry in active_where_expects if it is active. def get_active_expects(expect: Expect, expected_value, scope: Scope): - if ( - scope.where in active_where_expects - and active_where_expects[scope.where].frame_idx == 0 - ): + # Active if the matching frame index is either 0, or equal to scope.get_desired_frame_idx() if it is not None. + if scope.where in active_where_expects and active_where_expects[ + scope.where + ].frame_idx == (scope.get_desired_frame_idx() or 0): active_where_expects[scope.where].active_expects.append(expect) def get_active_thens(then: Then, scope: Scope): - if ( - scope.where in active_where_expects - and active_where_expects[scope.where].frame_idx == 0 - ): + # Active if the matching frame index is either 0, or equal to scope.get_desired_frame_idx() if it is not None. + if scope.where in active_where_expects and active_where_expects[ + scope.where + ].frame_idx == (scope.get_desired_frame_idx() or 0): active_where_expects[scope.where].active_thens.append(then) script.visit_script( diff --git a/cross-project-tests/debuginfo-tests/dexter/dex/test_script/Nodes.py b/cross-project-tests/debuginfo-tests/dexter/dex/test_script/Nodes.py index 78fe76645ca25..a3126357d8395 100644 --- a/cross-project-tests/debuginfo-tests/dexter/dex/test_script/Nodes.py +++ b/cross-project-tests/debuginfo-tests/dexter/dex/test_script/Nodes.py @@ -61,9 +61,10 @@ def __init__(self, attributes: dict, is_and: bool): if isinstance(lines, (int, Label)): lines = Line(lines) self.lines: Union[Line, DexRange, None] = lines + self.at_frame_idx: Optional[int] = attributes.pop("at_frame_idx", None) self.after_hit_count: Optional[int] = attributes.pop("after_hit_count", None) self.for_hit_count: Optional[int] = attributes.pop("for_hit_count", None) - self.conditions: Optional[dict] = attributes.pop("conditions", None) + self.conditions: dict = attributes.pop("conditions", None) self.is_and = is_and if attributes: raise DexterNodeError( @@ -77,6 +78,8 @@ def __init__(self, attributes: dict, is_and: bool): raise DexterNodeError( self, "can't check hit counts without an explicit lines or function arg" ) + if self.at_frame_idx is not None and not self.is_and: + raise DexterNodeError(self, "at_frame_idx can only be used with !and nodes") def __repr__(self): elts = [ @@ -92,6 +95,7 @@ def get_attrs(self) -> Dict[str, Any]: "file": self.file, "function": self.function, "lines": self.lines.value if isinstance(self.lines, Line) else self.lines, + "at_frame_idx": self.at_frame_idx, "for_hit_count": self.for_hit_count, "after_hit_count": self.after_hit_count, "conditions": self.conditions, 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 25ba27c70dc17..f1d430ea8cf43 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,14 @@ def get_known_file_for_where(self, where: Where) -> Optional[str]: next_scope = next_scope.parent_scope return next_scope.file + def get_desired_frame_idx(self) -> Optional[int]: + if not (self.where and self.where.is_and): + return None + if self.where.at_frame_idx is not None: + return self.where.at_frame_idx + assert self.parent_scope + return self.parent_scope.get_desired_frame_idx() + class ScriptLoadContext: """Contains information about the context that the script was loaded from.""" @@ -179,9 +187,24 @@ def validate_expect(expect: Expect, expected_value, scope: Scope): f"!expect/all node {expect} should not have an expected value." ) + def validate_where(where: Where, scope: Scope): + if where.is_and and not scope.where: + raise DexterScriptError( + f"!and node must be contained by another state node." + ) + if scope.get_desired_frame_idx() is not None: + if not where.is_and: + raise DexterScriptError( + f"!where node {where} cannot be contained by a node with at_frame_idx." + ) + if where.at_frame_idx: + raise DexterScriptError( + f"!and node {where} with at_frame_idx cannot be contained by another node with at_frame_idx." + ) + # `visit_script` will validate the structure of the script, as it traverses the full script and raises an # exception if it sees anything unexpected. - self.visit_script(visit_expect=validate_expect) + self.visit_script(visit_expect=validate_expect, visit_where=validate_where) # If a truthy value is returned, abort further visiting and return that value. def _visit_script( diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/debugging/then_at_frame.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/debugging/then_at_frame.cpp new file mode 100644 index 0000000000000..91aa278450074 --- /dev/null +++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/debugging/then_at_frame.cpp @@ -0,0 +1,60 @@ +// RUN: %dexter_regression_test_cxx_build %s -o %t +// RUN: %dexter_regression_test_run --use-script --skip-evaluate --binary %t \ +// RUN: -- %s | FileCheck %s --implicit-check-not="(int) -5" + +// NB: Test CHECKs use line numbers, update them accordingly if adding/removing +// lines in this test. + +int flipIt(int Input) { + int Result = -Input; // !dex_label flip_start + return Result; // !dex_label flip_ret +} + +int main() { + int Value = 5; + Value = flipIt(Value); + Value = flipIt(Value); // !dex_label second_call + Value = flipIt(Value); + return Value; +} + +/// Test that when we can use !then under an at_frame_idx state node. +/// In the second call to flipIt, we trigger `!then step_out` before we reach +/// the line where we evaluate Input, and so we should only see Input=5. + +// CHECK-LABEL: Step 0 + +// CHECK: flipIt(int) +// CHECK-NEXT: then_at_frame.cpp(9: +// CHECK-NOT: flipIt(int) + +// CHECK: flipIt(int) +// CHECK-NEXT: then_at_frame.cpp(10: +// CHECK: Variables: +// CHECK: "Input": (int) 5 +// CHECK-NOT: flipIt(int) + +// CHECK: flipIt(int) +// CHECK-NEXT: then_at_frame.cpp(9: +// CHECK-NOT: flipIt(int) + +// CHECK: flipIt(int) +// CHECK-NEXT: then_at_frame.cpp(9: +// CHECK-NOT: flipIt(int) + +// CHECK: flipIt(int) +// CHECK-NEXT: then_at_frame.cpp(10: +// CHECK: Variables: +// CHECK: "Input": (int) 5 +// CHECK-NOT: flipIt(int) + +/* +--- +!where {function: main}: + !where {function: flipIt}: + !and {lines: !label flip_ret}: + !value Input: 5 + !and {lines: !label flip_start}: + !and {at_frame_idx: 1, lines: !label second_call}: !then step_out +... +*/ diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/evaluation/eval_at_frame.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/evaluation/eval_at_frame.cpp new file mode 100644 index 0000000000000..fa01c7d7c2ab7 --- /dev/null +++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/evaluation/eval_at_frame.cpp @@ -0,0 +1,46 @@ +// RUN: %dexter_regression_test_cxx_build %s -o %t +// RUN: %dexter_regression_test_run --use-script --binary %t -- %s \ +// RUN: | FileCheck %s + +// Test evaluation of !and{at_frame_idx} nodes in Dexter. + +// CHECK: total_watched_steps: 18 +// CHECK: correct_steps: 18 +// CHECK: incorrect_steps: 0 +// CHECK: missing_var_steps: 0 +// CHECK: unexpected_value_steps: 0 +// CHECK: seen_values: 8 +// CHECK: missing_values: 0 + +int Global = 4; + +int bar(int Z) { + Global *= 2; + return Z / 2; +} + +int foo(int Y) { + int First = bar(Y); + return First + bar(Y * 2) * 2; // !dex_label second_call +} + +int main() { + int X = 9; + return foo(X + 1); // !dex_label root_call +} + +/* +--- +!where {function: main}: + !where {function: foo}: + !where {function: bar}: + !value Z: [10, 20] + !and {at_frame_idx: 1}: + !value Y: 10 + !and {lines: !label second_call}: + !value First: 5 + !and {at_frame_idx: 2, lines: !label root_call}: + !value X: 9 + !value Global: [4, 8, 16] +... +*/ diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/parser/reject-bad-at_frame_idx.test b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/parser/reject-bad-at_frame_idx.test new file mode 100644 index 0000000000000..2edf4139322e6 --- /dev/null +++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/parser/reject-bad-at_frame_idx.test @@ -0,0 +1,33 @@ +RUN: not %dexter_regression_test_run --binary %s --use-script --skip-run -- %s 2>&1 | FileCheck %s + +Tests that we reject invalid at_frame_idx uses. + +CHECK: No valid Dexter script found in file + +CHECK: Script starting line [[# @LINE + 2]]: +CHECK: Error with node: Where(function=bar, at_frame_idx=1): at_frame_idx can only be used with !and nodes +--- +!where {function: foo}: + !where {function: bar, at_frame_idx: 1}: + !value a: 0 +... + +CHECK: Script starting line [[# @LINE + 2]]: +CHECK: !where node Where(function=baz) cannot be contained by a node with at_frame_idx. +--- +!where {function: foo}: + !where {function: bar}: + !and {at_frame_idx: 1}: + !where {function: baz}: + !value b: 1 +... + +CHECK: Script starting line [[# @LINE + 2]]: +CHECK: !and node And(at_frame_idx=2) with at_frame_idx cannot be contained by another node with at_frame_idx. +--- +!where {function: foo}: + !where {function: bar}: + !and {at_frame_idx: 1}: + !and {at_frame_idx: 2}: + !value c: 2 +... diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/rewrite_at_frame_expected.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/rewrite_at_frame_expected.cpp new file mode 100644 index 0000000000000..b871b07896df1 --- /dev/null +++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/Inputs/rewrite_at_frame_expected.cpp @@ -0,0 +1,61 @@ +// 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_at_frame_expected.cpp + +/// Tests that we can rewrite variables and scopes at frames above the current +/// frame. + +/// 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. + +int ChangeCount = 0; + +void setVariable(int &Var, int NewValue) { + Var = NewValue; + ChangeCount += 1; + return; +} + +int main() { + int X = 1; + int Y = 2; + int Z = 3; + setVariable(X, 9); + setVariable(Y, 8); + setVariable(Z, 7); + return 0; +} + +// CHECK: total_watched_steps: 36 +// CHECK: correct_steps: 36 +// CHECK: incorrect_steps: 0 +// CHECK: missing_var_steps: 0 +// CHECK: unexpected_value_steps: 0 +// CHECK: seen_values: 10 +// CHECK: missing_values: 0 + +/* +--- +? !where {function: setVariable} +: ? !and {at_frame_idx: 1} + : !value 'X': + - '1' + - '9' + !value 'Y': + - '2' + - '8' + !value 'Z': + - '3' + - '7' + !value 'ChangeCount': + - '0' + - '1' + - '2' + - '3' +... +*/ diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/rewrite_at_frame.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/rewrite_at_frame.cpp new file mode 100644 index 0000000000000..48ce084769a21 --- /dev/null +++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/rewriting/rewrite_at_frame.cpp @@ -0,0 +1,49 @@ +// 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_at_frame_expected.cpp + +/// Tests that we can rewrite variables and scopes at frames above the current +/// frame. + +/// 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. + +int ChangeCount = 0; + +void setVariable(int &Var, int NewValue) { + Var = NewValue; + ChangeCount += 1; + return; +} + +int main() { + int X = 1; + int Y = 2; + int Z = 3; + setVariable(X, 9); + setVariable(Y, 8); + setVariable(Z, 7); + return 0; +} + +// CHECK: total_watched_steps: 36 +// CHECK: correct_steps: 36 +// CHECK: incorrect_steps: 0 +// CHECK: missing_var_steps: 0 +// CHECK: unexpected_value_steps: 0 +// CHECK: seen_values: 10 +// CHECK: missing_values: 0 + +/* +--- +!where {function: setVariable}: + !and {at_frame_idx: 1}: + ? !value/all Locals + ? !value ChangeCount +... +*/ _______________________________________________ llvm-branch-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits
