From ed3272cc44a5b1ae918b573e6c3d792665b6bbc7 Mon Sep 17 00:00:00 2001 From: Joe Darcy Date: Fri, 26 Jan 2024 20:55:46 +0000 Subject: [PATCH] 8042981: Strip type annotations in Types' utility methods Co-authored-by: Liam Miller-Cushon Reviewed-by: cushon, jjg, jlahoda --- .../classes/javax/lang/model/util/Types.java | 41 ++- .../com/sun/tools/javac/code/Type.java | 35 +- .../com/sun/tools/javac/model/JavacTypes.java | 11 +- .../lib/JavacTestingAbstractProcessor.java | 72 ++++- .../util/types/TestAnnotationStripping.java | 305 ++++++++++++++++++ 5 files changed, 451 insertions(+), 13 deletions(-) create mode 100644 test/langtools/tools/javac/processing/model/util/types/TestAnnotationStripping.java diff --git a/src/java.compiler/share/classes/javax/lang/model/util/Types.java b/src/java.compiler/share/classes/javax/lang/model/util/Types.java index 8c3cc7ba5d9..93b43c777be 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/Types.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/Types.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -35,6 +35,10 @@ import javax.lang.model.type.*; /** * Utility methods for operating on types. * + * Where a method returns a type mirror or a collection of type + * mirrors, any type mirrors represent types with no type annotations, + * unless otherwise indicated. + * *

Compatibility Note: Methods may be added to this interface * in future releases of the platform. * @@ -153,6 +157,8 @@ public interface Types { * the direct supertypes of a type mirror representing {@code * java.lang.Object}. * + * Annotations on the direct super types are preserved. + * * @param t the type being examined * @return the direct supertypes, or an empty list if none * @throws IllegalArgumentException if given a type for an executable, package, or module @@ -235,6 +241,8 @@ public interface Types { /** * {@return an array type with the specified component type} * + * Annotations on the component type are preserved. + * * @param componentType the component type * @throws IllegalArgumentException if the component type is not valid for * an array @@ -245,6 +253,8 @@ public interface Types { * {@return a new wildcard type} Either of the wildcard's * bounds may be specified, or neither, but not both. * + * Annotations on the bounds are preserved. + * * @param extendsBound the extends (upper) bound, or {@code null} if none * @param superBound the super (lower) bound, or {@code null} if none * @throws IllegalArgumentException if bounds are not valid @@ -260,6 +270,8 @@ public interface Types { * for example, this method may be used to get the * parameterized type {@code Set}. * + * Annotations on the type arguments are preserved. + * *

The number of type arguments must either equal the * number of the type element's formal type parameters, or must be * zero. If zero, and if the type element is generic, @@ -291,6 +303,8 @@ public interface Types { * to get the type {@code Outer}, and then invoking * this method. * + * Annotations on the type arguments are preserved. + * *

If the containing type is a parameterized type, * the number of type arguments must equal the * number of {@code typeElem}'s formal type parameters. @@ -324,4 +338,29 @@ public interface Types { * for the given type */ TypeMirror asMemberOf(DeclaredType containing, Element element); + + /** + * {@return a type mirror equivalent to the argument, but with no annotations} + * If the type mirror is a composite type, such as an array type + * or a wildcard type, any constitute types, such as the + * component type of an array and the type of the bounds of a + * wildcard type, also have no annotations, recursively. + * + *

For most kinds of type mirrors, the result of + * {@snippet lang="java" : + * types.isSameType(typeMirror, types.stripAnnotations(typeMirror)) + * } + * is {@code true}. The predicate is {@code false} on wildcard + * types for {@linkplain #isSameType(TypeMirror, TypeMirror) + * reasons discussed elsewhere}. + * + * @param t the type mirror + * @param the specific type of type mirror + * @implSpec + * The default implementation throws {@code UnsupportedOperationException}. + * @since 23 + */ + default T stripAnnotations(T t) { + throw new UnsupportedOperationException(); + } } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java index 533d756d641..1715dfa1451 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java @@ -340,7 +340,7 @@ public abstract class Type extends AnnoConstruct implements TypeMirror, PoolCons * it should not be used outside this class. */ protected Type typeNoMetadata() { - return metadata.isEmpty() ? this : baseType(); + return metadata.isEmpty() ? this : stripMetadata(); } /** @@ -426,25 +426,42 @@ public abstract class Type extends AnnoConstruct implements TypeMirror, PoolCons return accept(stripMetadata, null); } //where + /** + * Note: this visitor only needs to handle cases where + * 'contained' types can be annotated. These cases are + * described in JVMS 4.7.20.2 and are : classes (for type + * parameters and enclosing types), wildcards, and arrays. + */ private static final TypeMapping stripMetadata = new StructuralTypeMapping() { @Override public Type visitClassType(ClassType t, Void aVoid) { - return super.visitClassType((ClassType)t.typeNoMetadata(), aVoid); + return super.visitClassType((ClassType) dropMetadata(t), aVoid); } @Override public Type visitArrayType(ArrayType t, Void aVoid) { - return super.visitArrayType((ArrayType)t.typeNoMetadata(), aVoid); - } - - @Override - public Type visitTypeVar(TypeVar t, Void aVoid) { - return super.visitTypeVar((TypeVar)t.typeNoMetadata(), aVoid); + return super.visitArrayType((ArrayType) dropMetadata(t), aVoid); } @Override public Type visitWildcardType(WildcardType wt, Void aVoid) { - return super.visitWildcardType((WildcardType)wt.typeNoMetadata(), aVoid); + return super.visitWildcardType((WildcardType) dropMetadata(wt), aVoid); + } + + @Override + public Type visitType(Type t, Void aVoid) { + return dropMetadata(t); + } + + private static Type dropMetadata(Type t) { + if (t.getMetadata().isEmpty()) { + return t; + } + Type baseType = t.baseType(); + if (baseType.getMetadata().isEmpty()) { + return baseType; + } + return baseType.cloneWithMetadata(List.nil()); } }; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/model/JavacTypes.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/model/JavacTypes.java index 08d5e785a3c..1bc5de7f73a 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/model/JavacTypes.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/model/JavacTypes.java @@ -134,7 +134,7 @@ public class JavacTypes implements javax.lang.model.util.Types { TypeKind kind = t.getKind(); if (kind == TypeKind.PACKAGE || kind == TypeKind.MODULE) throw new IllegalArgumentException(t.toString()); - return types.erasure((Type)t).stripMetadataIfNeeded(); + return types.erasure((Type)t).stripMetadata(); } @DefinedBy(Api.LANGUAGE_MODEL) @@ -155,7 +155,7 @@ public class JavacTypes implements javax.lang.model.util.Types { @DefinedBy(Api.LANGUAGE_MODEL) public TypeMirror capture(TypeMirror t) { validateTypeNotIn(t, EXEC_OR_PKG_OR_MOD); - return types.capture((Type)t).stripMetadataIfNeeded(); + return types.capture((Type)t).stripMetadata(); } @DefinedBy(Api.LANGUAGE_MODEL) @@ -304,6 +304,13 @@ public class JavacTypes implements javax.lang.model.util.Types { } + @DefinedBy(Api.LANGUAGE_MODEL) + @SuppressWarnings("unchecked") + public T stripAnnotations(T t) { + return (T)((Type) t).stripMetadata(); + } + + private static final Set EXEC_OR_PKG_OR_MOD = EnumSet.of(TypeKind.EXECUTABLE, TypeKind.PACKAGE, TypeKind.MODULE); diff --git a/test/langtools/tools/javac/lib/JavacTestingAbstractProcessor.java b/test/langtools/tools/javac/lib/JavacTestingAbstractProcessor.java index a4f47d0ff2f..cf4de078e9e 100644 --- a/test/langtools/tools/javac/lib/JavacTestingAbstractProcessor.java +++ b/test/langtools/tools/javac/lib/JavacTestingAbstractProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,6 +26,7 @@ import java.util.*; import javax.annotation.processing.*; import javax.lang.model.SourceVersion; import javax.lang.model.element.*; +import javax.lang.model.type.*; import javax.lang.model.util.*; import static javax.lang.model.SourceVersion.*; @@ -322,4 +323,73 @@ public abstract class JavacTestingAbstractProcessor extends AbstractProcessor { @Override public boolean isFunctionalInterface(TypeElement type) {return false;} } + + /** + * Vacuous implementation of javax.lang.model.util.Types to aid + * in test development. Methods with defaults in the interface are + * *not* overridden to allow them to be tested. + */ + public static class VacuousTypes implements Types { + public VacuousTypes() {} + + @Override + public Element asElement(TypeMirror t) {return null;} + + @Override + public boolean isSameType(TypeMirror t1, TypeMirror t2) {return false;} + + @Override + public boolean isSubtype(TypeMirror t1, TypeMirror t2) {return false;}; + + @Override + public boolean isAssignable(TypeMirror t1, TypeMirror t2) {return false;}; + + @Override + public boolean contains(TypeMirror t1, TypeMirror t2) {return false;}; + + @Override + public boolean isSubsignature(ExecutableType m1, ExecutableType m2) {return false;} + + @Override + public List directSupertypes(TypeMirror t) {return null;} + + @Override + public TypeMirror erasure(TypeMirror t) {return null;} + + @Override + public TypeElement boxedClass(PrimitiveType p) {return null;} + + @Override + public PrimitiveType unboxedType(TypeMirror t) {return null;} + + @Override + public TypeMirror capture(TypeMirror t) {return null;} + + @Override + public PrimitiveType getPrimitiveType(TypeKind kind) {return null;} + + @Override + public NullType getNullType() {return null;} + + @Override + public NoType getNoType(TypeKind kind) {return null;} + + @Override + public ArrayType getArrayType(TypeMirror componentType) {return null;} + + @Override + public WildcardType getWildcardType(TypeMirror extendsBound, + TypeMirror superBound) {return null;} + + @Override + public DeclaredType getDeclaredType(TypeElement typeElem, TypeMirror... typeArgs) {return null;} + + + @Override + public DeclaredType getDeclaredType(DeclaredType containing, + TypeElement typeElem, TypeMirror... typeArgs) {return null;} + + @Override + public TypeMirror asMemberOf(DeclaredType containing, Element element) {return null;} + } } diff --git a/test/langtools/tools/javac/processing/model/util/types/TestAnnotationStripping.java b/test/langtools/tools/javac/processing/model/util/types/TestAnnotationStripping.java new file mode 100644 index 00000000000..79e970d7173 --- /dev/null +++ b/test/langtools/tools/javac/processing/model/util/types/TestAnnotationStripping.java @@ -0,0 +1,305 @@ +/* + * Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8042981 + * @summary Test if annotations are stripped from the results of Types' methods + * @library /tools/javac/lib + * @modules java.compiler + * jdk.compiler + * @build JavacTestingAbstractProcessor TestAnnotationStripping + * @compile -processor TestAnnotationStripping -proc:only TestAnnotationStripping.java + */ + +import java.lang.annotation.*; +import java.util.*; +import static java.util.Objects.*; +import javax.annotation.processing.*; +import javax.lang.model.SourceVersion; +import static javax.lang.model.SourceVersion.*; +import javax.lang.model.*; +import javax.lang.model.element.*; +import javax.lang.model.type.*; +import javax.lang.model.util.*; +import static javax.lang.model.util.ElementFilter.*; +import static javax.tools.Diagnostic.Kind.*; +import static javax.tools.StandardLocation.*; + +/** + * Test if annotations are stripped from the results of Types' methods + */ +public class TestAnnotationStripping extends JavacTestingAbstractProcessor { + private Types vacuousTypes = new VacuousTypes(); + + /** + * Check expected behavior on classes and packages. + */ + public boolean process(Set annotations, + RoundEnvironment roundEnv) { + if (!roundEnv.processingOver()) { + TypeElement juSetElt = eltUtils.getTypeElement("java.util.Set"); + TypeElement testElt = elements.getTypeElement("TestAnnotationStripping"); + TypeElement boxElt = elements.getTypeElement("TestAnnotationStripping.Box"); + + TypeMirror expectedAnnotation = eltUtils.getTypeElement("TestTypeAnnotation").asType(); + + for (ExecutableElement m : + methodsIn(eltUtils.getTypeElement("HostClass").getEnclosedElements())) { + /* + * The kinds of types include: + * + * arrays + * declared types (classes, interfaces, etc.) + * error types + * executable types + * intersection types + * no-type + * null type + * primitive types + * type variable + * union type + * wildcards + * + * A subset of these can appear at the return type of + * a method. The general methodology is to verify that + * types that can appear as return types when + * annotated with type annotations appear as specified + * as the result of type operations or when new types + * are constructed. + */ + + TypeMirror returnType = m.getReturnType(); + + System.err.println("Checking " + returnType); + + testVacuous(returnType); + checkDeepEmptyAnnotations(typeUtils.stripAnnotations(returnType)); + + checkExpectedTypeAnnotations(returnType, expectedAnnotation); + + // Note: the result of Types.asElement is *not* + // checked for its annotations since the return value + // is an Element and not a TypeMirror. + + System.err.print("\tcapture()"); + checkDeepEmptyAnnotations(typeUtils.capture(returnType)); + + System.err.print("\terasure()"); + checkDeepEmptyAnnotations(typeUtils.erasure(returnType)); + + System.err.print("\tgetArrayType()"); + ArrayType arrayType = typeUtils.getArrayType(returnType); + checkEmptyAnnotations(arrayType); + /* + * "Annotations on the component type are preserved." + */ + checkEqualTypeAndAnnotations(returnType, arrayType.getComponentType()); + + if (!returnType.getKind().isPrimitive()) { + /* + * For getWildcardType() + * "Annotations on the bounds are preserved." + */ + WildcardType wcType; + checkEmptyAnnotations(wcType = typeUtils.getWildcardType(returnType, null)); + checkEqualTypeAndAnnotations(returnType, wcType.getExtendsBound()); + + checkEmptyAnnotations(wcType = typeUtils.getWildcardType(null, returnType)); + checkEqualTypeAndAnnotations(returnType, wcType.getSuperBound()); + + /* + * For getDeclaredType() + * "Annotations on the type arguments are preserved." + */ + DeclaredType declaredType = typeUtils.getDeclaredType(juSetElt, returnType); + checkEqualTypeAndAnnotations(returnType, declaredType.getTypeArguments().get(0)); + + // Check both overloads + declaredType = typeUtils.getDeclaredType(typeUtils.getDeclaredType(testElt), // outer type + boxElt, + returnType); + checkEqualTypeAndAnnotations(returnType, declaredType.getTypeArguments().get(0)); + } + + System.out.println(returnType.getAnnotation(TestTypeAnnotation.class)); + System.out.println(returnType.getAnnotationsByType(TestTypeAnnotation.class).length); + TestTypeAnnotation ta = requireNonNull(returnType.getAnnotation(TestTypeAnnotation.class), + returnType.toString()); + + System.err.println(); + System.err.println(); + } + + if (failures > 0) + throw new RuntimeException(failures + " failures occured."); + } + return true; + } + + void testVacuous(TypeMirror tm ) { + try { + var result = vacuousTypes.stripAnnotations(tm); + messager.printError("Unexpected non-exceptional result returned " + result); + } catch(UnsupportedOperationException uoe) { + ; // Expected + } + } + + private int failures = 0; + + void checkExpectedTypeAnnotations(AnnotatedConstruct ac, TypeMirror expectedAnnotation) { + List annotations = ac.getAnnotationMirrors(); + if (annotations.size() != 1) { + failures++; + System.err.println("\t\t\tUnexpected annotations size: " + annotations.size()); + } else if (!typeUtils.isSameType(annotations.get(0).getAnnotationType(), expectedAnnotation)) { + failures++; + System.err.println("\t\t\tUnexpected annotations type: " + annotations); + } + } + + void checkEmptyAnnotations(AnnotatedConstruct ac) { + System.err.println("\t" + ac); + if (ac == null) + return; + else { + List annotations = ac.getAnnotationMirrors(); + int count = annotations.size(); + if (count != 0) { + failures++; + System.err.println(ac.getClass()); + System.err.println("\t\t\tUnexpected nonzero annotations size: " + annotations); + } + } + } + + void checkDeepEmptyAnnotations(TypeMirror ac) { + System.err.println("\t" + ac); + if (ac == null) { + return; + } + new SimpleTypeVisitor14() { + @Override + protected Void defaultAction(TypeMirror t, Void o) { + checkEmptyAnnotations(t); + return null; + } + + @Override + public Void visitArray(ArrayType t, Void o) { + scan(t.getComponentType()); + return super.visitArray(t, o); + } + + @Override + public Void visitDeclared(DeclaredType t, Void o) { + scan(t.getEnclosingType()); + t.getTypeArguments().stream().forEach(this::scan); + return super.visitDeclared(t, o); + } + + @Override + public Void visitTypeVariable(TypeVariable t, Void o) { + // the bounds correspond to the type variable declaration, not its use + // scan(t.getUpperBound()); + // scan(t.getLowerBound()); + return super.visitTypeVariable(t, o); + } + + @Override + public Void visitWildcard(WildcardType t, Void o) { + scan(t.getExtendsBound()); + scan(t.getSuperBound()); + return super.visitWildcard(t, o); + } + + private void scan(TypeMirror t) { + if (t != null) { + visit(t); + } + } + }.visit(ac); + } + + void checkEqualTypeAndAnnotations(TypeMirror tm1, TypeMirror tm2) { + if (!typeUtils.isSameType(tm1, tm2)) { + failures++; + System.err.printf("Unequal types %s and %s.%n", tm1, tm2); + } + + if (!Objects.equals(tm1.getAnnotationMirrors(), tm1.getAnnotationMirrors())) { + failures++; + System.err.printf("Unequal annotations on and %s.%n", tm1, tm2); + } + } + + // Nested class to test getDeclaredType overload. + class Box { + private T contents; + + public Box(T t){ + contents = t; + } + + T value() { return contents;}; + } +} + +/* + * Class to host annotations for testing + */ +class HostClass { + // Declared type Integer + public static @TestTypeAnnotation("foo") Integer foo() {return null;} + + // Primitive type int + public static @TestTypeAnnotation("foo2") int foo2() {return 0;} + + public static @TestTypeAnnotation("foo3") String foo3() {return null;} + + // Declared raw type Set + public static java.util.@TestTypeAnnotation("foo4")Set foo4() {return null;} + + // Array type + public static String @TestTypeAnnotation("foo5")[] foo5() {return null;} + + // Declared type Set with instantiated type parameter + public static java.util. @TestTypeAnnotation("foo6") Set < @TestTypeAnnotation("foo7") String> foo6() {return null;} + + // Type variable + public static <@TestTypeAnnotation("foo8") T extends @TestTypeAnnotation("foo9") String> @TestTypeAnnotation("foo10") T foo7() {return null;} + + // Declared type including wildcard + public static java.util. @TestTypeAnnotation("foo11") Set < @TestTypeAnnotation("foo12") ? extends @TestTypeAnnotation("foo13") Number> foo8() {return null;} + + // Type variable with intersection type + public static <@TestTypeAnnotation("foo14") S extends Number & Runnable> @TestTypeAnnotation("foo15") S foo9() {return null;} + +} + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE_USE) +@interface TestTypeAnnotation { + String value() default ""; +}