Skip to content

Commit 335d965

Browse files
committed
JS: Add ClassHarness
1 parent 8239758 commit 335d965

File tree

3 files changed

+107
-2
lines changed

3 files changed

+107
-2
lines changed

Diff for: javascript/ql/lib/semmle/javascript/internal/flow_summaries/AllFlowSummaries.qll

+1
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ private import Promises
1111
private import Sets
1212
private import Strings
1313
private import DynamicImportStep
14+
private import ClassHarness
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/**
2+
* Contains flow for the "class harness", which facilitates flow from constructor to methods in a class.
3+
*/
4+
5+
private import javascript
6+
private import semmle.javascript.dataflow.internal.DataFlowNode
7+
private import semmle.javascript.dataflow.internal.AdditionalFlowInternal
8+
private import semmle.javascript.dataflow.internal.DataFlowPrivate
9+
10+
/**
11+
* Synthesizes a callable for each class, which invokes the class constructor and every
12+
* instance method with the same value of `this`.
13+
*
14+
* This ensures flow between methods in a class when the source originated "within the class",
15+
* but not when the flow into the field came from an argument.
16+
*
17+
* For example:
18+
* ```js
19+
* class C {
20+
* constructor(arg) {
21+
* this.x = sourceOfTaint();
22+
* this.y = arg;
23+
* }
24+
* method() {
25+
* sink(this.x); // sourceOfTaint() flows here
26+
* sink(this.y); // but 'arg' does not flow here (only through real call sites)
27+
* }
28+
* }
29+
* ```
30+
*
31+
* The class harness for a class `C` can roughly be thought of as the following code:
32+
* ```js
33+
* function classHarness() {
34+
* var c = new C();
35+
* while (true) {
36+
* // call an arbitrary instance methods in the loop
37+
* c.arbitraryInstaceMethod();
38+
* }
39+
* }
40+
* ```
41+
*
42+
* This is realized with the following data flow graph:
43+
* ```
44+
* [Call to constructor]
45+
* |
46+
* | post-update for 'this' argument
47+
* V
48+
* [Data flow node] <----------------------+
49+
* | |
50+
* | 'this' argument | post-update for 'this' argument
51+
* V |
52+
* [Call to an instance method] -----------+
53+
* ```
54+
*/
55+
class ClassHarnessModel extends AdditionalFlowInternal {
56+
override predicate needsSynthesizedCallable(AstNode node, string tag) {
57+
node instanceof Function and
58+
not node instanceof ArrowFunctionExpr and // can't be called with 'new'
59+
not node.getTopLevel().isExterns() and // we don't need harnesses in externs
60+
tag = "class-harness"
61+
}
62+
63+
override predicate needsSynthesizedCall(AstNode node, string tag, DataFlowCallable container) {
64+
container = getSynthesizedCallable(node, "class-harness") and
65+
tag = ["class-harness-constructor-call", "class-harness-method-call"]
66+
}
67+
68+
override predicate needsSynthesizedNode(AstNode node, string tag, DataFlowCallable container) {
69+
// We synthesize two nodes, but note that `class-harness-constructor-this-arg` never actually has any
70+
// ingoing flow, we just need it to specify which post-update node to use for that argument.
71+
container = getSynthesizedCallable(node, "class-harness") and
72+
tag = ["class-harness-constructor-this-arg", "class-harness-method-this-arg"]
73+
}
74+
75+
override predicate argument(DataFlowCall call, ArgumentPosition pos, DataFlow::Node value) {
76+
pos.isThis() and
77+
exists(Function f |
78+
call = getSynthesizedCall(f, "class-harness-constructor-call") and
79+
value = getSynthesizedNode(f, "class-harness-constructor-this-arg")
80+
or
81+
call = getSynthesizedCall(f, "class-harness-method-call") and
82+
value = getSynthesizedNode(f, "class-harness-method-this-arg")
83+
)
84+
}
85+
86+
override predicate postUpdate(DataFlow::Node pre, DataFlow::Node post) {
87+
exists(Function f |
88+
pre =
89+
getSynthesizedNode(f,
90+
["class-harness-constructor-this-arg", "class-harness-method-this-arg"]) and
91+
post = getSynthesizedNode(f, "class-harness-method-this-arg")
92+
)
93+
}
94+
95+
override predicate viableCallable(DataFlowCall call, DataFlowCallable target) {
96+
exists(DataFlow::ClassNode cls, Function f | f = cls.getConstructor().getFunction() |
97+
call = getSynthesizedCall(f, "class-harness-constructor-call") and
98+
target.asSourceCallable() = f
99+
or
100+
call = getSynthesizedCall(f, "class-harness-method-call") and
101+
target.asSourceCallable() = cls.getAnInstanceMember().getFunction()
102+
)
103+
}
104+
}

Diff for: javascript/ql/test/library-tests/TripleDot/class-harness.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ function h1() {
66
this.x = source("h1.1")
77
}
88
method() {
9-
sink(this.x); // $ MISSING: hasValueFlow=h1.1
9+
sink(this.x); // $ hasValueFlow=h1.1
1010
}
1111
}
1212
}
@@ -17,7 +17,7 @@ function h2() {
1717
this.x = source("h2.1")
1818
}
1919
method2() {
20-
sink(this.x); // $ MISSING: hasValueFlow=h2.1
20+
sink(this.x); // $ hasValueFlow=h2.1
2121
}
2222
}
2323
}

0 commit comments

Comments
 (0)