-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
CheckMissingReturn.java
214 lines (186 loc) · 6.98 KB
/
CheckMissingReturn.java
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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Predicate;
import com.google.javascript.jscomp.ControlFlowGraph.Branch;
import com.google.javascript.jscomp.NodeTraversal.ScopedCallback;
import com.google.javascript.jscomp.graph.DiGraph.DiGraphEdge;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.JSTypeNative;
import com.google.javascript.rhino.jstype.TernaryValue;
/**
* Checks functions for missing return statements. Return statements are only
* expected for functions with return type information. Functions with empty
* bodies are ignored.
*
*
* NOTE(dimvar):
* Do not convert this pass to use TypeI. The pass is only used with the old type checker.
* The new type inference checks missing returns on its own.
*/
class CheckMissingReturn implements ScopedCallback {
static final DiagnosticType MISSING_RETURN_STATEMENT =
DiagnosticType.warning(
"JSC_MISSING_RETURN_STATEMENT",
"Missing return statement. Function expected to return {0}.");
private final AbstractCompiler compiler;
private final CodingConvention convention;
private static final Predicate<Node> IS_RETURN = new Predicate<Node>() {
@Override
public boolean apply(Node input) {
// Check for null because the control flow graph's implicit return node is
// represented by null, so this value might be input.
return input != null && input.isReturn();
}
};
/* Skips all exception edges and impossible edges. */
private static final Predicate<DiGraphEdge<Node, ControlFlowGraph.Branch>>
GOES_THROUGH_TRUE_CONDITION_PREDICATE =
new Predicate<DiGraphEdge<Node, ControlFlowGraph.Branch>>() {
@Override
public boolean apply(DiGraphEdge<Node, ControlFlowGraph.Branch> input) {
// First skill all exceptions.
Branch branch = input.getValue();
if (branch == Branch.ON_EX) {
return false;
} else if (branch.isConditional()) {
Node condition = NodeUtil.getConditionExpression(
input.getSource().getValue());
// TODO(user): We CAN make this bit smarter just looking at
// constants. We DO have a full blown ReverseAbstractInterupter and
// type system that can evaluate some impressions' boolean value but
// for now we will keep this pass lightweight.
if (condition != null) {
TernaryValue val = NodeUtil.getImpureBooleanValue(condition);
if (val != TernaryValue.UNKNOWN) {
return val.toBoolean(true) == (Branch.ON_TRUE == branch);
}
}
}
return true;
}
};
CheckMissingReturn(AbstractCompiler compiler) {
this.compiler = compiler;
this.convention = compiler.getCodingConvention();
}
@Override
public void enterScope(NodeTraversal t) {
Node n = t.getScopeRoot();
JSType returnType = explicitReturnExpected(n);
if (returnType == null) {
// No return value is expected, so nothing to check.
return;
}
if (n.isArrowFunction()) {
Node functionBody = NodeUtil.getFunctionBody(n);
if (!functionBody.isNormalBlock()) {
// Body is an expression, which is the implicit return value.
return;
}
}
if (fastAllPathsReturnCheck(t.getControlFlowGraph())) {
return;
}
CheckPathsBetweenNodes<Node, ControlFlowGraph.Branch> test =
new CheckPathsBetweenNodes<>(
t.getControlFlowGraph(),
t.getControlFlowGraph().getEntry(),
t.getControlFlowGraph().getImplicitReturn(),
IS_RETURN, GOES_THROUGH_TRUE_CONDITION_PREDICATE);
if (!test.allPathsSatisfyPredicate()) {
compiler.report(
t.makeError(t.getScopeRoot(), MISSING_RETURN_STATEMENT, returnType.toString()));
}
}
/**
* Fast check to see if all execution paths contain a return statement.
* May spuriously report that a return statement is missing.
*
* @return true if all paths return, converse not necessarily true
*/
private boolean fastAllPathsReturnCheck(ControlFlowGraph<Node> cfg) {
for (DiGraphEdge<Node, Branch> s : cfg.getImplicitReturn().getInEdges()) {
Node n = s.getSource().getValue();
// NOTE(dimvar): it is possible to change ControlFlowAnalysis.java, so
// that the calls that always throw are treated in the same way as THROW
// in the CFG. Then, we would not need to use the coding convention here.
if (!n.isReturn() && !convention.isFunctionCallThatAlwaysThrows(n)) {
return false;
}
}
return true;
}
@Override
public void exitScope(NodeTraversal t) {
}
@Override
public boolean shouldTraverse(
NodeTraversal nodeTraversal, Node n, Node parent) {
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
}
/**
* Determines if the given scope should explicitly return. All functions
* with non-void or non-unknown return types must have explicit returns.
*
* Exception: Constructors which specifically specify a return type are
* used to allow invocation without requiring the "new" keyword. They
* have an implicit return type. See unit tests.
*
* @return If a return type is expected, returns it. Otherwise, returns null.
*/
private JSType explicitReturnExpected(Node scopeRoot) {
FunctionType scopeType = JSType.toMaybeFunctionType(scopeRoot.getJSType());
if (scopeType == null) {
return null;
}
if (isEmptyFunction(scopeRoot)) {
return null;
}
if (scopeType.isConstructor()) {
return null;
}
JSType returnType = scopeType.getReturnType();
if (returnType == null) {
return null;
}
if (!isVoidOrUnknown(returnType)) {
return returnType;
}
return null;
}
/**
* @return {@code true} if function represents a JavaScript function
* with an empty body
*/
private static boolean isEmptyFunction(Node function) {
return function.getChildCount() == 3
&& !function.getSecondChild().getNext().hasChildren();
}
/**
* @return {@code true} if returnType is void, unknown, or a union
* containing void or unknown
*/
private boolean isVoidOrUnknown(JSType returnType) {
final JSType voidType = compiler.getTypeRegistry().getNativeType(JSTypeNative.VOID_TYPE);
return voidType.isSubtype(returnType);
}
}