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

com.topdesk.timetransformer.agent.TimeInstrumentationTransformer Maven / Gradle / Ivy

package com.topdesk.timetransformer.agent;

import static org.objectweb.asm.Opcodes.ACC_NATIVE;
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.ARETURN;
import static org.objectweb.asm.Opcodes.ASM9;
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;

/**
 * Bytecode transformer to 
 */
public class TimeInstrumentationTransformer implements ClassFileTransformer {
	private static final String TIME_TRANSFORMER_PACKAGE = "com/topdesk/timetransformer/";
	private static final String TIME_TRANSFORMER_CLASS = TIME_TRANSFORMER_PACKAGE + "TimeTransformer";
	private static final String DO_NOT_INSTRUMENT = "L" + TIME_TRANSFORMER_PACKAGE + "agent/DoNotInstrument;";
	
	@Override
	public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
		ClassReader classReader = new ClassReader(classfileBuffer);
		ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
		ClassInstrumenter classVisitor = new ClassInstrumenter(classWriter, className);
		
		try {
			classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES);
		} catch (Throwable t) {
			// Try catch block, because ASM sometimes fails silently...
			t.printStackTrace();
		}
		return classWriter.toByteArray();
	}
	
	private static class ClassInstrumenter extends ClassVisitor {
		private final String className;
		private boolean instrument = true;
		
		ClassInstrumenter(ClassVisitor classVisitor, String className) {
			super(ASM9, classVisitor);
			this.className = className;
		}
		
		@Override
		public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
			if (DO_NOT_INSTRUMENT.equals(desc)) {
				instrument = false;
			}
			return super.visitAnnotation(desc, visible);
		}
		
		@Override
		public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
			MethodVisitor methodVisitor = cv.visitMethod(access, name, desc, signature, exceptions);
			if (!instrument || (access & ACC_NATIVE) != 0) {
				return methodVisitor;
			}
			if ("java/time/Clock$SystemClock".equals(className) && 
					"instant".equals(name) && 
					"()Ljava/time/Instant;".equals(desc)) {
				return new SystemClockMethodInstrumenter(methodVisitor);
			}
			return new MethodInstrumenter(methodVisitor);
		}
	}
	
	/**
	 * MethodVisitor that will rewrite any call to {@link java.lang.System#currentTimeMillis()} and {@link java.lang.System#nanoTime()} to use the TimeTransformer instead
	 */
	private static class MethodInstrumenter extends MethodVisitor {
		MethodInstrumenter(MethodVisitor methodVisitor) {
			super(ASM9, methodVisitor);
		}
		
		@Override
		public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
			if ((owner.equals("java/lang/System") && name.equals("currentTimeMillis") && desc.equals("()J")) ||
					(owner.equals("java/lang/System") && name.equals("nanoTime") && desc.equals("()J"))) {
				mv.visitMethodInsn(opcode, TIME_TRANSFORMER_CLASS, name, desc, itf);
			}
			else {
				super.visitMethodInsn(opcode, owner, name, desc, itf);
			}
		}
	}
	
	/**
	 * MethodVisitor to transform the {@link java.time.Clock$SystemClock#instant()} method to its Java 8 implementation, such that its time is correctly transformed by the TimeTransformer.
	 * 
	 * Java 9 changed the implementation to achieve a higher resolution than System.currentTimeMillis. It calls another native method, so was not instrumented by the TimeTransformer.
	 */
	private static class SystemClockMethodInstrumenter extends MethodVisitor {
		private final MethodVisitor target;
		
		SystemClockMethodInstrumenter(MethodVisitor methodVisitor) {
			super(ASM9, null);
			target = methodVisitor;
		}
		
		@Override
		public void visitCode() {
			target.visitCode();
			Label label0 = new Label();
			target.visitLabel(label0);
			target.visitVarInsn(ALOAD, 0);
			target.visitMethodInsn(INVOKEVIRTUAL, "java/time/Clock$SystemClock", "millis", "()J", false);
			target.visitMethodInsn(INVOKESTATIC, "java/time/Instant", "ofEpochMilli", "(J)Ljava/time/Instant;", false);
			target.visitInsn(ARETURN);
			Label label1 = new Label();
			target.visitLabel(label1);
			target.visitLocalVariable("this", "Ljava/time/Clock$SystemClock;", null, label0, label1, 0);
			target.visitMaxs(2, 1);
			target.visitEnd();
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy