diff --git a/src/java.base/share/classes/java/lang/invoke/GenerateJLIClassesHelper.java b/src/java.base/share/classes/java/lang/invoke/GenerateJLIClassesHelper.java index 19a193a924a..46eeb67de54 100644 --- a/src/java.base/share/classes/java/lang/invoke/GenerateJLIClassesHelper.java +++ b/src/java.base/share/classes/java/lang/invoke/GenerateJLIClassesHelper.java @@ -28,7 +28,6 @@ package java.lang.invoke; import jdk.internal.org.objectweb.asm.ClassWriter; import jdk.internal.org.objectweb.asm.Opcodes; import sun.invoke.util.Wrapper; -import sun.util.logging.PlatformLogger; import java.util.ArrayList; import java.util.HashSet; @@ -73,6 +72,7 @@ class GenerateJLIClassesHelper { private final TreeSet<String> speciesTypes = new TreeSet<>(); private final TreeSet<String> invokerTypes = new TreeSet<>(); + private final TreeSet<String> linkerTypes = new TreeSet<>(); private final TreeSet<String> callSiteTypes = new TreeSet<>(); private final Map<String, Set<String>> dmhMethods = new TreeMap<>(); @@ -87,6 +87,12 @@ class GenerateJLIClassesHelper { return this; } + HolderClassBuilder addLinkerType(String methodType) { + validateMethodType(methodType); + linkerTypes.add(methodType); + return this; + } + HolderClassBuilder addCallSiteType(String csType) { validateMethodType(csType); callSiteTypes.add(csType); @@ -130,19 +136,33 @@ class GenerateJLIClassesHelper { } } - // The invoker type to ask for is retrieved by removing the first + // The linker type to ask for is retrieved by removing the first // and the last argument, which needs to be of Object.class + MethodType[] linkerMethodTypes = new MethodType[linkerTypes.size()]; + index = 0; + for (String linkerType : linkerTypes) { + MethodType mt = asMethodType(linkerType); + final int lastParam = mt.parameterCount() - 1; + if (!checkLinkerTypeParams(mt)) { + throw new RuntimeException( + "Linker type parameter must start and end with Object: " + linkerType); + } + mt = mt.dropParameterTypes(lastParam, lastParam + 1); + linkerMethodTypes[index] = mt.dropParameterTypes(0, 1); + index++; + } + + // The invoker type to ask for is retrieved by removing the first + // argument, which needs to be of Object.class MethodType[] invokerMethodTypes = new MethodType[invokerTypes.size()]; index = 0; for (String invokerType : invokerTypes) { MethodType mt = asMethodType(invokerType); - final int lastParam = mt.parameterCount() - 1; if (!checkInvokerTypeParams(mt)) { throw new RuntimeException( - "Invoker type parameter must start and end with Object: " + invokerType); + "Invoker type parameter must start with 2 Objects: " + invokerType); } - mt = mt.dropParameterTypes(lastParam, lastParam + 1); - invokerMethodTypes[index] = mt.dropParameterTypes(0, 1); + invokerMethodTypes[index] = mt.dropParameterTypes(0, 2); index++; } @@ -171,7 +191,7 @@ class GenerateJLIClassesHelper { DELEGATING_HOLDER, directMethodTypes)); result.put(INVOKERS_HOLDER, generateInvokersHolderClassBytes(INVOKERS_HOLDER, - invokerMethodTypes, callSiteMethodTypes)); + linkerMethodTypes, invokerMethodTypes, callSiteMethodTypes)); result.put(BASIC_FORMS_HOLDER, generateBasicFormsClassBytes(BASIC_FORMS_HOLDER)); @@ -207,6 +227,12 @@ class GenerateJLIClassesHelper { } public static boolean checkInvokerTypeParams(MethodType mt) { + return (mt.parameterCount() >= 2 && + mt.parameterType(0) == Object.class && + mt.parameterType(1) == Object.class); + } + + public static boolean checkLinkerTypeParams(MethodType mt) { final int lastParam = mt.parameterCount() - 1; return (mt.parameterCount() >= 2 && mt.parameterType(0) == Object.class && @@ -320,15 +346,11 @@ class GenerateJLIClassesHelper { if ("linkToTargetMethod".equals(parts[2]) || "linkToCallSite".equals(parts[2])) { builder.addCallSiteType(methodType); + } else if (parts[2].endsWith("nvoker")) { + // MH.exactInvoker exactInvoker MH.invoker invoker + builder.addInvokerType(methodType); } else { - MethodType mt = HolderClassBuilder.asMethodType(methodType); - // Work around JDK-8327499 - if (HolderClassBuilder.checkInvokerTypeParams(mt)) { - builder.addInvokerType(methodType); - } else { - PlatformLogger.getLogger("java.lang.invoke") - .warning("Invalid LF_RESOLVE " + parts[1] + " " + parts[2] + " " + parts[3]); - } + builder.addLinkerType(methodType); } } else if (parts[1].contains("DirectMethodHandle")) { String dmh = parts[2]; @@ -465,27 +487,27 @@ class GenerateJLIClassesHelper { /** * Returns a {@code byte[]} representation of a class implementing - * the invoker forms for the set of supplied {@code invokerMethodTypes} - * and {@code callSiteMethodTypes}. + * the invoker forms for the set of supplied {@code linkerMethodTypes} + * {@code invokerMethodTypes}, and {@code callSiteMethodTypes}. */ static byte[] generateInvokersHolderClassBytes(String className, - MethodType[] invokerMethodTypes, MethodType[] callSiteMethodTypes) { + MethodType[] linkerMethodTypes, MethodType[] invokerMethodTypes, + MethodType[] callSiteMethodTypes) { HashSet<MethodType> dedupSet = new HashSet<>(); ArrayList<LambdaForm> forms = new ArrayList<>(); ArrayList<String> names = new ArrayList<>(); - int[] types = { - MethodTypeForm.LF_EX_LINKER, + + int[] invokerTypes = { MethodTypeForm.LF_EX_INVOKER, - MethodTypeForm.LF_GEN_LINKER, - MethodTypeForm.LF_GEN_INVOKER + MethodTypeForm.LF_GEN_INVOKER, }; - for (int i = 0; i < invokerMethodTypes.length; i++) { + for (MethodType methodType : invokerMethodTypes) { // generate methods representing invokers of the specified type - if (dedupSet.add(invokerMethodTypes[i])) { - for (int type : types) { - LambdaForm invokerForm = Invokers.invokeHandleForm(invokerMethodTypes[i], + if (dedupSet.add(methodType)) { + for (int type : invokerTypes) { + LambdaForm invokerForm = Invokers.invokeHandleForm(methodType, /*customized*/false, type); forms.add(invokerForm); names.add(invokerForm.kind.defaultLambdaName); @@ -493,6 +515,24 @@ class GenerateJLIClassesHelper { } } + int[] linkerTypes = { + MethodTypeForm.LF_EX_LINKER, + MethodTypeForm.LF_GEN_LINKER, + }; + + dedupSet = new HashSet<>(); + for (MethodType methodType : linkerMethodTypes) { + // generate methods representing linkers of the specified type + if (dedupSet.add(methodType)) { + for (int type : linkerTypes) { + LambdaForm linkerForm = Invokers.invokeHandleForm(methodType, + /*customized*/false, type); + forms.add(linkerForm); + names.add(linkerForm.kind.defaultLambdaName); + } + } + } + dedupSet = new HashSet<>(); for (int i = 0; i < callSiteMethodTypes.length; i++) { // generate methods representing invokers of the specified type diff --git a/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/CDSLambdaInvoker.java b/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/CDSLambdaInvoker.java index f3485ee26a6..5d78d2bdd78 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/CDSLambdaInvoker.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/CDSLambdaInvoker.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 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 @@ -26,6 +26,9 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; +/** + * This is launched from TestLambdaInvokers. + */ public class CDSLambdaInvoker { public static void main(String args[]) throws Throwable { // The following calls trigger the generation of new Species classes @@ -44,6 +47,10 @@ public class CDSLambdaInvoker { boolean.class, Object.class, long.class, double.class); MethodHandle mh = lookup.findStatic(CDSLambdaInvoker.class, "callme", mt); mh.invokeExact(4.0f, 5.0, 6, true, (Object)args, 7L, 8.0); + + mh = MethodHandles.dropArguments(MethodHandles.zero(Object.class), 0, Object.class, int.class); + MethodHandle inv = MethodHandles.invoker(mh.type()); + invoke(inv, mh, args, 3); } private static Object invoke(MethodHandle mh, Object ... args) throws Throwable { diff --git a/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/TestLambdaInvokers.java b/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/TestLambdaInvokers.java index 9a16f55e149..3769ab97b93 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/TestLambdaInvokers.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/TestLambdaInvokers.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 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 @@ -26,7 +26,7 @@ * @test * @key randomness * @summary test archive lambda invoker species type in dynamic dump - * @bug 8280767 + * @bug 8280767 8327499 * @requires vm.cds * @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds /test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive * @compile CDSLambdaInvoker.java @@ -59,6 +59,7 @@ public class TestLambdaInvokers extends DynamicArchiveTestBase { "-Xlog:cds", "-Xlog:cds+dynamic=debug", "-Xlog:class+load", + "-Djava.lang.invoke.MethodHandle.TRACE_RESOLVE=true", "-cp", jarFile, mainClass) @@ -69,6 +70,10 @@ public class TestLambdaInvokers extends DynamicArchiveTestBase { // java.lang.invoke.BoundMethodHandle$Species_JL is generated from CDSLambdaInvoker and // stored in the dynamic archive output.shouldContain("java.lang.invoke.BoundMethodHandle$Species_JL source: shared objects file (top)"); + + // java.lang.invoke.Invokers$Holder has invoker(Object,Object,Object,int)Object available + // from the archives + output.shouldContain("[LF_RESOLVE] java.lang.invoke.Invokers$Holder invoker L3I_L (success)"); }); } diff --git a/test/jdk/tools/jlink/plugins/GenerateJLIClassesPluginTest.java b/test/jdk/tools/jlink/plugins/GenerateJLIClassesPluginTest.java index 275043ed8d8..5d06288fb59 100644 --- a/test/jdk/tools/jlink/plugins/GenerateJLIClassesPluginTest.java +++ b/test/jdk/tools/jlink/plugins/GenerateJLIClassesPluginTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 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 @@ -22,6 +22,8 @@ */ import java.io.IOException; +import java.lang.classfile.ClassFile; +import java.lang.constant.MethodTypeDesc; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; @@ -29,6 +31,7 @@ import java.util.Collection; import java.util.List; import java.util.stream.Collectors; +import org.testng.Assert; import tests.Helper; import tests.JImageGenerator; import tests.JImageValidator; @@ -37,9 +40,12 @@ import tests.Result; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; - /* +import static java.lang.constant.ConstantDescs.CD_Object; +import static java.lang.constant.ConstantDescs.CD_int; + +/* * @test - * @bug 8252919 + * @bug 8252919 8327499 * @library ../../lib * @summary Test --generate-jli-classes plugin * @enablePreview @@ -122,6 +128,47 @@ public class GenerateJLIClassesPluginTest { validateHolderClasses(image); } + @Test + public static void testInvokers() throws IOException { + var fileString = "[LF_RESOLVE] java.lang.invoke.Invokers$Holder invoker L3I_L (fail)"; + Path invokersTrace = Files.createTempFile("invokers", "trace"); + Files.writeString(invokersTrace, fileString, Charset.defaultCharset()); + Result result = JImageGenerator.getJLinkTask() + .modulePath(helper.defaultModulePath()) + .output(helper.createNewImageDir("jli-invokers")) + .option("--generate-jli-classes=@" + invokersTrace.toString()) + .addMods("java.base") + .call(); + + var image = result.assertSuccess(); + var targetMtd = MethodTypeDesc.of(CD_Object, CD_Object, CD_Object, CD_Object, CD_int); + + validateHolderClasses(image); + JImageValidator.validate(image.resolve("lib").resolve("modules"), + List.of(), List.of(), bytes -> { + var cf = ClassFile.of().parse(bytes); + if (!cf.thisClass().name().equalsString("java/lang/invoke/Invokers$Holder")) { + return; + } + + boolean found = false; + for (var m : cf.methods()) { + // LambdaForm.Kind + if (m.methodName().equalsString("invoker") && m.methodTypeSymbol().equals(targetMtd)) { + found = true; + break; + } + } + if (!found) { + var methodsInfo = cf.methods().stream() + .map(m -> m.methodName() + m.methodTypeSymbol().displayDescriptor()) + .collect(Collectors.joining("\n")); + + Assert.fail("Missing invoker L3I_L in java.lang.invoke.Invokers$Holder, found:\n" + methodsInfo); + } + }); + } + private static void validateHolderClasses(Path image) throws IOException { JImageValidator.validate(image.resolve("lib").resolve("modules"), List.of("/java.base/java/lang/invoke/DirectMethodHandle$Holder.class", diff --git a/test/jdk/tools/lib/tests/JImageValidator.java b/test/jdk/tools/lib/tests/JImageValidator.java index 125afb9d24c..3d22a558dbd 100644 --- a/test/jdk/tools/lib/tests/JImageValidator.java +++ b/test/jdk/tools/lib/tests/JImageValidator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 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 @@ -32,6 +32,7 @@ import java.util.List; import java.util.Properties; import java.lang.classfile.ClassFile; import java.lang.classfile.ClassHierarchyResolver; +import java.util.function.Consumer; import jdk.internal.jimage.BasicImageReader; import jdk.internal.jimage.ImageLocation; @@ -172,6 +173,11 @@ public class JImageValidator { public static void validate(Path jimage, List<String> expectedLocations, List<String> unexpectedPaths) throws IOException { + validate(jimage, expectedLocations, unexpectedPaths, _ -> {}); + } + + public static void validate(Path jimage, List<String> expectedLocations, + List<String> unexpectedPaths, Consumer<byte[]> classChecker) throws IOException { BasicImageReader reader = BasicImageReader.open(jimage); // Validate expected locations List<String> seenLocations = new ArrayList<>(); @@ -195,6 +201,7 @@ public class JImageValidator { throw new IOException("NULL RESOURCE " + s); } readClass(r); + classChecker.accept(r); } catch (IOException ex) { System.err.println(s + " ERROR " + ex); throw ex; @@ -222,7 +229,7 @@ public class JImageValidator { return moduleExecutionTime; } - public static void readClass(byte[] clazz) throws IOException{ + public static void readClass(byte[] clazz) throws IOException { var errors = ClassFile.of( //resolution of all classes as interfaces cancels assignability verification ClassFile.ClassHierarchyResolverOption.of(cls -> ClassHierarchyResolver.ClassHierarchyInfo.ofInterface()))