-
Notifications
You must be signed in to change notification settings - Fork 81
Blackbird: crack open access to classes from different classloaders #162
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
cowtowncoder
merged 1 commit into
FasterXML:2.14
from
stevenschlansker:blackbird-classloaders
Jan 9, 2022
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,276 @@ | ||
| 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; | ||
|
|
||
| /** | ||
| * 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. | ||
| * | ||
| * This code is not compiled or shipped with Blackbird, just here for maintainer reference. | ||
| */ | ||
| class GrantAccess { | ||
| 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()); | ||
| } | ||
| } | ||
| } | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.