Paul King created GROOVY-12461:
----------------------------------

             Summary: @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
            Reporter: Paul King
            Assignee: Paul King


h2. Problem

When a class or script uses {{@Grab}} together with 
{{@GrabConfig(systemProperties = ...)}}, {{GrabAnnotationTransformation}} 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 6.0.0-beta-2:

||Setup||Grab runs?||{{probe.flag}}||
|core jar only ({{java -cp groovy-6.0.0-beta-2.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
(v8.20.10#820010)

Reply via email to