mirror of
https://github.com/github/codeql.git
synced 2026-04-26 01:05:15 +02:00
Merge pull request #14405 from erik-krogh/tagCall
JS: recognize tagged template literals as `DataFlow::CallNode`
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Tagged template literals have been added to `DataFlow::CallNode`. This allows the analysis to find flow into functions called with a tagged template literal,
|
||||
and the arguments to a tagged template literal are part of the API-graph in `ApiGraphs.qll`.
|
||||
@@ -1269,6 +1269,41 @@ module DataFlow {
|
||||
result >= 0 and kind = "call" and result = originalCall.getNumArgument() - 1
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow node representing a call with a tagged template literal.
|
||||
*/
|
||||
private class TaggedTemplateLiteralCallNode extends CallNodeDef, ValueNode {
|
||||
override TaggedTemplateExpr astNode;
|
||||
|
||||
override InvokeExpr getInvokeExpr() { none() } // There is no InvokeExpr for this.
|
||||
|
||||
override string getCalleeName() {
|
||||
result = astNode.getTag().getUnderlyingValue().(Identifier).getName()
|
||||
}
|
||||
|
||||
override DataFlow::Node getCalleeNode() { result = DataFlow::valueNode(astNode.getTag()) }
|
||||
|
||||
override DataFlow::Node getArgument(int i) {
|
||||
// the first argument sent to the function is the array of string parts, which we don't model.
|
||||
// rank is 1-indexed, which is perfect here.
|
||||
result =
|
||||
DataFlow::valueNode(rank[i](Expr e, int index |
|
||||
e = astNode.getTemplate().getElement(index) and not e instanceof TemplateElement
|
||||
|
|
||||
e order by index
|
||||
))
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnArgument() { result = this.getArgument(_) }
|
||||
|
||||
override DataFlow::Node getASpreadArgument() { none() }
|
||||
|
||||
// we don't model the string constants as arguments, but we still count them.
|
||||
override int getNumArgument() { result = count(this.getArgument(_)) + 1 }
|
||||
|
||||
override DataFlow::Node getReceiver() { none() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -92,13 +92,20 @@ class InvokeNode extends DataFlow::SourceNode instanceof DataFlow::Impl::InvokeN
|
||||
* but the position of `z` cannot be determined, hence there are no first and second
|
||||
* argument nodes.
|
||||
*/
|
||||
DataFlow::Node getArgument(int i) { result = super.getArgument(i) }
|
||||
cached
|
||||
DataFlow::Node getArgument(int i) {
|
||||
result = super.getArgument(i) and Stages::DataFlowStage::ref()
|
||||
}
|
||||
|
||||
/** Gets the data flow node corresponding to an argument of this invocation. */
|
||||
DataFlow::Node getAnArgument() { result = super.getAnArgument() }
|
||||
cached
|
||||
DataFlow::Node getAnArgument() { result = super.getAnArgument() and Stages::DataFlowStage::ref() }
|
||||
|
||||
/** Gets the data flow node corresponding to the last argument of this invocation. */
|
||||
DataFlow::Node getLastArgument() { result = this.getArgument(this.getNumArgument() - 1) }
|
||||
cached
|
||||
DataFlow::Node getLastArgument() {
|
||||
result = this.getArgument(this.getNumArgument() - 1) and Stages::DataFlowStage::ref()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a data flow node corresponding to an array of values being passed as
|
||||
|
||||
@@ -145,6 +145,12 @@ module Stages {
|
||||
exists(any(DataFlow::PropRef ref).getBase())
|
||||
or
|
||||
exists(any(DataFlow::ClassNode cls))
|
||||
or
|
||||
exists(any(DataFlow::CallNode node).getArgument(_))
|
||||
or
|
||||
exists(any(DataFlow::CallNode node).getAnArgument())
|
||||
or
|
||||
exists(any(DataFlow::CallNode node).getLastArgument())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
import ApiGraphs.VerifyAssertions
|
||||
9
javascript/ql/test/ApiGraphs/tagged-template/index.js
Normal file
9
javascript/ql/test/ApiGraphs/tagged-template/index.js
Normal file
@@ -0,0 +1,9 @@
|
||||
const tag = require("tag");
|
||||
|
||||
tag.string`string1
|
||||
${23}` // def=moduleImport("tag").getMember("exports").getMember("string").getParameter(1)
|
||||
|
||||
tag.highlight`string2
|
||||
${23}
|
||||
morestring
|
||||
${42}` // def=moduleImport("tag").getMember("exports").getMember("highlight").getParameter(2)
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"name": "tagged-template"
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
import javascript
|
||||
|
||||
query predicate test_getACallee(DataFlow::InvokeNode c, Function res) { res = c.getACallee() }
|
||||
@@ -1,5 +0,0 @@
|
||||
import javascript
|
||||
|
||||
query predicate test_getAFunctionValue(DataFlow::Node node, DataFlow::FunctionNode res) {
|
||||
res = node.getAFunctionValue()
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import javascript
|
||||
|
||||
query predicate test_getAnArgument(DataFlow::InvokeNode invk, DataFlow::Node res) {
|
||||
res = invk.getAnArgument()
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import javascript
|
||||
|
||||
query predicate test_getArgument(DataFlow::InvokeNode invk, int i, DataFlow::Node res) {
|
||||
res = invk.getArgument(i)
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import javascript
|
||||
|
||||
query predicate test_getCalleeName(DataFlow::InvokeNode invk, string res) {
|
||||
res = invk.getCalleeName()
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import javascript
|
||||
|
||||
query predicate test_getCalleeNode(DataFlow::InvokeNode invk, DataFlow::Node res) {
|
||||
res = invk.getCalleeNode()
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import javascript
|
||||
|
||||
query predicate test_getLastArgument(DataFlow::InvokeNode invk, DataFlow::Node res) {
|
||||
res = invk.getLastArgument()
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import javascript
|
||||
|
||||
query predicate test_getNumArgument(DataFlow::InvokeNode invk, int res) {
|
||||
res = invk.getNumArgument()
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
import javascript
|
||||
|
||||
query predicate test_isImprecise(DataFlow::InvokeNode invk) { invk.isImprecise() }
|
||||
@@ -1,3 +0,0 @@
|
||||
import javascript
|
||||
|
||||
query predicate test_isIncomplete(DataFlow::InvokeNode invk) { invk.isIncomplete() }
|
||||
@@ -1,3 +0,0 @@
|
||||
import javascript
|
||||
|
||||
query predicate test_isUncertain(DataFlow::InvokeNode invk) { invk.isUncertain() }
|
||||
@@ -0,0 +1,5 @@
|
||||
function fooTag(strings, par1, par2) {
|
||||
|
||||
}
|
||||
|
||||
fooTag`hello ${arg1} world ${arg2}`
|
||||
@@ -126,6 +126,8 @@ test_getAFunctionValue
|
||||
| strict.js:1:1:8:2 | (functi ... ode.\\n}) | strict.js:1:2:8:1 | functio ... mode.\\n} |
|
||||
| strict.js:1:2:8:1 | functio ... mode.\\n} | strict.js:1:2:8:1 | functio ... mode.\\n} |
|
||||
| strict.js:3:5:5:5 | functio ... ;\\n } | strict.js:3:5:5:5 | functio ... ;\\n } |
|
||||
| taggedTemplate.js:1:1:3:1 | functio ... 2) {\\n\\n} | taggedTemplate.js:1:1:3:1 | functio ... 2) {\\n\\n} |
|
||||
| taggedTemplate.js:5:1:5:6 | fooTag | taggedTemplate.js:1:1:3:1 | functio ... 2) {\\n\\n} |
|
||||
| tst3.js:1:1:1:22 | functio ... fn() {} | tst3.js:1:1:1:22 | functio ... fn() {} |
|
||||
| tst3.js:2:1:2:23 | functio ... n2() {} | tst3.js:2:1:2:23 | functio ... n2() {} |
|
||||
| tst.js:1:1:1:15 | function f() {} | tst.js:1:1:1:15 | function f() {} |
|
||||
@@ -221,6 +223,8 @@ test_getArgument
|
||||
| reflection.js:7:1:7:22 | reflective call | 1 | reflection.js:7:20:7:21 | 19 |
|
||||
| reflection.js:8:1:8:25 | add.app ... 3, 19]) | 0 | reflection.js:8:11:8:14 | null |
|
||||
| reflection.js:8:1:8:25 | add.app ... 3, 19]) | 1 | reflection.js:8:17:8:24 | [23, 19] |
|
||||
| taggedTemplate.js:5:1:5:35 | fooTag` ... {arg2}` | 1 | taggedTemplate.js:5:16:5:19 | arg1 |
|
||||
| taggedTemplate.js:5:1:5:35 | fooTag` ... {arg2}` | 2 | taggedTemplate.js:5:30:5:33 | arg2 |
|
||||
| tst.js:22:1:22:4 | l(k) | 0 | tst.js:22:3:22:3 | k |
|
||||
| tst.js:42:2:42:29 | functio ... x; }(o) | 0 | tst.js:42:28:42:28 | o |
|
||||
test_getNumArgument
|
||||
@@ -259,6 +263,7 @@ test_getNumArgument
|
||||
| strict2.js:9:10:9:14 | foo() | 0 |
|
||||
| strict.js:1:1:8:4 | (functi ... e.\\n})() | 0 |
|
||||
| strict.js:7:10:7:14 | foo() | 0 |
|
||||
| taggedTemplate.js:5:1:5:35 | fooTag` ... {arg2}` | 3 |
|
||||
| tst.js:6:1:6:3 | f() | 0 |
|
||||
| tst.js:7:1:7:3 | g() | 0 |
|
||||
| tst.js:8:1:8:3 | h() | 0 |
|
||||
@@ -362,6 +367,7 @@ test_getCalleeNode
|
||||
| strict2.js:9:10:9:14 | foo() | strict2.js:9:10:9:12 | foo |
|
||||
| strict.js:1:1:8:4 | (functi ... e.\\n})() | strict.js:1:1:8:2 | (functi ... ode.\\n}) |
|
||||
| strict.js:7:10:7:14 | foo() | strict.js:7:10:7:12 | foo |
|
||||
| taggedTemplate.js:5:1:5:35 | fooTag` ... {arg2}` | taggedTemplate.js:5:1:5:6 | fooTag |
|
||||
| tst.js:6:1:6:3 | f() | tst.js:6:1:6:1 | f |
|
||||
| tst.js:7:1:7:3 | g() | tst.js:7:1:7:1 | g |
|
||||
| tst.js:8:1:8:3 | h() | tst.js:8:1:8:1 | h |
|
||||
@@ -400,6 +406,7 @@ test_getLastArgument
|
||||
| reflection.js:7:1:7:22 | add.cal ... 23, 19) | reflection.js:7:20:7:21 | 19 |
|
||||
| reflection.js:7:1:7:22 | reflective call | reflection.js:7:20:7:21 | 19 |
|
||||
| reflection.js:8:1:8:25 | add.app ... 3, 19]) | reflection.js:8:17:8:24 | [23, 19] |
|
||||
| taggedTemplate.js:5:1:5:35 | fooTag` ... {arg2}` | taggedTemplate.js:5:30:5:33 | arg2 |
|
||||
| tst.js:22:1:22:4 | l(k) | tst.js:22:3:22:3 | k |
|
||||
| tst.js:42:2:42:29 | functio ... x; }(o) | tst.js:42:28:42:28 | o |
|
||||
test_getAnArgument
|
||||
@@ -420,6 +427,8 @@ test_getAnArgument
|
||||
| reflection.js:7:1:7:22 | reflective call | reflection.js:7:20:7:21 | 19 |
|
||||
| reflection.js:8:1:8:25 | add.app ... 3, 19]) | reflection.js:8:11:8:14 | null |
|
||||
| reflection.js:8:1:8:25 | add.app ... 3, 19]) | reflection.js:8:17:8:24 | [23, 19] |
|
||||
| taggedTemplate.js:5:1:5:35 | fooTag` ... {arg2}` | taggedTemplate.js:5:16:5:19 | arg1 |
|
||||
| taggedTemplate.js:5:1:5:35 | fooTag` ... {arg2}` | taggedTemplate.js:5:30:5:33 | arg2 |
|
||||
| tst.js:22:1:22:4 | l(k) | tst.js:22:3:22:3 | k |
|
||||
| tst.js:42:2:42:29 | functio ... x; }(o) | tst.js:42:28:42:28 | o |
|
||||
test_getACallee
|
||||
@@ -449,6 +458,7 @@ test_getACallee
|
||||
| reflection.js:8:1:8:25 | reflective call | reflection.js:1:1:3:1 | functio ... x+y;\\n} |
|
||||
| strict2.js:2:1:10:4 | (functi ... e.\\n})() | strict2.js:2:2:10:1 | functio ... mode.\\n} |
|
||||
| strict.js:1:1:8:4 | (functi ... e.\\n})() | strict.js:1:2:8:1 | functio ... mode.\\n} |
|
||||
| taggedTemplate.js:5:1:5:35 | fooTag` ... {arg2}` | taggedTemplate.js:1:1:3:1 | functio ... 2) {\\n\\n} |
|
||||
| tst.js:6:1:6:3 | f() | tst.js:1:1:1:15 | function f() {} |
|
||||
| tst.js:7:1:7:3 | g() | tst.js:2:9:2:21 | function() {} |
|
||||
| tst.js:8:1:8:3 | h() | tst.js:3:5:3:17 | function() {} |
|
||||
@@ -509,6 +519,7 @@ test_getCalleeName
|
||||
| reflection.js:8:1:8:25 | add.app ... 3, 19]) | apply |
|
||||
| strict2.js:9:10:9:14 | foo() | foo |
|
||||
| strict.js:7:10:7:14 | foo() | foo |
|
||||
| taggedTemplate.js:5:1:5:35 | fooTag` ... {arg2}` | fooTag |
|
||||
| tst.js:6:1:6:3 | f() | f |
|
||||
| tst.js:7:1:7:3 | g() | g |
|
||||
| tst.js:8:1:8:3 | h() | h |
|
||||
|
||||
@@ -1,11 +1,37 @@
|
||||
import isUncertain
|
||||
import getAFunctionValue
|
||||
import getArgument
|
||||
import getNumArgument
|
||||
import isIncomplete
|
||||
import getCalleeNode
|
||||
import getLastArgument
|
||||
import getAnArgument
|
||||
import getACallee
|
||||
import getCalleeName
|
||||
import isImprecise
|
||||
import javascript
|
||||
|
||||
query predicate test_isUncertain(DataFlow::InvokeNode invk) { invk.isUncertain() }
|
||||
|
||||
query predicate test_getAFunctionValue(DataFlow::Node node, DataFlow::FunctionNode res) {
|
||||
res = node.getAFunctionValue()
|
||||
}
|
||||
|
||||
query predicate test_getArgument(DataFlow::InvokeNode invk, int i, DataFlow::Node res) {
|
||||
res = invk.getArgument(i)
|
||||
}
|
||||
|
||||
query predicate test_getNumArgument(DataFlow::InvokeNode invk, int res) {
|
||||
res = invk.getNumArgument()
|
||||
}
|
||||
|
||||
query predicate test_isIncomplete(DataFlow::InvokeNode invk) { invk.isIncomplete() }
|
||||
|
||||
query predicate test_getCalleeNode(DataFlow::InvokeNode invk, DataFlow::Node res) {
|
||||
res = invk.getCalleeNode()
|
||||
}
|
||||
|
||||
query predicate test_getLastArgument(DataFlow::InvokeNode invk, DataFlow::Node res) {
|
||||
res = invk.getLastArgument()
|
||||
}
|
||||
|
||||
query predicate test_getAnArgument(DataFlow::InvokeNode invk, DataFlow::Node res) {
|
||||
res = invk.getAnArgument()
|
||||
}
|
||||
|
||||
query predicate test_getACallee(DataFlow::InvokeNode c, Function res) { res = c.getACallee() }
|
||||
|
||||
query predicate test_getCalleeName(DataFlow::InvokeNode invk, string res) {
|
||||
res = invk.getCalleeName()
|
||||
}
|
||||
|
||||
query predicate test_isImprecise(DataFlow::InvokeNode invk) { invk.isImprecise() }
|
||||
|
||||
@@ -231,6 +231,7 @@ typeInferenceMismatch
|
||||
| tst.js:2:13:2:20 | source() | tst.js:47:10:47:30 | Buffer. ... 'hex') |
|
||||
| tst.js:2:13:2:20 | source() | tst.js:48:10:48:22 | new Buffer(x) |
|
||||
| tst.js:2:13:2:20 | source() | tst.js:51:10:51:31 | seriali ... ript(x) |
|
||||
| tst.js:2:13:2:20 | source() | tst.js:54:14:54:19 | unsafe |
|
||||
| xml.js:5:18:5:25 | source() | xml.js:8:14:8:17 | text |
|
||||
| xml.js:12:17:12:24 | source() | xml.js:13:14:13:19 | result |
|
||||
| xml.js:23:18:23:25 | source() | xml.js:20:14:20:17 | attr |
|
||||
|
||||
@@ -109,3 +109,4 @@
|
||||
| thisAssignments.js:4:17:4:24 | source() | thisAssignments.js:5:10:5:18 | obj.field |
|
||||
| thisAssignments.js:7:19:7:26 | source() | thisAssignments.js:8:10:8:20 | this.field2 |
|
||||
| tst.js:2:13:2:20 | source() | tst.js:4:10:4:10 | x |
|
||||
| tst.js:2:13:2:20 | source() | tst.js:54:14:54:19 | unsafe |
|
||||
|
||||
@@ -49,4 +49,12 @@ function test() {
|
||||
|
||||
const serializeJavaScript = require("serialize-javascript");
|
||||
sink(serializeJavaScript(x)) // NOT OK
|
||||
|
||||
function tagged(strings, safe, unsafe) {
|
||||
sink(unsafe) // NOT OK
|
||||
sink(safe) // OK
|
||||
sink(strings) // OK
|
||||
}
|
||||
|
||||
tagged`foo ${"safe"} bar ${x} baz`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user