mirror of
https://github.com/github/codeql.git
synced 2025-12-16 16:53:25 +01:00
Modernise equals-hash-mismatch
This commit is contained in:
@@ -14,50 +14,13 @@
|
||||
|
||||
import python
|
||||
|
||||
CallableValue defines_equality(ClassValue c, string name) {
|
||||
(
|
||||
name = "__eq__"
|
||||
or
|
||||
major_version() = 2 and name = "__cmp__"
|
||||
) and
|
||||
result = c.declaredAttribute(name)
|
||||
predicate missingEquality(Class cls, Function defined) {
|
||||
defined = cls.getMethod("__hash__") and
|
||||
not exists(cls.getMethod("__eq__"))
|
||||
// In python 3, the case of defined eq without hash automatically makes the class unhashable (even if a superclass defined hash)
|
||||
// So this is not an issue.
|
||||
}
|
||||
|
||||
CallableValue implemented_method(ClassValue c, string name) {
|
||||
result = defines_equality(c, name)
|
||||
or
|
||||
result = c.declaredAttribute("__hash__") and name = "__hash__"
|
||||
}
|
||||
|
||||
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
|
||||
/* Python 3 automatically makes classes unhashable if __eq__ is defined, but __hash__ is not */
|
||||
not c.declaresAttribute(result) and result = "__hash__" and major_version() = 2
|
||||
}
|
||||
|
||||
/** Holds if this class is unhashable */
|
||||
predicate unhashable(ClassValue cls) {
|
||||
cls.lookup("__hash__") = Value::named("None")
|
||||
or
|
||||
cls.lookup("__hash__").(CallableValue).neverReturns()
|
||||
}
|
||||
|
||||
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.getScope()) // Suppress results that aren't from source
|
||||
select method, "Class $@ implements " + present + " but does not define " + missing + ".", c,
|
||||
c.getName()
|
||||
from Class cls, Function defined
|
||||
where missingEquality(cls, defined)
|
||||
select cls, "This class implements $@, but does not implement __eq__.", defined, defined.getName()
|
||||
|
||||
Reference in New Issue
Block a user