Skip to content

Commit 3c00d4b

Browse files
author
Max Schaefer
authored
Merge pull request #607 from esben-semmle/js/more-react-methods
JS: model additional React component methods
2 parents 00779c5 + d63d838 commit 3c00d4b

15 files changed

+124
-7
lines changed

change-notes/1.20/analysis-javascript.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
## General improvements
44

55
* Support for popular libraries has been improved. Consequently, queries may produce more results on code bases that use the following features:
6-
- servers, for example [hapi](https://hapijs.com/)
6+
- client-side code, for example [React](https://reactjs.org/)
7+
- server-side code, for example [hapi](https://hapijs.com/)
78

89
## New queries
910

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

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ abstract class ReactComponent extends ASTNode {
3434
*/
3535
abstract Function getInstanceMethod(string name);
3636

37+
/**
38+
* Gets a static method of this component with the given name.
39+
*/
40+
abstract Function getStaticMethod(string name);
41+
3742
/**
3843
* Gets the abstract value that represents this component.
3944
*/
@@ -175,6 +180,11 @@ abstract class ReactComponent extends ASTNode {
175180
// setState with object: `this.setState({foo: 42})`
176181
result = arg0
177182
)
183+
or
184+
result.flowsToExpr(getStaticMethod("getDerivedStateFromProps").getAReturnedExpr())
185+
or
186+
// shouldComponentUpdate: (nextProps, nextState)
187+
result = DataFlow::parameterNode(getInstanceMethod("shouldComponentUpdate").getParameter(1))
178188
}
179189

180190
/**
@@ -190,9 +200,14 @@ abstract class ReactComponent extends ASTNode {
190200
callback = getAMethodCall("setState").getCallback(0) and
191201
stateParameterIndex = 0
192202
or
193-
// componentDidUpdate: (prevProps, prevState)
194-
callback = getInstanceMethod("componentDidUpdate").flow() and
195-
stateParameterIndex = 1
203+
stateParameterIndex = 1 and (
204+
// componentDidUpdate: (prevProps, prevState)
205+
callback = getInstanceMethod("componentDidUpdate").flow() or
206+
// getDerivedStateFromProps: (props, state)
207+
callback = getStaticMethod("getDerivedStateFromProps").flow() or
208+
// getSnapshotBeforeUpdate: (prevProps, prevState)
209+
callback = getInstanceMethod("getSnapshotBeforeUpdate").flow()
210+
)
196211
)
197212
}
198213

@@ -205,6 +220,9 @@ abstract class ReactComponent extends ASTNode {
205220
result.flowsTo(getComponentCreatorSource().getAnInvocation().getArgument(0))
206221
or
207222
result = getADefaultPropsSource()
223+
or
224+
// shouldComponentUpdate: (nextProps, nextState)
225+
result = DataFlow::parameterNode(getInstanceMethod("shouldComponentUpdate").getParameter(0))
208226
}
209227

210228
/**
@@ -234,9 +252,14 @@ abstract class ReactComponent extends ASTNode {
234252
callback = getAMethodCall("setState").getCallback(0) and
235253
propsParameterIndex = 1
236254
or
237-
// componentDidUpdate: (prevProps, prevState)
238-
callback = getInstanceMethod("componentDidUpdate").flow() and
239-
propsParameterIndex = 0
255+
propsParameterIndex = 0 and (
256+
// componentDidUpdate: (prevProps, prevState)
257+
callback = getInstanceMethod("componentDidUpdate").flow() or
258+
// getDerivedStateFromProps: (props, state)
259+
callback = getStaticMethod("getDerivedStateFromProps").flow() or
260+
// getSnapshotBeforeUpdate: (prevProps, prevState)
261+
callback = getInstanceMethod("getSnapshotBeforeUpdate").flow()
262+
)
240263
)
241264
}
242265

@@ -272,6 +295,10 @@ class FunctionalComponent extends ReactComponent, Function {
272295
name = "render" and result = this
273296
}
274297

298+
override Function getStaticMethod(string name) {
299+
none()
300+
}
301+
275302
override DataFlow::SourceNode getADirectPropsAccess() {
276303
result = DataFlow::parameterNode(getParameter(0))
277304
}
@@ -302,6 +329,14 @@ private abstract class SharedReactPreactClassComponent extends ReactComponent, C
302329
result = ClassDefinition.super.getInstanceMethod(name)
303330
}
304331

332+
override Function getStaticMethod(string name) {
333+
exists(MethodDeclaration decl |
334+
decl = getMethod(name) and
335+
decl.isStatic() and
336+
result = decl.getBody()
337+
)
338+
}
339+
305340
override DataFlow::SourceNode getADirectPropsAccess() {
306341
result.(DataFlow::PropRef).accesses(ref(), "props") or
307342
result = DataFlow::parameterNode(getConstructor().getBody().getParameter(0))
@@ -428,6 +463,10 @@ class ES5Component extends ReactComponent, ObjectExpr {
428463
result = getPropertyByName(name).getInit()
429464
}
430465

466+
override Function getStaticMethod(string name) {
467+
none()
468+
}
469+
431470
override DataFlow::SourceNode getADirectPropsAccess() {
432471
result.(DataFlow::PropRef).accesses(ref(), "props")
433472
}

javascript/ql/test/library-tests/frameworks/ReactJS/ReactComponent.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
| props.js:2:5:3:5 | class C ... {\\n } |
1515
| props.js:13:31:17:5 | {\\n ... }\\n } |
1616
| props.js:26:5:28:5 | functio ... ;\\n } |
17+
| rare-lifecycle-methods.js:1:1:11:1 | class C ... }\\n} |
1718
| statePropertyReads.js:1:1:13:1 | class R ... }\\n} |
1819
| statePropertyWrites.js:1:1:34:1 | class W ... };\\n} |
1920
| statePropertyWrites.js:36:19:45:1 | {\\n ren ... ;\\n }\\n} |

javascript/ql/test/library-tests/frameworks/ReactJS/ReactComponent_getACandidateStateSource.expected

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
| es6.js:14:1:20:1 | class H ... }\\n} | es6.js:18:22:18:31 | { baz: 42} |
2+
| rare-lifecycle-methods.js:1:1:11:1 | class C ... }\\n} | rare-lifecycle-methods.js:3:16:3:17 | {} |
3+
| rare-lifecycle-methods.js:1:1:11:1 | class C ... }\\n} | rare-lifecycle-methods.js:5:38:5:46 | nextState |
24
| statePropertyReads.js:1:1:13:1 | class R ... }\\n} | statePropertyReads.js:7:45:7:56 | prevState.p3 |
35
| statePropertyWrites.js:1:1:34:1 | class W ... };\\n} | statePropertyWrites.js:8:18:8:19 | {} |
46
| statePropertyWrites.js:1:1:34:1 | class W ... };\\n} | statePropertyWrites.js:12:18:12:19 | {} |

javascript/ql/test/library-tests/frameworks/ReactJS/ReactComponent_getADirectPropsSource.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
| probably-a-component.js:1:1:6:1 | class H ... }\\n} | probably-a-component.js:3:9:3:18 | this.props |
1616
| props.js:2:5:3:5 | class C ... {\\n } | props.js:2:37:2:36 | args |
1717
| props.js:26:5:28:5 | functio ... ;\\n } | props.js:26:16:26:20 | props |
18+
| rare-lifecycle-methods.js:1:1:11:1 | class C ... }\\n} | rare-lifecycle-methods.js:1:33:1:32 | args |
1819
| statePropertyWrites.js:36:19:45:1 | {\\n ren ... ;\\n }\\n} | statePropertyWrites.js:38:24:38:33 | this.props |
1920
| thisAccesses.js:31:2:36:1 | functio ... iv/>;\\n} | thisAccesses.js:31:12:31:16 | props |
2021
| thisAccesses.js:47:1:52:1 | class C ... }\\n} | thisAccesses.js:48:18:48:18 | y |
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
| rare-lifecycle-methods.js:1:1:11:1 | class C ... }\\n} | rare-lifecycle-methods.js:2:44:2:48 | state |
2+
| rare-lifecycle-methods.js:1:1:11:1 | class C ... }\\n} | rare-lifecycle-methods.js:8:40:8:48 | prevState |
3+
| statePropertyReads.js:1:1:13:1 | class R ... }\\n} | statePropertyReads.js:7:24:7:32 | prevState |
4+
| statePropertyReads.js:1:1:13:1 | class R ... }\\n} | statePropertyReads.js:10:35:10:43 | prevState |
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import semmle.javascript.frameworks.React
2+
3+
from ReactComponent c
4+
select c, c.getAPreviousStateSource()

javascript/ql/test/library-tests/frameworks/ReactJS/ReactComponent_getInstanceMethod.expected

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
| probably-a-component.js:1:1:6:1 | class H ... }\\n} | render | probably-a-component.js:2:11:5:5 | () {\\n ... ;\\n } |
1111
| props.js:13:31:17:5 | {\\n ... }\\n } | getDefaultProps | props.js:14:24:16:9 | () {\\n ... } |
1212
| props.js:26:5:28:5 | functio ... ;\\n } | render | props.js:26:5:28:5 | functio ... ;\\n } |
13+
| rare-lifecycle-methods.js:1:1:11:1 | class C ... }\\n} | getSnapshotBeforeUpdate | rare-lifecycle-methods.js:8:28:10:5 | (prevPr ... ;\\n } |
14+
| rare-lifecycle-methods.js:1:1:11:1 | class C ... }\\n} | shouldComponentUpdate | rare-lifecycle-methods.js:5:26:7:5 | (nextPr ... ;\\n } |
1315
| statePropertyReads.js:1:1:13:1 | class R ... }\\n} | componentDidUpdate | statePropertyReads.js:10:23:12:5 | (prevPr ... ;\\n } |
1416
| statePropertyWrites.js:1:1:34:1 | class W ... };\\n} | getInitialState | statePropertyWrites.js:25:20:29:5 | () { // ... ;\\n } |
1517
| statePropertyWrites.js:36:19:45:1 | {\\n ren ... ;\\n }\\n} | getInitialState | statePropertyWrites.js:40:20:44:3 | functio ... };\\n } |

javascript/ql/test/library-tests/frameworks/ReactJS/ReactComponent_ref.expected

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@
3131
| props.js:13:31:17:5 | {\\n ... }\\n } | props.js:14:24:14:23 | this |
3232
| props.js:26:5:28:5 | functio ... ;\\n } | props.js:26:5:26:4 | this |
3333
| props.js:26:5:28:5 | functio ... ;\\n } | props.js:34:5:34:55 | new C({ ... ctor"}) |
34+
| rare-lifecycle-methods.js:1:1:11:1 | class C ... }\\n} | rare-lifecycle-methods.js:1:33:1:32 | this |
35+
| rare-lifecycle-methods.js:1:1:11:1 | class C ... }\\n} | rare-lifecycle-methods.js:2:36:2:35 | this |
36+
| rare-lifecycle-methods.js:1:1:11:1 | class C ... }\\n} | rare-lifecycle-methods.js:5:26:5:25 | this |
37+
| rare-lifecycle-methods.js:1:1:11:1 | class C ... }\\n} | rare-lifecycle-methods.js:8:28:8:27 | this |
3438
| statePropertyReads.js:1:1:13:1 | class R ... }\\n} | statePropertyReads.js:2:16:2:15 | this |
3539
| statePropertyReads.js:1:1:13:1 | class R ... }\\n} | statePropertyReads.js:3:9:3:12 | this |
3640
| statePropertyReads.js:1:1:13:1 | class R ... }\\n} | statePropertyReads.js:5:9:5:12 | this |
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
class C extends React.Component {
2+
static getDerivedStateFromProps(props, state) {
3+
return {};
4+
}
5+
shouldComponentUpdate(nextProps, nextState) {
6+
return true;
7+
}
8+
getSnapshotBeforeUpdate(prevProps, prevState) {
9+
return {};
10+
}
11+
}

0 commit comments

Comments
 (0)