diff options
Diffstat (limited to 'Lib')
| -rw-r--r-- | Lib/dis.py | 23 | ||||
| -rw-r--r-- | Lib/test/test_dis.py | 89 |
2 files changed, 99 insertions, 13 deletions
diff --git a/Lib/dis.py b/Lib/dis.py index f3c18a5fde..b990839bcb 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -31,7 +31,7 @@ def _try_compile(source, name): c = compile(source, name, 'exec') return c -def dis(x=None, *, file=None): +def dis(x=None, *, file=None, depth=None): """Disassemble classes, methods, functions, generators, or code. With no argument, disassemble the last traceback. @@ -52,16 +52,16 @@ def dis(x=None, *, file=None): if isinstance(x1, _have_code): print("Disassembly of %s:" % name, file=file) try: - dis(x1, file=file) + dis(x1, file=file, depth=depth) except TypeError as msg: print("Sorry:", msg, file=file) print(file=file) elif hasattr(x, 'co_code'): # Code object - disassemble(x, file=file) + _disassemble_recursive(x, file=file, depth=depth) elif isinstance(x, (bytes, bytearray)): # Raw bytecode _disassemble_bytes(x, file=file) elif isinstance(x, str): # Source code - _disassemble_str(x, file=file) + _disassemble_str(x, file=file, depth=depth) else: raise TypeError("don't know how to disassemble %s objects" % type(x).__name__) @@ -338,6 +338,17 @@ def disassemble(co, lasti=-1, *, file=None): _disassemble_bytes(co.co_code, lasti, co.co_varnames, co.co_names, co.co_consts, cell_names, linestarts, file=file) +def _disassemble_recursive(co, *, file=None, depth=None): + disassemble(co, file=file) + if depth is None or depth > 0: + if depth is not None: + depth = depth - 1 + for x in co.co_consts: + if hasattr(x, 'co_code'): + print(file=file) + print("Disassembly of %r:" % (x,), file=file) + _disassemble_recursive(x, file=file, depth=depth) + def _disassemble_bytes(code, lasti=-1, varnames=None, names=None, constants=None, cells=None, linestarts=None, *, file=None, line_offset=0): @@ -368,9 +379,9 @@ def _disassemble_bytes(code, lasti=-1, varnames=None, names=None, print(instr._disassemble(lineno_width, is_current_instr, offset_width), file=file) -def _disassemble_str(source, *, file=None): +def _disassemble_str(source, **kwargs): """Compile the source string, then disassemble the code object.""" - disassemble(_try_compile(source, '<dis>'), file=file) + _disassemble_recursive(_try_compile(source, '<dis>'), **kwargs) disco = disassemble # XXX For backwards compatibility diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index e614b718ee..254b317e49 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -331,16 +331,77 @@ dis_fstring = """\ def _g(x): yield x +def _h(y): + def foo(x): + '''funcdoc''' + return [x + z for z in y] + return foo + +dis_nested_0 = """\ +%3d 0 LOAD_CLOSURE 0 (y) + 2 BUILD_TUPLE 1 + 4 LOAD_CONST 1 (<code object foo at 0x..., file "%s", line %d>) + 6 LOAD_CONST 2 ('_h.<locals>.foo') + 8 MAKE_FUNCTION 8 + 10 STORE_FAST 1 (foo) + +%3d 12 LOAD_FAST 1 (foo) + 14 RETURN_VALUE +""" % (_h.__code__.co_firstlineno + 1, + __file__, + _h.__code__.co_firstlineno + 1, + _h.__code__.co_firstlineno + 4, +) + +dis_nested_1 = """%s +Disassembly of <code object foo at 0x..., file "%s", line %d>: +%3d 0 LOAD_CLOSURE 0 (x) + 2 BUILD_TUPLE 1 + 4 LOAD_CONST 1 (<code object <listcomp> at 0x..., file "%s", line %d>) + 6 LOAD_CONST 2 ('_h.<locals>.foo.<locals>.<listcomp>') + 8 MAKE_FUNCTION 8 + 10 LOAD_DEREF 1 (y) + 12 GET_ITER + 14 CALL_FUNCTION 1 + 16 RETURN_VALUE +""" % (dis_nested_0, + __file__, + _h.__code__.co_firstlineno + 1, + _h.__code__.co_firstlineno + 3, + __file__, + _h.__code__.co_firstlineno + 3, +) + +dis_nested_2 = """%s +Disassembly of <code object <listcomp> at 0x..., file "%s", line %d>: +%3d 0 BUILD_LIST 0 + 2 LOAD_FAST 0 (.0) + >> 4 FOR_ITER 12 (to 18) + 6 STORE_FAST 1 (z) + 8 LOAD_DEREF 0 (x) + 10 LOAD_FAST 1 (z) + 12 BINARY_ADD + 14 LIST_APPEND 2 + 16 JUMP_ABSOLUTE 4 + >> 18 RETURN_VALUE +""" % (dis_nested_1, + __file__, + _h.__code__.co_firstlineno + 3, + _h.__code__.co_firstlineno + 3, +) + class DisTests(unittest.TestCase): - def get_disassembly(self, func, lasti=-1, wrapper=True): + maxDiff = None + + def get_disassembly(self, func, lasti=-1, wrapper=True, **kwargs): # We want to test the default printing behaviour, not the file arg output = io.StringIO() with contextlib.redirect_stdout(output): if wrapper: - dis.dis(func) + dis.dis(func, **kwargs) else: - dis.disassemble(func, lasti) + dis.disassemble(func, lasti, **kwargs) return output.getvalue() def get_disassemble_as_string(self, func, lasti=-1): @@ -350,7 +411,7 @@ class DisTests(unittest.TestCase): return re.sub(r'\b0x[0-9A-Fa-f]+\b', '0x...', text) def do_disassembly_test(self, func, expected): - got = self.get_disassembly(func) + got = self.get_disassembly(func, depth=0) if got != expected: got = self.strip_addresses(got) self.assertEqual(got, expected) @@ -502,15 +563,29 @@ class DisTests(unittest.TestCase): def test_dis_object(self): self.assertRaises(TypeError, dis.dis, object()) + def test_disassemble_recursive(self): + def check(expected, **kwargs): + dis = self.get_disassembly(_h, **kwargs) + dis = self.strip_addresses(dis) + self.assertEqual(dis, expected) + + check(dis_nested_0, depth=0) + check(dis_nested_1, depth=1) + check(dis_nested_2, depth=2) + check(dis_nested_2, depth=3) + check(dis_nested_2, depth=None) + check(dis_nested_2) + + class DisWithFileTests(DisTests): # Run the tests again, using the file arg instead of print - def get_disassembly(self, func, lasti=-1, wrapper=True): + def get_disassembly(self, func, lasti=-1, wrapper=True, **kwargs): output = io.StringIO() if wrapper: - dis.dis(func, file=output) + dis.dis(func, file=output, **kwargs) else: - dis.disassemble(func, lasti, file=output) + dis.disassemble(func, lasti, file=output, **kwargs) return output.getvalue() |
