Skip to content

Commit bb19d45

Browse files
authored
Merge pull request github#1638 from markshannon/python-port-to-new-api
Python: port a few queries to new API.
2 parents 9512b70 + 4b242dd commit bb19d45

File tree

11 files changed

+67
-53
lines changed

11 files changed

+67
-53
lines changed

python/ql/src/Classes/EqualsOrHash.ql

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,34 +13,41 @@
1313

1414
import python
1515

16-
FunctionObject defines_equality(ClassObject c, string name) {
16+
CallableValue defines_equality(ClassValue c, string name) {
1717
(name = "__eq__" or major_version() = 2 and name = "__cmp__")
1818
and
1919
result = c.declaredAttribute(name)
2020
}
2121

22-
FunctionObject implemented_method(ClassObject c, string name) {
22+
CallableValue implemented_method(ClassValue c, string name) {
2323
result = defines_equality(c, name)
2424
or
2525
result = c.declaredAttribute("__hash__") and name = "__hash__"
2626
}
2727

28-
string unimplemented_method(ClassObject c) {
28+
string unimplemented_method(ClassValue c) {
2929
not exists(defines_equality(c, _)) and
3030
(result = "__eq__" and major_version() = 3 or major_version() = 2 and result = "__eq__ or __cmp__")
3131
or
3232
/* Python 3 automatically makes classes unhashable if __eq__ is defined, but __hash__ is not */
3333
not c.declaresAttribute(result) and result = "__hash__" and major_version() = 2
3434
}
3535

36-
predicate violates_hash_contract(ClassObject c, string present, string missing, Object method) {
37-
not c.unhashable() and
36+
/** Holds if this class is unhashable */
37+
predicate unhashable(ClassValue cls) {
38+
cls.lookup("__hash__") = Value::named("None")
39+
or
40+
cls.lookup("__hash__").(CallableValue).neverReturns()
41+
}
42+
43+
predicate violates_hash_contract(ClassValue c, string present, string missing, Value method) {
44+
not unhashable(c) and
3845
missing = unimplemented_method(c) and
3946
method = implemented_method(c, present) and
40-
not c.unknowableAttributes()
47+
not c.failedInference(_)
4148
}
4249

43-
from ClassObject c, string present, string missing, FunctionObject method
50+
from ClassValue c, string present, string missing, CallableValue method
4451
where violates_hash_contract(c, present, missing, method) and
45-
exists(c.getPyClass()) // Suppress results that aren't from source
52+
exists(c.getScope()) // Suppress results that aren't from source
4653
select method, "Class $@ implements " + present + " but does not define " + missing + ".", c, c.getName()

python/ql/src/Classes/EqualsOrNotEquals.ql

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,26 +25,26 @@ predicate total_ordering(Class cls) {
2525
n.getId() = "total_ordering")
2626
}
2727

28-
FunctionObject implemented_method(ClassObject c, string name) {
28+
CallableValue implemented_method(ClassValue c, string name) {
2929
result = c.declaredAttribute(name) and name = equals_or_ne()
3030
}
3131

32-
string unimplemented_method(ClassObject c) {
32+
string unimplemented_method(ClassValue c) {
3333
not c.declaresAttribute(result) and result = equals_or_ne()
3434
}
3535

36-
predicate violates_equality_contract(ClassObject c, string present, string missing, FunctionObject method) {
36+
predicate violates_equality_contract(ClassValue c, string present, string missing, CallableValue method) {
3737
missing = unimplemented_method(c) and
3838
method = implemented_method(c, present) and
39-
not c.unknowableAttributes() and
40-
not total_ordering(c.getPyClass()) and
39+
not c.failedInference(_) and
40+
not total_ordering(c.getScope()) and
4141
/* Python 3 automatically implements __ne__ if __eq__ is defined, but not vice-versa */
4242
not (major_version() = 3 and present = "__eq__" and missing = "__ne__") and
43-
not method.getFunction() instanceof DelegatingEqualityMethod and
44-
not c.lookupAttribute(missing).(FunctionObject).getFunction() instanceof DelegatingEqualityMethod
43+
not method.getScope() instanceof DelegatingEqualityMethod and
44+
not c.lookup(missing).(CallableValue).getScope() instanceof DelegatingEqualityMethod
4545
}
4646

47-
from ClassObject c, string present, string missing, FunctionObject method
47+
from ClassValue c, string present, string missing, CallableValue method
4848
where violates_equality_contract(c, present, missing, method)
4949

5050
select method, "Class $@ implements " + present + " but does not implement " + missing + ".", c, c.getName()

python/ql/src/Classes/IncompleteOrdering.ql

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,25 +27,25 @@ string ordering_name(int n) {
2727
result = "__ge__" and n = 4
2828
}
2929

30-
predicate overrides_ordering_method(ClassObject c, string name) {
30+
predicate overrides_ordering_method(ClassValue c, string name) {
3131
name = ordering_name(_) and
3232
(
3333
c.declaresAttribute(name)
3434
or
35-
exists(ClassObject sup |
36-
sup = c.getASuperType() and not sup = theObjectType() |
35+
exists(ClassValue sup |
36+
sup = c.getASuperType() and not sup = Value::named("object") |
3737
sup.declaresAttribute(name)
3838
)
3939
)
4040
}
4141

42-
string unimplemented_ordering(ClassObject c, int n) {
43-
not c = theObjectType() and
42+
string unimplemented_ordering(ClassValue c, int n) {
43+
not c = Value::named("object") and
4444
not overrides_ordering_method(c, result) and
4545
result = ordering_name(n)
4646
}
4747

48-
string unimplemented_ordering_methods(ClassObject c, int n) {
48+
string unimplemented_ordering_methods(ClassValue c, int n) {
4949
n = 0 and result = "" and exists(unimplemented_ordering(c, _))
5050
or
5151
exists(string prefix, int nm1 |
@@ -58,14 +58,14 @@ string unimplemented_ordering_methods(ClassObject c, int n) {
5858
)
5959
}
6060

61-
Object ordering_method(ClassObject c, string name) {
61+
Value ordering_method(ClassValue c, string name) {
6262
/* If class doesn't declare a method then don't blame this class (the superclass will be blamed). */
6363
name = ordering_name(_) and result = c.declaredAttribute(name)
6464
}
6565

66-
from ClassObject c, Object ordering, string name
67-
where not c.unknowableAttributes() and
68-
not total_ordering(c.getPyClass())
66+
from ClassValue c, Value ordering, string name
67+
where not c.failedInference(_) and
68+
not total_ordering(c.getScope())
6969
and ordering = ordering_method(c, name) and
7070
exists(unimplemented_ordering(c, _))
7171

python/ql/src/Expressions/HashedButNoHash.ql

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,37 +17,39 @@ import python
1717
* For numpy arrays, the index may be a list, which are not hashable and needs to be treated specially.
1818
*/
1919

20-
predicate numpy_array_type(ClassObject na) {
21-
exists(ModuleObject np | np.getName() = "numpy" or np.getName() = "numpy.core" |
22-
na.getAnImproperSuperType() = np.attr("ndarray")
20+
predicate numpy_array_type(ClassValue na) {
21+
exists(ModuleValue np | np.getName() = "numpy" or np.getName() = "numpy.core" |
22+
na.getASuperType() = np.attr("ndarray")
2323
)
2424
}
2525

26-
predicate has_custom_getitem(ClassObject cls) {
27-
cls.lookupAttribute("__getitem__") instanceof PyFunctionObject
26+
predicate has_custom_getitem(Value v) {
27+
v.getClass().lookup("__getitem__") instanceof PythonFunctionValue
2828
or
29-
numpy_array_type(cls)
29+
numpy_array_type(v.getClass())
3030
}
3131

3232
predicate explicitly_hashed(ControlFlowNode f) {
3333
exists(CallNode c, GlobalVariable hash | c.getArg(0) = f and c.getFunction().(NameNode).uses(hash) and hash.getId() = "hash")
3434
}
3535

36-
predicate unhashable_subscript(ControlFlowNode f, ClassObject c, ControlFlowNode origin) {
36+
predicate unhashable_subscript(ControlFlowNode f, ClassValue c, ControlFlowNode origin) {
3737
is_unhashable(f, c, origin) and
3838
exists(SubscriptNode sub | sub.getIndex() = f |
39-
exists(ClassObject custom_getitem |
40-
sub.getObject().refersTo(_, custom_getitem, _) and
39+
exists(Value custom_getitem |
40+
sub.getObject().pointsTo(custom_getitem) and
4141
not has_custom_getitem(custom_getitem)
4242
)
4343
)
4444
}
4545

46-
predicate is_unhashable(ControlFlowNode f, ClassObject cls, ControlFlowNode origin) {
47-
f.refersTo(_, cls, origin) and
48-
(not cls.hasAttribute("__hash__") and not cls.unknowableAttributes() and cls.isNewStyle()
49-
or
50-
cls.lookupAttribute("__hash__") = theNoneObject()
46+
predicate is_unhashable(ControlFlowNode f, ClassValue cls, ControlFlowNode origin) {
47+
exists(Value v |
48+
f.pointsTo(v, origin) and v.getClass() = cls
49+
|
50+
not cls.hasAttribute("__hash__") and not cls.failedInference(_) and cls.isNewStyle()
51+
or
52+
cls.lookup("__hash__") = Value::named("None")
5153
)
5254
}
5355

@@ -70,7 +72,7 @@ predicate typeerror_is_caught(ControlFlowNode f) {
7072
try.getAHandler().getType().refersTo(theTypeErrorType()))
7173
}
7274

73-
from ControlFlowNode f, ClassObject c, ControlFlowNode origin
75+
from ControlFlowNode f, ClassValue c, ControlFlowNode origin
7476
where
7577
not typeerror_is_caught(f)
7678
and

python/ql/src/Expressions/NonCallableCalled.ql

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@
1414
import python
1515
import Exceptions.NotImplemented
1616

17-
from Call c, ClassObject t, Expr f, AstNode origin
18-
where f = c.getFunc() and f.refersTo(_, t, origin) and
19-
not t.isCallable() and not t.unknowableAttributes()
20-
and not t.isDescriptorType()
21-
and not t = theNoneType()
17+
from Call c, Value v, ClassValue t, Expr f, AstNode origin
18+
where f = c.getFunc() and f.pointsTo(v, origin) and t = v.getClass() and
19+
not t.isCallable() and not t.failedInference(_)
20+
and not t.hasAttribute("__get__")
21+
and not v = Value::named("None")
2222
and not use_of_not_implemented_in_raise(_, f)
2323

2424
select c, "Call to a $@ of $@.", origin, "non-callable", t, t.toString()

python/ql/src/semmle/python/objects/ObjectAPI.qll

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,11 @@ class ClassValue extends Value {
384384
result = this.(PythonClassObjectInternal).getScope()
385385
}
386386

387+
/** Gets the attribute declared in this class */
388+
Value declaredAttribute(string name) {
389+
Types::declaredAttribute(this, name, result, _)
390+
}
391+
387392
/** Holds if this class has the attribute `name`, including
388393
* attributes declared by super classes.
389394
*/
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
| equals_hash.py:24:5:24:23 | Function __hash__ | Class $@ implements __hash__ but does not define __eq__. | equals_hash.py:19:1:19:19 | class Hash | Hash |
1+
| equals_hash.py:24:5:24:23 | Function Hash.__hash__ | Class $@ implements __hash__ but does not define __eq__. | equals_hash.py:19:1:19:19 | class Hash | Hash |
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
| test.py:9:5:9:28 | Function __ne__ | Class $@ implements __ne__ but does not implement __eq__. | test.py:7:1:7:13 | class NotOK2 | NotOK2 |
1+
| test.py:9:5:9:28 | Function NotOK2.__ne__ | Class $@ implements __ne__ but does not implement __eq__. | test.py:7:1:7:13 | class NotOK2 | NotOK2 |
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
| incomplete_ordering.py:3:1:3:26 | class PartOrdered | Class PartOrdered implements $@, but does not implement __le__ or __gt__ or __ge__. | incomplete_ordering.py:13:5:13:28 | Function __lt__ | __lt__ |
1+
| incomplete_ordering.py:3:1:3:26 | class PartOrdered | Class PartOrdered implements $@, but does not implement __le__ or __gt__ or __ge__. | incomplete_ordering.py:13:5:13:28 | Function PartOrdered.__lt__ | __lt__ |
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
| test.py:16:5:16:12 | non() | Call to a $@ of $@. | test.py:15:11:15:23 | NonCallable() | non-callable | test.py:3:1:3:26 | class NonCallable | class NonCallable |
2-
| test.py:17:5:17:8 | Tuple() | Call to a $@ of $@. | test.py:17:5:17:6 | Tuple | non-callable | file://:Compiled Code:0:0:0:0 | builtin-class tuple | builtin-class tuple |
3-
| test.py:18:5:18:8 | List() | Call to a $@ of $@. | test.py:18:5:18:6 | List | non-callable | file://:Compiled Code:0:0:0:0 | builtin-class list | builtin-class list |
2+
| test.py:17:5:17:8 | Tuple() | Call to a $@ of $@. | test.py:17:5:17:6 | Tuple | non-callable | file://:0:0:0:0 | builtin-class tuple | builtin-class tuple |
3+
| test.py:18:5:18:8 | List() | Call to a $@ of $@. | test.py:18:5:18:6 | List | non-callable | file://:0:0:0:0 | builtin-class list | builtin-class list |
44
| test.py:26:9:26:16 | non() | Call to a $@ of $@. | test.py:15:11:15:23 | NonCallable() | non-callable | test.py:3:1:3:26 | class NonCallable | class NonCallable |
5-
| test.py:47:12:47:27 | NotImplemented() | Call to a $@ of $@. | test.py:47:12:47:25 | NotImplemented | non-callable | file://:Compiled Code:0:0:0:0 | builtin-class NotImplementedType | builtin-class NotImplementedType |
5+
| test.py:47:12:47:27 | NotImplemented() | Call to a $@ of $@. | test.py:47:12:47:25 | NotImplemented | non-callable | file://:0:0:0:0 | builtin-class NotImplementedType | builtin-class NotImplementedType |

0 commit comments

Comments
 (0)