diff --git a/javascript/ql/lib/semmle/javascript/dataflow/internal/CallGraphs.qll b/javascript/ql/lib/semmle/javascript/dataflow/internal/CallGraphs.qll index 7e55944038bf..541e3a6f3e90 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/internal/CallGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/internal/CallGraphs.qll @@ -279,6 +279,20 @@ module CallGraph { StepSummary::step(getAnAllocationSiteRef(node), result, objectWithMethodsStep()) } + /** + * Holds if `function` flows to a property of `host` via non-local data flow. + */ + pragma[nomagic] + private predicate complexMethodInstallation( + DataFlow::SourceNode host, DataFlow::FunctionNode function + ) { + not function = getAMethodOnObject(_) and + exists(DataFlow::TypeTracker t | + getAFunctionReference(function, 0, t) = host.getAPropertySource() and + t.start() // require call bit to be false + ) + } + /** * Holds if `pred` is assumed to flow to `succ` because a method is stored on an object that is assumed * to be the receiver of calls to that method. @@ -291,9 +305,18 @@ module CallGraph { */ cached predicate impliedReceiverStep(DataFlow::SourceNode pred, DataFlow::SourceNode succ) { + // To avoid double-recursion, we handle either complex flow for the host object, or for the function, but not both. exists(DataFlow::SourceNode host | + // Complex flow for the host object pred = getAnAllocationSiteRef(host) and succ = getAMethodOnObject(host).getReceiver() + or + // Complex flow for the function + exists(DataFlow::FunctionNode function | + complexMethodInstallation(host, function) and + pred = host and + succ = function.getReceiver() + ) ) } } diff --git a/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/implied-receiver.js b/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/implied-receiver.js new file mode 100644 index 000000000000..22638f35b566 --- /dev/null +++ b/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/implied-receiver.js @@ -0,0 +1,19 @@ +import 'dummy'; + +function fooFactoryFactory() { + return function fooFactory() { + return function foo() { + /** calls:F.member */ + this.member(); + } + } +} + +function F() { + this.foo = fooFactoryFactory()(); +} + +/** name:F.member */ +F.prototype.member = function() { + return 42; +};