xref: /llvm-project/cross-project-tests/debuginfo-tests/dexter/dex/command/commands/DexExpectWatchBase.py (revision f98ee40f4b5d7474fc67e82824bf6abbaedb7b1c)
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