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) {
// 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))) {
log.debug("Wrapping callable task {}", task);
return new CallableWrapper(task);

View File

@ -21,6 +21,8 @@ public final class RunnableWrapper implements Runnable {
}
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))) {
log.debug("Wrapping runnable task {}", 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
* ','s with '$'s.
* '.'s with '$'s.
*
* @param className class named to be converted
* @return convertd name

View File

@ -64,7 +64,7 @@ import net.bytebuddy.utility.JavaModule;
* <p>Example:<br>
* <em>InstrumentationContext.get(Runnable.class, RunnableState.class)")</em><br>
* is rewritten to:<br>
* <em>RunnableInstrumentation$ContextStore$Runnable$RunnableState12345.getContextStore(runnableRunnable.class,
* <em>FieldBackedProvider$ContextStore$Runnable$RunnableState12345.getContextStore(runnableRunnable.class,
* RunnableState.class)</em>
*/
@Slf4j
@ -88,6 +88,7 @@ public class FieldBackedProvider implements InstrumentationContextProvider {
}
private final Instrumenter.Default instrumenter;
private final ByteBuddy byteBuddy;
/** fields-accessor-interface-name -> fields-accessor-interface-dynamic-type */
private final Map<String, DynamicType.Unloaded<?>> fieldAccessorInterfaces;
@ -99,6 +100,7 @@ public class FieldBackedProvider implements InstrumentationContextProvider {
public FieldBackedProvider(final Instrumenter.Default instrumenter) {
this.instrumenter = instrumenter;
byteBuddy = new ByteBuddy();
fieldAccessorInterfaces = generateFieldAccessorInterfaces();
contextStoreImplementations = generateContextStoreImplementationClasses();
fieldInjectionEnabled = Config.get().isRuntimeContextFieldInjection();
@ -108,7 +110,12 @@ public class FieldBackedProvider implements InstrumentationContextProvider {
public AgentBuilder.Identified.Extendable instrumentationTransformer(
AgentBuilder.Identified.Extendable builder) {
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
@ -149,7 +156,7 @@ public class FieldBackedProvider implements InstrumentationContextProvider {
};
}
private AsmVisitorWrapper getInstrumentationVisitor() {
private AsmVisitorWrapper getContextStoreReadsRewritingVisitor() {
return new AsmVisitorWrapper() {
@Override
public int mergeWriter(final int flags) {
@ -211,6 +218,12 @@ public class FieldBackedProvider implements InstrumentationContextProvider {
&& CONTEXT_GET_METHOD.getName().equals(name)
&& Type.getMethodDescriptor(CONTEXT_GET_METHOD).equals(descriptor)) {
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
&& insnStack[1] == Opcodes.LDC
&& insnStack[2] == Opcodes.LDC)
@ -312,6 +325,10 @@ public class FieldBackedProvider implements InstrumentationContextProvider {
if (fieldInjectionEnabled) {
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
.type(
@ -375,8 +392,7 @@ public class FieldBackedProvider implements InstrumentationContextProvider {
final int readerFlags) {
return new ClassVisitor(Opcodes.ASM7, classVisitor) {
// We are using Object class name instead of contextClassName here because this gets
// injected onto Bootstrap
// classloader where context class may be unavailable
// injected onto Bootstrap classloader where context class may be unavailable
private final TypeDescription contextType =
new TypeDescription.ForLoadedType(Object.class);
private final String fieldName = getContextFieldName(keyClassName);
@ -502,7 +518,7 @@ public class FieldBackedProvider implements InstrumentationContextProvider {
*/
private DynamicType.Unloaded<?> makeContextStoreImplementationClass(
final String keyClassName, final String contextClassName) {
return new ByteBuddy()
return byteBuddy
.rebase(ContextStoreImplementationTemplate.class)
.modifiers(Visibility.PUBLIC, TypeManifestation.FINAL)
.name(getContextStoreImplementationClassName(keyClassName, contextClassName))
@ -859,10 +875,9 @@ public class FieldBackedProvider implements InstrumentationContextProvider {
private DynamicType.Unloaded<?> makeFieldAccessorInterface(
final String keyClassName, final String contextClassName) {
// We are using Object class name instead of contextClassName here because this gets injected
// onto Bootstrap
// classloader where context class may be unavailable
// onto Bootstrap classloader where context class may be unavailable
final TypeDescription contextType = new TypeDescription.ForLoadedType(Object.class);
return new ByteBuddy()
return byteBuddy
.makeInterface()
.name(getContextAccessorInterfaceName(keyClassName, contextClassName))
.defineMethod(getContextGetterName(keyClassName), contextType, Visibility.PUBLIC)

View File

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

View File

@ -93,7 +93,7 @@ public final class FutureInstrumentation extends Instrumenter.Default {
@Override
public Map<String, String> contextStore() {
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);
}

View File

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

View File

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