From 5c8b54eb04490da53d4ed0d405d7151d4962ee19 Mon Sep 17 00:00:00 2001 From: Nick Coghlan Date: Sat, 3 Jul 2010 07:36:51 +0000 Subject: Issue 6507: accept source strings directly in dis.dis(). Original patch by Daniel Urban --- Doc/library/dis.rst | 11 +++++++---- Lib/dis.py | 26 ++++++++++++++++++++++++-- Lib/test/test_dis.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ Misc/ACKS | 1 + Misc/NEWS | 2 ++ 5 files changed, 79 insertions(+), 6 deletions(-) diff --git a/Doc/library/dis.rst b/Doc/library/dis.rst index c9417ea..807ee8b 100644 --- a/Doc/library/dis.rst +++ b/Doc/library/dis.rst @@ -33,10 +33,13 @@ The :mod:`dis` module defines the following functions and constants: .. function:: dis(x=None) Disassemble the *x* object. *x* can denote either a module, a - class, a method, a function, or a code object. For a module, it disassembles - all functions. For a class, it disassembles all methods. For a single code - sequence, it prints one line per bytecode instruction. If no object is - provided, it disassembles the last traceback. + class, a method, a function, a code object, a string of source code or a + byte sequence of raw bytecode. For a module, it disassembles all + functions. For a class, it disassembles all methods. For a code object + or sequence of raw bytecode, it prints one line per bytecode instruction. + Strings are first compiled to code objects with the :func:`compile` + built-in function before being disassembled. If no object is provided, + this function disassembles the last traceback. .. function:: distb(tb=None) diff --git a/Lib/dis.py b/Lib/dis.py index 2c4e828..2f46774 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -12,6 +12,22 @@ del _opcodes_all _have_code = (types.MethodType, types.FunctionType, types.CodeType, type) +def _try_compile(source, name): + """Attempts to compile the given source, first as an expression and + then as a statement if the first approach fails. + + Utility function to accept strings in functions that otherwise + expect code objects + """ + # ncoghlan: currently only used by dis(), but plan to add an + # equivalent for show_code() as well (but one that returns a + # string rather than printing directly to the console) + try: + c = compile(source, name, 'eval') + except SyntaxError: + c = compile(source, name, 'exec') + return c + def dis(x=None): """Disassemble classes, methods, functions, or code. @@ -38,7 +54,9 @@ def dis(x=None): elif hasattr(x, 'co_code'): disassemble(x) elif isinstance(x, (bytes, bytearray)): - disassemble_string(x) + _disassemble_bytes(x) + elif isinstance(x, str): + _disassemble_str(x) else: raise TypeError("don't know how to disassemble %s objects" % type(x).__name__) @@ -157,7 +175,7 @@ def disassemble(co, lasti=-1): print('(' + free[oparg] + ')', end=' ') print() -def disassemble_string(code, lasti=-1, varnames=None, names=None, +def _disassemble_bytes(code, lasti=-1, varnames=None, names=None, constants=None): labels = findlabels(code) n = len(code) @@ -196,6 +214,10 @@ def disassemble_string(code, lasti=-1, varnames=None, names=None, print('(' + cmp_op[oparg] + ')', end=' ') print() +def _disassemble_str(source): + """Compile the source string, then disassemble the code object.""" + disassemble(_try_compile(source, '')) + disco = disassemble # XXX For backwards compatibility def findlabels(code): diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index 3eda2bd..7feee64 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -96,6 +96,46 @@ Disassembly of g: """ +expr_str = "x + 1" + +dis_expr_str = """\ + 1 0 LOAD_NAME 0 (x) + 3 LOAD_CONST 0 (1) + 6 BINARY_ADD + 7 RETURN_VALUE +""" + +simple_stmt_str = "x = x + 1" + +dis_simple_stmt_str = """\ + 1 0 LOAD_NAME 0 (x) + 3 LOAD_CONST 0 (1) + 6 BINARY_ADD + 7 STORE_NAME 0 (x) + 10 LOAD_CONST 1 (None) + 13 RETURN_VALUE +""" + +compound_stmt_str = """\ +x = 0 +while 1: + x += 1""" +# Trailing newline has been deliberately omitted + +dis_compound_stmt_str = """\ + 1 0 LOAD_CONST 0 (0) + 3 STORE_NAME 0 (x) + + 2 6 SETUP_LOOP 13 (to 22) + + 3 >> 9 LOAD_NAME 0 (x) + 12 LOAD_CONST 1 (1) + 15 INPLACE_ADD + 16 STORE_NAME 0 (x) + 19 JUMP_ABSOLUTE 9 + >> 22 LOAD_CONST 2 (None) + 25 RETURN_VALUE +""" class DisTests(unittest.TestCase): def do_disassembly_test(self, func, expected): @@ -166,6 +206,11 @@ class DisTests(unittest.TestCase): from test import dis_module self.do_disassembly_test(dis_module, dis_module_expected_results) + def test_disassemble_str(self): + self.do_disassembly_test(expr_str, dis_expr_str) + self.do_disassembly_test(simple_stmt_str, dis_simple_stmt_str) + self.do_disassembly_test(compound_stmt_str, dis_compound_stmt_str) + def test_main(): run_unittest(DisTests) diff --git a/Misc/ACKS b/Misc/ACKS index 7b1e8cb..4608843 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -797,6 +797,7 @@ Doobee R. Tzeck Eren Türkay Lionel Ulmer Roger Upole +Daniel Urban Michael Urman Hector Urtubia Andi Vajda diff --git a/Misc/NEWS b/Misc/NEWS index 403c731..ee0a5d7 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -468,6 +468,8 @@ C-API Library ------- +- Issue #6507: Accept source strings in dis.dis() + - Issue #7829: Clearly document that the dis module is exposing an implementation detail that is not stable between Python VMs or releases. -- cgit v0.12