]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Prevent __future__ imports in tornado from leaking into user code.
authorBen Darnell <ben@bendarnell.com>
Thu, 14 Feb 2013 02:05:41 +0000 (21:05 -0500)
committerBen Darnell <ben@bendarnell.com>
Thu, 14 Feb 2013 02:05:41 +0000 (21:05 -0500)
This could happen with the command-line mode of autoreload or
in templates.

tornado/template.py
tornado/test/template_test.py
tornado/test/util_test.py
tornado/util.py

index e2208b6b3d42eb686b308f8d451a564435c4d6af..4f61de3f8fe5783b5e6e7da7ca58df3d832cfea0 100644 (file)
@@ -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)
index a2d62979270244c1d96c4898761c33bcd592e200..67cad59b131f7f231b56612f28c7083b6142e21b 100644 (file)
@@ -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):
index 95b0c5813276ef199af28a4b9ee93ebea7c7fe9e..41ca2110521f7b60fc284029653cc09428fa82ec 100644 (file)
@@ -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')
index 33bc276fe4dd24c64b83c93ea3b7480da80396a1..deea41e2f45b186643f908ea0616f3da4a866028 100644 (file)
@@ -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, '<string>', '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, '<string>', 'exec', dont_inherit=True)
     exec code in glob, loc
 """)