[
https://issues.apache.org/jira/browse/GROOVY-12386?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18113284#comment-18113284
]
ASF GitHub Bot commented on GROOVY-12386:
-----------------------------------------
Copilot commented on code in PR #2908:
URL: https://github.com/apache/groovy/pull/2908#discussion_r3967752455
##########
subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/Exceptions.java:
##########
@@ -31,7 +31,23 @@
*/
public class Exceptions {
- private static final System.Logger LOGGER =
System.getLogger(Exceptions.class.getName());
+ /**
+ * Holds the logger so that loading this class does not resolve
+ * {@code System.Logger}, a Java 9 API absent from some runtimes built on
+ * the JDK class library (Android's ART); where it is unavailable the
+ * exception prints its stack trace the ordinary way (GROOVY-12386).
+ */
+ private static final class Log {
+ static final System.Logger LOGGER = create();
+
+ private static System.Logger create() {
+ try {
+ return System.getLogger(Exceptions.class.getName());
+ } catch (Throwable t) {
+ return null;
+ }
+ }
+ }
Review Comment:
This change aims to avoid requiring the Java 9 logging API on runtimes like
Android, but the nested `Log` class still has hard bytecode references to
`System.Logger` (and likely `System.Logger.Level` via `ERROR`). On runtimes
where `System.Logger` is absent, loading `Exceptions$Log` will fail with a
linkage error before `create()` can run and before `catch (Throwable)` can
return `null`, so the fallback path in `printStackTrace()` won’t be reached. To
make the fallback effective, avoid direct type references by using reflection
(store the logger as `Object` and invoke `log` reflectively, including
resolving `java.lang.System$Logger$Level` by name), or isolate the
System.Logger-based implementation into a Java 9+ specific artifact (e.g.,
MR-JAR / multi-release split) and keep the base runtime free of `System.Logger`
symbols.
##########
subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/Exceptions.java:
##########
@@ -234,10 +250,14 @@ public void printStackTrace(PrintWriter s) {
*/
@Override
public void printStackTrace() {
- LOGGER.log(ERROR, this.getMessage());
+ if (Log.LOGGER == null) {
+ super.printStackTrace();
+ return;
+ }
+ Log.LOGGER.log(ERROR, this.getMessage());
if (getCause() != null) {
- LOGGER.log(ERROR, "This Exception was wrapped, the original
exception stack trace is:", getCause());
+ Log.LOGGER.log(ERROR, "This Exception was wrapped, the
original exception stack trace is:", getCause());
Review Comment:
This change aims to avoid requiring the Java 9 logging API on runtimes like
Android, but the nested `Log` class still has hard bytecode references to
`System.Logger` (and likely `System.Logger.Level` via `ERROR`). On runtimes
where `System.Logger` is absent, loading `Exceptions$Log` will fail with a
linkage error before `create()` can run and before `catch (Throwable)` can
return `null`, so the fallback path in `printStackTrace()` won’t be reached. To
make the fallback effective, avoid direct type references by using reflection
(store the logger as `Object` and invoke `log` reflectively, including
resolving `java.lang.System$Logger$Level` by name), or isolate the
System.Logger-based implementation into a Java 9+ specific artifact (e.g.,
MR-JAR / multi-release split) and keep the base runtime free of `System.Logger`
symbols.
##########
subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/Sys.java:
##########
@@ -29,7 +29,23 @@
*/
class Sys {
- private static final System.Logger LOGGER =
System.getLogger(Sys.class.getName());
+ /**
+ * Holds the logger so that loading this class does not resolve
+ * {@code System.Logger}, a Java 9 API absent from some runtimes built on
+ * the JDK class library (Android's ART); the logger is only needed on a
+ * failure path (GROOVY-12386).
+ */
+ private static final class Log {
+ static final System.Logger LOGGER = create();
+
+ private static System.Logger create() {
+ try {
+ return System.getLogger(Sys.class.getName());
+ } catch (Throwable t) {
+ return null;
+ }
+ }
+ }
Review Comment:
Catching `Throwable` here will also swallow fatal VM conditions (e.g.,
`OutOfMemoryError`, `StackOverflowError`) and turn them into a silent `null`
logger. If you keep the try/catch approach, narrow the catch to the expected
linkage failures (e.g., `LinkageError`/`NoSuchMethodError`) and optionally
`SecurityException`, or rethrow `VirtualMachineError` (and possibly
`ThreadDeath`) to avoid masking unrecoverable failures.
##########
subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/Sys.java:
##########
@@ -55,7 +71,9 @@ class Sys {
v = new BigDecimal("1.9");
}
} catch (Exception ex) {
- LOGGER.log(WARNING, "Unable to determine build number or
version", ex);
+ if (Log.LOGGER != null) {
+ Log.LOGGER.log(WARNING, "Unable to determine build number
or version", ex);
+ }
Review Comment:
Catching `Throwable` here will also swallow fatal VM conditions (e.g.,
`OutOfMemoryError`, `StackOverflowError`) and turn them into a silent `null`
logger. If you keep the try/catch approach, narrow the catch to the expected
linkage failures (e.g., `LinkageError`/`NoSuchMethodError`) and optionally
`SecurityException`, or rethrow `VirtualMachineError` (and possibly
`ThreadDeath`) to avoid masking unrecoverable failures.
##########
subprojects/groovy-json/src/test/groovy/org/apache/groovy/json/internal/LazyLoggerTest.groovy:
##########
@@ -0,0 +1,58 @@
+/*
+ * 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.apache.groovy.json.internal
+
+import org.junit.jupiter.api.Test
+
+import static org.junit.jupiter.api.Assertions.assertNotNull
+import static org.junit.jupiter.api.Assertions.assertTrue
+
+/**
+ * GROOVY-12386: the internal classes create their {@code System.Logger}
lazily,
+ * in a nested holder, so that loading them does not require the Java 9 logging
+ * API, which Android's runtime lacks.
+ */
+class LazyLoggerTest {
+
+ @Test
+ void loggersLiveInNestedHoldersNotInTheClassesThemselves() {
+ [Sys, Exceptions].each { owner ->
+ assertTrue(owner.declaredFields.every { it.type != System.Logger
}, owner.name)
+ def holder = owner.declaredClasses.find { it.simpleName == 'Log' }
+ assertNotNull(holder, owner.name)
+ def field = holder.getDeclaredField('LOGGER')
+ field.accessible = true
+ assertNotNull(field.get(null), 'the logger resolves on a JVM')
Review Comment:
Using `field.accessible = true` can fail under stronger access checks (e.g.,
`InaccessibleObjectException` in some Java 9+ module configurations). Consider
using `trySetAccessible()` (when available) and asserting it succeeded (or
skipping the assertion with an assumption) to avoid brittle test failures in
more restricted runtimes.
> groovy-json: keep System.Logger out of internal static initialisers
> -------------------------------------------------------------------
>
> Key: GROOVY-12386
> URL: https://issues.apache.org/jira/browse/GROOVY-12386
> Project: Groovy
> Issue Type: Improvement
> Reporter: Paul King
> Assignee: Paul King
> Priority: Major
>
> Involves: two internal classes, Sys and Exceptions, create a System.Logger in
> a static field. The spike switched them to java.util.logging. A gentler
> option is a lazy holder so the logger is only created when something is
> actually logged, which is an error path in both classes.
> Impact on normal usage: with the JUL switch, users who installed a custom
> System.LoggerFinder would see these two rare messages routed differently.
> With the lazy option nothing changes on a JVM, so I would prefer that.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)