mirror of
https://github.com/github/codeql.git
synced 2026-03-24 00:16:49 +01:00
125 lines
3.6 KiB
Plaintext
125 lines
3.6 KiB
Plaintext
/**
|
|
* @name Unsupported state update in lifecycle method
|
|
* @description Attempting to update the state of a React component at the wrong time can cause undesired behavior.
|
|
* @kind problem
|
|
* @problem.severity warning
|
|
* @id js/react/unsupported-state-update-in-lifecycle-method
|
|
* @tags reliability
|
|
* frameworks/react
|
|
* @precision high
|
|
*/
|
|
|
|
import javascript
|
|
|
|
/**
|
|
* A call that invokes a method on its own receiver.
|
|
*/
|
|
class CallOnSelf extends DataFlow::CallNode {
|
|
|
|
CallOnSelf() {
|
|
exists (Function binder |
|
|
binder = getEnclosingFunction().getThisBinder() |
|
|
exists (DataFlow::ThisNode thiz |
|
|
this = thiz.getAMethodCall(_) and
|
|
thiz.getBinder().getAstNode() = binder
|
|
)
|
|
or
|
|
this.getACallee().(ArrowFunctionExpr).getThisBinder() = binder
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Gets a `CallOnSelf` in the callee of this call.
|
|
*/
|
|
CallOnSelf getACalleCallOnSelf() {
|
|
result.getEnclosingFunction() = this.getACallee()
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* A call that is definitely invoked by the caller, unless an exception occurs.
|
|
*/
|
|
class UnconditionalCallOnSelf extends CallOnSelf {
|
|
|
|
UnconditionalCallOnSelf() {
|
|
isUnconditionalCall(this)
|
|
}
|
|
|
|
override UnconditionalCallOnSelf getACalleCallOnSelf() {
|
|
result = CallOnSelf.super.getACalleCallOnSelf()
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Holds if `call` is guaranteed to occur in its enclosing function, unless an exception occurs.
|
|
*/
|
|
predicate isUnconditionalCall(DataFlow::CallNode call) {
|
|
exists (ReachableBasicBlock callBlock, ReachableBasicBlock entryBlock |
|
|
callBlock.postDominates(entryBlock) and
|
|
callBlock = call.getBasicBlock() and
|
|
entryBlock = call.getEnclosingFunction().getEntryBB()
|
|
)
|
|
}
|
|
|
|
predicate isStateUpdateMethodCall(DataFlow::MethodCallNode mce) {
|
|
exists (string updateMethodName |
|
|
updateMethodName = "setState" or
|
|
updateMethodName = "replaceState" or
|
|
updateMethodName = "forceUpdate" |
|
|
mce.getMethodName() = updateMethodName
|
|
)
|
|
}
|
|
|
|
/**
|
|
* A React component method in which state updates may have surprising effects.
|
|
*/
|
|
class StateUpdateVolatileMethod extends Function {
|
|
|
|
string methodName;
|
|
|
|
StateUpdateVolatileMethod() {
|
|
// methods that are known to be ok:
|
|
// - componentWillUnmount
|
|
// - componentsWillMount
|
|
// - componentsDidMount
|
|
|
|
exists (ReactComponent c |
|
|
methodName = "componentDidUnmount" or
|
|
methodName = "componentDidUpdate" or
|
|
methodName = "componentWillUpdate" or
|
|
methodName = "getDefaultProps" or
|
|
methodName = "getInitialState" or
|
|
methodName = "render" or
|
|
methodName = "shouldComponentUpdate" |
|
|
this = c.getInstanceMethod(methodName)
|
|
)
|
|
or
|
|
this = any(ES2015Component c).getConstructor().getBody() and
|
|
methodName = "constructor"
|
|
}
|
|
|
|
/**
|
|
* Holds if conditional state updates are benign in this method.
|
|
*/
|
|
predicate conditionalStateUpatesAreBenign() {
|
|
methodName = "componentDidUpdate" or
|
|
methodName = "componentWillUpdate" or
|
|
methodName = "shouldComponentUpdate"
|
|
}
|
|
|
|
}
|
|
|
|
from StateUpdateVolatileMethod root, CallOnSelf initCall, DataFlow::MethodCallNode stateUpdate,
|
|
string callDescription
|
|
where initCall.getEnclosingFunction() = root and
|
|
stateUpdate = initCall.getACalleCallOnSelf*() and
|
|
isStateUpdateMethodCall(stateUpdate) and
|
|
if root.conditionalStateUpatesAreBenign() then
|
|
initCall instanceof UnconditionalCallOnSelf and
|
|
callDescription = "Unconditional call"
|
|
else
|
|
callDescription = "Call"
|
|
select initCall, callDescription + " to state update method $@ is not allowed from within this method.", stateUpdate, " ." + stateUpdate.getMethodName()
|