Skip to content

Commit 76e1e4d

Browse files
authored
Merge pull request github#4712 from asgerf/js/api-graph-tweaks
Approved by max-schaefer
2 parents 4c0f54f + e6d9cd1 commit 76e1e4d

File tree

5 files changed

+117
-19
lines changed

5 files changed

+117
-19
lines changed

javascript/ql/src/semmle/javascript/ApiGraphs.qll

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,17 +54,17 @@ module API {
5454
/**
5555
* Gets a call to the function represented by this API component.
5656
*/
57-
DataFlow::CallNode getACall() { result = getReturn().getAnImmediateUse() }
57+
CallNode getACall() { result = getReturn().getAnImmediateUse() }
5858

5959
/**
6060
* Gets a `new` call to the function represented by this API component.
6161
*/
62-
DataFlow::NewNode getAnInstantiation() { result = getInstance().getAnImmediateUse() }
62+
NewNode getAnInstantiation() { result = getInstance().getAnImmediateUse() }
6363

6464
/**
6565
* Gets an invocation (with our without `new`) to the function represented by this API component.
6666
*/
67-
DataFlow::InvokeNode getAnInvocation() { result = getACall() or result = getAnInstantiation() }
67+
InvokeNode getAnInvocation() { result = getACall() or result = getAnInstantiation() }
6868

6969
/**
7070
* Gets a data-flow node corresponding to the right-hand side of a definition of the API
@@ -82,6 +82,12 @@ module API {
8282
*/
8383
DataFlow::Node getARhs() { Impl::rhs(this, result) }
8484

85+
/**
86+
* Gets a data-flow node that may interprocedurally flow to the right-hand side of a definition
87+
* of the API component represented by this node.
88+
*/
89+
DataFlow::Node getAValueReachingRhs() { result = Impl::trackDefNode(getARhs()) }
90+
8591
/**
8692
* Gets a node representing member `m` of this API component.
8793
*
@@ -114,11 +120,17 @@ module API {
114120
* For example, if this node represents a use of some class `A`, then there might be a node
115121
* representing instances of `A`, typically corresponding to expressions `new A()` at the
116122
* source level.
123+
*
124+
* This predicate may have multiple results when there are multiple constructor calls invoking this API component.
125+
* Consider using `getAnInstantiation()` if there is a need to distinguish between individual constructor calls.
117126
*/
118127
Node getInstance() { result = getASuccessor(Label::instance()) }
119128

120129
/**
121130
* Gets a node representing the `i`th parameter of the function represented by this node.
131+
*
132+
* This predicate may have multiple results when there are multiple invocations of this API component.
133+
* Consider using `getAnInvocation()` if there is a need to distingiush between individual calls.
122134
*/
123135
bindingset[i]
124136
Node getParameter(int i) { result = getASuccessor(Label::parameter(i)) }
@@ -133,6 +145,9 @@ module API {
133145

134146
/**
135147
* Gets a node representing the last parameter of the function represented by this node.
148+
*
149+
* This predicate may have multiple results when there are multiple invocations of this API component.
150+
* Consider using `getAnInvocation()` if there is a need to distingiush between individual calls.
136151
*/
137152
Node getLastParameter() { result = getParameter(getNumParameter() - 1) }
138153

@@ -144,6 +159,10 @@ module API {
144159
/**
145160
* Gets a node representing a parameter or the receiver of the function represented by this
146161
* node.
162+
*
163+
* This predicate may result in a mix of parameters from different call sites in cases where
164+
* there are multiple invocations of this API component.
165+
* Consider using `getAnInvocation()` if there is a need to distingiush between individual calls.
147166
*/
148167
Node getAParameter() {
149168
result = getASuccessor(Label::parameterByStringIndex(_)) or
@@ -152,6 +171,9 @@ module API {
152171

153172
/**
154173
* Gets a node representing the result of the function represented by this node.
174+
*
175+
* This predicate may have multiple results when there are multiple invocations of this API component.
176+
* Consider using `getACall()` if there is a need to distingiush between individual calls.
155177
*/
156178
Node getReturn() { result = getASuccessor(Label::return()) }
157179

@@ -871,6 +893,54 @@ module API {
871893
}
872894

873895
import Label as EdgeLabel
896+
897+
/**
898+
* An `InvokeNode` that is connected to the API graph.
899+
*
900+
* Can be used to reason about calls to an external API in which the correlation between
901+
* parameters and/or return values must be retained.
902+
*
903+
* The member predicates `getParameter`, `getReturn`, and `getInstance` mimic the corresponding
904+
* predicates from `API::Node`. These are guaranteed to exist and be unique to this call.
905+
*/
906+
class InvokeNode extends DataFlow::InvokeNode {
907+
API::Node callee;
908+
909+
InvokeNode() {
910+
this = callee.getReturn().getAnImmediateUse() or
911+
this = callee.getInstance().getAnImmediateUse()
912+
}
913+
914+
/** Gets the API node for the `i`th parameter of this invocation. */
915+
Node getParameter(int i) {
916+
result = callee.getParameter(i) and
917+
result.getARhs() = getArgument(i)
918+
}
919+
920+
/** Gets the API node for a parameter of this invocation. */
921+
Node getAParameter() { result = getParameter(_) }
922+
923+
/** Gets the API node for the last parameter of this invocation. */
924+
Node getLastParameter() { result = getParameter(getNumArgument() - 1) }
925+
926+
/** Gets the API node for the return value of this call. */
927+
Node getReturn() {
928+
result = callee.getReturn() and
929+
result.getAnImmediateUse() = this
930+
}
931+
932+
/** Gets the API node for the object constructed by this invocation. */
933+
Node getInstance() {
934+
result = callee.getInstance() and
935+
result.getAnImmediateUse() = this
936+
}
937+
}
938+
939+
/** A call connected to the API graph. */
940+
class CallNode extends InvokeNode, DataFlow::CallNode { }
941+
942+
/** A `new` call connected to the API graph. */
943+
class NewNode extends InvokeNode, DataFlow::NewNode { }
874944
}
875945

876946
private module Label {

javascript/ql/src/semmle/javascript/frameworks/NoSQL.qll

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -62,22 +62,20 @@ private module MongoDB {
6262
}
6363

6464
/** A call to a MongoDB query method. */
65-
private class QueryCall extends DatabaseAccess, DataFlow::CallNode {
65+
private class QueryCall extends DatabaseAccess, API::CallNode {
6666
int queryArgIdx;
67-
API::Node callee;
6867

6968
QueryCall() {
7069
exists(string method |
7170
CollectionMethodSignatures::interpretsArgumentAsQuery(method, queryArgIdx) and
72-
callee = getACollection().getMember(method)
73-
) and
74-
this = callee.getACall()
71+
this = getACollection().getMember(method).getACall()
72+
)
7573
}
7674

7775
override DataFlow::Node getAQueryArgument() { result = getArgument(queryArgIdx) }
7876

7977
DataFlow::Node getACodeOperator() {
80-
result = getADollarWhereProperty(callee.getParameter(queryArgIdx))
78+
result = getADollarWhereProperty(getParameter(queryArgIdx))
8179
}
8280
}
8381

@@ -679,22 +677,26 @@ private module Minimongo {
679677
}
680678

681679
/** A call to a Minimongo query method. */
682-
private class QueryCall extends DatabaseAccess, DataFlow::MethodCallNode {
680+
private class QueryCall extends DatabaseAccess, API::CallNode {
683681
int queryArgIdx;
684-
API::Node callee;
685682

686683
QueryCall() {
687684
exists(string m |
688-
callee = API::moduleImport("minimongo").getAMember().getReturn().getAMember().getMember(m) and
689-
this = callee.getACall() and
685+
this =
686+
API::moduleImport("minimongo")
687+
.getAMember()
688+
.getReturn()
689+
.getAMember()
690+
.getMember(m)
691+
.getACall() and
690692
CollectionMethodSignatures::interpretsArgumentAsQuery(m, queryArgIdx)
691693
)
692694
}
693695

694696
override DataFlow::Node getAQueryArgument() { result = getArgument(queryArgIdx) }
695697

696698
DataFlow::Node getACodeOperator() {
697-
result = getADollarWhereProperty(callee.getParameter(queryArgIdx))
699+
result = getADollarWhereProperty(getParameter(queryArgIdx))
698700
}
699701
}
700702

@@ -715,14 +717,13 @@ private module Minimongo {
715717
*/
716718
private module MarsDB {
717719
/** A call to a MarsDB query method. */
718-
private class QueryCall extends DatabaseAccess, DataFlow::MethodCallNode {
720+
private class QueryCall extends DatabaseAccess, API::CallNode {
719721
int queryArgIdx;
720-
API::Node callee;
721722

722723
QueryCall() {
723724
exists(string m |
724-
callee = API::moduleImport("marsdb").getMember("Collection").getInstance().getMember(m) and
725-
this = callee.getACall() and
725+
this =
726+
API::moduleImport("marsdb").getMember("Collection").getInstance().getMember(m).getACall() and
726727
// implements parts of the Minimongo interface
727728
Minimongo::CollectionMethodSignatures::interpretsArgumentAsQuery(m, queryArgIdx)
728729
)
@@ -731,7 +732,7 @@ private module MarsDB {
731732
override DataFlow::Node getAQueryArgument() { result = getArgument(queryArgIdx) }
732733

733734
DataFlow::Node getACodeOperator() {
734-
result = getADollarWhereProperty(callee.getParameter(queryArgIdx))
735+
result = getADollarWhereProperty(getParameter(queryArgIdx))
735736
}
736737
}
737738

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { foo } from 'mylibrary';
2+
3+
foo({ value: 1 }, { value: 1 });
4+
foo({ value: 2 }, { value: 2 });
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
values
2+
| index.js:3:1:3:31 | foo({ v ... e: 1 }) | 1 | 1 |
3+
| index.js:4:1:4:31 | foo({ v ... e: 2 }) | 2 | 2 |
4+
mismatch
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import javascript
2+
3+
class FooCall extends API::CallNode {
4+
FooCall() { this = API::moduleImport("mylibrary").getMember("foo").getACall() }
5+
6+
DataFlow::Node getFirst() { result = getParameter(0).getMember("value").getARhs() }
7+
8+
DataFlow::Node getSecond() { result = getParameter(1).getMember("value").getARhs() }
9+
}
10+
11+
query predicate values(FooCall call, int first, int second) {
12+
first = call.getFirst().getIntValue() and
13+
second = call.getSecond().getIntValue()
14+
}
15+
16+
query predicate mismatch(FooCall call, string msg) {
17+
call.getFirst().getIntValue() != call.getSecond().getIntValue() and
18+
msg = "mismatching parameter indices found for call"
19+
}

0 commit comments

Comments
 (0)