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

javascript/ql/lib/semmle/javascript/internal/flow_summaries/AllFlowSummaries.qll

Lines changed: 1 addition & 0 deletions
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
Lines changed: 104 additions & 0 deletions
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+
}

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

Lines changed: 2 additions & 2 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy