@@ -185,18 +185,20 @@ module PromiseTypeTracking {
185
185
/**
186
186
* Gets the result from a single step through a promise, from `pred` to `result` summarized by `summary`.
187
187
* 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`).
188
190
*/
189
191
pragma [ inline]
190
192
DataFlow:: Node promiseStep ( DataFlow:: Node pred , StepSummary summary ) {
191
- exists ( PromiseFlowStep step , string field | field = Promises:: valueProp ( ) |
193
+ exists ( string field | field = Promises:: valueProp ( ) |
192
194
summary = LoadStep ( field ) and
193
- step . load ( pred , result , field )
195
+ PromiseFlow :: loadStep ( pred , result , field )
194
196
or
195
197
summary = StoreStep ( field ) and
196
- step . store ( pred , result , field )
198
+ PromiseFlow :: storeStep ( pred , result , field )
197
199
or
198
200
summary = CopyStep ( field ) and
199
- step . loadStore ( pred , result , field )
201
+ PromiseFlow :: loadStoreStep ( pred , result , field )
200
202
)
201
203
}
202
204
@@ -221,55 +223,25 @@ module PromiseTypeTracking {
221
223
}
222
224
}
223
225
226
+ private import semmle.javascript.dataflow.internal.PreCallGraphStep
227
+
224
228
/**
225
- * An `AdditionalFlowStep` used to model a data-flow step related to promises.
229
+ * A step related to promises.
226
230
*
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()`.
232
233
*/
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 )
240
237
}
241
238
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 )
249
241
}
250
242
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 )
273
245
}
274
246
}
275
247
@@ -283,188 +255,143 @@ private module PromiseFlow {
283
255
private predicate errorProp = Promises:: errorProp / 0 ;
284
256
285
257
/**
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.
289
260
*/
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 ( ) |
296
264
prop = valueProp ( ) and
297
- pred = promise .getResolveParameter ( ) .getACall ( ) .getArgument ( 0 ) and
298
- succ = this
265
+ value .asExpr ( ) = await
299
266
or
300
267
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
+ |
310
275
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
+ )
314
288
}
315
289
316
290
/**
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.
318
293
*/
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 |
326
297
prop = valueProp ( ) and
327
- pred = promise .getValue ( ) and
328
- succ = this
298
+ value = promise .getResolveParameter ( ) .getACall ( ) .getArgument ( 0 )
329
299
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 |
337
308
not promise instanceof PromiseAllCreation and
338
309
prop = valueProp ( ) and
339
- pred = promise .getValue ( ) and
340
- succ = this
310
+ value = promise .getValue ( )
341
311
or
342
- promise instanceof PromiseAllCreation and
343
312
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 |
363
318
prop = valueProp ( ) and
364
- succ = this and
365
- pred = operand
319
+ value = call .getCallback ( [ 0 .. 1 ] ) .getAReturn ( )
366
320
or
367
321
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
+ )
371
340
}
372
341
373
342
/**
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.
375
345
*/
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.
380
350
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 ( )
383
360
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
384
368
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 ( )
394
370
or
395
371
// read the value of a resolved/rejected promise that is returned
396
372
( 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 |
425
378
prop = valueProp ( ) and
426
- pred = getReceiver ( ) .getALocalSource ( ) and
427
- succ = this
379
+ pred = call .getReceiver ( ) .getALocalSource ( )
428
380
or
429
381
// read the value of a resolved/rejected promise that is returned
430
382
( 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 |
453
388
( prop = valueProp ( ) or prop = errorProp ( ) ) and
454
- pred = getReceiver ( ) and
455
- succ = this
389
+ pred = call .getReceiver ( )
456
390
or
457
391
// read the value of a rejected promise that is returned
458
392
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
+ )
468
395
}
469
396
}
470
397
0 commit comments