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