Patchwork [2,of,2,STABLE] templater: abort if infinite recursion detected while compilation

login
register
mail settings
Submitter Yuya Nishihara
Date Jan. 23, 2016, 7:45 a.m.
Message ID <66fd78e17ad47d30f9c5.1453535153@mimosa>
Download mbox | patch
Permalink /patch/12877/
State Accepted
Headers show

Comments

Yuya Nishihara - Jan. 23, 2016, 7:45 a.m.
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1437662489 -32400
#      Thu Jul 23 23:41:29 2015 +0900
# Branch stable
# Node ID 66fd78e17ad47d30f9c540b9e10dd5b00cd1c563
# Parent  a4b3e8ef3603d01d683012266bf0e4124011df56
templater: abort if infinite recursion detected while compilation

In this case, a template is parsed recursively with no thunk for lazy
evaluation. This patch prevents recursion by putting a dummy of the same name
into a cache that will be referenced while parsing if there's a recursion.

  changeset = {files % changeset}\n
                       ~~~~~~~~~
                        = [(_runrecursivesymbol, 'changeset')]
Matt Mackall - Jan. 25, 2016, 11:28 p.m.
On Sat, 2016-01-23 at 16:45 +0900, Yuya Nishihara wrote:
> # HG changeset patch
> # User Yuya Nishihara <yuya@tcha.org>
> # Date 1437662489 -32400
> #      Thu Jul 23 23:41:29 2015 +0900
> # Branch stable
> # Node ID 66fd78e17ad47d30f9c540b9e10dd5b00cd1c563
> # Parent  a4b3e8ef3603d01d683012266bf0e4124011df56
> templater: abort if infinite recursion detected while compilation

These are queued for stable, thanks.

(I'll note that they also block finite recursion, so people trying to translate
their Lisp code into Mercurial templates are gonna have a bad time.)

-- 
Mathematics is the supreme nostalgia of our time.

Patch

diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -231,6 +231,9 @@  def _recursivesymbolblocker(key):
         raise error.Abort(_("recursive reference '%s' in template") % key)
     return showrecursion
 
+def _runrecursivesymbol(context, mapping, key):
+    raise error.Abort(_("recursive reference '%s' in template") % key)
+
 def runsymbol(context, mapping, key):
     v = mapping.get(key)
     if v is None:
@@ -826,7 +829,13 @@  class engine(object):
     def _load(self, t):
         '''load, parse, and cache a template'''
         if t not in self._cache:
-            self._cache[t] = compiletemplate(self._loader(t), self)
+            # put poison to cut recursion while compiling 't'
+            self._cache[t] = [(_runrecursivesymbol, t)]
+            try:
+                self._cache[t] = compiletemplate(self._loader(t), self)
+            except: # re-raises
+                del self._cache[t]
+                raise
         return self._cache[t]
 
     def process(self, t, mapping):
diff --git a/tests/test-command-template.t b/tests/test-command-template.t
--- a/tests/test-command-template.t
+++ b/tests/test-command-template.t
@@ -1053,6 +1053,12 @@  Check that recursive reference does not 
   abort: recursive reference 'foo' in template
   [255]
 
+ buildmap() -> gettemplate(), where no thunk was made:
+
+  $ hg log -T '{files % changeset}\n'
+  abort: recursive reference 'changeset' in template
+  [255]
+
  not a recursion if a keyword of the same name exists:
 
   $ cat << EOF > issue4758