blob: fc19c8ea3c58e7894b24f75c46323518de7a2dc6 [file] [log] [blame]
/*
* Copyright (c) 2017 Uber Technologies, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.uber.nullaway.handlers;
import static com.uber.nullaway.LibraryModels.MethodRef.methodRef;
import static com.uber.nullaway.Nullness.NONNULL;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Sets;
import com.google.errorprone.VisitorState;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Types;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.Name;
import com.sun.tools.javac.util.Names;
import com.uber.nullaway.LibraryModels;
import com.uber.nullaway.LibraryModels.MethodRef;
import com.uber.nullaway.NullAway;
import com.uber.nullaway.dataflow.AccessPath;
import com.uber.nullaway.dataflow.AccessPathNullnessPropagation;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.function.Function;
import javax.annotation.Nullable;
import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
import org.checkerframework.dataflow.cfg.node.Node;
/**
* This Handler deals with any methods from unannotated packages for which we need a nullability
* model.
*
* <p>It loads the required LibraryModels and inserts their nullability information at the right
* points through the flow of our analysis.
*/
public class LibraryModelsHandler extends BaseNoOpHandler {
private final LibraryModels libraryModels;
@Nullable private OptimizedLibraryModels optLibraryModels;
public LibraryModelsHandler() {
super();
libraryModels = loadLibraryModels();
}
@Override
public ImmutableSet<Integer> onUnannotatedInvocationGetNonNullPositions(
NullAway analysis,
VisitorState state,
Symbol.MethodSymbol methodSymbol,
List<? extends ExpressionTree> actualParams,
ImmutableSet<Integer> nonNullPositions) {
return Sets.union(
nonNullPositions, getOptLibraryModels(state.context).nonNullParameters(methodSymbol))
.immutableCopy();
}
@Override
public ImmutableSet<Integer> onUnannotatedInvocationGetExplicitlyNullablePositions(
Context context,
Symbol.MethodSymbol methodSymbol,
ImmutableSet<Integer> explicitlyNullablePositions) {
return Sets.union(
Sets.difference(
explicitlyNullablePositions,
getOptLibraryModels(context).nonNullParameters(methodSymbol)),
getOptLibraryModels(context).explicitlyNullableParameters(methodSymbol))
.immutableCopy();
}
@Override
public boolean onOverrideMayBeNullExpr(
NullAway analysis, ExpressionTree expr, VisitorState state, boolean exprMayBeNull) {
if (expr.getKind() == Tree.Kind.METHOD_INVOCATION) {
OptimizedLibraryModels optLibraryModels = getOptLibraryModels(state.context);
Symbol.MethodSymbol methodSymbol = (Symbol.MethodSymbol) ASTHelpers.getSymbol(expr);
if (optLibraryModels.hasNullableReturn(methodSymbol, state.getTypes())
|| !optLibraryModels.nullImpliesNullParameters(methodSymbol).isEmpty()) {
// These mean the method might be null, depending on dataflow and arguments. We force
// dataflow to run.
return analysis.nullnessFromDataflow(state, expr) || exprMayBeNull;
} else if (optLibraryModels.hasNonNullReturn(methodSymbol, state.getTypes())) {
// This means the method can't be null, so we return false outright.
return false;
}
}
return exprMayBeNull;
}
@Override
public NullnessHint onDataflowVisitMethodInvocation(
MethodInvocationNode node,
Types types,
Context context,
AccessPathNullnessPropagation.SubNodeValues inputs,
AccessPathNullnessPropagation.Updates thenUpdates,
AccessPathNullnessPropagation.Updates elseUpdates,
AccessPathNullnessPropagation.Updates bothUpdates) {
Symbol.MethodSymbol callee = ASTHelpers.getSymbol(node.getTree());
Preconditions.checkNotNull(callee);
setUnconditionalArgumentNullness(bothUpdates, node.getArguments(), callee, context);
setConditionalArgumentNullness(thenUpdates, elseUpdates, node.getArguments(), callee, context);
ImmutableSet<Integer> nullImpliesNullIndexes =
getOptLibraryModels(context).nullImpliesNullParameters(callee);
if (!nullImpliesNullIndexes.isEmpty()) {
// If the method is marked as having argument dependent nullability and any of the
// corresponding arguments is null, then the return is nullable. If the method is
// marked as having argument dependent nullability but NONE of the corresponding
// arguments is null, then the return should be non-null.
boolean anyNull = false;
for (int idx : nullImpliesNullIndexes) {
if (!inputs.valueOfSubNode(node.getArgument(idx)).equals(NONNULL)) {
anyNull = true;
}
}
return anyNull ? NullnessHint.HINT_NULLABLE : NullnessHint.FORCE_NONNULL;
}
if (getOptLibraryModels(context).hasNonNullReturn(callee, types)) {
return NullnessHint.FORCE_NONNULL;
} else if (getOptLibraryModels(context).hasNullableReturn(callee, types)) {
return NullnessHint.HINT_NULLABLE;
} else {
return NullnessHint.UNKNOWN;
}
}
private void setConditionalArgumentNullness(
AccessPathNullnessPropagation.Updates thenUpdates,
AccessPathNullnessPropagation.Updates elseUpdates,
List<Node> arguments,
Symbol.MethodSymbol callee,
Context context) {
Set<Integer> nullImpliesTrueParameters =
getOptLibraryModels(context).nullImpliesTrueParameters(callee);
Set<Integer> nullImpliesFalseParameters =
getOptLibraryModels(context).nullImpliesFalseParameters(callee);
for (AccessPath accessPath : accessPathsAtIndexes(nullImpliesTrueParameters, arguments)) {
elseUpdates.set(accessPath, NONNULL);
}
for (AccessPath accessPath : accessPathsAtIndexes(nullImpliesFalseParameters, arguments)) {
thenUpdates.set(accessPath, NONNULL);
}
}
private static Iterable<AccessPath> accessPathsAtIndexes(
Set<Integer> indexes, List<Node> arguments) {
List<AccessPath> result = new ArrayList<>();
for (Integer i : indexes) {
Preconditions.checkArgument(i >= 0 && i < arguments.size(), "Invalid argument index: " + i);
if (i >= 0 && i < arguments.size()) {
Node argument = arguments.get(i);
AccessPath ap = AccessPath.getAccessPathForNodeNoMapGet(argument);
if (ap != null) {
result.add(ap);
}
}
}
return result;
}
private OptimizedLibraryModels getOptLibraryModels(Context context) {
if (optLibraryModels == null) {
optLibraryModels = new OptimizedLibraryModels(libraryModels, context);
}
return optLibraryModels;
}
private void setUnconditionalArgumentNullness(
AccessPathNullnessPropagation.Updates bothUpdates,
List<Node> arguments,
Symbol.MethodSymbol callee,
Context context) {
Set<Integer> requiredNonNullParameters =
getOptLibraryModels(context).failIfNullParameters(callee);
for (AccessPath accessPath : accessPathsAtIndexes(requiredNonNullParameters, arguments)) {
bothUpdates.set(accessPath, NONNULL);
}
}
private static LibraryModels loadLibraryModels() {
Iterable<LibraryModels> externalLibraryModels =
ServiceLoader.load(LibraryModels.class, LibraryModels.class.getClassLoader());
ImmutableSet.Builder<LibraryModels> libModelsBuilder = new ImmutableSet.Builder<>();
libModelsBuilder.add(new DefaultLibraryModels()).addAll(externalLibraryModels);
return new CombinedLibraryModels(libModelsBuilder.build());
}
private static class DefaultLibraryModels implements LibraryModels {
private static final ImmutableSetMultimap<MethodRef, Integer> FAIL_IF_NULL_PARAMETERS =
new ImmutableSetMultimap.Builder<MethodRef, Integer>()
.put(methodRef("com.google.common.base.Preconditions", "<T>checkNotNull(T)"), 0)
.put(
methodRef(
"com.google.common.base.Preconditions", "<T>checkNotNull(T,java.lang.Object)"),
0)
.put(
methodRef(
"com.google.common.base.Preconditions",
"<T>checkNotNull(T,java.lang.String,java.lang.Object...)"),
0)
.put(
methodRef(
"com.google.common.base.Preconditions",
"<T>checkNotNull(T,java.lang.String,char)"),
0)
.put(
methodRef(
"com.google.common.base.Preconditions",
"<T>checkNotNull(T,java.lang.String,int)"),
0)
.put(
methodRef(
"com.google.common.base.Preconditions",
"<T>checkNotNull(T,java.lang.String,long)"),
0)
.put(
methodRef(
"com.google.common.base.Preconditions",
"<T>checkNotNull(T,java.lang.String,java.lang.Object)"),
0)
.put(
methodRef(
"com.google.common.base.Preconditions",
"<T>checkNotNull(T,java.lang.String,char,char)"),
0)
.put(
methodRef(
"com.google.common.base.Preconditions",
"<T>checkNotNull(T,java.lang.String,char,int)"),
0)
.put(
methodRef(
"com.google.common.base.Preconditions",
"<T>checkNotNull(T,java.lang.String,char,long)"),
0)
.put(
methodRef(
"com.google.common.base.Preconditions",
"<T>checkNotNull(T,java.lang.String,char,java.lang.Object)"),
0)
.put(
methodRef(
"com.google.common.base.Preconditions",
"<T>checkNotNull(T,java.lang.String,int,char)"),
0)
.put(
methodRef(
"com.google.common.base.Preconditions",
"<T>checkNotNull(T,java.lang.String,int,int)"),
0)
.put(
methodRef(
"com.google.common.base.Preconditions",
"<T>checkNotNull(T,java.lang.String,int,long)"),
0)
.put(
methodRef(
"com.google.common.base.Preconditions",
"<T>checkNotNull(T,java.lang.String,int,java.lang.Object)"),
0)
.put(
methodRef(
"com.google.common.base.Preconditions",
"<T>checkNotNull(T,java.lang.String,long,char)"),
0)
.put(
methodRef(
"com.google.common.base.Preconditions",
"<T>checkNotNull(T,java.lang.String,long,int)"),
0)
.put(
methodRef(
"com.google.common.base.Preconditions",
"<T>checkNotNull(T,java.lang.String,long,long)"),
0)
.put(
methodRef(
"com.google.common.base.Preconditions",
"<T>checkNotNull(T,java.lang.String,long,java.lang.Object)"),
0)
.put(
methodRef(
"com.google.common.base.Preconditions",
"<T>checkNotNull(T,java.lang.String,java.lang.Object,char)"),
0)
.put(
methodRef(
"com.google.common.base.Preconditions",
"<T>checkNotNull(T,java.lang.String,java.lang.Object,int)"),
0)
.put(
methodRef(
"com.google.common.base.Preconditions",
"<T>checkNotNull(T,java.lang.String,java.lang.Object,long)"),
0)
.put(
methodRef(
"com.google.common.base.Preconditions",
"<T>checkNotNull(T,java.lang.String,java.lang.Object,java.lang.Object)"),
0)
.put(
methodRef(
"com.google.common.base.Preconditions",
"<T>checkNotNull(T,java.lang.String,java.lang.Object,java.lang.Object,java.lang.Object)"),
0)
.put(
methodRef(
"com.google.common.base.Preconditions",
"<T>checkNotNull(T,java.lang.String,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object)"),
0)
.put(methodRef("java.util.Objects", "<T>requireNonNull(T)"), 0)
.put(methodRef("java.util.Objects", "<T>requireNonNull(T,java.lang.String)"), 0)
.put(methodRef("org.junit.Assert", "assertNotNull(java.lang.Object)"), 0)
.put(
methodRef("org.junit.Assert", "assertNotNull(java.lang.String,java.lang.Object)"),
1)
.put(
methodRef("org.junit.jupiter.api.Assertions", "assertNotNull(java.lang.Object)"), 0)
.put(
methodRef(
"org.junit.jupiter.api.Assertions",
"assertNotNull(java.lang.Object,java.lang.String)"),
0)
.put(
methodRef(
"org.junit.jupiter.api.Assertions",
"assertNotNull(java.lang.Object,java.util.function.Supplier<java.lang.String>)"),
0)
.build();
private static final ImmutableSetMultimap<MethodRef, Integer> EXPLICITLY_NULLABLE_PARAMETERS =
new ImmutableSetMultimap.Builder<MethodRef, Integer>()
.put(
methodRef(
"android.view.GestureDetector.OnGestureListener",
"onScroll(android.view.MotionEvent,android.view.MotionEvent,float,float)"),
0)
.build();
private static final ImmutableSetMultimap<MethodRef, Integer> NON_NULL_PARAMETERS =
new ImmutableSetMultimap.Builder<MethodRef, Integer>()
.put(
methodRef(
"com.android.sdklib.build.ApkBuilder",
"ApkBuilder(java.io.File,java.io.File,java.io.File,java.lang.String,java.io.PrintStream)"),
0)
.put(
methodRef(
"com.android.sdklib.build.ApkBuilder",
"ApkBuilder(java.io.File,java.io.File,java.io.File,java.lang.String,java.io.PrintStream)"),
1)
.put(methodRef("com.google.common.collect.ImmutableList.Builder", "add(E)"), 0)
.put(
methodRef(
"com.google.common.collect.ImmutableList.Builder",
"addAll(java.lang.Iterable<? extends E>)"),
0)
.put(methodRef("com.google.common.collect.ImmutableSet.Builder", "add(E)"), 0)
.put(
methodRef(
"com.google.common.collect.ImmutableSet.Builder",
"addAll(java.lang.Iterable<? extends E>)"),
0)
.put(methodRef("com.google.common.collect.ImmutableSortedSet.Builder", "add(E)"), 0)
.put(
methodRef(
"com.google.common.collect.ImmutableSortedSet.Builder",
"addAll(java.lang.Iterable<? extends E>)"),
0)
.put(
methodRef(
"com.google.common.collect.Iterables",
"<T>getFirst(java.lang.Iterable<? extends T>,T)"),
0)
.put(
methodRef(
"com.google.common.util.concurrent.SettableFuture",
"setException(java.lang.Throwable)"),
0)
.put(methodRef("com.google.common.base.Function", "apply(F)"), 0)
.put(methodRef("com.google.common.base.Predicate", "apply(T)"), 0)
.put(methodRef("com.google.common.util.concurrent.AsyncFunction", "apply(I)"), 0)
.put(methodRef("java.io.File", "File(java.lang.String)"), 0)
.put(methodRef("java.lang.Class", "getResource(java.lang.String)"), 0)
.put(methodRef("java.lang.Class", "isAssignableFrom(java.lang.Class<?>)"), 0)
.put(methodRef("java.lang.System", "getProperty(java.lang.String)"), 0)
.put(
methodRef(
"java.net.URLClassLoader", "newInstance(java.net.URL[],java.lang.ClassLoader)"),
0)
.put(
methodRef(
"javax.lang.model.element.Element", "<A>getAnnotation(java.lang.Class<A>)"),
0)
.put(
methodRef(
"javax.lang.model.util.Elements", "getPackageElement(java.lang.CharSequence)"),
0)
.put(
methodRef(
"javax.lang.model.util.Elements", "getTypeElement(java.lang.CharSequence)"),
0)
.put(
methodRef(
"javax.lang.model.util.Elements",
"getDocComment(javax.lang.model.element.Element)"),
0)
.put(methodRef("java.util.Optional", "<T>of(T)"), 0)
.put(methodRef("java.util.Deque", "addFirst(E)"), 0)
.put(methodRef("java.util.Deque", "addLast(E)"), 0)
.put(methodRef("java.util.Deque", "offerFirst(E)"), 0)
.put(methodRef("java.util.Deque", "offerLast(E)"), 0)
.put(methodRef("java.util.Deque", "add(E)"), 0)
.put(methodRef("java.util.Deque", "offer(E)"), 0)
.put(methodRef("java.util.Deque", "push(E)"), 0)
.put(methodRef("java.util.Collection", "<T>toArray(T[])"), 0)
.put(methodRef("java.util.ArrayDeque", "addFirst(E)"), 0)
.put(methodRef("java.util.ArrayDeque", "addLast(E)"), 0)
.put(methodRef("java.util.ArrayDeque", "offerFirst(E)"), 0)
.put(methodRef("java.util.ArrayDeque", "offerLast(E)"), 0)
.put(methodRef("java.util.ArrayDeque", "add(E)"), 0)
.put(methodRef("java.util.ArrayDeque", "offer(E)"), 0)
.put(methodRef("java.util.ArrayDeque", "push(E)"), 0)
.put(methodRef("java.util.ArrayDeque", "<T>toArray(T[])"), 0)
.build();
private static final ImmutableSetMultimap<MethodRef, Integer> NULL_IMPLIES_TRUE_PARAMETERS =
new ImmutableSetMultimap.Builder<MethodRef, Integer>()
.put(methodRef("com.google.common.base.Strings", "isNullOrEmpty(java.lang.String)"), 0)
.put(methodRef("java.util.Objects", "isNull(java.lang.Object)"), 0)
.put(methodRef("android.text.TextUtils", "isEmpty(java.lang.CharSequence)"), 0)
.put(methodRef("org.apache.commons.lang.StringUtils", "isEmpty(java.lang.String)"), 0)
.put(
methodRef(
"org.apache.commons.lang3.StringUtils", "isEmpty(java.lang.CharSequence)"),
0)
.put(methodRef("org.apache.commons.lang.StringUtils", "isBlank(java.lang.String)"), 0)
.put(
methodRef(
"org.apache.commons.lang3.StringUtils", "isBlank(java.lang.CharSequence)"),
0)
.build();
private static final ImmutableSetMultimap<MethodRef, Integer> NULL_IMPLIES_FALSE_PARAMETERS =
new ImmutableSetMultimap.Builder<MethodRef, Integer>()
.put(methodRef("java.util.Objects", "nonNull(java.lang.Object)"), 0)
.build();
private static final ImmutableSetMultimap<MethodRef, Integer> NULL_IMPLIES_NULL_PARAMETERS =
new ImmutableSetMultimap.Builder<MethodRef, Integer>()
.put(methodRef("java.util.Optional", "orElse(T)"), 0)
.build();
private static final ImmutableSet<MethodRef> NULLABLE_RETURNS =
new ImmutableSet.Builder<MethodRef>()
.add(methodRef("java.lang.ref.Reference", "get()"))
.add(methodRef("java.lang.ref.PhantomReference", "get()"))
.add(methodRef("java.lang.ref.SoftReference", "get()"))
.add(methodRef("java.lang.ref.WeakReference", "get()"))
.add(methodRef("java.util.concurrent.atomic.AtomicReference", "get()"))
.add(methodRef("java.util.Map", "get(java.lang.Object)"))
.add(methodRef("javax.lang.model.element.Element", "getEnclosingElement()"))
.add(methodRef("javax.lang.model.element.ExecutableElement", "getDefaultValue()"))
.add(methodRef("javax.lang.model.element.PackageElement", "getEnclosingElement()"))
.add(methodRef("javax.lang.model.element.VariableElement", "getConstantValue()"))
.add(methodRef("javax.lang.model.type.WildcardType", "getSuperBound()"))
.add(methodRef("android.app.ActivityManager", "getRunningAppProcesses()"))
.add(methodRef("android.view.View", "getHandler()"))
.add(methodRef("java.lang.Throwable", "getMessage()"))
.add(methodRef("android.webkit.WebView", "getUrl()"))
.add(methodRef("com.sun.source.tree.CompilationUnitTree", "getPackageName()"))
.build();
private static final ImmutableSet<MethodRef> NONNULL_RETURNS =
new ImmutableSet.Builder<MethodRef>()
.add(methodRef("com.google.gson", "<T>fromJson(String,Class)"))
.add(methodRef("com.google.common.base.Function", "apply(F)"))
.add(methodRef("com.google.common.base.Predicate", "apply(T)"))
.add(methodRef("com.google.common.util.concurrent.AsyncFunction", "apply(I)"))
.add(methodRef("android.app.Activity", "<T>findViewById(int)"))
.add(methodRef("android.view.View", "<T>findViewById(int)"))
.add(methodRef("android.view.View", "getResources()"))
.add(methodRef("android.view.ViewGroup", "getChildAt(int)"))
.add(
methodRef(
"android.content.res.Resources",
"getDrawable(int,android.content.res.Resources.Theme)"))
.add(methodRef("android.support.v4.app.Fragment", "getActivity()"))
.add(methodRef("androidx.fragment.app.Fragment", "getActivity()"))
.add(methodRef("android.support.v4.app.Fragment", "getArguments()"))
.add(methodRef("androidx.fragment.app.Fragment", "getArguments()"))
.add(methodRef("android.support.v4.app.Fragment", "getContext()"))
.add(methodRef("androidx.fragment.app.Fragment", "getContext()"))
.add(
methodRef(
"android.support.v4.app.Fragment",
"onCreateView(android.view.LayoutInflater,android.view.ViewGroup,android.os.Bundle)"))
.add(
methodRef(
"androidx.fragment.app.Fragment",
"onCreateView(android.view.LayoutInflater,android.view.ViewGroup,android.os.Bundle)"))
.add(
methodRef(
"android.support.v4.content.ContextCompat",
"getDrawable(android.content.Context,int)"))
.add(
methodRef(
"androidx.core.content.ContextCompat",
"getDrawable(android.content.Context,int)"))
.add(methodRef("android.support.v7.app.AppCompatDialog", "<T>findViewById(int)"))
.add(methodRef("androidx.appcompat.app.AppCompatDialog", "<T>findViewById(int)"))
.add(
methodRef(
"android.support.v7.content.res.AppCompatResources",
"getDrawable(android.content.Context,int)"))
.add(
methodRef(
"androidx.appcompat.content.res.AppCompatResources",
"getDrawable(android.content.Context,int)"))
.add(methodRef("android.support.design.widget.TextInputLayout", "getEditText()"))
.build();
@Override
public ImmutableSetMultimap<MethodRef, Integer> failIfNullParameters() {
return FAIL_IF_NULL_PARAMETERS;
}
@Override
public ImmutableSetMultimap<MethodRef, Integer> explicitlyNullableParameters() {
return EXPLICITLY_NULLABLE_PARAMETERS;
}
@Override
public ImmutableSetMultimap<MethodRef, Integer> nonNullParameters() {
return NON_NULL_PARAMETERS;
}
@Override
public ImmutableSetMultimap<MethodRef, Integer> nullImpliesTrueParameters() {
return NULL_IMPLIES_TRUE_PARAMETERS;
}
@Override
public ImmutableSetMultimap<MethodRef, Integer> nullImpliesFalseParameters() {
return NULL_IMPLIES_FALSE_PARAMETERS;
}
@Override
public ImmutableSetMultimap<MethodRef, Integer> nullImpliesNullParameters() {
return NULL_IMPLIES_NULL_PARAMETERS;
}
@Override
public ImmutableSet<MethodRef> nullableReturns() {
return NULLABLE_RETURNS;
}
@Override
public ImmutableSet<MethodRef> nonNullReturns() {
return NONNULL_RETURNS;
}
}
private static class CombinedLibraryModels implements LibraryModels {
private final ImmutableSetMultimap<MethodRef, Integer> failIfNullParameters;
private final ImmutableSetMultimap<MethodRef, Integer> explicitlyNullableParameters;
private final ImmutableSetMultimap<MethodRef, Integer> nonNullParameters;
private final ImmutableSetMultimap<MethodRef, Integer> nullImpliesTrueParameters;
private final ImmutableSetMultimap<MethodRef, Integer> nullImpliesFalseParameters;
private final ImmutableSetMultimap<MethodRef, Integer> nullImpliesNullParameters;
private final ImmutableSet<MethodRef> nullableReturns;
private final ImmutableSet<MethodRef> nonNullReturns;
public CombinedLibraryModels(Iterable<LibraryModels> models) {
ImmutableSetMultimap.Builder<MethodRef, Integer> failIfNullParametersBuilder =
new ImmutableSetMultimap.Builder<>();
ImmutableSetMultimap.Builder<MethodRef, Integer> explicitlyNullableParametersBuilder =
new ImmutableSetMultimap.Builder<>();
ImmutableSetMultimap.Builder<MethodRef, Integer> nonNullParametersBuilder =
new ImmutableSetMultimap.Builder<>();
ImmutableSetMultimap.Builder<MethodRef, Integer> nullImpliesTrueParametersBuilder =
new ImmutableSetMultimap.Builder<>();
ImmutableSetMultimap.Builder<MethodRef, Integer> nullImpliesFalseParametersBuilder =
new ImmutableSetMultimap.Builder<>();
ImmutableSetMultimap.Builder<MethodRef, Integer> nullImpliesNullParametersBuilder =
new ImmutableSetMultimap.Builder<>();
ImmutableSet.Builder<MethodRef> nullableReturnsBuilder = new ImmutableSet.Builder<>();
ImmutableSet.Builder<MethodRef> nonNullReturnsBuilder = new ImmutableSet.Builder<>();
for (LibraryModels libraryModels : models) {
for (Map.Entry<MethodRef, Integer> entry : libraryModels.failIfNullParameters().entries()) {
failIfNullParametersBuilder.put(entry);
}
for (Map.Entry<MethodRef, Integer> entry :
libraryModels.explicitlyNullableParameters().entries()) {
explicitlyNullableParametersBuilder.put(entry);
}
for (Map.Entry<MethodRef, Integer> entry : libraryModels.nonNullParameters().entries()) {
nonNullParametersBuilder.put(entry);
}
for (Map.Entry<MethodRef, Integer> entry :
libraryModels.nullImpliesTrueParameters().entries()) {
nullImpliesTrueParametersBuilder.put(entry);
}
for (Map.Entry<MethodRef, Integer> entry :
libraryModels.nullImpliesFalseParameters().entries()) {
nullImpliesFalseParametersBuilder.put(entry);
}
for (Map.Entry<MethodRef, Integer> entry :
libraryModels.nullImpliesNullParameters().entries()) {
nullImpliesNullParametersBuilder.put(entry);
}
for (MethodRef name : libraryModels.nullableReturns()) {
nullableReturnsBuilder.add(name);
}
for (MethodRef name : libraryModels.nonNullReturns()) {
nonNullReturnsBuilder.add(name);
}
}
failIfNullParameters = failIfNullParametersBuilder.build();
explicitlyNullableParameters = explicitlyNullableParametersBuilder.build();
nonNullParameters = nonNullParametersBuilder.build();
nullImpliesTrueParameters = nullImpliesTrueParametersBuilder.build();
nullImpliesFalseParameters = nullImpliesFalseParametersBuilder.build();
nullImpliesNullParameters = nullImpliesNullParametersBuilder.build();
nullableReturns = nullableReturnsBuilder.build();
nonNullReturns = nonNullReturnsBuilder.build();
}
@Override
public ImmutableSetMultimap<MethodRef, Integer> failIfNullParameters() {
return failIfNullParameters;
}
@Override
public ImmutableSetMultimap<MethodRef, Integer> explicitlyNullableParameters() {
return explicitlyNullableParameters;
}
@Override
public ImmutableSetMultimap<MethodRef, Integer> nonNullParameters() {
return nonNullParameters;
}
@Override
public ImmutableSetMultimap<MethodRef, Integer> nullImpliesTrueParameters() {
return nullImpliesTrueParameters;
}
@Override
public ImmutableSetMultimap<MethodRef, Integer> nullImpliesFalseParameters() {
return nullImpliesFalseParameters;
}
@Override
public ImmutableSetMultimap<MethodRef, Integer> nullImpliesNullParameters() {
return nullImpliesNullParameters;
}
@Override
public ImmutableSet<MethodRef> nullableReturns() {
return nullableReturns;
}
@Override
public ImmutableSet<MethodRef> nonNullReturns() {
return nonNullReturns;
}
}
/**
* A view of library models optimized to make lookup of {@link
* com.sun.tools.javac.code.Symbol.MethodSymbol}s fast
*/
private static class OptimizedLibraryModels {
/**
* Mapping from {@link MethodRef} to some state, where lookups first check for a matching method
* name as an optimization. The {@link Name} data structure is used to avoid unnecessary String
* conversions when looking up {@link com.sun.tools.javac.code.Symbol.MethodSymbol}s.
*
* @param <T> the type of the associated state.
*/
private static class NameIndexedMap<T> {
private final Map<Name, Map<MethodRef, T>> state;
NameIndexedMap(Map<Name, Map<MethodRef, T>> state) {
this.state = state;
}
@Nullable
public T get(Symbol.MethodSymbol symbol) {
Map<MethodRef, T> methodRefTMap = state.get(symbol.name);
if (methodRefTMap == null) {
return null;
}
MethodRef ref = MethodRef.fromSymbol(symbol);
return methodRefTMap.get(ref);
}
public boolean nameNotPresent(Symbol.MethodSymbol symbol) {
return state.get(symbol.name) == null;
}
}
private final NameIndexedMap<ImmutableSet<Integer>> failIfNullParams;
private final NameIndexedMap<ImmutableSet<Integer>> explicitlyNullableParams;
private final NameIndexedMap<ImmutableSet<Integer>> nonNullParams;
private final NameIndexedMap<ImmutableSet<Integer>> nullImpliesTrueParams;
private final NameIndexedMap<ImmutableSet<Integer>> nullImpliesFalseParams;
private final NameIndexedMap<ImmutableSet<Integer>> nullImpliesNullParams;
private final NameIndexedMap<Boolean> nullableRet;
private final NameIndexedMap<Boolean> nonNullRet;
public OptimizedLibraryModels(LibraryModels models, Context context) {
Names names = Names.instance(context);
failIfNullParams = makeOptimizedIntSetLookup(names, models.failIfNullParameters());
explicitlyNullableParams =
makeOptimizedIntSetLookup(names, models.explicitlyNullableParameters());
nonNullParams = makeOptimizedIntSetLookup(names, models.nonNullParameters());
nullImpliesTrueParams = makeOptimizedIntSetLookup(names, models.nullImpliesTrueParameters());
nullImpliesFalseParams =
makeOptimizedIntSetLookup(names, models.nullImpliesFalseParameters());
nullImpliesNullParams = makeOptimizedIntSetLookup(names, models.nullImpliesNullParameters());
nullableRet = makeOptimizedBoolLookup(names, models.nullableReturns());
nonNullRet = makeOptimizedBoolLookup(names, models.nonNullReturns());
}
public boolean hasNonNullReturn(Symbol.MethodSymbol symbol, Types types) {
return lookupHandlingOverrides(symbol, types, nonNullRet) != null;
}
public boolean hasNullableReturn(Symbol.MethodSymbol symbol, Types types) {
return lookupHandlingOverrides(symbol, types, nullableRet) != null;
}
ImmutableSet<Integer> failIfNullParameters(Symbol.MethodSymbol symbol) {
return lookupImmutableSet(symbol, failIfNullParams);
}
ImmutableSet<Integer> explicitlyNullableParameters(Symbol.MethodSymbol symbol) {
return lookupImmutableSet(symbol, explicitlyNullableParams);
}
ImmutableSet<Integer> nonNullParameters(Symbol.MethodSymbol symbol) {
return lookupImmutableSet(symbol, nonNullParams);
}
ImmutableSet<Integer> nullImpliesTrueParameters(Symbol.MethodSymbol symbol) {
return lookupImmutableSet(symbol, nullImpliesTrueParams);
}
ImmutableSet<Integer> nullImpliesFalseParameters(Symbol.MethodSymbol symbol) {
return lookupImmutableSet(symbol, nullImpliesFalseParams);
}
ImmutableSet<Integer> nullImpliesNullParameters(Symbol.MethodSymbol symbol) {
return lookupImmutableSet(symbol, nullImpliesNullParams);
}
private ImmutableSet<Integer> lookupImmutableSet(
Symbol.MethodSymbol symbol, NameIndexedMap<ImmutableSet<Integer>> lookup) {
ImmutableSet<Integer> result = lookup.get(symbol);
return (result == null) ? ImmutableSet.of() : result;
}
private NameIndexedMap<ImmutableSet<Integer>> makeOptimizedIntSetLookup(
Names names, ImmutableSetMultimap<MethodRef, Integer> ref2Ints) {
return makeOptimizedLookup(names, ref2Ints.keySet(), ref2Ints::get);
}
private NameIndexedMap<Boolean> makeOptimizedBoolLookup(
Names names, ImmutableSet<MethodRef> refs) {
return makeOptimizedLookup(names, refs, (ref) -> true);
}
private <T> NameIndexedMap<T> makeOptimizedLookup(
Names names, Set<MethodRef> refs, Function<MethodRef, T> getValForRef) {
Map<Name, Map<MethodRef, T>> nameMapping = new LinkedHashMap<>();
for (MethodRef ref : refs) {
Name methodName = names.fromString(ref.methodName);
Map<MethodRef, T> mapForName = nameMapping.get(methodName);
if (mapForName == null) {
mapForName = new LinkedHashMap<>();
nameMapping.put(methodName, mapForName);
}
mapForName.put(ref, getValForRef.apply(ref));
}
return new NameIndexedMap<>(nameMapping);
}
/**
* checks if symbol is present in the NameIndexedMap or if it overrides some method in the
* NameIndexedMap
*/
@Nullable
private static Symbol.MethodSymbol lookupHandlingOverrides(
Symbol.MethodSymbol symbol, Types types, NameIndexedMap<Boolean> optLookup) {
if (optLookup.nameNotPresent(symbol)) {
// no model matching the method name, so we don't need to check for overridden methods
return null;
}
if (optLookup.get(symbol) != null) {
return symbol;
}
for (Symbol.MethodSymbol superSymbol : ASTHelpers.findSuperMethods(symbol, types)) {
if (optLookup.get(superSymbol) != null) {
return superSymbol;
}
}
return null;
}
}
}