11364750dSJames Henderson# DExTer : Debugging Experience Tester 21364750dSJames Henderson# ~~~~~~ ~ ~~ ~ ~~ 31364750dSJames Henderson# 41364750dSJames Henderson# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 51364750dSJames Henderson# See https://llvm.org/LICENSE.txt for license information. 61364750dSJames Henderson# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 71364750dSJames Henderson 81364750dSJames Henderson"""DexExpectWatch base class, holds logic for how to build and process expected 91364750dSJames Henderson watch commands. 101364750dSJames Henderson""" 111364750dSJames Henderson 121364750dSJames Hendersonimport abc 131364750dSJames Hendersonimport difflib 141364750dSJames Hendersonimport os 150428d44dSStephen Tozerimport math 167e46a721SStephen Tozerfrom collections import namedtuple 17b03451bbSStephen Tozerfrom pathlib import PurePath 181364750dSJames Henderson 197e46a721SStephen Tozerfrom dex.command.CommandBase import CommandBase, StepExpectInfo 201364750dSJames Hendersonfrom dex.command.StepValueInfo import StepValueInfo 2130bb659cSStephen Tozerfrom dex.utils.Exceptions import NonFloatValueInCommand 221364750dSJames Henderson 23*f98ee40fSTobias Hieta 240428d44dSStephen Tozerclass AddressExpression(object): 250428d44dSStephen Tozer def __init__(self, name, offset=0): 260428d44dSStephen Tozer self.name = name 270428d44dSStephen Tozer self.offset = offset 281364750dSJames Henderson 290428d44dSStephen Tozer def is_resolved(self, resolutions): 300428d44dSStephen Tozer return self.name in resolutions 310428d44dSStephen Tozer 320428d44dSStephen Tozer # Given the resolved value of the address, resolve the final value of 330428d44dSStephen Tozer # this expression. 340428d44dSStephen Tozer def resolved_value(self, resolutions): 350428d44dSStephen Tozer if not self.name in resolutions or resolutions[self.name] is None: 360428d44dSStephen Tozer return None 370428d44dSStephen Tozer # Technically we should fill(8) if we're debugging on a 32bit architecture? 380428d44dSStephen Tozer return format_address(resolutions[self.name] + self.offset) 390428d44dSStephen Tozer 40*f98ee40fSTobias Hieta 410428d44dSStephen Tozerdef format_address(value, address_width=64): 420428d44dSStephen Tozer return "0x" + hex(value)[2:].zfill(math.ceil(address_width / 4)) 430428d44dSStephen Tozer 44*f98ee40fSTobias Hieta 450428d44dSStephen Tozerdef resolved_value(value, resolutions): 46*f98ee40fSTobias Hieta return ( 47*f98ee40fSTobias Hieta value.resolved_value(resolutions) 48*f98ee40fSTobias Hieta if isinstance(value, AddressExpression) 49*f98ee40fSTobias Hieta else value 50*f98ee40fSTobias Hieta ) 51*f98ee40fSTobias Hieta 527e46a721SStephen Tozer 531364750dSJames Hendersonclass DexExpectWatchBase(CommandBase): 541364750dSJames Henderson def __init__(self, *args, **kwargs): 551364750dSJames Henderson if len(args) < 2: 56*f98ee40fSTobias Hieta raise TypeError("expected at least two args") 571364750dSJames Henderson 581364750dSJames Henderson self.expression = args[0] 59*f98ee40fSTobias Hieta self.values = [ 60*f98ee40fSTobias Hieta arg if isinstance(arg, AddressExpression) else str(arg) for arg in args[1:] 61*f98ee40fSTobias Hieta ] 621364750dSJames Henderson try: 63*f98ee40fSTobias Hieta on_line = kwargs.pop("on_line") 641364750dSJames Henderson self._from_line = on_line 651364750dSJames Henderson self._to_line = on_line 661364750dSJames Henderson except KeyError: 67*f98ee40fSTobias Hieta self._from_line = kwargs.pop("from_line", 1) 68*f98ee40fSTobias Hieta self._to_line = kwargs.pop("to_line", 999999) 69*f98ee40fSTobias Hieta self._require_in_order = kwargs.pop("require_in_order", True) 70*f98ee40fSTobias Hieta self.float_range = kwargs.pop("float_range", None) 7130bb659cSStephen Tozer if self.float_range is not None: 7230bb659cSStephen Tozer for value in self.values: 7330bb659cSStephen Tozer try: 7430bb659cSStephen Tozer float(value) 7530bb659cSStephen Tozer except ValueError: 76*f98ee40fSTobias Hieta raise NonFloatValueInCommand( 77*f98ee40fSTobias Hieta f"Non-float value '{value}' when float_range arg provided" 78*f98ee40fSTobias Hieta ) 791364750dSJames Henderson if kwargs: 80*f98ee40fSTobias Hieta raise TypeError("unexpected named args: {}".format(", ".join(kwargs))) 811364750dSJames Henderson 821364750dSJames Henderson # Number of times that this watch has been encountered. 831364750dSJames Henderson self.times_encountered = 0 841364750dSJames Henderson 851364750dSJames Henderson # We'll pop from this set as we encounter values so anything left at 861364750dSJames Henderson # the end can be considered as not having been seen. 871364750dSJames Henderson self._missing_values = set(self.values) 881364750dSJames Henderson 891364750dSJames Henderson self.misordered_watches = [] 901364750dSJames Henderson 911364750dSJames Henderson # List of StepValueInfos for any watch that is encountered as invalid. 921364750dSJames Henderson self.invalid_watches = [] 931364750dSJames Henderson 941364750dSJames Henderson # List of StepValueInfo any any watch where we couldn't retrieve its 951364750dSJames Henderson # data. 961364750dSJames Henderson self.irretrievable_watches = [] 971364750dSJames Henderson 981364750dSJames Henderson # List of StepValueInfos for any watch that is encountered as having 991364750dSJames Henderson # been optimized out. 1001364750dSJames Henderson self.optimized_out_watches = [] 1011364750dSJames Henderson 1021364750dSJames Henderson # List of StepValueInfos for any watch that is encountered that has an 1031364750dSJames Henderson # expected value. 1041364750dSJames Henderson self.expected_watches = [] 1051364750dSJames Henderson 1061364750dSJames Henderson # List of StepValueInfos for any watch that is encountered that has an 1071364750dSJames Henderson # unexpected value. 1081364750dSJames Henderson self.unexpected_watches = [] 1091364750dSJames Henderson 1100428d44dSStephen Tozer # List of StepValueInfos for all observed watches that were not 1110428d44dSStephen Tozer # invalid, irretrievable, or optimized out (combines expected and 1120428d44dSStephen Tozer # unexpected). 1130428d44dSStephen Tozer self.observed_watches = [] 1140428d44dSStephen Tozer 1150428d44dSStephen Tozer # dict of address names to their final resolved values, None until it 1160428d44dSStephen Tozer # gets assigned externally. 1170428d44dSStephen Tozer self.address_resolutions = None 1180428d44dSStephen Tozer 1191364750dSJames Henderson super(DexExpectWatchBase, self).__init__() 1201364750dSJames Henderson 1210428d44dSStephen Tozer def resolve_value(self, value): 122*f98ee40fSTobias Hieta return ( 123*f98ee40fSTobias Hieta value.resolved_value(self.address_resolutions) 124*f98ee40fSTobias Hieta if isinstance(value, AddressExpression) 125*f98ee40fSTobias Hieta else value 126*f98ee40fSTobias Hieta ) 1270428d44dSStephen Tozer 1280428d44dSStephen Tozer def describe_value(self, value): 1290428d44dSStephen Tozer if isinstance(value, AddressExpression): 1300428d44dSStephen Tozer offset = "" 1310428d44dSStephen Tozer if value.offset > 0: 1320428d44dSStephen Tozer offset = f"+{value.offset}" 1330428d44dSStephen Tozer elif value.offset < 0: 1340428d44dSStephen Tozer offset = str(value.offset) 1350428d44dSStephen Tozer desc = f"address '{value.name}'{offset}" 1360428d44dSStephen Tozer if self.resolve_value(value) is not None: 1370428d44dSStephen Tozer desc += f" ({self.resolve_value(value)})" 1380428d44dSStephen Tozer return desc 1390428d44dSStephen Tozer return value 1401364750dSJames Henderson 1411364750dSJames Henderson def get_watches(self): 142*f98ee40fSTobias Hieta return [ 143*f98ee40fSTobias Hieta StepExpectInfo( 144*f98ee40fSTobias Hieta self.expression, self.path, 0, range(self._from_line, self._to_line + 1) 145*f98ee40fSTobias Hieta ) 146*f98ee40fSTobias Hieta ] 1471364750dSJames Henderson 1481364750dSJames Henderson @property 1491364750dSJames Henderson def line_range(self): 1501364750dSJames Henderson return list(range(self._from_line, self._to_line + 1)) 1511364750dSJames Henderson 1521364750dSJames Henderson @property 1531364750dSJames Henderson def missing_values(self): 1540428d44dSStephen Tozer return sorted(list(self.describe_value(v) for v in self._missing_values)) 1551364750dSJames Henderson 1561364750dSJames Henderson @property 1571364750dSJames Henderson def encountered_values(self): 158*f98ee40fSTobias Hieta return sorted( 159*f98ee40fSTobias Hieta list( 160*f98ee40fSTobias Hieta set( 161*f98ee40fSTobias Hieta self.describe_value(v) 162*f98ee40fSTobias Hieta for v in set(self.values) - self._missing_values 163*f98ee40fSTobias Hieta ) 164*f98ee40fSTobias Hieta ) 165*f98ee40fSTobias Hieta ) 1661364750dSJames Henderson 1671364750dSJames Henderson @abc.abstractmethod 1681364750dSJames Henderson def _get_expected_field(self, watch): 169*f98ee40fSTobias Hieta """Return a field from watch that this ExpectWatch command is checking.""" 1701364750dSJames Henderson 17130bb659cSStephen Tozer def _match_expected_floating_point(self, value): 17230bb659cSStephen Tozer """Checks to see whether value is a float that falls within the 17330bb659cSStephen Tozer acceptance range of one of this command's expected float values, and 17430bb659cSStephen Tozer returns the expected value if so; otherwise returns the original 17530bb659cSStephen Tozer value.""" 17630bb659cSStephen Tozer try: 17730bb659cSStephen Tozer value_as_float = float(value) 17830bb659cSStephen Tozer except ValueError: 17930bb659cSStephen Tozer return value 18030bb659cSStephen Tozer 18130bb659cSStephen Tozer possible_values = self.values 18230bb659cSStephen Tozer for expected in possible_values: 18330bb659cSStephen Tozer try: 18430bb659cSStephen Tozer expected_as_float = float(expected) 18530bb659cSStephen Tozer difference = abs(value_as_float - expected_as_float) 18630bb659cSStephen Tozer if difference <= self.float_range: 18730bb659cSStephen Tozer return expected 18830bb659cSStephen Tozer except ValueError: 18930bb659cSStephen Tozer pass 19030bb659cSStephen Tozer return value 19130bb659cSStephen Tozer 19230bb659cSStephen Tozer def _maybe_fix_float(self, value): 19330bb659cSStephen Tozer if self.float_range is not None: 19430bb659cSStephen Tozer return self._match_expected_floating_point(value) 19530bb659cSStephen Tozer else: 19630bb659cSStephen Tozer return value 19730bb659cSStephen Tozer 1981364750dSJames Henderson def _handle_watch(self, step_info): 1991364750dSJames Henderson self.times_encountered += 1 2001364750dSJames Henderson 2011364750dSJames Henderson if not step_info.watch_info.could_evaluate: 2021364750dSJames Henderson self.invalid_watches.append(step_info) 2031364750dSJames Henderson return 2041364750dSJames Henderson 2051364750dSJames Henderson if step_info.watch_info.is_optimized_away: 2061364750dSJames Henderson self.optimized_out_watches.append(step_info) 2071364750dSJames Henderson return 2081364750dSJames Henderson 2091364750dSJames Henderson if step_info.watch_info.is_irretrievable: 2101364750dSJames Henderson self.irretrievable_watches.append(step_info) 2111364750dSJames Henderson return 2121364750dSJames Henderson 21330bb659cSStephen Tozer expected_value = self._maybe_fix_float(step_info.expected_value) 21430bb659cSStephen Tozer 2150428d44dSStephen Tozer # Check to see if this value matches with a resolved address. 2160428d44dSStephen Tozer matching_address = None 2170428d44dSStephen Tozer for v in self.values: 218*f98ee40fSTobias Hieta if ( 219*f98ee40fSTobias Hieta isinstance(v, AddressExpression) 220*f98ee40fSTobias Hieta and v.name in self.address_resolutions 221*f98ee40fSTobias Hieta and self.resolve_value(v) == expected_value 222*f98ee40fSTobias Hieta ): 2230428d44dSStephen Tozer matching_address = v 2240428d44dSStephen Tozer break 2250428d44dSStephen Tozer 2260428d44dSStephen Tozer # If this is not an expected value, either a direct value or an address, 2270428d44dSStephen Tozer # then this is an unexpected watch. 22830bb659cSStephen Tozer if expected_value not in self.values and matching_address is None: 2291364750dSJames Henderson self.unexpected_watches.append(step_info) 2301364750dSJames Henderson return 2311364750dSJames Henderson 2321364750dSJames Henderson self.expected_watches.append(step_info) 233*f98ee40fSTobias Hieta value_to_remove = ( 234*f98ee40fSTobias Hieta matching_address if matching_address is not None else expected_value 235*f98ee40fSTobias Hieta ) 2361364750dSJames Henderson try: 2370428d44dSStephen Tozer self._missing_values.remove(value_to_remove) 2381364750dSJames Henderson except KeyError: 2391364750dSJames Henderson pass 2401364750dSJames Henderson 2411364750dSJames Henderson def _check_watch_order(self, actual_watches, expected_values): 2421364750dSJames Henderson """Use difflib to figure out whether the values are in the expected order 2431364750dSJames Henderson or not. 2441364750dSJames Henderson """ 2451364750dSJames Henderson differences = [] 246*f98ee40fSTobias Hieta actual_values = [ 247*f98ee40fSTobias Hieta self._maybe_fix_float(w.expected_value) for w in actual_watches 248*f98ee40fSTobias Hieta ] 249*f98ee40fSTobias Hieta value_differences = list( 250*f98ee40fSTobias Hieta difflib.Differ().compare(actual_values, expected_values) 251*f98ee40fSTobias Hieta ) 2521364750dSJames Henderson 2531364750dSJames Henderson missing_value = False 2541364750dSJames Henderson index = 0 2551364750dSJames Henderson for vd in value_differences: 2561364750dSJames Henderson kind = vd[0] 257*f98ee40fSTobias Hieta if kind == "+": 2581364750dSJames Henderson # A value that is encountered in the expected list but not in the 2591364750dSJames Henderson # actual list. We'll keep a note that something is wrong and flag 2601364750dSJames Henderson # the next value that matches as misordered. 2611364750dSJames Henderson missing_value = True 262*f98ee40fSTobias Hieta elif kind == " ": 2631364750dSJames Henderson # This value is as expected. It might still be wrong if we've 2641364750dSJames Henderson # previously encountered a value that is in the expected list but 2651364750dSJames Henderson # not the actual list. 2661364750dSJames Henderson if missing_value: 2671364750dSJames Henderson missing_value = False 2681364750dSJames Henderson differences.append(actual_watches[index]) 2691364750dSJames Henderson index += 1 270*f98ee40fSTobias Hieta elif kind == "-": 2711364750dSJames Henderson # A value that is encountered in the actual list but not the 2721364750dSJames Henderson # expected list. 2731364750dSJames Henderson differences.append(actual_watches[index]) 2741364750dSJames Henderson index += 1 2751364750dSJames Henderson else: 276*f98ee40fSTobias Hieta assert False, "unexpected diff:{}".format(vd) 2771364750dSJames Henderson 2781364750dSJames Henderson return differences 2791364750dSJames Henderson 2801364750dSJames Henderson def eval(self, step_collection): 2811364750dSJames Henderson for step in step_collection.steps: 2821364750dSJames Henderson loc = step.current_location 2831364750dSJames Henderson 284*f98ee40fSTobias Hieta if ( 285*f98ee40fSTobias Hieta loc.path 286*f98ee40fSTobias Hieta and self.path 287*f98ee40fSTobias Hieta and PurePath(loc.path) == PurePath(self.path) 288*f98ee40fSTobias Hieta and loc.lineno in self.line_range 289*f98ee40fSTobias Hieta ): 2901364750dSJames Henderson try: 2911364750dSJames Henderson watch = step.program_state.frames[0].watches[self.expression] 2921364750dSJames Henderson except KeyError: 2931364750dSJames Henderson pass 2941364750dSJames Henderson else: 2951364750dSJames Henderson expected_field = self._get_expected_field(watch) 296*f98ee40fSTobias Hieta step_info = StepValueInfo(step.step_index, watch, expected_field) 2971364750dSJames Henderson self._handle_watch(step_info) 2981364750dSJames Henderson 2991364750dSJames Henderson if self._require_in_order: 3001364750dSJames Henderson # A list of all watches where the value has changed. 3011364750dSJames Henderson value_change_watches = [] 3021364750dSJames Henderson prev_value = None 30330bb659cSStephen Tozer all_expected_values = [] 3041364750dSJames Henderson for watch in self.expected_watches: 30530bb659cSStephen Tozer expected_value = self._maybe_fix_float(watch.expected_value) 30630bb659cSStephen Tozer all_expected_values.append(expected_value) 30730bb659cSStephen Tozer if expected_value != prev_value: 3081364750dSJames Henderson value_change_watches.append(watch) 30930bb659cSStephen Tozer prev_value = expected_value 3101364750dSJames Henderson 3110428d44dSStephen Tozer resolved_values = [self.resolve_value(v) for v in self.values] 3121364750dSJames Henderson self.misordered_watches = self._check_watch_order( 313*f98ee40fSTobias Hieta value_change_watches, 314*f98ee40fSTobias Hieta [v for v in resolved_values if v in all_expected_values], 315*f98ee40fSTobias Hieta ) 316