Jex Jexler created GROOVY-12458:
-----------------------------------

             Summary: @Grab in a class defined by a non-GroovyClassLoader fails 
at runtime with groovy-grape-maven ("No suitable ClassLoader found for grab")
                 Key: GROOVY-12458
                 URL: https://issues.apache.org/jira/browse/GROOVY-12458
             Project: Groovy
          Issue Type: Bug
          Components: Grape
    Affects Versions: 6.0.0
            Reporter: Jex Jexler
         Attachments: GrabClassLoaderRepro.groovy

Since Groovy 6, the static initializer generated for @Grab passes the class's 
own class loader to Grape.grab() 
(GrabAnnotationTransformation.callGrabAsStaticInitIfNeeded: classLoader: 
<Class>.getClassLoader()).

If that class was defined by a class loader that is not a GroovyClassLoader (or 
RootLoader), but has one as an ancestor, both engines reject the passed loader 
in chooseClassLoader() without considering its parents, and fall back to the 
stack walk via calleeDepth, which (as the comment in 
callGrabAsStaticInitIfNeeded notes) overshoots from a static initializer:

* GrapeMaven.chooseClassLoader() then throws "RuntimeException: No suitable 
ClassLoader found for grab".
* GrapeIvy.chooseClassLoader() usually recovers via its fallbacks (scanning 
frames at calleeDepth +/- 2, which finds a class whose class loader has a valid 
ancestor), i.e. more by chance than by design.

Hosts that define compiled script classes with their own class loaders below a 
GroovyClassLoader are affected. We hit this with *Grengine* 
(https://grengine.ch/), where @Grab works with groovy-grape-ivy, but fails with 
groovy-grape-maven. Grengine now works around it in its GrapeEngine wrapper by 
replacing the passed class loader with its closest valid ancestor:
https://github.com/alainstalder/grengine/commit/bd0dd9845440f627e282c4b474f1ec111b6f9a5d
(code: 
https://github.com/alainstalder/grengine/blob/bd0dd9845440f627e282c4b474f1ec111b6f9a5d/src/main/java/ch/grengine/code/groovy/DefaultGroovyCompiler.java#L673-L699)

*Standalone reproducer (attached, GrabClassLoaderRepro.groovy):* compiles a 
script with @Grab using a GroovyClassLoader, defines the resulting class with a 
plain ClassLoader subclass whose parent is that GroovyClassLoader, and 
instantiates it, which runs the static initializer:

* with groovy-6.0.0 + groovy-grape-ivy (and its dependencies) on the classpath: 
OK
* with groovy-6.0.0 + groovy-grape-maven (and its dependencies) on the 
classpath:
  ExceptionInInitializerError, caused by RuntimeException: No suitable 
ClassLoader found for grab
  at groovy.grape.maven.GrapeMaven.chooseClassLoader(GrapeMaven.groovy:785)

Run e.g. with: java -cp <classpath> groovy.ui.GroovyMain 
GrabClassLoaderRepro.groovy

*Suggested fix:* in chooseClassLoader() (in both engines, or at least in 
GrapeMaven), if args.classLoader is set but not a valid target, walk up its 
parents to the first valid one before falling back to the stack walk, e.g.:

 
{code:java}
    ClassLoader loader = (ClassLoader) args.classLoader
    while (loader && !isValidTargetClassLoader(loader)) {
        loader = loader.getParent()
    }
    if (!isValidTargetClassLoader(loader)) {
        // existing stack walk (and fallbacks)
    }{code}
 



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

Reply via email to