diff --git a/agent/pom.xml b/agent/pom.xml index 03dfc8e..cbe2f9a 100644 --- a/agent/pom.xml +++ b/agent/pom.xml @@ -52,7 +52,7 @@ agent.net.bytebuddy - false + true diff --git a/agent/src/main/java/io/type/pollution/agent/Agent.java b/agent/src/main/java/io/type/pollution/agent/Agent.java index 3d4d5f2..cb1d374 100644 --- a/agent/src/main/java/io/type/pollution/agent/Agent.java +++ b/agent/src/main/java/io/type/pollution/agent/Agent.java @@ -21,6 +21,9 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; @@ -40,6 +43,8 @@ public class Agent { private static final Long REPORT_INTERVAL_SECS = Long.getLong("io.type.pollution.report.interval"); private static final boolean ENABLE_LAMBDA_INSTRUMENTATION = Boolean.getBoolean("io.type.pollution.lambda"); + private static final Set disabledClassLoaders = Collections.synchronizedSet(new HashSet<>()); + public static void premain(String agentArgs, Instrumentation inst) { if (ENABLE_FULL_STACK_TRACES) { TraceInstanceOf.startMetronome(FULL_STACK_TRACES_SAMPLING_PERIOD_MS); @@ -78,31 +83,43 @@ public static void premain(String agentArgs, Instrumentation inst) { typeDescription, classLoader, module, - protectionDomain) -> builder - .visit(TypeConstantAdjustment.INSTANCE) - .visit(new AsmVisitorWrapper.AbstractBase() { - - @Override - public int mergeWriter(int flags) { - return flags | ClassWriter.COMPUTE_FRAMES; - } - - @Override - public int mergeReader(int flags) { - return flags; - } - - @Override - public net.bytebuddy.jar.asm.ClassVisitor wrap(TypeDescription instrumentedType, - net.bytebuddy.jar.asm.ClassVisitor classVisitor, - Implementation.Context implementationContext, - TypePool typePool, - FieldList fields, - MethodList methods, - int writerFlags, int readerFlags) { - return new ByteBuddyUtils.ByteBuddyTypePollutionClassVisitor(net.bytebuddy.jar.asm.Opcodes.ASM9, classVisitor); - } - })).installOn(inst); + protectionDomain) -> { + if (classLoader != null) { + // in gradle test runners, there are class loaders that don't include the agent classes + try { + classLoader.loadClass(TraceInstanceOf.class.getName()); + } catch (ClassNotFoundException e) { + disabledClassLoaders.add(classLoader.toString()); + return builder; + } + } + + return builder + .visit(TypeConstantAdjustment.INSTANCE) + .visit(new AsmVisitorWrapper.AbstractBase() { + + @Override + public int mergeWriter(int flags) { + return flags | ClassWriter.COMPUTE_FRAMES; + } + + @Override + public int mergeReader(int flags) { + return flags; + } + + @Override + public net.bytebuddy.jar.asm.ClassVisitor wrap(TypeDescription instrumentedType, + net.bytebuddy.jar.asm.ClassVisitor classVisitor, + Implementation.Context implementationContext, + TypePool typePool, + FieldList fields, + MethodList methods, + int writerFlags, int readerFlags) { + return new ByteBuddyUtils.ByteBuddyTypePollutionClassVisitor(net.bytebuddy.jar.asm.Opcodes.ASM9, classVisitor); + } + }); + }).installOn(inst); } private static void printFinalReport() { @@ -125,6 +142,9 @@ private static CharSequence reportOf(Collection SAMPLING_TICK_UPDATER = AtomicLongFieldUpdater.newUpdater(TraceCounter.class, "lastSamplingTick"); - private final Class clazz; + final Class clazz; private volatile long lastSamplingTick = System.nanoTime(); private final ConcurrentHashMap traces = new ConcurrentHashMap<>(); private static final ThreadLocal TRACE = ThreadLocal.withInitial(Trace::new); diff --git a/agent/src/main/java/io/type/pollution/agent/TypeCheckThrashEvent.java b/agent/src/main/java/io/type/pollution/agent/TypeCheckThrashEvent.java new file mode 100644 index 0000000..821a6bd --- /dev/null +++ b/agent/src/main/java/io/type/pollution/agent/TypeCheckThrashEvent.java @@ -0,0 +1,27 @@ +package io.type.pollution.agent; + +import jdk.jfr.Category; +import jdk.jfr.Description; +import jdk.jfr.Enabled; +import jdk.jfr.Event; +import jdk.jfr.Label; +import jdk.jfr.Name; +import jdk.jfr.StackTrace; + +@Name(TypeCheckThrashEvent.NAME) +@Label("Type check cache thrashing") +@Category("Type check") +@Description("An interface type check is invalidating a previously cached type check result. If this happens often, it can lead to thrashing and contention of the cache field.") +@StackTrace +@Enabled(false) +public class TypeCheckThrashEvent extends Event { + static final String NAME = "io.type.pollution.agent.TypeCheckThrashEvent"; + + @Label("Concrete type") + @Description("Concrete class of the object that was type checked (the thrashed cache field is stored in this class)") + public String concreteClass; + + @Label("Interface type") + @Description("Interface that this object was checked against (this is the type that is stored in the thrashed cache field)") + public String interfaceClass; +}