]> git.ipfire.org Git - thirdparty/jinja.git/commitdiff
native only evals at end of render 1190/head
authorDavid Lord <davidism@gmail.com>
Mon, 13 Apr 2020 15:25:49 +0000 (08:25 -0700)
committerDavid Lord <davidism@gmail.com>
Mon, 13 Apr 2020 15:51:38 +0000 (08:51 -0700)
Co-authored-by: Martin Krizek <mkrizek@redhat.com>
CHANGES.rst
src/jinja2/nativetypes.py
tests/test_nativetypes.py

index e6608f4584e646efb560c71b72f552a1e19f78d7..495f693e479c6468f0f6533d3e1a7096d36dbe71 100644 (file)
@@ -27,6 +27,10 @@ Unreleased
     async environments. :issue:`1180`
 -   Fix whitespace being removed before tags in the middle of lines when
     ``lstrip_blocks`` is enabled. :issue:`1138`
+-   :class:`~nativetypes.NativeEnvironment` doesn't evaluate
+    intermediate strings during rendering. This prevents early
+    evaluation which could change the value of an expression.
+    :issue:`1186`
 
 
 Version 2.11.1
index 9866c962dcff21bb2dbc47c1a4252ef52f773124..a9ead4e2bbf09b881993b4c7112d83c684604c5c 100644 (file)
@@ -1,4 +1,3 @@
-import types
 from ast import literal_eval
 from itertools import chain
 from itertools import islice
@@ -11,7 +10,7 @@ from .environment import Environment
 from .environment import Template
 
 
-def native_concat(nodes, preserve_quotes=True):
+def native_concat(nodes):
     """Return a native Python type from the list of compiled nodes. If
     the result is a single node, its value is returned. Otherwise, the
     nodes are concatenated as strings. If the result can be parsed with
@@ -19,9 +18,6 @@ def native_concat(nodes, preserve_quotes=True):
     the string is returned.
 
     :param nodes: Iterable of nodes to concatenate.
-    :param preserve_quotes: Whether to re-wrap literal strings with
-        quotes, to preserve quotes around expressions for later parsing.
-        Should be ``False`` in :meth:`NativeEnvironment.render`.
     """
     head = list(islice(nodes, 2))
 
@@ -31,29 +27,17 @@ def native_concat(nodes, preserve_quotes=True):
     if len(head) == 1:
         raw = head[0]
     else:
-        if isinstance(nodes, types.GeneratorType):
-            nodes = chain(head, nodes)
-        raw = u"".join([text_type(v) for v in nodes])
+        raw = u"".join([text_type(v) for v in chain(head, nodes)])
 
     try:
-        literal = literal_eval(raw)
+        return literal_eval(raw)
     except (ValueError, SyntaxError, MemoryError):
         return raw
 
-    # If literal_eval returned a string, re-wrap with the original
-    # quote character to avoid dropping quotes between expression nodes.
-    # Without this, "'{{ a }}', '{{ b }}'" results in "a, b", but should
-    # be ('a', 'b').
-    if preserve_quotes and isinstance(literal, str):
-        return "{quote}{}{quote}".format(literal, quote=raw[0])
-
-    return literal
-
 
 class NativeCodeGenerator(CodeGenerator):
     """A code generator which renders Python types by not adding
-    ``to_string()`` around output nodes, and using :func:`native_concat`
-    to convert complex strings back to Python types if possible.
+    ``to_string()`` around output nodes.
     """
 
     @staticmethod
@@ -61,7 +45,7 @@ class NativeCodeGenerator(CodeGenerator):
         return value
 
     def _output_const_repr(self, group):
-        return repr(native_concat(group))
+        return repr(u"".join([text_type(v) for v in group]))
 
     def _output_child_to_const(self, node, frame, finalize):
         const = node.as_const(frame.eval_ctx)
@@ -100,10 +84,9 @@ class NativeTemplate(Template):
         Otherwise, the string is returned.
         """
         vars = dict(*args, **kwargs)
+
         try:
-            return native_concat(
-                self.root_render_func(self.new_context(vars)), preserve_quotes=False
-            )
+            return native_concat(self.root_render_func(self.new_context(vars)))
         except Exception:
             return self.environment.handle_exception()
 
index 77d378d27d44ef95f1705d233a9ae1739d1b8c5e..76871ab5de4bebf8d1dbd4c6bd010f1ee8ca75d9 100644 (file)
@@ -134,6 +134,15 @@ def test_concat_strings_with_quotes(env):
     assert result == "--host='localhost' --user \"Jinja\""
 
 
+def test_no_intermediate_eval(env):
+    t = env.from_string("0.000{{ a }}")
+    result = t.render(a=7)
+    assert isinstance(result, float)
+    # If intermediate eval happened, 0.000 would render 0.0, then 7
+    # would be appended, resulting in 0.07.
+    assert result < 0.007  # TODO use math.isclose in Python 3
+
+
 def test_spontaneous_env():
     t = NativeTemplate("{{ true }}")
     assert isinstance(t.environment, NativeEnvironment)