Add some comments and some other minor CR tweaks

This commit is contained in:
Nikolay Martynov 2018-11-20 20:35:02 -05:00
parent 1509286b15
commit ad98ebc01f
8 changed files with 47 additions and 37 deletions

View File

@ -22,6 +22,8 @@ public final class CallableWrapper implements Callable {
} }
public static Callable<?> wrapIfNeeded(final Callable<?> task) { public static Callable<?> wrapIfNeeded(final Callable<?> task) {
// We wrap only lambdas' anonymous classes and if given object has not already been wrapped.
// Anonymous classes have '/' in class name which is not allowed in 'normal' classes.
if (task.getClass().getName().contains("/") && (!(task instanceof CallableWrapper))) { if (task.getClass().getName().contains("/") && (!(task instanceof CallableWrapper))) {
log.debug("Wrapping callable task {}", task); log.debug("Wrapping callable task {}", task);
return new CallableWrapper(task); return new CallableWrapper(task);

View File

@ -21,6 +21,8 @@ public final class RunnableWrapper implements Runnable {
} }
public static Runnable wrapIfNeeded(final Runnable task) { public static Runnable wrapIfNeeded(final Runnable task) {
// We wrap only lambdas' anonymous classes and if given object has not already been wrapped.
// Anonymous classes have '/' in class name which is not allowed in 'normal' classes.
if (task.getClass().getName().contains("/") && (!(task instanceof RunnableWrapper))) { if (task.getClass().getName().contains("/") && (!(task instanceof RunnableWrapper))) {
log.debug("Wrapping runnable task {}", task); log.debug("Wrapping runnable task {}", task);
return new RunnableWrapper(task); return new RunnableWrapper(task);

View File

@ -92,7 +92,7 @@ public class Utils {
/** /**
* Convert class name to a format that can be used as part of inner class name by replacing all * Convert class name to a format that can be used as part of inner class name by replacing all
* ','s with '$'s. * '.'s with '$'s.
* *
* @param className class named to be converted * @param className class named to be converted
* @return convertd name * @return convertd name

View File

@ -64,7 +64,7 @@ import net.bytebuddy.utility.JavaModule;
* <p>Example:<br> * <p>Example:<br>
* <em>InstrumentationContext.get(Runnable.class, RunnableState.class)")</em><br> * <em>InstrumentationContext.get(Runnable.class, RunnableState.class)")</em><br>
* is rewritten to:<br> * is rewritten to:<br>
* <em>RunnableInstrumentation$ContextStore$Runnable$RunnableState12345.getContextStore(runnableRunnable.class, * <em>FieldBackedProvider$ContextStore$Runnable$RunnableState12345.getContextStore(runnableRunnable.class,
* RunnableState.class)</em> * RunnableState.class)</em>
*/ */
@Slf4j @Slf4j
@ -88,6 +88,7 @@ public class FieldBackedProvider implements InstrumentationContextProvider {
} }
private final Instrumenter.Default instrumenter; private final Instrumenter.Default instrumenter;
private final ByteBuddy byteBuddy;
/** fields-accessor-interface-name -> fields-accessor-interface-dynamic-type */ /** fields-accessor-interface-name -> fields-accessor-interface-dynamic-type */
private final Map<String, DynamicType.Unloaded<?>> fieldAccessorInterfaces; private final Map<String, DynamicType.Unloaded<?>> fieldAccessorInterfaces;
@ -99,6 +100,7 @@ public class FieldBackedProvider implements InstrumentationContextProvider {
public FieldBackedProvider(final Instrumenter.Default instrumenter) { public FieldBackedProvider(final Instrumenter.Default instrumenter) {
this.instrumenter = instrumenter; this.instrumenter = instrumenter;
byteBuddy = new ByteBuddy();
fieldAccessorInterfaces = generateFieldAccessorInterfaces(); fieldAccessorInterfaces = generateFieldAccessorInterfaces();
contextStoreImplementations = generateContextStoreImplementationClasses(); contextStoreImplementations = generateContextStoreImplementationClasses();
fieldInjectionEnabled = Config.get().isRuntimeContextFieldInjection(); fieldInjectionEnabled = Config.get().isRuntimeContextFieldInjection();
@ -108,7 +110,12 @@ public class FieldBackedProvider implements InstrumentationContextProvider {
public AgentBuilder.Identified.Extendable instrumentationTransformer( public AgentBuilder.Identified.Extendable instrumentationTransformer(
AgentBuilder.Identified.Extendable builder) { AgentBuilder.Identified.Extendable builder) {
if (instrumenter.contextStore().size() > 0) { if (instrumenter.contextStore().size() > 0) {
builder = builder.transform(getTransformerForASMVisitor(getInstrumentationVisitor())); /*
Install transformer that rewrites accesses to context store with specialized bytecode that invokes appropriate
storage implementation.
*/
builder =
builder.transform(getTransformerForASMVisitor(getContextStoreReadsRewritingVisitor()));
/* /*
We inject into bootstrap classloader because field accessor interfaces are needed by We inject into bootstrap classloader because field accessor interfaces are needed by
@ -149,7 +156,7 @@ public class FieldBackedProvider implements InstrumentationContextProvider {
}; };
} }
private AsmVisitorWrapper getInstrumentationVisitor() { private AsmVisitorWrapper getContextStoreReadsRewritingVisitor() {
return new AsmVisitorWrapper() { return new AsmVisitorWrapper() {
@Override @Override
public int mergeWriter(final int flags) { public int mergeWriter(final int flags) {
@ -211,6 +218,12 @@ public class FieldBackedProvider implements InstrumentationContextProvider {
&& CONTEXT_GET_METHOD.getName().equals(name) && CONTEXT_GET_METHOD.getName().equals(name)
&& Type.getMethodDescriptor(CONTEXT_GET_METHOD).equals(descriptor)) { && Type.getMethodDescriptor(CONTEXT_GET_METHOD).equals(descriptor)) {
log.debug("Found context-store access in {}", instrumenter.getClass().getName()); log.debug("Found context-store access in {}", instrumenter.getClass().getName());
/*
The idea here is that the rest if this method visitor collects last three instructions in `insnStack`
variable. Once we get here we check if those last three instructions constitute call that looks like
`InstrumentationContext.get(K.class, V.class)`. If it does the inside of this if rewrites it to call
dynamically injected context store implementation instead.
*/
if ((insnStack[0] == Opcodes.INVOKESTATIC if ((insnStack[0] == Opcodes.INVOKESTATIC
&& insnStack[1] == Opcodes.LDC && insnStack[1] == Opcodes.LDC
&& insnStack[2] == Opcodes.LDC) && insnStack[2] == Opcodes.LDC)
@ -312,6 +325,10 @@ public class FieldBackedProvider implements InstrumentationContextProvider {
if (fieldInjectionEnabled) { if (fieldInjectionEnabled) {
for (final Map.Entry<String, String> entry : instrumenter.contextStore().entrySet()) { for (final Map.Entry<String, String> entry : instrumenter.contextStore().entrySet()) {
/*
For each context store defined in a current instrumentation we create an agent builder
that injects necessary fields.
*/
builder = builder =
builder builder
.type( .type(
@ -375,8 +392,7 @@ public class FieldBackedProvider implements InstrumentationContextProvider {
final int readerFlags) { final int readerFlags) {
return new ClassVisitor(Opcodes.ASM7, classVisitor) { return new ClassVisitor(Opcodes.ASM7, classVisitor) {
// We are using Object class name instead of contextClassName here because this gets // We are using Object class name instead of contextClassName here because this gets
// injected onto Bootstrap // injected onto Bootstrap classloader where context class may be unavailable
// classloader where context class may be unavailable
private final TypeDescription contextType = private final TypeDescription contextType =
new TypeDescription.ForLoadedType(Object.class); new TypeDescription.ForLoadedType(Object.class);
private final String fieldName = getContextFieldName(keyClassName); private final String fieldName = getContextFieldName(keyClassName);
@ -502,7 +518,7 @@ public class FieldBackedProvider implements InstrumentationContextProvider {
*/ */
private DynamicType.Unloaded<?> makeContextStoreImplementationClass( private DynamicType.Unloaded<?> makeContextStoreImplementationClass(
final String keyClassName, final String contextClassName) { final String keyClassName, final String contextClassName) {
return new ByteBuddy() return byteBuddy
.rebase(ContextStoreImplementationTemplate.class) .rebase(ContextStoreImplementationTemplate.class)
.modifiers(Visibility.PUBLIC, TypeManifestation.FINAL) .modifiers(Visibility.PUBLIC, TypeManifestation.FINAL)
.name(getContextStoreImplementationClassName(keyClassName, contextClassName)) .name(getContextStoreImplementationClassName(keyClassName, contextClassName))
@ -859,10 +875,9 @@ public class FieldBackedProvider implements InstrumentationContextProvider {
private DynamicType.Unloaded<?> makeFieldAccessorInterface( private DynamicType.Unloaded<?> makeFieldAccessorInterface(
final String keyClassName, final String contextClassName) { final String keyClassName, final String contextClassName) {
// We are using Object class name instead of contextClassName here because this gets injected // We are using Object class name instead of contextClassName here because this gets injected
// onto Bootstrap // onto Bootstrap classloader where context class may be unavailable
// classloader where context class may be unavailable
final TypeDescription contextType = new TypeDescription.ForLoadedType(Object.class); final TypeDescription contextType = new TypeDescription.ForLoadedType(Object.class);
return new ByteBuddy() return byteBuddy
.makeInterface() .makeInterface()
.name(getContextAccessorInterfaceName(keyClassName, contextClassName)) .name(getContextAccessorInterfaceName(keyClassName, contextClassName))
.defineMethod(getContextGetterName(keyClassName), contextType, Visibility.PUBLIC) .defineMethod(getContextGetterName(keyClassName), contextType, Visibility.PUBLIC)

View File

@ -135,9 +135,9 @@ public final class ExecutorInstrumentation extends Instrumenter.Default {
@Override @Override
public Map<String, String> contextStore() { public Map<String, String> contextStore() {
final Map<String, String> map = new HashMap<>(); final Map<String, String> map = new HashMap<>();
map.put("java.lang.Runnable", State.class.getName()); map.put(Runnable.class.getName(), State.class.getName());
map.put("java.util.concurrent.Callable", State.class.getName()); map.put(Callable.class.getName(), State.class.getName());
map.put("java.util.concurrent.Future", State.class.getName()); map.put(Future.class.getName(), State.class.getName());
return Collections.unmodifiableMap(map); return Collections.unmodifiableMap(map);
} }

View File

@ -93,7 +93,7 @@ public final class FutureInstrumentation extends Instrumenter.Default {
@Override @Override
public Map<String, String> contextStore() { public Map<String, String> contextStore() {
final Map<String, String> map = new HashMap<>(); final Map<String, String> map = new HashMap<>();
map.put("java.util.concurrent.Future", State.class.getName()); map.put(Future.class.getName(), State.class.getName());
return Collections.unmodifiableMap(map); return Collections.unmodifiableMap(map);
} }

View File

@ -23,9 +23,9 @@ import net.bytebuddy.matcher.ElementMatcher;
@Slf4j @Slf4j
@AutoService(Instrumenter.class) @AutoService(Instrumenter.class)
public final class RunnableInstrumentation extends Instrumenter.Default { public final class RunnableCallableInstrumentation extends Instrumenter.Default {
public RunnableInstrumentation() { public RunnableCallableInstrumentation() {
super(ExecutorInstrumentation.EXEC_NAME); super(ExecutorInstrumentation.EXEC_NAME);
} }
@ -38,15 +38,15 @@ public final class RunnableInstrumentation extends Instrumenter.Default {
@Override @Override
public String[] helperClassNames() { public String[] helperClassNames() {
return new String[] { return new String[] {
RunnableInstrumentation.class.getName() + "$RunnableUtils", RunnableCallableInstrumentation.class.getName() + "$RunnableUtils",
}; };
} }
@Override @Override
public Map<String, String> contextStore() { public Map<String, String> contextStore() {
final Map<String, String> map = new HashMap<>(); final Map<String, String> map = new HashMap<>();
map.put("java.lang.Runnable", State.class.getName()); map.put(Runnable.class.getName(), State.class.getName());
map.put("java.util.concurrent.Callable", State.class.getName()); map.put(Callable.class.getName(), State.class.getName());
return Collections.unmodifiableMap(map); return Collections.unmodifiableMap(map);
} }

View File

@ -8,6 +8,7 @@ import net.bytebuddy.utility.JavaModule
import java.lang.instrument.ClassDefinition import java.lang.instrument.ClassDefinition
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import java.lang.reflect.Field import java.lang.reflect.Field
import java.util.concurrent.atomic.AtomicReference
import static context.ContextTestInstrumentation.IncorrectCallUsageKeyClass import static context.ContextTestInstrumentation.IncorrectCallUsageKeyClass
import static context.ContextTestInstrumentation.IncorrectContextClassUsageKeyClass import static context.ContextTestInstrumentation.IncorrectContextClassUsageKeyClass
@ -93,31 +94,21 @@ class FieldBackedProviderTest extends AgentTestRunner {
new UntransformableKeyClass() | _ new UntransformableKeyClass() | _
} }
def "backing map should not create strong refs to key class instances"() { def "backing map should not create strong refs to key class instances #keyValue.get().getClass().getName()"() {
when: when:
KeyClass instance = new KeyClass() final int count = keyValue.get().incrementContextCount()
final int count = instance.incrementContextCount() WeakReference<KeyClass> instanceRef = new WeakReference(keyValue.get())
WeakReference<KeyClass> instanceRef = new WeakReference(instance) keyValue.set(null)
instance = null
TestUtils.awaitGC(instanceRef) TestUtils.awaitGC(instanceRef)
then: then:
instanceRef.get() == null instanceRef.get() == null
count == 1 count == 1
}
// can't use spock 'where' in this test because that creates strong references where:
def "backing map should not create strong refs to untransformable key class instances"() { keyValue | _
when: new AtomicReference(new KeyClass()) | _
UntransformableKeyClass instance = new UntransformableKeyClass() new AtomicReference(new UntransformableKeyClass()) | _
final int count = instance.incrementContextCount()
WeakReference<KeyClass> instanceRef = new WeakReference(instance)
instance = null
TestUtils.awaitGC(instanceRef)
then:
instanceRef.get() == null
count == 1
} }
def "context classes are retransform safe"() { def "context classes are retransform safe"() {