Bug 508661 - Class is woven second time after modification by JMockit
Summary: Class is woven second time after modification by JMockit
Status: NEW
Alias: None
Product: AspectJ
Classification: Tools
Component: Runtime (show other bugs)
Version: 1.8.9   Edit
Hardware: PC Windows 7
: P3 normal (vote)
Target Milestone: ---   Edit
Assignee: aspectj inbox CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2016-12-05 05:23 EST by Denys Khanzhyiev CLA
Modified: 2016-12-09 12:02 EST (History)
1 user (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Denys Khanzhyiev CLA 2016-12-05 05:23:20 EST
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.
Comment 1 Andrew Clement CLA 2016-12-06 18:30:10 EST
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?
Comment 2 Andrew Clement CLA 2016-12-06 18:42:41 EST
> 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?
Comment 3 Denys Khanzhyiev CLA 2016-12-07 02:16:31 EST
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.
Comment 4 Andrew Clement CLA 2016-12-08 15:34:13 EST
> 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?
Comment 5 Denys Khanzhyiev CLA 2016-12-08 18:08:59 EST
> 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.
Comment 6 Andrew Clement CLA 2016-12-09 12:02:02 EST
> 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.