diff --git a/python/ql/src/Classes/SubclassShadowing.ql b/python/ql/src/Classes/SubclassShadowing.ql index 6e915250a54..eab8520857b 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 caad71a9a31..3852b977a22 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 |