Skip to content

Commit 2d7feb7

Browse files
committed
Refactor Promises.qll to use PreCallGraphStep
1 parent 3982da5 commit 2d7feb7

File tree

2 files changed

+119
-195
lines changed

2 files changed

+119
-195
lines changed

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

Lines changed: 119 additions & 192 deletions
Original file line numberDiff line numberDiff line change
@@ -185,18 +185,20 @@ module PromiseTypeTracking {
185185
/**
186186
* Gets the result from a single step through a promise, from `pred` to `result` summarized by `summary`.
187187
* 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.
188+
*
189+
* These type-tracking steps are already included in the default type-tracking steps (through `PreCallGraphStep`).
188190
*/
189191
pragma[inline]
190192
DataFlow::Node promiseStep(DataFlow::Node pred, StepSummary summary) {
191-
exists(PromiseFlowStep step, string field | field = Promises::valueProp() |
193+
exists(string field | field = Promises::valueProp() |
192194
summary = LoadStep(field) and
193-
step.load(pred, result, field)
195+
PromiseFlow::loadStep(pred, result, field)
194196
or
195197
summary = StoreStep(field) and
196-
step.store(pred, result, field)
198+
PromiseFlow::storeStep(pred, result, field)
197199
or
198200
summary = CopyStep(field) and
199-
step.loadStore(pred, result, field)
201+
PromiseFlow::loadStoreStep(pred, result, field)
200202
)
201203
}
202204

@@ -221,55 +223,25 @@ module PromiseTypeTracking {
221223
}
222224
}
223225

226+
private import semmle.javascript.dataflow.internal.PreCallGraphStep
227+
224228
/**
225-
* An `AdditionalFlowStep` used to model a data-flow step related to promises.
229+
* A step related to promises.
226230
*
227-
* The `loadStep`/`storeStep`/`loadStoreStep` methods are overloaded such that the new predicates
228-
* `load`/`store`/`loadStore` can be used in the `PromiseTypeTracking` module.
229-
* (Thereby avoiding conflicts with a "cousin" `AdditionalFlowStep` implementation.)
230-
*
231-
* The class is private and is only intended to be used inside the `PromiseTypeTracking` and `PromiseFlow` modules.
231+
* These steps are for `await p`, `new Promise()`, `Promise.resolve()`,
232+
* `Promise.then()`, `Promise.catch()`, and `Promise.finally()`.
232233
*/
233-
abstract private class PromiseFlowStep extends DataFlow::AdditionalFlowStep {
234-
final override predicate step(DataFlow::Node pred, DataFlow::Node succ) { none() }
235-
236-
final override predicate step(
237-
DataFlow::Node p, DataFlow::Node s, DataFlow::FlowLabel pl, DataFlow::FlowLabel sl
238-
) {
239-
none()
234+
private class PromiseStep extends PreCallGraphStep {
235+
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
236+
PromiseFlow::loadStep(obj, element, prop)
240237
}
241238

242-
/**
243-
* Holds if the property `prop` of the object `pred` should be loaded into `succ`.
244-
*/
245-
predicate load(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() }
246-
247-
final override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
248-
this.load(pred, succ, prop)
239+
override predicate storeStep(DataFlow::Node element, DataFlow::SourceNode obj, string prop) {
240+
PromiseFlow::storeStep(element, obj, prop)
249241
}
250242

251-
/**
252-
* Holds if `pred` should be stored in the object `succ` under the property `prop`.
253-
*/
254-
predicate store(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { none() }
255-
256-
final override predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
257-
this.store(pred, succ, prop)
258-
}
259-
260-
/**
261-
* Holds if the property `prop` should be copied from the object `pred` to the object `succ`.
262-
*/
263-
predicate loadStore(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() }
264-
265-
final override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
266-
this.loadStore(pred, succ, prop)
267-
}
268-
269-
final override predicate loadStoreStep(
270-
DataFlow::Node pred, DataFlow::Node succ, string loadProp, string storeProp
271-
) {
272-
none()
243+
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
244+
PromiseFlow::loadStoreStep(pred, succ, prop)
273245
}
274246
}
275247

@@ -283,188 +255,143 @@ private module PromiseFlow {
283255
private predicate errorProp = Promises::errorProp/0;
284256

285257
/**
286-
* A flow step describing a promise definition.
287-
*
288-
* The resolved/rejected value is written to a pseudo-field on the promise.
258+
* Holds if there is a step for loading a `value` from a `promise`.
259+
* `prop` is either `valueProp()` if the value is a resolved value, or `errorProp()` if the promise has been rejected.
289260
*/
290-
class PromiseDefitionStep extends PromiseFlowStep {
291-
PromiseDefinition promise;
292-
293-
PromiseDefitionStep() { this = promise }
294-
295-
override predicate store(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
261+
predicate loadStep(DataFlow::Node promise, DataFlow::Node value, string prop) {
262+
// await promise.
263+
exists(AwaitExpr await | await.getOperand() = promise.asExpr() |
296264
prop = valueProp() and
297-
pred = promise.getResolveParameter().getACall().getArgument(0) and
298-
succ = this
265+
value.asExpr() = await
299266
or
300267
prop = errorProp() and
301-
(
302-
pred = promise.getRejectParameter().getACall().getArgument(0) or
303-
pred = promise.getExecutor().getExceptionalReturn()
304-
) and
305-
succ = this
306-
}
307-
308-
override predicate loadStore(DataFlow::Node pred, DataFlow::Node succ, string prop) {
309-
// Copy the value of a resolved promise to the value of this promise.
268+
value = await.getExceptionTarget()
269+
)
270+
or
271+
// promise.then()
272+
exists(DataFlow::MethodCallNode call |
273+
call.getMethodName() = "then" and promise = call.getReceiver()
274+
|
310275
prop = valueProp() and
311-
pred = promise.getResolveParameter().getACall().getArgument(0) and
312-
succ = this
313-
}
276+
value = call.getCallback(0).getParameter(0)
277+
or
278+
prop = errorProp() and
279+
value = call.getCallback(1).getParameter(0)
280+
)
281+
or
282+
// promise.catch()
283+
exists(DataFlow::MethodCallNode call | call.getMethodName() = "catch" |
284+
prop = errorProp() and
285+
promise = call.getReceiver() and
286+
value = call.getCallback(0).getParameter(0)
287+
)
314288
}
315289

316290
/**
317-
* A flow step describing the a Promise.resolve (and similar) call.
291+
* Holds if there is a step for storing a `value` into a promise `obj`.
292+
* `prop` is either `valueProp()` if the value is a resolved value, or `errorProp()` if the promise has been rejected.
318293
*/
319-
class CreationStep extends PromiseFlowStep {
320-
PromiseCreationCall promise;
321-
322-
CreationStep() { this = promise }
323-
324-
override predicate store(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
325-
not promise instanceof PromiseAllCreation and
294+
predicate storeStep(DataFlow::Node value, DataFlow::SourceNode obj, string prop) {
295+
// promise definition, e.g. `new Promise()`
296+
exists(PromiseDefinition promise | obj = promise |
326297
prop = valueProp() and
327-
pred = promise.getValue() and
328-
succ = this
298+
value = promise.getResolveParameter().getACall().getArgument(0)
329299
or
330-
prop = valueProp() and
331-
pred = promise.(PromiseAllCreation).getArrayNode() and
332-
succ = this
333-
}
334-
335-
override predicate loadStore(DataFlow::Node pred, DataFlow::Node succ, string prop) {
336-
// Copy the value of a resolved promise to the value of this promise.
300+
prop = errorProp() and
301+
value =
302+
[promise.getRejectParameter().getACall().getArgument(0),
303+
promise.getExecutor().getExceptionalReturn()]
304+
)
305+
or
306+
// promise creation call, e.g. `Promise.resolve`.
307+
exists(PromiseCreationCall promise | obj = promise |
337308
not promise instanceof PromiseAllCreation and
338309
prop = valueProp() and
339-
pred = promise.getValue() and
340-
succ = this
310+
value = promise.getValue()
341311
or
342-
promise instanceof PromiseAllCreation and
343312
prop = valueProp() and
344-
pred = promise.(PromiseAllCreation).getArrayNode() and
345-
succ = this
346-
}
347-
}
348-
349-
/**
350-
* A load step loading the pseudo-field describing that the promise is rejected.
351-
* The rejected value is thrown as a exception.
352-
*/
353-
class AwaitStep extends PromiseFlowStep {
354-
DataFlow::Node operand;
355-
AwaitExpr await;
356-
357-
AwaitStep() {
358-
this.getEnclosingExpr() = await and
359-
operand.getEnclosingExpr() = await.getOperand()
360-
}
361-
362-
override predicate load(DataFlow::Node pred, DataFlow::Node succ, string prop) {
313+
value = promise.(PromiseAllCreation).getArrayNode()
314+
)
315+
or
316+
// promise.then()
317+
exists(DataFlow::MethodCallNode call | call.getMethodName() = "then" and obj = call |
363318
prop = valueProp() and
364-
succ = this and
365-
pred = operand
319+
value = call.getCallback([0 .. 1]).getAReturn()
366320
or
367321
prop = errorProp() and
368-
succ = await.getExceptionTarget() and
369-
pred = operand
370-
}
322+
value = call.getCallback([0 .. 1]).getExceptionalReturn()
323+
)
324+
or
325+
// promise.catch()
326+
exists(DataFlow::MethodCallNode call | call.getMethodName() = "catch" and obj = call |
327+
prop = errorProp() and
328+
value = call.getCallback(0).getExceptionalReturn()
329+
or
330+
prop = valueProp() and
331+
value = call.getCallback(0).getAReturn()
332+
)
333+
or
334+
// promise.finally()
335+
exists(DataFlow::MethodCallNode call | call.getMethodName() = "finally" |
336+
prop = errorProp() and
337+
value = call.getCallback(0).getExceptionalReturn() and
338+
obj = call
339+
)
371340
}
372341

373342
/**
374-
* A flow step describing the data-flow related to the `.then` method of a promise.
343+
* Holds if there is a step copying a resolved/rejected promise value from promise `pred` to promise `succ`.
344+
* `prop` is either `valueProp()` if the value is a resolved value, or `errorProp()` if the promise has been rejected.
375345
*/
376-
class ThenStep extends PromiseFlowStep, DataFlow::MethodCallNode {
377-
ThenStep() { this.getMethodName() = "then" }
378-
379-
override predicate load(DataFlow::Node pred, DataFlow::Node succ, string prop) {
346+
predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
347+
// promise definition, e.g. `new Promise()`
348+
exists(PromiseDefinition promise | succ = promise |
349+
// Copy the value of a resolved promise to the value of this promise.
380350
prop = valueProp() and
381-
pred = getReceiver() and
382-
succ = getCallback(0).getParameter(0)
351+
pred = promise.getResolveParameter().getACall().getArgument(0)
352+
)
353+
or
354+
// promise creation call, e.g. `Promise.resolve`.
355+
exists(PromiseCreationCall promise | succ = promise |
356+
// Copy the value of a resolved promise to the value of this promise.
357+
not promise instanceof PromiseAllCreation and
358+
pred = promise.getValue() and
359+
prop = valueProp()
383360
or
361+
pred = promise.(PromiseAllCreation).getArrayNode() and
362+
prop = valueProp()
363+
)
364+
or
365+
// promise.then()
366+
exists(DataFlow::MethodCallNode call | call.getMethodName() = "then" and succ = call |
367+
not exists(call.getArgument(1)) and
384368
prop = errorProp() and
385-
pred = getReceiver() and
386-
succ = getCallback(1).getParameter(0)
387-
}
388-
389-
override predicate loadStore(DataFlow::Node pred, DataFlow::Node succ, string prop) {
390-
not exists(this.getArgument(1)) and
391-
prop = errorProp() and
392-
pred = getReceiver() and
393-
succ = this
369+
pred = call.getReceiver()
394370
or
395371
// read the value of a resolved/rejected promise that is returned
396372
(prop = errorProp() or prop = valueProp()) and
397-
pred = getCallback([0 .. 1]).getAReturn() and
398-
succ = this
399-
}
400-
401-
override predicate store(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
402-
prop = valueProp() and
403-
pred = getCallback([0 .. 1]).getAReturn() and
404-
succ = this
405-
or
406-
prop = errorProp() and
407-
pred = getCallback([0 .. 1]).getExceptionalReturn() and
408-
succ = this
409-
}
410-
}
411-
412-
/**
413-
* A flow step describing the data-flow related to the `.catch` method of a promise.
414-
*/
415-
class CatchStep extends PromiseFlowStep, DataFlow::MethodCallNode {
416-
CatchStep() { this.getMethodName() = "catch" }
417-
418-
override predicate load(DataFlow::Node pred, DataFlow::Node succ, string prop) {
419-
prop = errorProp() and
420-
pred = getReceiver() and
421-
succ = getCallback(0).getParameter(0)
422-
}
423-
424-
override predicate loadStore(DataFlow::Node pred, DataFlow::Node succ, string prop) {
373+
pred = call.getCallback([0 .. 1]).getAReturn()
374+
)
375+
or
376+
// promise.catch()
377+
exists(DataFlow::MethodCallNode call | call.getMethodName() = "catch" and succ = call |
425378
prop = valueProp() and
426-
pred = getReceiver().getALocalSource() and
427-
succ = this
379+
pred = call.getReceiver().getALocalSource()
428380
or
429381
// read the value of a resolved/rejected promise that is returned
430382
(prop = errorProp() or prop = valueProp()) and
431-
pred = getCallback(0).getAReturn() and
432-
succ = this
433-
}
434-
435-
override predicate store(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
436-
prop = errorProp() and
437-
pred = getCallback(0).getExceptionalReturn() and
438-
succ = this
439-
or
440-
prop = valueProp() and
441-
pred = getCallback(0).getAReturn() and
442-
succ = this
443-
}
444-
}
445-
446-
/**
447-
* A flow step describing the data-flow related to the `.finally` method of a promise.
448-
*/
449-
class FinallyStep extends PromiseFlowStep, DataFlow::MethodCallNode {
450-
FinallyStep() { this.getMethodName() = "finally" }
451-
452-
override predicate loadStore(DataFlow::Node pred, DataFlow::Node succ, string prop) {
383+
pred = call.getCallback(0).getAReturn()
384+
)
385+
or
386+
// promise.finally()
387+
exists(DataFlow::MethodCallNode call | call.getMethodName() = "finally" and succ = call |
453388
(prop = valueProp() or prop = errorProp()) and
454-
pred = getReceiver() and
455-
succ = this
389+
pred = call.getReceiver()
456390
or
457391
// read the value of a rejected promise that is returned
458392
prop = errorProp() and
459-
pred = getCallback(0).getAReturn() and
460-
succ = this
461-
}
462-
463-
override predicate store(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
464-
prop = errorProp() and
465-
pred = getCallback(0).getExceptionalReturn() and
466-
succ = this
467-
}
393+
pred = call.getCallback(0).getAReturn()
394+
)
468395
}
469396
}
470397

javascript/ql/src/semmle/javascript/dataflow/internal/StepSummary.qll

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,6 @@ module StepSummary {
153153
name != ""
154154
)
155155
or
156-
// Step in/out of a promise
157-
succ = PromiseTypeTracking::promiseStep(pred, summary)
158-
or
159156
// Summarize calls with flow directly from a parameter to a return.
160157
exists(DataFlow::ParameterNode param, DataFlow::FunctionNode fun |
161158
(

0 commit comments

Comments
 (0)