blob: 4549028d00956b074f2ed2ccf3ea796923737d82 [file] [log] [blame]
package com.uber.nullaway.generics;
import static com.uber.nullaway.NullabilityUtil.castToNonNull;
import com.google.common.base.Preconditions;
import com.google.errorprone.VisitorState;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.AnnotatedTypeTree;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ArrayTypeTree;
import com.sun.source.tree.ParameterizedTypeTree;
import com.sun.source.tree.Tree;
import com.sun.source.util.SimpleTreeVisitor;
import com.sun.tools.javac.code.Attribute;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.TypeMetadata;
import com.sun.tools.javac.util.ListBuffer;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Visitor For getting the preserved Annotation Types for the nested generic type arguments within a
* ParameterizedTypeTree. This is required primarily since javac does not preserve annotations on
* generic type arguments in its types for NewClassTrees. We need a visitor since the nested
* arguments may appear on different kinds of type trees, e.g., ArrayTypeTrees.
*/
public class PreservedAnnotationTreeVisitor extends SimpleTreeVisitor<Type, Void> {
private final VisitorState state;
PreservedAnnotationTreeVisitor(VisitorState state) {
this.state = state;
}
@Override
public Type visitArrayType(ArrayTypeTree tree, Void p) {
Type elemType = tree.getType().accept(this, null);
return new Type.ArrayType(elemType, castToNonNull(ASTHelpers.getType(tree)).tsym);
}
@Override
public Type visitParameterizedType(ParameterizedTypeTree tree, Void p) {
Type.ClassType type = (Type.ClassType) ASTHelpers.getType(tree);
Preconditions.checkNotNull(type);
Type nullableType = GenericsChecks.JSPECIFY_NULLABLE_TYPE_SUPPLIER.get(state);
List<? extends Tree> typeArguments = tree.getTypeArguments();
List<Type> newTypeArgs = new ArrayList<>();
for (int i = 0; i < typeArguments.size(); i++) {
AnnotatedTypeTree annotatedType = null;
Tree curTypeArg = typeArguments.get(i);
// If the type argument has an annotation, it will either be an AnnotatedTypeTree, or a
// ParameterizedTypeTree in the case of a nested generic type
if (curTypeArg instanceof AnnotatedTypeTree) {
annotatedType = (AnnotatedTypeTree) curTypeArg;
} else if (curTypeArg instanceof ParameterizedTypeTree
&& ((ParameterizedTypeTree) curTypeArg).getType() instanceof AnnotatedTypeTree) {
annotatedType = (AnnotatedTypeTree) ((ParameterizedTypeTree) curTypeArg).getType();
}
List<? extends AnnotationTree> annotations =
annotatedType != null ? annotatedType.getAnnotations() : Collections.emptyList();
boolean hasNullableAnnotation = false;
for (AnnotationTree annotation : annotations) {
if (ASTHelpers.isSameType(
nullableType, ASTHelpers.getType(annotation.getAnnotationType()), state)) {
hasNullableAnnotation = true;
break;
}
}
// construct a TypeMetadata object containing a nullability annotation if needed
com.sun.tools.javac.util.List<Attribute.TypeCompound> nullableAnnotationCompound =
hasNullableAnnotation
? com.sun.tools.javac.util.List.from(
Collections.singletonList(
new Attribute.TypeCompound(
nullableType, com.sun.tools.javac.util.List.nil(), null)))
: com.sun.tools.javac.util.List.nil();
TypeMetadata typeMetadata = TYPE_METADATA_BUILDER.create(nullableAnnotationCompound);
Type currentTypeArgType = curTypeArg.accept(this, null);
Type newTypeArgType =
TYPE_METADATA_BUILDER.cloneTypeWithMetadata(currentTypeArgType, typeMetadata);
newTypeArgs.add(newTypeArgType);
}
Type.ClassType finalType =
new Type.ClassType(
type.getEnclosingType(), com.sun.tools.javac.util.List.from(newTypeArgs), type.tsym);
return finalType;
}
/** By default, just use the type computed by javac */
@Override
protected Type defaultAction(Tree node, Void unused) {
return castToNonNull(ASTHelpers.getType(node));
}
/**
* Abstracts over the different APIs for building {@link TypeMetadata} objects in different JDK
* versions.
*/
private interface TypeMetadataBuilder {
TypeMetadata create(com.sun.tools.javac.util.List<Attribute.TypeCompound> attrs);
Type cloneTypeWithMetadata(Type typeToBeCloned, TypeMetadata metaData);
static MethodHandle createAnnotationsCtorHandle(Class<?> paramTypeClass) {
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodType mt = MethodType.methodType(void.class, paramTypeClass);
try {
return lookup.findConstructor(TypeMetadata.Annotations.class, mt);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
/**
* Used to get a MethodHandle for a virtual method from the specified class
*
* @param retTypeClass Class to indicate the return type of the desired method
* @param paramTypeClass Class to indicate the parameter type of the desired method
* @param refClass Class within which the desired method is contained
* @param methodName Name of the desired method
* @return The appropriate MethodHandle for the virtual method
*/
static MethodHandle createVirtualMethodHandle(
Class<?> retTypeClass, Class<?> paramTypeClass, Class<?> refClass, String methodName) {
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodType mt = MethodType.methodType(retTypeClass, paramTypeClass);
try {
return lookup.findVirtual(refClass, methodName, mt);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
/**
* Provides implementations for methods under TypeMetadataBuilder compatible with JDK 17 and
* earlier versions.
*/
private static class JDK17AndEarlierTypeMetadataBuilder implements TypeMetadataBuilder {
private static final MethodHandle typeMetadataHandle =
TypeMetadataBuilder.createAnnotationsCtorHandle(com.sun.tools.javac.util.List.class);
private static final MethodHandle cloneHandle = TypeMetadataBuilder.createVirtualMethodHandle(
Type.class, TypeMetadata.class, Type.class, "cloneWithMetadata");
@Override
public TypeMetadata create(com.sun.tools.javac.util.List<Attribute.TypeCompound> attrs) {
try {
return (TypeMetadata) typeMetadataHandle.invoke(attrs);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
/**
* Clones the given type with the specified Metadata for getting the right nullability
* annotations.
*
* @param typeToBeCloned The Type we want to clone with the required Nullability Metadata
* @param metadata The required Nullability metadata which is lost from the type
* @return Type after it has been cloned by applying the required Nullability metadata
*/
@Override
public Type cloneTypeWithMetadata(Type typeToBeCloned, TypeMetadata metadata) {
try {
return (Type) cloneHandle.invoke(typeToBeCloned, metadata);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
}
/**
* Provides implementations for methods under TypeMetadataBuilder compatible with the updates made
* to the library methods for Jdk 21. The implementation calls the logic specific to JDK 21
* indirectly using MethodHandles since we still need the code to compile on earlier versions.
*/
private static class JDK21TypeMetadataBuilder implements TypeMetadataBuilder {
private static final MethodHandle typeMetadataHandle =
TypeMetadataBuilder.createAnnotationsCtorHandle(com.sun.tools.javac.util.ListBuffer.class);
private static final MethodHandle addMetadataHandle = TypeMetadataBuilder.createVirtualMethodHandle(
Type.class, TypeMetadata.class, Type.class, "addMetadata");
private static final MethodHandle dropMetadataHandle = TypeMetadataBuilder.createVirtualMethodHandle(
Type.class, Class.class, Type.class, "dropMetadata");
@Override
public TypeMetadata create(com.sun.tools.javac.util.List<Attribute.TypeCompound> attrs) {
ListBuffer<Attribute.TypeCompound> b = new ListBuffer<>();
b.appendList(attrs);
try {
return (TypeMetadata) typeMetadataHandle.invoke(b);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
/**
* Calls dropMetadata and addMetadata using MethodHandles for JDK 21, which removed the previous
* cloneWithMetadata method.
*
* @param typeToBeCloned The Type we want to clone with the required Nullability metadata
* @param metadata The required Nullability metadata
* @return Cloned Type with the necessary Nullability metadata
*/
@Override
public Type cloneTypeWithMetadata(Type typeToBeCloned, TypeMetadata metadata) {
try {
// In JDK 21 addMetadata works if there is no metadata associated with the type, so we
// create a copy without the existing metadata first and then add it
Type clonedTypeWithoutMetadata =
(Type) dropMetadataHandle.invoke(typeToBeCloned, metadata.getClass());
return (Type) addMetadataHandle.invoke(clonedTypeWithoutMetadata, metadata);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
}
/** The TypeMetadataBuilder to be used for the current JDK version. */
private static final TypeMetadataBuilder TYPE_METADATA_BUILDER =
getVersion() >= 21
? new JDK21TypeMetadataBuilder()
: new JDK17AndEarlierTypeMetadataBuilder();
/**
* Utility method to get the current JDK version, that works on Java 8 and above.
*
* <p>TODO remove this method once we drop support for Java 8
*
* @return the current JDK version
*/
private static int getVersion() {
String version = System.getProperty("java.version");
if (version.startsWith("1.")) {
version = version.substring(2, 3);
} else {
int dot = version.indexOf(".");
if (dot != -1) {
version = version.substring(0, dot);
}
}
return Integer.parseInt(version);
}
}