Merge pull request #2924 from BekaValentine/python-objectapi-to-valueapi-wrongnumberargumentsincall

Python: ObjectAPI to ValueAPI: WrongNumberArgumentsInCall
This commit is contained in:
Taus
2020-04-23 17:56:39 +02:00
committed by GitHub
12 changed files with 219 additions and 51 deletions

View File

@@ -154,14 +154,17 @@ predicate too_few_args(Call call, Value callable, int limit) {
not exists(call.getKwargs()) and
arg_count(call) < limit and
exists(FunctionValue func | func = get_function_or_initializer(callable) |
call = func.getACall().getNode() and
call = func.getAFunctionCall().getNode() and
limit = func.minParameters() and
// The combination of misuse of `mox.Mox().StubOutWithMock()`
// and a bug in mox's implementation of methods results in having to
// pass 1 too few arguments to the mocked function.
/*
* The combination of misuse of `mox.Mox().StubOutWithMock()`
* and a bug in mox's implementation of methods results in having to
* pass 1 too few arguments to the mocked function.
*/
not (useOfMoxInModule(call.getEnclosingModule()) and func.isNormalMethod())
or
call = func.getACall().getNode() and limit = func.minParameters() - 1
call = func.getAMethodCall().getNode() and limit = func.minParameters() - 1
or
callable instanceof ClassValue and
call.getAFlowNode() = get_a_call(callable) and
@@ -198,9 +201,9 @@ predicate too_many_args(Call call, Value callable, int limit) {
not func.getScope().hasVarArg() and
limit >= 0
|
call = func.getACall().getNode() and limit = func.maxParameters()
call = func.getAFunctionCall().getNode() and limit = func.maxParameters()
or
call = func.getACall().getNode() and limit = func.maxParameters() - 1
call = func.getAMethodCall().getNode() and limit = func.maxParameters() - 1
or
callable instanceof ClassValue and
call.getAFlowNode() = get_a_call(callable) and
@@ -254,3 +257,8 @@ predicate overridden_call(FunctionValue func, FunctionValue overriding, Call cal
overriding.overrides(func) and
overriding.getACall().getNode() = call
}
/** Holds if `func` will raise a `NotImplemented` error. */
predicate isAbstract(FunctionValue func) {
func.getARaisedType() = ClassValue::notImplementedError()
}

View File

@@ -14,22 +14,17 @@
import python
import CallArgs
from Call call, FunctionObject func, string too, string should, int limit
from Call call, FunctionValue func, string too, string should, int limit
where
(
too_many_args_objectapi(call, func, limit) and
too = "too many arguments" and
should = "no more than "
or
too_few_args_objectapi(call, func, limit) and
too = "too few arguments" and
should = "no fewer than "
) and
not func.isAbstract() and
not exists(FunctionObject overridden |
func.overrides(overridden) and correct_args_if_called_as_method_objectapi(call, overridden)
) and
/* The semantics of `__new__` can be a bit subtle, so we simply exclude `__new__` methods */
not func.getName() = "__new__"
select call, "Call to $@ with " + too + "; should be " + should + limit.toString() + ".", func,
func.descriptiveString()
(
too_many_args(call, func, limit) and too = "too many arguments" and should = "no more than "
or
too_few_args(call, func, limit) and too = "too few arguments" and should = "no fewer than "
) and
not isAbstract(func) and
not exists(FunctionValue overridden | func.overrides(overridden) and correct_args_if_called_as_method(call, overridden))
/* The semantics of `__new__` can be a bit subtle, so we simply exclude `__new__` methods */
and not func.getName() = "__new__"
select call, "Call to $@ with " + too + "; should be " + should + limit.toString() + ".", func, func.descriptiveString()

View File

@@ -608,6 +608,12 @@ abstract class FunctionValue extends CallableValue {
)
}
/** Gets a class that may be raised by this function */
abstract ClassValue getARaisedType();
/** Gets a call-site from where this function is called as a function */
CallNode getAFunctionCall() { result.getFunction().pointsTo() = this }
/** Gets a call-site from where this function is called as a method */
CallNode getAMethodCall() {
exists(BoundMethodObjectInternal bm |
@@ -656,6 +662,8 @@ class PythonFunctionValue extends FunctionValue {
/** Gets a control flow node corresponding to a return statement in this function */
ControlFlowNode getAReturnedNode() { result = this.getScope().getAReturnValueFlowNode() }
override ClassValue getARaisedType() { scope_raises(result, this.getScope()) }
override ClassValue getAnInferredReturnType() {
/* We have to do a special version of this because builtin functions have no
* explicit return nodes that we can query and get the class of.
@@ -676,6 +684,11 @@ class BuiltinFunctionValue extends FunctionValue {
override int maxParameters() { none() }
override ClassValue getARaisedType() {
/* Information is unavailable for C code in general */
none()
}
override ClassValue getAnInferredReturnType() {
/* We have to do a special version of this because builtin functions have no
* explicit return nodes that we can query and get the class of.
@@ -702,6 +715,11 @@ class BuiltinMethodValue extends FunctionValue {
override int maxParameters() { none() }
override ClassValue getARaisedType() {
/* Information is unavailable for C code in general */
none()
}
override ClassValue getAnInferredReturnType() {
result = TBuiltinClassObject(this.(BuiltinMethodObjectInternal).getReturnType())
}
@@ -929,6 +947,9 @@ module ClassValue {
/** Get the `ClassValue` for the `LookupError` class. */
ClassValue lookupError() { result = TBuiltinClassObject(Builtin::builtin("LookupError")) }
/** Get the `ClassValue` for the `IndexError` class. */
ClassValue indexError() { result = TBuiltinClassObject(Builtin::builtin("IndexError")) }
/** Get the `ClassValue` for the `IOError` class. */
ClassValue ioError() { result = TBuiltinClassObject(Builtin::builtin("IOError")) }
@@ -949,4 +970,7 @@ module ClassValue {
ClassValue unicodeDecodeError() {
result = TBuiltinClassObject(Builtin::builtin("UnicodeDecodeError"))
}
/** Get the `ClassValue` for the `SystemExit` class. */
ClassValue systemExit() { result = TBuiltinClassObject(Builtin::builtin("SystemExit")) }
}

View File

@@ -36,19 +36,36 @@ class RaisingNode extends ControlFlowNode {
* Gets the type of an exception that may be raised
* at this control flow node
*/
ClassObject getARaisedType() {
result = this.localRaisedType()
ClassObject getARaisedType_objectapi() {
result = this.localRaisedType_objectapi()
or
exists(FunctionObject func | this = func.getACall() | result = func.getARaisedType())
or
result = systemExitRaise_objectapi()
}
/**
* Gets the type of an exception that may be raised
* at this control flow node
*/
ClassValue getARaisedType() {
result = this.localRaisedType()
or
exists(FunctionValue func | this = func.getACall() | result = func.getARaisedType())
or
result = systemExitRaise()
}
pragma[noinline]
private ClassObject systemExitRaise() { this.quits() and result = Object::builtin("SystemExit") }
private ClassObject systemExitRaise_objectapi() {
this.quits() and result = Object::builtin("SystemExit")
}
pragma[noinline]
private ClassValue systemExitRaise() { this.quits() and result = ClassValue::systemExit() }
pragma[noinline, nomagic]
private ClassObject localRaisedType() {
private ClassObject localRaisedType_objectapi() {
result.isSubclassOf(theBaseExceptionType()) and
(
exists(ControlFlowNode ex |
@@ -62,8 +79,8 @@ class RaisingNode extends ControlFlowNode {
or
exists(ExceptFlowNode except |
except = this.getAnExceptionalSuccessor() and
except.handles(result) and
result = this.innateException()
except.handles_objectapi(result) and
result = this.innateException_objectapi()
)
or
not exists(ExceptFlowNode except | except = this.getAnExceptionalSuccessor()) and
@@ -74,8 +91,36 @@ class RaisingNode extends ControlFlowNode {
)
}
pragma[noinline, nomagic]
private ClassValue localRaisedType() {
result.getASuperType() = ClassValue::baseException() and
(
exists(ControlFlowNode ex |
ex = this.getExceptionNode() and
(ex.pointsTo(result) or ex.pointsTo().getClass() = result)
)
or
this.getNode() instanceof ImportExpr and result = ClassValue::importError()
or
this.getNode() instanceof Print and result = ClassValue::ioError()
or
exists(ExceptFlowNode except |
except = this.getAnExceptionalSuccessor() and
except.handles(result) and
result = this.innateException()
)
or
not exists(ExceptFlowNode except | except = this.getAnExceptionalSuccessor()) and
sequence_or_mapping(this) and
result = ClassValue::lookupError()
or
this.read_write_call() and result = ClassValue::ioError()
)
}
/** Holds if this is an innate exception (AttributeError, NameError, IndexError, or KeyError). */
pragma[noinline]
ClassObject innateException() {
ClassObject innateException_objectapi() {
this.getNode() instanceof Attribute and result = theAttributeErrorType()
or
this.getNode() instanceof Name and result = theNameErrorType()
@@ -85,6 +130,18 @@ class RaisingNode extends ControlFlowNode {
this.getNode() instanceof Subscript and result = theKeyErrorType()
}
/** Holds if this is an innate exception (AttributeError, NameError, IndexError, or KeyError). */
pragma[noinline]
ClassValue innateException() {
this.getNode() instanceof Attribute and result = ClassValue::attributeError()
or
this.getNode() instanceof Name and result = ClassValue::nameError()
or
this.getNode() instanceof Subscript and result = ClassValue::indexError()
or
this.getNode() instanceof Subscript and result = ClassValue::keyError()
}
/**
* Whether this control flow node raises an exception,
* but the type of the exception it raises cannot be inferred.
@@ -114,7 +171,7 @@ class RaisingNode extends ControlFlowNode {
/** Whether (as inferred by type inference) it is highly unlikely (or impossible) for control to flow from this to succ. */
predicate unlikelySuccessor(ControlFlowNode succ) {
succ = this.getAnExceptionalSuccessor() and
not this.viableExceptionEdge(succ, _) and
not this.viableExceptionEdge_objectapi(succ, _) and
not this.raisesUnknownType()
or
exists(FunctionObject func |
@@ -132,7 +189,30 @@ class RaisingNode extends ControlFlowNode {
}
/** Whether it is considered plausible that 'raised' can be raised across the edge this-succ */
predicate viableExceptionEdge(ControlFlowNode succ, ClassObject raised) {
predicate viableExceptionEdge_objectapi(ControlFlowNode succ, ClassObject raised) {
raised.isLegalExceptionType() and
raised = this.getARaisedType_objectapi() and
succ = this.getAnExceptionalSuccessor() and
(
/* An 'except' that handles raised and there is no more previous handler */
succ.(ExceptFlowNode).handles_objectapi(raised) and
not exists(ExceptFlowNode other, StmtList s, int i, int j |
not other = succ and
other.handles_objectapi(raised) and
s.getItem(i) = succ.getNode() and
s.getItem(j) = other.getNode()
|
j < i
)
or
/* Any successor that is not an 'except', provided that 'raised' is not handled by a different successor. */
not this.getAnExceptionalSuccessor().(ExceptFlowNode).handles_objectapi(raised) and
not succ instanceof ExceptFlowNode
)
}
/** Whether it is considered plausible that 'raised' can be raised across the edge this-succ */
predicate viableExceptionEdge(ControlFlowNode succ, ClassValue raised) {
raised.isLegalExceptionType() and
raised = this.getARaisedType() and
succ = this.getAnExceptionalSuccessor() and
@@ -159,7 +239,19 @@ class RaisingNode extends ControlFlowNode {
* plausible that the scope `s` can be exited with exception `raised`
* at this point.
*/
predicate viableExceptionalExit(Scope s, ClassObject raised) {
predicate viableExceptionalExit_objectapi(Scope s, ClassObject raised) {
raised.isLegalExceptionType() and
raised = this.getARaisedType_objectapi() and
this.isExceptionalExit(s) and
not this.getAnExceptionalSuccessor().(ExceptFlowNode).handles_objectapi(raised)
}
/**
* Whether this exceptional exit is viable. That is, is it
* plausible that the scope `s` can be exited with exception `raised`
* at this point.
*/
predicate viableExceptionalExit(Scope s, ClassValue raised) {
raised.isLegalExceptionType() and
raised = this.getARaisedType() and
this.isExceptionalExit(s) and
@@ -170,7 +262,29 @@ class RaisingNode extends ControlFlowNode {
/** Is this a sequence or mapping subscript x[i]? */
private predicate sequence_or_mapping(RaisingNode r) { r.getNode() instanceof Subscript }
private predicate current_exception(ClassObject ex, BasicBlock b) {
private predicate current_exception_objectapi(ClassObject ex, BasicBlock b) {
exists(RaisingNode r |
r.viableExceptionEdge_objectapi(b.getNode(0), ex) and not b.getNode(0) instanceof ExceptFlowNode
)
or
exists(BasicBlock prev |
current_exception_objectapi(ex, prev) and
exists(ControlFlowNode pred, ControlFlowNode succ |
pred = prev.getLastNode() and succ = b.getNode(0)
|
pred.getASuccessor() = succ and
(
/* Normal control flow */
not pred.getAnExceptionalSuccessor() = succ
or
/* Re-raise the current exception, propagating to the successor */
pred instanceof ReraisingNode
)
)
)
}
private predicate current_exception(ClassValue ex, BasicBlock b) {
exists(RaisingNode r |
r.viableExceptionEdge(b.getNode(0), ex) and not b.getNode(0) instanceof ExceptFlowNode
)
@@ -211,7 +325,19 @@ private predicate unknown_current_exception(BasicBlock b) {
}
/** INTERNAL -- Use FunctionObject.getARaisedType() instead */
predicate scope_raises(ClassObject ex, Scope s) {
predicate scope_raises_objectapi(ClassObject ex, Scope s) {
exists(BasicBlock b |
current_exception_objectapi(ex, b) and
b.getLastNode().isExceptionalExit(s)
|
b.getLastNode() instanceof ReraisingNode
)
or
exists(RaisingNode r | r.viableExceptionalExit_objectapi(s, ex))
}
/** INTERNAL -- Use FunctionObject.getARaisedType() instead */
predicate scope_raises(ClassValue ex, Scope s) {
exists(BasicBlock b |
current_exception(ex, b) and
b.getLastNode().isExceptionalExit(s)
@@ -299,11 +425,18 @@ class ExceptFlowNode extends ControlFlowNode {
}
/** Whether this `except` handles `cls` */
predicate handles(ClassObject cls) {
predicate handles_objectapi(ClassObject cls) {
exists(ClassObject handled | this.handledException_objectapi(handled, _, _) |
cls.getAnImproperSuperType() = handled
)
}
/** Whether this `except` handles `cls` */
predicate handles(ClassValue cls) {
exists(ClassValue handled | this.handledException(handled, _, _) |
cls.getASuperType() = handled
)
}
}
private ControlFlowNode element_from_tuple_objectapi(Object tuple) {
@@ -324,7 +457,15 @@ class ReraisingNode extends RaisingNode {
}
/** Gets a class that may be raised by this node */
override ClassObject getARaisedType() {
override ClassObject getARaisedType_objectapi() {
exists(BasicBlock b |
current_exception_objectapi(result, b) and
b.getNode(_) = this
)
}
/** Gets a class that may be raised by this node */
override ClassValue getARaisedType() {
exists(BasicBlock b |
current_exception(result, b) and
b.getNode(_) = this

View File

@@ -135,7 +135,7 @@ class PyFunctionObject extends FunctionObject {
/** Whether this function is a procedure, that is, it has no explicit return statement and is not a generator function */
override predicate isProcedure() { this.getFunction().isProcedure() }
override ClassObject getARaisedType() { scope_raises(result, this.getFunction()) }
override ClassObject getARaisedType() { scope_raises_objectapi(result, this.getFunction()) }
override predicate raisesUnknownType() { scope_raises_unknown(this.getFunction()) }

View File

@@ -1,5 +1,5 @@
import python
from RaisingNode r, Scope s, ClassObject cls
where r.viableExceptionalExit(s, cls)
where r.viableExceptionalExit_objectapi(s, cls)
select r.getLocation().getStartLine(), r.toString(), s.toString(), cls.toString()

View File

@@ -1,6 +1,6 @@
import python
from RaisingNode r, ControlFlowNode n, ClassObject ex
where r.viableExceptionEdge(n, ex)
where r.viableExceptionEdge_objectapi(n, ex)
select r.getLocation().getStartLine(), n.getLocation().getStartLine(), r.getNode().toString(),
n.getNode().toString(), ex.toString()

View File

@@ -1,5 +1,5 @@
import python
from RaisingNode r, Scope s, ClassObject cls
where r.viableExceptionalExit(s, cls)
where r.viableExceptionalExit_objectapi(s, cls)
select r.getLocation().getStartLine(), r, s.toString(), cls

View File

@@ -1,5 +1,5 @@
import python
from ExceptFlowNode n, ClassObject cls
where n.handles(cls)
where n.handles_objectapi(cls)
select n.getLocation().getStartLine(), cls.toString()

View File

@@ -1,6 +1,6 @@
import python
from RaisingNode r, ControlFlowNode n, ClassObject ex
where r.viableExceptionEdge(n, ex)
where r.viableExceptionEdge_objectapi(n, ex)
select r.getLocation().getStartLine(), n.getLocation().getStartLine(), r.getNode().toString(),
n.getNode().toString(), ex.toString()

View File

@@ -1,7 +1,7 @@
| use_mox.py:28:1:28:4 | f0() | Call to $@ with too few arguments; should be no fewer than 1. | use_mox.py:7:1:7:10 | Function f0 | function f0 |
| use_mox.py:29:1:29:5 | f1() | Call to $@ with too few arguments; should be no fewer than 2. | use_mox.py:10:1:10:13 | Function f1 | function f1 |
| use_mox.py:32:1:32:8 | Attribute() | Call to $@ with too few arguments; should be no fewer than 1. | use_mox.py:15:5:15:20 | Function m0 | method C.m0 |
| use_mox.py:33:1:33:9 | Attribute() | Call to $@ with too few arguments; should be no fewer than 2. | use_mox.py:18:5:18:23 | Function m1 | method C.m1 |
| use_mox.py:32:1:32:8 | Attribute() | Call to $@ with too few arguments; should be no fewer than 1. | use_mox.py:15:5:15:20 | Function C.m0 | method C.m0 |
| use_mox.py:33:1:33:9 | Attribute() | Call to $@ with too few arguments; should be no fewer than 2. | use_mox.py:18:5:18:23 | Function C.m1 | method C.m1 |
| wrong_arguments.py:29:1:29:4 | f0() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:3:1:3:10 | Function f0 | function f0 |
| wrong_arguments.py:30:1:30:4 | f1() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:6:1:6:20 | Function f1 | function f1 |
| wrong_arguments.py:31:1:31:4 | f2() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:9:1:9:14 | Function f2 | function f2 |
@@ -21,5 +21,5 @@
| wrong_arguments.py:86:1:86:4 | l1() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:71:6:71:21 | Function lambda | function lambda |
| wrong_arguments.py:96:1:96:12 | f6() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:21:1:21:13 | Function f6 | function f6 |
| wrong_arguments.py:97:1:97:7 | f6() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:21:1:21:13 | Function f6 | function f6 |
| wrong_arguments.py:130:1:130:9 | Attribute() | Call to $@ with too few arguments; should be no fewer than 2. | wrong_arguments.py:126:5:126:31 | Function spam | method Eggs2.spam |
| wrong_arguments.py:130:1:130:9 | Attribute() | Call to $@ with too many arguments; should be no more than 0. | wrong_arguments.py:121:5:121:19 | Function spam | method Eggs1.spam |
| wrong_arguments.py:130:1:130:9 | Attribute() | Call to $@ with too few arguments; should be no fewer than 2. | wrong_arguments.py:126:5:126:31 | Function Eggs2.spam | method Eggs2.spam |
| wrong_arguments.py:130:1:130:9 | Attribute() | Call to $@ with too many arguments; should be no more than 0. | wrong_arguments.py:121:5:121:19 | Function Eggs1.spam | method Eggs1.spam |

View File

@@ -1,2 +1,2 @@
| test.py:16:9:16:21 | Attribute() | Call to $@ with too many arguments; should be no more than 0. | test.py:5:5:5:20 | Function meth1 | method Base.meth1 |
| test.py:17:9:17:20 | Attribute() | Call to $@ with too few arguments; should be no fewer than 1. | test.py:8:5:8:26 | Function meth2 | method Base.meth2 |
| test.py:16:9:16:21 | Attribute() | Call to $@ with too many arguments; should be no more than 0. | test.py:5:5:5:20 | Function Base.meth1 | method Base.meth1 |
| test.py:17:9:17:20 | Attribute() | Call to $@ with too few arguments; should be no fewer than 1. | test.py:8:5:8:26 | Function Base.meth2 | method Base.meth2 |