Show More
@@ -65,8 +65,8 b' In [4]: run simpleerr.py' | |||
|
65 | 65 | ZeroDivisionError Traceback (most recent call last) |
|
66 | 66 | <BLANKLINE> |
|
67 | 67 | ... in <module> |
|
68 | 29 except IndexError: | |
|
68 | 69 | 30 mode = 'div' |
|
69 | 31 | |
|
70 | 70 | ---> 32 bar(mode) |
|
71 | 71 | <BLANKLINE> |
|
72 | 72 | ... in bar(mode) |
@@ -80,8 +80,6 b' ZeroDivisionError Traceback (most recent call last)' | |||
|
80 | 80 | 6 x = 1 |
|
81 | 81 | 7 y = 0 |
|
82 | 82 | ----> 8 x/y |
|
83 | 9 | |
|
84 | 10 def sysexit(stat, mode): | |
|
85 | 83 | <BLANKLINE> |
|
86 | 84 | ZeroDivisionError: ... |
|
87 | 85 | """ |
@@ -97,17 +95,15 b' In [6]: run simpleerr.py' | |||
|
97 | 95 | ZeroDivisionError Traceback (most recent call last) |
|
98 | 96 | <BLANKLINE> |
|
99 | 97 | ... in <module> |
|
98 | 29 except IndexError: | |
|
100 | 99 | 30 mode = 'div' |
|
101 | 31 | |
|
102 | 100 | ---> 32 bar(mode) |
|
103 | global bar = <function bar at ...> | |
|
104 | global mode = 'div' | |
|
101 | mode = 'div' | |
|
105 | 102 | <BLANKLINE> |
|
106 | 103 | ... in bar(mode='div') |
|
107 | 104 | 14 "bar" |
|
108 | 105 | 15 if mode=='div': |
|
109 | 106 | ---> 16 div0() |
|
110 | global div0 = <function div0 at ...> | |
|
111 | 107 | 17 elif mode=='exit': |
|
112 | 108 | 18 try: |
|
113 | 109 | <BLANKLINE> |
@@ -117,8 +113,6 b' ZeroDivisionError Traceback (most recent call last)' | |||
|
117 | 113 | ----> 8 x/y |
|
118 | 114 | x = 1 |
|
119 | 115 | y = 0 |
|
120 | 9 | |
|
121 | 10 def sysexit(stat, mode): | |
|
122 | 116 | <BLANKLINE> |
|
123 | 117 | ZeroDivisionError: ... |
|
124 | 118 | """ |
@@ -154,8 +148,8 b' In [22]: %tb' | |||
|
154 | 148 | SystemExit Traceback (most recent call last) |
|
155 | 149 | <BLANKLINE> |
|
156 | 150 | ...<module> |
|
151 | 29 except IndexError: | |
|
157 | 152 | 30 mode = 'div' |
|
158 | 31 | |
|
159 | 153 | ---> 32 bar(mode) |
|
160 | 154 | <BLANKLINE> |
|
161 | 155 | ...bar(mode) |
@@ -166,11 +160,8 b' SystemExit Traceback (most recent call last)' | |||
|
166 | 160 | 24 raise ValueError('Unknown mode') |
|
167 | 161 | <BLANKLINE> |
|
168 | 162 | ...sysexit(stat, mode) |
|
169 | 9 | |
|
170 | 163 | 10 def sysexit(stat, mode): |
|
171 | 164 | ---> 11 raise SystemExit(stat, 'Mode = %s' % mode) |
|
172 | 12 | |
|
173 | 13 def bar(mode): | |
|
174 | 165 | <BLANKLINE> |
|
175 | 166 | SystemExit: (2, 'Mode = exit') |
|
176 | 167 | |
@@ -182,31 +173,25 b' In [24]: %tb' | |||
|
182 | 173 | SystemExit Traceback (most recent call last) |
|
183 | 174 | <BLANKLINE> |
|
184 | 175 | ... in <module> |
|
176 | 29 except IndexError: | |
|
185 | 177 | 30 mode = 'div' |
|
186 | 31 | |
|
187 | 178 | ---> 32 bar(mode) |
|
188 | global bar = <function bar at ...> | |
|
189 | global mode = 'exit' | |
|
179 | mode = 'exit' | |
|
190 | 180 | <BLANKLINE> |
|
191 | 181 | ... in bar(mode='exit') |
|
192 | 182 | 20 except: |
|
193 | 183 | 21 stat = 1 |
|
194 | 184 | ---> 22 sysexit(stat, mode) |
|
195 | global sysexit = <function sysexit at ...> | |
|
196 | stat = 2 | |
|
197 | 185 | mode = 'exit' |
|
186 | stat = 2 | |
|
198 | 187 | 23 else: |
|
199 | 188 | 24 raise ValueError('Unknown mode') |
|
200 | 189 | <BLANKLINE> |
|
201 | 190 | ... in sysexit(stat=2, mode='exit') |
|
202 | 9 | |
|
203 | 191 | 10 def sysexit(stat, mode): |
|
204 | 192 | ---> 11 raise SystemExit(stat, 'Mode = %s' % mode) |
|
205 | global SystemExit = undefined | |
|
206 | 193 | stat = 2 |
|
207 | 194 | mode = 'exit' |
|
208 | 12 | |
|
209 | 13 def bar(mode): | |
|
210 | 195 | <BLANKLINE> |
|
211 | 196 | SystemExit: (2, 'Mode = exit') |
|
212 | 197 | """ |
@@ -3,15 +3,14 b'' | |||
|
3 | 3 | """ |
|
4 | 4 | import io |
|
5 | 5 | import logging |
|
6 | import re | |
|
6 | 7 | import sys |
|
7 | 8 | import os.path |
|
8 | 9 | from textwrap import dedent |
|
9 | 10 | import traceback |
|
10 | 11 | import unittest |
|
11 | from unittest import mock | |
|
12 | 12 | |
|
13 | import IPython.core.ultratb as ultratb | |
|
14 | from IPython.core.ultratb import ColorTB, VerboseTB, find_recursion | |
|
13 | from IPython.core.ultratb import ColorTB, VerboseTB | |
|
15 | 14 | |
|
16 | 15 | |
|
17 | 16 | from IPython.testing import tools as tt |
@@ -38,16 +37,12 b' def recursionlimit(frames):' | |||
|
38 | 37 | |
|
39 | 38 | def inner(test_function): |
|
40 | 39 | def wrapper(*args, **kwargs): |
|
41 | _orig_rec_limit = ultratb._FRAME_RECURSION_LIMIT | |
|
42 | ultratb._FRAME_RECURSION_LIMIT = 50 | |
|
43 | ||
|
44 | 40 | rl = sys.getrecursionlimit() |
|
45 | 41 | sys.setrecursionlimit(frames) |
|
46 | 42 | try: |
|
47 | 43 | return test_function(*args, **kwargs) |
|
48 | 44 | finally: |
|
49 | 45 | sys.setrecursionlimit(rl) |
|
50 | ultratb._FRAME_RECURSION_LIMIT = _orig_rec_limit | |
|
51 | 46 | |
|
52 | 47 | return wrapper |
|
53 | 48 | |
@@ -350,45 +345,24 b' def r3o2():' | |||
|
350 | 345 | ip.run_cell(self.DEFINITIONS) |
|
351 | 346 | |
|
352 | 347 | def test_no_recursion(self): |
|
353 |
with tt.AssertNotPrints("frames |
|
|
348 | with tt.AssertNotPrints("skipping similar frames"): | |
|
354 | 349 | ip.run_cell("non_recurs()") |
|
355 | 350 | |
|
356 | 351 | @recursionlimit(150) |
|
357 | 352 | def test_recursion_one_frame(self): |
|
358 |
with tt.AssertPrints( |
|
|
353 | with tt.AssertPrints(re.compile( | |
|
354 | r"\[\.\.\. skipping similar frames: r1 at line 5 \(\d{2} times\)\]") | |
|
355 | ): | |
|
359 | 356 | ip.run_cell("r1()") |
|
360 | 357 | |
|
361 | 358 | @recursionlimit(150) |
|
362 | 359 | def test_recursion_three_frames(self): |
|
363 |
with tt.AssertPrints(" |
|
|
364 | ip.run_cell("r3o2()") | |
|
365 | ||
|
366 | @recursionlimit(150) | |
|
367 | def test_find_recursion(self): | |
|
368 | captured = [] | |
|
369 | def capture_exc(*args, **kwargs): | |
|
370 | captured.append(sys.exc_info()) | |
|
371 | with mock.patch.object(ip, 'showtraceback', capture_exc): | |
|
360 | with tt.AssertPrints("[... skipping similar frames: "), \ | |
|
361 | tt.AssertPrints(re.compile(r"r3a at line 8 \(\d{2} times\)"), suppress=False), \ | |
|
362 | tt.AssertPrints(re.compile(r"r3b at line 11 \(\d{2} times\)"), suppress=False), \ | |
|
363 | tt.AssertPrints(re.compile(r"r3c at line 14 \(\d{2} times\)"), suppress=False): | |
|
372 | 364 | ip.run_cell("r3o2()") |
|
373 | 365 | |
|
374 | self.assertEqual(len(captured), 1) | |
|
375 | etype, evalue, tb = captured[0] | |
|
376 | self.assertIn("recursion", str(evalue)) | |
|
377 | ||
|
378 | records = ip.InteractiveTB.get_records(tb, 3, ip.InteractiveTB.tb_offset) | |
|
379 | for r in records[:10]: | |
|
380 | print(r[1:4]) | |
|
381 | ||
|
382 | # The outermost frames should be: | |
|
383 | # 0: the 'cell' that was running when the exception came up | |
|
384 | # 1: r3o2() | |
|
385 | # 2: r3o1() | |
|
386 | # 3: r3a() | |
|
387 | # Then repeating r3b, r3c, r3a | |
|
388 | last_unique, repeat_length = find_recursion(etype, evalue, records) | |
|
389 | self.assertEqual(last_unique, 2) | |
|
390 | self.assertEqual(repeat_length, 3) | |
|
391 | ||
|
392 | 366 | |
|
393 | 367 | #---------------------------------------------------------------------------- |
|
394 | 368 | |
@@ -432,35 +406,3 b' def test_handlers():' | |||
|
432 | 406 | except: |
|
433 | 407 | handler(*sys.exc_info()) |
|
434 | 408 | buff.write('') |
|
435 | ||
|
436 | from IPython.testing.decorators import skipif | |
|
437 | ||
|
438 | class TokenizeFailureTest(unittest.TestCase): | |
|
439 | """Tests related to https://github.com/ipython/ipython/issues/6864.""" | |
|
440 | ||
|
441 | # that appear to test that we are handling an exception that can be thrown | |
|
442 | # by the tokenizer due to a bug that seem to have been fixed in 3.8, though | |
|
443 | # I'm unsure if other sequences can make it raise this error. Let's just | |
|
444 | # skip in 3.8 for now | |
|
445 | @skipif(sys.version_info > (3,8)) | |
|
446 | def testLogging(self): | |
|
447 | message = "An unexpected error occurred while tokenizing input" | |
|
448 | cell = 'raise ValueError("""a\nb""")' | |
|
449 | ||
|
450 | stream = io.StringIO() | |
|
451 | handler = logging.StreamHandler(stream) | |
|
452 | logger = logging.getLogger() | |
|
453 | loglevel = logger.level | |
|
454 | logger.addHandler(handler) | |
|
455 | self.addCleanup(lambda: logger.removeHandler(handler)) | |
|
456 | self.addCleanup(lambda: logger.setLevel(loglevel)) | |
|
457 | ||
|
458 | logger.setLevel(logging.INFO) | |
|
459 | with tt.AssertNotPrints(message): | |
|
460 | ip.run_cell(cell) | |
|
461 | self.assertNotIn(message, stream.getvalue()) | |
|
462 | ||
|
463 | logger.setLevel(logging.DEBUG) | |
|
464 | with tt.AssertNotPrints(message): | |
|
465 | ip.run_cell(cell) | |
|
466 | self.assertIn(message, stream.getvalue()) |
This diff has been collapsed as it changes many lines, (523 lines changed) Show them Hide them | |||
@@ -89,23 +89,14 b' Inheritance diagram:' | |||
|
89 | 89 | #***************************************************************************** |
|
90 | 90 | |
|
91 | 91 | |
|
92 | import dis | |
|
93 | 92 | import inspect |
|
94 | import keyword | |
|
95 | 93 | import linecache |
|
96 | import os | |
|
97 | 94 | import pydoc |
|
98 | import re | |
|
99 | 95 | import sys |
|
100 | 96 | import time |
|
101 | import tokenize | |
|
102 | 97 | import traceback |
|
103 | 98 | |
|
104 | from tokenize import generate_tokens | |
|
105 | ||
|
106 | # For purposes of monkeypatching inspect to fix a bug in it. | |
|
107 | from inspect import getsourcefile, getfile, getmodule, \ | |
|
108 | ismodule, isclass, ismethod, isfunction, istraceback, isframe, iscode | |
|
99 | import stack_data | |
|
109 | 100 | |
|
110 | 101 | # IPython's own modules |
|
111 | 102 | from IPython import get_ipython |
@@ -115,13 +106,8 b' from IPython.core.excolors import exception_colors' | |||
|
115 | 106 | from IPython.utils import PyColorize |
|
116 | 107 | from IPython.utils import path as util_path |
|
117 | 108 | from IPython.utils import py3compat |
|
118 | from IPython.utils.data import uniq_stable | |
|
119 | 109 | from IPython.utils.terminal import get_terminal_size |
|
120 | 110 | |
|
121 | from logging import info, error, debug | |
|
122 | ||
|
123 | from importlib.util import source_from_cache | |
|
124 | ||
|
125 | 111 | import IPython.utils.colorable as colorable |
|
126 | 112 | |
|
127 | 113 | # Globals |
@@ -134,264 +120,26 b' INDENT_SIZE = 8' | |||
|
134 | 120 | # to users of ultratb who are NOT running inside ipython. |
|
135 | 121 | DEFAULT_SCHEME = 'NoColor' |
|
136 | 122 | |
|
137 | ||
|
138 | # Number of frame above which we are likely to have a recursion and will | |
|
139 | # **attempt** to detect it. Made modifiable mostly to speedup test suite | |
|
140 | # as detecting recursion is one of our slowest test | |
|
141 | _FRAME_RECURSION_LIMIT = 500 | |
|
142 | ||
|
143 | 123 | # --------------------------------------------------------------------------- |
|
144 | 124 | # Code begins |
|
145 | 125 | |
|
146 | # Utility functions | |
|
147 | def inspect_error(): | |
|
148 | """Print a message about internal inspect errors. | |
|
149 | ||
|
150 | These are unfortunately quite common.""" | |
|
151 | ||
|
152 | error('Internal Python error in the inspect module.\n' | |
|
153 | 'Below is the traceback from this internal error.\n') | |
|
154 | ||
|
155 | ||
|
156 | # This function is a monkeypatch we apply to the Python inspect module. We have | |
|
157 | # now found when it's needed (see discussion on issue gh-1456), and we have a | |
|
158 | # test case (IPython.core.tests.test_ultratb.ChangedPyFileTest) that fails if | |
|
159 | # the monkeypatch is not applied. TK, Aug 2012. | |
|
160 | def findsource(object): | |
|
161 | """Return the entire source file and starting line number for an object. | |
|
162 | ||
|
163 | The argument may be a module, class, method, function, traceback, frame, | |
|
164 | or code object. The source code is returned as a list of all the lines | |
|
165 | in the file and the line number indexes a line in that list. An IOError | |
|
166 | is raised if the source code cannot be retrieved. | |
|
167 | ||
|
168 | FIXED version with which we monkeypatch the stdlib to work around a bug.""" | |
|
169 | ||
|
170 | file = getsourcefile(object) or getfile(object) | |
|
171 | # If the object is a frame, then trying to get the globals dict from its | |
|
172 | # module won't work. Instead, the frame object itself has the globals | |
|
173 | # dictionary. | |
|
174 | globals_dict = None | |
|
175 | if inspect.isframe(object): | |
|
176 | # XXX: can this ever be false? | |
|
177 | globals_dict = object.f_globals | |
|
178 | else: | |
|
179 | module = getmodule(object, file) | |
|
180 | if module: | |
|
181 | globals_dict = module.__dict__ | |
|
182 | lines = linecache.getlines(file, globals_dict) | |
|
183 | if not lines: | |
|
184 | raise IOError('could not get source code') | |
|
185 | ||
|
186 | if ismodule(object): | |
|
187 | return lines, 0 | |
|
188 | ||
|
189 | if isclass(object): | |
|
190 | name = object.__name__ | |
|
191 | pat = re.compile(r'^(\s*)class\s*' + name + r'\b') | |
|
192 | # make some effort to find the best matching class definition: | |
|
193 | # use the one with the least indentation, which is the one | |
|
194 | # that's most probably not inside a function definition. | |
|
195 | candidates = [] | |
|
196 | for i, line in enumerate(lines): | |
|
197 | match = pat.match(line) | |
|
198 | if match: | |
|
199 | # if it's at toplevel, it's already the best one | |
|
200 | if line[0] == 'c': | |
|
201 | return lines, i | |
|
202 | # else add whitespace to candidate list | |
|
203 | candidates.append((match.group(1), i)) | |
|
204 | if candidates: | |
|
205 | # this will sort by whitespace, and by line number, | |
|
206 | # less whitespace first | |
|
207 | candidates.sort() | |
|
208 | return lines, candidates[0][1] | |
|
209 | else: | |
|
210 | raise IOError('could not find class definition') | |
|
211 | ||
|
212 | if ismethod(object): | |
|
213 | object = object.__func__ | |
|
214 | if isfunction(object): | |
|
215 | object = object.__code__ | |
|
216 | if istraceback(object): | |
|
217 | object = object.tb_frame | |
|
218 | if isframe(object): | |
|
219 | object = object.f_code | |
|
220 | if iscode(object): | |
|
221 | if not hasattr(object, 'co_firstlineno'): | |
|
222 | raise IOError('could not find function definition') | |
|
223 | pat = re.compile(r'^(\s*def\s)|(.*(?<!\w)lambda(:|\s))|^(\s*@)') | |
|
224 | pmatch = pat.match | |
|
225 | # fperez - fix: sometimes, co_firstlineno can give a number larger than | |
|
226 | # the length of lines, which causes an error. Safeguard against that. | |
|
227 | lnum = min(object.co_firstlineno, len(lines)) - 1 | |
|
228 | while lnum > 0: | |
|
229 | if pmatch(lines[lnum]): | |
|
230 | break | |
|
231 | lnum -= 1 | |
|
232 | ||
|
233 | return lines, lnum | |
|
234 | raise IOError('could not find code object') | |
|
235 | ||
|
236 | ||
|
237 | # This is a patched version of inspect.getargs that applies the (unmerged) | |
|
238 | # patch for http://bugs.python.org/issue14611 by Stefano Taschini. This fixes | |
|
239 | # https://github.com/ipython/ipython/issues/8205 and | |
|
240 | # https://github.com/ipython/ipython/issues/8293 | |
|
241 | def getargs(co): | |
|
242 | """Get information about the arguments accepted by a code object. | |
|
243 | ||
|
244 | Three things are returned: (args, varargs, varkw), where 'args' is | |
|
245 | a list of argument names (possibly containing nested lists), and | |
|
246 | 'varargs' and 'varkw' are the names of the * and ** arguments or None.""" | |
|
247 | if not iscode(co): | |
|
248 | raise TypeError('{!r} is not a code object'.format(co)) | |
|
249 | ||
|
250 | nargs = co.co_argcount | |
|
251 | names = co.co_varnames | |
|
252 | args = list(names[:nargs]) | |
|
253 | step = 0 | |
|
254 | ||
|
255 | # The following acrobatics are for anonymous (tuple) arguments. | |
|
256 | for i in range(nargs): | |
|
257 | if args[i][:1] in ('', '.'): | |
|
258 | stack, remain, count = [], [], [] | |
|
259 | while step < len(co.co_code): | |
|
260 | op = ord(co.co_code[step]) | |
|
261 | step = step + 1 | |
|
262 | if op >= dis.HAVE_ARGUMENT: | |
|
263 | opname = dis.opname[op] | |
|
264 | value = ord(co.co_code[step]) + ord(co.co_code[step+1])*256 | |
|
265 | step = step + 2 | |
|
266 | if opname in ('UNPACK_TUPLE', 'UNPACK_SEQUENCE'): | |
|
267 | remain.append(value) | |
|
268 | count.append(value) | |
|
269 | elif opname in ('STORE_FAST', 'STORE_DEREF'): | |
|
270 | if op in dis.haslocal: | |
|
271 | stack.append(co.co_varnames[value]) | |
|
272 | elif op in dis.hasfree: | |
|
273 | stack.append((co.co_cellvars + co.co_freevars)[value]) | |
|
274 | # Special case for sublists of length 1: def foo((bar)) | |
|
275 | # doesn't generate the UNPACK_TUPLE bytecode, so if | |
|
276 | # `remain` is empty here, we have such a sublist. | |
|
277 | if not remain: | |
|
278 | stack[0] = [stack[0]] | |
|
279 | break | |
|
280 | else: | |
|
281 | remain[-1] = remain[-1] - 1 | |
|
282 | while remain[-1] == 0: | |
|
283 | remain.pop() | |
|
284 | size = count.pop() | |
|
285 | stack[-size:] = [stack[-size:]] | |
|
286 | if not remain: | |
|
287 | break | |
|
288 | remain[-1] = remain[-1] - 1 | |
|
289 | if not remain: | |
|
290 | break | |
|
291 | args[i] = stack[0] | |
|
292 | ||
|
293 | varargs = None | |
|
294 | if co.co_flags & inspect.CO_VARARGS: | |
|
295 | varargs = co.co_varnames[nargs] | |
|
296 | nargs = nargs + 1 | |
|
297 | varkw = None | |
|
298 | if co.co_flags & inspect.CO_VARKEYWORDS: | |
|
299 | varkw = co.co_varnames[nargs] | |
|
300 | return inspect.Arguments(args, varargs, varkw) | |
|
301 | ||
|
302 | ||
|
303 | # Monkeypatch inspect to apply our bugfix. | |
|
304 | def with_patch_inspect(f): | |
|
305 | """ | |
|
306 | Deprecated since IPython 6.0 | |
|
307 | decorator for monkeypatching inspect.findsource | |
|
308 | """ | |
|
309 | ||
|
310 | def wrapped(*args, **kwargs): | |
|
311 | save_findsource = inspect.findsource | |
|
312 | save_getargs = inspect.getargs | |
|
313 | inspect.findsource = findsource | |
|
314 | inspect.getargs = getargs | |
|
315 | try: | |
|
316 | return f(*args, **kwargs) | |
|
317 | finally: | |
|
318 | inspect.findsource = save_findsource | |
|
319 | inspect.getargs = save_getargs | |
|
320 | ||
|
321 | return wrapped | |
|
322 | ||
|
323 | ||
|
324 | def fix_frame_records_filenames(records): | |
|
325 | """Try to fix the filenames in each record from inspect.getinnerframes(). | |
|
326 | ||
|
327 | Particularly, modules loaded from within zip files have useless filenames | |
|
328 | attached to their code object, and inspect.getinnerframes() just uses it. | |
|
329 | """ | |
|
330 | fixed_records = [] | |
|
331 | for frame, filename, line_no, func_name, lines, index in records: | |
|
332 | # Look inside the frame's globals dictionary for __file__, | |
|
333 | # which should be better. However, keep Cython filenames since | |
|
334 | # we prefer the source filenames over the compiled .so file. | |
|
335 | if not filename.endswith(('.pyx', '.pxd', '.pxi')): | |
|
336 | better_fn = frame.f_globals.get('__file__', None) | |
|
337 | if isinstance(better_fn, str): | |
|
338 | # Check the type just in case someone did something weird with | |
|
339 | # __file__. It might also be None if the error occurred during | |
|
340 | # import. | |
|
341 | filename = better_fn | |
|
342 | fixed_records.append((frame, filename, line_no, func_name, lines, index)) | |
|
343 | return fixed_records | |
|
344 | ||
|
345 | ||
|
346 | @with_patch_inspect | |
|
347 | def _fixed_getinnerframes(etb, context=1, tb_offset=0): | |
|
348 | LNUM_POS, LINES_POS, INDEX_POS = 2, 4, 5 | |
|
349 | ||
|
350 | records = fix_frame_records_filenames(inspect.getinnerframes(etb, context)) | |
|
351 | # If the error is at the console, don't build any context, since it would | |
|
352 | # otherwise produce 5 blank lines printed out (there is no file at the | |
|
353 | # console) | |
|
354 | rec_check = records[tb_offset:] | |
|
355 | try: | |
|
356 | rname = rec_check[0][1] | |
|
357 | if rname == '<ipython console>' or rname.endswith('<string>'): | |
|
358 | return rec_check | |
|
359 | except IndexError: | |
|
360 | pass | |
|
361 | ||
|
362 | aux = traceback.extract_tb(etb) | |
|
363 | assert len(records) == len(aux) | |
|
364 | for i, (file, lnum, _, _) in enumerate(aux): | |
|
365 | maybeStart = lnum - 1 - context // 2 | |
|
366 | start = max(maybeStart, 0) | |
|
367 | end = start + context | |
|
368 | lines = linecache.getlines(file)[start:end] | |
|
369 | buf = list(records[i]) | |
|
370 | buf[LNUM_POS] = lnum | |
|
371 | buf[INDEX_POS] = lnum - 1 - start | |
|
372 | buf[LINES_POS] = lines | |
|
373 | records[i] = tuple(buf) | |
|
374 | return records[tb_offset:] | |
|
375 | ||
|
376 | 126 | # Helper function -- largely belongs to VerboseTB, but we need the same |
|
377 | 127 | # functionality to produce a pseudo verbose TB for SyntaxErrors, so that they |
|
378 | 128 | # can be recognized properly by ipython.el's py-traceback-line-re |
|
379 | 129 | # (SyntaxErrors have to be treated specially because they have no traceback) |
|
380 | 130 | |
|
381 | 131 | |
|
382 |
def _format_traceback_lines( |
|
|
132 | def _format_traceback_lines(lines, Colors, lvals, _line_format): | |
|
383 | 133 | """ |
|
384 | 134 | Format tracebacks lines with pointing arrow, leading numbers... |
|
385 | 135 | |
|
386 | 136 | Parameters |
|
387 | 137 | ========== |
|
388 | 138 | |
|
389 | lnum: int | |
|
390 | index: int | |
|
391 | lines: list[string] | |
|
139 | lines: list[Line] | |
|
392 | 140 | Colors: |
|
393 | 141 | ColorScheme used. |
|
394 |
lvals: |
|
|
142 | lvals: str | |
|
395 | 143 | Values of local variables, already colored, to inject just after the error line. |
|
396 | 144 | _line_format: f (str) -> (str, bool) |
|
397 | 145 | return (colorized version of str, failure to do so) |
@@ -399,80 +147,34 b' def _format_traceback_lines(lnum, index, lines, Colors, lvals, _line_format):' | |||
|
399 | 147 | numbers_width = INDENT_SIZE - 1 |
|
400 | 148 | res = [] |
|
401 | 149 | |
|
402 | for i,line in enumerate(lines, lnum-index): | |
|
403 | line = py3compat.cast_unicode(line) | |
|
150 | for stack_line in lines: | |
|
151 | if stack_line is stack_data.LINE_GAP: | |
|
152 | res.append('%s (...)%s\n' % (Colors.linenoEm, Colors.Normal)) | |
|
153 | continue | |
|
154 | ||
|
155 | line = stack_line.text.rstrip('\n') + '\n' | |
|
404 | 156 | |
|
405 | 157 | new_line, err = _line_format(line, 'str') |
|
406 | 158 | if not err: |
|
407 | 159 | line = new_line |
|
408 | 160 | |
|
409 | if i == lnum: | |
|
161 | lineno = stack_line.lineno | |
|
162 | if stack_line.is_current: | |
|
410 | 163 | # This is the line with the error |
|
411 | pad = numbers_width - len(str(i)) | |
|
412 |
num = '%s%s' % (debugger.make_arrow(pad), str(l |
|
|
164 | pad = numbers_width - len(str(lineno)) | |
|
165 | num = '%s%s' % (debugger.make_arrow(pad), str(lineno)) | |
|
413 | 166 | line = '%s%s%s %s%s' % (Colors.linenoEm, num, |
|
414 | 167 | Colors.line, line, Colors.Normal) |
|
415 | 168 | else: |
|
416 | num = '%*s' % (numbers_width, i) | |
|
169 | num = '%*s' % (numbers_width, lineno) | |
|
417 | 170 | line = '%s%s%s %s' % (Colors.lineno, num, |
|
418 | 171 | Colors.Normal, line) |
|
419 | 172 | |
|
420 | 173 | res.append(line) |
|
421 |
if lvals and |
|
|
174 | if lvals and stack_line.is_current: | |
|
422 | 175 | res.append(lvals + '\n') |
|
423 | 176 | return res |
|
424 | 177 | |
|
425 | def is_recursion_error(etype, value, records): | |
|
426 | try: | |
|
427 | # RecursionError is new in Python 3.5 | |
|
428 | recursion_error_type = RecursionError | |
|
429 | except NameError: | |
|
430 | recursion_error_type = RuntimeError | |
|
431 | ||
|
432 | # The default recursion limit is 1000, but some of that will be taken up | |
|
433 | # by stack frames in IPython itself. >500 frames probably indicates | |
|
434 | # a recursion error. | |
|
435 | return (etype is recursion_error_type) \ | |
|
436 | and "recursion" in str(value).lower() \ | |
|
437 | and len(records) > _FRAME_RECURSION_LIMIT | |
|
438 | ||
|
439 | def find_recursion(etype, value, records): | |
|
440 | """Identify the repeating stack frames from a RecursionError traceback | |
|
441 | ||
|
442 | 'records' is a list as returned by VerboseTB.get_records() | |
|
443 | ||
|
444 | Returns (last_unique, repeat_length) | |
|
445 | """ | |
|
446 | # This involves a bit of guesswork - we want to show enough of the traceback | |
|
447 | # to indicate where the recursion is occurring. We guess that the innermost | |
|
448 | # quarter of the traceback (250 frames by default) is repeats, and find the | |
|
449 | # first frame (from in to out) that looks different. | |
|
450 | if not is_recursion_error(etype, value, records): | |
|
451 | return len(records), 0 | |
|
452 | ||
|
453 | # Select filename, lineno, func_name to track frames with | |
|
454 | records = [r[1:4] for r in records] | |
|
455 | inner_frames = records[-(len(records)//4):] | |
|
456 | frames_repeated = set(inner_frames) | |
|
457 | ||
|
458 | last_seen_at = {} | |
|
459 | longest_repeat = 0 | |
|
460 | i = len(records) | |
|
461 | for frame in reversed(records): | |
|
462 | i -= 1 | |
|
463 | if frame not in frames_repeated: | |
|
464 | last_unique = i | |
|
465 | break | |
|
466 | ||
|
467 | if frame in last_seen_at: | |
|
468 | distance = last_seen_at[frame] - i | |
|
469 | longest_repeat = max(longest_repeat, distance) | |
|
470 | ||
|
471 | last_seen_at[frame] = i | |
|
472 | else: | |
|
473 | last_unique = 0 # The whole traceback was recursion | |
|
474 | ||
|
475 | return last_unique, longest_repeat | |
|
476 | 178 | |
|
477 | 179 | #--------------------------------------------------------------------------- |
|
478 | 180 | # Module classes |
@@ -880,63 +582,33 b' class VerboseTB(TBTools):' | |||
|
880 | 582 | |
|
881 | 583 | self.debugger_cls = debugger_cls or debugger.Pdb |
|
882 | 584 | |
|
883 |
def format_record |
|
|
884 | """Format the stack frames of the traceback""" | |
|
885 | frames = [] | |
|
886 | for r in records[:last_unique+recursion_repeat+1]: | |
|
887 | #print '*** record:',file,lnum,func,lines,index # dbg | |
|
888 | frames.append(self.format_record(*r)) | |
|
889 | ||
|
890 | if recursion_repeat: | |
|
891 | frames.append('... last %d frames repeated, from the frame below ...\n' % recursion_repeat) | |
|
892 | frames.append(self.format_record(*records[last_unique+recursion_repeat+1])) | |
|
893 | ||
|
894 | return frames | |
|
895 | ||
|
896 | def format_record(self, frame, file, lnum, func, lines, index): | |
|
585 | def format_record(self, frame_info): | |
|
897 | 586 | """Format a single stack frame""" |
|
898 | 587 | Colors = self.Colors # just a shorthand + quicker name lookup |
|
899 | 588 | ColorsNormal = Colors.Normal # used a lot |
|
589 | ||
|
590 | if isinstance(frame_info, stack_data.RepeatedFrames): | |
|
591 | return ' %s[... skipping similar frames: %s]%s\n' % ( | |
|
592 | Colors.excName, frame_info.description, ColorsNormal) | |
|
593 | ||
|
900 | 594 | col_scheme = self.color_scheme_table.active_scheme_name |
|
901 | 595 | indent = ' ' * INDENT_SIZE |
|
902 | 596 | em_normal = '%s\n%s%s' % (Colors.valEm, indent, ColorsNormal) |
|
903 | undefined = '%sundefined%s' % (Colors.em, ColorsNormal) | |
|
904 | 597 | tpl_link = '%s%%s%s' % (Colors.filenameEm, ColorsNormal) |
|
905 | 598 | tpl_call = 'in %s%%s%s%%s%s' % (Colors.vName, Colors.valEm, |
|
906 | 599 | ColorsNormal) |
|
907 | 600 | tpl_call_fail = 'in %s%%s%s(***failed resolving arguments***)%s' % \ |
|
908 | 601 | (Colors.vName, Colors.valEm, ColorsNormal) |
|
909 | 602 | tpl_local_var = '%s%%s%s' % (Colors.vName, ColorsNormal) |
|
910 | tpl_global_var = '%sglobal%s %s%%s%s' % (Colors.em, ColorsNormal, | |
|
911 | Colors.vName, ColorsNormal) | |
|
912 | 603 | tpl_name_val = '%%s %s= %%s%s' % (Colors.valEm, ColorsNormal) |
|
913 | 604 | |
|
914 | if not file: | |
|
915 | file = '?' | |
|
916 | elif file.startswith(str("<")) and file.endswith(str(">")): | |
|
917 | # Not a real filename, no problem... | |
|
918 | pass | |
|
919 | elif not os.path.isabs(file): | |
|
920 | # Try to make the filename absolute by trying all | |
|
921 | # sys.path entries (which is also what linecache does) | |
|
922 | for dirname in sys.path: | |
|
923 | try: | |
|
924 | fullname = os.path.join(dirname, file) | |
|
925 | if os.path.isfile(fullname): | |
|
926 | file = os.path.abspath(fullname) | |
|
927 | break | |
|
928 | except Exception: | |
|
929 | # Just in case that sys.path contains very | |
|
930 | # strange entries... | |
|
931 | pass | |
|
932 | ||
|
605 | file = frame_info.filename | |
|
933 | 606 | file = py3compat.cast_unicode(file, util_path.fs_encoding) |
|
934 | 607 | link = tpl_link % util_path.compress_user(file) |
|
935 | args, varargs, varkw, locals_ = inspect.getargvalues(frame) | |
|
608 | args, varargs, varkw, locals_ = inspect.getargvalues(frame_info.frame) | |
|
936 | 609 | |
|
937 | if func == '?': | |
|
938 | call = '' | |
|
939 | elif func == '<module>': | |
|
610 | func = frame_info.executing.code_qualname() | |
|
611 | if func == '<module>': | |
|
940 | 612 | call = tpl_call % (func, '') |
|
941 | 613 | else: |
|
942 | 614 | # Decide whether to include variable details or not |
@@ -964,111 +636,19 b' class VerboseTB(TBTools):' | |||
|
964 | 636 | # disabled. |
|
965 | 637 | call = tpl_call_fail % func |
|
966 | 638 | |
|
967 | # Don't attempt to tokenize binary files. | |
|
968 | if file.endswith(('.so', '.pyd', '.dll')): | |
|
969 | return '%s %s\n' % (link, call) | |
|
970 | ||
|
971 | elif file.endswith(('.pyc', '.pyo')): | |
|
972 | # Look up the corresponding source file. | |
|
973 | try: | |
|
974 | file = source_from_cache(file) | |
|
975 | except ValueError: | |
|
976 | # Failed to get the source file for some reason | |
|
977 | # E.g. https://github.com/ipython/ipython/issues/9486 | |
|
978 | return '%s %s\n' % (link, call) | |
|
979 | ||
|
980 | def linereader(file=file, lnum=[lnum], getline=linecache.getline): | |
|
981 | line = getline(file, lnum[0]) | |
|
982 | lnum[0] += 1 | |
|
983 | return line | |
|
984 | ||
|
985 | # Build the list of names on this line of code where the exception | |
|
986 | # occurred. | |
|
987 | try: | |
|
988 | names = [] | |
|
989 | name_cont = False | |
|
990 | ||
|
991 | for token_type, token, start, end, line in generate_tokens(linereader): | |
|
992 | # build composite names | |
|
993 | if token_type == tokenize.NAME and token not in keyword.kwlist: | |
|
994 | if name_cont: | |
|
995 | # Continuation of a dotted name | |
|
996 | try: | |
|
997 | names[-1].append(token) | |
|
998 | except IndexError: | |
|
999 | names.append([token]) | |
|
1000 | name_cont = False | |
|
1001 | else: | |
|
1002 | # Regular new names. We append everything, the caller | |
|
1003 | # will be responsible for pruning the list later. It's | |
|
1004 | # very tricky to try to prune as we go, b/c composite | |
|
1005 | # names can fool us. The pruning at the end is easy | |
|
1006 | # to do (or the caller can print a list with repeated | |
|
1007 | # names if so desired. | |
|
1008 | names.append([token]) | |
|
1009 | elif token == '.': | |
|
1010 | name_cont = True | |
|
1011 | elif token_type == tokenize.NEWLINE: | |
|
1012 | break | |
|
1013 | ||
|
1014 | except (IndexError, UnicodeDecodeError, SyntaxError): | |
|
1015 | # signals exit of tokenizer | |
|
1016 | # SyntaxError can occur if the file is not actually Python | |
|
1017 | # - see gh-6300 | |
|
1018 | pass | |
|
1019 | except tokenize.TokenError as msg: | |
|
1020 | # Tokenizing may fail for various reasons, many of which are | |
|
1021 | # harmless. (A good example is when the line in question is the | |
|
1022 | # close of a triple-quoted string, cf gh-6864). We don't want to | |
|
1023 | # show this to users, but want make it available for debugging | |
|
1024 | # purposes. | |
|
1025 | _m = ("An unexpected error occurred while tokenizing input\n" | |
|
1026 | "The following traceback may be corrupted or invalid\n" | |
|
1027 | "The error message is: %s\n" % msg) | |
|
1028 | debug(_m) | |
|
1029 | ||
|
1030 | # Join composite names (e.g. "dict.fromkeys") | |
|
1031 | names = ['.'.join(n) for n in names] | |
|
1032 | # prune names list of duplicates, but keep the right order | |
|
1033 | unique_names = uniq_stable(names) | |
|
1034 | ||
|
1035 | # Start loop over vars | |
|
1036 | 639 | lvals = '' |
|
1037 | 640 | lvals_list = [] |
|
1038 | 641 | if self.include_vars: |
|
1039 | for name_full in unique_names: | |
|
1040 | name_base = name_full.split('.', 1)[0] | |
|
1041 | if name_base in frame.f_code.co_varnames: | |
|
1042 | if name_base in locals_: | |
|
1043 | try: | |
|
1044 | value = repr(eval(name_full, locals_)) | |
|
1045 | except: | |
|
1046 | value = undefined | |
|
1047 | else: | |
|
1048 | value = undefined | |
|
1049 | name = tpl_local_var % name_full | |
|
1050 | else: | |
|
1051 | if name_base in frame.f_globals: | |
|
1052 | try: | |
|
1053 | value = repr(eval(name_full, frame.f_globals)) | |
|
1054 | except: | |
|
1055 | value = undefined | |
|
1056 | else: | |
|
1057 | value = undefined | |
|
1058 | name = tpl_global_var % name_full | |
|
1059 | lvals_list.append(tpl_name_val % (name, value)) | |
|
642 | for var in frame_info.variables_in_executing_piece: | |
|
643 | lvals_list.append(tpl_name_val % (var.name, repr(var.value))) | |
|
1060 | 644 | if lvals_list: |
|
1061 | 645 | lvals = '%s%s' % (indent, em_normal.join(lvals_list)) |
|
1062 | 646 | |
|
1063 |
|
|
|
647 | result = '%s %s\n' % (link, call) | |
|
1064 | 648 | |
|
1065 | if index is None: | |
|
1066 | return level | |
|
1067 | else: | |
|
1068 | _line_format = PyColorize.Parser(style=col_scheme, parent=self).format2 | |
|
1069 | return '%s%s' % (level, ''.join( | |
|
1070 | _format_traceback_lines(lnum, index, lines, Colors, lvals, | |
|
1071 | _line_format))) | |
|
649 | _line_format = PyColorize.Parser(style=col_scheme, parent=self).format2 | |
|
650 | result += ''.join(_format_traceback_lines(frame_info.lines, Colors, lvals, _line_format)) | |
|
651 | return result | |
|
1072 | 652 | |
|
1073 | 653 | def prepare_header(self, etype, long_version=False): |
|
1074 | 654 | colors = self.Colors # just a shorthand + quicker name lookup |
@@ -1123,46 +703,23 b' class VerboseTB(TBTools):' | |||
|
1123 | 703 | head = self.prepare_header(etype, self.long_header) |
|
1124 | 704 | records = self.get_records(etb, number_of_lines_of_context, tb_offset) |
|
1125 | 705 | |
|
1126 | if records is None: | |
|
1127 | return "" | |
|
1128 | ||
|
1129 | last_unique, recursion_repeat = find_recursion(orig_etype, evalue, records) | |
|
1130 | ||
|
1131 | frames = self.format_records(records, last_unique, recursion_repeat) | |
|
706 | frames = list(map(self.format_record, records)) | |
|
1132 | 707 | |
|
1133 | 708 | formatted_exception = self.format_exception(etype, evalue) |
|
1134 | 709 | if records: |
|
1135 |
f |
|
|
1136 | filepath = os.path.abspath(filepath) | |
|
710 | frame_info = records[-1] | |
|
1137 | 711 | ipinst = get_ipython() |
|
1138 | 712 | if ipinst is not None: |
|
1139 |
ipinst.hooks.synchronize_with_editor(f |
|
|
713 | ipinst.hooks.synchronize_with_editor(frame_info.filename, frame_info.lineno, 0) | |
|
1140 | 714 | |
|
1141 | 715 | return [[head] + frames + [''.join(formatted_exception[0])]] |
|
1142 | 716 | |
|
1143 | 717 | def get_records(self, etb, number_of_lines_of_context, tb_offset): |
|
1144 | try: | |
|
1145 | # Try the default getinnerframes and Alex's: Alex's fixes some | |
|
1146 | # problems, but it generates empty tracebacks for console errors | |
|
1147 | # (5 blanks lines) where none should be returned. | |
|
1148 | return _fixed_getinnerframes(etb, number_of_lines_of_context, tb_offset) | |
|
1149 | except UnicodeDecodeError: | |
|
1150 | # This can occur if a file's encoding magic comment is wrong. | |
|
1151 | # I can't see a way to recover without duplicating a bunch of code | |
|
1152 | # from the stdlib traceback module. --TK | |
|
1153 | error('\nUnicodeDecodeError while processing traceback.\n') | |
|
1154 | return None | |
|
1155 | except: | |
|
1156 | # FIXME: I've been getting many crash reports from python 2.3 | |
|
1157 | # users, traceable to inspect.py. If I can find a small test-case | |
|
1158 | # to reproduce this, I should either write a better workaround or | |
|
1159 | # file a bug report against inspect (if that's the real problem). | |
|
1160 | # So far, I haven't been able to find an isolated example to | |
|
1161 | # reproduce the problem. | |
|
1162 | inspect_error() | |
|
1163 | traceback.print_exc(file=self.ostream) | |
|
1164 | info('\nUnfortunately, your original traceback can not be constructed.\n') | |
|
1165 | return None | |
|
718 | context = number_of_lines_of_context - 1 | |
|
719 | after = context // 2 | |
|
720 | before = context - after | |
|
721 | options = stack_data.Options(before=before, after=after) | |
|
722 | return list(stack_data.FrameInfo.stack_data(etb, options=options))[tb_offset:] | |
|
1166 | 723 | |
|
1167 | 724 | def structured_traceback(self, etype, evalue, etb, tb_offset=None, |
|
1168 | 725 | number_of_lines_of_context=5): |
General Comments 0
You need to be logged in to leave comments.
Login now