• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2014 Google Inc. All Rights Reserved.
3  *
4  * Modifications copyright (C) 2017 Uber Technologies, Inc.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 
19 package com.uber.nullaway.dataflow;
20 
21 import com.google.common.base.Preconditions;
22 import com.google.common.collect.ImmutableList;
23 import com.google.errorprone.VisitorState;
24 import com.sun.source.tree.Tree;
25 import com.sun.source.util.TreePath;
26 import com.sun.tools.javac.util.Context;
27 import com.uber.nullaway.Config;
28 import com.uber.nullaway.Nullness;
29 import com.uber.nullaway.handlers.Handler;
30 import com.uber.nullaway.handlers.contract.ContractNullnessStoreInitializer;
31 import java.util.Collections;
32 import java.util.LinkedHashSet;
33 import java.util.Set;
34 import java.util.function.Predicate;
35 import javax.annotation.Nullable;
36 import javax.lang.model.element.Element;
37 import javax.lang.model.element.ElementKind;
38 import javax.lang.model.element.VariableElement;
39 import org.checkerframework.nullaway.dataflow.analysis.AnalysisResult;
40 import org.checkerframework.nullaway.dataflow.cfg.node.MethodAccessNode;
41 import org.checkerframework.nullaway.dataflow.cfg.node.MethodInvocationNode;
42 import org.checkerframework.nullaway.dataflow.cfg.node.Node;
43 
44 /**
45  * API to our nullness dataflow analysis for access paths.
46  *
47  * <p>Based on code from Error Prone; see {@link NullnessAnalysis}
48  */
49 public final class AccessPathNullnessAnalysis {
50 
51   private static final Context.Key<AccessPathNullnessAnalysis> FIELD_NULLNESS_ANALYSIS_KEY =
52       new Context.Key<>();
53 
54   private final AccessPath.AccessPathContext apContext;
55 
56   private final AccessPathNullnessPropagation nullnessPropagation;
57 
58   private final DataFlow dataFlow;
59 
60   @Nullable private AccessPathNullnessPropagation contractNullnessPropagation;
61 
62   // Use #instance to instantiate
AccessPathNullnessAnalysis( Predicate<MethodInvocationNode> methodReturnsNonNull, VisitorState state, Config config, Handler handler)63   private AccessPathNullnessAnalysis(
64       Predicate<MethodInvocationNode> methodReturnsNonNull,
65       VisitorState state,
66       Config config,
67       Handler handler) {
68     apContext =
69         AccessPath.AccessPathContext.builder()
70             .setImmutableTypes(handler.onRegisterImmutableTypes())
71             .build();
72     this.nullnessPropagation =
73         new AccessPathNullnessPropagation(
74             Nullness.NONNULL,
75             methodReturnsNonNull,
76             state,
77             apContext,
78             config,
79             handler,
80             new CoreNullnessStoreInitializer());
81     this.dataFlow = new DataFlow(config.assertsEnabled());
82 
83     if (config.checkContracts()) {
84       this.contractNullnessPropagation =
85           new AccessPathNullnessPropagation(
86               Nullness.NONNULL,
87               methodReturnsNonNull,
88               state,
89               apContext,
90               config,
91               handler,
92               new ContractNullnessStoreInitializer());
93     }
94   }
95 
96   /**
97    * Get the per-Javac instance of the analysis.
98    *
99    * @param state visitor state for the compilation
100    * @param methodReturnsNonNull predicate determining whether a method is assumed to return NonNull
101    *     value
102    * @param config analysis config
103    * @return instance of the analysis
104    */
instance( VisitorState state, Predicate<MethodInvocationNode> methodReturnsNonNull, Config config, Handler handler)105   public static AccessPathNullnessAnalysis instance(
106       VisitorState state,
107       Predicate<MethodInvocationNode> methodReturnsNonNull,
108       Config config,
109       Handler handler) {
110     Context context = state.context;
111     AccessPathNullnessAnalysis instance = context.get(FIELD_NULLNESS_ANALYSIS_KEY);
112     if (instance == null) {
113       instance = new AccessPathNullnessAnalysis(methodReturnsNonNull, state, config, handler);
114       context.put(FIELD_NULLNESS_ANALYSIS_KEY, instance);
115     }
116     return instance;
117   }
118 
119   /**
120    * Get an expression's nullness info.
121    *
122    * @param exprPath tree path of expression
123    * @param context Javac context
124    * @return nullness info for expression, from dataflow
125    */
126   @Nullable
getNullness(TreePath exprPath, Context context)127   public Nullness getNullness(TreePath exprPath, Context context) {
128     return dataFlow.expressionDataflow(exprPath, context, nullnessPropagation);
129   }
130 
131   /**
132    * Gets nullness info for expression from the dataflow analysis, for the case of checking
133    * contracts
134    *
135    * @param exprPath tree path of expression
136    * @param context Javac context
137    * @return nullness info for expression, from dataflow in case contract check
138    */
139   @Nullable
getNullnessForContractDataflow(TreePath exprPath, Context context)140   public Nullness getNullnessForContractDataflow(TreePath exprPath, Context context) {
141     return dataFlow.expressionDataflow(exprPath, context, contractNullnessPropagation);
142   }
143 
144   /**
145    * Get the fields that are guaranteed to be nonnull after a method or initializer block.
146    *
147    * @param path tree path of method, or initializer block
148    * @param context Javac context
149    * @return fields guaranteed to be nonnull at exit of method (or initializer block)
150    */
getNonnullFieldsOfReceiverAtExit(TreePath path, Context context)151   public Set<Element> getNonnullFieldsOfReceiverAtExit(TreePath path, Context context) {
152     NullnessStore nullnessResult = dataFlow.finalResult(path, context, nullnessPropagation);
153     if (nullnessResult == null) {
154       // this case can occur if the method always throws an exception
155       // be conservative and say nothing is initialized
156       return Collections.emptySet();
157     }
158     return getNonnullReceiverFields(nullnessResult);
159   }
160 
getNonnullReceiverFields(NullnessStore nullnessResult)161   private Set<Element> getNonnullReceiverFields(NullnessStore nullnessResult) {
162     Set<AccessPath> nonnullAccessPaths = nullnessResult.getAccessPathsWithValue(Nullness.NONNULL);
163     Set<Element> result = new LinkedHashSet<>();
164     for (AccessPath ap : nonnullAccessPaths) {
165       if (ap.getRoot().isReceiver()) {
166         ImmutableList<AccessPathElement> elements = ap.getElements();
167         if (elements.size() == 1) {
168           Element elem = elements.get(0).getJavaElement();
169           if (elem.getKind().equals(ElementKind.FIELD)) {
170             result.add(elem);
171           }
172         }
173       }
174     }
175     return result;
176   }
177 
178   /**
179    * Get the instance fields that are guaranteed to be nonnull before the current expression.
180    *
181    * @param path tree path of some expression
182    * @param context Javac context
183    * @return fields of receiver guaranteed to be nonnull before expression is evaluated
184    */
getNonnullFieldsOfReceiverBefore(TreePath path, Context context)185   public Set<Element> getNonnullFieldsOfReceiverBefore(TreePath path, Context context) {
186     NullnessStore store = dataFlow.resultBeforeExpr(path, context, nullnessPropagation);
187     if (store == null) {
188       return Collections.emptySet();
189     }
190     return getNonnullReceiverFields(store);
191   }
192 
193   /**
194    * Get the static fields that are guaranteed to be nonnull before the current expression.
195    *
196    * @param path tree path of some expression
197    * @param context Javac context
198    * @return static fields guaranteed to be nonnull before expression is evaluated
199    */
getNonnullStaticFieldsBefore(TreePath path, Context context)200   public Set<Element> getNonnullStaticFieldsBefore(TreePath path, Context context) {
201     NullnessStore store = dataFlow.resultBeforeExpr(path, context, nullnessPropagation);
202     if (store == null) {
203       return Collections.emptySet();
204     }
205     return getNonnullStaticFields(store);
206   }
207 
208   /**
209    * Get nullness info for local variables before some node
210    *
211    * @param path tree path to some AST node within a method / lambda / initializer
212    * @param state visitor state
213    * @return nullness info for local variables just before the node
214    */
getNullnessInfoBeforeNewContext( TreePath path, VisitorState state, Handler handler)215   public NullnessStore getNullnessInfoBeforeNewContext(
216       TreePath path, VisitorState state, Handler handler) {
217     NullnessStore store = dataFlow.resultBefore(path, state.context, nullnessPropagation);
218     if (store == null) {
219       return NullnessStore.empty();
220     }
221     return store.filterAccessPaths(
222         (ap) -> {
223           if (ap.getElements().size() == 0) {
224             AccessPath.Root root = ap.getRoot();
225             if (!root.isReceiver()) {
226               Element e = root.getVarElement();
227               return e.getKind().equals(ElementKind.PARAMETER)
228                   || e.getKind().equals(ElementKind.LOCAL_VARIABLE);
229             }
230           }
231 
232           return handler.includeApInfoInSavedContext(ap, state);
233         });
234   }
235 
236   /**
237    * Get the {@link Nullness} value of an access path ending in a field at some program point.
238    *
239    * @param path Tree path to the specific program point.
240    * @param context Javac context.
241    * @param baseExpr The base expression {@code expr} for the access path {@code expr . f}
242    * @param field The field {@code f} for the access path {@code expr . f}
243    * @param trimReceiver if {@code true}, {@code baseExpr} will be trimmed to extract only the
244    *     receiver if the node associated to {@code baseExpr} is of type {@link MethodAccessNode}.
245    *     (e.g. {@code t.f()} will be converted to {@code t})
246    * @return The {@link Nullness} value of the access path at the program point. If the baseExpr and
247    *     field cannot be represented as an {@link AccessPath}, or if the dataflow analysis has no
248    *     result for the program point before {@code path}, conservatively returns {@link
249    *     Nullness#NULLABLE}
250    */
251   public Nullness getNullnessOfFieldForReceiverTree(
252       TreePath path, Context context, Tree baseExpr, VariableElement field, boolean trimReceiver) {
253     Preconditions.checkArgument(field.getKind().equals(ElementKind.FIELD));
254     AnalysisResult<Nullness, NullnessStore> result =
255         dataFlow.resultForExpr(path, context, nullnessPropagation);
256     if (result == null) {
257       return Nullness.NULLABLE;
258     }
259     NullnessStore store = result.getStoreBefore(path.getLeaf());
260     // used set of nodes, a tree can have multiple nodes.
261     Set<Node> baseNodes = result.getNodesForTree(baseExpr);
262     if (store == null || baseNodes == null) {
263       return Nullness.NULLABLE;
264     }
265     // look for all possible access paths might exist in store.
266     for (Node baseNode : baseNodes) {
267       // it trims the baseExpr to process only the receiver. (e.g. a.f() is trimmed to a)
268       if (trimReceiver && baseNode instanceof MethodAccessNode) {
269         baseNode = ((MethodAccessNode) baseNode).getReceiver();
270       }
271       AccessPath accessPath = AccessPath.fromBaseAndElement(baseNode, field, apContext);
272       if (accessPath == null) {
273         continue;
274       }
275       Nullness nullness = store.getNullnessOfAccessPath(accessPath);
276       // field is non-null if at least one access path referring to it exists with non-null
277       // nullness.
278       if (!nullness.equals(Nullness.NULLABLE)) {
279         return nullness;
280       }
281     }
282     return Nullness.NULLABLE;
283   }
284 
285   /**
286    * Get the static fields that are guaranteed to be nonnull after a method or initializer block.
287    *
288    * @param path tree path of static method, or initializer block
289    * @param context Javac context
290    * @return fields guaranteed to be nonnull at exit of static method (or initializer block)
291    */
292   public Set<Element> getNonnullStaticFieldsAtExit(TreePath path, Context context) {
293     NullnessStore nullnessResult = dataFlow.finalResult(path, context, nullnessPropagation);
294     if (nullnessResult == null) {
295       // this case can occur if the method always throws an exception
296       // be conservative and say nothing is initialized
297       return Collections.emptySet();
298     }
299     return getNonnullStaticFields(nullnessResult);
300   }
301 
302   private Set<Element> getNonnullStaticFields(NullnessStore nullnessResult) {
303     Set<AccessPath> nonnullAccessPaths = nullnessResult.getAccessPathsWithValue(Nullness.NONNULL);
304     Set<Element> result = new LinkedHashSet<>();
305     for (AccessPath ap : nonnullAccessPaths) {
306       assert !ap.getRoot().isReceiver();
307       Element varElement = ap.getRoot().getVarElement();
308       if (varElement.getKind().equals(ElementKind.FIELD)) {
309         result.add(varElement);
310       }
311     }
312     return result;
313   }
314 
315   /**
316    * Forces a run of the access path nullness analysis on the method (or lambda) at the given
317    * TreePath.
318    *
319    * <p>Because of caching, if the analysis has run in the past for this particular path, it might
320    * not be re-run. The intended usage of this method is to force an analysis pass and thus the
321    * execution of any Handler hooks into the dataflow analysis (such as `onDataflowInitialStore` or
322    * `onDataflowVisitX`).
323    *
324    * @param methodPath tree path of the method (or lambda) to analyze.
325    * @param context Javac context
326    * @return the final NullnessStore on exit from the method.
327    */
328   public NullnessStore forceRunOnMethod(TreePath methodPath, Context context) {
329     return dataFlow.finalResult(methodPath, context, nullnessPropagation);
330   }
331 
332   /**
333    * Nullness of the variable element field of the expression is checked in the store.
334    *
335    * @param exprPath tree path of the expression
336    * @param context Javac context
337    * @param variableElement variable element for which the nullness is evaluated
338    * @return nullness info of variable element field of the expression
339    */
340   public Nullness getNullnessOfExpressionNamedField(
341       TreePath exprPath, Context context, VariableElement variableElement) {
342     NullnessStore store = dataFlow.resultBeforeExpr(exprPath, context, nullnessPropagation);
343 
344     // We use the CFG to get the Node corresponding to the expression
345     Set<Node> exprNodes =
346         dataFlow
347             .getControlFlowGraph(exprPath, context, nullnessPropagation)
348             .getNodesCorrespondingToTree(exprPath.getLeaf());
349 
350     if (exprNodes.size() != 1) {
351       // Since the expression must have a single corresponding node
352       // NULLABLE is our default assumption
353       return Nullness.NULLABLE;
354     }
355 
356     AccessPath ap =
357         AccessPath.fromBaseAndElement(exprNodes.iterator().next(), variableElement, apContext);
358 
359     if (store != null && ap != null) {
360       if (store
361           .getAccessPathsWithValue(Nullness.NONNULL)
362           .stream()
363           .anyMatch(accessPath -> accessPath.equals(ap))) {
364         return Nullness.NONNULL;
365       }
366     }
367     return Nullness.NULLABLE;
368   }
369 
370   /** invalidate all caches */
371   public void invalidateCaches() {
372     dataFlow.invalidateCaches();
373   }
374 }
375