Community
Participate
Working Groups
It was really difficult to provide a testcase for this bug because it depends on the order of the source files given to ajc. When compiling the the attached sources with script-bad: ajc -cp "aspectjrt.jar" \ test/ListFactoryAspect.aj \ test/AbstractProcessor.java \ test/ListFactory.java \ test/ListFactoryConsumer.java \ test/Processor.java \ test/SimpleListFactoryConsumer.java \ -source 1.5 -outjar test.jar it results in compilation error: test/SimpleListFactoryConsumer.java:10 [error] Type mismatch: cannot convert from List<T> to List<List<String>> List<List<String>> list2 = this.createList(); ^^ 1 error Compiling with script-ok: ajc -cp "aspectjrt.jar" \ test/ListFactory.java \ test/ListFactoryConsumer.java \ test/SimpleListFactoryConsumer.java \ test/Processor.java \ test/ListFactoryAspect.aj \ test/AbstractProcessor.java \ -source 1.5 -outjar test.jar everything is fine. I do not know exactly what files are really necessary so there are quite a few.
Created attachment 57662 [details] testcase The sources of the testcase with scripts script-ok and script-bad.
does the problem disappear if you turn off pipeline compilation with "-Xset:pipelineCompilation=false" on the command line.
Yes, the problem still occurs. It occurs with aspectj 1.5.2, 1.5.2a and 1.5.3. Could you reproduce this problem?
i havent yet tried to recreate.
We ran into this problem again and it seems to be a more generall issue not only regarding generics. We have found advice not being woven at pointcuts where they should have without compilation errrors! So compiling with ajc is indeterministic and depends on the order of files given to ajc. The fact that you have a successfull compilation makes me really angry because we depend on ajc for transaction management etc. I compiled a second testcase. The problem here is having interface public interface Executable { void execute(); } and aspect public aspect ExecutionAspect { pointcut executions(Executable executable): execution(public void Executable.execute()) && this(executable); void around(Executable executable): executions(executable) { System.err.println(thisJoinPoint); } } leeds to advice not woven into public class SecondTestExecutable extends AbstractExecutable { public void execute() { // should not happen because of ExecutionAspect prevents execution throw new RuntimeException(); } public static void main(String[] args) { new SecondTestExecutable().execute(); } } with compilation using script-bad. Compilation with script-ok and everything is fine. Running java -cp aspectjrt.jar:test.jar test.SecondTestExecutable should not throw a RuntimeException Changing the pointcut to pointcut executions(Executable executable): execution(public void Executable+.execute()) && this(executable); and everything is fine again You found all files in the testcase2.jar
Created attachment 58974 [details] second testcase
You will not get an error if advice does not apply where you hope it will. But you will get an xlint warning if it doesnt apply anywhere - in your example the advice matches multiple places and because it advises at least one place, there is no xlint warning. To simplify your testcase I merged the aspects and changed it from an ITD of Runnable to an ITD of Serializable, which then removes the need for the ITD method. The program still behaves inconsistently between script-ok and script-bad. We can see exactly what is happening by turning on showWeaveInfo: Join point 'method-execution(void test.SecondTestExecutable.execute())' in Type 'test.SecondTestExecutable' (SecondTestExecutable.java:5) advised by around advice from 'test.ExecutionAspect' (ExecutionAspect.aj:9) Extending interface set for type 'test.AbstractExecutable' (AbstractExecutable.java) to include 'java.io.Serializable' (ExecutionAspect.aj) Join point 'method-execution(void test.SubTestExecutable.execute())' in Type 'test.SubTestExecutable' (SubTestExecutable.java:6) advised by around advice from 'test.ExecutionAspect' (ExecutionAspect.aj:9) Join point 'method-execution(void test.TestExecutable.execute())' in Type 'test.TestExecutable' (TestExecutable.java:5) advised by around advice from 'test.ExecutionAspect' (ExecutionAspect.aj:9) script-bad Extending interface set for type 'test.AbstractExecutable' (AbstractExecutable.java) to include 'java.io.Serializable' (ExecutionAspect.aj) Join point 'method-execution(void test.SubTestExecutable.execute())' in Type 'test.SubTestExecutable' (SubTestExecutable.java:6) advised by around advice from 'test.ExecutionAspect' (ExecutionAspect.aj:9) Join point 'method-execution(void test.TestExecutable.execute())' in Type 'test.TestExecutable' (TestExecutable.java:5) advised by around advice from 'test.ExecutionAspect' (ExecutionAspect.aj:9) In the bad case we can see an important join point is not advised - i currently have no idea why.
test and fix committed to CVS. The problem was that whilst walking the hierarchy up from the join point for 'methodexecution(void SecondTestExecutable.execute(..))' we were stopping at the first type that had an ITD applied to it. What the code *intended* to do was stop persuing any branches in the hierarchy that were introduced by ITD but it was just completely stopping persuing all branches once an ITD was encountered. So in this test we have the hierarchy: interface Executable { void execute(); } interface AnotherExecutable extends Executable {} class AbstractExecutable implements AnotherExecutable {} class SecondTestExecutable extends AbstractExecutable { void execute(); } and the pointcut execution(void Executable.execute()) when the join point for SecondTestExecutable.execute() is encountered, we walk up the hierarchy and discover it also has a signature 'Executable.execute()' and so the match is successful. When the ITD is in place, it changes the hierarchy : class AbstractExecutable implements AnotherExecutable,Serializable {} and because Serializable was introduced via ITD, we didn't walk any higher. When the intention was to continue walking up AnotherExecutable but not persue Serializable. i fixed the check for this situation. Types can be passed to the compiler in any order, but under the covers they are processed in the order of: aspects then classes - since we need to know all crosscutting information up front. Within the set of classes though, we cannot guarantee the order of processing. So if we happen to see AbstractExecutable before it has been changed to introduce Serializable then the test doesnt fail and we continue to walk up the hierarchy correctly. fix will be in next dev build.
Thank you very much. Does this also fix the first testcase?
i havent tried that testcase - i will take a look now.
no it doesnt fix that, that's a different problem.
Ok - I have now also fixed the problem in comment #1. As I half suspected, it was related to some caches maintained in the front end compilation phase - depending on the order for source processing the caches get populated in different orders and subsequent cache hits/misses occur in a different sequence. In the case here the problem is related to the fact that the Eclipse compiler relies on identity (==) equality, whereas AspectJ allows regular .equals() equality. When we process the ITD, we see a generic method declaration of <T> List<T> createList() And it is vitally important that we use the same typevariablebinding for each occurrence of 'T' - if we don't then it fails the eclipse == comparison and so we are unable to infer that List<T> is ok for the location where it is used in your program: List<List<String>> lls = createList() Basically the inference logic that calculates valid values for T looks at the context of where it is being used, comes up with the notion that <T> could be List<String> and if the reference to T in List<T> doesnt point at the same T, it doesn't try the substitution and that results in an error. Whether the same instance of TypeVariableBinding is used for 'T' depends on the caches in our eclipse factory which are used when transforming from a ResolvedMember (AspectJ's idea of a method) to an InterTypeMethodBinding (Eclipse compilers idea of a method). The compilation sequence works when there is a cache miss, the sequence fails when there is a cache hit (some other element processed from a previous source module has put something in the cache which we incorrectly find). The fix is to avoid looking in the cache for List<T>. We have code that avoid looking in the cache for <T> but it didnt allow for the use of a parameterized type where the type argument is a type variable. So I changed one line - now we get avoid looking in the cache and both compilation orders work fine. fixes commmitted
Thank you very much! I really appreciate the detailed explainations you give on bugs. This will help us to have a closer look into the source code of aspectj which we plan to do when we have some spare time in order to do some bug hunting on our own.
all fixes available in the latest AJ dev build.
Changing OS from Mac OS to Mac OS X as per bug 185991