|
2 | 2 | * @name Incomplete ordering |
3 | 3 | * @description Class defines one or more ordering method but does not define all 4 ordering comparison methods |
4 | 4 | * @kind problem |
5 | | - * @tags reliability |
| 5 | + * @tags quality |
| 6 | + * reliability |
6 | 7 | * correctness |
7 | 8 | * @problem.severity warning |
8 | 9 | * @sub-severity low |
|
11 | 12 | */ |
12 | 13 |
|
13 | 14 | import python |
| 15 | +import semmle.python.dataflow.new.internal.DataFlowDispatch |
| 16 | +import semmle.python.ApiGraphs |
14 | 17 |
|
15 | | -predicate total_ordering(Class cls) { |
16 | | - exists(Attribute a | a = cls.getADecorator() | a.getName() = "total_ordering") |
17 | | - or |
18 | | - exists(Name n | n = cls.getADecorator() | n.getId() = "total_ordering") |
19 | | -} |
20 | | - |
21 | | -string ordering_name(int n) { |
22 | | - result = "__lt__" and n = 1 |
23 | | - or |
24 | | - result = "__le__" and n = 2 |
25 | | - or |
26 | | - result = "__gt__" and n = 3 |
27 | | - or |
28 | | - result = "__ge__" and n = 4 |
| 18 | +predicate totalOrdering(Class cls) { |
| 19 | + cls.getADecorator() = |
| 20 | + API::moduleImport("functools").getMember("total_ordering").asSource().asExpr() |
29 | 21 | } |
30 | 22 |
|
31 | | -predicate overrides_ordering_method(ClassValue c, string name) { |
32 | | - name = ordering_name(_) and |
33 | | - ( |
34 | | - c.declaresAttribute(name) |
35 | | - or |
36 | | - exists(ClassValue sup | sup = c.getASuperType() and not sup = Value::named("object") | |
37 | | - sup.declaresAttribute(name) |
38 | | - ) |
39 | | - ) |
| 23 | +Function getMethod(Class cls, string name) { |
| 24 | + result = cls.getAMethod() and |
| 25 | + result.getName() = name |
40 | 26 | } |
41 | 27 |
|
42 | | -string unimplemented_ordering(ClassValue c, int n) { |
43 | | - not c = Value::named("object") and |
44 | | - not overrides_ordering_method(c, result) and |
45 | | - result = ordering_name(n) |
| 28 | +predicate definesStrictOrdering(Class cls, Function meth) { |
| 29 | + meth = getMethod(cls, "__lt__") |
| 30 | + or |
| 31 | + not exists(getMethod(cls, "__lt__")) and |
| 32 | + meth = getMethod(cls, "__gt__") |
46 | 33 | } |
47 | 34 |
|
48 | | -string unimplemented_ordering_methods(ClassValue c, int n) { |
49 | | - n = 0 and result = "" and exists(unimplemented_ordering(c, _)) |
| 35 | +predicate definesNonStrictOrdering(Class cls, Function meth) { |
| 36 | + meth = getMethod(cls, "__le__") |
50 | 37 | or |
51 | | - exists(string prefix, int nm1 | n = nm1 + 1 and prefix = unimplemented_ordering_methods(c, nm1) | |
52 | | - prefix = "" and result = unimplemented_ordering(c, n) |
53 | | - or |
54 | | - result = prefix and not exists(unimplemented_ordering(c, n)) and n < 5 |
55 | | - or |
56 | | - prefix != "" and result = prefix + " or " + unimplemented_ordering(c, n) |
57 | | - ) |
| 38 | + not exists(getMethod(cls, "__le__")) and |
| 39 | + meth = getMethod(cls, "__ge__") |
58 | 40 | } |
59 | 41 |
|
60 | | -Value ordering_method(ClassValue c, string name) { |
61 | | - /* If class doesn't declare a method then don't blame this class (the superclass will be blamed). */ |
62 | | - name = ordering_name(_) and result = c.declaredAttribute(name) |
| 42 | +predicate missingComparison(Class cls, Function defined, string missing) { |
| 43 | + definesStrictOrdering(cls, defined) and |
| 44 | + not definesNonStrictOrdering(getADirectSuperclass*(cls), _) and |
| 45 | + missing = "__le__ or __ge__" |
| 46 | + or |
| 47 | + definesNonStrictOrdering(cls, defined) and |
| 48 | + not definesStrictOrdering(getADirectSuperclass*(cls), _) and |
| 49 | + missing = "__lt__ or __gt__" |
63 | 50 | } |
64 | 51 |
|
65 | | -from ClassValue c, Value ordering, string name |
| 52 | +from Class cls, Function defined, string missing |
66 | 53 | where |
67 | | - not c.failedInference(_) and |
68 | | - not total_ordering(c.getScope()) and |
69 | | - ordering = ordering_method(c, name) and |
70 | | - exists(unimplemented_ordering(c, _)) |
71 | | -select c, |
72 | | - "Class " + c.getName() + " implements $@, but does not implement " + |
73 | | - unimplemented_ordering_methods(c, 4) + ".", ordering, name |
| 54 | + not totalOrdering(cls) and |
| 55 | + missingComparison(cls, defined, missing) |
| 56 | +select cls, "This class implements $@, but does not implement an " + missing + " method.", defined, |
| 57 | + defined.getName() |
0 commit comments