0

This question is related to my previous quesiton here: How to create a default constructor with Byte Buddy

I am creating a subclass which first sets up some context before delegatig method invocation to some instance. This already works quite well with one issue remaining.

I get the following error when loading my dynamically created subclass.

    java.lang.VerifyError: Bad access to protected data in invokevirtual
Exception Details:
  Location:
    com/frequentis/ps/service/test/saga/ProxyTestSaga$ByteBuddy$Rm8DV3Lj.setTimeoutManager(Lcom/codebullets/sagalib/timeout/TimeoutManager;)V @3: invokevirtual
  Reason:
    Type 'com/frequentis/ps/service/test/saga/ProxyTestSaga' (current frame, stack[0]) is not assignable to 'com/frequentis/ps/service/test/saga/ProxyTestSaga$ByteBuddy$Rm8DV3Lj'
  Current Frame:
    bci: @3
    flags: { }
    locals: { 'com/frequentis/ps/service/test/saga/ProxyTestSaga$ByteBuddy$Rm8DV3Lj', 'com/codebullets/sagalib/timeout/TimeoutManager' }
    stack: { 'com/frequentis/ps/service/test/saga/ProxyTestSaga' }
  Bytecode:
    0x0000000: b200 0cb6 0010 57b1                    

    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:124)
    at net.bytebuddy.implementation.LoadedTypeInitializer$Compound.onLoad(LoadedTypeInitializer.java:200)
    at net.bytebuddy.implementation.LoadedTypeInitializer$Compound.onLoad(LoadedTypeInitializer.java:200)
    at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.initialize(DynamicType.java:3497)
    at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.load(DynamicType.java:3485)
    at com.frequentis.ps.service.test.saga.DynamicSagaTypeBuilder.buildAndLoad(DynamicSagaTypeBuilder.java:65)
    at com.frequentis.ps.service.test.saga.MoreAbstractSpaceSagaUnitTest.generateProxyClassForSagaUnderTest(MoreAbstractSpaceSagaUnitTest.java:274)
    at com.frequentis.ps.service.test.saga.AbstractSpaceSagaUnitTest.enhance(AbstractSpaceSagaUnitTest.java:105)
    at com.frequentis.ps.service.test.saga.ProxyTestSagaTest.before(ProxyTestSagaTest.java:27)

This is currently my byte buddy setup, which works for almost all cases except for the "setTimeoutManager" and "setState" methods which result in the shown error.

// called within the unit test base class (as shown in the call stack above)
builder = new ByteBuddy()
    .subclass(sagaUnderTestClass, ConstructorStrategy.Default.IMITATE_SUPER_TYPE_PUBLIC);
    // define default ctor if necessary that passes "null" values to the super ctor
    builder.method(isAnnotatedWith(StartsSaga.class).or(isAnnotatedWith(EventHandler.class)))
           .intercept(MethodDelegation.to(new ForwardingContextSetupInterceptor<(sagaUnderTest, contextSetter))
                                      .appendParameterBinder(Pipe.Binder.install(Forwarder.class)))
           .method(isPublic()
                           .and(isDeclaredBy(sagaUnderTest.getClass()).or(isDeclaredBy(AbstractSaga.class)
                           .and(not(isAnnotatedWith(StartsSaga.class))).and(not(isAnnotatedWith(EventHandler.class))))
           .intercept(MethodDelegation.to(sagaUnderTest))))

Do I need a different setup for setters?
or is it caused by the abstract class?
I dont really understand why it says bad access to protected data, does it mean the private field?

My type hierachy looks like this. The top most base class which declares the setters:

public abstract class AbstractSaga<SAGA_STATE extends SagaState> implements Saga<SAGA_STATE>, NeedTimeouts, NeedContext {
    private SAGA_STATE state;
    private boolean completed;
    private TimeoutManager timeoutManager;
    private ExecutionContext context;

    protected AbstractSaga() {
        completed = false;
    }

    // i have omitted some method for clarity

    protected ExecutionContext context() {
        return context;
    }

    @Override
    public SAGA_STATE state() {
        return state;
    }

    @Override
    public void setState(final SAGA_STATE state) {
        this.state = state;
    }

    @Override
    public boolean isFinished() {
        return completed;
    }

    protected void setFinished() {
        completed = true;
    }

    @Override
    public void setTimeoutManager(final TimeoutManager timeoutManager) {
        this.timeoutManager = timeoutManager;
    }
}

Extended by:

public abstract class AbstractSpaceSaga<SAGA_STATE extends SpaceSagaState, MESSAGE extends Message> 
    extends AbstractSaga<SAGA_STATE> {
}

And finally again extended by:

public class ProxyTestSaga 
    extends AbstractSpaceSaga<SpaceSagaState, TestRequest> {

    @StartsSaga
    public void handle(final TestRequest request) {
    }

    @EventHandler
    public void handle(final TestEvent event) {
    }
}

I hope the code is somehow understandable, I can add more info if required any time.

Community
  • 1
  • 1
leozilla
  • 1,296
  • 2
  • 19
  • 29

2 Answers2

1

This is definitely a bug in Byte Buddy as it should not allow you the creation of illegal byte code. Yet, I am confused about what is happening as it seems like Byte Buddy is picking up a method that is not compatible to the interceptor. I just looked at the implementation and this is not supposed to happen. Are you using the latest version (0.6.14)?

From the byte code, the interceptor results in the following code:

GETSTATIC (interceptor of type ProxyTestSaga)
INVOKEVIRTUAL (some method without arguments)
POP (drop value of the invoked method)
RETURN

The error is happening when invoking the delegation method. A method is invoked that is defined for the proxy type. From looking at Byte Buddy's code, this is impossible when using a MethodDelegation so I assume another interceptor is picked up somewhere?

What would really help me would a runnable example that reproduces the error. Maybe you can break down your code such that I can run it. Or, if your application is open source, maybe you can provide a link to it such that I can run your code. Alternatively, get in touch with me such that I can access your code in private.

Finally, you might want to look into the Forwarding instrumentation instead of using the MethodDelegation for your second interception. Beyond, I would appreciate if you helped me fixing this bug.

Rafael Winterhalter
  • 42,759
  • 13
  • 108
  • 192
  • Yes i am using 0.6.14 via maven. I will try to create a small example to reproduce the issue for you until monday evening and upload it to github. If I dont manage to create it I need to check if I can give you access to the code above. I really like your work so I will definetly help you to fix this. How can I contact you? – leozilla Jul 25 '15 at 18:10
  • I am unsure why this was an accepted answer there are red flags all over the code you provided. – Xype Aug 06 '17 at 08:42
0

Umm There are a lot of things wrong with the example you are providing, that would certainly make things go bad.

I feel like it is possible you should research abstracct classes a little better, how to use them and why to use them.

An abstract class can be used as a blueprint for subclasses to initiate off of. It can also be used as like a central library for classes to get ahole of common fields and methods. Abstract classes can only contain primitive m ethods. Abstracts never initialize. If you need it to initialize you must subclass it, and the subclass can not initialize unless it implements all of the aqbstract methods inside of the parent abstract class. From what I see you are not initializing your methods in either of your extensions down the line, therefore none of those classes every became an object. You can put getters and setters in there, but they can't get or set anything until some extension of the class down the line finally finishes turning everything on and becomes a full instance of an object. It is kind of like a built in library until you do something. I use it to hold a great many common things a package might use so I can just grab them over and over and have simpler classes elsewhere in the package.

Anyway to conclude here, your class ProxyTestSaga is not initialized, and must be abstract, because it extended the abstract class AbstractSpace saga and did not initalize all of its inherited methods, which you did not finish doing in ProxyTestSage.

I am almost afraid to make this an answer because you redacted some code. However I am confident that your redaction commment means there is more code in the original abstract that never got initialized. The error is happening when it hits the final extension of the abstract down the line.

Now that the long wind is out of the way, I see you have overrides decclared there and they look like they are to override and make an some accessibility to other classes for your loaded classes.

You should be able to solve your problem by finishing all initializations.

However I see some other things that leads me to believe you are using it wrong. It seems you are trying to hold a set of methods from several other classes to use later from one point. That is not what abstract is for. You use an interface to create a central repository of many outward facing methods.

To put it in simple terms you can use an interface to implement several methods, grab those methods and other classes can then use your interface to get ahold and override them for their purposes.

Another thing that makes me believe you are using the wrong thing. Abstract classes can only inerit 1 class. Interfaces are not classes and can extend multiple classes. So that is another thing wrong with your code. I am really not sure why your IDE is not screaming at you.

You should take a couple hours to deeply read on interfaces and abstracts and their rules. They each have their quirks and advantages, and are each good tools if used right.

Xype
  • 68
  • 11