From 5407d0d253baa0cba1e5b7374f9ca45b5c50f622 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Fri, 24 Oct 2014 00:11:43 +0200 Subject: Making SCL work right is more complicated than it first seemed. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Right now the rules are: * _IF_ a class is being loaded, sourced by a lombok-jar originating class, we FIRST search the lombok jar, and if we can’t find it, farm out the job to the originating equinox-side loader. * _IF_ the equinox-side loader attempts to load a class, and it does NOT start with lombok, we don’t interfere and would never serve up any content from the lombok-jar (so if we have deps, they do NOT get loaded, by design). If it DOES start with lombok, we load it, and the loading class is SCL, not the equinox-side loader. * getResource() to load classes did not work (because internally classes end in .SCL.lombok and not .class). This breaks a bunch of things. Fixed by having getResource() be aware that it should try rewriting any request for a .class to .SCL.lombok. * launchified annotationprocessor, and cleaned up the launchified agent, which now, like all other launchers, just sets up classloader stuff and then calls into the lombok loader side to finish the actual processing, instead of trying to do it itself in a handicapped environment that can’t load much. --- .../lombok/eclipse/agent/EclipseLoaderPatcher.java | 107 +++++++++++++++++++++ .../lombok/eclipse/agent/EclipsePatcher.java | 14 +-- 2 files changed, 114 insertions(+), 7 deletions(-) create mode 100644 src/eclipseAgent/lombok/eclipse/agent/EclipseLoaderPatcher.java (limited to 'src/eclipseAgent') diff --git a/src/eclipseAgent/lombok/eclipse/agent/EclipseLoaderPatcher.java b/src/eclipseAgent/lombok/eclipse/agent/EclipseLoaderPatcher.java new file mode 100644 index 00000000..eb633406 --- /dev/null +++ b/src/eclipseAgent/lombok/eclipse/agent/EclipseLoaderPatcher.java @@ -0,0 +1,107 @@ +package lombok.eclipse.agent; + +import java.io.InputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.jar.JarFile; +import java.util.zip.ZipEntry; + +import lombok.patcher.ClassRootFinder; +import lombok.patcher.Hook; +import lombok.patcher.MethodTarget; +import lombok.patcher.ScriptManager; +import lombok.patcher.StackRequest; +import lombok.patcher.scripts.ScriptBuilder; + +public class EclipseLoaderPatcher { + public static boolean overrideLoadDecide(ClassLoader original, String name, boolean resolve) { + return name.startsWith("lombok."); + } + + public static Class overrideLoadResult(ClassLoader original, String name, boolean resolve) throws ClassNotFoundException { + try { + Field shadowLoaderField = original.getClass().getDeclaredField("lombok$shadowLoader"); + ClassLoader shadowLoader = (ClassLoader) shadowLoaderField.get(original); + if (shadowLoader == null) { + String jarLoc = (String) original.getClass().getDeclaredField("lombok$location").get(null); + System.out.println(jarLoc); + JarFile jf = new JarFile(jarLoc); + InputStream in = null; + try { + ZipEntry entry = jf.getEntry("lombok/launch/ShadowClassLoader.class"); + in = jf.getInputStream(entry); + byte[] bytes = new byte[65536]; + int len = 0; + while (true) { + int r = in.read(bytes, len, bytes.length - len); + if (r == -1) break; + len += r; + if (len == bytes.length) throw new IllegalStateException("lombok.launch.ShadowClassLoader too large."); + } + in.close(); + Class shadowClassLoaderClass; { + Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class); + defineClassMethod.setAccessible(true); + shadowClassLoaderClass = (Class) defineClassMethod.invoke(original, "lombok.launch.ShadowClassLoader", bytes, 0, len); + } + Constructor constructor = shadowClassLoaderClass.getDeclaredConstructor(ClassLoader.class, String.class, String.class, String[].class); + constructor.setAccessible(true); + shadowLoader = (ClassLoader) constructor.newInstance(original, "lombok", jarLoc, new String[] {"lombok."}); + shadowLoaderField.set(original, shadowLoader); + } finally { + if (in != null) in.close(); + jf.close(); + } + } + + if (resolve) { + Method m = shadowLoader.getClass().getDeclaredMethod("loadClass", String.class, boolean.class); + m.setAccessible(true); + return (Class) m.invoke(shadowLoader, name, true); + } else { + return shadowLoader.loadClass(name); + } + } catch (Exception ex) { + Throwable t = ex; + if (t instanceof InvocationTargetException) t = t.getCause(); + if (t instanceof RuntimeException) throw (RuntimeException) t; + if (t instanceof Error) throw (Error) t; + throw new RuntimeException(t); + } + } + + private static final String SELF_NAME = "lombok.eclipse.agent.EclipseLoaderPatcher"; + + public static void patchEquinoxLoaders(ScriptManager sm, Class launchingContext) { + sm.addScript(ScriptBuilder.exitEarly() + .target(new MethodTarget("org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader", "loadClass", + "java.lang.Class", "java.lang.String", "boolean")) + .target(new MethodTarget("org.eclipse.osgi.framework.adapter.core.AbstractClassLoader", "loadClass", + "java.lang.Class", "java.lang.String", "boolean")) + .target(new MethodTarget("org.eclipse.osgi.internal.loader.ModuleClassLoader", "loadClass", + "java.lang.Class", "java.lang.String", "boolean")) + .decisionMethod(new Hook(SELF_NAME, "overrideLoadDecide", "boolean", "java.lang.ClassLoader", "java.lang.String", "boolean")) + .valueMethod(new Hook(SELF_NAME, "overrideLoadResult", "java.lang.Class", "java.lang.ClassLoader", "java.lang.String", "boolean")) + .transplant() + .request(StackRequest.THIS, StackRequest.PARAM1, StackRequest.PARAM2).build()); + + sm.addScript(ScriptBuilder.addField().setPublic() + .fieldType("Ljava/lang/ClassLoader;") + .fieldName("lombok$shadowLoader") + .targetClass("org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader") + .targetClass("org.eclipse.osgi.framework.adapter.core.AbstractClassLoader") + .targetClass("org.eclipse.osgi.internal.loader.ModuleClassLoader") + .build()); + + sm.addScript(ScriptBuilder.addField().setPublic().setStatic().setFinal() + .fieldType("Ljava/lang/String;") + .fieldName("lombok$location") + .targetClass("org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader") + .targetClass("org.eclipse.osgi.framework.adapter.core.AbstractClassLoader") + .targetClass("org.eclipse.osgi.internal.loader.ModuleClassLoader") + .value(ClassRootFinder.findClassRootOfClass(launchingContext)) + .build()); + } +} diff --git a/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java b/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java index 3fce9626..96f253f2 100644 --- a/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java +++ b/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java @@ -28,12 +28,13 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import lombok.core.AgentLauncher; + import lombok.patcher.Hook; import lombok.patcher.MethodTarget; import lombok.patcher.ScriptManager; import lombok.patcher.StackRequest; import lombok.patcher.TargetMatcher; -import lombok.patcher.equinox.EquinoxClassLoader; import lombok.patcher.scripts.ScriptBuilder; /** @@ -43,12 +44,12 @@ import lombok.patcher.scripts.ScriptBuilder; * classes in this package for more information about which classes are transformed and how they are * transformed. */ -public class EclipsePatcher { +public class EclipsePatcher implements AgentLauncher.AgentLaunchable { // At some point I'd like the agent to be capable of auto-detecting if its on eclipse or on ecj. This class is a sure sign we're not in ecj but in eclipse. -ReinierZ @SuppressWarnings("unused") private static final String ECLIPSE_SIGNATURE_CLASS = "org/eclipse/core/runtime/adaptor/EclipseStarter"; - public void runAgent(String agentArgs, Instrumentation instrumentation, boolean injected) throws Exception { + @Override public void runAgent(String agentArgs, Instrumentation instrumentation, boolean injected, Class launchingContext) throws Exception { String[] args = agentArgs == null ? new String[0] : agentArgs.split(":"); boolean forceEcj = false; boolean forceEclipse = false; @@ -67,15 +68,14 @@ public class EclipsePatcher { else if (forceEclipse) ecj = false; else ecj = injected; - registerPatchScripts(instrumentation, injected, ecj); + registerPatchScripts(instrumentation, injected, ecj, launchingContext); } - private static void registerPatchScripts(Instrumentation instrumentation, boolean reloadExistingClasses, boolean ecjOnly) { + private static void registerPatchScripts(Instrumentation instrumentation, boolean reloadExistingClasses, boolean ecjOnly, Class launchingContext) { ScriptManager sm = new ScriptManager(); sm.registerTransformer(instrumentation); if (!ecjOnly) { - EquinoxClassLoader.addPrefix("lombok."); - EquinoxClassLoader.registerScripts(sm); + EclipseLoaderPatcher.patchEquinoxLoaders(sm, launchingContext); } if (!ecjOnly) { -- cgit