| # 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 |
| """Command for specifying a partial or complete state for the program to enter |
| during execution. |
| """ |
| |
| from itertools import chain |
| |
| from dex.command.CommandBase import CommandBase, StepExpectInfo |
| from dex.dextIR import ProgramState, SourceLocation, StackFrame, DextIR |
| |
| |
| def frame_from_dict(source: dict) -> StackFrame: |
| if "location" in source: |
| assert isinstance(source["location"], dict) |
| source["location"] = SourceLocation(**source["location"]) |
| return StackFrame(**source) |
| |
| |
| def state_from_dict(source: dict) -> ProgramState: |
| if "frames" in source: |
| assert isinstance(source["frames"], list) |
| source["frames"] = list(map(frame_from_dict, source["frames"])) |
| return ProgramState(**source) |
| |
| |
| class DexExpectProgramState(CommandBase): |
| """Expect to see a given program `state` a certain numer of `times`. |
| |
| DexExpectProgramState(state [,**times]) |
| |
| See Commands.md for more info. |
| """ |
| |
| def __init__(self, *args, **kwargs): |
| if len(args) != 1: |
| raise TypeError("expected exactly one unnamed arg") |
| |
| self.program_state_text = str(args[0]) |
| |
| self.expected_program_state = state_from_dict(args[0]) |
| |
| self.times = kwargs.pop("times", -1) |
| if kwargs: |
| raise TypeError("unexpected named args: {}".format(", ".join(kwargs))) |
| |
| # Step indices at which the expected program state was encountered. |
| self.encounters = [] |
| |
| super(DexExpectProgramState, self).__init__() |
| |
| @staticmethod |
| def get_name(): |
| return __class__.__name__ |
| |
| def get_watches(self): |
| frame_expects = set() |
| for idx, frame in enumerate(self.expected_program_state.frames): |
| path = ( |
| frame.location.path |
| if frame.location and frame.location.path |
| else self.path |
| ) |
| line_range = ( |
| range(frame.location.lineno, frame.location.lineno + 1) |
| if frame.location and frame.location.lineno |
| else None |
| ) |
| for watch in frame.watches: |
| frame_expects.add( |
| StepExpectInfo( |
| expression=watch, |
| path=path, |
| frame_idx=idx, |
| line_range=line_range, |
| ) |
| ) |
| return frame_expects |
| |
| def eval(self, step_collection: DextIR) -> bool: |
| for step in step_collection.steps: |
| if self.expected_program_state.match(step.program_state): |
| self.encounters.append(step.step_index) |
| |
| return ( |
| self.times < 0 < len(self.encounters) or len(self.encounters) == self.times |
| ) |