Skip to content

Commit 030bae9

Browse files
committed
JS: Canonicalize ThisNode
1 parent 3bc5e3b commit 030bae9

File tree

10 files changed

+78
-7
lines changed

10 files changed

+78
-7
lines changed

javascript/ql/src/semmle/javascript/Expr.qll

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,16 @@ class ThisExpr extends @thisexpr, Expr {
303303
Function getBinder() {
304304
result = getEnclosingFunction().getThisBinder()
305305
}
306+
307+
/**
308+
* Gets the function or top-level whose `this` binding this expression refers to,
309+
* which is the nearest enclosing non-arrow function or top-level.
310+
*/
311+
StmtContainer getBindingContainer() {
312+
result = getContainer().(Function).getThisBindingContainer()
313+
or
314+
result = getContainer().(TopLevel)
315+
}
306316
}
307317

308318
/** An array literal. */

javascript/ql/src/semmle/javascript/Functions.qll

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,17 @@ class Function extends @function, Parameterized, TypeParameterized, StmtContaine
206206
result = this
207207
}
208208

209+
/**
210+
* Gets the function or top-level whose `this` binding a `this` expression in this function refers to,
211+
* which is the nearest enclosing non-arrow function or top-level.
212+
*/
213+
StmtContainer getThisBindingContainer() {
214+
result = getThisBinder()
215+
or
216+
not exists(getThisBinder()) and
217+
result = getTopLevel()
218+
}
219+
209220
/**
210221
* Holds if this function has a mapped `arguments` variable whose indices are aliased
211222
* with the function's parameters.

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ module DataFlow {
3232
or TReflectiveCallNode(MethodCallExpr ce, string kind) {
3333
ce.getMethodName() = kind and (kind = "call" or kind = "apply")
3434
}
35+
or TThisNode(StmtContainer f) { f.(Function).getThisBinder() = f or f instanceof TopLevel }
3536

3637
/**
3738
* A node in the data flow graph.
@@ -867,6 +868,13 @@ module DataFlow {
867868
nd = TDestructuringPatternNode(p)
868869
}
869870

871+
/**
872+
* INTERNAL: Use `thisNode(StmtContainer container)` instead.
873+
*/
874+
predicate thisNode(DataFlow::Node node, StmtContainer container) {
875+
node = TThisNode(container)
876+
}
877+
870878
/**
871879
* A classification of flows that are not modeled, or only modeled incompletely, by
872880
* `DataFlowNode`:
@@ -970,6 +978,11 @@ module DataFlow {
970978
pred = valueNode(defSourceNode(def)) and
971979
succ = TDestructuringPatternNode(def.getTarget())
972980
)
981+
or
982+
// flow from 'this' parameter into 'this' expressions
983+
exists (ThisExpr thiz |
984+
pred = TThisNode(thiz.getBindingContainer()) and
985+
succ = valueNode(thiz))
973986
}
974987

975988
/**

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

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -198,16 +198,36 @@ class NewNode extends InvokeNode {
198198
override DataFlow::Impl::NewNodeDef impl;
199199
}
200200

201-
/** A data flow node corresponding to a `this` expression. */
202-
class ThisNode extends DataFlow::ValueNode, DataFlow::DefaultSourceNode {
203-
override ThisExpr astNode;
201+
/** A data flow node corresponding to the `this` parameter in a function or `this` at the top-level. */
202+
class ThisNode extends DataFlow::Node, DataFlow::DefaultSourceNode {
203+
ThisNode() {
204+
DataFlow::thisNode(this, _)
205+
}
204206

205207
/**
206208
* Gets the function whose `this` binding this expression refers to,
207209
* which is the nearest enclosing non-arrow function.
208210
*/
209211
FunctionNode getBinder() {
210-
result = DataFlow::valueNode(astNode.getBinder())
212+
exists (Function binder |
213+
DataFlow::thisNode(this, binder) and
214+
result = DataFlow::valueNode(binder))
215+
}
216+
217+
/**
218+
* Gets the function or top-level whose `this` binding this expression refers to,
219+
* which is the nearest enclosing non-arrow function or top-level.
220+
*/
221+
StmtContainer getBindingContainer() {
222+
DataFlow::thisNode(this, result)
223+
}
224+
225+
override string toString() { result = "this" }
226+
227+
override predicate hasLocationInfo(string filepath, int startline, int startcolumn,
228+
int endline, int endcolumn) {
229+
// Use the function entry as the location
230+
getBindingContainer().getEntry().getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
211231
}
212232
}
213233

javascript/ql/src/semmle/javascript/dataflow/Sources.qll

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,6 @@ class DefaultSourceNode extends SourceNode {
185185
astNode instanceof ObjectExpr or
186186
astNode instanceof ArrayExpr or
187187
astNode instanceof JSXNode or
188-
astNode instanceof ThisExpr or
189188
astNode instanceof GlobalVarAccess or
190189
astNode instanceof ExternalModuleReference
191190
)
@@ -198,5 +197,7 @@ class DefaultSourceNode extends SourceNode {
198197
DataFlow::parameterNode(this, _)
199198
or
200199
this instanceof DataFlow::Impl::InvokeNodeDef
200+
or
201+
DataFlow::thisNode(this, _)
201202
}
202203
}

javascript/ql/src/semmle/javascript/frameworks/React.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,10 @@ abstract class ReactComponent extends ASTNode {
5252
}
5353

5454
/**
55-
* Gets a `this` access in an instance method of this component.
55+
* Gets the `this` node in an instance method of this component.
5656
*/
5757
DataFlow::SourceNode getAThisAccess() {
58-
result.asExpr().(ThisExpr).getBinder() = getInstanceMethod(_)
58+
result.(DataFlow::ThisNode).getBinder().getFunction() = getInstanceMethod(_)
5959
}
6060

6161
/**

javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
| partialCalls.js:4:17:4:24 | source() | partialCalls.js:30:14:30:20 | x.value |
44
| partialCalls.js:4:17:4:24 | source() | partialCalls.js:41:10:41:18 | id(taint) |
55
| partialCalls.js:4:17:4:24 | source() | partialCalls.js:51:14:51:14 | x |
6+
| thisAssignments.js:4:17:4:24 | source() | thisAssignments.js:5:10:5:18 | obj.field |
7+
| thisAssignments.js:7:19:7:26 | source() | thisAssignments.js:8:10:8:20 | this.field2 |
68
| tst.js:2:13:2:20 | source() | tst.js:4:10:4:10 | x |
79
| tst.js:2:13:2:20 | source() | tst.js:5:10:5:22 | "/" + x + "!" |
810
| tst.js:2:13:2:20 | source() | tst.js:14:10:14:17 | x.sort() |
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
class C {
2+
foo() {
3+
let obj = {};
4+
obj.field = source();
5+
sink(obj.field); // NOT OK - tainted
6+
7+
this.field2 = source();
8+
sink(this.field2); // NOT OK - tainted
9+
}
10+
}

javascript/ql/test/query-tests/Security/CWE-079/Xss.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
| addEventListener.js:2:20:2:29 | event.data | Cross-site scripting vulnerability due to $@. | addEventListener.js:1:43:1:47 | event | user-provided value |
12
| jquery.js:4:5:4:11 | tainted | Cross-site scripting vulnerability due to $@. | jquery.js:2:17:2:33 | document.location | user-provided value |
23
| jquery.js:7:5:7:34 | "<div i ... + "\\">" | Cross-site scripting vulnerability due to $@. | jquery.js:2:17:2:33 | document.location | user-provided value |
34
| jquery.js:8:18:8:34 | "XSS: " + tainted | Cross-site scripting vulnerability due to $@. | jquery.js:2:17:2:33 | document.location | user-provided value |
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
this.addEventListener('message', function(event) {
2+
document.write(event.data); // NOT OK
3+
})

0 commit comments

Comments
 (0)