diff --git a/apps/jdkreflections/pom.xml b/apps/jdkreflections/pom.xml
new file mode 100644
index 0000000..e3709dd
--- /dev/null
+++ b/apps/jdkreflections/pom.xml
@@ -0,0 +1,39 @@
+
+
+ 4.0.0
+ jdkreflections
+ jdkreflections
+ 1
+
+ jdkreflections
+
+
+ org.graalvm.tests.integration
+ parent
+ 1.0.0-SNAPSHOT
+ ../../pom.xml
+
+
+
+ 3.2.0
+
+
+
+ jdkreflections
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ ${maven-jar-plugin.version}
+
+
+
+ jdkreflections.Main
+
+
+
+
+
+
+
diff --git a/apps/jdkreflections/src/main/java/jdkreflections/Main.java b/apps/jdkreflections/src/main/java/jdkreflections/Main.java
new file mode 100644
index 0000000..f05292a
--- /dev/null
+++ b/apps/jdkreflections/src/main/java/jdkreflections/Main.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2024, Red Hat Inc. All rights reserved.
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package jdkreflections;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+
+public class Main {
+
+ /**
+ * An intentionally elaborate way to do this to test the reflection support
+ * on java.lang.Thread for native-image build configuration.
+ * @param name
+ * @return
+ */
+ static ExecutorService createVirtualThreadExecutor(String name) {
+ try {
+ final Method virtualThreadBuilderMethod = Arrays.stream(Thread.class.getMethods())
+ .filter(m -> m.getName().equals("ofVirtual"))
+ .findAny().orElseThrow();
+ Object virtualThreadBuilder = virtualThreadBuilderMethod.invoke(null);
+ final Method nameVirtualThreadBuilderMethod = Arrays.stream(virtualThreadBuilderMethod.getReturnType().getMethods())
+ .filter(m -> m.getName().equals("name") && m.getParameterCount() == 2)
+ .findAny().orElseThrow();
+ virtualThreadBuilder = nameVirtualThreadBuilderMethod.invoke(virtualThreadBuilder, name, 10000L);
+ final Method factoryVirtualThreadBuilderMethod = Arrays.stream(Class.forName("java.lang.Thread$Builder").getMethods())
+ .filter(m -> m.getName().equals("factory"))
+ .findAny().orElseThrow();
+ final ThreadFactory factory = (ThreadFactory) factoryVirtualThreadBuilderMethod.invoke(virtualThreadBuilder);
+ final Method newThreadPerTaskExecutorMethod = Arrays.stream(Executors.class.getMethods())
+ .filter(m -> m.getName().equals("newThreadPerTaskExecutor") && m.getGenericParameterTypes()[0].equals(ThreadFactory.class))
+ .findAny().orElseThrow();
+ return (ExecutorService) newThreadPerTaskExecutorMethod.invoke(null, factory);
+ } catch (ReflectiveOperationException e) {
+ throw new IllegalStateException("Fail :-)", e);
+ }
+ }
+
+ public static void main(String[] args) throws InterruptedException {
+ final ExecutorService executor = createVirtualThreadExecutor("meh-");
+ executor.submit(() -> {
+ try {
+ final Method currentThreadMethod = Thread.class.getDeclaredMethod("currentThread");
+ final Thread currentThread = (Thread) currentThreadMethod.invoke(null);
+ final Method isVirtualMethod = Thread.class.getDeclaredMethod("isVirtual");
+ final boolean isVirtual = (boolean) isVirtualMethod.invoke(currentThread);
+ System.out.println("Hello from a " + (isVirtual ? "virtual" : "") + " thread called " + currentThread.getName());
+ final Field interruptedField = Thread.class.getDeclaredField("interrupted");
+ interruptedField.setAccessible(true);
+ System.out.println("interrupted: " + interruptedField.getBoolean(currentThread));
+ final Method holdsLockMethod = Arrays.stream(Thread.class.getMethods())
+ .filter(m -> m.getName().equals("holdsLock") && m.getGenericParameterTypes()[0].equals(Object.class))
+ .findAny().orElseThrow();
+ System.out.println("holdsLock: " + ((boolean) holdsLockMethod.invoke(null, new Object())));
+ final Method threadIdMethod = Thread.class.getDeclaredMethod("threadId");
+ System.out.println("getId: " + ((long) threadIdMethod.invoke(currentThread)));
+ final Method getNextThreadIdOffsetMethod = Thread.class.getDeclaredMethod("getNextThreadIdOffset");
+ getNextThreadIdOffsetMethod.setAccessible(true);
+ System.out.println("getNextThreadIdOffset: " + (long) getNextThreadIdOffsetMethod.invoke(null));
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ });
+ executor.shutdown();
+ executor.awaitTermination(1, java.util.concurrent.TimeUnit.SECONDS);
+ }
+}
diff --git a/pom.xml b/pom.xml
index 2e0fa88..2fb1045 100755
--- a/pom.xml
+++ b/pom.xml
@@ -89,6 +89,7 @@
apps/debug-symbols-smoke
apps/helidon-quickstart-se
apps/imageio
+ apps/jdkreflections
apps/jfr-native-image-performance
apps/quarkus-full-microprofile
apps/quarkus-json
diff --git a/testsuite/src/it/java/org/graalvm/tests/integration/AppReproducersTest.java b/testsuite/src/it/java/org/graalvm/tests/integration/AppReproducersTest.java
index 5144995..72b1b6b 100644
--- a/testsuite/src/it/java/org/graalvm/tests/integration/AppReproducersTest.java
+++ b/testsuite/src/it/java/org/graalvm/tests/integration/AppReproducersTest.java
@@ -858,6 +858,63 @@ public void monitorFieldOffsetNOK(TestInfo testInfo, Apps app) throws IOExceptio
}
}
+ @Test
+ @Tag("builder-image")
+ @IfMandrelVersion(minJDK = "21.0.3", inContainer = true)
+ public void jdkReflectionsContainerTest(TestInfo testInfo) throws IOException, InterruptedException {
+ jdkReflections(testInfo, Apps.JDK_REFLECTIONS_BUILDER_IMAGE);
+ }
+
+ @Test
+ @IfMandrelVersion(minJDK = "21.0.3")
+ public void jdkReflectionsTest(TestInfo testInfo) throws IOException, InterruptedException {
+ jdkReflections(testInfo, Apps.JDK_REFLECTIONS);
+ }
+
+ public void jdkReflections(TestInfo testInfo, Apps app) throws IOException, InterruptedException {
+ LOGGER.info("Testing app: " + app);
+ Process process = null;
+ File buildLog = null;
+ File runLog = null;
+ final StringBuilder report = new StringBuilder();
+ final File appDir = Path.of(BASE_DIR, app.dir).toFile();
+ final String cn = testInfo.getTestClass().get().getCanonicalName();
+ final String mn = testInfo.getTestMethod().get().getName();
+ final boolean inContainer = app.runtimeContainer != ContainerNames.NONE;
+ try {
+ // Cleanup
+ cleanTarget(app);
+ if (inContainer) {
+ removeContainers(app.runtimeContainer.name);
+ }
+ Files.createDirectories(Paths.get(appDir.getAbsolutePath() + File.separator + "logs"));
+ buildLog = Path.of(appDir.getAbsolutePath(), "logs", "build.log").toFile();
+ runLog = Path.of(appDir.getAbsolutePath(), "logs", "run.log").toFile();
+ builderRoutine(app, report, cn, mn, appDir, buildLog);
+ LOGGER.info("Running...");
+ final List cmd = getRunCommand(app.buildAndRunCmds.runCommands[0]);
+ process = runCommand(cmd, appDir, runLog, app);
+ assertNotNull(process, "The test application failed to run. Check " + getLogsDir(cn, mn) + File.separator + buildLog.getName() +
+ " and also check https://github.com/graalvm/graalvm-community-jdk21u/issues/28");
+ process.waitFor(5, TimeUnit.SECONDS);
+ Logs.appendln(report, appDir.getAbsolutePath());
+ Logs.appendlnSection(report, String.join(" ", cmd));
+ Logs.checkLog(cn, mn, app, buildLog);
+ Logs.checkLog(cn, mn, app, runLog);
+ processStopper(process, true);
+ final Pattern p = Pattern.compile(".*Hello from a virtual thread called meh-10000.*");
+ assertTrue(searchLogLines(p, runLog, Charset.defaultCharset()),
+ "Expected pattern " + p + " was not found in the log. Check " + getLogsDir(cn, mn) + File.separator + runLog.getName() +
+ " and also check https://github.com/graalvm/graalvm-community-jdk21u/issues/28");
+ final Pattern p1 = Pattern.compile(".*java.lang.NoSuchMethodException: java.lang.Thread.getNextThreadIdOffset.*");
+ assertTrue(searchLogLines(p, runLog, Charset.defaultCharset()),
+ "Expected pattern " + p1 + " was not found in the log. Check " + getLogsDir(cn, mn) + File.separator + runLog.getName() +
+ ". The method getNextThreadIdOffset is deleted from native-image intentionally.");
+ } finally {
+ cleanup(process, cn, mn, report, app, buildLog, runLog);
+ }
+ }
+
@Test
@Tag("builder-image")
@IfMandrelVersion(min = "23.1.5", max = "23.1.999", inContainer = true)
diff --git a/testsuite/src/it/java/org/graalvm/tests/integration/utils/Apps.java b/testsuite/src/it/java/org/graalvm/tests/integration/utils/Apps.java
index 87f40c5..1b3acde 100755
--- a/testsuite/src/it/java/org/graalvm/tests/integration/utils/Apps.java
+++ b/testsuite/src/it/java/org/graalvm/tests/integration/utils/Apps.java
@@ -203,7 +203,17 @@ public enum Apps {
URLContent.NONE,
WhitelistLogLines.FOR_SERIALIZATION,
BuildAndRunCmds.FOR_SERIALIZATION_BUILDER_IMAGE,
- ContainerNames.FOR_SERIALIZATION_BUILDER_IMAGE);
+ ContainerNames.FOR_SERIALIZATION_BUILDER_IMAGE),
+ JDK_REFLECTIONS("apps" + File.separator + "jdkreflections",
+ URLContent.NONE,
+ WhitelistLogLines.JDK_REFLECTIONS,
+ BuildAndRunCmds.JDK_REFLECTIONS,
+ ContainerNames.NONE),
+ JDK_REFLECTIONS_BUILDER_IMAGE("apps" + File.separator + "jdkreflections",
+ URLContent.NONE,
+ WhitelistLogLines.JDK_REFLECTIONS,
+ BuildAndRunCmds.JDK_REFLECTIONS_BUILDER_IMAGE,
+ ContainerNames.JDK_REFLECTIONS_BUILDER_IMAGE);
public final String dir;
public final URLContent urlContent;
diff --git a/testsuite/src/it/java/org/graalvm/tests/integration/utils/BuildAndRunCmds.java b/testsuite/src/it/java/org/graalvm/tests/integration/utils/BuildAndRunCmds.java
index 2e25a86..5c1e4d9 100755
--- a/testsuite/src/it/java/org/graalvm/tests/integration/utils/BuildAndRunCmds.java
+++ b/testsuite/src/it/java/org/graalvm/tests/integration/utils/BuildAndRunCmds.java
@@ -226,6 +226,32 @@ public enum BuildAndRunCmds {
new String[][] {
{ IS_THIS_WINDOWS ? "target\\timezones.exe" : "./target/timezones" } }
),
+ JDK_REFLECTIONS(
+ new String[][] {
+ { "mvn", "package" },
+ { "java", "--add-opens=java.base/java.lang=ALL-UNNAMED", "-agentlib:native-image-agent=config-output-dir=./target/AGENT",
+ "-cp", "target/jdkreflections.jar", "jdkreflections.Main" },
+ { "native-image", "-J--add-opens=java.base/java.lang=ALL-UNNAMED", "-H:ConfigurationFileDirectories=./target/AGENT",
+ "--link-at-build-time=", "--no-fallback", "-march=native", "-jar", "target/jdkreflections.jar", "target/jdkreflections" }
+ },
+ new String[][] {
+ { IS_THIS_WINDOWS ? "target\\jdkreflections.exe" : "./target/jdkreflections" } }
+ ),
+ JDK_REFLECTIONS_BUILDER_IMAGE(
+ new String[][] {
+ { "mvn", "package" },
+ { CONTAINER_RUNTIME, "run", IS_THIS_WINDOWS ? "" : "-u", IS_THIS_WINDOWS ? "" : getUnixUIDGID(),
+ "-t", "--entrypoint", "java", "-v", BASE_DIR + File.separator + "apps" + File.separator + "jdkreflections:/project:z",
+ BUILDER_IMAGE, "--add-opens=java.base/java.lang=ALL-UNNAMED",
+ "-agentlib:native-image-agent=config-output-dir=./target/AGENT", "-cp", "target/jdkreflections.jar", "jdkreflections.Main" },
+ { CONTAINER_RUNTIME, "run", IS_THIS_WINDOWS ? "" : "-u", IS_THIS_WINDOWS ? "" : getUnixUIDGID(),
+ "-v", BASE_DIR + File.separator + "apps" + File.separator + "jdkreflections:/project:z",
+ BUILDER_IMAGE, "-J--add-opens=java.base/java.lang=ALL-UNNAMED",
+ "-H:ConfigurationFileDirectories=./target/AGENT", "--link-at-build-time=", "--no-fallback", "-march=native",
+ "-jar", "target/jdkreflections.jar", "target/jdkreflections" } },
+ new String[][] {
+ { IS_THIS_WINDOWS ? "target\\jdkreflections.exe" : "./target/jdkreflections" } }
+ ),
CALENDARS(
new String[][] {
{ "mvn", "package" },
@@ -525,7 +551,7 @@ private static String[] hyperfoil() {
}
}
- final String[][] buildCommands;
+ public final String[][] buildCommands;
public final String[][] runCommands;
BuildAndRunCmds(String[][] buildCommands, String[][] runCommands)
diff --git a/testsuite/src/it/java/org/graalvm/tests/integration/utils/Commands.java b/testsuite/src/it/java/org/graalvm/tests/integration/utils/Commands.java
index 3cfc766..40a0d3e 100644
--- a/testsuite/src/it/java/org/graalvm/tests/integration/utils/Commands.java
+++ b/testsuite/src/it/java/org/graalvm/tests/integration/utils/Commands.java
@@ -293,7 +293,7 @@ public static Process runCommand(List command, File directory, File logF
if (logFile != null) {
final String c = "Command: " + String.join(" ", command) + "\n";
LOGGER.infof("Command: %s", command);
- Files.write(logFile.toPath(), c.getBytes(StandardCharsets.UTF_8), StandardOpenOption.APPEND);
+ Files.write(logFile.toPath(), c.getBytes(StandardCharsets.UTF_8), StandardOpenOption.APPEND, StandardOpenOption.CREATE);
processBuilder.redirectOutput(ProcessBuilder.Redirect.appendTo(logFile));
}
if (input != null) {
diff --git a/testsuite/src/it/java/org/graalvm/tests/integration/utils/ContainerNames.java b/testsuite/src/it/java/org/graalvm/tests/integration/utils/ContainerNames.java
index 27b5bc4..a7a2bb2 100755
--- a/testsuite/src/it/java/org/graalvm/tests/integration/utils/ContainerNames.java
+++ b/testsuite/src/it/java/org/graalvm/tests/integration/utils/ContainerNames.java
@@ -34,6 +34,7 @@ public enum ContainerNames {
HYPERFOIL("hyperfoil-container"),
MONITOR_OFFSET_BUILDER_IMAGE("my-monitor-offset-runner"),
FOR_SERIALIZATION_BUILDER_IMAGE("my-for-serialization-runner"),
+ JDK_REFLECTIONS_BUILDER_IMAGE("my-jdkreflections-runner"),
NONE("NO_CONTAINER");
public final String name;
diff --git a/testsuite/src/it/java/org/graalvm/tests/integration/utils/WhitelistLogLines.java b/testsuite/src/it/java/org/graalvm/tests/integration/utils/WhitelistLogLines.java
index c881e0b..a1b9267 100755
--- a/testsuite/src/it/java/org/graalvm/tests/integration/utils/WhitelistLogLines.java
+++ b/testsuite/src/it/java/org/graalvm/tests/integration/utils/WhitelistLogLines.java
@@ -362,6 +362,13 @@ public Pattern[] get(boolean inContainer) {
Pattern.compile(".*sun.reflect.ReflectionFactory is internal proprietary API.*")
};
}
+ },
+ JDK_REFLECTIONS {
+ @Override
+ public Pattern[] get(boolean inContainer) {
+ return new Pattern[]{
+ };
+ }
};
public abstract Pattern[] get(boolean inContainer);