Index: Python/peephole.c =================================================================== --- Python/peephole.c (revision 53289) +++ Python/peephole.c (working copy) @@ -261,10 +261,12 @@ 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 @@ 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 */ Index: Python/compile.c =================================================================== --- Python/compile.c (revision 53289) +++ Python/compile.c (working copy) @@ -1373,8 +1373,12 @@ 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; @@ -1405,6 +1409,12 @@ } 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; @@ -1455,6 +1465,9 @@ 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)) Index: Lib/test/test_compile.py =================================================================== --- Lib/test/test_compile.py (revision 53289) +++ Lib/test/test_compile.py (working copy) @@ -396,6 +396,19 @@ 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) Index: Lib/test/test_peepholer.py =================================================================== --- Lib/test/test_peepholer.py (revision 53289) +++ Lib/test/test_peepholer.py (working copy) @@ -195,6 +195,14 @@ # 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):