From adbc4a7777cf49d18e663be157b93ba06893f6be Mon Sep 17 00:00:00 2001 From: Taus Date: Fri, 20 Feb 2026 15:29:48 +0000 Subject: [PATCH 1/2] Python: Port InconsistentMRO.ql For this one we actually lose a test result. However, this is kind of to be expected since we no longer have the "precise" MRO that the points-to analysis computes. Honestly, I'm on the fence about even keeping this query at all. It seems like it might be superfluous in a world with good Python type checking. --- python/ql/src/Classes/InconsistentMRO.ql | 19 ++++++++++++------- .../inconsistent-mro/InconsistentMRO.expected | 2 +- .../inconsistent-mro/InconsistentMRO.expected | 3 +-- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/python/ql/src/Classes/InconsistentMRO.ql b/python/ql/src/Classes/InconsistentMRO.ql index aa319d56114e..73f6bf9240b8 100644 --- a/python/ql/src/Classes/InconsistentMRO.ql +++ b/python/ql/src/Classes/InconsistentMRO.ql @@ -12,19 +12,24 @@ */ import python -private import LegacyPointsTo +private import semmle.python.dataflow.new.internal.DataFlowDispatch -ClassObject left_base(ClassObject type, ClassObject base) { - exists(int i | i > 0 and type.getBaseType(i) = base and result = type.getBaseType(i - 1)) +/** + * Gets the `i`th base class of `cls`, if it can be resolved to a user-defined class. + */ +Class getBaseType(Class cls, int i) { cls.getBase(i) = classTracker(result).asExpr() } + +Class left_base(Class type, Class base) { + exists(int i | i > 0 and getBaseType(type, i) = base and result = getBaseType(type, i - 1)) } -predicate invalid_mro(ClassObject t, ClassObject left, ClassObject right) { - t.isNewStyle() and +predicate invalid_mro(Class t, Class left, Class right) { + DuckTyping::isNewStyle(t) and left = left_base(t, right) and - left = right.getAnImproperSuperType() + left = getADirectSuperclass*(right) } -from ClassObject t, ClassObject left, ClassObject right +from Class t, Class left, Class right where invalid_mro(t, left, right) select t, "Construction of class " + t.getName() + diff --git a/python/ql/test/2/query-tests/Classes/inconsistent-mro/InconsistentMRO.expected b/python/ql/test/2/query-tests/Classes/inconsistent-mro/InconsistentMRO.expected index ebec9bb61e0b..93d12fc0a454 100644 --- a/python/ql/test/2/query-tests/Classes/inconsistent-mro/InconsistentMRO.expected +++ b/python/ql/test/2/query-tests/Classes/inconsistent-mro/InconsistentMRO.expected @@ -1 +1 @@ -| inconsistent_mro.py:9:1:9:14 | class Z | Construction of class Z can fail due to invalid method resolution order(MRO) for bases $@ and $@. | inconsistent_mro.py:3:1:3:16 | class X | X | inconsistent_mro.py:6:1:6:11 | class Y | Y | +| inconsistent_mro.py:9:1:9:14 | Class Z | Construction of class Z can fail due to invalid method resolution order(MRO) for bases $@ and $@. | inconsistent_mro.py:3:1:3:16 | Class X | X | inconsistent_mro.py:6:1:6:11 | Class Y | Y | diff --git a/python/ql/test/3/query-tests/Classes/inconsistent-mro/InconsistentMRO.expected b/python/ql/test/3/query-tests/Classes/inconsistent-mro/InconsistentMRO.expected index ca0a64f70e1b..93d12fc0a454 100644 --- a/python/ql/test/3/query-tests/Classes/inconsistent-mro/InconsistentMRO.expected +++ b/python/ql/test/3/query-tests/Classes/inconsistent-mro/InconsistentMRO.expected @@ -1,2 +1 @@ -| inconsistent_mro.py:9:1:9:14 | class Z | Construction of class Z can fail due to invalid method resolution order(MRO) for bases $@ and $@. | inconsistent_mro.py:3:1:3:16 | class X | X | inconsistent_mro.py:6:1:6:11 | class Y | Y | -| inconsistent_mro.py:16:1:16:19 | class N | Construction of class N can fail due to invalid method resolution order(MRO) for bases $@ and $@. | file://:Compiled Code:0:0:0:0 | builtin-class object | object | inconsistent_mro.py:12:1:12:8 | class O | O | +| inconsistent_mro.py:9:1:9:14 | Class Z | Construction of class Z can fail due to invalid method resolution order(MRO) for bases $@ and $@. | inconsistent_mro.py:3:1:3:16 | Class X | X | inconsistent_mro.py:6:1:6:11 | Class Y | Y | From 5e34778d7278a5f449fd1edddf174fff9a486fa2 Mon Sep 17 00:00:00 2001 From: Taus Date: Thu, 9 Apr 2026 21:12:17 +0000 Subject: [PATCH 2/2] Python: Exclude "self-inheritance" results It's not possible for a class to actually inherit from itself, so if this happens, something has gone wrong with our analysis. We therefore explicitly exclude these results so they don't result in false positives. --- python/ql/src/Classes/InconsistentMRO.ql | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/python/ql/src/Classes/InconsistentMRO.ql b/python/ql/src/Classes/InconsistentMRO.ql index 73f6bf9240b8..93dde9288ddb 100644 --- a/python/ql/src/Classes/InconsistentMRO.ql +++ b/python/ql/src/Classes/InconsistentMRO.ql @@ -17,7 +17,10 @@ private import semmle.python.dataflow.new.internal.DataFlowDispatch /** * Gets the `i`th base class of `cls`, if it can be resolved to a user-defined class. */ -Class getBaseType(Class cls, int i) { cls.getBase(i) = classTracker(result).asExpr() } +Class getBaseType(Class cls, int i) { + cls.getBase(i) = classTracker(result).asExpr() and + result != cls +} Class left_base(Class type, Class base) { exists(int i | i > 0 and getBaseType(type, i) = base and result = getBaseType(type, i - 1))