Skip to content

Commit 2988d73

Browse files
committed
JS: introduce another AsyncTerminatableFunction
1 parent 47d3542 commit 2988d73

File tree

1 file changed

+30
-0
lines changed

1 file changed

+30
-0
lines changed

Diff for: javascript/ql/src/Security/CWE-730/UnclosedStream.ql

+30
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,36 @@ class PromiseExecutor extends AsyncTerminatableFunction {
5656
override string getKind() { result = "promise" }
5757
}
5858

59+
/**
60+
* A callback-invoking function heuristic as a function that can be terminated in an asynchronous context.
61+
*/
62+
class FunctionWithCallback extends AsyncTerminatableFunction {
63+
DataFlow::ParameterNode callbackParameter;
64+
65+
FunctionWithCallback() {
66+
// the last parameter is the callback
67+
callbackParameter = this.getLastParameter() and
68+
// simple escape analysis
69+
not exists(DataFlow::Node escape | callbackParameter.flowsTo(escape) |
70+
escape = any(DataFlow::PropWrite w).getRhs() or
71+
escape = any(DataFlow::CallNode c).getAnArgument()
72+
) and
73+
// no return value
74+
(this.getFunction() instanceof ArrowFunctionExpr or not exists(this.getAReturn())) and
75+
// all callback invocations are terminal (note that this permits calls in closures)
76+
forex(DataFlow::CallNode termination | termination = callbackParameter.getACall() |
77+
termination.asExpr() = any(Function f).getExit().getAPredecessor()
78+
) and
79+
// avoid confusion with promises
80+
not this instanceof PromiseExecutor and
81+
not exists(PromiseCandidate c | this.flowsTo(c.getAnArgument()))
82+
}
83+
84+
override DataFlow::CallNode getTermination() { result = callbackParameter.getACall() }
85+
86+
override string getKind() { result = "asynchronous function" }
87+
}
88+
5989
/**
6090
* A data stream.
6191
*/

0 commit comments

Comments
 (0)