Skip to content

Python: Modernise Superclass attribute shadows subclass method query #20142

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Next Next commit
Modernize attribute shadows subclass, Add cases for properties
  • Loading branch information
joefarebrother committed Jul 30, 2025
commit af94ebe1fc65229b2589b2eba93d8263340c1d47
66 changes: 44 additions & 22 deletions python/ql/src/Classes/SubclassShadowing.ql
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
Classes/SubclassShadowing.ql
query: Classes/SubclassShadowing.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql
Original file line number Diff line number Diff line change
@@ -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
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