codeparser: merge the nested python parsing classes
The split is even less necessary now that we use ast.walk rather than an actual NodeVisitor subclass. (Bitbake rev: d6c44fac184abae8395bfa7078f06675218aa534) Signed-off-by: Christopher Larson <kergoth@gmail.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
This commit is contained in:
parent
9a68fb1364
commit
ada59bde67
|
@ -151,116 +151,103 @@ def parser_cache_savemerge(d):
|
||||||
|
|
||||||
|
|
||||||
class PythonParser():
|
class PythonParser():
|
||||||
class ValueVisitor():
|
getvars = ("d.getVar", "bb.data.getVar", "data.getVar")
|
||||||
"""Visitor to traverse a python abstract syntax tree and obtain
|
expands = ("d.expand", "bb.data.expand", "data.expand")
|
||||||
the variables referenced via bitbake metadata APIs, and the external
|
execfuncs = ("bb.build.exec_func", "bb.build.exec_task")
|
||||||
functions called.
|
|
||||||
|
@classmethod
|
||||||
|
def _compare_name(cls, strparts, node):
|
||||||
|
"""Given a sequence of strings representing a python name,
|
||||||
|
where the last component is the actual Name and the prior
|
||||||
|
elements are Attribute nodes, determine if the supplied node
|
||||||
|
matches.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
getvars = ("d.getVar", "bb.data.getVar", "data.getVar")
|
if not strparts:
|
||||||
expands = ("d.expand", "bb.data.expand", "data.expand")
|
return True
|
||||||
execs = ("bb.build.exec_func", "bb.build.exec_task")
|
|
||||||
|
|
||||||
@classmethod
|
current, rest = strparts[0], strparts[1:]
|
||||||
def _compare_name(cls, strparts, node):
|
if isinstance(node, ast.Attribute):
|
||||||
"""Given a sequence of strings representing a python name,
|
if current == node.attr:
|
||||||
where the last component is the actual Name and the prior
|
return cls._compare_name(rest, node.value)
|
||||||
elements are Attribute nodes, determine if the supplied node
|
elif isinstance(node, ast.Name):
|
||||||
matches.
|
if current == node.id:
|
||||||
"""
|
|
||||||
|
|
||||||
if not strparts:
|
|
||||||
return True
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
current, rest = strparts[0], strparts[1:]
|
@classmethod
|
||||||
if isinstance(node, ast.Attribute):
|
def compare_name(cls, value, node):
|
||||||
if current == node.attr:
|
"""Convenience function for the _compare_node method, which
|
||||||
return cls._compare_name(rest, node.value)
|
can accept a string (which is split by '.' for you), or an
|
||||||
elif isinstance(node, ast.Name):
|
iterable of strings, in which case it checks to see if any of
|
||||||
if current == node.id:
|
them match, similar to isinstance.
|
||||||
return True
|
"""
|
||||||
return False
|
|
||||||
|
|
||||||
@classmethod
|
if isinstance(value, basestring):
|
||||||
def compare_name(cls, value, node):
|
return cls._compare_name(tuple(reversed(value.split("."))),
|
||||||
"""Convenience function for the _compare_node method, which
|
node)
|
||||||
can accept a string (which is split by '.' for you), or an
|
else:
|
||||||
iterable of strings, in which case it checks to see if any of
|
return any(cls.compare_name(item, node) for item in value)
|
||||||
them match, similar to isinstance.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if isinstance(value, basestring):
|
@classmethod
|
||||||
return cls._compare_name(tuple(reversed(value.split("."))),
|
def warn(cls, func, arg):
|
||||||
node)
|
"""Warn about calls of bitbake APIs which pass a non-literal
|
||||||
|
argument for the variable name, as we're not able to track such
|
||||||
|
a reference.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
funcstr = codegen.to_source(func)
|
||||||
|
argstr = codegen.to_source(arg)
|
||||||
|
except TypeError:
|
||||||
|
logger.debug(2, 'Failed to convert function and argument to source form')
|
||||||
|
else:
|
||||||
|
logger.debug(1, "Warning: in call to '%s', argument '%s' is "
|
||||||
|
"not a literal", funcstr, argstr)
|
||||||
|
|
||||||
|
def visit_Call(self, node):
|
||||||
|
if self.compare_name(self.getvars, node.func):
|
||||||
|
if isinstance(node.args[0], ast.Str):
|
||||||
|
self.var_references.add(node.args[0].s)
|
||||||
else:
|
else:
|
||||||
return any(cls.compare_name(item, node) for item in value)
|
self.warn(node.func, node.args[0])
|
||||||
|
elif self.compare_name(self.expands, node.func):
|
||||||
def __init__(self, value):
|
if isinstance(node.args[0], ast.Str):
|
||||||
self.var_references = set()
|
self.warn(node.func, node.args[0])
|
||||||
self.var_execs = set()
|
self.var_expands.add(node.args[0].s)
|
||||||
self.direct_func_calls = set()
|
elif isinstance(node.args[0], ast.Call) and \
|
||||||
self.var_expands = set()
|
self.compare_name(self.getvars, node.args[0].func):
|
||||||
self.value = value
|
pass
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def warn(cls, func, arg):
|
|
||||||
"""Warn about calls of bitbake APIs which pass a non-literal
|
|
||||||
argument for the variable name, as we're not able to track such
|
|
||||||
a reference.
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
funcstr = codegen.to_source(func)
|
|
||||||
argstr = codegen.to_source(arg)
|
|
||||||
except TypeError:
|
|
||||||
logger.debug(2, 'Failed to convert function and argument to source form')
|
|
||||||
else:
|
else:
|
||||||
logger.debug(1, "Warning: in call to '%s', argument '%s' is "
|
self.warn(node.func, node.args[0])
|
||||||
"not a literal", funcstr, argstr)
|
elif self.compare_name(self.execfuncs, node.func):
|
||||||
|
if isinstance(node.args[0], ast.Str):
|
||||||
def visit_Call(self, node):
|
self.var_execs.add(node.args[0].s)
|
||||||
if self.compare_name(self.getvars, node.func):
|
else:
|
||||||
if isinstance(node.args[0], ast.Str):
|
self.warn(node.func, node.args[0])
|
||||||
self.var_references.add(node.args[0].s)
|
elif isinstance(node.func, ast.Name):
|
||||||
else:
|
self.execs.add(node.func.id)
|
||||||
self.warn(node.func, node.args[0])
|
elif isinstance(node.func, ast.Attribute):
|
||||||
elif self.compare_name(self.expands, node.func):
|
# We must have a qualified name. Therefore we need
|
||||||
if isinstance(node.args[0], ast.Str):
|
# to walk the chain of 'Attribute' nodes to determine
|
||||||
self.warn(node.func, node.args[0])
|
# the qualification.
|
||||||
self.var_expands.add(node.args[0].s)
|
attr_node = node.func.value
|
||||||
elif isinstance(node.args[0], ast.Call) and \
|
identifier = node.func.attr
|
||||||
self.compare_name(self.getvars, node.args[0].func):
|
while isinstance(attr_node, ast.Attribute):
|
||||||
pass
|
identifier = attr_node.attr + "." + identifier
|
||||||
else:
|
attr_node = attr_node.value
|
||||||
self.warn(node.func, node.args[0])
|
if isinstance(attr_node, ast.Name):
|
||||||
elif self.compare_name(self.execs, node.func):
|
identifier = attr_node.id + "." + identifier
|
||||||
if isinstance(node.args[0], ast.Str):
|
self.execs.add(identifier)
|
||||||
self.var_execs.add(node.args[0].s)
|
|
||||||
else:
|
|
||||||
self.warn(node.func, node.args[0])
|
|
||||||
elif isinstance(node.func, ast.Name):
|
|
||||||
self.direct_func_calls.add(node.func.id)
|
|
||||||
elif isinstance(node.func, ast.Attribute):
|
|
||||||
# We must have a qualified name. Therefore we need
|
|
||||||
# to walk the chain of 'Attribute' nodes to determine
|
|
||||||
# the qualification.
|
|
||||||
attr_node = node.func.value
|
|
||||||
identifier = node.func.attr
|
|
||||||
while isinstance(attr_node, ast.Attribute):
|
|
||||||
identifier = attr_node.attr + "." + identifier
|
|
||||||
attr_node = attr_node.value
|
|
||||||
if isinstance(attr_node, ast.Name):
|
|
||||||
identifier = attr_node.id + "." + identifier
|
|
||||||
self.direct_func_calls.add(identifier)
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
#self.funcdefs = set()
|
self.var_references = set()
|
||||||
|
self.var_execs = set()
|
||||||
self.execs = set()
|
self.execs = set()
|
||||||
#self.external_cmds = set()
|
self.var_expands = set()
|
||||||
self.references = set()
|
self.references = set()
|
||||||
|
|
||||||
def parse_python(self, node):
|
def parse_python(self, node):
|
||||||
|
|
||||||
h = hash(str(node))
|
h = hash(str(node))
|
||||||
|
|
||||||
if h in pythonparsecache:
|
if h in pythonparsecache:
|
||||||
|
@ -271,14 +258,12 @@ class PythonParser():
|
||||||
code = compile(check_indent(str(node)), "<string>", "exec",
|
code = compile(check_indent(str(node)), "<string>", "exec",
|
||||||
ast.PyCF_ONLY_AST)
|
ast.PyCF_ONLY_AST)
|
||||||
|
|
||||||
visitor = self.ValueVisitor(code)
|
|
||||||
for n in ast.walk(code):
|
for n in ast.walk(code):
|
||||||
if n.__class__.__name__ == "Call":
|
if n.__class__.__name__ == "Call":
|
||||||
visitor.visit_Call(n)
|
self.visit_Call(n)
|
||||||
|
|
||||||
self.references.update(visitor.var_references)
|
self.references.update(self.var_references)
|
||||||
self.references.update(visitor.var_execs)
|
self.references.update(self.var_execs)
|
||||||
self.execs = visitor.direct_func_calls
|
|
||||||
|
|
||||||
pythonparsecache[h] = {}
|
pythonparsecache[h] = {}
|
||||||
pythonparsecache[h]["refs"] = self.references
|
pythonparsecache[h]["refs"] = self.references
|
||||||
|
|
Loading…
Reference in New Issue