8378796: java.lang.runtime bootstrap methods missing lookup validation

Reviewed-by: jvernee
This commit is contained in:
Chen Liang 2026-06-25 14:06:19 +00:00
parent 2b20a1391b
commit 5a19e49dc4
5 changed files with 67 additions and 54 deletions

View File

@ -479,12 +479,7 @@ public final class ObjectMethods {
* {@link java.lang.Record#toString()}.
*
*
* @param lookup Every bootstrap method is expected to have a {@code lookup}
* which usually represents a lookup context with the
* accessibility privileges of the caller. This is because
* {@code invokedynamic} call sites always provide a {@code lookup}
* to the corresponding bootstrap method, but this method just
* ignores the {@code lookup} parameter
* @param lookup the full-privilege lookup context of the caller
* @param methodName the name of the method to generate, which must be one of
* {@code "equals"}, {@code "hashCode"}, or {@code "toString"}
* @param type a {@link MethodType} corresponding the descriptor type
@ -503,8 +498,6 @@ public final class ObjectMethods {
* if invoked by a condy
* @throws IllegalArgumentException if the bootstrap arguments are invalid
* or inconsistent
* @throws NullPointerException if any argument is {@code null} or if any element
* in the {@code getters} array is {@code null}
* @throws Throwable if any exception is thrown during call site construction
*/
public static Object bootstrap(MethodHandles.Lookup lookup, String methodName, TypeDescriptor type,
@ -518,6 +511,9 @@ public final class ObjectMethods {
requireNonNull(names);
List<MethodHandle> getterList = List.of(getters); // deep null check
if (!lookup.hasFullPrivilegeAccess())
throw new IllegalArgumentException("Unprivileged lookup ".concat(lookup.toString()));
MethodType methodType;
if (type instanceof MethodType mt)
methodType = mt;

View File

@ -169,16 +169,13 @@ public final class SwitchBootstraps {
* the length of the {@code labels} array (inclusive),
* both or an {@link IndexOutOfBoundsException} is thrown.
*
* @param lookup Represents a lookup context with the accessibility
* privileges of the caller. When used with {@code invokedynamic},
* this is stacked automatically by the VM.
* @param lookup the full-privilege lookup context of the caller
* @param invocationName unused, {@code null} is permitted
* @param invocationType The invocation type of the {@code CallSite} with two parameters,
* a target type, an {@code int}, and {@code int} as a return type.
* @param labels case labels as described above
* @return a {@code CallSite} returning the first matching element as described above
*
* @throws NullPointerException if any argument is {@code null}, unless noted otherwise
* @throws IllegalArgumentException if any element in the labels array is null
* @throws IllegalArgumentException if the invocation type is not a method type of first parameter of a target type,
* second parameter of type {@code int} and with {@code int} as its return type
@ -198,6 +195,9 @@ public final class SwitchBootstraps {
requireNonNull(invocationType);
requireNonNull(labels);
if (!lookup.hasFullPrivilegeAccess())
throw new IllegalArgumentException("Unprivileged lookup ".concat(lookup.toString()));
Class<?> selectorType = invocationType.parameterType(0);
if (invocationType.parameterCount() != 2
|| (!invocationType.returnType().equals(int.class))
@ -275,9 +275,7 @@ public final class SwitchBootstraps {
* @apiNote It is permissible for the {@code labels} array to contain {@code String}
* values that do not represent any enum constants at runtime.
*
* @param lookup Represents a lookup context with the accessibility
* privileges of the caller. When used with {@code invokedynamic},
* this is stacked automatically by the VM.
* @param lookup the full-privilege lookup context of the caller
* @param invocationName unused, {@code null} is permitted
* @param invocationType The invocation type of the {@code CallSite} with two parameters,
* an enum type, an {@code int}, and {@code int} as a return type.
@ -285,7 +283,6 @@ public final class SwitchBootstraps {
* in any combination
* @return a {@code CallSite} returning the first matching element as described above
*
* @throws NullPointerException if any argument is {@code null}, unless noted otherwise
* @throws IllegalArgumentException if any element in the labels array is null
* @throws IllegalArgumentException if any element in the labels array is an empty {@code String}
* @throws IllegalArgumentException if the invocation type is not a method type
@ -305,6 +302,9 @@ public final class SwitchBootstraps {
requireNonNull(invocationType);
requireNonNull(labels);
if (!lookup.hasFullPrivilegeAccess())
throw new IllegalArgumentException("Unprivileged lookup ".concat(lookup.toString()));
if (invocationType.parameterCount() != 2
|| (!invocationType.returnType().equals(int.class))
|| invocationType.parameterType(0).isPrimitive()

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -26,8 +26,20 @@
/**
* The {@code java.lang.runtime} package provides low-level runtime support
* for the Java language.
* <p>
* Unless otherwise specified: <ul>
* <li>Methods and constructors in this package throw a {@link
* NullPointerException} when they are called with {@code null} or an array
* that contains {@code null} as an argument.
* <li>{@linkplain java.lang.invoke##bsm Bootstrap methods} in this package
* throw an {@link IllegalArgumentException} when they are called with a
* {@link Lookup Lookup} that does not have {@linkplain
* Lookup#hasFullPrivilegeAccess() full privilege access}.
* </ul>
*
* @since 14
*/
package java.lang.runtime;
import java.lang.invoke.MethodHandles.Lookup;

View File

@ -79,6 +79,7 @@ public class ObjectMethodsTest {
}
static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
static final MethodHandles.Lookup UNPRIVILEGED_LOOKUP = LOOKUP.dropLookupMode(MethodHandles.Lookup.PRIVATE);
@Test
public void testEqualsC() throws Throwable {
@ -184,6 +185,9 @@ public class ObjectMethodsTest {
assertThrows(NPE, () -> ObjectMethods.bootstrap(LOOKUP, null, type, C.class, "x;y", C.ACCESSORS));
assertThrows(NPE, () -> ObjectMethods.bootstrap(null, name, type, C.class, "x;y", C.ACCESSORS));
// Unprivileged lookup
assertThrows(IAE, () -> ObjectMethods.bootstrap(UNPRIVILEGED_LOOKUP, name, type, C.class, "x;y", C.ACCESSORS));
// Bad indy call receiver type - change C to this test class
assertThrows(IAE, () -> ObjectMethods.bootstrap(LOOKUP, name, type.changeParameterType(0, this.getClass()), C.class, "x;y", C.ACCESSORS));

View File

@ -42,40 +42,25 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
/*
* @test
* @bug 8318144
* @enablePreview
* @compile SwitchBootstrapsTest.java
* @run junit/othervm SwitchBootstrapsTest
* @run junit SwitchBootstrapsTest
*/
public class SwitchBootstrapsTest {
public static final MethodHandle BSM_TYPE_SWITCH;
public static final MethodHandle BSM_ENUM_SWITCH;
static {
try {
BSM_TYPE_SWITCH = MethodHandles.lookup().findStatic(SwitchBootstraps.class, "typeSwitch",
MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, Object[].class));
BSM_ENUM_SWITCH = MethodHandles.lookup().findStatic(SwitchBootstraps.class, "enumSwitch",
MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, Object[].class));
}
catch (ReflectiveOperationException e) {
throw new AssertionError("Should not happen", e);
}
}
private void testType(Object target, int start, int result, Object... labels) throws Throwable {
MethodType switchType = MethodType.methodType(int.class, Object.class, int.class);
MethodHandle indy = ((CallSite) BSM_TYPE_SWITCH.invoke(MethodHandles.lookup(), "", switchType, labels)).dynamicInvoker();
MethodHandle indy = SwitchBootstraps.typeSwitch(MethodHandles.lookup(), "", switchType, labels).dynamicInvoker();
assertEquals(result, (int) indy.invoke(target, start));
assertEquals(-1, (int) indy.invoke(null, start));
}
private void testPrimitiveType(Object target, Class<?> targetType, int start, int result, Object... labels) throws Throwable {
MethodType switchType = MethodType.methodType(int.class, targetType, int.class);
MethodHandle indy = ((CallSite) BSM_TYPE_SWITCH.invoke(MethodHandles.lookup(), "", switchType, labels)).dynamicInvoker();
MethodHandle indy = SwitchBootstraps.typeSwitch(MethodHandles.lookup(), "", switchType, labels).dynamicInvoker();
assertEquals(result, (int) indy.invoke(target, start));
}
@ -85,7 +70,7 @@ public class SwitchBootstrapsTest {
private void testEnum(Class<?> targetClass, Enum<?> target, int start, int result, Object... labels) throws Throwable {
MethodType switchType = MethodType.methodType(int.class, targetClass, int.class);
MethodHandle indy = ((CallSite) BSM_ENUM_SWITCH.invoke(MethodHandles.lookup(), "", switchType, labels)).dynamicInvoker();
MethodHandle indy = SwitchBootstraps.enumSwitch(MethodHandles.lookup(), "", switchType, labels).dynamicInvoker();
assertEquals(result, (int) indy.invoke(target, start));
assertEquals(-1, (int) indy.invoke(null, start));
}
@ -188,7 +173,7 @@ public class SwitchBootstrapsTest {
//null invocation name:
MethodType switchType = MethodType.methodType(int.class, E1.class, int.class);
MethodHandle indy = ((CallSite) BSM_ENUM_SWITCH.invoke(MethodHandles.lookup(), null, switchType)).dynamicInvoker();
MethodHandle indy = SwitchBootstraps.enumSwitch(MethodHandles.lookup(), null, switchType).dynamicInvoker();
assertEquals(0, (int) indy.invoke(E1.A, 0));
}
@ -229,7 +214,7 @@ public class SwitchBootstrapsTest {
};
for (MethodType switchType : switchTypes) {
assertThrows(IllegalArgumentException.class, () ->
BSM_TYPE_SWITCH.invoke(MethodHandles.lookup(), "", switchType)
SwitchBootstraps.typeSwitch(MethodHandles.lookup(), "", switchType)
);
}
MethodType[] enumSwitchTypes = new MethodType[] {
@ -240,7 +225,7 @@ public class SwitchBootstrapsTest {
};
for (MethodType enumSwitchType : enumSwitchTypes) {
assertThrows(IllegalArgumentException.class, () ->
BSM_ENUM_SWITCH.invoke(MethodHandles.lookup(), "", enumSwitchType)
SwitchBootstraps.enumSwitch(MethodHandles.lookup(), "", enumSwitchType)
);
}
}
@ -270,23 +255,23 @@ public class SwitchBootstrapsTest {
public void testNullLabels() throws Throwable {
MethodType switchType = MethodType.methodType(int.class, Object.class, int.class);
assertThrows(NullPointerException.class, () ->
BSM_TYPE_SWITCH.invoke(MethodHandles.lookup(), "", switchType, (Object[]) null)
SwitchBootstraps.typeSwitch(MethodHandles.lookup(), "", switchType, (Object[]) null)
);
assertThrows(IllegalArgumentException.class, () ->
BSM_TYPE_SWITCH.invoke(MethodHandles.lookup(), "", switchType,
SwitchBootstraps.typeSwitch(MethodHandles.lookup(), "", switchType,
new Object[] {1, null, String.class})
);
MethodType enumSwitchType = MethodType.methodType(int.class, E1.class, int.class);
assertThrows(NullPointerException.class, () ->
BSM_TYPE_SWITCH.invoke(MethodHandles.lookup(), "", enumSwitchType, (Object[]) null)
SwitchBootstraps.typeSwitch(MethodHandles.lookup(), "", enumSwitchType, (Object[]) null)
);
assertThrows(IllegalArgumentException.class, () ->
BSM_TYPE_SWITCH.invoke(MethodHandles.lookup(), "", enumSwitchType,
SwitchBootstraps.typeSwitch(MethodHandles.lookup(), "", enumSwitchType,
new Object[] {1, null, String.class})
);
//null invocationName is OK:
BSM_TYPE_SWITCH.invoke(MethodHandles.lookup(), null, switchType,
new Object[] {Object.class});
SwitchBootstraps.typeSwitch(MethodHandles.lookup(), null, switchType,
Object.class);
}
private static AtomicBoolean enumInitialized = new AtomicBoolean();
@ -304,7 +289,7 @@ public class SwitchBootstrapsTest {
MethodType enumSwitchType = MethodType.methodType(int.class, E.class, int.class);
CallSite invocation = (CallSite) BSM_ENUM_SWITCH.invoke(MethodHandles.lookup(), "", enumSwitchType, new Object[] {"A"});
CallSite invocation = SwitchBootstraps.enumSwitch(MethodHandles.lookup(), "", enumSwitchType, "A");
assertFalse(enumInitialized.get());
assertEquals(-1, invocation.dynamicInvoker().invoke(null, 0));
assertFalse(enumInitialized.get());
@ -330,7 +315,7 @@ public class SwitchBootstrapsTest {
EnumDesc.of(ClassDesc.of(E.class.getName()), "A"),
"test"
};
CallSite invocation = (CallSite) BSM_TYPE_SWITCH.invoke(MethodHandles.lookup(), "", switchType, labels);
CallSite invocation = (CallSite) SwitchBootstraps.typeSwitch(MethodHandles.lookup(), "", switchType, labels);
assertFalse(enumInitialized.get());
assertEquals(-1, invocation.dynamicInvoker().invoke(null, 0));
assertFalse(enumInitialized.get());
@ -402,21 +387,37 @@ public class SwitchBootstrapsTest {
}
@Test
public void testNullLookup() throws Throwable {
public void testNullLookup() {
assertThrows(NullPointerException.class, () -> {
MethodType switchType = MethodType.methodType(int.class, Object.class, int.class);
BSM_TYPE_SWITCH.invoke(null, "", switchType, Object.class);
SwitchBootstraps.typeSwitch(null, "", switchType, Object.class);
});
enum E {}
assertThrows(NullPointerException.class, () -> {
MethodType switchType = MethodType.methodType(int.class, E.class, int.class);
BSM_ENUM_SWITCH.invoke(null, "", switchType,
new Object[] {});
SwitchBootstraps.enumSwitch(null, "", switchType);
});
assertThrows(NullPointerException.class, () -> {
MethodType switchType = MethodType.methodType(int.class, E.class, int.class);
BSM_ENUM_SWITCH.invoke(null, "", switchType,
new Object[] {"A"});
SwitchBootstraps.enumSwitch(null, "", switchType, "A");
});
}
@Test
public void testUnprivilegedLookup() {
var lookup = MethodHandles.lookup().dropLookupMode(MethodHandles.Lookup.PRIVATE);
assertThrows(IllegalArgumentException.class, () -> {
MethodType switchType = MethodType.methodType(int.class, Object.class, int.class);
SwitchBootstraps.typeSwitch(lookup, "", switchType, Object.class);
});
enum E {}
assertThrows(IllegalArgumentException.class, () -> {
MethodType switchType = MethodType.methodType(int.class, E.class, int.class);
SwitchBootstraps.enumSwitch(lookup, "", switchType);
});
assertThrows(IllegalArgumentException.class, () -> {
MethodType switchType = MethodType.methodType(int.class, E.class, int.class);
SwitchBootstraps.enumSwitch(lookup, "", switchType, "A");
});
}
}