diff --git a/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java b/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java index 985e2bed434..4dac59771e8 100644 --- a/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java +++ b/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java @@ -185,13 +185,17 @@ import sun.invoke.util.Wrapper; return i < ARG_NAME_CACHE.length ? ARG_NAME_CACHE[i] : "arg$" + (i + 1); } - private static String lambdaClassName(Class targetClass) { + private static String sanitizedTargetClassName(Class targetClass) { String name = targetClass.getName(); if (targetClass.isHidden()) { // use the original class name name = name.replace('/', '_'); } - return name.replace('.', '/').concat("$$Lambda"); + return name.replace('.', '/'); + } + + private static String lambdaClassName(Class targetClass) { + return sanitizedTargetClassName(targetClass).concat("$$Lambda"); } /** @@ -430,7 +434,7 @@ import sun.invoke.util.Wrapper; public void accept(CodeBuilder cob) { cob.new_(SerializationSupport.CD_SerializedLambda) .dup() - .ldc(classDesc(targetClass)) + .ldc(ClassDesc.ofInternalName(sanitizedTargetClassName(targetClass))) .ldc(factoryType.returnType().getName().replace('.', '/')) .ldc(interfaceMethodName) .ldc(interfaceMethodType.toMethodDescriptorString()) diff --git a/src/java.base/share/classes/java/lang/invoke/LambdaMetafactory.java b/src/java.base/share/classes/java/lang/invoke/LambdaMetafactory.java index 0cc6f627543..d6099861cee 100644 --- a/src/java.base/share/classes/java/lang/invoke/LambdaMetafactory.java +++ b/src/java.base/share/classes/java/lang/invoke/LambdaMetafactory.java @@ -229,6 +229,22 @@ import java.util.Objects; * used, but this is not compatible with some implementation techniques and * would complicate the work implementations must do. * + *

Uses besides evaluation of lambda expressions and method references are + * unintended. These linkage methods may change their unspecified behaviors at + * any time to better suit the Java language features they were designed to + * support, and such changes may impact unintended uses. Unintended uses of + * these linkage methods may lead to resource leaks, or other unspecified + * negative effects. + * + * @implNote In the reference implementation, the classes implementing the created + * function objects are strongly reachable from the defining class loader of the + * caller, like classes and interfaces in Java source code. This technique + * reduces heap memory use, but as a consequence, the implementation classes can + * be unloaded only if the caller class can be unloaded. In particular, if the + * caller is a {@linkplain MethodHandles.Lookup.ClassOption#STRONG weak hidden + * class}, the implementation class, a strong hidden class, may not be unloaded + * even if the caller may be unloaded. + * * @since 1.8 */ public final class LambdaMetafactory { diff --git a/test/jdk/java/lang/invoke/lambda/LambdaHiddenCaller.java b/test/jdk/java/lang/invoke/lambda/LambdaHiddenCaller.java new file mode 100644 index 00000000000..aed19e6ca17 --- /dev/null +++ b/test/jdk/java/lang/invoke/lambda/LambdaHiddenCaller.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 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 + * 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. + */ + +import java.io.Serializable; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.function.IntSupplier; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/* + * @test + * @bug 8343064 + * @summary Test about when lambda caller/target class is a hidden class + * @run junit LambdaHiddenCaller + */ +public class LambdaHiddenCaller { + static Hooks hiddenCaller; + + @BeforeAll + static void setup() throws Throwable { + byte[] bytes; + try (var in = LambdaHiddenCaller.class.getResourceAsStream("HiddenHooks.class")) { + bytes = in.readAllBytes(); + } + var hiddenClassLookup = MethodHandles.lookup().defineHiddenClass(bytes, true); + hiddenCaller = (Hooks) hiddenClassLookup.findConstructor(hiddenClassLookup.lookupClass(), MethodType.methodType(void.class)) + .asType(MethodType.methodType(Hooks.class)).invokeExact(); + } + + @Test + void testStaticMethod() { + var is = hiddenCaller.callStaticMethodLambda(); + assertEquals(42, is.getAsInt()); + } + + @Test + void testSerializableLambda() { + var is = hiddenCaller.callSerializableLambda(); + assertEquals(42, is.getAsInt()); + assertTrue(Serializable.class.isAssignableFrom(is.getClass())); + // We do not guarantee serialization functionalities yet + } +} + +/** + * Hooks to call hidden class methods easily. + */ +interface Hooks { + IntSupplier callStaticMethodLambda(); + + IntSupplier callSerializableLambda(); +} + +class HiddenHooks implements Hooks { + private static int compute() { + return 42; + } + + @Override + public IntSupplier callStaticMethodLambda() { + return HiddenHooks::compute; + } + + @Override + public IntSupplier callSerializableLambda() { + return (IntSupplier & Serializable) HiddenHooks::compute; + } +} \ No newline at end of file