Python: Port a few queries to new API.

This commit is contained in:
Mark Shannon
2019-07-26 12:00:14 +01:00
parent fc4bb028b7
commit 4b242ddc86
11 changed files with 69 additions and 55 deletions

View File

@@ -13,19 +13,19 @@
import python
FunctionObject defines_equality(ClassObject c, string name) {
CallableValue defines_equality(ClassValue c, string name) {
(name = "__eq__" or major_version() = 2 and name = "__cmp__")
and
result = c.declaredAttribute(name)
}
FunctionObject implemented_method(ClassObject c, string name) {
CallableValue implemented_method(ClassValue c, string name) {
result = defines_equality(c, name)
or
result = c.declaredAttribute("__hash__") and name = "__hash__"
}
string unimplemented_method(ClassObject c) {
string unimplemented_method(ClassValue c) {
not exists(defines_equality(c, _)) and
(result = "__eq__" and major_version() = 3 or major_version() = 2 and result = "__eq__ or __cmp__")
or
@@ -33,14 +33,21 @@ string unimplemented_method(ClassObject c) {
not c.declaresAttribute(result) and result = "__hash__" and major_version() = 2
}
predicate violates_hash_contract(ClassObject c, string present, string missing, Object method) {
not c.unhashable() and
missing = unimplemented_method(c) and
method = implemented_method(c, present) and
not c.unknowableAttributes()
/** Holds if this class is unhashable */
predicate unhashable(ClassValue cls) {
cls.lookup("__hash__") = Value::named("None")
or
cls.lookup("__hash__").(CallableValue).neverReturns()
}
from ClassObject c, string present, string missing, FunctionObject method
predicate violates_hash_contract(ClassValue c, string present, string missing, Value method) {
not unhashable(c) and
missing = unimplemented_method(c) and
method = implemented_method(c, present) and
not c.failedInference(_)
}
from ClassValue c, string present, string missing, CallableValue method
where violates_hash_contract(c, present, missing, method) and
exists(c.getPyClass()) // Suppress results that aren't from source
exists(c.getScope()) // Suppress results that aren't from source
select method, "Class $@ implements " + present + " but does not define " + missing + ".", c, c.getName()

View File

@@ -25,26 +25,26 @@ predicate total_ordering(Class cls) {
n.getId() = "total_ordering")
}
FunctionObject implemented_method(ClassObject c, string name) {
CallableValue implemented_method(ClassValue c, string name) {
result = c.declaredAttribute(name) and name = equals_or_ne()
}
string unimplemented_method(ClassObject c) {
string unimplemented_method(ClassValue c) {
not c.declaresAttribute(result) and result = equals_or_ne()
}
predicate violates_equality_contract(ClassObject c, string present, string missing, FunctionObject method) {
predicate violates_equality_contract(ClassValue c, string present, string missing, CallableValue method) {
missing = unimplemented_method(c) and
method = implemented_method(c, present) and
not c.unknowableAttributes() and
not total_ordering(c.getPyClass()) and
not c.failedInference(_) and
not total_ordering(c.getScope()) and
/* Python 3 automatically implements __ne__ if __eq__ is defined, but not vice-versa */
not (major_version() = 3 and present = "__eq__" and missing = "__ne__") and
not method.getFunction() instanceof DelegatingEqualityMethod and
not c.lookupAttribute(missing).(FunctionObject).getFunction() instanceof DelegatingEqualityMethod
not method.getScope() instanceof DelegatingEqualityMethod and
not c.lookup(missing).(CallableValue).getScope() instanceof DelegatingEqualityMethod
}
from ClassObject c, string present, string missing, FunctionObject method
from ClassValue c, string present, string missing, CallableValue method
where violates_equality_contract(c, present, missing, method)
select method, "Class $@ implements " + present + " but does not implement " + missing + ".", c, c.getName()

View File

@@ -27,25 +27,25 @@ string ordering_name(int n) {
result = "__ge__" and n = 4
}
predicate overrides_ordering_method(ClassObject c, string name) {
predicate overrides_ordering_method(ClassValue c, string name) {
name = ordering_name(_) and
(
c.declaresAttribute(name)
or
exists(ClassObject sup |
sup = c.getASuperType() and not sup = theObjectType() |
exists(ClassValue sup |
sup = c.getASuperType() and not sup = Value::named("object") |
sup.declaresAttribute(name)
)
)
}
string unimplemented_ordering(ClassObject c, int n) {
not c = theObjectType() and
string unimplemented_ordering(ClassValue c, int n) {
not c = Value::named("object") and
not overrides_ordering_method(c, result) and
result = ordering_name(n)
}
string unimplemented_ordering_methods(ClassObject c, int n) {
string unimplemented_ordering_methods(ClassValue c, int n) {
n = 0 and result = "" and exists(unimplemented_ordering(c, _))
or
exists(string prefix, int nm1 |
@@ -58,14 +58,14 @@ string unimplemented_ordering_methods(ClassObject c, int n) {
)
}
Object ordering_method(ClassObject c, string name) {
Value ordering_method(ClassValue c, string name) {
/* If class doesn't declare a method then don't blame this class (the superclass will be blamed). */
name = ordering_name(_) and result = c.declaredAttribute(name)
}
from ClassObject c, Object ordering, string name
where not c.unknowableAttributes() and
not total_ordering(c.getPyClass())
from ClassValue c, Value ordering, string name
where not c.failedInference(_) and
not total_ordering(c.getScope())
and ordering = ordering_method(c, name) and
exists(unimplemented_ordering(c, _))

View File

@@ -17,37 +17,39 @@ import python
* For numpy arrays, the index may be a list, which are not hashable and needs to be treated specially.
*/
predicate numpy_array_type(ClassObject na) {
exists(ModuleObject np | np.getName() = "numpy" or np.getName() = "numpy.core" |
na.getAnImproperSuperType() = np.attr("ndarray")
predicate numpy_array_type(ClassValue na) {
exists(ModuleValue np | np.getName() = "numpy" or np.getName() = "numpy.core" |
na.getASuperType() = np.attr("ndarray")
)
}
predicate has_custom_getitem(ClassObject cls) {
cls.lookupAttribute("__getitem__") instanceof PyFunctionObject
predicate has_custom_getitem(Value v) {
v.getClass().lookup("__getitem__") instanceof PythonFunctionValue
or
numpy_array_type(cls)
numpy_array_type(v.getClass())
}
predicate explicitly_hashed(ControlFlowNode f) {
exists(CallNode c, GlobalVariable hash | c.getArg(0) = f and c.getFunction().(NameNode).uses(hash) and hash.getId() = "hash")
}
predicate unhashable_subscript(ControlFlowNode f, ClassObject c, ControlFlowNode origin) {
predicate unhashable_subscript(ControlFlowNode f, ClassValue c, ControlFlowNode origin) {
is_unhashable(f, c, origin) and
exists(SubscriptNode sub | sub.getIndex() = f |
exists(ClassObject custom_getitem |
sub.getObject().refersTo(_, custom_getitem, _) and
exists(Value custom_getitem |
sub.getObject().pointsTo(custom_getitem) and
not has_custom_getitem(custom_getitem)
)
)
}
predicate is_unhashable(ControlFlowNode f, ClassObject cls, ControlFlowNode origin) {
f.refersTo(_, cls, origin) and
(not cls.hasAttribute("__hash__") and not cls.unknowableAttributes() and cls.isNewStyle()
or
cls.lookupAttribute("__hash__") = theNoneObject()
predicate is_unhashable(ControlFlowNode f, ClassValue cls, ControlFlowNode origin) {
exists(Value v |
f.pointsTo(v, origin) and v.getClass() = cls
|
not cls.hasAttribute("__hash__") and not cls.failedInference(_) and cls.isNewStyle()
or
cls.lookup("__hash__") = Value::named("None")
)
}
@@ -70,7 +72,7 @@ predicate typeerror_is_caught(ControlFlowNode f) {
try.getAHandler().getType().refersTo(theTypeErrorType()))
}
from ControlFlowNode f, ClassObject c, ControlFlowNode origin
from ControlFlowNode f, ClassValue c, ControlFlowNode origin
where
not typeerror_is_caught(f)
and

View File

@@ -14,11 +14,11 @@
import python
import Exceptions.NotImplemented
from Call c, ClassObject t, Expr f, AstNode origin
where f = c.getFunc() and f.refersTo(_, t, origin) and
not t.isCallable() and not t.unknowableAttributes()
and not t.isDescriptorType()
and not t = theNoneType()
from Call c, Value v, ClassValue t, Expr f, AstNode origin
where f = c.getFunc() and f.pointsTo(v, origin) and t = v.getClass() and
not t.isCallable() and not t.failedInference(_)
and not t.hasAttribute("__get__")
and not v = Value::named("None")
and not use_of_not_implemented_in_raise(_, f)
select c, "Call to a $@ of $@.", origin, "non-callable", t, t.toString()

View File

@@ -384,6 +384,11 @@ class ClassValue extends Value {
result = this.(PythonClassObjectInternal).getScope()
}
/** Gets the attribute declared in this class */
Value declaredAttribute(string name) {
Types::declaredAttribute(this, name, result, _)
}
/** Holds if this class has the attribute `name`, including
* attributes declared by super classes.
*/