new way of gathering statements to evaluate for static analysis

This commit is contained in:
Dave Halter
2014-05-16 12:23:09 +02:00
parent 857a9b7621
commit d59e21f43c
5 changed files with 82 additions and 15 deletions

View File

@@ -587,16 +587,17 @@ class Script(object):
for o in origins if o.is_callable()]
def _analysis(self):
statements = set(chain(*self._parser.module().used_names.values()))
#statements = set(chain(*self._parser.module().used_names.values()))
stmts, imps = analysis.get_module_statements(self._parser.module())
# Sort the statements so that the results are reproducible.
for stmt in sorted(statements, key=lambda obj: obj.start_pos):
if isinstance(stmt, pr.Import):
imps = imports.ImportWrapper(self._evaluator, stmt,
nested_resolve=True).follow()
if stmt.is_nested() and any(not isinstance(i, pr.Module) for i in imps):
analysis.add(self._evaluator, 'import-error', stmt)
elif not (isinstance(stmt.parent, pr.ForFlow)
and stmt.parent.set_stmt == stmt):
for i in imps:
iw = imports.ImportWrapper(self._evaluator, i,
nested_resolve=True).follow()
if i.is_nested() and any(not isinstance(i, pr.Module) for i in iw):
analysis.add(self._evaluator, 'import-error', i)
for stmt in sorted(stmts, key=lambda obj: obj.start_pos):
if not (isinstance(stmt.parent, pr.ForFlow)
and stmt.parent.set_stmt == stmt):
self._evaluator.eval_statement(stmt)
ana = [a for a in self._evaluator.analysis if self.path == a.path]

View File

@@ -103,3 +103,37 @@ def _check_for_exception_catch(evaluator, jedi_obj, exception):
return True
jedi_obj = jedi_obj.parent
return False
def get_module_statements(module):
"""
Returns the statements used in a module. All these statements should be
evaluated to check for potential exceptions.
"""
def add_stmts(stmts):
new = set()
for stmt in stmts:
if isinstance(stmt, pr.Scope):
continue
if isinstance(stmt, pr.KeywordStatement):
stmt = stmt.stmt
if stmt is None:
continue
for expression in stmt.expression_list():
if isinstance(expression, pr.Array):
new |= add_stmts(expression.values)
if isinstance(expression, pr.StatementElement):
for element in expression.generate_call_path():
if isinstance(element, pr.Array):
new |= add_stmts(element.values)
new.add(stmt)
return new
stmts = set()
imports = set()
for scope in module.walk():
imports |= set(scope.imports)
stmts |= add_stmts(scope.statements)
return stmts, imports

View File

@@ -351,6 +351,18 @@ class Scope(Simple, IsScope, DocstringMixin):
return "<%s: %s@%s-%s>" % (type(self).__name__, name,
self.start_pos[0], self.end_pos[0])
def walk(self):
yield self
for s in self.subscopes:
for scope in s.walk():
yield scope
for r in self.statements:
while isinstance(r, Flow):
for scope in r.walk():
yield scope
r = r.next
class Module(IsScope):
"""
@@ -792,22 +804,25 @@ class KeywordStatement(Base):
For the following statements: `assert`, `del`, `global`, `nonlocal`,
`raise`, `return`, `yield`, `pass`, `continue`, `break`, `return`, `yield`.
"""
__slots__ = ('name', 'start_pos', '_stmt', 'parent')
__slots__ = ('name', 'start_pos', 'stmt', 'parent')
def __init__(self, name, start_pos, parent, stmt=None):
self.name = name
self.start_pos = start_pos
self._stmt = stmt
self.stmt = stmt
self.parent = parent
if stmt is not None:
stmt.parent = self
def __repr__(self):
return "<%s(%s): %s>" % (type(self).__name__, self.name, self.stmt)
def get_code(self):
if self._stmt is None:
if self.stmt is None:
return "%s\n" % self.name
else:
return '%s %s\n' % (self.name, self._stmt)
return '%s %s\n' % (self.name, self.stmt)
def get_defined_names(self):
return []
@@ -815,7 +830,7 @@ class KeywordStatement(Base):
@property
def end_pos(self):
try:
return self._stmt.end_pos
return self.stmt.end_pos
except AttributeError:
return self.start_pos[0], self.start_pos[1] + len(self.name)

View File

@@ -44,9 +44,26 @@ c.something = None
something = a
something
# -----------------
# Unused array variables should still raise attribute errors.
# -----------------
# should not raise anything.
for loop_variable in [1, 2]:
#! name-error
x = undefined
loop_variable
#! name-error
for loop_variable in [1, 2, undefined]:
pass
#! attribute-error
[1, ''.undefined_attr]
def return_one(something):
return 1
#! attribute-error
return_one(''.undefined_attribute)

View File

@@ -15,7 +15,7 @@ def assert_case_equal(case, actual, desired):
"""
assert actual == desired, """
Test %r failed.
actual = %s
actual = %s
desired = %s
""" % (case, actual, desired)