comparison mercurial/revset.py @ 16771:2f3317d53d51 stable

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.
author Patrick Mezard <patrick@mezard.eu>
date Sat, 19 May 2012 17:18:29 +0200
parents 0a730d3c5aae
children 30e46d7138de
comparison
equal deleted inserted replaced
16768:23a125545c3d 16771:2f3317d53d51
1281 else: 1281 else:
1282 w = 1 1282 w = 1
1283 return w + wa, (op, x[1], ta) 1283 return w + wa, (op, x[1], ta)
1284 return 1, x 1284 return 1, x
1285 1285
1286 _aliasarg = ('func', ('symbol', '_aliasarg'))
1287 def _getaliasarg(tree):
1288 """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
1289 return X, None otherwise.
1290 """
1291 if (len(tree) == 3 and tree[:2] == _aliasarg
1292 and tree[2][0] == 'string'):
1293 return tree[2][1]
1294 return None
1295
1296 def _checkaliasarg(tree, known=None):
1297 """Check tree contains no _aliasarg construct or only ones which
1298 value is in known. Used to avoid alias placeholders injection.
1299 """
1300 if isinstance(tree, tuple):
1301 arg = _getaliasarg(tree)
1302 if arg is not None and (not known or arg not in known):
1303 raise error.ParseError(_("not a function: %s") % '_aliasarg')
1304 for t in tree:
1305 _checkaliasarg(t, known)
1306
1286 class revsetalias(object): 1307 class revsetalias(object):
1287 funcre = re.compile('^([^(]+)\(([^)]+)\)$') 1308 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1288 args = None 1309 args = None
1289 1310
1290 def __init__(self, name, value): 1311 def __init__(self, name, value):
1297 if m: 1318 if m:
1298 self.name = m.group(1) 1319 self.name = m.group(1)
1299 self.tree = ('func', ('symbol', m.group(1))) 1320 self.tree = ('func', ('symbol', m.group(1)))
1300 self.args = [x.strip() for x in m.group(2).split(',')] 1321 self.args = [x.strip() for x in m.group(2).split(',')]
1301 for arg in self.args: 1322 for arg in self.args:
1302 value = value.replace(arg, repr(arg)) 1323 # _aliasarg() is an unknown symbol only used separate
1324 # alias argument placeholders from regular strings.
1325 value = value.replace(arg, '_aliasarg(%r)' % (arg,))
1303 else: 1326 else:
1304 self.name = name 1327 self.name = name
1305 self.tree = ('symbol', name) 1328 self.tree = ('symbol', name)
1306 1329
1307 self.replacement, pos = parse(value) 1330 self.replacement, pos = parse(value)
1308 if pos != len(value): 1331 if pos != len(value):
1309 raise error.ParseError(_('invalid token'), pos) 1332 raise error.ParseError(_('invalid token'), pos)
1333 # Check for placeholder injection
1334 _checkaliasarg(self.replacement, self.args)
1310 1335
1311 def _getalias(aliases, tree): 1336 def _getalias(aliases, tree):
1312 """If tree looks like an unexpanded alias, return it. Return None 1337 """If tree looks like an unexpanded alias, return it. Return None
1313 otherwise. 1338 otherwise.
1314 """ 1339 """
1325 if alias and alias.args is not None and alias.tree == tree[:2]: 1350 if alias and alias.args is not None and alias.tree == tree[:2]:
1326 return alias 1351 return alias
1327 return None 1352 return None
1328 1353
1329 def _expandargs(tree, args): 1354 def _expandargs(tree, args):
1330 """Replace all occurences of ('string', name) with the 1355 """Replace _aliasarg instances with the substitution value of the
1331 substitution value of the same name in args, recursively. 1356 same name in args, recursively.
1332 """ 1357 """
1333 if not isinstance(tree, tuple): 1358 if not tree or not isinstance(tree, tuple):
1334 return tree 1359 return tree
1335 if len(tree) == 2 and tree[0] == 'string': 1360 arg = _getaliasarg(tree)
1336 return args.get(tree[1], tree) 1361 if arg is not None:
1362 return args[arg]
1337 return tuple(_expandargs(t, args) for t in tree) 1363 return tuple(_expandargs(t, args) for t in tree)
1338 1364
1339 def _expandaliases(aliases, tree, expanding): 1365 def _expandaliases(aliases, tree, expanding):
1340 """Expand aliases in tree, recursively. 1366 """Expand aliases in tree, recursively.
1341 1367
1365 result = tuple(_expandaliases(aliases, t, expanding) 1391 result = tuple(_expandaliases(aliases, t, expanding)
1366 for t in tree) 1392 for t in tree)
1367 return result 1393 return result
1368 1394
1369 def findaliases(ui, tree): 1395 def findaliases(ui, tree):
1396 _checkaliasarg(tree)
1370 aliases = {} 1397 aliases = {}
1371 for k, v in ui.configitems('revsetalias'): 1398 for k, v in ui.configitems('revsetalias'):
1372 alias = revsetalias(k, v) 1399 alias = revsetalias(k, v)
1373 aliases[alias.name] = alias 1400 aliases[alias.name] = alias
1374 return _expandaliases(aliases, tree, []) 1401 return _expandaliases(aliases, tree, [])