Skip to content

Commit fa7ad03

Browse files
committed
JS: Add store/load steps for the new argument arrays
1 parent 623dbda commit fa7ad03

5 files changed

Lines changed: 158 additions & 72 deletions

File tree

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1780,8 +1780,7 @@ module DataFlow {
17801780
)
17811781
}
17821782

1783-
/** A load step from a reflective parameter node to each parameter. */
1784-
private class ReflectiveParamsStep extends PreCallGraphStep {
1783+
private class ReflectiveParamsStep extends LegacyPreCallGraphStep {
17851784
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
17861785
exists(DataFlow::ReflectiveParametersNode params, DataFlow::FunctionNode f, int i |
17871786
f.getFunction() = params.getFunction() and
@@ -1793,7 +1792,7 @@ module DataFlow {
17931792
}
17941793

17951794
/** A taint step from the reflective parameters node to any parameter. */
1796-
private class ReflectiveParamsTaintStep extends TaintTracking::SharedTaintStep {
1795+
private class ReflectiveParamsTaintStep extends TaintTracking::LegacyTaintStep {
17971796
override predicate step(DataFlow::Node obj, DataFlow::Node element) {
17981797
exists(DataFlow::ReflectiveParametersNode params, DataFlow::FunctionNode f |
17991798
f.getFunction() = params.getFunction() and

javascript/ql/lib/semmle/javascript/dataflow/internal/DataFlowNode.qll

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ private import semmle.javascript.dataflow.internal.VariableCapture as VariableCa
1515

1616
cached
1717
private module Cached {
18+
private Content dynamicArgumentsContent() {
19+
result.asArrayIndex() = [0 .. 10]
20+
or
21+
result.isUnknownArrayElement()
22+
}
23+
1824
/**
1925
* The raw data type underlying `DataFlow::Node`.
2026
*/
@@ -39,6 +45,16 @@ private module Cached {
3945
f.getAParameter().isRestParameter() or f.usesArgumentsObject()
4046
} or
4147
TDynamicParameterArrayNode(Function f) or
48+
/** Data about to be stored in the rest parameter object. Needed for shifting array indices. */
49+
TRestParameterStoreNode(Function f, Content storeContent) {
50+
f.getRestParameter().getIndex() > 0 and
51+
storeContent = dynamicArgumentsContent()
52+
} or
53+
/** Data about to be stored in the dynamic argument array of an invocation. Needed for shifting array indices. */
54+
TDynamicArgumentStoreNode(InvokeExpr invoke, Content storeContent) {
55+
invoke.isSpreadArgument(_) and
56+
storeContent = dynamicArgumentsContent()
57+
} or
4258
TDestructuredModuleImportNode(ImportDeclaration decl) {
4359
exists(decl.getASpecifier().getImportedName())
4460
} or
@@ -49,7 +65,7 @@ private module Cached {
4965
TExceptionalInvocationReturnNode(InvokeExpr e) or
5066
TGlobalAccessPathRoot() or
5167
TTemplatePlaceholderTag(Templating::TemplatePlaceholderTag tag) or
52-
TReflectiveParametersNode(Function f) or
68+
TReflectiveParametersNode(Function f) { f.usesArgumentsObject() } or
5369
TExprPostUpdateNode(AST::ValueNode e) {
5470
e = any(InvokeExpr invoke).getAnArgument() or
5571
e = any(PropAccess access).getBase() or

javascript/ql/lib/semmle/javascript/dataflow/internal/DataFlowPrivate.qll

Lines changed: 138 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,42 @@ class DynamicArgumentArrayNode extends DataFlow::Node, TDynamicArgumentArrayNode
113113
override Location getLocation() { result = invoke.getLocation() }
114114
}
115115

116+
/**
117+
* Intermediate node with data that will be stored in `DyanmicArgumentArrayNode`.
118+
*/
119+
class DynamicArgumentStoreNode extends DataFlow::Node, TDynamicArgumentStoreNode {
120+
private InvokeExpr invoke;
121+
private Content content;
122+
123+
DynamicArgumentStoreNode() { this = TDynamicArgumentStoreNode(invoke, content) }
124+
125+
override StmtContainer getContainer() { result = invoke.getContainer() }
126+
127+
override string toString() { result = "[dynamic argument store node] content=" + content }
128+
129+
override Location getLocation() { result = invoke.getLocation() }
130+
}
131+
132+
/**
133+
* Intermediate node with data that will be stored in the function's rest parameter node.
134+
*/
135+
class RestParameterStoreNode extends DataFlow::Node, TRestParameterStoreNode {
136+
private Function function;
137+
private Content content;
138+
139+
RestParameterStoreNode() { this = TRestParameterStoreNode(function, content) }
140+
141+
override StmtContainer getContainer() { result = function }
142+
143+
override string toString() {
144+
result =
145+
"[rest parameter store node] '..." + function.getRestParameter().getName() + "' content=" +
146+
content
147+
}
148+
149+
override Location getLocation() { result = function.getRestParameter().getLocation() }
150+
}
151+
116152
/**
117153
* A parameter containing an array of all positional arguments with an obvious index, i.e. not affected by spread or `.apply()`.
118154
*
@@ -297,8 +333,6 @@ private predicate isParameterNodeImpl(Node p, DataFlowCallable c, ParameterPosit
297333
or
298334
pos.isFunctionSelfReference() and p = TFunctionSelfReferenceNode(c.asSourceCallable())
299335
or
300-
pos.isArgumentsArray() and p = TReflectiveParametersNode(c.asSourceCallable()) // TODO: remove
301-
or
302336
pos.isStaticArgumentArray() and p = TStaticParameterArrayNode(c.asSourceCallable())
303337
or
304338
pos.isDynamicArgumentArray() and p = TDynamicParameterArrayNode(c.asSourceCallable())
@@ -317,6 +351,12 @@ predicate isParameterNode(ParameterNode p, DataFlowCallable c, ParameterPosition
317351
private predicate isArgumentNodeImpl(Node n, DataFlowCall call, ArgumentPosition pos) {
318352
n = call.asOrdinaryCall().getArgument(pos.asPositional())
319353
or
354+
exists(InvokeExpr invoke |
355+
call.asOrdinaryCall() = TReflectiveCallNode(invoke, "apply") and
356+
pos.isDynamicArgumentArray() and
357+
n = TValueNode(invoke.getArgument(1))
358+
)
359+
or
320360
pos.isThis() and n = call.asOrdinaryCall().(DataFlow::CallNode).getReceiver()
321361
or
322362
exists(DataFlow::PartialInvokeNode invoke, DataFlow::Node callback |
@@ -343,10 +383,6 @@ private predicate isArgumentNodeImpl(Node n, DataFlowCall call, ArgumentPosition
343383
or
344384
pos.isThis() and n = TConstructorThisArgumentNode(call.asOrdinaryCall().asExpr())
345385
or
346-
// For now, treat all spread argument as flowing into the 'arguments' array, regardless of preceding arguments
347-
n = call.asOrdinaryCall().getASpreadArgument() and
348-
pos.isArgumentsArray()
349-
or
350386
// receiver of accessor call
351387
pos.isThis() and n = call.asAccessorCall().getBase()
352388
or
@@ -953,6 +989,39 @@ predicate simpleLocalFlowStep(Node node1, Node node2) {
953989
or
954990
// NOTE: For consistency with readStep/storeStep, we do not translate these steps to jump steps automatically.
955991
DataFlow::AdditionalFlowStep::step(node1, node2)
992+
or
993+
exists(InvokeExpr invoke |
994+
// When the first argument is a spread argument, flow into the argument array as a local flow step
995+
// to ensure we preserve knowledge about array indices
996+
node1 = TValueNode(invoke.getArgument(0).stripParens().(SpreadElement).getOperand()) and
997+
node2 = TDynamicArgumentArrayNode(invoke)
998+
)
999+
or
1000+
exists(Function f |
1001+
// When the first parameter is a rest parameter, flow into the rest parameter as a local flow step
1002+
// to ensure we preserve knowledge about array indices
1003+
(node1 = TStaticParameterArrayNode(f) or node1 = TDynamicParameterArrayNode(f))
1004+
|
1005+
// rest parameter at initial position
1006+
exists(Parameter rest |
1007+
rest = f.getParameter(0) and
1008+
rest.isRestParameter() and
1009+
node2 = TValueNode(rest)
1010+
)
1011+
or
1012+
// 'arguments' array
1013+
node2 = TReflectiveParametersNode(f)
1014+
)
1015+
or
1016+
// Prepare to store non-spread arguments after a spread into the dynamic arguments array
1017+
exists(InvokeExpr invoke, int n, Expr argument, Content storeContent |
1018+
invoke.getArgument(n) = argument and
1019+
not argument instanceof SpreadElement and
1020+
n > firstSpreadArgumentIndex(invoke) and
1021+
node1 = TValueNode(argument) and
1022+
node2 = TDynamicArgumentStoreNode(invoke, storeContent) and
1023+
storeContent.isUnknownArrayElement()
1024+
)
9561025
}
9571026

9581027
predicate localMustFlowStep(Node node1, Node node2) { node1 = node2.getImmediatePredecessor() }
@@ -1018,6 +1087,42 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
10181087
)
10191088
or
10201089
DataFlow::AdditionalFlowStep::readStep(node1, c, node2)
1090+
or
1091+
// Pass dynamic arguments into plain parameters
1092+
exists(Function function, Parameter param, int n |
1093+
param = function.getParameter(n) and
1094+
not param.isRestParameter() and
1095+
node1 = TDynamicParameterArrayNode(function) and
1096+
node2 = TValueNode(param) and
1097+
c = ContentSet::arrayElementFromInt(n)
1098+
)
1099+
or
1100+
// Prepare to store dynamic and static arguments into the rest parameter array when it isn't the first parameter
1101+
exists(Function function, Content content, int restIndex |
1102+
restIndex = function.getRestParameter().getIndex() and
1103+
restIndex > 0 and
1104+
(node1 = TStaticParameterArrayNode(function) or node1 = TDynamicParameterArrayNode(function)) and
1105+
node2 = TRestParameterStoreNode(function, content)
1106+
|
1107+
// shift known array indices
1108+
c.asArrayIndex() = content.asArrayIndex() + restIndex
1109+
or
1110+
content.isUnknownArrayElement() and // TODO: don't read unknown array elements from static array
1111+
c = ContentSet::arrayElementUnknown()
1112+
)
1113+
or
1114+
// Prepare to store spread arguments into the dynamic arguments array, when it isn't the initial spread argument
1115+
exists(InvokeExpr invoke, int n, Expr argument, Content storeContent |
1116+
invoke.getArgument(n).stripParens().(SpreadElement).getOperand() = argument and
1117+
n > 0 and // n=0 is handled as a value step
1118+
node1 = TValueNode(argument) and
1119+
node2 = TDynamicArgumentStoreNode(invoke, storeContent) and
1120+
if n > firstSpreadArgumentIndex(invoke)
1121+
then
1122+
c = ContentSet::arrayElement() and // unknown start index when not the first spread operator
1123+
storeContent.isUnknownArrayElement()
1124+
else storeContent.asArrayIndex() = n + c.asArrayIndex()
1125+
)
10211126
}
10221127

10231128
/** Gets the post-update node for which `node` is the corresponding pre-update node. */
@@ -1032,6 +1137,11 @@ private Node tryGetPostUpdate(Node node) {
10321137
result = node
10331138
}
10341139

1140+
pragma[nomagic]
1141+
private int firstSpreadArgumentIndex(InvokeExpr expr) {
1142+
result = min(int i | expr.isSpreadArgument(i))
1143+
}
1144+
10351145
/**
10361146
* Holds if data can flow from `node1` to `node2` via a store into `c`. Thus,
10371147
* `node2` references an object with a content `c.getAStoreContent()` that
@@ -1063,6 +1173,25 @@ predicate storeStep(Node node1, ContentSet c, Node node2) {
10631173
)
10641174
or
10651175
DataFlow::AdditionalFlowStep::storeStep(node1, c, node2)
1176+
or
1177+
exists(Function f, Content storeContent |
1178+
node1 = TRestParameterStoreNode(f, storeContent) and
1179+
node2 = TValueNode(f.getRestParameter()) and
1180+
c.asSingleton() = storeContent
1181+
)
1182+
or
1183+
exists(InvokeExpr invoke, Content storeContent |
1184+
node1 = TDynamicArgumentStoreNode(invoke, storeContent) and
1185+
node2 = TDynamicArgumentArrayNode(invoke) and
1186+
c.asSingleton() = storeContent
1187+
)
1188+
or
1189+
exists(InvokeExpr invoke, int n |
1190+
node1 = TValueNode(invoke.getArgument(n)) and
1191+
node2 = TStaticArgumentArrayNode(invoke) and
1192+
c.asArrayIndex() = n and
1193+
not n >= firstSpreadArgumentIndex(invoke)
1194+
)
10661195
}
10671196

10681197
/**
@@ -1112,6 +1241,9 @@ predicate expectsContent(Node n, ContentSet c) {
11121241
c = MkPromiseFilter()
11131242
or
11141243
any(AdditionalFlowInternal flow).expectsContent(n, c)
1244+
or
1245+
c = ContentSet::arrayElement() and
1246+
n instanceof TDynamicParameterArrayNode
11151247
}
11161248

11171249
abstract class NodeRegion extends Unit {
Lines changed: 0 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +0,0 @@
1-
| tst.js:5:14:5:20 | rest[0] | Unexpected result: hasTaintFlow=t1.1 |
2-
| tst.js:5:24:5:45 | // $ ha ... ow=t1.1 | Missing result:hasValueFlow=t1.1 |
3-
| tst.js:6:14:6:20 | rest[1] | Unexpected result: hasTaintFlow=t1.1 |
4-
| tst.js:6:24:6:45 | // $ ha ... ow=t1.2 | Missing result:hasValueFlow=t1.2 |
5-
| tst.js:7:31:7:70 | // $ ha ... ow=t1.2 | Missing result:hasTaintFlow=t1.2 |
6-
| tst.js:15:31:15:70 | // $ ha ... ow=t2.3 | Missing result:hasTaintFlow=t2.3 |
7-
| tst.js:22:14:22:14 | x | Unexpected result: hasTaintFlow=t3.1 |
8-
| tst.js:22:18:22:39 | // $ ha ... ow=t3.1 | Missing result:hasValueFlow=t3.1 |
9-
| tst.js:23:14:23:14 | y | Unexpected result: hasTaintFlow=t3.1 |
10-
| tst.js:23:18:23:39 | // $ ha ... ow=t3.2 | Missing result:hasValueFlow=t3.2 |
11-
| tst.js:24:14:24:14 | z | Unexpected result: hasTaintFlow=t3.1 |
12-
| tst.js:24:18:24:39 | // $ ha ... ow=t3.3 | Missing result:hasValueFlow=t3.3 |
13-
| tst.js:34:14:34:14 | w | Unexpected result: hasTaintFlow=t4.1 |
14-
| tst.js:35:14:35:14 | x | Unexpected result: hasTaintFlow=t4.1 |
15-
| tst.js:35:18:35:39 | // $ ha ... ow=t4.1 | Missing result:hasValueFlow=t4.1 |
16-
| tst.js:36:14:36:14 | y | Unexpected result: hasTaintFlow=t4.1 |
17-
| tst.js:36:18:36:39 | // $ ha ... ow=t4.2 | Missing result:hasValueFlow=t4.2 |
18-
| tst.js:37:14:37:14 | z | Unexpected result: hasTaintFlow=t4.1 |
19-
| tst.js:37:18:37:39 | // $ ha ... ow=t4.3 | Missing result:hasValueFlow=t4.3 |
20-
| tst.js:47:14:47:14 | w | Unexpected result: hasTaintFlow=t5.2 |
21-
| tst.js:47:14:47:14 | w | Unexpected result: hasTaintFlow=t5.3 |
22-
| tst.js:47:14:47:14 | w | Unexpected result: hasValueFlow=t5.1 |
23-
| tst.js:48:14:48:14 | x | Unexpected result: hasTaintFlow=t5.1 |
24-
| tst.js:48:14:48:14 | x | Unexpected result: hasTaintFlow=t5.3 |
25-
| tst.js:48:14:48:14 | x | Unexpected result: hasValueFlow=t5.2 |
26-
| tst.js:48:18:48:39 | // $ ha ... ow=t5.1 | Missing result:hasValueFlow=t5.1 |
27-
| tst.js:49:14:49:14 | y | Unexpected result: hasTaintFlow=t5.1 |
28-
| tst.js:49:14:49:14 | y | Unexpected result: hasTaintFlow=t5.2 |
29-
| tst.js:49:14:49:14 | y | Unexpected result: hasValueFlow=t5.3 |
30-
| tst.js:49:18:49:39 | // $ ha ... ow=t5.2 | Missing result:hasValueFlow=t5.2 |
31-
| tst.js:50:14:50:14 | z | Unexpected result: hasTaintFlow=t5.1 |
32-
| tst.js:50:14:50:14 | z | Unexpected result: hasTaintFlow=t5.2 |
33-
| tst.js:50:14:50:14 | z | Unexpected result: hasTaintFlow=t5.3 |
34-
| tst.js:50:18:50:39 | // $ ha ... ow=t5.3 | Missing result:hasValueFlow=t5.3 |
35-
| tst.js:61:28:61:49 | // $ ha ... ow=t6.1 | Missing result:hasValueFlow=t6.1 |
36-
| tst.js:62:28:62:49 | // $ ha ... ow=t6.2 | Missing result:hasValueFlow=t6.2 |
37-
| tst.js:63:28:63:49 | // $ ha ... ow=t6.3 | Missing result:hasValueFlow=t6.3 |
38-
| tst.js:70:18:70:39 | // $ ha ... ow=t7.1 | Missing result:hasValueFlow=t7.1 |
39-
| tst.js:71:18:71:39 | // $ ha ... ow=t7.2 | Missing result:hasValueFlow=t7.2 |
40-
| tst.js:72:18:72:39 | // $ ha ... ow=t7.3 | Missing result:hasValueFlow=t7.3 |
41-
| tst.js:82:14:82:14 | x | Unexpected result: hasTaintFlow=t8.2 |
42-
| tst.js:82:14:82:14 | x | Unexpected result: hasTaintFlow=t8.4 |
43-
| tst.js:83:14:83:14 | y | Unexpected result: hasTaintFlow=t8.1 |
44-
| tst.js:83:14:83:14 | y | Unexpected result: hasTaintFlow=t8.3 |
45-
| tst.js:83:18:83:85 | // $ ha ... ow=t8.4 | Fixed spurious result:hasValueFlow=t8.3 |
46-
| tst.js:84:14:84:14 | z | Unexpected result: hasTaintFlow=t8.1 |
47-
| tst.js:84:14:84:14 | z | Unexpected result: hasTaintFlow=t8.2 |
48-
| tst.js:84:14:84:14 | z | Unexpected result: hasTaintFlow=t8.3 |
49-
| tst.js:84:14:84:14 | z | Unexpected result: hasTaintFlow=t8.4 |
50-
| tst.js:84:18:84:85 | // $ ha ... ow=t8.4 | Fixed spurious result:hasValueFlow=t8.3 |
51-
| tst.js:84:18:84:85 | // $ ha ... ow=t8.4 | Fixed spurious result:hasValueFlow=t8.4 |
52-
| tst.js:84:18:84:85 | // $ ha ... ow=t8.4 | Missing result:hasValueFlow=t8.3 |
53-
| tst.js:94:18:94:39 | // $ ha ... ow=t9.1 | Missing result:hasValueFlow=t9.1 |
54-
| tst.js:95:18:95:39 | // $ ha ... ow=t9.2 | Missing result:hasValueFlow=t9.2 |
55-
| tst.js:96:18:96:39 | // $ ha ... ow=t9.3 | Missing result:hasValueFlow=t9.3 |
56-
| tst.js:106:14:106:14 | x | Unexpected result: hasTaintFlow=t10.1 |
57-
| tst.js:106:18:106:40 | // $ ha ... w=t10.1 | Missing result:hasValueFlow=t10.1 |
58-
| tst.js:107:14:107:14 | y | Unexpected result: hasTaintFlow=t10.1 |
59-
| tst.js:107:18:107:40 | // $ ha ... w=t10.2 | Missing result:hasValueFlow=t10.2 |
60-
| tst.js:108:14:108:14 | z | Unexpected result: hasTaintFlow=t10.1 |
61-
| tst.js:108:18:108:40 | // $ ha ... w=t10.3 | Missing result:hasValueFlow=t10.3 |

javascript/ql/test/library-tests/TripleDot/tst.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ function t7() {
7979

8080
function t8() {
8181
function finalTarget(x, y, z) {
82-
sink(x); // $ hasValueFlow=t8.1 SPURIOUS: hasValueFlow=t8.3
82+
sink(x); // $ hasValueFlow=t8.1 SPURIOUS: hasValueFlow=t8.3 hasValueFlow=t8.4
8383
sink(y); // $ hasValueFlow=t8.2 SPURIOUS: hasValueFlow=t8.3 hasValueFlow=t8.4
8484
sink(z); // $ hasValueFlow=t8.3 SPURIOUS: hasValueFlow=t8.3 hasValueFlow=t8.4
8585
}

0 commit comments

Comments
 (0)