Skip to content

Commit

Permalink
Blackbird: crack open access to classes from different classloaders
Browse files Browse the repository at this point in the history
  • Loading branch information
stevenschlansker committed Jan 4, 2022
1 parent 5b045bd commit 662d96e
Show file tree
Hide file tree
Showing 6 changed files with 461 additions and 6 deletions.
272 changes: 272 additions & 0 deletions blackbird/GenBlackbirdAccess.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import javax.tools.Diagnostic;
import javax.tools.DiagnosticListener;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import javax.tools.FileObject;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import javax.tools.SimpleJavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;

class GrantAccess {
// The following code produces the class file stored in the data arrays in CrossLoaderAccess.
// Loading the Java compiler is extremely expensive so we inline the byte data instead.
private static MethodHandles.Lookup grantAccessSlow(final MethodHandles.Lookup lookup, final Package pkg) throws IOException, ReflectiveOperationException {
final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
final BlackbirdDiagnosticListener<JavaFileObject> diagnostics = new BlackbirdDiagnosticListener<>();
final StandardJavaFileManager delegateFileManager = compiler.getStandardFileManager(diagnostics, Locale.ROOT, StandardCharsets.UTF_8);
final String myPackage = pkg.getName();
final BlackbirdFileManager fileManager = new BlackbirdFileManager(delegateFileManager, myPackage);
final JavaFileObject file = new BlackbirdFileManager.BlackbirdInputJavaFileObject("BlackbirdAccess", "package " + myPackage + "; public class BlackbirdAccess { public static final java.lang.invoke.MethodHandles.Lookup LOOKUP = java.lang.invoke.MethodHandles.lookup(); }");
compiler
.getTask(null, fileManager, diagnostics, Arrays.asList("--release", "8"), null, Arrays.asList(file))
.call();
diagnostics.assertSuccess();
final FileObject accessClassFile = fileManager.getFileForInput(StandardLocation.CLASS_PATH, myPackage, "BlackbirdAccess");
final byte[] accessClassBytes = accessClassFile.openInputStream().readAllBytes();
final Class<?> accessClass = lookup.defineClass(accessClassBytes);
return (MethodHandles.Lookup) accessClass.getField("LOOKUP").get(null);
}
}

class BlackbirdDiagnosticListener<S> implements DiagnosticListener<S> {
private static final Set<Diagnostic.Kind> FAILURES;

static {
FAILURES = Collections.unmodifiableSet(
EnumSet.of(Diagnostic.Kind.ERROR, Diagnostic.Kind.WARNING, Diagnostic.Kind.MANDATORY_WARNING));
}

private final List<Diagnostic<? extends S>> failures = new ArrayList<>();

@Override
public void report(final Diagnostic<? extends S> diagnostic) {
if (FAILURES.contains(diagnostic.getKind())) {
failures.add(diagnostic);
}
}

public void assertSuccess() {
if (!failures.isEmpty()) {
throw new RuntimeException("Compilation failed:\n"
+ failures.stream()
.map(Object::toString)
.collect(Collectors.joining("\n")));
}
}
}


class BlackbirdFileManager implements JavaFileManager {
private final StandardJavaFileManager delegate;
private final Map<String, BlackbirdOutputJavaFileObject> createdFiles = new HashMap<>();
private final String myPackage;

public BlackbirdFileManager(final StandardJavaFileManager delegate, final String myPackage) {
this.delegate = delegate;
this.myPackage = myPackage;
}

@Override
public ClassLoader getClassLoader(final Location location) {
return delegate.getClassLoader(location);
}

@Override
public Iterable<JavaFileObject> list(final Location location, final String packageName, final Set<Kind> kinds, final boolean recurse) throws IOException {
final Iterable<JavaFileObject> stdList = delegate.list(location, packageName, kinds, recurse);
if (StandardLocation.CLASS_PATH.equals(location)) {
return () -> new Iterator<JavaFileObject>() {
boolean stdDone;
Iterator<? extends JavaFileObject> iter;

@Override
public boolean hasNext() {
if (iter == null) {
iter = stdList.iterator();
}
if (iter.hasNext()) {
return true;
}
if (stdDone) {
return false;
}
stdDone = true;
iter = createdFiles.values().iterator();
return iter.hasNext();
}

@Override
public JavaFileObject next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return iter.next();
}
};
}
return stdList;
}

@Override
public String inferBinaryName(final Location location, final JavaFileObject file) {
if (file instanceof BlackbirdOutputJavaFileObject) {
return file.getName();
}
return delegate.inferBinaryName(location, file);
}

@Override
public boolean handleOption(final String current, final Iterator<String> remaining) {
return delegate.handleOption(current, remaining);
}

@Override
public boolean hasLocation(final Location location) {
return delegate.hasLocation(location);
}

@Override
public JavaFileObject getJavaFileForInput(final Location location, final String className, final Kind kind) throws IOException {
return delegate.getJavaFileForInput(location, className, kind);
}

@Override
public JavaFileObject getJavaFileForOutput(final Location location, final String className, final Kind kind, final FileObject sibling) throws IOException {
final BlackbirdOutputJavaFileObject out = new BlackbirdOutputJavaFileObject(className, kind);
createdFiles.put(className, out);
return out;
}

@Override
public FileObject getFileForInput(final Location location, final String packageName, final String relativeName) throws IOException {
if (StandardLocation.CLASS_PATH.equals(location) && myPackage.equals(packageName)) {
final BlackbirdOutputJavaFileObject file = createdFiles.get(packageName + "." + relativeName);
if (file != null) {
return file;
}
}
return delegate.getFileForInput(location, packageName, relativeName);
}

@Override
public FileObject getFileForOutput(final Location location, final String packageName, final String relativeName, final FileObject sibling) throws IOException {
throw new UnsupportedOperationException();
}

@Override
public void flush() throws IOException {}

@Override
public void close() throws IOException {}

@Override
public int isSupportedOption(final String option) {
return delegate.isSupportedOption(option);
}

@Override
public boolean isSameFile(final FileObject a, final FileObject b) {
return delegate.isSameFile(a, b);
}

@Override
public Location getLocationForModule(final Location location, final String moduleName) throws IOException {
return delegate.getLocationForModule(location, moduleName);
}

@Override
public Location getLocationForModule(final Location location, final JavaFileObject fo) throws IOException {
return delegate.getLocationForModule(location, fo);
}

@Override
public String inferModuleName(final Location location) throws IOException {
return delegate.inferModuleName(location);
}

@Override
public Iterable<Set<Location>> listLocationsForModules(final Location location) throws IOException {
return delegate.listLocationsForModules(location);
}

@Override
public boolean contains(final Location location, final FileObject file) throws IOException {
return delegate.contains(location, file);
}

public static class BlackbirdJavaFileObject extends SimpleJavaFileObject {

protected BlackbirdJavaFileObject(final String name, final Kind kind) {
super(URI.create("string:///" + name.replace('.', '/')
+ kind.extension), kind);
}
}

public static class BlackbirdInputJavaFileObject extends BlackbirdJavaFileObject {
private final String contents;

protected BlackbirdInputJavaFileObject(final String name, final String contents) {
super(name, Kind.SOURCE);
this.contents = contents;
}

@Override
public CharSequence getCharContent(final boolean ignoreEncodingErrors) throws IOException {
return contents;
}
}

public static class BlackbirdOutputJavaFileObject extends BlackbirdJavaFileObject {
private final String name;
private ByteArrayOutputStream baos = new ByteArrayOutputStream();
private byte[] bytes;

protected BlackbirdOutputJavaFileObject(final String name, final Kind kind) {
super(name, kind);
this.name = name;
}

@Override
public String getName() {
return name;
}

public byte[] getBytes() {
if (bytes == null) {
bytes = baos.toByteArray();
baos = null;
}
return bytes;
}

@Override
public OutputStream openOutputStream() throws IOException {
return baos;
}

@Override
public InputStream openInputStream() throws IOException {
return new ByteArrayInputStream(getBytes());
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ public BlackbirdModule(Supplier<MethodHandles.Lookup> lookup) {
@Override
public void setupModule(SetupContext context)
{
context.addBeanDeserializerModifier(new BBDeserializerModifier(_lookups));
context.addBeanSerializerModifier(new BBSerializerModifier(_lookups));
CrossLoaderAccess openSesame = new CrossLoaderAccess();
context.addBeanDeserializerModifier(new BBDeserializerModifier(_lookups, openSesame));
context.addBeanSerializerModifier(new BBSerializerModifier(_lookups, openSesame));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package com.fasterxml.jackson.module.blackbird;

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.function.UnaryOperator;

class CrossLoaderAccess implements UnaryOperator<MethodHandles.Lookup> {
// Pre-compiled Java 8 bytecode:
// package <myPackage>;
// public class BlackbirdAccess {
// public static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
// }

private static final String CLASS_NAME = "$$JacksonBlackbirdAccess";
private static final int[] HEADER = new int[] {
0xca, 0xfe, 0xba, 0xbe, 0x00, 0x00, 0x00, 0x34, 0x00, 0x1c, 0x0a, 0x00,
0x02, 0x00, 0x03, 0x07, 0x00, 0x04, 0x0c, 0x00, 0x05, 0x00, 0x06, 0x01,
0x00, 0x10, 0x6a, 0x61, 0x76, 0x61, 0x2f, 0x6c, 0x61, 0x6e, 0x67, 0x2f,
0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x01, 0x00, 0x06, 0x3c, 0x69, 0x6e,
0x69, 0x74, 0x3e, 0x01, 0x00, 0x03, 0x28, 0x29, 0x56, 0x0a, 0x00, 0x08,
0x00, 0x09, 0x07, 0x00, 0x0a, 0x0c, 0x00, 0x0b, 0x00, 0x0c, 0x01, 0x00,
0x1e, 0x6a, 0x61, 0x76, 0x61, 0x2f, 0x6c, 0x61, 0x6e, 0x67, 0x2f, 0x69,
0x6e, 0x76, 0x6f, 0x6b, 0x65, 0x2f, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64,
0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x73, 0x01, 0x00, 0x06, 0x6c, 0x6f,
0x6f, 0x6b, 0x75, 0x70, 0x01, 0x00, 0x29, 0x28, 0x29, 0x4c, 0x6a, 0x61,
0x76, 0x61, 0x2f, 0x6c, 0x61, 0x6e, 0x67, 0x2f, 0x69, 0x6e, 0x76, 0x6f,
0x6b, 0x65, 0x2f, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x48, 0x61, 0x6e,
0x64, 0x6c, 0x65, 0x73, 0x24, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x3b,
0x09, 0x00, 0x0e, 0x00, 0x0f, 0x07, 0x00, 0x10, 0x0c, 0x00, 0x11, 0x00,
0x12, 0x01
};
private static final int[] FOOTER = new int[] {
0x01, 0x00, 0x06, 0x4c, 0x4f, 0x4f, 0x4b, 0x55, 0x50, 0x01, 0x00, 0x27,
0x4c, 0x6a, 0x61, 0x76, 0x61, 0x2f, 0x6c, 0x61, 0x6e, 0x67, 0x2f, 0x69,
0x6e, 0x76, 0x6f, 0x6b, 0x65, 0x2f, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64,
0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x73, 0x24, 0x4c, 0x6f, 0x6f, 0x6b,
0x75, 0x70, 0x3b, 0x01, 0x00, 0x04, 0x43, 0x6f, 0x64, 0x65, 0x01, 0x00,
0x0f, 0x4c, 0x69, 0x6e, 0x65, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x54,
0x61, 0x62, 0x6c, 0x65, 0x01, 0x00, 0x08, 0x3c, 0x63, 0x6c, 0x69, 0x6e,
0x69, 0x74, 0x3e, 0x01, 0x00, 0x0a, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65,
0x46, 0x69, 0x6c, 0x65, 0x01, 0x00, 0x14, 0x42, 0x6c, 0x61, 0x63, 0x6b,
0x62, 0x69, 0x72, 0x64, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x2e, 0x6a,
0x61, 0x76, 0x61, 0x01, 0x00, 0x0c, 0x49, 0x6e, 0x6e, 0x65, 0x72, 0x43,
0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, 0x07, 0x00, 0x1a, 0x01, 0x00, 0x25,
0x6a, 0x61, 0x76, 0x61, 0x2f, 0x6c, 0x61, 0x6e, 0x67, 0x2f, 0x69, 0x6e,
0x76, 0x6f, 0x6b, 0x65, 0x2f, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x48,
0x61, 0x6e, 0x64, 0x6c, 0x65, 0x73, 0x24, 0x4c, 0x6f, 0x6f, 0x6b, 0x75,
0x70, 0x01, 0x00, 0x06, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x00, 0x21,
0x00, 0x0e, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x19, 0x00, 0x11,
0x00, 0x12, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x05, 0x00, 0x06,
0x00, 0x01, 0x00, 0x13, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x01, 0x00, 0x01,
0x00, 0x00, 0x00, 0x05, 0x2a, 0xb7, 0x00, 0x01, 0xb1, 0x00, 0x00, 0x00,
0x01, 0x00, 0x14, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00,
0x01, 0x00, 0x08, 0x00, 0x15, 0x00, 0x06, 0x00, 0x01, 0x00, 0x13, 0x00,
0x00, 0x00, 0x1f, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xb8,
0x00, 0x07, 0xb3, 0x00, 0x0d, 0xb1, 0x00, 0x00, 0x00, 0x01, 0x00, 0x14,
0x00, 0x00, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02,
0x00, 0x16, 0x00, 0x00, 0x00, 0x02, 0x00, 0x17, 0x00, 0x18, 0x00, 0x00,
0x00, 0x0a, 0x00, 0x01, 0x00, 0x19, 0x00, 0x08, 0x00, 0x1b, 0x00, 0x19
};

@Override
public MethodHandles.Lookup apply(MethodHandles.Lookup lookup) {
try {
return grantAccess(lookup);
} catch (IOException | ReflectiveOperationException e) {
e.printStackTrace();
return lookup;
}
}

private static MethodHandles.Lookup grantAccess(MethodHandles.Lookup lookup) throws IOException, ReflectiveOperationException {
Package pkg = lookup.lookupClass().getPackage();
try {
return getLookup(Class.forName(pkg.getName() + "." + CLASS_NAME, true, lookup.lookupClass().getClassLoader()));
} catch (ClassNotFoundException ign) { }
String fqcn = pkg.getName()
.replace('.', '/')
+ "/" + CLASS_NAME;
ByteArrayOutputStream classBytes = new ByteArrayOutputStream(HEADER.length + FOOTER.length + fqcn.length() + 16);
DataOutputStream dataOut = new DataOutputStream(classBytes);
for (int b : HEADER) {
dataOut.writeByte(b);
}
dataOut.writeUTF(fqcn);
for (int b : FOOTER) {
dataOut.writeByte(b);
}
return getLookup(lookup.defineClass(classBytes.toByteArray()));
}

private static MethodHandles.Lookup getLookup(Class<?> clazz) throws ReflectiveOperationException {
return (MethodHandles.Lookup) clazz.getField("LOOKUP").get(null);
}
}
Loading

0 comments on commit 662d96e

Please sign in to comment.