forked from uber/NullAway
/
AccessPathNullnessAnalysis.java
248 lines (228 loc) · 9.15 KB
/
AccessPathNullnessAnalysis.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
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
/*
* Copyright 2014 Google Inc. All Rights Reserved.
*
* Modifications copyright (C) 2017 Uber Technologies, Inc.
*
* 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.uber.nullaway.dataflow;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.VisitorState;
import com.sun.source.util.TreePath;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.util.Context;
import com.uber.nullaway.Config;
import com.uber.nullaway.Nullness;
import com.uber.nullaway.handlers.Handler;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import javax.annotation.Nullable;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
/**
* API to our nullness dataflow analysis for access paths.
*
* <p>Based on code from Error Prone; see {@link NullnessAnalysis}
*/
public final class AccessPathNullnessAnalysis {
private static final Context.Key<AccessPathNullnessAnalysis> FIELD_NULLNESS_ANALYSIS_KEY =
new Context.Key<>();
private final AccessPathNullnessPropagation nullnessPropagation;
private final DataFlow dataFlow;
private static String OPTIONAL_PATH = "java.util.Optional";
// Use #instance to instantiate
private AccessPathNullnessAnalysis(
Predicate<MethodInvocationNode> methodReturnsNonNull,
Context context,
Config config,
Handler handler) {
this.nullnessPropagation =
new AccessPathNullnessPropagation(
Nullness.NONNULL, methodReturnsNonNull, context, config, handler);
this.dataFlow = new DataFlow(config.assertsEnabled());
}
/**
* @param context Javac context
* @param methodReturnsNonNull predicate determining whether a method is assumed to return NonNull
* value
* @param config analysis config
* @return instance of the analysis
*/
public static AccessPathNullnessAnalysis instance(
Context context,
Predicate<MethodInvocationNode> methodReturnsNonNull,
Config config,
Handler handler) {
AccessPathNullnessAnalysis instance = context.get(FIELD_NULLNESS_ANALYSIS_KEY);
if (instance == null) {
instance = new AccessPathNullnessAnalysis(methodReturnsNonNull, context, config, handler);
context.put(FIELD_NULLNESS_ANALYSIS_KEY, instance);
}
return instance;
}
/**
* @param exprPath tree path of expression
* @param context Javac context
* @return nullness info for expression, from dataflow
*/
@Nullable
public Nullness getNullness(TreePath exprPath, Context context) {
return dataFlow.expressionDataflow(exprPath, context, nullnessPropagation);
}
/**
* @param path tree path of method, or initializer block
* @param context Javac context
* @return fields guaranteed to be nonnull at exit of method (or initializer block)
*/
public Set<Element> getNonnullFieldsOfReceiverAtExit(TreePath path, Context context) {
NullnessStore nullnessResult = dataFlow.finalResult(path, context, nullnessPropagation);
if (nullnessResult == null) {
// this case can occur if the method always throws an exception
// be conservative and say nothing is initialized
return Collections.emptySet();
}
return getNonnullReceiverFields(nullnessResult);
}
private Set<Element> getNonnullReceiverFields(NullnessStore nullnessResult) {
Set<AccessPath> nonnullAccessPaths = nullnessResult.getAccessPathsWithValue(Nullness.NONNULL);
Set<Element> result = new LinkedHashSet<>();
for (AccessPath ap : nonnullAccessPaths) {
if (ap.getRoot().isReceiver()) {
ImmutableList<Element> elements = ap.getElements();
if (elements.size() == 1) {
Element elem = elements.get(0);
if (elem.getKind().equals(ElementKind.FIELD)) {
result.add(elem);
}
}
}
}
return result;
}
/**
* @param path tree path of some expression
* @param context Javac context
* @return fields of receiver guaranteed to be nonnull before expression is evaluated
*/
public Set<Element> getNonnullFieldsOfReceiverBefore(TreePath path, Context context) {
NullnessStore store = dataFlow.resultBeforeExpr(path, context, nullnessPropagation);
if (store == null) {
return Collections.emptySet();
}
return getNonnullReceiverFields(store);
}
/**
* @param path tree path of some expression
* @param context Javac context
* @return static fields guaranteed to be nonnull before expression is evaluated
*/
public Set<Element> getNonnullStaticFieldsBefore(TreePath path, Context context) {
NullnessStore store = dataFlow.resultBeforeExpr(path, context, nullnessPropagation);
if (store == null) {
return Collections.emptySet();
}
return getNonnullStaticFields(store);
}
/**
* Get nullness info for local variables before some node
*
* @param path tree path to some AST node within a method / lambda / initializer
* @param state visitor state
* @return nullness info for local variables just before the node
*/
public NullnessStore getLocalVarInfoBefore(TreePath path, VisitorState state) {
NullnessStore store = dataFlow.resultBefore(path, state.context, nullnessPropagation);
if (store == null) {
return NullnessStore.empty();
}
return store.filterAccessPaths(
(ap) -> {
if (ap.getElements().size() == 0) {
AccessPath.Root root = ap.getRoot();
if (!root.isReceiver()) {
Element e = root.getVarElement();
return e.getKind().equals(ElementKind.PARAMETER)
|| e.getKind().equals(ElementKind.LOCAL_VARIABLE);
}
}
// a filter for Optional get() call
if (ap.getElements().size() == 1) {
AccessPath.Root root = ap.getRoot();
if (!root.isReceiver() && (ap.getElements().get(0) instanceof Symbol.MethodSymbol)) {
final Element e = root.getVarElement();
final Symbol.MethodSymbol g = (Symbol.MethodSymbol) ap.getElements().get(0);
final Optional<Type> tbaseType =
Optional.ofNullable(state.getTypeFromString(OPTIONAL_PATH))
.map(state.getTypes()::erasure);
return e.getKind().equals(ElementKind.LOCAL_VARIABLE)
&& g.getSimpleName().toString().equals("get")
&& g.getParameters().length() == 0
&& tbaseType.isPresent()
&& state.getTypes().isSubtype(g.owner.type, tbaseType.get());
}
}
return false;
});
}
/**
* @param path tree path of static method, or initializer block
* @param context Javac context
* @return fields guaranteed to be nonnull at exit of static method (or initializer block)
*/
public Set<Element> getNonnullStaticFieldsAtExit(TreePath path, Context context) {
NullnessStore nullnessResult = dataFlow.finalResult(path, context, nullnessPropagation);
if (nullnessResult == null) {
// this case can occur if the method always throws an exception
// be conservative and say nothing is initialized
return Collections.emptySet();
}
return getNonnullStaticFields(nullnessResult);
}
private Set<Element> getNonnullStaticFields(NullnessStore nullnessResult) {
Set<AccessPath> nonnullAccessPaths = nullnessResult.getAccessPathsWithValue(Nullness.NONNULL);
Set<Element> result = new LinkedHashSet<>();
for (AccessPath ap : nonnullAccessPaths) {
assert !ap.getRoot().isReceiver();
Element varElement = ap.getRoot().getVarElement();
if (varElement.getKind().equals(ElementKind.FIELD)) {
result.add(varElement);
}
}
return result;
}
/**
* Forces a run of the access path nullness analysis on the method (or lambda) at the given
* TreePath.
*
* <p>Because of caching, if the analysis has run in the past for this particular path, it might
* not be re-run. The intended usage of this method is to force an analysis pass and thus the
* execution of any Handler hooks into the dataflow analysis (such as `onDataflowInitialStore` or
* `onDataflowVisitX`).
*
* @param methodPath tree path of the method (or lambda) to analyze.
* @param context Javac context
* @return the final NullnessStore on exit from the method.
*/
public NullnessStore forceRunOnMethod(TreePath methodPath, Context context) {
return dataFlow.finalResult(methodPath, context, nullnessPropagation);
}
/** invalidate all caches */
public void invalidateCaches() {
dataFlow.invalidateCaches();
}
}