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 | pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy