Skip to content

Eliminate the need for VM agent #27

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;
-    }
-}

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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