Skip to content

Commit 85bb14f

Browse files
authored
Merge pull request #14405 from erik-krogh/tagCall
JS: recognize tagged template literals as `DataFlow::CallNode`
2 parents aa7a667 + 6377e92 commit 85bb14f

25 files changed

+132
-61
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* Tagged template literals have been added to `DataFlow::CallNode`. This allows the analysis to find flow into functions called with a tagged template literal,
5+
and the arguments to a tagged template literal are part of the API-graph in `ApiGraphs.qll`.

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1269,6 +1269,41 @@ module DataFlow {
12691269
result >= 0 and kind = "call" and result = originalCall.getNumArgument() - 1
12701270
}
12711271
}
1272+
1273+
/**
1274+
* A data flow node representing a call with a tagged template literal.
1275+
*/
1276+
private class TaggedTemplateLiteralCallNode extends CallNodeDef, ValueNode {
1277+
override TaggedTemplateExpr astNode;
1278+
1279+
override InvokeExpr getInvokeExpr() { none() } // There is no InvokeExpr for this.
1280+
1281+
override string getCalleeName() {
1282+
result = astNode.getTag().getUnderlyingValue().(Identifier).getName()
1283+
}
1284+
1285+
override DataFlow::Node getCalleeNode() { result = DataFlow::valueNode(astNode.getTag()) }
1286+
1287+
override DataFlow::Node getArgument(int i) {
1288+
// the first argument sent to the function is the array of string parts, which we don't model.
1289+
// rank is 1-indexed, which is perfect here.
1290+
result =
1291+
DataFlow::valueNode(rank[i](Expr e, int index |
1292+
e = astNode.getTemplate().getElement(index) and not e instanceof TemplateElement
1293+
|
1294+
e order by index
1295+
))
1296+
}
1297+
1298+
override DataFlow::Node getAnArgument() { result = this.getArgument(_) }
1299+
1300+
override DataFlow::Node getASpreadArgument() { none() }
1301+
1302+
// we don't model the string constants as arguments, but we still count them.
1303+
override int getNumArgument() { result = count(this.getArgument(_)) + 1 }
1304+
1305+
override DataFlow::Node getReceiver() { none() }
1306+
}
12721307
}
12731308

12741309
/**

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,13 +92,20 @@ class InvokeNode extends DataFlow::SourceNode instanceof DataFlow::Impl::InvokeN
9292
* but the position of `z` cannot be determined, hence there are no first and second
9393
* argument nodes.
9494
*/
95-
DataFlow::Node getArgument(int i) { result = super.getArgument(i) }
95+
cached
96+
DataFlow::Node getArgument(int i) {
97+
result = super.getArgument(i) and Stages::DataFlowStage::ref()
98+
}
9699

97100
/** Gets the data flow node corresponding to an argument of this invocation. */
98-
DataFlow::Node getAnArgument() { result = super.getAnArgument() }
101+
cached
102+
DataFlow::Node getAnArgument() { result = super.getAnArgument() and Stages::DataFlowStage::ref() }
99103

100104
/** Gets the data flow node corresponding to the last argument of this invocation. */
101-
DataFlow::Node getLastArgument() { result = this.getArgument(this.getNumArgument() - 1) }
105+
cached
106+
DataFlow::Node getLastArgument() {
107+
result = this.getArgument(this.getNumArgument() - 1) and Stages::DataFlowStage::ref()
108+
}
102109

103110
/**
104111
* Gets a data flow node corresponding to an array of values being passed as

javascript/ql/lib/semmle/javascript/internal/CachedStages.qll

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,12 @@ module Stages {
145145
exists(any(DataFlow::PropRef ref).getBase())
146146
or
147147
exists(any(DataFlow::ClassNode cls))
148+
or
149+
exists(any(DataFlow::CallNode node).getArgument(_))
150+
or
151+
exists(any(DataFlow::CallNode node).getAnArgument())
152+
or
153+
exists(any(DataFlow::CallNode node).getLastArgument())
148154
}
149155
}
150156

javascript/ql/test/ApiGraphs/tagged-template/VerifyAssertions.expected

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import ApiGraphs.VerifyAssertions
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const tag = require("tag");
2+
3+
tag.string`string1
4+
${23}` // def=moduleImport("tag").getMember("exports").getMember("string").getParameter(1)
5+
6+
tag.highlight`string2
7+
${23}
8+
morestring
9+
${42}` // def=moduleImport("tag").getMember("exports").getMember("highlight").getParameter(2)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"name": "tagged-template"
3+
}

javascript/ql/test/library-tests/CallGraphs/FullTest/getACallee.qll

Lines changed: 0 additions & 3 deletions
This file was deleted.

javascript/ql/test/library-tests/CallGraphs/FullTest/getAFunctionValue.qll

Lines changed: 0 additions & 5 deletions
This file was deleted.

0 commit comments

Comments
 (0)