blob: 8771415cf7bf281e226297678adf33f5bdd6bf6d [file] [log] [blame]
/*
* Copyright (C) 2021 The Dagger Authors.
*
* 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 dagger.internal.codegen.binding;
import static androidx.room.compiler.processing.compat.XConverters.toJavac;
import static com.google.auto.common.MoreElements.asType;
import static com.google.auto.common.MoreElements.isType;
import static com.google.auto.common.MoreTypes.asDeclared;
import androidx.room.compiler.processing.XAnnotation;
import androidx.room.compiler.processing.XElement;
import androidx.room.compiler.processing.XExecutableElement;
import androidx.room.compiler.processing.XTypeElement;
import com.google.auto.common.AnnotationMirrors;
import com.google.auto.common.MoreTypes;
import com.google.common.base.Ascii;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.AnnotationValueVisitor;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ElementVisitor;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.ErrorType;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVisitor;
import javax.lang.model.type.WildcardType;
import javax.lang.model.util.AbstractElementVisitor8;
import javax.lang.model.util.SimpleAnnotationValueVisitor8;
import javax.lang.model.util.SimpleTypeVisitor8;
/**
* A fork of {@link com.google.auto.common.SuperficialValidation} that exposes validation for things
* like annotations and annotation values.
*/
// TODO(bcorso): Consider contributing this to Auto-Common's SuperficialValidation.
public final class DaggerSuperficialValidation {
/**
* Validates the {@link XElement#getType()} type of the given element.
*
* <p>Validating the type also validates any types it references, such as any type arguments or
* type bounds. For an {@link ExecutableType}, the parameter and return types must be fully
* defined, as must types declared in a {@code throws} clause or in the bounds of any type
* parameters.
*/
public static void validateTypeOf(XElement element) {
validateTypeOf(toJavac(element));
}
private static void validateTypeOf(Element element) {
try {
validateType(Ascii.toLowerCase(element.getKind().name()), element.asType());
} catch (RuntimeException exception) {
throw ValidationException.from(exception).append(element);
}
}
/**
* Validates the {@link XElement#getSuperType()} type of the given element.
*
* <p>Validating the type also validates any types it references, such as any type arguments or
* type bounds.
*/
public static void validateSuperTypeOf(XTypeElement element) {
validateSuperTypeOf(toJavac(element));
}
private static void validateSuperTypeOf(TypeElement element) {
try {
validateType("superclass", element.getSuperclass());
} catch (RuntimeException exception) {
throw ValidationException.from(exception).append(element);
}
}
/**
* Validates the {@link XExecutableElement#getThrownTypes()} types of the given element.
*
* <p>Validating the type also validates any types it references, such as any type arguments or
* type bounds.
*/
public static void validateThrownTypesOf(XExecutableElement element) {
validateThrownTypesOf(toJavac(element));
}
private static void validateThrownTypesOf(ExecutableElement element) {
try {
validateTypes("thrown type", element.getThrownTypes());
} catch (RuntimeException exception) {
throw ValidationException.from(exception).append(element);
}
}
/** Validate the annotations of the given element. */
public static void validateAnnotationsOf(XElement element) {
validateAnnotationsOf(toJavac(element));
}
public static void validateAnnotationsOf(Element element) {
try {
validateAnnotations(element.getAnnotationMirrors());
} catch (RuntimeException exception) {
throw ValidationException.from(exception).append(element);
}
}
public static void validateAnnotationOf(XElement element, XAnnotation annotation) {
validateAnnotationOf(toJavac(element), toJavac(annotation));
}
public static void validateAnnotationOf(Element element, AnnotationMirror annotation) {
try {
validateAnnotation(annotation);
} catch (RuntimeException exception) {
throw ValidationException.from(exception).append(element);
}
}
/**
* Strictly validates the given annotation belonging to the given element.
*
* <p>This validation is considered "strict" because it will fail if an annotation's type is not
* valid. This fixes the bug described in b/213880825, but cannot be applied indiscriminately or
* it would break in many cases where we don't care about an annotation.
*
* <p>Note: This method does not actually check that the given annotation belongs to the given
* element. The element is only used to given better context to the error message in the case that
* validation fails.
*/
public static void strictValidateAnnotationsOf(XElement element) {
strictValidateAnnotationsOf(toJavac(element));
}
/**
* Strictly validates the given annotation belonging to the given element.
*
* <p>This validation is considered "strict" because it will fail if an annotation's type is not
* valid. This fixes the bug described in b/213880825, but cannot be applied indiscriminately or
* it would break in many cases where we don't care about an annotation.
*
* <p>Note: This method does not actually check that the given annotation belongs to the given
* element. The element is only used to given better context to the error message in the case that
* validation fails.
*/
public static void strictValidateAnnotationsOf(Element element) {
element
.getAnnotationMirrors()
.forEach(annotation -> strictValidateAnnotationOf(element, annotation));
}
/**
* Strictly validates the given annotation belonging to the given element.
*
* <p>This validation is considered "strict" because it will fail if an annotation's type is not
* valid. This fixes the bug described in b/213880825, but cannot be applied indiscriminately or
* it would break in many cases where we don't care about an annotation.
*
* <p>Note: This method does not actually check that the given annotation belongs to the given
* element. The element is only used to given better context to the error message in the case that
* validation fails.
*/
public static void strictValidateAnnotationOf(XElement element, XAnnotation annotation) {
strictValidateAnnotationOf(toJavac(element), toJavac(annotation));
}
/**
* Strictly validates the given annotation belonging to the given element.
*
* <p>This validation is considered "strict" because it will fail if an annotation's type is not
* valid. This fixes the bug described in b/213880825, but cannot be applied indiscriminately or
* it would break in many cases where we don't care about an annotation.
*
* <p>Note: This method does not actually check that the given annotation belongs to the given
* element. The element is only used to given better context to the error message in the case that
* validation fails.
*/
public static void strictValidateAnnotationOf(Element element, AnnotationMirror annotation) {
try {
strictValidateAnnotation(annotation);
} catch (RuntimeException exception) {
throw ValidationException.from(exception).append(element);
}
}
/**
* Returns true if all of the given elements return true from {@link #validateElement(Element)}.
*/
public static void validateElements(Iterable<? extends Element> elements) {
for (Element element : elements) {
validateElement(element);
}
}
private static final ElementVisitor<Void, Void> ELEMENT_VALIDATING_VISITOR =
new AbstractElementVisitor8<Void, Void>() {
@Override
public Void visitPackage(PackageElement e, Void p) {
// don't validate enclosed elements because it will return types in the package
validateAnnotations(e.getAnnotationMirrors());
return null;
}
@Override
public Void visitType(TypeElement e, Void p) {
validateBaseElement(e);
validateElements(e.getTypeParameters());
validateTypes("interface", e.getInterfaces());
validateType("superclass", e.getSuperclass());
return null;
}
@Override
public Void visitVariable(VariableElement e, Void p) {
validateBaseElement(e);
return null;
}
@Override
public Void visitExecutable(ExecutableElement e, Void p) {
AnnotationValue defaultValue = e.getDefaultValue();
validateBaseElement(e);
if (defaultValue != null) {
validateAnnotationValue(defaultValue, e.getReturnType());
}
validateType("return type", e.getReturnType());
validateTypes("thrown type", e.getThrownTypes());
validateElements(e.getTypeParameters());
validateElements(e.getParameters());
return null;
}
@Override
public Void visitTypeParameter(TypeParameterElement e, Void p) {
validateBaseElement(e);
validateTypes("bound type", e.getBounds());
return null;
}
@Override
public Void visitUnknown(Element e, Void p) {
// just assume that unknown elements are OK
return null;
}
};
/**
* Returns true if all types referenced by the given element are defined. The exact meaning of
* this depends on the kind of element. For packages, it means that all annotations on the package
* are fully defined. For other element kinds, it means that types referenced by the element,
* anything it contains, and any of its annotations element are all defined.
*/
public static void validateElement(XElement element) {
validateElement(toJavac(element));
}
/**
* Returns true if all types referenced by the given element are defined. The exact meaning of
* this depends on the kind of element. For packages, it means that all annotations on the package
* are fully defined. For other element kinds, it means that types referenced by the element,
* anything it contains, and any of its annotations element are all defined.
*/
public static void validateElement(Element element) {
try {
element.accept(ELEMENT_VALIDATING_VISITOR, null);
} catch (RuntimeException exception) {
throw ValidationException.from(exception).append(element);
}
}
private static void validateBaseElement(Element e) {
validateType(Ascii.toLowerCase(e.getKind().name()), e.asType());
validateAnnotations(e.getAnnotationMirrors());
validateElements(e.getEnclosedElements());
}
private static void validateTypes(String desc, Iterable<? extends TypeMirror> types) {
for (TypeMirror type : types) {
validateType(desc, type);
}
}
/*
* This visitor does not test type variables specifically, but it seems that that is not actually
* an issue. Javac turns the whole type parameter into an error type if it can't figure out the
* bounds.
*/
private static final TypeVisitor<Void, Void> TYPE_VALIDATING_VISITOR =
new SimpleTypeVisitor8<Void, Void>() {
@Override
protected Void defaultAction(TypeMirror t, Void p) {
return null;
}
@Override
public Void visitArray(ArrayType t, Void p) {
validateType("array component type", t.getComponentType());
return null;
}
@Override
public Void visitDeclared(DeclaredType t, Void p) {
validateTypes("type argument", t.getTypeArguments());
return null;
}
@Override
public Void visitError(ErrorType t, Void p) {
throw new ValidationException.KnownErrorType(t);
}
@Override
public Void visitUnknown(TypeMirror t, Void p) {
// just make the default choice for unknown types
return defaultAction(t, p);
}
@Override
public Void visitWildcard(WildcardType t, Void p) {
TypeMirror extendsBound = t.getExtendsBound();
TypeMirror superBound = t.getSuperBound();
if (extendsBound != null) {
validateType("extends bound type", extendsBound);
}
if (superBound != null) {
validateType("super bound type", superBound);
}
return null;
}
@Override
public Void visitExecutable(ExecutableType t, Void p) {
validateTypes("parameter type", t.getParameterTypes());
validateType("return type", t.getReturnType());
validateTypes("thrown type", t.getThrownTypes());
validateTypes("type variable", t.getTypeVariables());
return null;
}
};
/**
* Returns true if the given type is fully defined. This means that the type itself is defined, as
* are any types it references, such as any type arguments or type bounds. For an {@link
* ExecutableType}, the parameter and return types must be fully defined, as must types declared
* in a {@code throws} clause or in the bounds of any type parameters.
*/
private static void validateType(String desc, TypeMirror type) {
try {
type.accept(TYPE_VALIDATING_VISITOR, null);
} catch (RuntimeException e) {
throw ValidationException.from(e)
.append(String.format("type (%s %s): %s", type.getKind().name(), desc, type));
}
}
private static void validateAnnotations(Iterable<? extends AnnotationMirror> annotationMirrors) {
for (AnnotationMirror annotationMirror : annotationMirrors) {
validateAnnotation(annotationMirror);
}
}
/**
* This validation is the same as {@link #validateAnnotation(AnnotationMirror)} but also validates
* the annotation's kind directly to look for {@link TypeKind.ERROR} types.
*
* <p>See b/213880825.
*/
// TODO(bcorso): Merge this into the normal validateAnnotation() method. For now, this method is
// separated to avoid breaking existing usages that aren't setup to handle this extra validation.
private static void strictValidateAnnotation(AnnotationMirror annotationMirror) {
try {
validateType("annotation type", annotationMirror.getAnnotationType());
// There's a bug in TypeVisitor specifically when validating annotation types which will
// visit the visitDeclared() method rather than visitError() even when it's an ERROR kind.
// Thus, we check the kind directly here and fail validation if it's an ERROR kind.
if (annotationMirror.getAnnotationType().getKind() == TypeKind.ERROR) {
throw new ValidationException.KnownErrorType(annotationMirror.getAnnotationType());
}
validateAnnotationValues(annotationMirror.getElementValues());
} catch (RuntimeException exception) {
throw ValidationException.from(exception).append(annotationMirror);
}
}
private static void validateAnnotation(AnnotationMirror annotationMirror) {
try {
validateType("annotation type", annotationMirror.getAnnotationType());
validateAnnotationValues(annotationMirror.getElementValues());
} catch (RuntimeException exception) {
throw ValidationException.from(exception).append(annotationMirror);
}
}
private static void validateAnnotationValues(
Map<? extends ExecutableElement, ? extends AnnotationValue> valueMap) {
valueMap.forEach(
(method, annotationValue) -> {
try {
TypeMirror expectedType = method.getReturnType();
validateAnnotationValue(annotationValue, expectedType);
} catch (RuntimeException exception) {
throw ValidationException.from(exception)
.append(String.format("annotation method: %s %s", method.getReturnType(), method));
}
});
}
private static final AnnotationValueVisitor<Void, TypeMirror> VALUE_VALIDATING_VISITOR =
new SimpleAnnotationValueVisitor8<Void, TypeMirror>() {
@Override
protected Void defaultAction(Object o, TypeMirror expectedType) {
try {
validateIsTypeOf(o.getClass(), expectedType);
} catch (RuntimeException exception) {
throw ValidationException.from(exception)
.append(exceptionMessage("DEFAULT", o, expectedType));
}
return null;
}
@Override
public Void visitString(String str, TypeMirror expectedType) {
try {
if (!MoreTypes.isTypeOf(String.class, expectedType)) {
if (str.contentEquals("<error>")) {
// Invalid annotation value types will visit visitString() with a value of "<error>"
// Technically, we don't know the error type in this case, but it will be referred
// to as "<error>" in the dependency trace, so we use that.
throw new ValidationException.KnownErrorType("<error>");
} else {
throw new ValidationException.UnknownErrorType();
}
}
} catch (RuntimeException exception) {
throw ValidationException.from(exception)
.append(exceptionMessage("STRING", str, expectedType));
}
return null;
}
@Override
public Void visitUnknown(AnnotationValue av, TypeMirror expectedType) {
// just take the default action for the unknown
defaultAction(av, expectedType);
return null;
}
@Override
public Void visitAnnotation(AnnotationMirror a, TypeMirror expectedType) {
try {
validateIsEquivalentType(a.getAnnotationType(), expectedType);
validateAnnotation(a);
} catch (RuntimeException exception) {
throw ValidationException.from(exception)
.append(exceptionMessage("ANNOTATION", a, expectedType));
}
return null;
}
@Override
public Void visitArray(List<? extends AnnotationValue> values, TypeMirror expectedType) {
try {
if (!expectedType.getKind().equals(TypeKind.ARRAY)) {
throw new ValidationException.UnknownErrorType();
}
TypeMirror componentType = MoreTypes.asArray(expectedType).getComponentType();
for (AnnotationValue value : values) {
value.accept(this, componentType);
}
} catch (RuntimeException exception) {
throw ValidationException.from(exception)
.append(exceptionMessage("ARRAY", values, expectedType));
}
return null;
}
@Override
public Void visitEnumConstant(VariableElement enumConstant, TypeMirror expectedType) {
try {
validateIsEquivalentType(asDeclared(enumConstant.asType()), expectedType);
validateElement(enumConstant);
} catch (RuntimeException exception) {
throw ValidationException.from(exception)
.append(exceptionMessage("ENUM_CONSTANT", enumConstant, expectedType));
}
return null;
}
@Override
public Void visitType(TypeMirror type, TypeMirror expectedType) {
try {
// We could check assignability here, but would require a Types instance. Since this
// isn't really the sort of thing that shows up in a bad AST from upstream compilation
// we ignore the expected type and just validate the type. It might be wrong, but
// it's valid.
validateType("annotation value type", type);
} catch (RuntimeException exception) {
throw ValidationException.from(exception)
.append(exceptionMessage("TYPE", type, expectedType));
}
return null;
}
@Override
public Void visitBoolean(boolean b, TypeMirror expectedType) {
try {
validateIsTypeOf(Boolean.TYPE, expectedType);
} catch (RuntimeException exception) {
throw ValidationException.from(exception)
.append(exceptionMessage("BOOLEAN", b, expectedType));
}
return null;
}
@Override
public Void visitByte(byte b, TypeMirror expectedType) {
try {
validateIsTypeOf(Byte.TYPE, expectedType);
} catch (RuntimeException exception) {
throw ValidationException.from(exception)
.append(exceptionMessage("BYTE", b, expectedType));
}
return null;
}
@Override
public Void visitChar(char c, TypeMirror expectedType) {
try {
validateIsTypeOf(Character.TYPE, expectedType);
} catch (RuntimeException exception) {
throw ValidationException.from(exception)
.append(exceptionMessage("CHAR", c, expectedType));
}
return null;
}
@Override
public Void visitDouble(double d, TypeMirror expectedType) {
try {
validateIsTypeOf(Double.TYPE, expectedType);
} catch (RuntimeException exception) {
throw ValidationException.from(exception)
.append(exceptionMessage("DOUBLE", d, expectedType));
}
return null;
}
@Override
public Void visitFloat(float f, TypeMirror expectedType) {
try {
validateIsTypeOf(Float.TYPE, expectedType);
} catch (RuntimeException exception) {
throw ValidationException.from(exception)
.append(exceptionMessage("FLOAT", f, expectedType));
}
return null;
}
@Override
public Void visitInt(int i, TypeMirror expectedType) {
try {
validateIsTypeOf(Integer.TYPE, expectedType);
} catch (RuntimeException exception) {
throw ValidationException.from(exception)
.append(exceptionMessage("INT", i, expectedType));
}
return null;
}
@Override
public Void visitLong(long l, TypeMirror expectedType) {
try {
validateIsTypeOf(Long.TYPE, expectedType);
} catch (RuntimeException exception) {
throw ValidationException.from(exception)
.append(exceptionMessage("LONG", l, expectedType));
}
return null;
}
@Override
public Void visitShort(short s, TypeMirror expectedType) {
try {
validateIsTypeOf(Short.TYPE, expectedType);
} catch (RuntimeException exception) {
throw ValidationException.from(exception)
.append(exceptionMessage("SHORT", s, expectedType));
}
return null;
}
private <T> String exceptionMessage(String valueType, T value, TypeMirror expectedType) {
return String.format(
"annotation value (%s): value '%s' with expected type %s",
valueType, value, expectedType);
}
};
private static void validateIsTypeOf(Class<?> clazz, TypeMirror expectedType) {
if (!MoreTypes.isTypeOf(clazz, expectedType)) {
throw new ValidationException.UnknownErrorType();
}
}
private static void validateIsEquivalentType(DeclaredType type, TypeMirror expectedType) {
if (!MoreTypes.equivalence().equivalent(type, expectedType)) {
throw new ValidationException.KnownErrorType(type);
}
}
/**
* A runtime exception that can be used during superficial validation to collect information about
* unexpected exceptions during validation.
*/
public abstract static class ValidationException extends RuntimeException {
/** A {@link ValidationException} that originated from an unexpected exception. */
public static final class UnexpectedException extends ValidationException {
private UnexpectedException(Throwable throwable) {
super(throwable);
}
}
/** A {@link ValidationException} that originated from a known error type. */
public static final class KnownErrorType extends ValidationException {
private final String errorTypeName;
private KnownErrorType(DeclaredType errorType) {
Element errorElement = errorType.asElement();
this.errorTypeName =
isType(errorElement)
? asType(errorElement).getQualifiedName().toString()
// Maybe this case should be handled by UnknownErrorType?
: errorElement.getSimpleName().toString();
}
private KnownErrorType(String errorTypeName) {
this.errorTypeName = errorTypeName;
}
public String getErrorTypeName() {
return errorTypeName;
}
}
/** A {@link ValidationException} that originated from an unknown error type. */
public static final class UnknownErrorType extends ValidationException {}
private static ValidationException from(Throwable throwable) {
// We only ever create one instance of the ValidationException.
return throwable instanceof ValidationException
? ((ValidationException) throwable)
: new UnexpectedException(throwable);
}
private Optional<Element> lastReportedElement = Optional.empty();
private final List<String> messages = new ArrayList<>();
private ValidationException() {
super("");
}
private ValidationException(Throwable throwable) {
super("", throwable);
}
/**
* Appends a message for the given element and returns this instance of {@link
* ValidationException}
*/
private ValidationException append(Element element) {
lastReportedElement = Optional.of(element);
return append(getMessageForElement(element));
}
/**
* Appends a message for the given annotation mirror and returns this instance of {@link
* ValidationException}
*/
private ValidationException append(AnnotationMirror annotationMirror) {
// Note: Calling #toString() directly on the annotation throws NPE (b/216180336).
return append(String.format("annotation: %s", AnnotationMirrors.toString(annotationMirror)));
}
/** Appends the given message and returns this instance of {@link ValidationException} */
private ValidationException append(String message) {
messages.add(message);
return this;
}
@Override
public String getMessage() {
return String.format("\n Validation trace:\n => %s", getTrace());
}
public String getTrace() {
return String.join("\n => ", getMessageInternal().reverse());
}
private ImmutableList<String> getMessageInternal() {
if (!lastReportedElement.isPresent()) {
return ImmutableList.copyOf(messages);
}
// Append any enclosing element information if needed.
List<String> newMessages = new ArrayList<>(messages);
Element element = lastReportedElement.get();
while (shouldAppendEnclosingElement(element)) {
element = element.getEnclosingElement();
newMessages.add(getMessageForElement(element));
}
return ImmutableList.copyOf(newMessages);
}
private static boolean shouldAppendEnclosingElement(Element element) {
return element.getEnclosingElement() != null
// We don't report enclosing elements for types because the type name should contain any
// enclosing type and package information we need.
&& !isType(element)
&& (isExecutable(element.getEnclosingElement()) || isType(element.getEnclosingElement()));
}
private static boolean isExecutable(Element element) {
return element.getKind() == ElementKind.METHOD
|| element.getKind() == ElementKind.CONSTRUCTOR;
}
private String getMessageForElement(Element element) {
return String.format("element (%s): %s", element.getKind().name(), element);
}
}
private static void validateAnnotationValue(
AnnotationValue annotationValue, TypeMirror expectedType) {
annotationValue.accept(VALUE_VALIDATING_VISITOR, expectedType);
}
private DaggerSuperficialValidation() {}
}