Skip to content

Commit

Permalink
Refactored listener semantics to also trigger ignore listeners for no…
Browse files Browse the repository at this point in the history
…n-transformable or non-matched types upon retransformation / redefinition.
  • Loading branch information
Rafael Winterhalter committed Oct 27, 2015
1 parent d334ba5 commit 97bd570
Show file tree
Hide file tree
Showing 2 changed files with 216 additions and 18 deletions.
Expand Up @@ -1062,8 +1062,17 @@ protected Collector makeCollector(Default.Transformation transformation) {
}, },


/** /**
* <p>
* Applies a <b>redefinition</b> to all classes that are already loaded and that would have been transformed if * Applies a <b>redefinition</b> to all classes that are already loaded and that would have been transformed if
* the built agent was registered before they were loaded. * the built agent was registered before they were loaded.
* </p>
* <p>
* <b>Important</b>: If a redefined class was previously instrumented, this instrumentation information is lost
* during the instrumentation. The redefinition is applied upon the original byte code that is provided by a class
* loader and not upon the code in its currently transformed format. Use
* {@link net.bytebuddy.agent.builder.AgentBuilder.RedefinitionStrategy#RETRANSFORMATION} if this is a factual or
* potential limitation.
* </p>
*/ */
REDEFINITION { REDEFINITION {
@Override @Override
Expand Down Expand Up @@ -1139,8 +1148,9 @@ protected interface Collector {
* Considers a loaded class for modification. * Considers a loaded class for modification.
* *
* @param type The type that is to be considered. * @param type The type that is to be considered.
* @return {@code true} if the class is considered to be redefined.
*/ */
void consider(Class<?> type); boolean consider(Class<?> type);


/** /**
* Applies the represented type modification on all collected types. * Applies the represented type modification on all collected types.
Expand Down Expand Up @@ -1193,14 +1203,12 @@ protected ForRedefinition(Default.Transformation transformation) {
} }


@Override @Override
public void consider(Class<?> type) { public boolean consider(Class<?> type) {
Default.Transformation.Resolution resolution = transformation.resolve(new TypeDescription.ForLoadedType(type), Default.Transformation.Resolution resolution = transformation.resolve(new TypeDescription.ForLoadedType(type),
type.getClassLoader(), type.getClassLoader(),
type, type,
type.getProtectionDomain()); type.getProtectionDomain());
if (resolution.isResolved()) { return resolution.isResolved() && entries.add(new Entry(type, resolution));
entries.add(new Entry(type, resolution));
}
} }


@Override @Override
Expand Down Expand Up @@ -1358,10 +1366,9 @@ protected ForRetransformation(Default.Transformation transformation) {
} }


@Override @Override
public void consider(Class<?> type) { public boolean consider(Class<?> type) {
if (transformation.resolve(new TypeDescription.ForLoadedType(type), type.getClassLoader(), type, type.getProtectionDomain()).isResolved()) { return transformation.resolve(new TypeDescription.ForLoadedType(type),
types.add(type); type.getClassLoader(), type, type.getProtectionDomain()).isResolved() && types.add(type);
}
} }


@Override @Override
Expand Down Expand Up @@ -1720,16 +1727,27 @@ public ClassFileTransformer installOn(Instrumentation instrumentation) {
if (redefinitionStrategy.isEnabled()) { if (redefinitionStrategy.isEnabled()) {
RedefinitionStrategy.Collector collector = redefinitionStrategy.makeCollector(transformation); RedefinitionStrategy.Collector collector = redefinitionStrategy.makeCollector(transformation);
for (Class<?> type : instrumentation.getAllLoadedClasses()) { for (Class<?> type : instrumentation.getAllLoadedClasses()) {
if (instrumentation.isModifiableClass(type)) { try {
// To enforce identical listener semantics for a redefinition as for an agent instrumentation, this catching is required. if (!instrumentation.isModifiableClass(type) || !collector.consider(type)) {
try {
try {
listener.onIgnored(new TypeDescription.ForLoadedType(type));
} finally {
listener.onComplete(type.getName());
}
} catch (Throwable ignored) {
// Ignore exceptions that are thrown by listeners to mimic the behavior of a transformation.
}
}
} catch (Throwable throwable) {
try { try {
collector.consider(type);
} catch (Throwable throwable) {
try { try {
listener.onError(type.getName(), throwable); listener.onError(type.getName(), throwable);
} finally { } finally {
listener.onComplete(type.getName()); listener.onComplete(type.getName());
} }
} catch (Throwable ignored) {
// Ignore exceptions that are thrown by listeners to mimic the behavior of a transformation.
} }
} }
} }
Expand Down
Expand Up @@ -62,7 +62,7 @@ public class AgentBuilderDefaultTest {


@Mock @Mock
private AgentBuilder.TypeStrategy typeStrategy; private AgentBuilder.TypeStrategy typeStrategy;

@Mock @Mock
private AgentBuilder.InitializationStrategy initializationStrategy; private AgentBuilder.InitializationStrategy initializationStrategy;


Expand Down Expand Up @@ -164,7 +164,9 @@ public void testSkipRetransformationWithNonRedefinable() throws Exception {
.withoutNativeMethodPrefix() .withoutNativeMethodPrefix()
.type(rawMatcher).transform(transformer) .type(rawMatcher).transform(transformer)
.installOn(instrumentation); .installOn(instrumentation);
verifyZeroInteractions(listener); verify(listener).onIgnored(new TypeDescription.ForLoadedType(REDEFINED));
verify(listener).onComplete(REDEFINED.getName());
verifyNoMoreInteractions(listener);
verify(instrumentation).addTransformer(classFileTransformer, true); verify(instrumentation).addTransformer(classFileTransformer, true);
verify(instrumentation).isModifiableClass(REDEFINED); verify(instrumentation).isModifiableClass(REDEFINED);
verify(instrumentation).getAllLoadedClasses(); verify(instrumentation).getAllLoadedClasses();
Expand All @@ -191,7 +193,71 @@ public void testSkipRetransformationWithNonMatched() throws Exception {
.withoutNativeMethodPrefix() .withoutNativeMethodPrefix()
.type(rawMatcher).transform(transformer) .type(rawMatcher).transform(transformer)
.installOn(instrumentation); .installOn(instrumentation);
verifyZeroInteractions(listener); verify(listener).onIgnored(new TypeDescription.ForLoadedType(REDEFINED));
verify(listener).onComplete(REDEFINED.getName());
verifyNoMoreInteractions(listener);
verify(instrumentation).addTransformer(classFileTransformer, true);
verify(instrumentation).isModifiableClass(REDEFINED);
verify(instrumentation).getAllLoadedClasses();
verify(instrumentation).isRetransformClassesSupported();
verifyNoMoreInteractions(instrumentation);
verify(rawMatcher).matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), REDEFINED, REDEFINED.getProtectionDomain());
verifyNoMoreInteractions(rawMatcher);
verifyZeroInteractions(initializationStrategy);
}

@Test
public void testSkipRetransformationWithNonMatchedListenerException() throws Exception {
when(unloaded.getBytes()).thenReturn(BAZ);
when(resolution.resolve()).thenReturn(new TypeDescription.ForLoadedType(REDEFINED));
when(rawMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), REDEFINED, REDEFINED.getProtectionDomain()))
.thenReturn(false);
when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(true);
when(instrumentation.isRetransformClassesSupported()).thenReturn(true);
doThrow(new RuntimeException()).when(listener).onIgnored(new TypeDescription.ForLoadedType(REDEFINED));
ClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
.withInitializationStrategy(initializationStrategy)
.withRedefinitionStrategy(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
.withBinaryLocator(binaryLocator)
.withTypeStrategy(typeStrategy)
.withListener(listener)
.withoutNativeMethodPrefix()
.type(rawMatcher).transform(transformer)
.installOn(instrumentation);
verify(listener).onIgnored(new TypeDescription.ForLoadedType(REDEFINED));
verify(listener).onComplete(REDEFINED.getName());
verifyNoMoreInteractions(listener);
verify(instrumentation).addTransformer(classFileTransformer, true);
verify(instrumentation).isModifiableClass(REDEFINED);
verify(instrumentation).getAllLoadedClasses();
verify(instrumentation).isRetransformClassesSupported();
verifyNoMoreInteractions(instrumentation);
verify(rawMatcher).matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), REDEFINED, REDEFINED.getProtectionDomain());
verifyNoMoreInteractions(rawMatcher);
verifyZeroInteractions(initializationStrategy);
}

@Test
public void testSkipRetransformationWithNonMatchedListenerCompleteException() throws Exception {
when(unloaded.getBytes()).thenReturn(BAZ);
when(resolution.resolve()).thenReturn(new TypeDescription.ForLoadedType(REDEFINED));
when(rawMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), REDEFINED, REDEFINED.getProtectionDomain()))
.thenReturn(false);
when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(true);
when(instrumentation.isRetransformClassesSupported()).thenReturn(true);
doThrow(new RuntimeException()).when(listener).onComplete(REDEFINED.getName());
ClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
.withInitializationStrategy(initializationStrategy)
.withRedefinitionStrategy(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
.withBinaryLocator(binaryLocator)
.withTypeStrategy(typeStrategy)
.withListener(listener)
.withoutNativeMethodPrefix()
.type(rawMatcher).transform(transformer)
.installOn(instrumentation);
verify(listener).onIgnored(new TypeDescription.ForLoadedType(REDEFINED));
verify(listener).onComplete(REDEFINED.getName());
verifyNoMoreInteractions(listener);
verify(instrumentation).addTransformer(classFileTransformer, true); verify(instrumentation).addTransformer(classFileTransformer, true);
verify(instrumentation).isModifiableClass(REDEFINED); verify(instrumentation).isModifiableClass(REDEFINED);
verify(instrumentation).getAllLoadedClasses(); verify(instrumentation).getAllLoadedClasses();
Expand Down Expand Up @@ -258,7 +324,9 @@ public void testSkipRedefinitionWithNonRedefinable() throws Exception {
.withoutNativeMethodPrefix() .withoutNativeMethodPrefix()
.type(rawMatcher).transform(transformer) .type(rawMatcher).transform(transformer)
.installOn(instrumentation); .installOn(instrumentation);
verifyZeroInteractions(listener); verify(listener).onIgnored(new TypeDescription.ForLoadedType(REDEFINED));
verify(listener).onComplete(REDEFINED.getName());
verifyNoMoreInteractions(listener);
verify(instrumentation).addTransformer(classFileTransformer, false); verify(instrumentation).addTransformer(classFileTransformer, false);
verify(instrumentation).isModifiableClass(REDEFINED); verify(instrumentation).isModifiableClass(REDEFINED);
verify(instrumentation).getAllLoadedClasses(); verify(instrumentation).getAllLoadedClasses();
Expand All @@ -285,7 +353,71 @@ public void testSkipRedefinitionWithNonMatched() throws Exception {
.withoutNativeMethodPrefix() .withoutNativeMethodPrefix()
.type(rawMatcher).transform(transformer) .type(rawMatcher).transform(transformer)
.installOn(instrumentation); .installOn(instrumentation);
verifyZeroInteractions(listener); verify(listener).onIgnored(new TypeDescription.ForLoadedType(REDEFINED));
verify(listener).onComplete(REDEFINED.getName());
verifyNoMoreInteractions(listener);
verify(instrumentation).addTransformer(classFileTransformer, false);
verify(instrumentation).isModifiableClass(REDEFINED);
verify(instrumentation).getAllLoadedClasses();
verify(instrumentation).isRedefineClassesSupported();
verifyNoMoreInteractions(instrumentation);
verify(rawMatcher).matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), REDEFINED, REDEFINED.getProtectionDomain());
verifyNoMoreInteractions(rawMatcher);
verifyZeroInteractions(initializationStrategy);
}

@Test
public void testSkipRedefinitionWithNonMatchedListenerException() throws Exception {
when(unloaded.getBytes()).thenReturn(BAZ);
when(resolution.resolve()).thenReturn(new TypeDescription.ForLoadedType(REDEFINED));
when(rawMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), REDEFINED, REDEFINED.getProtectionDomain()))
.thenReturn(false);
when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(true);
when(instrumentation.isRedefineClassesSupported()).thenReturn(true);
doThrow(new RuntimeException()).when(listener).onIgnored(new TypeDescription.ForLoadedType(REDEFINED));
ClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
.withInitializationStrategy(initializationStrategy)
.withRedefinitionStrategy(AgentBuilder.RedefinitionStrategy.REDEFINITION)
.withBinaryLocator(binaryLocator)
.withTypeStrategy(typeStrategy)
.withListener(listener)
.withoutNativeMethodPrefix()
.type(rawMatcher).transform(transformer)
.installOn(instrumentation);
verify(listener).onIgnored(new TypeDescription.ForLoadedType(REDEFINED));
verify(listener).onComplete(REDEFINED.getName());
verifyNoMoreInteractions(listener);
verify(instrumentation).addTransformer(classFileTransformer, false);
verify(instrumentation).isModifiableClass(REDEFINED);
verify(instrumentation).getAllLoadedClasses();
verify(instrumentation).isRedefineClassesSupported();
verifyNoMoreInteractions(instrumentation);
verify(rawMatcher).matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), REDEFINED, REDEFINED.getProtectionDomain());
verifyNoMoreInteractions(rawMatcher);
verifyZeroInteractions(initializationStrategy);
}

@Test
public void testSkipRedefinitionWithNonMatchedListenerFinishedException() throws Exception {
when(unloaded.getBytes()).thenReturn(BAZ);
when(resolution.resolve()).thenReturn(new TypeDescription.ForLoadedType(REDEFINED));
when(rawMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), REDEFINED, REDEFINED.getProtectionDomain()))
.thenReturn(false);
when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(true);
when(instrumentation.isRedefineClassesSupported()).thenReturn(true);
doThrow(new RuntimeException()).when(listener).onComplete(REDEFINED.getName());
ClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
.withInitializationStrategy(initializationStrategy)
.withRedefinitionStrategy(AgentBuilder.RedefinitionStrategy.REDEFINITION)
.withBinaryLocator(binaryLocator)
.withTypeStrategy(typeStrategy)
.withListener(listener)
.withoutNativeMethodPrefix()
.type(rawMatcher).transform(transformer)
.installOn(instrumentation);
verify(listener).onIgnored(new TypeDescription.ForLoadedType(REDEFINED));
verify(listener).onComplete(REDEFINED.getName());
verifyNoMoreInteractions(listener);
verify(instrumentation).addTransformer(classFileTransformer, false); verify(instrumentation).addTransformer(classFileTransformer, false);
verify(instrumentation).isModifiableClass(REDEFINED); verify(instrumentation).isModifiableClass(REDEFINED);
verify(instrumentation).getAllLoadedClasses(); verify(instrumentation).getAllLoadedClasses();
Expand Down Expand Up @@ -472,6 +604,54 @@ public void testRetransformationConsiderationException() throws Exception {
verifyNoMoreInteractions(listener); verifyNoMoreInteractions(listener);
} }


@Test
public void testRedefinitionConsiderationExceptionListenerException() throws Exception {
RuntimeException exception = new RuntimeException();
when(instrumentation.isRedefineClassesSupported()).thenReturn(true);
when(instrumentation.getAllLoadedClasses()).thenReturn(new Class<?>[]{REDEFINED});
when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(true);
when(rawMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), REDEFINED, REDEFINED.getProtectionDomain()))
.thenThrow(exception);
doThrow(new RuntimeException()).when(listener).onError(REDEFINED.getName(), exception);
ClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
.withInitializationStrategy(initializationStrategy)
.withRedefinitionStrategy(AgentBuilder.RedefinitionStrategy.REDEFINITION)
.withBinaryLocator(binaryLocator)
.withTypeStrategy(typeStrategy)
.withListener(listener)
.withoutNativeMethodPrefix()
.type(rawMatcher).transform(transformer)
.installOn(instrumentation);
verify(instrumentation).addTransformer(classFileTransformer, false);
verify(listener).onError(REDEFINED.getName(), exception);
verify(listener).onComplete(REDEFINED.getName());
verifyNoMoreInteractions(listener);
}

@Test
public void testRetransformationConsiderationExceptionListenerException() throws Exception {
RuntimeException exception = new RuntimeException();
when(instrumentation.isRetransformClassesSupported()).thenReturn(true);
when(instrumentation.getAllLoadedClasses()).thenReturn(new Class<?>[]{REDEFINED});
when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(true);
when(rawMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), REDEFINED, REDEFINED.getProtectionDomain()))
.thenThrow(exception);
doThrow(new RuntimeException()).when(listener).onError(REDEFINED.getName(), exception);
ClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
.withInitializationStrategy(initializationStrategy)
.withRedefinitionStrategy(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
.withBinaryLocator(binaryLocator)
.withTypeStrategy(typeStrategy)
.withListener(listener)
.withoutNativeMethodPrefix()
.type(rawMatcher).transform(transformer)
.installOn(instrumentation);
verify(instrumentation).addTransformer(classFileTransformer, true);
verify(listener).onError(REDEFINED.getName(), exception);
verify(listener).onComplete(REDEFINED.getName());
verifyNoMoreInteractions(listener);
}

@Test @Test
public void testObjectProperties() throws Exception { public void testObjectProperties() throws Exception {
ObjectPropertyAssertion.of(AgentBuilder.Default.class).create(new ObjectPropertyAssertion.Creator<AccessControlContext>() { ObjectPropertyAssertion.of(AgentBuilder.Default.class).create(new ObjectPropertyAssertion.Creator<AccessControlContext>() {
Expand Down

0 comments on commit 97bd570

Please sign in to comment.