1from _pydevd_bundle.pydevd_constants import EXCEPTION_TYPE_USER_UNHANDLED, EXCEPTION_TYPE_UNHANDLED
2from _pydev_bundle import pydev_log
3
4
5class Frame(object):
6
7    def __init__(
8            self,
9            f_back,
10            f_fileno,
11            f_code,
12            f_locals,
13            f_globals=None,
14            f_trace=None):
15        self.f_back = f_back
16        self.f_lineno = f_fileno
17        self.f_code = f_code
18        self.f_locals = f_locals
19        self.f_globals = f_globals
20        self.f_trace = f_trace
21
22        if self.f_globals is None:
23            self.f_globals = {}
24
25
26class FCode(object):
27
28    def __init__(self, name, filename):
29        self.co_name = name
30        self.co_filename = filename
31        self.co_firstlineno = 1
32
33
34def add_exception_to_frame(frame, exception_info):
35    frame.f_locals['__exception__'] = exception_info
36
37
38def remove_exception_from_frame(frame):
39    frame.f_locals.pop('__exception__', None)
40
41
42FILES_WITH_IMPORT_HOOKS = ['pydev_monkey_qt.py', 'pydev_import_hook.py']
43
44
45def just_raised(trace):
46    if trace is None:
47        return False
48    return trace.tb_next is None
49
50
51def ignore_exception_trace(trace):
52    while trace is not None:
53        filename = trace.tb_frame.f_code.co_filename
54        if filename in (
55            '<frozen importlib._bootstrap>', '<frozen importlib._bootstrap_external>'):
56            # Do not stop on inner exceptions in py3 while importing
57            return True
58
59        # ImportError should appear in a user's code, not inside debugger
60        for file in FILES_WITH_IMPORT_HOOKS:
61            if filename.endswith(file):
62                return True
63
64        trace = trace.tb_next
65
66    return False
67
68
69def cached_call(obj, func, *args):
70    cached_name = '_cached_' + func.__name__
71    if not hasattr(obj, cached_name):
72        setattr(obj, cached_name, func(*args))
73
74    return getattr(obj, cached_name)
75
76
77class FramesList(object):
78
79    def __init__(self):
80        self._frames = []
81
82        # If available, the line number for the frame will be gotten from this dict,
83        # otherwise frame.f_lineno will be used (needed for unhandled exceptions as
84        # the place where we report may be different from the place where it's raised).
85        self.frame_id_to_lineno = {}
86
87        self.exc_type = None
88        self.exc_desc = None
89        self.trace_obj = None
90
91        # This may be set to set the current frame (for the case where we have
92        # an unhandled exception where we want to show the root bu we have a different
93        # executing frame).
94        self.current_frame = None
95
96        # This is to know whether an exception was extracted from a __cause__ or __context__.
97        self.exc_context_msg = ''
98
99    def append(self, frame):
100        self._frames.append(frame)
101
102    def last_frame(self):
103        return self._frames[-1]
104
105    def __len__(self):
106        return len(self._frames)
107
108    def __iter__(self):
109        return iter(self._frames)
110
111    def __repr__(self):
112        lst = ['FramesList(']
113
114        lst.append('\n    exc_type: ')
115        lst.append(str(self.exc_type))
116
117        lst.append('\n    exc_desc: ')
118        lst.append(str(self.exc_desc))
119
120        lst.append('\n    trace_obj: ')
121        lst.append(str(self.trace_obj))
122
123        lst.append('\n    current_frame: ')
124        lst.append(str(self.current_frame))
125
126        for frame in self._frames:
127            lst.append('\n    ')
128            lst.append(repr(frame))
129            lst.append(',')
130        lst.append('\n)')
131        return ''.join(lst)
132
133    __str__ = __repr__
134
135
136class _DummyFrameWrapper(object):
137
138    def __init__(self, frame, f_lineno, f_back):
139        self._base_frame = frame
140        self.f_lineno = f_lineno
141        self.f_back = f_back
142        self.f_trace = None
143        original_code = frame.f_code
144        self.f_code = FCode(original_code.co_name , original_code.co_filename)
145
146    @property
147    def f_locals(self):
148        return self._base_frame.f_locals
149
150    @property
151    def f_globals(self):
152        return self._base_frame.f_globals
153
154
155_cause_message = (
156    "\nThe above exception was the direct cause "
157    "of the following exception:\n\n")
158
159_context_message = (
160    "\nDuring handling of the above exception, "
161    "another exception occurred:\n\n")
162
163
164def create_frames_list_from_exception_cause(trace_obj, frame, exc_type, exc_desc, memo):
165    lst = []
166    msg = '<Unknown context>'
167    try:
168        exc_cause = getattr(exc_desc, '__cause__', None)
169        msg = _cause_message
170    except Exception:
171        exc_cause = None
172
173    if exc_cause is None:
174        try:
175            exc_cause = getattr(exc_desc, '__context__', None)
176            msg = _context_message
177        except Exception:
178            exc_cause = None
179
180    if exc_cause is None or id(exc_cause) in memo:
181        return None
182
183    # The traceback module does this, so, let's play safe here too...
184    memo.add(id(exc_cause))
185
186    tb = exc_cause.__traceback__
187    frames_list = FramesList()
188    frames_list.exc_type = type(exc_cause)
189    frames_list.exc_desc = exc_cause
190    frames_list.trace_obj = tb
191    frames_list.exc_context_msg = msg
192
193    while tb is not None:
194        # Note: we don't use the actual tb.tb_frame because if the cause of the exception
195        # uses the same frame object, the id(frame) would be the same and the frame_id_to_lineno
196        # would be wrong as the same frame needs to appear with 2 different lines.
197        lst.append((_DummyFrameWrapper(tb.tb_frame, tb.tb_lineno, None), tb.tb_lineno))
198        tb = tb.tb_next
199
200    for tb_frame, tb_lineno in lst:
201        frames_list.append(tb_frame)
202        frames_list.frame_id_to_lineno[id(tb_frame)] = tb_lineno
203
204    return frames_list
205
206
207def create_frames_list_from_traceback(trace_obj, frame, exc_type, exc_desc, exception_type=None):
208    '''
209    :param trace_obj:
210        This is the traceback from which the list should be created.
211
212    :param frame:
213        This is the first frame to be considered (i.e.: topmost frame). If None is passed, all
214        the frames from the traceback are shown (so, None should be passed for unhandled exceptions).
215
216    :param exception_type:
217        If this is an unhandled exception or user unhandled exception, we'll not trim the stack to create from the passed
218        frame, rather, we'll just mark the frame in the frames list.
219    '''
220    lst = []
221
222    tb = trace_obj
223    if tb is not None and tb.tb_frame is not None:
224        f = tb.tb_frame.f_back
225        while f is not None:
226            lst.insert(0, (f, f.f_lineno))
227            f = f.f_back
228
229    while tb is not None:
230        lst.append((tb.tb_frame, tb.tb_lineno))
231        tb = tb.tb_next
232
233    curr = exc_desc
234    memo = set()
235    while True:
236        initial = curr
237        try:
238            curr = getattr(initial, '__cause__', None)
239        except Exception:
240            curr = None
241
242        if curr is None:
243            try:
244                curr = getattr(initial, '__context__', None)
245            except Exception:
246                curr = None
247
248        if curr is None or id(curr) in memo:
249            break
250
251        # The traceback module does this, so, let's play safe here too...
252        memo.add(id(curr))
253
254        tb = getattr(curr, '__traceback__', None)
255
256        while tb is not None:
257            # Note: we don't use the actual tb.tb_frame because if the cause of the exception
258            # uses the same frame object, the id(frame) would be the same and the frame_id_to_lineno
259            # would be wrong as the same frame needs to appear with 2 different lines.
260            lst.append((_DummyFrameWrapper(tb.tb_frame, tb.tb_lineno, None), tb.tb_lineno))
261            tb = tb.tb_next
262
263    frames_list = None
264
265    for tb_frame, tb_lineno in reversed(lst):
266        if frames_list is None and (
267                (frame is tb_frame) or
268                (frame is None) or
269                (exception_type == EXCEPTION_TYPE_USER_UNHANDLED)
270            ):
271            frames_list = FramesList()
272
273        if frames_list is not None:
274            frames_list.append(tb_frame)
275            frames_list.frame_id_to_lineno[id(tb_frame)] = tb_lineno
276
277    if frames_list is None and frame is not None:
278        # Fallback (shouldn't happen in practice).
279        pydev_log.info('create_frames_list_from_traceback did not find topmost frame in list.')
280        frames_list = create_frames_list_from_frame(frame)
281
282    frames_list.exc_type = exc_type
283    frames_list.exc_desc = exc_desc
284    frames_list.trace_obj = trace_obj
285
286    if exception_type == EXCEPTION_TYPE_USER_UNHANDLED:
287        frames_list.current_frame = frame
288    elif exception_type == EXCEPTION_TYPE_UNHANDLED:
289        if len(frames_list) > 0:
290            frames_list.current_frame = frames_list.last_frame()
291
292    return frames_list
293
294
295def create_frames_list_from_frame(frame):
296    lst = FramesList()
297    while frame is not None:
298        lst.append(frame)
299        frame = frame.f_back
300
301    return lst
302