Fixed a scoping bug that was introduced in the development version and was
authorArmin Ronacher <armin.ronacher@active-4.com>
Sun, 25 Oct 2009 11:46:31 +0000 (12:46 +0100)
committerArmin Ronacher <armin.ronacher@active-4.com>
Sun, 25 Oct 2009 11:46:31 +0000 (12:46 +0100)
triggered by multiple layers of local variables not tracked properly in if
statements.

--HG--
branch : trunk

jinja2/compiler.py
jinja2/utils.py
tests/test_old_bugs.py

index 93ae14b6aa4cdef234d5d727d49177debd47d874..2dd68cdda1e2d2f69f17c8f4d6ae3ed3b9573eae 100644 (file)
@@ -10,6 +10,7 @@
 """
 from cStringIO import StringIO
 from itertools import chain
+from copy import deepcopy
 from jinja2 import nodes
 from jinja2.visitor import NodeVisitor, NodeTransformer
 from jinja2.exceptions import TemplateAssertionError
@@ -115,6 +116,9 @@ class Identifiers(object):
             return False
         return name in self.declared
 
+    def copy(self):
+        return deepcopy(self)
+
 
 class Frame(object):
     """Holds compile time information for us."""
@@ -267,27 +271,32 @@ class FrameIdentifierVisitor(NodeVisitor):
 
     def visit_If(self, node):
         self.visit(node.test)
-
-        # remember all the names that are locally assigned in the body
-        old_locals = self.identifiers.declared_locally.copy()
-        for subnode in node.body:
-            self.visit(subnode)
-        body = self.identifiers.declared_locally - old_locals
-
-        # same for else.
-        self.identifiers.declared_locally = old_locals.copy()
-        for subnode in node.else_ or ():
-            self.visit(subnode)
-        else_ = self.identifiers.declared_locally - old_locals
+        real_identifiers = self.identifiers
+
+        old_names = real_identifiers.declared | \
+                    real_identifiers.declared_locally | \
+                    real_identifiers.declared_parameter
+
+        def inner_visit(nodes):
+            self.identifiers = real_identifiers.copy()
+            for subnode in nodes:
+                self.visit(subnode)
+            rv = self.identifiers.declared_locally - old_names
+            # we have to remember the undeclared variables of this branch
+            # because we will have to pull them.
+            real_identifiers.undeclared.update(self.identifiers.undeclared)
+            self.identifiers = real_identifiers
+            return rv
+
+        body = inner_visit(node.body)
+        else_ = inner_visit(node.else_ or ())
 
         # the differences between the two branches are also pulled as
         # undeclared variables
-        self.identifiers.undeclared.update(body.symmetric_difference(else_))
+        real_identifiers.undeclared.update(body.symmetric_difference(else_))
 
-        # declared_locally is currently the set of all variables assigned
-        # in the else part, add the new vars from body as well.  That means
-        # that undeclared variables if unbalanced are considered local.
-        self.identifiers.declared_locally.update(body)
+        # remember those that are declared.
+        real_identifiers.declared_locally.update(body | else_)
 
     def visit_Macro(self, node):
         self.identifiers.declared_locally.add(node.name)
index 7e5627954b3f850a38acce5c666f697219dd67c6..96167c0745a5d20c3b042e7c18d4709ea79a1b1c 100644 (file)
@@ -628,7 +628,11 @@ class LRUCache(object):
         self._wlock.acquire()
         try:
             if key in self._mapping:
-                self._remove(key)
+                try:
+                    self._remove(key)
+                except ValueError:
+                    # __getitem__ is not locked, it might happen
+                    pass
             elif len(self._mapping) == self.capacity:
                 del self._mapping[self._popleft()]
             self._append(key)
index c4468f12e93bbedbb38dc73e139095ee87719dd5..3e80c03bdb6db378ebc5893fa917170d427fd3f1 100644 (file)
@@ -88,3 +88,28 @@ def test_local_macros_first():
                        '{% block body %}{{ foo() }}{% endblock %}')
     }))
     assert env.get_template('child.html').render() == 'CHILD'
+
+
+def test_stacked_locals_scoping_bug():
+    env = Environment(line_statement_prefix='#')
+    t = env.from_string('''\
+# for j in [1, 2]:
+#   set x = 1
+#   for i in [1, 2]:
+#     print x
+#     if i % 2 == 0:
+#       set x = x + 1
+#     endif
+#   endfor
+# endfor
+# if a
+#   print 'A'
+# elif b
+#   print 'B'
+# elif c == d
+#   print 'C'
+# else
+#   print 'D'
+# endif
+''')
+    assert t.render(a=0, b=False, c=42, d=42.0) == '1111C'