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

net.amygdalum.testrecorder.SnapshotInstrumentor Maven / Gradle / Ivy

package net.amygdalum.testrecorder;

import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.toList;
import static net.amygdalum.testrecorder.asm.ByteCode.classFrom;
import static net.amygdalum.testrecorder.asm.ByteCode.isNative;
import static net.amygdalum.testrecorder.asm.ByteCode.isStatic;
import static net.amygdalum.testrecorder.asm.ByteCode.returnsResult;
import static org.objectweb.asm.Opcodes.ACC_ANNOTATION;
import static org.objectweb.asm.Opcodes.ACC_INTERFACE;
import static org.objectweb.asm.Opcodes.ACC_PRIVATE;

import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;

import net.amygdalum.testrecorder.asm.Assign;
import net.amygdalum.testrecorder.asm.ByteCode;
import net.amygdalum.testrecorder.asm.CaptureCall;
import net.amygdalum.testrecorder.asm.GetClass;
import net.amygdalum.testrecorder.asm.GetInvokedMethodArgumentTypes;
import net.amygdalum.testrecorder.asm.GetInvokedMethodName;
import net.amygdalum.testrecorder.asm.GetInvokedMethodResultType;
import net.amygdalum.testrecorder.asm.GetStatic;
import net.amygdalum.testrecorder.asm.GetThisOrClass;
import net.amygdalum.testrecorder.asm.GetThisOrNull;
import net.amygdalum.testrecorder.asm.InvokeStatic;
import net.amygdalum.testrecorder.asm.InvokeVirtual;
import net.amygdalum.testrecorder.asm.Ldc;
import net.amygdalum.testrecorder.asm.MemoizeBoxed;
import net.amygdalum.testrecorder.asm.MethodContext;
import net.amygdalum.testrecorder.asm.Recall;
import net.amygdalum.testrecorder.asm.Sequence;
import net.amygdalum.testrecorder.asm.SequenceInstruction;
import net.amygdalum.testrecorder.asm.WrapArgumentTypes;
import net.amygdalum.testrecorder.asm.WrapArguments;
import net.amygdalum.testrecorder.asm.WrapMethod;
import net.amygdalum.testrecorder.asm.WrapResultType;
import net.amygdalum.testrecorder.asm.WrapWithTryCatch;
import net.amygdalum.testrecorder.bridge.BridgedSnapshotManager;
import net.amygdalum.testrecorder.profile.AgentConfiguration;
import net.amygdalum.testrecorder.profile.Classes;
import net.amygdalum.testrecorder.profile.Fields;
import net.amygdalum.testrecorder.profile.Global;
import net.amygdalum.testrecorder.profile.Input;
import net.amygdalum.testrecorder.profile.Methods;
import net.amygdalum.testrecorder.profile.Output;
import net.amygdalum.testrecorder.profile.Recorded;
import net.amygdalum.testrecorder.profile.SerializationProfile;
import net.amygdalum.testrecorder.util.AttachableClassFileTransformer;
import net.amygdalum.testrecorder.util.Logger;

public class SnapshotInstrumentor extends AttachableClassFileTransformer implements ClassFileTransformer {

	private AgentConfiguration config;
	private SerializationProfile profile;
	private ClassNodeManager classes = new ClassNodeManager();
	private IOManager io = new IOManager();
	private Map instrumentedClassPrototypes;
	private Set> instrumentedClasses;

	public SnapshotInstrumentor(AgentConfiguration config) {
		this.config = config;
		this.profile = config.loadConfiguration(SerializationProfile.class);
		this.classes = new ClassNodeManager();
		this.instrumentedClassPrototypes = new LinkedHashMap<>();
		this.instrumentedClasses = new LinkedHashSet<>();
	}
	
	@Override
	public AttachableClassFileTransformer attach(Instrumentation inst) {
		initialize(inst);
		return super.attach(inst);
	}

	protected void initialize(Instrumentation inst) {
		SnapshotManager.init(config, inst);
	}
	
	@Override
	public void detach(Instrumentation inst) {
		super.detach(inst);
		shutdown(inst);
	}

	protected void shutdown(Instrumentation inst) {
		SnapshotManager.done(config, inst);
	}

	@Override
	public Collection> filterClassesToRetransform(Class[] loaded) {
		Set> classesToRetransform = new LinkedHashSet<>();
		for (Class clazz : loaded) {
			for (Classes classes : profile.getClasses()) {
				if (classes.matches(clazz)) {
					classesToRetransform.add(clazz);
				}
			}
		}
		return classesToRetransform;
	}

	@Override
	public Collection> getClassesToRetransform() {
		Set> classesToRetransform = new LinkedHashSet<>();
		classesToRetransform.addAll(instrumentedClasses);

		for (Map.Entry classPrototype : instrumentedClassPrototypes.entrySet()) {
			classesToRetransform.add(classFrom(classPrototype.getKey(), classPrototype.getValue()));
		}

		return classesToRetransform;
	}

	@Override
	public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
		boolean aquired = lock.acquire();
		if (!aquired) {
			return null;
		}
		try {
			if (className == null) {
				return null;
			}
			for (Classes clazz : profile.getClasses()) {
				if (clazz.matches(className)) {
					Logger.info("recording snapshots of " + className);

					byte[] instrument = instrument(classfileBuffer, classBeingRedefined, loader);
					if (classBeingRedefined != null) {
						instrumentedClasses.add(classBeingRedefined);
						instrumentedClassPrototypes.remove(Type.getObjectType(classBeingRedefined.getName()).getClassName());
					} else {
						instrumentedClassPrototypes.put(className, loader);
					}

					return instrument;
				}
			}
			return null;
		} catch (Throwable e) {
			Logger.error("exception occured while preparing recording of snapshots: ", e);
			return null;
		} finally {
			lock.release();
		}

	}

	public byte[] instrument(String className, Class clazz, ClassLoader loader) throws IOException {
		return instrument(classes.fetch(className, loader), clazz, loader);
	}

	public byte[] instrument(byte[] buffer, Class clazz, ClassLoader loader) {
		return instrument(classes.register(buffer), clazz, loader);
	}

	public byte[] instrument(ClassNode classNode, Class clazz, ClassLoader loader) {
		analyzeMethods(classNode);

		if (!isClass(classNode)) {
			return null;
		}
		Task task = needsBridging(classNode, clazz)
			? new BridgedTask(loader, profile, classes, io, classNode)
			: new DefaultTask(loader, profile, classes, io, classNode);

		task.logSkippedSnapshotMethods();

		task.registerCallbacks();

		task.instrumentSnapshotMethods();

		task.instrumentInputMethods();

		task.instrumentOutputMethods();

		task.instrumentNativeInputCalls();

		task.instrumentNativeOutputCalls();

		ClassWriter out = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
		classNode.accept(out);
		return out.toByteArray();
	}

	private void analyzeMethods(ClassNode classNode) {
		for (MethodNode methodNode : classNode.methods) {
			analyzeMethod(classNode, methodNode);
		}
	}

	private void analyzeMethod(ClassNode classNode, MethodNode methodNode) {
		if (annotations(methodNode).stream()
			.anyMatch(annotation -> annotation.desc.equals(Type.getDescriptor(Input.class)))) {
			io.registerInput(classNode.name, methodNode.name, methodNode.desc);
		}
		if (annotations(methodNode).stream()
			.anyMatch(annotation -> annotation.desc.equals(Type.getDescriptor(Output.class)))) {
			io.registerOutput(classNode.name, methodNode.name, methodNode.desc);
		}
	}

	private boolean isClass(ClassNode classNode) {
		return (classNode.access & (ACC_INTERFACE | ACC_ANNOTATION)) == 0;
	}

	private boolean needsBridging(ClassNode classNode, Class clazz) {
		if (clazz != null && clazz.getClassLoader() == null) {
			return true;
		}
		return false;
	}

	private static List annotations(FieldNode node) {
		if (node.visibleAnnotations == null) {
			return emptyList();
		}
		return node.visibleAnnotations;
	}

	private static List annotations(MethodNode node) {
		if (node.visibleAnnotations == null) {
			return emptyList();
		}
		return node.visibleAnnotations;
	}

	public abstract static class Task {

		private ClassLoader loader;
		private SerializationProfile profile;
		private ClassNodeManager classes;
		private IOManager io;

		protected ClassNode classNode;

		public Task(ClassLoader loader, SerializationProfile profile, ClassNodeManager classes, IOManager io, ClassNode classNode) {
			this.loader = loader;
			this.profile = profile;
			this.classes = classes;
			this.io = io;
			this.classNode = classNode;
			io.propagate(classNode.name, classNode.superName, classNode.interfaces);
		}

		public void logSkippedSnapshotMethods() {
			for (MethodNode methodNode : getSkippedSnapshotMethods()) {
				Logger.warn("method " + Type.getMethodType(methodNode.desc).getDescriptor() + " in " + Type.getObjectType(classNode.name) + " is not accessible, skipping");
			}
		}

		public void registerCallbacks() {
			for (MethodNode methodNode : getSnapshotMethods()) {
				SnapshotManager.MANAGER.registerRecordedMethod(keySignature(classNode, methodNode), classNode.name, methodNode.name, methodNode.desc);
			}
			for (FieldNode fieldNode : getGlobalFields()) {
				SnapshotManager.MANAGER.registerGlobal(classNode.name, fieldNode.name);
			}
		}

		private void instrumentSnapshotMethods() {
			for (MethodNode method : getSnapshotMethods()) {
				instrumentSnapshotMethod(method);
			}
		}

		protected void instrumentSnapshotMethod(MethodNode methodNode) {
			methodNode.instructions = new WrapWithTryCatch()
				.before(setupVariables(methodNode))
				.after(expectVariables(methodNode))
				.handler(throwVariables(methodNode))
				.build(new MethodContext(classNode, methodNode));
		}

		protected abstract SequenceInstruction setupVariables(MethodNode methodNode);

		protected abstract SequenceInstruction expectVariables(MethodNode methodNode);

		protected abstract SequenceInstruction throwVariables(MethodNode methodNode);

		public void instrumentInputMethods() {
			for (MethodNode method : getJavaInputMethods()) {
				instrumentInputMethod(method);
			}
		}

		protected void instrumentInputMethod(MethodNode methodNode) {
			methodNode.instructions = new WrapMethod()
				.prepend(inputVariables(methodNode))
				.append(inputArgumentsAndResult(methodNode))
				.build(new MethodContext(classNode, methodNode));
		}

		protected abstract SequenceInstruction inputArgumentsAndResult(MethodNode methodNode);

		protected abstract SequenceInstruction inputVariables(MethodNode methodNode);

		public void instrumentOutputMethods() {
			for (MethodNode method : getJavaOutputMethods()) {
				instrumentOutputMethod(method);
			}
		}

		protected void instrumentOutputMethod(MethodNode methodNode) {
			methodNode.instructions = new WrapMethod()
				.prepend(outputVariables(methodNode))
				.append(outputResult(methodNode))
				.build(new MethodContext(classNode, methodNode));
		}

		protected abstract SequenceInstruction outputVariables(MethodNode methodNode);

		protected abstract SequenceInstruction outputResult(MethodNode methodNode);

		private void instrumentNativeInputCalls() {
			for (MethodNode method : classNode.methods) {
				if (!isInputMethod(classNode, method)) {
					MethodContext context = new MethodContext(classNode, method);
					for (MethodInsnNode inputCall : getNativeInputCalls(method)) {
						method.instructions.insertBefore(inputCall, beforeNativeInputCall(context, inputCall));
						method.instructions.insert(inputCall, afterNativeInputCall(context, inputCall));
					}
				}
			}
		}

		private void instrumentNativeOutputCalls() {
			for (MethodNode method : classNode.methods) {
				if (!isOutputMethod(classNode, method)) {
					MethodContext context = new MethodContext(classNode, method);
					for (MethodInsnNode outputCall : getNativeOutputCalls(method)) {
						method.instructions.insertBefore(outputCall, beforeNativeOutputCall(context, outputCall));
						method.instructions.insert(outputCall, afterNativeOutputCall(context, outputCall));
					}
				}
			}
		}

		protected abstract InsnList afterNativeInputCall(MethodContext context, MethodInsnNode inputCall);

		protected abstract InsnList beforeNativeInputCall(MethodContext context, MethodInsnNode inputCall);

		protected abstract InsnList beforeNativeOutputCall(MethodContext context, MethodInsnNode inputCall);

		protected abstract InsnList afterNativeOutputCall(MethodContext context, MethodInsnNode inputCall);

		private List getSkippedSnapshotMethods() {
			return classNode.methods.stream()
				.filter(methodNode -> isSnapshotMethod(classNode, methodNode))
				.filter(methodNode -> !isVisible(classNode) || !isVisible(methodNode))
				.collect(toList());
		}

		private List getGlobalFields() {
			if (!isVisible(classNode)) {
				return emptyList();
			}
			return classNode.fields.stream()
				.filter(field -> isGlobalField(classNode.name, field))
				.filter(field -> isVisible(field))
				.collect(toList());
		}

		private List getSnapshotMethods() {
			if (!isVisible(classNode)) {
				return emptyList();
			}
			return classNode.methods.stream()
				.filter(methodNode -> isSnapshotMethod(classNode, methodNode))
				.filter(methodNode -> isVisible(methodNode))
				.collect(toList());
		}

		private List getJavaInputMethods() {
			if (!isVisible(classNode)) {
				return emptyList();
			}
			return classNode.methods.stream()
				.filter(methodNode -> isJavaInputMethod(classNode, methodNode))
				.collect(toList());
		}

		private List getJavaOutputMethods() {
			if (!isVisible(classNode)) {
				return emptyList();
			}
			return classNode.methods.stream()
				.filter(method -> isJavaOutputMethod(classNode, method))
				.collect(toList());
		}

		private List getNativeInputCalls(MethodNode methodNode) {
			List calls = new ArrayList<>();
			ListIterator instructions = methodNode.instructions.iterator();
			while (instructions.hasNext()) {
				AbstractInsnNode insn = instructions.next();
				if (insn instanceof MethodInsnNode) {
					MethodInsnNode methodinsn = (MethodInsnNode) insn;
					try {
						Type type = Type.getObjectType(methodinsn.owner);
						if (ByteCode.isPrimitive(type) || ByteCode.isArray(type)) {
							continue;
						}
						ClassNode calledClassNode = classes.fetch(methodinsn.owner, loader);
						MethodNode calledMethodNode = classes.fetch(calledClassNode, methodinsn.name, methodinsn.desc, loader);
						if (isNativeInputMethod(calledClassNode, calledMethodNode)) {
							calls.add(methodinsn);
						}
					} catch (IOException e) {
						Logger.warn("cannot find referenced class " + methodinsn.owner + ", skipping");
					} catch (NoSuchMethodException e) {
						Logger.warn("cannot find referenced method " + methodinsn.owner + "." + methodinsn.name + methodinsn.desc + ", skipping");
					}
				}
			}
			return calls;
		}

		private List getNativeOutputCalls(MethodNode methodNode) {
			List calls = new ArrayList<>();
			ListIterator instructions = methodNode.instructions.iterator();
			while (instructions.hasNext()) {
				AbstractInsnNode insn = instructions.next();
				if (insn instanceof MethodInsnNode) {
					MethodInsnNode methodinsn = (MethodInsnNode) insn;
					try {
						Type type = Type.getObjectType(methodinsn.owner);
						if (ByteCode.isPrimitive(type) || ByteCode.isArray(type)) {
							continue;
						}
						ClassNode calledClassNode = classes.fetch(methodinsn.owner, loader);
						MethodNode calledMethodNode = classes.fetch(calledClassNode, methodinsn.name, methodinsn.desc, loader);
						if (isNativeOutputMethod(calledClassNode, calledMethodNode)) {
							calls.add(methodinsn);
						}
					} catch (IOException e) {
						Logger.warn("cannot find referenced class " + methodinsn.owner + ", skipping");
					} catch (NoSuchMethodException e) {
						Logger.warn("cannot find referenced method " + methodinsn.owner + "." + methodinsn.name + methodinsn.desc + ", skipping");
					}
				}
			}
			return calls;
		}

		protected boolean isGlobalField(String className, FieldNode fieldNode) {
			boolean global = annotations(fieldNode).stream()
				.anyMatch(annotation -> annotation.desc.equals(Type.getDescriptor(Global.class)))
				|| profile.getGlobalFields().stream()
					.anyMatch(field -> matches(field, className, fieldNode.name, fieldNode.desc));
			if (global && !isStatic(fieldNode)) {
				Logger.warn("found annotation @Global on non static field " + fieldNode.desc + " " + fieldNode.name + ", skipping");
				return false;
			}
			return global;
		}

		protected boolean isSnapshotMethod(ClassNode classNode, MethodNode methodNode) {
			return annotations(methodNode).stream()
				.anyMatch(annotation -> annotation.desc.equals(Type.getDescriptor(Recorded.class)))
				|| profile.getRecorded().stream()
					.anyMatch(method -> matches(method, classNode.name, methodNode.name, methodNode.desc));
		}

		protected boolean isJavaInputMethod(ClassNode classNode, MethodNode methodNode) {
			return !isNative(methodNode)
				&& isInputMethod(classNode, methodNode);
		}

		protected boolean isNativeInputMethod(ClassNode classNode, MethodNode methodNode) {
			return isNative(methodNode)
				&& isInputMethod(classNode, methodNode);
		}

		protected boolean isInputMethod(ClassNode classNode, MethodNode methodNode) {
			boolean input = isQualifiedInputMethod(classNode, methodNode);
			if (input && (isQualifiedOutputMethod(classNode, methodNode) || isSnapshotMethod(classNode, methodNode))) {
				Logger.warn("found annotation @Input on method already annotated with @Recorded or @Output " + methodNode.name + methodNode.desc + ", skipping");
				return false;
			}

			return input;
		}

		private boolean isQualifiedInputMethod(ClassNode classNode, MethodNode methodNode) {
			return io.isInput(classNode.name, methodNode.name, methodNode.desc)
				|| profile.getInputs().stream()
					.anyMatch(method -> matches(method, classNode.name, methodNode.name, methodNode.desc));
		}

		protected boolean isJavaOutputMethod(ClassNode classNode, MethodNode methodNode) {
			return !isNative(methodNode)
				&& isOutputMethod(classNode, methodNode);
		}

		protected boolean isNativeOutputMethod(ClassNode classNode, MethodNode methodNode) {
			return isNative(methodNode)
				&& isOutputMethod(classNode, methodNode);
		}

		protected boolean isOutputMethod(ClassNode classNode, MethodNode methodNode) {
			boolean output = isQualifiedOutputMethod(classNode, methodNode);
			if (output && (isQualifiedInputMethod(classNode, methodNode) || isSnapshotMethod(classNode, methodNode))) {
				Logger.warn("found annotation @Output on method already annotated with @Recorded or @Input " + methodNode.name + methodNode.desc + ", skipping");
				return false;
			}

			return output;
		}

		private boolean isQualifiedOutputMethod(ClassNode classNode, MethodNode methodNode) {
			return io.isOutput(classNode.name, methodNode.name, methodNode.desc)
				|| profile.getOutputs().stream()
					.anyMatch(method -> matches(method, classNode.name, methodNode.name, methodNode.desc));
		}

		private boolean isVisible(ClassNode classNode) {
			if ((classNode.access & ACC_PRIVATE) != 0) {
				return false;
			}
			return classNode.innerClasses.stream()
				.filter(innerClassNode -> innerClassNode.name.equals(classNode.name))
				.map(innerClassNode -> (innerClassNode.access & ACC_PRIVATE) == 0)
				.findFirst()
				.orElse(true);
		}

		private boolean isVisible(MethodNode methodNode) {
			return (methodNode.access & ACC_PRIVATE) == 0;
		}

		private boolean isVisible(FieldNode fieldNode) {
			return (fieldNode.access & ACC_PRIVATE) == 0;
		}

		private boolean matches(Fields field, String className, String fieldName, String fieldDescriptor) {
			return field.matches(className, fieldName, fieldDescriptor);
		}

		private boolean matches(Methods method, String className, String methodName, String methodDescriptor) {
			return method.matches(className, methodName, methodDescriptor);
		}

		protected String keySignature(ClassNode classNode, MethodNode methodNode) {
			return classNode.name + ":" + methodNode.name + methodNode.desc;
		}

	}

	public static class BridgedTask extends Task {

		public BridgedTask(ClassLoader loader, SerializationProfile profile, ClassNodeManager classes, IOManager io, ClassNode classNode) {
			super(loader, profile, classes, io, classNode);
		}

		protected SequenceInstruction setupVariables(MethodNode methodNode) {
			return new InvokeStatic(BridgedSnapshotManager.class, "setupVariables", Class.class, Object.class, String.class, Object[].class)
				.withArgument(0, new GetClass())
				.withArgument(1, new GetThisOrNull())
				.withArgument(2, new Ldc(keySignature(classNode, methodNode)))
				.withArgument(3, new WrapArguments());
		}

		protected SequenceInstruction expectVariables(MethodNode methodNode) {
			if (returnsResult(methodNode)) {
				return Sequence.start()
					.then(new MemoizeBoxed("returnValue", Type.getReturnType(methodNode.desc)))
					.then(new InvokeStatic(BridgedSnapshotManager.class, "expectVariables", Object.class, String.class, Object.class, Object[].class)
						.withArgument(0, new GetThisOrNull())
						.withArgument(1, new Ldc(keySignature(classNode, methodNode)))
						.withArgument(2, new Recall("returnValue"))
						.withArgument(3, new WrapArguments()));
			} else {
				return Sequence.start()
					.then(new InvokeStatic(BridgedSnapshotManager.class, "expectVariables", Object.class, String.class, Object[].class)
						.withArgument(0, new GetThisOrNull())
						.withArgument(1, new Ldc(keySignature(classNode, methodNode)))
						.withArgument(2, new WrapArguments()));
			}
		}

		protected SequenceInstruction throwVariables(MethodNode methodNode) {
			return Sequence.start()
				.then(new MemoizeBoxed("throwable", Type.getType(Throwable.class)))
				.then(new InvokeStatic(BridgedSnapshotManager.class, "throwVariables", Throwable.class, Object.class, String.class, Object[].class)
					.withArgument(0, new Recall("throwable"))
					.withArgument(1, new GetThisOrNull())
					.withArgument(2, new Ldc(keySignature(classNode, methodNode)))
					.withArgument(3, new WrapArguments()));
		}

		@Override
		protected SequenceInstruction inputVariables(MethodNode methodNode) {
			return new Assign("inputId", Type.INT_TYPE)
				.value(new InvokeStatic(BridgedSnapshotManager.class, "inputVariables", Object.class, String.class, java.lang.reflect.Type.class, java.lang.reflect.Type[].class)
					.withArgument(0, new GetThisOrClass())
					.withArgument(1, new Ldc(methodNode.name))
					.withArgument(2, new WrapResultType())
					.withArgument(3, new WrapArgumentTypes()));
		}

		@Override
		protected SequenceInstruction inputArgumentsAndResult(MethodNode methodNode) {
			if (returnsResult(methodNode)) {
				return Sequence.start()
					.then(new MemoizeBoxed("returnValue", Type.getReturnType(methodNode.desc)))
					.then(new InvokeStatic(BridgedSnapshotManager.class, "inputArguments", int.class, Object[].class)
						.withArgument(0, new Recall("inputId"))
						.withArgument(1, new WrapArguments()))
					.then(new InvokeStatic(BridgedSnapshotManager.class, "inputResult", int.class, Object.class)
						.withArgument(0, new Recall("inputId"))
						.withArgument(1, new Recall("returnValue")));
			} else {
				return Sequence.start()
					.then(new InvokeStatic(BridgedSnapshotManager.class, "inputArguments", int.class, Object[].class)
						.withArgument(0, new Recall("inputId"))
						.withArgument(1, new WrapArguments()))
					.then(new InvokeStatic(BridgedSnapshotManager.class, "inputVoidResult", int.class)
						.withArgument(0, new Recall("inputId")));
			}
		}

		@Override
		protected SequenceInstruction outputVariables(MethodNode methodNode) {
			return Sequence.start()
				.then(new Assign("outputId", Type.INT_TYPE)
					.value(new InvokeStatic(BridgedSnapshotManager.class, "outputVariables", Object.class, String.class, java.lang.reflect.Type.class, java.lang.reflect.Type[].class)
						.withArgument(0, new GetThisOrClass())
						.withArgument(1, new Ldc(methodNode.name))
						.withArgument(2, new WrapResultType())
						.withArgument(3, new WrapArgumentTypes())))
				.then(new InvokeStatic(BridgedSnapshotManager.class, "outputArguments", int.class, Object[].class)
					.withArgument(0, new Recall("outputId"))
					.withArgument(1, new WrapArguments()));
		}

		@Override
		protected SequenceInstruction outputResult(MethodNode methodNode) {
			if (returnsResult(methodNode)) {
				return Sequence.start()
					.then(new MemoizeBoxed("returnValue", Type.getReturnType(methodNode.desc)))
					.then(new InvokeStatic(BridgedSnapshotManager.class, "outputResult", int.class, Object.class)
						.withArgument(0, new Recall("outputId"))
						.withArgument(1, new Recall("returnValue")));
			} else {
				return Sequence.start()
					.then(new InvokeStatic(BridgedSnapshotManager.class, "outputVoidResult", int.class)
						.withArgument(0, new Recall("outputId")));
			}
		}

		@Override
		protected InsnList beforeNativeInputCall(MethodContext context, MethodInsnNode inputCall) {
			return Sequence.start()
				.then(new CaptureCall(inputCall, "base", "arguments"))
				.then(new Assign("inputId", Type.INT_TYPE)
					.value(
						new InvokeStatic(BridgedSnapshotManager.class, "inputVariables", Object.class, String.class, java.lang.reflect.Type.class, java.lang.reflect.Type[].class)
							.withArgument(0, new Recall("base"))
							.withArgument(1, new GetInvokedMethodName(inputCall))
							.withArgument(2, new GetInvokedMethodResultType(inputCall))
							.withArgument(3, new GetInvokedMethodArgumentTypes(inputCall))))
				.build(context);
		}

		@Override
		protected InsnList afterNativeInputCall(MethodContext context, MethodInsnNode inputCall) {
			if (returnsResult(inputCall)) {
				return Sequence.start()
					.then(new MemoizeBoxed("returnValue", Type.getReturnType(inputCall.desc)))
					.then(new InvokeStatic(BridgedSnapshotManager.class, "inputArguments", int.class, Object[].class)
						.withArgument(0, new Recall("inputId"))
						.withArgument(1, new Recall("arguments")))
					.then(new InvokeStatic(BridgedSnapshotManager.class, "inputResult", int.class, Object.class)
						.withArgument(0, new Recall("inputId"))
						.withArgument(1, new Recall("returnValue")))
					.build(context);
			} else {
				return Sequence.start()
					.then(new InvokeStatic(BridgedSnapshotManager.class, "inputArguments", int.class, Object[].class)
						.withArgument(0, new Recall("inputId"))
						.withArgument(1, new Recall("arguments")))
					.then(new InvokeStatic(BridgedSnapshotManager.class, "inputVoidResult", int.class)
						.withArgument(0, new Recall("inputId")))
					.build(context);
			}
		}

		@Override
		protected InsnList beforeNativeOutputCall(MethodContext context, MethodInsnNode inputCall) {
			return Sequence.start()
				.then(new CaptureCall(inputCall, "base", "arguments"))
				.then(new Assign("outputId", Type.INT_TYPE)
					.value(
						new InvokeStatic(BridgedSnapshotManager.class, "outputVariables", Object.class, String.class, java.lang.reflect.Type.class, java.lang.reflect.Type[].class)
							.withArgument(0, new Recall("base"))
							.withArgument(1, new GetInvokedMethodName(inputCall))
							.withArgument(2, new GetInvokedMethodResultType(inputCall))
							.withArgument(3, new GetInvokedMethodArgumentTypes(inputCall))))
				.then(new InvokeStatic(BridgedSnapshotManager.class, "outputArguments", int.class, Object[].class)
					.withArgument(0, new Recall("outputId"))
					.withArgument(1, new Recall("arguments")))
				.build(context);
		}

		@Override
		protected InsnList afterNativeOutputCall(MethodContext context, MethodInsnNode inputCall) {
			if (returnsResult(inputCall)) {
				return Sequence.start()
					.then(new MemoizeBoxed("returnValue", Type.getReturnType(inputCall.desc)))
					.then(new InvokeStatic(BridgedSnapshotManager.class, "outputResult", int.class, Object.class)
						.withArgument(0, new Recall("outputId"))
						.withArgument(1, new Recall("returnValue")))
					.build(context);
			} else {
				return Sequence.start()
					.then(new InvokeStatic(BridgedSnapshotManager.class, "outputVoidResult", int.class)
						.withArgument(0, new Recall("outputId")))
					.build(context);
			}
		}

	}

	public static class DefaultTask extends Task {

		public DefaultTask(ClassLoader loader, SerializationProfile profile, ClassNodeManager classes, IOManager io, ClassNode classNode) {
			super(loader, profile, classes, io, classNode);
		}

		protected SequenceInstruction setupVariables(MethodNode methodNode) {
			return new InvokeVirtual(SnapshotManager.class, "setupVariables", Class.class, Object.class, String.class, Object[].class)
				.withBase(new GetStatic(SnapshotManager.class, "MANAGER"))
				.withArgument(0, new GetClass())
				.withArgument(1, new GetThisOrNull())
				.withArgument(2, new Ldc(keySignature(classNode, methodNode)))
				.withArgument(3, new WrapArguments());
		}

		protected SequenceInstruction expectVariables(MethodNode methodNode) {
			if (returnsResult(methodNode)) {
				return Sequence.start()
					.then(new MemoizeBoxed("returnValue", Type.getReturnType(methodNode.desc)))
					.then(new InvokeVirtual(SnapshotManager.class, "expectVariables", Object.class, String.class, Object.class, Object[].class)
						.withBase(new GetStatic(SnapshotManager.class, "MANAGER"))
						.withArgument(0, new GetThisOrNull())
						.withArgument(1, new Ldc(keySignature(classNode, methodNode)))
						.withArgument(2, new Recall("returnValue"))
						.withArgument(3, new WrapArguments()));
			} else {
				return Sequence.start()
					.then(new InvokeVirtual(SnapshotManager.class, "expectVariables", Object.class, String.class, Object[].class)
						.withBase(new GetStatic(SnapshotManager.class, "MANAGER"))
						.withArgument(0, new GetThisOrNull())
						.withArgument(1, new Ldc(keySignature(classNode, methodNode)))
						.withArgument(2, new WrapArguments()));
			}
		}

		protected SequenceInstruction throwVariables(MethodNode methodNode) {
			return Sequence.start()
				.then(new MemoizeBoxed("throwable", Type.getType(Throwable.class)))
				.then(new InvokeVirtual(SnapshotManager.class, "throwVariables", Throwable.class, Object.class, String.class, Object[].class)
					.withBase(new GetStatic(SnapshotManager.class, "MANAGER"))
					.withArgument(0, new Recall("throwable"))
					.withArgument(1, new GetThisOrNull())
					.withArgument(2, new Ldc(keySignature(classNode, methodNode)))
					.withArgument(3, new WrapArguments()));
		}

		@Override
		protected SequenceInstruction inputVariables(MethodNode methodNode) {
			return new Assign("inputId", Type.INT_TYPE)
				.value(new InvokeVirtual(SnapshotManager.class, "inputVariables", Object.class, String.class, java.lang.reflect.Type.class, java.lang.reflect.Type[].class)
					.withBase(new GetStatic(SnapshotManager.class, "MANAGER"))
					.withArgument(0, new GetThisOrClass())
					.withArgument(1, new Ldc(methodNode.name))
					.withArgument(2, new WrapResultType())
					.withArgument(3, new WrapArgumentTypes()));
		}

		@Override
		protected SequenceInstruction inputArgumentsAndResult(MethodNode methodNode) {
			if (returnsResult(methodNode)) {
				return Sequence.start()
					.then(new MemoizeBoxed("returnValue", Type.getReturnType(methodNode.desc)))
					.then(new InvokeVirtual(SnapshotManager.class, "inputArguments", int.class, Object[].class)
						.withBase(new GetStatic(SnapshotManager.class, "MANAGER"))
						.withArgument(0, new Recall("inputId"))
						.withArgument(1, new WrapArguments()))
					.then(new InvokeVirtual(SnapshotManager.class, "inputResult", int.class, Object.class)
						.withBase(new GetStatic(SnapshotManager.class, "MANAGER"))
						.withArgument(0, new Recall("inputId"))
						.withArgument(1, new Recall("returnValue")));
			} else {
				return Sequence.start()
					.then(new InvokeVirtual(SnapshotManager.class, "inputArguments", int.class, Object[].class)
						.withBase(new GetStatic(SnapshotManager.class, "MANAGER"))
						.withArgument(0, new Recall("inputId"))
						.withArgument(1, new WrapArguments()))
					.then(new InvokeVirtual(SnapshotManager.class, "inputVoidResult", int.class)
						.withBase(new GetStatic(SnapshotManager.class, "MANAGER"))
						.withArgument(0, new Recall("inputId")));
			}
		}

		@Override
		protected SequenceInstruction outputVariables(MethodNode methodNode) {
			return Sequence.start()
				.then(new Assign("outputId", Type.INT_TYPE)
					.value(
						new InvokeVirtual(SnapshotManager.class, "outputVariables", Object.class, String.class, java.lang.reflect.Type.class, java.lang.reflect.Type[].class)
							.withBase(new GetStatic(SnapshotManager.class, "MANAGER"))
							.withArgument(0, new GetThisOrClass())
							.withArgument(1, new Ldc(methodNode.name))
							.withArgument(2, new WrapResultType())
							.withArgument(3, new WrapArgumentTypes())))
				.then(new InvokeVirtual(SnapshotManager.class, "outputArguments", int.class, Object[].class)
					.withBase(new GetStatic(SnapshotManager.class, "MANAGER"))
					.withArgument(0, new Recall("outputId"))
					.withArgument(1, new WrapArguments()));
		}

		@Override
		protected SequenceInstruction outputResult(MethodNode methodNode) {
			if (returnsResult(methodNode)) {
				return Sequence.start()
					.then(new MemoizeBoxed("returnValue", Type.getReturnType(methodNode.desc)))
					.then(new InvokeVirtual(SnapshotManager.class, "outputResult", int.class, Object.class)
						.withBase(new GetStatic(SnapshotManager.class, "MANAGER"))
						.withArgument(0, new Recall("outputId"))
						.withArgument(1, new Recall("returnValue")));
			} else {
				return Sequence.start()
					.then(new InvokeVirtual(SnapshotManager.class, "outputVoidResult", int.class)
						.withBase(new GetStatic(SnapshotManager.class, "MANAGER"))
						.withArgument(0, new Recall("outputId")));
			}
		}

		@Override
		protected InsnList beforeNativeInputCall(MethodContext context, MethodInsnNode inputCall) {
			return Sequence.start()
				.then(new CaptureCall(inputCall, "base", "arguments"))
				.then(new Assign("inputId", Type.INT_TYPE)
					.value(
						new InvokeVirtual(SnapshotManager.class, "inputVariables", Object.class, String.class, java.lang.reflect.Type.class, java.lang.reflect.Type[].class)
							.withBase(new GetStatic(SnapshotManager.class, "MANAGER"))
							.withArgument(0, new Recall("base"))
							.withArgument(1, new GetInvokedMethodName(inputCall))
							.withArgument(2, new GetInvokedMethodResultType(inputCall))
							.withArgument(3, new GetInvokedMethodArgumentTypes(inputCall))))
				.build(context);
		}

		@Override
		protected InsnList afterNativeInputCall(MethodContext context, MethodInsnNode inputCall) {
			if (returnsResult(inputCall)) {
				return Sequence.start()
					.then(new MemoizeBoxed("returnValue", Type.getReturnType(inputCall.desc)))
					.then(new InvokeVirtual(SnapshotManager.class, "inputArguments", int.class, Object[].class)
						.withBase(new GetStatic(SnapshotManager.class, "MANAGER"))
						.withArgument(0, new Recall("inputId"))
						.withArgument(1, new Recall("arguments")))
					.then(new InvokeVirtual(SnapshotManager.class, "inputResult", int.class, Object.class)
						.withBase(new GetStatic(SnapshotManager.class, "MANAGER"))
						.withArgument(0, new Recall("inputId"))
						.withArgument(1, new Recall("returnValue")))
					.build(context);
			} else {
				return Sequence.start()
					.then(new InvokeVirtual(SnapshotManager.class, "inputArguments", int.class, Object[].class)
						.withBase(new GetStatic(SnapshotManager.class, "MANAGER"))
						.withArgument(0, new Recall("inputId"))
						.withArgument(1, new Recall("arguments")))
					.then(new InvokeVirtual(SnapshotManager.class, "inputVoidResult", int.class)
						.withBase(new GetStatic(SnapshotManager.class, "MANAGER"))
						.withArgument(0, new Recall("inputId")))
					.build(context);
			}
		}

		@Override
		protected InsnList beforeNativeOutputCall(MethodContext context, MethodInsnNode outputCall) {
			return Sequence.start()
				.then(new CaptureCall(outputCall, "base", "arguments"))
				.then(new Assign("outputId", Type.INT_TYPE)
					.value(
						new InvokeVirtual(SnapshotManager.class, "outputVariables", Object.class, String.class, java.lang.reflect.Type.class, java.lang.reflect.Type[].class)
							.withBase(new GetStatic(SnapshotManager.class, "MANAGER"))
							.withArgument(0, new Recall("base"))
							.withArgument(1, new GetInvokedMethodName(outputCall))
							.withArgument(2, new GetInvokedMethodResultType(outputCall))
							.withArgument(3, new GetInvokedMethodArgumentTypes(outputCall))))
				.then(new InvokeVirtual(SnapshotManager.class, "outputArguments", int.class, Object[].class)
					.withBase(new GetStatic(SnapshotManager.class, "MANAGER"))
					.withArgument(0, new Recall("outputId"))
					.withArgument(1, new Recall("arguments")))
				.build(context);
		}

		@Override
		protected InsnList afterNativeOutputCall(MethodContext context, MethodInsnNode outputCall) {
			if (returnsResult(outputCall)) {
				return Sequence.start()
					.then(new MemoizeBoxed("returnValue", Type.getReturnType(outputCall.desc)))
					.then(new InvokeVirtual(SnapshotManager.class, "outputResult", int.class, Object.class)
						.withBase(new GetStatic(SnapshotManager.class, "MANAGER"))
						.withArgument(0, new Recall("outputId"))
						.withArgument(1, new Recall("returnValue")))
					.build(context);
			} else {
				return Sequence.start()
					.then(new InvokeVirtual(SnapshotManager.class, "outputVoidResult", int.class)
						.withBase(new GetStatic(SnapshotManager.class, "MANAGER"))
						.withArgument(0, new Recall("outputId")))
					.build(context);
			}
		}

	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy