From 18fcf750d80c1ac90df3278607df83b60cbe4215 Mon Sep 17 00:00:00 2001 From: Tom Weaver <43210711+TomWeaver18@users.noreply.github.com> Date: Wed, 17 Jul 2019 14:03:34 +0100 Subject: [PATCH] Rework Dexter command imports and get valid commands code to be (#35) simpler and more static. --- dex/command/CommandBase.py | 7 +++ dex/command/ParseCommand.py | 47 ++++++---------- dex/command/commands/DexExpectProgramState.py | 4 ++ dex/command/commands/DexExpectStepKind.py | 4 ++ dex/command/commands/DexExpectStepOrder.py | 4 ++ dex/command/commands/DexExpectWatchValue.py | 21 +++++++- dex/command/commands/DexUnreachable.py | 54 ++++++++++++++----- dex/command/commands/DexWatch.py | 4 ++ dex/heuristic/Heuristic.py | 21 ++------ 9 files changed, 103 insertions(+), 63 deletions(-) diff --git a/dex/command/CommandBase.py b/dex/command/CommandBase.py index 1c5db31..db21ad7 100644 --- a/dex/command/CommandBase.py +++ b/dex/command/CommandBase.py @@ -32,6 +32,13 @@ def __init__(self): self.path = None self.lineno = None + @staticmethod + @abc.abstractstaticmethod + def get_name(): + """This abstract method is usually implemented in subclasses as: + return __class__.__name__ + """ + @abc.abstractmethod def eval(self): pass diff --git a/dex/command/ParseCommand.py b/dex/command/ParseCommand.py index dadddc6..dbcfb40 100644 --- a/dex/command/ParseCommand.py +++ b/dex/command/ParseCommand.py @@ -26,43 +26,28 @@ """ from collections import defaultdict -import imp -import inspect -import os -from dex.command.CommandBase import CommandBase from dex.utils.Exceptions import CommandParseError from dex.dextIR import CommandIR +from dex.command.CommandBase import CommandBase +from dex.command.commands.DexExpectProgramState import DexExpectProgramState +from dex.command.commands.DexExpectStepKind import DexExpectStepKind +from dex.command.commands.DexExpectStepOrder import DexExpectStepOrder +from dex.command.commands.DexExpectWatchValue import DexExpectWatchValue +from dex.command.commands.DexUnreachable import DexUnreachable +from dex.command.commands.DexWatch import DexWatch -def _get_valid_commands(): - """Search the commands subdirectory for any classes which are subclasses of - CommandBase and return a dict in the form of {name: class}. - """ - try: - return _get_valid_commands.cached - except AttributeError: - commands_directory = os.path.join( - os.path.dirname(__file__), 'commands') - potential_modules = [ - os.path.splitext(f)[0] for f in os.listdir(commands_directory) - ] - - commands = {} - for m in potential_modules: - try: - module_info = imp.find_module(m, [commands_directory]) - module = imp.load_module(m, *module_info) - except ImportError: - continue - commands.update({ - c[0]: c[1] - for c in inspect.getmembers(module, inspect.isclass) - if c[1] != CommandBase and issubclass(c[1], CommandBase) - }) - _get_valid_commands.cached = commands - return commands +def _get_valid_commands(): + return { + DexExpectProgramState.get_name() : DexExpectProgramState, + DexExpectStepKind.get_name() : DexExpectStepKind, + DexExpectStepOrder.get_name() : DexExpectStepOrder, + DexExpectWatchValue.get_name() : DexExpectWatchValue, + DexUnreachable.get_name() : DexUnreachable, + DexWatch.get_name() : DexWatch + } def _get_command_name(command_raw: str) -> str: diff --git a/dex/command/commands/DexExpectProgramState.py b/dex/command/commands/DexExpectProgramState.py index 6ea2249..fba5f84 100644 --- a/dex/command/commands/DexExpectProgramState.py +++ b/dex/command/commands/DexExpectProgramState.py @@ -58,6 +58,10 @@ def __init__(self, *args, **kwargs): super(DexExpectProgramState, self).__init__() + @staticmethod + def get_name(): + return __class__.__name__ + def eval(self, step_collection: DextIR) -> bool: for step in step_collection.steps: if self.expected_program_state.match(step.program_state): diff --git a/dex/command/commands/DexExpectStepKind.py b/dex/command/commands/DexExpectStepKind.py index 116c4f9..5efd90b 100644 --- a/dex/command/commands/DexExpectStepKind.py +++ b/dex/command/commands/DexExpectStepKind.py @@ -42,5 +42,9 @@ def __init__(self, *args): super(DexExpectStepKind, self).__init__() + @staticmethod + def get_name(): + return __class__.__name__ + def eval(self): pass diff --git a/dex/command/commands/DexExpectStepOrder.py b/dex/command/commands/DexExpectStepOrder.py index a5221ae..a9515c4 100644 --- a/dex/command/commands/DexExpectStepOrder.py +++ b/dex/command/commands/DexExpectStepOrder.py @@ -9,6 +9,10 @@ def __init__(self, *args): self.sequence = [int(x) for x in args] super(DexExpectStepOrder, self).__init__() + @staticmethod + def get_name(): + return __class__.__name__ + def eval(self, debugger): step_info = debugger.get_step_info() loc = step_info.current_location diff --git a/dex/command/commands/DexExpectWatchValue.py b/dex/command/commands/DexExpectWatchValue.py index 518a240..1d51e89 100644 --- a/dex/command/commands/DexExpectWatchValue.py +++ b/dex/command/commands/DexExpectWatchValue.py @@ -25,7 +25,22 @@ import difflib from dex.command.CommandBase import CommandBase -from dex.heuristic import StepValueInfo + + +class StepValueInfo(object): + def __init__(self, step_index, value_info): + self.step_index = step_index + self.value_info = value_info + + def __str__(self): + return '{}:{}'.format(self.step_index, self.value_info) + + def __eq__(self, other): + return (self.value_info.expression == other.value_info.expression + and self.value_info.value == other.value_info.value) + + def __hash__(self): + return hash(self.value_info.expression, self.value_info.value) def _check_watch_order(actual_watches, expected_values): @@ -154,6 +169,10 @@ def _handle_watch(self, step, watch): except KeyError: pass + @staticmethod + def get_name(): + return __class__.__name__ + def eval(self, step_collection): for step in step_collection.steps: loc = step.current_location diff --git a/dex/command/commands/DexUnreachable.py b/dex/command/commands/DexUnreachable.py index 8ed5f11..d946663 100644 --- a/dex/command/commands/DexUnreachable.py +++ b/dex/command/commands/DexUnreachable.py @@ -1,18 +1,46 @@ +# DExTer : Debugging Experience Tester +# ~~~~~~ ~ ~~ ~ ~~ +# +# Copyright (c) 2019 by SN Systems Ltd., Sony Interactive Entertainment Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + + from dex.command.CommandBase import CommandBase from dex.dextIR import ValueIR class DexUnreachable(CommandBase): - def __init(self): - super(DexUnreachable, self).__init__() - pass + def __init(self): + super(DexUnreachable, self).__init__() + pass + + @staticmethod + def get_name(): + return __class__.__name__ - def eval(self, debugger): - # If we're ever called, at all, then we're evaluating a line that has - # been marked as unreachable. Which means a failure. - vir = ValueIR(expression="Unreachable", - value="True", type_name=None, - error_string=None, - could_evaluate=True, - is_optimized_away=True, - is_irretrievable=False) - return {'DexUnreachable' : vir} + def eval(self, debugger): + # If we're ever called, at all, then we're evaluating a line that has + # been marked as unreachable. Which means a failure. + vir = ValueIR(expression="Unreachable", + value="True", type_name=None, + error_string=None, + could_evaluate=True, + is_optimized_away=True, + is_irretrievable=False) + return {'DexUnreachable' : vir} diff --git a/dex/command/commands/DexWatch.py b/dex/command/commands/DexWatch.py index e6010b8..83681a2 100644 --- a/dex/command/commands/DexWatch.py +++ b/dex/command/commands/DexWatch.py @@ -39,5 +39,9 @@ def __init__(self, *args): self._args = args super(DexWatch, self).__init__() + @staticmethod + def get_name(): + return __class__.__name__ + def eval(self, debugger): return {arg: debugger.evaluate_expression(arg) for arg in self._args} diff --git a/dex/heuristic/Heuristic.py b/dex/heuristic/Heuristic.py index 6f8532e..58b5f46 100644 --- a/dex/heuristic/Heuristic.py +++ b/dex/heuristic/Heuristic.py @@ -29,31 +29,16 @@ from collections import defaultdict, namedtuple, Counter import difflib import os -from itertools import repeat, chain, groupby +from itertools import groupby +from dex.command.commands.DexExpectWatchValue import StepValueInfo +from dex.command.ParseCommand import get_command_object -from dex.command import get_command_object PenaltyCommand = namedtuple('PenaltyCommand', ['pen_dict', 'max_penalty']) # 'meta' field used in different ways by different things PenaltyInstance = namedtuple('PenaltyInstance', ['meta', 'the_penalty']) -class StepValueInfo(object): - def __init__(self, step_index, value_info): - self.step_index = step_index - self.value_info = value_info - - def __str__(self): - return '{}:{}'.format(self.step_index, self.value_info) - - def __eq__(self, other): - return (self.value_info.expression == other.value_info.expression - and self.value_info.value == other.value_info.value) - - def __hash__(self): - return hash(self.value_info.expression, self.value_info.value) - - def add_heuristic_tool_arguments(parser): parser.add_argument( '--penalty-variable-optimized',