Skip to content

Commit a687b60

Browse files
Modernise equals-hash-mismatch
1 parent eb1b5a3 commit a687b60

File tree

1 file changed

+8
-45
lines changed

1 file changed

+8
-45
lines changed

python/ql/src/Classes/Comparisons/EqualsOrHash.ql

Lines changed: 8 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -14,50 +14,13 @@
1414

1515
import python
1616

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.
2422
}
2523

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

Comments
 (0)