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:
Christopher Larson 2011-10-27 22:23:05 -07:00 committed by Richard Purdie
parent 9a68fb1364
commit ada59bde67
1 changed files with 85 additions and 100 deletions

View File

@ -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