[
https://issues.apache.org/jira/browse/GROOVY-12461?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18131061#comment-18131061
]
ASF GitHub Bot commented on GROOVY-12461:
-----------------------------------------
Copilot commented on code in PR #2995:
URL: https://github.com/apache/groovy/pull/2995#discussion_r4362256802
##########
src/main/java/groovy/grape/GrabAnnotationTransformation.java:
##########
@@ -486,8 +486,10 @@ private void callGrabAsStaticInitIfNeeded(final ClassNode
classNode, final Class
for (Map.Entry<String, String> e : systemProperties.entrySet()) {
block.addStatement(stmt(callX(SYSTEM_CLASSNODE, "setProperty",
args(constX(e.getKey()), constX(e.getValue())))));
}
- StaticMethodCallExpression enabled = callX(SYSTEM_CLASSNODE,
"getProperty", args(constX("groovy.grape.enable"), constX("true")));
- grabInitializers.add(ifS(eqX(enabled, constX("true")), block));
+ // only set the properties when the grab they support will
actually run
+ Expression grabAvailable = andX(callX(grapeClassNode,
"getEnableGrapes"),
+ notNullX(callX(grapeClassNode,
"getInstance")));
+ grabInitializers.add(ifS(grabAvailable, block));
Review Comment:
The generated call to `getEnableGrapes` assumes that method exists on
`Grape`. If `enableGrapes` is a boolean property following standard JavaBeans
conventions, the getter may be `isEnableGrapes()` instead, which would cause a
runtime `MissingMethodException` in compiled scripts. Prefer generating a
property access for `enableGrapes` (so Groovy resolves `is*` vs `get*`
correctly), or update the call to match the actual `Grape` API method name.
##########
src/test/groovy/bugs/Groovy12461.groovy:
##########
@@ -0,0 +1,92 @@
+/*
+ * 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 bugs
+
+import groovy.grape.Grape
+import groovy.grape.GrapeEngine
+import org.junit.jupiter.api.AfterEach
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+
+/**
+ * The system properties of {@code @GrabConfig} are only set when the grab
runs.
+ */
+final class Groovy12461 {
+
+ private static final String PROBE = 'groovy.test.grabconfig.probe'
+
+ private static final String SCRIPT = """
+ @GrabConfig(systemProperties = '$PROBE=set-by-grabconfig')
+ @Grab('org.example:not-fetched:1.0')
+ import java.util.List
+ System.getProperty('$PROBE')
+ """
+
+ private GrapeEngine savedInstance
+ private boolean savedEnableGrapes
+ private String savedImpl
+
+ @BeforeEach
+ void setUp() {
+ savedInstance = Grape.instance
+ savedEnableGrapes = Grape.enableGrapes
+ savedImpl = System.getProperty('groovy.grape.impl')
+ System.clearProperty(PROBE)
+ }
+
+ @AfterEach
+ void tearDown() {
+ Grape.instance = savedInstance
+ Grape.enableGrapes = savedEnableGrapes
+ if (savedImpl == null) System.clearProperty('groovy.grape.impl')
+ else System.setProperty('groovy.grape.impl', savedImpl)
+ System.clearProperty(PROBE)
+ }
+
+ @Test
+ void testPropertiesSetWhenGrabRuns() {
+ Grape.instance = new NoOpGrapeEngine()
+ assert new GroovyShell().evaluate(SCRIPT) == 'set-by-grabconfig'
+ }
+
+ @Test
+ void testPropertiesNotSetWhenGrapesDisabled() {
+ Grape.instance = new NoOpGrapeEngine()
+ Grape.enableGrapes = false
+ assert new GroovyShell().evaluate(SCRIPT) == null
+ }
+
+ @Test
+ void testPropertiesNotSetWhenNoEngineAvailable() {
+ Grape.instance = null
+ System.setProperty('groovy.grape.impl',
'org.example.NoSuchGrapeEngine')
+ assert new GroovyShell().evaluate(SCRIPT) == null
+ }
+
+ private static class NoOpGrapeEngine implements GrapeEngine {
+ Object grab(String endorsedModule) { null }
+ Object grab(Map args) { null }
+ Object grab(Map args, Map... dependencies) { null }
+ Map<String, Map<String, List<String>>> enumerateGrapes() { [:] }
+ URI[] resolve(Map args, Map... dependencies) { new URI[0] }
+ URI[] resolve(Map args, List depsInfo, Map... dependencies) { new
URI[0] }
+ Map[] listDependencies(ClassLoader classLoader) { new Map[0] }
Review Comment:
Consider adding `@Override` annotations to these `GrapeEngine` method
implementations. It’s a low-cost way to ensure the test double stays in sync
with the interface (e.g., if method signatures change) and helps the compiler
catch accidental mismatches.
##########
src/test/groovy/bugs/Groovy12461.groovy:
##########
@@ -0,0 +1,92 @@
+/*
+ * 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 bugs
+
+import groovy.grape.Grape
+import groovy.grape.GrapeEngine
+import org.junit.jupiter.api.AfterEach
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+
+/**
+ * The system properties of {@code @GrabConfig} are only set when the grab
runs.
+ */
+final class Groovy12461 {
+
+ private static final String PROBE = 'groovy.test.grabconfig.probe'
+
+ private static final String SCRIPT = """
+ @GrabConfig(systemProperties = '$PROBE=set-by-grabconfig')
+ @Grab('org.example:not-fetched:1.0')
+ import java.util.List
+ System.getProperty('$PROBE')
+ """
+
+ private GrapeEngine savedInstance
+ private boolean savedEnableGrapes
+ private String savedImpl
+
+ @BeforeEach
+ void setUp() {
+ savedInstance = Grape.instance
+ savedEnableGrapes = Grape.enableGrapes
+ savedImpl = System.getProperty('groovy.grape.impl')
+ System.clearProperty(PROBE)
+ }
+
+ @AfterEach
+ void tearDown() {
+ Grape.instance = savedInstance
+ Grape.enableGrapes = savedEnableGrapes
+ if (savedImpl == null) System.clearProperty('groovy.grape.impl')
+ else System.setProperty('groovy.grape.impl', savedImpl)
+ System.clearProperty(PROBE)
+ }
Review Comment:
These tests mutate global JVM state (`Grape.instance`, `Grape.enableGrapes`,
and system properties). If the build enables JUnit 5 parallel execution, this
can become flaky due to cross-test interference. Consider adding JUnit 5
resource locks (e.g., locking `Resources.SYSTEM_PROPERTIES` and a custom lock
for `groovy.grape.Grape`) or otherwise marking the class to run in the same
thread to ensure isolation.
> @GrabConfig(systemProperties = ...) sets JVM system properties at runtime
> even when Grape is disabled or unavailable
> --------------------------------------------------------------------------------------------------------------------
>
> Key: GROOVY-12461
> URL: https://issues.apache.org/jira/browse/GROOVY-12461
> Project: Groovy
> Issue Type: Improvement
> Affects Versions: 6.0.0
> Reporter: Paul King
> Assignee: Paul King
> Priority: Major
>
> h2. Problem
> When a class or script uses {{@Grab}} together with
> {{@GrabConfig(systemProperties = ...)}}, {{GrabAnnotationTransformation}}
> (line 490 in 6.0.0) adds a static initializer that does two things in order:
> # sets each listed system property, guarded by
> {{System.getProperty("groovy.grape.enable", "true") == "true"}};
> # calls {{Grape.grab(...)}}.
> The {{@GrabConfig}} javadoc says the properties "must be set before invoking
> the grab", for example SSL or proxy settings. Their only purpose is to
> support the grab. But the guard looks at the system property alone. It
> ignores the other two ways Grape can be off:
> * *No engine on the classpath.* In Groovy 6, Grape engines live in
> {{groovy-grape-ivy}} and {{groovy-grape-maven}}. With neither present,
> {{Grape.getInstance()}} returns null, {{Grape.grab}} does nothing, and the
> log says "Grapes disabled". The properties are still set.
> * *{{Grape.setEnableGrapes(false)}}.* {{Grape.grab}} checks the static
> {{enableGrapes}} flag and does nothing. The generated guard never looks at
> that flag, so the properties are still set.
> In both cases the class changes process-wide state, possibly trust-store or
> proxy settings, for a grab that never happens. Hosts that turn Grape off,
> such as embedders, tooling and notebook kernels, reasonably expect
> {{@GrabConfig}} to be inert too.
> A smaller inconsistency: {{Grape}} reads the property with
> {{Boolean.parseBoolean}}, which ignores case, while the generated guard
> compares it to the exact string "true". So {{-Dgroovy.grape.enable=TRUE}}
> turns grabbing on but skips the properties.
> h2. Reproducer
> {code:groovy}
> // G2.groovy
> @GrabConfig(systemProperties = 'probe.flag=set-by-grabconfig')
> @Grab('org.apache.commons:commons-lang3:3.14.0')
> import java.util.List
> println "flag=" + System.getProperty('probe.flag')
> {code}
> Results against Groovy 6.0.0 (JDK 17):
> ||Setup||Grab runs?||{{probe.flag}}||
> |core jar only ({{java -cp groovy-6.0.0.jar groovy.ui.GroovyMain
> G2.groovy}})|no ("Grapes disabled")|*set-by-grabconfig*|
> |engines present, {{Grape.enableGrapes = false}} before evaluating with
> {{GroovyShell}}|no|*set-by-grabconfig*|
> |core jar only, {{-Dgroovy.grape.enable=false}}|no|null|
> |engines present, {{GrabAnnotationTransformation}} disabled via
> {{CompilerConfiguration.disabledGlobalASTTransformations}}|no|null|
> |engines present, defaults|yes|set-by-grabconfig (expected)|
> When {{@Grab}} is on a class rather than a script, the properties are set
> when that class initializes, not when the script starts.
> h2. Proposed fix
> Set the properties only when a grab will actually run. The simplest form is
> to add a small public helper to {{Grape}}, for example
> {{Grape.isGrabAvailable()}}, returning {{enableGrapes && getInstance() !=
> null}}. The generated guard then calls the helper instead of reading the
> system property. This also removes the case mismatch, because the helper uses
> the same parsed flag as {{Grape.grab}}.
> An alternative is to pass the properties to {{Grape.grab}} in its arguments
> map, which the compile-time path already does but no engine reads. The facade
> would then apply them just before delegating to the engine. That changes
> compile-time behaviour, because the properties would also be set in the
> compiler's JVM. That should be a separate decision, so the minimal fix above
> is preferred for this ticket.
> h2. Compatibility
> Behaviour changes only when Grape is disabled or no engine is available. That
> is exactly when the properties had no effect on any grab. Code that relied on
> {{@GrabConfig}} as a general way to set system properties, without a working
> grab, would need to call {{System.setProperty}} itself. That seems unlikely,
> and it could be noted in the release notes.
> h2. Tests
> * With no engine on the test classpath, a class using
> {{@GrabConfig(systemProperties)}} leaves the property unset. Run it in a
> forked JVM, or in {{groovy-grape-test}} with a classloader that hides the
> engines.
> * After {{Grape.setEnableGrapes(false)}}, the same class leaves the property
> unset. Restore the flag afterwards.
> * With an engine present and grapes enabled, behaviour is unchanged.
> h2. Context
> Found while drafting GEP-32 (restricted compiler profile). The profile turns
> off the Grab transform for each compilation, which avoids this. Hosts that
> rely on keeping the engines off the classpath, or on
> {{Grape.setEnableGrapes(false)}}, do not get that protection.
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)