@@ -167,11 +167,17 @@ module TypeTrackingInput implements Shared::TypeTrackingInput<Location> {
167167 }
168168
169169 /** Holds if there is a level step from `nodeFrom` to `nodeTo`, which may depend on the call graph. */
170- predicate levelStepCall ( Node nodeFrom , LocalSourceNode nodeTo ) { none ( ) }
170+ predicate levelStepCall ( Node nodeFrom , LocalSourceNode nodeTo ) {
171+ instanceFieldStep ( nodeFrom , nodeTo )
172+ or
173+ inheritedFieldStep ( nodeFrom , nodeTo )
174+ }
171175
172176 /** Holds if there is a level step from `nodeFrom` to `nodeTo`, which does not depend on the call graph. */
173177 predicate levelStepNoCall ( Node nodeFrom , LocalSourceNode nodeTo ) {
174178 TypeTrackerSummaryFlow:: levelStepNoCall ( nodeFrom , nodeTo )
179+ or
180+ localFieldStep ( nodeFrom , nodeTo )
175181 }
176182
177183 /**
@@ -317,6 +323,133 @@ module TypeTrackingInput implements Shared::TypeTrackingInput<Location> {
317323 )
318324 }
319325
326+ /**
327+ * Holds if `ref` accesses attribute `attr` of `self`, where `self` is the first
328+ * parameter of an instance method of `cls` (i.e. an access of the form `self.attr`).
329+ *
330+ * Static methods and class methods are excluded, since their first parameter is not a
331+ * `self` instance reference.
332+ */
333+ private predicate selfAttrRef ( Class cls , string attr , DataFlowPublic:: AttrRef ref ) {
334+ exists ( Function method , Name selfUse |
335+ method = cls .getAMethod ( ) and
336+ not DataFlowDispatch:: isStaticmethod ( method ) and
337+ not DataFlowDispatch:: isClassmethod ( method ) and
338+ selfUse .getVariable ( ) = method .getArg ( 0 ) .( Name ) .getVariable ( ) and
339+ ref .getObject ( ) .asCfgNode ( ) .getNode ( ) = selfUse and
340+ ref .mayHaveAttributeName ( attr )
341+ )
342+ }
343+
344+ /**
345+ * Holds if `read` reads attribute `attr` from an instance of `cls`, where the instance
346+ * is referred to from outside the methods of `cls` (i.e. an access of the form
347+ * `instance.attr`, where `instance` is a reference to an instance of `cls`).
348+ *
349+ * This complements `selfAttrRef`, which only handles `self.attr` accesses inside the
350+ * methods of `cls`. Unlike `selfAttrRef`, this depends on the call graph (via
351+ * `classInstanceTracker`), so steps using it must be reported as `levelStepCall`.
352+ */
353+ private predicate instanceAttrRead ( Class cls , string attr , DataFlowPublic:: AttrRead read ) {
354+ read .getObject ( ) = DataFlowDispatch:: classInstanceTracker ( cls ) and
355+ read .mayHaveAttributeName ( attr )
356+ }
357+
358+ /**
359+ * Holds if `nodeFrom` is written to attribute `self.attr` in some instance method of a
360+ * class, and `nodeTo` reads attribute `self.attr` in some (possibly different) instance
361+ * method of the same class.
362+ *
363+ * This models flow through instance attributes (`self.foo`): a value stored into
364+ * `self.foo` in one method can be read from `self.foo` in another method. Type-tracking
365+ * handles the store and read steps via `AttrWrite`/`AttrRead`, but on its own it cannot
366+ * relate the `self` of the writing method to the `self` of the reading method. Following
367+ * the approach used for Ruby and JavaScript, we model this directly as a level step from
368+ * the written value to the read reference, for any pair of methods on the class (not
369+ * just from `__init__`).
370+ *
371+ * Flow across the class hierarchy (a write in one class observed in a method inherited
372+ * from, or contributed by, a related class) is handled separately by
373+ * `inheritedFieldStep`, because resolving superclasses depends on the call graph and so
374+ * cannot appear in this call-graph-independent step.
375+ *
376+ * This is an over-approximation: it is instance-insensitive (it does not distinguish
377+ * between different instances of the same class) and order-insensitive (it does not
378+ * require the write to happen before the read), matching the precision of
379+ * instance-attribute handling for Ruby and JavaScript.
380+ */
381+ private predicate localFieldStep ( Node nodeFrom , LocalSourceNode nodeTo ) {
382+ exists ( Class cls , string attr , DataFlowPublic:: AttrWrite write , DataFlowPublic:: AttrRead read |
383+ selfAttrRef ( cls , attr , write ) and
384+ nodeFrom = write .getValue ( ) and
385+ selfAttrRef ( cls , attr , read ) and
386+ nodeTo = read
387+ )
388+ }
389+
390+ /**
391+ * Holds if `nodeFrom` is written to attribute `self.attr` in an instance method of one
392+ * class, and `nodeTo` reads attribute `self.attr` in an instance method of a different
393+ * class that is related to it by inheritance (one is a transitive superclass of the
394+ * other).
395+ *
396+ * This is the cross-hierarchy counterpart of `localFieldStep`: at runtime the receiver
397+ * of both methods may be an instance of the more-derived class, whose behaviour is made
398+ * up of the methods it declares together with those inherited from all of its ancestors.
399+ * It therefore models the common pattern of a base class storing `self.attr` that a
400+ * subclass reads, and vice versa. Resolving the superclass relationship depends on the
401+ * call graph (via `getADirectSuperclass`), so this step is reported as `levelStepCall`
402+ * rather than `levelStepNoCall`.
403+ *
404+ * Like `localFieldStep`, this is an over-approximation: it is both instance-insensitive
405+ * and order-insensitive.
406+ */
407+ private predicate inheritedFieldStep ( Node nodeFrom , LocalSourceNode nodeTo ) {
408+ exists (
409+ Class writeCls , Class readCls , string attr , DataFlowPublic:: AttrWrite write ,
410+ DataFlowPublic:: AttrRead read
411+ |
412+ selfAttrRef ( writeCls , attr , write ) and
413+ nodeFrom = write .getValue ( ) and
414+ selfAttrRef ( readCls , attr , read ) and
415+ nodeTo = read and
416+ writeCls != readCls and
417+ (
418+ writeCls = DataFlowDispatch:: getADirectSuperclass * ( readCls )
419+ or
420+ readCls = DataFlowDispatch:: getADirectSuperclass * ( writeCls )
421+ )
422+ )
423+ }
424+
425+ /**
426+ * Holds if `nodeFrom` is written to attribute `self.attr` in some instance method of a
427+ * class, and `nodeTo` reads attribute `attr` from an instance of that class (or a
428+ * subclass of it) outside its methods (e.g. `instance.attr`).
429+ *
430+ * This is the cross-instance counterpart of `localFieldStep`: it relates a write of
431+ * `self.attr` inside a class to a read of `attr` on a reference to an instance of that
432+ * class or one of its subclasses. Identifying instances relies on the call graph (via
433+ * `classInstanceTracker`), so this step is reported as `levelStepCall` rather than
434+ * `levelStepNoCall`. The write may occur in the instance's own class or in any of its
435+ * superclasses, since those methods are inherited.
436+ *
437+ * Like `localFieldStep`, this is an over-approximation: it is both instance-insensitive
438+ * and order-insensitive.
439+ */
440+ private predicate instanceFieldStep ( Node nodeFrom , LocalSourceNode nodeTo ) {
441+ exists (
442+ Class writeCls , Class instanceCls , string attr , DataFlowPublic:: AttrWrite write ,
443+ DataFlowPublic:: AttrRead read
444+ |
445+ selfAttrRef ( writeCls , attr , write ) and
446+ nodeFrom = write .getValue ( ) and
447+ instanceAttrRead ( instanceCls , attr , read ) and
448+ nodeTo = read and
449+ writeCls = DataFlowDispatch:: getADirectSuperclass * ( instanceCls )
450+ )
451+ }
452+
320453 /**
321454 * Holds if data can flow from `node1` to `node2` in a way that discards call contexts.
322455 */
0 commit comments