blob: dddb33408acb9eaacce91c474571ec5020a2e6c3 [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.base.Strings;
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.uber.nullaway.LibraryModels;
import com.uber.nullaway.NullAway;
import com.uber.nullaway.Nullness;
import com.uber.nullaway.dataflow.AccessPath;
import com.uber.nullaway.dataflow.AccessPathNullnessPropagation;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
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;
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,
libraryModels.nonNullParameters().get(LibraryModels.MethodRef.fromSymbol(methodSymbol)))
.immutableCopy();
}
@Override
public boolean onOverrideMayBeNullExpr(
NullAway analysis, ExpressionTree expr, VisitorState state, boolean exprMayBeNull) {
if (expr.getKind() == Tree.Kind.METHOD_INVOCATION
&& LibraryModels.LibraryModelUtil.hasNullableReturn(
libraryModels, (Symbol.MethodSymbol) ASTHelpers.getSymbol(expr), state.getTypes())) {
return analysis.nullnessFromDataflow(state, expr) || exprMayBeNull;
}
return exprMayBeNull;
}
@Override
public Nullness onDataflowVisitMethodInvocation(
MethodInvocationNode node,
Types types,
AccessPathNullnessPropagation.Updates thenUpdates,
AccessPathNullnessPropagation.Updates elseUpdates,
AccessPathNullnessPropagation.Updates bothUpdates) {
Symbol.MethodSymbol callee = ASTHelpers.getSymbol(node.getTree());
Preconditions.checkNotNull(callee);
setUnconditionalArgumentNullness(bothUpdates, node.getArguments(), callee);
setConditionalArgumentNullness(thenUpdates, elseUpdates, node.getArguments(), callee);
return LibraryModels.LibraryModelUtil.hasNullableReturn(libraryModels, callee, types)
? Nullness.NULLABLE
: Nullness.NONNULL;
}
private void setConditionalArgumentNullness(
AccessPathNullnessPropagation.Updates thenUpdates,
AccessPathNullnessPropagation.Updates elseUpdates,
List<Node> arguments,
Symbol.MethodSymbol callee) {
Set<Integer> nullImpliesTrueParameters =
libraryModels.nullImpliesTrueParameters().get(LibraryModels.MethodRef.fromSymbol(callee));
for (AccessPath accessPath : accessPathsAtIndexes(nullImpliesTrueParameters, arguments)) {
elseUpdates.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.getAccessPathForNode(argument);
if (ap != null) {
result.add(ap);
}
}
}
return result;
}
private void setUnconditionalArgumentNullness(
AccessPathNullnessPropagation.Updates bothUpdates,
List<Node> arguments,
Symbol.MethodSymbol callee) {
Set<Integer> requiredNonNullParameters =
libraryModels.failIfNullParameters().get(LibraryModels.MethodRef.fromSymbol(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(Preconditions.class, "<T>checkNotNull(T)"), 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("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.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(Strings.class, "isNullOrEmpty(java.lang.String)"), 0)
.put(methodRef("android.text.TextUtils", "isEmpty(java.lang.CharSequence)"), 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()"))
.build();
@Override
public ImmutableSetMultimap<MethodRef, Integer> failIfNullParameters() {
return FAIL_IF_NULL_PARAMETERS;
}
@Override
public ImmutableSetMultimap<MethodRef, Integer> nonNullParameters() {
return NON_NULL_PARAMETERS;
}
@Override
public ImmutableSetMultimap<MethodRef, Integer> nullImpliesTrueParameters() {
return NULL_IMPLIES_TRUE_PARAMETERS;
}
@Override
public ImmutableSet<MethodRef> nullableReturns() {
return NULLABLE_RETURNS;
}
}
private static class CombinedLibraryModels implements LibraryModels {
private final ImmutableSetMultimap<MethodRef, Integer> fail_if_null_parameters;
private final ImmutableSetMultimap<MethodRef, Integer> non_null_parameters;
private final ImmutableSetMultimap<MethodRef, Integer> null_implies_true_parameters;
private final ImmutableSet<MethodRef> nullable_returns;
public CombinedLibraryModels(Iterable<LibraryModels> models) {
ImmutableSetMultimap.Builder<MethodRef, Integer> failIfNullParametersBuilder =
new ImmutableSetMultimap.Builder<>();
ImmutableSetMultimap.Builder<MethodRef, Integer> nonNullParametersBuilder =
new ImmutableSetMultimap.Builder<>();
ImmutableSetMultimap.Builder<MethodRef, Integer> nullImpliesTrueParametersBuilder =
new ImmutableSetMultimap.Builder<>();
ImmutableSet.Builder<MethodRef> nullableReturnsBuilder = 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.nonNullParameters().entries()) {
nonNullParametersBuilder.put(entry);
}
for (Map.Entry<MethodRef, Integer> entry :
libraryModels.nullImpliesTrueParameters().entries()) {
nullImpliesTrueParametersBuilder.put(entry);
}
for (MethodRef name : libraryModels.nullableReturns()) {
nullableReturnsBuilder.add(name);
}
}
fail_if_null_parameters = failIfNullParametersBuilder.build();
non_null_parameters = nonNullParametersBuilder.build();
null_implies_true_parameters = nullImpliesTrueParametersBuilder.build();
nullable_returns = nullableReturnsBuilder.build();
}
@Override
public ImmutableSetMultimap<MethodRef, Integer> failIfNullParameters() {
return fail_if_null_parameters;
}
@Override
public ImmutableSetMultimap<MethodRef, Integer> nonNullParameters() {
return non_null_parameters;
}
@Override
public ImmutableSetMultimap<MethodRef, Integer> nullImpliesTrueParameters() {
return null_implies_true_parameters;
}
@Override
public ImmutableSet<MethodRef> nullableReturns() {
return nullable_returns;
}
}
}