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"""Set of data classes for representing the complete debug program state at a
8fixed point in execution.
9"""
10
11import os
12
13from collections import OrderedDict
14from typing import List
15
16class SourceLocation:
17    def __init__(self, path: str = None, lineno: int = None, column: int = None):
18        if path:
19            path = os.path.normcase(path)
20        self.path = path
21        self.lineno = lineno
22        self.column = column
23
24    def __str__(self):
25        return '{}({}:{})'.format(self.path, self.lineno, self.column)
26
27    def match(self, other) -> bool:
28        """Returns true iff all the properties that appear in `self` have the
29        same value in `other`, but not necessarily vice versa.
30        """
31        if not other or not isinstance(other, SourceLocation):
32            return False
33
34        if self.path and (self.path != other.path):
35            return False
36
37        if self.lineno and (self.lineno != other.lineno):
38            return False
39
40        if self.column and (self.column != other.column):
41            return False
42
43        return True
44
45
46class StackFrame:
47    def __init__(self,
48                 function: str = None,
49                 is_inlined: bool = None,
50                 location: SourceLocation = None,
51                 watches: OrderedDict = None):
52        if watches is None:
53            watches = {}
54
55        self.function = function
56        self.is_inlined = is_inlined
57        self.location = location
58        self.watches = watches
59
60    def __str__(self):
61        return '{}{}: {} | {}'.format(
62            self.function,
63            ' (inlined)' if self.is_inlined else '',
64            self.location,
65            {k: str(self.watches[k]) for k in self.watches})
66
67    def match(self, other) -> bool:
68        """Returns true iff all the properties that appear in `self` have the
69        same value in `other`, but not necessarily vice versa.
70        """
71        if not other or not isinstance(other, StackFrame):
72            return False
73
74        if self.location and not self.location.match(other.location):
75            return False
76
77        if self.watches:
78            for name in iter(self.watches):
79                try:
80                    if isinstance(self.watches[name], dict):
81                        for attr in iter(self.watches[name]):
82                            if (getattr(other.watches[name], attr, None) !=
83                                    self.watches[name][attr]):
84                                return False
85                    else:
86                        if other.watches[name].value != self.watches[name]:
87                            return False
88                except KeyError:
89                    return False
90
91        return True
92
93class ProgramState:
94    def __init__(self, frames: List[StackFrame] = None):
95        self.frames = frames
96
97    def __str__(self):
98        return '\n'.join(map(
99            lambda enum: 'Frame {}: {}'.format(enum[0], enum[1]),
100            enumerate(self.frames)))
101
102    def match(self, other) -> bool:
103        """Returns true iff all the properties that appear in `self` have the
104        same value in `other`, but not necessarily vice versa.
105        """
106        if not other or not isinstance(other, ProgramState):
107            return False
108
109        if self.frames:
110            for idx, frame in enumerate(self.frames):
111                try:
112                    if not frame.match(other.frames[idx]):
113                        return False
114                except (IndexError, KeyError):
115                    return False
116
117        return True
118