-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Expand file tree
/
Copy pathUseOfReturnlessFunction.ql
More file actions
174 lines (160 loc) · 5.54 KB
/
UseOfReturnlessFunction.ql
File metadata and controls
174 lines (160 loc) · 5.54 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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
/**
* @name Use of returnless function
* @description Using the return value of a function that does not return an expression is indicative of a mistake.
* @kind problem
* @problem.severity warning
* @id js/use-of-returnless-function
* @tags quality
* reliability
* correctness
* @precision high
*/
import javascript
import Declarations.UnusedVariable
import Expressions.ExprHasNoEffect
import Statements.UselessConditional
predicate returnsVoid(Function f) {
not f.isGenerator() and
not f.isAsync() and
not exists(f.getAReturnedExpr())
}
predicate isStub(Function f) {
f.getBody().(BlockStmt).getNumChild() = 0
or
f instanceof ExternalDecl
}
/**
* Holds if `e` is in a syntactic context where it likely is fine that the value of `e` comes from a call to a returnless function.
*/
predicate benignContext(Expr e) {
inVoidContext(e)
or
// A return statement is often used to just end the function.
e = any(Function f).getBody()
or
e = any(ReturnStmt r).getExpr()
or
exists(ConditionalExpr cond | cond.getABranch() = e and benignContext(cond))
or
exists(LogicalBinaryExpr bin | bin.getAnOperand() = e and benignContext(bin))
or
exists(Expr parent | parent.getUnderlyingValue() = e and benignContext(parent))
or
any(VoidExpr voidExpr).getOperand() = e
or
// weeds out calls inside HTML-attributes.
e.getParent().(ExprStmt).getParent() instanceof CodeInAttribute
or
// and JSX-attributes.
e = any(JsxAttribute attr).getValue()
or
exists(AwaitExpr await | await.getOperand() = e and benignContext(await))
or
// Avoid double reporting with js/trivial-conditional
isExplicitConditional(_, e)
or
// Avoid double reporting with js/comparison-between-incompatible-types
any(Comparison binOp).getAnOperand() = e
or
// Avoid double reporting with js/property-access-on-non-object
any(PropAccess ac).getBase() = e
or
// Avoid double-reporting with js/unused-local-variable
exists(VariableDeclarator v |
v.getInit() = e and v.getBindingPattern().getVariable() instanceof UnusedLocal
)
or
// Avoid double reporting with js/call-to-non-callable
any(InvokeExpr invoke).getCallee() = e
or
// arguments to Promise.resolve (and promise library variants) are benign.
e = any(PromiseCreationCall promise).getValue().asExpr()
or
// arguments to other (unknown) promise creations.
e = any(DataFlow::CallNode call | call.getCalleeName() = "resolve").getAnArgument().asExpr()
}
predicate oneshotClosure(DataFlow::CallNode call) {
call.getCalleeNode().asExpr().getUnderlyingValue() instanceof ImmediatelyInvokedFunctionExpr
}
predicate alwaysThrows(Function f) {
exists(ReachableBasicBlock entry, DataFlow::Node throwNode |
entry = f.getEntryBB() and
throwNode.asExpr() = any(ThrowStmt t).getExpr() and
entry.dominates(throwNode.getBasicBlock())
)
}
/**
* Holds if the last statement in the function is flagged by the js/useless-expression query.
*/
predicate lastStatementHasNoEffect(Function f) { hasNoEffect(f.getExit().getAPredecessor()) }
/**
* Holds if `func` is a callee of `call`, and all possible callees of `call` never return a value.
*/
predicate callToVoidFunction(DataFlow::CallNode call, Function func) {
not call.isIncomplete() and
func = call.getACallee() and
forall(Function f | f = call.getACallee() |
returnsVoid(f) and not isStub(f) and not alwaysThrows(f)
)
}
/**
* Holds if `name` is the name of a method from `Array.prototype` or Lodash,
* where that method takes a callback as parameter,
* and the callback is expected to return a value.
*/
predicate hasNonVoidCallbackMethod(string name) {
name =
[
"every", "filter", "find", "findIndex", "flatMap", "map", "reduce", "reduceRight", "some",
"sort", "findLastIndex", "findLast"
]
}
DataFlow::SourceNode array(DataFlow::TypeTracker t) {
t.start() and result instanceof DataFlow::ArrayCreationNode
or
exists(DataFlow::TypeTracker t2 | result = array(t2).track(t2, t))
}
DataFlow::SourceNode array() { result = array(DataFlow::TypeTracker::end()) }
/**
* Holds if `call` is an Array or Lodash method accepting a callback `func`,
* where the `call` expects a callback that returns an expression,
* but `func` does not return a value.
*/
predicate voidArrayCallback(DataFlow::CallNode call, Function func) {
hasNonVoidCallbackMethod(call.getCalleeName()) and
exists(int index |
index = min(int i | exists(call.getCallback(i))) and
func = call.getCallback(index).getFunction()
) and
returnsVoid(func) and
not isStub(func) and
not alwaysThrows(func) and
(
call.getReceiver().getALocalSource() = array()
or
call.getCalleeNode().getALocalSource() instanceof LodashUnderscore::Member
)
}
predicate hasNonVoidReturnType(Function f) {
exists(TypeAnnotation type | type = f.getReturnTypeAnnotation() | not type.isVoid())
}
from DataFlow::CallNode call, Function func, string name, string msg
where
(
callToVoidFunction(call, func) and
msg = "the $@ does not return anything, yet the return value is used." and
name = func.describe()
or
voidArrayCallback(call, func) and
msg =
"the $@ does not return anything, yet the return value from the call to " +
call.getCalleeName() + " is used." and
name = "callback function"
) and
not benignContext(call.getEnclosingExpr()) and
not lastStatementHasNoEffect(func) and
// anonymous one-shot closure. Those are used in weird ways and we ignore them.
not oneshotClosure(call) and
not hasNonVoidReturnType(func) and
not call.getEnclosingExpr() instanceof SuperCall
select call, msg, func, name