Modernize attribute shadows subclass, Add cases for properties

This commit is contained in:
Joe Farebrother
2025-07-29 14:16:16 +01:00
parent f432cf9c4d
commit af94ebe1fc
3 changed files with 81 additions and 37 deletions

View File

@@ -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
) {
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()
) 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())
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())
)
}
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"
predicate isProperty(Function prop) {
prop.getADecorator() = API::builtin("property").asSource().asExpr()
}
predicate shadowedBySuperclass(
Class cls, Class superclass, DataFlow::AttrWrite write, Function shadowed
) {
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
// 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 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()