Skip to content

Commit b43989e

Browse files
committed
JS: Use API nodes to track dispatch/dispatched value sources
1 parent 2850b8e commit b43989e

File tree

3 files changed

+32
-62
lines changed

3 files changed

+32
-62
lines changed

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

Lines changed: 24 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,10 @@ module Redux {
6262
StoreCreation() { this = range }
6363

6464
/** Gets a reference to the store. */
65-
DataFlow::SourceNode ref() {
66-
// We happen to know that all store-creation sources have API nodes, so just reuse the API node type tracking
67-
exists(API::Node apiNode |
68-
apiNode.getAnImmediateUse() = this and
69-
result = apiNode.getAUse()
70-
)
71-
}
65+
DataFlow::SourceNode ref() { result = asApiNode().getAUse() }
66+
67+
/** Gets an API node that refers to this store creation. */
68+
API::Node asApiNode() { result.getAnImmediateUse() = this }
7269

7370
/** Gets the data flow node holding the root reducer for this store. */
7471
DataFlow::Node getReducerArg() { result = range.getReducerArg() }
@@ -751,57 +748,34 @@ module Redux {
751748
}
752749

753750
/**
754-
* A source of the `dispatch` function, used as starting point for `getADispatchFunctionReference`.
751+
* A source of the `dispatch` function, used as starting point for `getADispatchFunctionNode`.
755752
*/
756-
abstract private class DispatchFunctionSource extends DataFlow::SourceNode { }
753+
abstract private class DispatchFunctionSource extends API::Node { }
757754

758755
/**
759756
* A value that is dispatched, that is, flows to the first argument of `dispatch`
760757
* (but where the call to `dispatch` is not necessarily explicit in the code).
761758
*
762-
* Used as starting point for `getADispatchedValueSource`.
759+
* Used as starting point for `getADispatchedValueNode`.
763760
*/
764-
abstract private class DispatchedValueSink extends DataFlow::Node { }
765-
766-
private class StoreDispatchSource extends DispatchFunctionSource {
767-
StoreDispatchSource() { this = any(StoreCreation c).ref().getAPropertyRead("dispatch") }
768-
}
761+
abstract private class DispatchedValueSink extends API::Node { }
769762

770-
/** Gets a data flow node referring to the `dispatch` function. */
771-
private DataFlow::SourceNode getADispatchFunctionReference(DataFlow::TypeTracker t) {
772-
t.start() and
763+
/** Gets an API node referring to the Redux `dispatch` function. */
764+
API::Node getADispatchFunctionNode() {
773765
result instanceof DispatchFunctionSource
774766
or
775-
// When using the redux-thunk middleware, dispatching a function value results in that
776-
// function being invoked with (dispatch, getState).
777-
// We simply assume redux-thunk middleware is always installed.
778-
t.start() and
779-
result = getADispatchedValueSource().(DataFlow::FunctionNode).getParameter(0)
780-
or
781-
exists(DataFlow::TypeTracker t2 | result = getADispatchFunctionReference(t2).track(t2, t))
767+
result = getADispatchedValueNode().getParameter(0)
782768
}
783769

784-
/** Gets a data flow node referring to the `dispatch` function. */
785-
DataFlow::SourceNode getADispatchFunctionReference() {
786-
result = getADispatchFunctionReference(DataFlow::TypeTracker::end())
787-
}
788-
789-
/** Gets a data flow node that is dispatched as an action. */
790-
private DataFlow::SourceNode getADispatchedValueSource(DataFlow::TypeBackTracker t) {
791-
t.start() and
792-
result = any(DispatchedValueSink d).getALocalSource()
793-
or
794-
t.start() and
795-
result = getADispatchFunctionReference().getACall().getArgument(0).getALocalSource()
770+
/** Gets an API node corresponding to a value being passed to the `dispatch` function. */
771+
API::Node getADispatchedValueNode() {
772+
result instanceof DispatchedValueSink
796773
or
797-
exists(DataFlow::TypeBackTracker t2 | result = getADispatchedValueSource(t2).backtrack(t2, t))
774+
result = getADispatchFunctionNode().getParameter(0)
798775
}
799776

800-
/**
801-
* Gets a data flow node that is dispatched as an action, that is, it flows to the first argument of `dispatch`.
802-
*/
803-
DataFlow::SourceNode getADispatchedValueSource() {
804-
result = getADispatchedValueSource(DataFlow::TypeBackTracker::end())
777+
private class StoreDispatchSource extends DispatchFunctionSource {
778+
StoreDispatchSource() { this = any(StoreCreation c).asApiNode().getMember("dispatch") }
805779
}
806780

807781
/** Gets the `action` parameter of a reducer that isn't behind an implied type guard. */
@@ -823,7 +797,7 @@ module Redux {
823797
/** The return value of a function flowing into `bindActionCreators`, seen as a value that is dispatched. */
824798
private class BindActionDispatchSink extends DispatchedValueSink {
825799
BindActionDispatchSink() {
826-
this = any(BindActionCreatorsCall c).getParameter(0).getAMember().getReturn().getARhs()
800+
this = any(BindActionCreatorsCall c).getParameter(0).getAMember().getReturn()
827801
}
828802
}
829803

@@ -958,7 +932,7 @@ module Redux {
958932
*/
959933
private DataFlow::ObjectLiteralNode getAManuallyDispatchedValue(string actionType) {
960934
result.getAPropertyWrite("type").getRhs().mayHaveStringValue(actionType) and
961-
result = getADispatchedValueSource()
935+
result = getADispatchedValueNode().getAValueReachingRhs()
962936
}
963937

964938
/**
@@ -1054,8 +1028,7 @@ module Redux {
10541028
/** A call to `useDispatch`, as a source of the `dispatch` function. */
10551029
private class UseDispatchFunctionSource extends DispatchFunctionSource {
10561030
UseDispatchFunctionSource() {
1057-
this =
1058-
API::moduleImport("react-redux").getMember("useDispatch").getReturn().getAnImmediateUse()
1031+
this = API::moduleImport("react-redux").getMember("useDispatch").getReturn()
10591032
}
10601033
}
10611034

@@ -1136,9 +1109,9 @@ module Redux {
11361109
/**
11371110
* An API entry point corresponding to a `connect` function which we couldn't recognize exactly.
11381111
*
1139-
* The `connect` call is recognized based on an argument being named either `mapStateToProps` or `mapDispatchToProps`.
1112+
* The `connect` call is recognized based on an argument being named either `mapStateToProps` or `mapDispatchToProps`.
11401113
* Used to catch cases where the `connect` function was not recognized by API graphs (usually because of it being
1141-
* wrapped in another function, which API graphs won't look through).
1114+
* wrapped in another function, which API graphs won't look through).
11421115
*/
11431116
private class HeuristicConnectEntryPoint extends API::EntryPoint {
11441117
HeuristicConnectEntryPoint() { this = "react-redux-connect" }
@@ -1197,15 +1170,13 @@ module Redux {
11971170

11981171
/** The first argument to `mapDispatchToProps` as a source of the `dispatch` function */
11991172
private class MapDispatchToPropsArg extends DispatchFunctionSource {
1200-
MapDispatchToPropsArg() {
1201-
this = any(ConnectCall c).getMapDispatchToProps().getParameter(0).getAnImmediateUse()
1202-
}
1173+
MapDispatchToPropsArg() { this = any(ConnectCall c).getMapDispatchToProps().getParameter(0) }
12031174
}
12041175

12051176
/** If `mapDispatchToProps` is an object, each method's return value is dispatched. */
12061177
private class MapDispatchToPropsMember extends DispatchedValueSink {
12071178
MapDispatchToPropsMember() {
1208-
this = any(ConnectCall c).getMapDispatchToProps().getAMember().getReturn().getARhs()
1179+
this = any(ConnectCall c).getMapDispatchToProps().getAMember().getReturn()
12091180
}
12101181
}
12111182

javascript/ql/test/library-tests/frameworks/Redux/test.expected

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -122,13 +122,12 @@ getAffectedStateAccessPath
122122
| react-redux.jsx:60:14:60:27 | toolkitReducer | toolkit |
123123
| react-redux.jsx:61:13:61:25 | manualReducer | manual |
124124
| trivial.js:130:14:130:46 | wrapper ... state) | wrapped |
125-
getADispatchFunctionReference
126-
| react-redux.jsx:65:20:65:32 | useDispatch() |
127-
getADispatchedValueSource
128-
| react-redux.jsx:26:1:31:1 | return of function manualAction |
129-
| react-redux.jsx:27:12:30:5 | {\\n ... x\\n } |
130-
| react-redux.jsx:69:18:69:39 | manualA ... urce()) |
131-
| react-redux.jsx:70:18:70:38 | asyncAc ... urce()) |
125+
getADispatchFunctionNode
126+
| react-redux.jsx:65:20:65:32 | use (return (member useDispatch (member exports (module react-redux)))) |
127+
getADispatchedValueNode
128+
| react-redux.jsx:27:12:30:5 | def (return (member manualAction (parameter 1 (react-redux-connect)))) |
129+
| react-redux.jsx:69:18:69:39 | def (parameter 0 (return (member useDispatch (member exports (module react-redux))))) |
130+
| react-redux.jsx:70:18:70:38 | def (parameter 0 (return (member useDispatch (member exports (module react-redux))))) |
132131
getAnUntypedActionInReducer
133132
| exportedReducer.js:12:20:12:25 | action |
134133
| react-redux.jsx:32:31:32:36 | action |

javascript/ql/test/library-tests/frameworks/Redux/test.ql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ query Redux::ReducerArg getUseSite(Redux::DelegatingReducer reducer) {
3030
result = reducer.getUseSite()
3131
}
3232

33-
query predicate getADispatchFunctionReference = Redux::getADispatchFunctionReference/0;
33+
query predicate getADispatchFunctionNode = Redux::getADispatchFunctionNode/0;
3434

35-
query predicate getADispatchedValueSource = Redux::getADispatchedValueSource/0;
35+
query predicate getADispatchedValueNode = Redux::getADispatchedValueNode/0;
3636

3737
query predicate getAnUntypedActionInReducer = Redux::getAnUntypedActionInReducer/0;
3838

0 commit comments

Comments
 (0)