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

Skip to content

Commit d09bce5

Browse files
committed
custom load/store steps to implement promise flow
1 parent c50de3a commit d09bce5

9 files changed

Lines changed: 316 additions & 323 deletions

File tree

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

Lines changed: 108 additions & 165 deletions
Original file line numberDiff line numberDiff line change
@@ -125,216 +125,159 @@ class AggregateES2015PromiseDefinition extends PromiseCreationCall {
125125
*/
126126
private module PromiseFlow {
127127
/**
128-
* A promise from which data-flow can flow into or out of.
129-
*
130-
* This promise can both be a promise created by e.g. `new Promise(..)` or `Promise.resolve(..)`,
131-
* or the result from calling a method on a promise e.g. `promise.then(..)`.
132-
*
133-
* The 4 methods in this class describe that ordinary and exceptional flow can flow into and out of this promise.
128+
* Gets the pseudo-field used to describe resolved values in a promise.
134129
*/
135-
private abstract class PromiseNode extends DataFlow::SourceNode {
136-
137-
/**
138-
* Get a DataFlow::Node for a value that this promise is resolved with.
139-
* The value is sent either to a chained promise, or to an `await` expression.
140-
*
141-
* The value is e.g. an argument to `resolve(..)`, or a return value from a `.then(..)` handler.
142-
*/
143-
DataFlow::Node getASentResolveValue() { none() }
144-
145-
/**
146-
* Get the DataFlow::Node that receives the value that this promise has been resolved with.
147-
*
148-
* E.g. the `x` in `promise.then((x) => ..)`.
149-
*/
150-
DataFlow::Node getReceivedResolveValue() { none() }
151-
152-
/**
153-
* Get a DataFlow::Node for a value that this promise is rejected with.
154-
* The value is sent either to a chained promise, or thrown by an `await` expression.
155-
*
156-
* The value is e.g. an argument to `reject(..)`, or an exception thrown by the promise executor.
157-
*/
158-
DataFlow::Node getASentRejectValue() { none() }
159-
160-
/**
161-
* Get the DataFlow::Node that receives the value that this promise has been rejected with.
162-
*
163-
* E.g. the `x` in `promise.catch((x) => ..)`.
164-
*/
165-
DataFlow::Node getReceivedRejectValue() { none() }
130+
string resolveField() {
131+
result = "$PromiseResolveField$"
166132
}
167-
133+
134+
/**
135+
* Gets the pseudo-field used to describe rejected values in a promise.
136+
*/
137+
string rejectField() {
138+
result = "$PromiseRejectField$"
139+
}
140+
168141
/**
169-
* A PromiseNode for a PromiseDefinition.
170-
* E.g. `new Promise(..)`.
142+
* A flow step describing a promise definition.
143+
*
144+
* The resolved/rejected value is written to a pseudo-field on the promise.
171145
*/
172-
private class PromiseDefinitionNode extends PromiseNode {
146+
class PromiseDefitionStep extends DataFlow::AdditionalFlowStep {
173147
PromiseDefinition promise;
174-
175-
PromiseDefinitionNode() { this = promise }
176-
177-
override DataFlow::Node getASentResolveValue() {
178-
result = promise.getResolveParameter().getACall().getArgument(0)
148+
PromiseDefitionStep() {
149+
this = promise
179150
}
180151

181-
override DataFlow::Node getASentRejectValue() {
182-
result = promise.getRejectParameter().getACall().getArgument(0)
152+
override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) {
153+
prop = resolveField() and
154+
pred = promise.getResolveParameter().getACall().getArgument(0) and
155+
succ = this
183156
or
184-
result = promise.getExecutor().getExceptionalReturn()
157+
prop = rejectField() and
158+
(
159+
pred = promise.getRejectParameter().getACall().getArgument(0) or
160+
pred = promise.getExecutor().getExceptionalReturn()
161+
) and
162+
succ = this
185163
}
186164
}
187165

188166
/**
189-
* A PromiseNode for a call that creates a promise.
190-
* E.g. `Promise.resolve(..)` or `Promise.all(..)`.
167+
* A flow step describing the a Promise.resolve (and similar) call.
191168
*/
192-
private class PromiseCreationNode extends PromiseNode {
169+
class CreationStep extends DataFlow::AdditionalFlowStep {
193170
PromiseCreationCall promise;
194-
195-
PromiseCreationNode() { this = promise }
196-
197-
override DataFlow::Node getASentResolveValue() {
198-
exists(DataFlow::Node value | value = promise.getValue() |
199-
not value instanceof PromiseNode and
200-
result = value
201-
or
202-
result = value.(PromiseNode).getASentResolveValue()
203-
)
171+
CreationStep() {
172+
this = promise
204173
}
205174

206-
override DataFlow::Node getASentRejectValue() {
207-
result = promise.getValue().(PromiseNode).getASentRejectValue()
175+
override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) {
176+
prop = resolveField() and
177+
pred = promise.getValue() and
178+
succ = this
208179
}
209180
}
210181

211182
/**
212-
* A node referring to a PromiseNode through type-tracking.
183+
* A load step loading the pseudo-field describing that the promise is either resolved or rejected.
184+
* A resolved value is forwarding as the resulting value of the `await` expression,
185+
* and a rejected value is thrown as a exception.
213186
*/
214-
private class TrackedPromiseNode extends PromiseNode {
215-
PromiseNode base;
216-
TrackedPromiseNode() {
217-
this = trackPromise(DataFlow::TypeTracker::end(), base) and
218-
not this instanceof PromiseDefinitionNode and
219-
not this instanceof PromiseCreationNode
187+
class AwaitStep extends DataFlow::AdditionalFlowStep {
188+
DataFlow::Node operand;
189+
AwaitExpr await;
190+
AwaitStep() {
191+
this.getEnclosingExpr() = await and
192+
operand.getEnclosingExpr() = await.getOperand()
220193
}
221194

222-
override DataFlow::Node getASentResolveValue() { result = base.getASentResolveValue() }
223-
override DataFlow::Node getReceivedResolveValue() { result = base.getReceivedResolveValue() }
224-
override DataFlow::Node getASentRejectValue() { result = base.getASentRejectValue() }
225-
override DataFlow::Node getReceivedRejectValue() { result = base.getReceivedRejectValue() }
226-
}
227-
228-
private DataFlow::SourceNode trackPromise(DataFlow::TypeTracker t, PromiseNode promise) {
229-
t.start() and result = promise
230-
or
231-
exists(DataFlow::TypeTracker t2 | result = trackPromise(t2, promise).track(t2, t))
232-
}
233-
234-
/**
235-
* A PromiseNode that is a method call on an existing PromiseNode.
236-
* E.g. `promise.then(..)`.
237-
*/
238-
private abstract class ChainedPromiseNode extends PromiseNode, DataFlow::MethodCallNode {
239-
PromiseNode base;
240-
241-
ChainedPromiseNode() { this = base.getAMethodCall(_) }
242-
243-
PromiseNode getBase() { result = base }
195+
override predicate load(DataFlow::Node pred, DataFlow::Node succ, string prop) {
196+
prop = resolveField() and
197+
succ = this and
198+
pred = operand.getALocalSource()
199+
or
200+
prop = rejectField() and
201+
succ = await.getExceptionTarget() and
202+
pred = operand.getALocalSource()
203+
}
244204
}
245205

246206
/**
247-
* A PromiseNode for the `.then(..)` method on an existing promise.
207+
* A flow step describing the data-flow related to the `.then` method of a promise.
248208
*/
249-
private class PromiseThenNode extends ChainedPromiseNode {
250-
PromiseThenNode() { this = base.getAMethodCall("then") }
251-
252-
override DataFlow::Node getASentResolveValue() {
253-
exists(DataFlow::Node ret | ret = this.getCallback(0).getAReturn() |
254-
if ret instanceof PromiseNode
255-
then result = ret.(PromiseNode).getReceivedResolveValue()
256-
else result = ret
257-
)
209+
class ThenStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode {
210+
ThenStep() {
211+
this.getMethodName() = "then"
258212
}
259213

260-
override DataFlow::Node getASentRejectValue() {
261-
not exists(this.getCallback(1)) and result = base.getASentRejectValue()
214+
override predicate load(DataFlow::Node pred, DataFlow::Node succ, string prop) {
215+
prop = resolveField() and
216+
pred = getReceiver().getALocalSource() and
217+
succ = getCallback(0).getParameter(0)
262218
or
263-
result = this.getCallback([0..1]).getExceptionalReturn()
219+
prop = rejectField() and
220+
pred = getReceiver().getALocalSource() and
221+
succ = getCallback(1).getParameter(0)
264222
}
265-
266-
override DataFlow::Node getReceivedResolveValue() { result = this.getCallback(0).getParameter(0) }
267-
268-
override DataFlow::Node getReceivedRejectValue() { result = this.getCallback(1).getParameter(0) }
269-
}
270-
271-
/**
272-
* A PromiseNode for the `.finally(..)` method on an existing promise.
273-
*/
274-
private class PromiseFinallyNode extends ChainedPromiseNode {
275-
PromiseFinallyNode() { this = base.getAMethodCall("finally") }
276-
277-
override DataFlow::Node getASentResolveValue() { result = base.getASentResolveValue() }
278-
279-
override DataFlow::Node getASentRejectValue() {
280-
result = base.getASentRejectValue()
223+
224+
override predicate copyProperty(DataFlow::Node pred, DataFlow::Node succ, string prop) {
225+
not exists(this.getArgument(1)) and
226+
prop = rejectField() and
227+
pred = getReceiver().getALocalSource() and
228+
succ = this
229+
}
230+
231+
override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) {
232+
prop = resolveField() and
233+
pred = getCallback([0..1]).getAReturn() and
234+
succ = this
281235
or
282-
result = this.getCallback(0).getExceptionalReturn()
236+
prop = rejectField() and
237+
pred = getCallback([0..1]).getExceptionalReturn() and
238+
succ = this
283239
}
284240
}
285-
241+
286242
/**
287-
* A PromiseNode for the `.catch(..)` method on an existing promise.
243+
* A flow step describing the data-flow related to the `.catch` method of a promise.
288244
*/
289-
private class PromiseCatchNode extends ChainedPromiseNode {
290-
PromiseCatchNode() { this = base.getAMethodCall("catch") }
291-
292-
override DataFlow::Node getASentResolveValue() {
293-
exists(DataFlow::Node ret | ret = this.getCallback(0).getAReturn() |
294-
if ret instanceof PromiseNode
295-
then result = ret.(PromiseNode).getReceivedResolveValue()
296-
else result = ret
297-
)
298-
or
299-
result = base.getASentResolveValue()
245+
class CatchStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode {
246+
CatchStep() {
247+
this.getMethodName() = "catch"
300248
}
301249

302-
override DataFlow::Node getASentRejectValue() { result = this.getCallback(0).getExceptionalReturn() }
250+
override predicate load(DataFlow::Node pred, DataFlow::Node succ, string prop) {
251+
prop = rejectField() and
252+
pred = getReceiver().getALocalSource() and
253+
succ = getCallback(0).getParameter(0)
254+
}
303255

304-
override DataFlow::Node getReceivedResolveValue() { none() }
256+
override predicate copyProperty(DataFlow::Node pred, DataFlow::Node succ, string prop) {
257+
prop = resolveField() and
258+
pred = getReceiver().getALocalSource() and
259+
succ = this
260+
}
305261

306-
override DataFlow::Node getReceivedRejectValue() { result = this.getCallback(0).getParameter(0) }
262+
override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) {
263+
prop = rejectField() and
264+
pred = getCallback([0..1]).getExceptionalReturn() and
265+
succ = this
266+
}
307267
}
308268

309-
310-
private ChainedPromiseNode getAChainedPromise(PromiseNode p) { result.getBase() = p}
311-
312269
/**
313-
* A data flow edge from a promise resolve/reject to the corresponding handler (or `await` expression).
270+
* A flow step describing the data-flow related to the `.finally` method of a promise.
314271
*/
315-
private class PromiseFlowStep extends DataFlow::AdditionalFlowStep {
316-
PromiseNode promise;
317-
318-
PromiseFlowStep() { this = promise }
272+
class FinallyStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode {
273+
FinallyStep() {
274+
this.getMethodName() = "finally"
275+
}
319276

320-
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
321-
pred = promise.getASentResolveValue() and
322-
succ = getAChainedPromise(promise).getReceivedResolveValue()
323-
or
324-
pred = promise.getASentRejectValue() and
325-
succ = getAChainedPromise(promise).getReceivedRejectValue()
326-
or
327-
pred = promise.getASentResolveValue() and
328-
exists(DataFlow::SourceNode awaitNode |
329-
awaitNode.asExpr().(AwaitExpr).getOperand() = promise.asExpr() and
330-
succ = awaitNode
331-
)
332-
or
333-
pred = promise.getASentRejectValue() and
334-
exists(DataFlow::SourceNode awaitNode |
335-
awaitNode.asExpr().(AwaitExpr).getOperand() = promise.asExpr() and
336-
succ = awaitNode.asExpr().getExceptionTarget()
337-
)
277+
override predicate copyProperty(DataFlow::Node pred, DataFlow::Node succ, string prop) {
278+
(prop = resolveField() or prop = rejectField()) and
279+
pred = getReceiver().getALocalSource() and
280+
succ = this
338281
}
339282
}
340283
}

0 commit comments

Comments
 (0)