diff --git a/src/java.base/share/classes/java/lang/Class.java b/src/java.base/share/classes/java/lang/Class.java index dfdf515f424..5341249e085 100644 --- a/src/java.base/share/classes/java/lang/Class.java +++ b/src/java.base/share/classes/java/lang/Class.java @@ -78,6 +78,7 @@ import jdk.internal.reflect.CallerSensitiveAdapter; import jdk.internal.reflect.ConstantPool; import jdk.internal.reflect.Reflection; import jdk.internal.reflect.ReflectionFactory; +import jdk.internal.util.ModifiedUtf; import jdk.internal.vm.annotation.AOTRuntimeSetup; import jdk.internal.vm.annotation.AOTSafeClassInitializer; import jdk.internal.vm.annotation.IntrinsicCandidate; @@ -467,6 +468,7 @@ public final class Class implements java.io.Serializable, @CallerSensitiveAdapter private static Class forName(String className, Class caller) throws ClassNotFoundException { + validateClassNameLength(className); ClassLoader loader = (caller == null) ? ClassLoader.getSystemClassLoader() : ClassLoader.getClassLoader(caller); return forName0(className, true, loader, caller); @@ -549,6 +551,7 @@ public final class Class implements java.io.Serializable, public static Class forName(String name, boolean initialize, ClassLoader loader) throws ClassNotFoundException { + validateClassNameLength(name); return forName0(name, initialize, loader, null); } @@ -598,6 +601,9 @@ public final class Class implements java.io.Serializable, public static Class forName(Module module, String name) { Objects.requireNonNull(module); Objects.requireNonNull(name); + if (!ModifiedUtf.isValidLengthInConstantPool(name)) { + return null; + } ClassLoader cl = module.getClassLoader(); if (cl != null) { @@ -4148,4 +4154,14 @@ public final class Class implements java.io.Serializable, int getClassFileAccessFlags() { return classFileAccessFlags; } + + // Validates the length of the class name and throws an exception if it exceeds the maximum allowed length. + private static void validateClassNameLength(String name) throws ClassNotFoundException { + if (!ModifiedUtf.isValidLengthInConstantPool(name)) { + throw new ClassNotFoundException( + "Class name length exceeds limit of " + + ModifiedUtf.CONSTANT_POOL_UTF8_MAX_BYTES + + ": " + name.substring(0,256) + "..."); + } + } } diff --git a/src/java.base/share/classes/jdk/internal/util/ModifiedUtf.java b/src/java.base/share/classes/jdk/internal/util/ModifiedUtf.java index a8d2fe8bb74..e8a4f27796f 100644 --- a/src/java.base/share/classes/jdk/internal/util/ModifiedUtf.java +++ b/src/java.base/share/classes/jdk/internal/util/ModifiedUtf.java @@ -33,6 +33,10 @@ import jdk.internal.vm.annotation.ForceInline; * @since 24 */ public abstract class ModifiedUtf { + // Maximum number of bytes allowed for a Modified UTF-8 encoded string + // in a ClassFile constant pool entry (CONSTANT_Utf8_info). + public static final int CONSTANT_POOL_UTF8_MAX_BYTES = 65535; + private ModifiedUtf() { } @@ -68,4 +72,26 @@ public abstract class ModifiedUtf { } return utflen; } + + /** + * Checks whether the Modified UTF-8 encoded length of the given string + * fits within the ClassFile constant pool limit (u2 length = 65535 bytes). + * @param str the string to check + */ + @ForceInline + public static boolean isValidLengthInConstantPool(String str) { + // Quick approximation: each char can be at most 3 bytes in Modified UTF-8. + // If the string is short enough, it definitely fits. + int strLen = str.length(); + if (strLen <= CONSTANT_POOL_UTF8_MAX_BYTES / 3) { + return true; + } + if (strLen > CONSTANT_POOL_UTF8_MAX_BYTES) { + return false; + } + // Check exact Modified UTF-8 length. + // The check strLen > CONSTANT_POOL_UTF8_MAX_BYTES above ensures that utfLen can't overflow here. + int utfLen = utfLen(str, 0); + return utfLen <= CONSTANT_POOL_UTF8_MAX_BYTES; + } } diff --git a/test/hotspot/jtreg/runtime/exceptionMsgs/NullPointerException/NullPointerExceptionTest.java b/test/hotspot/jtreg/runtime/exceptionMsgs/NullPointerException/NullPointerExceptionTest.java index e56d283783a..7f7aca87437 100644 --- a/test/hotspot/jtreg/runtime/exceptionMsgs/NullPointerException/NullPointerExceptionTest.java +++ b/test/hotspot/jtreg/runtime/exceptionMsgs/NullPointerException/NullPointerExceptionTest.java @@ -54,6 +54,7 @@ import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.invoke.MethodHandles.Lookup; +import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Random; @@ -1365,7 +1366,7 @@ public class NullPointerExceptionTest { // If NPE is thrown in a native method, the message should // not be generated. try { - Class.forName(null); + Array.get(null,0); Asserts.fail(); } catch (NullPointerException e) { Asserts.assertNull(e.getMessage()); diff --git a/test/jdk/java/lang/Class/forName/ForNameNames.java b/test/jdk/java/lang/Class/forName/ForNameNames.java index fdf16a37d85..aaffd3dd4b3 100644 --- a/test/jdk/java/lang/Class/forName/ForNameNames.java +++ b/test/jdk/java/lang/Class/forName/ForNameNames.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -23,7 +23,7 @@ /** * @test - * @bug 8310242 + * @bug 8310242 8328874 * @run junit ForNameNames * @summary Verify class names for Class.forName */ @@ -37,6 +37,16 @@ import org.junit.jupiter.params.provider.MethodSource; import static org.junit.jupiter.api.Assertions.*; public class ForNameNames { + //Max length in Modified UTF-8 bytes for class names. + private static final int JAVA_CLASSNAME_MAX_LEN = 65535; + + private static final String ONE_BYTE = "A"; // 1-byte UTF-8 + private static final String TWO_BYTE = "\u0100"; // 2-byte UTF-8 + private static final String THREE_BYTE = "\u2600"; // 3-byte UTF-8 + + private static final String ERR_MSG_IN_CORE = "Class name length exceeds limit of"; // check in corelib + private static final String ERR_MSG_IN_JVM = "Class name exceeds maximum length"; // check in jvm + static class Inner {} static Stream testCases() { return Stream.of( @@ -90,4 +100,78 @@ public class ForNameNames { assertNull(c); } -} + static Stream validLen() { + return Stream.of( + // 1-byte character + Arguments.of(ONE_BYTE.repeat(JAVA_CLASSNAME_MAX_LEN - 1)), + Arguments.of(ONE_BYTE.repeat(JAVA_CLASSNAME_MAX_LEN)), + Arguments.of(ONE_BYTE.repeat(JAVA_CLASSNAME_MAX_LEN / 3 - 1)), + Arguments.of(ONE_BYTE.repeat(JAVA_CLASSNAME_MAX_LEN / 3)), + Arguments.of(ONE_BYTE.repeat(JAVA_CLASSNAME_MAX_LEN / 3 + 1)), + // 2-byte characters + Arguments.of(TWO_BYTE.repeat(JAVA_CLASSNAME_MAX_LEN / 2)), + Arguments.of(TWO_BYTE.repeat(JAVA_CLASSNAME_MAX_LEN / 6)), + Arguments.of(TWO_BYTE.repeat(JAVA_CLASSNAME_MAX_LEN / 6 + 1)), + // 3-byte characters + Arguments.of(THREE_BYTE.repeat(JAVA_CLASSNAME_MAX_LEN / 3 - 1)), + Arguments.of(THREE_BYTE.repeat(JAVA_CLASSNAME_MAX_LEN / 3)), + Arguments.of(THREE_BYTE.repeat(JAVA_CLASSNAME_MAX_LEN / 9)), + Arguments.of(THREE_BYTE.repeat(JAVA_CLASSNAME_MAX_LEN / 9 + 1)) + ); + } + + /* + * Test class name length handling in 1-arg and 3-arg Class::forName + * with valid length. + */ + @ParameterizedTest + @MethodSource("validLen") + void testValidLen(String cn) { + ClassLoader loader = ForNameNames.class.getClassLoader(); + // 3-arg Class.forName + ClassNotFoundException ex = assertThrows(ClassNotFoundException.class, + () -> Class.forName(cn, false, loader)); + assertFalse(ex.getMessage().contains(ERR_MSG_IN_CORE) + || ex.getMessage().contains(ERR_MSG_IN_JVM), + "Unexpected exception message"); + + // 1-arg Class.forName + ex = assertThrows(ClassNotFoundException.class, + () -> Class.forName(cn)); + assertFalse(ex.getMessage().contains(ERR_MSG_IN_CORE) + || ex.getMessage().contains(ERR_MSG_IN_JVM), + "Unexpected exception message"); + } + + static Stream invalidLen() { + return Stream.of( + // 1-byte characters over the limit + Arguments.of(ONE_BYTE.repeat(JAVA_CLASSNAME_MAX_LEN + 1)), + // 2-byte characters over the limit + Arguments.of(TWO_BYTE.repeat(JAVA_CLASSNAME_MAX_LEN / 2 + 1)), + // 3-byte characters over the limit + Arguments.of(THREE_BYTE.repeat(JAVA_CLASSNAME_MAX_LEN / 3 + 1)) + ); + } + + /* + * Test class name length handling in 1-arg and 3-arg Class::forName + * with invalid (too long) length. + */ + @ParameterizedTest + @MethodSource("invalidLen") + void testInvalidLen(String cn) { + ClassLoader loader = ForNameNames.class.getClassLoader(); + // 3-arg Class.forName + ClassNotFoundException ex = assertThrows(ClassNotFoundException.class, + () -> Class.forName(cn, false, loader)); + assertTrue(ex.getMessage().contains(ERR_MSG_IN_CORE), + "Unexpected exception message"); + + // 1-arg Class.forName + ex = assertThrows(ClassNotFoundException.class, + () -> Class.forName(cn)); + assertTrue(ex.getMessage().contains(ERR_MSG_IN_CORE), + "Unexpected exception message"); + } +} \ No newline at end of file