diff options
Diffstat (limited to 'debuginfo-tests/dexter/dex/dextIR')
-rw-r--r-- | debuginfo-tests/dexter/dex/dextIR/BuilderIR.py | 16 | ||||
-rw-r--r-- | debuginfo-tests/dexter/dex/dextIR/DebuggerIR.py | 14 | ||||
-rw-r--r-- | debuginfo-tests/dexter/dex/dextIR/DextIR.py | 129 | ||||
-rw-r--r-- | debuginfo-tests/dexter/dex/dextIR/FrameIR.py | 16 | ||||
-rw-r--r-- | debuginfo-tests/dexter/dex/dextIR/LocIR.py | 45 | ||||
-rw-r--r-- | debuginfo-tests/dexter/dex/dextIR/ProgramState.py | 117 | ||||
-rw-r--r-- | debuginfo-tests/dexter/dex/dextIR/StepIR.py | 103 | ||||
-rw-r--r-- | debuginfo-tests/dexter/dex/dextIR/ValueIR.py | 38 | ||||
-rw-r--r-- | debuginfo-tests/dexter/dex/dextIR/__init__.py | 17 |
9 files changed, 495 insertions, 0 deletions
diff --git a/debuginfo-tests/dexter/dex/dextIR/BuilderIR.py b/debuginfo-tests/dexter/dex/dextIR/BuilderIR.py new file mode 100644 index 00000000000..b94a1fb7e81 --- /dev/null +++ b/debuginfo-tests/dexter/dex/dextIR/BuilderIR.py @@ -0,0 +1,16 @@ +# 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 + + +class BuilderIR: + """Data class which represents the compiler related options passed to Dexter + """ + + def __init__(self, name: str, cflags: str, ldflags: str): + self.name = name + self.cflags = cflags + self.ldflags = ldflags diff --git a/debuginfo-tests/dexter/dex/dextIR/DebuggerIR.py b/debuginfo-tests/dexter/dex/dextIR/DebuggerIR.py new file mode 100644 index 00000000000..5956db602b4 --- /dev/null +++ b/debuginfo-tests/dexter/dex/dextIR/DebuggerIR.py @@ -0,0 +1,14 @@ +# 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 + + +class DebuggerIR: + """Data class which represents a debugger.""" + + def __init__(self, name: str, version: str): + self.name = name + self.version = version diff --git a/debuginfo-tests/dexter/dex/dextIR/DextIR.py b/debuginfo-tests/dexter/dex/dextIR/DextIR.py new file mode 100644 index 00000000000..7638e8b4ab7 --- /dev/null +++ b/debuginfo-tests/dexter/dex/dextIR/DextIR.py @@ -0,0 +1,129 @@ +# 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 +from collections import OrderedDict +import os +from typing import List + +from dex.dextIR.BuilderIR import BuilderIR +from dex.dextIR.DebuggerIR import DebuggerIR +from dex.dextIR.StepIR import StepIR, StepKind + + +def _step_kind_func(context, step): + if (step.current_location.path is None or + not os.path.exists(step.current_location.path)): + return StepKind.FUNC_UNKNOWN + + if any(os.path.samefile(step.current_location.path, f) + for f in context.options.source_files): + return StepKind.FUNC + + return StepKind.FUNC_EXTERNAL + + +class DextIR: + """A full Dexter test report. + + This is composed of all the other *IR classes. They are used together to + record Dexter inputs and the resultant debugger steps, providing a single + high level access container. + + The Heuristic class works with dexter commands and the generated DextIR to + determine the debugging score for a given test. + + Args: + commands: { name (str), commands (list[CommandIR]) + """ + + def __init__(self, + dexter_version: str, + executable_path: str, + source_paths: List[str], + builder: BuilderIR = None, + debugger: DebuggerIR = None, + commands: OrderedDict = None): + self.dexter_version = dexter_version + self.executable_path = executable_path + self.source_paths = source_paths + self.builder = builder + self.debugger = debugger + self.commands = commands + self.steps: List[StepIR] = [] + + def __str__(self): + colors = 'rgby' + st = '## BEGIN ##\n' + color_idx = 0 + for step in self.steps: + if step.step_kind in (StepKind.FUNC, StepKind.FUNC_EXTERNAL, + StepKind.FUNC_UNKNOWN): + color_idx += 1 + + color = colors[color_idx % len(colors)] + st += '<{}>{}</>\n'.format(color, step) + st += '## END ({} step{}) ##\n'.format( + self.num_steps, '' if self.num_steps == 1 else 's') + return st + + @property + def num_steps(self): + return len(self.steps) + + def _get_prev_step_in_this_frame(self, step): + """Find the most recent step in the same frame as `step`. + + Returns: + StepIR or None if there is no previous step in this frame. + """ + return next((s for s in reversed(self.steps) + if s.current_function == step.current_function + and s.num_frames == step.num_frames), None) + + def _get_new_step_kind(self, context, step): + if step.current_function is None: + return StepKind.UNKNOWN + + if len(self.steps) == 0: + return _step_kind_func(context, step) + + prev_step = self.steps[-1] + + if prev_step.current_function is None: + return StepKind.UNKNOWN + + if prev_step.num_frames < step.num_frames: + return _step_kind_func(context, step) + + if prev_step.num_frames > step.num_frames: + frame_step = self._get_prev_step_in_this_frame(step) + prev_step = frame_step if frame_step is not None else prev_step + + # We're in the same func as prev step, check lineo. + if prev_step.current_location.lineno > step.current_location.lineno: + return StepKind.VERTICAL_BACKWARD + + if prev_step.current_location.lineno < step.current_location.lineno: + return StepKind.VERTICAL_FORWARD + + # We're on the same line as prev step, check column. + if prev_step.current_location.column > step.current_location.column: + return StepKind.HORIZONTAL_BACKWARD + + if prev_step.current_location.column < step.current_location.column: + return StepKind.HORIZONTAL_FORWARD + + # This step is in exactly the same location as the prev step. + return StepKind.SAME + + def new_step(self, context, step): + assert isinstance(step, StepIR), type(step) + step.step_kind = self._get_new_step_kind(context, step) + self.steps.append(step) + return step + + def clear_steps(self): + self.steps.clear() diff --git a/debuginfo-tests/dexter/dex/dextIR/FrameIR.py b/debuginfo-tests/dexter/dex/dextIR/FrameIR.py new file mode 100644 index 00000000000..a2c0523b47b --- /dev/null +++ b/debuginfo-tests/dexter/dex/dextIR/FrameIR.py @@ -0,0 +1,16 @@ +# 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 +from dex.dextIR.LocIR import LocIR + + +class FrameIR: + """Data class which represents a frame in the call stack""" + + def __init__(self, function: str, is_inlined: bool, loc: LocIR): + self.function = function + self.is_inlined = is_inlined + self.loc = loc diff --git a/debuginfo-tests/dexter/dex/dextIR/LocIR.py b/debuginfo-tests/dexter/dex/dextIR/LocIR.py new file mode 100644 index 00000000000..52a56a8fe80 --- /dev/null +++ b/debuginfo-tests/dexter/dex/dextIR/LocIR.py @@ -0,0 +1,45 @@ +# 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 +import os + + +class LocIR: + """Data class which represents a source location.""" + + def __init__(self, path: str, lineno: int, column: int): + if path: + path = os.path.normcase(path) + self.path = path + self.lineno = lineno + self.column = column + + def __str__(self): + return '{}({}:{})'.format(self.path, self.lineno, self.column) + + def __eq__(self, rhs): + return (os.path.exists(self.path) and os.path.exists(rhs.path) + and os.path.samefile(self.path, rhs.path) + and self.lineno == rhs.lineno + and self.column == rhs.column) + + def __lt__(self, rhs): + if self.path != rhs.path: + return False + + if self.lineno == rhs.lineno: + return self.column < rhs.column + + return self.lineno < rhs.lineno + + def __gt__(self, rhs): + if self.path != rhs.path: + return False + + if self.lineno == rhs.lineno: + return self.column > rhs.column + + return self.lineno > rhs.lineno diff --git a/debuginfo-tests/dexter/dex/dextIR/ProgramState.py b/debuginfo-tests/dexter/dex/dextIR/ProgramState.py new file mode 100644 index 00000000000..4f05189aed8 --- /dev/null +++ b/debuginfo-tests/dexter/dex/dextIR/ProgramState.py @@ -0,0 +1,117 @@ +# 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 +"""Set of data classes for representing the complete debug program state at a +fixed point in execution. +""" + +import os + +from collections import OrderedDict +from typing import List + +class SourceLocation: + def __init__(self, path: str = None, lineno: int = None, column: int = None): + if path: + path = os.path.normcase(path) + self.path = path + self.lineno = lineno + self.column = column + + def __str__(self): + return '{}({}:{})'.format(self.path, self.lineno, self.column) + + def match(self, other) -> bool: + """Returns true iff all the properties that appear in `self` have the + same value in `other`, but not necessarily vice versa. + """ + if not other or not isinstance(other, SourceLocation): + return False + + if self.path and (self.path != other.path): + return False + + if self.lineno and (self.lineno != other.lineno): + return False + + if self.column and (self.column != other.column): + return False + + return True + + +class StackFrame: + def __init__(self, + function: str = None, + is_inlined: bool = None, + location: SourceLocation = None, + watches: OrderedDict = None): + if watches is None: + watches = {} + + self.function = function + self.is_inlined = is_inlined + self.location = location + self.watches = watches + + def __str__(self): + return '{}{}: {} | {}'.format( + self.function, + ' (inlined)' if self.is_inlined else '', + self.location, + {k: str(self.watches[k]) for k in self.watches}) + + def match(self, other) -> bool: + """Returns true iff all the properties that appear in `self` have the + same value in `other`, but not necessarily vice versa. + """ + if not other or not isinstance(other, StackFrame): + return False + + if self.location and not self.location.match(other.location): + return False + + if self.watches: + for name in iter(self.watches): + try: + if isinstance(self.watches[name], dict): + for attr in iter(self.watches[name]): + if (getattr(other.watches[name], attr, None) != + self.watches[name][attr]): + return False + else: + if other.watches[name].value != self.watches[name]: + return False + except KeyError: + return False + + return True + +class ProgramState: + def __init__(self, frames: List[StackFrame] = None): + self.frames = frames + + def __str__(self): + return '\n'.join(map( + lambda enum: 'Frame {}: {}'.format(enum[0], enum[1]), + enumerate(self.frames))) + + def match(self, other) -> bool: + """Returns true iff all the properties that appear in `self` have the + same value in `other`, but not necessarily vice versa. + """ + if not other or not isinstance(other, ProgramState): + return False + + if self.frames: + for idx, frame in enumerate(self.frames): + try: + if not frame.match(other.frames[idx]): + return False + except (IndexError, KeyError): + return False + + return True diff --git a/debuginfo-tests/dexter/dex/dextIR/StepIR.py b/debuginfo-tests/dexter/dex/dextIR/StepIR.py new file mode 100644 index 00000000000..8111968efe9 --- /dev/null +++ b/debuginfo-tests/dexter/dex/dextIR/StepIR.py @@ -0,0 +1,103 @@ +# 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 +"""Classes which are used to represent debugger steps.""" + +import json + +from collections import OrderedDict +from typing import List +from enum import Enum +from dex.dextIR.FrameIR import FrameIR +from dex.dextIR.LocIR import LocIR +from dex.dextIR.ProgramState import ProgramState + + +class StopReason(Enum): + BREAKPOINT = 0 + STEP = 1 + PROGRAM_EXIT = 2 + ERROR = 3 + OTHER = 4 + + +class StepKind(Enum): + FUNC = 0 + FUNC_EXTERNAL = 1 + FUNC_UNKNOWN = 2 + VERTICAL_FORWARD = 3 + SAME = 4 + VERTICAL_BACKWARD = 5 + UNKNOWN = 6 + HORIZONTAL_FORWARD = 7 + HORIZONTAL_BACKWARD = 8 + + +class StepIR: + """A debugger step. + + Args: + watches (OrderedDict): { expression (str), result (ValueIR) } + """ + + def __init__(self, + step_index: int, + stop_reason: StopReason, + frames: List[FrameIR], + step_kind: StepKind = None, + watches: OrderedDict = None, + program_state: ProgramState = None): + self.step_index = step_index + self.step_kind = step_kind + self.stop_reason = stop_reason + self.program_state = program_state + + if frames is None: + frames = [] + self.frames = frames + + if watches is None: + watches = {} + self.watches = watches + + def __str__(self): + try: + frame = self.current_frame + frame_info = (frame.function, frame.loc.path, frame.loc.lineno, + frame.loc.column) + except AttributeError: + frame_info = (None, None, None, None) + + step_info = (self.step_index, ) + frame_info + ( + str(self.stop_reason), str(self.step_kind), + [w for w in self.watches]) + + return '{}{}'.format('. ' * (self.num_frames - 1), + json.dumps(step_info)) + + @property + def num_frames(self): + return len(self.frames) + + @property + def current_frame(self): + if not len(self.frames): + return None + return self.frames[0] + + @property + def current_function(self): + try: + return self.current_frame.function + except AttributeError: + return None + + @property + def current_location(self): + try: + return self.current_frame.loc + except AttributeError: + return LocIR(path=None, lineno=None, column=None) diff --git a/debuginfo-tests/dexter/dex/dextIR/ValueIR.py b/debuginfo-tests/dexter/dex/dextIR/ValueIR.py new file mode 100644 index 00000000000..9d532acbb21 --- /dev/null +++ b/debuginfo-tests/dexter/dex/dextIR/ValueIR.py @@ -0,0 +1,38 @@ +# 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 + + +class ValueIR: + """Data class to store the result of an expression evaluation.""" + + def __init__(self, + expression: str, + value: str, + type_name: str, + could_evaluate: bool, + error_string: str = None, + is_optimized_away: bool = False, + is_irretrievable: bool = False): + self.expression = expression + self.value = value + self.type_name = type_name + self.could_evaluate = could_evaluate + self.error_string = error_string + self.is_optimized_away = is_optimized_away + self.is_irretrievable = is_irretrievable + + def __str__(self): + prefix = '"{}": '.format(self.expression) + if self.error_string is not None: + return prefix + self.error_string + if self.value is not None: + return prefix + '({}) {}'.format(self.type_name, self.value) + return (prefix + + 'could_evaluate: {}; irretrievable: {}; optimized_away: {};' + .format(self.could_evaluate, self.is_irretrievable, + self.is_optimized_away)) + diff --git a/debuginfo-tests/dexter/dex/dextIR/__init__.py b/debuginfo-tests/dexter/dex/dextIR/__init__.py new file mode 100644 index 00000000000..463a2c13716 --- /dev/null +++ b/debuginfo-tests/dexter/dex/dextIR/__init__.py @@ -0,0 +1,17 @@ +# 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 +"""dextIR: DExTer Intermediate Representation of DExTer's debugger trace output. +""" + +from dex.dextIR.BuilderIR import BuilderIR +from dex.dextIR.DextIR import DextIR +from dex.dextIR.DebuggerIR import DebuggerIR +from dex.dextIR.FrameIR import FrameIR +from dex.dextIR.LocIR import LocIR +from dex.dextIR.StepIR import StepIR, StepKind, StopReason +from dex.dextIR.ValueIR import ValueIR +from dex.dextIR.ProgramState import ProgramState, SourceLocation, StackFrame |