From 3a9079742f2d71e6968823e155f3778473113538 Mon Sep 17 00:00:00 2001 From: Larry Hastings Date: Sat, 23 Nov 2013 14:49:22 -0800 Subject: Issue #19722: Added opcode.stack_effect(), which accurately computes the stack effect of bytecode instructions. --- Doc/library/dis.rst | 7 +++ Include/compile.h | 3 ++ Lib/opcode.py | 13 ++++++ Lib/test/test__opcode.py | 22 +++++++++ Misc/NEWS | 2 + Modules/_opcode.c | 116 +++++++++++++++++++++++++++++++++++++++++++++++ Python/compile.c | 20 ++++---- setup.py | 2 + 8 files changed, 177 insertions(+), 8 deletions(-) create mode 100644 Lib/test/test__opcode.py create mode 100644 Modules/_opcode.c diff --git a/Doc/library/dis.rst b/Doc/library/dis.rst index 2365f0a..7596228 100644 --- a/Doc/library/dis.rst +++ b/Doc/library/dis.rst @@ -217,6 +217,13 @@ object isn't useful: Detect all offsets in the code object *code* which are jump targets, and return a list of these offsets. + +.. function:: stack_effect(opcode, [oparg]) + + Compute the stack effect of *opcode* with argument *oparg*. + + .. versionadded:: 3.4 + .. _bytecodes: Python Bytecode Instructions diff --git a/Include/compile.h b/Include/compile.h index 12d75d3..c6650d7 100644 --- a/Include/compile.h +++ b/Include/compile.h @@ -54,6 +54,9 @@ PyAPI_FUNC(PyFutureFeatures *) PyFuture_FromASTObject( /* _Py_Mangle is defined in compile.c */ PyAPI_FUNC(PyObject*) _Py_Mangle(PyObject *p, PyObject *name); +#define PY_INVALID_STACK_EFFECT INT_MAX +PyAPI_FUNC(int) PyCompile_OpcodeStackEffect(int opcode, int oparg); + #ifdef __cplusplus } #endif diff --git a/Lib/opcode.py b/Lib/opcode.py index 78d1229..0bd1ee6 100644 --- a/Lib/opcode.py +++ b/Lib/opcode.py @@ -8,6 +8,19 @@ __all__ = ["cmp_op", "hasconst", "hasname", "hasjrel", "hasjabs", "haslocal", "hascompare", "hasfree", "opname", "opmap", "HAVE_ARGUMENT", "EXTENDED_ARG", "hasnargs"] +# It's a chicken-and-egg I'm afraid: +# We're imported before _opcode's made. +# With exception unheeded +# (stack_effect is not needed) +# Both our chickens and eggs are allayed. +# --Larry Hastings, 2013/11/23 + +try: + from _opcode import stack_effect + __all__.append('stack_effect') +except ImportError: + pass + cmp_op = ('<', '<=', '==', '!=', '>', '>=', 'in', 'not in', 'is', 'is not', 'exception match', 'BAD') diff --git a/Lib/test/test__opcode.py b/Lib/test/test__opcode.py new file mode 100644 index 0000000..cab8769 --- /dev/null +++ b/Lib/test/test__opcode.py @@ -0,0 +1,22 @@ +import dis +import _opcode +from test.support import run_unittest +import unittest + +class OpcodeTests(unittest.TestCase): + + def test_stack_effect(self): + self.assertEqual(_opcode.stack_effect(dis.opmap['POP_TOP']), -1) + self.assertEqual(_opcode.stack_effect(dis.opmap['DUP_TOP_TWO']), 2) + self.assertEqual(_opcode.stack_effect(dis.opmap['BUILD_SLICE'], 0), -1) + self.assertEqual(_opcode.stack_effect(dis.opmap['BUILD_SLICE'], 1), -1) + self.assertEqual(_opcode.stack_effect(dis.opmap['BUILD_SLICE'], 3), -2) + self.assertRaises(ValueError, _opcode.stack_effect, 30000) + self.assertRaises(ValueError, _opcode.stack_effect, dis.opmap['BUILD_SLICE']) + self.assertRaises(ValueError, _opcode.stack_effect, dis.opmap['POP_TOP'], 0) + +def test_main(): + run_unittest(OpcodeTests) + +if __name__ == "__main__": + test_main() diff --git a/Misc/NEWS b/Misc/NEWS index b8794f4..71ef030 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -67,6 +67,8 @@ Core and Builtins Library ------- +- Issue #19722: Added opcode.stack_effect(), which + computes the stack effect of bytecode instructions. - Issue #19735: Implement private function ssl._create_stdlib_context() to create SSLContext objects in Python's stdlib module. It provides a single diff --git a/Modules/_opcode.c b/Modules/_opcode.c new file mode 100644 index 0000000..c53f0e0 --- /dev/null +++ b/Modules/_opcode.c @@ -0,0 +1,116 @@ +#include "Python.h" +#include "opcode.h" + + +/*[clinic] + +module _opcode + +_opcode.stack_effect -> int + + opcode: int + + [ + oparg: int + ] + / + +Compute the stack effect of the opcode. +[clinic]*/ + +PyDoc_STRVAR(_opcode_stack_effect__doc__, +"Compute the stack effect of the opcode.\n" +"\n" +"_opcode.stack_effect(opcode, [oparg])"); + +#define _OPCODE_STACK_EFFECT_METHODDEF \ + {"stack_effect", (PyCFunction)_opcode_stack_effect, METH_VARARGS, _opcode_stack_effect__doc__}, + +static int +_opcode_stack_effect_impl(PyObject *module, int opcode, int group_right_1, int oparg); + +static PyObject * +_opcode_stack_effect(PyObject *module, PyObject *args) +{ + PyObject *return_value = NULL; + int opcode; + int group_right_1 = 0; + int oparg = 0; + int _return_value; + + switch (PyTuple_Size(args)) { + case 1: + if (!PyArg_ParseTuple(args, "i:stack_effect", &opcode)) + return NULL; + break; + case 2: + if (!PyArg_ParseTuple(args, "ii:stack_effect", &opcode, &oparg)) + return NULL; + group_right_1 = 1; + break; + default: + PyErr_SetString(PyExc_TypeError, "_opcode.stack_effect requires 1 to 2 arguments"); + return NULL; + } + _return_value = _opcode_stack_effect_impl(module, opcode, group_right_1, oparg); + if ((_return_value == -1) && PyErr_Occurred()) + goto exit; + return_value = PyLong_FromLong((long)_return_value); + +exit: + return return_value; +} + +static int +_opcode_stack_effect_impl(PyObject *module, int opcode, int group_right_1, int oparg) +/*[clinic checksum: 2312ded40abc9bcbce718942de21f53e61a2dfd3]*/ +{ + int effect; + if (HAS_ARG(opcode)) { + if (!group_right_1) { + PyErr_SetString(PyExc_ValueError, + "stack_effect: opcode requires oparg but oparg was not specified"); + return -1; + } + } + else if (group_right_1) { + PyErr_SetString(PyExc_ValueError, + "stack_effect: opcode does not permit oparg but oparg was specified"); + return -1; + } + effect = PyCompile_OpcodeStackEffect(opcode, oparg); + if (effect == PY_INVALID_STACK_EFFECT) { + PyErr_SetString(PyExc_ValueError, + "invalid opcode or oparg"); + return -1; + } + return effect; +} + + + + +static PyMethodDef +opcode_functions[] = { + _OPCODE_STACK_EFFECT_METHODDEF + {NULL, NULL, 0, NULL} +}; + + +static struct PyModuleDef opcodemodule = { + PyModuleDef_HEAD_INIT, + "_opcode", + "Opcode support module.", + -1, + opcode_functions, + NULL, + NULL, + NULL, + NULL +}; + +PyMODINIT_FUNC +PyInit__opcode(void) +{ + return PyModule_Create(&opcodemodule); +} diff --git a/Python/compile.c b/Python/compile.c index de55687..0fc9186 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -853,8 +853,8 @@ compiler_set_lineno(struct compiler *c, int off) b->b_instr[off].i_lineno = c->u->u_lineno; } -static int -opcode_stack_effect(int opcode, int oparg) +int +PyCompile_OpcodeStackEffect(int opcode, int oparg) { switch (opcode) { case POP_TOP: @@ -1044,11 +1044,9 @@ opcode_stack_effect(int opcode, int oparg) case DELETE_DEREF: return 0; default: - fprintf(stderr, "opcode = %d\n", opcode); - Py_FatalError("opcode_stack_effect()"); - + return PY_INVALID_STACK_EFFECT; } - return 0; /* not reachable */ + return PY_INVALID_STACK_EFFECT; /* not reachable */ } /* Add an opcode with no argument. @@ -3829,7 +3827,7 @@ dfs(struct compiler *c, basicblock *b, struct assembler *a) static int stackdepth_walk(struct compiler *c, basicblock *b, int depth, int maxdepth) { - int i, target_depth; + int i, target_depth, effect; struct instr *instr; if (b->b_seen || b->b_startdepth >= depth) return maxdepth; @@ -3837,7 +3835,13 @@ stackdepth_walk(struct compiler *c, basicblock *b, int depth, int maxdepth) b->b_startdepth = depth; for (i = 0; i < b->b_iused; i++) { instr = &b->b_instr[i]; - depth += opcode_stack_effect(instr->i_opcode, instr->i_oparg); + effect = PyCompile_OpcodeStackEffect(instr->i_opcode, instr->i_oparg); + if (effect == PY_INVALID_STACK_EFFECT) { + fprintf(stderr, "opcode = %d\n", instr->i_opcode); + Py_FatalError("PyCompile_OpcodeStackEffect()"); + } + depth += effect; + if (depth > maxdepth) maxdepth = depth; assert(depth >= 0); /* invalid code or bug in stackdepth() */ diff --git a/setup.py b/setup.py index c6ae1b2..7955341 100644 --- a/setup.py +++ b/setup.py @@ -596,6 +596,8 @@ class PyBuildExt(build_ext): exts.append( Extension('_lsprof', ['_lsprof.c', 'rotatingtree.c']) ) # static Unicode character database exts.append( Extension('unicodedata', ['unicodedata.c']) ) + # _opcode module + exts.append( Extension('_opcode', ['_opcode.c']) ) # Modules with some UNIX dependencies -- on by default: # (If you have a really backward UNIX, select and socket may not be -- cgit v0.12