Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b85f446

Browse files
author
Max Schaefer
authored
Merge pull request #1049 from asger-semmle/js-type-tracking
JS: Add TypeTracking library
2 parents c087394 + 732ddbc commit b85f446

9 files changed

Lines changed: 539 additions & 0 deletions

File tree

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1221,4 +1221,5 @@ module DataFlow {
12211221
import TypeInference
12221222
import Configuration
12231223
import TrackedNodes
1224+
import TypeTracking
12241225
}

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import javascript
10+
private import semmle.javascript.dataflow.TypeTracking
1011

1112
/**
1213
* A source node for local data flow, that is, a node from which local data flow is tracked.
@@ -153,6 +154,34 @@ class SourceNode extends DataFlow::Node {
153154
DataFlow::SourceNode getAPropertySource(string prop) {
154155
result.flowsTo(getAPropertyWrite(prop).getRhs())
155156
}
157+
158+
/**
159+
* EXPERIMENTAL.
160+
*
161+
* Gets a node that this node may flow to using one heap and/or interprocedural step.
162+
*
163+
* See `TypeTracker` for more details about how to use this.
164+
*/
165+
DataFlow::SourceNode track(TypeTracker t2, TypeTracker t) {
166+
exists(StepSummary summary |
167+
StepSummary::step(this, result, summary) and
168+
t = StepSummary::append(t2, summary)
169+
)
170+
}
171+
172+
/**
173+
* EXPERIMENTAL.
174+
*
175+
* Gets a node that may flow into this one using one heap and/or interprocedural step.
176+
*
177+
* See `TypeBackTracker` for more details about how to use this.
178+
*/
179+
DataFlow::SourceNode backtrack(TypeBackTracker t2, TypeBackTracker t) {
180+
exists(StepSummary summary |
181+
StepSummary::step(result, this, summary) and
182+
t = StepSummary::prepend(summary, t2)
183+
)
184+
}
156185
}
157186

158187
module SourceNode {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*/
55

66
import javascript
7+
private import internal.FlowSteps as FlowSteps
78

89
/**
910
* A data flow node that should be tracked inter-procedurally.
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
/**
2+
* Provides the `TypeTracker` class for tracking types interprocedurally.
3+
*
4+
* This provides an alternative to `DataFlow::TrackedNode` and `AbstractValue`
5+
* for tracking certain types interprocedurally without computing which source
6+
* a given value came from.
7+
*/
8+
9+
import javascript
10+
private import internal.FlowSteps
11+
12+
/**
13+
* A pair of booleans, indicating whether a path goes through a return and/or a call.
14+
*
15+
* Identical to `TPathSummary` except without flow labels.
16+
*/
17+
private newtype TStepSummary = MkStepSummary(boolean hasReturn, boolean hasCall) {
18+
(hasReturn = true or hasReturn = false) and
19+
(hasCall = true or hasCall = false)
20+
}
21+
22+
/**
23+
* INTERNAL: Use `TypeTracker` or `TypeBackTracker` instead.
24+
*
25+
* Summary of the steps needed to track a value to a given dataflow node.
26+
*/
27+
class StepSummary extends TStepSummary {
28+
Boolean hasReturn;
29+
30+
Boolean hasCall;
31+
32+
StepSummary() { this = MkStepSummary(hasReturn, hasCall) }
33+
34+
/** Indicates whether the path represented by this summary contains any return steps. */
35+
boolean hasReturn() { result = hasReturn }
36+
37+
/** Indicates whether the path represented by this summary contains any call steps. */
38+
boolean hasCall() { result = hasCall }
39+
40+
/**
41+
* Gets the summary for the path obtained by appending `that` to `this`.
42+
*
43+
* Note that a path containing a `return` step cannot be appended to a path containing
44+
* a `call` step in order to maintain well-formedness.
45+
*/
46+
StepSummary append(StepSummary that) {
47+
exists(Boolean hasReturn2, Boolean hasCall2 |
48+
that = MkStepSummary(hasReturn2, hasCall2)
49+
|
50+
result = MkStepSummary(hasReturn.booleanOr(hasReturn2), hasCall.booleanOr(hasCall2)) and
51+
// avoid constructing invalid paths
52+
not (hasCall = true and hasReturn2 = true)
53+
)
54+
}
55+
56+
/**
57+
* Gets the summary for the path obtained by appending `this` to `that`.
58+
*/
59+
StepSummary prepend(StepSummary that) { result = that.append(this) }
60+
61+
/** Gets a textual representation of this path summary. */
62+
string toString() {
63+
exists(string withReturn, string withCall |
64+
(if hasReturn = true then withReturn = "with" else withReturn = "without") and
65+
(if hasCall = true then withCall = "with" else withCall = "without")
66+
|
67+
result = "path " + withReturn + " return steps and " + withCall + " call steps"
68+
)
69+
}
70+
}
71+
72+
module StepSummary {
73+
/**
74+
* Gets a summary describing a path without any calls or returns.
75+
*/
76+
StepSummary level() { result = MkStepSummary(false, false) }
77+
78+
/**
79+
* Gets a summary describing a path with one or more calls, but no returns.
80+
*/
81+
StepSummary call() { result = MkStepSummary(false, true) }
82+
83+
/**
84+
* Gets a summary describing a path with one or more returns, but no calls.
85+
*/
86+
StepSummary return() { result = MkStepSummary(true, false) }
87+
88+
/**
89+
* INTERNAL: Use `SourceNode.track()` or `SourceNode.backtrack()` instead.
90+
*/
91+
predicate step(DataFlow::SourceNode pred, DataFlow::SourceNode succ, StepSummary summary) {
92+
exists (DataFlow::Node predNode | pred.flowsTo(predNode) |
93+
// Flow through properties of objects
94+
propertyFlowStep(predNode, succ) and
95+
summary = level()
96+
or
97+
// Flow through global variables
98+
globalFlowStep(predNode, succ) and
99+
summary = level()
100+
or
101+
// Flow into function
102+
callStep(predNode, succ) and
103+
summary = call()
104+
or
105+
// Flow out of function
106+
returnStep(predNode, succ) and
107+
summary = return()
108+
or
109+
// Flow through an instance field between members of the same class
110+
DataFlow::localFieldStep(predNode, succ) and
111+
summary = level()
112+
)
113+
}
114+
115+
/**
116+
* INTERNAL. Do not use.
117+
*
118+
* Appends a step summary onto a type-tracking summary.
119+
*/
120+
TypeTracker append(TypeTracker type, StepSummary summary) {
121+
not (type.hasCall() = true and summary.hasReturn() = true) and
122+
result.hasCall() = type.hasCall().booleanOr(summary.hasCall())
123+
}
124+
125+
/**
126+
* INTERNAL. Do not use.
127+
*
128+
* Prepends a step summary before a backwards type-tracking summary.
129+
*/
130+
TypeBackTracker prepend(StepSummary summary, TypeBackTracker type) {
131+
not (type.hasReturn() = true and summary.hasCall() = true) and
132+
result.hasReturn() = type.hasReturn().booleanOr(summary.hasReturn())
133+
}
134+
}
135+
136+
private newtype TTypeTracker = MkTypeTracker(boolean hasCall) {
137+
hasCall = true or hasCall = false
138+
}
139+
140+
/**
141+
* EXPERIMENTAL.
142+
*
143+
* Summary of the steps needed to track a value to a given dataflow node.
144+
*
145+
* This can be used to track objects that implement a certain API in order to
146+
* recognize calls to that API. Note that type-tracking does not provide a
147+
* source/sink relation, that is, it may determine that a node has a given type,
148+
* but it won't determine where that type came from.
149+
*
150+
* It is recommended that all uses of this type is written on the following form,
151+
* for tracking some type `myType`:
152+
* ```
153+
* DataFlow::SourceNode myType(DataFlow::TypeTracker t) {
154+
* t.start() and
155+
* result = < source of myType >
156+
* or
157+
* exists (DataFlow::TypeTracker t2 |
158+
* result = myType(t2).track(t2, t)
159+
* )
160+
* }
161+
*
162+
* DataFlow::SourceNode myType() { result = myType(_) }
163+
* ```
164+
*
165+
* To track values backwards, which can be useful for tracking
166+
* the type of a callback, use the `TypeBackTracker` class instead.
167+
*/
168+
class TypeTracker extends TTypeTracker {
169+
Boolean hasCall;
170+
171+
TypeTracker() { this = MkTypeTracker(hasCall) }
172+
173+
string toString() {
174+
hasCall = true and result = "type tracker with call steps"
175+
or
176+
hasCall = false and result = "type tracker without call steps"
177+
}
178+
179+
/**
180+
* Holds if this is the starting point of type tracking.
181+
*/
182+
predicate start() {
183+
hasCall = false
184+
}
185+
186+
/**
187+
* INTERNAL. DO NOT USE.
188+
*
189+
* Holds if this type has been tracked into a call.
190+
*/
191+
boolean hasCall() {
192+
result = hasCall
193+
}
194+
}
195+
196+
private newtype TTypeBackTracker = MkTypeBackTracker(boolean hasReturn) {
197+
hasReturn = true or hasReturn = false
198+
}
199+
200+
/**
201+
* EXPERIMENTAL.
202+
*
203+
* Summary of the steps needed to back-track a use of a value to a given dataflow node.
204+
*
205+
* This can be used to track callbacks that are passed to a certian API call, and are
206+
* therefore expected to called with a certain type of value.
207+
*
208+
* Note that type back-tracking does not provide a source/sink relation, that is,
209+
* it may determine that a node will be used in an API call somwwhere, but it won't
210+
* determine exactly where that use was, or the path that led to the use.
211+
*
212+
* It is recommended that all uses of this type is written on the following form,
213+
* for back-tracking some callback type `myCallback`:
214+
* ```
215+
* DataFlow::SourceNode myCallback(DataFlow::TypeBackTracker t) {
216+
* t.start() and
217+
* result = (< some API call >).getArgument(< n >).getALocalSource()
218+
* or
219+
* exists (DataFlow::TypeTracker t2 |
220+
* result = myCallback(t2).backtrack(t2, t)
221+
* )
222+
* }
223+
*
224+
* DataFlow::SourceNode myCallback() { result = myCallback(_) }
225+
* ```
226+
*/
227+
class TypeBackTracker extends TTypeBackTracker {
228+
Boolean hasReturn;
229+
230+
TypeBackTracker() { this = MkTypeBackTracker(hasReturn) }
231+
232+
string toString() {
233+
hasReturn = true and result = "type back-tracker with return steps"
234+
or
235+
hasReturn = false and result = "type back-tracker without return steps"
236+
}
237+
238+
/**
239+
* Holds if this is the starting point of type tracking.
240+
*/
241+
predicate start() {
242+
hasReturn = false
243+
}
244+
245+
/**
246+
* INTERNAL. DO NOT USE.
247+
*
248+
* Holds if this type has been back-tracked into a call through return edge.
249+
*/
250+
boolean hasReturn() {
251+
result = hasReturn
252+
}
253+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
test_ApiObject
2+
| tst.js:3:11:3:21 | new myapi() |
3+
| tst.js:15:10:15:21 | api.chain1() |
4+
| tst.js:15:10:15:30 | api.cha ... hain2() |
5+
test_Connection
6+
| tst.js:6:15:6:18 | conn |
7+
| tst.js:10:5:10:19 | this.connection |
8+
| tst.js:15:10:15:49 | api.cha ... ction() |
9+
| tst.js:18:7:18:21 | getConnection() |
10+
| tst.js:30:9:30:23 | getConnection() |
11+
| tst.js:39:7:39:21 | getConnection() |
12+
| tst.js:47:7:47:21 | getConnection() |
13+
test_DataCallback
14+
| tst.js:9:11:9:12 | cb |
15+
| tst.js:20:1:22:1 | functio ... ata);\\n} |
16+
| tst.js:29:26:29:27 | cb |
17+
| tst.js:32:17:32:26 | data => {} |
18+
| tst.js:37:10:37:19 | data => {} |
19+
| tst.js:39:32:39:45 | getDataCurry() |
20+
| tst.js:44:19:44:20 | cb |
21+
| tst.js:47:32:47:60 | identit ... llback) |
22+
test_DataValue
23+
| tst.js:20:18:20:21 | data |
24+
| tst.js:24:19:24:22 | data |
25+
| tst.js:32:17:32:20 | data |
26+
| tst.js:37:10:37:13 | data |

0 commit comments

Comments
 (0)