• 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.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 -&gt; !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    *       &lt;T&gt;checkNotNull(T)</code>
172    *   <li>Type bounds matter for generics, e.g., <code>addAll(java.lang.Iterable&lt;? extends
173    *   E&gt;)
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