All Downloads are FREE. Search and download functionalities are using the official Maven repository.

reactor.tools.agent.ReactorDebugAgent Maven / Gradle / Ivy

/*
 * Copyright (c) 2019-Present Pivotal Software Inc, All Rights Reserved.
 *
 * 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
 *
 *       https://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 reactor.tools.agent;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Stream;

import net.bytebuddy.agent.ByteBuddyAgent;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

public class ReactorDebugAgent {

	private static Instrumentation instrumentation;

	public static synchronized void init() {
		if (instrumentation != null) {
			return;
		}
		instrumentation = ByteBuddyAgent.install();

		ClassFileTransformer transformer = new ClassFileTransformer() {
			@Override
			public byte[] transform(
					ClassLoader loader,
					String className,
					Class clazz,
					ProtectionDomain protectionDomain,
					byte[] bytes
			) {
				if (loader == null) {
					return null;
				}

				if (
						className == null ||
								className.startsWith("java/") ||
								className.startsWith("jdk/") ||
								className.startsWith("sun/") ||
								className.startsWith("com/sun/") ||
								className.startsWith("reactor/core/")
				) {
					return null;
				}

				if (
						clazz != null && (
								clazz.isPrimitive() ||
										clazz.isArray() ||
										clazz.isAnnotation() ||
										clazz.isSynthetic()
						)
				) {
					return null;
				}

				ClassReader cr = new ClassReader(bytes);
				ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);

				AtomicBoolean changed = new AtomicBoolean();
				ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM7, cw) {

					private String currentClassName = "";

					private String currentSource = "";

					@Override
					public void visitSource(String source, String debug) {
						super.visitSource(source, debug);
						currentSource = source;
					}

					@Override
					public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
						super.visit(version, access, name, signature, superName, interfaces);
						currentClassName = name;
					}

					@Override
					public MethodVisitor visitMethod(int access, String currentMethod, String descriptor, String signature, String[] exceptions) {
						MethodVisitor visitor = super.visitMethod(access, currentMethod, descriptor, signature, exceptions);

						String returnType = Type.getReturnType(descriptor).getInternalName();
						switch (returnType) {
							// Handle every core publisher type.
							// Note that classes like `GroupedFlux` or `ConnectableFlux` are not included,
							// because they don't have a type-preserving "checkpoint" method
							case "reactor/core/publisher/Flux":
							case "reactor/core/publisher/Mono":
							case "reactor/core/publisher/ParallelFlux":
								visitor = new ReturnHandlingMethodVisitor(visitor, returnType, currentClassName, currentMethod, currentSource, changed);
						}

						return new CallSiteInfoAddingMethodVisitor(visitor, currentClassName, currentMethod, currentSource, changed);
					}
				};

				try {
					cr.accept(classVisitor, 0);
				}
				catch (Throwable e) {
					e.printStackTrace();
					throw e;
				}

				if (!changed.get()) {
					return null;
				}

				return cw.toByteArray();
			}
		};

		instrumentation.addTransformer(transformer, true);
	}

	public static synchronized void processExistingClasses() {
		if (instrumentation == null) {
			throw new IllegalStateException("Must be initialized first!");
		}

		try {
			Class[] classes = Stream
					.of(instrumentation.getInitiatedClasses(ClassLoader.getSystemClassLoader()))
					.filter(aClass -> {
						try {
							if (aClass.getClassLoader() == null) return false;
							if (aClass.isPrimitive() || aClass.isArray() || aClass.isInterface()) return false;
							if (aClass.isAnnotation() || aClass.isSynthetic()) return false;
							String name = aClass.getName();
							if (name == null) return false;
							if (name.startsWith("[")) return false;
							if (name.startsWith("java.")) return false;
							if (name.startsWith("sun.")) return false;
							if (name.startsWith("com.sun.")) return false;
							if (name.startsWith("jdk.")) return false;
							if (name.startsWith("reactor.core.")) return false;

							// May trigger NoClassDefFoundError, fail fast
							aClass.getConstructors();
						}
						catch (LinkageError e) {
							return false;
						}

						return true;
					})
					.toArray(Class[]::new);

			instrumentation.retransformClasses(classes);
		}
		catch (Throwable e) {
			e.printStackTrace();
			// Some classes fail to re-transform (e.g. worker.org.gradle.internal.UncheckedException)
			// See https://bugs.openjdk.java.net/browse/JDK-8014229
		}
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy