From: Armin Ronacher Date: Sun, 25 Oct 2009 11:46:31 +0000 (+0100) Subject: Fixed a scoping bug that was introduced in the development version and was X-Git-Tag: 2.3~49^2 X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=74230e6a386f077dd446a7dd3bf329c078e602ad;p=jinja2.git Fixed a scoping bug that was introduced in the development version and was triggered by multiple layers of local variables not tracked properly in if statements. --HG-- branch : trunk --- diff --git a/jinja2/compiler.py b/jinja2/compiler.py index 93ae14b..2dd68cd 100644 --- a/jinja2/compiler.py +++ b/jinja2/compiler.py @@ -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) diff --git a/jinja2/utils.py b/jinja2/utils.py index 7e56279..96167c0 100644 --- a/jinja2/utils.py +++ b/jinja2/utils.py @@ -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) diff --git a/tests/test_old_bugs.py b/tests/test_old_bugs.py index c4468f1..3e80c03 100644 --- a/tests/test_old_bugs.py +++ b/tests/test_old_bugs.py @@ -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'