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 A
B()._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 A
B()._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 |