|
2 | 2 | * @name Inconsistent equality and inequality |
3 | 3 | * @description Defining only an equality method or an inequality method for a class violates the object model. |
4 | 4 | * @kind problem |
5 | | - * @tags reliability |
| 5 | + * @tags quality |
| 6 | + * reliability |
6 | 7 | * correctness |
7 | 8 | * @problem.severity warning |
8 | 9 | * @sub-severity high |
|
11 | 12 | */ |
12 | 13 |
|
13 | 14 | import python |
14 | | -import Equality |
| 15 | +import Comparisons |
| 16 | +import semmle.python.dataflow.new.internal.DataFlowDispatch |
| 17 | +import Classes.Equality |
15 | 18 |
|
16 | | -string equals_or_ne() { result = "__eq__" or result = "__ne__" } |
17 | | - |
18 | | -predicate total_ordering(Class cls) { |
19 | | - exists(Attribute a | a = cls.getADecorator() | a.getName() = "total_ordering") |
| 19 | +predicate missingEquality(Class cls, Function defined, string missing) { |
| 20 | + defined = cls.getMethod("__ne__") and |
| 21 | + not exists(cls.getMethod("__eq__")) and |
| 22 | + missing = "__eq__" |
20 | 23 | or |
21 | | - exists(Name n | n = cls.getADecorator() | n.getId() = "total_ordering") |
22 | | -} |
23 | | - |
24 | | -CallableValue implemented_method(ClassValue c, string name) { |
25 | | - result = c.declaredAttribute(name) and name = equals_or_ne() |
26 | | -} |
27 | | - |
28 | | -string unimplemented_method(ClassValue c) { |
29 | | - not c.declaresAttribute(result) and result = equals_or_ne() |
30 | | -} |
31 | | - |
32 | | -predicate violates_equality_contract( |
33 | | - ClassValue c, string present, string missing, CallableValue method |
34 | | -) { |
35 | | - missing = unimplemented_method(c) and |
36 | | - method = implemented_method(c, present) and |
37 | | - not c.failedInference(_) and |
38 | | - not total_ordering(c.getScope()) and |
39 | | - /* Python 3 automatically implements __ne__ if __eq__ is defined, but not vice-versa */ |
40 | | - not (major_version() = 3 and present = "__eq__" and missing = "__ne__") and |
41 | | - not method.getScope() instanceof DelegatingEqualityMethod and |
42 | | - not c.lookup(missing).(CallableValue).getScope() instanceof DelegatingEqualityMethod |
| 24 | + // In python 3, __ne__ automatically delegates to __eq__ if its not defined in the hierarchy |
| 25 | + // However if it is defined in a superclass (and isn't a delegation method) then it will use the superclass method (which may be incorrect) |
| 26 | + defined = cls.getMethod("__eq__") and |
| 27 | + not exists(cls.getMethod("__ne__")) and |
| 28 | + exists(Function neMeth | |
| 29 | + neMeth = getADirectSuperclass+(cls).getMethod("__ne__") and |
| 30 | + not neMeth instanceof DelegatingEqualityMethod |
| 31 | + ) and |
| 32 | + missing = "__ne__" |
43 | 33 | } |
44 | 34 |
|
45 | | -from ClassValue c, string present, string missing, CallableValue method |
46 | | -where violates_equality_contract(c, present, missing, method) |
47 | | -select method, "Class $@ implements " + present + " but does not implement " + missing + ".", c, |
48 | | - c.getName() |
| 35 | +from Class cls, Function defined, string missing |
| 36 | +where |
| 37 | + not totalOrdering(cls) and |
| 38 | + missingEquality(cls, defined, missing) |
| 39 | +select cls, "This class implements $@, but does not implement " + missing + ".", defined, |
| 40 | + defined.getName() |
0 commit comments