# HG changeset patch # User Yuya Nishihara # Date 1437662489 -32400 # Node ID cfe7da66f5550c539a4b8bf46772e1366687f60c # Parent 7ed3a3c0cef18f11b57aa9fcd38e30dc8ffc6c88 templater: abort if infinite recursion detected while compiling 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')] diff -r 7ed3a3c0cef1 -r cfe7da66f555 mercurial/templater.py --- a/mercurial/templater.py Wed Jul 22 23:29:41 2015 +0900 +++ b/mercurial/templater.py Thu Jul 23 23:41:29 2015 +0900 @@ -231,6 +231,9 @@ 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 @@ 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 -r 7ed3a3c0cef1 -r cfe7da66f555 tests/test-command-template.t --- a/tests/test-command-template.t Wed Jul 22 23:29:41 2015 +0900 +++ b/tests/test-command-template.t Thu Jul 23 23:41:29 2015 +0900 @@ -1053,6 +1053,12 @@ 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