Skip to content

Commit 3ced5c9

Browse files
committed
JS: Resolve first N tokens instead of constructing each prefix
1 parent 772681d commit 3ced5c9

File tree

2 files changed

+76
-100
lines changed

2 files changed

+76
-100
lines changed

javascript/ql/lib/semmle/javascript/frameworks/data/internal/Impl.qll

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,9 @@ predicate isPackageUsed(string package) {
3434

3535
/** Holds if `global` is a global variable referenced via a the `global` package in a CSV row. */
3636
private predicate isRelevantGlobal(string global) {
37-
exists(AccessPathToken token |
38-
isRelevantPath("global", "", token) and
37+
exists(AccessPath path, AccessPathToken token |
38+
isRelevantFullPath("global", "", path) and
39+
token = path.getToken(0) and
3940
token.getName() = "Member" and
4041
global = token.getAnArgument()
4142
)
@@ -65,20 +66,21 @@ private API::Node getGlobalNode(string globalName) {
6566
result = any(GlobalApiEntryPoint e | e.getGlobal() = globalName).getNode()
6667
}
6768

68-
/** Gets a JavaScript-specific interpretation of the given `(package, type, path)` tuple. */
69-
API::Node getExtraNodeFromPath(string package, string type, string path) {
69+
/** Gets a JavaScript-specific interpretation of the `(package, type, path)` tuple after resolving the first `n` access path tokens. */
70+
bindingset[package, type, path]
71+
API::Node getExtraNodeFromPath(string package, string type, AccessPath path, int n) {
7072
// Global variable accesses is via the 'global' package
71-
exists(string globalName, AccessPathToken token |
72-
result = getGlobalNode(globalName) and
73+
exists(AccessPathToken token |
7374
package = getAPackageAlias("global") and
7475
type = "" and
75-
path = token and
76+
token = path.getToken(0) and
7677
token.getName() = "Member" and
77-
token.getAnArgument() = globalName
78+
result = getGlobalNode(token.getAnArgument()) and
79+
n = 1
7880
)
7981
or
8082
// Access instance of a type based on type annotations
81-
path = "" and
83+
n = 0 and
8284
result = API::Node::ofType(getAPackageAlias(package), type)
8385
}
8486

javascript/ql/lib/semmle/javascript/frameworks/data/internal/Shared.qll

Lines changed: 65 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -258,36 +258,30 @@ predicate isRelevantFullPath(string package, string type, string path) {
258258
}
259259

260260
/**
261-
* Holds if `path` or some suffix thereof is used with the `package,type` combination in some CSV row.
261+
* A string that occurs as an access path (either identifying or input/output spec)
262+
* which might be relevant for this database.
262263
*/
263-
pragma[nomagic]
264-
predicate isRelevantPath(string package, string type, string path) {
265-
exists(string fullPath |
266-
isRelevantFullPath(package, type, fullPath) and
267-
path = fullPath.prefix([0, fullPath.indexOf("."), fullPath.length()])
268-
)
269-
}
270-
271-
/** Holds if `path` has the form `basePath.token` where `token` is a single token. */
272-
bindingset[path]
273-
private predicate decomposePath(string path, string basePath, string token) {
274-
token = max(int n | | path.splitAt(".", n) order by n) and
275-
(
276-
basePath = path.prefix(path.length() - token.length() - 1)
264+
class AccessPath extends string {
265+
AccessPath() {
266+
isRelevantFullPath(_, _, this)
277267
or
278-
token = path and
279-
basePath = ""
280-
)
281-
}
268+
exists(string package | isRelevantPackage(package) |
269+
summaryModel(package, _, _, this, _, _) or
270+
summaryModel(package, _, _, _, this, _)
271+
)
272+
}
282273

283-
/**
284-
* Gets the result of appending `token` onto `path`.
285-
*
286-
* Only has a result for identifying access paths relevant for `package;type`.
287-
*/
288-
private string appendToken(string package, string type, string path, string token) {
289-
isRelevantPath(package, type, result) and
290-
decomposePath(result, path, token)
274+
/** Gets the `n`th token on the access path as a string. */
275+
string getRawToken(int n) {
276+
this != "" and // The empty path should have zero tokens, not a single empty token
277+
result = this.splitAt(".", n)
278+
}
279+
280+
/** Gets the `n`th token on the access path. */
281+
AccessPathToken getToken(int n) { result = getRawToken(n) }
282+
283+
/** Gets the number of tokens on the path. */
284+
int getNumToken() { result = count(int n | exists(getRawToken(n))) }
291285
}
292286

293287
/**
@@ -344,124 +338,104 @@ private predicate invocationMatchesCallSiteFilter(API::InvokeNode invoke, Access
344338
}
345339

346340
/**
347-
* Gets the API node identified by the given `(package, type, path)` tuple.
341+
* Gets the API node identified by the first `n` tokens of `path` in the given `(package, type, path)` tuple.
348342
*/
349343
pragma[nomagic]
350-
API::Node getNodeFromPath(string package, string type, string path) {
351-
isRelevantPath(package, type, path) and
344+
API::Node getNodeFromPath(string package, string type, AccessPath path, int n) {
345+
isRelevantFullPath(package, type, path) and
352346
(
353347
type = "" and
354-
path = "" and
348+
n = 0 and
355349
result = API::moduleImport(package)
356350
or
357-
path = "" and
358-
exists(string package2, string type2, string path2 |
351+
n = 0 and
352+
exists(string package2, string type2, AccessPath path2 |
359353
typeModel(package, type, package2, type2, path2) and
360-
result = getNodeFromPath(package2, type2, path2)
354+
result = getNodeFromPath(package2, type2, path2, path2.getNumToken())
361355
)
362356
or
363357
// Language-specific cases, such as handling of global variables
364-
result = Impl::getExtraNodeFromPath(package, type, path)
358+
result = Impl::getExtraNodeFromPath(package, type, path, n)
365359
)
366360
or
367-
exists(string basePath, AccessPathToken token |
368-
result = getSuccessorFromNode(getNodeFromPath(package, type, basePath), token) and
369-
path = appendToken(package, type, basePath, token)
370-
)
361+
result = getSuccessorFromNode(getNodeFromPath(package, type, path, n - 1), path.getToken(n - 1))
371362
or
372363
// Similar to the other recursive case, but where the path may have stepped through one or more call-site filters
373-
exists(string basePath, AccessPathToken token |
374-
result = getSuccessorFromInvoke(getInvocationFromPath(package, type, basePath), token) and
375-
path = appendToken(package, type, basePath, token)
376-
)
364+
result =
365+
getSuccessorFromInvoke(getInvocationFromPath(package, type, path, n - 1), path.getToken(n - 1))
366+
}
367+
368+
/** Gets the node identified by the given `(package, type, path)` tuple. */
369+
API::Node getNodeFromPath(string package, string type, AccessPath path) {
370+
result = getNodeFromPath(package, type, path, path.getNumToken())
377371
}
378372

379373
/**
380374
* Gets an invocation identified by the given `(package, type, path)` tuple.
381375
*
382376
* Unlike `getNodeFromPath`, the `path` may end with one or more call-site filters.
383377
*/
384-
API::InvokeNode getInvocationFromPath(string package, string type, string path) {
385-
result = getNodeFromPath(package, type, path).getAnInvocation()
378+
API::InvokeNode getInvocationFromPath(string package, string type, AccessPath path, int n) {
379+
result = getNodeFromPath(package, type, path, n).getAnInvocation()
386380
or
387-
exists(string basePath, AccessPathToken token |
388-
result = getInvocationFromPath(package, type, basePath) and
389-
path = appendToken(package, type, basePath, token) and
390-
invocationMatchesCallSiteFilter(result, token)
391-
)
381+
result = getInvocationFromPath(package, type, path, n - 1) and
382+
invocationMatchesCallSiteFilter(result, path.getToken(n - 1))
383+
}
384+
385+
/** Gets an invocation identified by the given `(package, type, path)` tuple. */
386+
API::InvokeNode getInvocationFromPath(string package, string type, AccessPath path) {
387+
result = getInvocationFromPath(package, type, path, path.getNumToken())
392388
}
393389

394390
/**
395391
* Holds if a summary edge with the given `input, output, kind` columns have a `package, type, path` tuple
396392
* that resolves to `baseNode`.
397393
*/
398394
private predicate resolvedSummaryBase(
399-
API::InvokeNode baseNode, string input, string output, string kind
395+
API::InvokeNode baseNode, AccessPath input, AccessPath output, string kind
400396
) {
401-
exists(string package, string type, string path |
397+
exists(string package, string type, AccessPath path |
402398
summaryModel(package, type, path, input, output, kind) and
403399
baseNode = getInvocationFromPath(package, type, path)
404400
)
405401
}
406402

407403
/**
408-
* Holds if `inputOrOutput` or some suffix thereof is used as the input or output part
409-
* of a summary edge using `base` as the base node.
404+
* Holds if `path` is an input or output spec for a summary with the given `base` node.
410405
*/
411406
pragma[nomagic]
412-
private predicate relevantInputOutputPath(API::InvokeNode base, string inputOrOutput) {
413-
exists(string baseIo | inputOrOutput = baseIo.prefix([0, baseIo.indexOf("."), baseIo.length()]) |
414-
resolvedSummaryBase(base, baseIo, _, _) or
415-
resolvedSummaryBase(base, _, baseIo, _)
416-
)
417-
}
418-
419-
/**
420-
* Gets the result of appending `token` onto `path`, if the resulting path is relevant for
421-
* a summary of `invoke`.
422-
*/
423-
private string appendToken(API::InvokeNode invoke, string path, string token) {
424-
relevantInputOutputPath(invoke, result) and
425-
decomposePath(result, path, token)
407+
private predicate relevantInputOutputPath(API::InvokeNode base, AccessPath path) {
408+
resolvedSummaryBase(base, path, _, _)
409+
or
410+
resolvedSummaryBase(base, _, path, _)
426411
}
427412

428413
/**
429-
* Gets the API node for the given input/output path, evaluated relative to `baseNode`, which corresponds to `package,type,path`.
414+
* Gets the API node for the first `n` tokens of the given input/output path, evaluated relative to `baseNode`.
430415
*/
431-
private API::Node getNodeFromInputOutputPath(API::InvokeNode baseNode, string path) {
416+
private API::Node getNodeFromInputOutputPath(API::InvokeNode baseNode, AccessPath path, int n) {
432417
relevantInputOutputPath(baseNode, path) and
433418
(
434-
result = getSuccessorFromInvoke(baseNode, path)
419+
n = 1 and
420+
result = getSuccessorFromInvoke(baseNode, path.getToken(0))
435421
or
436-
exists(string basePath, string token |
437-
result = getSuccessorFromNode(getNodeFromInputOutputPath(baseNode, basePath), token) and
438-
path = appendToken(baseNode, basePath, token)
439-
)
422+
result =
423+
getSuccessorFromNode(getNodeFromInputOutputPath(baseNode, path, n - 1), path.getToken(n - 1))
440424
)
441425
}
442426

443427
/**
444-
* Holds if `token` is a token used in an access path, that is,
445-
* either the `path`, `input`, or `output` part of a CSV row.
428+
* Gets the API node for the given input/output path, evaluated relative to `baseNode`.
446429
*/
447-
private predicate isAccessPathToken(string token) {
448-
exists(string path |
449-
isRelevantFullPath(_, _, path)
450-
or
451-
exists(string package | isRelevantPackage(package) |
452-
summaryModel(_, _, _, path, _, _) or
453-
summaryModel(_, _, _, _, path, _)
454-
)
455-
|
456-
token = path.splitAt(".")
457-
)
430+
private API::Node getNodeFromInputOutputPath(API::InvokeNode baseNode, AccessPath path) {
431+
result = getNodeFromInputOutputPath(baseNode, path, path.getNumToken())
458432
}
459433

460434
/**
461435
* An access part token such as `Argument[1]` or `ReturnValue`, appearing in one or more access paths.
462436
*/
463437
class AccessPathToken extends string {
464-
AccessPathToken() { isAccessPathToken(this) }
438+
AccessPathToken() { this = any(AccessPath path).getRawToken(_) }
465439

466440
/** Gets the name of the token, such as `Member` from `Member[x]` */
467441
string getName() { result = this.regexpCapture("(.+?)(?:\\[.*?\\])?", 1) }
@@ -593,7 +567,7 @@ module ModelOutput {
593567
* Holds if a CSV summary contributed the step `pred -> succ` of the given `kind`.
594568
*/
595569
predicate summaryStep(API::Node pred, API::Node succ, string kind) {
596-
exists(API::InvokeNode base, string input, string output |
570+
exists(API::InvokeNode base, AccessPath input, AccessPath output |
597571
resolvedSummaryBase(base, input, output, kind) and
598572
pred = getNodeFromInputOutputPath(base, input) and
599573
succ = getNodeFromInputOutputPath(base, output)
@@ -605,7 +579,7 @@ module ModelOutput {
605579
* contributed by a CSV model.
606580
*/
607581
API::Node getATypeNode(string package, string type) {
608-
exists(string package2, string type2, string path |
582+
exists(string package2, string type2, AccessPath path |
609583
typeModel(package, type, package2, type2, path) and
610584
result = getNodeFromPath(package2, type2, path)
611585
)

0 commit comments

Comments
 (0)