Skip to content

Eliminate the need for VM agent #27

Closed
@jtulach

Description

@jtulach

I eliminated the need for VM agent by hooking into internal lambda classes dumping mechanism. I don't know if that is direction you'd like to move, so I am not starting a pull request, rather than recording my work in an issue.


Eliminating need for agent (and thus starting separate VM) by hooking into internal lamda classes dump mechanism. Works on 1.8.0_11-b12

diff --git a/retrolambda-maven-plugin/src/main/java/net/orfjackal/retrolambda/maven/ProcessClassesMojo.java b/retrolambda-maven-plugin/src/main/java/net/orfjackal/retrolambda/maven/ProcessClassesMojo.java
index a945a78..f766043 100644
--- a/retrolambda-maven-plugin/src/main/java/net/orfjackal/retrolambda/maven/ProcessClassesMojo.java
+++ b/retrolambda-maven-plugin/src/main/java/net/orfjackal/retrolambda/maven/ProcessClassesMojo.java
@@ -6,14 +6,18 @@ package net.orfjackal.retrolambda.maven;

 import com.google.common.base.Joiner;
 import com.google.common.collect.ImmutableMap;
+import java.io.*;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.*;
+import org.apache.maven.artifact.Artifact;
 import org.apache.maven.execution.MavenSession;
 import org.apache.maven.plugin.*;
 import org.apache.maven.plugins.annotations.*;
 import org.apache.maven.project.MavenProject;
 import org.apache.maven.toolchain.*;

-import java.io.*;
-import java.util.*;

 import static org.twdata.maven.mojoexecutor.MojoExecutor.*;

@@ -132,28 +136,29 @@ abstract class ProcessClassesMojo extends AbstractMojo {

     private void processClasses() throws MojoExecutionException {
         String retrolambdaJar = getRetrolambdaJarPath();
-        executeMojo(
-                plugin(groupId("org.apache.maven.plugins"),
-                        artifactId("maven-antrun-plugin"),
-                        version("1.7")),
-                goal("run"),
-                configuration(element(
-                        "target",
-                        element("property",
-                                attributes(attribute("name", "the_classpath"),
-                                        attribute("refid", getClasspathId()))),
-                        element("exec",
-                                attributes(
-                                        attribute("executable", getJavaCommand()),
-                                        attribute("failonerror", "true")),
-                                element("arg", attribute("value", "-Dretrolambda.bytecodeVersion=" + targetBytecodeVersions.get(target))),
-                                element("arg", attribute("value", "-Dretrolambda.inputDir=" + getInputDir().getAbsolutePath())),
-                                element("arg", attribute("value", "-Dretrolambda.outputDir=" + getOutputDir().getAbsolutePath())),
-                                element("arg", attribute("value", "-Dretrolambda.classpath=${the_classpath}")),
-                                element("arg", attribute("value", "-javaagent:" + retrolambdaJar)),
-                                element("arg", attribute("value", "-jar")),
-                                element("arg", attribute("value", retrolambdaJar))))),
-                executionEnvironment(project, session, pluginManager));
+        File jar = new File(retrolambdaJar);
+        
+        try {
+            StringBuilder sb = new StringBuilder();
+            sb.append(getInputDir());
+            for (Artifact a : project.getArtifacts()) {
+                if (a.getFile() != null) {
+                    sb.append(File.pathSeparator);
+                    sb.append(a.getFile());
+                }
+            }
+        
+            URLClassLoader url = new URLClassLoader(new URL[] { jar.toURI().toURL() });
+            Class<?> mainClass = Class.forName("net.orfjackal.retrolambda.Main", true, url);
+            System.setProperty("retrolambda.bytecodeVersion", "" + targetBytecodeVersions.get(target));
+            System.setProperty("retrolambda.inputDir", getInputDir().getAbsolutePath());
+            System.setProperty("retrolambda.outputDir", getOutputDir().getAbsolutePath());
+            System.setProperty("retrolambda.classpath", sb.toString());
+            Method main = mainClass.getMethod("main", String[].class);
+            main.invoke(null, (Object) new String[0]);
+        } catch (Exception ex) {
+            throw new MojoExecutionException("Cannot initialize classloader for " + retrolambdaJar, ex);
+        }
     }

     private String getRetrolambdaJarPath() {
diff --git a/retrolambda/src/main/java/net/orfjackal/retrolambda/Config.java b/retrolambda/src/main/java/net/orfjackal/retrolambda/Config.java
index 7d07447..502716d 100644
--- a/retrolambda/src/main/java/net/orfjackal/retrolambda/Config.java
+++ b/retrolambda/src/main/java/net/orfjackal/retrolambda/Config.java
@@ -43,7 +43,7 @@ public class Config {
     }

     public boolean isFullyConfigured() {
-        return hasAllRequiredProperties() && PreMain.isAgentLoaded();
+        return hasAllRequiredProperties();
     }

     private boolean hasAllRequiredProperties() {
diff --git a/retrolambda/src/main/java/net/orfjackal/retrolambda/LambdaSavingClassFileTransformer.java b/retrolambda/src/main/java/net/orfjackal/retrolambda/LambdaSavingClassFileTransformer.java
index fef4f1d..eda6e2d 100644
--- a/retrolambda/src/main/java/net/orfjackal/retrolambda/LambdaSavingClassFileTransformer.java
+++ b/retrolambda/src/main/java/net/orfjackal/retrolambda/LambdaSavingClassFileTransformer.java
@@ -4,13 +4,28 @@

 package net.orfjackal.retrolambda;

-import org.objectweb.asm.ClassReader;
-
-import java.lang.instrument.*;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.nio.channels.Channels;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.channels.WritableByteChannel;
 import java.nio.file.*;
-import java.security.ProtectionDomain;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileAttribute;
+import java.nio.file.attribute.FileAttributeView;
+import java.nio.file.attribute.UserPrincipalLookupService;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;

-public class LambdaSavingClassFileTransformer implements ClassFileTransformer {
+public class LambdaSavingClassFileTransformer {

     private final Path outputDir;
     private final int targetVersion;
@@ -20,17 +35,35 @@ public class LambdaSavingClassFileTransformer implements ClassFileTransformer {
         this.targetVersion = targetVersion;
     }

-    @Override
-    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
-        if (className == null) {
-            // Since JDK 8 build b121 or so, lambda classes have a null class name,
-            // but we can read it from the bytecode where the name still exists.
-            className = new ClassReader(classfileBuffer).getClassName();
+    private Field field;
+    void registerDumper() {
+        try {
+            Class<?> dumper = Class.forName("java.lang.invoke.ProxyClassesDumper");
+            Constructor<?> cnstr = dumper.getDeclaredConstructor(Path.class);
+            cnstr.setAccessible(true);
+            Class<?> mf = Class.forName("java.lang.invoke.InnerClassLambdaMetafactory");
+            field = mf.getDeclaredField("dumper");
+            Field m = field.getClass().getDeclaredField("modifiers");
+            m.setAccessible(true);
+            int mod = m.getInt(field);
+            m.setInt(field, mod & ~Modifier.FINAL);
+            field.setAccessible(true);
+            
+            Path p = new VirtualPath("");
+            field.set(null, cnstr.newInstance(p));
+        } catch (Exception ex) {
+            throw new IllegalStateException("Cannot initialize dumper", ex);
         }
-        if (LambdaReifier.isLambdaClassToReify(className)) {
-            reifyLambdaClass(className, classfileBuffer);
+    }
+    
+    void unregisterDumper() {
+        if (field != null) {
+            try {
+                field.set(null, null);
+            } catch (Exception ex) {
+                throw new IllegalArgumentException(ex);
+            }
         }
-        return null;
     }

     private void reifyLambdaClass(String className, byte[] classfileBuffer) {
@@ -47,4 +80,361 @@ public class LambdaSavingClassFileTransformer implements ClassFileTransformer {
             t.printStackTrace(System.out);
         }
     }
+    
+    private final class VirtualProvider extends FileSystemProvider {
+
+        @Override
+        public String getScheme() {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public FileSystem getFileSystem(URI uri) {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public Path getPath(URI uri) {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
+            return new ClassChannel(path);
+        }
+
+        @Override
+        public DirectoryStream<Path> newDirectoryStream(Path dir, DirectoryStream.Filter<? super Path> filter) throws IOException {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
+        }
+
+        @Override
+        public void delete(Path path) throws IOException {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public void copy(Path source, Path target, CopyOption... options) throws IOException {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public void move(Path source, Path target, CopyOption... options) throws IOException {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public boolean isSameFile(Path path, Path path2) throws IOException {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public boolean isHidden(Path path) throws IOException {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public FileStore getFileStore(Path path) throws IOException {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public void checkAccess(Path path, AccessMode... modes) throws IOException {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public <V extends FileAttributeView> V getFileAttributeView(Path path, Class<V> type, LinkOption... options) {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public <A extends BasicFileAttributes> A readAttributes(Path path, Class<A> type, LinkOption... options) throws IOException {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options) throws IOException {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException {
+            throw new IllegalStateException();
+        }
+        
+    }
+    
+    private final class VirtualFS extends FileSystem {
+
+        @Override
+        public FileSystemProvider provider() {
+            return new VirtualProvider();
+        }
+
+        @Override
+        public void close() throws IOException {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public boolean isOpen() {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public boolean isReadOnly() {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public String getSeparator() {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public Iterable<Path> getRootDirectories() {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public Iterable<FileStore> getFileStores() {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public Set<String> supportedFileAttributeViews() {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public Path getPath(String first, String... more) {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public PathMatcher getPathMatcher(String syntaxAndPattern) {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public UserPrincipalLookupService getUserPrincipalLookupService() {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public WatchService newWatchService() throws IOException {
+            throw new IllegalStateException();
+        }
+        
+    }
+    
+    private final class VirtualPath implements Path {
+        private final String path;
+
+        public VirtualPath(String path) {
+            this.path = path;
+        }
+
+        @Override
+        public FileSystem getFileSystem() {
+            return new VirtualFS();
+        }
+
+        @Override
+        public boolean isAbsolute() {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public Path getRoot() {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public Path getFileName() {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public Path getParent() {
+            return this;
+        }
+
+        @Override
+        public int getNameCount() {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public Path getName(int index) {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public Path subpath(int beginIndex, int endIndex) {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public boolean startsWith(Path other) {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public boolean startsWith(String other) {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public boolean endsWith(Path other) {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public boolean endsWith(String other) {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public Path normalize() {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public Path resolve(Path other) {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public Path resolve(String other) {
+            assert path.isEmpty();
+            return new VirtualPath(other);
+        }
+
+        @Override
+        public Path resolveSibling(Path other) {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public Path resolveSibling(String other) {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public Path relativize(Path other) {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public URI toUri() {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public Path toAbsolutePath() {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public Path toRealPath(LinkOption... options) throws IOException {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public File toFile() {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public WatchKey register(WatchService watcher, WatchEvent.Kind<?>[] events, WatchEvent.Modifier... modifiers) throws IOException {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public WatchKey register(WatchService watcher, WatchEvent.Kind<?>... events) throws IOException {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public Iterator<Path> iterator() {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public int compareTo(Path other) {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public String toString() {
+            return path;
+        }
+    }
+    
+    private final class ClassChannel implements SeekableByteChannel {
+        private final Path path;
+        private final ByteArrayOutputStream os;
+        private final WritableByteChannel ch;
+
+        public ClassChannel(Path path) {
+            this.path = path;
+            this.os = new ByteArrayOutputStream();
+            this.ch = Channels.newChannel(os);
+        }
+        
+        @Override
+        public int read(ByteBuffer dst) throws IOException {
+            throw new IOException();
+        }
+
+        @Override
+        public int write(ByteBuffer src) throws IOException {
+            return ch.write(src);
+        }
+
+        @Override
+        public long position() throws IOException {
+            throw new IOException();
+        }
+
+        @Override
+        public SeekableByteChannel position(long newPosition) throws IOException {
+            throw new IOException();
+        }
+
+        @Override
+        public long size() throws IOException {
+            throw new IOException();
+        }
+
+        @Override
+        public SeekableByteChannel truncate(long size) throws IOException {
+            throw new IOException();
+        }
+
+        @Override
+        public boolean isOpen() {
+            return true;
+        }
+
+        @Override
+        public void close() throws IOException {
+            String className = path.toString();
+            className = className.substring(0, className.length() - 6);
+            if (LambdaReifier.isLambdaClassToReify(className)) {
+                reifyLambdaClass(className, os.toByteArray());
+            }
+        }
+    } // end of ClassCastException
 }
diff --git a/retrolambda/src/main/java/net/orfjackal/retrolambda/Main.java b/retrolambda/src/main/java/net/orfjackal/retrolambda/Main.java
index feba36e..98d98c0 100644
--- a/retrolambda/src/main/java/net/orfjackal/retrolambda/Main.java
+++ b/retrolambda/src/main/java/net/orfjackal/retrolambda/Main.java
@@ -43,7 +43,11 @@ public class Main {
             visitFiles(inputDir, includedFiles, new BytecodeTransformingFileVisitor(inputDir, outputDir) {
                 @Override
                 protected byte[] transform(byte[] bytecode) {
-                    return LambdaUsageBackporter.transform(bytecode, bytecodeVersion);
+                    final LambdaSavingClassFileTransformer trans = new LambdaSavingClassFileTransformer(outputDir, bytecodeVersion);
+                    trans.registerDumper();
+                    byte[] ret = LambdaUsageBackporter.transform(bytecode, bytecodeVersion);
+                    trans.unregisterDumper();
+                    return ret;
                 }
             });
         } catch (Throwable t) {
diff --git a/retrolambda/src/main/java/net/orfjackal/retrolambda/PreMain.java b/retrolambda/src/main/java/net/orfjackal/retrolambda/PreMain.java
deleted file mode 100644
index ddb1a6d..0000000
--- a/retrolambda/src/main/java/net/orfjackal/retrolambda/PreMain.java
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright © 2013 Esko Luontola <www.orfjackal.net>
-// This software is released under the Apache License 2.0.
-// The license text is at http://www.apache.org/licenses/LICENSE-2.0
-
-package net.orfjackal.retrolambda;
-
-import java.lang.instrument.Instrumentation;
-import java.nio.file.Path;
-
-public class PreMain {
-
-    private static boolean agentLoaded = false;
-
-    public static void premain(String agentArgs, Instrumentation inst) {
-        Config config = new Config(System.getProperties());
-        int bytecodeVersion = config.getBytecodeVersion();
-        Path outputDir = config.getOutputDir();
-        inst.addTransformer(new LambdaSavingClassFileTransformer(outputDir, bytecodeVersion));
-        agentLoaded = true;
-    }
-
-    public static boolean isAgentLoaded() {
-        return agentLoaded;
-    }
-}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions