net.bytebuddy.agent.ByteBuddyAgent Maven / Gradle / Ivy
Show all versions of byte-buddy-agent Show documentation
/*
* 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;
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.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.CodeSource;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.util.*;
import java.util.jar.*;
/**
*
* The Byte Buddy agent provides a JVM {@link java.lang.instrument.Instrumentation} in order to allow Byte Buddy the
* redefinition of already loaded classes. An agent must normally be specified via the command line via the
* {@code javaagent} parameter. As an argument to this parameter, one must specify the location of this agent's jar
* file such as for example in
*
*
*
* java -javaagent:byte-buddy-agent.jar -jar app.jar
*
*
*
* Note: The runtime installation of a Java agent is not possible on all JVMs. See the documentation for
* {@link ByteBuddyAgent#install()} for details on JVMs that are supported out of the box.
*
*
* 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 {@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 its own code location for a self-attachment.
*/
public static final String LATENT_RESOLVE = "net.bytebuddy.agent.latent";
/**
* The manifest property specifying the agent class.
*/
private static final String AGENT_CLASS_PROPERTY = "Agent-Class";
/**
* The manifest property specifying the can redefine property.
*/
private static final String CAN_REDEFINE_CLASSES_PROPERTY = "Can-Redefine-Classes";
/**
* The manifest property specifying the can retransform property.
*/
private static final String CAN_RETRANSFORM_CLASSES_PROPERTY = "Can-Retransform-Classes";
/**
* The manifest property specifying the can set native method prefix property.
*/
private static final String CAN_SET_NATIVE_METHOD_PREFIX = "Can-Set-Native-Method-Prefix";
/**
* The manifest property value for the manifest version.
*/
private static final String MANIFEST_VERSION_VALUE = "1.0";
/**
* 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;
/**
* The naming prefix of all artifacts for an attacher jar.
*/
private static final String ATTACHER_FILE_NAME = "byteBuddyAttacher";
/**
* The file extension for a class file.
*/
private static final String CLASS_FILE_EXTENSION = ".class";
/**
* The file extension for a jar file.
*/
private static final String JAR_FILE_EXTENSION = ".jar";
/**
* The class path argument to specify the class path elements.
*/
private static final String CLASS_PATH_ARGUMENT = "-cp";
/**
* The character that is used to mark the beginning of the argument to the agent.
*/
private static final String AGENT_ARGUMENT_SEPARATOR = "=";
/**
* The Java property denoting the Java home directory.
*/
private static final String JAVA_HOME = "java.home";
/**
* The Java property denoting the operating system name.
*/
private static final String OS_NAME = "os.name";
/**
* Represents the {@code file} URL protocol.
*/
private static final String FILE_PROTOCOL = "file";
/**
* The attachment type evaluator to be used for determining if an attachment requires an external process.
*/
private static final AttachmentTypeEvaluator ATTACHMENT_TYPE_EVALUATOR = doPrivileged(AttachmentTypeEvaluator.InstallationAction.INSTANCE);
/**
* The agent provides only {@code static} utility methods and should not be instantiated.
*/
private ByteBuddyAgent() {
throw new UnsupportedOperationException("This class is a utility class and not supposed to be instantiated");
}
/**
* A proxy for {@code java.security.AccessController#doPrivileged} that is activated if available.
*
* @param action The action to execute from a privileged context.
* @param The type of the action's resolved value.
* @return The action's resolved value.
*/
@SuppressWarnings("unchecked")
private static T doPrivileged(PrivilegedAction action) {
try {
return (T) Class.forName("java.security.AccessController")
.getMethod("doPrivileged", PrivilegedAction.class)
.invoke(null, action);
} catch (ClassNotFoundException ignored) {
return action.run();
} catch (InvocationTargetException exception) {
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) {
throw new IllegalStateException("Failed to resolve well-known access controller method", exception);
}
}
/**
*
* Looks up the {@link java.lang.instrument.Instrumentation} instance of an installed Byte Buddy agent. Note that
* this method implies reflective lookup and reflective invocation such that the returned value should be cached
* rather than calling this method several times.
*
*
* Note: This method throws an {@link java.lang.IllegalStateException} If the Byte Buddy agent is not
* properly installed.
*
*
* @return The {@link java.lang.instrument.Instrumentation} instance which is provided by an installed
* Byte Buddy agent.
*/
public static Instrumentation getInstrumentation() {
Instrumentation instrumentation = doGetInstrumentation();
if (instrumentation == null) {
throw new IllegalStateException("The Byte Buddy agent is not initialized or unavailable");
} else {
return instrumentation;
}
}
/**
*
* Attaches the given agent Jar on the target process which must be a virtual machine process. The default attachment provider
* is used for applying the attachment. This operation blocks until the attachment is complete. If the current VM does not supply
* any known form of attachment to a remote VM, an {@link IllegalStateException} is thrown. The agent is not provided an argument.
*
*
* Important: It is only possible to attach to processes that are executed by the same operating system user.
*
*
* @param agentJar The agent jar file.
* @param processId The target process id.
*/
public static void attach(File agentJar, String processId) {
attach(agentJar, processId, WITHOUT_ARGUMENT);
}
/**
*
* Attaches the given agent Jar on the target process which must be a virtual machine process. The default attachment provider
* is used for applying the attachment. This operation blocks until the attachment is complete. If the current VM does not supply
* any known form of attachment to a remote VM, an {@link IllegalStateException} is thrown.
*
*
* Important: It is only possible to attach to processes that are executed by the same operating system user.
*
*
* @param agentJar The agent jar file.
* @param processId The target process id.
* @param argument The argument to provide to the agent.
*/
public static void attach(File agentJar, String processId, @MaybeNull String argument) {
attach(agentJar, processId, argument, AttachmentProvider.DEFAULT);
}
/**
*
* Attaches the given agent Jar on the target process which must be a virtual machine process. This operation blocks until the
* attachment is complete. The agent is not provided an argument.
*
*
* Important: It is only possible to attach to processes that are executed by the same operating system user.
*
*
* @param agentJar The agent jar file.
* @param processId The target process id.
* @param attachmentProvider The attachment provider to use.
*/
public static void attach(File agentJar, String processId, AttachmentProvider attachmentProvider) {
attach(agentJar, processId, WITHOUT_ARGUMENT, attachmentProvider);
}
/**
*
* Attaches the given agent Jar on the target process which must be a virtual machine process. This operation blocks until the
* attachment is complete.
*
*
* Important: It is only possible to attach to processes that are executed by the same operating system user.
*
*
* @param agentJar The agent jar file.
* @param processId The target process id.
* @param argument The argument to provide to the agent.
* @param attachmentProvider The attachment provider to use.
*/
public static void attach(File agentJar, String processId, @MaybeNull String argument, AttachmentProvider attachmentProvider) {
install(attachmentProvider, processId, argument, new AgentProvider.ForExistingAgent(agentJar), false);
}
/**
*
* Attaches the given agent Jar on the target process which must be a virtual machine process. The default attachment provider
* is used for applying the attachment. This operation blocks until the attachment is complete. If the current VM does not supply
* any known form of attachment to a remote VM, an {@link IllegalStateException} is thrown. The agent is not provided an argument.
*
*
* Important: It is only possible to attach to processes that are executed by the same operating system user.
*
*
* @param agentJar The agent jar file.
* @param processProvider A provider of the target process id.
*/
public static void attach(File agentJar, ProcessProvider processProvider) {
attach(agentJar, processProvider, WITHOUT_ARGUMENT);
}
/**
*
* Attaches the given agent Jar on the target process which must be a virtual machine process. The default attachment provider
* is used for applying the attachment. This operation blocks until the attachment is complete. If the current VM does not supply
* any known form of attachment to a remote VM, an {@link IllegalStateException} is thrown.
*
*
* Important: It is only possible to attach to processes that are executed by the same operating system user.
*
*
* @param agentJar The agent jar file.
* @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, @MaybeNull String argument) {
attach(agentJar, processProvider, argument, AttachmentProvider.DEFAULT);
}
/**
*
* Attaches the given agent Jar on the target process which must be a virtual machine process. This operation blocks until the
* attachment is complete. The agent is not provided an argument.
*
*
* Important: It is only possible to attach to processes that are executed by the same operating system user.
*
*
* @param agentJar The agent jar file.
* @param processProvider A provider of the target process id.
* @param attachmentProvider The attachment provider to use.
*/
public static void attach(File agentJar, ProcessProvider processProvider, AttachmentProvider attachmentProvider) {
attach(agentJar, processProvider, WITHOUT_ARGUMENT, attachmentProvider);
}
/**
*
* Attaches the given agent Jar on the target process which must be a virtual machine process. This operation blocks until the
* attachment is complete.
*
*
* Important: It is only possible to attach to processes that are executed by the same operating system user.
*
*
* @param agentJar The agent jar file.
* @param processProvider A provider of the target process id.
* @param argument The argument to provide to the agent.
* @param attachmentProvider The attachment provider to use.
*/
public static void attach(File agentJar, ProcessProvider processProvider, @MaybeNull String argument, AttachmentProvider attachmentProvider) {
install(attachmentProvider, processProvider.resolve(), argument, new AgentProvider.ForExistingAgent(agentJar), false);
}
/**
*
* Attaches the given agent library on the target process which must be a virtual machine process. The default attachment provider
* is used for applying the attachment. This operation blocks until the attachment is complete. If the current VM does not supply
* any known form of attachment to a remote VM, an {@link IllegalStateException} is thrown. The agent is not provided an argument.
*
*
* Important: It is only possible to attach to processes that are executed by the same operating system user.
*
*
* @param agentLibrary The agent jar file.
* @param processId The target process id.
*/
public static void attachNative(File agentLibrary, String processId) {
attachNative(agentLibrary, processId, WITHOUT_ARGUMENT);
}
/**
*
* Attaches the given agent library on the target process which must be a virtual machine process. The default attachment provider
* is used for applying the attachment. This operation blocks until the attachment is complete. If the current VM does not supply
* any known form of attachment to a remote VM, an {@link IllegalStateException} is thrown.
*
*
* Important: It is only possible to attach to processes that are executed by the same operating system user.
*
*
* @param agentLibrary The agent library.
* @param processId The target process id.
* @param argument The argument to provide to the agent.
*/
public static void attachNative(File agentLibrary, String processId, @MaybeNull String argument) {
attachNative(agentLibrary, processId, argument, AttachmentProvider.DEFAULT);
}
/**
*
* Attaches the given agent library on the target process which must be a virtual machine process. This operation blocks until the
* attachment is complete. The agent is not provided an argument.
*
*
* Important: It is only possible to attach to processes that are executed by the same operating system user.
*
*
* @param agentLibrary The agent library.
* @param processId The target process id.
* @param attachmentProvider The attachment provider to use.
*/
public static void attachNative(File agentLibrary, String processId, AttachmentProvider attachmentProvider) {
attachNative(agentLibrary, processId, WITHOUT_ARGUMENT, attachmentProvider);
}
/**
*
* Attaches the given agent library on the target process which must be a virtual machine process. This operation blocks until the
* attachment is complete.
*
*
* Important: It is only possible to attach to processes that are executed by the same operating system user.
*
*
* @param agentLibrary The agent library.
* @param processId The target process id.
* @param argument The argument to provide to the agent.
* @param attachmentProvider The attachment provider to use.
*/
public static void attachNative(File agentLibrary, String processId, @MaybeNull String argument, AttachmentProvider attachmentProvider) {
install(attachmentProvider, processId, argument, new AgentProvider.ForExistingAgent(agentLibrary), true);
}
/**
*
* Attaches the given agent library on the target process which must be a virtual machine process. The default attachment provider
* is used for applying the attachment. This operation blocks until the attachment is complete. If the current VM does not supply
* any known form of attachment to a remote VM, an {@link IllegalStateException} is thrown. The agent is not provided an argument.
*
*
* Important: It is only possible to attach to processes that are executed by the same operating system user.
*
*
* @param agentLibrary The agent library.
* @param processProvider A provider of the target process id.
*/
public static void attachNative(File agentLibrary, ProcessProvider processProvider) {
attachNative(agentLibrary, processProvider, WITHOUT_ARGUMENT);
}
/**
*
* Attaches the given agent library on the target process which must be a virtual machine process. The default attachment provider
* is used for applying the attachment. This operation blocks until the attachment is complete. If the current VM does not supply
* any known form of attachment to a remote VM, an {@link IllegalStateException} is thrown.
*
*
* Important: It is only possible to attach to processes that are executed by the same operating system user.
*
*
* @param agentLibrary The agent library.
* @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, @MaybeNull String argument) {
attachNative(agentLibrary, processProvider, argument, AttachmentProvider.DEFAULT);
}
/**
*
* Attaches the given agent library on the target process which must be a virtual machine process. This operation blocks until the
* attachment is complete. The agent is not provided an argument.
*
*
* Important: It is only possible to attach to processes that are executed by the same operating system user.
*
*
* @param agentLibrary The agent library.
* @param processProvider A provider of the target process id.
* @param attachmentProvider The attachment provider to use.
*/
public static void attachNative(File agentLibrary, ProcessProvider processProvider, AttachmentProvider attachmentProvider) {
attachNative(agentLibrary, processProvider, WITHOUT_ARGUMENT, attachmentProvider);
}
/**
*
* Attaches the given agent library on the target process which must be a virtual machine process. This operation blocks until the
* attachment is complete.
*
*
* Important: It is only possible to attach to processes that are executed by the same operating system user.
*
*
* @param agentLibrary The agent library.
* @param processProvider A provider of the target process id.
* @param argument The argument to provide to the agent.
* @param attachmentProvider The attachment provider to use.
*/
public static void attachNative(File agentLibrary, ProcessProvider processProvider, @MaybeNull String argument, AttachmentProvider attachmentProvider) {
install(attachmentProvider, processProvider.resolve(), argument, new AgentProvider.ForExistingAgent(agentLibrary), true);
}
/**
*
* Installs an agent on the currently running Java virtual machine. Unfortunately, this does
* not always work. The runtime installation of a Java agent is supported for:
*
*
* - JVM version 9+: For Java VM of at least version 9, the attachment API was moved
* into a module and the runtime installation is possible if the {@code jdk.attach} module is
* available to Byte Buddy which is typically only available for VMs shipped with a JDK.
* - OpenJDK / Oracle JDK / IBM J9 versions 8-: The installation for HotSpot is only
* possible when bundled with a JDK and requires a {@code tools.jar} bundled with the VM which
* is typically only available for JDK-versions of the JVM.
* - When running Linux and including the optional junixsocket-native-common depedency,
* Byte Buddy emulates a Unix socket connection to attach to the target VM.
*
*
* If an agent cannot be installed, an {@link IllegalStateException} is thrown.
*
*
* Important: This is a rather computation-heavy operation. Therefore, this operation is
* not repeated after an agent was successfully installed for the first time. Instead, the previous
* instrumentation instance is returned. However, invoking this method requires synchronization
* such that subsequently to an installation, {@link ByteBuddyAgent#getInstrumentation()} should
* be invoked instead.
*
*
* @return An instrumentation instance representing the currently running JVM.
*/
public static Instrumentation install() {
return install(AttachmentProvider.DEFAULT);
}
/**
*
* Installs an agent on the currently running Java virtual machine using the supplied
* attachment provider.
*
*
* If an agent cannot be installed, an {@link IllegalStateException} is thrown.
*
*
* Important: This is a rather computation-heavy operation. Therefore, this operation is
* not repeated after an agent was successfully installed for the first time. Instead, the previous
* instrumentation instance is returned. However, invoking this method requires synchronization
* such that subsequently to an installation, {@link ByteBuddyAgent#getInstrumentation()} should
* be invoked instead.
*
*
* @param attachmentProvider The attachment provider to use for the installation.
* @return An instrumentation instance representing the currently running JVM.
*/
public static Instrumentation install(AttachmentProvider attachmentProvider) {
return install(attachmentProvider, ProcessProvider.ForCurrentVm.INSTANCE);
}
/**
*
* Installs an agent on the Java virtual machine resolved by the process provider. Unfortunately, this does
* not always work. The runtime installation of a Java agent is supported for:
*
*
* - JVM version 9+: For Java VM of at least version 9, the attachment API was moved
* into a module and the runtime installation is possible if the {@code jdk.attach} module is
* available to Byte Buddy which is typically only available for VMs shipped with a JDK.
* - OpenJDK / Oracle JDK / IBM J9 versions 8-: The installation for HotSpot is only
* possible when bundled with a JDK and requires a {@code tools.jar} bundled with the VM which
* is typically only available for JDK-versions of the JVM.
* - When running Linux and including the optional junixsocket-native-common depedency,
* Byte Buddy emulates a Unix socket connection to attach to the target VM.
*
*
* If an agent cannot be installed, an {@link IllegalStateException} is thrown.
*
*
* @param processProvider The provider for the current JVM's process id.
* @return An instrumentation instance representing the currently running JVM.
*/
public static Instrumentation install(ProcessProvider processProvider) {
return install(AttachmentProvider.DEFAULT, processProvider);
}
/**
*
* Installs an agent on the currently running Java virtual machine using the supplied
* attachment provider and process provider.
*
*
* If an agent cannot be installed, an {@link IllegalStateException} is thrown.
*
*
* @param attachmentProvider The attachment provider to use for the installation.
* @param processProvider The provider for the current JVM's process id.
* @return An instrumentation instance representing the currently running JVM.
*/
public static synchronized Instrumentation install(AttachmentProvider attachmentProvider, ProcessProvider processProvider) {
Instrumentation instrumentation = doGetInstrumentation();
if (instrumentation != null) {
return instrumentation;
}
install(attachmentProvider, processProvider.resolve(), WITHOUT_ARGUMENT, AgentProvider.ForByteBuddyAgent.INSTANCE, false);
return getInstrumentation();
}
/**
* Installs a Java agent on a target VM.
*
* @param attachmentProvider The attachment provider to use.
* @param processId The process id of the target JVM process.
* @param argument The argument to provide to the agent.
* @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, @MaybeNull String argument, AgentProvider agentProvider, boolean isNative) {
AttachmentProvider.Accessor attachmentAccessor = attachmentProvider.attempt();
if (!attachmentAccessor.isAvailable()) {
throw new IllegalStateException("No compatible attachment provider is available");
}
try {
if (attachmentAccessor.isExternalAttachmentRequired() && ATTACHMENT_TYPE_EVALUATOR.requiresExternalAttachment(processId)) {
installExternal(attachmentAccessor.getExternalAttachment(), processId, agentProvider.resolve(), isNative, argument);
} else {
Attacher.install(attachmentAccessor.getVirtualMachineType(), processId, agentProvider.resolve().getAbsolutePath(), isNative, argument);
}
} catch (RuntimeException exception) {
throw exception;
} catch (Exception exception) {
throw new IllegalStateException("Error during attachment using: " + attachmentProvider, exception);
}
}
/**
* Installs a Java agent to the current VM via an external process. This is typically required starting with OpenJDK 9
* when the {@code jdk.attach.allowAttachSelf} property is set to {@code false} what is the default setting.
*
* @param externalAttachment A description of the external attachment.
* @param processId The process id of the current process.
* @param agent The Java agent to install.
* @param isNative {@code true} if the agent is native.
* @param argument The argument to provide to the agent or {@code null} if no argument should be supplied.
* @throws Exception If an exception occurs during the attachment or the external process fails the attachment.
*/
private static void installExternal(AttachmentProvider.Accessor.ExternalAttachment externalAttachment,
String processId,
File agent,
boolean isNative,
@MaybeNull String argument) throws Exception {
File selfResolvedJar = trySelfResolve(), attachmentJar = null;
try {
if (selfResolvedJar == null) {
InputStream inputStream = Attacher.class.getResourceAsStream('/' + Attacher.class.getName().replace('.', '/') + CLASS_FILE_EXTENSION);
if (inputStream == null) {
throw new IllegalStateException("Cannot locate class file for Byte Buddy installation process");
}
try {
attachmentJar = File.createTempFile(ATTACHER_FILE_NAME, JAR_FILE_EXTENSION);
JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(attachmentJar));
try {
jarOutputStream.putNextEntry(new JarEntry(Attacher.class.getName().replace('.', '/') + CLASS_FILE_EXTENSION));
byte[] buffer = new byte[1024 * 8];
int index;
while ((index = inputStream.read(buffer)) != -1) {
jarOutputStream.write(buffer, 0, index);
}
jarOutputStream.closeEntry();
} finally {
jarOutputStream.close();
}
} finally {
inputStream.close();
}
}
StringBuilder classPath = new StringBuilder().append((selfResolvedJar == null
? attachmentJar
: selfResolvedJar).getCanonicalPath());
for (File jar : externalAttachment.getClassPath()) {
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 + AGENT_ARGUMENT_SEPARATOR + System.getProperty(Attacher.DUMP_PROPERTY, ""),
CLASS_PATH_ARGUMENT,
classPath.toString(),
Attacher.class.getName(),
externalAttachment.getVirtualMachineType(),
processId,
agent.getAbsolutePath(),
Boolean.toString(isNative),
argument == null ? "" : (AGENT_ARGUMENT_SEPARATOR + argument)).start().waitFor() != 0) {
throw new IllegalStateException("Could not self-attach to current VM using external process - set a property "
+ Attacher.DUMP_PROPERTY
+ " to dump the process output to a file at the specified location");
}
} finally {
if (attachmentJar != null) {
if (!attachmentJar.delete()) {
attachmentJar.deleteOnExit();
}
}
}
}
/**
* Attempts to resolve the location of the {@link Attacher} class for a self-attachment. Doing so avoids the creation of a temporary jar file.
*
* @return The self-resolved jar file or {@code null} if the jar file cannot be located.
*/
@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)) {
return null;
}
ProtectionDomain protectionDomain = Attacher.class.getProtectionDomain();
if (protectionDomain == null) {
return null;
}
CodeSource codeSource = protectionDomain.getCodeSource();
if (codeSource == null) {
return null;
}
URL location = codeSource.getLocation();
if (!location.getProtocol().equals(FILE_PROTOCOL)) {
return null;
}
try {
File file = new File(location.toURI());
if (file.getPath().contains(AGENT_ARGUMENT_SEPARATOR)) {
return null;
}
return file;
} catch (URISyntaxException ignored) {
return new File(location.getPath());
}
} catch (Exception ignored) {
return null;
}
}
/**
* Performs the actual lookup of the {@link java.lang.instrument.Instrumentation} from an installed
* 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.
*/
@MaybeNull
private static Instrumentation doGetInstrumentation() {
if (!Installer.NAME.equals(Installer.class.getName())) {
Instrumentation instrumentation = doGetInstrumentation(Installer.NAME);
if (instrumentation != null) {
return instrumentation;
}
}
return doGetInstrumentation(Installer.class.getName());
}
/**
* Performs the actual lookup of the {@link java.lang.instrument.Instrumentation} from an installed
* Byte Buddy agent and returns the instance, or returns {@code null} if not present.
*
* @param name The name of the {@link Installer} class which might be shaded.
* @return The Byte Buddy agent's {@link java.lang.instrument.Instrumentation} instance.
*/
@MaybeNull
@SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback.")
private static Instrumentation doGetInstrumentation(String name) {
try {
Class> installer = Class.forName(name, 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(name, true, ClassLoader.getSystemClassLoader())
.getMethod("getInstrumentation")
.invoke(null);
} catch (Exception ignored) {
return null;
}
}
/**
* An attachment provider is responsible for making the Java attachment API available.
*/
@SuppressFBWarnings(value = "IC_SUPERCLASS_USES_SUBCLASS_DURING_INITIALIZATION", justification = "Safe initialization is implied")
public interface AttachmentProvider {
/**
* The default attachment provider to be used.
*/
AttachmentProvider DEFAULT = new Compound(ForModularizedVm.INSTANCE,
ForJ9Vm.INSTANCE,
ForStandardToolsJarVm.JVM_ROOT,
ForStandardToolsJarVm.JDK_ROOT,
ForStandardToolsJarVm.MACINTOSH,
ForUserDefinedToolsJar.INSTANCE,
ForEmulatedAttachment.INSTANCE);
/**
* Attempts the creation of an accessor for a specific JVM's attachment API.
*
* @return The accessor this attachment provider can supply for the currently running JVM.
*/
Accessor attempt();
/**
* An accessor for a JVM's attachment API.
*/
interface Accessor {
/**
* The name of the {@code VirtualMachine} class on any OpenJDK or Oracle JDK implementation.
*/
String VIRTUAL_MACHINE_TYPE_NAME = "com.sun.tools.attach.VirtualMachine";
/**
* The name of the {@code VirtualMachine} class on IBM J9 VMs.
*/
String VIRTUAL_MACHINE_TYPE_NAME_J9 = "com.ibm.tools.attach.VirtualMachine";
/**
* Determines if this accessor is applicable for the currently running JVM.
*
* @return {@code true} if this accessor is available.
*/
boolean isAvailable();
/**
* Returns {@code true} if this accessor prohibits attachment to the same virtual machine in Java 9 and later.
*
* @return {@code true} if this accessor prohibits attachment to the same virtual machine in Java 9 and later.
*/
boolean isExternalAttachmentRequired();
/**
* Returns a {@code VirtualMachine} class. This method must only be called for available accessors.
*
* @return The virtual machine type.
*/
Class> getVirtualMachineType();
/**
* Returns a description of a virtual machine class for an external attachment.
*
* @return A description of the external attachment.
*/
ExternalAttachment getExternalAttachment();
/**
* A canonical implementation of an unavailable accessor.
*/
enum Unavailable implements Accessor {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public boolean isAvailable() {
return false;
}
/**
* {@inheritDoc}
*/
public boolean isExternalAttachmentRequired() {
throw new IllegalStateException("Cannot read the virtual machine type for an unavailable accessor");
}
/**
* {@inheritDoc}
*/
public Class> getVirtualMachineType() {
throw new IllegalStateException("Cannot read the virtual machine type for an unavailable accessor");
}
/**
* {@inheritDoc}
*/
public ExternalAttachment getExternalAttachment() {
throw new IllegalStateException("Cannot read the virtual machine type for an unavailable accessor");
}
}
/**
* Describes an external attachment to a Java virtual machine.
*/
class ExternalAttachment {
/**
* The fully-qualified binary name of the virtual machine type.
*/
private final String virtualMachineType;
/**
* The class path elements required for loading the supplied virtual machine type.
*/
private final List classPath;
/**
* Creates an external attachment.
*
* @param virtualMachineType The fully-qualified binary name of the virtual machine type.
* @param classPath The class path elements required for loading the supplied virtual machine type.
*/
public ExternalAttachment(String virtualMachineType, List classPath) {
this.virtualMachineType = virtualMachineType;
this.classPath = classPath;
}
/**
* Returns the fully-qualified binary name of the virtual machine type.
*
* @return The fully-qualified binary name of the virtual machine type.
*/
public String getVirtualMachineType() {
return virtualMachineType;
}
/**
* Returns the class path elements required for loading the supplied virtual machine type.
*
* @return The class path elements required for loading the supplied virtual machine type.
*/
public List getClassPath() {
return classPath;
}
}
/**
* A simple implementation of an accessible accessor.
*/
abstract class Simple implements Accessor {
/**
* A {@code VirtualMachine} class.
*/
protected final Class> virtualMachineType;
/**
* Creates a new simple accessor.
*
* @param virtualMachineType A {@code VirtualMachine} class.
*/
protected Simple(Class> virtualMachineType) {
this.virtualMachineType = virtualMachineType;
}
/**
*
* Creates an accessor by reading the process id from the JMX runtime bean and by attempting
* to load the {@code com.sun.tools.attach.VirtualMachine} class from the provided class loader.
*
*
* This accessor is supposed to work on any implementation of the OpenJDK or Oracle JDK.
*
*
* @param classLoader A class loader that is capable of loading the virtual machine type.
* @param classPath The class path required to load the virtual machine class.
* @return An appropriate accessor.
*/
public static Accessor of(@MaybeNull ClassLoader classLoader, File... classPath) {
try {
return new Simple.WithExternalAttachment(Class.forName(VIRTUAL_MACHINE_TYPE_NAME,
false,
classLoader), Arrays.asList(classPath));
} catch (ClassNotFoundException ignored) {
return Unavailable.INSTANCE;
}
}
/**
*
* Creates an accessor by reading the process id from the JMX runtime bean and by attempting
* to load the {@code com.ibm.tools.attach.VirtualMachine} class from the provided class loader.
*
*
* This accessor is supposed to work on any implementation of IBM's J9.
*
*
* @return An appropriate accessor.
*/
public static Accessor ofJ9() {
try {
return new Simple.WithExternalAttachment(ClassLoader.getSystemClassLoader().loadClass(VIRTUAL_MACHINE_TYPE_NAME_J9),
Collections.emptyList());
} catch (ClassNotFoundException ignored) {
return Unavailable.INSTANCE;
}
}
/**
* {@inheritDoc}
*/
public boolean isAvailable() {
return true;
}
/**
* {@inheritDoc}
*/
public Class> getVirtualMachineType() {
return virtualMachineType;
}
/**
* A simple implementation of an accessible accessor that allows for external attachment.
*/
protected static class WithExternalAttachment extends Simple {
/**
* The class path required for loading the virtual machine type.
*/
private final List classPath;
/**
* Creates a new simple accessor that allows for external attachment.
*
* @param virtualMachineType The {@code com.sun.tools.attach.VirtualMachine} class.
* @param classPath The class path required for loading the virtual machine type.
*/
public WithExternalAttachment(Class> virtualMachineType, List classPath) {
super(virtualMachineType);
this.classPath = classPath;
}
/**
* {@inheritDoc}
*/
public boolean isExternalAttachmentRequired() {
return true;
}
/**
* {@inheritDoc}
*/
public ExternalAttachment getExternalAttachment() {
return new ExternalAttachment(virtualMachineType.getName(), classPath);
}
}
/**
* A simple implementation of an accessible accessor that attaches using a virtual machine emulation that does not require external attachment.
*/
protected static class WithDirectAttachment extends Simple {
/**
* Creates a new simple accessor that implements direct attachment.
*
* @param virtualMachineType A {@code VirtualMachine} class.
*/
public WithDirectAttachment(Class> virtualMachineType) {
super(virtualMachineType);
}
/**
* {@inheritDoc}
*/
public boolean isExternalAttachmentRequired() {
return false;
}
/**
* {@inheritDoc}
*/
public ExternalAttachment getExternalAttachment() {
throw new IllegalStateException("Cannot apply external attachment");
}
}
}
}
/**
* An attachment provider that locates the attach API directly from the system class loader, as possible since
* introducing the Java module system via the {@code jdk.attach} module.
*/
enum ForModularizedVm implements AttachmentProvider {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public Accessor attempt() {
return Accessor.Simple.of(ClassLoader.getSystemClassLoader());
}
}
/**
* An attachment provider that locates the attach API directly from the system class loader expecting
* an IBM J9 VM.
*/
enum ForJ9Vm implements AttachmentProvider {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public Accessor attempt() {
return Accessor.Simple.ofJ9();
}
}
/**
* An attachment provider that is dependant on the existence of a tools.jar file on the local
* file system.
*/
enum ForStandardToolsJarVm implements AttachmentProvider {
/**
* An attachment provider that locates the tools.jar from a Java home directory.
*/
JVM_ROOT("../lib/tools.jar"),
/**
* An attachment provider that locates the tools.jar from a Java installation directory.
* In practice, several virtual machines do not return the JRE's location for the
* java.home property against the property's specification.
*/
JDK_ROOT("lib/tools.jar"),
/**
* An attachment provider that locates the tools.jar as it is set for several JVM
* installations on Apple Macintosh computers.
*/
MACINTOSH("../Classes/classes.jar");
/**
* The Java home system property.
*/
private static final String JAVA_HOME_PROPERTY = "java.home";
/**
* The path to the tools.jar file, starting from the Java home directory.
*/
private final String toolsJarPath;
/**
* Creates a new attachment provider that loads the virtual machine class from the tools.jar.
*
* @param toolsJarPath The path to the tools.jar file, starting from the Java home directory.
*/
ForStandardToolsJarVm(String toolsJarPath) {
this.toolsJarPath = toolsJarPath;
}
/**
* {@inheritDoc}
*/
@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 {
return toolsJar.isFile() && toolsJar.canRead()
? Accessor.Simple.of(new URLClassLoader(new URL[]{toolsJar.toURI().toURL()}, BOOTSTRAP_CLASS_LOADER), toolsJar)
: Accessor.Unavailable.INSTANCE;
} catch (MalformedURLException exception) {
throw new IllegalStateException("Could not represent " + toolsJar + " as URL");
}
}
}
/**
* An attachment provider that attempts to locate a {@code tools.jar} from a custom location set via a system property.
*/
enum ForUserDefinedToolsJar implements AttachmentProvider {
/**
* The singelton instance.
*/
INSTANCE;
/**
* The property being read for locating {@code tools.jar}.
*/
public static final String PROPERTY = "net.bytebuddy.agent.toolsjar";
/**
* {@inheritDoc}
*/
@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) {
return Accessor.Unavailable.INSTANCE;
} else {
File toolsJar = new File(location);
try {
return Accessor.Simple.of(new URLClassLoader(new URL[]{toolsJar.toURI().toURL()}, BOOTSTRAP_CLASS_LOADER), toolsJar);
} catch (MalformedURLException exception) {
throw new IllegalStateException("Could not represent " + toolsJar + " as URL");
}
}
}
}
/**
* An attachment provider that uses Byte Buddy's attachment API emulation. To use this feature, JNA is required.
*/
enum ForEmulatedAttachment implements AttachmentProvider {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public Accessor attempt() {
try {
return new Accessor.Simple.WithDirectAttachment(doPrivileged(VirtualMachine.Resolver.INSTANCE));
} catch (Throwable ignored) {
return Accessor.Unavailable.INSTANCE;
}
}
}
/**
* A compound attachment provider that attempts the attachment by delegation to other providers. If
* none of the providers of this compound provider is capable of providing a valid accessor, an
* non-available accessor is returned.
*/
class Compound implements AttachmentProvider {
/**
* A list of attachment providers in the order of their application.
*/
private final List attachmentProviders;
/**
* Creates a new compound attachment provider.
*
* @param attachmentProvider A list of attachment providers in the order of their application.
*/
public Compound(AttachmentProvider... attachmentProvider) {
this(Arrays.asList(attachmentProvider));
}
/**
* Creates a new compound attachment provider.
*
* @param attachmentProviders A list of attachment providers in the order of their application.
*/
public Compound(List extends AttachmentProvider> attachmentProviders) {
this.attachmentProviders = new ArrayList();
for (AttachmentProvider attachmentProvider : attachmentProviders) {
if (attachmentProvider instanceof Compound) {
this.attachmentProviders.addAll(((Compound) attachmentProvider).attachmentProviders);
} else {
this.attachmentProviders.add(attachmentProvider);
}
}
}
/**
* {@inheritDoc}
*/
public Accessor attempt() {
for (AttachmentProvider attachmentProvider : attachmentProviders) {
Accessor accessor = attachmentProvider.attempt();
if (accessor.isAvailable()) {
return accessor;
}
}
return Accessor.Unavailable.INSTANCE;
}
}
}
/**
* A process provider is responsible for providing the process id of the current VM.
*/
public interface ProcessProvider {
/**
* Resolves a process id for the current JVM.
*
* @return The resolved process id.
*/
String resolve();
/**
* Supplies the current VM's process id.
*/
enum ForCurrentVm implements ProcessProvider {
/**
* The singleton instance.
*/
INSTANCE;
/**
* The best process provider for the current VM.
*/
private final ProcessProvider dispatcher;
/**
* Creates a process provider that supplies the current VM's process id.
*/
ForCurrentVm() {
dispatcher = ForJava9CapableVm.make();
}
/**
* {@inheritDoc}
*/
public String resolve() {
return dispatcher.resolve();
}
/**
* 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 {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
@SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback.")
public String resolve() {
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");
} else {
return runtimeName.substring(0, processIdIndex);
}
}
}
/**
* A process provider for a Java 9 capable VM with access to the introduced process API.
*/
protected static class ForJava9CapableVm implements ProcessProvider {
/**
* The {@code java.lang.ProcessHandle#current()} method.
*/
private final Method current;
/**
* The {@code java.lang.ProcessHandle#pid()} method.
*/
private final Method pid;
/**
* Creates a new Java 9 capable dispatcher for reading the current process's id.
*
* @param current The {@code java.lang.ProcessHandle#current()} method.
* @param pid The {@code java.lang.ProcessHandle#pid()} method.
*/
protected ForJava9CapableVm(Method current, Method pid) {
this.current = current;
this.pid = pid;
}
/**
* Attempts to create a dispatcher for a Java 9 VM and falls back to a legacy dispatcher
* if this is not possible.
*
* @return A dispatcher for the current VM.
*/
@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"),
Class.forName("java.lang.ProcessHandle").getMethod("pid"));
} catch (Exception ignored) {
return ForLegacyVm.INSTANCE;
}
}
/**
* {@inheritDoc}
*/
public String resolve() {
try {
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.getTargetException());
}
}
}
}
}
/**
* An agent provider is responsible for handling and providing the jar file of an agent that is being attached.
*/
protected interface AgentProvider {
/**
* Provides an agent jar file for attachment.
*
* @return The provided agent.
* @throws IOException If the agent cannot be written to disk.
*/
File resolve() throws IOException;
/**
* An agent provider for a temporary Byte Buddy agent.
*/
enum ForByteBuddyAgent implements AgentProvider {
/**
* The singleton instance.
*/
INSTANCE;
/**
* The default prefix of the Byte Buddy agent jar file.
*/
private static final String AGENT_FILE_NAME = "byteBuddyAgent";
/**
* Attempts to resolve the {@link Installer} class from this jar file if it can be located. Doing so, it is possible
* to avoid the creation of a temporary jar file which can remain undeleted on Windows operating systems where the agent
* is linked by a class loader such that {@link File#deleteOnExit()} does not have an effect.
*
* @param installer The installer class to attempt to resolve which might be a shaded version of the class.
* @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(Class> installer) throws IOException {
ProtectionDomain protectionDomain = installer.getProtectionDomain();
if (Boolean.getBoolean(LATENT_RESOLVE)) {
return null;
}
if (protectionDomain == null) {
return null;
}
CodeSource codeSource = protectionDomain.getCodeSource();
if (codeSource == null) {
return null;
}
URL location = codeSource.getLocation();
if (!location.getProtocol().equals(FILE_PROTOCOL)) {
return null;
}
File agentJar;
try {
agentJar = new File(location.toURI());
} catch (URISyntaxException ignored) {
agentJar = new File(location.getPath());
}
if (!agentJar.isFile() || !agentJar.canRead()) {
return null;
}
// It is necessary to check the manifest of the containing file as this code can be shaded into another artifact.
JarInputStream jarInputStream = new JarInputStream(new FileInputStream(agentJar));
try {
Manifest manifest = jarInputStream.getManifest();
if (manifest == null) {
return null;
}
Attributes attributes = manifest.getMainAttributes();
if (attributes == null) {
return null;
}
if (installer.getName().equals(attributes.getValue(AGENT_CLASS_PROPERTY))
&& Boolean.parseBoolean(attributes.getValue(CAN_REDEFINE_CLASSES_PROPERTY))
&& Boolean.parseBoolean(attributes.getValue(CAN_RETRANSFORM_CLASSES_PROPERTY))
&& Boolean.parseBoolean(attributes.getValue(CAN_SET_NATIVE_METHOD_PREFIX))) {
return agentJar;
} else {
return null;
}
} finally {
jarInputStream.close();
}
}
/**
* Creates an agent jar file containing the {@link Installer} class.
*
* @return The agent jar file.
* @throws IOException If an I/O exception occurs.
*/
private static File createJarFile() throws IOException {
InputStream inputStream = Installer.class.getResourceAsStream('/' + Installer.class.getName().replace('.', '/') + CLASS_FILE_EXTENSION);
if (inputStream == null) {
throw new IllegalStateException("Cannot locate class file for Byte Buddy installer");
}
try {
File agentJar = File.createTempFile(AGENT_FILE_NAME, JAR_FILE_EXTENSION);
agentJar.deleteOnExit(); // Agent jar is required until VM shutdown due to lazy class loading.
Manifest manifest = new Manifest();
manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, MANIFEST_VERSION_VALUE);
manifest.getMainAttributes().put(new Attributes.Name(AGENT_CLASS_PROPERTY), Installer.class.getName());
manifest.getMainAttributes().put(new Attributes.Name(CAN_REDEFINE_CLASSES_PROPERTY), Boolean.TRUE.toString());
manifest.getMainAttributes().put(new Attributes.Name(CAN_RETRANSFORM_CLASSES_PROPERTY), Boolean.TRUE.toString());
manifest.getMainAttributes().put(new Attributes.Name(CAN_SET_NATIVE_METHOD_PREFIX), Boolean.TRUE.toString());
JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(agentJar), manifest);
try {
jarOutputStream.putNextEntry(new JarEntry(Installer.class.getName().replace('.', '/') + CLASS_FILE_EXTENSION));
byte[] buffer = new byte[1024 * 8];
int index;
while ((index = inputStream.read(buffer)) != -1) {
jarOutputStream.write(buffer, 0, index);
}
jarOutputStream.closeEntry();
} finally {
jarOutputStream.close();
}
return agentJar;
} finally {
inputStream.close();
}
}
/**
* {@inheritDoc}
*/
@SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback.")
public File resolve() throws IOException {
try {
if (!Installer.class.getName().equals(Installer.NAME)) {
try {
File resolved = trySelfResolve(Class.forName(Installer.NAME,
false,
ClassLoader.getSystemClassLoader()));
if (resolved != null) {
return resolved;
}
} catch (ClassNotFoundException ignored) {
/* do nothing */
}
}
File resolved = trySelfResolve(Installer.class);
if (resolved != null) {
return resolved;
}
} catch (Exception ignored) {
/* do nothing */
}
return createJarFile();
}
}
/**
* An agent provider that supplies an existing agent that is not deleted after attachment.
*/
class ForExistingAgent implements AgentProvider {
/**
* The supplied agent.
*/
private final File agent;
/**
* Creates an agent provider for an existing agent.
*
* @param agent The supplied agent.
*/
protected ForExistingAgent(File agent) {
this.agent = agent;
}
/**
* {@inheritDoc}
*/
public File resolve() {
return agent;
}
}
}
/**
* An attachment evaluator is responsible for deciding if an agent can be attached from the current process.
*/
protected interface AttachmentTypeEvaluator {
/**
* Checks if the current VM requires external attachment for the supplied process id.
*
* @param processId The process id of the process to which to attach.
* @return {@code true} if the current VM requires external attachment for the supplied process.
*/
boolean requiresExternalAttachment(String processId);
/**
* An installation action for creating an attachment type evaluator.
*/
enum InstallationAction implements PrivilegedAction {
/**
* The singleton instance.
*/
INSTANCE;
/**
* The OpenJDK's property for specifying the legality of self-attachment.
*/
private static final String JDK_ALLOW_SELF_ATTACH = "jdk.attach.allowAttachSelf";
/**
* {@inheritDoc}
*/
@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)) {
return Disabled.INSTANCE;
} else {
return new ForJava9CapableVm(Class.forName("java.lang.ProcessHandle").getMethod("current"),
Class.forName("java.lang.ProcessHandle").getMethod("pid"));
}
} catch (Exception ignored) {
return Disabled.INSTANCE;
}
}
}
/**
* An attachment type evaluator that never requires external attachment.
*/
enum Disabled implements AttachmentTypeEvaluator {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public boolean requiresExternalAttachment(String processId) {
return false;
}
}
/**
* An attachment type evaluator that checks a process id against the current process id.
*/
class ForJava9CapableVm implements AttachmentTypeEvaluator {
/**
* The {@code java.lang.ProcessHandle#current()} method.
*/
private final Method current;
/**
* The {@code java.lang.ProcessHandle#pid()} method.
*/
private final Method pid;
/**
* Creates a new attachment type evaluator.
*
* @param current The {@code java.lang.ProcessHandle#current()} method.
* @param pid The {@code java.lang.ProcessHandle#pid()} method.
*/
protected ForJava9CapableVm(Method current, Method pid) {
this.current = current;
this.pid = pid;
}
/**
* {@inheritDoc}
*/
public boolean requiresExternalAttachment(String processId) {
try {
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.getTargetException());
}
}
}
}
}