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.auto.value.AutoValue; 26 import com.google.common.collect.ImmutableList; 27 import com.google.common.collect.ImmutableSet; 28 import com.google.common.collect.ImmutableSetMultimap; 29 import com.sun.tools.javac.code.Symbol; 30 import com.uber.nullaway.handlers.stream.StreamTypeRecord; 31 import java.util.Objects; 32 import java.util.regex.Matcher; 33 import java.util.regex.Pattern; 34 35 /** Provides models for library routines for the null checker. */ 36 public interface LibraryModels { 37 38 /** 39 * Get methods which fail/error out when passed null. 40 * 41 * @return map from the names of null-rejecting methods to the indexes of the arguments that 42 * aren't permitted to be null. 43 */ failIfNullParameters()44 ImmutableSetMultimap<MethodRef, Integer> failIfNullParameters(); 45 46 /** 47 * Get (method, parameter) pairs that must be modeled as if explicitly annotated with @Nullable. 48 * 49 * @return map from the names of methods with @Nullable parameters to the indexes of the arguments 50 * that are @Nullable. 51 * <p>This is taken into account for override checks, requiring methods that override the 52 * methods listed here to take @Nullable parameters on the same indexes. The main use for this 53 * is to document which API callbacks can be passed null values. 54 */ explicitlyNullableParameters()55 ImmutableSetMultimap<MethodRef, Integer> explicitlyNullableParameters(); 56 57 /** 58 * Get (method, parameter) pairs that must be modeled as @NonNull. 59 * 60 * @return map from the names of methods with @NonNull parameters to the indexes of the arguments 61 * that are @NonNull. 62 * <p>Note that these methods are different from the {@link #failIfNullParameters()} methods, 63 * in that we expect the null checker to ensure that the parameters passed to these methods 64 * are @NonNull. In contrast, the null checker does no such enforcement for methods in {@link 65 * #failIfNullParameters()}, it just learns that after the call the relevant parameters cannot 66 * be null. 67 */ nonNullParameters()68 ImmutableSetMultimap<MethodRef, Integer> nonNullParameters(); 69 70 /** 71 * Get (method, parameter) pairs that cause the method to return <code>true</code> when null. 72 * 73 * @return map from the names of null-querying methods to the indexes of the arguments that are 74 * compared against null. 75 */ nullImpliesTrueParameters()76 ImmutableSetMultimap<MethodRef, Integer> nullImpliesTrueParameters(); 77 78 /** 79 * Get (method, parameter) pairs that cause the method to return <code>false</code> when null. 80 * 81 * @return map from the names of non-null-querying methods to the indexes of the arguments that 82 * are compared against null. 83 */ nullImpliesFalseParameters()84 ImmutableSetMultimap<MethodRef, Integer> nullImpliesFalseParameters(); 85 86 /** 87 * Get (method, parameter) pairs that cause the method to return <code>null</code> when passed 88 * <code>null</code> on that parameter. 89 * 90 * <p>This is equivalent to annotating a method with both a {@code @Nullable} return type 91 * <em>and</em> a {@code @Contract} annotation specifying that if the parameter is 92 * {@code @NonNull} then the return is {@code @NonNull}, e.g.: 93 * 94 * <pre><code>@Contract("!null -> !null") @Nullable</code></pre> 95 * 96 * @return map from the names of null-in-implies-null out methods to the indexes of the arguments 97 * that determine nullness of the return. 98 */ nullImpliesNullParameters()99 ImmutableSetMultimap<MethodRef, Integer> nullImpliesNullParameters(); 100 101 /** 102 * Get the set of library methods that may return null. 103 * 104 * @return set of library methods that may return null 105 */ nullableReturns()106 ImmutableSet<MethodRef> nullableReturns(); 107 108 /** 109 * Get the set of library methods that are assumed not to return null. 110 * 111 * @return set of library methods that are assumed not to return null 112 */ nonNullReturns()113 ImmutableSet<MethodRef> nonNullReturns(); 114 115 /** 116 * Get (method, parameter) pairs that act as castToNonNull(...) methods. 117 * 118 * <p>Here, the parameter index determines the argument position of the reference being cast to 119 * non-null. 120 * 121 * <p>We still provide the CLI configuration `-XepOpt:NullAway:CastToNonNullMethod` as the default 122 * way to define the common case of a single-argument {@code @NonNull Object 123 * castToNonNull(@Nullable Object o)}} cast method. 124 * 125 * <p>However, in some cases, the user might wish to have a cast method that takes multiple 126 * arguments, in addition to the <code>@Nullable</code> value being cast. For these cases, 127 * providing a library model allows for more precise error reporting whenever a known non-null 128 * value is passed to such method, rendering the cast unnecessary. 129 * 130 * <p>Note that we can't auto-add castToNonNull(...) methods taking more than one argument, simply 131 * because there might be no general, automated way of synthesizing the required arguments. 132 */ castToNonNullMethods()133 ImmutableSetMultimap<MethodRef, Integer> castToNonNullMethods(); 134 135 /** 136 * Get the set of library fields that may be {@code null}. 137 * 138 * @return set of library fields that may be {@code null}. 139 */ nullableFields()140 ImmutableSet<FieldRef> nullableFields(); 141 142 /** 143 * Get a list of custom stream library specifications. 144 * 145 * <p>This allows users to define filter/map/other methods for APIs which behave similarly to Java 146 * 8 streams or ReactiveX streams, so that NullAway is able to understand nullability invariants 147 * across stream API calls. See {@link com.uber.nullaway.handlers.stream.StreamModelBuilder} for 148 * details on how to construct these {@link com.uber.nullaway.handlers.stream.StreamTypeRecord} 149 * specs. A full example is available at {@link 150 * com.uber.nullaway.testlibrarymodels.TestLibraryModels}. 151 * 152 * @return A list of StreamTypeRecord specs (usually generated using StreamModelBuilder). 153 */ customStreamNullabilitySpecs()154 default ImmutableList<StreamTypeRecord> customStreamNullabilitySpecs() { 155 return ImmutableList.of(); 156 } 157 158 /** 159 * Representation of a method as a qualified class name + a signature for the method 160 * 161 * <p>The formatting of a method signature should match the result of calling {@link 162 * Symbol.MethodSymbol#toString()} on the corresponding symbol. See {@link 163 * com.uber.nullaway.handlers.LibraryModelsHandler.DefaultLibraryModels} for examples. Basic 164 * principles: 165 * 166 * <ul> 167 * <li>signature is a method name plus argument types, e.g., <code>foo(java.lang.Object, 168 * java.lang.String)</code> 169 * <li>constructor for class Foo looks like <code>Foo(java.lang.String)</code> 170 * <li>If the method has its own type parameters, they need to be declared, like <code> 171 * <T>checkNotNull(T)</code> 172 * <li>Type bounds matter for generics, e.g., <code>addAll(java.lang.Iterable<? extends 173 * E>) 174 * </code> 175 * </ul> 176 */ 177 final class MethodRef { 178 179 public final String enclosingClass; 180 181 /** 182 * we store the method name separately to enable fast comparison with MethodSymbols. See {@link 183 * com.uber.nullaway.handlers.LibraryModelsHandler.OptimizedLibraryModels} 184 */ 185 public final String methodName; 186 187 public final String fullMethodSig; 188 MethodRef(String enclosingClass, String methodName, String fullMethodSig)189 private MethodRef(String enclosingClass, String methodName, String fullMethodSig) { 190 this.enclosingClass = enclosingClass; 191 this.methodName = methodName; 192 this.fullMethodSig = fullMethodSig; 193 } 194 195 private static final Pattern METHOD_SIG_PATTERN = Pattern.compile("^(<.*>)?(\\w+)(\\(.*\\))$"); 196 197 /** 198 * Construct a method reference. 199 * 200 * @param enclosingClass containing class 201 * @param methodSignature method signature in the appropriate format (see class docs) 202 * @return corresponding {@link MethodRef} 203 */ methodRef(String enclosingClass, String methodSignature)204 public static MethodRef methodRef(String enclosingClass, String methodSignature) { 205 Matcher matcher = METHOD_SIG_PATTERN.matcher(methodSignature); 206 if (matcher.find()) { 207 String methodName = matcher.group(2); 208 if (methodName.equals(enclosingClass.substring(enclosingClass.lastIndexOf('.') + 1))) { 209 // constructor 210 methodName = "<init>"; 211 } 212 return new MethodRef(enclosingClass, methodName, methodSignature); 213 } else { 214 throw new IllegalArgumentException("malformed method signature " + methodSignature); 215 } 216 } 217 fromSymbol(Symbol.MethodSymbol symbol)218 public static MethodRef fromSymbol(Symbol.MethodSymbol symbol) { 219 String methodStr = symbol.toString(); 220 221 return new MethodRef( 222 symbol.owner.getQualifiedName().toString(), symbol.name.toString(), methodStr); 223 } 224 225 @Override equals(Object o)226 public boolean equals(Object o) { 227 if (this == o) { 228 return true; 229 } 230 if (o == null || getClass() != o.getClass()) { 231 return false; 232 } 233 MethodRef methodRef = (MethodRef) o; 234 return Objects.equals(enclosingClass, methodRef.enclosingClass) 235 && Objects.equals(fullMethodSig, methodRef.fullMethodSig); 236 } 237 238 @Override hashCode()239 public int hashCode() { 240 return Objects.hash(enclosingClass, fullMethodSig); 241 } 242 243 @Override toString()244 public String toString() { 245 return "MethodRef{" 246 + "enclosingClass='" 247 + enclosingClass 248 + '\'' 249 + ", fullMethodSig='" 250 + fullMethodSig 251 + '\'' 252 + '}'; 253 } 254 } 255 256 /** Representation of a field as a qualified class name + a field name */ 257 @AutoValue 258 abstract class FieldRef { 259 getEnclosingClassName()260 public abstract String getEnclosingClassName(); 261 getFieldName()262 public abstract String getFieldName(); 263 fieldRef(String enclosingClass, String fieldName)264 public static FieldRef fieldRef(String enclosingClass, String fieldName) { 265 return new AutoValue_LibraryModels_FieldRef(enclosingClass, fieldName); 266 } 267 } 268 } 269