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