From a0821e97f6c0e262d08e8504ac5cee0c9c8ab7d9 Mon Sep 17 00:00:00 2001 From: liach Date: Wed, 30 Apr 2025 18:50:48 -0500 Subject: [PATCH 1/2] 8356022: Migrate descriptor parsing from generics to BytecodeDescriptor --- .../share/classes/java/lang/Class.java | 36 ++++++------------- .../sun/invoke/util/BytecodeDescriptor.java | 29 +++++++++++++-- .../classes/sun/invoke/util/Wrapper.java | 15 +++++++- .../reflect/annotation/AnnotationParser.java | 27 ++++---------- 4 files changed, 58 insertions(+), 49 deletions(-) diff --git a/src/java.base/share/classes/java/lang/Class.java b/src/java.base/share/classes/java/lang/Class.java index d86baaac3621a..a70621f23998b 100644 --- a/src/java.base/share/classes/java/lang/Class.java +++ b/src/java.base/share/classes/java/lang/Class.java @@ -47,7 +47,6 @@ import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.lang.reflect.Proxy; import java.lang.reflect.RecordComponent; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; @@ -82,12 +81,11 @@ import jdk.internal.vm.annotation.IntrinsicCandidate; import jdk.internal.vm.annotation.Stable; +import sun.invoke.util.BytecodeDescriptor; import sun.invoke.util.Wrapper; import sun.reflect.generics.factory.CoreReflectionFactory; import sun.reflect.generics.factory.GenericsFactory; import sun.reflect.generics.repository.ClassRepository; -import sun.reflect.generics.repository.MethodRepository; -import sun.reflect.generics.repository.ConstructorRepository; import sun.reflect.generics.scope.ClassScope; import sun.reflect.annotation.*; @@ -1437,17 +1435,10 @@ public Method getEnclosingMethod() { if (!enclosingInfo.isMethod()) return null; - MethodRepository typeInfo = MethodRepository.make(enclosingInfo.getDescriptor(), - getFactory()); - Class returnType = toClass(typeInfo.getReturnType()); - Type [] parameterTypes = typeInfo.getParameterTypes(); - Class[] parameterClasses = new Class[parameterTypes.length]; - - // Convert Types to Classes; returned types *should* - // be class objects since the methodDescriptor's used - // don't have generics information - for(int i = 0; i < parameterClasses.length; i++) - parameterClasses[i] = toClass(parameterTypes[i]); + // Descriptor already validated by VM + List> types = BytecodeDescriptor.parseMethod(enclosingInfo.getDescriptor(), getClassLoader()); + Class returnType = types.removeLast(); + Class[] parameterClasses = types.toArray(EMPTY_CLASS_ARRAY); final Class enclosingCandidate = enclosingInfo.getEnclosingClass(); Method[] candidates = enclosingCandidate.privateGetDeclaredMethods(false); @@ -1566,17 +1557,10 @@ public Constructor getEnclosingConstructor() { if (!enclosingInfo.isConstructor()) return null; - ConstructorRepository typeInfo = ConstructorRepository.make(enclosingInfo.getDescriptor(), - getFactory()); - Type [] parameterTypes = typeInfo.getParameterTypes(); - Class[] parameterClasses = new Class[parameterTypes.length]; - - // Convert Types to Classes; returned types *should* - // be class objects since the methodDescriptor's used - // don't have generics information - for (int i = 0; i < parameterClasses.length; i++) - parameterClasses[i] = toClass(parameterTypes[i]); - + // Descriptor already validated by VM + List> types = BytecodeDescriptor.parseMethod(enclosingInfo.getDescriptor(), getClassLoader()); + types.removeLast(); + Class[] parameterClasses = types.toArray(EMPTY_CLASS_ARRAY); final Class enclosingCandidate = enclosingInfo.getEnclosingClass(); Constructor[] candidates = enclosingCandidate @@ -1882,7 +1866,7 @@ public Class[] getClasses() { } currentClass = currentClass.getSuperclass(); } - return list.toArray(new Class[0]); + return list.toArray(EMPTY_CLASS_ARRAY); } diff --git a/src/java.base/share/classes/sun/invoke/util/BytecodeDescriptor.java b/src/java.base/share/classes/sun/invoke/util/BytecodeDescriptor.java index 53fd632275837..bf842bf33d70b 100644 --- a/src/java.base/share/classes/sun/invoke/util/BytecodeDescriptor.java +++ b/src/java.base/share/classes/sun/invoke/util/BytecodeDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2025, 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 @@ -37,9 +37,26 @@ public class BytecodeDescriptor { private BytecodeDescriptor() { } // cannot instantiate + /** + * @throws IllegalArgumentException if the descriptor is invalid + * @throws TypeNotPresentException if the descriptor is valid, but + * the class cannot be found by the loader + */ + public static Class parseClass(String descriptor, ClassLoader loader) { + int[] i = {0}; + var ret = parseSig(descriptor, i, descriptor.length(), loader); + if (i[0] != descriptor.length() || ret == null) { + parseError("not a class descriptor", descriptor); + } + return ret; + } + /** * @param loader the class loader in which to look up the types (null means * bootstrap class loader) + * @throws IllegalArgumentException if the descriptor is invalid + * @throws TypeNotPresentException if the descriptor is valid, but + * a reference type cannot be found by the loader */ public static List> parseMethod(String bytecodeSignature, ClassLoader loader) { return parseMethod(bytecodeSignature, 0, bytecodeSignature.length(), loader); @@ -78,6 +95,13 @@ private static void parseError(String str, String msg) { } /** + * Parse a single type in a descriptor. Results can be: + *
    + *
  • A {@code Class} for successful parsing + *
  • {@code null} for malformed descriptor format + *
  • Throwing a {@link TypeNotPresentException} for valid class name, + * but class cannot be discovered + *
* @param loader the class loader in which to look up the types (null means * bootstrap class loader) */ @@ -100,7 +124,8 @@ private static Class parseSig(String str, int[] i, int end, ClassLoader loade t = t.arrayType(); return t; } else { - return Wrapper.forBasicType(c).primitiveType(); + var w = Wrapper.forPrimitiveTypeOrNull(c); + return w == null ? null : w.primitiveType(); } } diff --git a/src/java.base/share/classes/sun/invoke/util/Wrapper.java b/src/java.base/share/classes/sun/invoke/util/Wrapper.java index 4e8beaabb3486..361b35abfd80d 100644 --- a/src/java.base/share/classes/sun/invoke/util/Wrapper.java +++ b/src/java.base/share/classes/sun/invoke/util/Wrapper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2025, 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 @@ -352,6 +352,19 @@ public static Wrapper forBasicType(char type) { throw basicTypeError(type); } + /** + * Return the primitive wrapper for the given char. Does not return + * {@code OBJECT}. Returns {@code null} to allow flexible error messages. + * Dedicated for {@link BytecodeDescriptor}. + */ + static Wrapper forPrimitiveTypeOrNull(char type) { + Wrapper w = FROM_CHAR[(type + (type >> 1)) & 0xf]; + if (w != null && w != OBJECT && w.basicTypeChar == type) { + return w; + } + return null; + } + @DontInline private static RuntimeException basicTypeError(char type) { for (Wrapper x : values()) { 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 82751e3fcd293..74ed1e015531a 100644 --- a/src/java.base/share/classes/sun/reflect/annotation/AnnotationParser.java +++ b/src/java.base/share/classes/sun/reflect/annotation/AnnotationParser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2025, 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 @@ -34,12 +34,7 @@ import jdk.internal.reflect.ConstantPool; -import sun.reflect.generics.parser.SignatureParser; -import sun.reflect.generics.tree.TypeSignature; -import sun.reflect.generics.factory.GenericsFactory; -import sun.reflect.generics.factory.CoreReflectionFactory; -import sun.reflect.generics.visitor.Reifier; -import sun.reflect.generics.scope.ClassScope; +import sun.invoke.util.BytecodeDescriptor; /** * Parser for Java programming language annotations. Translates @@ -429,19 +424,11 @@ private static Object parseClassValue(ByteBuffer buf, } private static Class parseSig(String sig, Class container) { - if (sig.equals("V")) return void.class; - SignatureParser parser = SignatureParser.make(); - TypeSignature typeSig = parser.parseTypeSig(sig); - GenericsFactory factory = CoreReflectionFactory.make(container, ClassScope.make(container)); - Reifier reify = Reifier.make(factory); - typeSig.accept(reify); - Type result = reify.getResult(); - return toClass(result); - } - static Class toClass(Type o) { - if (o instanceof GenericArrayType gat) - return toClass(gat.getGenericComponentType()).arrayType(); - return (Class) o; + try { + return BytecodeDescriptor.parseClass(sig, container.getClassLoader()); + } catch (IllegalArgumentException ex) { + throw new GenericSignatureFormatError(ex.getMessage()); + } } /** From 7cc014b348287d76ba68306b182f2a728af1be51 Mon Sep 17 00:00:00 2001 From: Chen Liang Date: Fri, 22 Aug 2025 21:00:42 -0500 Subject: [PATCH 2/2] Roll back wrapper, test for BytecodeDescriptor --- .../sun/invoke/util/BytecodeDescriptor.java | 66 +++++---- .../classes/sun/invoke/util/Wrapper.java | 15 +- .../invoke/util/BytecodeDescriptorTest.java | 128 ++++++++++++++++++ 3 files changed, 168 insertions(+), 41 deletions(-) create mode 100644 test/jdk/sun/invoke/util/BytecodeDescriptorTest.java diff --git a/src/java.base/share/classes/sun/invoke/util/BytecodeDescriptor.java b/src/java.base/share/classes/sun/invoke/util/BytecodeDescriptor.java index bf842bf33d70b..9aaff563076de 100644 --- a/src/java.base/share/classes/sun/invoke/util/BytecodeDescriptor.java +++ b/src/java.base/share/classes/sun/invoke/util/BytecodeDescriptor.java @@ -37,11 +37,14 @@ public class BytecodeDescriptor { private BytecodeDescriptor() { } // cannot instantiate - /** - * @throws IllegalArgumentException if the descriptor is invalid - * @throws TypeNotPresentException if the descriptor is valid, but - * the class cannot be found by the loader - */ + /// Parses and validates a field descriptor string in the {@code loader} context. + /// + /// @param descriptor a field descriptor string + /// @param loader the class loader in which to look up the types (null means + /// bootstrap class loader) + /// @throws IllegalArgumentException if the descriptor is invalid + /// @throws TypeNotPresentException if the descriptor is valid, but + /// the class cannot be found by the loader public static Class parseClass(String descriptor, ClassLoader loader) { int[] i = {0}; var ret = parseSig(descriptor, i, descriptor.length(), loader); @@ -51,15 +54,16 @@ public static Class parseClass(String descriptor, ClassLoader loader) { return ret; } - /** - * @param loader the class loader in which to look up the types (null means - * bootstrap class loader) - * @throws IllegalArgumentException if the descriptor is invalid - * @throws TypeNotPresentException if the descriptor is valid, but - * a reference type cannot be found by the loader - */ - public static List> parseMethod(String bytecodeSignature, ClassLoader loader) { - return parseMethod(bytecodeSignature, 0, bytecodeSignature.length(), loader); + /// Parses and validates a method descriptor string in the {@code loader} context. + /// + /// @param descriptor a method descriptor string + /// @param loader the class loader in which to look up the types (null means + /// bootstrap class loader) + /// @throws IllegalArgumentException if the descriptor is invalid + /// @throws TypeNotPresentException if a reference type cannot be found by + /// the loader (before the descriptor is found invalid) + public static List> parseMethod(String descriptor, ClassLoader loader) { + return parseMethod(descriptor, 0, descriptor.length(), loader); } /** @@ -94,17 +98,19 @@ private static void parseError(String str, String msg) { throw new IllegalArgumentException("bad signature: "+str+": "+msg); } - /** - * Parse a single type in a descriptor. Results can be: - *
    - *
  • A {@code Class} for successful parsing - *
  • {@code null} for malformed descriptor format - *
  • Throwing a {@link TypeNotPresentException} for valid class name, - * but class cannot be discovered - *
- * @param loader the class loader in which to look up the types (null means - * bootstrap class loader) - */ + /// Parse a single type in a descriptor. Results can be: + /// + /// - A `Class` for successful parsing + /// - `null` for malformed descriptor format + /// - Throwing a [TypeNotPresentException] for valid class name, + /// but class cannot be found + /// + /// @param str contains the string to parse + /// @param i cursor for the next token in the string, modified in-place + /// @param end the limit for parsing + /// @param loader the class loader in which to look up the types (null means + /// bootstrap class loader) + /// private static Class parseSig(String str, int[] i, int end, ClassLoader loader) { if (i[0] == end) return null; char c = str.charAt(i[0]++); @@ -124,8 +130,14 @@ private static Class parseSig(String str, int[] i, int end, ClassLoader loade t = t.arrayType(); return t; } else { - var w = Wrapper.forPrimitiveTypeOrNull(c); - return w == null ? null : w.primitiveType(); + Wrapper w; + try { + w = Wrapper.forBasicType(c); + } catch (IllegalArgumentException ex) { + // Our reporting has better error message + return null; + } + return w.primitiveType(); } } diff --git a/src/java.base/share/classes/sun/invoke/util/Wrapper.java b/src/java.base/share/classes/sun/invoke/util/Wrapper.java index 361b35abfd80d..4e8beaabb3486 100644 --- a/src/java.base/share/classes/sun/invoke/util/Wrapper.java +++ b/src/java.base/share/classes/sun/invoke/util/Wrapper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -352,19 +352,6 @@ public static Wrapper forBasicType(char type) { throw basicTypeError(type); } - /** - * Return the primitive wrapper for the given char. Does not return - * {@code OBJECT}. Returns {@code null} to allow flexible error messages. - * Dedicated for {@link BytecodeDescriptor}. - */ - static Wrapper forPrimitiveTypeOrNull(char type) { - Wrapper w = FROM_CHAR[(type + (type >> 1)) & 0xf]; - if (w != null && w != OBJECT && w.basicTypeChar == type) { - return w; - } - return null; - } - @DontInline private static RuntimeException basicTypeError(char type) { for (Wrapper x : values()) { diff --git a/test/jdk/sun/invoke/util/BytecodeDescriptorTest.java b/test/jdk/sun/invoke/util/BytecodeDescriptorTest.java new file mode 100644 index 0000000000000..fa99c20e64476 --- /dev/null +++ b/test/jdk/sun/invoke/util/BytecodeDescriptorTest.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8356022 + * @summary Tests for sun.invoke.util.BytecodeDescriptor + * @library /test/lib + * @modules java.base/sun.invoke.util + * @run junit BytecodeDescriptorTest + */ + +import java.lang.classfile.ClassFile; +import java.lang.constant.ClassDesc; +import java.util.List; +import java.util.Map; + +import jdk.test.lib.ByteCodeLoader; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import sun.invoke.util.BytecodeDescriptor; + +import static org.junit.jupiter.api.Assertions.*; + +class BytecodeDescriptorTest { + + private static final String FOO_NAME = "dummy.Foo"; + private static final String BAR_NAME = "dummy.Bar"; + private static final String FOO_DESC = "L" + FOO_NAME.replace('.', '/') + ";"; + private static final String BAR_DESC = "L" + BAR_NAME.replace('.', '/') + ";"; + private static final String DOES_NOT_EXIST_DESC = "Ldoes/not/Exist;"; + static Class foo1, foo2, bar1; + static ClassLoader cl1, cl2; + + @BeforeAll + static void setup() throws Throwable { + var fooBytes = ClassFile.of().build(ClassDesc.of(FOO_NAME), _ -> {}); + var barBytes = ClassFile.of().build(ClassDesc.of(BAR_NAME), _ -> {}); + cl1 = new ByteCodeLoader(Map.of(FOO_NAME, fooBytes, BAR_NAME, barBytes), ClassLoader.getSystemClassLoader()); + foo1 = cl1.loadClass(FOO_NAME); + bar1 = cl1.loadClass(BAR_NAME); + foo2 = ByteCodeLoader.load(FOO_NAME, fooBytes); + cl2 = foo2.getClassLoader(); + + // Sanity + assertNotSame(foo1, foo2); + assertNotSame(cl1, cl2); + assertSame(cl1, foo1.getClassLoader()); + assertSame(cl1, bar1.getClassLoader()); + assertNotSame(cl1, foo2.getClassLoader()); + assertEquals(FOO_DESC, foo1.descriptorString()); + assertEquals(FOO_DESC, foo2.descriptorString()); + assertEquals(BAR_DESC, bar1.descriptorString()); + } + + @Test + void testParseClass() throws ReflectiveOperationException { + assertSame(void.class, BytecodeDescriptor.parseClass("V", null), "void"); + assertSame(int.class, BytecodeDescriptor.parseClass("I", null), "primitive"); + assertSame(long[][].class, BytecodeDescriptor.parseClass("[[J", null), "array"); + assertSame(Object.class, BytecodeDescriptor.parseClass("Ljava/lang/Object;", null), "class or interface"); + assertThrows(IllegalArgumentException.class, () -> BytecodeDescriptor.parseClass("java/lang/Object", null), "internal name"); + assertThrows(IllegalArgumentException.class, () -> BytecodeDescriptor.parseClass("[V", null), "bad array"); + assertSame(Class.forName("[".repeat(255) + "I"), BytecodeDescriptor.parseClass("[".repeat(255) + "I", null), "good array"); + assertThrows(IllegalArgumentException.class, () -> BytecodeDescriptor.parseClass("[".repeat(256) + "I", null), "bad array"); + + assertSame(foo2, BytecodeDescriptor.parseClass(FOO_DESC, cl2), "class loader"); + assertThrows(TypeNotPresentException.class, () -> BytecodeDescriptor.parseClass(DOES_NOT_EXIST_DESC, null), "not existent"); + assertThrows(TypeNotPresentException.class, () -> BytecodeDescriptor.parseClass(BAR_DESC, cl2), "cross loader"); + } + + @Test + void testParseMethod() { + assertEquals(List.of(void.class), + BytecodeDescriptor.parseMethod("()V", null), + "no-arg"); + assertEquals(List.of(int.class, Object.class, long[].class, void.class), + BytecodeDescriptor.parseMethod("(ILjava/lang/Object;[J)V", null), + "sanity"); + assertThrows(IllegalArgumentException.class, + () -> BytecodeDescriptor.parseMethod("()", null), + "no return"); + assertThrows(IllegalArgumentException.class, + () -> BytecodeDescriptor.parseMethod("(V)V", null), + "bad arg"); + var voidInMsgIAE = assertThrows(IllegalArgumentException.class, + () -> BytecodeDescriptor.parseMethod("([V)I", null), + "bad arg"); + assertTrue(voidInMsgIAE.getMessage().contains("[V"), () -> "missing [V type in: '%s'".formatted(voidInMsgIAE.getMessage())); + assertThrows(IllegalArgumentException.class, + () -> BytecodeDescriptor.parseClass("([".repeat(256) + "I)J", null), + "bad arg"); + + assertEquals(List.of(foo1, bar1), + BytecodeDescriptor.parseMethod("(" + FOO_DESC + ")" + BAR_DESC, cl1), + "class loader"); + assertThrows(TypeNotPresentException.class, + () -> BytecodeDescriptor.parseMethod("(" + FOO_DESC + ")" + BAR_DESC, cl2), + "no bar"); + assertThrows(TypeNotPresentException.class, + () -> BytecodeDescriptor.parseMethod("(" + FOO_DESC + "V)V", null), + "first encounter TNPE"); + assertThrows(IllegalArgumentException.class, + () -> BytecodeDescriptor.parseMethod("(V" + FOO_DESC + ")V", null), + "first encounter IAE"); + } + +}