• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2014 Google Inc. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 // Based on com.google.errorprone.dataflow.nullnesspropagation.*
18 
19 package com.uber.nullaway;
20 
21 import com.sun.tools.javac.code.Symbol;
22 import com.sun.tools.javac.code.Type;
23 import com.sun.tools.javac.util.List;
24 import java.util.stream.Stream;
25 import javax.lang.model.element.AnnotationMirror;
26 import org.checkerframework.nullaway.dataflow.analysis.AbstractValue;
27 
28 /**
29  * Represents one of the possible nullness values in our nullness analysis.
30  *
31  * @author deminguyen@google.com (Demi Nguyen)
32  */
33 public enum Nullness implements AbstractValue<Nullness> {
34 
35   /**
36    * The lattice for nullness looks like:
37    *
38    * <pre>
39    *        Nullable
40    *       /        \
41    *   Null          Non-null
42    *        \      /
43    *         Bottom
44    * </pre>
45    */
46   NULLABLE("Nullable"),
47   NULL("Null"),
48   NONNULL("Non-null"),
49   BOTTOM("Bottom");
50 
51   private final String displayName;
52 
Nullness(String displayName)53   Nullness(String displayName) {
54     this.displayName = displayName;
55   }
56 
57   // The following leastUpperBound and greatestLowerBound methods were created by handwriting a
58   // truth table and then encoding the values into these functions. A better approach would be to
59   // represent the lattice directly and compute these functions from the lattice.
60 
61   @Override
leastUpperBound(Nullness other)62   public Nullness leastUpperBound(Nullness other) {
63     if (this == other) {
64       return this;
65     }
66     // Bottom loses.
67     if (this == BOTTOM) {
68       return other;
69     }
70     if (other == BOTTOM) {
71       return this;
72     }
73     // They disagree, and neither is bottom.
74     return NULLABLE;
75   }
76 
greatestLowerBound(Nullness other)77   public Nullness greatestLowerBound(Nullness other) {
78     if (this == other) {
79       return this;
80     }
81     // Nullable loses.
82     if (this == NULLABLE) {
83       return other;
84     }
85     if (other == NULLABLE) {
86       return this;
87     }
88     // They disagree, and neither is nullable.
89     return BOTTOM;
90   }
91 
92   /**
93    * Returns the {@code Nullness} that corresponds to what you can deduce by knowing that some
94    * expression is not equal to another expression with this {@code Nullness}.
95    *
96    * <p>A {@code Nullness} represents a set of possible values for a expression. Suppose you have
97    * two variables {@code var1} and {@code var2}. If {@code var1 != var2}, then {@code var1} must be
98    * an element of the complement of the singleton set containing the value of {@code var2}. If you
99    * union these complement sets over all possible values of {@code var2}, the set that results is
100    * what this method returns, assuming that {@code this} is the {@code Nullness} of {@code var2}.
101    *
102    * <p>Example 1: Suppose {@code nv2 == NULL}. Then {@code var2} can have exactly one value, {@code
103    * null}, and {@code var1} must have a value in the set of all values except {@code null}. That
104    * set is exactly {@code NONNULL}.
105    *
106    * <p>Example 2: Suppose {@code nv2 == NONNULL}. Then {@code var2} can have any value except
107    * {@code null}. Suppose {@code var2} has value {@code "foo"}. Then {@code var1} must have a value
108    * in the set of all values except {@code "foo"}. Now suppose {@code var2} has value {@code "bar"}
109    * . Then {@code var1} must have a value in set of all values except {@code "bar"}. Since we don't
110    * know which value in the set {@code NONNULL var2} has, we union all possible complement sets to
111    * get the set of all values, or {@code NULLABLE}.
112    */
deducedValueWhenNotEqual()113   public Nullness deducedValueWhenNotEqual() {
114     switch (this) {
115       case NULLABLE:
116         return NULLABLE;
117       case NONNULL:
118         return NULLABLE;
119       case NULL:
120         return NONNULL;
121       case BOTTOM:
122         return BOTTOM;
123       default:
124         throw new AssertionError("Inverse of " + this + " not defined");
125     }
126   }
127 
128   @Override
toString()129   public String toString() {
130     return displayName;
131   }
132 
hasNullableAnnotation( Stream<? extends AnnotationMirror> annotations, Config config)133   public static boolean hasNullableAnnotation(
134       Stream<? extends AnnotationMirror> annotations, Config config) {
135     return annotations
136         .map(anno -> anno.getAnnotationType().toString())
137         .anyMatch(anno -> isNullableAnnotation(anno, config));
138   }
139 
hasNonNullAnnotation( Stream<? extends AnnotationMirror> annotations, Config config)140   public static boolean hasNonNullAnnotation(
141       Stream<? extends AnnotationMirror> annotations, Config config) {
142     return annotations
143         .map(anno -> anno.getAnnotationType().toString())
144         .anyMatch(anno -> isNonNullAnnotation(anno, config));
145   }
146 
147   /**
148    * Check whether an annotation should be treated as equivalent to <code>@Nullable</code>.
149    *
150    * @param annotName annotation name
151    * @return true if we treat annotName as a <code>@Nullable</code> annotation, false otherwise
152    */
isNullableAnnotation(String annotName, Config config)153   public static boolean isNullableAnnotation(String annotName, Config config) {
154     return annotName.endsWith(".Nullable")
155         // endsWith and not equals and no `org.`, because gradle's shadow plug in rewrites strings
156         // and will replace `org.checkerframework` with `shadow.checkerframework`. Yes, really...
157         // I assume it's something to handle reflection.
158         || annotName.endsWith(".checkerframework.checker.nullness.compatqual.NullableDecl")
159         // matches javax.annotation.CheckForNull and edu.umd.cs.findbugs.annotations.CheckForNull
160         || annotName.endsWith(".CheckForNull")
161         // matches any of the multiple @ParametricNullness annotations used within Guava
162         // (see https://github.com/google/guava/issues/6126)
163         // We check the simple name first and the package prefix second for boolean short
164         // circuiting, as Guava includes
165         // many annotations
166         || (annotName.endsWith(".ParametricNullness") && annotName.startsWith("com.google.common."))
167         || (config.acknowledgeAndroidRecent()
168             && annotName.equals("androidx.annotation.RecentlyNullable"))
169         || config.isCustomNullableAnnotation(annotName);
170   }
171 
172   /**
173    * Check whether an annotation should be treated as equivalent to <code>@NonNull</code>.
174    *
175    * @param annotName annotation name
176    * @return true if we treat annotName as a <code>@NonNull</code> annotation, false otherwise
177    */
isNonNullAnnotation(String annotName, Config config)178   private static boolean isNonNullAnnotation(String annotName, Config config) {
179     return annotName.endsWith(".NonNull")
180         || annotName.endsWith(".NotNull")
181         || annotName.endsWith(".Nonnull")
182         || (config.acknowledgeAndroidRecent()
183             && annotName.equals("androidx.annotation.RecentlyNonNull"))
184         || config.isCustomNonnullAnnotation(annotName);
185   }
186 
187   /**
188    * Does the symbol have a {@code @NonNull} declaration or type-use annotation?
189    *
190    * <p>NOTE: this method does not work for checking all annotations of parameters of methods from
191    * class files. For that case, use {@link #paramHasNullableAnnotation(Symbol.MethodSymbol, int,
192    * Config)}
193    */
hasNonNullAnnotation(Symbol symbol, Config config)194   public static boolean hasNonNullAnnotation(Symbol symbol, Config config) {
195     return hasNonNullAnnotation(NullabilityUtil.getAllAnnotations(symbol, config), config);
196   }
197 
198   /**
199    * Does the symbol have a {@code @Nullable} declaration or type-use annotation?
200    *
201    * <p>NOTE: this method does not work for checking all annotations of parameters of methods from
202    * class files. For that case, use {@link #paramHasNullableAnnotation(Symbol.MethodSymbol, int,
203    * Config)}
204    */
hasNullableAnnotation(Symbol symbol, Config config)205   public static boolean hasNullableAnnotation(Symbol symbol, Config config) {
206     return hasNullableAnnotation(NullabilityUtil.getAllAnnotations(symbol, config), config);
207   }
208 
209   /**
210    * Does the parameter of {@code symbol} at {@code paramInd} have a {@code @Nullable} declaration
211    * or type-use annotation? This method works for methods defined in either source or class files.
212    */
paramHasNullableAnnotation( Symbol.MethodSymbol symbol, int paramInd, Config config)213   public static boolean paramHasNullableAnnotation(
214       Symbol.MethodSymbol symbol, int paramInd, Config config) {
215     // We treat the (generated) equals() method of record types to have a @Nullable parameter, as
216     // the generated implementation handles null (as required by the contract of Object.equals())
217     if (isRecordEqualsParam(symbol, paramInd)) {
218       return true;
219     }
220     return hasNullableAnnotation(
221         NullabilityUtil.getAllAnnotationsForParameter(symbol, paramInd, config), config);
222   }
223 
isRecordEqualsParam(Symbol.MethodSymbol symbol, int paramInd)224   private static boolean isRecordEqualsParam(Symbol.MethodSymbol symbol, int paramInd) {
225     // Here we compare with toString() to preserve compatibility with JDK 11 (records only
226     // introduced in JDK 16)
227     if (!symbol.owner.getKind().toString().equals("RECORD")) {
228       return false;
229     }
230     if (!symbol.getSimpleName().contentEquals("equals")) {
231       return false;
232     }
233     // Check for a boolean return type and a single parameter of type java.lang.Object
234     Type type = symbol.type;
235     List<Type> parameterTypes = type.getParameterTypes();
236     if (!(type.getReturnType().toString().equals("boolean")
237         && parameterTypes != null
238         && parameterTypes.size() == 1
239         && parameterTypes.get(0).toString().equals("java.lang.Object"))) {
240       return false;
241     }
242     return paramInd == 0;
243   }
244 
245   /**
246    * Does the parameter of {@code symbol} at {@code paramInd} have a {@code @NonNull} declaration or
247    * type-use annotation? This method works for methods defined in either source or class files.
248    */
paramHasNonNullAnnotation( Symbol.MethodSymbol symbol, int paramInd, Config config)249   public static boolean paramHasNonNullAnnotation(
250       Symbol.MethodSymbol symbol, int paramInd, Config config) {
251     return hasNonNullAnnotation(
252         NullabilityUtil.getAllAnnotationsForParameter(symbol, paramInd, config), config);
253   }
254 }
255