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