Skip to content

Commit

Permalink
Merge pull request #120 from jglick/CleanGroovyClassLoader-JENKINS-43019
Browse files Browse the repository at this point in the history
[JENKINS-43019] Suppressing GroovyClassLoader.InnerLoader fixes stuff
  • Loading branch information
jglick committed Apr 24, 2017
2 parents b904ffb + 16769c8 commit 651e986
Showing 1 changed file with 55 additions and 6 deletions.
@@ -1,23 +1,30 @@
package org.jenkinsci.plugins.workflow.cps;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import groovy.lang.Binding;
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyCodeSource;
import groovy.lang.GroovyShell;
import groovy.lang.Script;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.CheckForNull;
import org.codehaus.groovy.control.CompilationFailedException;
import org.codehaus.groovy.control.CompilationUnit;
import org.codehaus.groovy.control.CompilerConfiguration;

import javax.annotation.CheckForNull;
import java.io.IOException;
import org.codehaus.groovy.control.SourceUnit;

/**
* {@link GroovyShell} with additional tweaks necessary to run {@link CpsScript}
*
* @author Kohsuke Kawaguchi
* @see "doc/clasloader.md"
* @see "doc/classloader.md"
* @see CpsGroovyShellFactory
*/
class CpsGroovyShell extends GroovyShell {

private static final Logger LOGGER = Logger.getLogger(CpsGroovyShell.class.getName());

/**
* {@link CpsFlowExecution} for which this shell is created.
*
Expand All @@ -28,9 +35,51 @@ class CpsGroovyShell extends GroovyShell {
/**
* Use {@link CpsGroovyShellFactory} to instantiate it.
*/
@SuppressFBWarnings(value="DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED", justification="irrelevant")
CpsGroovyShell(ClassLoader parent, @CheckForNull CpsFlowExecution execution, CompilerConfiguration cc) {
super(parent,new Binding(),cc);
this.execution = execution;
try {
// Apparently there is no supported way to initialize a GroovyShell with a specified GroovyClassLoader.
Field loaderF = GroovyShell.class.getDeclaredField("loader");
loaderF.setAccessible(true);
loaderF.set(this, new CleanGroovyClassLoader(parent, cc));
} catch (Exception x) {
LOGGER.log(Level.WARNING, "failed to install CleanGroovyClassLoader", x);
}
}

/**
* Disables the weird and unreliable {@link groovy.lang.GroovyClassLoader.InnerLoader}.
* This is apparently only necessary when you are using class recompilation, which we are not.
* We want the {@linkplain Class#getClassLoader defining loader} of {@code *.groovy} to be this one.
* Otherwise the defining loader will be an {@code InnerLoader}, and not necessarily the same instance from load to load.
* @see GroovyClassLoader#getTimeStamp
*/
private static final class CleanGroovyClassLoader extends GroovyClassLoader {

CleanGroovyClassLoader(ClassLoader loader, CompilerConfiguration config) {
super(loader, config);
}

@Override protected ClassCollector createCollector(CompilationUnit unit, SourceUnit su) {
// Super implementation is what creates the InnerLoader.
return new CleanClassCollector(unit, su);
}

private final class CleanClassCollector extends ClassCollector {

CleanClassCollector(CompilationUnit unit, SourceUnit su) {
// Cannot override {@code final cl} field so have to do it this way.
super(null, unit, su);
}

@Override public GroovyClassLoader getDefiningClassLoader() {
return CleanGroovyClassLoader.this;
}

}

}

public void prepareScript(Script script) {
Expand Down

0 comments on commit 651e986

Please sign in to comment.