@@ -294,6 +294,30 @@ private DataFlowCallable viableLibraryCallable(DataFlowCall call) {
294294 )
295295}
296296
297+ /** Holds if there is a call like `receiver.extend(M)`. */
298+ pragma [ nomagic]
299+ private predicate extendCall ( DataFlow:: ExprNode receiver , Module m ) {
300+ exists ( DataFlow:: CallNode extendCall |
301+ extendCall .getMethodName ( ) = "extend" and
302+ exists ( DataFlow:: LocalSourceNode sourceNode | sourceNode .flowsTo ( extendCall .getArgument ( _) ) |
303+ selfInModule ( sourceNode .( SsaSelfDefinitionNode ) .getVariable ( ) , m ) or
304+ m = resolveConstantReadAccess ( sourceNode .asExpr ( ) .getExpr ( ) )
305+ ) and
306+ receiver = extendCall .getReceiver ( )
307+ )
308+ }
309+
310+ /** Holds if there is a call like `M.extend(N)` */
311+ pragma [ nomagic]
312+ private predicate extendCallModule ( Module m , Module n ) {
313+ exists ( DataFlow:: LocalSourceNode receiver , DataFlow:: ExprNode e |
314+ receiver .flowsTo ( e ) and extendCall ( e , n )
315+ |
316+ selfInModule ( receiver .( SsaSelfDefinitionNode ) .getVariable ( ) , m ) or
317+ m = resolveConstantReadAccess ( receiver .asExpr ( ) .getExpr ( ) )
318+ )
319+ }
320+
297321cached
298322private module Cached {
299323 cached
@@ -340,12 +364,29 @@ private module Cached {
340364 // def c.singleton; end # <- result
341365 // c.singleton # <- call
342366 // ```
367+ // or an `extend`ed instance, e.g.
368+ // ```rb
369+ // c = C.new
370+ // module M
371+ // def instance; end # <- result
372+ // end
373+ // c.extend M
374+ // c.instance # <- call
375+ // ```
343376 exists ( DataFlow:: Node receiver |
344377 methodCall ( call , receiver , method ) and
345378 receiver = trackSingletonMethodOnInstance ( result , method )
346379 )
347380 or
348381 // singleton method defined on a module
382+ // or an `extend`ed module, e.g.
383+ // ```rb
384+ // module M
385+ // def instance; end # <- result
386+ // end
387+ // M.extend(M)
388+ // M.instance # <- call
389+ // ```
349390 exists ( DataFlow:: Node sourceNode , Module m |
350391 flowsToMethodCall ( call , sourceNode , method ) and
351392 singletonMethodOnModule ( result , method , m )
@@ -664,6 +705,12 @@ private predicate flowsToSingletonMethodObject(
664705 * class << c
665706 * def m6; end # not included
666707 * end
708+ *
709+ * module M
710+ * def instance; end # included in `N` via `extend` call below
711+ * end
712+ * N.extend(M)
713+ * N.instance
667714 * ```
668715 */
669716pragma [ nomagic]
@@ -674,6 +721,11 @@ private predicate singletonMethodOnModule(MethodBase method, string name, Module
674721 )
675722 or
676723 flowsToSingletonMethodObject ( trackModuleAccess ( m ) , method , name )
724+ or
725+ exists ( Module other |
726+ extendCallModule ( m , other ) and
727+ method = lookupMethod ( other , name )
728+ )
677729}
678730
679731/**
@@ -700,6 +752,11 @@ private predicate singletonMethodOnModule(MethodBase method, string name, Module
700752 * class << c
701753 * def m6; end # included
702754 * end
755+ *
756+ * module M
757+ * def instance; end # included in `c` via `extend` call below
758+ * end
759+ * c.extend(M)
703760 * ```
704761 */
705762pragma [ nomagic]
@@ -708,6 +765,12 @@ predicate singletonMethodOnInstance(MethodBase method, string name, Expr object)
708765 not selfInModule ( object .( SelfVariableReadAccess ) .getVariable ( ) , _) and
709766 // cannot use `trackModuleAccess` because of negative recursion
710767 not exists ( resolveConstantReadAccess ( object ) )
768+ or
769+ exists ( DataFlow:: ExprNode receiver , Module other |
770+ extendCall ( receiver , other ) and
771+ object = receiver .getExprNode ( ) .getExpr ( ) and
772+ method = lookupMethod ( other , name )
773+ )
711774}
712775
713776/**
0 commit comments