Skip to content

Commit ef627b4

Browse files
committed
Add support for TypeVar[x] and typeVariable rows
1 parent b414192 commit ef627b4

File tree

4 files changed

+132
-3
lines changed

4 files changed

+132
-3
lines changed

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

Lines changed: 91 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,22 @@ module ModelInput {
155155
*/
156156
abstract predicate row(string row);
157157
}
158+
159+
/**
160+
* A unit class for adding additional type variable model rows.
161+
*/
162+
class TypeVariableModelCsv extends Unit {
163+
/**
164+
* Holds if `row` specifies a path through a type variable.
165+
*
166+
* A row of form,
167+
* ```
168+
* name;path
169+
* ```
170+
* means `path` can be substituted for a token `TypeVar[name]`.
171+
*/
172+
abstract predicate row(string row);
173+
}
158174
}
159175

160176
private import ModelInput
@@ -182,6 +198,8 @@ private predicate summaryModel(string row) { any(SummaryModelCsv s).row(inverseP
182198

183199
private predicate typeModel(string row) { any(TypeModelCsv s).row(inversePad(row)) }
184200

201+
private predicate typeVariableModel(string row) { any(TypeVariableModelCsv s).row(inversePad(row)) }
202+
185203
/** Holds if a source model exists for the given parameters. */
186204
predicate sourceModel(string package, string type, string path, string kind) {
187205
exists(string row |
@@ -219,7 +237,7 @@ private predicate summaryModel(
219237
)
220238
}
221239

222-
/** Holds if an type model exists for the given parameters. */
240+
/** Holds if a type model exists for the given parameters. */
223241
private predicate typeModel(
224242
string package1, string type1, string package2, string type2, string path
225243
) {
@@ -233,6 +251,15 @@ private predicate typeModel(
233251
)
234252
}
235253

254+
/** Holds if a type variable model exists for the given parameters. */
255+
private predicate typeVariableModel(string name, string path) {
256+
exists(string row |
257+
typeVariableModel(row) and
258+
row.splitAt(";", 0) = name and
259+
row.splitAt(";", 1) = path
260+
)
261+
}
262+
236263
/**
237264
* Gets a package that should be seen as an alias for the given other `package`,
238265
* or the `package` itself.
@@ -290,6 +317,8 @@ private class AccessPathRange extends AccessPath::Range {
290317
summaryModel(package, _, _, this, _, _) or
291318
summaryModel(package, _, _, _, this, _)
292319
)
320+
or
321+
typeVariableModel(_, this)
293322
}
294323
}
295324

@@ -361,6 +390,60 @@ private API::Node getNodeFromPath(string package, string type, AccessPath path,
361390
// Similar to the other recursive case, but where the path may have stepped through one or more call-site filters
362391
result =
363392
getSuccessorFromInvoke(getInvocationFromPath(package, type, path, n - 1), path.getToken(n - 1))
393+
or
394+
// Apply a subpath
395+
result =
396+
getNodeFromSubPath(getNodeFromPath(package, type, path, n - 1), getSubPathAt(path, n - 1))
397+
}
398+
399+
/**
400+
* Gets a subpath for the `TypeVar` token found at the `n`th token of `path`.
401+
*/
402+
pragma[nomagic]
403+
private AccessPath getSubPathAt(AccessPath path, int n) {
404+
exists(string typeVarName |
405+
path.getToken(n).getAnArgument("TypeVar") = typeVarName and
406+
typeVariableModel(typeVarName, result)
407+
)
408+
}
409+
410+
/**
411+
* Gets a node that is found by evaluating the first `n` tokens of `subPath` starting at `base`.
412+
*/
413+
pragma[nomagic]
414+
private API::Node getNodeFromSubPath(API::Node base, AccessPath subPath, int n) {
415+
exists(AccessPath path, int k |
416+
base = [getNodeFromPath(_, _, path, k), getNodeFromSubPath(_, path, k)] and
417+
subPath = getSubPathAt(path, k) and
418+
result = base and
419+
n = 0
420+
)
421+
or
422+
result = getSuccessorFromNode(getNodeFromSubPath(base, subPath, n - 1), subPath.getToken(n - 1))
423+
or
424+
result =
425+
getSuccessorFromInvoke(getInvocationFromSubPath(base, subPath, n - 1), subPath.getToken(n - 1))
426+
or
427+
result =
428+
getNodeFromSubPath(getNodeFromSubPath(base, subPath, n - 1), getSubPathAt(subPath, n - 1))
429+
}
430+
431+
/**
432+
* Gets a call site that is found by evaluating the first `n` tokens of `subPath` starting at `base`.
433+
*/
434+
private Specific::InvokeNode getInvocationFromSubPath(API::Node base, AccessPath subPath, int n) {
435+
result = Specific::getAnInvocationOf(getNodeFromSubPath(base, subPath, n))
436+
or
437+
result = getInvocationFromSubPath(base, subPath, n - 1) and
438+
invocationMatchesCallSiteFilter(result, subPath.getToken(n - 1))
439+
}
440+
441+
/**
442+
* Gets a node that is found by evaluating `subPath` starting at `base`.
443+
*/
444+
pragma[nomagic]
445+
private API::Node getNodeFromSubPath(API::Node base, AccessPath subPath) {
446+
result = getNodeFromSubPath(base, subPath, subPath.getNumToken())
364447
}
365448

366449
/** Gets the node identified by the given `(package, type, path)` tuple. */
@@ -390,7 +473,7 @@ Specific::InvokeNode getInvocationFromPath(string package, string type, AccessPa
390473
*/
391474
bindingset[name]
392475
predicate isValidTokenNameInIdentifyingAccessPath(string name) {
393-
name = ["Argument", "Parameter", "ReturnValue", "WithArity"]
476+
name = ["Argument", "Parameter", "ReturnValue", "WithArity", "TypeVar"]
394477
or
395478
Specific::isExtraValidTokenNameInIdentifyingAccessPath(name)
396479
}
@@ -418,6 +501,9 @@ predicate isValidTokenArgumentInIdentifyingAccessPath(string name, string argume
418501
name = "WithArity" and
419502
argument.regexpMatch("\\d+(\\.\\.(\\d+)?)?")
420503
or
504+
name = "TypeVar" and
505+
exists(argument)
506+
or
421507
Specific::isExtraValidTokenArgumentInIdentifyingAccessPath(name, argument)
422508
}
423509

@@ -489,6 +575,8 @@ module ModelOutput {
489575
any(SummaryModelCsv csv).row(row) and kind = "summary" and expectedArity = 6
490576
or
491577
any(TypeModelCsv csv).row(row) and kind = "type" and expectedArity = 5
578+
or
579+
any(TypeVariableModelCsv csv).row(row) and kind = "type-variable" and expectedArity = 2
492580
|
493581
actualArity = count(row.indexOf(";")) + 1 and
494582
actualArity != expectedArity and
@@ -499,7 +587,7 @@ module ModelOutput {
499587
or
500588
// Check names and arguments of access path tokens
501589
exists(AccessPath path, AccessPathToken token |
502-
isRelevantFullPath(_, _, path) and
590+
(isRelevantFullPath(_, _, path) or typeVariableModel(_, path)) and
503591
token = path.getToken(_)
504592
|
505593
not isValidTokenNameInIdentifyingAccessPath(token.getName()) and

javascript/ql/test/library-tests/frameworks/data/test.expected

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ taintFlow
5858
| test.js:207:24:207:31 | source() | test.js:207:24:207:31 | source() |
5959
| test.js:208:24:208:31 | source() | test.js:208:24:208:31 | source() |
6060
| test.js:211:34:211:41 | source() | test.js:211:34:211:41 | source() |
61+
| test.js:214:34:214:41 | source() | test.js:214:34:214:41 | source() |
62+
| test.js:223:45:223:52 | source() | test.js:223:45:223:52 | source() |
63+
| test.js:225:39:225:46 | source() | test.js:225:39:225:46 | source() |
64+
| test.js:226:50:226:57 | source() | test.js:226:50:226:57 | source() |
6165
isSink
6266
| test.js:54:18:54:25 | source() | test-sink |
6367
| test.js:55:22:55:29 | source() | test-sink |
@@ -119,6 +123,11 @@ isSink
119123
| test.js:207:24:207:31 | source() | test-sink |
120124
| test.js:208:24:208:31 | source() | test-sink |
121125
| test.js:211:34:211:41 | source() | test-sink |
126+
| test.js:214:34:214:41 | source() | test-sink |
127+
| test.js:222:52:222:52 | 0 | test-sink |
128+
| test.js:223:45:223:52 | source() | test-sink |
129+
| test.js:225:39:225:46 | source() | test-sink |
130+
| test.js:226:50:226:57 | source() | test-sink |
122131
syntaxErrors
123132
| Member[foo |
124133
| Member[foo] .Member[bar] |

javascript/ql/test/library-tests/frameworks/data/test.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,3 +209,21 @@ testlib.bar.memberSink(source()); // NOT OK
209209
testlib.memberSink(source()); // OK
210210
testlib.overloadedSink('safe', source()); // OK
211211
testlib.overloadedSink('danger', source()); // NOT OK
212+
213+
function typeVars() {
214+
testlib.typevar.a.b().c.mySink(source()); // NOT OK
215+
216+
testlib.typevar.mySink(source()); // OK - does not match sub path
217+
testlib.typevar.a.mySink(source()); // OK - does not match sub path
218+
testlib.typevar.a.b.mySink(source()); // OK - does not match sub path
219+
testlib.typevar.a.b.c.mySink(source()); // OK - does not match sub path
220+
testlib.typevar.a.b(1).c.mySink(source()); // OK - does not match sub path
221+
222+
testlib.typevar.a.b().c.a.b().c.mySink(source(), 0); // OK
223+
testlib.typevar.a.b().c.a.b().c.mySink(0, source()); // NOT OK
224+
225+
testlib.typevar.left.x.right.mySink(source()); // NOT OK
226+
testlib.typevar.left.left.x.right.right.mySink(source()); // NOT OK
227+
testlib.typevar.left.x.right.right.mySink(source()); // OK - mismatched left and right
228+
testlib.typevar.left.left.x.right.mySink(source()); // OK - mismatched left and right
229+
}

javascript/ql/test/library-tests/frameworks/data/test.ql

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,20 @@ class Sinks extends ModelInput::SinkModelCsv {
4040
"testlib;;Member[ParamDecoratorSink].DecoratedParameter;test-sink",
4141
"testlib;;AnyMember.Member[memberSink].Argument[0];test-sink",
4242
"testlib;;Member[overloadedSink].WithStringArgument[0=danger].Argument[1];test-sink",
43+
"testlib;;Member[typevar].TypeVar[ABC].Member[mySink].Argument[0];test-sink",
44+
"testlib;;Member[typevar].TypeVar[ABC].TypeVar[ABC].Member[mySink].Argument[1];test-sink",
45+
"testlib;;Member[typevar].TypeVar[LeftRight].Member[mySink].Argument[0];test-sink",
46+
]
47+
}
48+
}
49+
50+
class TypeVars extends ModelInput::TypeVariableModelCsv {
51+
override predicate row(string row) {
52+
row =
53+
[
54+
"ABC;Member[a].Member[b].WithArity[0].ReturnValue.Member[c]", //
55+
"LeftRight;Member[left].TypeVar[LeftRight].Member[right]", //
56+
"LeftRight;Member[x]",
4357
]
4458
}
4559
}

0 commit comments

Comments
 (0)