From: Ben Darnell Date: Thu, 14 Feb 2013 02:05:41 +0000 (-0500) Subject: Prevent __future__ imports in tornado from leaking into user code. X-Git-Tag: v3.0.0~128 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3b1093777326b77b7211571cffd2c2029ada47b4;p=thirdparty%2Ftornado.git Prevent __future__ imports in tornado from leaking into user code. This could happen with the command-line mode of autoreload or in templates. --- diff --git a/tornado/template.py b/tornado/template.py index e2208b6b3..4f61de3f8 100644 --- a/tornado/template.py +++ b/tornado/template.py @@ -230,10 +230,12 @@ class Template(object): try: # Under python2.5, the fake filename used here must match # the module name used in __name__ below. + # The dont_inherit flag prevents template.py's future imports + # from being applied to the generated code. self.compiled = compile( escape.to_unicode(self.code), "%s.generated.py" % self.name.replace('.', '_'), - "exec") + "exec", dont_inherit=True) except Exception: formatted_code = _format_code(self.code).rstrip() app_log.error("%s code:\n%s", self.name, formatted_code) diff --git a/tornado/test/template_test.py b/tornado/test/template_test.py index a2d629792..67cad59b1 100644 --- a/tornado/test/template_test.py +++ b/tornado/test/template_test.py @@ -1,6 +1,7 @@ from __future__ import absolute_import, division, print_function, with_statement import os +import sys import traceback from tornado.escape import utf8, native_str, to_unicode @@ -163,6 +164,15 @@ try{% set y = 1/x %} except ParseError: pass + @unittest.skipIf(sys.version_info >= division.getMandatoryRelease(), + 'no testable future imports') + def test_no_inherit_future(self): + # This file has from __future__ import division... + self.assertEqual(1 / 2, 0.5) + # ...but the template doesn't + template = Template('{{ 1 / 2 }}') + self.assertEqual(template.generate(), '0') + class StackTraceTest(unittest.TestCase): def test_error_line_number_expression(self): diff --git a/tornado/test/util_test.py b/tornado/test/util_test.py index 95b0c5813..41ca21105 100644 --- a/tornado/test/util_test.py +++ b/tornado/test/util_test.py @@ -3,9 +3,13 @@ from __future__ import absolute_import, division, print_function, with_statement import sys from tornado.escape import utf8 -from tornado.util import raise_exc_info, Configurable, u +from tornado.util import raise_exc_info, Configurable, u, exec_in from tornado.test.util import unittest +try: + from cStringIO import StringIO # py2 +except ImportError: + from io import StringIO # py3 class RaiseExcInfoTest(unittest.TestCase): def test_two_arg_exception(self): @@ -123,3 +127,17 @@ class ConfigurableTest(unittest.TestCase): class UnicodeLiteralTest(unittest.TestCase): def test_unicode_escapes(self): self.assertEqual(utf8(u('\u00e9')), b'\xc3\xa9') + + +class ExecInTest(unittest.TestCase): + # This test is python 2 only because there are no new future imports + # defined in python 3 yet. + @unittest.skipIf(sys.version_info >= print_function.getMandatoryRelease(), + 'no testable future imports') + def test_no_inherit_future(self): + # This file has from __future__ import print_function... + f = StringIO() + print('hello', file=f) + # ...but the template doesn't + exec_in('print >> f, "world"', dict(f=f)) + self.assertEqual(f.getvalue(), 'hello\nworld\n') diff --git a/tornado/util.py b/tornado/util.py index 33bc276fe..deea41e2f 100644 --- a/tornado/util.py +++ b/tornado/util.py @@ -82,28 +82,14 @@ else: basestring_type = basestring -# def raise_exc_info(exc_info): -# """Re-raise an exception (with original traceback) from an exc_info tuple. - -# The argument is a ``(type, value, traceback)`` tuple as returned by -# `sys.exc_info`. -# """ -# # 2to3 isn't smart enough to convert three-argument raise -# # statements correctly in some cases. -# if isinstance(exc_info[1], exc_info[0]): -# raise exc_info[1], None, exc_info[2] -# # After 2to3: raise exc_info[1].with_traceback(exc_info[2]) -# else: -# # I think this branch is only taken for string exceptions, -# # which were removed in Python 2.6. -# raise exc_info[0], exc_info[1], exc_info[2] -# # After 2to3: raise exc_info[0](exc_info[1]).with_traceback(exc_info[2]) if sys.version_info > (3,): exec(""" def raise_exc_info(exc_info): raise exc_info[1].with_traceback(exc_info[2]) def exec_in(code, glob, loc=None): + if isinstance(code, str): + code = compile(code, '', 'exec', dont_inherit=True) exec(code, glob, loc) """) else: @@ -112,6 +98,10 @@ def raise_exc_info(exc_info): raise exc_info[0], exc_info[1], exc_info[2] def exec_in(code, glob, loc=None): + if isinstance(code, basestring): + # exec(string) inherits the caller's future imports; compile + # the string first to prevent that. + code = compile(code, '', 'exec', dont_inherit=True) exec code in glob, loc """)