/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.errorprone.dataflow.nullnesspropagation;
import com.google.common.base.Predicate;
import com.google.errorprone.dataflow.LocalStore;
import java.util.List;
import javax.annotation.Nullable;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import org.checkerframework.dataflow.cfg.UnderlyingAST;
import org.checkerframework.dataflow.cfg.node.LocalVariableNode;
/**
* Transfer function for {@link TrustingNullnessAnalysis}. It "trusts" annotations, meaning:
* <ul>
* <li>The parameters of the analyzed method are assumed non-null unless annotated {@code Nullable}.
* <li>Field reads and method calls are assumed to return non-null unless annotated.
* </ul>
*
* <p>This transfer function also uses {@link Nullness#NONNULL} as its default, which means:
* <ul>
* <li>all array reads are assumed non-null. In the absence of Java 8 type annotations that matches
* what we'll do for the result of {@link List#get} etc. Will need to revisit with Java 8.
* <li>we'll assume non-null for local variables we don't have any information about. Since we seed
* {@link #initialStore} based on annotations on method parameters, the only known source of
* unknown locals would be "local variables" from outer scopes accessed in anonymous and local
* inner classes.
* <li>in the case of missing method or field symbols, non-null is assumed as well.
* </ul>
*/
// TODO(kmb): Respect type annotations on arrays
// TODO(kmb): Use annotations on captured locals from outer scopes
class TrustingNullnessPropagation extends NullnessPropagationTransfer {
private static final long serialVersionUID = -3128676755493202966L;
TrustingNullnessPropagation() {
super(Nullness.NONNULL, TrustReturnAnnotation.INSTANCE);
}
@Override
public LocalStore<Nullness> initialStore(
UnderlyingAST underlyingAST, List<LocalVariableNode> parameters) {
if (parameters == null) {
// Documentation of this method states, "parameters is only set if the underlying AST is a
// method"
return LocalStore.empty();
}
LocalStore.Builder<Nullness> result = LocalStore.<Nullness>empty().toBuilder();
for (LocalVariableNode param : parameters) {
Element element = param.getElement();
Nullness assumed = nullnessFromAnnotations(element);
result.setInformation(element, assumed);
}
return result.build();
}
@Override
Nullness fieldNullness(@Nullable ClassAndField accessed) {
if (accessed == null) {
return Nullness.NONNULL; // optimistically assume non-null if we can't resolve
}
// In the absence of annotations, this will do the right thing for things like primitives,
// array length, .class, etc.
return nullnessFromAnnotations(accessed.symbol);
}
/**
* Returns nullability based on the presence of a {@code Nullable} annotation.
*/
static Nullness nullnessFromAnnotations(Element element) {
for (AnnotationMirror anno : element.getAnnotationMirrors()) {
// Check for Nullable like ReturnValueIsNonNull
if (anno.getAnnotationType().toString().endsWith(".Nullable")) {
return Nullness.NULLABLE;
}
}
return Nullness.NONNULL;
}
private enum TrustReturnAnnotation implements Predicate<MethodInfo> {
INSTANCE;
/**
* Returns {@code true} where {@link #nullnessFromAnnotations} would return
* {@link Nullness#NONNULL}.
*/
@Override
public boolean apply(MethodInfo input) {
for (String annotation : input.annotations()) {
if (annotation.endsWith(".Nullable")) {
return false;
}
}
return true;
}
}
}