mirror of
https://github.com/github/codeql.git
synced 2026-02-06 18:21:07 +01:00
78 lines
2.8 KiB
Plaintext
78 lines
2.8 KiB
Plaintext
/**
|
|
* @name Unbound event handler receiver
|
|
* @description Invoking an event handler method as a function can cause a runtime error.
|
|
* @kind problem
|
|
* @problem.severity error
|
|
* @id js/unbound-event-handler-receiver
|
|
* @tags correctness
|
|
* @precision high
|
|
*/
|
|
import javascript
|
|
|
|
/**
|
|
* Holds if the receiver of `method` is bound.
|
|
*/
|
|
private predicate isBoundInMethod(MethodDeclaration method) {
|
|
exists (DataFlow::ThisNode thiz, MethodDeclaration bindingMethod |
|
|
bindingMethod.getDeclaringClass() = method.getDeclaringClass() and
|
|
not bindingMethod.isStatic() and
|
|
thiz.getBinder().getAstNode() = bindingMethod.getBody() |
|
|
// require("auto-bind")(this)
|
|
thiz.flowsTo(DataFlow::moduleImport("auto-bind").getACall().getArgument(0))
|
|
or
|
|
exists (string name |
|
|
name = method.getName() |
|
|
exists (DataFlow::MethodCallNode bind |
|
|
// this.<methodName> = <expr>.bind(...)
|
|
bind = thiz.getAPropertySource(name) and
|
|
bind.getMethodName() = "bind"
|
|
)
|
|
or
|
|
exists (DataFlow::MethodCallNode bindAll |
|
|
bindAll.getMethodName() = "bindAll" and
|
|
thiz.flowsTo(bindAll.getArgument(0)) |
|
|
// _.bindAll(this, <name1>)
|
|
bindAll.getArgument(1).mayHaveStringValue(name)
|
|
or
|
|
// _.bindAll(this, [<name1>, <name2>])
|
|
exists (DataFlow::ArrayCreationNode names |
|
|
names.flowsTo(bindAll.getArgument(1)) and
|
|
names.getAnElement().mayHaveStringValue(name)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
or
|
|
exists (Expr decoration, string name |
|
|
(
|
|
decoration = method.getADecorator().getExpression()
|
|
or
|
|
decoration = method.getDeclaringType().(ClassDefinition).getADecorator().getExpression()
|
|
) and
|
|
name.regexpMatch("(?i).*(bind|bound).*") |
|
|
// @autobind
|
|
decoration.(Identifier).getName() = name or
|
|
// @action.bound
|
|
decoration.(PropAccess).getPropertyName() = name
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Gets an event handler attribute (onClick, onTouch, ...).
|
|
*/
|
|
private DOM::AttributeDefinition getAnEventHandlerAttribute() {
|
|
exists (ReactComponent c, JSXNode rendered, string attributeName |
|
|
c.getRenderMethod().getAReturnedExpr().flow().getALocalSource().asExpr() = rendered and
|
|
result = rendered.getABodyElement*().(JSXElement).getAttributeByName(attributeName) and
|
|
attributeName.regexpMatch("on[A-Z][a-zA-Z]+") // camelCased with 'on'-prefix
|
|
)
|
|
}
|
|
|
|
from MethodDeclaration callback, DOM::AttributeDefinition attribute, ThisExpr unbound
|
|
where
|
|
attribute = getAnEventHandlerAttribute() and
|
|
attribute.getValueNode().analyze().getAValue().(AbstractFunction).getFunction() = callback.getBody() and
|
|
unbound.getBinder() = callback.getBody() and
|
|
not isBoundInMethod(callback)
|
|
select attribute, "The receiver of this event handler call is unbound, `$@` will be `undefined` in the call to $@", unbound, "this", callback, callback.getName()
|