Skip to content

Commit 7f14722

Browse files
committed
refactor to include promise tracking as a core part of type tracking
1 parent 066568e commit 7f14722

File tree

4 files changed

+75
-68
lines changed

4 files changed

+75
-68
lines changed

javascript/ql/src/semmle/javascript/Promises.qll

Lines changed: 54 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -120,50 +120,56 @@ class AggregateES2015PromiseDefinition extends PromiseCreationCall {
120120
}
121121
}
122122

123+
/**
124+
* Common predicates shared between type-tracking and data-flow for promises.
125+
*/
126+
module Promises {
127+
/**
128+
* Gets the pseudo-field used to describe resolved values in a promise.
129+
*/
130+
string valueProp() { result = "$PromiseResolveField$" }
131+
132+
/**
133+
* Gets the pseudo-field used to describe rejected values in a promise.
134+
*/
135+
string errorProp() { result = "$PromiseRejectField$" }
136+
}
137+
123138
/**
124139
* A module for supporting promises in type-tracking predicates.
125-
* The `PromiseTypeTracking::promiseStep` predicate can be used to add type-tracking in and out of promises,
126-
* and the `PromiseTypeTracking::valueInPromiseTracker` predicate can be used to initiate a type-tracker
127-
* where the tracked value is inside a promise.
140+
* The `PromiseTypeTracking::promiseStep` is used for type tracking in and out of promises,
141+
* and is included in the standard type-tracking steps (`SourceNode::track`).
142+
* The `TypeTracker::startInPromise()` predicate can be used to initiate a type-tracker
143+
* where the tracked value is a promise.
128144
*
129-
* The below is an example of a type-tracking predicate where the initial value is inside a promise:
145+
* The below is an example of a type-tracking predicate where the initial value is a promise:
130146
* ```
131147
* DataFlow::SourceNode myType(DataFlow::TypeTracker t) {
132-
* t = PromiseTypeTracking::valueInPromiseTracker()
148+
* t.startInPromise() and
133149
* result = <the promise value> and
134150
* or
135151
* exists(DataFlow::TypeTracker t2 | result = myType(t2).track(t2, t))
136-
* or
137-
* exists(DataFlow::TypeTracker t2, DataFlow::StepSummary summary |
138-
* result = PromiseTypeTracking::promiseStep(myType(t2), summary) and
139-
* t = t2.append(summary)
140-
* )
141152
* }
142153
* ```
143-
* The above example uses all the standard type-tracking steps and the promise specific type-tracking steps.
144-
* The standard type-tracking steps can be removed for a type-tracking predicate that only tracks flow out of a promise.
154+
*
155+
* The type-tracking predicate above will only end (`t = DataFlow::TypeTracker::end()`) after the tracked value has been
156+
* extracted from the promise.
157+
*
158+
* The `PromiseTypeTracking::promiseStep` predicate can be used instead of `SourceNode::track`
159+
* to get type-tracking only for promise steps.
145160
*
146-
* Replace `t = PromiseTypeTracking::valueInPromiseTracker()` in the above example with `t.start()` to create a type-tracking predicate
161+
* Replace `t.startInPromise()` in the above example with `t.start()` to create a type-tracking predicate
147162
* where the value is not initially inside a promise.
148163
*/
149164
module PromiseTypeTracking {
150-
/**
151-
* A type-tracker used to start a type-tracker where the tracked value is initially inside a promise.
152-
*/
153-
DataFlow::TypeTracker valueInPromiseTracker() {
154-
exists(DataFlow::TypeTracker start | start.start() |
155-
result = start.append(DataFlow::StoreStep(resolveField()))
156-
)
157-
}
158-
159165
/**
160166
* Gets the result from a single step through a promise, from `pred` to `result` summarized by `summary`.
161167
* This can be loading a resolved value from a promise, storing a value in a promise, or copying a resolved value from one promise to another.
162168
*
163169
* See the qldoc for the `PromiseTypeTracking` module for an example of how to use this predicate.
164170
*/
165171
DataFlow::SourceNode promiseStep(DataFlow::SourceNode pred, DataFlow::StepSummary summary) {
166-
exists(PromiseFlowStep step, string field | field = resolveField() |
172+
exists(PromiseFlowStep step, string field | field = Promises::valueProp() |
167173
summary = DataFlow::LoadStep(field) and
168174
step.load(pred, result, field)
169175
or
@@ -179,14 +185,14 @@ module PromiseTypeTracking {
179185
* A class enabling the use of the `resolveField` as a pseudo-property in type-tracking predicates.
180186
*/
181187
private class ResolveFieldAsTypeTrackingProperty extends DataFlow::TypeTrackingPseudoProperty {
182-
ResolveFieldAsTypeTrackingProperty() { this = resolveField() }
188+
ResolveFieldAsTypeTrackingProperty() { this = Promises::valueProp() }
183189
}
184190
}
185191

186192
/**
187193
* An `AdditionalFlowStep` used to model a data-flow step related to promises.
188194
*
189-
* The `loadStep`/`storeStep`/`loadStoreStep` methods are overloaded such that the new overloads
195+
* The `loadStep`/`storeStep`/`loadStoreStep` methods are overloaded such that the new predicates
190196
* `load`/`store`/`loadStore` can be used in the `PromiseTypeTracking` module.
191197
* (Thereby avoiding conflicts with a "cousin" `AdditionalFlowStep` implementation.)
192198
*
@@ -229,20 +235,13 @@ abstract private class PromiseFlowStep extends DataFlow::AdditionalFlowStep {
229235
}
230236
}
231237

232-
/**
233-
* Gets the pseudo-field used to describe resolved values in a promise.
234-
*/
235-
private string resolveField() { result = "$PromiseResolveField$" }
236-
237238
/**
238239
* This module defines how data-flow propagates into and out of a Promise.
239240
* The data-flow is based on pseudo-properties rather than tainting the Promise object (which is what `PromiseTaintStep` does).
240241
*/
241242
private module PromiseFlow {
242-
/**
243-
* Gets the pseudo-field used to describe rejected values in a promise.
244-
*/
245-
string rejectField() { result = "$PromiseRejectField$" }
243+
private predicate valueProp = Promises::valueProp/0;
244+
private predicate errorProp = Promises::errorProp/0;
246245

247246
/**
248247
* A flow step describing a promise definition.
@@ -255,11 +254,11 @@ private module PromiseFlow {
255254
PromiseDefitionStep() { this = promise }
256255

257256
override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) {
258-
prop = resolveField() and
257+
prop = valueProp() and
259258
pred = promise.getResolveParameter().getACall().getArgument(0) and
260259
succ = this
261260
or
262-
prop = rejectField() and
261+
prop = errorProp() and
263262
(
264263
pred = promise.getRejectParameter().getACall().getArgument(0) or
265264
pred = promise.getExecutor().getExceptionalReturn()
@@ -269,7 +268,7 @@ private module PromiseFlow {
269268

270269
override predicate loadStore(DataFlow::Node pred, DataFlow::Node succ, string prop) {
271270
// Copy the value of a resolved promise to the value of this promise.
272-
prop = resolveField() and
271+
prop = valueProp() and
273272
pred = promise.getResolveParameter().getACall().getArgument(0) and
274273
succ = this
275274
}
@@ -284,14 +283,14 @@ private module PromiseFlow {
284283
CreationStep() { this = promise }
285284

286285
override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) {
287-
prop = resolveField() and
286+
prop = valueProp() and
288287
pred = promise.getValue() and
289288
succ = this
290289
}
291290

292291
override predicate loadStore(DataFlow::Node pred, DataFlow::Node succ, string prop) {
293292
// Copy the value of a resolved promise to the value of this promise.
294-
prop = resolveField() and
293+
prop = valueProp() and
295294
pred = promise.getValue() and
296295
succ = this
297296
}
@@ -311,11 +310,11 @@ private module PromiseFlow {
311310
}
312311

313312
override predicate load(DataFlow::Node pred, DataFlow::Node succ, string prop) {
314-
prop = resolveField() and
313+
prop = valueProp() and
315314
succ = this and
316315
pred = operand
317316
or
318-
prop = rejectField() and
317+
prop = errorProp() and
319318
succ = await.getExceptionTarget() and
320319
pred = operand
321320
}
@@ -328,33 +327,33 @@ private module PromiseFlow {
328327
ThenStep() { this.getMethodName() = "then" }
329328

330329
override predicate load(DataFlow::Node pred, DataFlow::Node succ, string prop) {
331-
prop = resolveField() and
330+
prop = valueProp() and
332331
pred = getReceiver() and
333332
succ = getCallback(0).getParameter(0)
334333
or
335-
prop = rejectField() and
334+
prop = errorProp() and
336335
pred = getReceiver() and
337336
succ = getCallback(1).getParameter(0)
338337
}
339338

340339
override predicate loadStore(DataFlow::Node pred, DataFlow::Node succ, string prop) {
341340
not exists(this.getArgument(1)) and
342-
prop = rejectField() and
341+
prop = errorProp() and
343342
pred = getReceiver() and
344343
succ = this
345344
or
346345
// read the value of a resolved/rejected promise that is returned
347-
(prop = rejectField() or prop = resolveField()) and
346+
(prop = errorProp() or prop = valueProp()) and
348347
pred = getCallback([0 .. 1]).getAReturn() and
349348
succ = this
350349
}
351350

352351
override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) {
353-
prop = resolveField() and
352+
prop = valueProp() and
354353
pred = getCallback([0 .. 1]).getAReturn() and
355354
succ = this
356355
or
357-
prop = rejectField() and
356+
prop = errorProp() and
358357
pred = getCallback([0 .. 1]).getExceptionalReturn() and
359358
succ = this
360359
}
@@ -367,28 +366,28 @@ private module PromiseFlow {
367366
CatchStep() { this.getMethodName() = "catch" }
368367

369368
override predicate load(DataFlow::Node pred, DataFlow::Node succ, string prop) {
370-
prop = rejectField() and
369+
prop = errorProp() and
371370
pred = getReceiver() and
372371
succ = getCallback(0).getParameter(0)
373372
}
374373

375374
override predicate loadStore(DataFlow::Node pred, DataFlow::Node succ, string prop) {
376-
prop = resolveField() and
375+
prop = valueProp() and
377376
pred = getReceiver().getALocalSource() and
378377
succ = this
379378
or
380379
// read the value of a resolved/rejected promise that is returned
381-
(prop = rejectField() or prop = resolveField()) and
380+
(prop = errorProp() or prop = valueProp()) and
382381
pred = getCallback(0).getAReturn() and
383382
succ = this
384383
}
385384

386385
override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) {
387-
prop = rejectField() and
386+
prop = errorProp() and
388387
pred = getCallback(0).getExceptionalReturn() and
389388
succ = this
390389
or
391-
prop = resolveField() and
390+
prop = valueProp() and
392391
pred = getCallback(0).getAReturn() and
393392
succ = this
394393
}
@@ -401,18 +400,18 @@ private module PromiseFlow {
401400
FinallyStep() { this.getMethodName() = "finally" }
402401

403402
override predicate loadStore(DataFlow::Node pred, DataFlow::Node succ, string prop) {
404-
(prop = resolveField() or prop = rejectField()) and
403+
(prop = valueProp() or prop = errorProp()) and
405404
pred = getReceiver() and
406405
succ = this
407406
or
408407
// read the value of a rejected promise that is returned
409-
prop = rejectField() and
408+
prop = errorProp() and
410409
pred = getCallback(0).getAReturn() and
411410
succ = this
412411
}
413412

414413
override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) {
415-
prop = rejectField() and
414+
prop = errorProp() and
416415
pred = getCallback(0).getExceptionalReturn() and
417416
succ = this
418417
}

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,9 @@ module StepSummary {
136136
name != ""
137137
)
138138
or
139+
// Step in/out of a promise
140+
succ = PromiseTypeTracking::promiseStep(pred, summary)
141+
or
139142
// Summarize calls with flow directly from a parameter to a return.
140143
exists(DataFlow::ParameterNode param, DataFlow::FunctionNode fun |
141144
(
@@ -226,6 +229,20 @@ class TypeTracker extends TTypeTracker {
226229
*/
227230
predicate start() { hasCall = false and prop = "" }
228231

232+
/**
233+
* Holds if this is the starting point of type tracking, and the value starts in the property named `propName`.
234+
* The type tracking only ends after the property has been loaded.
235+
*/
236+
predicate startInProp(PropertyName propName) { hasCall = false and prop = propName }
237+
238+
/**
239+
* Holds if this is the starting point of type tracking, and the initial value is a promise.
240+
* The type tracking only ends after the value has been extracted from the promise.
241+
*/
242+
predicate startInPromise() {
243+
startInProp(Promises::valueProp())
244+
}
245+
229246
/**
230247
* Holds if this is the starting point of type tracking
231248
* when tracking a parameter into a call, but not out of it.

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

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -571,20 +571,13 @@ module ClientRequest {
571571
call = DataFlow::moduleImport("chrome-remote-interface").getAnInvocation()
572572
|
573573
// the client is inside in a promise.
574-
t = PromiseTypeTracking::valueInPromiseTracker() and result = call
574+
t.startInPromise() and result = call
575575
or
576576
// the client is accessed directly using a callback.
577577
t.start() and result = call.getCallback([0 .. 1]).getParameter(0)
578578
)
579579
or
580-
// standard type-tracking steps
581580
exists(DataFlow::TypeTracker t2 | result = chromeRemoteInterface(t2).track(t2, t))
582-
or
583-
// Simple promise tracking.
584-
exists(DataFlow::TypeTracker t2, DataFlow::StepSummary summary |
585-
result = PromiseTypeTracking::promiseStep(chromeRemoteInterface(t2), summary) and
586-
t = t2.append(summary)
587-
)
588581
}
589582

590583
/**

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ private DataFlow::SourceNode globbyFileNameSource(DataFlow::TypeTracker t) {
6868
result = DataFlow::moduleMember(moduleName, "sync").getACall()
6969
or
7070
// `files` in `require('globby')(_).then(files => ...)`
71-
t = PromiseTypeTracking::valueInPromiseTracker() and
71+
t.startInPromise() and
7272
result = DataFlow::moduleImport(moduleName).getACall()
7373
)
7474
or
@@ -93,8 +93,7 @@ private class GlobbyFileNameSource extends FileNameSource {
9393
private DataFlow::Node fastGlobFileNameSource(DataFlow::TypeTracker t) {
9494
exists(string moduleName | moduleName = "fast-glob" |
9595
// `require('fast-glob').sync(_)
96-
t.start() and
97-
result = DataFlow::moduleMember(moduleName, "sync").getACall()
96+
t.start() and result = DataFlow::moduleMember(moduleName, "sync").getACall()
9897
or
9998
exists(DataFlow::SourceNode f |
10099
f = DataFlow::moduleImport(moduleName)
@@ -103,8 +102,7 @@ private DataFlow::Node fastGlobFileNameSource(DataFlow::TypeTracker t) {
103102
|
104103
// `files` in `require('fast-glob')(_).then(files => ...)` and
105104
// `files` in `require('fast-glob').async(_).then(files => ...)`
106-
t = PromiseTypeTracking::valueInPromiseTracker() and
107-
result = f.getACall()
105+
t.startInPromise() and result = f.getACall()
108106
)
109107
or
110108
// `file` in `require('fast-glob').stream(_).on(_, file => ...)`

0 commit comments

Comments
 (0)