revset: explicitely tag alias arguments for expansion

The current revset alias expansion code works like:
1- Get the replacement tree
2- Substitute the variables in the replacement tree
3- Expand the replacement tree

It makes it easy to substitute alias arguments because the placeholders
are always replaced before the updated replacement tree is expanded
again. Unfortunately, to fix other alias expansion issues, we need to
reorder the sequence and delay the argument substitution. To solve this,
a new "virtual" construct called _aliasarg() is introduced and injected
when parsing the aliases definitions. Only _aliasarg() will be
substituted in the argument expansion phase instead of all regular
matching string. We also check user inputs do not contain unexpected
_aliasarg() instances to avoid argument injections.
This commit is contained in:
Patrick Mezard 2012-05-19 17:18:29 +02:00
parent 3dc5160169
commit c7a80dee31
2 changed files with 46 additions and 6 deletions

View File

@ -1283,6 +1283,27 @@ def optimize(x, small):
return w + wa, (op, x[1], ta)
return 1, x
_aliasarg = ('func', ('symbol', '_aliasarg'))
def _getaliasarg(tree):
"""If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
return X, None otherwise.
"""
if (len(tree) == 3 and tree[:2] == _aliasarg
and tree[2][0] == 'string'):
return tree[2][1]
return None
def _checkaliasarg(tree, known=None):
"""Check tree contains no _aliasarg construct or only ones which
value is in known. Used to avoid alias placeholders injection.
"""
if isinstance(tree, tuple):
arg = _getaliasarg(tree)
if arg is not None and (not known or arg not in known):
raise error.ParseError(_("not a function: %s") % '_aliasarg')
for t in tree:
_checkaliasarg(t, known)
class revsetalias(object):
funcre = re.compile('^([^(]+)\(([^)]+)\)$')
args = None
@ -1299,7 +1320,9 @@ class revsetalias(object):
self.tree = ('func', ('symbol', m.group(1)))
self.args = [x.strip() for x in m.group(2).split(',')]
for arg in self.args:
value = value.replace(arg, repr(arg))
# _aliasarg() is an unknown symbol only used separate
# alias argument placeholders from regular strings.
value = value.replace(arg, '_aliasarg(%r)' % (arg,))
else:
self.name = name
self.tree = ('symbol', name)
@ -1307,6 +1330,8 @@ class revsetalias(object):
self.replacement, pos = parse(value)
if pos != len(value):
raise error.ParseError(_('invalid token'), pos)
# Check for placeholder injection
_checkaliasarg(self.replacement, self.args)
def _getalias(aliases, tree):
"""If tree looks like an unexpanded alias, return it. Return None
@ -1327,13 +1352,14 @@ def _getalias(aliases, tree):
return None
def _expandargs(tree, args):
"""Replace all occurences of ('string', name) with the
substitution value of the same name in args, recursively.
"""Replace _aliasarg instances with the substitution value of the
same name in args, recursively.
"""
if not isinstance(tree, tuple):
if not tree or not isinstance(tree, tuple):
return tree
if len(tree) == 2 and tree[0] == 'string':
return args.get(tree[1], tree)
arg = _getaliasarg(tree)
if arg is not None:
return args[arg]
return tuple(_expandargs(t, args) for t in tree)
def _expandaliases(aliases, tree, expanding):
@ -1367,6 +1393,7 @@ def _expandaliases(aliases, tree, expanding):
return result
def findaliases(ui, tree):
_checkaliasarg(tree)
aliases = {}
for k, v in ui.configitems('revsetalias'):
alias = revsetalias(k, v)

View File

@ -558,6 +558,19 @@ far away.
abort: unknown revision '$1'!
[255]
$ echo 'injectparamasstring2 = max(_aliasarg("$1"))' >> .hg/hgrc
$ echo 'callinjection2($1) = descendants(injectparamasstring2)' >> .hg/hgrc
$ try 'callinjection2(2:5)'
(func
('symbol', 'callinjection2')
(range
('symbol', '2')
('symbol', '5')))
hg: parse error: not a function: _aliasarg
[255]
>>> data = file('.hg/hgrc', 'rb').read()
>>> file('.hg/hgrc', 'wb').write(data.replace('_aliasarg', ''))
$ try 'd(2:5)'
(func
('symbol', 'd')