From 944ea9059d6453f454ad76d834b3efaabe774afc Mon Sep 17 00:00:00 2001 From: Rafael Winterhalter Date: Tue, 18 Sep 2018 21:57:03 +0200 Subject: [PATCH] Extend class injector API. --- .../dynamic/loading/ClassInjector.java | 252 +++++++++++------- ...ClassInjectorUsingInstrumentationTest.java | 9 + .../loading/ClassInjectorUsingLookupTest.java | 13 +- .../ClassInjectorUsingReflectionTest.java | 4 +- .../loading/ClassInjectorUsingUnsafeTest.java | 1 + 5 files changed, 183 insertions(+), 96 deletions(-) diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/loading/ClassInjector.java b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/loading/ClassInjector.java index 3f8750e5a98..e8cbfb3a644 100644 --- a/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/loading/ClassInjector.java +++ b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/loading/ClassInjector.java @@ -26,6 +26,7 @@ import java.security.PrivilegedAction; import java.security.ProtectionDomain; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; @@ -48,6 +49,13 @@ public interface ClassInjector { */ boolean ALLOW_EXISTING_TYPES = false; + /** + * Indicates if this class injector is available on the current VM. + * + * @return {@code true} if this injector is available on the current VM. + */ + boolean isAlive(); + /** * Injects the given types into the represented class loader. * @@ -56,11 +64,41 @@ public interface ClassInjector { */ Map> inject(Map types); + /** + * Injects the given types into the represented class loader using a mapping from name to binary representation. + * + * @param types The types to load via injection. + * @return The loaded types that were passed as arguments. + */ + Map> injectRaw(Map types); + + /** + * An abstract base implementation of a class injector. + */ + abstract class AbstractBase implements ClassInjector { + + /** + * {@inheritDoc} + */ + public Map> inject(Map types) { + Map binaryRepresentations = new LinkedHashMap(); + for (Map.Entry entry : types.entrySet()) { + binaryRepresentations.put(entry.getKey().getName(), entry.getValue()); + } + Map> loadedTypes = injectRaw(binaryRepresentations); + Map> result = new LinkedHashMap>(); + for (TypeDescription typeDescription : types.keySet()) { + result.put(typeDescription, loadedTypes.get(typeDescription.getName())); + } + return result; + } + } + /** * A class injector that uses reflective method calls. */ @HashCodeAndEqualsPlugin.Enhance - class UsingReflection implements ClassInjector { + class UsingReflection extends AbstractBase { /** * The dispatcher to use for accessing a class loader via reflection. @@ -134,38 +172,26 @@ public UsingReflection(ClassLoader classLoader, } /** - * Indicates if this class injection is available on the current VM. - * - * @return {@code true} if this class injection is available. - */ - public static boolean isAvailable() { - return DISPATCHER.isAvailable(); - } - - /** - * Creates a class injector for the system class loader. - * - * @return A class injector for the system class loader. + * {@inheritDoc} */ - public static ClassInjector ofSystemClassLoader() { - return new UsingReflection(ClassLoader.getSystemClassLoader()); + public boolean isAlive() { + return isAvailable(); } /** * {@inheritDoc} */ - public Map> inject(Map types) { + public Map> injectRaw(Map types) { Dispatcher dispatcher = DISPATCHER.initialize(); - Map> loadedTypes = new HashMap>(); - for (Map.Entry entry : types.entrySet()) { - String typeName = entry.getKey().getName(); - synchronized (dispatcher.getClassLoadingLock(classLoader, typeName)) { - Class type = dispatcher.findClass(classLoader, typeName); + Map> result = new HashMap>(); + for (Map.Entry entry : types.entrySet()) { + synchronized (dispatcher.getClassLoadingLock(classLoader, entry.getKey())) { + Class type = dispatcher.findClass(classLoader, entry.getKey()); if (type == null) { - int packageIndex = typeName.lastIndexOf('.'); + int packageIndex = entry.getKey().lastIndexOf('.'); if (packageIndex != -1) { - String packageName = typeName.substring(0, packageIndex); - PackageDefinitionStrategy.Definition definition = packageDefinitionStrategy.define(classLoader, packageName, typeName); + String packageName = entry.getKey().substring(0, packageIndex); + PackageDefinitionStrategy.Definition definition = packageDefinitionStrategy.define(classLoader, packageName, entry.getKey()); if (definition.isDefined()) { Package definedPackage = dispatcher.getPackage(classLoader, packageName); if (definedPackage == null) { @@ -183,14 +209,32 @@ public Map> inject(Map */ @HashCodeAndEqualsPlugin.Enhance - class UsingLookup implements ClassInjector { + class UsingLookup extends AbstractBase { /** * The dispatcher to interacting with method handles. @@ -1286,15 +1330,6 @@ public static UsingLookup of(Object lookup) { return new UsingLookup(DISPATCHER.dropLookupMode(lookup, Opcodes.ACC_PRIVATE)); } - /** - * Checks if the current VM is capable of defining classes using a method handle lookup. - * - * @return {@code true} if the current VM is capable of defining classes using a lookup. - */ - public static boolean isAvailable() { - return DISPATCHER.isAlive(); - } - /** * Returns the lookup type this injector is based upon. * @@ -1317,15 +1352,32 @@ public UsingLookup in(Class type) { /** * {@inheritDoc} */ - public Map> inject(Map types) { - Map> loaded = new HashMap>(); - for (Map.Entry entry : types.entrySet()) { - if (!entry.getKey().isSamePackage(TypeDescription.ForLoadedType.of(lookupType()))) { + public boolean isAlive() { + return isAvailable(); + } + + /** + * {@inheritDoc} + */ + public Map> injectRaw(Map types) { + Map> result = new HashMap>(); + for (Map.Entry entry : types.entrySet()) { + int index = entry.getKey().lastIndexOf('.'); + if (index == -1 || !entry.getKey().substring(0, index).equals(TypeDescription.ForLoadedType.of(lookupType()).getPackage().getName())) { throw new IllegalArgumentException(entry.getKey() + " must be defined in the same package as " + lookup); } - loaded.put(entry.getKey(), DISPATCHER.defineClass(lookup, entry.getValue())); + result.put(entry.getKey(), DISPATCHER.defineClass(lookup, entry.getValue())); } - return loaded; + return result; + } + + /** + * Checks if the current VM is capable of defining classes using a method handle lookup. + * + * @return {@code true} if the current VM is capable of defining classes using a lookup. + */ + public static boolean isAvailable() { + return DISPATCHER.isAlive(); } /** @@ -1601,7 +1653,7 @@ public Class defineClass(Object lookup, byte[] binaryRepresentation) { *

*/ @HashCodeAndEqualsPlugin.Enhance - class UsingUnsafe implements ClassInjector { + class UsingUnsafe extends AbstractBase { /** * The dispatcher to use. @@ -1645,6 +1697,33 @@ public UsingUnsafe(ClassLoader classLoader, ProtectionDomain protectionDomain) { this.protectionDomain = protectionDomain; } + /** + * {@inheritDoc} + */ + public boolean isAlive() { + return isAvailable(); + } + + /** + * {@inheritDoc} + */ + public Map> injectRaw(Map types) { + Dispatcher dispatcher = DISPATCHER.initialize(); + Map> result = new HashMap>(); + synchronized (classLoader == null + ? BOOTSTRAP_LOADER_LOCK + : classLoader) { + for (Map.Entry entry : types.entrySet()) { + try { + result.put(entry.getKey(), Class.forName(entry.getKey(), false, classLoader)); + } catch (ClassNotFoundException ignored) { + result.put(entry.getKey(), dispatcher.defineClass(classLoader, entry.getKey(), entry.getValue(), protectionDomain)); + } + } + } + return result; + } + /** * Checks if unsafe class injection is available on the current VM. * @@ -1672,26 +1751,6 @@ public static ClassInjector ofClassPath() { return new UsingUnsafe(ClassLoader.getSystemClassLoader()); } - /** - * {@inheritDoc} - */ - public Map> inject(Map types) { - Dispatcher dispatcher = DISPATCHER.initialize(); - Map> loaded = new HashMap>(); - synchronized (classLoader == null - ? BOOTSTRAP_LOADER_LOCK - : classLoader) { - for (Map.Entry entry : types.entrySet()) { - try { - loaded.put(entry.getKey(), Class.forName(entry.getKey().getName(), false, classLoader)); - } catch (ClassNotFoundException ignored) { - loaded.put(entry.getKey(), dispatcher.defineClass(classLoader, entry.getKey().getName(), entry.getValue(), protectionDomain)); - } - } - } - return loaded; - } - /** * A dispatcher for using {@code sun.misc.Unsafe}. */ @@ -1863,7 +1922,7 @@ public Dispatcher initialize() { * or the system class path. */ @HashCodeAndEqualsPlugin.Enhance - class UsingInstrumentation implements ClassInjector { + class UsingInstrumentation extends AbstractBase { /** * The jar file name extension. @@ -1900,27 +1959,6 @@ class UsingInstrumentation implements ClassInjector { */ private final RandomString randomString; - /** - * Creates an instrumentation-based class injector. - * - * @param folder The folder to be used for storing jar files. - * @param target A representation of the target path to which classes are to be appended. - * @param instrumentation The instrumentation to use for appending to the class path or the boot path. - * @return An appropriate class injector that applies instrumentation. - */ - public static ClassInjector of(File folder, Target target, Instrumentation instrumentation) { - return new UsingInstrumentation(folder, target, instrumentation, new RandomString()); - } - - /** - * Returns {@code true} if this class injector is available on this VM. - * - * @return {@code true} if this class injector is available on this VM. - */ - public static boolean isAvailable() { - return DISPATCHER.isAlive(); - } - /** * Creates an instrumentation-based class injector. * @@ -1939,10 +1977,29 @@ protected UsingInstrumentation(File folder, this.randomString = randomString; } + /** + * Creates an instrumentation-based class injector. + * + * @param folder The folder to be used for storing jar files. + * @param target A representation of the target path to which classes are to be appended. + * @param instrumentation The instrumentation to use for appending to the class path or the boot path. + * @return An appropriate class injector that applies instrumentation. + */ + public static ClassInjector of(File folder, Target target, Instrumentation instrumentation) { + return new UsingInstrumentation(folder, target, instrumentation, new RandomString()); + } + /** * {@inheritDoc} */ - public Map> inject(Map types) { + public boolean isAlive() { + return isAvailable(); + } + + /** + * {@inheritDoc} + */ + public Map> injectRaw(Map types) { File jarFile = new File(folder, JAR + randomString.nextString() + "." + JAR); try { if (!jarFile.createNewFile()) { @@ -1950,19 +2007,19 @@ public Map> inject(Map entry : types.entrySet()) { - jarOutputStream.putNextEntry(new JarEntry(entry.getKey().getInternalName() + CLASS_FILE_EXTENSION)); + for (Map.Entry entry : types.entrySet()) { + jarOutputStream.putNextEntry(new JarEntry(entry.getKey().replace('.', '/') + CLASS_FILE_EXTENSION)); jarOutputStream.write(entry.getValue()); } } finally { jarOutputStream.close(); } target.inject(instrumentation, new JarFile(jarFile)); - Map> loaded = new HashMap>(); - for (TypeDescription typeDescription : types.keySet()) { - loaded.put(typeDescription, Class.forName(typeDescription.getName(), false, ClassLoader.getSystemClassLoader())); + Map> result = new HashMap>(); + for (String name : types.keySet()) { + result.put(name, Class.forName(name, false, ClassLoader.getSystemClassLoader())); } - return loaded; + return result; } catch (IOException exception) { throw new IllegalStateException("Cannot write jar file to disk", exception); } catch (ClassNotFoundException exception) { @@ -1970,6 +2027,15 @@ public Map> inject(Map type = new ByteBuddy().rebase(Bar.class) + Class type = new ByteBuddy() + .rebase(Bar.class) .method(named(BAR)) .intercept(MethodDelegation.to(Interceptor.class)).make() .load(classLoader, ClassLoadingStrategy.Default.INJECTION) @@ -211,6 +212,7 @@ public void testInjectionOrderNoPrematureAuxiliaryInjection() throws Exception { @ClassReflectionInjectionAvailableRule.Enforce public void testAvailability() throws Exception { assertThat(ClassInjector.UsingReflection.isAvailable(), is(true)); + assertThat(new ClassInjector.UsingReflection(ClassLoader.getSystemClassLoader()).isAlive(), is(true)); assertThat(new ClassInjector.UsingReflection.Dispatcher.Initializable.Unavailable(null).isAvailable(), is(false)); } diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ClassInjectorUsingUnsafeTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ClassInjectorUsingUnsafeTest.java index ac154db3f6e..e2eb9105e89 100644 --- a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ClassInjectorUsingUnsafeTest.java +++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ClassInjectorUsingUnsafeTest.java @@ -42,6 +42,7 @@ public void testUnsafeInjection() throws Exception { @ClassUnsafeInjectionAvailableRule.Enforce public void testAvailability() throws Exception { assertThat(ClassInjector.UsingUnsafe.isAvailable(), is(true)); + assertThat(new ClassInjector.UsingUnsafe(ClassLoader.getSystemClassLoader()).isAlive(), is(true)); assertThat(new ClassInjector.UsingUnsafe.Dispatcher.Disabled(null).isAvailable(), is(false)); }