Skip to content

Commit b333c6a

Browse files
authored
Merge pull request #2106 from asger-semmle/call-graph-3
JS: Call graph changes
2 parents d501316 + 04ee483 commit b333c6a

File tree

26 files changed

+567
-317
lines changed

26 files changed

+567
-317
lines changed

javascript/ql/src/LanguageFeatures/IllegalInvocation.ql

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,5 +61,7 @@ where
6161
forex(DataFlow::InvokeNode cs2, Function otherCallee |
6262
cs2.getInvokeExpr() = cs.getInvokeExpr() and otherCallee = cs2.getACallee() |
6363
illegalInvocation(cs, otherCallee, _, _)
64-
)
64+
) and
65+
// require that all callees are known
66+
not cs.isIncomplete()
6567
select cs, "Illegal invocation of $@ " + how + ".", callee, calleeDesc

javascript/ql/src/meta/analysis-quality/CallGraphQuality.qll

Lines changed: 2 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -47,97 +47,6 @@ class RelevantFunction extends Function {
4747
}
4848
}
4949

50-
/**
51-
* Holds if `name` is a the name of an external module.
52-
*/
53-
predicate isExternalLibrary(string name) {
54-
// Mentioned in package.json
55-
any(Dependency dep).info(name, _) or
56-
// Node.js built-in
57-
name = "assert" or
58-
name = "async_hooks" or
59-
name = "child_process" or
60-
name = "cluster" or
61-
name = "crypto" or
62-
name = "dns" or
63-
name = "domain" or
64-
name = "events" or
65-
name = "fs" or
66-
name = "http" or
67-
name = "http2" or
68-
name = "https" or
69-
name = "inspector" or
70-
name = "net" or
71-
name = "os" or
72-
name = "path" or
73-
name = "perf_hooks" or
74-
name = "process" or
75-
name = "punycode" or
76-
name = "querystring" or
77-
name = "readline" or
78-
name = "repl" or
79-
name = "stream" or
80-
name = "string_decoder" or
81-
name = "timer" or
82-
name = "tls" or
83-
name = "trace_events" or
84-
name = "tty" or
85-
name = "dgram" or
86-
name = "url" or
87-
name = "util" or
88-
name = "v8" or
89-
name = "vm" or
90-
name = "worker_threads" or
91-
name = "zlib"
92-
}
93-
94-
/**
95-
* Holds if the global variable `name` is defined externally.
96-
*/
97-
predicate isExternalGlobal(string name) {
98-
exists(ExternalGlobalDecl decl | decl.getName() = name)
99-
or
100-
exists(Dependency dep |
101-
// If name is never assigned anywhere, and it coincides with a dependency,
102-
// it's most likely coming from there.
103-
dep.info(name, _) and
104-
not exists(Assignment assign | assign.getLhs().(GlobalVarAccess).getName() = name)
105-
)
106-
or
107-
name = "_"
108-
}
109-
110-
/**
111-
* Gets a node that was derived from an import of `moduleName`.
112-
*
113-
* This is a rough approximation as it follows all property reads, invocations,
114-
* and callbacks, so some of these might refer to internal objects.
115-
*
116-
* Additionally, we don't recognize when a project imports another file in the
117-
* same project using its module name (for example import "vscode" from inside the vscode project).
118-
*/
119-
SourceNode externalNode() {
120-
exists(string moduleName |
121-
result = moduleImport(moduleName) and
122-
isExternalLibrary(moduleName)
123-
)
124-
or
125-
exists(string name |
126-
result = globalVarRef(name) and
127-
isExternalGlobal(name)
128-
)
129-
or
130-
result = DOM::domValueRef()
131-
or
132-
result = jquery()
133-
or
134-
result = externalNode().getAPropertyRead()
135-
or
136-
result = externalNode().getAnInvocation()
137-
or
138-
result = externalNode().(InvokeNode).getCallback(_).getParameter(_)
139-
}
140-
14150
/**
14251
* Gets a data flow node that can be resolved to a function, usually a callback.
14352
*
@@ -167,7 +76,7 @@ SourceNode nodeLeadingToInvocation() {
16776
result.flowsTo(arg)
16877
)
16978
or
170-
exists(AdditionalPartialInvokeNode invoke, Node arg |
79+
exists(PartialInvokeNode invoke, Node arg |
17180
invoke.isPartialArgument(arg, _, _) and
17281
result.flowsTo(arg)
17382
)
@@ -192,49 +101,15 @@ class ResolvableCall extends RelevantInvoke {
192101
}
193102
}
194103

195-
/**
196-
* A call site that is believed to call an external function.
197-
*/
198-
class ExternalCall extends RelevantInvoke {
199-
ExternalCall() {
200-
not this instanceof ResolvableCall and // avoid double counting
201-
(
202-
// Call to modelled external library
203-
this = externalNode()
204-
or
205-
// 'require' call or similar
206-
this = moduleImport(_)
207-
or
208-
// Resolved to externs file
209-
exists(this.(InvokeNode).getACallee(1))
210-
or
211-
// Modelled as taint step but isn't from an NPM module, for example, `substring` or `push`.
212-
exists(TaintTracking::AdditionalTaintStep step |
213-
step.step(_, this)
214-
or
215-
step.step(this.getAnArgument(), _)
216-
)
217-
)
218-
}
219-
}
220-
221104
/**
222105
* A call site that could not be resolved.
223106
*/
224107
class UnresolvableCall extends RelevantInvoke {
225108
UnresolvableCall() {
226-
not this instanceof ResolvableCall and
227-
not this instanceof ExternalCall
109+
not this instanceof ResolvableCall
228110
}
229111
}
230112

231-
/**
232-
* A call that is believed to call a function within the same project.
233-
*/
234-
class NonExternalCall extends RelevantInvoke {
235-
NonExternalCall() { not this instanceof ExternalCall }
236-
}
237-
238113
/**
239114
* A function with at least one call site.
240115
*/

javascript/ql/src/semmle/javascript/Closure.qll

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,4 +248,25 @@ module Closure {
248248
DataFlow::SourceNode moduleImport(string moduleName) {
249249
getClosureNamespaceFromSourceNode(result) = moduleName
250250
}
251+
252+
/**
253+
* A call to `goog.bind`, as a partial function invocation.
254+
*/
255+
private class BindCall extends DataFlow::PartialInvokeNode::Range, DataFlow::CallNode {
256+
BindCall() { this = moduleImport("goog.bind").getACall() }
257+
258+
override predicate isPartialArgument(DataFlow::Node callback, DataFlow::Node argument, int index) {
259+
index >= 0 and
260+
callback = getArgument(0) and
261+
argument = getArgument(index + 2)
262+
}
263+
264+
override DataFlow::SourceNode getBoundFunction(DataFlow::Node callback, int boundArgs) {
265+
boundArgs = getNumArgument() - 2 and
266+
callback = getArgument(0) and
267+
result = this
268+
}
269+
270+
override DataFlow::Node getBoundReceiver() { result = getArgument(1) }
271+
}
251272
}

javascript/ql/src/semmle/javascript/dataflow/Configuration.qll

Lines changed: 0 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -432,18 +432,6 @@ abstract class AdditionalSink extends DataFlow::Node {
432432
predicate isSinkFor(Configuration cfg, FlowLabel lbl) { none() }
433433
}
434434

435-
/**
436-
* An invocation that is modeled as a partial function application.
437-
*
438-
* This contributes additional argument-passing flow edges that should be added to all data flow configurations.
439-
*/
440-
abstract class AdditionalPartialInvokeNode extends DataFlow::InvokeNode {
441-
/**
442-
* Holds if `argument` is passed as argument `index` to the function in `callback`.
443-
*/
444-
abstract predicate isPartialArgument(DataFlow::Node callback, DataFlow::Node argument, int index);
445-
}
446-
447435
/**
448436
* Additional flow step to model flow from import specifiers into the SSA variable
449437
* corresponding to the imported variable.
@@ -457,45 +445,6 @@ private class FlowStepThroughImport extends AdditionalFlowStep, DataFlow::ValueN
457445
}
458446
}
459447

460-
/**
461-
* A partial call through the built-in `Function.prototype.bind`.
462-
*/
463-
private class BindPartialCall extends AdditionalPartialInvokeNode, DataFlow::MethodCallNode {
464-
BindPartialCall() { getMethodName() = "bind" }
465-
466-
override predicate isPartialArgument(DataFlow::Node callback, DataFlow::Node argument, int index) {
467-
callback = getReceiver() and
468-
argument = getArgument(index + 1)
469-
}
470-
}
471-
472-
/**
473-
* A partial call through `_.partial`.
474-
*/
475-
private class LodashPartialCall extends AdditionalPartialInvokeNode {
476-
LodashPartialCall() { this = LodashUnderscore::member("partial").getACall() }
477-
478-
override predicate isPartialArgument(DataFlow::Node callback, DataFlow::Node argument, int index) {
479-
callback = getArgument(0) and
480-
argument = getArgument(index + 1)
481-
}
482-
}
483-
484-
/**
485-
* A partial call through `ramda.partial`.
486-
*/
487-
private class RamdaPartialCall extends AdditionalPartialInvokeNode {
488-
RamdaPartialCall() { this = DataFlow::moduleMember("ramda", "partial").getACall() }
489-
490-
override predicate isPartialArgument(DataFlow::Node callback, DataFlow::Node argument, int index) {
491-
callback = getArgument(0) and
492-
exists(DataFlow::ArrayCreationNode array |
493-
array.flowsTo(getArgument(1)) and
494-
argument = array.getElement(index)
495-
)
496-
}
497-
}
498-
499448
/**
500449
* Holds if there is a flow step from `pred` to `succ` described by `summary`
501450
* under configuration `cfg`.

javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
*/
2020

2121
import javascript
22+
private import internal.CallGraphs
2223

2324
module DataFlow {
2425
cached
@@ -117,14 +118,23 @@ module DataFlow {
117118
int getIntValue() { result = asExpr().getIntValue() }
118119

119120
/** Gets a function value that may reach this node. */
120-
FunctionNode getAFunctionValue() {
121-
result.getAstNode() = analyze().getAValue().(AbstractCallable).getFunction()
121+
final FunctionNode getAFunctionValue() {
122+
CallGraph::getAFunctionReference(result, 0).flowsTo(this)
123+
}
124+
125+
/** Gets a function value that may reach this node with the given `imprecision` level. */
126+
final FunctionNode getAFunctionValue(int imprecision) {
127+
CallGraph::getAFunctionReference(result, imprecision).flowsTo(this)
128+
}
129+
130+
/**
131+
* Gets a function value that may reach this node,
132+
* possibly derived from a partial function invocation.
133+
*/
134+
final FunctionNode getABoundFunctionValue(int boundArgs) {
135+
result = getAFunctionValue() and boundArgs = 0
122136
or
123-
exists(string name |
124-
GlobalAccessPath::isAssignedInUniqueFile(name) and
125-
GlobalAccessPath::fromRhs(result) = name and
126-
GlobalAccessPath::fromReference(this) = name
127-
)
137+
CallGraph::getABoundFunctionReference(result, boundArgs).flowsTo(this)
128138
}
129139

130140
/**

0 commit comments

Comments
 (0)