diff -Nru byte-buddy-1.11.4/byte-buddy/pom.xml byte-buddy-1.12.21/byte-buddy/pom.xml --- byte-buddy-1.11.4/byte-buddy/pom.xml 2021-06-19 21:31:07.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy/pom.xml 2023-01-04 22:45:00.000000000 +0000 @@ -5,7 +5,7 @@ net.bytebuddy byte-buddy-parent - 1.11.4 + 1.12.21 byte-buddy @@ -28,7 +28,7 @@ org.objectweb.asm net.bytebuddy.jar.asm - + net.bytebuddy, net.bytebuddy.agent.builder, net.bytebuddy.asm, @@ -60,12 +60,17 @@ net.bytebuddy.matcher, net.bytebuddy.pool, net.bytebuddy.utility, + net.bytebuddy.utility.nullability, net.bytebuddy.utility.privilege, net.bytebuddy.utility.visitor, ${shade.target}, ${shade.target}.signature, ${shade.target}.commons - + + + net.bytebuddy.utility.dispatcher + + true - - org.apache.maven.plugins - maven-shade-plugin - ${version.plugin.shade} - - - package - - shade - - - false - true - ${bytebuddy.extras} - true - - - ${shade.source} - ${shade.target} - - - - - net.bytebuddy:byte-buddy-dep:* - - META-INF/MANIFEST.MF - - - - org.ow2.asm:* - - META-INF/MANIFEST.MF - **/module-info.class - **/LICENSE - **/NOTICE - - - - org.ow2.asm:asm-commons - - org/objectweb/asm/commons/AnnotationRemapper.** - org/objectweb/asm/commons/ClassRemapper.** - org/objectweb/asm/commons/FieldRemapper.** - org/objectweb/asm/commons/MethodRemapper.** - org/objectweb/asm/commons/ModuleHashesAttribute.** - org/objectweb/asm/commons/ModuleRemapper.** - org/objectweb/asm/commons/RecordComponentRemapper.** - org/objectweb/asm/commons/Remapper.** - org/objectweb/asm/commons/SignatureRemapper.** - org/objectweb/asm/commons/SimpleRemapper.** - - - - - - net.bytebuddy.build.Plugin$Engine$Default - - - META-INF/LICENSE - - - - - - - - org.ow2.asm - asm - ${version.asm} - - - org.ow2.asm - asm-commons - ${version.asm} - - - org.pitest @@ -219,15 +145,15 @@ manifest + + + true + + ${packages.list.external} + + - - - true - - ${packages.list} - - @@ -243,27 +169,45 @@ - package + prepare-package - inject-module + make-module + + ${modulemaker.skip} + ${project.groupId} + ${project.version} + true + ${packages.list.external},${packages.list.internal} + ${packages.list.external} + + java.instrument, + java.management, + jdk.unsupported, + net.bytebuddy.agent, + com.sun.jna, + com.sun.jna.platform + + - - net.bytebuddy - true - ${packages.list} - ${packages.list} - - java.instrument, - jdk.unsupported, - net.bytebuddy.agent, - com.sun.jna, - com.sun.jna.platform - - + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${version.plugin.javadoc} + + true + + ${project.groupId}:byte-buddy-dep + + + + + @@ -283,17 +227,186 @@ true - + + + + + shade-current + + [1.8,) + + + + + + org.apache.maven.plugins + maven-shade-plugin + ${version.plugin.shade} + + + package + + shade + + + false + true + ${bytebuddy.extras} + true + + + ${shade.source} + ${shade.target} + + + + + net.bytebuddy:byte-buddy-dep:* + + META-INF/MANIFEST.MF + META-INF/maven/** + + + + org.ow2.asm:* + + META-INF/MANIFEST.MF + **/module-info.class + **/LICENSE + **/NOTICE + + + + org.ow2.asm:asm-commons + + org/objectweb/asm/commons/AnnotationRemapper.** + org/objectweb/asm/commons/ClassRemapper.** + org/objectweb/asm/commons/FieldRemapper.** + org/objectweb/asm/commons/MethodRemapper.** + org/objectweb/asm/commons/ModuleHashesAttribute.** + org/objectweb/asm/commons/ModuleRemapper.** + org/objectweb/asm/commons/RecordComponentRemapper.** + org/objectweb/asm/commons/Remapper.** + org/objectweb/asm/commons/SignatureRemapper.** + org/objectweb/asm/commons/SimpleRemapper.** + + + + + + net.bytebuddy.build.Plugin$Engine$Default + + + sources-jar + + + + META-INF/LICENSE + + + + + + + + org.ow2.asm + asm + ${version.asm} + + + org.ow2.asm + asm-commons + ${version.asm} + + + + + + + + shade-legacy + + (,1.7] + + + + org.apache.maven.plugins - maven-javadoc-plugin - ${version.plugin.javadoc} - - true - - ${project.groupId}:byte-buddy-dep - - + maven-shade-plugin + ${version.plugin.shade} + + + package + + shade + + + false + true + ${bytebuddy.extras} + true + + + ${shade.source} + ${shade.target} + + + + + net.bytebuddy:byte-buddy-dep:* + + META-INF/MANIFEST.MF + + + + org.ow2.asm:* + + META-INF/MANIFEST.MF + **/module-info.class + **/LICENSE + **/NOTICE + + + + org.ow2.asm:asm-commons + + org/objectweb/asm/commons/AnnotationRemapper.** + org/objectweb/asm/commons/ClassRemapper.** + org/objectweb/asm/commons/FieldRemapper.** + org/objectweb/asm/commons/MethodRemapper.** + org/objectweb/asm/commons/ModuleHashesAttribute.** + org/objectweb/asm/commons/ModuleRemapper.** + org/objectweb/asm/commons/RecordComponentRemapper.** + org/objectweb/asm/commons/Remapper.** + org/objectweb/asm/commons/SignatureRemapper.** + org/objectweb/asm/commons/SimpleRemapper.** + + + + + + net.bytebuddy.build.Plugin$Engine$Default + + + META-INF/LICENSE + + + + + + + + org.ow2.asm + asm + ${version.asm} + + + org.ow2.asm + asm-commons + ${version.asm} + + diff -Nru byte-buddy-1.11.4/byte-buddy-agent/pom.xml byte-buddy-1.12.21/byte-buddy-agent/pom.xml --- byte-buddy-1.11.4/byte-buddy-agent/pom.xml 2021-06-19 21:31:07.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-agent/pom.xml 2023-01-04 22:45:00.000000000 +0000 @@ -3,9 +3,9 @@ 4.0.0 - byte-buddy-parent net.bytebuddy - 1.11.4 + byte-buddy-parent + 1.12.21 byte-buddy-agent @@ -15,9 +15,10 @@ net.bytebuddy.agent.Installer com.sun.tools.attach com.ibm.tools.attach - net.bytebuddy.agent + net.bytebuddy.agent,net.bytebuddy.agent.utility.nullability i686-w64-mingw32-gcc x86_64-w64-mingw32-gcc + Byte Buddy agent @@ -33,13 +34,13 @@ net.java.dev.jna jna - ${jna.version} + ${version.jna} provided net.java.dev.jna jna-platform - ${jna.version} + ${version.jna} provided @@ -68,7 +69,7 @@ net.bytebuddy byte-buddy - 1.11.3 + 1.12.20 test @@ -89,6 +90,15 @@ + + + org.apache.maven.plugins + maven-surefire-plugin + ${version.plugin.surefire} + + -Djna.library.path=${net.bytebuddy.test.jnapath} + + org.apache.maven.plugins @@ -111,23 +121,23 @@ manifest + + + true + ${bytebuddy.agent} + ${bytebuddy.agent} + true + true + true + + ${attach.package.sun};resolution:="optional", + ${attach.package.ibm};resolution:="optional" + + ${packages.list} + + - - - true - ${bytebuddy.agent} - ${bytebuddy.agent} - true - true - true - - ${attach.package.sun};resolution:="optional", - ${attach.package.ibm};resolution:="optional" - - ${packages.list} - - @@ -143,24 +153,26 @@ - package + prepare-package - inject-module + make-module + + ${modulemaker.skip} + ${project.groupId}.agent + ${project.version} + true + ${packages.list} + ${packages.list} + java.instrument + + jdk.attach, + com.sun.jna, + com.sun.jna.platform + + - - net.bytebuddy.agent - true - ${packages.list} - ${packages.list} - java.instrument - - jdk.attach, - com.sun.jna, - com.sun.jna.platform - - @@ -216,5 +228,4 @@ - diff -Nru byte-buddy-1.11.4/byte-buddy-agent/src/main/java/net/bytebuddy/agent/Attacher.java byte-buddy-1.12.21/byte-buddy-agent/src/main/java/net/bytebuddy/agent/Attacher.java --- byte-buddy-1.11.4/byte-buddy-agent/src/main/java/net/bytebuddy/agent/Attacher.java 2021-06-19 21:31:07.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-agent/src/main/java/net/bytebuddy/agent/Attacher.java 2023-01-04 22:45:00.000000000 +0000 @@ -16,7 +16,10 @@ package net.bytebuddy.agent; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import net.bytebuddy.agent.utility.nullability.MaybeNull; +import java.io.FileOutputStream; +import java.io.PrintStream; import java.lang.reflect.InvocationTargetException; /** @@ -25,29 +28,9 @@ public class Attacher { /** - * Base for access to a reflective member to make the code more readable. + * Indicates that any error during attachment should be dumped to a given file location. */ - private static final Object STATIC_MEMBER = null; - - /** - * The name of the {@code attach} method of the {@code VirtualMachine} class. - */ - private static final String ATTACH_METHOD_NAME = "attach"; - - /** - * The name of the {@code loadAgent} method of the {@code VirtualMachine} class. - */ - private static final String LOAD_AGENT_METHOD_NAME = "loadAgent"; - - /** - * The name of the {@code loadAgentPath} method of the {@code VirtualMachine} class. - */ - private static final String LOAD_AGENT_PATH_METHOD_NAME = "loadAgentPath"; - - /** - * The name of the {@code detach} method of the {@code VirtualMachine} class. - */ - private static final String DETACH_METHOD_NAME = "detach"; + public static final String DUMP_PROPERTY = "net.bytebuddy.agent.attacher.dump"; /** * The attacher provides only {@code static} utility methods and should not be instantiated. @@ -65,7 +48,7 @@ * of strings where the first argument is proceeded by any single character * which is stripped off. */ - @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback") + @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback.") public static void main(String[] args) { try { String argument; @@ -79,7 +62,20 @@ argument = stringBuilder.toString(); } install(Class.forName(args[0]), args[1], args[2], Boolean.parseBoolean(args[3]), argument); - } catch (Throwable ignored) { + } catch (Throwable throwable) { + try { + String property = System.getProperty(DUMP_PROPERTY); + if (property != null && property.length() > 0) { + PrintStream outputStream = new PrintStream(new FileOutputStream(property, true), false, "UTF-8"); + try { + throwable.printStackTrace(outputStream); + } finally { + outputStream.close(); + } + } + } catch (Throwable ignored) { + /* do nothing */ + } System.exit(1); } } @@ -100,17 +96,17 @@ String processId, String agent, boolean isNative, - String argument) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + @MaybeNull String argument) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Object virtualMachineInstance = virtualMachineType - .getMethod(ATTACH_METHOD_NAME, String.class) - .invoke(STATIC_MEMBER, processId); + .getMethod("attach", String.class) + .invoke(null, processId); try { virtualMachineType - .getMethod(isNative ? LOAD_AGENT_PATH_METHOD_NAME : LOAD_AGENT_METHOD_NAME, String.class, String.class) + .getMethod(isNative ? "loadAgentPath" : "loadAgent", String.class, String.class) .invoke(virtualMachineInstance, agent, argument); } finally { virtualMachineType - .getMethod(DETACH_METHOD_NAME) + .getMethod("detach") .invoke(virtualMachineInstance); } } diff -Nru byte-buddy-1.11.4/byte-buddy-agent/src/main/java/net/bytebuddy/agent/ByteBuddyAgent.java byte-buddy-1.12.21/byte-buddy-agent/src/main/java/net/bytebuddy/agent/ByteBuddyAgent.java --- byte-buddy-1.11.4/byte-buddy-agent/src/main/java/net/bytebuddy/agent/ByteBuddyAgent.java 2021-06-19 21:31:07.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-agent/src/main/java/net/bytebuddy/agent/ByteBuddyAgent.java 2023-01-04 22:45:00.000000000 +0000 @@ -16,10 +16,11 @@ package net.bytebuddy.agent; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import net.bytebuddy.agent.utility.nullability.AlwaysNull; +import net.bytebuddy.agent.utility.nullability.MaybeNull; import java.io.*; import java.lang.instrument.Instrumentation; -import java.lang.management.ManagementFactory; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.MalformedURLException; @@ -52,14 +53,15 @@ * Important: This class's name is known to the Byte Buddy main application and must not be altered. *

*

- * Note: Byte Buddy does not execute code using an {@link java.security.AccessController}. If a security manager - * is present, the user of this class is responsible for assuring any required privileges. + * Note: Byte Buddy does not execute code using an {@code java.security.AccessController}. If a security manager + * is present, the user of this class is responsible for assuring any required privileges. To read an + * {@link Instrumentation}, the net.bytebuddy.agent.getInstrumentation runtime permission is required. *

*/ public class ByteBuddyAgent { /** - * Indicates that the agent should not resolve it's own code location for a self-attachment. + * Indicates that the agent should not resolve its own code location for a self-attachment. */ public static final String LATENT_RESOLVE = "net.bytebuddy.agent.latent"; @@ -91,7 +93,7 @@ /** * The size of the buffer for copying the agent installer file into another jar. */ - private static final int BUFFER_SIZE = 1024; + private static final int BUFFER_SIZE = 1024 * 8; /** * Convenience indices for reading and writing to the buffer to make the code more readable. @@ -104,18 +106,15 @@ private static final int SUCCESSFUL_ATTACH = 0; /** - * Base for access to a reflective member to make the code more readable. - */ - private static final Object STATIC_MEMBER = null; - - /** * Representation of the bootstrap {@link java.lang.ClassLoader}. */ + @AlwaysNull private static final ClassLoader BOOTSTRAP_CLASS_LOADER = null; /** * Represents a no-op argument for a dynamic agent attachment. */ + @AlwaysNull private static final String WITHOUT_ARGUMENT = null; /** @@ -161,11 +160,13 @@ /** * An indicator variable to express that no instrumentation is available. */ + @AlwaysNull private static final Instrumentation UNAVAILABLE = null; /** * Represents a failed attempt to self-resolve a jar file location. */ + @AlwaysNull private static final File CANNOT_SELF_RESOLVE = null; /** @@ -196,7 +197,7 @@ } catch (ClassNotFoundException ignored) { return action.run(); } catch (InvocationTargetException exception) { - throw new IllegalStateException("Failed to invoke access controller", exception.getCause()); + throw new IllegalStateException("Failed to invoke access controller", exception.getTargetException()); } catch (IllegalAccessException exception) { throw new IllegalStateException("Failed to access access controller", exception); } catch (NoSuchMethodException exception) { @@ -221,9 +222,10 @@ public static Instrumentation getInstrumentation() { Instrumentation instrumentation = doGetInstrumentation(); if (instrumentation == null) { - throw new IllegalStateException("The Byte Buddy agent is not initialized"); + throw new IllegalStateException("The Byte Buddy agent is not initialized or unavailable"); + } else { + return instrumentation; } - return instrumentation; } /** @@ -257,7 +259,7 @@ * @param processId The target process id. * @param argument The argument to provide to the agent. */ - public static void attach(File agentJar, String processId, String argument) { + public static void attach(File agentJar, String processId, @MaybeNull String argument) { attach(agentJar, processId, argument, AttachmentProvider.DEFAULT); } @@ -292,7 +294,7 @@ * @param argument The argument to provide to the agent. * @param attachmentProvider The attachment provider to use. */ - public static void attach(File agentJar, String processId, String argument, AttachmentProvider attachmentProvider) { + public static void attach(File agentJar, String processId, @MaybeNull String argument, AttachmentProvider attachmentProvider) { install(attachmentProvider, processId, argument, new AgentProvider.ForExistingAgent(agentJar), false); } @@ -327,7 +329,7 @@ * @param processProvider A provider of the target process id. * @param argument The argument to provide to the agent. */ - public static void attach(File agentJar, ProcessProvider processProvider, String argument) { + public static void attach(File agentJar, ProcessProvider processProvider, @MaybeNull String argument) { attach(agentJar, processProvider, argument, AttachmentProvider.DEFAULT); } @@ -362,7 +364,7 @@ * @param argument The argument to provide to the agent. * @param attachmentProvider The attachment provider to use. */ - public static void attach(File agentJar, ProcessProvider processProvider, String argument, AttachmentProvider attachmentProvider) { + public static void attach(File agentJar, ProcessProvider processProvider, @MaybeNull String argument, AttachmentProvider attachmentProvider) { install(attachmentProvider, processProvider.resolve(), argument, new AgentProvider.ForExistingAgent(agentJar), false); } @@ -397,7 +399,7 @@ * @param processId The target process id. * @param argument The argument to provide to the agent. */ - public static void attachNative(File agentLibrary, String processId, String argument) { + public static void attachNative(File agentLibrary, String processId, @MaybeNull String argument) { attachNative(agentLibrary, processId, argument, AttachmentProvider.DEFAULT); } @@ -432,7 +434,7 @@ * @param argument The argument to provide to the agent. * @param attachmentProvider The attachment provider to use. */ - public static void attachNative(File agentLibrary, String processId, String argument, AttachmentProvider attachmentProvider) { + public static void attachNative(File agentLibrary, String processId, @MaybeNull String argument, AttachmentProvider attachmentProvider) { install(attachmentProvider, processId, argument, new AgentProvider.ForExistingAgent(agentLibrary), true); } @@ -467,7 +469,7 @@ * @param processProvider A provider of the target process id. * @param argument The argument to provide to the agent. */ - public static void attachNative(File agentLibrary, ProcessProvider processProvider, String argument) { + public static void attachNative(File agentLibrary, ProcessProvider processProvider, @MaybeNull String argument) { attachNative(agentLibrary, processProvider, argument, AttachmentProvider.DEFAULT); } @@ -502,7 +504,7 @@ * @param argument The argument to provide to the agent. * @param attachmentProvider The attachment provider to use. */ - public static void attachNative(File agentLibrary, ProcessProvider processProvider, String argument, AttachmentProvider attachmentProvider) { + public static void attachNative(File agentLibrary, ProcessProvider processProvider, @MaybeNull String argument, AttachmentProvider attachmentProvider) { install(attachmentProvider, processProvider.resolve(), argument, new AgentProvider.ForExistingAgent(agentLibrary), true); } @@ -607,7 +609,7 @@ return instrumentation; } install(attachmentProvider, processProvider.resolve(), WITHOUT_ARGUMENT, AgentProvider.ForByteBuddyAgent.INSTANCE, false); - return doGetInstrumentation(); + return getInstrumentation(); } /** @@ -619,7 +621,7 @@ * @param agentProvider The agent provider for the agent jar or library. * @param isNative {@code true} if the agent is native. */ - private static void install(AttachmentProvider attachmentProvider, String processId, String argument, AgentProvider agentProvider, boolean isNative) { + private static void install(AttachmentProvider attachmentProvider, String processId, @MaybeNull String argument, AgentProvider agentProvider, boolean isNative) { AttachmentProvider.Accessor attachmentAccessor = attachmentProvider.attempt(); if (!attachmentAccessor.isAvailable()) { throw new IllegalStateException("No compatible attachment provider is available"); @@ -652,7 +654,7 @@ String processId, File agent, boolean isNative, - String argument) throws Exception { + @MaybeNull String argument) throws Exception { File selfResolvedJar = trySelfResolve(), attachmentJar = null; try { if (selfResolvedJar == null) { @@ -678,21 +680,22 @@ inputStream.close(); } } - StringBuilder classPath = new StringBuilder().append(quote((selfResolvedJar == null + StringBuilder classPath = new StringBuilder().append((selfResolvedJar == null ? attachmentJar - : selfResolvedJar).getCanonicalPath())); + : selfResolvedJar).getCanonicalPath()); for (File jar : externalAttachment.getClassPath()) { - classPath.append(File.pathSeparatorChar).append(quote(jar.getCanonicalPath())); + classPath.append(File.pathSeparatorChar).append(jar.getCanonicalPath()); } if (new ProcessBuilder(System.getProperty(JAVA_HOME) + File.separatorChar + "bin" + File.separatorChar + (System.getProperty(OS_NAME, "").toLowerCase(Locale.US).contains("windows") ? "java.exe" : "java"), + "-D" + Attacher.DUMP_PROPERTY + "=" + System.getProperty(Attacher.DUMP_PROPERTY, ""), CLASS_PATH_ARGUMENT, classPath.toString(), Attacher.class.getName(), externalAttachment.getVirtualMachineType(), processId, - quote(agent.getAbsolutePath()), + agent.getAbsolutePath(), Boolean.toString(isNative), argument == null ? "" : ("=" + argument)).start().waitFor() != SUCCESSFUL_ATTACH) { throw new IllegalStateException("Could not self-attach to current VM using external process"); @@ -711,7 +714,8 @@ * * @return The self-resolved jar file or {@code null} if the jar file cannot be located. */ - @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback") + @MaybeNull + @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback.") private static File trySelfResolve() { try { if (Boolean.getBoolean(LATENT_RESOLVE)) { @@ -740,30 +744,29 @@ } /** - * Quotes a value if it contains a white space. - * - * @param value The value to quote. - * @return The value being quoted if necessary. - */ - private static String quote(String value) { - return value.contains(" ") - ? '"' + value + '"' - : value; - } - - /** * Performs the actual lookup of the {@link java.lang.instrument.Instrumentation} from an installed - * Byte Buddy agent. + * Byte Buddy agent and returns the instance, or returns {@code null} if not present. * * @return The Byte Buddy agent's {@link java.lang.instrument.Instrumentation} instance. */ - @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Legal outcome where reflection communicates errors by throwing an exception") + @MaybeNull + @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback.") private static Instrumentation doGetInstrumentation() { try { - return (Instrumentation) ClassLoader.getSystemClassLoader() - .loadClass(Installer.class.getName()) + Class installer = Class.forName(Installer.class.getName(), true, ClassLoader.getSystemClassLoader()); + try { + Class module = Class.forName("java.lang.Module"); + Method getModule = Class.class.getMethod("getModule"); + Object source = getModule.invoke(ByteBuddyAgent.class), target = getModule.invoke(installer); + if (!((Boolean) module.getMethod("canRead", module).invoke(source, target))) { + module.getMethod("addReads", module).invoke(source, target); + } + } catch (ClassNotFoundException ignored) { + /* empty */ + } + return (Instrumentation) Class.forName(Installer.class.getName(), true, ClassLoader.getSystemClassLoader()) .getMethod(INSTRUMENTATION_METHOD) - .invoke(STATIC_MEMBER); + .invoke(null); } catch (Exception ignored) { return UNAVAILABLE; } @@ -952,10 +955,11 @@ * @param classPath The class path required to load the virtual machine class. * @return An appropriate accessor. */ - public static Accessor of(ClassLoader classLoader, File... classPath) { + public static Accessor of(@MaybeNull ClassLoader classLoader, File... classPath) { try { - return new Simple.WithExternalAttachment(classLoader.loadClass(VIRTUAL_MACHINE_TYPE_NAME), - Arrays.asList(classPath)); + return new Simple.WithExternalAttachment(Class.forName(VIRTUAL_MACHINE_TYPE_NAME, + false, + classLoader), Arrays.asList(classPath)); } catch (ClassNotFoundException ignored) { return Unavailable.INSTANCE; } @@ -1146,7 +1150,7 @@ /** * {@inheritDoc} */ - @SuppressFBWarnings(value = "DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED", justification = "Privilege is explicit user responsibility") + @SuppressFBWarnings(value = "DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED", justification = "Assuring privilege is explicit user responsibility.") public Accessor attempt() { File toolsJar = new File(System.getProperty(JAVA_HOME_PROPERTY), toolsJarPath); try { @@ -1177,7 +1181,7 @@ /** * {@inheritDoc} */ - @SuppressFBWarnings(value = "DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED", justification = "Privilege is explicit user responsibility") + @SuppressFBWarnings(value = "DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED", justification = "Assuring privilege is explicit user responsibility.") public Accessor attempt() { String location = System.getProperty(PROPERTY); if (location == null) { @@ -1309,7 +1313,9 @@ } /** - * A process provider for a legacy VM that reads the process id from its JMX properties. + * A process provider for a legacy VM that reads the process id from its JMX properties. This strategy + * is only used prior to Java 9 such that the java.management module never is resolved, even if + * the module system is used, as the module system was not available in any relevant JVM version. */ protected enum ForLegacyVm implements ProcessProvider { @@ -1321,8 +1327,15 @@ /** * {@inheritDoc} */ + @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback.") public String resolve() { - String runtimeName = ManagementFactory.getRuntimeMXBean().getName(); + String runtimeName; + try { + Method method = Class.forName("java.lang.management.ManagementFactory").getMethod("getRuntimeMXBean"); + runtimeName = (String) method.getReturnType().getMethod("getName").invoke(method.invoke(null)); + } catch (Exception exception) { + throw new IllegalStateException("Failed to access VM name via management factory", exception); + } int processIdIndex = runtimeName.indexOf('@'); if (processIdIndex == -1) { throw new IllegalStateException("Cannot extract process id from runtime management bean"); @@ -1364,7 +1377,7 @@ * * @return A dispatcher for the current VM. */ - @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback") + @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback.") public static ProcessProvider make() { try { return new ForJava9CapableVm(Class.forName("java.lang.ProcessHandle").getMethod("current"), @@ -1379,11 +1392,11 @@ */ public String resolve() { try { - return pid.invoke(current.invoke(STATIC_MEMBER)).toString(); + return pid.invoke(current.invoke(null)).toString(); } catch (IllegalAccessException exception) { throw new IllegalStateException("Cannot access Java 9 process API", exception); } catch (InvocationTargetException exception) { - throw new IllegalStateException("Error when accessing Java 9 process API", exception.getCause()); + throw new IllegalStateException("Error when accessing Java 9 process API", exception.getTargetException()); } } } @@ -1426,6 +1439,7 @@ * @return This jar file's location or {@code null} if this jar file's location is inaccessible. * @throws IOException If an I/O exception occurs. */ + @MaybeNull private static File trySelfResolve() throws IOException { ProtectionDomain protectionDomain = Installer.class.getProtectionDomain(); if (Boolean.getBoolean(LATENT_RESOLVE)) { @@ -1536,7 +1550,7 @@ /** * The supplied agent. */ - private File agent; + private final File agent; /** * Creates an agent provider for an existing agent. @@ -1587,7 +1601,7 @@ /** * {@inheritDoc} */ - @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback") + @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback.") public AttachmentTypeEvaluator run() { try { if (Boolean.getBoolean(JDK_ALLOW_SELF_ATTACH)) { @@ -1651,11 +1665,11 @@ */ public boolean requiresExternalAttachment(String processId) { try { - return pid.invoke(current.invoke(STATIC_MEMBER)).toString().equals(processId); + return pid.invoke(current.invoke(null)).toString().equals(processId); } catch (IllegalAccessException exception) { throw new IllegalStateException("Cannot access Java 9 process API", exception); } catch (InvocationTargetException exception) { - throw new IllegalStateException("Error when accessing Java 9 process API", exception.getCause()); + throw new IllegalStateException("Error when accessing Java 9 process API", exception.getTargetException()); } } } diff -Nru byte-buddy-1.11.4/byte-buddy-agent/src/main/java/net/bytebuddy/agent/Installer.java byte-buddy-1.12.21/byte-buddy-agent/src/main/java/net/bytebuddy/agent/Installer.java --- byte-buddy-1.11.4/byte-buddy-agent/src/main/java/net/bytebuddy/agent/Installer.java 2021-06-19 21:31:07.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-agent/src/main/java/net/bytebuddy/agent/Installer.java 2023-01-04 22:45:00.000000000 +0000 @@ -15,6 +15,8 @@ */ package net.bytebuddy.agent; +import net.bytebuddy.agent.utility.nullability.MaybeNull; + import java.lang.instrument.Instrumentation; import java.lang.reflect.InvocationTargetException; import java.security.Permission; @@ -31,6 +33,7 @@ * of the Byte Buddy agent as this class might be loaded by a different class loader than the system class * loader. */ + @MaybeNull private static volatile Instrumentation instrumentation; /** @@ -56,14 +59,16 @@ try { Object securityManager = System.class.getMethod("getSecurityManager").invoke(null); if (securityManager != null) { - securityManager.getClass() + Class.forName("java.lang.SecurityManager") .getMethod("checkPermission", Permission.class) - .invoke(securityManager, new RuntimePermission("getInstrumentation")); + .invoke(securityManager, new RuntimePermission("net.bytebuddy.agent.getInstrumentation")); } } catch (NoSuchMethodException ignored) { /* security manager not available on current VM */ + } catch (ClassNotFoundException ignored) { + /* security manager not available on current VM */ } catch (InvocationTargetException exception) { - Throwable cause = exception.getCause(); + Throwable cause = exception.getTargetException(); if (cause instanceof RuntimeException) { throw (RuntimeException) cause; } else { @@ -90,7 +95,7 @@ } /** - * Allows the installation of this agent via the Attach API. + * Allows the installation of this agent via the attach API. * * @param arguments The unused agent arguments. * @param instrumentation The instrumentation instance. diff -Nru byte-buddy-1.11.4/byte-buddy-agent/src/main/java/net/bytebuddy/agent/package-info.java byte-buddy-1.12.21/byte-buddy-agent/src/main/java/net/bytebuddy/agent/package-info.java --- byte-buddy-1.11.4/byte-buddy-agent/src/main/java/net/bytebuddy/agent/package-info.java 2021-06-19 21:31:07.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-agent/src/main/java/net/bytebuddy/agent/package-info.java 2023-01-04 22:45:00.000000000 +0000 @@ -16,4 +16,7 @@ /** * The Byte Buddy agent allows the redefinition of classes at runtime. */ +@NeverNull.ByDefault package net.bytebuddy.agent; + +import net.bytebuddy.agent.utility.nullability.NeverNull; diff -Nru byte-buddy-1.11.4/byte-buddy-agent/src/main/java/net/bytebuddy/agent/utility/nullability/AlwaysNull.java byte-buddy-1.12.21/byte-buddy-agent/src/main/java/net/bytebuddy/agent/utility/nullability/AlwaysNull.java --- byte-buddy-1.11.4/byte-buddy-agent/src/main/java/net/bytebuddy/agent/utility/nullability/AlwaysNull.java 1970-01-01 00:00:00.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-agent/src/main/java/net/bytebuddy/agent/utility/nullability/AlwaysNull.java 2023-01-04 22:45:00.000000000 +0000 @@ -0,0 +1,33 @@ +/* + * Copyright 2014 - Present Rafael Winterhalter + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.bytebuddy.agent.utility.nullability; + +import javax.annotation.Nonnull; +import javax.annotation.meta.TypeQualifierNickname; +import javax.annotation.meta.When; +import java.lang.annotation.*; + +/** + * Indicates that a field, method or parameter can only be {@code null}. + */ +@Documented +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +@Nonnull(when = When.NEVER) +@TypeQualifierNickname +public @interface AlwaysNull { + /* empty */ +} diff -Nru byte-buddy-1.11.4/byte-buddy-agent/src/main/java/net/bytebuddy/agent/utility/nullability/MaybeNull.java byte-buddy-1.12.21/byte-buddy-agent/src/main/java/net/bytebuddy/agent/utility/nullability/MaybeNull.java --- byte-buddy-1.11.4/byte-buddy-agent/src/main/java/net/bytebuddy/agent/utility/nullability/MaybeNull.java 1970-01-01 00:00:00.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-agent/src/main/java/net/bytebuddy/agent/utility/nullability/MaybeNull.java 2023-01-04 22:45:00.000000000 +0000 @@ -0,0 +1,33 @@ +/* + * Copyright 2014 - Present Rafael Winterhalter + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.bytebuddy.agent.utility.nullability; + +import javax.annotation.Nonnull; +import javax.annotation.meta.TypeQualifierNickname; +import javax.annotation.meta.When; +import java.lang.annotation.*; + +/** + * Indicates that a field, method or parameter can sometimes be {@code null}. + */ +@Documented +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +@Nonnull(when = When.MAYBE) +@TypeQualifierNickname +public @interface MaybeNull { + /* empty */ +} diff -Nru byte-buddy-1.11.4/byte-buddy-agent/src/main/java/net/bytebuddy/agent/utility/nullability/NeverNull.java byte-buddy-1.12.21/byte-buddy-agent/src/main/java/net/bytebuddy/agent/utility/nullability/NeverNull.java --- byte-buddy-1.11.4/byte-buddy-agent/src/main/java/net/bytebuddy/agent/utility/nullability/NeverNull.java 1970-01-01 00:00:00.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-agent/src/main/java/net/bytebuddy/agent/utility/nullability/NeverNull.java 2023-01-04 22:45:00.000000000 +0000 @@ -0,0 +1,45 @@ +/* + * Copyright 2014 - Present Rafael Winterhalter + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.bytebuddy.agent.utility.nullability; + +import javax.annotation.Nonnull; +import javax.annotation.meta.TypeQualifierDefault; +import javax.annotation.meta.TypeQualifierNickname; +import java.lang.annotation.*; + +/** + * Indicates that a field, method or parameter can never be {@code null}. Typically, this does not need to + * be declared explicitly but is guaranteed by {@link ByDefault}. + */ +@Documented +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +@Nonnull +@TypeQualifierNickname +public @interface NeverNull { + + /** + * Indicates that any field, method return or method and constructor parameter of a package is never {@code null}. + */ + @Documented + @Target(ElementType.PACKAGE) + @Retention(RetentionPolicy.RUNTIME) + @Nonnull + @TypeQualifierDefault({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) + @interface ByDefault { + /* empty */ + } +} diff -Nru byte-buddy-1.11.4/byte-buddy-agent/src/main/java/net/bytebuddy/agent/utility/nullability/package-info.java byte-buddy-1.12.21/byte-buddy-agent/src/main/java/net/bytebuddy/agent/utility/nullability/package-info.java --- byte-buddy-1.11.4/byte-buddy-agent/src/main/java/net/bytebuddy/agent/utility/nullability/package-info.java 1970-01-01 00:00:00.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-agent/src/main/java/net/bytebuddy/agent/utility/nullability/package-info.java 2023-01-04 22:45:00.000000000 +0000 @@ -0,0 +1,19 @@ +/* + * Copyright 2014 - Present Rafael Winterhalter + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * A package to contain nullability annotations to be used within the Byte Buddy agent project. + */ +package net.bytebuddy.agent.utility.nullability; diff -Nru byte-buddy-1.11.4/byte-buddy-agent/src/main/java/net/bytebuddy/agent/utility/nullability/UnknownNull.java byte-buddy-1.12.21/byte-buddy-agent/src/main/java/net/bytebuddy/agent/utility/nullability/UnknownNull.java --- byte-buddy-1.11.4/byte-buddy-agent/src/main/java/net/bytebuddy/agent/utility/nullability/UnknownNull.java 1970-01-01 00:00:00.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-agent/src/main/java/net/bytebuddy/agent/utility/nullability/UnknownNull.java 2023-01-04 22:45:00.000000000 +0000 @@ -0,0 +1,33 @@ +/* + * Copyright 2014 - Present Rafael Winterhalter + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.bytebuddy.agent.utility.nullability; + +import javax.annotation.Nonnull; +import javax.annotation.meta.TypeQualifierNickname; +import javax.annotation.meta.When; +import java.lang.annotation.*; + +/** + * Indicates that a field, method or parameter is undefined for its usage {@code null}. + */ +@Documented +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +@Nonnull(when = When.UNKNOWN) +@TypeQualifierNickname +public @interface UnknownNull { + /* empty */ +} diff -Nru byte-buddy-1.11.4/byte-buddy-agent/src/main/java/net/bytebuddy/agent/VirtualMachine.java byte-buddy-1.12.21/byte-buddy-agent/src/main/java/net/bytebuddy/agent/VirtualMachine.java --- byte-buddy-1.11.4/byte-buddy-agent/src/main/java/net/bytebuddy/agent/VirtualMachine.java 2021-06-19 21:31:07.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-agent/src/main/java/net/bytebuddy/agent/VirtualMachine.java 2023-01-04 22:45:00.000000000 +0000 @@ -21,6 +21,8 @@ import com.sun.jna.win32.StdCallLibrary; import com.sun.jna.win32.W32APIOptions; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import net.bytebuddy.agent.utility.nullability.MaybeNull; +import net.bytebuddy.agent.utility.nullability.UnknownNull; import java.io.*; import java.net.ServerSocket; @@ -76,7 +78,7 @@ * @param argument The argument to provide or {@code null} if no argument should be provided. * @throws IOException If an I/O exception occurs. */ - void loadAgent(String jarFile, String argument) throws IOException; + void loadAgent(String jarFile, @MaybeNull String argument) throws IOException; /** * Loads a native agent into the represented virtual machine. @@ -93,7 +95,7 @@ * @param argument The argument to provide or {@code null} if no argument should be provided. * @throws IOException If an I/O exception occurs. */ - void loadAgentPath(String path, String argument) throws IOException; + void loadAgentPath(String path, @MaybeNull String argument) throws IOException; /** * Loads a native agent library into the represented virtual machine. @@ -110,7 +112,7 @@ * @param argument The argument to provide or {@code null} if no argument should be provided. * @throws IOException If an I/O exception occurs. */ - void loadAgentLibrary(String library, String argument) throws IOException; + void loadAgentLibrary(String library, @MaybeNull String argument) throws IOException; /** * Starts a JMX management agent. @@ -331,21 +333,21 @@ /** * {@inheritDoc} */ - public void loadAgent(String jarFile, String argument) throws IOException { + public void loadAgent(String jarFile, @MaybeNull String argument) throws IOException { load(jarFile, false, argument); } /** * {@inheritDoc} */ - public void loadAgentPath(String path, String argument) throws IOException { + public void loadAgentPath(String path, @MaybeNull String argument) throws IOException { load(path, true, argument); } /** * {@inheritDoc} */ - public void loadAgentLibrary(String library, String argument) throws IOException { + public void loadAgentLibrary(String library, @MaybeNull String argument) throws IOException { load(library, false, argument); } @@ -357,7 +359,7 @@ * @param argument The argument to the agent or {@code null} if no argument is given. * @throws IOException If an I/O exception occurs. */ - protected void load(String file, boolean absolute, String argument) throws IOException { + protected void load(String file, boolean absolute, @MaybeNull String argument) throws IOException { Connection.Response response = connection.execute(PROTOCOL_VERSION, LOAD_COMMAND, INSTRUMENT_COMMAND, Boolean.toString(absolute), (argument == null ? file : file + ARGUMENT_DELIMITER + argument)); @@ -931,7 +933,7 @@ /** * Reads a configuration dependant variable into a memory segment. * - * @param name The name of the variable. + * @param name The name of the variable. * @param buffer The buffer to read the variable into. * @param length The length of the buffer. * @return The amount of bytes written to the buffer. @@ -1129,14 +1131,15 @@ * @param threadId A pointer to the thread id or {@code null} if no thread reference is set. * @return A handle to the created remote thread or {@code null} if the creation failed. */ + @MaybeNull @SuppressWarnings("checkstyle:methodname") WinNT.HANDLE CreateRemoteThread(WinNT.HANDLE process, - WinBase.SECURITY_ATTRIBUTES securityAttributes, + @MaybeNull WinBase.SECURITY_ATTRIBUTES securityAttributes, int stackSize, Pointer code, Pointer argument, - WinDef.DWORD creationFlags, - Pointer threadId); + @MaybeNull WinDef.DWORD creationFlags, + @MaybeNull Pointer threadId); /** * Receives the exit code of a given thread. @@ -1160,6 +1163,7 @@ * @param process A handle to the target process. * @return A pointer to the allocated code or {@code null} if the code could not be allocated. */ + @MaybeNull @SuppressWarnings("checkstyle:methodname") WinDef.LPVOID allocate_remote_code(WinNT.HANDLE process); @@ -1174,13 +1178,14 @@ * @param argument3 The forth argument or {@code null} if no such argument is provided. * @return A pointer to the allocated argument or {@code null} if the argument could not be allocated. */ + @MaybeNull @SuppressWarnings("checkstyle:methodname") WinDef.LPVOID allocate_remote_argument(WinNT.HANDLE process, String pipe, - String argument0, - String argument1, - String argument2, - String argument3); + @MaybeNull String argument0, + @MaybeNull String argument1, + @MaybeNull String argument2, + @MaybeNull String argument3); } /** @@ -1438,6 +1443,7 @@ /** * A pointer to the operation argument. */ + @MaybeNull public Pointer dataPointer; /** @@ -1448,6 +1454,7 @@ /** * A pointer to the operation descriptor. */ + @MaybeNull public Pointer descriptorPointer; /** @@ -1458,6 +1465,7 @@ /** * A pointer to the operation result. */ + @UnknownNull public Pointer resultPointer; /** @@ -1566,6 +1574,11 @@ private static final String IBM_TEMPORARY_FOLDER = "com.ibm.tools.attach.directory"; /** + * A secure random for generating randomized ids. + */ + private static final SecureRandom SECURE_RANDOM = new SecureRandom(); + + /** * The socket on which this VM and the target VM communicate. */ private final Socket socket; @@ -1602,7 +1615,7 @@ * @throws IOException If an IO exception occurs during establishing the connection. */ public static VirtualMachine attach(String processId, int timeout, Dispatcher dispatcher) throws IOException { - File directory = new File(System.getProperty(IBM_TEMPORARY_FOLDER, dispatcher.getTemporaryFolder()), ".com_ibm_tools_attach"); + File directory = new File(System.getProperty(IBM_TEMPORARY_FOLDER, dispatcher.getTemporaryFolder(processId)), ".com_ibm_tools_attach"); RandomAccessFile attachLock = new RandomAccessFile(new File(directory, "_attachlock"), "rw"); try { FileLock attachLockLock = attachLock.getChannel().lock(); @@ -1677,7 +1690,10 @@ try { serverSocket.setSoTimeout(timeout); File receiver = new File(directory, target.getProperty("vmId")); - String key = Long.toHexString(new SecureRandom().nextLong()); + String key; + synchronized (SECURE_RANDOM) { + key = Long.toHexString(SECURE_RANDOM.nextLong()); + } File reply = new File(receiver, "replyInfo"); try { if (reply.createNewFile()) { @@ -1787,7 +1803,7 @@ /** * {@inheritDoc} */ - public void loadAgent(String jarFile, String argument) throws IOException { + public void loadAgent(String jarFile, @MaybeNull String argument) throws IOException { write(socket, ("ATTACH_LOADAGENT(instrument," + jarFile + '=' + (argument == null ? "" : argument) + ')').getBytes("UTF-8")); String answer = new String(read(socket), "UTF-8"); if (answer.startsWith("ATTACH_ERR")) { @@ -1800,7 +1816,7 @@ /** * {@inheritDoc} */ - public void loadAgentPath(String path, String argument) throws IOException { + public void loadAgentPath(String path, @MaybeNull String argument) throws IOException { write(socket, ("ATTACH_LOADAGENTPATH(" + path + (argument == null ? "" : (',' + argument)) + ')').getBytes("UTF-8")); String answer = new String(read(socket), "UTF-8"); if (answer.startsWith("ATTACH_ERR")) { @@ -1813,7 +1829,7 @@ /** * {@inheritDoc} */ - public void loadAgentLibrary(String library, String argument) throws IOException { + public void loadAgentLibrary(String library, @MaybeNull String argument) throws IOException { write(socket, ("ATTACH_LOADAGENTLIBRARY(" + library + (argument == null ? "" : (',' + argument)) + ')').getBytes("UTF-8")); String answer = new String(read(socket), "UTF-8"); if (answer.startsWith("ATTACH_ERR")) { @@ -1911,9 +1927,10 @@ /** * Returns this machine's temporary folder. * + * @param processId The target process's id. * @return The temporary folder. */ - String getTemporaryFolder(); + String getTemporaryFolder(String processId); /** * Returns the process id of this process. @@ -2016,7 +2033,13 @@ /** * {@inheritDoc} */ - public String getTemporaryFolder() { + public String getTemporaryFolder(String processId) { + if (Platform.isLinux()) { + File file = new File("/proc/" + processId + "/root/tmp"); + if (file.isDirectory() && file.canRead()) { + return file.getAbsolutePath(); + } + } String temporaryFolder = System.getenv("TMPDIR"); return temporaryFolder == null ? "/tmp" : temporaryFolder; } @@ -2050,8 +2073,10 @@ try { // The binding for 'stat' is very platform dependant. To avoid the complexity of binding the correct method, // stat is called as a separate command. This is less efficient but more portable. - String statUserSwitch = Platform.isMac() ? "-f" : "-c"; - Process process = Runtime.getRuntime().exec("stat " + statUserSwitch + " %u " + file.getAbsolutePath()); + Process process = Runtime.getRuntime().exec(new String[]{"stat", + Platform.isMac() ? "-f" : "-c", + "%u", + file.getAbsolutePath()}); int attempts = this.attempts; boolean exited = false; String line = new BufferedReader(new InputStreamReader(process.getInputStream(), "UTF-8")).readLine(); @@ -2124,7 +2149,7 @@ library.semop(semaphore, target, 1); } catch (LastErrorException exception) { if (acceptUnavailable && (Native.getLastError() == PosixLibrary.EAGAIN - || Native.getLastError() == PosixLibrary.EDEADLK)) { + || Native.getLastError() == PosixLibrary.EDEADLK)) { break; } else { throw exception; @@ -2299,7 +2324,7 @@ /** * {@inheritDoc} */ - public String getTemporaryFolder() { + public String getTemporaryFolder(String processId) { WinDef.DWORD length = new WinDef.DWORD(WinDef.MAX_PATH); char[] path = new char[length.intValue()]; if (Kernel32.INSTANCE.GetTempPath(length, path).intValue() == 0) { @@ -2483,6 +2508,7 @@ * @param name The semaphore's name. * @return The handle or {@code null} if the handle could not be created. */ + @MaybeNull @SuppressWarnings("checkstyle:methodname") WinNT.HANDLE OpenSemaphoreW(int access, boolean inheritHandle, String name); @@ -2495,8 +2521,12 @@ * @param name The semaphore's name. * @return The handle or {@code null} if the handle could not be created. */ + @MaybeNull @SuppressWarnings("checkstyle:methodname") - WinNT.HANDLE CreateSemaphoreW(WinBase.SECURITY_ATTRIBUTES securityAttributes, long count, long maximumCount, String name); + WinNT.HANDLE CreateSemaphoreW(@MaybeNull WinBase.SECURITY_ATTRIBUTES securityAttributes, + long count, + long maximumCount, + String name); /** * Releases the semaphore. @@ -2507,7 +2537,7 @@ * @return {@code true} if the semaphore was successfully released. */ @SuppressWarnings("checkstyle:methodname") - boolean ReleaseSemaphore(WinNT.HANDLE handle, long count, Long previousCount); + boolean ReleaseSemaphore(WinNT.HANDLE handle, long count, @MaybeNull Long previousCount); /** * Create or opens a mutex. @@ -2517,6 +2547,7 @@ * @param name The mutex name. * @return The handle to the mutex or {@code null} if the mutex could not be created. */ + @MaybeNull @SuppressWarnings("checkstyle:methodname") WinNT.HANDLE CreateMutex(SecurityAttributes attributes, boolean owner, String name); @@ -2549,11 +2580,13 @@ /** * The descriptor's length. */ + @MaybeNull public WinDef.DWORD length; /** * A pointer to the descriptor. */ + @MaybeNull public Pointer securityDescriptor; /** diff -Nru byte-buddy-1.11.4/byte-buddy-agent/src/test/java/net/bytebuddy/agent/AttacherTest.java byte-buddy-1.12.21/byte-buddy-agent/src/test/java/net/bytebuddy/agent/AttacherTest.java --- byte-buddy-1.11.4/byte-buddy-agent/src/test/java/net/bytebuddy/agent/AttacherTest.java 2021-06-19 21:31:07.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-agent/src/test/java/net/bytebuddy/agent/AttacherTest.java 2023-01-04 22:45:00.000000000 +0000 @@ -46,7 +46,7 @@ constructor.newInstance(); fail(); } catch (InvocationTargetException exception) { - throw (Exception) exception.getCause(); + throw (Exception) exception.getTargetException(); } } diff -Nru byte-buddy-1.11.4/byte-buddy-agent/src/test/java/net/bytebuddy/agent/ByteBuddyAgentAttachmentTypeEvaluator.java byte-buddy-1.12.21/byte-buddy-agent/src/test/java/net/bytebuddy/agent/ByteBuddyAgentAttachmentTypeEvaluator.java --- byte-buddy-1.11.4/byte-buddy-agent/src/test/java/net/bytebuddy/agent/ByteBuddyAgentAttachmentTypeEvaluator.java 2021-06-19 21:31:07.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-agent/src/test/java/net/bytebuddy/agent/ByteBuddyAgentAttachmentTypeEvaluator.java 2023-01-04 22:45:00.000000000 +0000 @@ -2,8 +2,8 @@ import org.junit.Test; -import static org.hamcrest.CoreMatchers.*; -import static org.hamcrest.MatcherAssert.*; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; public class ByteBuddyAgentAttachmentTypeEvaluator { diff -Nru byte-buddy-1.11.4/byte-buddy-agent/src/test/java/net/bytebuddy/agent/ByteBuddyAgentTest.java byte-buddy-1.12.21/byte-buddy-agent/src/test/java/net/bytebuddy/agent/ByteBuddyAgentTest.java --- byte-buddy-1.11.4/byte-buddy-agent/src/test/java/net/bytebuddy/agent/ByteBuddyAgentTest.java 2021-06-19 21:31:07.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-agent/src/test/java/net/bytebuddy/agent/ByteBuddyAgentTest.java 2023-01-04 22:45:00.000000000 +0000 @@ -16,40 +16,36 @@ public class ByteBuddyAgentTest { - private static final String INSTRUMENTATION = "instrumentation"; - - private static final Object STATIC_FIELD = null; - private Instrumentation actualInstrumentation; @Before public void setUp() throws Exception { - Field field = Installer.class.getDeclaredField(INSTRUMENTATION); + Field field = Installer.class.getDeclaredField("instrumentation"); field.setAccessible(true); - actualInstrumentation = (Instrumentation) field.get(STATIC_FIELD); + actualInstrumentation = (Instrumentation) field.get(null); } @After public void tearDown() throws Exception { - Field field = Installer.class.getDeclaredField(INSTRUMENTATION); + Field field = Installer.class.getDeclaredField("instrumentation"); field.setAccessible(true); - field.set(STATIC_FIELD, actualInstrumentation); + field.set(null, actualInstrumentation); } @Test public void testInstrumentationExtraction() throws Exception { - Field field = Installer.class.getDeclaredField(INSTRUMENTATION); + Field field = Installer.class.getDeclaredField("instrumentation"); field.setAccessible(true); Instrumentation instrumentation = mock(Instrumentation.class); - field.set(STATIC_FIELD, instrumentation); + field.set(null, instrumentation); assertThat(ByteBuddyAgent.getInstrumentation(), is(instrumentation)); } @Test(expected = IllegalStateException.class) public void testMissingInstrumentationThrowsException() throws Exception { - Field field = Installer.class.getDeclaredField(INSTRUMENTATION); + Field field = Installer.class.getDeclaredField("instrumentation"); field.setAccessible(true); - field.set(STATIC_FIELD, null); + field.set(null, null); ByteBuddyAgent.getInstrumentation(); } @@ -61,7 +57,7 @@ constructor.newInstance(); fail(); } catch (InvocationTargetException exception) { - throw (Exception) exception.getCause(); + throw (Exception) exception.getTargetException(); } } } diff -Nru byte-buddy-1.11.4/byte-buddy-agent/src/test/java/net/bytebuddy/agent/InstallerTest.java byte-buddy-1.12.21/byte-buddy-agent/src/test/java/net/bytebuddy/agent/InstallerTest.java --- byte-buddy-1.11.4/byte-buddy-agent/src/test/java/net/bytebuddy/agent/InstallerTest.java 2021-06-19 21:31:07.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-agent/src/test/java/net/bytebuddy/agent/InstallerTest.java 2023-01-04 22:45:00.000000000 +0000 @@ -1,12 +1,12 @@ package net.bytebuddy.agent; -import net.bytebuddy.test.utility.MockitoRule; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.junit.rules.TestRule; +import org.junit.rules.MethodRule; import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; import java.lang.instrument.Instrumentation; import java.lang.reflect.*; @@ -20,12 +20,8 @@ private static final String FOO = "foo"; - private static final String INSTRUMENTATION = "instrumentation"; - - private static final Object STATIC_FIELD = null; - @Rule - public final TestRule mockitoRule = new MockitoRule(this); + public final MethodRule mockitoRule = MockitoJUnit.rule().silent(); @Mock private Instrumentation instrumentation; @@ -34,16 +30,16 @@ @Before public void setUp() throws Exception { - Field field = Installer.class.getDeclaredField(INSTRUMENTATION); + Field field = Installer.class.getDeclaredField("instrumentation"); field.setAccessible(true); - actualInstrumentation = (Instrumentation) field.get(STATIC_FIELD); + actualInstrumentation = (Instrumentation) field.get(null); } @After public void tearDown() throws Exception { - Field field = Installer.class.getDeclaredField(INSTRUMENTATION); + Field field = Installer.class.getDeclaredField("instrumentation"); field.setAccessible(true); - field.set(STATIC_FIELD, actualInstrumentation); + field.set(null, actualInstrumentation); } @Test @@ -86,7 +82,7 @@ constructor.newInstance(); fail(); } catch (InvocationTargetException exception) { - throw (Exception) exception.getCause(); + throw (Exception) exception.getTargetException(); } } } diff -Nru byte-buddy-1.11.4/byte-buddy-agent/src/test/java/net/bytebuddy/agent/SampleAgent.java byte-buddy-1.12.21/byte-buddy-agent/src/test/java/net/bytebuddy/agent/SampleAgent.java --- byte-buddy-1.11.4/byte-buddy-agent/src/test/java/net/bytebuddy/agent/SampleAgent.java 2021-06-19 21:31:07.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-agent/src/test/java/net/bytebuddy/agent/SampleAgent.java 2023-01-04 22:45:00.000000000 +0000 @@ -1,9 +1,9 @@ package net.bytebuddy.agent; public class SampleAgent { - + public static String argument; - + public static void agentmain(String argument) { SampleAgent.argument = argument; } diff -Nru byte-buddy-1.11.4/byte-buddy-agent/src/test/java/net/bytebuddy/agent/VirtualMachineAttachmentTest.java byte-buddy-1.12.21/byte-buddy-agent/src/test/java/net/bytebuddy/agent/VirtualMachineAttachmentTest.java --- byte-buddy-1.11.4/byte-buddy-agent/src/test/java/net/bytebuddy/agent/VirtualMachineAttachmentTest.java 2021-06-19 21:31:07.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-agent/src/test/java/net/bytebuddy/agent/VirtualMachineAttachmentTest.java 2023-01-04 22:45:00.000000000 +0000 @@ -1,10 +1,12 @@ package net.bytebuddy.agent; import net.bytebuddy.dynamic.ClassFileLocator; -import org.hamcrest.CoreMatchers; +import net.bytebuddy.test.utility.JnaRule; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TestRule; import java.io.File; import java.io.FileOutputStream; @@ -14,15 +16,16 @@ import java.util.jar.JarOutputStream; import java.util.jar.Manifest; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; public class VirtualMachineAttachmentTest { private static final String FOO = "foo"; + @Rule + public TestRule jnaRule = new JnaRule(); + private File agent; @Before @@ -59,7 +62,7 @@ } assertThat(SampleAgent.argument, is(FOO)); } - + @Test(timeout = 10000L) public void testSystemProperties() throws Exception { VirtualMachine virtualMachine = (VirtualMachine) VirtualMachine.Resolver.INSTANCE.run() diff -Nru byte-buddy-1.11.4/byte-buddy-agent/src/test/java/net/bytebuddy/agent/VirtualMachineForHotSpotTest.java byte-buddy-1.12.21/byte-buddy-agent/src/test/java/net/bytebuddy/agent/VirtualMachineForHotSpotTest.java --- byte-buddy-1.11.4/byte-buddy-agent/src/test/java/net/bytebuddy/agent/VirtualMachineForHotSpotTest.java 2021-06-19 21:31:07.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-agent/src/test/java/net/bytebuddy/agent/VirtualMachineForHotSpotTest.java 2023-01-04 22:45:00.000000000 +0000 @@ -13,7 +13,7 @@ import static org.mockito.Mockito.*; public class VirtualMachineForHotSpotTest { - + private static final String FOO = "foo", BAR = "bar"; @Test diff -Nru byte-buddy-1.11.4/byte-buddy-agent/src/test/java/net/bytebuddy/agent/VirtualMachineForOpenJ9Test.java byte-buddy-1.12.21/byte-buddy-agent/src/test/java/net/bytebuddy/agent/VirtualMachineForOpenJ9Test.java --- byte-buddy-1.11.4/byte-buddy-agent/src/test/java/net/bytebuddy/agent/VirtualMachineForOpenJ9Test.java 2021-06-19 21:31:07.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-agent/src/test/java/net/bytebuddy/agent/VirtualMachineForOpenJ9Test.java 2023-01-04 22:45:00.000000000 +0000 @@ -57,11 +57,12 @@ } @Test(timeout = 10000L) - @JavaVersionRule.Enforce(7) // Fails sometimes on Java 6 due to timeout issues that are difficult to reproduce and solve. + @JavaVersionRule.Enforce(7) + // Fails sometimes on Java 6 due to timeout issues that are difficult to reproduce and solve. public void testAttachment() throws Throwable { final AtomicReference error = new AtomicReference(); VirtualMachine.ForOpenJ9.Dispatcher dispatcher = mock(VirtualMachine.ForOpenJ9.Dispatcher.class); - when(dispatcher.getTemporaryFolder()).thenReturn(temporaryFolder.getAbsolutePath()); + when(dispatcher.getTemporaryFolder(Integer.toString(PROCESS_ID))).thenReturn(temporaryFolder.getAbsolutePath()); File targetFolder = new File(attachFolder, Integer.toString(PROCESS_ID)); assertThat(targetFolder.mkdir(), is(true)); try { diff -Nru byte-buddy-1.11.4/byte-buddy-agent/src/test/java/net/bytebuddy/test/utility/AgentAttachmentRule.java byte-buddy-1.12.21/byte-buddy-agent/src/test/java/net/bytebuddy/test/utility/AgentAttachmentRule.java --- byte-buddy-1.11.4/byte-buddy-agent/src/test/java/net/bytebuddy/test/utility/AgentAttachmentRule.java 2021-06-19 21:31:07.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-agent/src/test/java/net/bytebuddy/test/utility/AgentAttachmentRule.java 2023-01-04 22:45:00.000000000 +0000 @@ -62,7 +62,7 @@ } public void evaluate() { - Logger.getLogger("net.bytebuddy").warning("Ignoring test case: " + reason); + Logger.getLogger("net.bytebuddy").info("Omitting test case: " + reason); } } } diff -Nru byte-buddy-1.11.4/byte-buddy-agent/src/test/java/net/bytebuddy/test/utility/JavaVersionRule.java byte-buddy-1.12.21/byte-buddy-agent/src/test/java/net/bytebuddy/test/utility/JavaVersionRule.java --- byte-buddy-1.11.4/byte-buddy-agent/src/test/java/net/bytebuddy/test/utility/JavaVersionRule.java 2021-06-19 21:31:07.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-agent/src/test/java/net/bytebuddy/test/utility/JavaVersionRule.java 2023-01-04 22:45:00.000000000 +0000 @@ -76,7 +76,7 @@ } public void evaluate() { - Logger.getLogger("net.bytebuddy").warning("Ignoring test case: Requires a Java version " + + Logger.getLogger("net.bytebuddy").info("Omitting test case: Requires a Java version " + "of " + sort + " " + requiredVersion + (target == void.class ? "" : (" for target " + target))); } @@ -85,7 +85,7 @@ private static class OpenJ9Statement extends Statement { public void evaluate() { - Logger.getLogger("net.bytebuddy").warning("Ignoring test case: Test not supported on OpenJ9"); + Logger.getLogger("net.bytebuddy").info("Omitting test case: Test not supported on OpenJ9"); } } } diff -Nru byte-buddy-1.11.4/byte-buddy-agent/src/test/java/net/bytebuddy/test/utility/JnaRule.java byte-buddy-1.12.21/byte-buddy-agent/src/test/java/net/bytebuddy/test/utility/JnaRule.java --- byte-buddy-1.11.4/byte-buddy-agent/src/test/java/net/bytebuddy/test/utility/JnaRule.java 1970-01-01 00:00:00.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-agent/src/test/java/net/bytebuddy/test/utility/JnaRule.java 2023-01-04 22:45:00.000000000 +0000 @@ -0,0 +1,46 @@ +package net.bytebuddy.test.utility; + +import com.sun.jna.Library; +import com.sun.jna.Native; +import com.sun.jna.Platform; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import java.util.logging.Logger; + +public class JnaRule implements TestRule { + + private final boolean available; + + @SuppressWarnings("deprecation") + public JnaRule() { + boolean available; + try { + Native.loadLibrary((Platform.isWindows() ? "msvcrt" : "c"), CLibrary.class); + available = true; + } catch (Throwable ignored) { + available = false; + } + this.available = available; + } + + public Statement apply(Statement base, Description description) { + return available + ? base + : new NoOpStatement(); + } + + private static class NoOpStatement extends Statement { + + public void evaluate() { + Logger.getLogger("net.bytebuddy").info("Omitting test case: JNA not available"); + } + } + + public interface CLibrary extends Library { + + @SuppressWarnings("unused") + void printf(String format, Object... args); + } +} diff -Nru byte-buddy-1.11.4/byte-buddy-agent/src/test/java/net/bytebuddy/test/utility/MockitoRule.java byte-buddy-1.12.21/byte-buddy-agent/src/test/java/net/bytebuddy/test/utility/MockitoRule.java --- byte-buddy-1.11.4/byte-buddy-agent/src/test/java/net/bytebuddy/test/utility/MockitoRule.java 2021-06-19 21:31:07.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-agent/src/test/java/net/bytebuddy/test/utility/MockitoRule.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,28 +0,0 @@ -package net.bytebuddy.test.utility; - -import org.junit.rules.TestRule; -import org.junit.runner.Description; -import org.junit.runners.model.Statement; -import org.mockito.MockitoAnnotations; - -/** - * A rule that applies Mockito's annotations to any test. This is preferred over the Mockito runner since it allows - * to use tests with parameters that require a specific runner. - */ -public class MockitoRule implements TestRule { - - private final Object target; - - public MockitoRule(Object target) { - this.target = target; - } - - public Statement apply(final Statement base, Description description) { - return new Statement() { - public void evaluate() throws Throwable { - MockitoAnnotations.initMocks(target); - base.evaluate(); - } - }; - } -} diff -Nru byte-buddy-1.11.4/byte-buddy-android/pom.xml byte-buddy-1.12.21/byte-buddy-android/pom.xml --- byte-buddy-1.11.4/byte-buddy-android/pom.xml 2021-06-19 21:31:07.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-android/pom.xml 2023-01-04 22:45:00.000000000 +0000 @@ -3,16 +3,19 @@ 4.0.0 - byte-buddy-parent net.bytebuddy - 1.11.4 + byte-buddy-parent + 1.12.21 byte-buddy-android jar - 9.0.0_r3 + 11.0.0_r3 + 1.15 + 4.5.13 + 20220924 Byte Buddy for Android @@ -22,7 +25,6 @@ ${project.groupId} byte-buddy - ${project.version} com.google.android @@ -59,6 +61,26 @@ + + + + commons-codec + commons-codec + ${version.commons-codec} + + + org.apache.httpcomponents + httpclient + ${version.httpcomponents} + + + org.json + json + ${version.org-json} + + + + @@ -74,6 +96,23 @@ + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${version.plugin.javadoc} + + + + https://javadoc.io/doc/net.bytebuddy/byte-buddy/${project.version} + ${project.basedir}/../byte-buddy/target/apidocs + + + + + + diff -Nru byte-buddy-1.11.4/byte-buddy-android/src/main/java/net/bytebuddy/android/AndroidClassLoadingStrategy.java byte-buddy-1.12.21/byte-buddy-android/src/main/java/net/bytebuddy/android/AndroidClassLoadingStrategy.java --- byte-buddy-1.11.4/byte-buddy-android/src/main/java/net/bytebuddy/android/AndroidClassLoadingStrategy.java 2021-06-19 21:31:07.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-android/src/main/java/net/bytebuddy/android/AndroidClassLoadingStrategy.java 2023-01-04 22:45:00.000000000 +0000 @@ -30,6 +30,8 @@ import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; import net.bytebuddy.utility.RandomString; +import net.bytebuddy.utility.nullability.AlwaysNull; +import net.bytebuddy.utility.nullability.MaybeNull; import java.io.*; import java.lang.reflect.Constructor; @@ -86,6 +88,7 @@ /** * A value for a {@link dalvik.system.DexClassLoader} to indicate that the library path is empty. */ + @AlwaysNull private static final String EMPTY_LIBRARY_PATH = null; /** @@ -124,7 +127,7 @@ /** * {@inheritDoc} */ - public Map> load(ClassLoader classLoader, Map types) { + public Map> load(@MaybeNull ClassLoader classLoader, Map types) { DexProcessor.Conversion conversion = dexProcessor.create(); for (Map.Entry entry : types.entrySet()) { conversion.register(entry.getKey().getName(), entry.getValue()); @@ -161,7 +164,7 @@ * @return A mapping of all type descriptions to their loaded types. * @throws IOException If an I/O exception occurs. */ - protected abstract Map> doLoad(ClassLoader classLoader, Set typeDescriptions, File jar) throws IOException; + protected abstract Map> doLoad(@MaybeNull ClassLoader classLoader, Set typeDescriptions, File jar) throws IOException; /** * A dex processor is responsible for converting a collection of Java class files into a Android dex file. @@ -271,6 +274,7 @@ /** * Indicates that a dex file should be written without providing a human readable output. */ + @AlwaysNull private static final Writer NO_PRINT_OUTPUT = null; /** @@ -463,7 +467,7 @@ } catch (IllegalAccessException exception) { throw new IllegalStateException("Cannot access an Android dex file translation method", exception); } catch (InvocationTargetException exception) { - throw new IllegalStateException("Cannot invoke Android dex file translation method", exception.getCause()); + throw new IllegalStateException("Cannot invoke Android dex file translation method", exception.getTargetException()); } } @@ -526,7 +530,7 @@ } catch (InstantiationException exception) { throw new IllegalStateException("Cannot instantiate dex context", exception); } catch (InvocationTargetException exception) { - throw new IllegalStateException("Cannot invoke Android dex file translation method", exception.getCause()); + throw new IllegalStateException("Cannot invoke Android dex file translation method", exception.getTargetException()); } } @@ -572,7 +576,7 @@ * {@inheritDoc} */ @SuppressFBWarnings(value = "DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED", justification = "Android discourages the use of access controllers") - protected Map> doLoad(ClassLoader classLoader, Set typeDescriptions, File jar) { + protected Map> doLoad(@MaybeNull ClassLoader classLoader, Set typeDescriptions, File jar) { ClassLoader dexClassLoader = new DexClassLoader(jar.getAbsolutePath(), privateDirectory.getAbsolutePath(), EMPTY_LIBRARY_PATH, classLoader); Map> loadedTypes = new HashMap>(); for (TypeDescription typeDescription : typeDescriptions) { @@ -634,7 +638,7 @@ /** * {@inheritDoc} */ - public Map> load(ClassLoader classLoader, Map types) { + public Map> load(@MaybeNull ClassLoader classLoader, Map types) { if (classLoader == null) { throw new IllegalArgumentException("Cannot inject classes into the bootstrap class loader on Android"); } @@ -644,7 +648,7 @@ /** * {@inheritDoc} */ - protected Map> doLoad(ClassLoader classLoader, Set typeDescriptions, File jar) throws IOException { + protected Map> doLoad(@MaybeNull ClassLoader classLoader, Set typeDescriptions, File jar) throws IOException { dalvik.system.DexFile dexFile = DISPATCHER.loadDex(privateDirectory, jar, classLoader, randomString); try { Map> loadedTypes = new HashMap>(); @@ -680,7 +684,8 @@ * @return The created {@link dalvik.system.DexFile} or {@code null} if no such file is created. * @throws IOException If an I/O exception is thrown. */ - dalvik.system.DexFile loadDex(File privateDirectory, File jar, ClassLoader classLoader, RandomString randomString) throws IOException; + @MaybeNull + dalvik.system.DexFile loadDex(File privateDirectory, File jar, @MaybeNull ClassLoader classLoader, RandomString randomString) throws IOException; /** * Loads a class. @@ -690,7 +695,8 @@ * @param typeDescription The type to load. * @return The loaded class. */ - Class loadClass(dalvik.system.DexFile dexFile, ClassLoader classLoader, TypeDescription typeDescription); + @MaybeNull + Class loadClass(dalvik.system.DexFile dexFile, @MaybeNull ClassLoader classLoader, TypeDescription typeDescription); /** * A dispatcher for legacy VMs that allow {@link dalvik.system.DexFile#loadDex(String, String, int)}. @@ -715,9 +721,10 @@ /** * {@inheritDoc} */ + @MaybeNull public dalvik.system.DexFile loadDex(File privateDirectory, File jar, - ClassLoader classLoader, + @MaybeNull ClassLoader classLoader, RandomString randomString) throws IOException { return dalvik.system.DexFile.loadDex(jar.getAbsolutePath(), new File(privateDirectory.getAbsolutePath(), randomString.nextString() + EXTENSION).getAbsolutePath(), @@ -727,7 +734,8 @@ /** * {@inheritDoc} */ - public Class loadClass(dalvik.system.DexFile dexFile, ClassLoader classLoader, TypeDescription typeDescription) { + @MaybeNull + public Class loadClass(dalvik.system.DexFile dexFile, @MaybeNull ClassLoader classLoader, TypeDescription typeDescription) { return dexFile.loadClass(typeDescription.getName(), classLoader); } } @@ -740,6 +748,7 @@ /** * Indicates that this dispatcher does not return a {@link dalvik.system.DexFile} instance. */ + @AlwaysNull private static final dalvik.system.DexFile NO_RETURN_VALUE = null; /** @@ -759,9 +768,10 @@ /** * {@inheritDoc} */ + @MaybeNull public dalvik.system.DexFile loadDex(File privateDirectory, File jar, - ClassLoader classLoader, + @MaybeNull ClassLoader classLoader, RandomString randomString) throws IOException { if (!(classLoader instanceof BaseDexClassLoader)) { throw new IllegalArgumentException("On Android P, a class injection can only be applied to BaseDexClassLoader: " + classLoader); @@ -772,7 +782,7 @@ } catch (IllegalAccessException exception) { throw new IllegalStateException("Cannot access BaseDexClassLoader#addDexPath(String, boolean)", exception); } catch (InvocationTargetException exception) { - Throwable cause = exception.getCause(); + Throwable cause = exception.getTargetException(); if (cause instanceof IOException) { throw (IOException) cause; } else { @@ -784,7 +794,7 @@ /** * {@inheritDoc} */ - public Class loadClass(dalvik.system.DexFile dexFile, ClassLoader classLoader, TypeDescription typeDescription) { + public Class loadClass(@MaybeNull dalvik.system.DexFile dexFile, @MaybeNull ClassLoader classLoader, TypeDescription typeDescription) { try { return Class.forName(typeDescription.getName(), false, classLoader); } catch (ClassNotFoundException exception) { diff -Nru byte-buddy-1.11.4/byte-buddy-android/src/main/java/net/bytebuddy/android/package-info.java byte-buddy-1.12.21/byte-buddy-android/src/main/java/net/bytebuddy/android/package-info.java --- byte-buddy-1.11.4/byte-buddy-android/src/main/java/net/bytebuddy/android/package-info.java 2021-06-19 21:31:07.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-android/src/main/java/net/bytebuddy/android/package-info.java 2023-01-04 22:45:00.000000000 +0000 @@ -16,4 +16,7 @@ /** * This package is dedicated to supporting Byte Buddy on Android devices. */ +@NeverNull.ByDefault package net.bytebuddy.android; + +import net.bytebuddy.utility.nullability.NeverNull; diff -Nru byte-buddy-1.11.4/byte-buddy-android/src/test/java/net/bytebuddy/android/AndroidClassLoadingStrategyTest.java byte-buddy-1.12.21/byte-buddy-android/src/test/java/net/bytebuddy/android/AndroidClassLoadingStrategyTest.java --- byte-buddy-1.11.4/byte-buddy-android/src/test/java/net/bytebuddy/android/AndroidClassLoadingStrategyTest.java 2021-06-19 21:31:07.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-android/src/test/java/net/bytebuddy/android/AndroidClassLoadingStrategyTest.java 2023-01-04 22:45:00.000000000 +0000 @@ -8,14 +8,14 @@ import net.bytebuddy.dynamic.DynamicType; import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; import net.bytebuddy.implementation.FixedValue; -import net.bytebuddy.test.utility.MockitoRule; import org.hamcrest.CoreMatchers; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.junit.rules.TestRule; +import org.junit.rules.MethodRule; import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; import java.io.File; import java.io.IOException; @@ -34,7 +34,7 @@ private static final byte[] QUX = new byte[]{1, 2, 3}, BAZ = new byte[]{4, 5, 6}; @Rule - public TestRule dexCompilerRule = new MockitoRule(this); + public MethodRule mockitoRule = MockitoJUnit.rule().silent(); private File folder; diff -Nru byte-buddy-1.11.4/byte-buddy-android/src/test/java/net/bytebuddy/test/utility/MockitoRule.java byte-buddy-1.12.21/byte-buddy-android/src/test/java/net/bytebuddy/test/utility/MockitoRule.java --- byte-buddy-1.11.4/byte-buddy-android/src/test/java/net/bytebuddy/test/utility/MockitoRule.java 2021-06-19 21:31:07.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-android/src/test/java/net/bytebuddy/test/utility/MockitoRule.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,28 +0,0 @@ -package net.bytebuddy.test.utility; - -import org.junit.rules.TestRule; -import org.junit.runner.Description; -import org.junit.runners.model.Statement; -import org.mockito.MockitoAnnotations; - -/** - * A rule that applies Mockito's annotations to any test. This is preferred over the Mockito runner since it allows - * to use tests with parameters that require a specific runner. - */ -public class MockitoRule implements TestRule { - - private final Object target; - - public MockitoRule(Object target) { - this.target = target; - } - - public Statement apply(final Statement base, Description description) { - return new Statement() { - public void evaluate() throws Throwable { - MockitoAnnotations.initMocks(target); - base.evaluate(); - } - }; - } -} diff -Nru byte-buddy-1.11.4/byte-buddy-android-test/pom.xml byte-buddy-1.12.21/byte-buddy-android-test/pom.xml --- byte-buddy-1.11.4/byte-buddy-android-test/pom.xml 2021-06-19 21:31:07.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-android-test/pom.xml 2023-01-04 22:45:00.000000000 +0000 @@ -5,7 +5,7 @@ net.bytebuddy byte-buddy-parent - 1.11.4 + 1.12.21 byte-buddy-android-test @@ -19,6 +19,7 @@ com.simpligility.maven.plugins 4.6.0 4 + true + ${project.build.sourceEncoding} + diff -Nru byte-buddy-1.11.4/byte-buddy-benchmark/pom.xml byte-buddy-1.12.21/byte-buddy-benchmark/pom.xml --- byte-buddy-1.11.4/byte-buddy-benchmark/pom.xml 2021-06-19 21:31:07.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-benchmark/pom.xml 2023-01-04 22:45:00.000000000 +0000 @@ -3,9 +3,9 @@ 4.0.0 - byte-buddy-parent net.bytebuddy - 1.11.4 + byte-buddy-parent + 1.12.21 byte-buddy-benchmark @@ -24,18 +24,13 @@ net.bytebuddy.benchmark.runner.BenchmarkRunner - - 1.16 - 3.2.12 - - 3.22.0-GA + true ${project.groupId} byte-buddy-dep - ${project.version} cglib @@ -101,10 +96,35 @@ + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${version.plugin.javadoc} + + + + https://javadoc.io/doc/net.bytebuddy/byte-buddy/${project.version} + ${project.basedir}/../byte-buddy/target/apidocs + + + + https://javadoc.io/doc/org.openjdk.jmh/jmh-core/${version.jmh} + ${project.basedir}/target/javadoc-lists/jmh-core-${version.jmh} + + + + net.bytebuddy.benchmark.generated, + net.bytebuddy.benchmark.jmh_generated + + + + + - extras @@ -112,6 +132,29 @@ + + + org.apache.maven.plugins + maven-antrun-plugin + ${version.plugin.antrun} + + + download-javadoc-lists + process-resources + + run + + + ${javadoc.download.skip} + + + + + + + + + org.apache.maven.plugins maven-shade-plugin @@ -137,6 +180,7 @@ META-INF/MANIFEST.MF **/module-info.class + **/NOTICE diff -Nru byte-buddy-1.11.4/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/ClassByExtensionBenchmark.java byte-buddy-1.12.21/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/ClassByExtensionBenchmark.java --- byte-buddy-1.11.4/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/ClassByExtensionBenchmark.java 2021-06-19 21:31:07.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/ClassByExtensionBenchmark.java 2023-01-04 22:45:00.000000000 +0000 @@ -28,6 +28,7 @@ import net.bytebuddy.implementation.SuperMethodCall; import net.bytebuddy.implementation.bind.annotation.*; import net.bytebuddy.pool.TypePool; +import net.bytebuddy.utility.nullability.MaybeNull; import net.sf.cglib.proxy.*; import org.openjdk.jmh.annotations.*; @@ -86,51 +87,61 @@ /** * An implementation to be used by {@link ClassByExtensionBenchmark#benchmarkByteBuddyWithProxyAndReusedDelegator()}. */ + @MaybeNull private Implementation proxyInterceptor; /** * An implementation to be used by {@link ClassByExtensionBenchmark#benchmarkByteBuddyWithAccessorAndReusedDelegator()}. */ + @MaybeNull private Implementation accessInterceptor; /** * An implementation to be used by {@link ClassByExtensionBenchmark#benchmarkByteBuddyWithPrefixAndReusedDelegator()}. */ + @MaybeNull private Implementation.Composable prefixInterceptor; /** * A description of {@link ClassByExtensionBenchmark#baseClass}. */ + @MaybeNull private TypeDescription baseClassDescription; /** * A description of {@link ByteBuddyProxyInterceptor}. */ + @MaybeNull private TypeDescription proxyClassDescription; /** * A description of {@link ByteBuddyAccessInterceptor}. */ + @MaybeNull private TypeDescription accessClassDescription; /** * A description of {@link ByteBuddyPrefixInterceptor}. */ + @MaybeNull private TypeDescription prefixClassDescription; /** * A method delegation to {@link ByteBuddyProxyInterceptor}. */ + @MaybeNull private Implementation proxyInterceptorDescription; /** * A method delegation to {@link ByteBuddyAccessInterceptor}. */ + @MaybeNull private Implementation accessInterceptorDescription; /** * A method delegation to {@link ByteBuddyPrefixInterceptor}. */ + @MaybeNull private Implementation.Composable prefixInterceptorDescription; /** @@ -453,7 +464,7 @@ enhancer.setInterceptDuringConstruction(true); enhancer.setClassLoader(newClassLoader()); enhancer.setSuperclass(baseClass); - CallbackHelper callbackHelper = new CallbackHelper(baseClass, new Class[0]) { + CallbackHelper callbackHelper = new CallbackHelper(baseClass, new Class[0]) { protected Object getCallback(Method method) { if (method.getDeclaringClass() == baseClass) { return new MethodInterceptor() { diff -Nru byte-buddy-1.11.4/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/ClassByImplementationBenchmark.java byte-buddy-1.12.21/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/ClassByImplementationBenchmark.java --- byte-buddy-1.11.4/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/ClassByImplementationBenchmark.java 2021-06-19 21:31:07.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/ClassByImplementationBenchmark.java 2023-01-04 22:45:00.000000000 +0000 @@ -25,6 +25,8 @@ import net.bytebuddy.dynamic.scaffold.TypeValidation; import net.bytebuddy.implementation.StubMethod; import net.bytebuddy.pool.TypePool; +import net.bytebuddy.utility.nullability.AlwaysNull; +import net.bytebuddy.utility.nullability.MaybeNull; import net.sf.cglib.proxy.CallbackHelper; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.FixedValue; @@ -66,6 +68,7 @@ * The default reference value. By defining the default reference value as a string type instead of as an object * type, the field is inlined by the compiler, similar to the primitive values. */ + @AlwaysNull public static final String DEFAULT_REFERENCE_VALUE = null; /** @@ -178,6 +181,7 @@ /** * A description of {@link ClassByExtensionBenchmark#baseClass}. */ + @MaybeNull private TypeDescription baseClassDescription; /** @@ -196,74 +200,138 @@ @Benchmark public ExampleInterface baseline() { return new ExampleInterface() { + /** + * {@inheritDoc} + */ public boolean method(boolean arg) { return false; } + /** + * {@inheritDoc} + */ public byte method(byte arg) { return 0; } + /** + * {@inheritDoc} + */ public short method(short arg) { return 0; } + /** + * {@inheritDoc} + */ public int method(int arg) { return 0; } + /** + * {@inheritDoc} + */ public char method(char arg) { return 0; } + /** + * {@inheritDoc} + */ public long method(long arg) { return 0; } + /** + * {@inheritDoc} + */ public float method(float arg) { return 0; } + /** + * {@inheritDoc} + */ public double method(double arg) { return 0; } + /** + * {@inheritDoc} + */ + @AlwaysNull public Object method(Object arg) { return null; } + /** + * {@inheritDoc} + */ + @AlwaysNull public boolean[] method(boolean arg1, boolean arg2, boolean arg3) { return null; } + /** + * {@inheritDoc} + */ + @AlwaysNull public byte[] method(byte arg1, byte arg2, byte arg3) { return null; } + /** + * {@inheritDoc} + */ + @AlwaysNull public short[] method(short arg1, short arg2, short arg3) { return null; } + /** + * {@inheritDoc} + */ + @AlwaysNull public int[] method(int arg1, int arg2, int arg3) { return null; } + /** + * {@inheritDoc} + */ + @AlwaysNull public char[] method(char arg1, char arg2, char arg3) { return null; } + /** + * {@inheritDoc} + */ + @AlwaysNull public long[] method(long arg1, long arg2, long arg3) { return null; } + /** + * {@inheritDoc} + */ + @AlwaysNull public float[] method(float arg1, float arg2, float arg3) { return null; } + /** + * {@inheritDoc} + */ + @AlwaysNull public double[] method(double arg1, double arg2, double arg3) { return null; } + /** + * {@inheritDoc} + */ + @AlwaysNull public Object[] method(Object arg1, Object arg2, Object arg3) { return null; } @@ -322,7 +390,7 @@ enhancer.setUseCache(false); enhancer.setClassLoader(newClassLoader()); enhancer.setSuperclass(baseClass); - CallbackHelper callbackHelper = new CallbackHelper(Object.class, new Class[]{baseClass}) { + CallbackHelper callbackHelper = new CallbackHelper(Object.class, new Class[]{baseClass}) { protected Object getCallback(Method method) { if (method.getDeclaringClass() == baseClass) { return new FixedValue() { @@ -407,7 +475,8 @@ return (ExampleInterface) Proxy.newProxyInstance(newClassLoader(), new Class[]{baseClass}, new InvocationHandler() { - public Object invoke(Object proxy, Method method, Object[] args) { + @MaybeNull + public Object invoke(Object proxy, Method method, @MaybeNull Object[] argument) { Class returnType = method.getReturnType(); if (returnType.isPrimitive()) { if (returnType == boolean.class) { diff -Nru byte-buddy-1.11.4/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/package-info.java byte-buddy-1.12.21/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/package-info.java --- byte-buddy-1.11.4/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/package-info.java 2021-06-19 21:31:07.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/package-info.java 2023-01-04 22:45:00.000000000 +0000 @@ -16,4 +16,7 @@ /** * A package containing benchmarks for Byte Buddy and other code generation libraries. */ +@NeverNull.ByDefault package net.bytebuddy.benchmark; + +import net.bytebuddy.utility.nullability.NeverNull; diff -Nru byte-buddy-1.11.4/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/runner/package-info.java byte-buddy-1.12.21/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/runner/package-info.java --- byte-buddy-1.11.4/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/runner/package-info.java 2021-06-19 21:31:07.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/runner/package-info.java 2023-01-04 22:45:00.000000000 +0000 @@ -16,4 +16,7 @@ /** * A package dedicated to running benchmarks. */ +@NeverNull.ByDefault package net.bytebuddy.benchmark.runner; + +import net.bytebuddy.utility.nullability.NeverNull; diff -Nru byte-buddy-1.11.4/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/specimen/ExampleInterface.java byte-buddy-1.12.21/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/specimen/ExampleInterface.java --- byte-buddy-1.11.4/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/specimen/ExampleInterface.java 2021-06-19 21:31:07.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/specimen/ExampleInterface.java 2023-01-04 22:45:00.000000000 +0000 @@ -15,6 +15,8 @@ */ package net.bytebuddy.benchmark.specimen; +import net.bytebuddy.utility.nullability.MaybeNull; + /** * An example interface with several methods which is used as a specimen in benchmarks. */ @@ -90,6 +92,7 @@ * @param arg An argument. * @return The input argument. */ + @MaybeNull Object method(Object arg); /** @@ -100,6 +103,7 @@ * @param arg3 An argument. * @return All arguments stored in an array. */ + @MaybeNull boolean[] method(boolean arg1, boolean arg2, boolean arg3); /** @@ -110,6 +114,7 @@ * @param arg3 An argument. * @return All arguments stored in an array. */ + @MaybeNull byte[] method(byte arg1, byte arg2, byte arg3); /** @@ -120,6 +125,7 @@ * @param arg3 An argument. * @return All arguments stored in an array. */ + @MaybeNull short[] method(short arg1, short arg2, short arg3); /** @@ -130,6 +136,7 @@ * @param arg3 An argument. * @return All arguments stored in an array. */ + @MaybeNull int[] method(int arg1, int arg2, int arg3); /** @@ -140,6 +147,7 @@ * @param arg3 An argument. * @return All arguments stored in an array. */ + @MaybeNull char[] method(char arg1, char arg2, char arg3); /** @@ -150,6 +158,7 @@ * @param arg3 An argument. * @return All arguments stored in an array. */ + @MaybeNull long[] method(long arg1, long arg2, long arg3); /** @@ -160,6 +169,7 @@ * @param arg3 An argument. * @return All arguments stored in an array. */ + @MaybeNull float[] method(float arg1, float arg2, float arg3); /** @@ -170,6 +180,7 @@ * @param arg3 An argument. * @return All arguments stored in an array. */ + @MaybeNull double[] method(double arg1, double arg2, double arg3); /** @@ -180,5 +191,6 @@ * @param arg3 An argument. * @return All arguments stored in an array. */ + @MaybeNull Object[] method(Object arg1, Object arg2, Object arg3); } diff -Nru byte-buddy-1.11.4/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/specimen/package-info.java byte-buddy-1.12.21/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/specimen/package-info.java --- byte-buddy-1.11.4/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/specimen/package-info.java 2021-06-19 21:31:07.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/specimen/package-info.java 2023-01-04 22:45:00.000000000 +0000 @@ -16,4 +16,7 @@ /** * Specimen classes which are required for some benchmarks. */ +@NeverNull.ByDefault package net.bytebuddy.benchmark.specimen; + +import net.bytebuddy.utility.nullability.NeverNull; diff -Nru byte-buddy-1.11.4/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/StubInvocationBenchmark.java byte-buddy-1.12.21/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/StubInvocationBenchmark.java --- byte-buddy-1.11.4/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/StubInvocationBenchmark.java 2021-06-19 21:31:07.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/StubInvocationBenchmark.java 2023-01-04 22:45:00.000000000 +0000 @@ -16,6 +16,7 @@ package net.bytebuddy.benchmark; import net.bytebuddy.benchmark.specimen.ExampleInterface; +import net.bytebuddy.utility.nullability.MaybeNull; import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.infra.Blackhole; @@ -85,26 +86,31 @@ /** * A casual instance that serves as a baseline. */ + @MaybeNull private ExampleInterface baselineInstance; /** * An instance created by Byte Buddy for performing benchmarks on. */ + @MaybeNull private ExampleInterface byteBuddyInstance; /** * An instance created by cglib for performing benchmarks on. */ + @MaybeNull private ExampleInterface cglibInstance; /** * An instance created by javassist for performing benchmarks on. */ + @MaybeNull private ExampleInterface javassistInstance; /** * An instance created by the JDK proxy for performing benchmarks on. */ + @MaybeNull private ExampleInterface jdkProxyInstance; /** diff -Nru byte-buddy-1.11.4/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/SuperClassInvocationBenchmark.java byte-buddy-1.12.21/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/SuperClassInvocationBenchmark.java --- byte-buddy-1.11.4/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/SuperClassInvocationBenchmark.java 2021-06-19 21:31:07.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/SuperClassInvocationBenchmark.java 2023-01-04 22:45:00.000000000 +0000 @@ -16,6 +16,7 @@ package net.bytebuddy.benchmark; import net.bytebuddy.benchmark.specimen.ExampleClass; +import net.bytebuddy.utility.nullability.MaybeNull; import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.infra.Blackhole; @@ -85,40 +86,47 @@ /** * A casual instance that serves as a baseline. */ + @MaybeNull private ExampleClass baselineInstance; /** * An instance created by Byte Buddy for performing benchmarks on. This instance is created by adding * auxiliary classes that allow for an invocation of a method from a delegation target. */ + @MaybeNull private ExampleClass byteBuddyWithProxyInstance; /** * An instance created by Byte Buddy for performing benchmarks on. This instance is created by adding * super invocation methods which are exposed via the reflection API. */ + @MaybeNull private ExampleClass byteBuddyWithAccessorInstance; /** * An instance created by Byte Buddy for performing benchmarks on. This instance is created by a delegation * followed by a hard-coded super method call. */ + @MaybeNull private ExampleClass byteBuddyWithPrefixInstance; /** * An instance created by Byte Buddy for performing benchmarks on. This instance is created by hard-coding * a super method invocation into the intercepted method. */ + @MaybeNull private ExampleClass byteBuddySpecializedInstance; /** * An instance created by cglib for performing benchmarks on. */ + @MaybeNull private ExampleClass cglibInstance; /** * An instance created by javassist for performing benchmarks on. */ + @MaybeNull private ExampleClass javassistInstance; /** diff -Nru byte-buddy-1.11.4/byte-buddy-benchmark/src/test/java/net/bytebuddy/benchmark/ClassByExtensionBenchmarkByteBuddyInterceptorTest.java byte-buddy-1.12.21/byte-buddy-benchmark/src/test/java/net/bytebuddy/benchmark/ClassByExtensionBenchmarkByteBuddyInterceptorTest.java --- byte-buddy-1.11.4/byte-buddy-benchmark/src/test/java/net/bytebuddy/benchmark/ClassByExtensionBenchmarkByteBuddyInterceptorTest.java 2021-06-19 21:31:07.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-benchmark/src/test/java/net/bytebuddy/benchmark/ClassByExtensionBenchmarkByteBuddyInterceptorTest.java 2023-01-04 22:45:00.000000000 +0000 @@ -17,7 +17,7 @@ constructor.newInstance(); fail(); } catch (InvocationTargetException exception) { - throw (UnsupportedOperationException) exception.getCause(); + throw (UnsupportedOperationException) exception.getTargetException(); } } @@ -29,7 +29,7 @@ constructor.newInstance(); fail(); } catch (InvocationTargetException exception) { - throw (UnsupportedOperationException) exception.getCause(); + throw (UnsupportedOperationException) exception.getTargetException(); } } } diff -Nru byte-buddy-1.11.4/byte-buddy-dep/pom.xml byte-buddy-1.12.21/byte-buddy-dep/pom.xml --- byte-buddy-1.11.4/byte-buddy-dep/pom.xml 2021-06-19 21:31:07.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-dep/pom.xml 2023-01-04 22:45:00.000000000 +0000 @@ -5,9 +5,24 @@ net.bytebuddy byte-buddy-parent - 1.11.4 + 1.12.21 + + byte-buddy-dep jar @@ -42,13 +57,13 @@ net.java.dev.jna jna - ${jna.version} + ${version.jna} provided net.java.dev.jna jna-platform - ${jna.version} + ${version.jna} provided @@ -88,7 +103,6 @@ ${project.groupId} byte-buddy-agent - ${project.version} test @@ -113,7 +127,7 @@ net.bytebuddy byte-buddy-maven-plugin - 1.11.3 + 1.12.20 compile @@ -128,36 +142,88 @@ net.bytebuddy byte-buddy - 1.11.3 + 1.12.20 net.bytebuddy.build.HashCodeAndEqualsPlugin$WithNonNullableFields + + + 0 + net.bytebuddy.utility.nullability.MaybeNull + + net.bytebuddy byte-buddy - 1.11.3 + 1.12.20 net.bytebuddy.build.CachedReturnPlugin + + net.bytebuddy + byte-buddy + 1.12.20 + net.bytebuddy.build.AccessControllerPlugin + + + 0 + net.bytebuddy.securitymanager + + + + + net.bytebuddy + byte-buddy + 1.12.20 + net.bytebuddy.build.DispatcherAnnotationPlugin + + + net.bytebuddy + byte-buddy + 1.12.20 + net.bytebuddy.build.RepeatedAnnotationPlugin + + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${version.plugin.javadoc} + + + + + https://javadoc.io/doc/org.ow2.asm/asm/${version.asm} + ${project.basedir}/target/javadoc-lists/asm-${version.asm} + + + + https://javadoc.io/doc/net.java.dev.jna/jna/${version.jna} + + + + + - legacy-build + build-java-7 false 1.7 - + org.apache.maven.plugins maven-surefire-plugin + ${version.plugin.surefire} net.bytebuddy.agent.builder.AgentBuilderDefaultApplicationTest @@ -168,5 +234,856 @@ + + build-java-11 + + false + 11 + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${version.plugin.surefire} + + + net.bytebuddy.description.type.TypeDescriptionArrayProjectionTest + net.bytebuddy.description.annotation.AnnotationDescriptionForLoadedAnnotationTest + net.bytebuddy.description.annotation.AnnotationDescriptionForLoadedAnnotationDifferentClassLoaderTest + net.bytebuddy.description.annotation.AnnotationDescriptionLatentTest + net.bytebuddy.pool.TypePoolDefaultAnnotationDescriptionTest + + + + + + + + extras + + false + + + + + + org.apache.maven.plugins + maven-antrun-plugin + ${version.plugin.antrun} + + + download-javadoc-lists + process-resources + + run + + + ${javadoc.download.skip} + + + + + + + + + + + + + native-compile + + false + + + + + org.codehaus.mojo + exec-maven-plugin + ${version.plugin.exec} + + + compile-test + compile + + exec + + + gcc + + -fPIC + -I${java.home}/../include + -I${java.home}/../include/linux + -I${project.basedir}/src/test/c + -shared + -o + ${project.basedir}/src/test/resources/net_bytebuddy_test_c_NativeSample.so + ${project.basedir}/src/test/c/net_bytebuddy_test_c_NativeSample.c + + + + + + + + + + + java-4-precompile + + false + + java-4-precompile + + + + 1.4 + 1.4 + + + + + org.codehaus.mojo + build-helper-maven-plugin + ${version.plugin.buildhelper} + + + java-4-precompile-test + generate-sources + + add-test-source + + + + src/test/java-4 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${version.plugin.compiler} + + + default-testCompile + test-compile + + testCompile + + + + net/bytebuddy/test/precompiled/v4/** + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + ${version.plugin.antrun} + + + java-4-precompile-test-copy + process-test-classes + + run + + + + + + + + + + + + + + + + java-4-no-precompile + + true + + !java-4-precompile + + + + + + src/test/precompiled-4 + + + + + + + java-4-jsr14-precompile + + false + + java-4-jsr14-precompile + + + + 2.0.1 + 1.4 + jsr14 + + + + + org.codehaus.mojo + build-helper-maven-plugin + ${version.plugin.buildhelper} + + + java-4-jsr14-precompile-test + generate-sources + + add-test-source + + + + src/test/java-4-jsr14 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${version.plugin.compiler} + + + default-testCompile + test-compile + + testCompile + + + ${bytecode.test.version} + + net/bytebuddy/test/precompiled/v4jsr14/** + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + ${version.plugin.antrun} + + + java-4-precompile-test-copy + process-test-classes + + run + + + + + + + + + + + + + + + + java-4-jsr14-no-precompile + + true + + !java-4-jsr14-precompile + + + + + + src/test/precompiled-4-jsr14 + + + + + + + java-6-precompile + + false + + java-6-precompile + + + + 1.6 + 1.6 + + + + + org.codehaus.mojo + build-helper-maven-plugin + ${version.plugin.buildhelper} + + + java-6-precompile + generate-sources + + add-source + + + + src/main/java-6 + + + + + java-6-precompile-test + generate-sources + + add-test-source + + + + src/test/java-6 + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + ${version.plugin.antrun} + + + java-6-precompile-copy + process-classes + + run + + + + + + + + + + + java-6-precompile-test-copy + process-test-classes + + run + + + + + + + + + + + + + + + + java-6-no-precompile + + true + + !java-6-precompile + + + + + + src/main/precompiled-6 + + + + + src/test/precompiled-6 + + + + + + + java-7-precompile + + false + + java-7-precompile + + + + 1.7 + + + + + org.codehaus.mojo + build-helper-maven-plugin + ${version.plugin.buildhelper} + + + java-7-precompile-test + generate-sources + + add-test-source + + + + src/test/java-7 + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + ${version.plugin.antrun} + + + java-7-precompile-test-copy + process-test-classes + + run + + + + + + + + + + + + + + + + java-7-no-precompile + + true + + !java-7-precompile + + + + + + src/test/precompiled-7 + + + + + + + java-8-precompile + + false + + java-8-precompile + + + + 1.8 + 1.8 + + + + + org.codehaus.mojo + build-helper-maven-plugin + ${version.plugin.buildhelper} + + + java-8-precompile-test + generate-sources + + add-test-source + + + + src/test/java-8 + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + ${version.plugin.antrun} + + + java-8-precompile-test-copy + process-test-classes + + run + + + + + + + + + + + + + + + + java-8-no-precompile + + true + + !java-8-precompile + + + + + + src/test/precompiled-8 + + + + + + + java-8-parameters-precompile + + false + + java-8-parameters-precompile + + + + 1.8 + 1.8 + + + + + org.codehaus.mojo + build-helper-maven-plugin + ${version.plugin.buildhelper} + + + java-8-parameters-precompile-test + generate-sources + + add-test-source + + + + src/test/java-8-parameters + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${version.plugin.compiler} + + + default-testCompile + test-compile + + testCompile + + + true + + net/bytebuddy/test/precompiled/v8parameters/** + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + ${version.plugin.antrun} + + + java-8-parameters-precompile-test-copy + process-test-classes + + run + + + + + + + + + + + + + + + + java-8-parameters-no-precompile + + true + + !java-8-parameters-precompile + + + + + + src/test/precompiled-8-parameters + + + + + + + java-11-precompile + + false + + java-11-precompile + + + + 11 + 11 + + + + + org.codehaus.mojo + build-helper-maven-plugin + ${version.plugin.buildhelper} + + + java-11-precompile-test + generate-sources + + add-test-source + + + + src/test/java-11 + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + ${version.plugin.antrun} + + + java-11-precompile-test-copy + process-test-classes + + run + + + + + + + + + + + + + + + + java-11-no-precompile + + true + + !java-11-precompile + + + + + + src/test/precompiled-11 + + + + + + + java-16-precompile + + false + + java-16-precompile + + + + 16 + 16 + + + + + org.codehaus.mojo + build-helper-maven-plugin + ${version.plugin.buildhelper} + + + java-16-precompile-test + generate-sources + + add-test-source + + + + src/test/java-16 + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + ${version.plugin.antrun} + + + java-16-precompile-test-copy + process-test-classes + + run + + + + + + + + + + + + + + + + java-16-no-precompile + + true + + !java-16-precompile + + + + + + src/test/precompiled-16 + + + + + + + java-17-precompile + + false + + java-17-precompile + + + + 17 + 17 + + + + + org.codehaus.mojo + build-helper-maven-plugin + ${version.plugin.buildhelper} + + + java-17-precompile-test + generate-sources + + add-test-source + + + + src/test/java-17 + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + ${version.plugin.antrun} + + + java-17-precompile-test-copy + process-test-classes + + run + + + + + + + + + + + + + + + + java-17-no-precompile + + true + + !java-17-precompile + + + + + + src/test/precompiled-17 + + + + diff -Nru byte-buddy-1.11.4/byte-buddy-dep/src/main/java/net/bytebuddy/agent/builder/AgentBuilder.java byte-buddy-1.12.21/byte-buddy-dep/src/main/java/net/bytebuddy/agent/builder/AgentBuilder.java --- byte-buddy-1.11.4/byte-buddy-dep/src/main/java/net/bytebuddy/agent/builder/AgentBuilder.java 2021-06-19 21:31:07.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-dep/src/main/java/net/bytebuddy/agent/builder/AgentBuilder.java 2023-01-04 22:45:00.000000000 +0000 @@ -29,6 +29,7 @@ import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.method.ParameterDescription; import net.bytebuddy.description.modifier.*; +import net.bytebuddy.description.type.PackageDescription; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.dynamic.*; import net.bytebuddy.dynamic.loading.ClassInjector; @@ -66,21 +67,15 @@ import net.bytebuddy.utility.JavaModule; import net.bytebuddy.utility.JavaType; import net.bytebuddy.utility.dispatcher.JavaDispatcher; -import org.objectweb.asm.Label; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; +import net.bytebuddy.utility.nullability.AlwaysNull; +import net.bytebuddy.utility.nullability.MaybeNull; +import org.objectweb.asm.*; import java.io.*; -import java.lang.instrument.ClassDefinition; -import java.lang.instrument.ClassFileTransformer; -import java.lang.instrument.Instrumentation; -import java.lang.instrument.UnmodifiableClassException; +import java.lang.instrument.*; import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; -import java.security.AccessControlContext; -import java.security.AccessController; import java.security.PrivilegedAction; import java.security.ProtectionDomain; import java.util.*; @@ -158,6 +153,7 @@ * @param locationStrategy The location strategy to use. * @return A new instance of this agent builder which uses the given location strategy for looking up class files. */ + @SuppressWarnings("overloads") AgentBuilder with(LocationStrategy locationStrategy); /** @@ -236,6 +232,7 @@ * @param fallbackStrategy The fallback strategy to be used. * @return A new agent builder that applies the supplied fallback strategy. */ + @SuppressWarnings("overloads") AgentBuilder with(FallbackStrategy fallbackStrategy); /** @@ -262,6 +259,7 @@ * @param injectionStrategy The injection strategy to use. * @return A new agent builder with the supplied injection strategy configured. */ + @SuppressWarnings("overloads") AgentBuilder with(InjectionStrategy injectionStrategy); /** @@ -307,6 +305,44 @@ AgentBuilder disableClassFormatChanges(); /** + *

+ * Warms up the generated {@link ClassFileTransformer} to trigger class loading of classes used by the transformer + * prior to its actual use. Ideally, warmup should include classes that cause a transformation and classes that + * are ignored. Warming up can be especially useful when transforming classes on the boot path, where circularity + * errors are more likely. At the same time, warming up might load classes that are expected to be unloaded + * when this agent is installed. + *

+ *

+ * Important: Warming up is applied just as a regular transformation and will also invoke the {@link Listener}. + * This is done to avoid that listener classes can cause circularities. It is the users responsibility to suppress + * such log output, if necessary. + *

+ * + * @param type The types to include in the warmup. + * @return A new agent builder that considers the supplied classes in its warmup. + */ + AgentBuilder warmUp(Class... type); + + /** + *

+ * Warms up the generated {@link ClassFileTransformer} to trigger class loading of classes used by the transformer + * prior to its actual use. Ideally, warmup should include classes that cause a transformation and classes that + * are ignored. Warming up can be especially useful when transforming classes on the boot path, where circularity + * errors are more likely. At the same time, warming up might load classes that are expected to be unloaded + * when this agent is installed. + *

+ *

+ * Important: Warming up is applied just as a regular transformation and will also invoke the {@link Listener}. + * This is done to avoid that listener classes can cause circularities. It is the users responsibility to suppress + * such log output, if necessary. + *

+ * + * @param types The types to include in the warmup. + * @return A new agent builder that considers the supplied classes in its warmup. + */ + AgentBuilder warmUp(Collection> types); + + /** * Assures that all modules of the supplied types are read by the module of any instrumented type. If the current VM does not support * the Java module system, calling this method has no effect and this instance is returned. * @@ -669,7 +705,7 @@ * Creates and installs a {@link ResettableClassFileTransformer} that implements the configuration of * this agent builder with a given {@link java.lang.instrument.Instrumentation}. If retransformation is enabled, * the installation also causes all loaded types to be retransformed which have changed compared to the previous - * class file transformer that is provided as an argument. + * class file transformer that is provided as an argument. Without specification, {@link PatchMode#OVERLAP} is used. *

*

* In order to assure the correct handling of the {@link InstallationListener}, an uninstallation should be applied @@ -685,9 +721,28 @@ /** *

* Creates and installs a {@link ResettableClassFileTransformer} that implements the configuration of + * this agent builder with a given {@link java.lang.instrument.Instrumentation}. If retransformation is enabled, + * the installation also causes all loaded types to be retransformed which have changed compared to the previous + * class file transformer that is provided as an argument. + *

+ *

+ * In order to assure the correct handling of the {@link InstallationListener}, an uninstallation should be applied + * via the {@link ResettableClassFileTransformer}'s {@code reset} methods. + *

+ * + * @param instrumentation The instrumentation on which this agent builder's configuration is to be installed. + * @param classFileTransformer The class file transformer that is being patched. + * @param patchMode The patch mode to apply. + * @return The installed class file transformer. + */ + ResettableClassFileTransformer patchOn(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer, PatchMode patchMode); + + /** + *

+ * Creates and installs a {@link ResettableClassFileTransformer} that implements the configuration of * this agent builder with the Byte Buddy-agent which must be installed prior to calling this method. If retransformation * is enabled, the installation also causes all loaded types to be retransformed which have changed compared to the previous - * class file transformer that is provided as an argument. + * class file transformer that is provided as an argument. Without specification, {@link PatchMode#OVERLAP} is used. *

*

* In order to assure the correct handling of the {@link InstallationListener}, an uninstallation should be applied @@ -701,6 +756,25 @@ ResettableClassFileTransformer patchOnByteBuddyAgent(ResettableClassFileTransformer classFileTransformer); /** + *

+ * Creates and installs a {@link ResettableClassFileTransformer} that implements the configuration of + * this agent builder with the Byte Buddy-agent which must be installed prior to calling this method. If retransformation + * is enabled, the installation also causes all loaded types to be retransformed which have changed compared to the previous + * class file transformer that is provided as an argument. + *

+ *

+ * In order to assure the correct handling of the {@link InstallationListener}, an uninstallation should be applied + * via the {@link ResettableClassFileTransformer}'s {@code reset} methods. + *

+ * + * @param classFileTransformer The class file transformer that is being patched. + * @param patchMode The patch mode to apply. + * @return The installed class file transformer. + * @see AgentBuilder#patchOn(Instrumentation, ResettableClassFileTransformer, PatchMode) + */ + ResettableClassFileTransformer patchOnByteBuddyAgent(ResettableClassFileTransformer classFileTransformer, PatchMode patchMode); + + /** * An abstraction for extending a matcher. * * @param The type that is produced by chaining a matcher. @@ -834,7 +908,7 @@ * @param module The module of the instrumented type or {@code null} if the current VM does not support modules. * @return {@code true} if the type should be resubmitted. */ - boolean matches(Throwable throwable, String typeName, ClassLoader classLoader, JavaModule module); + boolean matches(Throwable throwable, String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module); /** * A trivial matcher for resubmission upon an exception. @@ -868,7 +942,7 @@ /** * {@inheritDoc} */ - public boolean matches(Throwable throwable, String typeName, ClassLoader classLoader, JavaModule module) { + public boolean matches(Throwable throwable, String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module) { return matching; } } @@ -912,7 +986,7 @@ /** * {@inheritDoc} */ - public boolean matches(Throwable throwable, String typeName, ClassLoader classLoader, JavaModule module) { + public boolean matches(Throwable throwable, String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module) { for (ResubmissionOnErrorMatcher matcher : matchers) { if (!matcher.matches(throwable, typeName, classLoader, module)) { return false; @@ -961,7 +1035,7 @@ /** * {@inheritDoc} */ - public boolean matches(Throwable throwable, String typeName, ClassLoader classLoader, JavaModule module) { + public boolean matches(Throwable throwable, String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module) { for (ResubmissionOnErrorMatcher matcher : matchers) { if (matcher.matches(throwable, typeName, classLoader, module)) { return true; @@ -1018,7 +1092,7 @@ /** * {@inheritDoc} */ - public boolean matches(Throwable throwable, String typeName, ClassLoader classLoader, JavaModule module) { + public boolean matches(Throwable throwable, String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module) { return exceptionMatcher.matches(throwable) && typeNameMatcher.matches(typeName) && classLoaderMatcher.matches(classLoader) @@ -1040,7 +1114,7 @@ * @param module The module of the instrumented type or {@code null} if the current VM does not support modules. * @return {@code true} if the type should be resubmitted. */ - boolean matches(String typeName, ClassLoader classLoader, JavaModule module); + boolean matches(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module); /** * A trivial matcher for immediate resubmission. @@ -1074,7 +1148,7 @@ /** * {@inheritDoc} */ - public boolean matches(String typeName, ClassLoader classLoader, JavaModule module) { + public boolean matches(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module) { return matching; } } @@ -1118,7 +1192,7 @@ /** * {@inheritDoc} */ - public boolean matches(String typeName, ClassLoader classLoader, JavaModule module) { + public boolean matches(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module) { for (ResubmissionImmediateMatcher matcher : matchers) { if (!matcher.matches(typeName, classLoader, module)) { return false; @@ -1167,7 +1241,7 @@ /** * {@inheritDoc} */ - public boolean matches(String typeName, ClassLoader classLoader, JavaModule module) { + public boolean matches(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module) { for (ResubmissionImmediateMatcher matcher : matchers) { if (matcher.matches(typeName, classLoader, module)) { return true; @@ -1216,7 +1290,7 @@ /** * {@inheritDoc} */ - public boolean matches(String typeName, ClassLoader classLoader, JavaModule module) { + public boolean matches(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module) { return typeNameMatcher.matches(typeName) && classLoaderMatcher.matches(classLoader) && moduleMatcher.matches(module); @@ -1371,6 +1445,7 @@ * @param redefinitionDiscoveryStrategy The redefinition discovery strategy to use. * @return A new instance of this agent builder which makes use of the specified discovery strategy. */ + @SuppressWarnings("overloads") RedefinitionListenable with(RedefinitionStrategy.DiscoveryStrategy redefinitionDiscoveryStrategy); } @@ -1387,6 +1462,7 @@ * @param redefinitionBatchAllocator The batch allocator to use. * @return A new instance of this agent builder which makes use of the specified batch allocator. */ + @SuppressWarnings("overloads") WithImplicitDiscoveryStrategy with(RedefinitionStrategy.BatchAllocator redefinitionBatchAllocator); } } @@ -1455,9 +1531,9 @@ * be applied for the given {@code typeDescription}. */ boolean matches(TypeDescription typeDescription, - ClassLoader classLoader, - JavaModule module, - Class classBeingRedefined, + @MaybeNull ClassLoader classLoader, + @MaybeNull JavaModule module, + @MaybeNull Class classBeingRedefined, ProtectionDomain protectionDomain); /** @@ -1493,9 +1569,9 @@ * {@inheritDoc} */ public boolean matches(TypeDescription typeDescription, - ClassLoader classLoader, - JavaModule module, - Class classBeingRedefined, + @MaybeNull ClassLoader classLoader, + @MaybeNull JavaModule module, + @MaybeNull Class classBeingRedefined, ProtectionDomain protectionDomain) { return matches; } @@ -1534,9 +1610,9 @@ * {@inheritDoc} */ public boolean matches(TypeDescription typeDescription, - ClassLoader classLoader, - JavaModule module, - Class classBeingRedefined, + @MaybeNull ClassLoader classLoader, + @MaybeNull JavaModule module, + @MaybeNull Class classBeingRedefined, ProtectionDomain protectionDomain) { return classBeingRedefined == null == unloaded; } @@ -1557,9 +1633,9 @@ * {@inheritDoc} */ public boolean matches(TypeDescription typeDescription, - ClassLoader classLoader, - JavaModule module, - Class classBeingRedefined, + @MaybeNull ClassLoader classLoader, + @MaybeNull JavaModule module, + @MaybeNull Class classBeingRedefined, ProtectionDomain protectionDomain) { if (classBeingRedefined != null) { try { @@ -1622,9 +1698,9 @@ * {@inheritDoc} */ public boolean matches(TypeDescription typeDescription, - ClassLoader classLoader, - JavaModule module, - Class classBeingRedefined, + @MaybeNull ClassLoader classLoader, + @MaybeNull JavaModule module, + @MaybeNull Class classBeingRedefined, ProtectionDomain protectionDomain) { for (RawMatcher matcher : matchers) { if (!matcher.matches(typeDescription, classLoader, module, classBeingRedefined, protectionDomain)) { @@ -1675,9 +1751,9 @@ * {@inheritDoc} */ public boolean matches(TypeDescription typeDescription, - ClassLoader classLoader, - JavaModule module, - Class classBeingRedefined, + @MaybeNull ClassLoader classLoader, + @MaybeNull JavaModule module, + @MaybeNull Class classBeingRedefined, ProtectionDomain protectionDomain) { for (RawMatcher matcher : matchers) { if (matcher.matches(typeDescription, classLoader, module, classBeingRedefined, protectionDomain)) { @@ -1712,9 +1788,9 @@ * {@inheritDoc} */ public boolean matches(TypeDescription typeDescription, - ClassLoader classLoader, - JavaModule module, - Class classBeingRedefined, + @MaybeNull ClassLoader classLoader, + @MaybeNull JavaModule module, + @MaybeNull Class classBeingRedefined, ProtectionDomain protectionDomain) { return !matcher.matches(typeDescription, classLoader, module, classBeingRedefined, protectionDomain); } @@ -1788,9 +1864,9 @@ * {@inheritDoc} */ public boolean matches(TypeDescription typeDescription, - ClassLoader classLoader, - JavaModule module, - Class classBeingRedefined, + @MaybeNull ClassLoader classLoader, + @MaybeNull JavaModule module, + @MaybeNull Class classBeingRedefined, ProtectionDomain protectionDomain) { return moduleMatcher.matches(module) && classLoaderMatcher.matches(classLoader) && typeMatcher.matches(typeDescription); } @@ -1811,53 +1887,53 @@ * Invoked upon a type being supplied to a transformer. * * @param typeName The binary name of the instrumented type. - * @param classLoader The class loader which is loading this type. + * @param classLoader The class loader which is loading this type or {@code null} if loaded by the boots loader. * @param module The instrumented type's module or {@code null} if the current VM does not support modules. * @param loaded {@code true} if the type is already loaded. */ - void onDiscovery(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded); + void onDiscovery(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded); /** * Invoked prior to a successful transformation being applied. * * @param typeDescription The type that is being transformed. - * @param classLoader The class loader which is loading this type. + * @param classLoader The class loader which is loading this type or {@code null} if loaded by the boots loader. * @param module The transformed type's module or {@code null} if the current VM does not support modules. * @param loaded {@code true} if the type is already loaded. * @param dynamicType The dynamic type that was created. */ - void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded, DynamicType dynamicType); + void onTransformation(TypeDescription typeDescription, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded, DynamicType dynamicType); /** * Invoked when a type is not transformed but ignored. * * @param typeDescription The type being ignored for transformation. - * @param classLoader The class loader which is loading this type. + * @param classLoader The class loader which is loading this type or {@code null} if loaded by the boots loader. * @param module The ignored type's module or {@code null} if the current VM does not support modules. * @param loaded {@code true} if the type is already loaded. */ - void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded); + void onIgnored(TypeDescription typeDescription, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded); /** * Invoked when an error has occurred during transformation. * * @param typeName The binary name of the instrumented type. - * @param classLoader The class loader which is loading this type. + * @param classLoader The class loader which is loading this type or {@code null} if loaded by the boots loader. * @param module The instrumented type's module or {@code null} if the current VM does not support modules. * @param loaded {@code true} if the type is already loaded. * @param throwable The occurred error. */ - void onError(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded, Throwable throwable); + void onError(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded, Throwable throwable); /** * Invoked after a class was attempted to be loaded, independently of its treatment. * * @param typeName The binary name of the instrumented type. - * @param classLoader The class loader which is loading this type. + * @param classLoader The class loader which is loading this type or {@code null} if loaded by the boots loader. * @param module The instrumented type's module or {@code null} if the current VM does not support modules. * @param loaded {@code true} if the type is already loaded. */ - void onComplete(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded); + void onComplete(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded); /** * A no-op implementation of a {@link net.bytebuddy.agent.builder.AgentBuilder.Listener}. @@ -1872,35 +1948,35 @@ /** * {@inheritDoc} */ - public void onDiscovery(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) { + public void onDiscovery(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded) { /* do nothing */ } /** * {@inheritDoc} */ - public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded, DynamicType dynamicType) { + public void onTransformation(TypeDescription typeDescription, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded, DynamicType dynamicType) { /* do nothing */ } /** * {@inheritDoc} */ - public void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded) { + public void onIgnored(TypeDescription typeDescription, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded) { /* do nothing */ } /** * {@inheritDoc} */ - public void onError(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded, Throwable throwable) { + public void onError(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded, Throwable throwable) { /* do nothing */ } /** * {@inheritDoc} */ - public void onComplete(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) { + public void onComplete(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded) { /* do nothing */ } } @@ -1913,35 +1989,35 @@ /** * {@inheritDoc} */ - public void onDiscovery(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) { + public void onDiscovery(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded) { /* do nothing */ } /** * {@inheritDoc} */ - public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded, DynamicType dynamicType) { + public void onTransformation(TypeDescription typeDescription, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded, DynamicType dynamicType) { /* do nothing */ } /** * {@inheritDoc} */ - public void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded) { + public void onIgnored(TypeDescription typeDescription, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded) { /* do nothing */ } /** * {@inheritDoc} */ - public void onError(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded, Throwable throwable) { + public void onError(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded, Throwable throwable) { /* do nothing */ } /** * {@inheritDoc} */ - public void onComplete(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) { + public void onComplete(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded) { /* do nothing */ } } @@ -2011,28 +2087,28 @@ /** * {@inheritDoc} */ - public void onDiscovery(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) { + public void onDiscovery(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded) { printStream.printf(PREFIX + " DISCOVERY %s [%s, %s, %s, loaded=%b]%n", typeName, classLoader, module, Thread.currentThread(), loaded); } /** * {@inheritDoc} */ - public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded, DynamicType dynamicType) { + public void onTransformation(TypeDescription typeDescription, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded, DynamicType dynamicType) { printStream.printf(PREFIX + " TRANSFORM %s [%s, %s, %s, loaded=%b]%n", typeDescription.getName(), classLoader, module, Thread.currentThread(), loaded); } /** * {@inheritDoc} */ - public void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded) { + public void onIgnored(TypeDescription typeDescription, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded) { printStream.printf(PREFIX + " IGNORE %s [%s, %s, %s, loaded=%b]%n", typeDescription.getName(), classLoader, module, Thread.currentThread(), loaded); } /** * {@inheritDoc} */ - public void onError(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded, Throwable throwable) { + public void onError(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded, Throwable throwable) { synchronized (printStream) { printStream.printf(PREFIX + " ERROR %s [%s, %s, %s, loaded=%b]%n", typeName, classLoader, module, Thread.currentThread(), loaded); throwable.printStackTrace(printStream); @@ -2042,7 +2118,7 @@ /** * {@inheritDoc} */ - public void onComplete(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) { + public void onComplete(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded) { printStream.printf(PREFIX + " COMPLETE %s [%s, %s, %s, loaded=%b]%n", typeName, classLoader, module, Thread.currentThread(), loaded); } } @@ -2077,7 +2153,7 @@ /** * {@inheritDoc} */ - public void onDiscovery(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) { + public void onDiscovery(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded) { if (matcher.matches(typeName)) { delegate.onDiscovery(typeName, classLoader, module, loaded); } @@ -2086,7 +2162,7 @@ /** * {@inheritDoc} */ - public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded, DynamicType dynamicType) { + public void onTransformation(TypeDescription typeDescription, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded, DynamicType dynamicType) { if (matcher.matches(typeDescription.getName())) { delegate.onTransformation(typeDescription, classLoader, module, loaded, dynamicType); } @@ -2095,7 +2171,7 @@ /** * {@inheritDoc} */ - public void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded) { + public void onIgnored(TypeDescription typeDescription, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded) { if (matcher.matches(typeDescription.getName())) { delegate.onIgnored(typeDescription, classLoader, module, loaded); } @@ -2104,7 +2180,7 @@ /** * {@inheritDoc} */ - public void onError(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded, Throwable throwable) { + public void onError(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded, Throwable throwable) { if (matcher.matches(typeName)) { delegate.onError(typeName, classLoader, module, loaded, throwable); } @@ -2113,7 +2189,7 @@ /** * {@inheritDoc} */ - public void onComplete(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) { + public void onComplete(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded) { if (matcher.matches(typeName)) { delegate.onComplete(typeName, classLoader, module, loaded); } @@ -2141,12 +2217,12 @@ } @Override - public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded, DynamicType dynamicType) { + public void onTransformation(TypeDescription typeDescription, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded, DynamicType dynamicType) { delegate.onTransformation(typeDescription, classLoader, module, loaded, dynamicType); } @Override - public void onError(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded, Throwable throwable) { + public void onError(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded, Throwable throwable) { delegate.onError(typeName, classLoader, module, loaded, throwable); } } @@ -2172,7 +2248,7 @@ } @Override - public void onError(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded, Throwable throwable) { + public void onError(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded, Throwable throwable) { delegate.onError(typeName, classLoader, module, loaded, throwable); } } @@ -2235,17 +2311,18 @@ } @Override - public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded, DynamicType dynamicType) { + public void onTransformation(TypeDescription typeDescription, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded, DynamicType dynamicType) { if (module != JavaModule.UNSUPPORTED && module.isNamed()) { for (JavaModule target : modules) { if (!module.canRead(target) || addTargetEdge && !module.isOpened(typeDescription.getPackage(), target)) { + PackageDescription location = typeDescription.getPackage(); ClassInjector.UsingInstrumentation.redefineModule(instrumentation, module, Collections.singleton(target), Collections.>emptyMap(), - !addTargetEdge || typeDescription.getPackage() == null + !addTargetEdge || location == null || location.isDefault() ? Collections.>emptyMap() - : Collections.singletonMap(typeDescription.getPackage().getName(), Collections.singleton(target)), + : Collections.singletonMap(location.getName(), Collections.singleton(target)), Collections.>emptySet(), Collections., List>>emptyMap()); } @@ -2302,7 +2379,7 @@ /** * {@inheritDoc} */ - public void onDiscovery(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) { + public void onDiscovery(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded) { for (Listener listener : listeners) { listener.onDiscovery(typeName, classLoader, module, loaded); } @@ -2311,7 +2388,7 @@ /** * {@inheritDoc} */ - public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded, DynamicType dynamicType) { + public void onTransformation(TypeDescription typeDescription, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded, DynamicType dynamicType) { for (Listener listener : listeners) { listener.onTransformation(typeDescription, classLoader, module, loaded, dynamicType); } @@ -2320,7 +2397,7 @@ /** * {@inheritDoc} */ - public void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded) { + public void onIgnored(TypeDescription typeDescription, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded) { for (Listener listener : listeners) { listener.onIgnored(typeDescription, classLoader, module, loaded); } @@ -2329,7 +2406,7 @@ /** * {@inheritDoc} */ - public void onError(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded, Throwable throwable) { + public void onError(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded, Throwable throwable) { for (Listener listener : listeners) { listener.onError(typeName, classLoader, module, loaded, throwable); } @@ -2338,7 +2415,7 @@ /** * {@inheritDoc} */ - public void onComplete(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) { + public void onComplete(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded) { for (Listener listener : listeners) { listener.onComplete(typeName, classLoader, module, loaded); } @@ -2395,30 +2472,37 @@ * A default implementation of a circularity lock. Since class loading already synchronizes on a class loader, * it suffices to apply a thread-local lock. */ - class Default extends ThreadLocal implements CircularityLock { + class Default implements CircularityLock { /** - * Indicates that the circularity lock is not currently acquired. + * A map of threads to an unused boolean to emulate a thread-local state without using + * thread locals. This avoids using thread-local maps and does not interfere with Java + * fibers in case that an instrumentation is executed from a virtual thread where thread + * locals are not permitted. */ - private static final Boolean NOT_ACQUIRED = null; + private final ConcurrentMap threads = new ConcurrentHashMap(); /** * {@inheritDoc} */ public boolean acquire() { - if (get() == NOT_ACQUIRED) { - set(true); - return true; - } else { - return false; - } + return threads.putIfAbsent(Thread.currentThread(), true) == null; } /** * {@inheritDoc} */ public void release() { - set(NOT_ACQUIRED); + threads.remove(Thread.currentThread()); + } + + /** + * Returns {@code true} if the current thread is currently locked. + * + * @return {@code true} if the current thread is currently locked. + */ + protected boolean isLocked() { + return threads.containsKey(Thread.currentThread()); } } @@ -2505,9 +2589,9 @@ ByteBuddy byteBuddy, ClassFileLocator classFileLocator, MethodNameTransformer methodNameTransformer, - ClassLoader classLoader, - JavaModule module, - ProtectionDomain protectionDomain); + @MaybeNull ClassLoader classLoader, + @MaybeNull JavaModule module, + @MaybeNull ProtectionDomain protectionDomain); /** * Default implementations of type strategies. @@ -2523,9 +2607,9 @@ ByteBuddy byteBuddy, ClassFileLocator classFileLocator, MethodNameTransformer methodNameTransformer, - ClassLoader classLoader, - JavaModule module, - ProtectionDomain protectionDomain) { + @MaybeNull ClassLoader classLoader, + @MaybeNull JavaModule module, + @MaybeNull ProtectionDomain protectionDomain) { return byteBuddy.rebase(typeDescription, classFileLocator, methodNameTransformer); } }, @@ -2549,9 +2633,9 @@ ByteBuddy byteBuddy, ClassFileLocator classFileLocator, MethodNameTransformer methodNameTransformer, - ClassLoader classLoader, - JavaModule module, - ProtectionDomain protectionDomain) { + @MaybeNull ClassLoader classLoader, + @MaybeNull JavaModule module, + @MaybeNull ProtectionDomain protectionDomain) { return byteBuddy.redefine(typeDescription, classFileLocator); } }, @@ -2576,9 +2660,9 @@ ByteBuddy byteBuddy, ClassFileLocator classFileLocator, MethodNameTransformer methodNameTransformer, - ClassLoader classLoader, - JavaModule module, - ProtectionDomain protectionDomain) { + @MaybeNull ClassLoader classLoader, + @MaybeNull JavaModule module, + @MaybeNull ProtectionDomain protectionDomain) { return byteBuddy.with(InstrumentedType.Factory.Default.FROZEN) .with(VisibilityBridgeStrategy.Default.NEVER) .redefine(typeDescription, classFileLocator) @@ -2609,9 +2693,9 @@ ByteBuddy byteBuddy, ClassFileLocator classFileLocator, MethodNameTransformer methodNameTransformer, - ClassLoader classLoader, - JavaModule module, - ProtectionDomain protectionDomain) { + @MaybeNull ClassLoader classLoader, + @MaybeNull JavaModule module, + @MaybeNull ProtectionDomain protectionDomain) { return byteBuddy.decorate(typeDescription, classFileLocator); } } @@ -2644,9 +2728,9 @@ ByteBuddy byteBuddy, ClassFileLocator classFileLocator, MethodNameTransformer methodNameTransformer, - ClassLoader classLoader, - JavaModule module, - ProtectionDomain protectionDomain) { + @MaybeNull ClassLoader classLoader, + @MaybeNull JavaModule module, + @MaybeNull ProtectionDomain protectionDomain) { return entryPoint.transform(typeDescription, byteBuddy, classFileLocator, methodNameTransformer); } } @@ -2661,16 +2745,18 @@ /** * Allows for a transformation of a {@link net.bytebuddy.dynamic.DynamicType.Builder}. * - * @param builder The dynamic builder to transform. - * @param typeDescription The description of the type currently being instrumented. - * @param classLoader The class loader of the instrumented class. Might be {@code null} to represent the bootstrap class loader. - * @param module The class's module or {@code null} if the current VM does not support modules. + * @param builder The dynamic builder to transform. + * @param typeDescription The description of the type currently being instrumented. + * @param classLoader The class loader of the instrumented class. Might be {@code null} to represent the bootstrap class loader. + * @param module The class's module or {@code null} if the current VM does not support modules. + * @param protectionDomain The protection domain of the transformed type. * @return A transformed version of the supplied {@code builder}. */ DynamicType.Builder transform(DynamicType.Builder builder, TypeDescription typeDescription, - ClassLoader classLoader, - JavaModule module); + @MaybeNull ClassLoader classLoader, + @MaybeNull JavaModule module, + ProtectionDomain protectionDomain); /** * A transformer that applies a build {@link Plugin}. Note that a transformer is never completed as class loading @@ -2698,8 +2784,9 @@ */ public DynamicType.Builder transform(DynamicType.Builder builder, TypeDescription typeDescription, - ClassLoader classLoader, - JavaModule module) { + @MaybeNull ClassLoader classLoader, + @MaybeNull JavaModule module, + ProtectionDomain protectionDomain) { return plugin.apply(builder, typeDescription, ClassFileLocator.ForClassLoader.of(classLoader)); } } @@ -2803,8 +2890,9 @@ */ public DynamicType.Builder transform(DynamicType.Builder builder, TypeDescription typeDescription, - ClassLoader classLoader, - JavaModule module) { + @MaybeNull ClassLoader classLoader, + @MaybeNull JavaModule module, + ProtectionDomain protectionDomain) { ClassFileLocator classFileLocator = new ClassFileLocator.Compound(this.classFileLocator, locationStrategy.classFileLocator(classLoader, module)); TypePool typePool = poolStrategy.typePool(classFileLocator, classLoader); AsmVisitorWrapper.ForDeclaredMethods asmVisitorWrapper = new AsmVisitorWrapper.ForDeclaredMethods(); @@ -2909,6 +2997,7 @@ * @param name The fully-qualified, binary name of the advice class. * @return A new instance of this advice transformer that applies the given advice to all matched methods of an instrumented type. */ + @SuppressWarnings("overloads") public ForAdvice advice(ElementMatcher matcher, String name) { return advice(new LatentMatcher.Resolved(matcher), name); } @@ -2920,6 +3009,7 @@ * @param name The fully-qualified, binary name of the advice class. * @return A new instance of this advice transformer that applies the given advice to all matched methods of an instrumented type. */ + @SuppressWarnings("overloads") public ForAdvice advice(LatentMatcher matcher, String name) { return new ForAdvice(advice, exceptionHandler, @@ -2938,6 +3028,7 @@ * @param exit The fully-qualified, binary name of the exit advice class. * @return A new instance of this advice transformer that applies the given advice to all matched methods of an instrumented type. */ + @SuppressWarnings("overloads") public ForAdvice advice(ElementMatcher matcher, String enter, String exit) { return advice(new LatentMatcher.Resolved(matcher), enter, exit); } @@ -2950,6 +3041,7 @@ * @param exit The fully-qualified, binary name of the exit advice class. * @return A new instance of this advice transformer that applies the given advice to all matched methods of an instrumented type. */ + @SuppressWarnings("overloads") public ForAdvice advice(LatentMatcher matcher, String enter, String exit) { return new ForAdvice(advice, exceptionHandler, @@ -3074,21 +3166,23 @@ * Creates a type pool for a given class file locator. * * @param classFileLocator The class file locator to use. - * @param classLoader The class loader for which the class file locator was created. + * @param classLoader The class loader for which the class file locator was + * created or {@code null} if the boot loader. * @return A type pool for the supplied class file locator. */ - TypePool typePool(ClassFileLocator classFileLocator, ClassLoader classLoader); + TypePool typePool(ClassFileLocator classFileLocator, @MaybeNull ClassLoader classLoader); /** * Creates a type pool for a given class file locator. If a cache is used, the type that is * currently instrumented is not used. * * @param classFileLocator The class file locator to use. - * @param classLoader The class loader for which the class file locator was created. + * @param classLoader The class loader for which the class file locator + * was created or {@code null} if the boot loader. * @param name The name of the currently instrumented type. * @return A type pool for the supplied class file locator. */ - TypePool typePool(ClassFileLocator classFileLocator, ClassLoader classLoader, String name); + TypePool typePool(ClassFileLocator classFileLocator, @MaybeNull ClassLoader classLoader, String name); /** *

@@ -3134,14 +3228,14 @@ /** * {@inheritDoc} */ - public TypePool typePool(ClassFileLocator classFileLocator, ClassLoader classLoader) { + public TypePool typePool(ClassFileLocator classFileLocator, @MaybeNull ClassLoader classLoader) { return new TypePool.Default.WithLazyResolution(TypePool.CacheProvider.Simple.withObjectType(), classFileLocator, readerMode); } /** * {@inheritDoc} */ - public TypePool typePool(ClassFileLocator classFileLocator, ClassLoader classLoader, String name) { + public TypePool typePool(ClassFileLocator classFileLocator, @MaybeNull ClassLoader classLoader, String name) { return typePool(classFileLocator, classLoader); } } @@ -3190,14 +3284,14 @@ /** * {@inheritDoc} */ - public TypePool typePool(ClassFileLocator classFileLocator, ClassLoader classLoader) { + public TypePool typePool(ClassFileLocator classFileLocator, @MaybeNull ClassLoader classLoader) { return new TypePool.Default(TypePool.CacheProvider.Simple.withObjectType(), classFileLocator, readerMode); } /** * {@inheritDoc} */ - public TypePool typePool(ClassFileLocator classFileLocator, ClassLoader classLoader, String name) { + public TypePool typePool(ClassFileLocator classFileLocator, @MaybeNull ClassLoader classLoader, String name) { return typePool(classFileLocator, classLoader); } } @@ -3247,14 +3341,14 @@ /** * {@inheritDoc} */ - public TypePool typePool(ClassFileLocator classFileLocator, ClassLoader classLoader) { + public TypePool typePool(ClassFileLocator classFileLocator, @MaybeNull ClassLoader classLoader) { return TypePool.ClassLoading.of(classLoader, new TypePool.Default.WithLazyResolution(TypePool.CacheProvider.Simple.withObjectType(), classFileLocator, readerMode)); } /** * {@inheritDoc} */ - public TypePool typePool(ClassFileLocator classFileLocator, ClassLoader classLoader, String name) { + public TypePool typePool(ClassFileLocator classFileLocator, @MaybeNull ClassLoader classLoader, String name) { return typePool(classFileLocator, classLoader); } } @@ -3290,14 +3384,14 @@ /** * {@inheritDoc} */ - public TypePool typePool(ClassFileLocator classFileLocator, ClassLoader classLoader) { + public TypePool typePool(ClassFileLocator classFileLocator, @MaybeNull ClassLoader classLoader) { return new TypePool.Default.WithLazyResolution(locate(classLoader), classFileLocator, readerMode); } /** * {@inheritDoc} */ - public TypePool typePool(ClassFileLocator classFileLocator, ClassLoader classLoader, String name) { + public TypePool typePool(ClassFileLocator classFileLocator, @MaybeNull ClassLoader classLoader, String name) { return new TypePool.Default.WithLazyResolution(new TypePool.CacheProvider.Discriminating(ElementMatchers.is(name), new TypePool.CacheProvider.Simple(), locate(classLoader)), classFileLocator, readerMode); @@ -3306,10 +3400,11 @@ /** * Locates a cache provider for a given class loader. * - * @param classLoader The class loader for which to locate a cache. This class loader might be {@code null} to represent the bootstrap loader. + * @param classLoader The class loader for which to locate a cache. This class loader might + * be {@code null} to represent the bootstrap loader. * @return The cache provider to use. */ - protected abstract TypePool.CacheProvider locate(ClassLoader classLoader); + protected abstract TypePool.CacheProvider locate(@MaybeNull ClassLoader classLoader); /** * An implementation of a type locator {@link WithTypePoolCache} (note documentation of the linked class) that is based on a @@ -3345,7 +3440,7 @@ } @Override - protected TypePool.CacheProvider locate(ClassLoader classLoader) { + protected TypePool.CacheProvider locate(@MaybeNull ClassLoader classLoader) { classLoader = classLoader == null ? getBootstrapMarkerLoader() : classLoader; TypePool.CacheProvider cacheProvider = cacheProviders.get(classLoader); while (cacheProvider == null) { @@ -3414,7 +3509,7 @@ * @param protectionDomain The instrumented type's protection domain or {@code null} if no protection domain is available. * @param injectionStrategy The injection strategy to use. */ - void register(DynamicType dynamicType, ClassLoader classLoader, ProtectionDomain protectionDomain, InjectionStrategy injectionStrategy); + void register(DynamicType dynamicType, @MaybeNull ClassLoader classLoader, @MaybeNull ProtectionDomain protectionDomain, InjectionStrategy injectionStrategy); } /** @@ -3444,7 +3539,7 @@ /** * {@inheritDoc} */ - public void register(DynamicType dynamicType, ClassLoader classLoader, ProtectionDomain protectionDomain, InjectionStrategy injectionStrategy) { + public void register(DynamicType dynamicType, @MaybeNull ClassLoader classLoader, @MaybeNull ProtectionDomain protectionDomain, InjectionStrategy injectionStrategy) { /* do nothing */ } } @@ -3478,7 +3573,7 @@ /** * {@inheritDoc} */ - public void register(DynamicType dynamicType, ClassLoader classLoader, ProtectionDomain protectionDomain, InjectionStrategy injectionStrategy) { + public void register(DynamicType dynamicType, @MaybeNull ClassLoader classLoader, @MaybeNull ProtectionDomain protectionDomain, InjectionStrategy injectionStrategy) { Map auxiliaryTypes = dynamicType.getAuxiliaryTypes(); Map independentTypes = new LinkedHashMap(auxiliaryTypes); for (TypeDescription auxiliaryType : auxiliaryTypes.keySet()) { @@ -3520,7 +3615,7 @@ /** * {@inheritDoc} */ - @SuppressFBWarnings(value = "DMI_RANDOM_USED_ONLY_ONCE", justification = "Avoiding synchronization without security concerns") + @SuppressFBWarnings(value = "DMI_RANDOM_USED_ONLY_ONCE", justification = "Avoids thread-contention.") public InitializationStrategy.Dispatcher dispatcher() { return dispatcher(new Random().nextInt()); } @@ -3677,7 +3772,7 @@ /** * {@inheritDoc} */ - public void register(DynamicType dynamicType, ClassLoader classLoader, ProtectionDomain protectionDomain, InjectionStrategy injectionStrategy) { + public void register(DynamicType dynamicType, @MaybeNull ClassLoader classLoader, @MaybeNull ProtectionDomain protectionDomain, InjectionStrategy injectionStrategy) { Map auxiliaryTypes = dynamicType.getAuxiliaryTypes(); LoadedTypeInitializer loadedTypeInitializer; if (!auxiliaryTypes.isEmpty()) { @@ -3753,7 +3848,7 @@ /** * {@inheritDoc} */ - public void register(DynamicType dynamicType, ClassLoader classLoader, ProtectionDomain protectionDomain, InjectionStrategy injectionStrategy) { + public void register(DynamicType dynamicType, @MaybeNull ClassLoader classLoader, @MaybeNull ProtectionDomain protectionDomain, InjectionStrategy injectionStrategy) { Map auxiliaryTypes = dynamicType.getAuxiliaryTypes(); LoadedTypeInitializer loadedTypeInitializer = auxiliaryTypes.isEmpty() ? dynamicType.getLoadedTypeInitializers().get(dynamicType.getTypeDescription()) @@ -3807,7 +3902,7 @@ /** * {@inheritDoc} */ - public void register(DynamicType dynamicType, ClassLoader classLoader, ProtectionDomain protectionDomain, InjectionStrategy injectionStrategy) { + public void register(DynamicType dynamicType, @MaybeNull ClassLoader classLoader, @MaybeNull ProtectionDomain protectionDomain, InjectionStrategy injectionStrategy) { Map auxiliaryTypes = dynamicType.getAuxiliaryTypes(); Map loadedTypeInitializers = dynamicType.getLoadedTypeInitializers(); if (!auxiliaryTypes.isEmpty()) { @@ -3831,11 +3926,11 @@ /** * Resolves the class injector to use for a given class loader and protection domain. * - * @param classLoader The class loader to use. - * @param protectionDomain The protection domain to use. + * @param classLoader The class loader to use or {@code null} if using the bootstrap loader. + * @param protectionDomain The protection domain to use or {@code null} if all privileges should be assigned. * @return The class injector to use. */ - ClassInjector resolve(ClassLoader classLoader, ProtectionDomain protectionDomain); + ClassInjector resolve(@MaybeNull ClassLoader classLoader, @MaybeNull ProtectionDomain protectionDomain); /** * An injection strategy that does not permit class injection. @@ -3850,7 +3945,7 @@ /** * {@inheritDoc} */ - public ClassInjector resolve(ClassLoader classLoader, ProtectionDomain protectionDomain) { + public ClassInjector resolve(@MaybeNull ClassLoader classLoader, @MaybeNull ProtectionDomain protectionDomain) { throw new IllegalStateException("Class injection is disabled"); } } @@ -3868,7 +3963,7 @@ /** * {@inheritDoc} */ - public ClassInjector resolve(ClassLoader classLoader, ProtectionDomain protectionDomain) { + public ClassInjector resolve(@MaybeNull ClassLoader classLoader, @MaybeNull ProtectionDomain protectionDomain) { if (classLoader == null) { throw new IllegalStateException("Cannot inject auxiliary class into bootstrap loader using reflection"); } else if (ClassInjector.UsingReflection.isAvailable()) { @@ -3892,7 +3987,7 @@ /** * {@inheritDoc} */ - public ClassInjector resolve(ClassLoader classLoader, ProtectionDomain protectionDomain) { + public ClassInjector resolve(@MaybeNull ClassLoader classLoader, @MaybeNull ProtectionDomain protectionDomain) { if (ClassInjector.UsingUnsafe.isAvailable()) { return new ClassInjector.UsingUnsafe(classLoader, protectionDomain); } else { @@ -3923,7 +4018,7 @@ /** * {@inheritDoc} */ - public ClassInjector resolve(ClassLoader classLoader, ProtectionDomain protectionDomain) { + public ClassInjector resolve(@MaybeNull ClassLoader classLoader, @MaybeNull ProtectionDomain protectionDomain) { return factory.make(classLoader, protectionDomain); } } @@ -3942,7 +4037,7 @@ /** * {@inheritDoc} */ - public ClassInjector resolve(ClassLoader classLoader, ProtectionDomain protectionDomain) { + public ClassInjector resolve(@MaybeNull ClassLoader classLoader, @MaybeNull ProtectionDomain protectionDomain) { if (ClassInjector.UsingJna.isAvailable()) { return new ClassInjector.UsingJna(classLoader, protectionDomain); } else { @@ -3981,7 +4076,7 @@ /** * {@inheritDoc} */ - public ClassInjector resolve(ClassLoader classLoader, ProtectionDomain protectionDomain) { + public ClassInjector resolve(@MaybeNull ClassLoader classLoader, @MaybeNull ProtectionDomain protectionDomain) { return classLoader == null ? ClassInjector.UsingInstrumentation.of(folder, ClassInjector.UsingInstrumentation.Target.BOOTSTRAP, instrumentation) : UsingReflection.INSTANCE.resolve(classLoader, protectionDomain); @@ -4013,7 +4108,7 @@ * @param module The type's module or {@code null} if the current VM does not support modules. * @return An appropriate type description. */ - TypeDescription apply(String name, Class type, TypePool typePool, CircularityLock circularityLock, ClassLoader classLoader, JavaModule module); + TypeDescription apply(String name, @MaybeNull Class type, TypePool typePool, CircularityLock circularityLock, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module); /** * Default implementations of a {@link DescriptionStrategy}. @@ -4035,11 +4130,11 @@ HYBRID(true) { /** {@inheritDoc} */ public TypeDescription apply(String name, - Class type, + @MaybeNull Class type, TypePool typePool, CircularityLock circularityLock, - ClassLoader classLoader, - JavaModule module) { + @MaybeNull ClassLoader classLoader, + @MaybeNull JavaModule module) { return type == null ? typePool.describe(name).resolve() : TypeDescription.ForLoadedType.of(type); @@ -4062,11 +4157,11 @@ POOL_ONLY(false) { /** {@inheritDoc} */ public TypeDescription apply(String name, - Class type, + @MaybeNull Class type, TypePool typePool, CircularityLock circularityLock, - ClassLoader classLoader, - JavaModule module) { + @MaybeNull ClassLoader classLoader, + @MaybeNull JavaModule module) { return typePool.describe(name).resolve(); } }, @@ -4086,11 +4181,11 @@ POOL_FIRST(false) { /** {@inheritDoc} */ public TypeDescription apply(String name, - Class type, + @MaybeNull Class type, TypePool typePool, CircularityLock circularityLock, - ClassLoader classLoader, - JavaModule module) { + @MaybeNull ClassLoader classLoader, + @MaybeNull JavaModule module) { TypePool.Resolution resolution = typePool.describe(name); return resolution.isResolved() || type == null ? resolution.resolve() @@ -4184,11 +4279,11 @@ * {@inheritDoc} */ public TypeDescription apply(String name, - Class type, + @MaybeNull Class type, TypePool typePool, CircularityLock circularityLock, - ClassLoader classLoader, - JavaModule module) { + @MaybeNull ClassLoader classLoader, + @MaybeNull JavaModule module) { TypeDescription typeDescription = delegate.apply(name, type, typePool, circularityLock, classLoader, module); return typeDescription instanceof TypeDescription.ForLoadedType ? typeDescription @@ -4218,7 +4313,7 @@ /** * {@inheritDoc} */ - public Class load(String name, ClassLoader classLoader) throws ClassNotFoundException { + public Class load(String name, @MaybeNull ClassLoader classLoader) throws ClassNotFoundException { circularityLock.release(); try { return Class.forName(name, false, classLoader); @@ -4293,11 +4388,11 @@ * {@inheritDoc} */ public TypeDescription apply(String name, - Class type, + @MaybeNull Class type, TypePool typePool, CircularityLock circularityLock, - ClassLoader classLoader, - JavaModule module) { + @MaybeNull ClassLoader classLoader, + @MaybeNull JavaModule module) { TypeDescription typeDescription = delegate.apply(name, type, typePool, circularityLock, classLoader, module); return typeDescription instanceof TypeDescription.ForLoadedType ? typeDescription @@ -4327,7 +4422,7 @@ /** * {@inheritDoc} */ - public Class load(String name, ClassLoader classLoader) { + public Class load(String name, @MaybeNull ClassLoader classLoader) { boolean holdsLock = classLoader != null && Thread.holdsLock(classLoader); AtomicBoolean signal = new AtomicBoolean(holdsLock); Future> future = executorService.submit(holdsLock @@ -4359,6 +4454,8 @@ /** * The type's class loader or {@code null} if the type is loaded by the bootstrap loader. */ + @MaybeNull + @HashCodeAndEqualsPlugin.ValueHandling(HashCodeAndEqualsPlugin.ValueHandling.Sort.REVERSE_NULLABILITY) private final ClassLoader classLoader; /** @@ -4367,7 +4464,7 @@ * @param name The loaded type's name. * @param classLoader The type's class loader or {@code null} if the type is loaded by the bootstrap loader. */ - protected SimpleClassLoadingAction(String name, ClassLoader classLoader) { + protected SimpleClassLoadingAction(String name, @MaybeNull ClassLoader classLoader) { this.name = name; this.classLoader = classLoader; } @@ -4383,6 +4480,7 @@ /** * A class loading action that notifies the class loader's lock after the type was loaded. */ + @HashCodeAndEqualsPlugin.Enhance protected static class NotifyingClassLoadingAction implements Callable> { /** @@ -4391,7 +4489,7 @@ private final String name; /** - * The type's class loader or {@code null} if the type is loaded by the bootstrap loader. + * The type's class loader which must not be the boot loader, i.e {@code null}. */ private final ClassLoader classLoader; @@ -4404,7 +4502,7 @@ * Creates a notifying class loading action. * * @param name The loaded type's name. - * @param classLoader The type's class loader or {@code null} if the type is loaded by the bootstrap loader. + * @param classLoader The type's class loader which must not be the boot loader, i.e {@code null}. * @param signal The signal that indicates the completion of the class loading with {@code false}. */ protected NotifyingClassLoadingAction(String name, ClassLoader classLoader, AtomicBoolean signal) { @@ -4444,7 +4542,7 @@ * @param module The type's module or {@code null} if Java modules are not supported on the current VM. * @return The class file locator to use. */ - ClassFileLocator classFileLocator(ClassLoader classLoader, JavaModule module); + ClassFileLocator classFileLocator(@MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module); /** * A location strategy that never locates any byte code. @@ -4459,7 +4557,7 @@ /** * {@inheritDoc} */ - public ClassFileLocator classFileLocator(ClassLoader classLoader, JavaModule module) { + public ClassFileLocator classFileLocator(@MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module) { return ClassFileLocator.NoOp.INSTANCE; } } @@ -4474,7 +4572,7 @@ */ STRONG { /** {@inheritDoc} */ - public ClassFileLocator classFileLocator(ClassLoader classLoader, JavaModule module) { + public ClassFileLocator classFileLocator(@MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module) { return ClassFileLocator.ForClassLoader.of(classLoader); } }, @@ -4485,7 +4583,7 @@ */ WEAK { /** {@inheritDoc} */ - public ClassFileLocator classFileLocator(ClassLoader classLoader, JavaModule module) { + public ClassFileLocator classFileLocator(@MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module) { return ClassFileLocator.ForClassLoader.WeaklyReferenced.of(classLoader); } }; @@ -4563,7 +4661,7 @@ /** * {@inheritDoc} */ - public ClassFileLocator classFileLocator(ClassLoader classLoader, JavaModule module) { + public ClassFileLocator classFileLocator(@MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module) { return classFileLocator; } } @@ -4607,7 +4705,7 @@ /** * {@inheritDoc} */ - public ClassFileLocator classFileLocator(ClassLoader classLoader, JavaModule module) { + public ClassFileLocator classFileLocator(@MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module) { List classFileLocators = new ArrayList(locationStrategies.size()); for (LocationStrategy locationStrategy : locationStrategies) { classFileLocators.add(locationStrategy.classFileLocator(classLoader, module)); @@ -4734,6 +4832,7 @@ /** * Indicates that an exception is handled. */ + @AlwaysNull Throwable SUPPRESS_ERROR = null; /** @@ -4762,6 +4861,7 @@ * @param throwable The throwable that causes the error. * @return The error to propagate or {@code null} if the error is handled. Any subsequent listeners are not called if the exception is handled. */ + @MaybeNull Throwable onError(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer, Throwable throwable); /** @@ -4773,6 +4873,33 @@ void onReset(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer); /** + * Invoked before a warump is executed. + * + * @param types The types that are used for the warmup. + * @param classFileTransformer The class file transformer that is warmed up. + */ + void onBeforeWarmUp(Set> types, ResettableClassFileTransformer classFileTransformer); + + /** + * Invoked when a class yields an unexpected error that is not catched by the listener. + * + * @param type The type that caused the error. + * @param classFileTransformer The class file transformer that is warmed up. + * @param throwable The throwable that represents the error. + */ + void onWarmUpError(Class type, ResettableClassFileTransformer classFileTransformer, Throwable throwable); + + /** + * Invoked after a warump is executed. + * + * @param types The types that are used for the warmup mapped to their transformed byte code + * or {@code null} if the type was not transformed or failed to transform. + * @param classFileTransformer The class file transformer that is warmed up. + * @param transformed {@code true} if at least one class caused an actual transformation. + */ + void onAfterWarmUp(Map, byte[]> types, ResettableClassFileTransformer classFileTransformer, boolean transformed); + + /** * A non-operational listener that does not do anything. */ enum NoOp implements InstallationListener { @@ -4809,6 +4936,27 @@ public void onReset(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer) { /* do nothing */ } + + /** + * {@inheritDoc} + */ + public void onBeforeWarmUp(Set> types, ResettableClassFileTransformer classFileTransformer) { + /* do nothing */ + } + + /** + * {@inheritDoc} + */ + public void onWarmUpError(Class type, ResettableClassFileTransformer classFileTransformer, Throwable throwable) { + /* do nothing */ + } + + /** + * {@inheritDoc} + */ + public void onAfterWarmUp(Map, byte[]> types, ResettableClassFileTransformer classFileTransformer, boolean transformed) { + /* do nothing */ + } } /** @@ -4838,6 +4986,7 @@ /** * {@inheritDoc} */ + @MaybeNull public Throwable onError(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer, Throwable throwable) { return SUPPRESS_ERROR; } @@ -4848,6 +4997,27 @@ public void onReset(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer) { /* do nothing */ } + + /** + * {@inheritDoc} + */ + public void onBeforeWarmUp(Set> types, ResettableClassFileTransformer classFileTransformer) { + /* do nothing */ + } + + /** + * {@inheritDoc} + */ + public void onWarmUpError(Class type, ResettableClassFileTransformer classFileTransformer, Throwable throwable) { + /* do nothing */ + } + + /** + * {@inheritDoc} + */ + public void onAfterWarmUp(Map, byte[]> types, ResettableClassFileTransformer classFileTransformer, boolean transformed) { + /* do nothing */ + } } /** @@ -4882,6 +5052,27 @@ public void onReset(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer) { /* do nothing */ } + + /** + * {@inheritDoc} + */ + public void onBeforeWarmUp(Set> types, ResettableClassFileTransformer classFileTransformer) { + /* do nothing */ + } + + /** + * {@inheritDoc} + */ + public void onWarmUpError(Class type, ResettableClassFileTransformer classFileTransformer, Throwable throwable) { + /* do nothing */ + } + + /** + * {@inheritDoc} + */ + public void onAfterWarmUp(Map, byte[]> types, ResettableClassFileTransformer classFileTransformer, boolean transformed) { + /* do nothing */ + } } /** @@ -4958,6 +5149,30 @@ public void onReset(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer) { printStream.printf(PREFIX + " RESET %s on %s%n", classFileTransformer, instrumentation); } + + /** + * {@inheritDoc} + */ + public void onBeforeWarmUp(Set> types, ResettableClassFileTransformer classFileTransformer) { + printStream.printf(PREFIX + " BEFORE_WARMUP %s on %s%n", classFileTransformer, types); + } + + /** + * {@inheritDoc} + */ + public void onWarmUpError(Class type, ResettableClassFileTransformer classFileTransformer, Throwable throwable) { + synchronized (printStream) { + printStream.printf(PREFIX + " ERROR_WARMUP %s on %s%n", classFileTransformer, type); + throwable.printStackTrace(printStream); + } + } + + /** + * {@inheritDoc} + */ + public void onAfterWarmUp(Map, byte[]> types, ResettableClassFileTransformer classFileTransformer, boolean transformed) { + printStream.printf(PREFIX + " AFTER_WARMUP %s %s on %s%n", transformed ? "transformed" : "not transformed", classFileTransformer, types.keySet()); + } } /** @@ -5017,6 +5232,7 @@ /** * {@inheritDoc} */ + @MaybeNull public Throwable onError(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer, Throwable throwable) { for (InstallationListener installationListener : installationListeners) { if (throwable == SUPPRESS_ERROR) { @@ -5035,6 +5251,33 @@ installationListener.onReset(instrumentation, classFileTransformer); } } + + /** + * {@inheritDoc} + */ + public void onBeforeWarmUp(Set> types, ResettableClassFileTransformer classFileTransformer) { + for (InstallationListener installationListener : installationListeners) { + installationListener.onBeforeWarmUp(types, classFileTransformer); + } + } + + /** + * {@inheritDoc} + */ + public void onWarmUpError(Class type, ResettableClassFileTransformer classFileTransformer, Throwable throwable) { + for (InstallationListener installationListener : installationListeners) { + installationListener.onWarmUpError(type, classFileTransformer, throwable); + } + } + + /** + * {@inheritDoc} + */ + public void onAfterWarmUp(Map, byte[]> types, ResettableClassFileTransformer classFileTransformer, boolean transformed) { + for (InstallationListener installationListener : installationListeners) { + installationListener.onAfterWarmUp(types, classFileTransformer, transformed); + } + } } } @@ -5053,7 +5296,7 @@ * @param protectionDomain The instrumented type's protection domain. * @return An appropriate class file locator. */ - ClassFileLocator resolve(String name, byte[] binaryRepresentation, ClassLoader classLoader, JavaModule module, ProtectionDomain protectionDomain); + ClassFileLocator resolve(String name, byte[] binaryRepresentation, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, ProtectionDomain protectionDomain); /** * Resolves the type pool for a given type name by the supplied {@link PoolStrategy}. @@ -5064,7 +5307,7 @@ * @param name The name of the type for which the type pool is resolved. * @return A suitable type pool. */ - TypePool typePool(PoolStrategy poolStrategy, ClassFileLocator classFileLocator, ClassLoader classLoader, String name); + TypePool typePool(PoolStrategy poolStrategy, ClassFileLocator classFileLocator, @MaybeNull ClassLoader classLoader, String name); /** * An implementation of default class file buffer strategy. @@ -5078,8 +5321,8 @@ /** {@inheritDoc} */ public ClassFileLocator resolve(String name, byte[] binaryRepresentation, - ClassLoader classLoader, - JavaModule module, + @MaybeNull ClassLoader classLoader, + @MaybeNull JavaModule module, ProtectionDomain protectionDomain) { return ClassFileLocator.Simple.of(name, binaryRepresentation); } @@ -5087,7 +5330,7 @@ /** {@inheritDoc} */ public TypePool typePool(PoolStrategy poolStrategy, ClassFileLocator classFileLocator, - ClassLoader classLoader, + @MaybeNull ClassLoader classLoader, String name) { return poolStrategy.typePool(classFileLocator, classLoader, name); } @@ -5105,8 +5348,8 @@ /** {@inheritDoc} */ public ClassFileLocator resolve(String name, byte[] binaryRepresentation, - ClassLoader classLoader, - JavaModule module, + @MaybeNull ClassLoader classLoader, + @MaybeNull JavaModule module, ProtectionDomain protectionDomain) { return ClassFileLocator.NoOp.INSTANCE; } @@ -5114,7 +5357,7 @@ /** {@inheritDoc} */ public TypePool typePool(PoolStrategy poolStrategy, ClassFileLocator classFileLocator, - ClassLoader classLoader, + @MaybeNull ClassLoader classLoader, String name) { return poolStrategy.typePool(classFileLocator, classLoader); } @@ -5254,7 +5497,7 @@ *

* Note: When applying a redefinition, it is normally required to use a {@link TypeStrategy} that applies * a redefinition instead of rebasing classes such as {@link TypeStrategy.Default#REDEFINE}. Also, consider - * the constrains given by this type strategy. + * the constraints given by this type strategy. *

*/ REDEFINITION(true, false) { @@ -5296,7 +5539,7 @@ *

* Note: When applying a retransformation, it is normally required to use a {@link TypeStrategy} that applies * a redefinition instead of rebasing classes such as {@link TypeStrategy.Default#REDEFINE}. Also, consider - * the constrains given by this type strategy. + * the constraints given by this type strategy. *

*/ RETRANSFORMATION(true, true) { @@ -6325,6 +6568,7 @@ /** * The current iterator or {@code null} if no such iterator is defined. */ + @MaybeNull private Iterator>> current; /** @@ -6481,6 +6725,7 @@ /** * The current list of types or {@code null} if the current list of types is not prepared. */ + @MaybeNull private List> types; /** @@ -6530,6 +6775,127 @@ throw new UnsupportedOperationException("remove"); } } + + /** + *

+ * A discovery strategy that simplifies the application of {@link Reiterating} by assuming that the + * loaded classes that are returned by {@link Instrumentation#getAllLoadedClasses()} are always + * returned in the same order. + *

+ *

+ * Important: While this increases the performance of reiteration, it relies on an implementation + * detail of the JVM. Also, this strategy does not consider the possibility of classes being unloaded + * during reiteration. For these reasons, this strategy has to be used with care! + *

+ */ + enum WithSortOrderAssumption implements DiscoveryStrategy { + + /** + * The singleton instance. + */ + INSTANCE; + + /** + * {@inheritDoc} + */ + public Iterable>> resolve(Instrumentation instrumentation) { + return new OrderedReiteratingIterable(instrumentation); + } + + /** + * An iterable that reiterates over an array of loaded classes by the previously observed length. + */ + @HashCodeAndEqualsPlugin.Enhance + protected static class OrderedReiteratingIterable implements Iterable>> { + + /** + * The instrumentation instance to use. + */ + private final Instrumentation instrumentation; + + /** + * Creates a new reiterating iterable. + * + * @param instrumentation The instrumentation instance to use. + */ + protected OrderedReiteratingIterable(Instrumentation instrumentation) { + this.instrumentation = instrumentation; + } + + /** + * {@inheritDoc} + */ + public Iterator>> iterator() { + return new OrderedReiteratingIterator(instrumentation); + } + } + + /** + * An iterator that reiterates over an array of loaded classes by the previously observed length. + */ + protected static class OrderedReiteratingIterator implements Iterator>> { + + /** + * The instrumentation instance to use. + */ + private final Instrumentation instrumentation; + + /** + * The length of the last known array of known classes. + */ + private int index; + + /** + * The current list of types or {@code null} if the current list of types is not prepared. + */ + @MaybeNull + private List> types; + + /** + * Creates a new reiterating iterator. + * + * @param instrumentation The instrumentation instance to use. + */ + protected OrderedReiteratingIterator(Instrumentation instrumentation) { + this.instrumentation = instrumentation; + index = 0; + } + + /** + * {@inheritDoc} + */ + public boolean hasNext() { + if (types == null) { + Class[] type = instrumentation.getAllLoadedClasses(); + types = new ArrayList>(Arrays.asList(type).subList(index, type.length)); + index = type.length; + } + return !types.isEmpty(); + } + + /** + * {@inheritDoc} + */ + public Iterable> next() { + if (hasNext()) { + try { + return types; + } finally { + types = null; + } + } else { + throw new NoSuchElementException(); + } + } + + /** + * {@inheritDoc} + */ + public void remove() { + throw new UnsupportedOperationException("remove"); + } + } + } } /** @@ -6947,8 +7313,8 @@ /** * {@inheritDoc} */ - @SuppressFBWarnings(value = "GC_UNRELATED_TYPES", justification = "Use of unrelated key is intended for avoiding unnecessary weak reference") - public void onError(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded, Throwable throwable) { + @SuppressFBWarnings(value = "GC_UNRELATED_TYPES", justification = "Cross-comparison is intended.") + public void onError(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded, Throwable throwable) { if (!loaded && resubmissionOnErrorMatcher.matches(throwable, typeName, classLoader, module)) { Set types = this.types.get(new LookupKey(classLoader)); if (types == null) { @@ -6965,8 +7331,8 @@ /** * {@inheritDoc} */ - @SuppressFBWarnings(value = "GC_UNRELATED_TYPES", justification = "Use of unrelated key is intended for avoiding unnecessary weak reference") - public boolean isEnforced(String typeName, ClassLoader classLoader, JavaModule module, Class classBeingRedefined) { + @SuppressFBWarnings(value = "GC_UNRELATED_TYPES", justification = "Cross-comparison is intended.") + public boolean isEnforced(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, @MaybeNull Class classBeingRedefined) { if (classBeingRedefined == null && resubmissionImmediateMatcher.matches(typeName, classLoader, module)) { Set types = this.types.get(new LookupKey(classLoader)); if (types == null) { @@ -7101,6 +7467,7 @@ /** * This scheduler's cancelable or {@code null} if no cancelable was registered. */ + @MaybeNull private volatile ResubmissionScheduler.Cancelable cancelable; /** @@ -7223,6 +7590,7 @@ /** * The represented class loader. */ + @MaybeNull private final ClassLoader classLoader; /** @@ -7235,7 +7603,7 @@ * * @param classLoader The represented class loader. */ - protected LookupKey(ClassLoader classLoader) { + protected LookupKey(@MaybeNull ClassLoader classLoader) { this.classLoader = classLoader; hashCode = System.identityHashCode(classLoader); } @@ -7246,8 +7614,8 @@ } @Override - @SuppressFBWarnings(value = "EQ_CHECK_FOR_OPERAND_NOT_COMPATIBLE_WITH_THIS", justification = "Cross-comparison is intended") - public boolean equals(Object other) { + @SuppressFBWarnings(value = "EQ_CHECK_FOR_OPERAND_NOT_COMPATIBLE_WITH_THIS", justification = "Cross-comparison is intended.") + public boolean equals(@MaybeNull Object other) { if (this == other) { return true; } else if (other instanceof LookupKey) { @@ -7276,7 +7644,7 @@ * * @param classLoader The represented class loader or {@code null} for the bootstrap class loader. */ - protected StorageKey(ClassLoader classLoader) { + protected StorageKey(@MaybeNull ClassLoader classLoader) { super(classLoader); hashCode = System.identityHashCode(classLoader); } @@ -7296,8 +7664,8 @@ } @Override - @SuppressFBWarnings(value = "EQ_CHECK_FOR_OPERAND_NOT_COMPATIBLE_WITH_THIS", justification = "Cross-comparison is intended") - public boolean equals(Object other) { + @SuppressFBWarnings(value = "EQ_CHECK_FOR_OPERAND_NOT_COMPATIBLE_WITH_THIS", justification = "Cross-comparison is intended.") + public boolean equals(@MaybeNull Object other) { if (this == other) { return true; } else if (other instanceof LookupKey) { @@ -7385,12 +7753,12 @@ * Returns {@code true} if a class should be scheduled for resubmission. * * @param typeName The name of the instrumented class. - * @param classLoader The class loader of the instrumented class. + * @param classLoader The class loader of the instrumented class or {@code null} if the boot loader. * @param module The module of the instrumented class or {@code null} if the module system is not supported. * @param classBeingRedefined The class to be redefined or {@code null} if the current type is loaded for the first time. * @return {@code true} if the class should be scheduled for resubmission. */ - boolean isEnforced(String typeName, ClassLoader classLoader, JavaModule module, Class classBeingRedefined); + boolean isEnforced(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, @MaybeNull Class classBeingRedefined); /** * A resubmission enforcer that does not consider non-loaded classes. @@ -7405,7 +7773,7 @@ /** * {@inheritDoc} */ - public boolean isEnforced(String typeName, ClassLoader classLoader, JavaModule module, Class classBeingRedefined) { + public boolean isEnforced(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, @MaybeNull Class classBeingRedefined) { return false; } } @@ -7581,8 +7949,8 @@ AgentBuilder.Listener listener, TypeDescription typeDescription, Class type, - Class classBeingRedefined, - JavaModule module, + @MaybeNull Class classBeingRedefined, + @MaybeNull JavaModule module, boolean modifiable) { if (!modifiable || !matcher.matches(typeDescription, type.getClassLoader(), module, classBeingRedefined, type.getProtectionDomain())) { try { @@ -7656,7 +8024,7 @@ /** * The backlog of iterators to apply. */ - private final LinkedList>>> backlog; + private final List>>> backlog; /** * Creates a new prependable iterator. @@ -7665,7 +8033,7 @@ */ protected PrependableIterator(Iterable>> origin) { current = origin.iterator(); - backlog = new LinkedList>>>(); + backlog = new ArrayList>>>(); } /** @@ -7677,7 +8045,7 @@ Iterator>> iterator = iterable.iterator(); if (iterator.hasNext()) { if (current.hasNext()) { - backlog.addLast(current); + backlog.add(current); } current = iterator; } @@ -7698,7 +8066,7 @@ return current.next(); } finally { while (!current.hasNext() && !backlog.isEmpty()) { - current = backlog.removeLast(); + current = backlog.remove(backlog.size() - 1); } } } @@ -7817,7 +8185,13 @@ } /** + *

* Implements the instrumentation of the {@code LambdaMetafactory} if this feature is enabled. + *

+ *

+ * Warning: This feature is not recommended for production systems but only for experiments or debugging + * purposes. It might no longer work in a future release and makes assumptions over JVM-internal API. + *

*/ enum LambdaInstrumentationStrategy { @@ -7847,16 +8221,17 @@ } byteBuddy.with(Implementation.Context.Disabled.Factory.INSTANCE) .redefine(lambdaMetaFactory) - .visit(new AsmVisitorWrapper.ForDeclaredMethods() - .method(named("metafactory"), MetaFactoryRedirection.INSTANCE) - .method(named("altMetafactory"), AlternativeMetaFactoryRedirection.INSTANCE)) + .method(ElementMatchers.isPublic().and(named("metafactory"))) + .intercept(new Implementation.Simple(LambdaMetafactoryFactory.REGULAR)) + .method(ElementMatchers.isPublic().and(named("altMetafactory"))) + .intercept(new Implementation.Simple(LambdaMetafactoryFactory.ALTERNATIVE)) .make() .load(lambdaMetaFactory.getClassLoader(), ClassReloadingStrategy.of(instrumentation)); } } @Override - protected boolean isInstrumented(Class type) { + protected boolean isInstrumented(@MaybeNull Class type) { return true; } }, @@ -7873,24 +8248,12 @@ } @Override - protected boolean isInstrumented(Class type) { + protected boolean isInstrumented(@MaybeNull Class type) { return type == null || !type.getName().contains("/"); } }; /** - * The name of the current VM's {@code Unsafe} class that is visible to the bootstrap loader. - */ - private static final String UNSAFE_CLASS = ClassFileVersion.ofThisVm(ClassFileVersion.JAVA_V5).isAtLeast(ClassFileVersion.JAVA_V9) - ? "jdk/internal/misc/Unsafe" - : "sun/misc/Unsafe"; - - /** - * Indicates that an original implementation can be ignored when redefining a method. - */ - protected static final MethodVisitor IGNORE_ORIGINAL = null; - - /** * Releases the supplied class file transformer when it was built with {@link AgentBuilder#with(LambdaInstrumentationStrategy)} enabled. * Subsequently, the class file transformer is no longer applied when a class that represents a lambda expression is created. * @@ -7944,156 +8307,859 @@ * @param type The redefined type or {@code null} if no such type exists. * @return {@code true} if the supplied type should be instrumented according to this strategy. */ - protected abstract boolean isInstrumented(Class type); + protected abstract boolean isInstrumented(@MaybeNull Class type); /** - * A factory that creates instances that represent lambda expressions. + * A factory for rewriting the JDK's {@code java.lang.invoke.LambdaMetafactory} methods for use with Byte Buddy. The code that is + * created by this factory is roughly equivalent to the following: + *
+         * public static CallSite metafactory(MethodHandles.Lookup caller,
+         *                                    String interfaceMethodName,
+         *                                    MethodType factoryType,
+         *                                    MethodType interfaceMethodType,
+         *                                    MethodHandle implementation,
+         *                                    MethodType dynamicMethodType) throws Exception {
+         *   boolean serializable = false;
+         *  {@code List>} markerInterfaces = Collections.emptyList();
+         *  {@code List} additionalBridges = Collections.emptyList();
+         *   byte[] binaryRepresentation = (byte[]) ClassLoader.getSystemClassLoader().loadClass("net.bytebuddy.agent.builder.LambdaFactory").getDeclaredMethod("make",
+         *     Object.class,
+         *     String.class,
+         *     Object.class,
+         *     Object.class,
+         *     Object.class,
+         *     Object.class,
+         *     boolean.class,
+         *     List.class,
+         *     List.class).invoke(null,
+         *       caller,
+         *       interfaceMethodName,
+         *       factoryType,
+         *       interfaceMethodType,
+         *       implementation,
+         *       dynamicMethodType,
+         *       serializable,
+         *       markerInterfaces,
+         *       additionalBridges);
+         *  {@code Class} lambdaClass = ... // loading code
+         *   return factoryType.parameterCount() == 0
+         *     ? new ConstantCallSite(MethodHandles.constant(factoryType.returnType(), lambdaClass.getDeclaredConstructors()[0].newInstance()))
+         *     : new ConstantCallSite(Lookup.IMPL_LOOKUP.findStatic(lambdaClass, "get$Lambda", factoryType));
+         * }
+         * 
+ * The code's preamble is adjusted for the alternative metafactory to ressemble the following: + *
+         * public static CallSite altMetafactory(MethodHandles.Lookup caller,
+         *                                       String interfaceMethodName,
+         *                                       MethodType factoryType,
+         *                                       Object... argument) throws Exception {
+         *   int flags = (Integer) argument[3];
+         *   int index = 4;
+         *  {@code Class[]} markerInterface;
+         *   if ((flags{@code &} 2) != 0) {
+         *     int count = (Integer) argument[index++];
+         *     markerInterface = new{@code Class}[count];
+         *     System.arraycopy(argument, index, markerInterface, 0, count);
+         *     index += count;
+         *   } else {
+         *     markerInterface = new{@code Class}[0];
+         *   }
+         *   MethodType[] additionalBridge;
+         *   if ((flags{@code &} 2) != 0) {
+         *     int count = (Integer) argument[index++];
+         *     additionalBridge = new MethodType[count];
+         *     System.arraycopy(argument, index, additionalBridge, 0, count);
+         *   } else {
+         *     additionalBridge = new MethodType[0];
+         *   }
+         *   MethodType interfaceMethodType = (MethodType) argument[0];
+         *   MethodHandle implementation = (MethodHandle) argument[1];
+         *   MethodType dynamicMethodType = (MethodType) argument[2];
+         *   boolean serializable = (flags{@code &} 1) != 0;
+         *  {@code List>} markerInterfaces = Arrays.asList(markerInterface);
+         *  {@code List} additionalBridges = Arrays.asList(additionalBridge);
+         *   // ... reminder of method as before
+         * }
+         * 
*/ - @HashCodeAndEqualsPlugin.Enhance - protected static class LambdaInstanceFactory { + protected enum LambdaMetafactoryFactory implements ByteCodeAppender { /** - * The name of a factory for a lambda expression. + * Implements the {@code java.lang.invoke.LambdaMetafactory#metafactory} method. */ - private static final String LAMBDA_FACTORY = "get$Lambda"; + REGULAR(6, 11) { + @Override + protected void onDispatch(MethodVisitor methodVisitor) { + methodVisitor.visitInsn(Opcodes.ICONST_0); + methodVisitor.visitVarInsn(Opcodes.ISTORE, 6); + methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Collections", "emptyList", "()Ljava/util/List;", false); + methodVisitor.visitVarInsn(Opcodes.ASTORE, 7); + methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Collections", "emptyList", "()Ljava/util/List;", false); + methodVisitor.visitVarInsn(Opcodes.ASTORE, 8); + methodVisitor.visitFrame(Opcodes.F_APPEND, 3, new Object[]{Opcodes.INTEGER, "java/util/List", "java/util/List"}, 0, null); + } + }, /** - * A prefix for a field that represents a property of a lambda expression. + * Implements the {@code java.lang.invoke.LambdaMetafactory#altMetafactory} method. */ - private static final String FIELD_PREFIX = "arg$"; + ALTERNATIVE(6, 16) { + @Override + protected void onDispatch(MethodVisitor methodVisitor) { + methodVisitor.visitVarInsn(Opcodes.ALOAD, 3); + methodVisitor.visitInsn(Opcodes.ICONST_3); + methodVisitor.visitInsn(Opcodes.AALOAD); + methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Integer"); + methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false); + methodVisitor.visitVarInsn(Opcodes.ISTORE, 4); + methodVisitor.visitInsn(Opcodes.ICONST_4); + methodVisitor.visitVarInsn(Opcodes.ISTORE, 5); + methodVisitor.visitVarInsn(Opcodes.ILOAD, 4); + methodVisitor.visitInsn(Opcodes.ICONST_2); + methodVisitor.visitInsn(Opcodes.IAND); + Label first = new Label(); + methodVisitor.visitJumpInsn(Opcodes.IFEQ, first); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 3); + methodVisitor.visitVarInsn(Opcodes.ILOAD, 5); + methodVisitor.visitIincInsn(5, 1); + methodVisitor.visitInsn(Opcodes.AALOAD); + methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Integer"); + methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false); + methodVisitor.visitVarInsn(Opcodes.ISTORE, 7); + methodVisitor.visitVarInsn(Opcodes.ILOAD, 7); + methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Class"); + methodVisitor.visitVarInsn(Opcodes.ASTORE, 6); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 3); + methodVisitor.visitVarInsn(Opcodes.ILOAD, 5); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 6); + methodVisitor.visitInsn(Opcodes.ICONST_0); + methodVisitor.visitVarInsn(Opcodes.ILOAD, 7); + methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "arraycopy", "(Ljava/lang/Object;ILjava/lang/Object;II)V", false); + methodVisitor.visitVarInsn(Opcodes.ILOAD, 5); + methodVisitor.visitVarInsn(Opcodes.ILOAD, 7); + methodVisitor.visitInsn(Opcodes.IADD); + methodVisitor.visitVarInsn(Opcodes.ISTORE, 5); + Label second = new Label(); + methodVisitor.visitJumpInsn(Opcodes.GOTO, second); + methodVisitor.visitLabel(first); + methodVisitor.visitFrame(Opcodes.F_APPEND, 2, new Object[]{Opcodes.INTEGER, Opcodes.INTEGER}, 0, null); + methodVisitor.visitInsn(Opcodes.ICONST_0); + methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Class"); + methodVisitor.visitVarInsn(Opcodes.ASTORE, 6); + methodVisitor.visitLabel(second); + methodVisitor.visitFrame(Opcodes.F_APPEND, 1, new Object[]{"[Ljava/lang/Class;"}, 0, null); + methodVisitor.visitVarInsn(Opcodes.ILOAD, 4); + methodVisitor.visitInsn(Opcodes.ICONST_2); + methodVisitor.visitInsn(Opcodes.IAND); + Label third = new Label(); + methodVisitor.visitJumpInsn(Opcodes.IFEQ, third); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 3); + methodVisitor.visitVarInsn(Opcodes.ILOAD, 5); + methodVisitor.visitIincInsn(5, 1); + methodVisitor.visitInsn(Opcodes.AALOAD); + methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Integer"); + methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false); + methodVisitor.visitVarInsn(Opcodes.ISTORE, 8); + methodVisitor.visitVarInsn(Opcodes.ILOAD, 8); + methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/invoke/MethodType"); + methodVisitor.visitVarInsn(Opcodes.ASTORE, 7); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 3); + methodVisitor.visitVarInsn(Opcodes.ILOAD, 5); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 7); + methodVisitor.visitInsn(Opcodes.ICONST_0); + methodVisitor.visitVarInsn(Opcodes.ILOAD, 8); + methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "arraycopy", "(Ljava/lang/Object;ILjava/lang/Object;II)V", false); + Label forth = new Label(); + methodVisitor.visitJumpInsn(Opcodes.GOTO, forth); + methodVisitor.visitLabel(third); + methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + methodVisitor.visitInsn(Opcodes.ICONST_0); + methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/invoke/MethodType"); + methodVisitor.visitVarInsn(Opcodes.ASTORE, 7); + methodVisitor.visitLabel(forth); + methodVisitor.visitFrame(Opcodes.F_APPEND, 1, new Object[]{"[Ljava/lang/invoke/MethodType;"}, 0, null); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 3); + methodVisitor.visitInsn(Opcodes.ICONST_0); + methodVisitor.visitInsn(Opcodes.AALOAD); + methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/invoke/MethodType"); + methodVisitor.visitVarInsn(Opcodes.ASTORE, 8); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 3); + methodVisitor.visitInsn(Opcodes.ICONST_1); + methodVisitor.visitInsn(Opcodes.AALOAD); + methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/invoke/MethodHandle"); + methodVisitor.visitVarInsn(Opcodes.ASTORE, 9); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 3); + methodVisitor.visitInsn(Opcodes.ICONST_2); + methodVisitor.visitInsn(Opcodes.AALOAD); + methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/invoke/MethodType"); + methodVisitor.visitVarInsn(Opcodes.ASTORE, 10); + methodVisitor.visitVarInsn(Opcodes.ILOAD, 4); + methodVisitor.visitInsn(Opcodes.ICONST_1); + methodVisitor.visitInsn(Opcodes.IAND); + Label fifth = new Label(); + methodVisitor.visitJumpInsn(Opcodes.IFEQ, fifth); + methodVisitor.visitInsn(Opcodes.ICONST_1); + Label sixth = new Label(); + methodVisitor.visitJumpInsn(Opcodes.GOTO, sixth); + methodVisitor.visitLabel(fifth); + methodVisitor.visitFrame(Opcodes.F_APPEND, 3, new Object[]{"java/lang/invoke/MethodType", "java/lang/invoke/MethodHandle", "java/lang/invoke/MethodType"}, 0, null); + methodVisitor.visitInsn(Opcodes.ICONST_0); + methodVisitor.visitLabel(sixth); + methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]{Opcodes.INTEGER}); + methodVisitor.visitVarInsn(Opcodes.ISTORE, 11); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 6); + methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "asList", "([Ljava/lang/Object;)Ljava/util/List;", false); + methodVisitor.visitVarInsn(Opcodes.ASTORE, 12); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 7); + methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "asList", "([Ljava/lang/Object;)Ljava/util/List;", false); + methodVisitor.visitVarInsn(Opcodes.ASTORE, 13); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 8); + methodVisitor.visitVarInsn(Opcodes.ASTORE, 3); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 9); + methodVisitor.visitVarInsn(Opcodes.ASTORE, 4); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 10); + methodVisitor.visitVarInsn(Opcodes.ASTORE, 5); + methodVisitor.visitVarInsn(Opcodes.ILOAD, 11); + methodVisitor.visitVarInsn(Opcodes.ISTORE, 6); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 12); + methodVisitor.visitVarInsn(Opcodes.ASTORE, 7); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 13); + methodVisitor.visitVarInsn(Opcodes.ASTORE, 8); + methodVisitor.visitFrame(Opcodes.F_FULL, 9, new Object[]{"java/lang/invoke/MethodHandles$Lookup", + "java/lang/String", + "java/lang/invoke/MethodType", + "java/lang/invoke/MethodType", + "java/lang/invoke/MethodHandle", + "java/lang/invoke/MethodType", + Opcodes.INTEGER, + "java/util/List", + "java/util/List"}, 0, null); + } + }; /** - * The infix to use for naming classes that represent lambda expression. The additional prefix - * is necessary because the subsequent counter is not sufficient to keep names unique compared - * to the original factory. + * A loader for the generated lambda class. */ - private static final String LAMBDA_TYPE_INFIX = "$$Lambda$ByteBuddy$"; + private static final Loader LOADER = resolve(); /** - * A type-safe constant to express that a class is not already loaded when applying a class file transformer. + * Resolves the loader for the current VM. + * + * @return An appropriate loader. */ - private static final Class NOT_PREVIOUSLY_DEFINED = null; + @SuppressFBWarnings(value = {"DE_MIGHT_IGNORE", "REC_CATCH_EXCEPTION"}, justification = "Exception should not be rethrown but trigger a fallback.") + private static Loader resolve() { + try { + Class type = Class.forName("java.lang.invoke.MethodHandles$Lookup", false, null); + type.getMethod("defineHiddenClass", + byte[].class, + boolean.class, + Class.forName("[Ljava.lang.invoke.MethodHandles$Lookup$ClassOption;", false, null)); + type.getMethod("defineHiddenClassWithClassData", + byte[].class, + Object.class, + boolean.class, + Class.forName("[Ljava.lang.invoke.MethodHandles$Lookup$ClassOption;", false, null)); + return Loader.UsingMethodHandleLookup.INSTANCE; + } catch (Exception ignored) { + /* do nothing */ + } + for (Loader.UsingUnsafe loader : Loader.UsingUnsafe.values()) { + try { + Class.forName(loader.getType().replace('/', '.'), + false, + null).getMethod("defineAnonymousClass", Class.class, byte[].class, Object[].class); + return loader; + } catch (Exception ignored) { + /* do nothing */ + } + } + return Loader.Unavailable.INSTANCE; + } /** - * A counter for naming lambda expressions randomly. + * The required stack size for this factory. */ - private static final AtomicInteger LAMBDA_NAME_COUNTER = new AtomicInteger(); + private final int stackSize; /** - * The Byte Buddy instance to use for creating lambda objects. + * The required local variable length for this factory. */ - private final ByteBuddy byteBuddy; + private final int localVariableLength; /** - * Creates a new lambda instance factory. + * Creates a new factory. * - * @param byteBuddy The Byte Buddy instance to use for creating lambda objects. + * @param stackSize The required stack size for this factory. + * @param localVariableLength The required local variable length for this factory. */ - protected LambdaInstanceFactory(ByteBuddy byteBuddy) { - this.byteBuddy = byteBuddy; + LambdaMetafactoryFactory(int stackSize, int localVariableLength) { + this.stackSize = stackSize; + this.localVariableLength = localVariableLength; } /** - * Applies this lambda meta factory. - * - * @param targetTypeLookup A lookup context representing the creating class of this lambda expression. - * @param lambdaMethodName The name of the lambda expression's represented method. - * @param factoryMethodType The type of the lambda expression's represented method. - * @param lambdaMethodType The type of the lambda expression's factory method. - * @param targetMethodHandle A handle representing the target of the lambda expression's method. - * @param specializedLambdaMethodType A specialization of the type of the lambda expression's represented method. - * @param serializable {@code true} if the lambda expression should be serializable. - * @param markerInterfaces A list of interfaces for the lambda expression to represent. - * @param additionalBridges A list of additional bridge methods to be implemented by the lambda expression. - * @param classFileTransformers A collection of class file transformers to apply when creating the class. - * @return A binary representation of the transformed class file. + * {@inheritDoc} */ - public byte[] make(Object targetTypeLookup, - String lambdaMethodName, - Object factoryMethodType, - Object lambdaMethodType, - Object targetMethodHandle, - Object specializedLambdaMethodType, - boolean serializable, - List> markerInterfaces, - List additionalBridges, - Collection classFileTransformers) { - JavaConstant.MethodType factoryMethod = JavaConstant.MethodType.ofLoaded(factoryMethodType); - JavaConstant.MethodType lambdaMethod = JavaConstant.MethodType.ofLoaded(lambdaMethodType); - JavaConstant.MethodHandle targetMethod = JavaConstant.MethodHandle.ofLoaded(targetMethodHandle, targetTypeLookup); - JavaConstant.MethodType specializedLambdaMethod = JavaConstant.MethodType.ofLoaded(specializedLambdaMethodType); - Class targetType = JavaConstant.MethodHandle.lookupType(targetTypeLookup); - String lambdaClassName = targetType.getName() + LAMBDA_TYPE_INFIX + LAMBDA_NAME_COUNTER.incrementAndGet(); - DynamicType.Builder builder = byteBuddy - .subclass(factoryMethod.getReturnType(), ConstructorStrategy.Default.NO_CONSTRUCTORS) - .modifiers(TypeManifestation.FINAL, Visibility.PUBLIC) - .implement(markerInterfaces) - .name(lambdaClassName) - .defineConstructor(Visibility.PUBLIC) - .withParameters(factoryMethod.getParameterTypes()) - .intercept(ConstructorImplementation.INSTANCE) - .method(named(lambdaMethodName) - .and(takesArguments(lambdaMethod.getParameterTypes())) - .and(returns(lambdaMethod.getReturnType()))) - .intercept(new LambdaMethodImplementation(targetMethod, specializedLambdaMethod)); - int index = 0; - for (TypeDescription capturedType : factoryMethod.getParameterTypes()) { - builder = builder.defineField(FIELD_PREFIX + ++index, capturedType, Visibility.PRIVATE, FieldManifestation.FINAL); - } - if (!factoryMethod.getParameterTypes().isEmpty()) { - builder = builder.defineMethod(LAMBDA_FACTORY, factoryMethod.getReturnType(), Visibility.PRIVATE, Ownership.STATIC) - .withParameters(factoryMethod.getParameterTypes()) - .intercept(FactoryImplementation.INSTANCE); - } - if (serializable) { - if (!markerInterfaces.contains(Serializable.class)) { - builder = builder.implement(Serializable.class); - } - builder = builder.defineMethod("writeReplace", Object.class, Visibility.PRIVATE) - .intercept(new SerializationImplementation(TypeDescription.ForLoadedType.of(targetType), - factoryMethod.getReturnType(), - lambdaMethodName, - lambdaMethod, - targetMethod, - JavaConstant.MethodType.ofLoaded(specializedLambdaMethodType))); - } else if (factoryMethod.getReturnType().isAssignableTo(Serializable.class)) { - builder = builder.defineMethod("readObject", void.class, Visibility.PRIVATE) - .withParameters(ObjectInputStream.class) - .throwing(NotSerializableException.class) - .intercept(ExceptionMethod.throwing(NotSerializableException.class, "Non-serializable lambda")) - .defineMethod("writeObject", void.class, Visibility.PRIVATE) - .withParameters(ObjectOutputStream.class) - .throwing(NotSerializableException.class) - .intercept(ExceptionMethod.throwing(NotSerializableException.class, "Non-serializable lambda")); - } - for (Object additionalBridgeType : additionalBridges) { - JavaConstant.MethodType additionalBridge = JavaConstant.MethodType.ofLoaded(additionalBridgeType); - builder = builder.defineMethod(lambdaMethodName, additionalBridge.getReturnType(), MethodManifestation.BRIDGE, Visibility.PUBLIC) - .withParameters(additionalBridge.getParameterTypes()) - .intercept(new BridgeMethodImplementation(lambdaMethodName, lambdaMethod)); - } - byte[] classFile = builder.make().getBytes(); - for (ClassFileTransformer classFileTransformer : classFileTransformers) { - try { - byte[] transformedClassFile = classFileTransformer.transform(targetType.getClassLoader(), - lambdaClassName.replace('.', '/'), - NOT_PREVIOUSLY_DEFINED, - targetType.getProtectionDomain(), - classFile); - classFile = transformedClassFile == null - ? classFile - : transformedClassFile; - } catch (Throwable ignored) { - /* do nothing */ - } - } - return classFile; + public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext, MethodDescription instrumentedMethod) { + onDispatch(methodVisitor); + methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/ClassLoader", "getSystemClassLoader", "()Ljava/lang/ClassLoader;", false); + methodVisitor.visitLdcInsn("net.bytebuddy.agent.builder.LambdaFactory"); + methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/ClassLoader", "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;", false); + methodVisitor.visitLdcInsn("make"); + methodVisitor.visitIntInsn(Opcodes.BIPUSH, 9); + methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Class"); + methodVisitor.visitInsn(Opcodes.DUP); + methodVisitor.visitInsn(Opcodes.ICONST_0); + methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/Object;")); + methodVisitor.visitInsn(Opcodes.AASTORE); + methodVisitor.visitInsn(Opcodes.DUP); + methodVisitor.visitInsn(Opcodes.ICONST_1); + methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/String;")); + methodVisitor.visitInsn(Opcodes.AASTORE); + methodVisitor.visitInsn(Opcodes.DUP); + methodVisitor.visitInsn(Opcodes.ICONST_2); + methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/Object;")); + methodVisitor.visitInsn(Opcodes.AASTORE); + methodVisitor.visitInsn(Opcodes.DUP); + methodVisitor.visitInsn(Opcodes.ICONST_3); + methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/Object;")); + methodVisitor.visitInsn(Opcodes.AASTORE); + methodVisitor.visitInsn(Opcodes.DUP); + methodVisitor.visitInsn(Opcodes.ICONST_4); + methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/Object;")); + methodVisitor.visitInsn(Opcodes.AASTORE); + methodVisitor.visitInsn(Opcodes.DUP); + methodVisitor.visitInsn(Opcodes.ICONST_5); + methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/Object;")); + methodVisitor.visitInsn(Opcodes.AASTORE); + methodVisitor.visitInsn(Opcodes.DUP); + methodVisitor.visitIntInsn(Opcodes.BIPUSH, 6); + methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Boolean", "TYPE", "Ljava/lang/Class;"); + methodVisitor.visitInsn(Opcodes.AASTORE); + methodVisitor.visitInsn(Opcodes.DUP); + methodVisitor.visitIntInsn(Opcodes.BIPUSH, 7); + methodVisitor.visitLdcInsn(Type.getType("Ljava/util/List;")); + methodVisitor.visitInsn(Opcodes.AASTORE); + methodVisitor.visitInsn(Opcodes.DUP); + methodVisitor.visitIntInsn(Opcodes.BIPUSH, 8); + methodVisitor.visitLdcInsn(Type.getType("Ljava/util/List;")); + methodVisitor.visitInsn(Opcodes.AASTORE); + methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "getDeclaredMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", false); + methodVisitor.visitInsn(Opcodes.ACONST_NULL); + methodVisitor.visitIntInsn(Opcodes.BIPUSH, 9); + methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object"); + methodVisitor.visitInsn(Opcodes.DUP); + methodVisitor.visitInsn(Opcodes.ICONST_0); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); + methodVisitor.visitInsn(Opcodes.AASTORE); + methodVisitor.visitInsn(Opcodes.DUP); + methodVisitor.visitInsn(Opcodes.ICONST_1); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); + methodVisitor.visitInsn(Opcodes.AASTORE); + methodVisitor.visitInsn(Opcodes.DUP); + methodVisitor.visitInsn(Opcodes.ICONST_2); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 2); + methodVisitor.visitInsn(Opcodes.AASTORE); + methodVisitor.visitInsn(Opcodes.DUP); + methodVisitor.visitInsn(Opcodes.ICONST_3); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 3); + methodVisitor.visitInsn(Opcodes.AASTORE); + methodVisitor.visitInsn(Opcodes.DUP); + methodVisitor.visitInsn(Opcodes.ICONST_4); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 4); + methodVisitor.visitInsn(Opcodes.AASTORE); + methodVisitor.visitInsn(Opcodes.DUP); + methodVisitor.visitInsn(Opcodes.ICONST_5); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 5); + methodVisitor.visitInsn(Opcodes.AASTORE); + methodVisitor.visitInsn(Opcodes.DUP); + methodVisitor.visitIntInsn(Opcodes.BIPUSH, 6); + methodVisitor.visitVarInsn(Opcodes.ILOAD, 6); + methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false); + methodVisitor.visitInsn(Opcodes.AASTORE); + methodVisitor.visitInsn(Opcodes.DUP); + methodVisitor.visitIntInsn(Opcodes.BIPUSH, 7); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 7); + methodVisitor.visitInsn(Opcodes.AASTORE); + methodVisitor.visitInsn(Opcodes.DUP); + methodVisitor.visitIntInsn(Opcodes.BIPUSH, 8); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 8); + methodVisitor.visitInsn(Opcodes.AASTORE); + methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", false); + methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, "[B"); + methodVisitor.visitVarInsn(Opcodes.ASTORE, 9); + LOADER.apply(methodVisitor); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 2); + methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodType", "parameterCount", "()I", false); + Label first = new Label(); + methodVisitor.visitJumpInsn(Opcodes.IFNE, first); + methodVisitor.visitTypeInsn(Opcodes.NEW, "java/lang/invoke/ConstantCallSite"); + methodVisitor.visitInsn(Opcodes.DUP); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 2); + methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodType", "returnType", "()Ljava/lang/Class;", false); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 10); + methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "getDeclaredConstructors", "()[Ljava/lang/reflect/Constructor;", false); + methodVisitor.visitInsn(Opcodes.ICONST_0); + methodVisitor.visitInsn(Opcodes.AALOAD); + methodVisitor.visitInsn(Opcodes.ICONST_0); + methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object"); + methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/reflect/Constructor", "newInstance", "([Ljava/lang/Object;)Ljava/lang/Object;", false); + methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/invoke/MethodHandles", "constant", "(Ljava/lang/Class;Ljava/lang/Object;)Ljava/lang/invoke/MethodHandle;", false); + methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/invoke/ConstantCallSite", "", "(Ljava/lang/invoke/MethodHandle;)V", false); + Label second = new Label(); + methodVisitor.visitJumpInsn(Opcodes.GOTO, second); + methodVisitor.visitLabel(first); + methodVisitor.visitFrame(Opcodes.F_FULL, 11, new Object[]{"java/lang/invoke/MethodHandles$Lookup", "java/lang/String", "java/lang/invoke/MethodType", "java/lang/invoke/MethodType", "java/lang/invoke/MethodHandle", "java/lang/invoke/MethodType", Opcodes.INTEGER, "java/util/List", "java/util/List", "[B", "java/lang/Class"}, 0, new Object[]{}); + methodVisitor.visitTypeInsn(Opcodes.NEW, "java/lang/invoke/ConstantCallSite"); + methodVisitor.visitInsn(Opcodes.DUP); + methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/invoke/MethodHandles$Lookup", "IMPL_LOOKUP", "Ljava/lang/invoke/MethodHandles$Lookup;"); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 10); + methodVisitor.visitLdcInsn("get$Lambda"); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 2); + methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodHandles$Lookup", "findStatic", "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;", false); + methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/invoke/ConstantCallSite", "", "(Ljava/lang/invoke/MethodHandle;)V", false); + methodVisitor.visitLabel(second); + methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]{"java/lang/invoke/CallSite"}); + methodVisitor.visitInsn(Opcodes.ARETURN); + return new Size(Math.max(stackSize, LOADER.getStackSize()), Math.max(localVariableLength, LOADER.getLocalVariableLength())); } /** - * Implements a lambda class's executing transformer. + * Invoked upon dispatch. As a result, all values that are presented to Byte Buddy's code generator + * must be arranged on the stack. + * + * @param methodVisitor The method visitor to use. + */ + protected abstract void onDispatch(MethodVisitor methodVisitor); + + /** + * A loader is responsible for loading a generated class file in the current VM. + */ + protected interface Loader { + + /** + * Applies this loader. + * + * @param methodVisitor The method visitor to use. + */ + void apply(MethodVisitor methodVisitor); + + /** + * Returns the stack size that is required to implement this loader. + * + * @return The stack size that is required to implement this loader. + */ + int getStackSize(); + + /** + * Returns the local variable length that is required to implement this loader. + * + * @return The local variable length that is required to implement this loader. + */ + int getLocalVariableLength(); + + /** + * An implementation that indicates that no loader is available. + */ + enum Unavailable implements Loader { + + /** + * The singleton instance. + */ + INSTANCE; + + /** + * {@inheritDoc} + */ + public void apply(MethodVisitor methodVisitor) { + throw new IllegalStateException("No lambda expression loading strategy available on current VM"); + } + + /** + * {@inheritDoc} + */ + public int getStackSize() { + throw new IllegalStateException("No lambda expression loading strategy available on current VM"); + } + + /** + * {@inheritDoc} + */ + public int getLocalVariableLength() { + throw new IllegalStateException("No lambda expression loading strategy available on current VM"); + } + } + + /** + * A loader that uses a method handle lookup object to load a class. This is implemented as follows: + *
+                 * MethodHandleInfo info = caller.revealDirect(implementation);
+                 * boolean classData = (Modifier.isProtected(info.getModifiers())
+                 *  {@code &&} !VerifyAccess.isSamePackage(caller.lookupClass(), info.getDeclaringClass()))
+                 *   || info.getReferenceKind() == Opcodes.H_INVOKESPECIAL;
+                 * MethodHandles.Lookup lookup;
+                 * if (classData) {
+                 *   lookup = caller.defineHiddenClassWithClassData(binaryRepresentation,
+                 *     info,
+                 *     true,
+                 *     MethodHandles.Lookup.ClassOption.NESTMATE,
+                 *     MethodHandles.Lookup.ClassOption.STRONG);
+                 * } else {
+                 *   lookup = caller.defineHiddenClass(binaryRepresentation,
+                 *     true,
+                 *     MethodHandles.Lookup.ClassOption.NESTMATE,
+                 *     MethodHandles.Lookup.ClassOption.STRONG);
+                 * }
+                 * {@code Class} lambdaClass = lookup.lookupClass();
+                 * 
+ */ + enum UsingMethodHandleLookup implements Loader { + + /** + * The singleton instance. + */ + INSTANCE; + + /** + * {@inheritDoc} + */ + public void apply(MethodVisitor methodVisitor) { + methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 4); + methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodHandles$Lookup", "revealDirect", "(Ljava/lang/invoke/MethodHandle;)Ljava/lang/invoke/MethodHandleInfo;", false); + methodVisitor.visitVarInsn(Opcodes.ASTORE, 10); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 10); + methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/lang/invoke/MethodHandleInfo", "getModifiers", "()I", true); + methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/reflect/Modifier", "isProtected", "(I)Z", false); + Label first = new Label(); + methodVisitor.visitJumpInsn(Opcodes.IFEQ, first); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); + methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodHandles$Lookup", "lookupClass", "()Ljava/lang/Class;", false); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 10); + methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/lang/invoke/MethodHandleInfo", "getDeclaringClass", "()Ljava/lang/Class;", true); + methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "sun/invoke/util/VerifyAccess", "isSamePackage", "(Ljava/lang/Class;Ljava/lang/Class;)Z", false); + Label second = new Label(); + methodVisitor.visitJumpInsn(Opcodes.IFEQ, second); + methodVisitor.visitLabel(first); + methodVisitor.visitFrame(Opcodes.F_FULL, 11, new Object[]{"java/lang/invoke/MethodHandles$Lookup", "java/lang/String", "java/lang/invoke/MethodType", "java/lang/invoke/MethodType", "java/lang/invoke/MethodHandle", "java/lang/invoke/MethodType", Opcodes.INTEGER, "java/util/List", "java/util/List", "[B", "java/lang/invoke/MethodHandleInfo"}, 0, new Object[]{}); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 10); + methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/lang/invoke/MethodHandleInfo", "getReferenceKind", "()I", true); + methodVisitor.visitIntInsn(Opcodes.BIPUSH, 7); + Label third = new Label(); + methodVisitor.visitJumpInsn(Opcodes.IF_ICMPNE, third); + methodVisitor.visitLabel(second); + methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + methodVisitor.visitInsn(Opcodes.ICONST_1); + Label forth = new Label(); + methodVisitor.visitJumpInsn(Opcodes.GOTO, forth); + methodVisitor.visitLabel(third); + methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + methodVisitor.visitInsn(Opcodes.ICONST_0); + methodVisitor.visitLabel(forth); + methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]{Opcodes.INTEGER}); + methodVisitor.visitVarInsn(Opcodes.ISTORE, 11); + methodVisitor.visitVarInsn(Opcodes.ILOAD, 11); + Label fifth = new Label(); + methodVisitor.visitJumpInsn(Opcodes.IFEQ, fifth); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 9); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 10); + methodVisitor.visitInsn(Opcodes.ICONST_1); + methodVisitor.visitInsn(Opcodes.ICONST_2); + methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/invoke/MethodHandles$Lookup$ClassOption"); + methodVisitor.visitInsn(Opcodes.DUP); + methodVisitor.visitInsn(Opcodes.ICONST_0); + methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/invoke/MethodHandles$Lookup$ClassOption", "NESTMATE", "Ljava/lang/invoke/MethodHandles$Lookup$ClassOption;"); + methodVisitor.visitInsn(Opcodes.AASTORE); + methodVisitor.visitInsn(Opcodes.DUP); + methodVisitor.visitInsn(Opcodes.ICONST_1); + methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/invoke/MethodHandles$Lookup$ClassOption", "STRONG", "Ljava/lang/invoke/MethodHandles$Lookup$ClassOption;"); + methodVisitor.visitInsn(Opcodes.AASTORE); + methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodHandles$Lookup", "defineHiddenClassWithClassData", "([BLjava/lang/Object;Z[Ljava/lang/invoke/MethodHandles$Lookup$ClassOption;)Ljava/lang/invoke/MethodHandles$Lookup;", false); + methodVisitor.visitVarInsn(Opcodes.ASTORE, 12); + Label sixth = new Label(); + methodVisitor.visitLabel(sixth); + Label seventh = new Label(); + methodVisitor.visitJumpInsn(Opcodes.GOTO, seventh); + methodVisitor.visitLabel(fifth); + methodVisitor.visitFrame(Opcodes.F_APPEND, 1, new Object[]{Opcodes.INTEGER}, 0, null); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 9); + methodVisitor.visitInsn(Opcodes.ICONST_1); + methodVisitor.visitInsn(Opcodes.ICONST_2); + methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/invoke/MethodHandles$Lookup$ClassOption"); + methodVisitor.visitInsn(Opcodes.DUP); + methodVisitor.visitInsn(Opcodes.ICONST_0); + methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/invoke/MethodHandles$Lookup$ClassOption", "NESTMATE", "Ljava/lang/invoke/MethodHandles$Lookup$ClassOption;"); + methodVisitor.visitInsn(Opcodes.AASTORE); + methodVisitor.visitInsn(Opcodes.DUP); + methodVisitor.visitInsn(Opcodes.ICONST_1); + methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/invoke/MethodHandles$Lookup$ClassOption", "STRONG", "Ljava/lang/invoke/MethodHandles$Lookup$ClassOption;"); + methodVisitor.visitInsn(Opcodes.AASTORE); + methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodHandles$Lookup", "defineHiddenClass", "([BZ[Ljava/lang/invoke/MethodHandles$Lookup$ClassOption;)Ljava/lang/invoke/MethodHandles$Lookup;", false); + methodVisitor.visitVarInsn(Opcodes.ASTORE, 12); + methodVisitor.visitLabel(seventh); + methodVisitor.visitFrame(Opcodes.F_APPEND, 1, new Object[]{"java/lang/invoke/MethodHandles$Lookup"}, 0, null); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 12); + methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodHandles$Lookup", "lookupClass", "()Ljava/lang/Class;", false); + methodVisitor.visitVarInsn(Opcodes.ASTORE, 10); + methodVisitor.visitFrame(Opcodes.F_FULL, 10, new Object[]{"java/lang/invoke/MethodHandles$Lookup", + "java/lang/String", + "java/lang/invoke/MethodType", + "java/lang/invoke/MethodType", + "java/lang/invoke/MethodHandle", + "java/lang/invoke/MethodType", + Opcodes.INTEGER, + "java/util/List", + "java/util/List", + "java/lang/Class"}, 0, null); + } + + /** + * {@inheritDoc} + */ + public int getStackSize() { + return 8; + } + + /** + * {@inheritDoc} + */ + public int getLocalVariableLength() { + return 15; + } + } + + /** + * A loader that is using unsafe API to load a lambda implementation. The code for loading + * the class looks similar to the following: + *
+                 * Unsafe unsafe = Unsafe.getUnsafe();
+                 * {@code Class} lambdaClass = unsafe.defineAnonymousClass(caller.lookupClass(),
+                 *   binaryRepresentation,
+                 *   null);
+                 * unsafe.ensureClassInitialized(lambdaClass);
+                 * 
+ */ + enum UsingUnsafe implements Loader { + + /** + * A loader that uses {@code jdk.internal.misc.Unsafe}. + */ + JDK_INTERNAL_MISC_UNSAFE("jdk/internal/misc/Unsafe"), + + /** + * A loader that uses {@code sun.misc.Unsafe}. + */ + SUN_MISC_UNSAFE("sun/misc/Unsafe"); + + /** + * The internal name of the unsafe type. + */ + private final String type; + + /** + * Creates a new loader using unsafe API. + * + * @param type The internal name of the unsafe type. + */ + UsingUnsafe(String type) { + this.type = type; + } + + /** + * Returns the internal name of the unsafe type. + * + * @return The internal name of the unsafe type. + */ + protected String getType() { + return type; + } + + /** + * {@inheritDoc} + */ + public void apply(MethodVisitor methodVisitor) { + methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, type, "getUnsafe", "()L" + type + ";", false); + methodVisitor.visitVarInsn(Opcodes.ASTORE, 11); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 11); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); + methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodHandles$Lookup", "lookupClass", "()Ljava/lang/Class;", false); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 9); + methodVisitor.visitInsn(Opcodes.ACONST_NULL); + methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, type, "defineAnonymousClass", "(Ljava/lang/Class;[B[Ljava/lang/Object;)Ljava/lang/Class;", false); + methodVisitor.visitVarInsn(Opcodes.ASTORE, 10); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 11); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 10); + methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, type, "ensureClassInitialized", "(Ljava/lang/Class;)V", false); + } + + /** + * {@inheritDoc} + */ + public int getStackSize() { + return 4; + } + + /** + * {@inheritDoc} + */ + public int getLocalVariableLength() { + return 13; + } + } + } + } + + /** + * A factory that creates instances that represent lambda expressions. + */ + @HashCodeAndEqualsPlugin.Enhance + protected static class LambdaInstanceFactory { + + /** + * The name of a factory for a lambda expression. + */ + private static final String LAMBDA_FACTORY = "get$Lambda"; + + /** + * A prefix for a field that represents a property of a lambda expression. + */ + private static final String FIELD_PREFIX = "arg$"; + + /** + * The infix to use for naming classes that represent lambda expression. The additional prefix + * is necessary because the subsequent counter is not sufficient to keep names unique compared + * to the original factory. + */ + private static final String LAMBDA_TYPE_INFIX = "$$Lambda$ByteBuddy$"; + + /** + * A type-safe constant to express that a class is not already loaded when applying a class file transformer. + */ + @AlwaysNull + private static final Class NOT_PREVIOUSLY_DEFINED = null; + + /** + * A counter for naming lambda expressions randomly. + */ + private static final AtomicInteger LAMBDA_NAME_COUNTER = new AtomicInteger(); + + /** + * The Byte Buddy instance to use for creating lambda objects. + */ + private final ByteBuddy byteBuddy; + + /** + * Creates a new lambda instance factory. + * + * @param byteBuddy The Byte Buddy instance to use for creating lambda objects. + */ + protected LambdaInstanceFactory(ByteBuddy byteBuddy) { + this.byteBuddy = byteBuddy; + } + + /** + * Applies this lambda meta factory. + * + * @param targetTypeLookup A lookup context representing the creating class of this lambda expression. + * @param lambdaMethodName The name of the lambda expression's represented method. + * @param factoryMethodType The type of the lambda expression's represented method. + * @param lambdaMethodType The type of the lambda expression's factory method. + * @param targetMethodHandle A handle representing the target of the lambda expression's method. + * @param specializedLambdaMethodType A specialization of the type of the lambda expression's represented method. + * @param serializable {@code true} if the lambda expression should be serializable. + * @param markerInterfaces A list of interfaces for the lambda expression to represent. + * @param additionalBridges A list of additional bridge methods to be implemented by the lambda expression. + * @param classFileTransformers A collection of class file transformers to apply when creating the class. + * @return A binary representation of the transformed class file. + */ + public byte[] make(Object targetTypeLookup, + String lambdaMethodName, + Object factoryMethodType, + Object lambdaMethodType, + Object targetMethodHandle, + Object specializedLambdaMethodType, + boolean serializable, + List> markerInterfaces, + List additionalBridges, + Collection classFileTransformers) { + JavaConstant.MethodType factoryMethod = JavaConstant.MethodType.ofLoaded(factoryMethodType); + JavaConstant.MethodType lambdaMethod = JavaConstant.MethodType.ofLoaded(lambdaMethodType); + JavaConstant.MethodHandle targetMethod = JavaConstant.MethodHandle.ofLoaded(targetMethodHandle, targetTypeLookup); + JavaConstant.MethodType specializedLambdaMethod = JavaConstant.MethodType.ofLoaded(specializedLambdaMethodType); + Class targetType = JavaConstant.MethodHandle.lookupType(targetTypeLookup); + String lambdaClassName = targetType.getName() + LAMBDA_TYPE_INFIX + LAMBDA_NAME_COUNTER.incrementAndGet(); + DynamicType.Builder builder = byteBuddy + .subclass(factoryMethod.getReturnType(), ConstructorStrategy.Default.NO_CONSTRUCTORS) + .modifiers(TypeManifestation.FINAL, Visibility.PUBLIC) + .implement(markerInterfaces) + .name(lambdaClassName) + .defineConstructor(Visibility.PUBLIC) + .withParameters(factoryMethod.getParameterTypes()) + .intercept(ConstructorImplementation.INSTANCE) + .method(named(lambdaMethodName) + .and(takesArguments(lambdaMethod.getParameterTypes())) + .and(returns(lambdaMethod.getReturnType()))) + .intercept(new LambdaMethodImplementation(TypeDescription.ForLoadedType.of(targetType), targetMethod, specializedLambdaMethod)); + int index = 0; + for (TypeDescription capturedType : factoryMethod.getParameterTypes()) { + builder = builder.defineField(FIELD_PREFIX + ++index, capturedType, Visibility.PRIVATE, FieldManifestation.FINAL); + } + if (!factoryMethod.getParameterTypes().isEmpty()) { + builder = builder.defineMethod(LAMBDA_FACTORY, factoryMethod.getReturnType(), Visibility.PRIVATE, Ownership.STATIC) + .withParameters(factoryMethod.getParameterTypes()) + .intercept(FactoryImplementation.INSTANCE); + } + if (serializable) { + if (!markerInterfaces.contains(Serializable.class)) { + builder = builder.implement(Serializable.class); + } + builder = builder.defineMethod("writeReplace", Object.class, Visibility.PRIVATE) + .intercept(new SerializationImplementation(TypeDescription.ForLoadedType.of(targetType), + factoryMethod.getReturnType(), + lambdaMethodName, + lambdaMethod, + targetMethod, + JavaConstant.MethodType.ofLoaded(specializedLambdaMethodType))); + } else if (factoryMethod.getReturnType().isAssignableTo(Serializable.class)) { + builder = builder.defineMethod("readObject", void.class, Visibility.PRIVATE) + .withParameters(ObjectInputStream.class) + .throwing(NotSerializableException.class) + .intercept(ExceptionMethod.throwing(NotSerializableException.class, "Non-serializable lambda")) + .defineMethod("writeObject", void.class, Visibility.PRIVATE) + .withParameters(ObjectOutputStream.class) + .throwing(NotSerializableException.class) + .intercept(ExceptionMethod.throwing(NotSerializableException.class, "Non-serializable lambda")); + } + for (Object additionalBridgeType : additionalBridges) { + JavaConstant.MethodType additionalBridge = JavaConstant.MethodType.ofLoaded(additionalBridgeType); + builder = builder.defineMethod(lambdaMethodName, additionalBridge.getReturnType(), MethodManifestation.BRIDGE, Visibility.PUBLIC) + .withParameters(additionalBridge.getParameterTypes()) + .intercept(new BridgeMethodImplementation(lambdaMethodName, lambdaMethod)); + } + byte[] classFile = builder.make().getBytes(); + for (ClassFileTransformer classFileTransformer : classFileTransformers) { + try { + byte[] transformedClassFile = classFileTransformer.transform(targetType.getClassLoader(), + lambdaClassName.replace('.', '/'), + NOT_PREVIOUSLY_DEFINED, + targetType.getProtectionDomain(), + classFile); + classFile = transformedClassFile == null + ? classFile + : transformedClassFile; + } catch (Throwable ignored) { + /* do nothing */ + } + } + return classFile; + } + + /** + * Implements a lambda class's executing transformer. */ - @SuppressFBWarnings(value = "SE_BAD_FIELD", justification = "An enumeration does not serialize fields") protected enum ConstructorImplementation implements Implementation { /** @@ -8104,13 +9170,13 @@ /** * A reference to the {@link Object} class's default executing transformer. */ - private final MethodDescription.InDefinedShape objectConstructor; + private final transient MethodDescription.InDefinedShape objectConstructor; /** * Creates a new executing transformer implementation. */ ConstructorImplementation() { - objectConstructor = TypeDescription.OBJECT.getDeclaredMethods().filter(isConstructor()).getOnly(); + objectConstructor = TypeDescription.ForLoadedType.of(Object.class).getDeclaredMethods().filter(isConstructor()).getOnly(); } /** @@ -8233,6 +9299,11 @@ protected static class LambdaMethodImplementation implements Implementation { /** + * The type that defines the lambda expression. + */ + private final TypeDescription targetType; + + /** * The handle of the target method of the lambda expression. */ private final JavaConstant.MethodHandle targetMethod; @@ -8245,10 +9316,14 @@ /** * Creates a implementation of a lambda expression's functional method. * + * @param targetType The type that defines the lambda expression. * @param targetMethod The target method of the lambda expression. * @param specializedLambdaMethod The specialized type of the lambda method. */ - protected LambdaMethodImplementation(JavaConstant.MethodHandle targetMethod, JavaConstant.MethodType specializedLambdaMethod) { + protected LambdaMethodImplementation(TypeDescription targetType, + JavaConstant.MethodHandle targetMethod, + JavaConstant.MethodType specializedLambdaMethod) { + this.targetType = targetType; this.targetMethod = targetMethod; this.specializedLambdaMethod = specializedLambdaMethod; } @@ -8257,14 +9332,16 @@ * {@inheritDoc} */ public ByteCodeAppender appender(Target implementationTarget) { - return new Appender(targetMethod.getOwnerType() - .getDeclaredMethods() - .filter(hasMethodName(targetMethod.getName()) - .and(returns(targetMethod.getReturnType())) - .and(takesArguments(targetMethod.getParameterTypes()))) - .getOnly(), + return Appender.of(targetMethod.getOwnerType() + .getDeclaredMethods() + .filter(hasMethodName(targetMethod.getName()) + .and(returns(targetMethod.getReturnType())) + .and(takesArguments(targetMethod.getParameterTypes()))) + .getOnly(), specializedLambdaMethod, - implementationTarget.getInstrumentedType().getDeclaredFields()); + implementationTarget.getInstrumentedType().getDeclaredFields(), + targetMethod.getHandleType(), + targetType); } /** @@ -8281,7 +9358,30 @@ protected static class Appender implements ByteCodeAppender { /** - * The target method of the lambda expression. + * A dispatcher using method handle lookup data, if available. + */ + private static final Dispatcher LOOKUP_DATA_DISPATCHER = dispatcher(); + + /** + * Resolves the dispatcher to use for special method invocations. + * + * @return An appropriate dispatcher for the current VM. + */ + @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback.") + private static Dispatcher dispatcher() { + try { + Class type = Class.forName("java.lang.invoke.MethodHandles$Lookup", false, null); + type.getMethod("classData", type, String.class, Class.class); + return new Dispatcher.UsingMethodHandle(new MethodDescription.ForLoadedMethod(Class.forName("java.lang.invoke.MethodHandle", + false, + null).getMethod("invokeExact", Object[].class))); + } catch (Exception ignored) { + return Dispatcher.UsingDirectInvocation.INSTANCE; + } + } + + /** + * The target method of the lambda expression. */ private final MethodDescription targetMethod; @@ -8296,27 +9396,55 @@ private final List declaredFields; /** + * The dispatcher to use. + */ + private final Dispatcher dispatcher; + + /** * Creates an appender of a lambda expression's functional method. * * @param targetMethod The target method of the lambda expression. * @param specializedLambdaMethod The specialized type of the lambda method. * @param declaredFields The instrumented type's declared fields. + * @param dispatcher The dispatcher to use. */ protected Appender(MethodDescription targetMethod, JavaConstant.MethodType specializedLambdaMethod, - List declaredFields) { + List declaredFields, + Dispatcher dispatcher) { this.targetMethod = targetMethod; this.specializedLambdaMethod = specializedLambdaMethod; this.declaredFields = declaredFields; + this.dispatcher = dispatcher; + } + + /** + * Resolves an appropriate appender for this lambda expression. + * + * @param targetMethod The target method of the lambda expression. + * @param specializedLambdaMethod The specialized type of the lambda method. + * @param declaredFields The instrumented type's declared fields. + * @param handleType The handle type of the lambda expression's method handle. + * @param targetType The target type that defines the lambda expression. + * @return An appropriate byte code appender. + */ + protected static ByteCodeAppender of(MethodDescription targetMethod, + JavaConstant.MethodType specializedLambdaMethod, + List declaredFields, + JavaConstant.MethodHandle.HandleType handleType, + TypeDescription targetType) { + return new Appender(targetMethod, + specializedLambdaMethod, + declaredFields, + handleType == JavaConstant.MethodHandle.HandleType.INVOKE_SPECIAL || !targetMethod.getDeclaringType().asErasure().isVisibleTo(targetType) + ? LOOKUP_DATA_DISPATCHER + : Dispatcher.UsingDirectInvocation.INSTANCE); } /** * {@inheritDoc} */ public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) { - StackManipulation preparation = targetMethod.isConstructor() - ? new StackManipulation.Compound(TypeCreation.of(targetMethod.getDeclaringType().asErasure()), Duplication.SINGLE) - : StackManipulation.Trivial.INSTANCE; List fieldAccess = new ArrayList(declaredFields.size() * 2 + 1); for (FieldDescription.InDefinedShape fieldDescription : declaredFields) { fieldAccess.add(MethodVariableAccess.loadThis()); @@ -8330,10 +9458,13 @@ Assigner.Typing.DYNAMIC)); } return new Size(new StackManipulation.Compound( - preparation, + targetMethod.isConstructor() + ? new StackManipulation.Compound(TypeCreation.of(targetMethod.getDeclaringType().asErasure()), Duplication.SINGLE) + : StackManipulation.Trivial.INSTANCE, + dispatcher.initialize(), new StackManipulation.Compound(fieldAccess), new StackManipulation.Compound(parameterAccess), - MethodInvocation.invoke(targetMethod), + dispatcher.invoke(targetMethod), Assigner.DEFAULT.assign(targetMethod.isConstructor() ? targetMethod.getDeclaringType().asGenericType() : targetMethod.getReturnType(), @@ -8342,6 +9473,101 @@ MethodReturn.of(specializedLambdaMethod.getReturnType()) ).apply(methodVisitor, implementationContext).getMaximalSize(), instrumentedMethod.getStackSize()); } + + /** + * A dispatcher for a lambda expression's implementation. + */ + protected interface Dispatcher { + + /** + * Initializes this invocation. + * + * @return A stack manipulation that represents the initialization. + */ + StackManipulation initialize(); + + /** + * Invokes this invocation. + * + * @param methodDescription A description of the invoked method. + * @return A stack manipulation that represents the invocation. + */ + StackManipulation invoke(MethodDescription methodDescription); + + /** + * An invocation that is using a direct call to the target method. + */ + enum UsingDirectInvocation implements Dispatcher { + + /** + * The singleton instance. + */ + INSTANCE; + + /** + * {@inheritDoc} + */ + public StackManipulation initialize() { + return StackManipulation.Trivial.INSTANCE; + } + + /** + * {@inheritDoc} + */ + public StackManipulation invoke(MethodDescription methodDescription) { + return MethodInvocation.invoke(methodDescription); + } + } + + /** + * An invocation that is using an exact invocation of a method handle. + */ + @HashCodeAndEqualsPlugin.Enhance + class UsingMethodHandle extends StackManipulation.AbstractBase implements Dispatcher { + + /** + * A description of {@code java.lang.invoke.MethodHandle#invokeExact(Object...)}. + */ + private final MethodDescription.InDefinedShape invokeExact; + + /** + * Creates a new dispatcher that is using a method handle. + * + * @param invokeExact A description of {@code java.lang.invoke.MethodHandle#invokeExact(Object...)}. + */ + protected UsingMethodHandle(MethodDescription.InDefinedShape invokeExact) { + this.invokeExact = invokeExact; + } + + /** + * {@inheritDoc} + */ + public StackManipulation initialize() { + return this; + } + + /** + * {@inheritDoc} + */ + public StackManipulation invoke(MethodDescription methodDescription) { + return MethodInvocation.invoke(invokeExact); + } + + /** + * {@inheritDoc} + */ + public Size apply(MethodVisitor methodVisitor, Context implementationContext) { + methodVisitor.visitLdcInsn(new ConstantDynamic(JavaConstant.Dynamic.DEFAULT_NAME, + "Ljava/lang/invoke/MethodHandle;", + new Handle(Opcodes.H_INVOKESTATIC, + "java/lang/invoke/MethodHandles", + "classData", + "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;", + false))); + return new Size(1, 1); + } + } + } } } @@ -8419,7 +9645,7 @@ for (FieldDescription.InDefinedShape fieldDescription : implementationTarget.getInstrumentedType().getDeclaredFields()) { lambdaArguments.add(new StackManipulation.Compound(MethodVariableAccess.loadThis(), FieldAccess.forField(fieldDescription).read(), - Assigner.DEFAULT.assign(fieldDescription.getType(), TypeDescription.Generic.OBJECT, Assigner.Typing.STATIC))); + Assigner.DEFAULT.assign(fieldDescription.getType(), TypeDescription.Generic.OfNonGenericType.ForLoadedType.of(Object.class), Assigner.Typing.STATIC))); } return new ByteCodeAppender.Simple(new StackManipulation.Compound( TypeCreation.of(serializedLambda), @@ -8433,7 +9659,7 @@ new TextConstant(targetMethod.getName()), new TextConstant(targetMethod.getDescriptor()), new TextConstant(specializedMethod.getDescriptor()), - ArrayFactory.forType(TypeDescription.Generic.OBJECT).withValues(lambdaArguments), + ArrayFactory.forType(TypeDescription.Generic.OfNonGenericType.ForLoadedType.of(Object.class)).withValues(lambdaArguments), MethodInvocation.invoke(serializedLambda.getDeclaredMethods().filter(isConstructor()).getOnly()), MethodReturn.REFERENCE )); @@ -8521,7 +9747,7 @@ bridgeTargetInvocation, bridgeTargetInvocation.getMethodDescription().getReturnType().asErasure().isAssignableTo(instrumentedMethod.getReturnType().asErasure()) ? StackManipulation.Trivial.INSTANCE - : TypeCasting.to(instrumentedMethod.getReceiverType()), + : TypeCasting.to(instrumentedMethod.getReturnType()), MethodReturn.of(instrumentedMethod.getReturnType()) )).apply(methodVisitor, implementationContext, instrumentedMethod); @@ -8529,493 +9755,162 @@ } } } + } + + /** + * Determines how patching a {@link ResettableClassFileTransformer} resolves the transformer exchange. + */ + enum PatchMode { /** - * Implements the regular lambda meta factory. The implementation represents the following code: - *
-         * public static CallSite metafactory(MethodHandles.Lookup caller,
-         *     String invokedName,
-         *     MethodType invokedType,
-         *     MethodType samMethodType,
-         *     MethodHandle implMethod,
-         *     MethodType instantiatedMethodType) throws Exception {
-         *   Unsafe unsafe = Unsafe.getUnsafe();
-         *   {@code Class} lambdaClass = unsafe.defineAnonymousClass(caller.lookupClass(),
-         *       (byte[]) ClassLoader.getSystemClassLoader().loadClass("net.bytebuddy.agent.builder.LambdaFactory").getDeclaredMethod("make",
-         *           Object.class,
-         *           String.class,
-         *           Object.class,
-         *           Object.class,
-         *           Object.class,
-         *           Object.class,
-         *           boolean.class,
-         *           List.class,
-         *           List.class).invoke(null,
-         *               caller,
-         *               invokedName,
-         *               invokedType,
-         *               samMethodType,
-         *               implMethod,
-         *               instantiatedMethodType,
-         *               false,
-         *               Collections.emptyList(),
-         *               Collections.emptyList()),
-         *       null);
-         *   unsafe.ensureClassInitialized(lambdaClass);
-         *   return invokedType.parameterCount() == 0
-         *     ? new ConstantCallSite(MethodHandles.constant(invokedType.returnType(), lambdaClass.getDeclaredConstructors()[0].newInstance()))
-         *     : new ConstantCallSite(MethodHandles.Lookup.IMPL_LOOKUP.findStatic(lambdaClass, "get$Lambda", invokedType));
-         * 
+ * Allows for a short period where neither class file transformer is registered. This might allow + * for classes to execute without instrumentation if they are loaded in a short moment where neither + * transformer is registered. In some rare cases, this might also cause that these classes are not + * instrumented as a result of the patching. + */ + GAP { + @Override + protected Handler toHandler(ResettableClassFileTransformer classFileTransformer) { + return new Handler.ForPatchWithGap(classFileTransformer); + } + }, + + /** + * Allows for a short period where both class file transformer are registered. This might allow + * for classes to apply both instrumentations. In some rare cases, this might also cause that both + * instrumentations are permanently applied. + */ + OVERLAP { + @Override + protected Handler toHandler(ResettableClassFileTransformer classFileTransformer) { + return new Handler.ForPatchWithOverlap(classFileTransformer); + } + }; + + /** + * Resolves this strategy to a handler. + * + * @param classFileTransformer The class file transformer to deregister. + * @return The handler to apply. + */ + protected abstract Handler toHandler(ResettableClassFileTransformer classFileTransformer); + + /** + * A handler to allow for callbacks prior and after registering a {@link ClassFileTransformer}. */ - protected enum MetaFactoryRedirection implements AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper { + protected interface Handler { /** - * The singleton instance. + * Invoked prior to registering a class file transformer. + * + * @param instrumentation The instrumentation to use. */ - INSTANCE; + void onBeforeRegistration(Instrumentation instrumentation); /** - * {@inheritDoc} + * Invoked right after registering a class file transformer. + * + * @param instrumentation The instrumentation to use. */ - public MethodVisitor wrap(TypeDescription instrumentedType, - MethodDescription instrumentedMethod, - MethodVisitor methodVisitor, - Implementation.Context implementationContext, - TypePool typePool, - int writerFlags, - int readerFlags) { - methodVisitor.visitCode(); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, UNSAFE_CLASS, "getUnsafe", "()L" + UNSAFE_CLASS + ";", false); - methodVisitor.visitVarInsn(Opcodes.ASTORE, 6); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 6); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodHandles$Lookup", "lookupClass", "()Ljava/lang/Class;", false); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/ClassLoader", "getSystemClassLoader", "()Ljava/lang/ClassLoader;", false); - methodVisitor.visitLdcInsn("net.bytebuddy.agent.builder.LambdaFactory"); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/ClassLoader", "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;", false); - methodVisitor.visitLdcInsn("make"); - methodVisitor.visitIntInsn(Opcodes.BIPUSH, 9); - methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Class"); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitInsn(Opcodes.ICONST_0); - methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/Object;")); - methodVisitor.visitInsn(Opcodes.AASTORE); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitInsn(Opcodes.ICONST_1); - methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/String;")); - methodVisitor.visitInsn(Opcodes.AASTORE); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitInsn(Opcodes.ICONST_2); - methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/Object;")); - methodVisitor.visitInsn(Opcodes.AASTORE); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitInsn(Opcodes.ICONST_3); - methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/Object;")); - methodVisitor.visitInsn(Opcodes.AASTORE); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitInsn(Opcodes.ICONST_4); - methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/Object;")); - methodVisitor.visitInsn(Opcodes.AASTORE); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitInsn(Opcodes.ICONST_5); - methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/Object;")); - methodVisitor.visitInsn(Opcodes.AASTORE); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitIntInsn(Opcodes.BIPUSH, 6); - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Boolean", "TYPE", "Ljava/lang/Class;"); - methodVisitor.visitInsn(Opcodes.AASTORE); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitIntInsn(Opcodes.BIPUSH, 7); - methodVisitor.visitLdcInsn(Type.getType("Ljava/util/List;")); - methodVisitor.visitInsn(Opcodes.AASTORE); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitIntInsn(Opcodes.BIPUSH, 8); - methodVisitor.visitLdcInsn(Type.getType("Ljava/util/List;")); - methodVisitor.visitInsn(Opcodes.AASTORE); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "getDeclaredMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", false); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - methodVisitor.visitIntInsn(Opcodes.BIPUSH, 9); - methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object"); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitInsn(Opcodes.ICONST_0); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitInsn(Opcodes.AASTORE); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitInsn(Opcodes.ICONST_1); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); - methodVisitor.visitInsn(Opcodes.AASTORE); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitInsn(Opcodes.ICONST_2); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 2); - methodVisitor.visitInsn(Opcodes.AASTORE); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitInsn(Opcodes.ICONST_3); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 3); - methodVisitor.visitInsn(Opcodes.AASTORE); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitInsn(Opcodes.ICONST_4); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 4); - methodVisitor.visitInsn(Opcodes.AASTORE); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitInsn(Opcodes.ICONST_5); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 5); - methodVisitor.visitInsn(Opcodes.AASTORE); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitIntInsn(Opcodes.BIPUSH, 6); - methodVisitor.visitInsn(Opcodes.ICONST_0); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false); - methodVisitor.visitInsn(Opcodes.AASTORE); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitIntInsn(Opcodes.BIPUSH, 7); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Collections", "emptyList", "()Ljava/util/List;", false); - methodVisitor.visitInsn(Opcodes.AASTORE); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitIntInsn(Opcodes.BIPUSH, 8); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Collections", "emptyList", "()Ljava/util/List;", false); - methodVisitor.visitInsn(Opcodes.AASTORE); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", false); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, "[B"); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, UNSAFE_CLASS, "defineAnonymousClass", "(Ljava/lang/Class;[B[Ljava/lang/Object;)Ljava/lang/Class;", false); - methodVisitor.visitVarInsn(Opcodes.ASTORE, 7); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 6); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 7); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, UNSAFE_CLASS, "ensureClassInitialized", "(Ljava/lang/Class;)V", false); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 2); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodType", "parameterCount", "()I", false); - Label conditionalDefault = new Label(); - methodVisitor.visitJumpInsn(Opcodes.IFNE, conditionalDefault); - methodVisitor.visitTypeInsn(Opcodes.NEW, "java/lang/invoke/ConstantCallSite"); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 2); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodType", "returnType", "()Ljava/lang/Class;", false); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 7); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "getDeclaredConstructors", "()[Ljava/lang/reflect/Constructor;", false); - methodVisitor.visitInsn(Opcodes.ICONST_0); - methodVisitor.visitInsn(Opcodes.AALOAD); - methodVisitor.visitInsn(Opcodes.ICONST_0); - methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object"); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/reflect/Constructor", "newInstance", "([Ljava/lang/Object;)Ljava/lang/Object;", false); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/invoke/MethodHandles", "constant", "(Ljava/lang/Class;Ljava/lang/Object;)Ljava/lang/invoke/MethodHandle;", false); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/invoke/ConstantCallSite", "", "(Ljava/lang/invoke/MethodHandle;)V", false); - Label conditionalAlternative = new Label(); - methodVisitor.visitJumpInsn(Opcodes.GOTO, conditionalAlternative); - methodVisitor.visitLabel(conditionalDefault); - methodVisitor.visitFrame(Opcodes.F_APPEND, 2, new Object[]{UNSAFE_CLASS, "java/lang/Class"}, 0, null); - methodVisitor.visitTypeInsn(Opcodes.NEW, "java/lang/invoke/ConstantCallSite"); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/invoke/MethodHandles$Lookup", "IMPL_LOOKUP", "Ljava/lang/invoke/MethodHandles$Lookup;"); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 7); - methodVisitor.visitLdcInsn("get$Lambda"); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 2); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodHandles$Lookup", "findStatic", "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;", false); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/invoke/ConstantCallSite", "", "(Ljava/lang/invoke/MethodHandle;)V", false); - methodVisitor.visitLabel(conditionalAlternative); - methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]{"java/lang/invoke/CallSite"}); - methodVisitor.visitInsn(Opcodes.ARETURN); - methodVisitor.visitMaxs(8, 8); - methodVisitor.visitEnd(); - return IGNORE_ORIGINAL; - } - } + void onAfterRegistration(Instrumentation instrumentation); - /** - * Implements the alternative lambda meta factory. The implementation represents the following code: - *
-         * public static CallSite altMetafactory(MethodHandles.Lookup caller,
-         *     String invokedName,
-         *     MethodType invokedType,
-         *     Object... args) throws Exception {
-         *   int flags = (Integer) args[3];
-         *   int argIndex = 4;
-         *   {@code Class[]} markerInterface;
-         *   if ((flags {@code &} FLAG_MARKERS) != 0) {
-         *     int markerCount = (Integer) args[argIndex++];
-         *     markerInterface = new {@code Class}[markerCount];
-         *     System.arraycopy(args, argIndex, markerInterface, 0, markerCount);
-         *     argIndex += markerCount;
-         *   } else {
-         *     markerInterface = new {@code Class}[0];
-         *   }
-         *   MethodType[] additionalBridge;
-         *   if ((flags {@code &} FLAG_BRIDGES) != 0) {
-         *     int bridgeCount = (Integer) args[argIndex++];
-         *     additionalBridge = new MethodType[bridgeCount];
-         *     System.arraycopy(args, argIndex, additionalBridge, 0, bridgeCount);
-         *     // argIndex += bridgeCount;
-         *   } else {
-         *     additionalBridge = new MethodType[0];
-         *   }
-         *   Unsafe unsafe = Unsafe.getUnsafe();
-         *   {@code Class} lambdaClass = unsafe.defineAnonymousClass(caller.lookupClass(),
-         *       (byte[]) ClassLoader.getSystemClassLoader().loadClass("net.bytebuddy.agent.builder.LambdaFactory").getDeclaredMethod("make",
-         *           Object.class,
-         *           String.class,
-         *           Object.class,
-         *           Object.class,
-         *           Object.class,
-         *           Object.class,
-         *           boolean.class,
-         *           List.class,
-         *           List.class).invoke(null,
-         *               caller,
-         *               invokedName,
-         *               invokedType,
-         *               args[0],
-         *               args[1],
-         *               args[2],
-         *               (flags {@code &} FLAG_SERIALIZABLE) != 0,
-         *               Arrays.asList(markerInterface),
-         *               Arrays.asList(additionalBridge)),
-         *       null);
-         *   unsafe.ensureClassInitialized(lambdaClass);
-         *   return invokedType.parameterCount() == 0
-         *     ? new ConstantCallSite(MethodHandles.constant(invokedType.returnType(), lambdaClass.getDeclaredConstructors()[0].newInstance()))
-         *     : new ConstantCallSite(MethodHandles.Lookup.IMPL_LOOKUP.findStatic(lambdaClass, "get$Lambda", invokedType));
-         * }
-         * 
- */ - protected enum AlternativeMetaFactoryRedirection implements AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper { + /** + * A non-operational handler. + */ + enum NoOp implements Handler { + + /** + * The singleton instance. + */ + INSTANCE; + + /** + * {@inheritDoc} + */ + public void onBeforeRegistration(Instrumentation instrumentation) { + /* do nothing */ + } + + /** + * {@inheritDoc} + */ + public void onAfterRegistration(Instrumentation instrumentation) { + /* do nothing */ + } + } /** - * The singleton instance. + * A handler for patching by {@link PatchMode#GAP}. */ - INSTANCE; + @HashCodeAndEqualsPlugin.Enhance + class ForPatchWithGap implements Handler { + + /** + * The class file transformer to deregister. + */ + private final ResettableClassFileTransformer classFileTransformer; + + /** + * Creates a new handler. + * + * @param classFileTransformer The class file transformer to deregister. + */ + protected ForPatchWithGap(ResettableClassFileTransformer classFileTransformer) { + this.classFileTransformer = classFileTransformer; + } + + /** + * {@inheritDoc} + */ + public void onBeforeRegistration(Instrumentation instrumentation) { + if (!classFileTransformer.reset(instrumentation, RedefinitionStrategy.DISABLED)) { + throw new IllegalArgumentException("Failed to deregister patched class file transformer: " + classFileTransformer); + } + } + + /** + * {@inheritDoc} + */ + public void onAfterRegistration(Instrumentation instrumentation) { + /* do nothing */ + } + } /** - * {@inheritDoc} + * A handler for patching by {@link PatchMode#OVERLAP}. */ - public MethodVisitor wrap(TypeDescription instrumentedType, - MethodDescription instrumentedMethod, - MethodVisitor methodVisitor, - Implementation.Context implementationContext, - TypePool typePool, - int writerFlags, - int readerFlags) { - methodVisitor.visitCode(); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 3); - methodVisitor.visitInsn(Opcodes.ICONST_3); - methodVisitor.visitInsn(Opcodes.AALOAD); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Integer"); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false); - methodVisitor.visitVarInsn(Opcodes.ISTORE, 4); - methodVisitor.visitInsn(Opcodes.ICONST_4); - methodVisitor.visitVarInsn(Opcodes.ISTORE, 5); - methodVisitor.visitVarInsn(Opcodes.ILOAD, 4); - methodVisitor.visitInsn(Opcodes.ICONST_2); - methodVisitor.visitInsn(Opcodes.IAND); - Label markerInterfaceLoop = new Label(); - methodVisitor.visitJumpInsn(Opcodes.IFEQ, markerInterfaceLoop); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 3); - methodVisitor.visitVarInsn(Opcodes.ILOAD, 5); - methodVisitor.visitIincInsn(5, 1); - methodVisitor.visitInsn(Opcodes.AALOAD); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Integer"); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false); - methodVisitor.visitVarInsn(Opcodes.ISTORE, 7); - methodVisitor.visitVarInsn(Opcodes.ILOAD, 7); - methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Class"); - methodVisitor.visitVarInsn(Opcodes.ASTORE, 6); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 3); - methodVisitor.visitVarInsn(Opcodes.ILOAD, 5); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 6); - methodVisitor.visitInsn(Opcodes.ICONST_0); - methodVisitor.visitVarInsn(Opcodes.ILOAD, 7); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "arraycopy", "(Ljava/lang/Object;ILjava/lang/Object;II)V", false); - methodVisitor.visitVarInsn(Opcodes.ILOAD, 5); - methodVisitor.visitVarInsn(Opcodes.ILOAD, 7); - methodVisitor.visitInsn(Opcodes.IADD); - methodVisitor.visitVarInsn(Opcodes.ISTORE, 5); - Label markerInterfaceExit = new Label(); - methodVisitor.visitJumpInsn(Opcodes.GOTO, markerInterfaceExit); - methodVisitor.visitLabel(markerInterfaceLoop); - methodVisitor.visitFrame(Opcodes.F_APPEND, 2, new Object[]{Opcodes.INTEGER, Opcodes.INTEGER}, 0, null); - methodVisitor.visitInsn(Opcodes.ICONST_0); - methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Class"); - methodVisitor.visitVarInsn(Opcodes.ASTORE, 6); - methodVisitor.visitLabel(markerInterfaceExit); - methodVisitor.visitFrame(Opcodes.F_APPEND, 1, new Object[]{"[Ljava/lang/Class;"}, 0, null); - methodVisitor.visitVarInsn(Opcodes.ILOAD, 4); - methodVisitor.visitInsn(Opcodes.ICONST_4); - methodVisitor.visitInsn(Opcodes.IAND); - Label additionalBridgesLoop = new Label(); - methodVisitor.visitJumpInsn(Opcodes.IFEQ, additionalBridgesLoop); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 3); - methodVisitor.visitVarInsn(Opcodes.ILOAD, 5); - methodVisitor.visitIincInsn(5, 1); - methodVisitor.visitInsn(Opcodes.AALOAD); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Integer"); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false); - methodVisitor.visitVarInsn(Opcodes.ISTORE, 8); - methodVisitor.visitVarInsn(Opcodes.ILOAD, 8); - methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/invoke/MethodType"); - methodVisitor.visitVarInsn(Opcodes.ASTORE, 7); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 3); - methodVisitor.visitVarInsn(Opcodes.ILOAD, 5); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 7); - methodVisitor.visitInsn(Opcodes.ICONST_0); - methodVisitor.visitVarInsn(Opcodes.ILOAD, 8); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "arraycopy", "(Ljava/lang/Object;ILjava/lang/Object;II)V", false); - Label additionalBridgesExit = new Label(); - methodVisitor.visitJumpInsn(Opcodes.GOTO, additionalBridgesExit); - methodVisitor.visitLabel(additionalBridgesLoop); - methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null); - methodVisitor.visitInsn(Opcodes.ICONST_0); - methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/invoke/MethodType"); - methodVisitor.visitVarInsn(Opcodes.ASTORE, 7); - methodVisitor.visitLabel(additionalBridgesExit); - methodVisitor.visitFrame(Opcodes.F_APPEND, 1, new Object[]{"[Ljava/lang/invoke/MethodType;"}, 0, null); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, UNSAFE_CLASS, "getUnsafe", "()L" + UNSAFE_CLASS + ";", false); - methodVisitor.visitVarInsn(Opcodes.ASTORE, 8); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 8); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodHandles$Lookup", "lookupClass", "()Ljava/lang/Class;", false); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/ClassLoader", "getSystemClassLoader", "()Ljava/lang/ClassLoader;", false); - methodVisitor.visitLdcInsn("net.bytebuddy.agent.builder.LambdaFactory"); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/ClassLoader", "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;", false); - methodVisitor.visitLdcInsn("make"); - methodVisitor.visitIntInsn(Opcodes.BIPUSH, 9); - methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Class"); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitInsn(Opcodes.ICONST_0); - methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/Object;")); - methodVisitor.visitInsn(Opcodes.AASTORE); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitInsn(Opcodes.ICONST_1); - methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/String;")); - methodVisitor.visitInsn(Opcodes.AASTORE); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitInsn(Opcodes.ICONST_2); - methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/Object;")); - methodVisitor.visitInsn(Opcodes.AASTORE); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitInsn(Opcodes.ICONST_3); - methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/Object;")); - methodVisitor.visitInsn(Opcodes.AASTORE); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitInsn(Opcodes.ICONST_4); - methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/Object;")); - methodVisitor.visitInsn(Opcodes.AASTORE); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitInsn(Opcodes.ICONST_5); - methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/Object;")); - methodVisitor.visitInsn(Opcodes.AASTORE); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitIntInsn(Opcodes.BIPUSH, 6); - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Boolean", "TYPE", "Ljava/lang/Class;"); - methodVisitor.visitInsn(Opcodes.AASTORE); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitIntInsn(Opcodes.BIPUSH, 7); - methodVisitor.visitLdcInsn(Type.getType("Ljava/util/List;")); - methodVisitor.visitInsn(Opcodes.AASTORE); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitIntInsn(Opcodes.BIPUSH, 8); - methodVisitor.visitLdcInsn(Type.getType("Ljava/util/List;")); - methodVisitor.visitInsn(Opcodes.AASTORE); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "getDeclaredMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", false); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - methodVisitor.visitIntInsn(Opcodes.BIPUSH, 9); - methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object"); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitInsn(Opcodes.ICONST_0); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitInsn(Opcodes.AASTORE); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitInsn(Opcodes.ICONST_1); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); - methodVisitor.visitInsn(Opcodes.AASTORE); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitInsn(Opcodes.ICONST_2); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 2); - methodVisitor.visitInsn(Opcodes.AASTORE); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitInsn(Opcodes.ICONST_3); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 3); - methodVisitor.visitInsn(Opcodes.ICONST_0); - methodVisitor.visitInsn(Opcodes.AALOAD); - methodVisitor.visitInsn(Opcodes.AASTORE); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitInsn(Opcodes.ICONST_4); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 3); - methodVisitor.visitInsn(Opcodes.ICONST_1); - methodVisitor.visitInsn(Opcodes.AALOAD); - methodVisitor.visitInsn(Opcodes.AASTORE); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitInsn(Opcodes.ICONST_5); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 3); - methodVisitor.visitInsn(Opcodes.ICONST_2); - methodVisitor.visitInsn(Opcodes.AALOAD); - methodVisitor.visitInsn(Opcodes.AASTORE); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitIntInsn(Opcodes.BIPUSH, 6); - methodVisitor.visitVarInsn(Opcodes.ILOAD, 4); - methodVisitor.visitInsn(Opcodes.ICONST_1); - methodVisitor.visitInsn(Opcodes.IAND); - Label callSiteConditional = new Label(); - methodVisitor.visitJumpInsn(Opcodes.IFEQ, callSiteConditional); - methodVisitor.visitInsn(Opcodes.ICONST_1); - Label callSiteAlternative = new Label(); - methodVisitor.visitJumpInsn(Opcodes.GOTO, callSiteAlternative); - methodVisitor.visitLabel(callSiteConditional); - methodVisitor.visitFrame(Opcodes.F_FULL, 9, new Object[]{"java/lang/invoke/MethodHandles$Lookup", "java/lang/String", "java/lang/invoke/MethodType", "[Ljava/lang/Object;", Opcodes.INTEGER, Opcodes.INTEGER, "[Ljava/lang/Class;", "[Ljava/lang/invoke/MethodType;", UNSAFE_CLASS}, 7, new Object[]{UNSAFE_CLASS, "java/lang/Class", "java/lang/reflect/Method", Opcodes.NULL, "[Ljava/lang/Object;", "[Ljava/lang/Object;", Opcodes.INTEGER}); - methodVisitor.visitInsn(Opcodes.ICONST_0); - methodVisitor.visitLabel(callSiteAlternative); - methodVisitor.visitFrame(Opcodes.F_FULL, 9, new Object[]{"java/lang/invoke/MethodHandles$Lookup", "java/lang/String", "java/lang/invoke/MethodType", "[Ljava/lang/Object;", Opcodes.INTEGER, Opcodes.INTEGER, "[Ljava/lang/Class;", "[Ljava/lang/invoke/MethodType;", UNSAFE_CLASS}, 8, new Object[]{UNSAFE_CLASS, "java/lang/Class", "java/lang/reflect/Method", Opcodes.NULL, "[Ljava/lang/Object;", "[Ljava/lang/Object;", Opcodes.INTEGER, Opcodes.INTEGER}); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false); - methodVisitor.visitInsn(Opcodes.AASTORE); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitIntInsn(Opcodes.BIPUSH, 7); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 6); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "asList", "([Ljava/lang/Object;)Ljava/util/List;", false); - methodVisitor.visitInsn(Opcodes.AASTORE); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitIntInsn(Opcodes.BIPUSH, 8); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 7); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "asList", "([Ljava/lang/Object;)Ljava/util/List;", false); - methodVisitor.visitInsn(Opcodes.AASTORE); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", false); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, "[B"); - methodVisitor.visitInsn(Opcodes.ACONST_NULL); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, UNSAFE_CLASS, "defineAnonymousClass", "(Ljava/lang/Class;[B[Ljava/lang/Object;)Ljava/lang/Class;", false); - methodVisitor.visitVarInsn(Opcodes.ASTORE, 9); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 8); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 9); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, UNSAFE_CLASS, "ensureClassInitialized", "(Ljava/lang/Class;)V", false); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 2); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodType", "parameterCount", "()I", false); - Label callSiteJump = new Label(); - methodVisitor.visitJumpInsn(Opcodes.IFNE, callSiteJump); - methodVisitor.visitTypeInsn(Opcodes.NEW, "java/lang/invoke/ConstantCallSite"); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 2); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodType", "returnType", "()Ljava/lang/Class;", false); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 9); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "getDeclaredConstructors", "()[Ljava/lang/reflect/Constructor;", false); - methodVisitor.visitInsn(Opcodes.ICONST_0); - methodVisitor.visitInsn(Opcodes.AALOAD); - methodVisitor.visitInsn(Opcodes.ICONST_0); - methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object"); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/reflect/Constructor", "newInstance", "([Ljava/lang/Object;)Ljava/lang/Object;", false); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/invoke/MethodHandles", "constant", "(Ljava/lang/Class;Ljava/lang/Object;)Ljava/lang/invoke/MethodHandle;", false); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/invoke/ConstantCallSite", "", "(Ljava/lang/invoke/MethodHandle;)V", false); - Label callSiteExit = new Label(); - methodVisitor.visitJumpInsn(Opcodes.GOTO, callSiteExit); - methodVisitor.visitLabel(callSiteJump); - methodVisitor.visitFrame(Opcodes.F_APPEND, 1, new Object[]{"java/lang/Class"}, 0, null); - methodVisitor.visitTypeInsn(Opcodes.NEW, "java/lang/invoke/ConstantCallSite"); - methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/invoke/MethodHandles$Lookup", "IMPL_LOOKUP", "Ljava/lang/invoke/MethodHandles$Lookup;"); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 9); - methodVisitor.visitLdcInsn("get$Lambda"); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 2); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodHandles$Lookup", "findStatic", "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;", false); - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/invoke/ConstantCallSite", "", "(Ljava/lang/invoke/MethodHandle;)V", false); - methodVisitor.visitLabel(callSiteExit); - methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]{"java/lang/invoke/CallSite"}); - methodVisitor.visitInsn(Opcodes.ARETURN); - methodVisitor.visitMaxs(9, 10); - methodVisitor.visitEnd(); - return IGNORE_ORIGINAL; + @HashCodeAndEqualsPlugin.Enhance + class ForPatchWithOverlap implements Handler { + + /** + * The class file transformer to deregister. + */ + private final ResettableClassFileTransformer classFileTransformer; + + /** + * Creates a new handler. + * + * @param classFileTransformer The class file transformer to deregister. + */ + protected ForPatchWithOverlap(ResettableClassFileTransformer classFileTransformer) { + this.classFileTransformer = classFileTransformer; + } + + /** + * {@inheritDoc} + */ + public void onBeforeRegistration(Instrumentation instrumentation) { + /* do nothing */ + } + + /** + * {@inheritDoc} + */ + public void onAfterRegistration(Instrumentation instrumentation) { + if (!classFileTransformer.reset(instrumentation, RedefinitionStrategy.DISABLED)) { + throw new IllegalArgumentException("Failed to deregister patched class file transformer: " + classFileTransformer); + } + } } } } @@ -9040,25 +9935,22 @@ private static final String INSTALLER_TYPE = "net.bytebuddy.agent.Installer"; /** - * The name of the {@code net.bytebuddy.agent.Installer} getter for reading an installed {@link Instrumentation}. - */ - private static final String INSTRUMENTATION_GETTER = "getInstrumentation"; - - /** - * Indicator for access to a static member via reflection to make the code more readable. + * The name of the getter for {@code net.bytebuddy.agent.Installer} to read the {@link Instrumentation}. */ - private static final Object STATIC_MEMBER = null; + private static final String INSTALLER_GETTER = "getInstrumentation"; /** * The value that is to be returned from a {@link java.lang.instrument.ClassFileTransformer} to indicate * that no class file transformation is to be applied. */ + @AlwaysNull private static final byte[] NO_TRANSFORMATION = null; /** - * Indicates that a loaded type should be considered as non-available. + * A type-safe constant to express that a class is not already loaded when applying a class file transformer. */ - private static final Class NO_LOADED_TYPE = null; + @AlwaysNull + private static final Class NOT_PREVIOUSLY_DEFINED = null; /** * A dipatcher to use for interacting with the instrumentation API. @@ -9107,6 +9999,11 @@ protected final NativeMethodStrategy nativeMethodStrategy; /** + * The warmup strategy to use. + */ + protected final WarmupStrategy warmupStrategy; + + /** * A decorator to wrap the created class file transformer. */ protected final TransformerDecorator transformerDecorator; @@ -9205,6 +10102,7 @@ TypeStrategy.Default.REBASE, LocationStrategy.ForClassLoader.STRONG, NativeMethodStrategy.Disabled.INSTANCE, + WarmupStrategy.NoOp.INSTANCE, TransformerDecorator.NoOp.INSTANCE, new InitializationStrategy.SelfInjection.Split(), RedefinitionStrategy.DISABLED, @@ -9221,8 +10119,8 @@ new RawMatcher.Disjunction( new RawMatcher.ForElementMatchers(any(), isBootstrapClassLoader().or(isExtensionClassLoader())), new RawMatcher.ForElementMatchers(nameStartsWith("net.bytebuddy.") - .and(not(ElementMatchers.nameStartsWith(NamingStrategy.SuffixingRandom.BYTE_BUDDY_RENAME_PACKAGE + "."))) - .or(nameStartsWith("sun.reflect.")) + .and(not(ElementMatchers.nameStartsWith(NamingStrategy.BYTE_BUDDY_RENAME_PACKAGE + "."))) + .or(nameStartsWith("sun.reflect.").or(nameStartsWith("jdk.internal.reflect."))) .or(isSynthetic()))), Collections.emptyList()); } @@ -9237,6 +10135,7 @@ * @param typeStrategy The definition handler to use. * @param locationStrategy The location strategy to use. * @param nativeMethodStrategy The native method strategy to apply. + * @param warmupStrategy The warmup strategy to use. * @param transformerDecorator A decorator to wrap the created class file transformer. * @param initializationStrategy The initialization strategy to use for transformed types. * @param redefinitionStrategy The redefinition strategy to apply. @@ -9261,6 +10160,7 @@ TypeStrategy typeStrategy, LocationStrategy locationStrategy, NativeMethodStrategy nativeMethodStrategy, + WarmupStrategy warmupStrategy, TransformerDecorator transformerDecorator, InitializationStrategy initializationStrategy, RedefinitionStrategy redefinitionStrategy, @@ -9283,6 +10183,7 @@ this.typeStrategy = typeStrategy; this.locationStrategy = locationStrategy; this.nativeMethodStrategy = nativeMethodStrategy; + this.warmupStrategy = warmupStrategy; this.transformerDecorator = transformerDecorator; this.initializationStrategy = initializationStrategy; this.redefinitionStrategy = redefinitionStrategy; @@ -9309,7 +10210,7 @@ */ @AccessControllerPlugin.Enhance private static T doPrivileged(PrivilegedAction action) { - return AccessController.doPrivileged(action); // action.run(); + return action.run(); } /** @@ -9415,6 +10316,7 @@ typeStrategy, locationStrategy, nativeMethodStrategy, + warmupStrategy, transformerDecorator, initializationStrategy, redefinitionStrategy, @@ -9443,6 +10345,7 @@ typeStrategy, locationStrategy, nativeMethodStrategy, + warmupStrategy, transformerDecorator, initializationStrategy, redefinitionStrategy, @@ -9471,6 +10374,7 @@ typeStrategy, locationStrategy, nativeMethodStrategy, + warmupStrategy, transformerDecorator, initializationStrategy, redefinitionStrategy, @@ -9499,6 +10403,7 @@ typeStrategy, locationStrategy, nativeMethodStrategy, + warmupStrategy, transformerDecorator, initializationStrategy, redefinitionStrategy, @@ -9527,6 +10432,7 @@ typeStrategy, locationStrategy, nativeMethodStrategy, + warmupStrategy, transformerDecorator, initializationStrategy, redefinitionStrategy, @@ -9555,6 +10461,7 @@ typeStrategy, locationStrategy, nativeMethodStrategy, + warmupStrategy, transformerDecorator, initializationStrategy, redefinitionStrategy, @@ -9583,6 +10490,7 @@ typeStrategy, locationStrategy, NativeMethodStrategy.ForPrefix.of(prefix), + warmupStrategy, transformerDecorator, initializationStrategy, redefinitionStrategy, @@ -9611,6 +10519,51 @@ typeStrategy, locationStrategy, NativeMethodStrategy.Disabled.INSTANCE, + warmupStrategy, + transformerDecorator, + initializationStrategy, + redefinitionStrategy, + redefinitionDiscoveryStrategy, + redefinitionBatchAllocator, + redefinitionListener, + redefinitionResubmissionStrategy, + injectionStrategy, + lambdaInstrumentationStrategy, + descriptionStrategy, + fallbackStrategy, + classFileBufferStrategy, + installationListener, + ignoreMatcher, + transformations); + } + + /** + * {@inheritDoc} + */ + public AgentBuilder warmUp(Class... type) { + return warmUp(Arrays.asList(type)); + } + + /** + * {@inheritDoc} + */ + public AgentBuilder warmUp(Collection> types) { + if (types.isEmpty()) { + return this; + } + for (Class type : types) { + if (type.isPrimitive() || type.isArray()) { + throw new IllegalArgumentException("Cannot warm up primitive or array type: " + type); + } + } + return new Default(byteBuddy, + listener, + circularityLock, + poolStrategy, + typeStrategy, + locationStrategy, + nativeMethodStrategy, + warmupStrategy.with(types), transformerDecorator, initializationStrategy, redefinitionStrategy, @@ -9639,6 +10592,7 @@ typeStrategy, locationStrategy, nativeMethodStrategy, + warmupStrategy, new TransformerDecorator.Compound(this.transformerDecorator, transformerDecorator), initializationStrategy, redefinitionStrategy, @@ -9667,6 +10621,7 @@ typeStrategy, locationStrategy, nativeMethodStrategy, + warmupStrategy, transformerDecorator, initializationStrategy, redefinitionStrategy, @@ -9695,6 +10650,7 @@ typeStrategy, locationStrategy, nativeMethodStrategy, + warmupStrategy, transformerDecorator, initializationStrategy, redefinitionStrategy, @@ -9723,6 +10679,7 @@ typeStrategy, locationStrategy, nativeMethodStrategy, + warmupStrategy, transformerDecorator, initializationStrategy, redefinitionStrategy, @@ -9751,6 +10708,7 @@ typeStrategy, locationStrategy, nativeMethodStrategy, + warmupStrategy, transformerDecorator, initializationStrategy, redefinitionStrategy, @@ -9779,6 +10737,7 @@ typeStrategy, locationStrategy, nativeMethodStrategy, + warmupStrategy, transformerDecorator, initializationStrategy, redefinitionStrategy, @@ -9807,6 +10766,7 @@ typeStrategy, locationStrategy, nativeMethodStrategy, + warmupStrategy, transformerDecorator, initializationStrategy, redefinitionStrategy, @@ -9835,6 +10795,7 @@ typeStrategy, locationStrategy, nativeMethodStrategy, + warmupStrategy, transformerDecorator, initializationStrategy, redefinitionStrategy, @@ -9863,6 +10824,7 @@ typeStrategy, locationStrategy, nativeMethodStrategy, + warmupStrategy, transformerDecorator, initializationStrategy, redefinitionStrategy, @@ -9893,6 +10855,7 @@ : TypeStrategy.Default.REDEFINE_FROZEN, locationStrategy, NativeMethodStrategy.Disabled.INSTANCE, + warmupStrategy, transformerDecorator, InitializationStrategy.NoOp.INSTANCE, redefinitionStrategy, @@ -10054,66 +11017,66 @@ } /** - * {@inheritDoc} + * Resolves the instrumentation provided by {@code net.bytebuddy.agent.Installer}. + * + * @return The installed instrumentation instance. */ - public ResettableClassFileTransformer installOn(Instrumentation instrumentation) { - if (!circularityLock.acquire()) { - throw new IllegalStateException("Could not acquire the circularity lock upon installation."); - } + private static Instrumentation resolveByteBuddyAgentInstrumentation() { try { - return doInstall(instrumentation, new Transformation.SimpleMatcher(ignoreMatcher, transformations)); - } finally { - circularityLock.release(); + Class installer = ClassLoader.getSystemClassLoader().loadClass(INSTALLER_TYPE); + JavaModule source = JavaModule.ofType(AgentBuilder.class), target = JavaModule.ofType(installer); + if (source != null && !source.canRead(target)) { + Class module = Class.forName("java.lang.Module"); + module.getMethod("addReads", module).invoke(source.unwrap(), target.unwrap()); + } + return (Instrumentation) installer.getMethod(INSTALLER_GETTER).invoke(null); + } catch (RuntimeException exception) { + throw exception; + } catch (Exception exception) { + throw new IllegalStateException("The Byte Buddy agent is not installed or not accessible", exception); } } /** * {@inheritDoc} */ + public ResettableClassFileTransformer installOn(Instrumentation instrumentation) { + return doInstall(instrumentation, new Transformation.SimpleMatcher(ignoreMatcher, transformations), PatchMode.Handler.NoOp.INSTANCE); + } + + /** + * {@inheritDoc} + */ public ResettableClassFileTransformer installOnByteBuddyAgent() { - try { - return installOn((Instrumentation) ClassLoader.getSystemClassLoader() - .loadClass(INSTALLER_TYPE) - .getMethod(INSTRUMENTATION_GETTER) - .invoke(STATIC_MEMBER)); - } catch (RuntimeException exception) { - throw exception; - } catch (Exception exception) { - throw new IllegalStateException("The Byte Buddy agent is not installed or not accessible", exception); - } + return installOn(resolveByteBuddyAgentInstrumentation()); } /** * {@inheritDoc} */ public ResettableClassFileTransformer patchOn(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer) { - if (!circularityLock.acquire()) { - throw new IllegalStateException("Could not acquire the circularity lock upon installation."); - } - try { - if (!classFileTransformer.reset(instrumentation, RedefinitionStrategy.DISABLED)) { - throw new IllegalArgumentException("Cannot patch unregistered class file transformer: " + classFileTransformer); - } - return doInstall(instrumentation, new Transformation.DifferentialMatcher(ignoreMatcher, transformations, classFileTransformer)); - } finally { - circularityLock.release(); - } + return patchOn(instrumentation, classFileTransformer, PatchMode.OVERLAP); + } + + /** + * {@inheritDoc} + */ + public ResettableClassFileTransformer patchOn(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer, PatchMode patchMode) { + return doInstall(instrumentation, new Transformation.DifferentialMatcher(ignoreMatcher, transformations, classFileTransformer), patchMode.toHandler(classFileTransformer)); } /** * {@inheritDoc} */ public ResettableClassFileTransformer patchOnByteBuddyAgent(ResettableClassFileTransformer classFileTransformer) { - try { - return patchOn((Instrumentation) ClassLoader.getSystemClassLoader() - .loadClass(INSTALLER_TYPE) - .getMethod(INSTRUMENTATION_GETTER) - .invoke(STATIC_MEMBER), classFileTransformer); - } catch (RuntimeException exception) { - throw exception; - } catch (Exception exception) { - throw new IllegalStateException("The Byte Buddy agent is not installed or not accessible", exception); - } + return patchOnByteBuddyAgent(classFileTransformer, PatchMode.OVERLAP); + } + + /** + * {@inheritDoc} + */ + public ResettableClassFileTransformer patchOnByteBuddyAgent(ResettableClassFileTransformer classFileTransformer, PatchMode patchMode) { + return patchOn(resolveByteBuddyAgentInstrumentation(), classFileTransformer, patchMode); } /** @@ -10121,54 +11084,69 @@ * * @param instrumentation The instrumentation to install the matcher on. * @param matcher The matcher to identify redefined types. + * @param handler The handler to use for implementing a patch mode. * @return The created class file transformer. */ - private ResettableClassFileTransformer doInstall(Instrumentation instrumentation, RawMatcher matcher) { - RedefinitionStrategy.ResubmissionStrategy.Installation installation = redefinitionResubmissionStrategy.apply(instrumentation, - poolStrategy, - locationStrategy, - descriptionStrategy, - fallbackStrategy, - listener, - installationListener, - circularityLock, - new Transformation.SimpleMatcher(ignoreMatcher, transformations), - redefinitionStrategy, - redefinitionBatchAllocator, - redefinitionListener); - ResettableClassFileTransformer classFileTransformer = transformerDecorator.decorate(makeRaw(installation.getListener(), - installation.getInstallationListener(), - installation.getResubmissionEnforcer())); - installation.getInstallationListener().onBeforeInstall(instrumentation, classFileTransformer); + private ResettableClassFileTransformer doInstall(Instrumentation instrumentation, RawMatcher matcher, PatchMode.Handler handler) { + if (!circularityLock.acquire()) { + throw new IllegalStateException("Could not acquire the circularity lock upon installation."); + } try { - if (redefinitionStrategy.isRetransforming()) { - DISPATCHER.addTransformer(instrumentation, classFileTransformer, true); - } else { - instrumentation.addTransformer(classFileTransformer); - } - nativeMethodStrategy.apply(instrumentation, classFileTransformer); - lambdaInstrumentationStrategy.apply(byteBuddy, instrumentation, classFileTransformer); - redefinitionStrategy.apply(instrumentation, + RedefinitionStrategy.ResubmissionStrategy.Installation installation = redefinitionResubmissionStrategy.apply(instrumentation, poolStrategy, locationStrategy, descriptionStrategy, fallbackStrategy, - redefinitionDiscoveryStrategy, - lambdaInstrumentationStrategy, - installation.getListener(), - redefinitionListener, - matcher, + listener, + installationListener, + circularityLock, + new Transformation.SimpleMatcher(ignoreMatcher, transformations), + redefinitionStrategy, redefinitionBatchAllocator, - circularityLock); - } catch (Throwable throwable) { - throwable = installation.getInstallationListener().onError(instrumentation, classFileTransformer, throwable); - if (throwable != null) { - instrumentation.removeTransformer(classFileTransformer); - throw new IllegalStateException("Could not install class file transformer", throwable); + redefinitionListener); + ResettableClassFileTransformer classFileTransformer = transformerDecorator.decorate(makeRaw(installation.getListener(), + installation.getInstallationListener(), + installation.getResubmissionEnforcer())); + installation.getInstallationListener().onBeforeInstall(instrumentation, classFileTransformer); + try { + warmupStrategy.apply(classFileTransformer, + locationStrategy, + redefinitionStrategy, + circularityLock, + installation.getInstallationListener()); + handler.onBeforeRegistration(instrumentation); + if (redefinitionStrategy.isRetransforming()) { + DISPATCHER.addTransformer(instrumentation, classFileTransformer, true); + } else { + instrumentation.addTransformer(classFileTransformer); + } + handler.onAfterRegistration(instrumentation); + nativeMethodStrategy.apply(instrumentation, classFileTransformer); + lambdaInstrumentationStrategy.apply(byteBuddy, instrumentation, classFileTransformer); + redefinitionStrategy.apply(instrumentation, + poolStrategy, + locationStrategy, + descriptionStrategy, + fallbackStrategy, + redefinitionDiscoveryStrategy, + lambdaInstrumentationStrategy, + installation.getListener(), + redefinitionListener, + matcher, + redefinitionBatchAllocator, + circularityLock); + } catch (@MaybeNull Throwable throwable) { + throwable = installation.getInstallationListener().onError(instrumentation, classFileTransformer, throwable); + if (throwable != null) { + instrumentation.removeTransformer(classFileTransformer); + throw new IllegalStateException("Could not install class file transformer", throwable); + } } + installation.getInstallationListener().onInstall(instrumentation, classFileTransformer); + return classFileTransformer; + } finally { + circularityLock.release(); } - installation.getInstallationListener().onInstall(instrumentation, classFileTransformer); - return classFileTransformer; } /** @@ -10303,6 +11281,196 @@ } /** + * A strategy to warm up a {@link ClassFileTransformer} before using it to eagerly load classes and to avoid + * circularity errors when classes are loaded during actual transformation for the first time. + */ + protected interface WarmupStrategy { + + /** + * Applies this warm up strategy. + * + * @param classFileTransformer The class file transformer to warm up. + * @param locationStrategy The location strategy to use. + * @param redefinitionStrategy The redefinition strategy being used. + * @param circularityLock The circularity lock to use. + * @param listener The listener to notify over warmup events. + */ + void apply(ResettableClassFileTransformer classFileTransformer, + LocationStrategy locationStrategy, + RedefinitionStrategy redefinitionStrategy, + CircularityLock circularityLock, + InstallationListener listener); + + /** + * Adds the provided types to this warmup strategy. + * + * @param types The types to add. + * @return An appropriate warmup strategy. + */ + WarmupStrategy with(Collection> types); + + /** + * A non-operational warmup strategy. + */ + enum NoOp implements WarmupStrategy { + + /** + * The singleton instance. + */ + INSTANCE; + + /** + * {@inheritDoc} + */ + public void apply(ResettableClassFileTransformer classFileTransformer, + LocationStrategy locationStrategy, + RedefinitionStrategy redefinitionStrategy, + CircularityLock circularityLock, + InstallationListener listener) { + /* do nothing */ + } + + /** + * {@inheritDoc} + */ + public WarmupStrategy with(Collection> types) { + return new Enabled(new LinkedHashSet>(types)); + } + } + + /** + * An enabled warmup strategy. + */ + @HashCodeAndEqualsPlugin.Enhance + class Enabled implements WarmupStrategy { + + /** + * A dispatcher for invoking a {@link ClassFileTransformer} when the module system is available. + */ + private static final Dispatcher DISPATCHER = Default.doPrivileged(JavaDispatcher.of(Dispatcher.class)); + + /** + * The types to warm up. + */ + private final Set> types; + + /** + * Creates a new enabled warmup strategy. + * + * @param types The types to warm up. + */ + protected Enabled(Set> types) { + this.types = types; + } + + /** + * {@inheritDoc} + */ + public void apply(ResettableClassFileTransformer classFileTransformer, + LocationStrategy locationStrategy, + RedefinitionStrategy redefinitionStrategy, + CircularityLock circularityLock, + InstallationListener listener) { + listener.onBeforeWarmUp(types, classFileTransformer); + boolean transformed = false; + Map, byte[]> results = new LinkedHashMap, byte[]>(); + for (Class type : types) { + try { + JavaModule module = JavaModule.ofType(type); + byte[] binaryRepresentation = locationStrategy.classFileLocator(type.getClassLoader(), module) + .locate(type.getName()) + .resolve(); + circularityLock.release(); + try { + byte[] result; + if (module == null) { + result = classFileTransformer.transform(type.getClassLoader(), + Type.getInternalName(type), + NOT_PREVIOUSLY_DEFINED, + type.getProtectionDomain(), + binaryRepresentation); + transformed |= result != null; + if (redefinitionStrategy.isEnabled()) { + result = classFileTransformer.transform(type.getClassLoader(), + Type.getInternalName(type), + type, + type.getProtectionDomain(), + binaryRepresentation); + transformed |= result != null; + } + } else { + result = DISPATCHER.transform(classFileTransformer, + module.unwrap(), + type.getClassLoader(), + Type.getInternalName(type), + NOT_PREVIOUSLY_DEFINED, + type.getProtectionDomain(), + binaryRepresentation); + transformed |= result != null; + if (redefinitionStrategy.isEnabled()) { + result = DISPATCHER.transform(classFileTransformer, + module.unwrap(), + type.getClassLoader(), + Type.getInternalName(type), + type, + type.getProtectionDomain(), + binaryRepresentation); + transformed |= result != null; + } + } + results.put(type, result); + } finally { + circularityLock.acquire(); + } + } catch (Throwable throwable) { + listener.onWarmUpError(type, classFileTransformer, throwable); + results.put(type, NO_TRANSFORMATION); + } + } + listener.onAfterWarmUp(results, classFileTransformer, transformed); + } + + /** + * {@inheritDoc} + */ + public WarmupStrategy with(Collection> types) { + Set> combined = new LinkedHashSet>(this.types); + combined.addAll(types); + return new Enabled(combined); + } + + /** + * A dispatcher to interact with a {@link ClassFileTransformer} when the module system is active. + */ + @JavaDispatcher.Proxied("java.lang.instrument.ClassFileTransformer") + protected interface Dispatcher { + + /** + * Transforms a class. + * + * @param target The transformer to use for transformation. + * @param module The Java module of the transformed class. + * @param classLoader The class loader of the transformed class or {@code null} if loaded by the boot loader. + * @param name The internal name of the transformed class. + * @param classBeingRedefined The class being redefined or {@code null} if not a retransformation. + * @param protectionDomain The class's protection domain. + * @param binaryRepresentation The class's binary representation. + * @return The transformed class file or {@code null} if untransformed. + * @throws IllegalClassFormatException If the class file cannot be generated. + */ + @MaybeNull + byte[] transform(ClassFileTransformer target, + @MaybeNull @JavaDispatcher.Proxied("java.lang.Module") Object module, + @MaybeNull ClassLoader classLoader, + String name, + @MaybeNull Class classBeingRedefined, + ProtectionDomain protectionDomain, + byte[] binaryRepresentation) throws IllegalClassFormatException; + } + } + } + + /** * A transformation to apply. */ @HashCodeAndEqualsPlugin.Enhance @@ -10311,6 +11479,7 @@ /** * Indicates that a type should not be ignored. */ + @AlwaysNull private static final byte[] NONE = null; /** @@ -10399,9 +11568,9 @@ * {@inheritDoc} */ public boolean matches(TypeDescription typeDescription, - ClassLoader classLoader, - JavaModule module, - Class classBeingRedefined, + @MaybeNull ClassLoader classLoader, + @MaybeNull JavaModule module, + @MaybeNull Class classBeingRedefined, ProtectionDomain protectionDomain) { if (ignoreMatcher.matches(typeDescription, classLoader, module, classBeingRedefined, protectionDomain)) { return false; @@ -10455,9 +11624,9 @@ * {@inheritDoc} */ public boolean matches(TypeDescription typeDescription, - ClassLoader classLoader, - JavaModule module, - Class classBeingRedefined, + @MaybeNull ClassLoader classLoader, + @MaybeNull JavaModule module, + @MaybeNull Class classBeingRedefined, ProtectionDomain protectionDomain) { Iterator iterator = classFileTransformer.iterator(typeDescription, classLoader, module, classBeingRedefined, protectionDomain); if (ignoreMatcher.matches(typeDescription, classLoader, module, classBeingRedefined, protectionDomain)) { @@ -10489,16 +11658,19 @@ /** * The type's class loader. */ + @MaybeNull private final ClassLoader classLoader; /** * The type's module. */ + @MaybeNull private final JavaModule module; /** * The class being redefined or {@code null} if the type was not previously loaded. */ + @MaybeNull private final Class classBeingRedefined; /** @@ -10527,9 +11699,9 @@ * @param transformations The matched transformations. */ protected TransformerIterator(TypeDescription typeDescription, - ClassLoader classLoader, - JavaModule module, - Class classBeingRedefined, + @MaybeNull ClassLoader classLoader, + @MaybeNull JavaModule module, + @MaybeNull Class classBeingRedefined, ProtectionDomain protectionDomain, List transformations) { this.typeDescription = typeDescription; @@ -10679,6 +11851,7 @@ * The access control context to use for loading classes or {@code null} if the * access controller is not available on the current VM. */ + @MaybeNull private final Object accessControlContext; /** @@ -10744,9 +11917,10 @@ * * @return The current access control context or {@code null} if the current VM does not support it. */ + @MaybeNull @AccessControllerPlugin.Enhance private static Object getContext() { - return AccessController.getContext(); // null; + return null; } /** @@ -10758,16 +11932,17 @@ * @return The action's resolved value. */ @AccessControllerPlugin.Enhance - private static T doPrivileged(PrivilegedAction action, @SuppressWarnings("unused") Object context) { - return AccessController.doPrivileged(action, (AccessControlContext) context); // action.run(); + private static T doPrivileged(PrivilegedAction action, @MaybeNull @SuppressWarnings("unused") Object context) { + return action.run(); } /** * {@inheritDoc} */ - public byte[] transform(ClassLoader classLoader, - String internalTypeName, - Class classBeingRedefined, + @MaybeNull + public byte[] transform(@MaybeNull ClassLoader classLoader, + @MaybeNull String internalTypeName, + @MaybeNull Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] binaryRepresentation) { if (circularityLock.acquire()) { @@ -10777,8 +11952,6 @@ classBeingRedefined, protectionDomain, binaryRepresentation), accessControlContext); - } catch (Throwable ignored) { - return NO_TRANSFORMATION; } finally { circularityLock.release(); } @@ -10799,10 +11972,11 @@ * @param binaryRepresentation The class file of the instrumented class in its current state. * @return The transformed class file or an empty byte array if this transformer does not apply an instrumentation. */ + @MaybeNull protected byte[] transform(Object rawModule, - ClassLoader classLoader, - String internalTypeName, - Class classBeingRedefined, + @MaybeNull ClassLoader classLoader, + @MaybeNull String internalTypeName, + @MaybeNull Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] binaryRepresentation) { if (circularityLock.acquire()) { @@ -10813,8 +11987,6 @@ classBeingRedefined, protectionDomain, binaryRepresentation), accessControlContext); - } catch (Throwable ignored) { - return NO_TRANSFORMATION; } finally { circularityLock.release(); } @@ -10834,10 +12006,11 @@ * @param binaryRepresentation The class file of the instrumented class in its current state. * @return The transformed class file or an empty byte array if this transformer does not apply an instrumentation. */ - private byte[] transform(JavaModule module, - ClassLoader classLoader, - String internalTypeName, - Class classBeingRedefined, + @MaybeNull + private byte[] transform(@MaybeNull JavaModule module, + @MaybeNull ClassLoader classLoader, + @MaybeNull String internalTypeName, + @MaybeNull Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] binaryRepresentation) { if (internalTypeName == null || !lambdaInstrumentationStrategy.isInstrumented(classBeingRedefined)) { @@ -10854,7 +12027,7 @@ } finally { listener.onError(name, classLoader, module, classBeingRedefined != null, throwable); } - return NO_TRANSFORMATION; + throw new IllegalStateException("Failed transformation of " + name, throwable); } try { listener.onDiscovery(name, classLoader, module, classBeingRedefined != null); @@ -10868,14 +12041,14 @@ return doTransform(module, classLoader, name, classBeingRedefined, classBeingRedefined != null, protectionDomain, typePool, classFileLocator); } catch (Throwable throwable) { if (classBeingRedefined != null && descriptionStrategy.isLoadedFirst() && fallbackStrategy.isFallback(classBeingRedefined, throwable)) { - return doTransform(module, classLoader, name, NO_LOADED_TYPE, Listener.LOADED, protectionDomain, typePool, classFileLocator); + return doTransform(module, classLoader, name, NOT_PREVIOUSLY_DEFINED, Listener.LOADED, protectionDomain, typePool, classFileLocator); } else { throw throwable; } } } catch (Throwable throwable) { listener.onError(name, classLoader, module, classBeingRedefined != null, throwable); - return NO_TRANSFORMATION; + throw new IllegalStateException("Failed transformation of " + name, throwable); } finally { listener.onComplete(name, classLoader, module, classBeingRedefined != null); } @@ -10894,10 +12067,11 @@ * @param classFileLocator The class file locator to use. * @return The transformed class file or an empty byte array if this transformer does not apply an instrumentation. */ - private byte[] doTransform(JavaModule module, - ClassLoader classLoader, + @MaybeNull + private byte[] doTransform(@MaybeNull JavaModule module, + @MaybeNull ClassLoader classLoader, String name, - Class classBeingRedefined, + @MaybeNull Class classBeingRedefined, boolean loaded, ProtectionDomain protectionDomain, TypePool typePool, @@ -10927,7 +12101,7 @@ protectionDomain); InitializationStrategy.Dispatcher dispatcher = initializationStrategy.dispatcher(); for (Transformer transformer : transformers) { - builder = transformer.transform(builder, typeDescription, classLoader, module); + builder = transformer.transform(builder, typeDescription, classLoader, module, protectionDomain); } DynamicType.Unloaded dynamicType = dispatcher.apply(builder).make(TypeResolutionStrategy.Disabled.INSTANCE, typePool); dispatcher.register(dynamicType, classLoader, protectionDomain, injectionStrategy); @@ -10939,9 +12113,9 @@ * {@inheritDoc} */ public Iterator iterator(TypeDescription typeDescription, - ClassLoader classLoader, - JavaModule module, - Class classBeingRedefined, + @MaybeNull ClassLoader classLoader, + @MaybeNull JavaModule module, + @MaybeNull Class classBeingRedefined, ProtectionDomain protectionDomain) { return ignoreMatcher.matches(typeDescription, classLoader, module, classBeingRedefined, protectionDomain) ? Collections.emptySet().iterator() @@ -11037,7 +12211,7 @@ /** * {@inheritDoc} */ - @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback") + @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback.") public Factory run() { try { return new Factory.ForJava9CapableVm(new ByteBuddy() @@ -11145,7 +12319,7 @@ } catch (InstantiationException exception) { throw new IllegalStateException("Cannot instantiate " + executingTransformer.getDeclaringClass(), exception); } catch (InvocationTargetException exception) { - throw new IllegalStateException("Cannot invoke " + executingTransformer, exception.getCause()); + throw new IllegalStateException("Cannot invoke " + executingTransformer, exception.getTargetException()); } } } @@ -11210,16 +12384,22 @@ /** * The type's class loader or {@code null} if the bootstrap class loader is represented. */ + @MaybeNull + @HashCodeAndEqualsPlugin.ValueHandling(HashCodeAndEqualsPlugin.ValueHandling.Sort.REVERSE_NULLABILITY) private final ClassLoader classLoader; /** * The type's internal name or {@code null} if no such name exists. */ + @MaybeNull + @HashCodeAndEqualsPlugin.ValueHandling(HashCodeAndEqualsPlugin.ValueHandling.Sort.REVERSE_NULLABILITY) private final String internalTypeName; /** * The class being redefined or {@code null} if no such class exists. */ + @MaybeNull + @HashCodeAndEqualsPlugin.ValueHandling(HashCodeAndEqualsPlugin.ValueHandling.Sort.REVERSE_NULLABILITY) private final Class classBeingRedefined; /** @@ -11241,9 +12421,9 @@ * @param protectionDomain The type's protection domain. * @param binaryRepresentation The type's binary representation. */ - protected LegacyVmDispatcher(ClassLoader classLoader, - String internalTypeName, - Class classBeingRedefined, + protected LegacyVmDispatcher(@MaybeNull ClassLoader classLoader, + @MaybeNull String internalTypeName, + @MaybeNull Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] binaryRepresentation) { this.classLoader = classLoader; @@ -11256,6 +12436,7 @@ /** * {@inheritDoc} */ + @MaybeNull public byte[] run() { return transform(JavaModule.UNSUPPORTED, classLoader, @@ -11280,16 +12461,22 @@ /** * The type's class loader or {@code null} if the type is loaded by the bootstrap loader. */ + @MaybeNull + @HashCodeAndEqualsPlugin.ValueHandling(HashCodeAndEqualsPlugin.ValueHandling.Sort.REVERSE_NULLABILITY) private final ClassLoader classLoader; /** * The type's internal name or {@code null} if no such name exists. */ + @MaybeNull + @HashCodeAndEqualsPlugin.ValueHandling(HashCodeAndEqualsPlugin.ValueHandling.Sort.REVERSE_NULLABILITY) private final String internalTypeName; /** * The class being redefined or {@code null} if no such class exists. */ + @MaybeNull + @HashCodeAndEqualsPlugin.ValueHandling(HashCodeAndEqualsPlugin.ValueHandling.Sort.REVERSE_NULLABILITY) private final Class classBeingRedefined; /** @@ -11313,9 +12500,9 @@ * @param binaryRepresentation The type's binary representation. */ protected Java9CapableVmDispatcher(Object rawModule, - ClassLoader classLoader, - String internalTypeName, - Class classBeingRedefined, + @MaybeNull ClassLoader classLoader, + @MaybeNull String internalTypeName, + @MaybeNull Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] binaryRepresentation) { this.rawModule = rawModule; @@ -11329,6 +12516,7 @@ /** * {@inheritDoc} */ + @MaybeNull public byte[] run() { return transform(JavaModule.of(rawModule), classLoader, @@ -11481,6 +12669,20 @@ /** * {@inheritDoc} */ + public AgentBuilder warmUp(Class... type) { + return materialize().warmUp(type); + } + + /** + * {@inheritDoc} + */ + public AgentBuilder warmUp(Collection> types) { + return materialize().warmUp(types); + } + + /** + * {@inheritDoc} + */ public AgentBuilder assureReadEdgeTo(Instrumentation instrumentation, Class... type) { return materialize().assureReadEdgeTo(instrumentation, type); } @@ -11611,11 +12813,25 @@ /** * {@inheritDoc} */ + public ResettableClassFileTransformer patchOn(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer, PatchMode patchMode) { + return materialize().patchOn(instrumentation, classFileTransformer, patchMode); + } + + /** + * {@inheritDoc} + */ public ResettableClassFileTransformer patchOnByteBuddyAgent(ResettableClassFileTransformer classFileTransformer) { return materialize().patchOnByteBuddyAgent(classFileTransformer); } /** + * {@inheritDoc} + */ + public ResettableClassFileTransformer patchOnByteBuddyAgent(ResettableClassFileTransformer classFileTransformer, PatchMode patchMode) { + return materialize().patchOnByteBuddyAgent(classFileTransformer, patchMode); + } + + /** * An abstract base implementation of a matchable. * * @param The type that is produced by chaining a matcher. @@ -11699,6 +12915,7 @@ typeStrategy, locationStrategy, nativeMethodStrategy, + warmupStrategy, transformerDecorator, initializationStrategy, redefinitionStrategy, @@ -11776,6 +12993,7 @@ typeStrategy, locationStrategy, nativeMethodStrategy, + warmupStrategy, transformerDecorator, initializationStrategy, redefinitionStrategy, @@ -11837,6 +13055,7 @@ * @param typeStrategy The definition handler to use. * @param locationStrategy The location strategy to use. * @param nativeMethodStrategy The native method strategy to apply. + * @param warmupStrategy The warmup strategy to use. * @param transformerDecorator A decorator to wrap the created class file transformer. * @param initializationStrategy The initialization strategy to use for transformed types. * @param redefinitionStrategy The redefinition strategy to apply. @@ -11861,6 +13080,7 @@ TypeStrategy typeStrategy, LocationStrategy locationStrategy, NativeMethodStrategy nativeMethodStrategy, + WarmupStrategy warmupStrategy, TransformerDecorator transformerDecorator, InitializationStrategy initializationStrategy, RedefinitionStrategy redefinitionStrategy, @@ -11883,6 +13103,7 @@ typeStrategy, locationStrategy, nativeMethodStrategy, + warmupStrategy, transformerDecorator, initializationStrategy, redefinitionStrategy, @@ -11914,6 +13135,7 @@ typeStrategy, locationStrategy, nativeMethodStrategy, + warmupStrategy, transformerDecorator, initializationStrategy, redefinitionStrategy, @@ -11952,6 +13174,7 @@ typeStrategy, locationStrategy, nativeMethodStrategy, + warmupStrategy, transformerDecorator, initializationStrategy, redefinitionStrategy, @@ -11983,6 +13206,7 @@ typeStrategy, locationStrategy, nativeMethodStrategy, + warmupStrategy, transformerDecorator, initializationStrategy, redefinitionStrategy, @@ -12056,6 +13280,7 @@ typeStrategy, locationStrategy, nativeMethodStrategy, + warmupStrategy, transformerDecorator, initializationStrategy, redefinitionStrategy, diff -Nru byte-buddy-1.11.4/byte-buddy-dep/src/main/java/net/bytebuddy/agent/builder/LambdaFactory.java byte-buddy-1.12.21/byte-buddy-dep/src/main/java/net/bytebuddy/agent/builder/LambdaFactory.java --- byte-buddy-1.11.4/byte-buddy-dep/src/main/java/net/bytebuddy/agent/builder/LambdaFactory.java 2021-06-19 21:31:07.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-dep/src/main/java/net/bytebuddy/agent/builder/LambdaFactory.java 2023-01-04 22:45:00.000000000 +0000 @@ -47,7 +47,7 @@ * A mapping of all registered class file transformers and their lambda factories, linked in their application order. * This field must not be accessed directly but only by reading this class from the system class loader. */ - @SuppressFBWarnings(value = "MS_MUTABLE_COLLECTION_PKGPROTECT", justification = "The field must be accessible by different class loader instances") + @SuppressFBWarnings(value = "MS_MUTABLE_COLLECTION_PKGPROTECT", justification = "The field must be accessible by different class loader instances.") public static final Map CLASS_FILE_TRANSFORMERS = new ConcurrentHashMap(); /** diff -Nru byte-buddy-1.11.4/byte-buddy-dep/src/main/java/net/bytebuddy/agent/builder/package-info.java byte-buddy-1.12.21/byte-buddy-dep/src/main/java/net/bytebuddy/agent/builder/package-info.java --- byte-buddy-1.11.4/byte-buddy-dep/src/main/java/net/bytebuddy/agent/builder/package-info.java 2021-06-19 21:31:07.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-dep/src/main/java/net/bytebuddy/agent/builder/package-info.java 2023-01-04 22:45:00.000000000 +0000 @@ -19,4 +19,7 @@ * but offers higher-level APIs in order to allow for the implementation of very readable transformations using * {@link net.bytebuddy.ByteBuddy}. */ +@NeverNull.ByDefault package net.bytebuddy.agent.builder; + +import net.bytebuddy.utility.nullability.NeverNull; diff -Nru byte-buddy-1.11.4/byte-buddy-dep/src/main/java/net/bytebuddy/agent/builder/ResettableClassFileTransformer.java byte-buddy-1.12.21/byte-buddy-dep/src/main/java/net/bytebuddy/agent/builder/ResettableClassFileTransformer.java --- byte-buddy-1.11.4/byte-buddy-dep/src/main/java/net/bytebuddy/agent/builder/ResettableClassFileTransformer.java 2021-06-19 21:31:07.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-dep/src/main/java/net/bytebuddy/agent/builder/ResettableClassFileTransformer.java 2023-01-04 22:45:00.000000000 +0000 @@ -18,6 +18,7 @@ import net.bytebuddy.build.HashCodeAndEqualsPlugin; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.utility.JavaModule; +import net.bytebuddy.utility.nullability.MaybeNull; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; @@ -33,16 +34,16 @@ * Creates an iterator over the transformers that are applied for a given type. * * @param typeDescription A description of a type. - * @param classLoader The type's class loader. - * @param module The type's module. + * @param classLoader The type's class loader or {@code null} if the boot loader. + * @param module The type's module or {@code null} if the module system is not supported by the current VM. * @param classBeingRedefined The class being redefined or {@code null} if the type is not yet loaded. * @param protectionDomain The type's protection domain. * @return An iterator over the transformers that are applied by this class file transformer if the given type is discovered. */ Iterator iterator(TypeDescription typeDescription, - ClassLoader classLoader, - JavaModule module, - Class classBeingRedefined, + @MaybeNull ClassLoader classLoader, + @MaybeNull JavaModule module, + @MaybeNull Class classBeingRedefined, ProtectionDomain protectionDomain); /** @@ -90,6 +91,7 @@ * @param redefinitionBatchAllocator The batch allocator to use. * @return {@code true} if a reset was applied and this transformer was not previously removed. */ + @SuppressWarnings("overloads") boolean reset(Instrumentation instrumentation, AgentBuilder.RedefinitionStrategy redefinitionStrategy, AgentBuilder.RedefinitionStrategy.BatchAllocator redefinitionBatchAllocator); @@ -116,6 +118,7 @@ * @param redefinitionDiscoveryStrategy The discovery strategy for the types to reset. * @return {@code true} if a reset was applied and this transformer was not previously removed. */ + @SuppressWarnings("overloads") boolean reset(Instrumentation instrumentation, AgentBuilder.RedefinitionStrategy redefinitionStrategy, AgentBuilder.RedefinitionStrategy.DiscoveryStrategy redefinitionDiscoveryStrategy); @@ -171,6 +174,7 @@ * @param redefinitionListener The redefinition listener to apply. * @return {@code true} if a reset was applied and this transformer was not previously removed. */ + @SuppressWarnings("overloads") boolean reset(Instrumentation instrumentation, AgentBuilder.RedefinitionStrategy redefinitionStrategy, AgentBuilder.RedefinitionStrategy.DiscoveryStrategy redefinitionDiscoveryStrategy, @@ -199,6 +203,7 @@ * @param redefinitionListener The redefinition listener to apply. * @return {@code true} if a reset was applied and this transformer was not previously removed. */ + @SuppressWarnings("overloads") boolean reset(Instrumentation instrumentation, AgentBuilder.RedefinitionStrategy redefinitionStrategy, AgentBuilder.RedefinitionStrategy.BatchAllocator redefinitionBatchAllocator, @@ -389,9 +394,9 @@ * {@inheritDoc} */ public Iterator iterator(TypeDescription typeDescription, - ClassLoader classLoader, - JavaModule module, - Class classBeingRedefined, + @MaybeNull ClassLoader classLoader, + @MaybeNull JavaModule module, + @MaybeNull Class classBeingRedefined, ProtectionDomain protectionDomain) { return classFileTransformer.iterator(typeDescription, classLoader, module, classBeingRedefined, protectionDomain); } diff -Nru byte-buddy-1.11.4/byte-buddy-dep/src/main/java/net/bytebuddy/asm/Advice.java byte-buddy-1.12.21/byte-buddy-dep/src/main/java/net/bytebuddy/asm/Advice.java --- byte-buddy-1.11.4/byte-buddy-dep/src/main/java/net/bytebuddy/asm/Advice.java 2021-06-19 21:31:07.000000000 +0000 +++ byte-buddy-1.12.21/byte-buddy-dep/src/main/java/net/bytebuddy/asm/Advice.java 2023-01-04 22:45:00.000000000 +0000 @@ -18,6 +18,7 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import net.bytebuddy.ClassFileVersion; import net.bytebuddy.build.HashCodeAndEqualsPlugin; +import net.bytebuddy.build.RepeatedAnnotationPlugin; import net.bytebuddy.description.annotation.AnnotationDescription; import net.bytebuddy.description.annotation.AnnotationValue; import net.bytebuddy.description.enumeration.EnumerationDescription; @@ -52,8 +53,9 @@ import net.bytebuddy.utility.JavaConstant; import net.bytebuddy.utility.JavaType; import net.bytebuddy.utility.OpenedClassReader; +import net.bytebuddy.utility.nullability.AlwaysNull; +import net.bytebuddy.utility.nullability.MaybeNull; import net.bytebuddy.utility.visitor.ExceptionTableSensitiveMethodVisitor; -import net.bytebuddy.utility.visitor.FramePaddingMethodVisitor; import net.bytebuddy.utility.visitor.LineNumberPrependingMethodVisitor; import net.bytebuddy.utility.visitor.StackAwareMethodVisitor; import org.objectweb.asm.*; @@ -162,6 +164,7 @@ /** * Indicates that no class reader is available to an advice method. */ + @AlwaysNull private static final ClassReader UNDEFINED = null; /** @@ -458,10 +461,10 @@ * * @param type The annotation type that indicates a given form of advice that is currently resolved. * @param property An annotation property that indicates if the advice method should be inlined. - * @param dispatcher Any previous dispatcher that was discovered or {@code null} if no such dispatcher was yet found. + * @param dispatcher Any previous dispatcher that was discovered or the previous dispatcher if found. * @param methodDescription The method description that is considered as an advice method. * @param delegator The delegator to use. - * @return A resolved dispatcher or {@code null} if no dispatcher was resolved. + * @return A resolved dispatcher or the previous dispatcher if none was found. */ private static Dispatcher.Unresolved locate(Class type, MethodDescription.InDefinedShape property, @@ -534,9 +537,9 @@ Implementation.Context implementationContext, int writerFlags, int readerFlags) { - methodVisitor = new FramePaddingMethodVisitor(methodEnter.isPrependLineNumber() - ? new LineNumberPrependingMethodVisitor(methodVisitor) - : methodVisitor); + if (methodEnter.isPrependLineNumber()) { + methodVisitor = new LineNumberPrependingMethodVisitor(methodVisitor); + } if (!methodExit.isAlive()) { return new AdviceVisitor.WithoutExitAdvice(methodVisitor, implementationContext, @@ -1130,6 +1133,65 @@ } /** + * A write-only mapping for a field value, typically to be used for constructors prior to invoking the super-constructor. + */ + @HashCodeAndEqualsPlugin.Enhance + public static class WriteOnly implements Target { + + /** + * The field value to load. + */ + private final FieldDescription fieldDescription; + + /** + * An assignment to apply prior to a field write. + */ + private final StackManipulation writeAssignment; + + /** + * Creates a write-only mapping for a field value. + * + * @param fieldDescription The field value to load. + * @param writeAssignment An assignment to apply prior to a field write. + */ + protected WriteOnly(FieldDescription fieldDescription, StackManipulation writeAssignment) { + this.fieldDescription = fieldDescription; + this.writeAssignment = writeAssignment; + } + + /** + * {@inheritDoc} + */ + public StackManipulation resolveRead() { + throw new IllegalStateException("Cannot read write-only field value"); + } + + /** + * {@inheritDoc} + */ + public StackManipulation resolveWrite() { + StackManipulation preparation; + if (fieldDescription.isStatic()) { + preparation = StackManipulation.Trivial.INSTANCE; + } else { + preparation = new StackManipulation.Compound( + MethodVariableAccess.loadThis(), + Duplication.SINGLE.flipOver(fieldDescription.getType()), + Removal.SINGLE + ); + } + return new StackManipulation.Compound(writeAssignment, preparation, FieldAccess.forField(fieldDescription).write()); + } + + /** + * {@inheritDoc} + */ + public StackManipulation resolveIncrement(int value) { + throw new IllegalStateException("Cannot increment write-only field value"); + } + } + + /** * A mapping for a writable field. */ @HashCodeAndEqualsPlugin.Enhance @@ -1298,6 +1360,55 @@ public StackManipulation resolveIncrement(int value) { throw new IllegalStateException("Cannot write to constant value: " + stackManipulation); } + + /** + * A constant value that can be written to. + */ + @HashCodeAndEqualsPlugin.Enhance + public static class Writable implements Target { + + /** + * The reading stack manipulation. + */ + private final StackManipulation read; + + /** + * The writing stack manipulation. + */ + private final StackManipulation write; + + /** + * Creates a writable target. + * + * @param read The reading stack manipulation. + * @param write The writing stack manipulation. + */ + public Writable(StackManipulation read, StackManipulation write) { + this.read = read; + this.write = write; + } + + /** + * {@inheritDoc} + */ + public StackManipulation resolveRead() { + return read; + } + + /** + * {@inheritDoc} + */ + public StackManipulation resolveWrite() { + return write; + } + + /** + * {@inheritDoc} + */ + public StackManipulation resolveIncrement(int value) { + throw new IllegalStateException("Cannot increment mutable constant value: " + write); + } + } } } @@ -1565,11 +1676,14 @@ /** * Creates a new offset binding for a parameter with a given index. * - * @param target The target type. - * @param argument The annotation that triggers this binding. + * @param target The target type. + * @param annotation The annotation that triggers this binding. */ - protected Unresolved(TypeDescription.Generic target, Argument argument) { - this(target, argument.readOnly(), argument.typing(), argument.value(), argument.optional()); + protected Unresolved(TypeDescription.Generic target, AnnotationDescription.Loadable annotation) { + this(target, annotation.getValue(Factory.ARGUMENT_READ_ONLY).resolve(Boolean.class), + annotation.getValue(Factory.ARGUMENT_TYPING).load(Argument.class.getClassLoader()).resolve(Assigner.Typing.class), + annotation.getValue(Factory.ARGUMENT_VALUE).resolve(Integer.class), + annotation.getValue(Factory.ARGUMENT_OPTIONAL).resolve(Boolean.class)); } /** @@ -1645,6 +1759,37 @@ INSTANCE; /** + * A description of the {@link Argument#value()} method. + */ + private static final MethodDescription.InDefinedShape ARGUMENT_VALUE; + + /** + * A description of the {@link Argument#readOnly()} method. + */ + private static final MethodDescription.InDefinedShape ARGUMENT_READ_ONLY; + + /** + * A description of the {@link Argument#typing()} method. + */ + private static final MethodDescription.InDefinedShape ARGUMENT_TYPING; + + /** + * A description of the {@link Argument#optional()} method. + */ + private static final MethodDescription.InDefinedShape ARGUMENT_OPTIONAL; + + /* + * Resolves annotation properties. + */ + static { + MethodList methods = TypeDescription.ForLoadedType.of(Argument.class).getDeclaredMethods(); + ARGUMENT_VALUE = methods.filter(named("value")).getOnly(); + ARGUMENT_READ_ONLY = methods.filter(named("readOnly")).getOnly(); + ARGUMENT_TYPING = methods.filter(named("typing")).getOnly(); + ARGUMENT_OPTIONAL = methods.filter(named("optional")).getOnly(); + } + + /** * {@inheritDoc} */ public Class getAnnotationType() { @@ -1657,10 +1802,10 @@ public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { - if (adviceType.isDelegation() && !annotation.load().readOnly()) { + if (adviceType.isDelegation() && !annotation.getValue(ARGUMENT_READ_ONLY).resolve(Boolean.class)) { throw new IllegalStateException("Cannot define writable field access for " + target + " when using delegation"); } else { - return new ForArgument.Unresolved(target.getType(), annotation.load()); + return new ForArgument.Unresolved(target.getType(), annotation); } } } @@ -1802,8 +1947,11 @@ * @param target The type that the advice method expects for the {@code this} reference. * @param annotation The mapped annotation. */ - protected ForThisReference(TypeDescription.Generic target, This annotation) { - this(target, annotation.readOnly(), annotation.typing(), annotation.optional()); + protected ForThisReference(TypeDescription.Generic target, AnnotationDescription.Loadable annotation) { + this(target, + annotation.getValue(Factory.THIS_READ_ONLY).resolve(Boolean.class), + annotation.getValue(Factory.THIS_TYPING).load(This.class.getClassLoader()).resolve(Assigner.Typing.class), + annotation.getValue(Factory.THIS_OPTIONAL).resolve(Boolean.class)); } /** @@ -1863,6 +2011,31 @@ INSTANCE; /** + * A description of the {@link This#readOnly()} method. + */ + private static final MethodDescription.InDefinedShape THIS_READ_ONLY; + + /** + * A description of the {@link This#typing()} method. + */ + private static final MethodDescription.InDefinedShape THIS_TYPING; + + /** + * A description of the {@link This#optional()} method. + */ + private static final MethodDescription.InDefinedShape THIS_OPTIONAL; + + /* + * Resolves annotation properties. + */ + static { + MethodList methods = TypeDescription.ForLoadedType.of(This.class).getDeclaredMethods(); + THIS_READ_ONLY = methods.filter(named("readOnly")).getOnly(); + THIS_TYPING = methods.filter(named("typing")).getOnly(); + THIS_OPTIONAL = methods.filter(named("optional")).getOnly(); + } + + /** * {@inheritDoc} */ public Class getAnnotationType() { @@ -1875,10 +2048,10 @@ public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { - if (adviceType.isDelegation() && !annotation.load().readOnly()) { + if (adviceType.isDelegation() && !annotation.getValue(THIS_READ_ONLY).resolve(Boolean.class)) { throw new IllegalStateException("Cannot write to this reference for " + target + " in read-only context"); } else { - return new ForThisReference(target.getType(), annotation.load()); + return new ForThisReference(target.getType(), annotation); } } } @@ -1906,26 +2079,37 @@ private final Assigner.Typing typing; /** + * {@code true} if a {@code null} value should be assigned if the + * instrumented method does not declare any parameters. + */ + private final boolean nullIfEmpty; + + /** * Creates a new offset mapping for an array containing all arguments. * * @param target The component target type. * @param annotation The mapped annotation. */ - protected ForAllArguments(TypeDescription.Generic target, AllArguments annotation) { - this(target, annotation.readOnly(), annotation.typing()); + protected ForAllArguments(TypeDescription.Generic target, AnnotationDescription.Loadable annotation) { + this(target, annotation.getValue(Factory.ALL_ARGUMENTS_READ_ONLY).resolve(Boolean.class), + annotation.getValue(Factory.ALL_ARGUMENTS_TYPING).load(AllArguments.class.getClassLoader()).resolve(Assigner.Typing.class), + annotation.getValue(Factory.ALL_ARGUMENTS_NULL_IF_EMPTY).resolve(Boolean.class)); } /** * Creates a new offset mapping for an array containing all arguments. * - * @param target The component target type. - * @param readOnly {@code true} if the array is read-only. - * @param typing The typing to apply. + * @param target The component target type. + * @param readOnly {@code true} if the array is read-only. + * @param typing The typing to apply. + * @param nullIfEmpty {@code true} if a {@code null} value should be assigned if the + * instrumented method does not declare any parameters. */ - public ForAllArguments(TypeDescription.Generic target, boolean readOnly, Assigner.Typing typing) { + public ForAllArguments(TypeDescription.Generic target, boolean readOnly, Assigner.Typing typing, boolean nullIfEmpty) { this.target = target; this.readOnly = readOnly; this.typing = typing; + this.nullIfEmpty = nullIfEmpty; } /** @@ -1936,6 +2120,11 @@ Assigner assigner, ArgumentHandler argumentHandler, Sort sort) { + if (nullIfEmpty && instrumentedMethod.getParameters().isEmpty()) { + return readOnly + ? new Target.ForStackManipulation(NullConstant.INSTANCE) + : new Target.ForStackManipulation.Writable(NullConstant.INSTANCE, Removal.SINGLE); + } List valueReads = new ArrayList(instrumentedMethod.getParameters().size()); for (ParameterDescription parameterDescription : instrumentedMethod.getParameters()) { StackManipulation readAssignment = assigner.assign(parameterDescription.getType(), target, typing); @@ -1972,6 +2161,31 @@ INSTANCE; /** + * A description of the {@link AllArguments#readOnly()} method. + */ + private static final MethodDescription.InDefinedShape ALL_ARGUMENTS_READ_ONLY; + + /** + * A description of the {@link AllArguments#typing()} method. + */ + private static final MethodDescription.InDefinedShape ALL_ARGUMENTS_TYPING; + + /** + * A description of the {@link AllArguments#nullIfEmpty()} method. + */ + private static final MethodDescription.InDefinedShape ALL_ARGUMENTS_NULL_IF_EMPTY; + + /* + * Resolves annotation properties. + */ + static { + MethodList methods = TypeDescription.ForLoadedType.of(AllArguments.class).getDeclaredMethods(); + ALL_ARGUMENTS_READ_ONLY = methods.filter(named("readOnly")).getOnly(); + ALL_ARGUMENTS_TYPING = methods.filter(named("typing")).getOnly(); + ALL_ARGUMENTS_NULL_IF_EMPTY = methods.filter(named("nullIfEmpty")).getOnly(); + } + + /** * {@inheritDoc} */ public Class getAnnotationType() { @@ -1981,17 +2195,18 @@ /** * {@inheritDoc} */ + @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "Assuming component type for array type.") public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { if (!target.getType().represents(Object.class) && !target.getType().isArray()) { throw new IllegalStateException("Cannot use AllArguments annotation on a non-array type"); - } else if (adviceType.isDelegation() && !annotation.load().readOnly()) { + } else if (adviceType.isDelegation() && !annotation.getValue(ALL_ARGUMENTS_READ_ONLY).resolve(Boolean.class)) { throw new IllegalStateException("Cannot define writable field access for " + target); } else { return new ForAllArguments(target.getType().represents(Object.class) - ? TypeDescription.Generic.OBJECT - : target.getType().getComponentType(), annotation.load()); + ? TypeDescription.Generic.OfNonGenericType.ForLoadedType.of(Object.class) + : target.getType().getComponentType(), annotation); } } } @@ -2153,20 +2368,30 @@ FieldDescription fieldDescription = resolve(instrumentedType, instrumentedMethod); if (!fieldDescription.isStatic() && instrumentedMethod.isStatic()) { throw new IllegalStateException("Cannot read non-static field " + fieldDescription + " from static method " + instrumentedMethod); - } else if (sort.isPremature(instrumentedMethod) && !fieldDescription.isStatic()) { - throw new IllegalStateException("Cannot access non-static field before calling constructor: " + instrumentedMethod); } - StackManipulation readAssignment = assigner.assign(fieldDescription.getType(), target, typing); - if (!readAssignment.isValid()) { - throw new IllegalStateException("Cannot assign " + fieldDescription + " to " + target); - } else if (readOnly) { - return new Target.ForField.ReadOnly(fieldDescription, readAssignment); + if (sort.isPremature(instrumentedMethod) && !fieldDescription.isStatic()) { + if (readOnly) { + throw new IllegalStateException("Cannot assign " + fieldDescription + " to " + target); + } else { + StackManipulation writeAssignment = assigner.assign(target, fieldDescription.getType(), typing); + if (!writeAssignment.isValid()) { + throw new IllegalStateException("Cannot assign " + target + " to " + fieldDescription); + } + return new Target.ForField.WriteOnly(fieldDescription.asDefined(), writeAssignment); + } } else { - StackManipulation writeAssignment = assigner.assign(target, fieldDescription.getType(), typing); - if (!writeAssignment.isValid()) { - throw new IllegalStateException("Cannot assign " + target + " to " + fieldDescription); + StackManipulation readAssignment = assigner.assign(fieldDescription.getType(), target, typing); + if (!readAssignment.isValid()) { + throw new IllegalStateException("Cannot assign " + fieldDescription + " to " + target); + } else if (readOnly) { + return new Target.ForField.ReadOnly(fieldDescription, readAssignment); + } else { + StackManipulation writeAssignment = assigner.assign(target, fieldDescription.getType(), typing); + if (!writeAssignment.isValid()) { + throw new IllegalStateException("Cannot assign " + target + " to " + fieldDescription); + } + return new Target.ForField.ReadWrite(fieldDescription.asDefined(), readAssignment, writeAssignment); } - return new Target.ForField.ReadWrite(fieldDescription.asDefined(), readAssignment, writeAssignment); } } @@ -2399,10 +2624,11 @@ } @Override + @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "Assuming declaring type for type member.") protected FieldDescription resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod) { if (!fieldDescription.isStatic() && !fieldDescription.getDeclaringType().asErasure().isAssignableFrom(instrumentedType)) { throw new IllegalStateException(fieldDescription + " is no member of " + instrumentedType); - } else if (!fieldDescription.isAccessibleTo(instrumentedType)) { + } else if (!fieldDescription.isVisibleTo(instrumentedType)) { throw new IllegalStateException("Cannot access " + fieldDescription + " from " + instrumentedType); } return fieldDescription; @@ -2795,6 +3021,14 @@ INSTANCE; /** + * A description of the {@link Origin#value()} method. + */ + private static final MethodDescription.InDefinedShape ORIGIN_VALUE = TypeDescription.ForLoadedType.of(Origin.class) + .getDeclaredMethods() + .filter(named("value")) + .getOnly(); + + /** * {@inheritDoc} */ public Class getAnnotationType() { @@ -2816,7 +3050,7 @@ } else if (JavaType.EXECUTABLE.getTypeStub().equals(target.getType().asErasure())) { return OffsetMapping.ForInstrumentedMethod.EXECUTABLE; } else if (target.getType().asErasure().isAssignableFrom(String.class)) { - return ForOrigin.parse(annotation.load().value()); + return ForOrigin.parse(annotation.getValue(ORIGIN_VALUE).resolve(String.class)); } else { throw new IllegalStateException("Non-supported type " + target.getType() + " for @Origin annotation"); } @@ -2903,7 +3137,7 @@ ArgumentHandler argumentHandler, Sort sort) { return new Target.ForDefaultValue.ReadOnly(instrumentedMethod.getReturnType(), assigner.assign(instrumentedMethod.getReturnType(), - TypeDescription.Generic.OBJECT, + TypeDescription.Generic.OfNonGenericType.ForLoadedType.of(Object.class), Assigner.Typing.DYNAMIC)); } @@ -2957,12 +3191,15 @@ /** * Creates a new offset mapping for the enter type. * - * @param target The represented target type. - * @param enterType The enter type. - * @param enter The represented annotation. - */ - protected ForEnterValue(TypeDescription.Generic target, TypeDescription.Generic enterType, Enter enter) { - this(target, enterType, enter.readOnly(), enter.typing()); + * @param target The represented target type. + * @param enterType The enter type. + * @param annotation The represented annotation. + */ + protected ForEnterValue(TypeDescription.Generic target, TypeDescription.Generic enterType, AnnotationDescription.Loadable annotation) { + this(target, + enterType, + annotation.getValue(Factory.ENTER_READ_ONLY).resolve(Boolean.class), + annotation.getValue(Factory.ENTER_TYPING).load(Enter.class.getClassLoader()).resolve(Assigner.Typing.class)); } /** @@ -3009,6 +3246,25 @@ protected static class Factory implements OffsetMapping.Factory { /** + * A description of the {@link Argument#readOnly()} method. + */ + private static final MethodDescription.InDefinedShape ENTER_READ_ONLY; + + /** + * A description of the {@link Argument#typing()} method. + */ + private static final MethodDescription.InDefinedShape ENTER_TYPING; + + /* + * Resolves annotation properties. + */ + static { + MethodList methods = TypeDescription.ForLoadedType.of(Enter.class).getDeclaredMethods(); + ENTER_READ_ONLY = methods.filter(named("readOnly")).getOnly(); + ENTER_TYPING = methods.filter(named("typing")).getOnly(); + } + + /** * The supplied type of the enter advice. */ private final TypeDefinition enterType; @@ -3047,10 +3303,10 @@ public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { - if (adviceType.isDelegation() && !annotation.load().readOnly()) { + if (adviceType.isDelegation() && !annotation.getValue(ENTER_READ_ONLY).resolve(Boolean.class)) { throw new IllegalStateException("Cannot use writable " + target + " on read-only parameter"); } else { - return new ForEnterValue(target.getType(), enterType.asGenericType(), annotation.load()); + return new ForEnterValue(target.getType(), enterType.asGenericType(), annotation); } } } @@ -3085,12 +3341,15 @@ /** * Creates a new offset mapping for the exit type. * - * @param target The represented target type. - * @param exitType The exit type. - * @param exit The represented annotation. - */ - protected ForExitValue(TypeDescription.Generic target, TypeDescription.Generic exitType, Exit exit) { - this(target, exitType, exit.readOnly(), exit.typing()); + * @param target The represented target type. + * @param exitType The exit type. + * @param annotation The represented annotation. + */ + protected ForExitValue(TypeDescription.Generic target, TypeDescription.Generic exitType, AnnotationDescription.Loadable annotation) { + this(target, + exitType, + annotation.getValue(Factory.EXIT_READ_ONLY).resolve(Boolean.class), + annotation.getValue(Factory.EXIT_TYPING).load(Exit.class.getClassLoader()).resolve(Assigner.Typing.class)); } /** @@ -3137,6 +3396,25 @@ protected static class Factory implements OffsetMapping.Factory { /** + * A description of the {@link Exit#readOnly()} method. + */ + private static final MethodDescription.InDefinedShape EXIT_READ_ONLY; + + /** + * A description of the {@link Exit#typing()} method. + */ + private static final MethodDescription.InDefinedShape EXIT_TYPING; + + /* + * Resolves annotation properties. + */ + static { + MethodList methods = TypeDescription.ForLoadedType.of(Exit.class).getDeclaredMethods(); + EXIT_READ_ONLY = methods.filter(named("readOnly")).getOnly(); + EXIT_TYPING = methods.filter(named("typing")).getOnly(); + } + + /** * The supplied type of the exit advice. */ private final TypeDefinition exitType; @@ -3175,10 +3453,10 @@ public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { - if (adviceType.isDelegation() && !annotation.load().readOnly()) { + if (adviceType.isDelegation() && !annotation.getValue(EXIT_READ_ONLY).resolve(Boolean.class)) { throw new IllegalStateException("Cannot use writable " + target + " on read-only parameter"); } else { - return new ForExitValue(target.getType(), exitType.asGenericType(), annotation.load()); + return new ForExitValue(target.getType(), exitType.asGenericType(), annotation); } } } @@ -3242,6 +3520,14 @@ protected static class Factory implements OffsetMapping.Factory { /** + * A description of the {@link Local#value()} method. + */ + protected static final MethodDescription.InDefinedShape LOCAL_VALUE = TypeDescription.ForLoadedType.of(Local.class) + .getDeclaredMethods() + .filter(named("value")) + .getOnly(); + + /** * The mapping of type names to their type that are available. */ private final Map namedTypes; @@ -3268,7 +3554,7 @@ public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { - String name = annotation.load().value(); + String name = annotation.getValue(LOCAL_VALUE).resolve(String.class); TypeDefinition namedType = namedTypes.get(name); if (namedType == null) { throw new IllegalStateException("Named local variable is unknown: " + name); @@ -3305,8 +3591,10 @@ * @param target The type that the advice method expects for the return value. * @param annotation The annotation being bound. */ - protected ForReturnValue(TypeDescription.Generic target, Return annotation) { - this(target, annotation.readOnly(), annotation.typing()); + protected ForReturnValue(TypeDescription.Generic target, AnnotationDescription.Loadable annotation) { + this(target, + annotation.getValue(Factory.RETURN_READ_ONLY).resolve(Boolean.class), + annotation.getValue(Factory.RETURN_TYPING).load(Return.class.getClassLoader()).resolve(Assigner.Typing.class)); } /** @@ -3359,6 +3647,25 @@ INSTANCE; /** + * A description of the {@link Return#readOnly()} method. + */ + private static final MethodDescription.InDefinedShape RETURN_READ_ONLY; + + /** + * A description of the {@link Return#typing()} method. + */ + private static final MethodDescription.InDefinedShape RETURN_TYPING; + + /* + * Resolves annotation properties. + */ + static { + MethodList methods = TypeDescription.ForLoadedType.of(Return.class).getDeclaredMethods(); + RETURN_READ_ONLY = methods.filter(named("readOnly")).getOnly(); + RETURN_TYPING = methods.filter(named("typing")).getOnly(); + } + + /** * {@inheritDoc} */ public Class getAnnotationType() { @@ -3371,10 +3678,10 @@ public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { - if (adviceType.isDelegation() && !annotation.load().readOnly()) { + if (adviceType.isDelegation() && !annotation.getValue(RETURN_READ_ONLY).resolve(Boolean.class)) { throw new IllegalStateException("Cannot write return value for " + target + " in read-only context"); } else { - return new ForReturnValue(target.getType(), annotation.load()); + return new ForReturnValue(target.getType(), annotation); } } } @@ -3407,8 +3714,10 @@ * @param target The type of parameter that is being accessed. * @param annotation The annotation to bind. */ - protected ForThrowable(TypeDescription.Generic target, Thrown annotation) { - this(target, annotation.readOnly(), annotation.typing()); + protected ForThrowable(TypeDescription.Generic target, AnnotationDescription.Loadable annotation) { + this(target, + annotation.getValue(Factory.THROWN_READ_ONLY).resolve(Boolean.class), + annotation.getValue(Factory.THROWN_TYPING).load(Thrown.class.getClassLoader()).resolve(Assigner.Typing.class)); } /** @@ -3432,17 +3741,17 @@ Assigner assigner, ArgumentHandler argumentHandler, Sort sort) { - StackManipulation readAssignment = assigner.assign(TypeDescription.THROWABLE.asGenericType(), target, typing); + StackManipulation readAssignment = assigner.assign(TypeDescription.ForLoadedType.of(Throwable.class).asGenericType(), target, typing); if (!readAssignment.isValid()) { throw new IllegalStateException("Cannot assign Throwable to " + target); } else if (readOnly) { - return new Target.ForVariable.ReadOnly(TypeDescription.THROWABLE, argumentHandler.thrown(), readAssignment); + return new Target.ForVariable.ReadOnly(TypeDescription.ForLoadedType.of(Throwable.class), argumentHandler.thrown(), readAssignment); } else { - StackManipulation writeAssignment = assigner.assign(target, TypeDescription.THROWABLE.asGenericType(), typing); + StackManipulation writeAssignment = assigner.assign(target, TypeDescription.ForLoadedType.of(Throwable.class).asGenericType(), typing); if (!writeAssignment.isValid()) { throw new IllegalStateException("Cannot assign " + target + " to Throwable"); } - return new Target.ForVariable.ReadWrite(TypeDescription.THROWABLE, argumentHandler.thrown(), readAssignment, writeAssignment); + return new Target.ForVariable.ReadWrite(TypeDescription.ForLoadedType.of(Throwable.class), argumentHandler.thrown(), readAssignment, writeAssignment); } } @@ -3457,12 +3766,32 @@ INSTANCE; /** + * A description of the {@link Thrown#readOnly()} method. + */ + private static final MethodDescription.InDefinedShape THROWN_READ_ONLY; + + /** + * A description of the {@link Thrown#typing()} method. + */ + private static final MethodDescription.InDefinedShape THROWN_TYPING; + + /* + * Resolves annotation properties. + */ + static { + MethodList methods = TypeDescription.ForLoadedType.of(Thrown.class).getDeclaredMethods(); + THROWN_READ_ONLY = methods.filter(named("readOnly")).getOnly(); + THROWN_TYPING = methods.filter(named("typing")).getOnly(); + } + + /** * Resolves an appropriate offset mapping factory for the {@link Thrown} parameter annotation. * * @param adviceMethod The exit advice method, annotated with {@link OnMethodExit}. * @return An appropriate offset mapping factory. */ @SuppressWarnings("unchecked") // In absence of @SafeVarargs + @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "Assuming annotation for exit advice.") protected static OffsetMapping.Factory of(MethodDescription.InDefinedShape adviceMethod) { return adviceMethod.getDeclaredAnnotations() .ofType(OnMethodExit.class) @@ -3484,10 +3813,10 @@ public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { - if (adviceType.isDelegation() && !annotation.load().readOnly()) { + if (adviceType.isDelegation() && !annotation.getValue(THROWN_READ_ONLY).resolve(Boolean.class)) { throw new IllegalStateException("Cannot use writable " + target + " on read-only parameter"); } else { - return new ForThrowable(target.getType(), annotation.load()); + return new ForThrowable(target.getType(), annotation); } } } @@ -3582,7 +3911,7 @@ * @param typeDescription The type to bind. */ public Factory(Class annotationType, TypeDescription typeDescription) { - this(annotationType, ClassConstant.of(typeDescription), TypeDescription.CLASS.asGenericType()); + this(annotationType, ClassConstant.of(typeDescription), TypeDescription.ForLoadedType.of(Class.class).asGenericType()); } /** @@ -3612,11 +3941,11 @@ * Creates a binding for a fixed {@link String}, a primitive value or a method handle or type. * * @param annotationType The annotation type. - * @param value The primitive (wrapper) value, {@link String} value, method handle or type to bind. + * @param value The constant value to bind or {@code null} to bind the parameter type's default value. * @param The annotation type. * @return A factory for creating an offset mapping that binds the supplied value. */ - public static OffsetMapping.Factory of(Class annotationType, Object value) { + public static OffsetMapping.Factory of(Class annotationType, @MaybeNull Object value) { StackManipulation stackManipulation; TypeDescription typeDescription; if (value == null) { @@ -3647,13 +3976,13 @@ typeDescription = TypeDescription.ForLoadedType.of(double.class); } else if (value instanceof String) { stackManipulation = new TextConstant((String) value); - typeDescription = TypeDescription.STRING; + typeDescription = TypeDescription.ForLoadedType.of(String.class); } else if (value instanceof Class) { stackManipulation = ClassConstant.of(TypeDescription.ForLoadedType.of((Class) value)); - typeDescription = TypeDescription.CLASS; + typeDescription = TypeDescription.ForLoadedType.of(Class.class); } else if (value instanceof TypeDescription) { stackManipulation = ClassConstant.of((TypeDescription) value); - typeDescription = TypeDescription.CLASS; + typeDescription = TypeDescription.ForLoadedType.of(Class.class); } else if (value instanceof Enum) { stackManipulation = FieldAccess.forEnumeration(new EnumerationDescription.ForLoadedEnumeration((Enum) value)); typeDescription = TypeDescription.ForLoadedType.of(((Enum) value).getDeclaringClass()); @@ -3865,7 +4194,7 @@ } return new ForStackManipulation(MethodInvocation.invoke(bootstrapMethod).dynamic(methodCandidates.getOnly().getInternalName(), target.getType().asErasure(), - methodCandidates.getOnly().getParameters().asTypeList().asErasures(), + Collections.emptyList(), arguments), target.getType(), target.getType(), Assigner.Typing.STATIC); } } @@ -4113,7 +4442,7 @@ /** * A mapping of all available local variables by their name to their type. */ - protected final TreeMap namedTypes; + protected final SortedMap namedTypes; /** * The enter type or {@code void} if no enter type is defined. @@ -4130,7 +4459,7 @@ */ protected Default(MethodDescription instrumentedMethod, TypeDefinition exitType, - TreeMap namedTypes, + SortedMap namedTypes, TypeDefinition enterType) { this.instrumentedMethod = instrumentedMethod; this.namedTypes = namedTypes; @@ -4230,7 +4559,7 @@ */ protected Simple(MethodDescription instrumentedMethod, TypeDefinition exitType, - TreeMap namedTypes, + SortedMap namedTypes, TypeDefinition enterType) { super(instrumentedMethod, exitType, namedTypes, enterType); } @@ -4250,7 +4579,7 @@ public int variable(int index) { return index < (instrumentedMethod.isStatic() ? 0 : 1) + instrumentedMethod.getParameters().size() ? index - : index + (exitType.represents(void.class) ? 0 : 1) + StackSize.of(namedTypes.values()) + (enterType.represents(void.class) ? 0 : 1); + : index + (exitType.represents(void.class) ? 0 : 1) + namedTypes.size() + (enterType.represents(void.class) ? 0 : 1); } /** @@ -4284,7 +4613,7 @@ */ protected Copying(MethodDescription instrumentedMethod, TypeDefinition exitType, - TreeMap namedTypes, + SortedMap namedTypes, TypeDefinition enterType) { super(instrumentedMethod, exitType, namedTypes, enterType); } @@ -4386,7 +4715,7 @@ /** * A mapping of all available local variables by their name to their type. */ - protected final TreeMap namedTypes; + protected final SortedMap namedTypes; /** * Creates a new argument handler for an enter advice. @@ -4399,7 +4728,7 @@ protected Default(MethodDescription instrumentedMethod, MethodDescription adviceMethod, TypeDefinition exitType, - TreeMap namedTypes) { + SortedMap namedTypes) { this.instrumentedMethod = instrumentedMethod; this.adviceMethod = adviceMethod; this.exitType = exitType; @@ -4455,7 +4784,7 @@ protected ForMethodEnter(MethodDescription instrumentedMethod, MethodDescription adviceMethod, TypeDefinition exitType, - TreeMap namedTypes) { + SortedMap namedTypes) { super(instrumentedMethod, adviceMethod, exitType, namedTypes); } @@ -4513,7 +4842,7 @@ protected ForMethodExit(MethodDescription instrumentedMethod, MethodDescription adviceMethod, TypeDefinition exitType, - TreeMap namedTypes, + SortedMap namedTypes, TypeDefinition enterType, StackSize throwableSize) { super(instrumentedMethod, adviceMethod, exitType, namedTypes); @@ -4572,10 +4901,10 @@ protected ForInstrumentedMethod resolve(MethodDescription instrumentedMethod, TypeDefinition enterType, TypeDefinition exitType, - Map namedTypes) { + SortedMap namedTypes) { return new ForInstrumentedMethod.Default.Simple(instrumentedMethod, exitType, - new TreeMap(namedTypes), + namedTypes, enterType); } }, @@ -4588,10 +4917,10 @@ protected ForInstrumentedMethod resolve(MethodDescription instrumentedMethod, TypeDefinition enterType, TypeDefinition exitType, - Map namedTypes) { + SortedMap namedTypes) { return new ForInstrumentedMethod.Default.Copying(instrumentedMethod, exitType, - new TreeMap(namedTypes), + namedTypes, enterType); } }; @@ -4608,28 +4937,46 @@ protected abstract ForInstrumentedMethod resolve(MethodDescription instrumentedMethod, TypeDefinition enterType, TypeDefinition exitType, - Map namedTypes); + SortedMap namedTypes); } } /** - * A post processor for advice methods that is invoked after advice is executed. + *

+ * A post processor for advice methods that is invoked after advice is executed. A post processor + * is invoked after the instrumented method and only after a regular completion of the method. When + * invoked, the advice method's return value is stored in the local variable array. Upon completion, + * the local variable array must still be intact and the stack must be empty. A frame is added + * subsequently to the post processor's execution, making it feasible to add a jump instruction to the + * end of the method after which no further byte code instructions must be issued. This also applies + * to compound post processors. If a post processor emits a frame as its last instruction, it should + * yield a NOP instruction to avoid that subsequent code starts with a frame. + *

+ *

+ * Important: A post processor is triggered after the suppression handler. Exceptions triggered + * by post processing code will therefore cause those exceptions to be propagated unless the post + * processor configures explicit exception handling. + *

*/ public interface PostProcessor { /** * Resolves this post processor for a given instrumented method. * - * @param instrumentedType The instrumented type. - * @param instrumentedMethod The instrumented method. - * @param assigner The assigner to use. - * @param argumentHandler The argument handler for the instrumented method. + * @param instrumentedType The instrumented type. + * @param instrumentedMethod The instrumented method. + * @param assigner The assigner to use. + * @param argumentHandler The argument handler to use. + * @param stackMapFrameHandler The argument handler for the instrumented method. + * @param exceptionHandler The exception handler that is resolved for the instrumented method. * @return The stack manipulation to apply. */ StackManipulation resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, - ArgumentHandler argumentHandler); + ArgumentHandler argumentHandler, + StackMapFrameHandler.ForPostProcessor stackMapFrameHandler, + StackManipulation exceptionHandler); /** * A factory for creating a {@link PostProcessor}. @@ -4710,7 +5057,9 @@ public StackManipulation resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, - ArgumentHandler argumentHandler) { + ArgumentHandler argumentHandler, + StackMapFrameHandler.ForPostProcessor stackMapFrameHandler, + StackManipulation exceptionHandler) { return StackManipulation.Trivial.INSTANCE; } @@ -4748,10 +5097,17 @@ public StackManipulation resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, - ArgumentHandler argumentHandler) { + ArgumentHandler argumentHandler, + StackMapFrameHandler.ForPostProcessor stackMapFrameHandler, + StackManipulation exceptionHandler) { List stackManipulations = new ArrayList(postProcessors.size()); for (PostProcessor postProcessor : postProcessors) { - stackManipulations.add(postProcessor.resolve(instrumentedType, instrumentedMethod, assigner, argumentHandler)); + stackManipulations.add(postProcessor.resolve(instrumentedType, + instrumentedMethod, + assigner, + argumentHandler, + stackMapFrameHandler, + exceptionHandler)); } return new StackManipulation.Compound(stackManipulations); } @@ -4847,10 +5203,10 @@ boolean exit) { Object[] argument; if (instrumentedMethod.isTypeInitializer()) { - if (!bootstrapMethod.isInvokeBootstrap(Arrays.asList(TypeDescription.STRING, + if (!bootstrapMethod.isInvokeBootstrap(Arrays.asList(TypeDescription.ForLoadedType.of(String.class), TypeDescription.ForLoadedType.of(int.class), - TypeDescription.CLASS, - TypeDescription.STRING))) { + TypeDescription.ForLoadedType.of(Class.class), + TypeDescription.ForLoadedType.of(String.class)))) { throw new IllegalArgumentException(bootstrapMethod + " is not accepting advice bootstrap arguments"); } argument = new Object[]{adviceMethod.getDeclaringType().getName(), @@ -4858,10 +5214,10 @@ Type.getType(instrumentedType.getDescriptor()), instrumentedMethod.getInternalName()}; } else { - if (!bootstrapMethod.isInvokeBootstrap(Arrays.asList(TypeDescription.STRING, + if (!bootstrapMethod.isInvokeBootstrap(Arrays.asList(TypeDescription.ForLoadedType.of(String.class), TypeDescription.ForLoadedType.of(int.class), - TypeDescription.CLASS, - TypeDescription.STRING, + TypeDescription.ForLoadedType.of(Class.class), + TypeDescription.ForLoadedType.of(String.class), JavaType.METHOD_HANDLE.getTypeStub()))) { throw new IllegalArgumentException(bootstrapMethod + " is not accepting advice bootstrap arguments"); } @@ -5330,7 +5686,7 @@ /** * A handler for computing and translating stack map frames. */ - protected interface StackMapFrameHandler { + public interface StackMapFrameHandler { /** * Translates a frame. @@ -5342,7 +5698,12 @@ * @param stackSize The size of the operand stack. * @param stack An array containing the types of the current operand stack. */ - void translateFrame(MethodVisitor methodVisitor, int type, int localVariableLength, Object[] localVariable, int stackSize, Object[] stack); + void translateFrame(MethodVisitor methodVisitor, + int type, + int localVariableLength, + @MaybeNull Object[] localVariable, + int stackSize, + @MaybeNull Object[] stack); /** * Injects a frame indicating the beginning of a return value handler for the currently handled method. @@ -5366,6 +5727,21 @@ void injectCompletionFrame(MethodVisitor methodVisitor); /** + * A stack map frame handler that can be used within a post processor. Emitting frames via this + * handler is the only legal way for a post processor to produce frames. + */ + interface ForPostProcessor { + + /** + * Injects a frame that represents the current state. + * + * @param methodVisitor The method visitor onto which to apply the stack map frame. + * @param stack A list of types that are currently on the stack. + */ + void injectIntermediateFrame(MethodVisitor methodVisitor, List stack); + } + + /** * A stack map frame handler for an instrumented method. */ interface ForInstrumentedMethod extends StackMapFrameHandler { @@ -5418,7 +5794,7 @@ /** * A stack map frame handler for an advice method. */ - interface ForAdvice extends StackMapFrameHandler { + interface ForAdvice extends StackMapFrameHandler, ForPostProcessor { /* marker interface */ } @@ -5459,9 +5835,9 @@ public void translateFrame(MethodVisitor methodVisitor, int type, int localVariableLength, - Object[] localVariable, + @MaybeNull Object[] localVariable, int stackSize, - Object[] stack) { + @MaybeNull Object[] stack) { /* do nothing */ } @@ -5506,6 +5882,13 @@ public void injectPostCompletionFrame(MethodVisitor methodVisitor) { /* do nothing */ } + + /** + * {@inheritDoc} + */ + public void injectIntermediateFrame(MethodVisitor methodVisitor, List stack) { + /* do nothing */ + } } /** @@ -5534,6 +5917,11 @@ protected final List initialTypes; /** + * A list of virtual arguments that are available after the enter advice method is executed. + */ + protected final List latentTypes; + + /** * A list of virtual method arguments that are available before the instrumented method is executed. */ protected final List preMethodTypes; @@ -5559,6 +5947,7 @@ * @param instrumentedType The instrumented type. * @param instrumentedMethod The instrumented method. * @param initialTypes A list of virtual method arguments that are explicitly added before any code execution. + * @param latentTypes A list of virtual arguments that are available after the enter advice method is executed. * @param preMethodTypes A list of virtual method arguments that are available before the instrumented method is executed. * @param postMethodTypes A list of virtual method arguments that are available after the instrumented method has completed. * @param expandFrames {@code true} if the meta data handler is expected to expand its frames. @@ -5566,12 +5955,14 @@ protected Default(TypeDescription instrumentedType, MethodDescription instrumentedMethod, List initialTypes, + List latentTypes, List preMethodTypes, List postMethodTypes, boolean expandFrames) { this.instrumentedType = instrumentedType; this.instrumentedMethod = instrumentedMethod; this.initialTypes = initialTypes; + this.latentTypes = latentTypes; this.preMethodTypes = preMethodTypes; this.postMethodTypes = postMethodTypes; this.expandFrames = expandFrames; @@ -5583,6 +5974,7 @@ * @param instrumentedType The instrumented type. * @param instrumentedMethod The instrumented method. * @param initialTypes A list of virtual method arguments that are explicitly added before any code execution. + * @param latentTypes A list of virtual arguments that are available after the enter advice method is executed. * @param preMethodTypes A list of virtual method arguments that are available before the instrumented method is executed. * @param postMethodTypes A list of virtual method arguments that are available after the instrumented method has completed. * @param exitAdvice {@code true} if the current advice implies exit advice. @@ -5595,6 +5987,7 @@ protected static ForInstrumentedMethod of(TypeDescription instrumentedType, MethodDescription instrumentedMethod, List initialTypes, + List latentTypes, List preMethodTypes, List postMethodTypes, boolean exitAdvice, @@ -5604,22 +5997,24 @@ int readerFlags) { if ((writerFlags & ClassWriter.COMPUTE_FRAMES) != 0 || classFileVersion.isLessThan(ClassFileVersion.JAVA_V6)) { return NoOp.INSTANCE; - } else if (!exitAdvice) { - if (!initialTypes.isEmpty()) { - throw new IllegalStateException("Local parameters are not supported if no exit advice is present"); - } - return new Trivial(instrumentedType, instrumentedMethod, (readerFlags & ClassReader.EXPAND_FRAMES) != 0); + } else if (!exitAdvice && initialTypes.isEmpty()) { + return new Trivial(instrumentedType, + instrumentedMethod, + latentTypes, + (readerFlags & ClassReader.EXPAND_FRAMES) != 0); } else if (copyArguments) { - return new WithPreservedArguments.UsingArgumentCopy(instrumentedType, + return new WithPreservedArguments.WithArgumentCopy(instrumentedType, instrumentedMethod, initialTypes, + latentTypes, preMethodTypes, postMethodTypes, (readerFlags & ClassReader.EXPAND_FRAMES) != 0); } else { - return new WithPreservedArguments.RequiringConsistentShape(instrumentedType, + return new WithPreservedArguments.WithoutArgumentCopy(instrumentedType, instrumentedMethod, initialTypes, + latentTypes, preMethodTypes, postMethodTypes, (readerFlags & ClassReader.EXPAND_FRAMES) != 0, @@ -5631,7 +6026,7 @@ * {@inheritDoc} */ public StackMapFrameHandler.ForAdvice bindEnter(MethodDescription.InDefinedShape adviceMethod) { - return new ForAdvice(adviceMethod, initialTypes, preMethodTypes, TranslationMode.ENTER, instrumentedMethod.isConstructor() + return new ForAdvice(adviceMethod, initialTypes, latentTypes, preMethodTypes, TranslationMode.ENTER, instrumentedMethod.isConstructor() ? Initialization.UNITIALIZED : Initialization.INITIALIZED); } @@ -5664,9 +6059,9 @@ List additionalTypes, int type, int localVariableLength, - Object[] localVariable, + @MaybeNull Object[] localVariable, int stackSize, - Object[] stack) { + @MaybeNull Object[] stack) { switch (type) { case Opcodes.F_SAME: case Opcodes.F_SAME1: @@ -5880,6 +6275,9 @@ * {@inheritDoc} */ protected Object toFrame(TypeDescription typeDescription) { + if (typeDescription.isPrimitive()) { + throw new IllegalArgumentException("Cannot assume primitive uninitialized value: " + typeDescription); + } return Opcodes.UNINITIALIZED_THIS; } }, @@ -5929,12 +6327,14 @@ * * @param instrumentedType The instrumented type. * @param instrumentedMethod The instrumented method. + * @param latentTypes A list of virtual arguments that are available after the enter advice method is executed. * @param expandFrames {@code true} if the meta data handler is expected to expand its frames. */ - protected Trivial(TypeDescription instrumentedType, MethodDescription instrumentedMethod, boolean expandFrames) { + protected Trivial(TypeDescription instrumentedType, MethodDescription instrumentedMethod, List latentTypes, boolean expandFrames) { super(instrumentedType, instrumentedMethod, Collections.emptyList(), + latentTypes, Collections.emptyList(), Collections.emptyList(), expandFrames); @@ -5946,9 +6346,9 @@ public void translateFrame(MethodVisitor methodVisitor, int type, int localVariableLength, - Object[] localVariable, + @MaybeNull Object[] localVariable, int stackSize, - Object[] stack) { + @MaybeNull Object[] stack) { methodVisitor.visitFrame(type, localVariableLength, localVariable, stackSize, stack); } @@ -6018,6 +6418,7 @@ * @param instrumentedType The instrumented type. * @param instrumentedMethod The instrumented method. * @param initialTypes A list of virtual method arguments that are explicitly added before any code execution. + * @param latentTypes A list of virtual arguments that are available after the enter advice method is executed. * @param preMethodTypes A list of virtual method arguments that are available before the instrumented method is executed. * @param postMethodTypes A list of virtual method arguments that are available after the instrumented method has completed. * @param expandFrames {@code true} if the meta data handler is expected to expand its frames. @@ -6026,25 +6427,26 @@ protected WithPreservedArguments(TypeDescription instrumentedType, MethodDescription instrumentedMethod, List initialTypes, + List latentTypes, List preMethodTypes, List postMethodTypes, boolean expandFrames, boolean allowCompactCompletionFrame) { - super(instrumentedType, instrumentedMethod, initialTypes, preMethodTypes, postMethodTypes, expandFrames); + super(instrumentedType, instrumentedMethod, initialTypes, latentTypes, preMethodTypes, postMethodTypes, expandFrames); this.allowCompactCompletionFrame = allowCompactCompletionFrame; } @Override - @SuppressFBWarnings(value = "RC_REF_COMPARISON_BAD_PRACTICE", justification = "ASM models frames by reference comparison.") + @SuppressFBWarnings(value = "RC_REF_COMPARISON_BAD_PRACTICE", justification = "ASM models frames by reference identity.") protected void translateFrame(MethodVisitor methodVisitor, TranslationMode translationMode, MethodDescription methodDescription, List additionalTypes, int type, int localVariableLength, - Object[] localVariable, + @MaybeNull Object[] localVariable, int stackSize, - Object[] stack) { + @MaybeNull Object[] stack) { if (type == Opcodes.F_FULL && localVariableLength > 0 && localVariable[0] != Opcodes.UNINITIALIZED_THIS) { allowCompactCompletionFrame = true; } @@ -6058,6 +6460,7 @@ return new ForAdvice(adviceMethod, CompoundList.of(initialTypes, preMethodTypes, postMethodTypes), Collections.emptyList(), + Collections.emptyList(), TranslationMode.EXIT, Initialization.INITIALIZED); } @@ -6090,7 +6493,7 @@ if (!expandFrames && currentFrameDivergence == 0) { methodVisitor.visitFrame(Opcodes.F_SAME1, EMPTY.length, EMPTY, 1, new Object[]{Type.getInternalName(Throwable.class)}); } else { - injectFullFrame(methodVisitor, Initialization.INITIALIZED, CompoundList.of(initialTypes, preMethodTypes), Collections.singletonList(TypeDescription.THROWABLE)); + injectFullFrame(methodVisitor, Initialization.INITIALIZED, CompoundList.of(initialTypes, preMethodTypes), Collections.singletonList(TypeDescription.ForLoadedType.of(Throwable.class))); } } @@ -6161,7 +6564,7 @@ /** * A stack map frame handler that expects that the original argument frames remain preserved throughout the original invocation. */ - protected static class RequiringConsistentShape extends WithPreservedArguments { + protected static class WithoutArgumentCopy extends WithPreservedArguments { /** * Creates a new stack map frame handler that expects the original frames to be preserved. @@ -6169,19 +6572,21 @@ * @param instrumentedType The instrumented type. * @param instrumentedMethod The instrumented method. * @param initialTypes A list of virtual method arguments that are explicitly added before any code execution. + * @param latentTypes A list of virtual arguments that are available after the enter advice method is executed. * @param preMethodTypes A list of virtual method arguments that are available before the instrumented method is executed. * @param postMethodTypes A list of virtual method arguments that are available after the instrumented method has completed. * @param expandFrames {@code true} if the meta data handler is expected to expand its frames. * @param allowCompactCompletionFrame {@code true} if a completion frame for the method bust be a full frame to reflect an initialization change. */ - protected RequiringConsistentShape(TypeDescription instrumentedType, - MethodDescription instrumentedMethod, - List initialTypes, - List preMethodTypes, - List postMethodTypes, - boolean expandFrames, - boolean allowCompactCompletionFrame) { - super(instrumentedType, instrumentedMethod, initialTypes, preMethodTypes, postMethodTypes, expandFrames, allowCompactCompletionFrame); + protected WithoutArgumentCopy(TypeDescription instrumentedType, + MethodDescription instrumentedMethod, + List initialTypes, + List latentTypes, + List preMethodTypes, + List postMethodTypes, + boolean expandFrames, + boolean allowCompactCompletionFrame) { + super(instrumentedType, instrumentedMethod, initialTypes, latentTypes, preMethodTypes, postMethodTypes, expandFrames, allowCompactCompletionFrame); } /** @@ -6197,9 +6602,9 @@ public void translateFrame(MethodVisitor methodVisitor, int type, int localVariableLength, - Object[] localVariable, + @MaybeNull Object[] localVariable, int stackSize, - Object[] stack) { + @MaybeNull Object[] stack) { translateFrame(methodVisitor, TranslationMode.COPY, instrumentedMethod, @@ -6215,7 +6620,7 @@ /** * A stack map frame handler that expects that an argument copy of the original method arguments was made. */ - protected static class UsingArgumentCopy extends WithPreservedArguments { + protected static class WithArgumentCopy extends WithPreservedArguments { /** * Creates a new stack map frame handler that expects an argument copy. @@ -6223,17 +6628,19 @@ * @param instrumentedType The instrumented type. * @param instrumentedMethod The instrumented method. * @param initialTypes A list of virtual method arguments that are explicitly added before any code execution. + * @param latentTypes The types that are given post execution of a possible enter advice. * @param preMethodTypes A list of virtual method arguments that are available before the instrumented method is executed. * @param postMethodTypes A list of virtual method arguments that are available after the instrumented method has completed. * @param expandFrames {@code true} if the meta data handler is expected to expand its frames. */ - protected UsingArgumentCopy(TypeDescription instrumentedType, - MethodDescription instrumentedMethod, - List initialTypes, - List preMethodTypes, - List postMethodTypes, - boolean expandFrames) { - super(instrumentedType, instrumentedMethod, initialTypes, preMethodTypes, postMethodTypes, expandFrames, true); + protected WithArgumentCopy(TypeDescription instrumentedType, + MethodDescription instrumentedMethod, + List initialTypes, + List latentTypes, + List preMethodTypes, + List postMethodTypes, + boolean expandFrames) { + super(instrumentedType, instrumentedMethod, initialTypes, latentTypes, preMethodTypes, postMethodTypes, expandFrames, true); } /** @@ -6290,13 +6697,13 @@ /** * {@inheritDoc} */ - @SuppressFBWarnings(value = "RC_REF_COMPARISON_BAD_PRACTICE", justification = "Reference equality is required by ASM") + @SuppressFBWarnings(value = "RC_REF_COMPARISON_BAD_PRACTICE", justification = "ASM models frames by reference identity.") public void translateFrame(MethodVisitor methodVisitor, int type, int localVariableLength, - Object[] localVariable, + @MaybeNull Object[] localVariable, int stackSize, - Object[] stack) { + @MaybeNull Object[] stack) { switch (type) { case Opcodes.F_SAME: case Opcodes.F_SAME1: @@ -6336,7 +6743,9 @@ for (TypeDescription typeDescription : preMethodTypes) { translated[index++] = Initialization.INITIALIZED.toFrame(typeDescription); } - System.arraycopy(localVariable, 0, translated, index, localVariableLength); + if (localVariableLength > 0) { + System.arraycopy(localVariable, 0, translated, index, localVariableLength); + } localVariableLength = translated.length; localVariable = translated; currentFrameDivergence = localVariableLength; @@ -6365,6 +6774,11 @@ protected final List startTypes; /** + * The types that are given post execution of the advice. + */ + private final List intermediateTypes; + + /** * The types provided after execution of the advice code. */ protected final List endTypes; @@ -6380,25 +6794,34 @@ private final Initialization initialization; /** + * {@code true} if an intermediate frame was yielded. + */ + private boolean intermedate; + + /** * Creates a new meta data handler for an advice method. * - * @param adviceMethod The method description for which frames are translated. - * @param startTypes The types provided before execution of the advice code. - * @param endTypes The types provided after execution of the advice code. - * @param translationMode The translation mode to apply for this advice method. Should be - * either {@link TranslationMode#ENTER} or {@link TranslationMode#EXIT}. - * @param initialization The initialization to apply when resolving a reference to the instance on which a non-static method is invoked. + * @param adviceMethod The method description for which frames are translated. + * @param startTypes The types provided before execution of the advice code. + * @param intermediateTypes The types that are given post execution of the advice. + * @param endTypes The types provided after execution of the advice code. + * @param translationMode The translation mode to apply for this advice method. Should be + * either {@link TranslationMode#ENTER} or {@link TranslationMode#EXIT}. + * @param initialization The initialization to apply when resolving a reference to the instance on which a non-static method is invoked. */ protected ForAdvice(MethodDescription.InDefinedShape adviceMethod, List startTypes, + List intermediateTypes, List endTypes, TranslationMode translationMode, Initialization initialization) { this.adviceMethod = adviceMethod; this.startTypes = startTypes; + this.intermediateTypes = intermediateTypes; this.endTypes = endTypes; this.translationMode = translationMode; this.initialization = initialization; + intermedate = false; } /** @@ -6407,9 +6830,9 @@ public void translateFrame(MethodVisitor methodVisitor, int type, int localVariableLength, - Object[] localVariable, + @MaybeNull Object[] localVariable, int stackSize, - Object[] stack) { + @MaybeNull Object[] stack) { Default.this.translateFrame(methodVisitor, translationMode, adviceMethod, @@ -6449,7 +6872,7 @@ if (!expandFrames && currentFrameDivergence == 0) { methodVisitor.visitFrame(Opcodes.F_SAME1, EMPTY.length, EMPTY, 1, new Object[]{Type.getInternalName(Throwable.class)}); } else { - injectFullFrame(methodVisitor, initialization, startTypes, Collections.singletonList(TypeDescription.THROWABLE)); + injectFullFrame(methodVisitor, initialization, startTypes, Collections.singletonList(TypeDescription.ForLoadedType.of(Throwable.class))); } } @@ -6459,8 +6882,8 @@ public void injectCompletionFrame(MethodVisitor methodVisitor) { if (expandFrames) { injectFullFrame(methodVisitor, initialization, CompoundList.of(startTypes, endTypes), Collections.emptyList()); - } else if (currentFrameDivergence == 0 && endTypes.size() < 4) { - if (endTypes.isEmpty()) { + } else if (currentFrameDivergence == 0 && (intermedate || endTypes.size() < 4)) { + if (intermedate || endTypes.isEmpty()) { methodVisitor.visitFrame(Opcodes.F_SAME, EMPTY.length, EMPTY, EMPTY.length, EMPTY); } else { Object[] local = new Object[endTypes.size()]; @@ -6472,26 +6895,65 @@ } } else if (currentFrameDivergence < 3 && endTypes.isEmpty()) { methodVisitor.visitFrame(Opcodes.F_CHOP, currentFrameDivergence, EMPTY, EMPTY.length, EMPTY); + currentFrameDivergence = 0; } else { injectFullFrame(methodVisitor, initialization, CompoundList.of(startTypes, endTypes), Collections.emptyList()); } } - } - } - } - - /** - * An exception handler is responsible for providing byte code for handling an exception thrown from a suppressing advice method. - */ - public interface ExceptionHandler { - /** - * Resolves a stack manipulation to apply. - * - * @param instrumentedMethod The instrumented method. - * @param instrumentedType The instrumented type. - * @return The stack manipulation to use. - */ + /** + * {@inheritDoc} + */ + public void injectIntermediateFrame(MethodVisitor methodVisitor, List stack) { + if (expandFrames) { + injectFullFrame(methodVisitor, initialization, CompoundList.of(startTypes, intermediateTypes), stack); + } else if (intermedate && stack.size() < 2) { + if (stack.isEmpty()) { + methodVisitor.visitFrame(Opcodes.F_SAME, EMPTY.length, EMPTY, EMPTY.length, EMPTY); + } else { + methodVisitor.visitFrame(Opcodes.F_SAME1, EMPTY.length, EMPTY, 1, new Object[]{Initialization.INITIALIZED.toFrame(stack.get(0))}); + } + } else if (currentFrameDivergence == 0 + && intermediateTypes.size() < 4 + && (stack.isEmpty() || stack.size() < 2 && intermediateTypes.isEmpty())) { + if (intermediateTypes.isEmpty()) { + if (stack.isEmpty()) { + methodVisitor.visitFrame(Opcodes.F_SAME, EMPTY.length, EMPTY, EMPTY.length, EMPTY); + } else { + methodVisitor.visitFrame(Opcodes.F_SAME1, EMPTY.length, EMPTY, 1, new Object[]{Initialization.INITIALIZED.toFrame(stack.get(0))}); + } + } else { + Object[] local = new Object[intermediateTypes.size()]; + int index = 0; + for (TypeDescription typeDescription : intermediateTypes) { + local[index++] = Initialization.INITIALIZED.toFrame(typeDescription); + } + methodVisitor.visitFrame(Opcodes.F_APPEND, local.length, local, EMPTY.length, EMPTY); + } + } else if (currentFrameDivergence < 3 && intermediateTypes.isEmpty() && stack.isEmpty()) { + methodVisitor.visitFrame(Opcodes.F_CHOP, currentFrameDivergence, EMPTY, EMPTY.length, EMPTY); + } else { + injectFullFrame(methodVisitor, initialization, CompoundList.of(startTypes, intermediateTypes), stack); + } + currentFrameDivergence = intermediateTypes.size() - endTypes.size(); + intermedate = true; + } + } + } + } + + /** + * An exception handler is responsible for providing byte code for handling an exception thrown from a suppressing advice method. + */ + public interface ExceptionHandler { + + /** + * Resolves a stack manipulation to apply. + * + * @param instrumentedMethod The instrumented method. + * @param instrumentedType The instrumented type. + * @return The stack manipulation to use. + */ StackManipulation resolve(MethodDescription instrumentedMethod, TypeDescription instrumentedType); /** @@ -6573,11 +7035,13 @@ /** * Indicates that a method does not represent advice and does not need to be visited. */ + @AlwaysNull MethodVisitor IGNORE_METHOD = null; /** * Expresses that an annotation should not be visited. */ + @AlwaysNull AnnotationVisitor IGNORE_ANNOTATION = null; /** @@ -6623,7 +7087,7 @@ * @return This dispatcher as a dispatcher for entering a method. */ Resolved.ForMethodEnter asMethodEnter(List> userFactories, - ClassReader classReader, + @MaybeNull ClassReader classReader, Unresolved methodExit, PostProcessor.Factory postProcessorFactory); @@ -6637,7 +7101,7 @@ * @return This dispatcher as a dispatcher for exiting a method. */ Resolved.ForMethodExit asMethodExit(List> userFactories, - ClassReader classReader, + @MaybeNull ClassReader classReader, Unresolved methodEnter, PostProcessor.Factory postProcessorFactory); } @@ -7297,6 +7761,13 @@ interface Resolved extends Dispatcher { /** + * Returns the named types defined by this advice. + * + * @return The named types defined by this advice. + */ + Map getNamedTypes(); + + /** * Binds this dispatcher for resolution to a specific method. * * @param instrumentedType The instrumented type. @@ -7335,11 +7806,11 @@ boolean isPrependLineNumber(); /** - * Returns the named types declared by this enter advice. + * Returns the actual advice type, even if it is not required post advice processing. * - * @return The named types declared by this enter advice. + * @return The actual advice type, even if it is not required post advice processing. */ - Map getNamedTypes(); + TypeDefinition getActualAdviceType(); } /** @@ -7499,7 +7970,7 @@ * {@inheritDoc} */ public TypeDescription getAdviceType() { - return TypeDescription.VOID; + return TypeDescription.ForLoadedType.of(void.class); } /** @@ -7512,6 +7983,13 @@ /** * {@inheritDoc} */ + public TypeDefinition getActualAdviceType() { + return TypeDescription.ForLoadedType.of(void.class); + } + + /** + * {@inheritDoc} + */ public Map getNamedTypes() { return Collections.emptyMap(); } @@ -7534,7 +8012,7 @@ * {@inheritDoc} */ public Resolved.ForMethodEnter asMethodEnter(List> userFactories, - ClassReader classReader, + @MaybeNull ClassReader classReader, Unresolved methodExit, PostProcessor.Factory postProcessorFactory) { return this; @@ -7544,7 +8022,7 @@ * {@inheritDoc} */ public Resolved.ForMethodExit asMethodExit(List> userFactories, - ClassReader classReader, + @MaybeNull ClassReader classReader, Unresolved methodEnter, PostProcessor.Factory postProcessorFactory) { return this; @@ -7612,11 +8090,14 @@ protected Inlining(MethodDescription.InDefinedShape adviceMethod) { this.adviceMethod = adviceMethod; namedTypes = new HashMap(); - for (ParameterDescription parameterDescription : adviceMethod.getParameters().filter(isAnnotatedWith(Local.class))) { - String name = parameterDescription.getDeclaredAnnotations().ofType(Local.class).load().value(); - TypeDefinition previous = namedTypes.put(name, parameterDescription.getType()); - if (previous != null && !previous.equals(parameterDescription.getType())) { - throw new IllegalStateException("Local variable for " + name + " is defined with inconsistent types"); + for (ParameterDescription parameterDescription : adviceMethod.getParameters()) { + AnnotationDescription.Loadable annotationDescription = parameterDescription.getDeclaredAnnotations().ofType(Local.class); + if (annotationDescription != null) { + String name = annotationDescription.getValue(OffsetMapping.ForLocalValue.Factory.LOCAL_VALUE).resolve(String.class); + TypeDefinition previous = namedTypes.put(name, parameterDescription.getType()); + if (previous != null && !previous.equals(parameterDescription.getType())) { + throw new IllegalStateException("Local variable for " + name + " is defined with inconsistent types"); + } } } } @@ -7653,9 +8134,12 @@ * {@inheritDoc} */ public Dispatcher.Resolved.ForMethodEnter asMethodEnter(List> userFactories, - ClassReader classReader, + @MaybeNull ClassReader classReader, Unresolved methodExit, PostProcessor.Factory postProcessorFactory) { + if (classReader == null) { + throw new IllegalStateException("Class reader not expected null"); + } return Resolved.ForMethodEnter.of(adviceMethod, postProcessorFactory.make(adviceMethod, false), namedTypes, @@ -7669,19 +8153,20 @@ * {@inheritDoc} */ public Dispatcher.Resolved.ForMethodExit asMethodExit(List> userFactories, - ClassReader classReader, + @MaybeNull ClassReader classReader, Unresolved methodEnter, PostProcessor.Factory postProcessorFactory) { - Map namedTypes = methodEnter.getNamedTypes(); + Map namedTypes = new HashMap(methodEnter.getNamedTypes()), uninitializedNamedTypes = new HashMap(); for (Map.Entry entry : this.namedTypes.entrySet()) { - TypeDefinition typeDefinition = this.namedTypes.get(entry.getKey()); - if (typeDefinition == null) { - throw new IllegalStateException(adviceMethod + " attempts use of undeclared local variable " + entry.getKey()); - } else if (!typeDefinition.equals(entry.getValue())) { - throw new IllegalStateException(adviceMethod + " does not read variable " + entry.getKey() + " as " + typeDefinition); + TypeDefinition typeDefinition = namedTypes.get(entry.getKey()), uninitializedTypeDefinition = uninitializedNamedTypes.get(entry.getKey()); + if (typeDefinition == null && uninitializedTypeDefinition == null) { + namedTypes.put(entry.getKey(), entry.getValue()); + uninitializedNamedTypes.put(entry.getKey(), entry.getValue()); + } else if (!(typeDefinition == null ? uninitializedTypeDefinition : typeDefinition).equals(entry.getValue())) { + throw new IllegalStateException("Local variable for " + entry.getKey() + " is defined with inconsistent types"); } } - return Resolved.ForMethodExit.of(adviceMethod, postProcessorFactory.make(adviceMethod, true), namedTypes, userFactories, classReader, methodEnter.getAdviceType()); + return Resolved.ForMethodExit.of(adviceMethod, postProcessorFactory.make(adviceMethod, true), namedTypes, uninitializedNamedTypes, userFactories, classReader, methodEnter.getAdviceType()); } /** @@ -7735,6 +8220,7 @@ * @param instrumentedMethod A description of the instrumented method. * @param suppressionHandler A bound suppression handler that is used for suppressing exceptions of this advice method. * @param relocationHandler A bound relocation handler that is responsible for considering a non-standard control flow. + * @param exceptionHandler The exception handler that is resolved for the instrumented method. * @return A method visitor for visiting the advice method's byte code. */ protected abstract MethodVisitor apply(MethodVisitor methodVisitor, @@ -7746,7 +8232,8 @@ TypeDescription instrumentedType, MethodDescription instrumentedMethod, SuppressionHandler.Bound suppressionHandler, - RelocationHandler.Bound relocationHandler); + RelocationHandler.Bound relocationHandler, + StackManipulation exceptionHandler); /** * A bound advice method that copies the code by first extracting the exception table and later appending the @@ -7805,6 +8292,11 @@ protected final RelocationHandler.Bound relocationHandler; /** + * The exception handler that is resolved for the instrumented method. + */ + protected final StackManipulation exceptionHandler; + + /** * A class reader for parsing the class file containing the represented advice method. */ protected final ClassReader classReader; @@ -7827,6 +8319,7 @@ * @param stackMapFrameHandler A handler for translating and injecting stack map frames. * @param suppressionHandler A bound suppression handler that is used for suppressing exceptions of this advice method. * @param relocationHandler A bound relocation handler that is responsible for considering a non-standard control flow. + * @param exceptionHandler The exception handler that is resolved for the instrumented method. * @param classReader A class reader for parsing the class file containing the represented advice method. */ protected AdviceMethodInliner(TypeDescription instrumentedType, @@ -7839,6 +8332,7 @@ StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler, SuppressionHandler.Bound suppressionHandler, RelocationHandler.Bound relocationHandler, + StackManipulation exceptionHandler, ClassReader classReader) { super(OpenedClassReader.ASM_API); this.instrumentedType = instrumentedType; @@ -7850,8 +8344,9 @@ this.methodSizeHandler = methodSizeHandler; this.stackMapFrameHandler = stackMapFrameHandler; this.suppressionHandler = suppressionHandler; - this.classReader = classReader; this.relocationHandler = relocationHandler; + this.exceptionHandler = exceptionHandler; + this.classReader = classReader; labels = new ArrayList