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