diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp index caf6788c028..16141c3ed47 100644 --- a/src/hotspot/share/classfile/vmSymbols.hpp +++ b/src/hotspot/share/classfile/vmSymbols.hpp @@ -756,6 +756,8 @@ template(encodeThrowable_name, "encodeThrowable") \ template(encodeThrowable_signature, "(Ljava/lang/Throwable;JI)I") \ template(decodeAndThrowThrowable_name, "decodeAndThrowThrowable") \ + template(encodeAnnotations_name, "encodeAnnotations") \ + template(encodeAnnotations_signature, "([BLjava/lang/Class;Ljdk/internal/reflect/ConstantPool;Z[Ljava/lang/Class;)[B")\ template(decodeAndThrowThrowable_signature, "(JZ)V") \ template(classRedefinedCount_name, "classRedefinedCount") \ template(classLoader_name, "classLoader") \ diff --git a/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp b/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp index 74672c4ee30..503c77b2393 100644 --- a/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp +++ b/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp @@ -2672,9 +2672,7 @@ C2V_VMENTRY_NULL(jobject, asReflectionExecutable, (JNIEnv* env, jobject, ARGUMEN return JNIHandles::make_local(THREAD, executable); } -C2V_VMENTRY_NULL(jobject, asReflectionField, (JNIEnv* env, jobject, ARGUMENT_PAIR(klass), jint index)) - requireInHotSpot("asReflectionField", JVMCI_CHECK_NULL); - Klass* klass = UNPACK_PAIR(Klass, klass); +static InstanceKlass* check_field(Klass* klass, jint index, JVMCI_TRAPS) { if (!klass->is_instance_klass()) { JVMCI_THROW_MSG_NULL(IllegalArgumentException, err_msg("Expected non-primitive type, got %s", klass->external_name())); @@ -2684,11 +2682,100 @@ C2V_VMENTRY_NULL(jobject, asReflectionField, (JNIEnv* env, jobject, ARGUMENT_PAI JVMCI_THROW_MSG_NULL(IllegalArgumentException, err_msg("Field index %d out of bounds for %s", index, klass->external_name())); } + return iklass; +} + +C2V_VMENTRY_NULL(jobject, asReflectionField, (JNIEnv* env, jobject, ARGUMENT_PAIR(klass), jint index)) + requireInHotSpot("asReflectionField", JVMCI_CHECK_NULL); + Klass* klass = UNPACK_PAIR(Klass, klass); + InstanceKlass* iklass = check_field(klass, index, JVMCIENV); fieldDescriptor fd(iklass, index); oop reflected = Reflection::new_field(&fd, CHECK_NULL); return JNIHandles::make_local(THREAD, reflected); } +static jbyteArray get_encoded_annotation_data(InstanceKlass* holder, AnnotationArray* annotations_array, bool for_class, + jint filter_length, jlong filter_klass_pointers, + JavaThread* THREAD, JVMCIEnv* JVMCIENV) { + // Get a ConstantPool object for annotation parsing + Handle jcp = reflect_ConstantPool::create(CHECK_NULL); + reflect_ConstantPool::set_cp(jcp(), holder->constants()); + + // load VMSupport + Symbol* klass = vmSymbols::jdk_internal_vm_VMSupport(); + Klass* k = SystemDictionary::resolve_or_fail(klass, true, CHECK_NULL); + + InstanceKlass* vm_support = InstanceKlass::cast(k); + if (vm_support->should_be_initialized()) { + vm_support->initialize(CHECK_NULL); + } + + typeArrayOop annotations_oop = Annotations::make_java_array(annotations_array, CHECK_NULL); + typeArrayHandle annotations = typeArrayHandle(THREAD, annotations_oop); + + InstanceKlass** filter = filter_length == 1 ? + (InstanceKlass**) &filter_klass_pointers: + (InstanceKlass**) filter_klass_pointers; + objArrayOop filter_oop = oopFactory::new_objectArray(filter_length, CHECK_NULL); + objArrayHandle filter_classes(THREAD, filter_oop); + for (int i = 0; i < filter_length; i++) { + filter_classes->obj_at_put(i, filter[i]->java_mirror()); + } + + // invoke VMSupport.encodeAnnotations + JavaValue result(T_OBJECT); + JavaCallArguments args; + args.push_oop(annotations); + args.push_oop(Handle(THREAD, holder->java_mirror())); + args.push_oop(jcp); + args.push_int(for_class); + args.push_oop(filter_classes); + Symbol* signature = vmSymbols::encodeAnnotations_signature(); + JavaCalls::call_static(&result, + vm_support, + vmSymbols::encodeAnnotations_name(), + signature, + &args, + CHECK_NULL); + + oop res = result.get_oop(); + if (JVMCIENV->is_hotspot()) { + return (jbyteArray) JNIHandles::make_local(THREAD, res); + } + + typeArrayOop ba = typeArrayOop(res); + int ba_len = ba->length(); + jbyte* ba_buf = NEW_RESOURCE_ARRAY_IN_THREAD_RETURN_NULL(THREAD, jbyte, ba_len); + if (ba_buf == nullptr) { + JVMCI_THROW_MSG_NULL(InternalError, + err_msg("could not allocate %d bytes", ba_len)); + + } + memcpy(ba_buf, ba->byte_at_addr(0), ba_len); + JVMCIPrimitiveArray ba_dest = JVMCIENV->new_byteArray(ba_len, JVMCI_CHECK_NULL); + JVMCIENV->copy_bytes_from(ba_buf, ba_dest, 0, ba_len); + return JVMCIENV->get_jbyteArray(ba_dest); +} + +C2V_VMENTRY_NULL(jbyteArray, getEncodedClassAnnotationData, (JNIEnv* env, jobject, ARGUMENT_PAIR(klass), + jobject filter, jint filter_length, jlong filter_klass_pointers)) + InstanceKlass* holder = InstanceKlass::cast(UNPACK_PAIR(Klass, klass)); + return get_encoded_annotation_data(holder, holder->class_annotations(), true, filter_length, filter_klass_pointers, THREAD, JVMCIENV); +C2V_END + +C2V_VMENTRY_NULL(jbyteArray, getEncodedExecutableAnnotationData, (JNIEnv* env, jobject, ARGUMENT_PAIR(method), + jobject filter, jint filter_length, jlong filter_klass_pointers)) + methodHandle method(THREAD, UNPACK_PAIR(Method, method)); + return get_encoded_annotation_data(method->method_holder(), method->annotations(), false, filter_length, filter_klass_pointers, THREAD, JVMCIENV); +C2V_END + +C2V_VMENTRY_NULL(jbyteArray, getEncodedFieldAnnotationData, (JNIEnv* env, jobject, ARGUMENT_PAIR(klass), jint index, + jobject filter, jint filter_length, jlong filter_klass_pointers)) + InstanceKlass* holder = check_field(InstanceKlass::cast(UNPACK_PAIR(Klass, klass)), index, JVMCIENV); + fieldDescriptor fd(holder, index); + return get_encoded_annotation_data(holder, fd.annotations(), false, filter_length, filter_klass_pointers, THREAD, JVMCIENV); +C2V_END + C2V_VMENTRY_NULL(jobjectArray, getFailedSpeculations, (JNIEnv* env, jobject, jlong failed_speculations_address, jobjectArray current)) FailedSpeculation* head = *((FailedSpeculation**)(address) failed_speculations_address); int result_length = 0; @@ -2969,6 +3056,9 @@ JNINativeMethod CompilerToVM::methods[] = { {CC "getCode", CC "(" HS_INSTALLED_CODE ")[B", FN_PTR(getCode)}, {CC "asReflectionExecutable", CC "(" HS_METHOD2 ")" REFLECTION_EXECUTABLE, FN_PTR(asReflectionExecutable)}, {CC "asReflectionField", CC "(" HS_KLASS2 "I)" REFLECTION_FIELD, FN_PTR(asReflectionField)}, + {CC "getEncodedClassAnnotationData", CC "(" HS_KLASS2 OBJECT "IJ)[B", FN_PTR(getEncodedClassAnnotationData)}, + {CC "getEncodedExecutableAnnotationData", CC "(" HS_METHOD2 OBJECT "IJ)[B", FN_PTR(getEncodedExecutableAnnotationData)}, + {CC "getEncodedFieldAnnotationData", CC "(" HS_KLASS2 "I" OBJECT "IJ)[B", FN_PTR(getEncodedFieldAnnotationData)}, {CC "getFailedSpeculations", CC "(J[[B)[[B", FN_PTR(getFailedSpeculations)}, {CC "getFailedSpeculationsAddress", CC "(" HS_METHOD2 ")J", FN_PTR(getFailedSpeculationsAddress)}, {CC "releaseFailedSpeculations", CC "(J)V", FN_PTR(releaseFailedSpeculations)}, diff --git a/src/hotspot/share/jvmci/vmStructs_jvmci.cpp b/src/hotspot/share/jvmci/vmStructs_jvmci.cpp index 15276d44d65..05631351de4 100644 --- a/src/hotspot/share/jvmci/vmStructs_jvmci.cpp +++ b/src/hotspot/share/jvmci/vmStructs_jvmci.cpp @@ -104,6 +104,7 @@ \ static_field(Abstract_VM_Version, _features, uint64_t) \ \ + nonstatic_field(Annotations, _class_annotations, AnnotationArray*) \ nonstatic_field(Annotations, _fields_annotations, Array*) \ \ nonstatic_field(Array, _length, int) \ diff --git a/src/java.base/share/classes/jdk/internal/vm/VMSupport.java b/src/java.base/share/classes/jdk/internal/vm/VMSupport.java index 86472e83830..659645a5006 100644 --- a/src/java.base/share/classes/jdk/internal/vm/VMSupport.java +++ b/src/java.base/share/classes/jdk/internal/vm/VMSupport.java @@ -24,16 +24,28 @@ */ package jdk.internal.vm; +import jdk.internal.misc.Unsafe; +import jdk.internal.misc.VM; +import jdk.internal.access.SharedSecrets; +import jdk.internal.access.JavaLangAccess; +import jdk.internal.reflect.ConstantPool; +import sun.reflect.annotation.AnnotationParser; +import sun.reflect.annotation.AnnotationSupport; +import sun.reflect.annotation.AnnotationType; + +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.annotation.IncompleteAnnotationException; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; import java.util.Properties; import java.util.Set; -import java.util.jar.JarFile; -import java.util.jar.Manifest; -import java.util.jar.Attributes; - -import jdk.internal.misc.VM; -import jdk.internal.misc.Unsafe; +import java.util.List; /* * Support class used by JVMCI, JVMTI and VM attach mechanism. @@ -167,4 +179,404 @@ public class VMSupport { U.copyMemory(encoding, Unsafe.ARRAY_BYTE_BASE_OFFSET, null, buffer + 4, encoding.length); return requiredSize; } + + /** + * Parses {@code rawAnnotations} into a list of {@link Annotation}s and then + * serializes them to a byte array with {@link #encodeAnnotations(Collection)}. + */ + public static byte[] encodeAnnotations(byte[] rawAnnotations, + Class declaringClass, + ConstantPool cp, + boolean forClass, + Class[] selectAnnotationClasses) + { + for (Class c : selectAnnotationClasses) { + if (!c.isAnnotation()) { + throw new IllegalArgumentException(c + " is not an annotation interface"); + } + } + Map, Annotation> annotations = + AnnotationParser.parseSelectAnnotations(rawAnnotations, cp, declaringClass, selectAnnotationClasses); + if (forClass && annotations.size() != selectAnnotationClasses.length) { + Class superClass = declaringClass.getSuperclass(); + nextSuperClass: + while (superClass != null) { + JavaLangAccess jla = SharedSecrets.getJavaLangAccess(); + Map, Annotation> superAnnotations = + AnnotationParser.parseSelectAnnotations( + jla.getRawClassAnnotations(superClass), + jla.getConstantPool(superClass), + superClass, + selectAnnotationClasses); + + for (Map.Entry, Annotation> e : superAnnotations.entrySet()) { + Class annotationClass = e.getKey(); + if (!annotations.containsKey(annotationClass) && AnnotationType.getInstance(annotationClass).isInherited()) { + if (annotations.isEmpty()) { + // An empty map might be unmodifiable (e.g. Collections.emptyMap()). + annotations = new LinkedHashMap, Annotation>(); + } + annotations.put(annotationClass, e.getValue()); + if (annotations.size() == selectAnnotationClasses.length) { + break nextSuperClass; + } + } + } + superClass = superClass.getSuperclass(); + } + } + return encodeAnnotations(annotations.values()); + } + + /** + * Encodes annotations to a byte array. The byte array can be decoded with {@link #decodeAnnotations(byte[], AnnotationDecoder)}. + */ + public static byte[] encodeAnnotations(Collection annotations) { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(128); + try (DataOutputStream dos = new DataOutputStream(baos)) { + writeLength(dos, annotations.size()); + for (Annotation a : annotations) { + encodeAnnotation(dos, a); + } + } + return baos.toByteArray(); + } catch (Exception e) { + throw new InternalError(e); + } + } + + private static void encodeAnnotation(DataOutputStream dos, Annotation a) throws Exception { + Class type = a.annotationType(); + Map values = AnnotationSupport.memberValues(a); + dos.writeUTF(type.getName()); + writeLength(dos, values.size()); + for (Map.Entry e : values.entrySet()) { + Object value = e.getValue(); + if (value == null) { + // IncompleteAnnotationException + dos.writeByte('x'); + dos.writeUTF(new IncompleteAnnotationException(type, e.getKey()).toString()); + continue; + } + Class valueType = value.getClass(); + dos.writeUTF(e.getKey()); + if (valueType == Byte.class) { + dos.writeByte('B'); + dos.writeByte((byte) value); + } else if (valueType == Character.class) { + dos.writeByte('C'); + dos.writeChar((char) value); + } else if (valueType == Double.class) { + dos.writeByte('D'); + dos.writeDouble((double) value); + } else if (valueType == Float.class) { + dos.writeByte('F'); + dos.writeFloat((float) value); + } else if (valueType == Integer.class) { + dos.writeByte('I'); + dos.writeInt((int) value); + } else if (valueType == Long.class) { + dos.writeByte('J'); + dos.writeLong((long) value); + } else if (valueType == Short.class) { + dos.writeByte('S'); + dos.writeShort((short) value); + } else if (valueType == Boolean.class) { + dos.writeByte('Z'); + dos.writeBoolean((boolean) value); + } else if (valueType == String.class) { + dos.writeByte('s'); + dos.writeUTF((String) value); + } else if (valueType == Class.class) { + dos.writeByte('c'); + dos.writeUTF(((Class) value).getName()); + } else if (valueType.isEnum()) { + dos.writeByte('e'); + dos.writeUTF(valueType.getName()); + dos.writeUTF(((Enum) value).name()); + } else if (value instanceof Annotation) { + dos.writeByte('@'); + encodeAnnotation(dos, (Annotation) value); + } else if (valueType.isArray()) { + Class componentType = valueType.getComponentType(); + if (componentType == byte.class) { + byte[] array = (byte[]) value; + dos.writeByte('['); + dos.writeByte('B'); + writeLength(dos, array.length); + dos.write(array); + } else if (componentType == char.class) { + char[] array = (char[]) value; + dos.writeByte('['); + dos.writeByte('C'); + writeLength(dos, array.length); + for (char c : array) { + dos.writeChar(c); + } + } else if (componentType == double.class) { + double[] array = (double[]) value; + dos.writeByte('['); + dos.writeByte('D'); + writeLength(dos, array.length); + for (double v : array) { + dos.writeDouble(v); + } + } else if (componentType == float.class) { + float[] array = (float[]) value; + dos.writeByte('['); + dos.writeByte('F'); + writeLength(dos, array.length); + for (float v : array) { + dos.writeFloat(v); + } + } else if (componentType == int.class) { + int[] array = (int[]) value; + dos.writeByte('['); + dos.writeByte('I'); + writeLength(dos, array.length); + for (int j : array) { + dos.writeInt(j); + } + } else if (componentType == long.class) { + long[] array = (long[]) value; + dos.writeByte('['); + dos.writeByte('J'); + writeLength(dos, array.length); + for (long l : array) { + dos.writeLong(l); + } + } else if (componentType == short.class) { + short[] array = (short[]) value; + dos.writeByte('['); + dos.writeByte('S'); + writeLength(dos, array.length); + for (short item : array) { + dos.writeShort(item); + } + } else if (componentType == boolean.class) { + boolean[] array = (boolean[]) value; + dos.writeByte('['); + dos.writeByte('Z'); + writeLength(dos, array.length); + for (boolean b : array) { + dos.writeBoolean(b); + } + } else if (componentType == String.class) { + String[] array = (String[]) value; + dos.writeByte('['); + dos.writeByte('s'); + writeLength(dos, array.length); + for (String s : array) { + dos.writeUTF(s); + } + } else if (componentType == Class.class) { + Class[] array = (Class[]) value; + dos.writeByte('['); + dos.writeByte('c'); + writeLength(dos, array.length); + for (Class aClass : array) { + dos.writeUTF(aClass.getName()); + } + } else if (componentType.isEnum()) { + Enum[] array = (Enum[]) value; + dos.writeByte('['); + dos.writeByte('e'); + dos.writeUTF(componentType.getName()); + writeLength(dos, array.length); + for (Enum anEnum : array) { + dos.writeUTF(anEnum.name()); + } + } else if (componentType.isAnnotation()) { + Annotation[] array = (Annotation[]) value; + dos.writeByte('['); + dos.writeByte('@'); + writeLength(dos, array.length); + for (Annotation annotation : array) { + encodeAnnotation(dos, annotation); + } + } else { + dos.writeByte('x'); + dos.writeUTF(value.toString()); + } + + } else { + dos.writeByte('x'); + dos.writeUTF(value.toString()); + } + } + } + + /** + * Helper for {@link #decodeAnnotations(byte[], AnnotationDecoder)} to convert a byte + * array (ostensibly produced by {@link VMSupport#encodeAnnotations}) into objects. + * + * @param type to which a type name is {@linkplain #resolveType(String) resolved} + * @param type of the object representing a decoded annotation + * @param type of the object representing a decoded enum constant + * @param type of the object representing a decoded error + */ + public interface AnnotationDecoder { + /** + * Resolves a name in {@link Class#getName()} format to an object of type {@code T}. + */ + T resolveType(String name); + + /** + * Creates an object representing a decoded annotation. + * + * @param type the annotation interface of the annotation + * @param elements elements of the annotation + */ + A newAnnotation(T type, Map.Entry[] elements); + + /** + * Creates an object representing a decoded enum constant. + * + * @param enumType the enum type + * @param name the name of the enum constant + */ + E newEnumValue(T enumType, String name); + + /** + * Creates an object representing a decoded error value. + * + * @param description of the error + */ + X newErrorValue(String description); + } + + /** + * Decodes annotations serialized in {@code encoded} to objects. + * + * @param type to which a type name is resolved + * @param type of the object representing a decoded annotation + * @param type of the object representing a decoded enum constant + * @param type of the object representing a decoded error + * @return an immutable list of {@code A} objects + */ + @SuppressWarnings({"rawtypes", "unchecked"}) + public static List decodeAnnotations(byte[] encoded, AnnotationDecoder decoder) { + try { + ByteArrayInputStream bais = new ByteArrayInputStream(encoded); + DataInputStream dis = new DataInputStream(bais); + return (List) readArray(dis, () -> decodeAnnotation(dis, decoder)); + } catch (Exception e) { + throw new InternalError(e); + } + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + private static A decodeAnnotation(DataInputStream dis, AnnotationDecoder decoder) throws IOException { + String typeName = dis.readUTF(); + T type = decoder.resolveType(typeName); + int n = readLength(dis); + Map.Entry[] elements = new Map.Entry[n]; + for (int i = 0; i < n; i++) { + String name = dis.readUTF(); + byte tag = dis.readByte(); + elements[i] = Map.entry(name, switch (tag) { + case 'B' -> dis.readByte(); + case 'C' -> dis.readChar(); + case 'D' -> dis.readDouble(); + case 'F' -> dis.readFloat(); + case 'I' -> dis.readInt(); + case 'J' -> dis.readLong(); + case 'S' -> dis.readShort(); + case 'Z' -> dis.readBoolean(); + case 's' -> dis.readUTF(); + case 'c' -> decoder.resolveType(dis.readUTF()); + case 'e' -> decoder.newEnumValue(decoder.resolveType(dis.readUTF()), dis.readUTF()); + case '@' -> decodeAnnotation(dis, decoder); + case '[' -> decodeArray(dis, decoder); + case 'x' -> decoder.newErrorValue(dis.readUTF()); + default -> throw new InternalError("Unsupported tag: " + tag); + }); + } + return decoder.newAnnotation(type, (Map.Entry[]) elements); + } + @FunctionalInterface + interface IOReader { + Object read() throws IOException; + } + + private static Object decodeArray(DataInputStream dis, AnnotationDecoder decoder) throws IOException { + byte componentTag = dis.readByte(); + return switch (componentTag) { + case 'B' -> readArray(dis, dis::readByte); + case 'C' -> readArray(dis, dis::readChar); + case 'D' -> readArray(dis, dis::readDouble); + case 'F' -> readArray(dis, dis::readFloat); + case 'I' -> readArray(dis, dis::readInt); + case 'J' -> readArray(dis, dis::readLong); + case 'S' -> readArray(dis, dis::readShort); + case 'Z' -> readArray(dis, dis::readBoolean); + case 's' -> readArray(dis, dis::readUTF); + case 'c' -> readArray(dis, () -> readClass(dis, decoder)); + case 'e' -> { + T enumType = decoder.resolveType(dis.readUTF()); + yield readArray(dis, () -> readEnum(dis, decoder, enumType)); + } + case '@' -> readArray(dis, () -> decodeAnnotation(dis, decoder)); + default -> throw new InternalError("Unsupported component tag: " + componentTag); + }; + } + + /** + * Reads an enum encoded at the current read position of {@code dis} and + * returns it as an object of type {@code E}. + */ + private static E readEnum(DataInputStream dis, AnnotationDecoder decoder, T enumType) throws IOException { + return decoder.newEnumValue(enumType, dis.readUTF()); + } + + /** + * Reads a class encoded at the current read position of {@code dis} and + * returns it as an object of type {@code T}. + */ + private static T readClass(DataInputStream dis, AnnotationDecoder decoder) throws IOException { + return decoder.resolveType(dis.readUTF()); + } + + /** + * Reads an array encoded at the current read position of {@code dis} and + * returns it in an immutable list. + * + * @param reader reads array elements from {@code dis} + * @return an immutable list of {@code A} objects + */ + private static List readArray(DataInputStream dis, IOReader reader) throws IOException { + Object[] array = new Object[readLength(dis)]; + for (int i = 0; i < array.length; i++) { + array[i] = reader.read(); + } + return List.of(array); + } + + /** + * Encodes {@code length} in 1 byte if it is less than 128. + */ + private static void writeLength(DataOutputStream dos, int length) throws IOException { + if (length < 0) { + throw new NegativeArraySizeException(); + } else if (length <= 127) { + dos.writeByte((byte) (0x80 | length)); + } else { + dos.writeInt(length); + } + } + + private static int readLength(DataInputStream dis) throws IOException { + int ch1 = dis.readByte(); + int length; + if (ch1 < 0) { + length = ch1 & 0x7F; + } else { + int ch2 = dis.read(); + int ch3 = dis.read(); + int ch4 = dis.read(); + length = (ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0); + } + return length; + } } diff --git a/src/java.base/share/classes/module-info.java b/src/java.base/share/classes/module-info.java index 1576df9a236..64d621b602c 100644 --- a/src/java.base/share/classes/module-info.java +++ b/src/java.base/share/classes/module-info.java @@ -259,7 +259,8 @@ module java.base { jdk.incubator.concurrent, jdk.internal.jvmstat, jdk.management, - jdk.management.agent; + jdk.management.agent, + jdk.internal.vm.ci; exports jdk.internal.vm.annotation to java.instrument, jdk.internal.vm.ci, diff --git a/src/java.base/share/classes/sun/reflect/annotation/AnnotationInvocationHandler.java b/src/java.base/share/classes/sun/reflect/annotation/AnnotationInvocationHandler.java index c006c60e4eb..8ae1be2c54e 100644 --- a/src/java.base/share/classes/sun/reflect/annotation/AnnotationInvocationHandler.java +++ b/src/java.base/share/classes/sun/reflect/annotation/AnnotationInvocationHandler.java @@ -677,6 +677,13 @@ class AnnotationInvocationHandler implements InvocationHandler, Serializable { UnsafeAccessor.setMemberValues(this, mv); } + /** + * Gets an unmodifiable view on the member values. + */ + Map memberValues() { + return Collections.unmodifiableMap(memberValues); + } + private static class UnsafeAccessor { private static final jdk.internal.misc.Unsafe unsafe = jdk.internal.misc.Unsafe.getUnsafe(); diff --git a/src/java.base/share/classes/sun/reflect/annotation/AnnotationParser.java b/src/java.base/share/classes/sun/reflect/annotation/AnnotationParser.java index 16058df7aea..616fa075d57 100644 --- a/src/java.base/share/classes/sun/reflect/annotation/AnnotationParser.java +++ b/src/java.base/share/classes/sun/reflect/annotation/AnnotationParser.java @@ -83,14 +83,14 @@ public class AnnotationParser { * Like {@link #parseAnnotations(byte[], sun.reflect.ConstantPool, Class)} * with an additional parameter {@code selectAnnotationClasses} which selects the * annotation types to parse (other than selected are quickly skipped).

- * This method is only used to parse select meta annotations in the construction + * This method is used to parse select meta annotations in the construction * phase of {@link AnnotationType} instances to prevent infinite recursion. * * @param selectAnnotationClasses an array of annotation types to select when parsing */ @SafeVarargs @SuppressWarnings("varargs") // selectAnnotationClasses is used safely - static Map, Annotation> parseSelectAnnotations( + public static Map, Annotation> parseSelectAnnotations( byte[] rawAnnotations, ConstantPool constPool, Class container, @@ -336,6 +336,8 @@ public class AnnotationParser { ByteBuffer buf, ConstantPool constPool, Class container) { + // Note that VMSupport.encodeAnnotation (used by JVMCI) may need to + // be updated if new annotation member types are added. Object result = null; int tag = buf.get(); switch(tag) { diff --git a/src/java.base/share/classes/sun/reflect/annotation/AnnotationSupport.java b/src/java.base/share/classes/sun/reflect/annotation/AnnotationSupport.java index 55b6edea730..a70ffafd363 100644 --- a/src/java.base/share/classes/sun/reflect/annotation/AnnotationSupport.java +++ b/src/java.base/share/classes/sun/reflect/annotation/AnnotationSupport.java @@ -281,4 +281,13 @@ public final class AnnotationSupport { } } } + + /** + * Gets an unmodifiable view of {@code a}'s elements. + * + * @return a map from element names to element values + */ + public static Map memberValues(Annotation a) { + return ((AnnotationInvocationHandler) Proxy.getInvocationHandler(a)).memberValues(); + } } diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/AnnotationDataDecoder.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/AnnotationDataDecoder.java new file mode 100644 index 00000000000..9cc33a395fc --- /dev/null +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/AnnotationDataDecoder.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2023, 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. + */ +package jdk.vm.ci.hotspot; + +import java.util.Map; + +import jdk.internal.vm.VMSupport.AnnotationDecoder; +import jdk.vm.ci.meta.AnnotationData; +import jdk.vm.ci.meta.EnumData; +import jdk.vm.ci.meta.ErrorData; +import jdk.vm.ci.meta.JavaType; +import jdk.vm.ci.meta.MetaUtil; +import jdk.vm.ci.meta.ResolvedJavaType; +import jdk.vm.ci.meta.UnresolvedJavaType; + +/** + * Implementation of {@link AnnotationDecoder} that resolves type names to {@link JavaType} values + * and employs {@link AnnotationData} and {@link EnumData} to represent decoded annotations and enum + * constants respectively. + */ +final class AnnotationDataDecoder implements AnnotationDecoder { + + static final AnnotationDataDecoder INSTANCE = new AnnotationDataDecoder(); + + @Override + public JavaType resolveType(String name) { + String internalName = MetaUtil.toInternalName(name); + return UnresolvedJavaType.create(internalName); + } + + @Override + public AnnotationData newAnnotation(JavaType type, Map.Entry[] elements) { + return new AnnotationData(type, elements); + } + + @Override + public EnumData newEnumValue(JavaType enumType, String name) { + return new EnumData(enumType, name); + } + + @Override + public ErrorData newErrorValue(String description) { + return new ErrorData(description); + } + + static ResolvedJavaType[] asArray(ResolvedJavaType type1, ResolvedJavaType type2, ResolvedJavaType... types) { + ResolvedJavaType[] filter = new ResolvedJavaType[2 + types.length]; + filter[0] = type1; + filter[1] = type2; + System.arraycopy(types, 0, filter, 2, types.length); + return filter; + } +} diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/CompilerToVM.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/CompilerToVM.java index cbc542a7618..21aef3d9f7c 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/CompilerToVM.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/CompilerToVM.java @@ -29,6 +29,7 @@ import static jdk.vm.ci.hotspot.HotSpotJVMCIRuntime.runtime; import java.lang.reflect.Executable; import java.lang.reflect.Field; +import jdk.internal.misc.Unsafe; import jdk.vm.ci.code.BytecodeFrame; import jdk.vm.ci.code.InstalledCode; import jdk.vm.ci.code.InvalidInstalledCodeException; @@ -48,6 +49,12 @@ import jdk.vm.ci.meta.ResolvedJavaType; * Calls from Java into HotSpot. The behavior of all the methods in this class that take a native * pointer as an argument (e.g., {@link #getSymbol(long)}) is undefined if the argument does not * denote a valid native object. + * + * Note also that some calls pass a raw VM value to avoid a JNI upcall. For example, + * {@link #getBytecode(HotSpotResolvedJavaMethodImpl, long)} needs the raw {@code Method*} value + * (stored in {@link HotSpotResolvedJavaMethodImpl#methodHandle}) in the C++ implementation. The + * {@link HotSpotResolvedJavaMethodImpl} wrapper is still passed as well as it may be the last + * reference keeping the raw value alive. */ final class CompilerToVM { /** @@ -1303,4 +1310,89 @@ final class CompilerToVM { native void notifyCompilerInliningEvent(int compileId, HotSpotResolvedJavaMethodImpl caller, long callerPointer, HotSpotResolvedJavaMethodImpl callee, long calleePointer, boolean succeeded, String message, int bci); + + /** + * Gets the serialized annotation info for {@code type} by calling + * {@code VMSupport.encodeAnnotations} in the HotSpot heap. + */ + byte[] getEncodedClassAnnotationData(HotSpotResolvedObjectTypeImpl type, ResolvedJavaType[] filter) { + try (KlassPointers a = new KlassPointers(filter)) { + return getEncodedClassAnnotationData(type, type.getKlassPointer(), + a.types, a.types.length, a.buffer()); + } + } + + native byte[] getEncodedClassAnnotationData(HotSpotResolvedObjectTypeImpl type, long klassPointer, + Object filter, int filterLength, long filterKlassPointers); + + /** + * Gets the serialized annotation info for {@code method} by calling + * {@code VMSupport.encodeAnnotations} in the HotSpot heap. + */ + byte[] getEncodedExecutableAnnotationData(HotSpotResolvedJavaMethodImpl method, ResolvedJavaType[] filter) { + try (KlassPointers a = new KlassPointers(filter)) { + return getEncodedExecutableAnnotationData(method, method.getMethodPointer(), + a.types, a.types.length, a.buffer()); + } + } + + native byte[] getEncodedExecutableAnnotationData(HotSpotResolvedJavaMethodImpl method, long methodPointer, + Object filter, int filterLength, long filterKlassPointers); + + /** + * Gets the serialized annotation info for the field denoted by {@code holder} and + * {@code fieldIndex} by calling {@code VMSupport.encodeAnnotations} in the HotSpot heap. + */ + byte[] getEncodedFieldAnnotationData(HotSpotResolvedObjectTypeImpl holder, int fieldIndex, ResolvedJavaType[] filter) { + try (KlassPointers a = new KlassPointers(filter)) { + return getEncodedFieldAnnotationData(holder, holder.getKlassPointer(), fieldIndex, + a.types, a.types.length, a.buffer()); + } + } + + native byte[] getEncodedFieldAnnotationData(HotSpotResolvedObjectTypeImpl holder, long klassPointer, int fieldIndex, + Object filterTypes, int filterLength, long filterKlassPointers); + + /** + * Helper for passing {@Klass*} values to native code. + */ + static final class KlassPointers implements AutoCloseable { + final ResolvedJavaType[] types; + long pointersArray; + final Unsafe unsafe = UnsafeAccess.UNSAFE; + + KlassPointers(ResolvedJavaType[] types) { + this.types = types; + } + + /** + * Gets the buffer in which to pass the {@Klass*} values to JNI. + * + * @return a {@Klass*} value if {@code types.length == 1} otherwise the address of a native + * buffer holding an array of {@Klass*} values + */ + long buffer() { + int length = types.length; + if (length == 1) { + return ((HotSpotResolvedObjectTypeImpl) types[0]).getKlassPointer(); + } else { + pointersArray = unsafe.allocateMemory(length * Long.BYTES); + long pos = pointersArray; + for (int i = 0; i < types.length; i++) { + HotSpotResolvedObjectTypeImpl hsType = (HotSpotResolvedObjectTypeImpl) types[i]; + unsafe.putLong(pos, hsType.getKlassPointer()); + pos += Long.BYTES; + } + } + return pointersArray; + } + + @Override + public void close() { + if (types.length != 1 && pointersArray != 0) { + unsafe.freeMemory(pointersArray); + pointersArray = 0; + } + } + } } diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedJavaFieldImpl.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedJavaFieldImpl.java index 1103f592b26..6eb4dfb9b02 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedJavaFieldImpl.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedJavaFieldImpl.java @@ -23,14 +23,17 @@ package jdk.vm.ci.hotspot; import static jdk.internal.misc.Unsafe.ADDRESS_SIZE; +import static jdk.vm.ci.hotspot.CompilerToVM.compilerToVM; import static jdk.vm.ci.hotspot.HotSpotJVMCIRuntime.runtime; import static jdk.vm.ci.hotspot.HotSpotVMConfig.config; import static jdk.vm.ci.hotspot.UnsafeAccess.UNSAFE; import java.lang.annotation.Annotation; +import java.util.Collections; +import java.util.List; -import jdk.internal.vm.annotation.Stable; - +import jdk.internal.vm.VMSupport; +import jdk.vm.ci.meta.AnnotationData; import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.JavaType; import jdk.vm.ci.meta.ResolvedJavaType; @@ -227,4 +230,25 @@ class HotSpotResolvedJavaFieldImpl implements HotSpotResolvedJavaField { public JavaConstant getConstantValue() { return holder.getFieldInfo(index).getConstantValue(holder); } + + @Override + public AnnotationData getAnnotationData(ResolvedJavaType annotationType) { + if (!hasAnnotations()) { + return null; + } + return getAnnotationData0(annotationType).get(0); + } + + @Override + public List getAnnotationData(ResolvedJavaType type1, ResolvedJavaType type2, ResolvedJavaType... types) { + if (!hasAnnotations()) { + return Collections.emptyList(); + } + return getAnnotationData0(AnnotationDataDecoder.asArray(type1, type2, types)); + } + + private List getAnnotationData0(ResolvedJavaType... filter) { + byte[] encoded = compilerToVM().getEncodedFieldAnnotationData(holder, index, filter); + return VMSupport.decodeAnnotations(encoded, AnnotationDataDecoder.INSTANCE); + } } diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedJavaMethodImpl.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedJavaMethodImpl.java index 0850d3625d6..3cbecb684d7 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedJavaMethodImpl.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedJavaMethodImpl.java @@ -35,10 +35,14 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Executable; import java.lang.reflect.Modifier; import java.lang.reflect.Type; +import java.util.Collections; +import java.util.List; import java.util.Objects; +import jdk.internal.vm.VMSupport; import jdk.vm.ci.common.JVMCIError; import jdk.vm.ci.hotspot.HotSpotJVMCIRuntime.Option; +import jdk.vm.ci.meta.AnnotationData; import jdk.vm.ci.meta.Constant; import jdk.vm.ci.meta.ConstantPool; import jdk.vm.ci.meta.DefaultProfilingInfo; @@ -523,7 +527,7 @@ final class HotSpotResolvedJavaMethodImpl extends HotSpotMethod implements HotSp @Override public Annotation[] getAnnotations() { - if ((getConstMethodFlags() & config().constMethodHasMethodAnnotations) == 0 || isClassInitializer()) { + if (!hasAnnotations()) { return new Annotation[0]; } return runtime().reflection.getMethodAnnotations(this); @@ -531,7 +535,7 @@ final class HotSpotResolvedJavaMethodImpl extends HotSpotMethod implements HotSp @Override public Annotation[] getDeclaredAnnotations() { - if ((getConstMethodFlags() & config().constMethodHasMethodAnnotations) == 0 || isClassInitializer()) { + if (!hasAnnotations()) { return new Annotation[0]; } return runtime().reflection.getMethodDeclaredAnnotations(this); @@ -539,12 +543,19 @@ final class HotSpotResolvedJavaMethodImpl extends HotSpotMethod implements HotSp @Override public T getAnnotation(Class annotationClass) { - if ((getConstMethodFlags() & config().constMethodHasMethodAnnotations) == 0 || isClassInitializer()) { + if (!hasAnnotations()) { return null; } return runtime().reflection.getMethodAnnotation(this, annotationClass); } + /** + * Returns whether this method has annotations. + */ + private boolean hasAnnotations() { + return (getConstMethodFlags() & config().constMethodHasMethodAnnotations) != 0 && !isClassInitializer(); + } + @Override public boolean isBridge() { return (BRIDGE & getModifiers()) != 0; @@ -752,4 +763,25 @@ final class HotSpotResolvedJavaMethodImpl extends HotSpotMethod implements HotSp public int methodIdnum() { return UNSAFE.getChar(getConstMethod() + config().constMethodMethodIdnumOffset); } + + @Override + public AnnotationData getAnnotationData(ResolvedJavaType type) { + if (!hasAnnotations()) { + return null; + } + return getAnnotationData0(type).get(0); + } + + @Override + public List getAnnotationData(ResolvedJavaType type1, ResolvedJavaType type2, ResolvedJavaType... types) { + if (!hasAnnotations()) { + return Collections.emptyList(); + } + return getAnnotationData0(AnnotationDataDecoder.asArray(type1, type2, types)); + } + + private List getAnnotationData0(ResolvedJavaType... filter) { + byte[] encoded = compilerToVM().getEncodedExecutableAnnotationData(this, filter); + return VMSupport.decodeAnnotations(encoded, AnnotationDataDecoder.INSTANCE); + } } diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedObjectTypeImpl.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedObjectTypeImpl.java index 11d08eb1c2b..ad61d11a4c4 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedObjectTypeImpl.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedObjectTypeImpl.java @@ -35,10 +35,14 @@ import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.nio.ByteOrder; import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.List; +import jdk.internal.vm.VMSupport; import jdk.vm.ci.common.JVMCIError; +import jdk.vm.ci.meta.AnnotationData; import jdk.vm.ci.meta.Assumptions.AssumptionResult; import jdk.vm.ci.meta.Assumptions.ConcreteMethod; import jdk.vm.ci.meta.Assumptions.ConcreteSubtype; @@ -871,18 +875,56 @@ final class HotSpotResolvedObjectTypeImpl extends HotSpotResolvedJavaType implem return getConstantPool().getSourceFileName(); } + /** + * Determines if this type may have annotations. A positive result does not mean this type has + * annotations but a negative result guarantees this type has no annotations. + * + * @param includingInherited if true, expand this query to include superclasses of this type + */ + private boolean mayHaveAnnotations(boolean includingInherited) { + if (isArray()) { + return false; + } + HotSpotVMConfig config = config(); + final long metaspaceAnnotations = UNSAFE.getAddress(getKlassPointer() + config.instanceKlassAnnotationsOffset); + if (metaspaceAnnotations != 0) { + long classAnnotations = UNSAFE.getAddress(metaspaceAnnotations + config.annotationsClassAnnotationsOffset); + if (classAnnotations != 0) { + return true; + } + } + if (includingInherited) { + HotSpotResolvedObjectTypeImpl superClass = getSuperclass(); + if (superClass != null) { + return superClass.mayHaveAnnotations(true); + } + } + return false; + } + + private static final Annotation[] NO_ANNOTATIONS = {}; + @Override public Annotation[] getAnnotations() { + if (!mayHaveAnnotations(true)) { + return NO_ANNOTATIONS; + } return runtime().reflection.getAnnotations(this); } @Override public Annotation[] getDeclaredAnnotations() { + if (!mayHaveAnnotations(false)) { + return NO_ANNOTATIONS; + } return runtime().reflection.getDeclaredAnnotations(this); } @Override public T getAnnotation(Class annotationClass) { + if (!mayHaveAnnotations(true)) { + return null; + } return runtime().reflection.getAnnotation(this, annotationClass); } @@ -1062,4 +1104,25 @@ final class HotSpotResolvedObjectTypeImpl extends HotSpotResolvedJavaType implem public boolean isCloneableWithAllocation() { return (getAccessFlags() & config().jvmAccIsCloneableFast) != 0; } + + @Override + public AnnotationData getAnnotationData(ResolvedJavaType annotationType) { + if (!mayHaveAnnotations(true)) { + return null; + } + return getAnnotationData0(annotationType).get(0); + } + + @Override + public List getAnnotationData(ResolvedJavaType type1, ResolvedJavaType type2, ResolvedJavaType... types) { + if (!mayHaveAnnotations(true)) { + return Collections.emptyList(); + } + return getAnnotationData0(AnnotationDataDecoder.asArray(type1, type2, types)); + } + + private List getAnnotationData0(ResolvedJavaType... filter) { + byte[] encoded = compilerToVM().getEncodedClassAnnotationData(this, filter); + return VMSupport.decodeAnnotations(encoded, AnnotationDataDecoder.INSTANCE); + } } diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedPrimitiveType.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedPrimitiveType.java index 84a1ab13fae..d95824998bc 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedPrimitiveType.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedPrimitiveType.java @@ -27,9 +27,12 @@ import static jdk.vm.ci.hotspot.HotSpotJVMCIRuntime.runtime; import java.lang.annotation.Annotation; import java.lang.reflect.Modifier; +import java.util.Collections; +import java.util.List; import jdk.vm.ci.common.JVMCIError; import jdk.vm.ci.common.NativeImageReinitialize; +import jdk.vm.ci.meta.AnnotationData; import jdk.vm.ci.meta.Assumptions.AssumptionResult; import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.JavaKind; @@ -317,4 +320,15 @@ public final class HotSpotResolvedPrimitiveType extends HotSpotResolvedJavaType JavaConstant getJavaMirror() { return mirror; } + + @Override + public AnnotationData getAnnotationData(ResolvedJavaType type) { + return null; + } + + @Override + public List getAnnotationData(ResolvedJavaType type1, ResolvedJavaType type2, ResolvedJavaType... types) { + return Collections.emptyList(); + } + } diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotVMConfig.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotVMConfig.java index 223b210c555..b9ad5b77102 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotVMConfig.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotVMConfig.java @@ -110,6 +110,7 @@ class HotSpotVMConfig extends HotSpotVMConfigAccess { final int instanceKlassStateBeingInitialized = getConstant("InstanceKlass::being_initialized", Integer.class); final int annotationsFieldAnnotationsOffset = getFieldOffset("Annotations::_fields_annotations", Integer.class, "Array*"); + final int annotationsClassAnnotationsOffset = getFieldOffset("Annotations::_class_annotations", Integer.class, "AnnotationArray*"); final int fieldsAnnotationsBaseOffset = getFieldValue("CompilerToVM::Data::_fields_annotations_base_offset", Integer.class, "int"); final int arrayU1LengthOffset = getFieldOffset("Array::_length", Integer.class, "int"); diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/IndirectHotSpotObjectConstantImpl.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/IndirectHotSpotObjectConstantImpl.java index b43859fe5d4..b89aae78aeb 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/IndirectHotSpotObjectConstantImpl.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/IndirectHotSpotObjectConstantImpl.java @@ -40,7 +40,7 @@ import jdk.vm.ci.meta.JavaConstant; */ final class IndirectHotSpotObjectConstantImpl extends HotSpotObjectConstantImpl { /** - * An object handle in {@code JVMCI::_object_handles}. + * An object handle in {@code JVMCIRuntime::_oop_handles}. */ private long objectHandle; diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/Annotated.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/Annotated.java new file mode 100644 index 00000000000..666e1181cb1 --- /dev/null +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/Annotated.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2023, 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. + */ +package jdk.vm.ci.meta; + +import java.lang.annotation.Inherited; +import java.util.List; + +/** + * Represents a program element such as a method, constructor, field or class for which annotations + * may be present. + */ +public interface Annotated { + + /** + * Constructs the annotations present on this element whose types are in the set composed of {@code type1}, + * {@code type2} and {@code types}. All enum types referenced by the returned annotation are + * initialized. Class initialization is not triggered for enum types referenced by other + * annotations of this element. + * + * If this element is a class, then {@link Inherited} annotations are included in the set of + * annotations considered. + * + * See {@link java.lang.reflect.AnnotatedElement} for the definition of present. + * + * @param type1 an annotation type + * @param type2 an annotation type + * @param types more annotation types + * @return an immutable list of the annotations present on this element that match one of the + * given types + * @throws IllegalArgumentException if any type in the set composed of {@code type1}, + * {@code type2} and {@code types} is not an annotation interface type + * @throws UnsupportedOperationException if this operation is not supported + */ + default List getAnnotationData(ResolvedJavaType type1, ResolvedJavaType type2, ResolvedJavaType... types) { + throw new UnsupportedOperationException(); + } + + /** + * Constructs the annotation present on this element of type {@code type}. + * + * See {@link java.lang.reflect.AnnotatedElement} for the definition of present. + * + * @param type the type object corresponding to the annotation interface type + * @return this element's annotation for the specified annotation type if present on this + * element, else null + * @throws IllegalArgumentException if {@code type} is not an annotation interface type + * @throws UnsupportedOperationException if this operation is not supported + */ + default AnnotationData getAnnotationData(ResolvedJavaType type) { + throw new UnsupportedOperationException(); + } +} diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/AnnotationData.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/AnnotationData.java new file mode 100644 index 00000000000..17b6b714ba4 --- /dev/null +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/AnnotationData.java @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2023, 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. + */ +package jdk.vm.ci.meta; + +import java.lang.annotation.Annotation; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** + * Represents an annotation where element values are represented with the types described + * {@linkplain #get here}. + * + * In contrast to the standard annotation API based on {@link Annotation}, use of + * {@link AnnotationData} allows annotations to be queried without the JVMCI runtime having to + * support dynamic loading of arbitrary {@link Annotation} classes. Such support is impossible in a + * closed world, ahead-of-time compiled environment such as libgraal. + */ +public final class AnnotationData { + + private final JavaType type; + private final Map elements; + + private static final Set> ELEMENT_TYPES = Set.of( + Boolean.class, + Byte.class, + Character.class, + Short.class, + Integer.class, + Float.class, + Long.class, + Double.class, + String.class, + EnumData.class, + AnnotationData.class); + + /** + * Creates an annotation. + * + * @param type the annotation interface of this annotation, represented as a {@link JavaType} + * @param elements the names and values of this annotation's element values. Each value's type + * must be one of the {@code AnnotationData} types described {@linkplain #get here} + * or it must be a {@link ErrorData} object whose {@code toString()} value describes + * the error raised while parsing the element. There is no distinction between a + * value explicitly present in the annotation and an element's default value. + * @throws IllegalArgumentException if the value of an entry in {@code elements} is not of an + * accepted type + * @throws NullPointerException if any of the above parameters is null or any entry in + * {@code elements} is null + */ + @SuppressWarnings({"rawtypes", "unchecked"}) + public AnnotationData(JavaType type, Map.Entry[] elements) { + this.type = Objects.requireNonNull(type); + for (Map.Entry e : elements) { + Object value = e.getValue(); + if (!(value instanceof ErrorData) && + !(value instanceof JavaType) && + !(value instanceof List) && + !ELEMENT_TYPES.contains(value.getClass())) { + throw new IllegalArgumentException("illegal type for element " + e.getKey() + ": " + value.getClass().getName()); + } + } + this.elements = Map.ofEntries(elements); + } + + /** + * @return the annotation interface of this annotation, represented as a {@link JavaType} + */ + public JavaType getAnnotationType() { + return type; + } + + // @formatter:off + /** + * Gets the annotation element denoted by {@code name}. The following table shows the + * correspondence between the type of an element as declared by a method in the annotation + * interface and the type of value returned by this method: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Annotation AnnotationData
boolean Boolean
byte Byte
char Character
short Short
int Integer
float Float
long Long
double Double
String String
Class JavaType
Enum EnumData
Annotation AnnotationData
[]immutable List<T> where T is one of the above types
+ * + * @param the type of the element as per the {@code AnnotationData} column in the above + * table or {@link Object} + * @param elementType the class for the type of the element + * @return the annotation element denoted by {@code name} + * @throws ClassCastException if the element is not of type {@code V} + * @throws IllegalArgumentException if this annotation has no element named {@code name} or if + * there was an error parsing or creating the element value + */ + // @formatter:on + @SuppressWarnings("unchecked") + public V get(String name, Class elementType) { + Object val = elements.get(name); + if (val == null) { + throw new IllegalArgumentException("no element named " + name); + } + Class valClass = val.getClass(); + if (valClass == ErrorData.class) { + throw new IllegalArgumentException(val.toString()); + } + return elementType.cast(val); + } + + @Override + public String toString() { + return "@" + type.getName() + "(" + elements + ")"; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof AnnotationData) { + AnnotationData that = (AnnotationData) obj; + return this.type.equals(that.type) && this.elements.equals(that.elements); + + } + return false; + } + + @Override + public int hashCode() { + return type.hashCode() ^ elements.hashCode(); + } +} diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/EnumData.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/EnumData.java new file mode 100644 index 00000000000..4e8fc3a8ab7 --- /dev/null +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/EnumData.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2023, 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. + */ +package jdk.vm.ci.meta; + +/** + * Represents an enum constant within {@link AnnotationData}. + */ +public final class EnumData { + private final JavaType type; + private final String name; + + /** + * Creates an enum constant. + * + * @param type the {@linkplain Enum enum type} + * @param name the {@linkplain Enum#name() name} of the enum + */ + public EnumData(JavaType type, String name) { + this.type = type; + this.name = name; + } + + /** + * Gets the {@linkplain Enum enum type}. + */ + public JavaType getEnumType() { + return type; + } + + /** + * Gets the {@linkplain Enum#name() name} of the enum. + */ + public String getName() { + return name; + } + + @Override + public String toString() { + return name; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof EnumData) { + EnumData that = (EnumData) obj; + return this.type.equals(that.type) && this.name.equals(that.name); + } + return false; + } + + @Override + public int hashCode() { + return this.type.hashCode() ^ this.name.hashCode(); + } +} diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ErrorData.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ErrorData.java new file mode 100644 index 00000000000..72177ddcbbc --- /dev/null +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ErrorData.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2023, 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. + */ +package jdk.vm.ci.meta; + +/** + * Represents an error constant within {@link AnnotationData}. + * + * Similar to {@code sun.reflect.annotation.ExceptionProxy}. + */ +public final class ErrorData { + private final String description; + + /** + * Creates an error constant. + * + * @param description description of the error + */ + public ErrorData(String description) { + this.description = description; + } + + @Override + public String toString() { + return description; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof ErrorData) { + ErrorData that = (ErrorData) obj; + return this.description.equals(that.description); + } + return false; + } + + @Override + public int hashCode() { + return description.hashCode(); + } +} diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ResolvedJavaField.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ResolvedJavaField.java index a6b5b7fb2d0..cb891ab2e1b 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ResolvedJavaField.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ResolvedJavaField.java @@ -29,7 +29,7 @@ import java.lang.reflect.Modifier; * Represents a reference to a resolved Java field. Fields, like methods and types, are resolved * through {@link ConstantPool constant pools}. */ -public interface ResolvedJavaField extends JavaField, ModifiersProvider, AnnotatedElement { +public interface ResolvedJavaField extends JavaField, ModifiersProvider, AnnotatedElement, Annotated { /** * {@inheritDoc} diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ResolvedJavaMethod.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ResolvedJavaMethod.java index efb67ebf2f5..ce927e03ea1 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ResolvedJavaMethod.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ResolvedJavaMethod.java @@ -33,7 +33,7 @@ import java.lang.reflect.Type; * Represents a resolved Java method. Methods, like fields and types, are resolved through * {@link ConstantPool constant pools}. */ -public interface ResolvedJavaMethod extends JavaMethod, InvokeTarget, ModifiersProvider, AnnotatedElement { +public interface ResolvedJavaMethod extends JavaMethod, InvokeTarget, ModifiersProvider, AnnotatedElement, Annotated { /** * Returns the method's bytecode. The returned bytecode does not contain breakpoints or non-Java diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ResolvedJavaType.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ResolvedJavaType.java index faff741c794..37762e759fc 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ResolvedJavaType.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ResolvedJavaType.java @@ -31,7 +31,7 @@ import jdk.vm.ci.meta.Assumptions.AssumptionResult; * thereof. Types, like fields and methods, are resolved through {@link ConstantPool constant pools} * . */ -public interface ResolvedJavaType extends JavaType, ModifiersProvider, AnnotatedElement { +public interface ResolvedJavaType extends JavaType, ModifiersProvider, AnnotatedElement, Annotated { /** * Checks whether this type has a finalizer method. * @@ -137,8 +137,8 @@ public interface ResolvedJavaType extends JavaType, ModifiersProvider, Annotated boolean isAssignableFrom(ResolvedJavaType other); /** - * Returns {@code null} since support for VM anonymous class was removed by JDK-8243287. - * This method is preserved for JVMCI backwards compatibility. + * Returns {@code null} since support for VM anonymous class was removed by JDK-8243287. This + * method is preserved for JVMCI backwards compatibility. */ @Deprecated default ResolvedJavaType getHostClass() { diff --git a/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.runtime.test/src/jdk/vm/ci/runtime/test/TestResolvedJavaField.java b/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.runtime.test/src/jdk/vm/ci/runtime/test/TestResolvedJavaField.java index a159dc64cc6..d3097629355 100644 --- a/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.runtime.test/src/jdk/vm/ci/runtime/test/TestResolvedJavaField.java +++ b/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.runtime.test/src/jdk/vm/ci/runtime/test/TestResolvedJavaField.java @@ -25,10 +25,20 @@ * @test * @requires vm.jvmci * @library ../../../../../ + * @compile ../../../../../../../../../../../jdk/jdk/internal/vm/AnnotationEncodingDecoding/AnnotationTestInput.java + * ../../../../../../../../../../../jdk/jdk/internal/vm/AnnotationEncodingDecoding/MemberDeleted.java + * ../../../../../../../../../../../jdk/jdk/internal/vm/AnnotationEncodingDecoding/MemberTypeChanged.java + * TestResolvedJavaType.java + * @clean jdk.internal.vm.test.AnnotationTestInput$Missing + * @compile ../../../../../../../../../../../jdk/jdk/internal/vm/AnnotationEncodingDecoding/alt/MemberDeleted.java + * ../../../../../../../../../../../jdk/jdk/internal/vm/AnnotationEncodingDecoding/alt/MemberTypeChanged.java * @modules jdk.internal.vm.ci/jdk.vm.ci.meta * jdk.internal.vm.ci/jdk.vm.ci.runtime * jdk.internal.vm.ci/jdk.vm.ci.common + * java.base/jdk.internal.reflect * java.base/jdk.internal.misc + * java.base/jdk.internal.vm + * java.base/sun.reflect.annotation * @run junit/othervm -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:-UseJVMCICompiler jdk.vm.ci.runtime.test.TestResolvedJavaField */ @@ -57,6 +67,7 @@ import java.util.Set; import org.junit.Assert; import org.junit.Test; +import jdk.internal.vm.test.AnnotationTestInput; import jdk.vm.ci.common.JVMCIError; import jdk.vm.ci.meta.ConstantReflectionProvider; import jdk.vm.ci.meta.JavaConstant; @@ -181,6 +192,14 @@ public class TestResolvedJavaField extends FieldUniverse { return null; } + @Test + public void getAnnotationDataTest() throws Exception { + TestResolvedJavaType.getAnnotationDataTest(AnnotationTestInput.class.getDeclaredField("annotatedField")); + for (Field f : fields.keySet()) { + TestResolvedJavaType.getAnnotationDataTest(f); + } + } + // @formatter:off private static final String[] untestedApiMethods = { }; diff --git a/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.runtime.test/src/jdk/vm/ci/runtime/test/TestResolvedJavaMethod.java b/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.runtime.test/src/jdk/vm/ci/runtime/test/TestResolvedJavaMethod.java index 43116be77ba..12bad736adf 100644 --- a/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.runtime.test/src/jdk/vm/ci/runtime/test/TestResolvedJavaMethod.java +++ b/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.runtime.test/src/jdk/vm/ci/runtime/test/TestResolvedJavaMethod.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2023, 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 @@ -25,9 +25,20 @@ * @test * @requires vm.jvmci * @library ../../../../../ + * @compile ../../../../../../../../../../../jdk/jdk/internal/vm/AnnotationEncodingDecoding/AnnotationTestInput.java + * ../../../../../../../../../../../jdk/jdk/internal/vm/AnnotationEncodingDecoding/MemberDeleted.java + * ../../../../../../../../../../../jdk/jdk/internal/vm/AnnotationEncodingDecoding/MemberTypeChanged.java + * TestResolvedJavaType.java + * @clean jdk.internal.vm.test.AnnotationTestInput$Missing + * @compile ../../../../../../../../../../../jdk/jdk/internal/vm/AnnotationEncodingDecoding/alt/MemberDeleted.java + * ../../../../../../../../../../../jdk/jdk/internal/vm/AnnotationEncodingDecoding/alt/MemberTypeChanged.java * @modules jdk.internal.vm.ci/jdk.vm.ci.meta * jdk.internal.vm.ci/jdk.vm.ci.runtime + * jdk.internal.vm.ci/jdk.vm.ci.common + * java.base/jdk.internal.reflect * java.base/jdk.internal.misc + * java.base/jdk.internal.vm + * java.base/sun.reflect.annotation * @run junit/othervm -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:-UseJVMCICompiler jdk.vm.ci.runtime.test.TestResolvedJavaMethod */ @@ -61,11 +72,16 @@ import java.util.Set; import org.junit.Assert; import org.junit.Test; +import jdk.internal.vm.test.AnnotationTestInput; import jdk.vm.ci.meta.ConstantPool; import jdk.vm.ci.meta.ExceptionHandler; import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaMethod.Parameter; import jdk.vm.ci.meta.ResolvedJavaType; +import jdk.vm.ci.runtime.test.TestResolvedJavaMethod.AnnotationDataTest.Annotation1; +import jdk.vm.ci.runtime.test.TestResolvedJavaMethod.AnnotationDataTest.Annotation2; +import jdk.vm.ci.runtime.test.TestResolvedJavaMethod.AnnotationDataTest.Annotation3; +import jdk.vm.ci.runtime.test.TestResolvedJavaMethod.AnnotationDataTest.NumbersDE; /** * Tests for {@link ResolvedJavaMethod}. @@ -474,6 +490,83 @@ public class TestResolvedJavaMethod extends MethodUniverse { } } + /** + * Encapsulates input for {@link TestResolvedJavaMethod#getAnnotationDataTest}. + */ + static class AnnotationDataTest { + + public enum NumbersEN { + One, + Two; + } + + public enum NumbersDE { + Eins, + Zwei; + } + + public enum NumbersUA { + Odyn, + Dva; + } + + @Retention(RetentionPolicy.RUNTIME) + public @interface Annotation1 { + NumbersEN value() default NumbersEN.One; + } + + @Retention(RetentionPolicy.RUNTIME) + public @interface Annotation2 { + NumbersDE value() default NumbersDE.Eins; + } + + @Retention(RetentionPolicy.RUNTIME) + public @interface Annotation3 { + NumbersUA value() default NumbersUA.Odyn; + } + + @Annotation1 + @Annotation2 + @Annotation3(NumbersUA.Dva) + static void methodWithThreeAnnotations() { + + } + } + + @Test + public void getAnnotationDataTest() throws Exception { + TestResolvedJavaType.getAnnotationDataTest(AnnotationTestInput.class.getDeclaredMethod("annotatedMethod")); + TestResolvedJavaType.getAnnotationDataTest(AnnotationTestInput.class.getDeclaredMethod("missingAnnotation")); + try { + TestResolvedJavaType.getAnnotationDataTest(AnnotationTestInput.class.getDeclaredMethod("missingNestedAnnotation")); + throw new AssertionError("expected " + NoClassDefFoundError.class.getName()); + } catch (NoClassDefFoundError e) { + Assert.assertEquals("jdk/internal/vm/test/AnnotationTestInput$Missing", e.getMessage()); + } + TestResolvedJavaType.getAnnotationDataTest(AnnotationTestInput.class.getDeclaredMethod("missingTypeOfClassMember")); + TestResolvedJavaType.getAnnotationDataTest(AnnotationTestInput.class.getDeclaredMethod("missingMember")); + TestResolvedJavaType.getAnnotationDataTest(AnnotationTestInput.class.getDeclaredMethod("changeTypeOfMember")); + + for (Method m : methods.keySet()) { + TestResolvedJavaType.getAnnotationDataTest(m); + } + + ResolvedJavaMethod m = metaAccess.lookupJavaMethod(AnnotationDataTest.class.getDeclaredMethod("methodWithThreeAnnotations")); + ResolvedJavaType a1 = metaAccess.lookupJavaType(Annotation1.class); + ResolvedJavaType a2 = metaAccess.lookupJavaType(Annotation2.class); + ResolvedJavaType a3 = metaAccess.lookupJavaType(Annotation3.class); + ResolvedJavaType a4 = metaAccess.lookupJavaType(AnnotationDataTest.class); + ResolvedJavaType numbersDEType = metaAccess.lookupJavaType(NumbersDE.class); + + // Ensure NumbersDE is not initialized before Annotation2 is requested + Assert.assertFalse(numbersDEType.isInitialized()); + Assert.assertEquals(2, m.getAnnotationData(a1, a3).size()); + + // Ensure NumbersDE is initialized after Annotation2 is requested + Assert.assertNotNull(m.getAnnotationData(a2)); + Assert.assertTrue(numbersDEType.isInitialized()); + } + private Method findTestMethod(Method apiMethod) { String testName = apiMethod.getName() + "Test"; for (Method m : getClass().getDeclaredMethods()) { diff --git a/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.runtime.test/src/jdk/vm/ci/runtime/test/TestResolvedJavaType.java b/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.runtime.test/src/jdk/vm/ci/runtime/test/TestResolvedJavaType.java index b8afd840e87..9540b965520 100644 --- a/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.runtime.test/src/jdk/vm/ci/runtime/test/TestResolvedJavaType.java +++ b/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.runtime.test/src/jdk/vm/ci/runtime/test/TestResolvedJavaType.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2023, 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 @@ -25,12 +25,20 @@ * @test * @requires vm.jvmci * @library ../../../../../ + * @compile ../../../../../../../../../../../jdk/jdk/internal/vm/AnnotationEncodingDecoding/AnnotationTestInput.java + * ../../../../../../../../../../../jdk/jdk/internal/vm/AnnotationEncodingDecoding/MemberDeleted.java + * ../../../../../../../../../../../jdk/jdk/internal/vm/AnnotationEncodingDecoding/MemberTypeChanged.java + * @clean jdk.internal.vm.test.AnnotationTestInput$Missing + * @compile ../../../../../../../../../../../jdk/jdk/internal/vm/AnnotationEncodingDecoding/alt/MemberDeleted.java + * ../../../../../../../../../../../jdk/jdk/internal/vm/AnnotationEncodingDecoding/alt/MemberTypeChanged.java * @modules java.base/jdk.internal.org.objectweb.asm * java.base/jdk.internal.reflect * jdk.internal.vm.ci/jdk.vm.ci.meta * jdk.internal.vm.ci/jdk.vm.ci.runtime * jdk.internal.vm.ci/jdk.vm.ci.common * java.base/jdk.internal.misc + * java.base/jdk.internal.vm + * java.base/sun.reflect.annotation * @run junit/othervm -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:-UseJVMCICompiler jdk.vm.ci.runtime.test.TestResolvedJavaType */ @@ -57,30 +65,43 @@ import java.io.InputStream; import java.lang.annotation.Annotation; import java.lang.invoke.MethodHandles.Lookup; import java.lang.reflect.AccessibleObject; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.Collections; +import java.util.function.BiConsumer; import java.util.function.Supplier; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.junit.Assert; import org.junit.Test; -import jdk.internal.org.objectweb.asm.*; import jdk.internal.reflect.ConstantPool; +import jdk.internal.vm.test.AnnotationTestInput; import jdk.vm.ci.common.JVMCIError; +import jdk.vm.ci.meta.Annotated; +import jdk.vm.ci.meta.AnnotationData; +import jdk.vm.ci.meta.EnumData; import jdk.vm.ci.meta.Assumptions.AssumptionResult; import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.JavaType; +import jdk.vm.ci.meta.MetaUtil; import jdk.vm.ci.meta.ResolvedJavaField; import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaType; +import jdk.vm.ci.meta.UnresolvedJavaType; +import sun.reflect.annotation.AnnotationSupport; /** * Tests for {@link ResolvedJavaType}. @@ -177,7 +198,8 @@ public class TestResolvedJavaType extends TypeUniverse { @Test public void lambdaInternalNameTest() { - // Verify that the last dot in lambda types is properly handled when transitioning from internal name to java + // Verify that the last dot in lambda types is properly handled when transitioning from + // internal name to java // name and vice versa. Supplier lambda = () -> () -> System.out.println("run"); ResolvedJavaType lambdaType = metaAccess.lookupJavaType(lambda.getClass()); @@ -903,11 +925,11 @@ public class TestResolvedJavaType extends TypeUniverse { return f.getName().equals("allowedModes") || f.getName().equals("lookupClass"); } if (f.getDeclaringClass().equals(metaAccess.lookupJavaType(ClassLoader.class)) || - f.getDeclaringClass().equals(metaAccess.lookupJavaType(AccessibleObject.class)) || - f.getDeclaringClass().equals(metaAccess.lookupJavaType(Constructor.class)) || - f.getDeclaringClass().equals(metaAccess.lookupJavaType(Field.class)) || - f.getDeclaringClass().equals(metaAccess.lookupJavaType(Method.class)) || - f.getDeclaringClass().equals(metaAccess.lookupJavaType(Module.class))) { + f.getDeclaringClass().equals(metaAccess.lookupJavaType(AccessibleObject.class)) || + f.getDeclaringClass().equals(metaAccess.lookupJavaType(Constructor.class)) || + f.getDeclaringClass().equals(metaAccess.lookupJavaType(Field.class)) || + f.getDeclaringClass().equals(metaAccess.lookupJavaType(Method.class)) || + f.getDeclaringClass().equals(metaAccess.lookupJavaType(Module.class))) { return true; } return false; @@ -1131,6 +1153,40 @@ public class TestResolvedJavaType extends TypeUniverse { return null; } + @Test + public void getAnnotationDataTest() throws Exception { + getAnnotationDataTest(AnnotationTestInput.AnnotatedClass.class); + getAnnotationDataTest(int.class); + getAnnotationDataTest(void.class); + for (Class c : classes) { + getAnnotationDataTest(c); + } + + // Primitive classes have no annotations but we cannot directly + // test absence of annotations. Instead, just ensure empty answers + // are returned when looking up an arbitrary annotation type. + Class[] prims = {void.class, byte.class, int.class, double.class, float.class, short.class, char.class, long.class}; + ResolvedJavaType overrideType = metaAccess.lookupJavaType(Override.class); + for (Class c : prims) { + ResolvedJavaType type = metaAccess.lookupJavaType(c); + AnnotationData ad = type.getAnnotationData(overrideType); + Assert.assertNull(String.valueOf(ad), ad); + List adArray = type.getAnnotationData(overrideType, overrideType); + Assert.assertEquals(0, adArray.size()); + } + + // Test that inherited annotations are handled properly. + ResolvedJavaType namedType = metaAccess.lookupJavaType(AnnotationTestInput.Named.class); + AnnotationData ad = metaAccess.lookupJavaType(AnnotationTestInput.OwnName.class).getAnnotationData(namedType); + Assert.assertEquals("NonInheritedValue", ad.get("value", String.class)); + ad = metaAccess.lookupJavaType(AnnotationTestInput.InheritedName1.class).getAnnotationData(namedType); + Assert.assertEquals("Super1", ad.get("value", String.class)); + ad = metaAccess.lookupJavaType(AnnotationTestInput.InheritedName2.class).getAnnotationData(namedType); + Assert.assertEquals("Super2", ad.get("value", String.class)); + ad = metaAccess.lookupJavaType(AnnotationTestInput.InheritedName3.class).getAnnotationData(namedType); + Assert.assertEquals("Super1", ad.get("value", String.class)); + } + // @formatter:off private static final String[] untestedApiMethods = { "initialize", @@ -1174,4 +1230,129 @@ public class TestResolvedJavaType extends TypeUniverse { private static boolean isSignaturePolymorphic(ResolvedJavaMethod method) { return method.getAnnotation(SIGNATURE_POLYMORPHIC_CLASS) != null; } + + /** + * Tests that {@link AnnotationData} obtained from a {@link Class}, {@link Method} or + * {@link Field} matches {@link AnnotatedElement#getAnnotations()} for the corresponding JVMCI + * object. + * + * @param annotated a {@link Class}, {@link Method} or {@link Field} object + */ + public static void getAnnotationDataTest(AnnotatedElement annotated) throws Exception { + testGetAnnotationData(annotated, List.of(annotated.getAnnotations())); + } + + private static void testGetAnnotationData(AnnotatedElement annotated, List annotations) throws AssertionError { + for (Annotation a : annotations) { + AnnotationData ad = toAnnotated(annotated).getAnnotationData(metaAccess.lookupJavaType(a.annotationType())); + assertAnnotationsEquals(a, ad); + + // Check that encoding/decoding produces a stable result + AnnotationData ad2 = toAnnotated(annotated).getAnnotationData(metaAccess.lookupJavaType(a.annotationType())); + assertEquals(ad, ad2); + } + if (annotations.size() < 2) { + return; + } + ResolvedJavaType type1 = metaAccess.lookupJavaType(annotations.get(0).annotationType()); + ResolvedJavaType type2 = metaAccess.lookupJavaType(annotations.get(1).annotationType()); + for (int i = 2; i < annotations.size(); i++) { + + ResolvedJavaType[] types = annotations.// + subList(2, i + 1).// + stream().map(a -> metaAccess.lookupJavaType(a.annotationType())).// + toArray(ResolvedJavaType[]::new); + List annotationData = toAnnotated(annotated).getAnnotationData(type1, type2, types); + assertEquals(2 + types.length, annotationData.size()); + + for (int j = 0; j < annotationData.size(); j++) { + Annotation a = annotations.get(j); + AnnotationData ad = annotationData.get(j); + assertAnnotationsEquals(a, ad); + } + } + } + + private static Annotated toAnnotated(AnnotatedElement element) { + if (element instanceof Class t) { + return metaAccess.lookupJavaType(t); + } else if (element instanceof Method m) { + return metaAccess.lookupJavaMethod(m); + } else { + Field f = (Field) element; + return metaAccess.lookupJavaField(f); + } + } + + private static UnresolvedJavaType asType(Class valueType) { + return UnresolvedJavaType.create(MetaUtil.toInternalName(valueType.getName())); + } + + private static void assertAnnotationsEquals(Annotation a, AnnotationData ad) { + Map values = AnnotationSupport.memberValues(a); + for (Map.Entry e : values.entrySet()) { + String name = e.getKey(); + Object aValue = e.getValue(); + Object adValue; + try { + adValue = ad.get(name, Object.class); + } catch (IllegalArgumentException ex) { + assertEquals(aValue.toString(), ex.getMessage()); + continue; + } + try { + assertAnnotationElementsEqual(aValue, adValue); + } catch (ClassCastException ex) { + throw new AssertionError(a.getClass().getName() + "." + name + " has wrong type: " + adValue.getClass().getName(), ex); + } + } + } + + private static void assertAnnotationElementsEqual(Object aValue, Object adValue) { + Class valueType = aValue.getClass(); + if (valueType.isEnum()) { + assertEnumObjectsEquals(aValue, adValue); + } else if (aValue instanceof Class) { + assertClassObjectsEquals(aValue, adValue); + } else if (aValue instanceof Annotation) { + assertAnnotationObjectsEquals(aValue, adValue); + } else if (valueType.isArray()) { + List adList = (List) adValue; + int length = Array.getLength(aValue); + assertEquals(length, adList.size()); + for (int i = 0; i < length; i++) { + assertAnnotationElementsEqual(Array.get(aValue, i), adList.get(i)); + } + } else { + assertEquals(aValue.getClass(), adValue.getClass()); + assertEquals(aValue, adValue); + } + } + + private static void assertClassObjectsEquals(Object aValue, Object adValue) { + String aName = ((Class) aValue).getName(); + String adName = ((JavaType) adValue).toClassName(); + assertEquals(aName, adName); + } + + private static void assertEnumObjectsEquals(Object aValue, Object adValue) { + EnumData adEnum = (EnumData) adValue; + String adEnumName = adEnum.getName(); + String aEnumName = ((Enum) aValue).name(); + assertEquals(adEnumName, aEnumName); + } + + private static void assertAnnotationObjectsEquals(Object aValue, Object adValue) { + Annotation aAnnotation = (Annotation) aValue; + AnnotationData adAnnotation = (AnnotationData) adValue; + assertAnnotationsEquals(aAnnotation, adAnnotation); + } + + private static void assertArraysEqual(Object aValue, Object adValue, int length, BiConsumer assertEqualty) { + Object[] aArray = (Object[]) aValue; + Object[] adArray = (Object[]) adValue; + for (int i = 0; i < length; i++) { + assertEqualty.accept(aArray[i], adArray[i]); + } + } } diff --git a/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.runtime.test/src/jdk/vm/ci/runtime/test/TypeUniverse.java b/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.runtime.test/src/jdk/vm/ci/runtime/test/TypeUniverse.java index 1416c317d3e..2420a133b63 100644 --- a/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.runtime.test/src/jdk/vm/ci/runtime/test/TypeUniverse.java +++ b/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.runtime.test/src/jdk/vm/ci/runtime/test/TypeUniverse.java @@ -22,14 +22,8 @@ */ package jdk.vm.ci.runtime.test; -import jdk.internal.misc.Unsafe; -import jdk.vm.ci.meta.ConstantReflectionProvider; -import jdk.vm.ci.meta.JavaConstant; -import jdk.vm.ci.meta.MetaAccessProvider; -import jdk.vm.ci.meta.ResolvedJavaField; -import jdk.vm.ci.meta.ResolvedJavaType; -import jdk.vm.ci.runtime.JVMCI; -import org.junit.Test; +import static java.lang.reflect.Modifier.isFinal; +import static java.lang.reflect.Modifier.isStatic; import java.io.Serializable; import java.lang.reflect.Array; @@ -54,8 +48,15 @@ import java.util.TreeMap; import java.util.function.Predicate; import java.util.stream.Collectors; -import static java.lang.reflect.Modifier.isFinal; -import static java.lang.reflect.Modifier.isStatic; +import org.junit.Test; + +import jdk.internal.misc.Unsafe; +import jdk.vm.ci.meta.ConstantReflectionProvider; +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.MetaAccessProvider; +import jdk.vm.ci.meta.ResolvedJavaField; +import jdk.vm.ci.meta.ResolvedJavaType; +import jdk.vm.ci.runtime.JVMCI; /** * Context for type related tests. diff --git a/test/jdk/jdk/internal/vm/AnnotationEncodingDecoding/AnnotationTestInput.java b/test/jdk/jdk/internal/vm/AnnotationEncodingDecoding/AnnotationTestInput.java new file mode 100644 index 00000000000..446fb891ac4 --- /dev/null +++ b/test/jdk/jdk/internal/vm/AnnotationEncodingDecoding/AnnotationTestInput.java @@ -0,0 +1,375 @@ +/* + * Copyright (c) 2023, 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. + */ +package jdk.internal.vm.test; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +public class AnnotationTestInput { + + enum Mood { + HAPPY, + SAD, + CONFUSED; + } + + private class PrivateClass {} + + @Single(string = "a", + stringArray = {"a", "b"}, + classValue = String.class, + classArray = {String.class, Exception.class}, + byteValue = 1, + byteArray = {1, 2, Byte.MIN_VALUE, Byte.MAX_VALUE}, + charValue = 'a', + charArray = {'a', 'b', + Character.MIN_VALUE, Character.MAX_VALUE, + '\b', '\f', '\n', '\r', '\t', '\\', '\'', '\"', '\u012A'}, + doubleValue = 3.3D, + doubleArray = {3.3D, 4.4D, + Double.MIN_VALUE, Double.MAX_VALUE, + Double.NaN, + Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY}, + floatValue = 4.4F, + floatArray = {4.4F, 5.5F, + Float.MIN_VALUE, Float.MAX_VALUE, + Float.NaN, + Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY}, + intValue = 5, + intArray = {5, 6, Integer.MIN_VALUE, Integer.MAX_VALUE}, + longValue = 6L, + longArray = {6L, 7L, Long.MIN_VALUE, Long.MAX_VALUE}, + shortValue = 7, + shortArray = {7, 8, Short.MIN_VALUE, Short.MAX_VALUE}, + booleanValue = true, + booleanArray = {true, false}, + mood = Mood.SAD, + moodArray = {Mood.CONFUSED, Mood.HAPPY}, + nested = @NestedAnno("nested1"), + nestedArray = {@NestedAnno("nested2"), @NestedAnno("nested3")}) + @Single(string = "A", + stringArray = {"A", "B"}, + classValue = Thread.class, + classArray = {Thread.class, PrivateClass.class}, + byteValue = -1, + byteArray = {-1, -2}, + charValue = 'A', + charArray = {'a', 'b'}, + doubleValue = -3.3D, + doubleArray = {3.3D, 4.4D}, + floatValue = -4.4F, + floatArray = {4.4F, 5.5F}, + intValue = -5, + intArray = {5, 6}, + longValue = -6L, + longArray = {6L, 7L}, + shortValue = -7, + shortArray = {7, 8}, + booleanValue = true, + booleanArray = {true, false}, + mood = Mood.CONFUSED, + moodArray = {Mood.SAD, Mood.CONFUSED}, + nested = @NestedAnno("nested4"), + nestedArray = {@NestedAnno("nested5"), @NestedAnno("nested6")}) + @SingleWithDefaults + @Deprecated + @SuppressWarnings("unchecked") + public void annotatedMethod() { + } + + @Named("Super1") + public static class Super1 {} + @Named("Super2") + public static class Super2 extends Super1 {} + public static class Super3 extends Super1 {} + + @Named("NonInheritedValue") + public static class OwnName extends Super1 {} + + public static class InheritedName1 extends Super1 {} + public static class InheritedName2 extends Super2 {} + public static class InheritedName3 extends Super3 {} + + @Named("AnnotatedClass") + @Single(string = "a", + stringArray = {"a", "b"}, + classValue = String.class, + classArray = {String.class, Exception.class}, + byteValue = 1, + byteArray = {1, 2, Byte.MIN_VALUE, Byte.MAX_VALUE}, + charValue = 'a', + charArray = {'a', 'b', + Character.MIN_VALUE, Character.MAX_VALUE, + '\b', '\f', '\n', '\r', '\t', '\\', '\'', '\"', '\u012A'}, + doubleValue = 3.3D, + doubleArray = {3.3D, 4.4D, + Double.MIN_VALUE, Double.MAX_VALUE, + Double.NaN, + Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY}, + floatValue = 4.4F, + floatArray = {4.4F, 5.5F, + Float.MIN_VALUE, Float.MAX_VALUE, + Float.NaN, + Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY}, + intValue = 5, + intArray = {5, 6, Integer.MIN_VALUE, Integer.MAX_VALUE}, + longValue = 6L, + longArray = {6L, 7L, Long.MIN_VALUE, Long.MAX_VALUE}, + shortValue = 7, + shortArray = {7, 8, Short.MIN_VALUE, Short.MAX_VALUE}, + booleanValue = true, + booleanArray = {true, false}, + mood = Mood.SAD, + moodArray = {Mood.CONFUSED, Mood.HAPPY}, + nested = @NestedAnno("nested7"), + nestedArray = {@NestedAnno("nested8"), @NestedAnno("nested9")}) + @Single(string = "A", + stringArray = {"A", "B"}, + classValue = Thread.class, + classArray = {Thread.class, PrivateClass.class}, + byteValue = -1, + byteArray = {-1, -2}, + charValue = 'A', + charArray = {'a', 'b'}, + doubleValue = -3.3D, + doubleArray = {3.3D, 4.4D}, + floatValue = -4.4F, + floatArray = {4.4F, 5.5F}, + intValue = -5, + intArray = {5, 6}, + longValue = -6L, + longArray = {6L, 7L}, + shortValue = -7, + shortArray = {7, 8}, + booleanValue = true, + booleanArray = {true, false}, + mood = Mood.CONFUSED, + moodArray = {Mood.SAD, Mood.CONFUSED}, + nested = @NestedAnno("nested10"), + nestedArray = {@NestedAnno("nested11"), @NestedAnno("nested12")}) + @Deprecated + @SuppressWarnings({"rawtypes", "all"}) + public static class AnnotatedClass {} + + @Single(string = "a", + stringArray = {"a", "b"}, + classValue = String.class, + classArray = {String.class, Exception.class}, + byteValue = 1, + byteArray = {1, 2, Byte.MIN_VALUE, Byte.MAX_VALUE}, + charValue = 'a', + charArray = {'a', 'b', + Character.MIN_VALUE, Character.MAX_VALUE, + '\b', '\f', '\n', '\r', '\t', '\\', '\'', '\"', '\u012A'}, + doubleValue = 3.3D, + doubleArray = {3.3D, 4.4D, + Double.MIN_VALUE, Double.MAX_VALUE, + Double.NaN, + Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY}, + floatValue = 4.4F, + floatArray = {4.4F, 5.5F, + Float.MIN_VALUE, Float.MAX_VALUE, + Float.NaN, + Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY}, + intValue = 5, + intArray = {5, 6, Integer.MIN_VALUE, Integer.MAX_VALUE}, + longValue = 6L, + longArray = {6L, 7L, Long.MIN_VALUE, Long.MAX_VALUE}, + shortValue = 7, + shortArray = {7, 8, Short.MIN_VALUE, Short.MAX_VALUE}, + booleanValue = true, + booleanArray = {true, false}, + mood = Mood.SAD, + moodArray = {Mood.CONFUSED, Mood.HAPPY}, + nested = @NestedAnno("nested12"), + nestedArray = {@NestedAnno("nested13"), @NestedAnno("nested14")}) + @Single(string = "A", + stringArray = {"A", "B"}, + classValue = Thread.class, + classArray = {Thread.class, PrivateClass.class}, + byteValue = -1, + byteArray = {-1, -2}, + charValue = 'A', + charArray = {'a', 'b'}, + doubleValue = -3.3D, + doubleArray = {3.3D, 4.4D}, + floatValue = -4.4F, + floatArray = {4.4F, 5.5F}, + intValue = -5, + intArray = {5, 6}, + longValue = -6L, + longArray = {6L, 7L}, + shortValue = -7, + shortArray = {7, 8}, + booleanValue = true, + booleanArray = {true, false}, + mood = Mood.CONFUSED, + moodArray = {Mood.SAD, Mood.CONFUSED}, + nested = @NestedAnno("nested15"), + nestedArray = {@NestedAnno("nested16"), @NestedAnno("nested17")}) + private static final int annotatedField = 45; + + @Retention(RetentionPolicy.RUNTIME) + public @interface NestedAnno { + String value(); + } + + @Inherited + @Retention(RetentionPolicy.RUNTIME) + public @interface Named { + String value(); + } + + @Retention(RetentionPolicy.RUNTIME) + @Repeatable(SingleList.class) + public @interface Single { + Class classValue(); + Class[] classArray(); + + String string(); + String[] stringArray(); + + byte byteValue(); + byte[] byteArray(); + + char charValue(); + char[] charArray(); + + double doubleValue(); + double[] doubleArray(); + + float floatValue(); + float[] floatArray(); + + int intValue(); + int[] intArray(); + + long longValue(); + long[] longArray(); + + short shortValue(); + short[] shortArray(); + + boolean booleanValue(); + boolean[] booleanArray(); + + Mood mood(); + Mood[] moodArray(); + + NestedAnno nested(); + NestedAnno[] nestedArray(); + } + + @Retention(RetentionPolicy.RUNTIME) + @interface SingleWithDefaults { + Class classValue() default SingleWithDefaults.class; + Class[] classArray() default {}; + + String string() default "anonymous"; + String[] stringArray() default {}; + + byte byteValue() default 101; + byte[] byteArray() default {}; + + char charValue() default 'Z'; + char[] charArray() default {}; + + double doubleValue() default 102.102D; + double[] doubleArray() default {}; + + float floatValue() default 103.103F; + float[] floatArray() default {}; + + int intValue() default 104; + int[] intArray() default {}; + + long longValue() default 105L; + long[] longArray() default {}; + + short shortValue() default 105; + short[] shortArray() default {}; + + boolean booleanValue() default true; + boolean[] booleanArray() default {}; + + Mood mood() default Mood.HAPPY; + Mood[] moodArray() default {}; + } + + @Retention(RetentionPolicy.RUNTIME) + public @interface SingleList { + Single[] value(); + } + + @Retention(RetentionPolicy.RUNTIME) + public @interface Missing {} + + @Retention(RetentionPolicy.RUNTIME) + public @interface MissingWrapper { + Missing value(); + } + + @Retention(RetentionPolicy.RUNTIME) + public @interface MissingContainer { + Class value(); + } + + /** + * Method with a directly missing annotation. + */ + @Missing + public void missingAnnotation() {} + + /** + * Method with an indirectly missing nested annotation. + */ + @MissingWrapper(@Missing) + public void missingNestedAnnotation() {} + + /** + * Method with an annotation that has a Class member + * that cannot be resolved. + */ + @MissingContainer(Missing.class) + public void missingTypeOfClassMember() {} + + /** + * Method with an annotation that has a member + * that is deleted in a newer version of the annotation. + */ + @MemberDeleted(value = "evolving", retained = -34, deleted = 56) + public void missingMember() {} + + /** + * Method with an annotation that has a member named "any" + * whose type is changed from int to String in a newer version + * of the annotation. + */ + @MemberTypeChanged(value = "evolving", retained = -34, any = 56) + public void changeTypeOfMember() {} + +} + diff --git a/test/jdk/jdk/internal/vm/AnnotationEncodingDecoding/MemberDeleted.java b/test/jdk/jdk/internal/vm/AnnotationEncodingDecoding/MemberDeleted.java new file mode 100644 index 00000000000..2707886b4c7 --- /dev/null +++ b/test/jdk/jdk/internal/vm/AnnotationEncodingDecoding/MemberDeleted.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2023, 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. + */ +package jdk.internal.vm.test; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface MemberDeleted { + String value(); + int retained(); + int deleted(); +} diff --git a/test/jdk/jdk/internal/vm/AnnotationEncodingDecoding/MemberTypeChanged.java b/test/jdk/jdk/internal/vm/AnnotationEncodingDecoding/MemberTypeChanged.java new file mode 100644 index 00000000000..99670e63b9a --- /dev/null +++ b/test/jdk/jdk/internal/vm/AnnotationEncodingDecoding/MemberTypeChanged.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2023, 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. + */ +package jdk.internal.vm.test; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface MemberTypeChanged { + String value(); + int retained(); + int any(); +} diff --git a/test/jdk/jdk/internal/vm/AnnotationEncodingDecoding/TestAnnotationEncodingDecoding.java b/test/jdk/jdk/internal/vm/AnnotationEncodingDecoding/TestAnnotationEncodingDecoding.java new file mode 100644 index 00000000000..64e559a5713 --- /dev/null +++ b/test/jdk/jdk/internal/vm/AnnotationEncodingDecoding/TestAnnotationEncodingDecoding.java @@ -0,0 +1,257 @@ +/* + * Copyright (c) 2023, 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 + * @compile AnnotationTestInput.java MemberDeleted.java MemberTypeChanged.java + * @modules java.base/jdk.internal.vm + * java.base/sun.reflect.annotation + * @clean jdk.internal.vm.test.AnnotationTestInput$Missing + * @compile alt/MemberDeleted.java alt/MemberTypeChanged.java + * @run testng/othervm + * jdk.internal.vm.test.TestAnnotationEncodingDecoding + */ +package jdk.internal.vm.test; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Array; +import java.lang.reflect.Method; +import java.util.LinkedHashMap; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.TreeMap; + +import org.testng.Assert; +import org.testng.annotations.Test; + +import sun.reflect.annotation.AnnotationSupport; +import sun.reflect.annotation.AnnotationParser; +import sun.reflect.annotation.ExceptionProxy; + +import jdk.internal.vm.VMSupport; +import jdk.internal.vm.VMSupport.AnnotationDecoder; + +public class TestAnnotationEncodingDecoding { + + @Test + public void encodeDecodeTest() throws Exception { + checkDecodedEqualsEncoded(AnnotationTestInput.class.getDeclaredField("annotatedField")); + checkDecodedEqualsEncoded(AnnotationTestInput.class.getDeclaredMethod("annotatedMethod")); + checkDecodedEqualsEncoded(AnnotationTestInput.AnnotatedClass.class); + + checkDecodedEqualsEncoded(AnnotationTestInput.class.getDeclaredMethod("missingAnnotation")); + checkDecodedEqualsEncoded(AnnotationTestInput.class.getDeclaredMethod("missingNestedAnnotation"), true); + checkDecodedEqualsEncoded(AnnotationTestInput.class.getDeclaredMethod("missingTypeOfClassMember"), false); + checkDecodedEqualsEncoded(AnnotationTestInput.class.getDeclaredMethod("missingMember")); + checkDecodedEqualsEncoded(AnnotationTestInput.class.getDeclaredMethod("changeTypeOfMember"), false); + } + + private void checkDecodedEqualsEncoded(AnnotatedElement annotated) { + checkDecodedEqualsEncoded(annotated, false); + } + + private void checkDecodedEqualsEncoded(AnnotatedElement annotated, boolean expectNCDFE) { + Annotation[] annotations = getAnnotations(annotated, expectNCDFE); + if (annotations == null) { + return; + } + + byte[] encoded = VMSupport.encodeAnnotations(List.of(annotations)); + MyDecoder decoder = new MyDecoder(); + List decoded = VMSupport.decodeAnnotations(encoded, decoder); + int i = 0; + for (AnnotationConst actual : decoded) { + AnnotationConst expect = new AnnotationConst(annotations[i]); + checkEquals(actual, expect); + checkEquals(actual.toString(), expect.toString()); + i++; + } + } + + private static Annotation[] getAnnotations(AnnotatedElement annotated, boolean expectNCDFE) throws AssertionError { + try { + Annotation[] annotations = annotated.getAnnotations(); + Assert.assertFalse(expectNCDFE, annotated.toString()); + return annotations; + } catch (NoClassDefFoundError e) { + if (!expectNCDFE) { + throw new AssertionError(annotated.toString(), e); + } + return null; + } + } + + private static void checkEquals(Object actual, Object expect) { + if (!actual.equals(expect)) { + throw new AssertionError(String.format("actual != expect%nactual: %s%n%nexpect: %s", actual, expect)); + } + } + + public static final class AnnotationConst { + final Class type; + final Map elements; + + AnnotationConst(Class type, Map.Entry[] elements) { + this.type = type; + this.elements = Map.ofEntries(elements); + } + + AnnotationConst(Annotation a) { + Map values = AnnotationSupport.memberValues(a); + this.type = a.annotationType(); + Map.Entry[] elements = new Map.Entry[values.size()]; + int i = 0; + for (Map.Entry e : values.entrySet()) { + elements[i++] = Map.entry(e.getKey(), decodeValue(e.getValue())); + } + this.elements = Map.ofEntries(elements); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof AnnotationConst) { + AnnotationConst that = (AnnotationConst) obj; + return this.type.equals(that.type) && + this.elements.equals(that.elements); + } + return false; + } + + @Override + public String toString() { + return "@" + type.getName() + "(" + elements + ")"; + } + + private Object decodeValue(Object value) { + Class valueType = value.getClass(); + if (value instanceof Enum) { + return new EnumConst(valueType, ((Enum) value).name()); + } else if (value instanceof Annotation) { + return new AnnotationConst((Annotation) value); + } else if (valueType.isArray()) { + int len = Array.getLength(value); + Object[] arr = new Object[len]; + for (int i = 0; i < len; i++) { + arr[i] = decodeValue(Array.get(value, i)); + } + return List.of(arr); + } else if (value instanceof ExceptionProxy) { + return new ErrorConst(value.toString()); + } else { + return value; + } + } + + public Class getType() { + return type; + } + } + + public static final class ErrorConst { + final String desc; + public ErrorConst(String desc) { + this.desc = Objects.requireNonNull(desc); + } + + @Override + public String toString() { + return desc; + } + + @Override + public int hashCode() { + return desc.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof ErrorConst) { + return ((ErrorConst) obj).desc.equals(desc); + } + return false; + } + } + + public static final class EnumConst { + final Class type; + final String name; + + public EnumConst(Class type, String name) { + this.type = type; + this.name = name; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof EnumConst) { + EnumConst that = (EnumConst) obj; + return this.type.equals(that.type) && + this.name.equals(that.name); + } + return false; + } + + @Override + public String toString() { + return type.getName() + "." + name; + } + + public Class getEnumType() { + return type; + } + + public String getName() { + return name; + } + } + + static class MyDecoder implements AnnotationDecoder, AnnotationConst, EnumConst, ErrorConst> { + @Override + public Class resolveType(String name) { + try { + return Class.forName(name); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + @Override + public AnnotationConst newAnnotation(Class type, Map.Entry[] elements) { + return new AnnotationConst(type, elements); + } + + @Override + public EnumConst newEnumValue(Class enumType, String name) { + return new EnumConst(enumType, name); + } + + @Override + public ErrorConst newErrorValue(String description) { + return new ErrorConst(description); + } + } +} diff --git a/test/jdk/jdk/internal/vm/AnnotationEncodingDecoding/alt/MemberDeleted.java b/test/jdk/jdk/internal/vm/AnnotationEncodingDecoding/alt/MemberDeleted.java new file mode 100644 index 00000000000..c27eca83229 --- /dev/null +++ b/test/jdk/jdk/internal/vm/AnnotationEncodingDecoding/alt/MemberDeleted.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2023, 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. + */ +package jdk.internal.vm.test; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface MemberDeleted { + String value(); + int retained(); +} diff --git a/test/jdk/jdk/internal/vm/AnnotationEncodingDecoding/alt/MemberTypeChanged.java b/test/jdk/jdk/internal/vm/AnnotationEncodingDecoding/alt/MemberTypeChanged.java new file mode 100644 index 00000000000..b0518582166 --- /dev/null +++ b/test/jdk/jdk/internal/vm/AnnotationEncodingDecoding/alt/MemberTypeChanged.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2023, 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. + */ +package jdk.internal.vm.test; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface MemberTypeChanged { + String value(); + int retained(); + String any(); +} diff --git a/test/jdk/jdk/internal/vm/TestTranslatedException.java b/test/jdk/jdk/internal/vm/TestTranslatedException.java index 91dab187110..335f71ccf3a 100644 --- a/test/jdk/jdk/internal/vm/TestTranslatedException.java +++ b/test/jdk/jdk/internal/vm/TestTranslatedException.java @@ -58,15 +58,6 @@ public class TestTranslatedException { } encodeDecode(throwable); } - @SuppressWarnings("unchecked") - @Test - public void encodeDecodeTest2() throws Exception { - Throwable throwable = new ExceptionInInitializerError(new InvocationTargetException(new Untranslatable("test exception", new NullPointerException()), "invoke")); - for (int i = 0; i < 10; i++) { - throwable = new ExceptionInInitializerError(new InvocationTargetException(new RuntimeException(String.valueOf(i), throwable), "invoke")); - } - encodeDecode(throwable); - } private void encodeDecode(Throwable throwable) throws Exception { Unsafe unsafe = Unsafe.getUnsafe();