• 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 java.util.stream.Stream;
23 import javax.lang.model.element.AnnotationMirror;
24 import org.checkerframework.nullaway.dataflow.analysis.AbstractValue;
25 
26 /**
27  * Represents one of the possible nullness values in our nullness analysis.
28  *
29  * @author deminguyen@google.com (Demi Nguyen)
30  */
31 public enum Nullness implements AbstractValue<Nullness> {
32 
33   /**
34    * The lattice for nullness looks like:
35    *
36    * <pre>
37    *        Nullable
38    *       /        \
39    *   Null          Non-null
40    *        \      /
41    *         Bottom
42    * </pre>
43    */
44   NULLABLE("Nullable"),
45   NULL("Null"),
46   NONNULL("Non-null"),
47   BOTTOM("Bottom");
48 
49   private final String displayName;
50 
Nullness(String displayName)51   Nullness(String displayName) {
52     this.displayName = displayName;
53   }
54 
55   // The following leastUpperBound and greatestLowerBound methods were created by handwriting a
56   // truth table and then encoding the values into these functions. A better approach would be to
57   // represent the lattice directly and compute these functions from the lattice.
58 
59   @Override
leastUpperBound(Nullness other)60   public Nullness leastUpperBound(Nullness other) {
61     if (this == other) {
62       return this;
63     }
64     // Bottom loses.
65     if (this == BOTTOM) {
66       return other;
67     }
68     if (other == BOTTOM) {
69       return this;
70     }
71     // They disagree, and neither is bottom.
72     return NULLABLE;
73   }
74 
greatestLowerBound(Nullness other)75   public Nullness greatestLowerBound(Nullness other) {
76     if (this == other) {
77       return this;
78     }
79     // Nullable loses.
80     if (this == NULLABLE) {
81       return other;
82     }
83     if (other == NULLABLE) {
84       return this;
85     }
86     // They disagree, and neither is nullable.
87     return BOTTOM;
88   }
89 
90   /**
91    * Returns the {@code Nullness} that corresponds to what you can deduce by knowing that some
92    * expression is not equal to another expression with this {@code Nullness}.
93    *
94    * <p>A {@code Nullness} represents a set of possible values for a expression. Suppose you have
95    * two variables {@code var1} and {@code var2}. If {@code var1 != var2}, then {@code var1} must be
96    * an element of the complement of the singleton set containing the value of {@code var2}. If you
97    * union these complement sets over all possible values of {@code var2}, the set that results is
98    * what this method returns, assuming that {@code this} is the {@code Nullness} of {@code var2}.
99    *
100    * <p>Example 1: Suppose {@code nv2 == NULL}. Then {@code var2} can have exactly one value, {@code
101    * null}, and {@code var1} must have a value in the set of all values except {@code null}. That
102    * set is exactly {@code NONNULL}.
103    *
104    * <p>Example 2: Suppose {@code nv2 == NONNULL}. Then {@code var2} can have any value except
105    * {@code null}. Suppose {@code var2} has value {@code "foo"}. Then {@code var1} must have a value
106    * in the set of all values except {@code "foo"}. Now suppose {@code var2} has value {@code "bar"}
107    * . Then {@code var1} must have a value in set of all values except {@code "bar"}. Since we don't
108    * know which value in the set {@code NONNULL var2} has, we union all possible complement sets to
109    * get the set of all values, or {@code NULLABLE}.
110    */
deducedValueWhenNotEqual()111   public Nullness deducedValueWhenNotEqual() {
112     switch (this) {
113       case NULLABLE:
114         return NULLABLE;
115       case NONNULL:
116         return NULLABLE;
117       case NULL:
118         return NONNULL;
119       case BOTTOM:
120         return BOTTOM;
121       default:
122         throw new AssertionError("Inverse of " + this + " not defined");
123     }
124   }
125 
126   @Override
toString()127   public String toString() {
128     return displayName;
129   }
130 
hasNullableAnnotation( Stream<? extends AnnotationMirror> annotations, Config config)131   private static boolean hasNullableAnnotation(
132       Stream<? extends AnnotationMirror> annotations, Config config) {
133     return annotations
134         .map(anno -> anno.getAnnotationType().toString())
135         .anyMatch(anno -> isNullableAnnotation(anno, config));
136   }
137 
hasNonNullAnnotation( Stream<? extends AnnotationMirror> annotations, Config config)138   private static boolean hasNonNullAnnotation(
139       Stream<? extends AnnotationMirror> annotations, Config config) {
140     return annotations
141         .map(anno -> anno.getAnnotationType().toString())
142         .anyMatch(anno -> isNonNullAnnotation(anno, config));
143   }
144 
145   /**
146    * Check whether an annotation should be treated as equivalent to <code>@Nullable</code>.
147    *
148    * @param annotName annotation name
149    * @return true if we treat annotName as a <code>@Nullable</code> annotation, false otherwise
150    */
isNullableAnnotation(String annotName, Config config)151   public static boolean isNullableAnnotation(String annotName, Config config) {
152     return annotName.endsWith(".Nullable")
153         // endsWith and not equals and no `org.`, because gradle's shadow plug in rewrites strings
154         // and will replace `org.checkerframework` with `shadow.checkerframework`. Yes, really...
155         // I assume it's something to handle reflection.
156         || annotName.endsWith(".checkerframework.checker.nullness.compatqual.NullableDecl")
157         // matches javax.annotation.CheckForNull and edu.umd.cs.findbugs.annotations.CheckForNull
158         || annotName.endsWith(".CheckForNull")
159         || (config.acknowledgeAndroidRecent()
160             && annotName.equals("androidx.annotation.RecentlyNullable"))
161         || config.isCustomNullableAnnotation(annotName);
162   }
163 
164   /**
165    * Check whether an annotation should be treated as equivalent to <code>@NonNull</code>.
166    *
167    * @param annotName annotation name
168    * @return true if we treat annotName as a <code>@NonNull</code> annotation, false otherwise
169    */
isNonNullAnnotation(String annotName, Config config)170   private static boolean isNonNullAnnotation(String annotName, Config config) {
171     return annotName.endsWith(".NonNull")
172         || annotName.endsWith(".NotNull")
173         || annotName.endsWith(".Nonnull")
174         || (config.acknowledgeAndroidRecent()
175             && annotName.equals("androidx.annotation.RecentlyNonNull"))
176         || config.isCustomNonnullAnnotation(annotName);
177   }
178 
179   /**
180    * Does the symbol have a {@code @NonNull} declaration or type-use annotation?
181    *
182    * <p>NOTE: this method does not work for checking all annotations of parameters of methods from
183    * class files. For that case, use {@link #paramHasNullableAnnotation(Symbol.MethodSymbol, int,
184    * Config)}
185    */
hasNonNullAnnotation(Symbol symbol, Config config)186   public static boolean hasNonNullAnnotation(Symbol symbol, Config config) {
187     return hasNonNullAnnotation(NullabilityUtil.getAllAnnotations(symbol), config);
188   }
189 
190   /**
191    * Does the symbol have a {@code @Nullable} declaration or type-use annotation?
192    *
193    * <p>NOTE: this method does not work for checking all annotations of parameters of methods from
194    * class files. For that case, use {@link #paramHasNullableAnnotation(Symbol.MethodSymbol, int,
195    * Config)}
196    */
hasNullableAnnotation(Symbol symbol, Config config)197   public static boolean hasNullableAnnotation(Symbol symbol, Config config) {
198     return hasNullableAnnotation(NullabilityUtil.getAllAnnotations(symbol), config);
199   }
200 
201   /**
202    * Does the parameter of {@code symbol} at {@code paramInd} have a {@code @Nullable} declaration
203    * or type-use annotation? This method works for methods defined in either source or class files.
204    */
paramHasNullableAnnotation( Symbol.MethodSymbol symbol, int paramInd, Config config)205   public static boolean paramHasNullableAnnotation(
206       Symbol.MethodSymbol symbol, int paramInd, Config config) {
207     return hasNullableAnnotation(
208         NullabilityUtil.getAllAnnotationsForParameter(symbol, paramInd), config);
209   }
210 
211   /**
212    * Does the parameter of {@code symbol} at {@code paramInd} have a {@code @NonNull} declaration or
213    * type-use annotation? This method works for methods defined in either source or class files.
214    */
paramHasNonNullAnnotation( Symbol.MethodSymbol symbol, int paramInd, Config config)215   public static boolean paramHasNonNullAnnotation(
216       Symbol.MethodSymbol symbol, int paramInd, Config config) {
217     return hasNonNullAnnotation(
218         NullabilityUtil.getAllAnnotationsForParameter(symbol, paramInd), config);
219   }
220 }
221