diff options
author | Guido van Rossum <guido@python.org> | 2007-02-26 21:23:50 (GMT) |
---|---|---|
committer | Guido van Rossum <guido@python.org> | 2007-02-26 21:23:50 (GMT) |
commit | 0240b92a6c3a17fac38d93ee80fc8e8523388786 (patch) | |
tree | 8434f85d5b00ca30cc2fad24082ba454a43a4409 | |
parent | f74225d63b84a4d3b508fd5657cfe2596633876a (diff) | |
download | cpython-0240b92a6c3a17fac38d93ee80fc8e8523388786.zip cpython-0240b92a6c3a17fac38d93ee80fc8e8523388786.tar.gz cpython-0240b92a6c3a17fac38d93ee80fc8e8523388786.tar.bz2 |
Two more patches by Tony Lownds (SF# 1607548).
(1)
Combines the code paths for MAKE_FUNCTION and MAKE_CLOSURE.
Fixes a crash where functions with closures and either annotations or
keyword-only arguments result in MAKE_CLOSURE, but only
MAKE_FUNCTION has the code to handle annotations or keyword-only
arguments.
Includes enough tests to trigger the bug.
(2)
Change peepholer to not bail in the presence of EXTENDED_ARG +
MAKE_FUNCTION.
Enforce the natural 16-bit limit of annotations in compile.c.
Also update Misc/NEWS with the "input = raw_input" change.
-rw-r--r-- | Lib/test/test_compile.py | 13 | ||||
-rw-r--r-- | Lib/test/test_grammar.py | 7 | ||||
-rw-r--r-- | Lib/test/test_peepholer.py | 8 | ||||
-rw-r--r-- | Misc/NEWS | 6 | ||||
-rw-r--r-- | Python/ceval.c | 35 | ||||
-rw-r--r-- | Python/compile.c | 21 | ||||
-rw-r--r-- | Python/peephole.c | 16 |
7 files changed, 68 insertions, 38 deletions
diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index 022f7c0..d5fda13 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -393,6 +393,19 @@ if 1: del d[..., ...] self.assertEqual((Ellipsis, Ellipsis) in d, False) + def test_annotation_limit(self): + # 16 bits are available for # of annotations, and the + # tuple of annotations names is counted, hence 65534 + # is the max. Ensure the result of too many annotations is a + # SyntaxError. + s = "def f((%s)): pass" + s %= ', '.join('a%d:%d' % (i,i) for i in xrange(65535)) + self.assertRaises(SyntaxError, compile, s, '?', 'exec') + # Test that the max # of annotations compiles. + s = "def f((%s)): pass" + s %= ', '.join('a%d:%d' % (i,i) for i in xrange(65534)) + compile(s, '?', 'exec') + def test_main(): test_support.run_unittest(TestSpecifics) diff --git a/Lib/test/test_grammar.py b/Lib/test/test_grammar.py index cb37021..1a14756 100644 --- a/Lib/test/test_grammar.py +++ b/Lib/test/test_grammar.py @@ -321,6 +321,13 @@ class GrammarTests(unittest.TestCase): self.assertEquals(f.__annotations__, {'b': 1, 'c': 2, 'e': 3, 'g': 6, 'h': 7, 'j': 9, 'k': 11, 'return': 12}) + + # test MAKE_CLOSURE with a variety of oparg's + closure = 1 + def f(): return closure + def f(x=1): return closure + def f(*, k=1): return closure + def f() -> int: return closure def testLambdef(self): ### lambdef: 'lambda' [varargslist] ':' test diff --git a/Lib/test/test_peepholer.py b/Lib/test/test_peepholer.py index 213edd2..9ed814a 100644 --- a/Lib/test/test_peepholer.py +++ b/Lib/test/test_peepholer.py @@ -195,6 +195,14 @@ class TestTranforms(unittest.TestCase): # There should be one jump for the while loop. self.assertEqual(asm.split().count('JUMP_ABSOLUTE'), 1) self.assertEqual(asm.split().count('RETURN_VALUE'), 2) + + def test_make_function_doesnt_bail(self): + def f(): + def g()->1+1: + pass + return g + asm = disassemble(f) + self.assert_('BINARY_ADD' not in asm) def test_main(verbose=None): @@ -28,6 +28,10 @@ TO DO Core and Builtins ----------------- +- input() becomes raw_input(): the name input() now implements the + functionality formerly known as raw_input(); the name raw_input() + is no longer defined. + - Objects listed in an 'except' clause must inherit from BaseException. - PEP 3106: dict.iterkeys(), .iteritems(), .itervalues() are now gone; @@ -82,7 +86,7 @@ Core and Builtins backticks (`x`), <> - Removed these Python builtins: - apply(), coerce(), input(), raw_input() + apply(), coerce() - Removed these Python methods: {}.has_key diff --git a/Python/ceval.c b/Python/ceval.c index 86dcea2..fe5de03 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -2236,6 +2236,7 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag) break; } + case MAKE_CLOSURE: case MAKE_FUNCTION: { int posdefaults = oparg & 0xff; @@ -2245,6 +2246,12 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag) v = POP(); /* code object */ x = PyFunction_New(v, f->f_globals); Py_DECREF(v); + + if (x != NULL && opcode == MAKE_CLOSURE) { + v = POP(); + err = PyFunction_SetClosure(x, v); + Py_DECREF(v); + } if (x != NULL && num_annotations > 0) { Py_ssize_t name_ix; @@ -2308,34 +2315,6 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag) break; } - case MAKE_CLOSURE: - { - v = POP(); /* code object */ - x = PyFunction_New(v, f->f_globals); - Py_DECREF(v); - if (x != NULL) { - v = POP(); - err = PyFunction_SetClosure(x, v); - Py_DECREF(v); - } - if (x != NULL && oparg > 0) { - v = PyTuple_New(oparg); - if (v == NULL) { - Py_DECREF(x); - x = NULL; - break; - } - while (--oparg >= 0) { - w = POP(); - PyTuple_SET_ITEM(v, oparg, w); - } - err = PyFunction_SetDefaults(x, v); - Py_DECREF(v); - } - PUSH(x); - break; - } - case BUILD_SLICE: if (oparg == 3) w = POP(); diff --git a/Python/compile.c b/Python/compile.c index ed0bdcf..7f0fc50 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -836,6 +836,8 @@ opcode_stack_effect(int opcode, int oparg) return -NARGS(oparg)-2; case MAKE_FUNCTION: return -NARGS(oparg) - ((oparg >> 16) & 0xffff); + case MAKE_CLOSURE: + return -1 - NARGS(oparg) - ((oparg >> 16) & 0xffff); #undef NARGS case BUILD_SLICE: if (oparg == 3) @@ -843,8 +845,6 @@ opcode_stack_effect(int opcode, int oparg) else return -1; - case MAKE_CLOSURE: - return -oparg; case LOAD_CLOSURE: return 1; case LOAD_DEREF: @@ -1367,8 +1367,12 @@ static int compiler_visit_annotations(struct compiler *c, arguments_ty args, expr_ty returns) { - /* push arg annotations and a list of the argument names. return the # - of items pushed. this is out-of-order wrt the source code. */ + /* Push arg annotations and a list of the argument names. Return the # + of items pushed. The expressions are evaluated out-of-order wrt the + source code. + + More than 2^16-1 annotations is a SyntaxError. Returns -1 on error. + */ static identifier return_str; PyObject *names; int len; @@ -1399,6 +1403,12 @@ compiler_visit_annotations(struct compiler *c, arguments_ty args, } len = PyList_GET_SIZE(names); + if (len > 65534) { + /* len must fit in 16 bits, and len is incremented below */ + PyErr_SetString(PyExc_SyntaxError, + "too many annotations"); + goto error; + } if (len) { /* convert names to a tuple and place on stack */ PyObject *elt; @@ -1449,6 +1459,9 @@ compiler_function(struct compiler *c, stmt_ty s) if (args->defaults) VISIT_SEQ(c, expr, args->defaults); num_annotations = compiler_visit_annotations(c, args, returns); + if (num_annotations < 0) + return 0; + assert((num_annotations & 0xFFFF) == num_annotations); if (!compiler_enter_scope(c, s->v.FunctionDef.name, (void *)s, s->lineno)) diff --git a/Python/peephole.c b/Python/peephole.c index 28e4c4c..f2e0c0b 100644 --- a/Python/peephole.c +++ b/Python/peephole.c @@ -261,10 +261,12 @@ markblocks(unsigned char *code, int len) The consts object should still be in list form to allow new constants to be appended. - To keep the optimizer simple, it bails out (does nothing) for code - containing extended arguments or that has a length over 32,700. That - allows us to avoid overflow and sign issues. Likewise, it bails when - the lineno table has complex encoding for gaps >= 255. + To keep the optimizer simple, it bails out (does nothing) for code that + has a length over 32,700, and does not calculate extended arguments. + That allows us to avoid overflow and sign issues. Likewise, it bails when + the lineno table has complex encoding for gaps >= 255. EXTENDED_ARG can + appear before MAKE_FUNCTION; in this case both opcodes are skipped. + EXTENDED_ARG preceding any other opcode causes the optimizer to bail. Optimizations are restricted to simple transformations occuring within a single basic block. All transformations keep the code size the same or @@ -535,7 +537,11 @@ PyCode_Optimize(PyObject *code, PyObject* consts, PyObject *names, break; case EXTENDED_ARG: - goto exitUnchanged; + if (codestr[i+3] != MAKE_FUNCTION) + goto exitUnchanged; + /* don't visit MAKE_FUNCTION as GETARG will be wrong */ + i += 3; + break; /* Replace RETURN LOAD_CONST None RETURN with just RETURN */ /* Remove unreachable JUMPs after RETURN */ |