diff --git a/src/java.base/share/classes/java/lang/runtime/ObjectMethods.java b/src/java.base/share/classes/java/lang/runtime/ObjectMethods.java index e4b2886404f9e..922ac651f7e8d 100644 --- a/src/java.base/share/classes/java/lang/runtime/ObjectMethods.java +++ b/src/java.base/share/classes/java/lang/runtime/ObjectMethods.java @@ -479,12 +479,7 @@ private static List> split(MethodHandle[] getters) { * {@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 @@ private static List> split(MethodHandle[] getters) { * 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 static Object bootstrap(MethodHandles.Lookup lookup, String methodName, T requireNonNull(names); List 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; diff --git a/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java b/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java index d15e701b94d17..c52fc9ec75c8b 100644 --- a/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java +++ b/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java @@ -169,16 +169,13 @@ private static class StaticHolders { * 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 static CallSite typeSwitch(MethodHandles.Lookup lookup, 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 @@ private static void verifyLabel(Object label, Class selectorType) { * @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 @@ private static void verifyLabel(Object label, Class selectorType) { * 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 static CallSite enumSwitch(MethodHandles.Lookup lookup, 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() diff --git a/src/java.base/share/classes/java/lang/runtime/package-info.java b/src/java.base/share/classes/java/lang/runtime/package-info.java index 9e19ef9bd7e79..e2597e45c3444 100644 --- a/src/java.base/share/classes/java/lang/runtime/package-info.java +++ b/src/java.base/share/classes/java/lang/runtime/package-info.java @@ -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. + *

+ * Unless otherwise specified:

    + *
  • 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. + *
  • {@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}. + *
* * @since 14 */ package java.lang.runtime; + +import java.lang.invoke.MethodHandles.Lookup; diff --git a/test/jdk/java/lang/runtime/ObjectMethodsTest.java b/test/jdk/java/lang/runtime/ObjectMethodsTest.java index d7ca5912273b8..56be7008f4105 100644 --- a/test/jdk/java/lang/runtime/ObjectMethodsTest.java +++ b/test/jdk/java/lang/runtime/ObjectMethodsTest.java @@ -79,6 +79,7 @@ static class Empty { } 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 @@ void commonExceptions(NamePlusType npt) { 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)); diff --git a/test/jdk/java/lang/runtime/SwitchBootstrapsTest.java b/test/jdk/java/lang/runtime/SwitchBootstrapsTest.java index 061ce2ae24129..76bfe7616f693 100644 --- a/test/jdk/java/lang/runtime/SwitchBootstrapsTest.java +++ b/test/jdk/java/lang/runtime/SwitchBootstrapsTest.java @@ -42,40 +42,25 @@ 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 @@ private void testEnum(Enum target, int start, int result, Object... labels) t 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 void testEnums() throws Throwable { //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 void testWrongSwitchTypes() throws Throwable { }; 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 void testWrongSwitchTypes() throws Throwable { }; for (MethodType enumSwitchType : enumSwitchTypes) { assertThrows(IllegalArgumentException.class, () -> - BSM_ENUM_SWITCH.invoke(MethodHandles.lookup(), "", enumSwitchType) + SwitchBootstraps.enumSwitch(MethodHandles.lookup(), "", enumSwitchType) ); } } @@ -270,23 +255,23 @@ enum E {A, B, C} 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 @@ enum E { 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 @@ enum E { 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 @@ private static byte[] createClass() { } @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"); }); } }