Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"Cannot resolve type description" when class is not present #780

Closed
bsideup opened this issue Nov 22, 2019 · 6 comments
Closed

"Cannot resolve type description" when class is not present #780

bsideup opened this issue Nov 22, 2019 · 6 comments
Assignees
Labels
Milestone

Comments

@bsideup
Copy link

bsideup commented Nov 22, 2019

Hi!

We received a bug report in BlockHound ( reactor/BlockHound#71 ) that looks more like a bug in ByteBuddy.

I was able to reproduce it with the following setup:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
        <version>2.2.1.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>net.bytebuddy</groupId>
        <artifactId>byte-buddy</artifactId>
        <version>1.10.2</version>
    </dependency>

    <dependency>
        <groupId>net.bytebuddy</groupId>
        <artifactId>byte-buddy-agent</artifactId>
        <version>1.10.2</version>
    </dependency>
</dependencies>
public static void main(String[] args) throws Exception {
    Instrumentation instrumentation = ByteBuddyAgent.install();

    String testClass = "ch.qos.logback.classic.Logger";

    new AgentBuilder.Default()
            .with(RedefinitionStrategy.RETRANSFORMATION)
            .with(InitializationStrategy.NoOp.INSTANCE)
            .with((PoolStrategy) (classFileLocator, classLoader) -> new TypePool.Default(
                    new CacheProvider.Simple(),
                    classFileLocator,
                    TypePool.Default.ReaderMode.FAST
            ))
            .with(AgentBuilder.Listener.StreamWriting.toSystemError().withErrorsOnly())

            .type(it -> testClass.equals(it.getName()))
            .transform((builder, typeDescription, classLoader, javaModule) -> {
                return builder.visit(Advice.to(TestAdvice.class).on(named("callAppenders")));
            })
            .installOn(instrumentation);

    Class.forName(testClass);
}

public static class TestAdvice {
    @Advice.OnMethodEnter
    public static void onEnter() {
    }
}

It fails with this exception:

java.lang.IllegalStateException: Cannot resolve type description for org.slf4j.event.LoggingEvent
	at net.bytebuddy.pool.TypePool$Resolution$Illegal.resolve(TypePool.java:159)
	at net.bytebuddy.pool.TypePool$Default$LazyTypeDescription$TokenizedGenericType.toErasure(TypePool.java:6241)
	at net.bytebuddy.pool.TypePool$Default$LazyTypeDescription$GenericTypeToken$Resolution$Raw$RawAnnotatedType.of(TypePool.java:3412)
	at net.bytebuddy.pool.TypePool$Default$LazyTypeDescription$GenericTypeToken$Resolution$Raw$RawAnnotatedType$LazyRawAnnotatedTypeList.get(TypePool.java:3511)
	at net.bytebuddy.pool.TypePool$Default$LazyTypeDescription$GenericTypeToken$Resolution$Raw$RawAnnotatedType$LazyRawAnnotatedTypeList.get(TypePool.java:3456)
	at net.bytebuddy.pool.TypePool$Default$LazyTypeDescription$LazyMethodDescription$LazyParameterDescription.getType(TypePool.java:6994)
	at net.bytebuddy.description.method.ParameterDescription$AbstractBase.asToken(ParameterDescription.java:178)
	at net.bytebuddy.description.method.ParameterDescription$AbstractBase.asToken(ParameterDescription.java:128)
	at net.bytebuddy.description.method.ParameterList$AbstractBase.asTokenList(ParameterList.java:98)
	at net.bytebuddy.description.method.MethodDescription$AbstractBase.asToken(MethodDescription.java:825)
	at net.bytebuddy.description.method.MethodDescription$AbstractBase.asToken(MethodDescription.java:376)
	at net.bytebuddy.description.method.MethodList$AbstractBase.asTokenList(MethodList.java:70)
	at net.bytebuddy.dynamic.scaffold.InstrumentedType$Factory$Default$1.represent(InstrumentedType.java:364)
	at net.bytebuddy.ByteBuddy.rebase(ByteBuddy.java:851)
	at net.bytebuddy.agent.builder.AgentBuilder$TypeStrategy$Default$1.builder(AgentBuilder.java:2007)
	at net.bytebuddy.agent.builder.AgentBuilder$Default$Transformation$Simple$Resolution.apply(AgentBuilder.java:10117)
	at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.doTransform(AgentBuilder.java:10494)
	at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.transform(AgentBuilder.java:10457)
	at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.access$1500(AgentBuilder.java:10223)
	at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$Java9CapableVmDispatcher.run(AgentBuilder.java:10907)
	at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$Java9CapableVmDispatcher.run(AgentBuilder.java:10845)
	at java.base/java.security.AccessController.doPrivileged(Native Method)
	at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.transform(AgentBuilder.java:10413)
	at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$ByteBuddy$ModuleSupport.transform(Unknown Source)
	at java.instrument/sun.instrument.TransformerManager.transform(TransformerManager.java:188)
	at java.instrument/sun.instrument.InstrumentationImpl.transform(InstrumentationImpl.java:563)
	at java.base/java.lang.ClassLoader.defineClass1(Native Method)
	at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1016)
	at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:174)
	at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:802)
	at java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:700)
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:623)
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
	at java.base/java.lang.Class.forName0(Native Method)
	at java.base/java.lang.Class.forName(Class.java:315)
	at reactor.SampleReactor.main(SampleReactor.java:39)
@raphw
Copy link
Owner

raphw commented Nov 22, 2019

It's less a bug than it's a feature. "Optional types" is something several developers assume for the JVM to work due to it's implicit type resolution but for legally transforming a type, all signature type information needs to be available during instrumentation.

Byte Buddy requires all type information for a transformed type to be available but for ch.qos.logback.classic.Logger, it cannot find org.slf4j.event.LoggingEvent. It is the equivalent exception to:

Class.forName("ch.qos.logback.classic.Logger").getDeclaredMethods()

Introspection only works if the introspected types are available. The good news is that this does not break anything but only aborts the instrumentation of the incomplete type.

@raphw raphw self-assigned this Nov 22, 2019
@raphw raphw added the question label Nov 22, 2019
@raphw raphw added this to the 1.10.3 milestone Nov 22, 2019
@bsideup
Copy link
Author

bsideup commented Nov 22, 2019

The good news is that this does not break anything

Well, it does seem to break InstrumentationImpl.retransformClasses and every transformation we apply in BlockHound.

Would it be possible to add some graceful handling of such cases?
Extra points if only incomplete signatures will be ignored but other methods will still be instrumented

@raphw
Copy link
Owner

raphw commented Nov 22, 2019

It should not and I cannot reproduce this behavior from the example either. I assume that you see this single log statement but otherwise, everything works as expected.

If a retransformation fails (due to an unhandled error typically), you can split batches by using for example: AgentBuilder.RedefinitionStrategy.Listener.BatchReallocator.splitting() which keeps errors to a minimum since failed batches will be resubmitted in a divide and conquer fashion by breaking down to single type batches with failing classes.

You can also suppress installation errors by AgentBuilder.InstallationListener.ErrorSuppressing.INSTANCE.

For ignoring methods with incomplete signatures, you could add a MethodGraph.Compiler that probes and drops those methods. It would be a rather expensive operation, though. Normally, types with incomplete signatures are only loaded by accident but cannot work properly anyways.

@bsideup
Copy link
Author

bsideup commented Nov 25, 2019

@raphw

Sorry, I had a side effect that made me think that the whole instrumentation fails, so it is indeed only about logging.

However, adding AgentBuilder.InstallationListener.ErrorSuppressing.INSTANCE does not seem to make the statement disappear.

The error seems to happen when the class is being defined (initial classloading) and reported in ExecutingTransformer#transform:

listener.onError(typeName, classLoader, module, classBeingRedefined != null, throwable);

which goes directly to AgentBuilder.Listener.StreamWriting.toSystemError().withErrorsOnly()

@raphw
Copy link
Owner

raphw commented Nov 26, 2019

That was what I expected.

The installation only fails when errors are not catched by Byte Buddy and illegal byte code is issued. This should never happen but can occasionally with very rare byte code combinations, typically when other Java agents are involved.

@raphw
Copy link
Owner

raphw commented Dec 11, 2019

I assume the question is answered, therefore I am closing this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants