Skip to content

Commit abd4933

Browse files
committed
Shared: move numeric parsing into AccessPathSyntax.qll
1 parent 7d55771 commit abd4933

File tree

2 files changed

+103
-92
lines changed

2 files changed

+103
-92
lines changed

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

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,111 @@
66
* (which does not use the shared data flow libraries).
77
*/
88

9+
/**
10+
* Convenience-predicate for extracting two capture groups at once.
11+
*/
12+
bindingset[input, regexp]
13+
private predicate regexpCaptureTwo(string input, string regexp, string capture1, string capture2) {
14+
capture1 = input.regexpCapture(regexp, 1) and
15+
capture2 = input.regexpCapture(regexp, 2)
16+
}
17+
918
/** Companion module to the `AccessPath` class. */
1019
module AccessPath {
1120
/** A string that should be parsed as an access path. */
1221
abstract class Range extends string {
1322
bindingset[this]
1423
Range() { any() }
1524
}
25+
26+
/**
27+
* Parses an integer constant `n` or interval `n1..n2` (inclusive) and gets the value
28+
* of the constant or any value contained in the interval.
29+
*/
30+
bindingset[arg]
31+
int parseInt(string arg) {
32+
result = arg.toInt()
33+
or
34+
// Match "n1..n2"
35+
exists(string lo, string hi |
36+
regexpCaptureTwo(arg, "(-?\\d+)\\.\\.(-?\\d+)", lo, hi) and
37+
result = [lo.toInt() .. hi.toInt()]
38+
)
39+
}
40+
41+
/**
42+
* Parses a lower-bounded interval `n..` and gets the lower bound.
43+
*/
44+
bindingset[arg]
45+
private int parseLowerBound(string arg) {
46+
result = arg.regexpCapture("(-?\\d+)\\.\\.", 1).toInt()
47+
}
48+
49+
/**
50+
* Parses an integer constant or interval (bounded or unbounded) that explicitly
51+
* references the arity, such as `N-1` or `N-3..N-1`.
52+
*
53+
* Note that expressions of form `N-x` will never resolve to a negative index,
54+
* even if `N` is zero (it will have no result in that case).
55+
*/
56+
bindingset[arg, arity]
57+
private int parseIntWithExplicitArity(string arg, int arity) {
58+
result >= 0 and // do not allow N-1 to resolve to a negative index
59+
exists(string lo |
60+
// N-x
61+
lo = arg.regexpCapture("N-(\\d+)", 1) and
62+
result = arity - lo.toInt()
63+
or
64+
// N-x..
65+
lo = arg.regexpCapture("N-(\\d+)\\.\\.", 1) and
66+
result = [arity - lo.toInt(), arity - 1]
67+
)
68+
or
69+
exists(string lo, string hi |
70+
// x..N-y
71+
regexpCaptureTwo(arg, "(-?\\d+)\\.\\.N-(\\d+)", lo, hi) and
72+
result = [lo.toInt() .. arity - hi.toInt()]
73+
or
74+
// N-x..Ny
75+
regexpCaptureTwo(arg, "N-(\\d+)\\.\\.N-(\\d+)", lo, hi) and
76+
result = [arity - lo.toInt() .. arity - hi.toInt()] and
77+
result >= 0
78+
or
79+
// N-x..y
80+
regexpCaptureTwo(arg, "N-(\\d+)\\.\\.(\\d+)", lo, hi) and
81+
result = [arity - lo.toInt() .. hi.toInt()] and
82+
result >= 0
83+
)
84+
}
85+
86+
/**
87+
* Parses an integer constant or interval (bounded or unbounded) and gets any
88+
* of the integers contained within (of which there may be infinitely many).
89+
*
90+
* Has no result for arguments involving an explicit arity, such as `N-1`.
91+
*/
92+
bindingset[arg, result]
93+
int parseIntUnbounded(string arg) {
94+
result = parseInt(arg)
95+
or
96+
result >= parseLowerBound(arg)
97+
}
98+
99+
/**
100+
* Parses an integer constant or interval (bounded or unbounded) that
101+
* may reference the arity of a call, such as `N-1` or `N-3..N-1`.
102+
*
103+
* Note that expressions of form `N-x` will never resolve to a negative index,
104+
* even if `N` is zero (it will have no result in that case).
105+
*/
106+
bindingset[arg, arity]
107+
int parseIntWithArity(string arg, int arity) {
108+
result = parseInt(arg)
109+
or
110+
result in [parseLowerBound(arg) .. arity - 1]
111+
or
112+
result = parseIntWithExplicitArity(arg, arity)
113+
}
16114
}
17115

18116
/** Gets the `n`th token on the access path as a string. */

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

Lines changed: 5 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ API::Node getSuccessorFromNode(API::Node node, AccessPathToken token) {
274274
// use-node represents be an argument, and an edge originating from a def-node represents a parameter.
275275
// We just map both to the same thing.
276276
token.getName() = ["Argument", "Parameter"] and
277-
result = node.getParameter(getAnIntFromStringUnbounded(token.getAnArgument()))
277+
result = node.getParameter(AccessPath::parseIntUnbounded(token.getAnArgument()))
278278
or
279279
token.getName() = "ReturnValue" and
280280
result = node.getReturn()
@@ -289,13 +289,9 @@ API::Node getSuccessorFromNode(API::Node node, AccessPathToken token) {
289289
bindingset[token]
290290
API::Node getSuccessorFromInvoke(Specific::InvokeNode invoke, AccessPathToken token) {
291291
token.getName() = "Argument" and
292-
(
293-
result = invoke.getParameter(getAnIntFromStringUnbounded(token.getAnArgument()))
294-
or
295-
result =
296-
invoke
297-
.getParameter(getAnIntFromStringWithArity(token.getAnArgument(), invoke.getNumArgument()))
298-
)
292+
result =
293+
invoke
294+
.getParameter(AccessPath::parseIntWithArity(token.getAnArgument(), invoke.getNumArgument()))
299295
or
300296
token.getName() = "ReturnValue" and
301297
result = invoke.getReturn()
@@ -310,7 +306,7 @@ API::Node getSuccessorFromInvoke(Specific::InvokeNode invoke, AccessPathToken to
310306
pragma[inline]
311307
private predicate invocationMatchesCallSiteFilter(Specific::InvokeNode invoke, AccessPathToken token) {
312308
token.getName() = "WithArity" and
313-
invoke.getNumArgument() = getAnIntFromStringUnbounded(token.getAnArgument())
309+
invoke.getNumArgument() = AccessPath::parseIntUnbounded(token.getAnArgument())
314310
or
315311
Specific::invocationMatchesExtraCallSiteFilter(invoke, token)
316312
}
@@ -361,89 +357,6 @@ Specific::InvokeNode getInvocationFromPath(string package, string type, AccessPa
361357
result = getInvocationFromPath(package, type, path, path.getNumToken())
362358
}
363359

364-
/**
365-
* Convenience-predicate for extracting two capture groups at once.
366-
*/
367-
bindingset[input, regexp]
368-
private predicate regexpCaptureTwo(string input, string regexp, string capture1, string capture2) {
369-
capture1 = input.regexpCapture(regexp, 1) and
370-
capture2 = input.regexpCapture(regexp, 2)
371-
}
372-
373-
/**
374-
* Parses an integer constant `n` or interval `n1..n2` (inclusive) and gets the value
375-
* of the constant or any value contained in the interval.
376-
*/
377-
bindingset[arg]
378-
private int getAnIntFromString(string arg) {
379-
result = arg.toInt()
380-
or
381-
// Match "n1..n2"
382-
exists(string lo, string hi |
383-
regexpCaptureTwo(arg, "(\\d+)\\.\\.(\\d+)", lo, hi) and
384-
result = [lo.toInt() .. hi.toInt()]
385-
)
386-
}
387-
388-
/**
389-
* Parses a lower-bounded interval `n..` and gets the lower bound.
390-
*/
391-
bindingset[arg]
392-
private int getLowerBoundFromString(string arg) {
393-
// Match "n.."
394-
result = arg.regexpCapture("(\\d+)\\.\\.", 1).toInt()
395-
}
396-
397-
/**
398-
* Parses an integer constant or interval (bounded or unbounded) and gets any
399-
* of the integers contained within (of which there may be infinitely many).
400-
*
401-
* Has no result for arguments involving an explicit arity, such as `N-1`.
402-
*/
403-
bindingset[arg, result]
404-
private int getAnIntFromStringUnbounded(string arg) {
405-
result = getAnIntFromString(arg)
406-
or
407-
result >= getLowerBoundFromString(arg)
408-
}
409-
410-
/**
411-
* Parses an integer constant or interval (bounded or unbounded) that explicitly
412-
* references the arity, such as `N-1` or `N-3..N-1`.
413-
*
414-
* Note that such expressions will never resolve to a negative index, even if the
415-
* arity is zero (it will have no result in that case).
416-
*/
417-
bindingset[arg, arity]
418-
private int getAnIntFromStringWithArity(string arg, int arity) {
419-
result >= 0 and // do not allow N-1 to resolve to a negative index
420-
exists(string lo |
421-
// N-x
422-
lo = arg.regexpCapture("N-(\\d+)", 1) and
423-
result = arity - lo.toInt()
424-
or
425-
// N-x..
426-
lo = arg.regexpCapture("N-(\\d+)\\.\\.", 1) and
427-
result = [arity - lo.toInt(), arity - 1]
428-
)
429-
or
430-
exists(string lo, string hi |
431-
// x..N-y
432-
regexpCaptureTwo(arg, "(\\d+)\\.\\.N-(\\d+)", lo, hi) and
433-
result = [lo.toInt() .. arity - hi.toInt()]
434-
or
435-
// N-x..Ny
436-
regexpCaptureTwo(arg, "N-(\\d+)\\.\\.N-(\\d+)", lo, hi) and
437-
result = [arity - lo.toInt() .. arity - hi.toInt()] and
438-
result >= 0
439-
or
440-
// N-x..y
441-
regexpCaptureTwo(arg, "N-(\\d+)\\.\\.(\\d+)", lo, hi) and
442-
result = [arity - lo.toInt() .. hi.toInt()] and
443-
result >= 0
444-
)
445-
}
446-
447360
/**
448361
* Module providing access to the imported models in terms of API graph nodes.
449362
*/

0 commit comments

Comments
 (0)