forked from github/codeql
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathUnboundEventHandlerReceiver.ql
More file actions
74 lines (71 loc) · 2.75 KB
/
UnboundEventHandlerReceiver.ql
File metadata and controls
74 lines (71 loc) · 2.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
/**
* @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::Node rhs, DataFlow::MethodCallNode bind |
// this.<methodName> = <expr>.bind(...)
thiz.hasPropertyWrite(name, rhs) and
bind.flowsTo(rhs) 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::ArrayLiteralNode names |
names.flowsTo(bindAll.getArgument(1)) and
names.getAnElement().mayHaveStringValue(name)
)
)
)
)
or
exists (Expr decoration, string name |
decoration = method.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()