[ 
https://issues.apache.org/jira/browse/GROOVY-12383?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18113281#comment-18113281
 ] 

ASF GitHub Bot commented on GROOVY-12383:
-----------------------------------------

Copilot commented on code in PR #2905:
URL: https://github.com/apache/groovy/pull/2905#discussion_r3967737736


##########
src/main/java/org/codehaus/groovy/vmplugin/v9/Java9.java:
##########
@@ -294,25 +297,43 @@ private static Set<String> exportedPackageList(final 
Module module) {
     private static final Map<String, Set<String>> CONCEALED_PACKAGES_TO_OPEN;
     private static final Map<String, Set<String>> EXPORTED_PACKAGES_TO_OPEN;
 
-    static {
-        ModuleFinder finder = ModuleFinder.ofSystem();
-        Map<String, ModuleDescriptor> packages = new HashMap<>(1024);
-        finder.findAll().stream()
-                .map(ModuleReference::descriptor)
-                .forEach(md -> md.packages().forEach(pn -> 
packages.putIfAbsent(pn, md)));
+    /**
+     * Whether the running VM has the Java Platform Module System. Runtimes 
built
+     * on the JDK class library without it (Android's ART) lack {@code Module},
+     * {@code ModuleFinder} and {@code trySetAccessible}; there the Java 8
+     * accessibility rules apply and the package tables below stay empty
+     * (GROOVY-12383).
+     */
+    private static final boolean MODULES_AVAILABLE = modulesAvailable();
+
+    static boolean modulesAvailable() {
+        try {
+            Class.class.getMethod("getModule");
+            return true;
+        } catch (Throwable t) {
+            return false;
+        }
+    }

Review Comment:
   `MODULES_AVAILABLE` is currently inferred only from `Class#getModule` 
existence, but the guarded code also depends on other JPMS APIs 
(`ModuleFinder`, and in `trySetAccessible`, 
`AccessibleObject#trySetAccessible`). If a runtime provides `getModule` but is 
missing other JPMS pieces, this can still crash with 
`NoClassDefFoundError`/`NoSuchMethodError`. Consider making 
`modulesAvailable()` validate all required symbols (e.g., `java.lang.Module`, 
`java.lang.module.ModuleFinder`, and `AccessibleObject.trySetAccessible`) to 
ensure the guard matches the APIs actually used.



##########
src/main/java/org/codehaus/groovy/vmplugin/v9/Java9.java:
##########
@@ -294,25 +297,43 @@ private static Set<String> exportedPackageList(final 
Module module) {
     private static final Map<String, Set<String>> CONCEALED_PACKAGES_TO_OPEN;
     private static final Map<String, Set<String>> EXPORTED_PACKAGES_TO_OPEN;
 
-    static {
-        ModuleFinder finder = ModuleFinder.ofSystem();
-        Map<String, ModuleDescriptor> packages = new HashMap<>(1024);
-        finder.findAll().stream()
-                .map(ModuleReference::descriptor)
-                .forEach(md -> md.packages().forEach(pn -> 
packages.putIfAbsent(pn, md)));
+    /**
+     * Whether the running VM has the Java Platform Module System. Runtimes 
built
+     * on the JDK class library without it (Android's ART) lack {@code Module},
+     * {@code ModuleFinder} and {@code trySetAccessible}; there the Java 8
+     * accessibility rules apply and the package tables below stay empty
+     * (GROOVY-12383).
+     */
+    private static final boolean MODULES_AVAILABLE = modulesAvailable();
+
+    static boolean modulesAvailable() {
+        try {
+            Class.class.getMethod("getModule");
+            return true;
+        } catch (Throwable t) {

Review Comment:
   Catching `Throwable` will also swallow serious JVM errors (e.g., 
`OutOfMemoryError`, `StackOverflowError`, `ThreadDeath`) that should generally 
propagate. Prefer catching the narrowest set of exceptions relevant to 
reflection/linkage checks (e.g., `NoSuchMethodException`, `LinkageError`, 
`SecurityException`) so unexpected VM failures aren't silently converted into 
'modules unavailable'.



##########
src/test/groovy/org/codehaus/groovy/vmplugin/v9/Java9AccessibilityTest.groovy:
##########
@@ -0,0 +1,63 @@
+/*
+ *  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.v9
+
+import org.codehaus.groovy.vmplugin.VMPlugin
+import org.codehaus.groovy.vmplugin.VMPluginFactory
+import org.junit.jupiter.api.Test
+
+import java.lang.reflect.Modifier
+
+import static org.junit.jupiter.api.Assertions.assertFalse
+import static org.junit.jupiter.api.Assertions.assertTrue
+
+/**
+ * GROOVY-12383: the module-system accessibility rules apply on a JVM, and the
+ * plugin initialises with them; where the module system is absent the Java 8
+ * rules take over, which only a runtime such as Android's ART can exercise.
+ */
+final class Java9AccessibilityTest {
+
+    private final VMPlugin plugin = VMPluginFactory.plugin
+
+    @Test
+    void moduleSystemIsDetectedOnAJvm() {
+        assertTrue(Java9.modulesAvailable())
+        assertTrue(plugin instanceof Java9)
+    }
+
+    @Test
+    void publicMemberOfAnExportedJdkPackageIsAccessible() {
+        def method = ArrayList.getMethod('size')
+        assertTrue(plugin.checkCanSetAccessible(method, 
Java9AccessibilityTest))
+        assertTrue(plugin.checkAccessible(Java9AccessibilityTest, ArrayList, 
method.modifiers, false))
+    }
+
+    @Test
+    void concealedJdkMemberIsNotAccessibleWithoutIllegalAccess() {
+        def stringCoding = Class.forName('java.lang.StringCoding')
+        assertFalse(plugin.checkAccessible(Java9AccessibilityTest, 
stringCoding, Modifier.PUBLIC | Modifier.STATIC, false))
+    }

Review Comment:
   `java.lang.StringCoding` is an internal JDK class and may not exist (or may 
be strongly encapsulated/renamed) across different Java versions, which can 
make the test fail with `ClassNotFoundException` rather than asserting 
accessibility behavior. To keep the test stable, either (a) use an API/class 
that is reliably present but non-exported, or (b) guard with a JUnit assumption 
and skip when the target class cannot be loaded.



##########
src/test/groovy/org/codehaus/groovy/vmplugin/v9/Java9AccessibilityTest.groovy:
##########
@@ -0,0 +1,63 @@
+/*
+ *  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.v9
+
+import org.codehaus.groovy.vmplugin.VMPlugin
+import org.codehaus.groovy.vmplugin.VMPluginFactory
+import org.junit.jupiter.api.Test
+
+import java.lang.reflect.Modifier
+
+import static org.junit.jupiter.api.Assertions.assertFalse
+import static org.junit.jupiter.api.Assertions.assertTrue
+
+/**
+ * GROOVY-12383: the module-system accessibility rules apply on a JVM, and the
+ * plugin initialises with them; where the module system is absent the Java 8
+ * rules take over, which only a runtime such as Android's ART can exercise.
+ */
+final class Java9AccessibilityTest {
+
+    private final VMPlugin plugin = VMPluginFactory.plugin
+
+    @Test
+    void moduleSystemIsDetectedOnAJvm() {
+        assertTrue(Java9.modulesAvailable())
+        assertTrue(plugin instanceof Java9)
+    }
+
+    @Test
+    void publicMemberOfAnExportedJdkPackageIsAccessible() {
+        def method = ArrayList.getMethod('size')
+        assertTrue(plugin.checkCanSetAccessible(method, 
Java9AccessibilityTest))
+        assertTrue(plugin.checkAccessible(Java9AccessibilityTest, ArrayList, 
method.modifiers, false))
+    }
+
+    @Test
+    void concealedJdkMemberIsNotAccessibleWithoutIllegalAccess() {
+        def stringCoding = Class.forName('java.lang.StringCoding')
+        assertFalse(plugin.checkAccessible(Java9AccessibilityTest, 
stringCoding, Modifier.PUBLIC | Modifier.STATIC, false))
+    }
+
+    @Test
+    void classConstructorIsNeverMadeAccessible() {
+        def ctor = Class.getDeclaredConstructors()[0]

Review Comment:
   `getDeclaredConstructors()[0]` is order-dependent and can be 
non-deterministic across JVMs/vendors/versions, making this test potentially 
flaky. Select a specific constructor deterministically (e.g., match by 
parameter types/count) and assert on that.



##########
src/main/java/org/codehaus/groovy/vmplugin/v9/Java9.java:
##########
@@ -294,25 +297,43 @@ private static Set<String> exportedPackageList(final 
Module module) {
     private static final Map<String, Set<String>> CONCEALED_PACKAGES_TO_OPEN;
     private static final Map<String, Set<String>> EXPORTED_PACKAGES_TO_OPEN;
 
-    static {
-        ModuleFinder finder = ModuleFinder.ofSystem();
-        Map<String, ModuleDescriptor> packages = new HashMap<>(1024);
-        finder.findAll().stream()
-                .map(ModuleReference::descriptor)
-                .forEach(md -> md.packages().forEach(pn -> 
packages.putIfAbsent(pn, md)));
+    /**
+     * Whether the running VM has the Java Platform Module System. Runtimes 
built
+     * on the JDK class library without it (Android's ART) lack {@code Module},
+     * {@code ModuleFinder} and {@code trySetAccessible}; there the Java 8
+     * accessibility rules apply and the package tables below stay empty
+     * (GROOVY-12383).
+     */
+    private static final boolean MODULES_AVAILABLE = modulesAvailable();
+
+    static boolean modulesAvailable() {
+        try {
+            Class.class.getMethod("getModule");
+            return true;
+        } catch (Throwable t) {
+            return false;
+        }
+    }
 
+    static {
         Map<String, Set<String>> concealedPackagesToOpen = new 
ConcurrentHashMap<>(64);
         Map<String, Set<String>> exportedPackagesToOpen = new 
ConcurrentHashMap<>(64);
+        if (MODULES_AVAILABLE) {
+            ModuleFinder finder = ModuleFinder.ofSystem();

Review Comment:
   `MODULES_AVAILABLE` is currently inferred only from `Class#getModule` 
existence, but the guarded code also depends on other JPMS APIs 
(`ModuleFinder`, and in `trySetAccessible`, 
`AccessibleObject#trySetAccessible`). If a runtime provides `getModule` but is 
missing other JPMS pieces, this can still crash with 
`NoClassDefFoundError`/`NoSuchMethodError`. Consider making 
`modulesAvailable()` validate all required symbols (e.g., `java.lang.Module`, 
`java.lang.module.ModuleFinder`, and `AccessibleObject.trySetAccessible`) to 
ensure the guard matches the APIs actually used.



##########
src/main/java/org/codehaus/groovy/vmplugin/v9/Java9.java:
##########
@@ -219,6 +219,7 @@ private static class BigIntegerMultiplyMethodHolder {
     /** {@inheritDoc} */
     @Override
     public boolean trySetAccessible(final AccessibleObject ao) {
+        if (!MODULES_AVAILABLE) return super.trySetAccessible(ao);
         return ao.trySetAccessible();

Review Comment:
   `MODULES_AVAILABLE` is currently inferred only from `Class#getModule` 
existence, but the guarded code also depends on other JPMS APIs 
(`ModuleFinder`, and in `trySetAccessible`, 
`AccessibleObject#trySetAccessible`). If a runtime provides `getModule` but is 
missing other JPMS pieces, this can still crash with 
`NoClassDefFoundError`/`NoSuchMethodError`. Consider making 
`modulesAvailable()` validate all required symbols (e.g., `java.lang.Module`, 
`java.lang.module.ModuleFinder`, and `AccessibleObject.trySetAccessible`) to 
ensure the guard matches the APIs actually used.





> VM plugin: fall back to Java 8 accessibility rules when the module system is 
> absent
> -----------------------------------------------------------------------------------
>
>                 Key: GROOVY-12383
>                 URL: https://issues.apache.org/jira/browse/GROOVY-12383
>             Project: Groovy
>          Issue Type: Improvement
>            Reporter: Paul King
>            Assignee: Paul King
>            Priority: Major
>
> Involves: a MODULES_AVAILABLE flag in the Java9 code, a guard around the 
> static initialiser that walks ModuleFinder, and three accessibility methods 
> delegating to the Java 8 versions when the flag is off. One catch: 
> GROOVY-11906 deprecated Java8, Java9, Java10 and Java16 for removal and left 
> only Java17 live, so this should land in whatever shape that collapse takes, 
> or it becomes churn twice.
> Impact on normal usage: none, the flag is always true on a JVM. The cost is 
> one reflective lookup at plugin initialisation.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to