Community
Participate
Working Groups
Background: After modification of an aspect in our code a test started to fail with ClassFormatError. The test uses JMockit on LTW woven class. Further investigation and tracing showed that ClassFormatError is thrown after aspectj agent modifies class second time - when JMockit tries to redefine class. The whole picture is the following: Formerly (no error): Class M is woven with aspects A and B. Aspect A contains several around aspects, so it generates AspectJ closures for M. Aspect B is there by strange reason (it should not be there, but it is not related to this bug) - the result is that Aspect B adds to M marker interface and a field referencing aspect B instance. As B is 'perthis' aspect. I have traced that in this situation M is not woven second time as it generates closures and so added as a key to generatedClasses map in WeavingAdaptor. Now after change in Aspect A: Aspect A has changed so it does not generate closures and it is not added to generatedClasses and M is woven second time by both A and B. After that it has 2 times modified constructor by A (no problem) and 2 times marker interface and field with same name by B - which JVM does not like. I see 2 problems here: 1. WeavingAdaptor should remember all woven classes, not only those with generated closures. 2. Check if you do not add something with the same name second time. About aspect B may be it is worth of filling separate bug report. It has simple signature public aspect CacheMethodResultAspect perthis(cache()) { pointcut cache() : execution(@CacheMethodResult * *.*(..)); Object around() : cache() { ... } by some reason it adds marker interface and field to classes which have no methods annotated with @CacheMethodResult, but woven by other aspects.
Unless you are switching on overweaving (which you don't mention) what typically happens (when weaving multiple times): - on first weave we store a diff describing how to get back to the original form of a class from the woven version - on secondary weave the diff is applied to go back to the original and the aspect is applied again. That means you should see no duplicate weaving inside a class. It isn't clear to me how you are actually doing the double weaving though. If you are using overweaving where it doesn't revert to the original form, it shouldn't create duplicates but I will admit there are very few testcases for that, so something may be getting overlooked. So how/what are you using to do the weaving, pure ltw? (in which case how are you making it fire twice), weavingurlclassloader? compile time weaving then ltw?
> About aspect B may be it is worth of filling separate bug report. > It has simple signature > > public aspect CacheMethodResultAspect perthis(cache()) { > > pointcut cache() : execution(@CacheMethodResult * *.*(..)); > > Object around() : cache() { > ... > } > > by some reason it adds marker interface and field to classes which have no methods > annotated with @CacheMethodResult, but woven by other aspects. I would presume the supertype of the type you don't think should have the marker interface, has a marked method. If your aspect is applied to this type: class D { public void m() {} } class D will not get ajc$mightHaveAspect but here: class B { @CacheMethodResult public void m() {} } class C extends B { } Both B and C will get it because you can have an instance of C that will run method m(). Is that what is happening?
Yes this is pure LTW with javaagent, aop.xml contains only aspects and filter by package so all options are default. The aspectj agent (actually transformer) is called several times on the same class as JMockit redefines classes. As for class hierarchy - super class of M is Object.
> As for class hierarchy - super class of M is Object. Hmm, is that true even after jmockit has transformed classes? (I don't know what jmockit does to classes). Going to be tricky for me to sort out without a way to recreate it. If you can distill it down to a mvn project or similar I will be able to investigate further. If jmockit is altering the output of LTW and then feeding that back in for another LTW, I'm surprised it doesn't crash in a more serious way because that will damage the diff stored in the class that should be applied to the current form of the type to get back to the original bytes. This problem is why overweaving was invented, maybe you want to turn that on: http://andrewclement.blogspot.ca/2010/05/aspectj-overweaving.html Can you use simple binary weave before running it to avoid the need for multiple runs of the weaver at load time?
> Hmm, is that true even after jmockit has transformed classes Yes. I also think the problem with strange CacheMethodResultAspect application is not related to JMockit. JMockit just rewrites methods, it does not add interfaces or change inheritance. Yes JMockit redefines mocked class before run of unit test and after it defines the class back to original. But this calls all transforms registered by java agents. So org.aspectj.weaver.loadtime.ClassPreProcessorAgentAdapter#transform is called each time JMockit redefines class and i see INFO: (Enh120375): AspectJ attempting reweave of '" + className + "' on stdout As I already said for the class with generated closures this reweaving stops in org.aspectj.weaver.tools.WeavingAdaptor#couldWeave but if the class has no ajc closures - it will be woven second time. As workaround I just have added dummy around advice to aspect A - and now my unit tests work with JMockit. But it looks like error - that org.aspectj.weaver.tools.WeavingAdaptor remembrs only classes with generated closures.
> I also think the problem with strange CacheMethodResultAspect application is not related to JMockit. I wasn't blaming JMockit specifically, more the existing of any secondary agent redriving the process. I don't have a test harness where I can play around with that. All my basic LTW tests are working just fine. Having to recreate this will take longer than I have cycles for right now so I'm afraid the fixes won't make it into 1.8.10. But glad you found some kind of workaround.