@@ -172,6 +172,8 @@ module TypeTrackingInput implements Shared::TypeTrackingInput<Location> {
172172 /** Holds if there is a level step from `nodeFrom` to `nodeTo`, which does not depend on the call graph. */
173173 predicate levelStepNoCall ( Node nodeFrom , LocalSourceNode nodeTo ) {
174174 TypeTrackerSummaryFlow:: levelStepNoCall ( nodeFrom , nodeTo )
175+ or
176+ localFieldStep ( nodeFrom , nodeTo )
175177 }
176178
177179 /**
@@ -317,6 +319,51 @@ module TypeTrackingInput implements Shared::TypeTrackingInput<Location> {
317319 )
318320 }
319321
322+ /**
323+ * Holds if `ref` accesses attribute `attr` of `self`, where `self` is the first
324+ * parameter of an instance method of `cls` (i.e. an access of the form `self.attr`).
325+ *
326+ * Static methods and class methods are excluded, since their first parameter is not a
327+ * `self` instance reference.
328+ */
329+ private predicate selfAttrRef ( Class cls , string attr , DataFlowPublic:: AttrRef ref ) {
330+ exists ( Function method , Name selfUse |
331+ method = cls .getAMethod ( ) and
332+ not DataFlowDispatch:: isStaticmethod ( method ) and
333+ not DataFlowDispatch:: isClassmethod ( method ) and
334+ selfUse .getVariable ( ) = method .getArg ( 0 ) .( Name ) .getVariable ( ) and
335+ ref .getObject ( ) .asCfgNode ( ) .getNode ( ) = selfUse and
336+ ref .mayHaveAttributeName ( attr )
337+ )
338+ }
339+
340+ /**
341+ * Holds if `nodeFrom` is written to attribute `self.attr` in some instance method of a
342+ * class, and `nodeTo` reads attribute `self.attr` in some (possibly different) instance
343+ * method of the same class.
344+ *
345+ * This models flow through instance attributes (`self.foo`): a value stored into
346+ * `self.foo` in one method can be read from `self.foo` in another method. Type-tracking
347+ * handles the store and read steps via `AttrWrite`/`AttrRead`, but on its own it cannot
348+ * relate the `self` of the writing method to the `self` of the reading method. Following
349+ * the approach used for Ruby and JavaScript, we model this directly as a level step from
350+ * the written value to the read reference, for any pair of methods on the class (not
351+ * just from `__init__`).
352+ *
353+ * This is an over-approximation: it is instance-insensitive (it does not distinguish
354+ * between different instances of the same class) and order-insensitive (it does not
355+ * require the write to happen before the read), matching the precision of
356+ * instance-attribute handling for Ruby and JavaScript.
357+ */
358+ private predicate localFieldStep ( Node nodeFrom , LocalSourceNode nodeTo ) {
359+ exists ( Class cls , string attr , DataFlowPublic:: AttrWrite write , DataFlowPublic:: AttrRead read |
360+ selfAttrRef ( cls , attr , write ) and
361+ nodeFrom = write .getValue ( ) and
362+ selfAttrRef ( cls , attr , read ) and
363+ nodeTo = read
364+ )
365+ }
366+
320367 /**
321368 * Holds if data can flow from `node1` to `node2` in a way that discards call contexts.
322369 */
0 commit comments