• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2017 Uber Technologies, Inc.
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to deal
6  * in the Software without restriction, including without limitation the rights
7  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8  * copies of the Software, and to permit persons to whom the Software is
9  * furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in
12  * all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20  * THE SOFTWARE.
21  */
22 
23 package com.uber.nullaway;
24 
25 import com.google.common.base.Preconditions;
26 import com.google.common.collect.ImmutableSet;
27 import com.google.errorprone.VisitorState;
28 import com.google.errorprone.suppliers.Supplier;
29 import com.google.errorprone.suppliers.Suppliers;
30 import com.google.errorprone.util.ASTHelpers;
31 import com.sun.source.tree.BlockTree;
32 import com.sun.source.tree.ClassTree;
33 import com.sun.source.tree.ExpressionTree;
34 import com.sun.source.tree.LambdaExpressionTree;
35 import com.sun.source.tree.MemberReferenceTree;
36 import com.sun.source.tree.MethodTree;
37 import com.sun.source.tree.Tree;
38 import com.sun.source.tree.VariableTree;
39 import com.sun.source.util.TreePath;
40 import com.sun.tools.javac.code.Attribute;
41 import com.sun.tools.javac.code.Symbol;
42 import com.sun.tools.javac.code.TargetType;
43 import com.sun.tools.javac.code.Type;
44 import com.sun.tools.javac.code.Types;
45 import com.sun.tools.javac.tree.JCTree;
46 import com.sun.tools.javac.util.JCDiagnostic;
47 import java.util.List;
48 import java.util.Map;
49 import java.util.Set;
50 import java.util.stream.Collectors;
51 import java.util.stream.Stream;
52 import javax.annotation.Nullable;
53 import javax.lang.model.element.AnnotationMirror;
54 import javax.lang.model.element.AnnotationValue;
55 import javax.lang.model.element.ExecutableElement;
56 import org.checkerframework.nullaway.javacutil.AnnotationUtils;
57 
58 /** Helpful utility methods for nullability analysis. */
59 public class NullabilityUtil {
60 
61   private static final Supplier<Type> MAP_TYPE_SUPPLIER = Suppliers.typeFromString("java.util.Map");
62 
NullabilityUtil()63   private NullabilityUtil() {}
64 
65   /**
66    * finds the corresponding functional interface method for a lambda expression or method reference
67    *
68    * @param tree the lambda expression or method reference
69    * @return the functional interface method
70    */
getFunctionalInterfaceMethod(ExpressionTree tree, Types types)71   public static Symbol.MethodSymbol getFunctionalInterfaceMethod(ExpressionTree tree, Types types) {
72     Preconditions.checkArgument(
73         (tree instanceof LambdaExpressionTree) || (tree instanceof MemberReferenceTree));
74     Type funcInterfaceType = ((JCTree.JCFunctionalExpression) tree).type;
75     return (Symbol.MethodSymbol) types.findDescriptorSymbol(funcInterfaceType.tsym);
76   }
77 
78   /**
79    * determines whether a lambda parameter is missing an explicit type declaration
80    *
81    * @param lambdaParameter the parameter
82    * @return true if there is no type declaration, false otherwise
83    */
lambdaParamIsImplicitlyTyped(VariableTree lambdaParameter)84   public static boolean lambdaParamIsImplicitlyTyped(VariableTree lambdaParameter) {
85     // kind of a hack; the "preferred position" seems to be the position
86     // of the variable name.  if this differs from the start position, it
87     // means there is an explicit type declaration
88     JCDiagnostic.DiagnosticPosition diagnosticPosition =
89         (JCDiagnostic.DiagnosticPosition) lambdaParameter;
90     return diagnosticPosition.getStartPosition() == diagnosticPosition.getPreferredPosition();
91   }
92 
93   /**
94    * find the closest ancestor method in a superclass or superinterface that method overrides
95    *
96    * @param method the subclass method
97    * @param types the types data structure from javac
98    * @return closest overridden ancestor method, or <code>null</code> if method does not override
99    *     anything
100    */
101   @Nullable
getClosestOverriddenMethod( Symbol.MethodSymbol method, Types types)102   public static Symbol.MethodSymbol getClosestOverriddenMethod(
103       Symbol.MethodSymbol method, Types types) {
104     // taken from Error Prone MethodOverrides check
105     Symbol.ClassSymbol owner = method.enclClass();
106     for (Type s : types.closure(owner.type)) {
107       if (types.isSameType(s, owner.type)) {
108         continue;
109       }
110       for (Symbol m : s.tsym.members().getSymbolsByName(method.name)) {
111         if (!(m instanceof Symbol.MethodSymbol)) {
112           continue;
113         }
114         Symbol.MethodSymbol msym = (Symbol.MethodSymbol) m;
115         if (msym.isStatic()) {
116           continue;
117         }
118         if (method.overrides(msym, owner, types, /*checkReturn*/ false)) {
119           return msym;
120         }
121       }
122     }
123     return null;
124   }
125   /**
126    * finds the symbol for the top-level class containing the given symbol
127    *
128    * @param symbol the given symbol
129    * @return symbol for the non-nested enclosing class
130    */
getOutermostClassSymbol(Symbol symbol)131   public static Symbol.ClassSymbol getOutermostClassSymbol(Symbol symbol) {
132     // get the symbol for the outermost enclosing class.  this handles
133     // the case of anonymous classes
134     Symbol.ClassSymbol outermostClassSymbol = ASTHelpers.enclosingClass(symbol);
135     while (outermostClassSymbol.getNestingKind().isNested()) {
136       Symbol.ClassSymbol enclosingSymbol = ASTHelpers.enclosingClass(outermostClassSymbol.owner);
137       if (enclosingSymbol != null) {
138         outermostClassSymbol = enclosingSymbol;
139       } else {
140         // enclosingSymbol can be null in weird cases like for array methods
141         break;
142       }
143     }
144     return outermostClassSymbol;
145   }
146 
147   /**
148    * find the enclosing method, lambda expression or initializer block for the leaf of some tree
149    * path
150    *
151    * @param path the tree path
152    * @param others also stop and return in case of any of these tree kinds
153    * @return the closest enclosing method / lambda
154    */
155   @Nullable
findEnclosingMethodOrLambdaOrInitializer( TreePath path, ImmutableSet<Tree.Kind> others)156   public static TreePath findEnclosingMethodOrLambdaOrInitializer(
157       TreePath path, ImmutableSet<Tree.Kind> others) {
158     TreePath curPath = path.getParentPath();
159     while (curPath != null) {
160       if (curPath.getLeaf() instanceof MethodTree
161           || curPath.getLeaf() instanceof LambdaExpressionTree
162           || others.contains(curPath.getLeaf().getKind())) {
163         return curPath;
164       }
165       TreePath parent = curPath.getParentPath();
166       if (parent != null && parent.getLeaf() instanceof ClassTree) {
167         if (curPath.getLeaf() instanceof BlockTree) {
168           // found initializer block
169           return curPath;
170         }
171         if (curPath.getLeaf() instanceof VariableTree
172             && ((VariableTree) curPath.getLeaf()).getInitializer() != null) {
173           // found field with an inline initializer
174           return curPath;
175         }
176       }
177       curPath = parent;
178     }
179     return null;
180   }
181 
182   /**
183    * find the enclosing method, lambda expression or initializer block for the leaf of some tree
184    * path
185    *
186    * @param path the tree path
187    * @return the closest enclosing method / lambda
188    */
189   @Nullable
findEnclosingMethodOrLambdaOrInitializer(TreePath path)190   public static TreePath findEnclosingMethodOrLambdaOrInitializer(TreePath path) {
191     return findEnclosingMethodOrLambdaOrInitializer(path, ImmutableSet.of());
192   }
193 
194   /**
195    * NOTE: this method does not work for getting all annotations of parameters of methods from class
196    * files. For that case, use {@link #getAllAnnotationsForParameter(Symbol.MethodSymbol, int)}
197    *
198    * @param symbol the symbol
199    * @return all annotations on the symbol and on the type of the symbol
200    */
getAllAnnotations(Symbol symbol)201   public static Stream<? extends AnnotationMirror> getAllAnnotations(Symbol symbol) {
202     // for methods, we care about annotations on the return type, not on the method type itself
203     Stream<? extends AnnotationMirror> typeUseAnnotations = getTypeUseAnnotations(symbol);
204     return Stream.concat(symbol.getAnnotationMirrors().stream(), typeUseAnnotations);
205   }
206 
207   /**
208    * Retrieve the {@code value} attribute of a method annotation of some type.
209    *
210    * @param methodSymbol A method to check for the annotation.
211    * @param annotName The qualified name of the annotation.
212    * @return The {@code value} attribute of the annotation, or {@code null} if the annotation is not
213    *     present.
214    */
getAnnotationValue( Symbol.MethodSymbol methodSymbol, String annotName)215   public static @Nullable String getAnnotationValue(
216       Symbol.MethodSymbol methodSymbol, String annotName) {
217     AnnotationMirror annot =
218         AnnotationUtils.getAnnotationByName(methodSymbol.getAnnotationMirrors(), annotName);
219     if (annot == null) {
220       return null;
221     }
222 
223     Map<? extends ExecutableElement, ? extends AnnotationValue> elementValues =
224         annot.getElementValues();
225     for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry :
226         elementValues.entrySet()) {
227       ExecutableElement elem = entry.getKey();
228       if (elem.getSimpleName().contentEquals("value")) {
229         return (String) entry.getValue().getValue();
230       }
231     }
232     // not found
233     return null;
234   }
235 
236   /**
237    * Retrieve the {@code value} attribute of a method annotation of some type where the {@code
238    * value} is an array.
239    *
240    * @param methodSymbol A method to check for the annotation.
241    * @param annotName The qualified name or simple name of the annotation depending on the value of
242    *     {@code exactMatch}.
243    * @param exactMatch If true, the annotation name must match the full qualified name given in
244    *     {@code annotName}, otherwise, simple names will be checked.
245    * @return The {@code value} attribute of the annotation as a {@code Set}, or {@code null} if the
246    *     annotation is not present.
247    */
getAnnotationValueArray( Symbol.MethodSymbol methodSymbol, String annotName, boolean exactMatch)248   public static @Nullable Set<String> getAnnotationValueArray(
249       Symbol.MethodSymbol methodSymbol, String annotName, boolean exactMatch) {
250     AnnotationMirror annot = null;
251     for (AnnotationMirror annotationMirror : methodSymbol.getAnnotationMirrors()) {
252       String name = AnnotationUtils.annotationName(annotationMirror);
253       if ((exactMatch && name.equals(annotName)) || (!exactMatch && name.endsWith(annotName))) {
254         annot = annotationMirror;
255         break;
256       }
257     }
258     if (annot == null) {
259       return null;
260     }
261     Map<? extends ExecutableElement, ? extends AnnotationValue> elementValues =
262         annot.getElementValues();
263     for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry :
264         elementValues.entrySet()) {
265       ExecutableElement elem = entry.getKey();
266       if (elem.getSimpleName().contentEquals("value")) {
267         @SuppressWarnings("unchecked")
268         List<AnnotationValue> values = (List<AnnotationValue>) entry.getValue().getValue();
269         return values.stream().map((av) -> ((String) av.getValue())).collect(Collectors.toSet());
270       }
271     }
272     return null;
273   }
274 
275   /**
276    * Works for method parameters defined either in source or in class files
277    *
278    * @param symbol the method symbol
279    * @param paramInd index of the parameter
280    * @return all declaration and type-use annotations for the parameter
281    */
getAllAnnotationsForParameter( Symbol.MethodSymbol symbol, int paramInd)282   public static Stream<? extends AnnotationMirror> getAllAnnotationsForParameter(
283       Symbol.MethodSymbol symbol, int paramInd) {
284     Symbol.VarSymbol varSymbol = symbol.getParameters().get(paramInd);
285     return Stream.concat(
286         varSymbol.getAnnotationMirrors().stream(),
287         symbol
288             .getRawTypeAttributes()
289             .stream()
290             .filter(
291                 t ->
292                     t.position.type.equals(TargetType.METHOD_FORMAL_PARAMETER)
293                         && t.position.parameter_index == paramInd));
294   }
295 
getTypeUseAnnotations(Symbol symbol)296   private static Stream<? extends AnnotationMirror> getTypeUseAnnotations(Symbol symbol) {
297     Stream<Attribute.TypeCompound> rawTypeAttributes = symbol.getRawTypeAttributes().stream();
298     if (symbol instanceof Symbol.MethodSymbol) {
299       // for methods, we want the type-use annotations on the return type
300       return rawTypeAttributes.filter((t) -> t.position.type.equals(TargetType.METHOD_RETURN));
301     }
302     return rawTypeAttributes;
303   }
304 
305   /**
306    * Check if a field might be null, based on the type.
307    *
308    * @param symbol symbol for field; must be non-null
309    * @param config NullAway config
310    * @return true if based on the type, package, and name of the field, the analysis should assume
311    *     the field might be null; false otherwise
312    * @throws NullPointerException if {@code symbol} is null
313    */
mayBeNullFieldFromType(Symbol symbol, Config config)314   public static boolean mayBeNullFieldFromType(Symbol symbol, Config config) {
315     Preconditions.checkNotNull(
316         symbol, "mayBeNullFieldFromType should never be called with a null symbol");
317     return !(symbol.getSimpleName().toString().equals("class")
318             || symbol.isEnum()
319             || isUnannotated(symbol, config))
320         && Nullness.hasNullableAnnotation(symbol, config);
321   }
322 
323   /**
324    * Converts a {@link Nullness} to a {@code bool} value.
325    *
326    * @param nullness The nullness value.
327    * @return true if the nullness value represents a {@code Nullable} value. To be more specific, it
328    *     returns true if the nullness value is either {@link Nullness#NULL} or {@link
329    *     Nullness#NULLABLE}.
330    */
nullnessToBool(Nullness nullness)331   public static boolean nullnessToBool(Nullness nullness) {
332     switch (nullness) {
333       case BOTTOM:
334       case NONNULL:
335         return false;
336       case NULL:
337       case NULLABLE:
338         return true;
339       default:
340         throw new AssertionError("Impossible: " + nullness);
341     }
342   }
343 
344   /**
345    * Check if a symbol comes from unannotated code.
346    *
347    * @param symbol symbol for entity
348    * @param config NullAway config
349    * @return true if symbol represents an entity from a class that is unannotated; false otherwise
350    */
isUnannotated(Symbol symbol, Config config)351   public static boolean isUnannotated(Symbol symbol, Config config) {
352     Symbol.ClassSymbol outermostClassSymbol = getOutermostClassSymbol(symbol);
353     return !config.fromAnnotatedPackage(outermostClassSymbol)
354         || config.isUnannotatedClass(outermostClassSymbol);
355   }
356 
357   /**
358    * Check if a symbol comes from generated code.
359    *
360    * @param symbol symbol for entity
361    * @return true if symbol represents an entity from a class annotated with {@code @Generated};
362    *     false otherwise
363    */
isGenerated(Symbol symbol)364   public static boolean isGenerated(Symbol symbol) {
365     Symbol.ClassSymbol outermostClassSymbol = getOutermostClassSymbol(symbol);
366     return ASTHelpers.hasDirectAnnotationWithSimpleName(outermostClassSymbol, "Generated");
367   }
368 
369   /**
370    * Checks if {@code symbol} is a method on {@code java.util.Map} (or a subtype) with name {@code
371    * methodName} and {@code numParams} parameters
372    */
isMapMethod( Symbol.MethodSymbol symbol, VisitorState state, String methodName, int numParams)373   public static boolean isMapMethod(
374       Symbol.MethodSymbol symbol, VisitorState state, String methodName, int numParams) {
375     if (!symbol.getSimpleName().toString().equals(methodName)) {
376       return false;
377     }
378     if (symbol.getParameters().size() != numParams) {
379       return false;
380     }
381     Symbol owner = symbol.owner;
382     return ASTHelpers.isSubtype(owner.type, MAP_TYPE_SUPPLIER.get(state), state);
383   }
384 }
385