You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by su...@apache.org on 2026/09/09 14:28:45 UTC

(groovy) 01/01: GROOVY-12392: NativeImageMetadataGenerator references deprecated-for-removal Java9

This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch GROOVY-12392
in repository https://gitbox.apache.org/repos/asf/groovy.git

commit e4634917f265a0e61748cee73f49699411d056db
Author: Daniel Sun <su...@apache.org>
AuthorDate: Wed Sep 9 23:14:25 2026 +0900

    GROOVY-12392: NativeImageMetadataGenerator references deprecated-for-removal Java9
    
    NativeImageMetadataGenerator referenced the deprecated-for-removal Java9
    class, which javac reported as [removal] on CI. Register those
    Class.forName targets under Java17, the VM plugin Groovy 6 constructs,
    as their own generate() family rather than as a rider on indyMachinery.
    Package-list membership in checkAccessible now uses getOrDefault so
    unknown modules are not inserted into the maps.
---
 .../groovy/tools/NativeImageMetadataGenerator.java |  22 ++++-
 .../codehaus/groovy/vmplugin/v17/package-info.java |   4 +
 .../org/codehaus/groovy/vmplugin/v9/Java9.java     |  19 +++-
 .../codehaus/groovy/vmplugin/v9/package-info.java  |   6 +-
 .../groovy/tools/NativeImageMetadataTest.groovy    |  13 +++
 .../codehaus/groovy/vmplugin/v17/Java17Test.groovy | 104 +++++++++++++++++++++
 6 files changed, 161 insertions(+), 7 deletions(-)

diff --git a/src/main/java/org/codehaus/groovy/tools/NativeImageMetadataGenerator.java b/src/main/java/org/codehaus/groovy/tools/NativeImageMetadataGenerator.java
index c0b084b44d..5843652b31 100644
--- a/src/main/java/org/codehaus/groovy/tools/NativeImageMetadataGenerator.java
+++ b/src/main/java/org/codehaus/groovy/tools/NativeImageMetadataGenerator.java
@@ -62,12 +62,12 @@ import org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl;
 import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation;
 import org.codehaus.groovy.runtime.typehandling.GroovyCastException;
 import org.codehaus.groovy.vmplugin.VMPluginFactory;
+import org.codehaus.groovy.vmplugin.v17.Java17;
 import org.codehaus.groovy.vmplugin.v8.IndyArrayAccess;
 import org.codehaus.groovy.vmplugin.v8.IndyCompoundAssign;
 import org.codehaus.groovy.vmplugin.v8.IndyGuardsFiltersAndSignatures;
 import org.codehaus.groovy.vmplugin.v8.IndyInterface;
 import org.codehaus.groovy.vmplugin.v8.IndyMath;
-import org.codehaus.groovy.vmplugin.v9.Java9;
 
 import java.io.File;
 import java.io.IOException;
@@ -118,6 +118,11 @@ import java.util.concurrent.Executors;
  * Its classes obtain method handles to their own methods and to a few JDK and
  * MOP methods through a {@code Lookup} held in a run-time-initialised class,
  * which the image builder cannot fold, so the targets are registered.</li>
+ * <li><b>Default-import class discovery</b> (conditional on {@link Java17}).
+ * {@code getDefaultImportClasses} loads {@code GroovySystem} and
+ * {@code groovy.beans.ListenerList} by name to locate the groovy jar; those
+ * names are not constant {@code Class.forName} arguments the image builder
+ * can fold.</li>
  * <li><b>Groovy-owned types that get a metaclass</b> (conditional on the
  * registry too, since a metaclass can be created for a class before the class
  * initialises). {@code CachedClass} reads their declared constructors, methods
@@ -392,6 +397,7 @@ public class NativeImageMetadataGenerator {
         NativeImageMetadataGenerator generator = new NativeImageMetadataGenerator();
         generator.registryBootstrap(records);
         generator.indyMachinery();
+        generator.defaultImportClassLookups();
         generator.asyncRuntime();
         generator.introspectedTypes(records);
         return generator.toJson();
@@ -512,9 +518,17 @@ public class NativeImageMetadataGenerator {
         String reflectionUtils = ReflectionUtils.class.getName();
         method(reflectionUtils, Class.class, "isSealed");
         method(reflectionUtils, Class.class, "getPermittedSubclasses");
-        // the Java 9 plugin locates the groovy jar by loading two of its classes by name
-        addName(Java9.class.getName(), GroovySystem.class.getName(), NONE);
-        addName(Java9.class.getName(), "groovy.beans.ListenerList", NONE); // Groovy source, not visible here
+    }
+
+    /**
+     * {@code getDefaultImportClasses} locates the groovy jar by loading two of
+     * its classes by name. Condition on {@link Java17}, not a deprecated Java 9
+     * ancestor.
+     */
+    private void defaultImportClassLookups() {
+        String vmPlugin = Java17.class.getName();
+        addName(vmPlugin, GroovySystem.class.getName(), NONE);
+        addName(vmPlugin, "groovy.beans.ListenerList", NONE); // Groovy source, not visible here
     }
 
     private void introspectedTypes(List<GeneratedMetaMethod.DgmMethodRecord> records) {
diff --git a/src/main/java/org/codehaus/groovy/vmplugin/v17/package-info.java b/src/main/java/org/codehaus/groovy/vmplugin/v17/package-info.java
index ee21d04433..9e84ced70f 100644
--- a/src/main/java/org/codehaus/groovy/vmplugin/v17/package-info.java
+++ b/src/main/java/org/codehaus/groovy/vmplugin/v17/package-info.java
@@ -19,5 +19,9 @@
 
 /**
  * Java 17 VM plugin. Compatibility layer for Java 17 LTS features.
+ * <p>
+ * This is the {@link org.codehaus.groovy.vmplugin.VMPluginFactory} entry point
+ * for Groovy 6 (JDK 17+). JPMS accessibility and default-import discovery are
+ * inherited from {@code org.codehaus.groovy.vmplugin.v9}.
  */
 package org.codehaus.groovy.vmplugin.v17;
diff --git a/src/main/java/org/codehaus/groovy/vmplugin/v9/Java9.java b/src/main/java/org/codehaus/groovy/vmplugin/v9/Java9.java
index e12de46762..3f63056716 100644
--- a/src/main/java/org/codehaus/groovy/vmplugin/v9/Java9.java
+++ b/src/main/java/org/codehaus/groovy/vmplugin/v9/Java9.java
@@ -55,6 +55,13 @@ import java.util.logging.Logger;
 import java.util.stream.Collectors;
 
 /**
+ * Java 9+ JPMS support: module-aware accessibility checks,
+ * {@code MethodHandles.privateLookupIn}, and default-import class discovery
+ * via the {@code jrt:} filesystem.
+ * <p>
+ * Groovy 6 selects {@link Java17} as the {@link org.codehaus.groovy.vmplugin.VMPluginFactory}
+ * entry point; this class remains the JPMS implementation that {@link Java17} inherits.
+ *
  * @deprecated Use {@link Java17} instead. Groovy 6.0 requires JDK 17+.
  */
 @Deprecated(since = "6.0.0", forRemoval = true)
@@ -283,12 +290,20 @@ public class Java9 extends Java8 {
         return false;
     }
 
+    /**
+     * JDK 8 packages in {@code module} that are now concealed.
+     * Unknown modules are not inserted into the map: callers only test membership.
+     */
     private static Set<String> concealedPackageList(final Module module) {
-        return CONCEALED_PACKAGES_TO_OPEN.computeIfAbsent(module.getName(), m -> new HashSet<>());
+        return CONCEALED_PACKAGES_TO_OPEN.getOrDefault(module.getName(), Set.of());
     }
 
+    /**
+     * JDK 8 packages in {@code module} that are exported but not open.
+     * Unknown modules are not inserted into the map: callers only test membership.
+     */
     private static Set<String> exportedPackageList(final Module module) {
-        return EXPORTED_PACKAGES_TO_OPEN.computeIfAbsent(module.getName(), m -> new HashSet<>());
+        return EXPORTED_PACKAGES_TO_OPEN.getOrDefault(module.getName(), Set.of());
     }
 
     private static final Map<String, Set<String>> CONCEALED_PACKAGES_TO_OPEN;
diff --git a/src/main/java/org/codehaus/groovy/vmplugin/v9/package-info.java b/src/main/java/org/codehaus/groovy/vmplugin/v9/package-info.java
index e859199bf3..9a1684d089 100644
--- a/src/main/java/org/codehaus/groovy/vmplugin/v9/package-info.java
+++ b/src/main/java/org/codehaus/groovy/vmplugin/v9/package-info.java
@@ -18,6 +18,10 @@
  */
 
 /**
- * Java 9 VM plugin. Compatibility layer for Java 9 module system.
+ * Java 9 VM plugin. Compatibility layer for the Java 9 module system (JPMS).
+ * <p>
+ * Groovy 6 requires JDK 17+ and selects {@link org.codehaus.groovy.vmplugin.v17.Java17}
+ * as the {@link org.codehaus.groovy.vmplugin.VMPluginFactory} entry point; types in this
+ * package remain the inherited JPMS implementation and are not constructed directly.
  */
 package org.codehaus.groovy.vmplugin.v9;
diff --git a/src/test/groovy/org/codehaus/groovy/tools/NativeImageMetadataTest.groovy b/src/test/groovy/org/codehaus/groovy/tools/NativeImageMetadataTest.groovy
index cfb4081ee1..aebf2f7cad 100644
--- a/src/test/groovy/org/codehaus/groovy/tools/NativeImageMetadataTest.groovy
+++ b/src/test/groovy/org/codehaus/groovy/tools/NativeImageMetadataTest.groovy
@@ -25,6 +25,7 @@ import org.codehaus.groovy.reflection.GeneratedMetaMethod
 import org.codehaus.groovy.runtime.DefaultGroovyMethods
 import org.codehaus.groovy.runtime.DefaultGroovyStaticMethods
 import org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl
+import org.codehaus.groovy.vmplugin.VMPluginFactory
 import org.codehaus.groovy.vmplugin.v8.IndyInterface
 import org.junit.jupiter.api.Test
 
@@ -167,6 +168,18 @@ final class NativeImageMetadataTest {
         }
     }
 
+    @Test
+    void vmPluginLocatesTheGroovyJarByLoadingTwoOfItsClasses() {
+        // getDefaultImportClasses loads these by name to find the groovy jar;
+        // the condition must be the live plugin, not a deprecated ancestor.
+        String plugin = VMPluginFactory.plugin.class.name
+        ['groovy.lang.GroovySystem', 'groovy.beans.ListenerList'].each { name ->
+            def hits = reflection().findAll { it.type == name && it.condition.typeReached == plugin }
+            assert hits.size() == 1 : "$name under $plugin: $hits"
+            assert hits[0].keySet() == ['condition', 'type'] as Set : "name-only load: ${hits[0]}"
+        }
+    }
+
     @Test
     void asyncRuntimeLookupsAreRegisteredByName() {
         def executors = reflection().findAll { it.type == 'java.util.concurrent.Executors' }
diff --git a/src/test/groovy/org/codehaus/groovy/vmplugin/v17/Java17Test.groovy b/src/test/groovy/org/codehaus/groovy/vmplugin/v17/Java17Test.groovy
new file mode 100644
index 0000000000..6ac2744700
--- /dev/null
+++ b/src/test/groovy/org/codehaus/groovy/vmplugin/v17/Java17Test.groovy
@@ -0,0 +1,104 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 org.codehaus.groovy.vmplugin.v17
+
+import org.codehaus.groovy.vmplugin.VMPlugin
+import org.codehaus.groovy.vmplugin.VMPluginFactory
+import org.junit.jupiter.api.Test
+
+import java.lang.reflect.Modifier
+import java.net.http.HttpClient
+
+import static org.junit.jupiter.api.Assumptions.assumeTrue
+
+/**
+ * Membership lookups used by {@code checkAccessible}: JDK 8 packages that are
+ * now concealed or exported-but-not-open, and modules absent from those maps.
+ */
+final class Java17Test {
+
+    private static final VMPlugin PLUGIN = VMPluginFactory.plugin
+
+    @Test
+    void publicExportedJdkTypeIsAccessible() {
+        [false, true].each { allow ->
+            assert PLUGIN.checkAccessible(Java17Test, String, Modifier.PUBLIC, allow) : allow
+        }
+    }
+
+    @Test
+    void publicTypeInPostJava8ModuleIsAccessible() {
+        // java.net.http is not a JDK 8 package, so the module is absent from
+        // the concealed/exported-to-open maps; membership must still succeed.
+        [false, true].each { allow ->
+            assert PLUGIN.checkAccessible(Java17Test, HttpClient, Modifier.PUBLIC, allow) : allow
+        }
+    }
+
+    @Test
+    void concealedJdkPackageIsNotAccessible() {
+        Class type = concealedJdkType()
+        assumeTrue(type != null, 'no non-exported JDK class visible on this runtime')
+        [false, true].each { allow ->
+            assert !PLUGIN.checkAccessible(Java17Test, type, Modifier.PUBLIC, allow) : "$type $allow"
+            assert !PLUGIN.checkAccessible(Java17Test, type, Modifier.PRIVATE, allow) : "$type $allow"
+        }
+    }
+
+    @Test
+    void privateMemberOfDescriptorOpenedJdkPackageIsAccessible() {
+        // jdk.unsupported opens sun.misc / sun.reflect to all modules; those
+        // packages are omitted from the Java 8 exported-to-open list, so the
+        // unnamed module may read private members even without allowIllegalAccess.
+        Class type = descriptorOpenedJdkType()
+        assumeTrue(type != null, 'no descriptor-open JDK type visible on this runtime')
+        [false, true].each { allow ->
+            assert PLUGIN.checkAccessible(Java17Test, type, Modifier.PUBLIC, allow) : "$type $allow"
+            assert PLUGIN.checkAccessible(Java17Test, type, Modifier.PRIVATE, allow) : "$type $allow"
+        }
+    }
+
+    /** First JDK class whose package is not exported to this (unnamed) module. */
+    private static Class concealedJdkType() {
+        for (String name : ['jdk.internal.misc.Unsafe', 'jdk.internal.access.SharedSecrets', 'sun.misc.Unsafe']) {
+            try {
+                Class c = Class.forName(name)
+                if (!c.module.isExported(c.packageName, Java17Test.module)) return c
+            } catch (ClassNotFoundException ignored) {
+            }
+        }
+        null
+    }
+
+    /** First public JDK class whose package is open to this module by the module descriptor. */
+    private static Class descriptorOpenedJdkType() {
+        for (String name : ['sun.misc.Unsafe', 'sun.reflect.ReflectionFactory']) {
+            try {
+                Class c = Class.forName(name)
+                if (c.module.isNamed()
+                        && c.module.isExported(c.packageName, Java17Test.module)
+                        && c.module.isOpen(c.packageName, Java17Test.module)) {
+                    return c
+                }
+            } catch (ClassNotFoundException ignored) {
+            }
+        }
+        null
+    }
+}