1 package com.uber.nullaway.generics; 2 3 import static com.uber.nullaway.NullabilityUtil.castToNonNull; 4 5 import com.google.common.base.Preconditions; 6 import com.google.errorprone.VisitorState; 7 import com.google.errorprone.util.ASTHelpers; 8 import com.sun.source.tree.AnnotatedTypeTree; 9 import com.sun.source.tree.AnnotationTree; 10 import com.sun.source.tree.ArrayTypeTree; 11 import com.sun.source.tree.ParameterizedTypeTree; 12 import com.sun.source.tree.Tree; 13 import com.sun.source.util.SimpleTreeVisitor; 14 import com.sun.tools.javac.code.Attribute; 15 import com.sun.tools.javac.code.Type; 16 import com.sun.tools.javac.code.TypeMetadata; 17 import com.sun.tools.javac.util.ListBuffer; 18 import java.lang.invoke.MethodHandle; 19 import java.lang.invoke.MethodHandles; 20 import java.lang.invoke.MethodType; 21 import java.util.ArrayList; 22 import java.util.Collections; 23 import java.util.List; 24 25 /** 26 * Visitor For getting the preserved Annotation Types for the nested generic type arguments within a 27 * ParameterizedTypeTree. This is required primarily since javac does not preserve annotations on 28 * generic type arguments in its types for NewClassTrees. We need a visitor since the nested 29 * arguments may appear on different kinds of type trees, e.g., ArrayTypeTrees. 30 */ 31 public class PreservedAnnotationTreeVisitor extends SimpleTreeVisitor<Type, Void> { 32 33 private final VisitorState state; 34 PreservedAnnotationTreeVisitor(VisitorState state)35 PreservedAnnotationTreeVisitor(VisitorState state) { 36 this.state = state; 37 } 38 39 @Override visitArrayType(ArrayTypeTree tree, Void p)40 public Type visitArrayType(ArrayTypeTree tree, Void p) { 41 Type elemType = tree.getType().accept(this, null); 42 return new Type.ArrayType(elemType, castToNonNull(ASTHelpers.getType(tree)).tsym); 43 } 44 45 @Override visitParameterizedType(ParameterizedTypeTree tree, Void p)46 public Type visitParameterizedType(ParameterizedTypeTree tree, Void p) { 47 Type.ClassType type = (Type.ClassType) ASTHelpers.getType(tree); 48 Preconditions.checkNotNull(type); 49 Type nullableType = GenericsChecks.JSPECIFY_NULLABLE_TYPE_SUPPLIER.get(state); 50 List<? extends Tree> typeArguments = tree.getTypeArguments(); 51 List<Type> newTypeArgs = new ArrayList<>(); 52 for (int i = 0; i < typeArguments.size(); i++) { 53 AnnotatedTypeTree annotatedType = null; 54 Tree curTypeArg = typeArguments.get(i); 55 // If the type argument has an annotation, it will either be an AnnotatedTypeTree, or a 56 // ParameterizedTypeTree in the case of a nested generic type 57 if (curTypeArg instanceof AnnotatedTypeTree) { 58 annotatedType = (AnnotatedTypeTree) curTypeArg; 59 } else if (curTypeArg instanceof ParameterizedTypeTree 60 && ((ParameterizedTypeTree) curTypeArg).getType() instanceof AnnotatedTypeTree) { 61 annotatedType = (AnnotatedTypeTree) ((ParameterizedTypeTree) curTypeArg).getType(); 62 } 63 List<? extends AnnotationTree> annotations = 64 annotatedType != null ? annotatedType.getAnnotations() : Collections.emptyList(); 65 boolean hasNullableAnnotation = false; 66 for (AnnotationTree annotation : annotations) { 67 if (ASTHelpers.isSameType( 68 nullableType, ASTHelpers.getType(annotation.getAnnotationType()), state)) { 69 hasNullableAnnotation = true; 70 break; 71 } 72 } 73 // construct a TypeMetadata object containing a nullability annotation if needed 74 com.sun.tools.javac.util.List<Attribute.TypeCompound> nullableAnnotationCompound = 75 hasNullableAnnotation 76 ? com.sun.tools.javac.util.List.from( 77 Collections.singletonList( 78 new Attribute.TypeCompound( 79 nullableType, com.sun.tools.javac.util.List.nil(), null))) 80 : com.sun.tools.javac.util.List.nil(); 81 TypeMetadata typeMetadata = TYPE_METADATA_BUILDER.create(nullableAnnotationCompound); 82 Type currentTypeArgType = curTypeArg.accept(this, null); 83 Type newTypeArgType = 84 TYPE_METADATA_BUILDER.cloneTypeWithMetadata(currentTypeArgType, typeMetadata); 85 newTypeArgs.add(newTypeArgType); 86 } 87 Type.ClassType finalType = 88 new Type.ClassType( 89 type.getEnclosingType(), com.sun.tools.javac.util.List.from(newTypeArgs), type.tsym); 90 return finalType; 91 } 92 93 /** By default, just use the type computed by javac */ 94 @Override defaultAction(Tree node, Void unused)95 protected Type defaultAction(Tree node, Void unused) { 96 return castToNonNull(ASTHelpers.getType(node)); 97 } 98 99 /** 100 * Abstracts over the different APIs for building {@link TypeMetadata} objects in different JDK 101 * versions. 102 */ 103 private interface TypeMetadataBuilder { create(com.sun.tools.javac.util.List<Attribute.TypeCompound> attrs)104 TypeMetadata create(com.sun.tools.javac.util.List<Attribute.TypeCompound> attrs); 105 cloneTypeWithMetadata(Type typeToBeCloned, TypeMetadata metaData)106 Type cloneTypeWithMetadata(Type typeToBeCloned, TypeMetadata metaData); 107 createAnnotationsCtorHandle(Class<?> paramTypeClass)108 static MethodHandle createAnnotationsCtorHandle(Class<?> paramTypeClass) { 109 MethodHandles.Lookup lookup = MethodHandles.lookup(); 110 MethodType mt = MethodType.methodType(void.class, paramTypeClass); 111 try { 112 return lookup.findConstructor(TypeMetadata.Annotations.class, mt); 113 } catch (NoSuchMethodException e) { 114 throw new RuntimeException(e); 115 } catch (IllegalAccessException e) { 116 throw new RuntimeException(e); 117 } 118 } 119 120 /** 121 * Used to get a MethodHandle for a virtual method from the specified class 122 * 123 * @param retTypeClass Class to indicate the return type of the desired method 124 * @param paramTypeClass Class to indicate the parameter type of the desired method 125 * @param refClass Class within which the desired method is contained 126 * @param methodName Name of the desired method 127 * @return The appropriate MethodHandle for the virtual method 128 */ createVirtualMethodHandle( Class<?> retTypeClass, Class<?> paramTypeClass, Class<?> refClass, String methodName)129 static MethodHandle createVirtualMethodHandle( 130 Class<?> retTypeClass, Class<?> paramTypeClass, Class<?> refClass, String methodName) { 131 MethodHandles.Lookup lookup = MethodHandles.lookup(); 132 MethodType mt = MethodType.methodType(retTypeClass, paramTypeClass); 133 try { 134 return lookup.findVirtual(refClass, methodName, mt); 135 } catch (NoSuchMethodException e) { 136 throw new RuntimeException(e); 137 } catch (IllegalAccessException e) { 138 throw new RuntimeException(e); 139 } 140 } 141 142 } 143 144 /** 145 * Provides implementations for methods under TypeMetadataBuilder compatible with JDK 17 and 146 * earlier versions. 147 */ 148 private static class JDK17AndEarlierTypeMetadataBuilder implements TypeMetadataBuilder { 149 150 private static final MethodHandle typeMetadataHandle = 151 TypeMetadataBuilder.createAnnotationsCtorHandle(com.sun.tools.javac.util.List.class); 152 private static final MethodHandle cloneHandle = TypeMetadataBuilder.createVirtualMethodHandle( 153 Type.class, TypeMetadata.class, Type.class, "cloneWithMetadata"); 154 155 @Override create(com.sun.tools.javac.util.List<Attribute.TypeCompound> attrs)156 public TypeMetadata create(com.sun.tools.javac.util.List<Attribute.TypeCompound> attrs) { 157 try { 158 return (TypeMetadata) typeMetadataHandle.invoke(attrs); 159 } catch (Throwable e) { 160 throw new RuntimeException(e); 161 } 162 } 163 164 /** 165 * Clones the given type with the specified Metadata for getting the right nullability 166 * annotations. 167 * 168 * @param typeToBeCloned The Type we want to clone with the required Nullability Metadata 169 * @param metadata The required Nullability metadata which is lost from the type 170 * @return Type after it has been cloned by applying the required Nullability metadata 171 */ 172 @Override cloneTypeWithMetadata(Type typeToBeCloned, TypeMetadata metadata)173 public Type cloneTypeWithMetadata(Type typeToBeCloned, TypeMetadata metadata) { 174 try { 175 return (Type) cloneHandle.invoke(typeToBeCloned, metadata); 176 } catch (Throwable e) { 177 throw new RuntimeException(e); 178 } 179 } 180 } 181 182 /** 183 * Provides implementations for methods under TypeMetadataBuilder compatible with the updates made 184 * to the library methods for Jdk 21. The implementation calls the logic specific to JDK 21 185 * indirectly using MethodHandles since we still need the code to compile on earlier versions. 186 */ 187 private static class JDK21TypeMetadataBuilder implements TypeMetadataBuilder { 188 189 private static final MethodHandle typeMetadataHandle = 190 TypeMetadataBuilder.createAnnotationsCtorHandle(com.sun.tools.javac.util.ListBuffer.class); 191 private static final MethodHandle addMetadataHandle = TypeMetadataBuilder.createVirtualMethodHandle( 192 Type.class, TypeMetadata.class, Type.class, "addMetadata"); 193 private static final MethodHandle dropMetadataHandle = TypeMetadataBuilder.createVirtualMethodHandle( 194 Type.class, Class.class, Type.class, "dropMetadata"); 195 196 @Override create(com.sun.tools.javac.util.List<Attribute.TypeCompound> attrs)197 public TypeMetadata create(com.sun.tools.javac.util.List<Attribute.TypeCompound> attrs) { 198 ListBuffer<Attribute.TypeCompound> b = new ListBuffer<>(); 199 b.appendList(attrs); 200 try { 201 return (TypeMetadata) typeMetadataHandle.invoke(b); 202 } catch (Throwable e) { 203 throw new RuntimeException(e); 204 } 205 } 206 207 /** 208 * Calls dropMetadata and addMetadata using MethodHandles for JDK 21, which removed the previous 209 * cloneWithMetadata method. 210 * 211 * @param typeToBeCloned The Type we want to clone with the required Nullability metadata 212 * @param metadata The required Nullability metadata 213 * @return Cloned Type with the necessary Nullability metadata 214 */ 215 @Override cloneTypeWithMetadata(Type typeToBeCloned, TypeMetadata metadata)216 public Type cloneTypeWithMetadata(Type typeToBeCloned, TypeMetadata metadata) { 217 try { 218 // In JDK 21 addMetadata works if there is no metadata associated with the type, so we 219 // create a copy without the existing metadata first and then add it 220 Type clonedTypeWithoutMetadata = 221 (Type) dropMetadataHandle.invoke(typeToBeCloned, metadata.getClass()); 222 return (Type) addMetadataHandle.invoke(clonedTypeWithoutMetadata, metadata); 223 } catch (Throwable e) { 224 throw new RuntimeException(e); 225 } 226 } 227 } 228 229 /** The TypeMetadataBuilder to be used for the current JDK version. */ 230 private static final TypeMetadataBuilder TYPE_METADATA_BUILDER = 231 getVersion() >= 21 232 ? new JDK21TypeMetadataBuilder() 233 : new JDK17AndEarlierTypeMetadataBuilder(); 234 235 /** 236 * Utility method to get the current JDK version, that works on Java 8 and above. 237 * 238 * <p>TODO remove this method once we drop support for Java 8 239 * 240 * @return the current JDK version 241 */ getVersion()242 private static int getVersion() { 243 String version = System.getProperty("java.version"); 244 if (version.startsWith("1.")) { 245 version = version.substring(2, 3); 246 } else { 247 int dot = version.indexOf("."); 248 if (dot != -1) { 249 version = version.substring(0, dot); 250 } 251 } 252 return Integer.parseInt(version); 253 } 254 } 255