raphw / byte-buddy

Runtime code generation for the Java virtual machine.
https://bytebuddy.net
Apache License 2.0
6.23k stars 804 forks source link

VerifyError: Bad access to protected data in invokevirtual #1576

Closed perlun closed 6 days ago

perlun commented 9 months ago

Hi,

Similarly as in https://github.com/raphw/byte-buddy/issues/539, we are seeing <subj> when trying to use ByteBuddy in our code base. More specifically, this exception (note that the bytecode is different to the one in that issue):

java.lang.VerifyError: Bad access to protected data in invokevirtual
Exception Details:
  Location:
    fi/hibox/centre/domain/service/PackageService$ByteBuddy$XKOo4HgE.clone()Ljava/lang/Object; @3: invokevirtual
  Reason:
    Type 'fi/hibox/centre/domain/service/PackageService' (current frame, stack[0]) is not assignable to 'fi/hibox/centre/domain/service/PackageService$ByteBuddy$XKOo4HgE'
  Current Frame:
    bci: @3
    flags: { }
    locals: { 'fi/hibox/centre/domain/service/PackageService$ByteBuddy$XKOo4HgE' }
    stack: { 'fi/hibox/centre/domain/service/PackageService' }
  Bytecode:
    0x0000000: b200 0cb6 000e b0                      

    at java.lang.Class.getDeclaredFields0(Native Method)
    at java.lang.Class.privateGetDeclaredFields(Class.java:2583)
    at java.lang.Class.getDeclaredField(Class.java:2068)
    at net.bytebuddy.implementation.LoadedTypeInitializer$ForStaticField.onLoad(LoadedTypeInitializer.java:163)
    at net.bytebuddy.implementation.LoadedTypeInitializer$Compound.onLoad(LoadedTypeInitializer.java:234)
    at net.bytebuddy.dynamic.TypeResolutionStrategy$Passive.initialize(TypeResolutionStrategy.java:103)
    at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.load(DynamicType.java:6325)
    at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.load(DynamicType.java:6313)
    at fi.hibox.centre.domain.immutable.AbstractIdentifiableEntityBuilder.buildTemporaryInstance(AbstractIdentifiableEntityBuilder.java:121)
    at fi.hibox.centre.server.internalbilling.billingrules.NextIntervalSubscriptionBill.calculateBill(NextIntervalSubscriptionBill.java:59)
    at fi.hibox.centre.server.internalbilling.billingrules.AbstractPriceSourceAwareBillingRule.calculateBill(AbstractPriceSourceAwareBillingRule.java:47)
    at fi.hibox.centre.server.internalbilling.billingrules.AbstractBillingRule.createServiceBill(AbstractBillingRule.java:34)
    at fi.hibox.centre.server.internalbilling.DefaultBillingRuleSet.createServiceBill(DefaultBillingRuleSet.java:78)
    at fi.hibox.centre.server.commerce.ServiceBillTestUtil.createServiceBill(ServiceBillTestUtil.java:61)
    at fi.hibox.centre.client.services.RegistrationServiceTest$1.answer(RegistrationServiceTest.java:168)
    at fi.hibox.centre.client.services.RegistrationServiceTest$1.answer(RegistrationServiceTest.java:165)
    at org.mockito.internal.stubbing.StubbedInvocationMatcher.answer(StubbedInvocationMatcher.java:42)
    at org.mockito.internal.handler.MockHandlerImpl.handle(MockHandlerImpl.java:103)
    at org.mockito.internal.handler.NullResultGuardian.handle(NullResultGuardian.java:29)
    at org.mockito.internal.handler.InvocationNotifierHandler.handle(InvocationNotifierHandler.java:34)
    at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.doIntercept(MockMethodInterceptor.java:82)
    at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.doIntercept(MockMethodInterceptor.java:56)
    at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor$DispatcherDefaultingToRealMethod.interceptAbstract(MockMethodInterceptor.java:161)
    at fi.hibox.centre.protocol.CentreServer$MockitoMock$nfiFyNVm.getCalculatedServiceBill(Unknown Source)
    at fi.hibox.centre.client.services.RegistrationService.getAccountSubscriptionServiceBill(RegistrationService.java:292)
    at fi.hibox.centre.client.services.RegistrationService.applyAccountSubscriptionVoucher(RegistrationService.java:460)
    at fi.hibox.centre.client.services.RegistrationServiceTest.testApplyAccountSubscriptionVoucher(RegistrationServiceTest.java:216)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at java.util.ArrayList.forEach(ArrayList.java:1259)
    at java.util.ArrayList.forEach(ArrayList.java:1259)

The calling code (where we try to create the ByteBuddy-based instance) looks like this. It's the load( getClass().getClassLoader() ) call that fails.

    public final T buildTemporaryInstance() {
        T instance = create();

        try {
            return (T)new ByteBuddy()
                    .subclass( this.instance.getClass() )
                    .method( named( "builder" ) ).intercept( throwing( IllegalStateException.class, "can't create builder from temporary " + instance.getClass().getSimpleName() + " instance" ) )
                    .method( any() ).intercept( MethodCall.invokeSelf().on( instance ).withAllArguments() )
                    .make()
                    .load( getClass().getClassLoader() )
                    .getLoaded()
                    .newInstance();
        }
        catch ( InstantiationException | IllegalAccessException e ) {
            throw new RuntimeException( e );
        }
    }

I looked at this in the debugger and I get the feeling that ByteBuddy somehow fails to get the fields from the type it has defined itself. :thinking: But I am clearly not an expert on the ByteBuddy internals so this may clearly be an incorrect understanding of the problem at hand.

image

I realize that this might be hard to say much about without a MCVE, but do you have any obvious ideas of what could go wrong here, or where we could/should look next? If so, much appreciated. :bow:

(ByteBuddy version: 1.14.11. JDK versions: reproduced on JDK 8, 17 and 21)

slovdahl commented 9 months ago

539 gave some good hints of what the problem was: the superclass constructor was package-protected but needs to be at least protected for this to work (with the default class loading strategy).

perlun commented 9 months ago

Yep, thanks for noting this down for the sake of other people. :bow: I'll leave this open for now in case @raphw or anyone else wants to improve the UX here, to make it easier for people running into this to know how they need to modify their code.

raphw commented 9 months ago

That's a tricky thing because the class loading will actually implicate a visibility here. This cannot be decided during compilation, and might be legal when loading, this is why it is not failing the class creation.