1# DExTer : Debugging Experience Tester 2# ~~~~~~ ~ ~~ ~ ~~ 3# 4# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 5# See https://llvm.org/LICENSE.txt for license information. 6# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 7 8"""DexExpectWatch base class, holds logic for how to build and process expected 9 watch commands. 10""" 11 12import abc 13import difflib 14import os 15import math 16from collections import namedtuple 17 18from dex.command.CommandBase import CommandBase, StepExpectInfo 19from dex.command.StepValueInfo import StepValueInfo 20 21class AddressExpression(object): 22 def __init__(self, name, offset=0): 23 self.name = name 24 self.offset = offset 25 26 def is_resolved(self, resolutions): 27 return self.name in resolutions 28 29 # Given the resolved value of the address, resolve the final value of 30 # this expression. 31 def resolved_value(self, resolutions): 32 if not self.name in resolutions or resolutions[self.name] is None: 33 return None 34 # Technically we should fill(8) if we're debugging on a 32bit architecture? 35 return format_address(resolutions[self.name] + self.offset) 36 37def format_address(value, address_width=64): 38 return "0x" + hex(value)[2:].zfill(math.ceil(address_width/4)) 39 40def resolved_value(value, resolutions): 41 return value.resolved_value(resolutions) if isinstance(value, AddressExpression) else value 42 43class DexExpectWatchBase(CommandBase): 44 def __init__(self, *args, **kwargs): 45 if len(args) < 2: 46 raise TypeError('expected at least two args') 47 48 self.expression = args[0] 49 self.values = [arg if isinstance(arg, AddressExpression) else str(arg) for arg in args[1:]] 50 try: 51 on_line = kwargs.pop('on_line') 52 self._from_line = on_line 53 self._to_line = on_line 54 except KeyError: 55 self._from_line = kwargs.pop('from_line', 1) 56 self._to_line = kwargs.pop('to_line', 999999) 57 self._require_in_order = kwargs.pop('require_in_order', True) 58 if kwargs: 59 raise TypeError('unexpected named args: {}'.format( 60 ', '.join(kwargs))) 61 62 # Number of times that this watch has been encountered. 63 self.times_encountered = 0 64 65 # We'll pop from this set as we encounter values so anything left at 66 # the end can be considered as not having been seen. 67 self._missing_values = set(self.values) 68 69 self.misordered_watches = [] 70 71 # List of StepValueInfos for any watch that is encountered as invalid. 72 self.invalid_watches = [] 73 74 # List of StepValueInfo any any watch where we couldn't retrieve its 75 # data. 76 self.irretrievable_watches = [] 77 78 # List of StepValueInfos for any watch that is encountered as having 79 # been optimized out. 80 self.optimized_out_watches = [] 81 82 # List of StepValueInfos for any watch that is encountered that has an 83 # expected value. 84 self.expected_watches = [] 85 86 # List of StepValueInfos for any watch that is encountered that has an 87 # unexpected value. 88 self.unexpected_watches = [] 89 90 # List of StepValueInfos for all observed watches that were not 91 # invalid, irretrievable, or optimized out (combines expected and 92 # unexpected). 93 self.observed_watches = [] 94 95 # dict of address names to their final resolved values, None until it 96 # gets assigned externally. 97 self.address_resolutions = None 98 99 super(DexExpectWatchBase, self).__init__() 100 101 def resolve_value(self, value): 102 return value.resolved_value(self.address_resolutions) if isinstance(value, AddressExpression) else value 103 104 def describe_value(self, value): 105 if isinstance(value, AddressExpression): 106 offset = "" 107 if value.offset > 0: 108 offset = f"+{value.offset}" 109 elif value.offset < 0: 110 offset = str(value.offset) 111 desc = f"address '{value.name}'{offset}" 112 if self.resolve_value(value) is not None: 113 desc += f" ({self.resolve_value(value)})" 114 return desc 115 return value 116 117 def get_watches(self): 118 return [StepExpectInfo(self.expression, self.path, 0, range(self._from_line, self._to_line + 1))] 119 120 @property 121 def line_range(self): 122 return list(range(self._from_line, self._to_line + 1)) 123 124 @property 125 def missing_values(self): 126 return sorted(list(self.describe_value(v) for v in self._missing_values)) 127 128 @property 129 def encountered_values(self): 130 return sorted(list(set(self.describe_value(v) for v in set(self.values) - self._missing_values))) 131 132 @abc.abstractmethod 133 def _get_expected_field(self, watch): 134 """Return a field from watch that this ExpectWatch command is checking. 135 """ 136 137 def _handle_watch(self, step_info): 138 self.times_encountered += 1 139 140 if not step_info.watch_info.could_evaluate: 141 self.invalid_watches.append(step_info) 142 return 143 144 if step_info.watch_info.is_optimized_away: 145 self.optimized_out_watches.append(step_info) 146 return 147 148 if step_info.watch_info.is_irretrievable: 149 self.irretrievable_watches.append(step_info) 150 return 151 152 # Check to see if this value matches with a resolved address. 153 matching_address = None 154 for v in self.values: 155 if (isinstance(v, AddressExpression) and 156 v.name in self.address_resolutions and 157 self.resolve_value(v) == step_info.expected_value): 158 matching_address = v 159 break 160 161 # If this is not an expected value, either a direct value or an address, 162 # then this is an unexpected watch. 163 if step_info.expected_value not in self.values and matching_address is None: 164 self.unexpected_watches.append(step_info) 165 return 166 167 self.expected_watches.append(step_info) 168 value_to_remove = matching_address if matching_address is not None else step_info.expected_value 169 try: 170 self._missing_values.remove(value_to_remove) 171 except KeyError: 172 pass 173 174 def _check_watch_order(self, actual_watches, expected_values): 175 """Use difflib to figure out whether the values are in the expected order 176 or not. 177 """ 178 differences = [] 179 actual_values = [w.expected_value for w in actual_watches] 180 value_differences = list(difflib.Differ().compare(actual_values, 181 expected_values)) 182 183 missing_value = False 184 index = 0 185 for vd in value_differences: 186 kind = vd[0] 187 if kind == '+': 188 # A value that is encountered in the expected list but not in the 189 # actual list. We'll keep a note that something is wrong and flag 190 # the next value that matches as misordered. 191 missing_value = True 192 elif kind == ' ': 193 # This value is as expected. It might still be wrong if we've 194 # previously encountered a value that is in the expected list but 195 # not the actual list. 196 if missing_value: 197 missing_value = False 198 differences.append(actual_watches[index]) 199 index += 1 200 elif kind == '-': 201 # A value that is encountered in the actual list but not the 202 # expected list. 203 differences.append(actual_watches[index]) 204 index += 1 205 else: 206 assert False, 'unexpected diff:{}'.format(vd) 207 208 return differences 209 210 def eval(self, step_collection): 211 assert os.path.exists(self.path) 212 for step in step_collection.steps: 213 loc = step.current_location 214 215 if (loc.path and os.path.exists(loc.path) and 216 os.path.samefile(loc.path, self.path) and 217 loc.lineno in self.line_range): 218 try: 219 watch = step.program_state.frames[0].watches[self.expression] 220 except KeyError: 221 pass 222 else: 223 expected_field = self._get_expected_field(watch) 224 step_info = StepValueInfo(step.step_index, watch, 225 expected_field) 226 self._handle_watch(step_info) 227 228 if self._require_in_order: 229 # A list of all watches where the value has changed. 230 value_change_watches = [] 231 prev_value = None 232 for watch in self.expected_watches: 233 if watch.expected_value != prev_value: 234 value_change_watches.append(watch) 235 prev_value = watch.expected_value 236 237 resolved_values = [self.resolve_value(v) for v in self.values] 238 self.misordered_watches = self._check_watch_order( 239 value_change_watches, [ 240 v for v in resolved_values if v in 241 [w.expected_value for w in self.expected_watches] 242 ]) 243