diff --git a/ulyp-agent-core/src/main/java/com/ulyp/agent/AgentDataWriter.java b/ulyp-agent-core/src/main/java/com/ulyp/agent/AgentDataWriter.java index 5e2327b7..e5581b3d 100644 --- a/ulyp-agent-core/src/main/java/com/ulyp/agent/AgentDataWriter.java +++ b/ulyp-agent-core/src/main/java/com/ulyp/agent/AgentDataWriter.java @@ -23,7 +23,6 @@ public class AgentDataWriter { @Getter private final MethodRepository methodRepository; private final AtomicInteger lastIndexOfMethodWritten = new AtomicInteger(-1); - private final AtomicInteger lastIndexOfMethodToRecordWritten = new AtomicInteger(-1); private final AtomicInteger lastIndexOfTypeWritten = new AtomicInteger(-1); public AgentDataWriter(RecordingDataWriter recordingDataWriter, MethodRepository methodRepository) { @@ -59,31 +58,6 @@ public void write(TypeResolver typeResolver, RecordingMetadata recordingMetadata } } - methodsList = new SerializedMethodList(); - methods = methodRepository.getRecordingStartMethods(); - upToExcluding = methods.size() - 1; - startFrom = lastIndexOfMethodToRecordWritten.get() + 1; - - for (int i = startFrom; i <= upToExcluding; i++) { - Method method = methods.get(i); - log.debug("Will write {} to storage", method); - methodsList.add(method); - } - if (methodsList.size() > 0) { - recordingDataWriter.write(methodsList); - for (;;) { - int currentIndex = lastIndexOfMethodToRecordWritten.get(); - if (currentIndex < upToExcluding) { - if (lastIndexOfMethodToRecordWritten.compareAndSet(currentIndex, upToExcluding)) { - break; - } - } else { - // Someone else must have written methods already - break; - } - } - } - SerializedTypeList typesList = new SerializedTypeList(); ConcurrentArrayList types = typeResolver.values(); upToExcluding = types.size() - 1; diff --git a/ulyp-agent-core/src/main/java/com/ulyp/agent/Recorder.java b/ulyp-agent-core/src/main/java/com/ulyp/agent/Recorder.java index eedd1066..e5103242 100644 --- a/ulyp-agent-core/src/main/java/com/ulyp/agent/Recorder.java +++ b/ulyp-agent-core/src/main/java/com/ulyp/agent/Recorder.java @@ -149,6 +149,10 @@ public int onConstructorEnter(RecordingState recordingState, int methodId, Objec return onMethodEnter(recordingState, methodId, null, args); } + public int onMethodEnter(int methodId, @Nullable Object callee) { + return onMethodEnter(threadLocalRecordingState.get(), methodId, callee, null); + } + public int onMethodEnter(int methodId, @Nullable Object callee, Object[] args) { return onMethodEnter(threadLocalRecordingState.get(), methodId, callee, args); } diff --git a/ulyp-agent-core/src/main/java/com/ulyp/agent/Settings.java b/ulyp-agent-core/src/main/java/com/ulyp/agent/Settings.java index 5d8d1928..1cb5c337 100644 --- a/ulyp-agent-core/src/main/java/com/ulyp/agent/Settings.java +++ b/ulyp-agent-core/src/main/java/com/ulyp/agent/Settings.java @@ -1,19 +1,18 @@ package com.ulyp.agent; +import com.ulyp.core.ProcessMetadata; import com.ulyp.core.recorders.collections.CollectionsRecordingMode; +import com.ulyp.core.util.MethodMatcher; import com.ulyp.core.util.TypeMatcher; import com.ulyp.core.util.CommaSeparatedList; import com.ulyp.core.util.PackageList; -import com.ulyp.storage.writer.RecordingDataWriter; import lombok.Getter; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.nio.file.Paths; import java.util.List; import java.util.Optional; import java.util.Set; -import java.util.function.Supplier; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -121,6 +120,11 @@ public static Settings fromSystemProperties() { String methodsToRecordRaw = System.getProperty(START_RECORDING_METHODS_PROPERTY, ""); String excludeMethodsToRecordRaw = System.getProperty(EXCLUDE_RECORDING_METHODS_PROPERTY, ""); StartRecordingMethods recordingStartMethods = StartRecordingMethods.parse(methodsToRecordRaw, excludeMethodsToRecordRaw); + if (recordingStartMethods.isEmpty()) { + recordingStartMethods = StartRecordingMethods.of( + new MethodMatcher(TypeMatcher.parse(ProcessMetadata.getMainClassNameFromProp()), "main") + ); + } String recordingDataFilePath = System.getProperty(FILE_PATH_PROPERTY); if (recordingDataFilePath == null) { diff --git a/ulyp-agent-core/src/main/java/com/ulyp/agent/queue/RecordingEventProcessor.java b/ulyp-agent-core/src/main/java/com/ulyp/agent/queue/RecordingEventProcessor.java index 3372108c..cd8c32bc 100644 --- a/ulyp-agent-core/src/main/java/com/ulyp/agent/queue/RecordingEventProcessor.java +++ b/ulyp-agent-core/src/main/java/com/ulyp/agent/queue/RecordingEventProcessor.java @@ -58,7 +58,7 @@ void onEnterCallRecord(EnterRecordQueueEvent enterRecord) { buffer.recordMethodEnter( enterRecord.getCallId(), typeResolver, - /* TODO remove after advice split */methodRepository.get(enterRecord.getMethodId()).getId(), + enterRecord.getMethodId(), cachedQueuedIdentityCallee, enterRecord.getArgs(), nanoTime diff --git a/ulyp-agent-core/src/main/java/com/ulyp/agent/queue/RecordingQueue.java b/ulyp-agent-core/src/main/java/com/ulyp/agent/queue/RecordingQueue.java index 1d37e399..6ad2c660 100644 --- a/ulyp-agent-core/src/main/java/com/ulyp/agent/queue/RecordingQueue.java +++ b/ulyp-agent-core/src/main/java/com/ulyp/agent/queue/RecordingQueue.java @@ -128,6 +128,9 @@ public void enqueueMethodExit(int recordingId, int callId, Object returnValue, b } private Object[] convert(Object[] args) { + if (args == null) { + return null; + } for (int i = 0; i < args.length; i++) { args[i] = convert(args[i]); } diff --git a/ulyp-agent-core/src/main/java/com/ulyp/agent/queue/disruptor/EventHandlerGroup.java b/ulyp-agent-core/src/main/java/com/ulyp/agent/queue/disruptor/EventHandlerGroup.java index 312503c4..ecfff92d 100644 --- a/ulyp-agent-core/src/main/java/com/ulyp/agent/queue/disruptor/EventHandlerGroup.java +++ b/ulyp-agent-core/src/main/java/com/ulyp/agent/queue/disruptor/EventHandlerGroup.java @@ -1,5 +1,6 @@ package com.ulyp.agent.queue.disruptor; +import com.lmax.disruptor.EventHandler; import com.lmax.disruptor.EventProcessor; import com.lmax.disruptor.Sequence; import com.lmax.disruptor.WorkHandler; diff --git a/ulyp-agent/src/main/java/com/ulyp/agent/Agent.java b/ulyp-agent/src/main/java/com/ulyp/agent/Agent.java index e7fb2289..98ea0e6a 100644 --- a/ulyp-agent/src/main/java/com/ulyp/agent/Agent.java +++ b/ulyp-agent/src/main/java/com/ulyp/agent/Agent.java @@ -1,171 +1,203 @@ -package com.ulyp.agent; - -import java.lang.instrument.Instrumentation; -import java.util.Optional; - -import com.ulyp.agent.util.ByteBuddyTypeConverter; -import com.ulyp.agent.util.ErrorLoggingInstrumentationListener; -import com.ulyp.core.ProcessMetadata; -import com.ulyp.core.recorders.ObjectRecorderRegistry; -import com.ulyp.core.recorders.PrintingRecorder; -import com.ulyp.core.recorders.collections.CollectionRecorder; -import com.ulyp.core.recorders.collections.MapRecorder; -import com.ulyp.core.util.TypeMatcher; -import com.ulyp.core.util.LoggingSettings; -import com.ulyp.core.util.MethodMatcher; -import com.ulyp.core.util.PackageList; - -import net.bytebuddy.agent.builder.AgentBuilder; -import net.bytebuddy.asm.Advice; -import net.bytebuddy.asm.AsmVisitorWrapper; -import net.bytebuddy.description.method.MethodDescription; -import net.bytebuddy.description.type.TypeDescription; -import net.bytebuddy.matcher.ElementMatcher; -import net.bytebuddy.matcher.ElementMatchers; - -/** - * The agent entry point which is invoked by JVM itself - */ -public class Agent { - - private static final String ULYP_LOGO = - " __ __ __ __ __ ____ \n" + - " / / / / / / \\ \\/ / / __ \\\n" + - " / / / / / / \\ / / /_/ /\n" + - "/ /_/ / / /___ / / / ____/ \n" + - "\\____/ /_____//_/ /_/ \n" + - " "; - - public static void start(String args, Instrumentation instrumentation) { - - Settings settings = Settings.fromSystemProperties(); - if (!settings.isAgentEnabled()) { - System.out.println("ULYP agent disabled, no code will be instrumented"); - return; - } - - // Touch first and initialize shadowed slf4j - String logLevel = LoggingSettings.getLoggingLevel(); - - if (AgentContext.isLoaded()) { - return; - } else { - AgentContext.init(); - } - AgentContext context = AgentContext.getCtx(); - - StartRecordingMethods startRecordingMethods = settings.getRecordMethodList(); - - if (startRecordingMethods.isEmpty()) { - startRecordingMethods = StartRecordingMethods.of( - new MethodMatcher(TypeMatcher.parse(ProcessMetadata.getMainClassNameFromProp()), "main") - ); - } - - System.out.println(ULYP_LOGO); - System.out.println("ULYP agent started, logging level = " + logLevel + ", settings: " + settings); - - CollectionRecorder recorder = (CollectionRecorder) ObjectRecorderRegistry.COLLECTION_RECORDER.getInstance(); - recorder.setMode(settings.getCollectionsRecordingMode()); - - MapRecorder mapRecorder = (MapRecorder) ObjectRecorderRegistry.MAP_RECORDER.getInstance(); - mapRecorder.setMode(settings.getCollectionsRecordingMode()); - - PrintingRecorder toStringRecorder = (PrintingRecorder) (ObjectRecorderRegistry.TO_STRING_RECORDER.getInstance()); - toStringRecorder.addClassesToPrint(settings.getTypesToPrint()); - - ElementMatcher.Junction ignoreMatcher = buildIgnoreMatcher(settings); - ElementMatcher.Junction instrumentationMatcher = buildInstrumentationMatcher(settings); - - MethodIdFactory methodIdFactory = new MethodIdFactory(context.getMethodRepository(), startRecordingMethods); - - AsmVisitorWrapper.ForDeclaredMethods methodCallAdvice = Advice.withCustomMapping() - .bind(methodIdFactory) - .to(MethodCallRecordingAdvice.class) - .on(buildMethodsMatcher(settings)); - - AgentBuilder.Identified.Extendable agentBuilder = new AgentBuilder.Default() - .ignore(ignoreMatcher) - .type(instrumentationMatcher) - .transform((builder, typeDescription, classLoader, module, protectionDomain) -> builder.visit(methodCallAdvice)); - - if (settings.instrumentConstructors()) { - AsmVisitorWrapper.ForDeclaredMethods constructorAdvice = Advice.withCustomMapping() - .bind(methodIdFactory) - .to(ConstructorCallRecordingAdvice.class) - .on(ElementMatchers.isConstructor()); - - agentBuilder = agentBuilder.transform((builder, typeDescription, classLoader, module, protectionDomain) -> builder.visit(constructorAdvice)); - } - - AgentBuilder agent = agentBuilder.with(AgentBuilder.TypeStrategy.Default.REDEFINE); - if (settings.instrumentLambdas()) { - agent = agent.with(AgentBuilder.LambdaInstrumentationStrategy.ENABLED); - } - - if (LoggingSettings.TRACE_ENABLED) { - agent = agent.with(AgentBuilder.Listener.StreamWriting.toSystemOut()); - } else { - agent = agent.with(new ErrorLoggingInstrumentationListener()); - } - - agent.installOn(instrumentation); - } - - private static ElementMatcher.Junction buildMethodsMatcher(Settings settings) { - ElementMatcher.Junction methodMatcher = ElementMatchers.isMethod() - .and(ElementMatchers.not(ElementMatchers.isAbstract())) - .and(ElementMatchers.not(ElementMatchers.isConstructor())); - - if (settings.instrumentTypeInitializers()) { - return methodMatcher.or(ElementMatchers.isTypeInitializer()); - } else { - return methodMatcher; - } - } - - private static ElementMatcher.Junction buildInstrumentationMatcher(Settings settings) { - PackageList instrumentatedPackages = settings.getInstrumentatedPackages(); - ElementMatcher.Junction instrumentationMatcher = null; - - for (String packageToInstrument : instrumentatedPackages) { - if (instrumentationMatcher == null) { - instrumentationMatcher = ElementMatchers.nameStartsWith(packageToInstrument); - } else { - instrumentationMatcher = instrumentationMatcher.or(ElementMatchers.nameStartsWith(packageToInstrument)); - } - } - - return Optional.ofNullable(instrumentationMatcher).orElse(ElementMatchers.any()); - } - - private static ElementMatcher.Junction buildIgnoreMatcher(Settings settings) { - PackageList excludedPackages = settings.getExcludedFromInstrumentationPackages(); - - ElementMatcher.Junction ignoreMatcher = ElementMatchers.nameStartsWith("java.") - .or(ElementMatchers.nameStartsWith("javax.")) - .or(ElementMatchers.nameStartsWith("jdk.")) - .or(ElementMatchers.nameStartsWith("sun")) - .or(ElementMatchers.nameStartsWith("shadowed")) - .or(ElementMatchers.nameStartsWith("com.sun")) - .or(ElementMatchers.nameStartsWith("com.ulyp")); - - ElementMatcher.Junction instrumentationMatcher = buildInstrumentationMatcher(settings); - if (instrumentationMatcher != ElementMatchers.any()) { - ignoreMatcher = ElementMatchers.not(instrumentationMatcher).and(ignoreMatcher); - } - - for (String excludedPackage : excludedPackages) { - ignoreMatcher = ignoreMatcher.or(ElementMatchers.nameStartsWith(excludedPackage)); - } - - for (TypeMatcher excludeTypeMatcher : settings.getExcludeFromInstrumentationClasses()) { - ByteBuddyTypeConverter typeConverter = ByteBuddyTypeConverter.SUPER_TYPE_DERIVING_INSTANCE; - ignoreMatcher = ignoreMatcher.or( - target -> excludeTypeMatcher.matches(typeConverter.convert(target.asGenericType())) - ); - } - - return ignoreMatcher; - } -} +package com.ulyp.agent; + +import java.lang.instrument.Instrumentation; +import java.util.Optional; + +import com.ulyp.agent.advice.*; +import com.ulyp.agent.util.ByteBuddyMethodResolver; +import com.ulyp.agent.util.ByteBuddyTypeConverter; +import com.ulyp.agent.util.ErrorLoggingInstrumentationListener; +import com.ulyp.core.recorders.ObjectRecorderRegistry; +import com.ulyp.core.recorders.PrintingRecorder; +import com.ulyp.core.recorders.collections.CollectionRecorder; +import com.ulyp.core.recorders.collections.MapRecorder; +import com.ulyp.core.util.TypeMatcher; +import com.ulyp.core.util.LoggingSettings; +import com.ulyp.core.util.PackageList; + +import net.bytebuddy.agent.builder.AgentBuilder; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.asm.AsmVisitorWrapper; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.dynamic.DynamicType; +import net.bytebuddy.matcher.ElementMatcher; +import net.bytebuddy.matcher.ElementMatchers; + +/** + * The agent entry point which is invoked by JVM itself + */ +public class Agent { + + private static final String ULYP_LOGO = + " __ __ __ __ __ ____ \n" + + " / / / / / / \\ \\/ / / __ \\\n" + + " / / / / / / \\ / / /_/ /\n" + + "/ /_/ / / /___ / / / ____/ \n" + + "\\____/ /_____//_/ /_/ \n" + + " "; + + public static void start(String args, Instrumentation instrumentation) { + + Settings settings = Settings.fromSystemProperties(); + if (!settings.isAgentEnabled()) { + System.out.println("ULYP agent disabled, no code will be instrumented"); + return; + } + + // Touch first and initialize shadowed slf4j + String logLevel = LoggingSettings.getLoggingLevel(); + + if (AgentContext.isLoaded()) { + return; + } else { + AgentContext.init(); + } + AgentContext context = AgentContext.getCtx(); + StartRecordingMethods startRecordingMethods = settings.getRecordMethodList(); + + System.out.println(ULYP_LOGO); + System.out.println("ULYP agent started, logging level = " + logLevel + ", settings: " + settings); + + CollectionRecorder recorder = (CollectionRecorder) ObjectRecorderRegistry.COLLECTION_RECORDER.getInstance(); + recorder.setMode(settings.getCollectionsRecordingMode()); + + MapRecorder mapRecorder = (MapRecorder) ObjectRecorderRegistry.MAP_RECORDER.getInstance(); + mapRecorder.setMode(settings.getCollectionsRecordingMode()); + + PrintingRecorder toStringRecorder = (PrintingRecorder) (ObjectRecorderRegistry.TO_STRING_RECORDER.getInstance()); + toStringRecorder.addClassesToPrint(settings.getTypesToPrint()); + + ElementMatcher.Junction ignoreMatcher = buildIgnoreMatcher(settings); + ElementMatcher.Junction instrumentationMatcher = buildInstrumentationMatcher(settings); + + MethodIdFactory methodIdFactory = new MethodIdFactory(context.getMethodRepository()); + + AsmVisitorWrapper.ForDeclaredMethods startRecordingMethodAdvice = Advice.withCustomMapping() + .bind(methodIdFactory) + .to(StartRecordingMethodAdvice.class) + .on(buildStartRecordingMethodsMatcher(settings)); + AsmVisitorWrapper.ForDeclaredMethods methodCallAdvice = Advice.withCustomMapping() + .bind(methodIdFactory) + .to(MethodAdvice.class) + .on(buildContinueRecordingMethodsMatcher(settings)); +/* AsmVisitorWrapper.ForDeclaredMethods methodCallAdvice0Args = Advice.withCustomMapping() + .bind(methodIdFactory) + .to(MethodAdvice0Args.class) + .on(buildContinueRecordingMethodsMatcher(settings).and(ElementMatchers.takesNoArguments()));*/ + AsmVisitorWrapper.ForDeclaredMethods startRecordingConstructorAdvice = Advice.withCustomMapping() + .bind(methodIdFactory) + .to(StartRecordingConstructorAdvice.class) + .on(buildStartRecordingConstructorMatcher(settings)); + AsmVisitorWrapper.ForDeclaredMethods constructorAdvice = Advice.withCustomMapping() + .bind(methodIdFactory) + .to(ConstructorAdvice.class) + .on(buildContinueRecordingConstructorMatcher(settings)); + + AgentBuilder.Identified.Extendable agentBuilder = new AgentBuilder.Default() + .ignore(ignoreMatcher) + .type(instrumentationMatcher) + .transform((builder, typeDescription, classLoader, module, protectionDomain) -> { + DynamicType.Builder visitor = builder.visit(startRecordingMethodAdvice).visit(methodCallAdvice); + if (settings.instrumentConstructors()) { + visitor = visitor.visit(startRecordingConstructorAdvice).visit(constructorAdvice); + } + return visitor; + }); + + AgentBuilder agent = agentBuilder.with(AgentBuilder.TypeStrategy.Default.REDEFINE); + if (settings.instrumentLambdas()) { + agent = agent.with(AgentBuilder.LambdaInstrumentationStrategy.ENABLED); + } + + if (LoggingSettings.TRACE_ENABLED) { + agent = agent.with(AgentBuilder.Listener.StreamWriting.toSystemOut()); + } else { + agent = agent.with(new ErrorLoggingInstrumentationListener()); + } + + agent.installOn(instrumentation); + } + + // TODO reduce convertions to domain model + private static ElementMatcher.Junction buildStartRecordingConstructorMatcher(Settings settings) { + return ElementMatchers.isConstructor().and( + methodDescription -> settings.getRecordMethodList().shouldStartRecording(ByteBuddyMethodResolver.INSTANCE.resolve(methodDescription)) + ); + } + + private static ElementMatcher.Junction buildContinueRecordingConstructorMatcher(Settings settings) { + return ElementMatchers.isConstructor().and( + methodDescription -> !settings.getRecordMethodList().shouldStartRecording(ByteBuddyMethodResolver.INSTANCE.resolve(methodDescription)) + ); + } + + private static ElementMatcher.Junction buildStartRecordingMethodsMatcher(Settings settings) { + return basicMethodsMatcher(settings).and( + methodDescription -> settings.getRecordMethodList().shouldStartRecording(ByteBuddyMethodResolver.INSTANCE.resolve(methodDescription)) + ); + } + + private static ElementMatcher.Junction buildContinueRecordingMethodsMatcher(Settings settings) { + return basicMethodsMatcher(settings).and( + methodDescription -> !settings.getRecordMethodList().shouldStartRecording(ByteBuddyMethodResolver.INSTANCE.resolve(methodDescription)) + ); + } + + private static ElementMatcher.Junction basicMethodsMatcher(Settings settings) { + ElementMatcher.Junction methodMatcher = ElementMatchers.isMethod() + .and(ElementMatchers.not(ElementMatchers.isAbstract())) + .and(ElementMatchers.not(ElementMatchers.isConstructor())); + + if (settings.instrumentTypeInitializers()) { + return methodMatcher.or(ElementMatchers.isTypeInitializer()); + } else { + return methodMatcher; + } + } + + private static ElementMatcher.Junction buildInstrumentationMatcher(Settings settings) { + PackageList instrumentatedPackages = settings.getInstrumentatedPackages(); + ElementMatcher.Junction instrumentationMatcher = null; + + for (String packageToInstrument : instrumentatedPackages) { + if (instrumentationMatcher == null) { + instrumentationMatcher = ElementMatchers.nameStartsWith(packageToInstrument); + } else { + instrumentationMatcher = instrumentationMatcher.or(ElementMatchers.nameStartsWith(packageToInstrument)); + } + } + + return Optional.ofNullable(instrumentationMatcher).orElse(ElementMatchers.any()); + } + + private static ElementMatcher.Junction buildIgnoreMatcher(Settings settings) { + PackageList excludedPackages = settings.getExcludedFromInstrumentationPackages(); + + ElementMatcher.Junction ignoreMatcher = ElementMatchers.nameStartsWith("java.") + .or(ElementMatchers.nameStartsWith("javax.")) + .or(ElementMatchers.nameStartsWith("jdk.")) + .or(ElementMatchers.nameStartsWith("sun")) + .or(ElementMatchers.nameStartsWith("shadowed")) + .or(ElementMatchers.nameStartsWith("com.sun")) + .or(ElementMatchers.nameStartsWith("com.ulyp")); + + ElementMatcher.Junction instrumentationMatcher = buildInstrumentationMatcher(settings); + if (instrumentationMatcher != ElementMatchers.any()) { + ignoreMatcher = ElementMatchers.not(instrumentationMatcher).and(ignoreMatcher); + } + + for (String excludedPackage : excludedPackages) { + ignoreMatcher = ignoreMatcher.or(ElementMatchers.nameStartsWith(excludedPackage)); + } + + for (TypeMatcher excludeTypeMatcher : settings.getExcludeFromInstrumentationClasses()) { + ByteBuddyTypeConverter typeConverter = ByteBuddyTypeConverter.SUPER_TYPE_DERIVING_INSTANCE; + ignoreMatcher = ignoreMatcher.or( + target -> excludeTypeMatcher.matches(typeConverter.convert(target.asGenericType())) + ); + } + + return ignoreMatcher; + } +} diff --git a/ulyp-agent/src/main/java/com/ulyp/agent/MethodIdFactory.java b/ulyp-agent/src/main/java/com/ulyp/agent/MethodIdFactory.java index d68f4d0b..8d4280c7 100644 --- a/ulyp-agent/src/main/java/com/ulyp/agent/MethodIdFactory.java +++ b/ulyp-agent/src/main/java/com/ulyp/agent/MethodIdFactory.java @@ -1,5 +1,7 @@ package com.ulyp.agent; +import com.ulyp.agent.advice.ConstructorAdvice; +import com.ulyp.agent.advice.MethodAdvice; import com.ulyp.agent.util.ByteBuddyMethodResolver; import com.ulyp.agent.util.ByteBuddyTypeConverter; import com.ulyp.core.Method; @@ -13,7 +15,7 @@ /** - * Allows wiring method id into advice classes {@link ConstructorCallRecordingAdvice} and {@link MethodCallRecordingAdvice} + * Allows wiring method id into advice classes {@link ConstructorAdvice} and {@link MethodAdvice} *

* Uses a singleton instance of {@link MethodRepository} to store methods into it. */ @@ -21,12 +23,9 @@ public class MethodIdFactory implements Advice.OffsetMapping.Factory { private final ForMethodIdOffsetMapping instance; - public MethodIdFactory(MethodRepository methodRepository, StartRecordingMethods startRecordingMethods) { - ByteBuddyMethodResolver byteBuddyMethodResolver = new ByteBuddyMethodResolver( - ByteBuddyTypeConverter.INSTANCE, - ByteBuddyTypeConverter.SUPER_TYPE_DERIVING_INSTANCE - ); - this.instance = new ForMethodIdOffsetMapping(byteBuddyMethodResolver, methodRepository, startRecordingMethods); + public MethodIdFactory(MethodRepository methodRepository) { + ByteBuddyMethodResolver byteBuddyMethodResolver = new ByteBuddyMethodResolver(ByteBuddyTypeConverter.SUPER_TYPE_DERIVING_INSTANCE); + this.instance = new ForMethodIdOffsetMapping(byteBuddyMethodResolver, methodRepository); } @Override @@ -55,12 +54,10 @@ static class ForMethodIdOffsetMapping implements Advice.OffsetMapping { private final MethodRepository methodRepository; private final ThreadLocal lastMethod = new ThreadLocal<>(); private final ByteBuddyMethodResolver byteBuddyMethodResolver; - private final StartRecordingMethods startRecordingMethods; - ForMethodIdOffsetMapping(ByteBuddyMethodResolver byteBuddyMethodResolver, MethodRepository methodRepository, StartRecordingMethods startRecordingMethods) { + ForMethodIdOffsetMapping(ByteBuddyMethodResolver byteBuddyMethodResolver, MethodRepository methodRepository) { this.byteBuddyMethodResolver = byteBuddyMethodResolver; this.methodRepository = methodRepository; - this.startRecordingMethods = startRecordingMethods; } public Target resolve(TypeDescription instrumentedType, @@ -80,9 +77,6 @@ public Target resolve(TypeDescription instrumentedType, id = idMapping.methodId; } else { Method method = byteBuddyMethodResolver.resolve(instrumentedMethod); - if (startRecordingMethods.shouldStartRecording(method)) { - method.setShouldStartRecording(true); - } id = methodRepository.putAndGetId(method); lastMethod.set(new IdMapping(instrumentedMethod, id)); } diff --git a/ulyp-agent/src/main/java/com/ulyp/agent/ConstructorCallRecordingAdvice.java b/ulyp-agent/src/main/java/com/ulyp/agent/advice/ConstructorAdvice.java similarity index 70% rename from ulyp-agent/src/main/java/com/ulyp/agent/ConstructorCallRecordingAdvice.java rename to ulyp-agent/src/main/java/com/ulyp/agent/advice/ConstructorAdvice.java index 4f9eb319..5de3a9f4 100644 --- a/ulyp-agent/src/main/java/com/ulyp/agent/ConstructorCallRecordingAdvice.java +++ b/ulyp-agent/src/main/java/com/ulyp/agent/advice/ConstructorAdvice.java @@ -1,6 +1,9 @@ -package com.ulyp.agent; +package com.ulyp.agent.advice; -import com.ulyp.core.MethodRepository; +import com.ulyp.agent.MethodId; +import com.ulyp.agent.MethodIdFactory; +import com.ulyp.agent.Recorder; +import com.ulyp.agent.RecorderInstance; import net.bytebuddy.asm.Advice; @@ -8,7 +11,7 @@ * Advice which instructs how to instrument constructors. The byte buddy library copies the bytecode of methods into * constructors being instrumented. */ -public class ConstructorCallRecordingAdvice { +public class ConstructorAdvice { /** * @param methodId injected right into bytecode unique method id. Mapping is made by @@ -20,14 +23,9 @@ static void enter( @Advice.Local("callId") int callId, @MethodId int methodId, @Advice.AllArguments Object[] arguments) { - // This if check is ugly, but the code is wired into bytecode, so it's more efficient to check right away instead of calling a method - if (methodId >= MethodRepository.RECORD_METHODS_MIN_ID) { - callId = RecorderInstance.instance.startOrContinueRecordingOnConstructorEnter(methodId, arguments); - } else { - if (Recorder.currentRecordingSessionCount.get() > 0 && RecorderInstance.instance.recordingIsActiveInCurrentThread()) { - callId = RecorderInstance.instance.onConstructorEnter(methodId, arguments); - } + if (Recorder.currentRecordingSessionCount.get() > 0 && RecorderInstance.instance.recordingIsActiveInCurrentThread()) { + callId = RecorderInstance.instance.onConstructorEnter(methodId, arguments); } } diff --git a/ulyp-agent/src/main/java/com/ulyp/agent/MethodCallRecordingAdvice.java b/ulyp-agent/src/main/java/com/ulyp/agent/advice/MethodAdvice.java similarity index 60% rename from ulyp-agent/src/main/java/com/ulyp/agent/MethodCallRecordingAdvice.java rename to ulyp-agent/src/main/java/com/ulyp/agent/advice/MethodAdvice.java index af86917c..aca90294 100644 --- a/ulyp-agent/src/main/java/com/ulyp/agent/MethodCallRecordingAdvice.java +++ b/ulyp-agent/src/main/java/com/ulyp/agent/advice/MethodAdvice.java @@ -1,6 +1,9 @@ -package com.ulyp.agent; +package com.ulyp.agent.advice; -import com.ulyp.core.MethodRepository; +import com.ulyp.agent.MethodId; +import com.ulyp.agent.MethodIdFactory; +import com.ulyp.agent.Recorder; +import com.ulyp.agent.RecorderInstance; import net.bytebuddy.asm.Advice; import net.bytebuddy.implementation.bytecode.assign.Assigner; @@ -9,7 +12,7 @@ * Advice which instructs how to instrument methods. The byte buddy library copies the bytecode of methods into * constructors being instrumented. */ -public class MethodCallRecordingAdvice { +public class MethodAdvice { /** * @param methodId injected right into bytecode unique method id. Mapping is made by @@ -21,18 +24,9 @@ static void enter( @Advice.Local("callId") int callId, @Advice.This(optional = true) Object callee, @Advice.AllArguments Object[] arguments) { - - // This if check is ugly, but the code is wired into bytecode, so it's more efficient to check right away instead of calling a method - if (methodId >= MethodRepository.RECORD_METHODS_MIN_ID) { - - // noinspection UnusedAssignment local variable callId is used by exit() method - callId = RecorderInstance.instance.startOrContinueRecordingOnMethodEnter(methodId, callee, arguments); - } else { - - if (Recorder.currentRecordingSessionCount.get() > 0 && RecorderInstance.instance.recordingIsActiveInCurrentThread()) { - //noinspection UnusedAssignment - callId = RecorderInstance.instance.onMethodEnter(methodId, callee, arguments); - } + if (Recorder.currentRecordingSessionCount.get() > 0 && RecorderInstance.instance.recordingIsActiveInCurrentThread()) { + //noinspection UnusedAssignment + callId = RecorderInstance.instance.onMethodEnter(methodId, callee, arguments); } } diff --git a/ulyp-agent/src/main/java/com/ulyp/agent/advice/MethodAdvice0Args.java b/ulyp-agent/src/main/java/com/ulyp/agent/advice/MethodAdvice0Args.java new file mode 100644 index 00000000..1cefc7d5 --- /dev/null +++ b/ulyp-agent/src/main/java/com/ulyp/agent/advice/MethodAdvice0Args.java @@ -0,0 +1,49 @@ +package com.ulyp.agent.advice; + +import com.ulyp.agent.MethodId; +import com.ulyp.agent.MethodIdFactory; +import com.ulyp.agent.Recorder; +import com.ulyp.agent.RecorderInstance; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.implementation.bytecode.assign.Assigner; + +/** + * Advice which instructs how to instrument methods. The byte buddy library copies the bytecode of methods into + * constructors being instrumented. + */ +public class MethodAdvice0Args { + + /** + * @param methodId injected right into bytecode unique method id. Mapping is made by + * {@link MethodIdFactory} class. + */ + @Advice.OnMethodEnter + static void enter( + @MethodId int methodId, + @Advice.Local("callId") int callId, + @Advice.This(optional = true) Object callee) { + if (Recorder.currentRecordingSessionCount.get() > 0 && RecorderInstance.instance.recordingIsActiveInCurrentThread()) { + //noinspection UnusedAssignment + if (true) { + throw new RuntimeException("ASD"); + } + callId = RecorderInstance.instance.onMethodEnter(methodId, callee); + } + } + + /** + * @param methodId injected right into bytecode unique method id. Mapping is made by + * {@link MethodIdFactory} class. Guaranteed to be the same + * as for enter advice + */ + @Advice.OnMethodExit(onThrowable = Throwable.class) + static void exit( + @MethodId int methodId, + @Advice.Local("callId") int callId, + @Advice.Thrown Throwable throwable, + @Advice.Return(typing = Assigner.Typing.DYNAMIC) Object returnValue) { + if (callId > 0) { + RecorderInstance.instance.onMethodExit(methodId, returnValue, throwable, callId); + } + } +} diff --git a/ulyp-agent/src/main/java/com/ulyp/agent/advice/StartRecordingConstructorAdvice.java b/ulyp-agent/src/main/java/com/ulyp/agent/advice/StartRecordingConstructorAdvice.java new file mode 100644 index 00000000..204177f5 --- /dev/null +++ b/ulyp-agent/src/main/java/com/ulyp/agent/advice/StartRecordingConstructorAdvice.java @@ -0,0 +1,42 @@ +package com.ulyp.agent.advice; + +import com.ulyp.agent.MethodId; +import com.ulyp.agent.MethodIdFactory; +import com.ulyp.agent.RecorderInstance; + +import net.bytebuddy.asm.Advice; + +/** + * Advice which instructs how to instrument constructors. The byte buddy library copies the bytecode of methods into + * constructors being instrumented. + */ +public class StartRecordingConstructorAdvice { + + /** + * @param methodId injected right into bytecode unique method id. Mapping is made by + * {@link MethodIdFactory} class. + */ + @SuppressWarnings("UnusedAssignment") + @Advice.OnMethodEnter + static void enter( + @Advice.Local("callId") int callId, + @MethodId int methodId, + @Advice.AllArguments Object[] arguments) { + callId = RecorderInstance.instance.startOrContinueRecordingOnConstructorEnter(methodId, arguments); + } + + /** + * @param methodId injected right into bytecode unique method id. Mapping is made by + * {@link MethodIdFactory} class. Guaranteed to be the same + * as for enter advice + */ + @Advice.OnMethodExit + static void exit( + @Advice.Local("callId") int callId, + @MethodId int methodId, + @Advice.This Object returnValue) { + if (callId > 0) { + RecorderInstance.instance.onConstructorExit(methodId, returnValue, callId); + } + } +} diff --git a/ulyp-agent/src/main/java/com/ulyp/agent/advice/StartRecordingMethodAdvice.java b/ulyp-agent/src/main/java/com/ulyp/agent/advice/StartRecordingMethodAdvice.java new file mode 100644 index 00000000..f11a791a --- /dev/null +++ b/ulyp-agent/src/main/java/com/ulyp/agent/advice/StartRecordingMethodAdvice.java @@ -0,0 +1,45 @@ +package com.ulyp.agent.advice; + +import com.ulyp.agent.MethodId; +import com.ulyp.agent.MethodIdFactory; +import com.ulyp.agent.RecorderInstance; + +import net.bytebuddy.asm.Advice; +import net.bytebuddy.implementation.bytecode.assign.Assigner; + +/** + * Advice which instructs how to instrument methods. The byte buddy library copies the bytecode of methods into + * constructors being instrumented. + */ +public class StartRecordingMethodAdvice { + + /** + * @param methodId injected right into bytecode unique method id. Mapping is made by + * {@link MethodIdFactory} class. + */ + @Advice.OnMethodEnter + static void enter( + @MethodId int methodId, + @Advice.Local("callId") int callId, + @Advice.This(optional = true) Object callee, + @Advice.AllArguments Object[] arguments) { + // noinspection UnusedAssignment local variable callId is used by exit() method + callId = RecorderInstance.instance.startOrContinueRecordingOnMethodEnter(methodId, callee, arguments); + } + + /** + * @param methodId injected right into bytecode unique method id. Mapping is made by + * {@link MethodIdFactory} class. Guaranteed to be the same + * as for enter advice + */ + @Advice.OnMethodExit(onThrowable = Throwable.class) + static void exit( + @MethodId int methodId, + @Advice.Local("callId") int callId, + @Advice.Thrown Throwable throwable, + @Advice.Return(typing = Assigner.Typing.DYNAMIC) Object returnValue) { + if (callId > 0) { + RecorderInstance.instance.onMethodExit(methodId, returnValue, throwable, callId); + } + } +} diff --git a/ulyp-agent/src/main/java/com/ulyp/agent/matchers/ContinueRecordingMethodMatcher.java b/ulyp-agent/src/main/java/com/ulyp/agent/matchers/ContinueRecordingMethodMatcher.java new file mode 100644 index 00000000..81f220ee --- /dev/null +++ b/ulyp-agent/src/main/java/com/ulyp/agent/matchers/ContinueRecordingMethodMatcher.java @@ -0,0 +1,14 @@ +package com.ulyp.agent.matchers; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class ContinueRecordingMethodMatcher implements ElementMatcher { + + + + @Override + public boolean matches(MethodDescription methodDescription) { + return false; + } +} diff --git a/ulyp-agent/src/main/java/com/ulyp/agent/util/ByteBuddyMethodResolver.java b/ulyp-agent/src/main/java/com/ulyp/agent/util/ByteBuddyMethodResolver.java index 9b4bbae9..20c46ded 100644 --- a/ulyp-agent/src/main/java/com/ulyp/agent/util/ByteBuddyMethodResolver.java +++ b/ulyp-agent/src/main/java/com/ulyp/agent/util/ByteBuddyMethodResolver.java @@ -2,18 +2,12 @@ import com.ulyp.core.Method; import com.ulyp.core.Type; -import com.ulyp.core.recorders.ObjectRecorder; -import com.ulyp.core.recorders.ObjectRecorderRegistry; -import com.ulyp.core.recorders.RecorderChooser; import com.ulyp.core.util.LoggingSettings; import lombok.extern.slf4j.Slf4j; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription; -import java.util.List; import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; -import java.util.stream.Collectors; /** * Converts byte buddy method description to internal domain class {@link Method} @@ -21,19 +15,18 @@ @Slf4j public class ByteBuddyMethodResolver { - private static final AtomicInteger idGenerator = new AtomicInteger(); + public static final ByteBuddyMethodResolver INSTANCE = new ByteBuddyMethodResolver(ByteBuddyTypeConverter.SUPER_TYPE_DERIVING_INSTANCE); private final ByteBuddyTypeConverter typeConverter; - private final ByteBuddyTypeConverter declaringTypeConverter; + private final AtomicInteger idGenerator = new AtomicInteger(-1); - public ByteBuddyMethodResolver(ByteBuddyTypeConverter typeConverter, ByteBuddyTypeConverter declaringTypeConverter) { + public ByteBuddyMethodResolver(ByteBuddyTypeConverter typeConverter) { this.typeConverter = typeConverter; - this.declaringTypeConverter = declaringTypeConverter; } public Method resolve(MethodDescription description) { boolean returns = !description.getReturnType().asGenericType().equals(TypeDescription.Generic.VOID); - Type declaringType = declaringTypeConverter.convert(description.getDeclaringType().asGenericType()); + Type declaringType = typeConverter.convert(description.getDeclaringType().asGenericType()); String actualName = description.getActualName(); String name; if (description.isConstructor()) { diff --git a/ulyp-agent/src/main/java/com/ulyp/agent/util/ByteBuddyTypeConverter.java b/ulyp-agent/src/main/java/com/ulyp/agent/util/ByteBuddyTypeConverter.java index 09c95094..6a069734 100644 --- a/ulyp-agent/src/main/java/com/ulyp/agent/util/ByteBuddyTypeConverter.java +++ b/ulyp-agent/src/main/java/com/ulyp/agent/util/ByteBuddyTypeConverter.java @@ -13,7 +13,6 @@ @Slf4j public class ByteBuddyTypeConverter { - public static final ByteBuddyTypeConverter INSTANCE = new ByteBuddyTypeConverter(false); public static final ByteBuddyTypeConverter SUPER_TYPE_DERIVING_INSTANCE = new ByteBuddyTypeConverter(true); private static final AtomicInteger typeIdGenerator = new AtomicInteger(0); diff --git a/ulyp-common/src/main/java/com/ulyp/core/Method.java b/ulyp-common/src/main/java/com/ulyp/core/Method.java index e3384cca..0eb47345 100644 --- a/ulyp-common/src/main/java/com/ulyp/core/Method.java +++ b/ulyp-common/src/main/java/com/ulyp/core/Method.java @@ -6,7 +6,7 @@ @Builder @ToString -@EqualsAndHashCode(exclude = "shouldStartRecording") +@EqualsAndHashCode public class Method { private final int id; @@ -15,15 +15,6 @@ public class Method { private final boolean isStatic; private final boolean isConstructor; private final boolean returnsSomething; - private volatile boolean shouldStartRecording; - - public boolean shouldStartRecording() { - return shouldStartRecording; - } - - public void setShouldStartRecording(boolean shouldStartRecording) { - this.shouldStartRecording = shouldStartRecording; - } public int getId() { return id; diff --git a/ulyp-common/src/main/java/com/ulyp/core/MethodRepository.java b/ulyp-common/src/main/java/com/ulyp/core/MethodRepository.java index 9031e3f4..a96c702f 100644 --- a/ulyp-common/src/main/java/com/ulyp/core/MethodRepository.java +++ b/ulyp-common/src/main/java/com/ulyp/core/MethodRepository.java @@ -5,55 +5,35 @@ import java.util.ArrayList; import java.util.Collection; - public class MethodRepository { - public static final int RECORD_METHODS_MIN_ID = 1_000_000_000; - private final ConcurrentArrayList methods = new ConcurrentArrayList<>(64_000); - private final ConcurrentArrayList recordingStartMethods = new ConcurrentArrayList<>(64_000); public Method get(int id) { - if (id < RECORD_METHODS_MIN_ID) { - return methods.get(id); - } else { - return recordingStartMethods.get(id - RECORD_METHODS_MIN_ID); - } + return methods.get(id); } public int putAndGetId(Method method) { - if (method.shouldStartRecording()) { - return RECORD_METHODS_MIN_ID + recordingStartMethods.add(method); - } else { - return methods.add(method); + // TODO we rely on method.id here which is bad + int id = methods.add(method); + if (method.getId() != id) { + System.out.println(method + " put at id " + id); } + return id; } public ConcurrentArrayList getMethods() { return methods; } - public ConcurrentArrayList getRecordingStartMethods() { - return recordingStartMethods; - } - public Collection values() { Collection values = new ArrayList<>(); - for (int i = 0; i < methods.size(); i++) { Method method = methods.get(i); if (method != null) { values.add(method); } } - - for (int i = 0; i < recordingStartMethods.size(); i++) { - Method method = recordingStartMethods.get(i); - if (method != null) { - values.add(method); - } - } - return values; } } diff --git a/ulyp-common/src/main/java/com/ulyp/core/serializers/RecordedEnterMethodCallSerializer.java b/ulyp-common/src/main/java/com/ulyp/core/serializers/RecordedEnterMethodCallSerializer.java index ef99b64b..d41e21b1 100644 --- a/ulyp-common/src/main/java/com/ulyp/core/serializers/RecordedEnterMethodCallSerializer.java +++ b/ulyp-common/src/main/java/com/ulyp/core/serializers/RecordedEnterMethodCallSerializer.java @@ -7,7 +7,13 @@ import com.ulyp.core.recorders.*; import com.ulyp.core.bytes.BytesIn; import com.ulyp.core.bytes.BytesOut; +import com.ulyp.core.bytes.BinaryInput; +import com.ulyp.core.bytes.BinaryOutput; +import com.ulyp.core.recorders.*; +import com.ulyp.core.bytes.BinaryInput; +import com.ulyp.core.bytes.BinaryOutput; import com.ulyp.core.repository.ReadableRepository; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List;