From af94ebe1fc65229b2589b2eba93d8263340c1d47 Mon Sep 17 00:00:00 2001 From: Joe Farebrother Date: Tue, 29 Jul 2025 14:16:16 +0100 Subject: [PATCH 1/9] Modernize attribute shadows subclass, Add cases for properties --- python/ql/src/Classes/SubclassShadowing.ql | 66 ++++++++++++------- .../SubclassShadowing.qlref | 3 +- .../subclass-shadowing/subclass_shadowing.py | 45 +++++++++---- 3 files changed, 79 insertions(+), 35 deletions(-) diff --git a/python/ql/src/Classes/SubclassShadowing.ql b/python/ql/src/Classes/SubclassShadowing.ql index 542cf31c76aa..6e915250a546 100644 --- a/python/ql/src/Classes/SubclassShadowing.ql +++ b/python/ql/src/Classes/SubclassShadowing.ql @@ -17,31 +17,53 @@ * defined in a super-class */ -/* Need to find attributes defined in superclass (only in __init__?) */ import python +import semmle.python.ApiGraphs +import semmle.python.dataflow.new.internal.DataFlowDispatch -predicate shadowed_by_super_class( - ClassObject c, ClassObject supercls, Assign assign, FunctionObject f +predicate isSettableProperty(Function prop) { + isProperty(prop) and + exists(Function setter, DataFlow::AttrRead setterRead, FunctionExpr propExpr | + setterRead.asExpr() = setter.getADecorator() and + setterRead.getAttributeName() = "setter" and + propExpr.getInnerScope() = prop and + DataFlow::exprNode(propExpr).(DataFlow::LocalSourceNode).flowsTo(setterRead.getObject()) + ) +} + +predicate isProperty(Function prop) { + prop.getADecorator() = API::builtin("property").asSource().asExpr() +} + +predicate shadowedBySuperclass( + Class cls, Class superclass, DataFlow::AttrWrite write, Function shadowed ) { - c.getASuperType() = supercls and - c.declaredAttribute(_) = f and - exists(FunctionObject init, Attribute attr | - supercls.declaredAttribute("__init__") = init and - attr = assign.getATarget() and - attr.getObject().(Name).getId() = "self" and - attr.getName() = f.getName() and - assign.getScope() = init.getOrigin().(FunctionExpr).getInnerScope() + getADirectSuperclass+(cls) = superclass and + shadowed = cls.getAMethod() and + exists(Function init | + init = superclass.getInitMethod() and + DataFlow::parameterNode(init.getArg(0)).(DataFlow::LocalSourceNode).flowsTo(write.getObject()) and + write.getAttributeName() = shadowed.getName() ) and - /* - * It's OK if the super class defines the method as well. - * We assume that the original method must have been defined for a reason. - */ - - not supercls.hasAttribute(f.getName()) + // Allow cases in which the super class defines the method as well. + // We assume that the original method must have been defined for a reason. + not exists(Function superShadowed | + superShadowed = superclass.getAMethod() and + superShadowed.getName() = shadowed.getName() + ) and + // Allow properties if they have setters, as the write in the superclass will call the setter. + not isSettableProperty(shadowed) } -from ClassObject c, ClassObject supercls, Assign assign, FunctionObject shadowed -where shadowed_by_super_class(c, supercls, assign, shadowed) -select shadowed.getOrigin(), - "Method " + shadowed.getName() + " is shadowed by an $@ in super class '" + supercls.getName() + - "'.", assign, "attribute" +from Class cls, Class superclass, DataFlow::AttrWrite write, Function shadowed, string extra +where + shadowedBySuperclass(cls, superclass, write, shadowed) and + ( + if isProperty(shadowed) + then + not isSettableProperty(shadowed) and + extra = " (read-only property may cause an error if written to.)" + else extra = "" + ) +select shadowed, "This method is shadowed by $@ in superclass $@." + extra, write, + "attribute " + write.getAttributeName(), superclass, superclass.getName() diff --git a/python/ql/test/query-tests/Classes/subclass-shadowing/SubclassShadowing.qlref b/python/ql/test/query-tests/Classes/subclass-shadowing/SubclassShadowing.qlref index 5fed3f9f8fc6..ab31ad285c5e 100644 --- a/python/ql/test/query-tests/Classes/subclass-shadowing/SubclassShadowing.qlref +++ b/python/ql/test/query-tests/Classes/subclass-shadowing/SubclassShadowing.qlref @@ -1 +1,2 @@ -Classes/SubclassShadowing.ql +query: Classes/SubclassShadowing.ql +postprocess: utils/test/InlineExpectationsTestQuery.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Classes/subclass-shadowing/subclass_shadowing.py b/python/ql/test/query-tests/Classes/subclass-shadowing/subclass_shadowing.py index 98e7f992e84e..b9fcd975eb33 100644 --- a/python/ql/test/query-tests/Classes/subclass-shadowing/subclass_shadowing.py +++ b/python/ql/test/query-tests/Classes/subclass-shadowing/subclass_shadowing.py @@ -1,30 +1,51 @@ #Subclass shadowing -class Base(object): +# BAD: `shadow` method shadows attribute +class Base: def __init__(self): self.shadow = 4 class Derived(Base): - def shadow(self): + def shadow(self): # $ Alert pass -#OK if the super class defines the method as well. -#Since the original method must exist for some reason. -#See JSONEncoder.default for real example +# OK: Allow if superclass also shadows its own method, as this is likely intended. +# Example: stdlib JSONEncoder.default uses this pattern. +class Base2: -class Base2(object): + def __init__(self, default=None): + if default: + self.default = default - def __init__(self, shadowy=None): - if shadowy: - self.shadow = shadowy - - def shadow(self): + def default(self): pass class Derived2(Base2): - def shadow(self): + def default(self): # No alert return 0 + +# Properties + +class Base3: + def __init__(self): + self.foo = 1 + self.bar = 2 + +class Derived3(Base3): + # BAD: Write to foo in superclass init raises an error. + @property + def foo(self): # $ Alert + return 2 + + # OK: This property has a setter, so the write is OK. + @property + def bar(self): # No alert + return self._bar + + @bar.setter + def bar(self, val): + self._bar = val \ No newline at end of file From 796a6060b204a6cc243618606e38bb4bd4583721 Mon Sep 17 00:00:00 2001 From: Joe Farebrother Date: Wed, 30 Jul 2025 10:11:59 +0100 Subject: [PATCH 2/9] Exclude setters and update tests --- python/ql/src/Classes/SubclassShadowing.ql | 28 ++++++++++--------- .../SubclassShadowing.expected | 3 +- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/python/ql/src/Classes/SubclassShadowing.ql b/python/ql/src/Classes/SubclassShadowing.ql index 6e915250a546..eab8520857b2 100644 --- a/python/ql/src/Classes/SubclassShadowing.ql +++ b/python/ql/src/Classes/SubclassShadowing.ql @@ -12,22 +12,23 @@ * @id py/attribute-shadows-method */ -/* - * Determine if a class defines a method that is shadowed by an attribute - * defined in a super-class - */ - import python import semmle.python.ApiGraphs import semmle.python.dataflow.new.internal.DataFlowDispatch predicate isSettableProperty(Function prop) { isProperty(prop) and - exists(Function setter, DataFlow::AttrRead setterRead, FunctionExpr propExpr | - setterRead.asExpr() = setter.getADecorator() and - setterRead.getAttributeName() = "setter" and - propExpr.getInnerScope() = prop and - DataFlow::exprNode(propExpr).(DataFlow::LocalSourceNode).flowsTo(setterRead.getObject()) + exists(Function setter | + setter.getScope() = prop.getScope() and + setter.getName() = prop.getName() and + isSetter(setter) + ) +} + +predicate isSetter(Function f) { + exists(DataFlow::AttrRead attr | + f.getADecorator() = attr.asExpr() and + attr.getAttributeName() = "setter" ) } @@ -52,7 +53,8 @@ predicate shadowedBySuperclass( superShadowed.getName() = shadowed.getName() ) and // Allow properties if they have setters, as the write in the superclass will call the setter. - not isSettableProperty(shadowed) + not isSettableProperty(shadowed) and + not isSetter(shadowed) } from Class cls, Class superclass, DataFlow::AttrWrite write, Function shadowed, string extra @@ -61,8 +63,8 @@ where ( if isProperty(shadowed) then - not isSettableProperty(shadowed) and - extra = " (read-only property may cause an error if written to.)" + // it's not a setter, so it's a read-only property + extra = " (read-only property may cause an error if written to in the superclass.)" else extra = "" ) select shadowed, "This method is shadowed by $@ in superclass $@." + extra, write, diff --git a/python/ql/test/query-tests/Classes/subclass-shadowing/SubclassShadowing.expected b/python/ql/test/query-tests/Classes/subclass-shadowing/SubclassShadowing.expected index caad71a9a31f..3852b977a22f 100644 --- a/python/ql/test/query-tests/Classes/subclass-shadowing/SubclassShadowing.expected +++ b/python/ql/test/query-tests/Classes/subclass-shadowing/SubclassShadowing.expected @@ -1 +1,2 @@ -| subclass_shadowing.py:10:5:10:21 | FunctionExpr | Method shadow is shadowed by an $@ in super class 'Base'. | subclass_shadowing.py:6:9:6:23 | AssignStmt | attribute | +| subclass_shadowing.py:11:5:11:21 | Function shadow | This method is shadowed by $@ in superclass $@. | subclass_shadowing.py:7:9:7:19 | ControlFlowNode for Attribute | attribute shadow | subclass_shadowing.py:4:1:4:11 | Class Base | Base | +| subclass_shadowing.py:41:5:41:18 | Function foo | This method is shadowed by $@ in superclass $@. (read-only property may cause an error if written to.) | subclass_shadowing.py:35:9:35:16 | ControlFlowNode for Attribute | attribute foo | subclass_shadowing.py:33:1:33:12 | Class Base3 | Base3 | From 34317d2d4ad66e2f5cd33a10a92389116ef5a2f1 Mon Sep 17 00:00:00 2001 From: Joe Farebrother Date: Wed, 30 Jul 2025 13:24:43 +0100 Subject: [PATCH 3/9] Update documentation --- python/ql/src/Classes/SubclassShadowing.py | 20 ++++--------- python/ql/src/Classes/SubclassShadowing.qhelp | 30 ++++++++++++------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/python/ql/src/Classes/SubclassShadowing.py b/python/ql/src/Classes/SubclassShadowing.py index 617db3c58e0b..4699b58d7e4c 100644 --- a/python/ql/src/Classes/SubclassShadowing.py +++ b/python/ql/src/Classes/SubclassShadowing.py @@ -1,17 +1,9 @@ -class Mammal(object): - - def __init__(self, milk = 0): - self.milk = milk - - -class Cow(Mammal): - +class A: def __init__(self): - Mammal.__init__(self) - - def milk(self): - return "Milk" + self._foo = 3 -#Cow().milk() will raise an error as Cow().milk is the 'milk' attribute -#set in Mammal.__init__, not the 'milk' method defined on Cow. +class B: + # BAD: _foo is shadowed by attribute A._foo + def _foo(self): + return 2 diff --git a/python/ql/src/Classes/SubclassShadowing.qhelp b/python/ql/src/Classes/SubclassShadowing.qhelp index 90daa9a992ab..c0a82012af34 100644 --- a/python/ql/src/Classes/SubclassShadowing.qhelp +++ b/python/ql/src/Classes/SubclassShadowing.qhelp @@ -3,25 +3,35 @@ "qhelp.dtd"> -

Subclass shadowing occurs when an instance attribute of a superclass has the -the same name as a method of a subclass, or vice-versa. -The semantics of Python attribute look-up mean that the instance attribute of -the superclass hides the method in the subclass. +

+When an object has an attribute that shares the same name a method on the object's class (or another class attribute), the instance attribute is +prioritized during attribute lookup, shadowing the method. + +If a method on a subclass is shadowed by an attribute on a superclass in this way, this may lead to unexpected results or errors, as this +shadowing behavior is nonlocal and may be unintended.

-

Rename the method in the subclass or rename the attribute in the superclass.

+

+Ensure method names on subclasses don't conflict with attribute names on superclasses, and rename one. +If the shadowing behavior is intended, ensure this is explicit in the superclass. +

-

The following code includes an example of subclass shadowing. When you call Cow().milk() -an error is raised because Cow().milk is interpreted as the 'milk' attribute set in -Mammal.__init__, not the 'milk' method defined within Cow. This can be fixed -by changing the name of either the 'milk' attribute or the 'milk' method.

+

+In the following example, the _foo attribute of class A shadows the method _foo of class B. +Calls to B()._foo() will result in a TypeError, as 3 will be called instead. +

+ + + +

+In the following example... +

-
From 2516f9452e8b725f923c8834bc3ad65c3bd2886f Mon Sep 17 00:00:00 2001 From: Joe Farebrother Date: Wed, 30 Jul 2025 15:17:19 +0100 Subject: [PATCH 4/9] Move to subfolder --- .../src/Classes/{ => SubclassShadowing}/SubclassShadowing.qhelp | 2 +- .../ql/src/Classes/{ => SubclassShadowing}/SubclassShadowing.ql | 0 .../examples/SubclassShadowingGood.py} | 0 .../Classes/subclass-shadowing/SubclassShadowing.qlref | 2 +- 4 files changed, 2 insertions(+), 2 deletions(-) rename python/ql/src/Classes/{ => SubclassShadowing}/SubclassShadowing.qhelp (95%) rename python/ql/src/Classes/{ => SubclassShadowing}/SubclassShadowing.ql (100%) rename python/ql/src/Classes/{SubclassShadowing.py => SubclassShadowing/examples/SubclassShadowingGood.py} (100%) diff --git a/python/ql/src/Classes/SubclassShadowing.qhelp b/python/ql/src/Classes/SubclassShadowing/SubclassShadowing.qhelp similarity index 95% rename from python/ql/src/Classes/SubclassShadowing.qhelp rename to python/ql/src/Classes/SubclassShadowing/SubclassShadowing.qhelp index c0a82012af34..acbcae653189 100644 --- a/python/ql/src/Classes/SubclassShadowing.qhelp +++ b/python/ql/src/Classes/SubclassShadowing/SubclassShadowing.qhelp @@ -26,7 +26,7 @@ In the following example, the _foo attribute of class AB()._foo() will result in a TypeError, as 3 will be called instead.

- +

In the following example... diff --git a/python/ql/src/Classes/SubclassShadowing.ql b/python/ql/src/Classes/SubclassShadowing/SubclassShadowing.ql similarity index 100% rename from python/ql/src/Classes/SubclassShadowing.ql rename to python/ql/src/Classes/SubclassShadowing/SubclassShadowing.ql diff --git a/python/ql/src/Classes/SubclassShadowing.py b/python/ql/src/Classes/SubclassShadowing/examples/SubclassShadowingGood.py similarity index 100% rename from python/ql/src/Classes/SubclassShadowing.py rename to python/ql/src/Classes/SubclassShadowing/examples/SubclassShadowingGood.py diff --git a/python/ql/test/query-tests/Classes/subclass-shadowing/SubclassShadowing.qlref b/python/ql/test/query-tests/Classes/subclass-shadowing/SubclassShadowing.qlref index ab31ad285c5e..5205014a3d55 100644 --- a/python/ql/test/query-tests/Classes/subclass-shadowing/SubclassShadowing.qlref +++ b/python/ql/test/query-tests/Classes/subclass-shadowing/SubclassShadowing.qlref @@ -1,2 +1,2 @@ -query: Classes/SubclassShadowing.ql +query: Classes/SubclassShadowing/SubclassShadowing.ql postprocess: utils/test/InlineExpectationsTestQuery.ql \ No newline at end of file From 63577f0cca1f9346390c46a34893468172f6c4d5 Mon Sep 17 00:00:00 2001 From: Joe Farebrother Date: Wed, 30 Jul 2025 15:52:26 +0100 Subject: [PATCH 5/9] Add extra example --- .../SubclassShadowing/SubclassShadowing.qhelp | 6 ++++-- .../examples/SubclassShadowingBad.py | 9 +++++++++ .../examples/SubclassShadowingGood.py | 20 ++++++++++++------- 3 files changed, 26 insertions(+), 9 deletions(-) create mode 100644 python/ql/src/Classes/SubclassShadowing/examples/SubclassShadowingBad.py diff --git a/python/ql/src/Classes/SubclassShadowing/SubclassShadowing.qhelp b/python/ql/src/Classes/SubclassShadowing/SubclassShadowing.qhelp index acbcae653189..5345d2c91780 100644 --- a/python/ql/src/Classes/SubclassShadowing/SubclassShadowing.qhelp +++ b/python/ql/src/Classes/SubclassShadowing/SubclassShadowing.qhelp @@ -26,12 +26,14 @@ In the following example, the _foo attribute of class AB()._foo() will result in a TypeError, as 3 will be called instead.

- +

-In the following example... +In the following example, the behavior of the default attribute being shadowed to allow for customization during initialization is +intended in within the superclass A. Overriding default in the subclass B is then OK.

+ diff --git a/python/ql/src/Classes/SubclassShadowing/examples/SubclassShadowingBad.py b/python/ql/src/Classes/SubclassShadowing/examples/SubclassShadowingBad.py new file mode 100644 index 000000000000..4699b58d7e4c --- /dev/null +++ b/python/ql/src/Classes/SubclassShadowing/examples/SubclassShadowingBad.py @@ -0,0 +1,9 @@ +class A: + def __init__(self): + self._foo = 3 + +class B: + # BAD: _foo is shadowed by attribute A._foo + def _foo(self): + return 2 + diff --git a/python/ql/src/Classes/SubclassShadowing/examples/SubclassShadowingGood.py b/python/ql/src/Classes/SubclassShadowing/examples/SubclassShadowingGood.py index 4699b58d7e4c..8fca041176ca 100644 --- a/python/ql/src/Classes/SubclassShadowing/examples/SubclassShadowingGood.py +++ b/python/ql/src/Classes/SubclassShadowing/examples/SubclassShadowingGood.py @@ -1,9 +1,15 @@ class A: - def __init__(self): - self._foo = 3 - -class B: - # BAD: _foo is shadowed by attribute A._foo - def _foo(self): - return 2 + def __init__(self, default_func=None): + if default_func is not None: + self.default = default_func + # GOOD: The shadowing behavior is explicitly intended in the superclass. + def default(self): + return [] + +class B(A): + + # Subclasses may override the method `default`, which will still be shadowed by the attribute `default` if it is set. + # As this is part of the expected behavior of the superclass, this is fine. + def default(self): + return {} \ No newline at end of file From 1efc09bbba4c408652544404054a5de0b4cdaff1 Mon Sep 17 00:00:00 2001 From: Joe Farebrother Date: Wed, 30 Jul 2025 15:54:39 +0100 Subject: [PATCH 6/9] Update integration tests --- .../query-suite/python-code-quality-extended.qls.expected | 2 +- .../query-suite/python-code-quality.qls.expected | 2 +- .../query-suite/python-security-and-quality.qls.expected | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/python/ql/integration-tests/query-suite/python-code-quality-extended.qls.expected b/python/ql/integration-tests/query-suite/python-code-quality-extended.qls.expected index 960972c508c8..bb44ee105b58 100644 --- a/python/ql/integration-tests/query-suite/python-code-quality-extended.qls.expected +++ b/python/ql/integration-tests/query-suite/python-code-quality-extended.qls.expected @@ -6,7 +6,7 @@ ql/python/ql/src/Classes/InitCallsSubclass/InitCallsSubclassMethod.ql ql/python/ql/src/Classes/MissingCallToDel.ql ql/python/ql/src/Classes/MissingCallToInit.ql ql/python/ql/src/Classes/MutatingDescriptor.ql -ql/python/ql/src/Classes/SubclassShadowing.ql +ql/python/ql/src/Classes/SubclassShadowing/SubclassShadowing.ql ql/python/ql/src/Classes/SuperclassDelCalledMultipleTimes.ql ql/python/ql/src/Classes/SuperclassInitCalledMultipleTimes.ql ql/python/ql/src/Classes/WrongNameForArgumentInClassInstantiation.ql diff --git a/python/ql/integration-tests/query-suite/python-code-quality.qls.expected b/python/ql/integration-tests/query-suite/python-code-quality.qls.expected index 960972c508c8..bb44ee105b58 100644 --- a/python/ql/integration-tests/query-suite/python-code-quality.qls.expected +++ b/python/ql/integration-tests/query-suite/python-code-quality.qls.expected @@ -6,7 +6,7 @@ ql/python/ql/src/Classes/InitCallsSubclass/InitCallsSubclassMethod.ql ql/python/ql/src/Classes/MissingCallToDel.ql ql/python/ql/src/Classes/MissingCallToInit.ql ql/python/ql/src/Classes/MutatingDescriptor.ql -ql/python/ql/src/Classes/SubclassShadowing.ql +ql/python/ql/src/Classes/SubclassShadowing/SubclassShadowing.ql ql/python/ql/src/Classes/SuperclassDelCalledMultipleTimes.ql ql/python/ql/src/Classes/SuperclassInitCalledMultipleTimes.ql ql/python/ql/src/Classes/WrongNameForArgumentInClassInstantiation.ql diff --git a/python/ql/integration-tests/query-suite/python-security-and-quality.qls.expected b/python/ql/integration-tests/query-suite/python-security-and-quality.qls.expected index 170d9f442f92..8799990b86e1 100644 --- a/python/ql/integration-tests/query-suite/python-security-and-quality.qls.expected +++ b/python/ql/integration-tests/query-suite/python-security-and-quality.qls.expected @@ -11,7 +11,7 @@ ql/python/ql/src/Classes/MutatingDescriptor.ql ql/python/ql/src/Classes/OverwritingAttributeInSuperClass.ql ql/python/ql/src/Classes/PropertyInOldStyleClass.ql ql/python/ql/src/Classes/SlotsInOldStyleClass.ql -ql/python/ql/src/Classes/SubclassShadowing.ql +ql/python/ql/src/Classes/SubclassShadowing/SubclassShadowing.ql ql/python/ql/src/Classes/SuperInOldStyleClass.ql ql/python/ql/src/Classes/SuperclassDelCalledMultipleTimes.ql ql/python/ql/src/Classes/SuperclassInitCalledMultipleTimes.ql From 71a6b22815ef97b581de675470a6a128fe922667 Mon Sep 17 00:00:00 2001 From: Joe Farebrother Date: Thu, 31 Jul 2025 06:05:25 +0100 Subject: [PATCH 7/9] Update python/ql/src/Classes/SubclassShadowing/examples/SubclassShadowingBad.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Classes/SubclassShadowing/examples/SubclassShadowingBad.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ql/src/Classes/SubclassShadowing/examples/SubclassShadowingBad.py b/python/ql/src/Classes/SubclassShadowing/examples/SubclassShadowingBad.py index 4699b58d7e4c..00a221760b4c 100644 --- a/python/ql/src/Classes/SubclassShadowing/examples/SubclassShadowingBad.py +++ b/python/ql/src/Classes/SubclassShadowing/examples/SubclassShadowingBad.py @@ -2,7 +2,7 @@ class A: def __init__(self): self._foo = 3 -class B: +class B(A): # BAD: _foo is shadowed by attribute A._foo def _foo(self): return 2 From 79d1deb28d0927a51b9909a24c59778dcd4bc325 Mon Sep 17 00:00:00 2001 From: Joe Farebrother Date: Thu, 31 Jul 2025 06:05:48 +0100 Subject: [PATCH 8/9] Update python/ql/src/Classes/SubclassShadowing/SubclassShadowing.ql Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- python/ql/src/Classes/SubclassShadowing/SubclassShadowing.ql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ql/src/Classes/SubclassShadowing/SubclassShadowing.ql b/python/ql/src/Classes/SubclassShadowing/SubclassShadowing.ql index eab8520857b2..39a320f75ac6 100644 --- a/python/ql/src/Classes/SubclassShadowing/SubclassShadowing.ql +++ b/python/ql/src/Classes/SubclassShadowing/SubclassShadowing.ql @@ -64,7 +64,7 @@ where if isProperty(shadowed) then // it's not a setter, so it's a read-only property - extra = " (read-only property may cause an error if written to in the superclass.)" + extra = " (read-only property may cause an error if written to in the superclass)" else extra = "" ) select shadowed, "This method is shadowed by $@ in superclass $@." + extra, write, From bc60914ed7edc9ffd1065b8c64288f175c6264ad Mon Sep 17 00:00:00 2001 From: Joe Farebrother Date: Fri, 1 Aug 2025 12:37:51 +0100 Subject: [PATCH 9/9] Update test output --- .../Classes/subclass-shadowing/SubclassShadowing.expected | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ql/test/query-tests/Classes/subclass-shadowing/SubclassShadowing.expected b/python/ql/test/query-tests/Classes/subclass-shadowing/SubclassShadowing.expected index 3852b977a22f..5f5513ae9906 100644 --- a/python/ql/test/query-tests/Classes/subclass-shadowing/SubclassShadowing.expected +++ b/python/ql/test/query-tests/Classes/subclass-shadowing/SubclassShadowing.expected @@ -1,2 +1,2 @@ | subclass_shadowing.py:11:5:11:21 | Function shadow | This method is shadowed by $@ in superclass $@. | subclass_shadowing.py:7:9:7:19 | ControlFlowNode for Attribute | attribute shadow | subclass_shadowing.py:4:1:4:11 | Class Base | Base | -| subclass_shadowing.py:41:5:41:18 | Function foo | This method is shadowed by $@ in superclass $@. (read-only property may cause an error if written to.) | subclass_shadowing.py:35:9:35:16 | ControlFlowNode for Attribute | attribute foo | subclass_shadowing.py:33:1:33:12 | Class Base3 | Base3 | +| subclass_shadowing.py:41:5:41:18 | Function foo | This method is shadowed by $@ in superclass $@. (read-only property may cause an error if written to in the superclass.) | subclass_shadowing.py:35:9:35:16 | ControlFlowNode for Attribute | attribute foo | subclass_shadowing.py:33:1:33:12 | Class Base3 | Base3 |