1
0
forked from VimPlug/jedi

Fix try/except checks in static analysis.

This commit is contained in:
Dave Halter
2014-12-12 02:26:16 +01:00
parent c3106c10ef
commit 8eaa008b5f
2 changed files with 50 additions and 25 deletions

View File

@@ -1,6 +1,7 @@
""" """
Module for statical analysis. Module for statical analysis.
""" """
from itertools import chain
from jedi import debug from jedi import debug
from jedi.parser import tree as pr from jedi.parser import tree as pr
@@ -127,59 +128,70 @@ def _check_for_exception_catch(evaluator, jedi_obj, exception, payload=None):
it. it.
Returns True if the exception was catched. Returns True if the exception was catched.
""" """
def check_match(cls): def check_match(cls, exception):
try: try:
return isinstance(cls, CompiledObject) and issubclass(exception, cls.obj) return isinstance(cls, CompiledObject) and issubclass(exception, cls.obj)
except TypeError: except TypeError:
return False return False
def check_try_for_except(obj): def check_try_for_except(obj, exception):
while obj.next is not None: # Only nodes in try
obj = obj.next iterator = iter(obj.children)
if not obj.inputs: for branch_type in iterator:
# No import implies a `except:` catch, which catches colon = next(iterator)
# everything. suite = next(iterator)
return True if branch_type == 'try' \
and not (branch_type.start_pos < jedi_obj.start_pos <= suite.end_pos):
return False
for i in obj.inputs: for node in obj.except_clauses():
except_classes = evaluator.eval_statement(i) if node is None:
return True # An exception block that catches everything.
else:
except_classes = evaluator.eval_element(node)
for cls in except_classes: for cls in except_classes:
from jedi.evaluate import iterable from jedi.evaluate import iterable
if isinstance(cls, iterable.Array) and cls.type == 'tuple': if isinstance(cls, iterable.Array) and cls.type == 'tuple':
# multiple exceptions # multiple exceptions
for c in cls.values(): for c in cls.values():
if check_match(c): if check_match(c, exception):
return True return True
else: else:
if check_match(cls): if check_match(cls, exception):
return True return True
return False
def check_hasattr(node): def check_hasattr(node):
try: try:
assert len(expression_list) == 1 assert node.type == 'power'
call = expression_list[0] base = node.children[0]
assert isinstance(call, pr.Call) and str(call.name) == 'hasattr' assert base.type == 'name' and base.value == 'hasattr'
assert call.next_is_execution() trailer = node.children[1]
execution = call.next assert trailer.type == 'trailer'
assert execution and len(execution) == 2 arglist = trailer.children[1]
assert arglist.type == 'arglist'
from jedi.evaluate.param import Arguments
args = list(Arguments(evaluator, arglist).unpack())
# Arguments should be very simple
assert len(args) == 2
# check if the names match # Check name
names = evaluator.eval_statement(execution[1]) assert len(args[1]) == 1
names = evaluator.eval_element(args[1][0])
assert len(names) == 1 and isinstance(names[0], CompiledObject) assert len(names) == 1 and isinstance(names[0], CompiledObject)
assert names[0].obj == str(payload[1]) assert names[0].obj == str(payload[1])
objects = evaluator.eval_statement(execution[0]) # Check objects
assert len(args[0]) == 1
objects = evaluator.eval_element(args[0][0])
return payload[0] in objects return payload[0] in objects
except AssertionError: except AssertionError:
pass return False
return False
obj = jedi_obj obj = jedi_obj
while obj is not None and not obj.isinstance(pr.Function, pr.Class): while obj is not None and not obj.isinstance(pr.Function, pr.Class):
if obj.isinstance(pr.Flow): if obj.isinstance(pr.Flow):
# try/except catch check # try/except catch check
if obj.isinstance(pr.TryStmt) and check_try_for_except(obj): if obj.isinstance(pr.TryStmt) and check_try_for_except(obj, exception):
return True return True
# hasattr check # hasattr check
if exception == AttributeError and obj.isinstance(pr.IfStmt, pr.WhileStmt): if exception == AttributeError and obj.isinstance(pr.IfStmt, pr.WhileStmt):
@@ -243,6 +255,8 @@ def get_module_statements(module):
for flow in scope.flows: for flow in scope.flows:
if flow.type == 'for_stmt': if flow.type == 'for_stmt':
nodes.add(flow.children[3]) nodes.add(flow.children[3])
elif flow.type == 'try_stmt':
nodes.update(e for e in flow.except_clauses() if e is not None)
try: try:
decorators = scope.get_decorators() decorators = scope.get_decorators()

View File

@@ -955,6 +955,17 @@ class ForStmt(Flow):
class TryStmt(Flow): class TryStmt(Flow):
type = 'try_stmt' type = 'try_stmt'
def except_clauses(self):
"""
Returns the ``test`` nodes found in ``except_clause`` nodes.
Returns ``[None]`` for except clauses without an exception given.
"""
for node in self.children:
if node.type == 'except_clause':
yield node.children[1]
elif node == 'except':
yield None
class WithStmt(Flow): class WithStmt(Flow):
type = 'with_stmt' type = 'with_stmt'