|
| 1 | +/** |
| 2 | + * @name Incomplete ordering |
| 3 | + * @description Class defines ordering comparison methods, but does not define both strict and nonstrict ordering methods, to ensure all four comparison operators behave as expected. |
| 4 | + * @kind problem |
| 5 | + * @tags quality |
| 6 | + * reliability |
| 7 | + * correctness |
| 8 | + * @problem.severity warning |
| 9 | + * @sub-severity low |
| 10 | + * @precision very-high |
| 11 | + * @id py/incomplete-ordering |
| 12 | + */ |
| 13 | + |
| 14 | +import python |
| 15 | +import semmle.python.dataflow.new.internal.DataFlowDispatch |
| 16 | +import semmle.python.ApiGraphs |
| 17 | + |
| 18 | +/** Holds if `cls` has the `functools.total_ordering` decorator. */ |
| 19 | +predicate totalOrdering(Class cls) { |
| 20 | + API::moduleImport("functools") |
| 21 | + .getMember("total_ordering") |
| 22 | + .asSource() |
| 23 | + .flowsTo(DataFlow::exprNode(cls.getADecorator())) |
| 24 | +} |
| 25 | + |
| 26 | +predicate definesStrictOrdering(Class cls, Function meth) { |
| 27 | + meth = cls.getMethod("__lt__") |
| 28 | + or |
| 29 | + not exists(cls.getMethod("__lt__")) and |
| 30 | + meth = cls.getMethod("__gt__") |
| 31 | +} |
| 32 | + |
| 33 | +predicate definesNonStrictOrdering(Class cls, Function meth) { |
| 34 | + meth = cls.getMethod("__le__") |
| 35 | + or |
| 36 | + not exists(cls.getMethod("__le__")) and |
| 37 | + meth = cls.getMethod("__ge__") |
| 38 | +} |
| 39 | + |
| 40 | +predicate missingComparison(Class cls, Function defined, string missing) { |
| 41 | + definesStrictOrdering(cls, defined) and |
| 42 | + not definesNonStrictOrdering(getADirectSuperclass*(cls), _) and |
| 43 | + missing = "__le__ or __ge__" |
| 44 | + or |
| 45 | + definesNonStrictOrdering(cls, defined) and |
| 46 | + not definesStrictOrdering(getADirectSuperclass*(cls), _) and |
| 47 | + missing = "__lt__ or __gt__" |
| 48 | +} |
| 49 | + |
| 50 | +from Class cls, Function defined, string missing |
| 51 | +where |
| 52 | + not totalOrdering(cls) and |
| 53 | + missingComparison(cls, defined, missing) |
| 54 | +select cls, "This class implements $@, but does not implement " + missing + ".", defined, |
| 55 | + defined.getName() |
0 commit comments