@@ -556,6 +556,75 @@ predicate runtimeJumpStep(Node nodeFrom, Node nodeTo) {
556556 nodeFrom .asCfgNode ( ) = param .getDefault ( ) and
557557 nodeTo .asCfgNode ( ) = param .getDefiningNode ( )
558558 )
559+ or
560+ // Enhanced global variable field access tracking
561+ globalVariableNestedFieldJumpStep ( nodeFrom , nodeTo )
562+ }
563+
564+ /** Helper predicate for `globalVariableNestedFieldJumpStep`. */
565+ pragma [ nomagic]
566+ private predicate globalVariableAttrPathRead (
567+ ModuleVariableNode globalVar , string accessPath , AttrRead r , string attrName
568+ ) {
569+ globalVariableAttrPathAtDepth ( globalVar , accessPath , r .getObject ( ) , _) and
570+ attrName = r .getAttributeName ( )
571+ }
572+
573+ /** Helper predicate for `globalVariableNestedFieldJumpStep`. */
574+ pragma [ nomagic]
575+ private predicate globalVariableAttrPathWrite (
576+ ModuleVariableNode globalVar , string accessPath , AttrWrite w , string attrName
577+ ) {
578+ globalVariableAttrPathAtDepth ( globalVar , accessPath , w .getObject ( ) , _) and
579+ attrName = w .getAttributeName ( )
580+ }
581+
582+ /**
583+ * Holds if there is a jump step from `nodeFrom` to `nodeTo` through global variable field access.
584+ * This supports tracking nested object field access through global variables like `app.obj.foo`.
585+ */
586+ pragma [ nomagic]
587+ private predicate globalVariableNestedFieldJumpStep ( Node nodeFrom , Node nodeTo ) {
588+ exists ( ModuleVariableNode globalVar , AttrWrite write , AttrRead read |
589+ // Match writes and reads on the same global variable attribute path
590+ exists ( string accessPath , string attrName |
591+ globalVariableAttrPathRead ( globalVar , accessPath , read , attrName ) and
592+ globalVariableAttrPathWrite ( globalVar , accessPath , write , attrName )
593+ ) and
594+ nodeFrom = write .getValue ( ) and
595+ nodeTo = read
596+ )
597+ }
598+
599+ /**
600+ * Maximum depth for global variable nested attribute access.
601+ * Depth 1 = globalVar.foo, depth 2 = globalVar.foo.bar, depth 3 = globalVar.foo.bar.baz, etc.
602+ */
603+ private int getMaxGlobalVariableDepth ( ) { result = 2 }
604+
605+ /**
606+ * Holds if `node` is an attribute access path starting from global variable `globalVar` at specific `depth`.
607+ */
608+ private predicate globalVariableAttrPathAtDepth (
609+ ModuleVariableNode globalVar , string accessPath , Node node , int depth
610+ ) {
611+ // Base case: Direct global variable access (depth 0)
612+ depth = 0 and
613+ // We use `globalVar` instead of `globalVar.getAWrite()` due to some weirdness with how
614+ // attribute writes are handled in the global scope (see `GlobalAttributeAssignmentAsAttrWrite`).
615+ node in [ globalVar .getARead ( ) , globalVar ] and
616+ accessPath = ""
617+ or
618+ exists ( Node obj , string attrName , string parentAccessPath , int parentDepth |
619+ node .( AttrRead ) .reads ( obj , attrName )
620+ or
621+ any ( AttrWrite aw ) .writes ( obj , attrName , node )
622+ |
623+ globalVariableAttrPathAtDepth ( globalVar , parentAccessPath , obj , parentDepth ) and
624+ accessPath = parentAccessPath + "." + attrName and
625+ depth = parentDepth + 1 and
626+ depth <= getMaxGlobalVariableDepth ( )
627+ )
559628}
560629
561630//--------
0 commit comments