Skip to content

Commit 8d8148d

Browse files
author
Max Schaefer
authored
Merge pull request #294 from asger-semmle/canonical-this-source
JS: Canonicalize 'this' in the data-flow graph
2 parents 355786c + 9fb73f4 commit 8d8148d

File tree

22 files changed

+172
-30
lines changed

22 files changed

+172
-30
lines changed

change-notes/1.19/analysis-javascript.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,8 @@
3535

3636
## Changes to QL libraries
3737

38-
* The flow configuration framework now supports distinguishing and tracking different kinds of taint, specified by an extensible class `FlowLabel` (which can also be referred to by its alias `TaintKind`).
38+
* The flow configuration framework now supports distinguishing and tracking different kinds of taint, specified by an extensible class `FlowLabel` (which can also be referred to by its alias `TaintKind`).
39+
40+
* The `DataFlow::ThisNode` class now corresponds to the implicit receiver parameter of a function, as opposed to an indivdual `this` expression. This means `getALocalSource` now maps all `this` expressions within a given function to the same source. The data-flow node associated with a `ThisExpr` can no longer be cast to `DataFlow::SourceNode` or `DataFlow::ThisNode` - it is recomended to use `getALocalSource` before casting or instead of casting.
41+
42+
* `ReactComponent::getAThisAccess` has been renamed to `getAThisNode`. The old name is still usable but is deprecated. It no longer gets individual `this` expressions, but the `ThisNode` mentioned above.

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/StandardLibrary.qll

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,9 @@ class JsonParseCall extends MethodCallExpr {
6262
* However, since the function could be invoked in another way, we additionally
6363
* still infer the ordinary abstract value.
6464
*/
65-
private class AnalyzedThisInArrayIterationFunction extends AnalyzedValueNode, DataFlow::ThisNode {
65+
private class AnalyzedThisInArrayIterationFunction extends AnalyzedNode, DataFlow::ThisNode {
6666

67-
AnalyzedValueNode thisSource;
67+
AnalyzedNode thisSource;
6868

6969
AnalyzedThisInArrayIterationFunction() {
7070
exists(DataFlow::MethodCallNode bindingCall, string name |
@@ -82,7 +82,7 @@ private class AnalyzedThisInArrayIterationFunction extends AnalyzedValueNode, Da
8282

8383
override AbstractValue getALocalValue() {
8484
result = thisSource.getALocalValue() or
85-
result = AnalyzedValueNode.super.getALocalValue()
85+
result = AnalyzedNode.super.getALocalValue()
8686
}
8787

8888
}

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/dataflow/internal/InterProceduralTypeInference.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import AbstractValuesImpl
1010
/**
1111
* Flow analysis for `this` expressions inside functions.
1212
*/
13-
private abstract class AnalyzedThisExpr extends DataFlow::AnalyzedValueNode, DataFlow::ThisNode {
13+
private abstract class AnalyzedThisExpr extends DataFlow::AnalyzedNode, DataFlow::ThisNode {
1414
DataFlow::FunctionNode binder;
1515

1616
AnalyzedThisExpr() {
@@ -29,7 +29,7 @@ private abstract class AnalyzedThisExpr extends DataFlow::AnalyzedValueNode, Dat
2929
*/
3030
private class AnalyzedThisInBoundFunction extends AnalyzedThisExpr {
3131

32-
AnalyzedValueNode thisSource;
32+
AnalyzedNode thisSource;
3333

3434
AnalyzedThisInBoundFunction() {
3535
exists(string name |

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ module LodashUnderscore {
2727
* However, since the function could be invoked in another way, we additionally
2828
* still infer the ordinary abstract value.
2929
*/
30-
private class AnalyzedThisInBoundCallback extends AnalyzedValueNode, DataFlow::ThisNode {
30+
private class AnalyzedThisInBoundCallback extends AnalyzedNode, DataFlow::ThisNode {
3131

32-
AnalyzedValueNode thisSource;
32+
AnalyzedNode thisSource;
3333

3434
AnalyzedThisInBoundCallback() {
3535
exists(DataFlow::CallNode bindingCall, string binderName, int callbackIndex, int contextIndex, int argumentCount |
@@ -128,7 +128,7 @@ private class AnalyzedThisInBoundCallback extends AnalyzedValueNode, DataFlow::T
128128

129129
override AbstractValue getALocalValue() {
130130
result = thisSource.getALocalValue() or
131-
result = AnalyzedValueNode.super.getALocalValue()
131+
result = AnalyzedNode.super.getALocalValue()
132132
}
133133

134134
}

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

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,20 @@ 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
*/
57+
DataFlow::SourceNode getAThisNode() {
58+
result.(DataFlow::ThisNode).getBinder().getFunction() = getInstanceMethod(_)
59+
}
60+
61+
/**
62+
* Gets the `this` node in an instance method of this component.
63+
*
64+
* DEPRECATED: Use `getAThisNode` instead.
65+
*/
66+
deprecated
5767
DataFlow::SourceNode getAThisAccess() {
58-
result.asExpr().(ThisExpr).getBinder() = getInstanceMethod(_)
68+
result = getAThisNode()
5969
}
6070

6171
/**
@@ -515,9 +525,9 @@ private class FactoryDefinition extends ReactElementDefinition {
515525
* However, since the function could be invoked in another way, we additionally
516526
* still infer the ordinary abstract value.
517527
*/
518-
private class AnalyzedThisInBoundCallback extends AnalyzedValueNode, DataFlow::ThisNode {
528+
private class AnalyzedThisInBoundCallback extends AnalyzedNode, DataFlow::ThisNode {
519529

520-
AnalyzedValueNode thisSource;
530+
AnalyzedNode thisSource;
521531

522532
AnalyzedThisInBoundCallback() {
523533
exists(DataFlow::CallNode bindingCall, string binderName |
@@ -533,7 +543,7 @@ private class AnalyzedThisInBoundCallback extends AnalyzedValueNode, DataFlow::T
533543

534544
override AbstractValue getALocalValue() {
535545
result = thisSource.getALocalValue() or
536-
result = AnalyzedValueNode.super.getALocalValue()
546+
result = AnalyzedNode.super.getALocalValue()
537547
}
538548

539549
}

0 commit comments

Comments
 (0)