Skip to content

Commit c06edd3

Browse files
author
Esben Sparre Andreasen
authored
Merge pull request #15 from xiemaisi/js/call-graph-data-flow
JavaScript: Lift call graph library to data flow graph.
2 parents 3ccd582 + 9ba3d80 commit c06edd3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+758
-112
lines changed

change-notes/1.18/analysis-javascript.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,4 @@
5050

5151
* HTTP header names are now always normalized to lower case to reflect the fact that they are case insensitive. In particular, the result of `HeaderDefinition.getAHeaderName`, and the first parameter of `HeaderDefinition.defines`, `ExplicitHeaderDefinition.definesExplicitly` and `RouteHandler.getAResponseHeader` is now always a lower-case string.
5252
* The class `JsonParseCall` has been deprecated. Use `JsonParserCall` instead.
53+
* The handling of spread arguments in the data flow library has been changed: `DataFlow::InvokeNode.getArgument(i)` is now only defined when there is no spread argument at or before argument position `i`, and similarly `InvokeNode.getNumArgument` is only defined for invocations without spread arguments.

javascript/ql/src/Expressions/ExprHasNoEffect.ql

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,7 @@ predicate noSideEffects(Expr e) {
121121
e.isPure()
122122
or
123123
// `new Error(...)`, `new SyntaxError(...)`, etc.
124-
e instanceof NewExpr and
125-
forex (Function f | f = e.(CallSite).getACallee() |
124+
forex (Function f | f = e.flow().(DataFlow::NewNode).getACallee() |
126125
f.(ExternalType).getASupertype*().getName() = "Error"
127126
)
128127
}

javascript/ql/src/LanguageFeatures/IllegalInvocation.ql

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@ import javascript
1616
/**
1717
* Holds if call site `cs` may invoke function `callee` as specified by `how`.
1818
*/
19-
predicate calls(CallSite cs, Function callee, string how) {
19+
predicate calls(DataFlow::InvokeNode cs, Function callee, string how) {
2020
callee = cs.getACallee() and
2121
(
22-
cs instanceof CallExpr and not cs instanceof SuperCall and
22+
cs instanceof DataFlow::CallNode and not cs.asExpr() instanceof SuperCall and
2323
how = "as a function"
2424
or
25-
cs instanceof NewExpr and
25+
cs instanceof DataFlow::NewNode and
2626
how = "using 'new'"
2727
)
2828
}
@@ -31,7 +31,7 @@ predicate calls(CallSite cs, Function callee, string how) {
3131
* Holds if call site `cs` may illegally invoke function `callee` as specified by `how`;
3232
* `calleeDesc` describes what kind of function `callee` is.
3333
*/
34-
predicate illegalInvocation(CallSite cs, Function callee, string calleeDesc, string how) {
34+
predicate illegalInvocation(DataFlow::InvokeNode cs, Function callee, string calleeDesc, string how) {
3535
calls(cs, callee, how) and
3636
(
3737
how = "as a function" and
@@ -44,15 +44,16 @@ predicate illegalInvocation(CallSite cs, Function callee, string calleeDesc, str
4444
}
4545

4646
/**
47-
* Holds if `ce` has at least one call target that isn't a constructor.
47+
* Holds if `ce` is a call with at least one call target that isn't a constructor.
4848
*/
49-
predicate isCallToFunction(CallExpr ce) {
50-
exists (Function f | f = ce.(CallSite).getACallee() |
49+
predicate isCallToFunction(DataFlow::InvokeNode ce) {
50+
ce instanceof DataFlow::CallNode and
51+
exists (Function f | f = ce.getACallee() |
5152
not f instanceof Constructor
5253
)
5354
}
5455

55-
from CallSite cs, Function callee, string calleeDesc, string how
56+
from DataFlow::InvokeNode cs, Function callee, string calleeDesc, string how
5657
where illegalInvocation(cs, callee, calleeDesc, how) and
5758
// filter out some easy cases
5859
not isCallToFunction(cs) and

javascript/ql/src/LanguageFeatures/InconsistentNew.ql

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ import semmle.javascript.RestrictedLocations
2222
* have to call itself using `new`, so that is what we look for.
2323
*/
2424
predicate guardsAgainstMissingNew(Function f) {
25-
exists (CallSite new |
26-
new.(NewExpr).getEnclosingFunction() = f and
25+
exists (DataFlow::NewNode new |
26+
new.asExpr().getEnclosingFunction() = f and
2727
f = new.getACallee()
2828
)
2929
}
@@ -34,13 +34,13 @@ predicate guardsAgainstMissingNew(Function f) {
3434
* is only suggested as a potential callee due to imprecise analysis of global
3535
* variables and is not, in fact, a viable callee at all.
3636
*/
37-
predicate calls(CallSite cs, Function callee, int imprecision) {
37+
predicate calls(DataFlow::InvokeNode cs, Function callee, int imprecision) {
3838
callee = cs.getACallee() and
3939
(
4040
// if global flow was used to derive the callee, we may be imprecise
4141
if cs.isIndefinite("global") then
4242
// callees within the same file are probably genuine
43-
callee.getFile() = cs.(Locatable).getFile() and imprecision = 0
43+
callee.getFile() = cs.getFile() and imprecision = 0
4444
or
4545
// calls to global functions declared in an externs file are fairly
4646
// safe as well
@@ -58,11 +58,11 @@ predicate calls(CallSite cs, Function callee, int imprecision) {
5858
* Gets a function that may be invoked at `cs`, preferring callees that
5959
* are less likely to be derived due to analysis imprecision.
6060
*/
61-
Function getALikelyCallee(CallSite cs) {
61+
Function getALikelyCallee(DataFlow::InvokeNode cs) {
6262
calls(cs, result, min(int p | calls(cs, _, p)))
6363
}
6464

65-
from Function f, NewExpr new, CallExpr call
65+
from Function f, DataFlow::NewNode new, DataFlow::CallNode call
6666
where // externs are special, so don't flag them
6767
not f.inExternsFile() and
6868
// illegal constructor calls are flagged by query 'Illegal invocation',
@@ -71,11 +71,11 @@ where // externs are special, so don't flag them
7171
f = getALikelyCallee(new) and
7272
f = getALikelyCallee(call) and
7373
not guardsAgainstMissingNew(f) and
74-
not new.(CallSite).isUncertain() and
75-
not call.(CallSite).isUncertain() and
74+
not new.isUncertain() and
75+
not call.isUncertain() and
7676
// super constructor calls behave more like `new`, so don't flag them
77-
not call instanceof SuperCall and
78-
// reflective calls provide an explicit receiver object, so don't flag them either
79-
not call instanceof ReflectiveCallSite
77+
not call.asExpr() instanceof SuperCall and
78+
// don't flag if there is a receiver object
79+
not exists(call.getReceiver())
8080
select (FirstLineOf)f, capitalize(f.describe()) + " is invoked as a constructor here $@, " +
8181
"and as a normal function here $@.", new, new.toString(), call, call.toString()

javascript/ql/src/LanguageFeatures/SpuriousArguments.ql

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -29,23 +29,11 @@ predicate isFixedArity(Function fn) {
2929
*
3030
* This is only defined if all potential callees have a fixed arity.
3131
*/
32-
int maxArity(CallSite invk) {
32+
int maxArity(DataFlow::InvokeNode invk) {
3333
forall (Function callee | callee = invk.getACallee() | isFixedArity(callee)) and
3434
result = max(invk.getACallee().getNumParameter())
3535
}
3636

37-
/**
38-
* Holds if call site `invk` has more arguments than the maximum arity
39-
* of any function that it may invoke, and the first of those
40-
* arguments is `arg`.
41-
*
42-
* This predicate is only defined for call sites where callee information is complete.
43-
*/
44-
predicate firstSpuriousArgument(CallSite invk, Expr arg) {
45-
arg = invk.getArgumentNode(maxArity(invk)).asExpr() and
46-
not invk.isIncomplete()
47-
}
48-
4937
/**
5038
* A list of spurious arguments passed at a call site.
5139
*
@@ -54,15 +42,18 @@ predicate firstSpuriousArgument(CallSite invk, Expr arg) {
5442
* defined to cover all subsequent arguments as well.
5543
*/
5644
class SpuriousArguments extends Expr {
45+
DataFlow::InvokeNode invk;
46+
5747
SpuriousArguments() {
58-
firstSpuriousArgument(_, this)
48+
this = invk.getArgument(maxArity(invk)).asExpr() and
49+
not invk.isIncomplete()
5950
}
6051

6152
/**
6253
* Gets the call site at which the spurious arguments are passed.
6354
*/
64-
CallSite getCall() {
65-
firstSpuriousArgument(result, this)
55+
DataFlow::InvokeNode getCall() {
56+
result = invk
6657
}
6758

6859
/**
@@ -71,7 +62,7 @@ class SpuriousArguments extends Expr {
7162
* expected by any potential callee.
7263
*/
7364
int getCount() {
74-
result = getCall().(InvokeExpr).getNumArgument() - maxArity(getCall())
65+
result = count(int i | exists(invk.getArgument(i)) and i >= maxArity(getCall()))
7566
}
7667

7768
/**
@@ -82,14 +73,15 @@ class SpuriousArguments extends Expr {
8273
* [LGTM locations](https://lgtm.com/help/ql/locations).
8374
*/
8475
predicate hasLocationInfo(string filepath, int startline, int startcolumn, int endline, int endcolumn) {
85-
this.getLocation().hasLocationInfo(filepath, startline, startcolumn, _, _) and
86-
exists (Expr lastArg | lastArg = getCall().(InvokeExpr).getLastArgument() |
87-
lastArg.getLocation().hasLocationInfo(_, _, _, endline, endcolumn)
76+
getLocation().hasLocationInfo(filepath, startline, startcolumn, _, _) and
77+
exists (DataFlow::Node lastArg |
78+
lastArg = max(DataFlow::Node arg, int i | arg = invk.getArgument(i) | arg order by i) |
79+
lastArg.hasLocationInfo(_, _, _, endline, endcolumn)
8880
)
8981
}
9082
}
9183

9284
from SpuriousArguments args, Function f, string arguments
9385
where f = args.getCall().getACallee() and
9486
if args.getCount() = 1 then arguments = "argument" else arguments = "arguments"
95-
select args, "Superfluous " + arguments + " passed to $@.", f, f.describe()
87+
select args, "Superfluous " + arguments + " passed to $@.", f, f.describe()

javascript/ql/src/React/UnsupportedStateUpdateInLifecycleMethod.ql

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,25 @@ import javascript
1414
/**
1515
* A call that invokes a method on its own receiver.
1616
*/
17-
class CallOnSelf extends CallExpr {
17+
class CallOnSelf extends DataFlow::CallNode {
1818

1919
CallOnSelf() {
2020
exists (Function binder |
2121
binder = getEnclosingFunction().getThisBinder() |
2222
exists (DataFlow::ThisNode thiz |
23-
this = thiz.getAMethodCall(_).asExpr() and
23+
this = thiz.getAMethodCall(_) and
2424
thiz.getBinder().getAstNode() = binder
2525
)
2626
or
27-
this.(CallSite).getACallee().(ArrowFunctionExpr).getThisBinder() = binder
27+
this.getACallee().(ArrowFunctionExpr).getThisBinder() = binder
2828
)
2929
}
3030

3131
/**
3232
* Gets a `CallOnSelf` in the callee of this call.
3333
*/
3434
CallOnSelf getACalleCallOnSelf() {
35-
result.getEnclosingFunction() = this.(CallSite).getACallee()
35+
result.getEnclosingFunction() = this.getACallee()
3636
}
3737

3838
}
@@ -55,15 +55,15 @@ class UnconditionalCallOnSelf extends CallOnSelf {
5555
/**
5656
* Holds if `call` is guaranteed to occur in its enclosing function, unless an exception occurs.
5757
*/
58-
predicate isUnconditionalCall(CallExpr call) {
58+
predicate isUnconditionalCall(DataFlow::CallNode call) {
5959
exists (ReachableBasicBlock callBlock, ReachableBasicBlock entryBlock |
6060
callBlock.postDominates(entryBlock) and
61-
callBlock.getANode() = call and
61+
callBlock = call.getBasicBlock() and
6262
entryBlock = call.getEnclosingFunction().getEntryBB()
6363
)
6464
}
6565

66-
predicate isStateUpdateMethodCall(MethodCallExpr mce) {
66+
predicate isStateUpdateMethodCall(DataFlow::MethodCallNode mce) {
6767
exists (string updateMethodName |
6868
updateMethodName = "setState" or
6969
updateMethodName = "replaceState" or
@@ -111,7 +111,8 @@ class StateUpdateVolatileMethod extends Function {
111111

112112
}
113113

114-
from StateUpdateVolatileMethod root, CallOnSelf initCall, MethodCallExpr stateUpdate, string callDescription
114+
from StateUpdateVolatileMethod root, CallOnSelf initCall, DataFlow::MethodCallNode stateUpdate,
115+
string callDescription
115116
where initCall.getEnclosingFunction() = root and
116117
stateUpdate = initCall.getACalleCallOnSelf*() and
117118
isStateUpdateMethodCall(stateUpdate) and

javascript/ql/src/Statements/ImplicitReturn.ql

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,9 @@ int numRet(Function f) {
5555
*/
5656
predicate isDualUseConstructor(Function f) {
5757
numRet(f) = 1 and
58-
exists (ReturnStmt ret, NewExpr new | ret.getContainer() = f |
59-
new = ret.getExpr().stripParens() and
60-
new.(CallSite).getACallee() = f
58+
exists (ReturnStmt ret, DataFlow::NewNode new | ret.getContainer() = f |
59+
new.asExpr() = ret.getExpr().stripParens() and
60+
new.getACallee() = f
6161
)
6262
}
6363

javascript/ql/src/semmle/javascript/StandardLibrary.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class CallToObjectDefineProperty extends DataFlow::MethodCallNode {
99
CallToObjectDefineProperty() {
1010
exists (GlobalVariable obj |
1111
obj.getName() = "Object" and
12-
astNode.calls(obj.getAnAccess(), "defineProperty")
12+
calls(DataFlow::valueNode(obj.getAnAccess()), "defineProperty")
1313
)
1414
}
1515

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ import javascript
66
private import InferredTypes
77

88
/**
9+
* DEPRECATED: Use `DataFlow::InvokeNode` instead.
10+
*
911
* A function call or `new` expression, with information about its potential callees.
1012
*
1113
* Both direct calls and reflective calls using `call` or `apply` are modelled.
1214
*/
13-
class CallSite extends @invokeexpr {
15+
deprecated class CallSite extends @invokeexpr {
1416
InvokeExpr invk;
1517

1618
CallSite() { invk = this }
@@ -120,7 +122,7 @@ class CallSite extends @invokeexpr {
120122
/**
121123
* A reflective function call using `call` or `apply`.
122124
*/
123-
class ReflectiveCallSite extends CallSite {
125+
deprecated class ReflectiveCallSite extends CallSite {
124126
DataFlow::AnalyzedNode callee;
125127
string callMode;
126128

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ private predicate callInputStep(Function f, DataFlow::Node invk,
407407
or
408408
exists (SsaDefinition prevDef, SsaDefinition def |
409409
pred = DataFlow::ssaDefinitionNode(prevDef) and
410-
calls(invk.asExpr(), f) and captures(f, prevDef, def) and
410+
calls(invk, f) and captures(f, prevDef, def) and
411411
succ = DataFlow::ssaDefinitionNode(def)
412412
)
413413
) and

0 commit comments

Comments
 (0)