[llvm-branch-commits] [llvm] [Dexter] Add for_hit_count for state nodes (PR #203359)
Stephen Tozer via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Mon Jun 15 04:39:29 PDT 2026
https://github.com/SLTozer updated https://github.com/llvm/llvm-project/pull/203359
>From 6755dd4ec887cb4b0f5f9528ea3fc2a95d58c206 Mon Sep 17 00:00:00 2001
From: Stephen Tozer <stephen.tozer at sony.com>
Date: Thu, 11 Jun 2026 18:44:38 +0100
Subject: [PATCH 1/2] [Dexter] Add for_hit_count for state nodes
This patch adds the ability for state nodes to use a `for_hit_count: <int>`
field to limit the number of times that a given state node will be active.
---
.../ScriptDebuggerController.py | 46 +++++++---
.../dexter/dex/dextIR/StepIR.py | 3 +-
.../dexter/dex/evaluation/ExpectWriter.py | 14 ++-
.../dexter/dex/evaluation/RunMatch.py | 13 ++-
.../dexter/dex/evaluation/StateMatch.py | 86 ++++++++++++++++---
.../dexter/dex/test_script/Nodes.py | 2 +-
.../scripts/debugging/where_fn_hit_count.cpp | 26 ++++++
.../scripts/debugging/where_for_hit_count.cpp | 78 +++++++++++++++++
.../debugging/where_hit_count_early_exit.cpp | 33 +++++++
9 files changed, 269 insertions(+), 32 deletions(-)
create mode 100644 cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/debugging/where_fn_hit_count.cpp
create mode 100644 cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/debugging/where_for_hit_count.cpp
create mode 100644 cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/debugging/where_hit_count_early_exit.cpp
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 f1404c37116fa..fc052b5274089 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
@@ -19,7 +19,7 @@
)
from dex.debugger.DebuggerBase import DebuggerBase
from dex.debugger.DAP import DAP
-from dex.evaluation.StateMatch import get_active_where_matches
+from dex.evaluation.StateMatch import StateMatchContext, get_active_where_matches
from dex.test_script.Nodes import Where
from dex.test_script.Script import DexterScript, Scope
from dex.tools import Context
@@ -87,6 +87,7 @@ def _run_debugger_custom(self, cmdline):
self.step_collection.clear_steps()
script: DexterScript = self.script
+ state_match_context = StateMatchContext()
self._init_bps()
self.debugger.launch(cmdline)
@@ -119,8 +120,22 @@ def _run_debugger_custom(self, cmdline):
## Fetch frame information and breakpoint information from the debugger.
step_info: StepIR = self.debugger.get_stack_frames(self._step_index)
+ # For some niche cases around function breakpoints, we may fail to
+ # correctly notice a !where being hit; therefore we explicitly track
+ # !where breakpoints that get hit in the StepIR.
+ hit_bps = self.debugger.get_triggered_breakpoint_ids()
+ step_info.hit_where_bps = sorted(
+ (
+ where
+ for where, bp_ids in self._where_bps.items()
+ if any(bp_id in hit_bps for bp_id in bp_ids)
+ ),
+ key=lambda where: str(where),
+ )
- active_where_matches = get_active_where_matches(script, step_info)
+ active_where_matches = get_active_where_matches(
+ script, step_info, state_match_context
+ )
watches = [
watch
@@ -160,6 +175,11 @@ def _run_debugger_custom(self, cmdline):
next_action = DebuggerAction.STEP_OVER
elif active_where_matches:
next_action = DebuggerAction.STEP_OUT
+ elif all(
+ where in state_match_context.expired_wheres
+ for where in script.root_wheres
+ ):
+ next_action = DebuggerAction.EXIT
else:
next_action = DebuggerAction.CONTINUE
@@ -170,19 +190,23 @@ def _run_debugger_custom(self, cmdline):
for where_match in active_where_matches.values()
for where in where_match.pending_wheres
)
+
+ def where_should_have_breakpoint(where: Where):
+ if where in state_match_context.expired_wheres:
+ return False
+ if where in script.root_wheres:
+ return True
+ if should_step_out:
+ return False
+ return where in pending_wheres
for where, bp_ids in self._where_bps.items():
- if (
- bp_ids
- and where not in script.root_wheres
- and (where not in pending_wheres or should_step_out)
- ):
+ if bp_ids and not where_should_have_breakpoint(where):
bp_to_delete.extend(bp_ids)
bp_ids.clear()
self.debugger.delete_breakpoints(bp_to_delete)
- if not should_step_out:
- for where in pending_wheres:
- if not self._where_bps[where]:
- self.add_where_entry_bp(where)
+ for where in pending_wheres:
+ if not self._where_bps[where] and where_should_have_breakpoint(where):
+ self.add_where_entry_bp(where)
if step_info.current_frame:
self._step_index += 1
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 1b23015d02bcb..d2860a0ce8b95 100644
--- a/cross-project-tests/debuginfo-tests/dexter/dex/dextIR/StepIR.py
+++ b/cross-project-tests/debuginfo-tests/dexter/dex/dextIR/StepIR.py
@@ -14,6 +14,7 @@
from dex.dextIR.FrameIR import FrameIR
from dex.dextIR.LocIR import LocIR
from dex.dextIR.ProgramState import ProgramState
+from dex.test_script.Nodes import Where
class StopReason(Enum):
@@ -66,7 +67,7 @@ def __init__(
watches = {}
self.watches = watches
self.scope_watches = scope_watches or OrderedDict()
- self.hit_fn_bps: List[str] = []
+ self.hit_where_bps: List[Where] = []
def __str__(self):
try:
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 d288f4caed70f..a39549d271209 100644
--- a/cross-project-tests/debuginfo-tests/dexter/dex/evaluation/ExpectWriter.py
+++ b/cross-project-tests/debuginfo-tests/dexter/dex/evaluation/ExpectWriter.py
@@ -10,7 +10,7 @@
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.evaluation.StateMatch import StateMatchContext, get_active_where_matches
from dex.test_script.Nodes import DexRange, Expect, Line, Then, Value, ValueAll, Where
from dex.test_script.Script import DexterScript, Scope
from dex.tools.Main import Context
@@ -158,10 +158,12 @@ 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):
+ def __init__(
+ self, step: StepIR, script: DexterScript, state_match_context: StateMatchContext
+ ):
self.step = step
self.script = script
- self.state_match = get_active_where_matches(script, step)
+ self.state_match = get_active_where_matches(script, step, state_match_context)
active_expects = {
expect
for where_match in self.state_match.values()
@@ -227,7 +229,11 @@ def collect_expects_to_write(expect: Expect, expected_value: Any, scope: Scope):
if not self.unknown_expect_rewrites and not self.scope_expect_rewrites:
return
- self.step_writers = [StepExpectWriter(step, script) for step in dext_ir.steps]
+ state_match_context = StateMatchContext()
+ self.step_writers = [
+ StepExpectWriter(step, script, state_match_context)
+ for step in dext_ir.steps
+ ]
for step_writer in self.step_writers:
step_idx = step_writer.step.step_index
for (
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 73b12a918bd5f..e09cce69e6c28 100644
--- a/cross-project-tests/debuginfo-tests/dexter/dex/evaluation/RunMatch.py
+++ b/cross-project-tests/debuginfo-tests/dexter/dex/evaluation/RunMatch.py
@@ -25,7 +25,7 @@
get_variable_metrics,
serialize_metric_to_json,
)
-from dex.evaluation.StateMatch import get_active_where_matches
+from dex.evaluation.StateMatch import StateMatchContext, get_active_where_matches
from dex.test_script import DexterScript, Scope
from dex.test_script.Nodes import Expect, Value
@@ -35,12 +35,16 @@ class DebuggerStepMatch:
expected output."""
def __init__(
- self, step: StepIR, script: DexterScript, match_context: ExpectMatchContext
+ self,
+ step: StepIR,
+ script: DexterScript,
+ match_context: ExpectMatchContext,
+ state_match_context: StateMatchContext,
):
self.step = step
self.script = script
self.match_context = match_context
- self.state_match = get_active_where_matches(script, step)
+ self.state_match = get_active_where_matches(script, step, state_match_context)
expects_to_match = {
expect
for where_match in self.state_match.values()
@@ -92,9 +96,10 @@ def add_expected_values(expect: Expect, expected_value: Any, scope: Scope):
script.visit_script(visit_expect=add_expected_values)
# Then produce all of our step matches.
+ state_match_context = StateMatchContext()
for step in self.dext_ir.steps:
self.step_matches.append(
- DebuggerStepMatch(step, script, self.match_context)
+ DebuggerStepMatch(step, script, self.match_context, state_match_context)
)
# Then, for each expect, produce the list of results for just that variable.
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 03a9d4c0d0b89..2161511584ca5 100644
--- a/cross-project-tests/debuginfo-tests/dexter/dex/evaluation/StateMatch.py
+++ b/cross-project-tests/debuginfo-tests/dexter/dex/evaluation/StateMatch.py
@@ -7,15 +7,55 @@
"""Utilities for matching debugger state, such as the call stack, conditions, or historical state (e.g. breakpoint
hitcounts) to descriptions of expected state in a DexterScript."""
+from collections import Counter
from dataclasses import dataclass, field
import os
-from typing import Dict, List, Tuple
+from typing import Dict, List, Optional, Set, Tuple
from dex.dextIR import FrameIR, StepIR
from dex.test_script import DexterScript, Scope
from dex.test_script.Nodes import Expect, FileLabels, Where, Then
+class StateMatchContext:
+ """Class that holds any state needed for matching state nodes to debugger state across a run."""
+
+ def __init__(self):
+ self.where_hit_counts: Counter[Where] = Counter()
+ self.expired_wheres: Set[Where] = set()
+ self._last_match_result: Optional[StateMatchResult] = None
+
+ def where_hit_is_new(self, where: Where, step: StepIR) -> bool:
+ """Returns True if the current step can be counted as a new "hit" for `where`, assuming that `where` was hit in
+ this step (but does not check if `where` has actually been hit in the current step).
+ """
+ # If this is the first step, all hits are new.
+ if self._last_match_result is None:
+ return True
+ # If this !where did not appear in any frame in the previous step, this is a fresh hit.
+ if where not in self._last_match_result:
+ return True
+ # If !where uses a function breakpoint and that breakpoint was hit this step, this is a fresh hit.
+ if where.function and not where.lines and where in step.hit_where_bps:
+ return True
+ return False
+
+ def add_hit_if_where_hit_is_new(self, where: Where, step: StepIR) -> bool:
+ """Checks whether the current step can be counted as a new "hit" for `where`. Increments `where`'s hit count if
+ it has a new hit, and returns True iff so."""
+ assert (
+ where.for_hit_count is not None
+ ), "Tried to add hit count for !where without for_hit_count?"
+ if self.where_hit_is_new(where, step):
+ self.where_hit_counts[where] += 1
+ if self.where_hit_counts[where] >= where.for_hit_count:
+ self.expired_wheres.add(where)
+ return True
+ return False
+
+ def update(self, new_match_result: "StateMatchResult"):
+ self._last_match_result = new_match_result
+
def is_subpath(subpath: str, superpath: str) -> bool:
"""Returns True if subpath is a trailing subpath of superpath, i.e. if `superpath` ends with `subpath` after
normalizing both paths."""
@@ -24,11 +64,11 @@ def is_subpath(subpath: str, superpath: str) -> bool:
return normalized_superpath.endswith(normalized_subpath)
-# A very simple matcher, returns True iff `where` matches `frame`.
-def match_where_to_frame(
+def _match_where_to_frame(
where: Where,
frame: FrameIR,
labels: FileLabels,
+ context: StateMatchContext,
) -> bool:
"""A very simple matcher, returns True iff `where` matches `frame`."""
if where.file is not None and not is_subpath(where.file, frame.loc.path):
@@ -42,16 +82,30 @@ def match_where_to_frame(
if where.lines is not None:
if frame.loc.lineno not in where.get_lines(labels):
return False
- if (
- where.for_hit_count is not None
- or where.after_hit_count is not None
- or where.conditions is not None
- ):
+ if where.for_hit_count is not None:
+ where_hit_count = context.where_hit_counts[where]
+ if where_hit_count > where.for_hit_count:
+ return False
+ if where.after_hit_count is not None or where.conditions is not None:
raise NotImplementedError(
"!where hit counts and conditions currently unsupported."
)
return True
+def match_where_to_frame(
+ where: Where,
+ frame: FrameIR,
+ step: StepIR,
+ labels: Dict[str, int],
+ context: StateMatchContext,
+) -> bool:
+ """Returns True if `where` matches `frame`. As part of this check, we perform the check once, and if necessary we
+ may increment `where`'s hit count and check again."""
+ result = _match_where_to_frame(where, frame, labels, context)
+ if result == True and where.for_hit_count is not None:
+ if context.add_hit_if_where_hit_is_new(where, step):
+ result = _match_where_to_frame(where, frame, labels, context)
+ return result
@dataclass
class WhereMatchResult:
@@ -63,10 +117,13 @@ class WhereMatchResult:
active_expects: List[Expect] = field(default_factory=list)
active_thens: List[Then] = field(default_factory=list)
pending_wheres: List[Where] = field(default_factory=list)
+ expired_wheres: List[Where] = field(default_factory=list)
+
+StateMatchResult = Dict[Where, WhereMatchResult]
def get_active_where_matches(
- script: DexterScript, step_info: StepIR
+ script: DexterScript, step_info: StepIR, match_context: StateMatchContext
) -> Dict[Where, WhereMatchResult]:
"""Match the script against the step_info, producing a dict that maps each !where that matches a stack frame to the
index of the (rootmost) stack frame that it matches, and if the frame that it matches is the current stack frame
@@ -93,14 +150,20 @@ def get_active_wheres(where: Where, scope: Scope):
labels = script.get_labels(
expected_file or step_info.frames[target_frame_idx].loc.path
)
- if match_where_to_frame(where, step_info.frames[target_frame_idx], labels):
+ if match_where_to_frame(
+ where,
+ step_info.frames[target_frame_idx],
+ step_info,
+ labels,
+ match_context,
+ ):
active_where_expects[where] = WhereMatchResult(target_frame_idx)
return
# For this !where, search for the rootmost stack frame that matches it.
matching_frame_idx = None
for frame_idx, frame in reversed(list(enumerate(step_info.frames))):
labels = script.get_labels(expected_file or frame.loc.path)
- if match_where_to_frame(where, frame, labels):
+ if match_where_to_frame(where, frame, step_info, labels, match_context):
matching_frame_idx = frame_idx
break
@@ -129,4 +192,5 @@ def get_active_thens(then: Then, scope: Scope):
visit_then=get_active_thens,
)
+ match_context.update(active_where_expects)
return active_where_expects
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 d441657f5761d..893d7f3c1ba31 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
@@ -63,7 +63,7 @@ def __init__(self, attributes: dict, is_and: bool):
self.lines: Union[Line, DexRange, None] = lines
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: dict = attributes.pop("conditions", None)
+ self.conditions: Optional[dict] = attributes.pop("conditions", None)
self.is_and = is_and
if attributes:
raise DexterNodeError(
diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/debugging/where_fn_hit_count.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/debugging/where_fn_hit_count.cpp
new file mode 100644
index 0000000000000..0796968b47943
--- /dev/null
+++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/debugging/where_fn_hit_count.cpp
@@ -0,0 +1,26 @@
+// RUN: %dexter_regression_test_cxx_build %s -o %t
+// RUN: %dexter_regression_test_run --use-script --skip-evaluate --binary %t \
+// RUN: -- %s | FileCheck %s
+
+/// Test that we record hit counts for !where{function} nodes, even when there
+/// are no other steps between each breakpoint hit.
+
+/// We should only hit countdown 3 times, even though there is no gap between
+/// the tail calls.
+// CHECK-LABEL: Step 0
+// CHECK-COUNT-3: countdown
+// CHECK-NOT: countdown
+
+/// All on one line for simplicity so that we only get one step per call.
+// clang-format off
+int countdown(int Num) { if (!Num) return 0; __attribute__((musttail)) return countdown(Num - 1); }
+// clang-format on
+
+int main() { return countdown(2) + countdown(2); }
+
+/*
+---
+!where {function: countdown, for_hit_count: 3}:
+ !value Num: [2, 1, 0]
+...
+*/
diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/debugging/where_for_hit_count.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/debugging/where_for_hit_count.cpp
new file mode 100644
index 0000000000000..8a540b356b952
--- /dev/null
+++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/debugging/where_for_hit_count.cpp
@@ -0,0 +1,78 @@
+// RUN: %dexter_regression_test_cxx_build %s -o %t
+// RUN: %dexter_regression_test_run --use-script --skip-evaluate --binary %t \
+// RUN: -- %s | FileCheck %s
+
+/// Test !where nodes work with for_hit_count.
+
+/// All on one line for simplicity so that we only get one step per call.
+int collatz(int N) { return (N % 2) ? N * 3 + 1 : N / 2; }
+
+int main() {
+ int MaxAttempts = 50;
+ int Value = 472959593;
+ for (int I = 0; I < MaxAttempts; ++I) {
+ Value = collatz(Value); // !dex_label loop_start
+ if (Value == 1)
+ break; // !dex_label loop_end
+ }
+ return Value == 1 ? 0 : 1;
+}
+
+// CHECK: Step 0
+// CHECK: Frame 0:
+// CHECK-NEXT: main
+// CHECK-NEXT: where_for_hit_count.cpp(14:21)
+// CHECK: Step 1
+// CHECK: Frame 0:
+// CHECK-NEXT: collatz(int)
+// CHECK-NEXT: where_for_hit_count.cpp(8:30)
+// CHECK: Step 2
+// CHECK: Frame 0:
+// CHECK-NEXT: main
+// CHECK-NEXT: where_for_hit_count.cpp(14:11)
+// CHECK: Step 3
+// CHECK: Frame 0:
+// CHECK-NEXT: main
+// CHECK-NEXT: where_for_hit_count.cpp(15:15)
+// CHECK: Step 4
+// CHECK: Frame 0:
+// CHECK-NEXT: main
+// CHECK-NEXT: where_for_hit_count.cpp(17:3)
+// CHECK: Step 5
+// CHECK: Frame 0:
+// CHECK-NEXT: main
+// CHECK-NEXT: where_for_hit_count.cpp(14:21)
+// CHECK: Step 6
+// CHECK: Frame 0:
+// CHECK-NEXT: collatz(int)
+// CHECK-NEXT: where_for_hit_count.cpp(8:30)
+// CHECK: Step 7
+// CHECK: Frame 0:
+// CHECK-NEXT: main
+// CHECK-NEXT: where_for_hit_count.cpp(14:11)
+// CHECK: Step 8
+// CHECK: Frame 0:
+// CHECK-NEXT: main
+// CHECK-NEXT: where_for_hit_count.cpp(15:15)
+// CHECK: Step 9
+// CHECK: Frame 0:
+// CHECK-NEXT: main
+// CHECK-NEXT: where_for_hit_count.cpp(17:3)
+// CHECK: Step 10
+// CHECK: Frame 0:
+// CHECK-NEXT: collatz(int)
+// CHECK-NEXT: where_for_hit_count.cpp(8:30)
+// CHECK: Step 11
+// CHECK: Frame 0:
+// CHECK-NEXT: main
+// CHECK-NEXT: where_for_hit_count.cpp(14:11)
+
+/*
+---
+!where {function: collatz, for_hit_count: 3}:
+ !value N: 0
+!where {lines: !range [!label loop_start, !label loop_end], for_hit_count: 2}:
+ !value Value: 0
+ !value I: 0
+...
+*/
diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/debugging/where_hit_count_early_exit.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/debugging/where_hit_count_early_exit.cpp
new file mode 100644
index 0000000000000..5d799f4119c47
--- /dev/null
+++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/scripts/debugging/where_hit_count_early_exit.cpp
@@ -0,0 +1,33 @@
+// RUN: %dexter_regression_test_cxx_build %s -o %t
+// RUN: %dexter_regression_test_run --use-script --skip-evaluate --binary %t \
+// RUN: --timeout-total 10 -- %s 2>&1 | FileCheck %s
+
+/// Test that when all root !where nodes have expired, we exit without waiting
+/// for the debuggee to finish.
+
+// CHECK-NOT: timeout reached
+
+// CHECK-LABEL: Step 0
+// CHECK-COUNT-3: getRandomNumber
+// CHECK-NOT: getRandomNumber
+
+/// All on one line for simplicity so that we only get one step per call.
+int getRandomNumber(int Max) { return 4 % Max; }
+
+int main() {
+ // Bogo search
+ int List[] = {0, 0, 0, 0, 5, 0, 0, 0, 0, 0};
+ int SearchTarget = 0;
+ while (true) {
+ int NextSearch = getRandomNumber(10);
+ if (List[NextSearch] == SearchTarget)
+ return NextSearch;
+ }
+}
+
+/*
+---
+!where {function: getRandomNumber, for_hit_count: 3}:
+ !value Max: 10
+...
+*/
>From 1e2dcb17e1c7667864a96bebac5c66c840fdf453 Mon Sep 17 00:00:00 2001
From: Stephen Tozer <stephen.tozer at sony.com>
Date: Mon, 15 Jun 2026 09:50:12 +0100
Subject: [PATCH 2/2] darker
---
.../debuginfo-tests/dexter/dex/evaluation/StateMatch.py | 2 ++
1 file changed, 2 insertions(+)
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 2161511584ca5..563ec798e6c83 100644
--- a/cross-project-tests/debuginfo-tests/dexter/dex/evaluation/StateMatch.py
+++ b/cross-project-tests/debuginfo-tests/dexter/dex/evaluation/StateMatch.py
@@ -92,6 +92,7 @@ def _match_where_to_frame(
)
return True
+
def match_where_to_frame(
where: Where,
frame: FrameIR,
@@ -122,6 +123,7 @@ class WhereMatchResult:
StateMatchResult = Dict[Where, WhereMatchResult]
+
def get_active_where_matches(
script: DexterScript, step_info: StepIR, match_context: StateMatchContext
) -> Dict[Where, WhereMatchResult]:
More information about the llvm-branch-commits
mailing list