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 15 16from dex.command.CommandBase import CommandBase 17from dex.command.StepValueInfo import StepValueInfo 18 19 20class DexExpectWatchBase(CommandBase): 21 def __init__(self, *args, **kwargs): 22 if len(args) < 2: 23 raise TypeError('expected at least two args') 24 25 self.expression = args[0] 26 self.values = [str(arg) for arg in args[1:]] 27 try: 28 on_line = kwargs.pop('on_line') 29 self._from_line = on_line 30 self._to_line = on_line 31 except KeyError: 32 self._from_line = kwargs.pop('from_line', 1) 33 self._to_line = kwargs.pop('to_line', 999999) 34 self._require_in_order = kwargs.pop('require_in_order', True) 35 if kwargs: 36 raise TypeError('unexpected named args: {}'.format( 37 ', '.join(kwargs))) 38 39 # Number of times that this watch has been encountered. 40 self.times_encountered = 0 41 42 # We'll pop from this set as we encounter values so anything left at 43 # the end can be considered as not having been seen. 44 self._missing_values = set(self.values) 45 46 self.misordered_watches = [] 47 48 # List of StepValueInfos for any watch that is encountered as invalid. 49 self.invalid_watches = [] 50 51 # List of StepValueInfo any any watch where we couldn't retrieve its 52 # data. 53 self.irretrievable_watches = [] 54 55 # List of StepValueInfos for any watch that is encountered as having 56 # been optimized out. 57 self.optimized_out_watches = [] 58 59 # List of StepValueInfos for any watch that is encountered that has an 60 # expected value. 61 self.expected_watches = [] 62 63 # List of StepValueInfos for any watch that is encountered that has an 64 # unexpected value. 65 self.unexpected_watches = [] 66 67 super(DexExpectWatchBase, self).__init__() 68 69 70 def get_watches(self): 71 return [self.expression] 72 73 @property 74 def line_range(self): 75 return list(range(self._from_line, self._to_line + 1)) 76 77 @property 78 def missing_values(self): 79 return sorted(list(self._missing_values)) 80 81 @property 82 def encountered_values(self): 83 return sorted(list(set(self.values) - self._missing_values)) 84 85 86 def resolve_label(self, label_line_pair): 87 # from_line and to_line could have the same label. 88 label, lineno = label_line_pair 89 if self._to_line == label: 90 self._to_line = lineno 91 if self._from_line == label: 92 self._from_line = lineno 93 94 def has_labels(self): 95 return len(self.get_label_args()) > 0 96 97 def get_label_args(self): 98 return [label for label in (self._from_line, self._to_line) 99 if isinstance(label, str)] 100 101 @abc.abstractmethod 102 def _get_expected_field(self, watch): 103 """Return a field from watch that this ExpectWatch command is checking. 104 """ 105 106 def _handle_watch(self, step_info): 107 self.times_encountered += 1 108 109 if not step_info.watch_info.could_evaluate: 110 self.invalid_watches.append(step_info) 111 return 112 113 if step_info.watch_info.is_optimized_away: 114 self.optimized_out_watches.append(step_info) 115 return 116 117 if step_info.watch_info.is_irretrievable: 118 self.irretrievable_watches.append(step_info) 119 return 120 121 if step_info.expected_value not in self.values: 122 self.unexpected_watches.append(step_info) 123 return 124 125 self.expected_watches.append(step_info) 126 try: 127 self._missing_values.remove(step_info.expected_value) 128 except KeyError: 129 pass 130 131 def _check_watch_order(self, actual_watches, expected_values): 132 """Use difflib to figure out whether the values are in the expected order 133 or not. 134 """ 135 differences = [] 136 actual_values = [w.expected_value for w in actual_watches] 137 value_differences = list(difflib.Differ().compare(actual_values, 138 expected_values)) 139 140 missing_value = False 141 index = 0 142 for vd in value_differences: 143 kind = vd[0] 144 if kind == '+': 145 # A value that is encountered in the expected list but not in the 146 # actual list. We'll keep a note that something is wrong and flag 147 # the next value that matches as misordered. 148 missing_value = True 149 elif kind == ' ': 150 # This value is as expected. It might still be wrong if we've 151 # previously encountered a value that is in the expected list but 152 # not the actual list. 153 if missing_value: 154 missing_value = False 155 differences.append(actual_watches[index]) 156 index += 1 157 elif kind == '-': 158 # A value that is encountered in the actual list but not the 159 # expected list. 160 differences.append(actual_watches[index]) 161 index += 1 162 else: 163 assert False, 'unexpected diff:{}'.format(vd) 164 165 return differences 166 167 def eval(self, step_collection): 168 for step in step_collection.steps: 169 loc = step.current_location 170 171 if (loc.path and os.path.exists(loc.path) and 172 os.path.exists(self.path) and 173 os.path.samefile(loc.path, self.path) and 174 loc.lineno in self.line_range): 175 try: 176 watch = step.program_state.frames[0].watches[self.expression] 177 except KeyError: 178 pass 179 else: 180 expected_field = self._get_expected_field(watch) 181 step_info = StepValueInfo(step.step_index, watch, 182 expected_field) 183 self._handle_watch(step_info) 184 185 if self._require_in_order: 186 # A list of all watches where the value has changed. 187 value_change_watches = [] 188 prev_value = None 189 for watch in self.expected_watches: 190 if watch.expected_value != prev_value: 191 value_change_watches.append(watch) 192 prev_value = watch.expected_value 193 194 self.misordered_watches = self._check_watch_order( 195 value_change_watches, [ 196 v for v in self.values if v in 197 [w.expected_value for w in self.expected_watches] 198 ]) 199