|
14 | 14 |
|
15 | 15 | import python |
16 | 16 |
|
17 | | -CallableValue defines_equality(ClassValue c, string name) { |
18 | | - ( |
19 | | - name = "__eq__" |
20 | | - or |
21 | | - major_version() = 2 and name = "__cmp__" |
22 | | - ) and |
23 | | - result = c.declaredAttribute(name) |
| 17 | +predicate missingEquality(Class cls, Function defined) { |
| 18 | + defined = cls.getMethod("__hash__") and |
| 19 | + not exists(cls.getMethod("__eq__")) |
| 20 | + // In python 3, the case of defined eq without hash automatically makes the class unhashable (even if a superclass defined hash) |
| 21 | + // So this is not an issue. |
24 | 22 | } |
25 | 23 |
|
26 | | -CallableValue implemented_method(ClassValue c, string name) { |
27 | | - result = defines_equality(c, name) |
28 | | - or |
29 | | - result = c.declaredAttribute("__hash__") and name = "__hash__" |
30 | | -} |
31 | | - |
32 | | -string unimplemented_method(ClassValue c) { |
33 | | - not exists(defines_equality(c, _)) and |
34 | | - ( |
35 | | - result = "__eq__" and major_version() = 3 |
36 | | - or |
37 | | - major_version() = 2 and result = "__eq__ or __cmp__" |
38 | | - ) |
39 | | - or |
40 | | - /* Python 3 automatically makes classes unhashable if __eq__ is defined, but __hash__ is not */ |
41 | | - not c.declaresAttribute(result) and result = "__hash__" and major_version() = 2 |
42 | | -} |
43 | | - |
44 | | -/** Holds if this class is unhashable */ |
45 | | -predicate unhashable(ClassValue cls) { |
46 | | - cls.lookup("__hash__") = Value::named("None") |
47 | | - or |
48 | | - cls.lookup("__hash__").(CallableValue).neverReturns() |
49 | | -} |
50 | | - |
51 | | -predicate violates_hash_contract(ClassValue c, string present, string missing, Value method) { |
52 | | - not unhashable(c) and |
53 | | - missing = unimplemented_method(c) and |
54 | | - method = implemented_method(c, present) and |
55 | | - not c.failedInference(_) |
56 | | -} |
57 | | - |
58 | | -from ClassValue c, string present, string missing, CallableValue method |
59 | | -where |
60 | | - violates_hash_contract(c, present, missing, method) and |
61 | | - exists(c.getScope()) // Suppress results that aren't from source |
62 | | -select method, "Class $@ implements " + present + " but does not define " + missing + ".", c, |
63 | | - c.getName() |
| 24 | +from Class cls, Function defined |
| 25 | +where missingEquality(cls, defined) |
| 26 | +select cls, "This class implements $@, but does not implement __eq__.", defined, defined.getName() |
0 commit comments