From de6667cf11aa59d1bab78ae5fb235ea0b901d5c4 Mon Sep 17 00:00:00 2001 From: Liam Miller-Cushon Date: Fri, 3 Nov 2023 17:31:05 +0000 Subject: [PATCH] 8225377: type annotations are not visible to javac plugins across compilation boundaries Reviewed-by: vromero --- .../com/sun/tools/javac/jvm/ClassReader.java | 301 ++++++++++++++++++ .../processing/model/type/BasicAnnoTests.java | 3 +- 2 files changed, 303 insertions(+), 1 deletion(-) diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassReader.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassReader.java index 0bc7f8994f6..f86a95e501c 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassReader.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassReader.java @@ -36,7 +36,9 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.function.BiFunction; import java.util.function.IntFunction; +import java.util.function.Predicate; import javax.lang.model.element.Modifier; import javax.lang.model.element.NestingKind; @@ -2261,12 +2263,311 @@ public class ClassReader { currentClassFile = classFile; List newList = deproxyTypeCompoundList(proxies); sym.setTypeAttributes(newList.prependList(sym.getRawTypeAttributes())); + addTypeAnnotationsToSymbol(sym, newList); } finally { currentClassFile = previousClassFile; } } } + /** + * Rewrites types in the given symbol to include type annotations. + * + *

The list of type annotations includes annotations for all types in the signature of the + * symbol. Associating the annotations with the correct type requires interpreting the JVMS + * 4.7.20-A target_type to locate the correct type to rewrite, and then interpreting the JVMS + * 4.7.20.2 type_path to associate the annotation with the correct contained type. + */ + private static void addTypeAnnotationsToSymbol( + Symbol s, List attributes) { + new TypeAnnotationSymbolVisitor(attributes).visit(s, null); + } + + private static class TypeAnnotationSymbolVisitor + extends Types.DefaultSymbolVisitor { + + private final List attributes; + + private TypeAnnotationSymbolVisitor(List attributes) { + this.attributes = attributes; + } + + @Override + public Void visitClassSymbol(Symbol.ClassSymbol s, Void unused) { + ClassType t = (ClassType) s.type; + int i = 0; + ListBuffer interfaces = new ListBuffer<>(); + for (Type itf : t.interfaces_field) { + interfaces.add(addTypeAnnotations(itf, classExtends(i++))); + } + t.interfaces_field = interfaces.toList(); + t.supertype_field = addTypeAnnotations(t.supertype_field, classExtends(65535)); + if (t.typarams_field != null) { + t.typarams_field = + rewriteTypeParameters( + t.typarams_field, TargetType.CLASS_TYPE_PARAMETER_BOUND); + } + return null; + } + + @Override + public Void visitMethodSymbol(Symbol.MethodSymbol s, Void unused) { + Type t = s.type; + if (t.hasTag(TypeTag.FORALL)) { + Type.ForAll fa = (Type.ForAll) t; + fa.tvars = rewriteTypeParameters(fa.tvars, TargetType.METHOD_TYPE_PARAMETER_BOUND); + t = fa.qtype; + } + MethodType mt = (MethodType) t; + ListBuffer argtypes = new ListBuffer<>(); + int i = 0; + for (Symbol.VarSymbol param : s.params) { + param.type = addTypeAnnotations(param.type, methodFormalParameter(i++)); + argtypes.add(param.type); + } + mt.argtypes = argtypes.toList(); + ListBuffer thrown = new ListBuffer<>(); + i = 0; + for (Type thrownType : mt.thrown) { + thrown.add(addTypeAnnotations(thrownType, thrownType(i++))); + } + mt.thrown = thrown.toList(); + mt.restype = addTypeAnnotations(mt.restype, TargetType.METHOD_RETURN); + if (mt.recvtype != null) { + mt.recvtype = addTypeAnnotations(mt.recvtype, TargetType.METHOD_RECEIVER); + } + return null; + } + + @Override + public Void visitVarSymbol(Symbol.VarSymbol s, Void unused) { + s.type = addTypeAnnotations(s.type, TargetType.FIELD); + return null; + } + + @Override + public Void visitSymbol(Symbol s, Void unused) { + return null; + } + + private List rewriteTypeParameters(List tvars, TargetType boundType) { + ListBuffer tvarbuf = new ListBuffer<>(); + int typeVariableIndex = 0; + for (Type tvar : tvars) { + Type bound = tvar.getUpperBound(); + if (bound.isCompound()) { + ClassType ct = (ClassType) bound; + int boundIndex = 0; + if (ct.supertype_field != null) { + ct.supertype_field = + addTypeAnnotations( + ct.supertype_field, + typeParameterBound( + boundType, typeVariableIndex, boundIndex++)); + } + ListBuffer itfbuf = new ListBuffer<>(); + for (Type itf : ct.interfaces_field) { + itfbuf.add( + addTypeAnnotations( + itf, + typeParameterBound( + boundType, typeVariableIndex, boundIndex++))); + } + ct.interfaces_field = itfbuf.toList(); + } else { + bound = + addTypeAnnotations( + bound, + typeParameterBound( + boundType, + typeVariableIndex, + bound.isInterface() ? 1 : 0)); + } + ((TypeVar) tvar).setUpperBound(bound); + tvarbuf.add(tvar); + typeVariableIndex++; + } + return tvarbuf.toList(); + } + + private Type addTypeAnnotations(Type type, TargetType targetType) { + return addTypeAnnotations(type, pos -> pos.type == targetType); + } + + private Type addTypeAnnotations(Type type, Predicate filter) { + Assert.checkNonNull(type); + + // Find type annotations that match the given target type + ListBuffer filtered = new ListBuffer<>(); + for (Attribute.TypeCompound attribute : this.attributes) { + if (filter.test(attribute.position)) { + filtered.add(attribute); + } + } + if (filtered.isEmpty()) { + return type; + } + + // Group the matching annotations by their type path. Each group of annotations will be + // added to a type at that location. + Map, ListBuffer> + attributesByPath = new HashMap<>(); + for (Attribute.TypeCompound attribute : filtered.toList()) { + attributesByPath + .computeIfAbsent(attribute.position.location, k -> new ListBuffer<>()) + .add(attribute); + } + + // Search the structure of the type to find the contained types at each type path + Map> attributesByType = new HashMap<>(); + new TypeAnnotationLocator(attributesByPath, attributesByType).visit(type, List.nil()); + + // Rewrite the type and add the annotations + type = new TypeAnnotationTypeMapping(attributesByType).visit(type, null); + Assert.check(attributesByType.isEmpty(), "Failed to apply annotations to types"); + + return type; + } + + private static Predicate typeParameterBound( + TargetType targetType, int parameterIndex, int boundIndex) { + return pos -> + pos.type == targetType + && pos.parameter_index == parameterIndex + && pos.bound_index == boundIndex; + } + + private static Predicate methodFormalParameter(int index) { + return pos -> + pos.type == TargetType.METHOD_FORMAL_PARAMETER && pos.parameter_index == index; + } + + private static Predicate thrownType(int index) { + return pos -> pos.type == TargetType.THROWS && pos.type_index == index; + } + + private static Predicate classExtends(int index) { + return pos -> pos.type == TargetType.CLASS_EXTENDS && pos.type_index == index; + } + } + + /** + * Visit all contained types, assembling a type path to represent the current location, and + * record the types at each type path that need to be annotated. + */ + private static class TypeAnnotationLocator + extends Types.DefaultTypeVisitor> { + private final Map, + ListBuffer> attributesByPath; + private final Map> attributesByType; + + private TypeAnnotationLocator( + Map, ListBuffer> + attributesByPath, + Map> attributesByType) { + this.attributesByPath = attributesByPath; + this.attributesByType = attributesByType; + } + + @Override + public Void visitClassType(ClassType t, List path) { + // As described in JVMS 4.7.20.2, type annotations on nested types are located with + // 'left-to-right' steps starting on 'the outermost part of the type for which a type + // annotation is admissible'. So the current path represents the outermost containing + // type of the type being visited, and we add type path steps for every contained nested + // type. + List enclosing = List.nil(); + for (Type curr = t; + curr != null && curr != Type.noType; + curr = curr.getEnclosingType()) { + enclosing = enclosing.prepend((ClassType) curr); + } + for (ClassType te : enclosing) { + if (te.typarams_field != null) { + int i = 0; + for (Type typaram : te.typarams_field) { + visit(typaram, path.append(new TypeAnnotationPosition.TypePathEntry( + TypeAnnotationPosition.TypePathEntryKind.TYPE_ARGUMENT, i++))); + } + } + visitType(te, path); + path = path.append(TypeAnnotationPosition.TypePathEntry.INNER_TYPE); + } + return null; + } + + @Override + public Void visitWildcardType( + WildcardType t, List path) { + visit(t.type, path.append(TypeAnnotationPosition.TypePathEntry.WILDCARD)); + return super.visitWildcardType(t, path); + } + + @Override + public Void visitArrayType(ArrayType t, List path) { + visit(t.elemtype, path.append(TypeAnnotationPosition.TypePathEntry.ARRAY)); + return super.visitArrayType(t, path); + } + + @Override + public Void visitType(Type t, List path) { + ListBuffer attributes = attributesByPath.remove(path); + if (attributes != null) { + attributesByType.put(t, attributes.toList()); + } + return null; + } + } + + /** A type mapping that rewrites the type to include type annotations. */ + private static class TypeAnnotationTypeMapping extends Type.StructuralTypeMapping { + + private final Map> attributesByType; + + private TypeAnnotationTypeMapping( + Map> attributesByType) { + this.attributesByType = attributesByType; + } + + private Type reannotate(T t, BiFunction f) { + // We're relying on object identify of Type instances to record where the annotations + // need to be added, so we have to retrieve the annotations for each type before + // rewriting it, and then add them after its contained types have been rewritten. + List attributes = attributesByType.remove(t); + Type mapped = f.apply(t, null); + if (attributes == null) { + return mapped; + } + // Runtime-visible and -invisible annotations are completed separately, so if the same + // type has annotations from both it will get annotated twice. + TypeMetadata.Annotations existing = mapped.getMetadata(TypeMetadata.Annotations.class); + if (existing != null) { + existing.annotationBuffer().addAll(attributes); + return mapped; + } + return mapped.annotatedType(attributes); + } + + @Override + public Type visitClassType(ClassType t, Void unused) { + return reannotate(t, super::visitClassType); + } + + @Override + public Type visitWildcardType(WildcardType t, Void unused) { + return reannotate(t, super::visitWildcardType); + } + + @Override + public Type visitArrayType(ArrayType t, Void unused) { + return reannotate(t, super::visitArrayType); + } + + @Override + public Type visitType(Type t, Void unused) { + return reannotate(t, (x, u) -> x); + } + } /************************************************************************ * Reading Symbols diff --git a/test/langtools/tools/javac/processing/model/type/BasicAnnoTests.java b/test/langtools/tools/javac/processing/model/type/BasicAnnoTests.java index d4f14e8a1da..af63575052c 100644 --- a/test/langtools/tools/javac/processing/model/type/BasicAnnoTests.java +++ b/test/langtools/tools/javac/processing/model/type/BasicAnnoTests.java @@ -23,7 +23,7 @@ /* * @test - * @bug 8013852 8031744 + * @bug 8013852 8031744 8225377 * @summary Annotations on types * @library /tools/javac/lib * @modules jdk.compiler/com.sun.tools.javac.api @@ -33,6 +33,7 @@ * jdk.compiler/com.sun.tools.javac.util * @build JavacTestingAbstractProcessor DPrinter BasicAnnoTests * @compile/process -XDaccessInternalAPI -processor BasicAnnoTests -proc:only BasicAnnoTests.java + * @compile/process -XDaccessInternalAPI -processor BasicAnnoTests -proc:only BasicAnnoTests */ import java.io.PrintWriter;