Bug 171953 - Code that calls introduced generic methods sometimes leads to compilation errors
Summary: Code that calls introduced generic methods sometimes leads to compilation errors
Status: RESOLVED FIXED
Alias: None
Product: AspectJ
Classification: Tools
Component: Compiler (show other bugs)
Version: 1.5.3   Edit
Hardware: Macintosh Mac OS X - Carbon (unsup.)
: P3 critical (vote)
Target Milestone: 1.5.4   Edit
Assignee: aspectj inbox CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2007-01-28 18:38 EST by Vincenz Braun CLA
Modified: 2007-10-10 10:21 EDT (History)
1 user (show)

See Also:


Attachments
testcase (2.62 KB, application/octet-stream)
2007-01-28 18:43 EST, Vincenz Braun CLA
no flags Details
second testcase (3.03 KB, application/java-archive)
2007-02-14 11:05 EST, Vincenz Braun CLA
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Vincenz Braun CLA 2007-01-28 18:38:26 EST
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.
Comment 1 Vincenz Braun CLA 2007-01-28 18:43:52 EST
Created attachment 57662 [details]
testcase

The sources of the testcase with scripts script-ok and script-bad.
Comment 2 Andrew Clement CLA 2007-01-30 04:37:46 EST
does the problem disappear if you turn off pipeline compilation with "-Xset:pipelineCompilation=false" on the command line.
Comment 3 Vincenz Braun CLA 2007-01-30 05:36:36 EST
Yes, the problem still occurs.
It occurs with aspectj 1.5.2, 1.5.2a and 1.5.3.
Could you reproduce this problem?
Comment 4 Andrew Clement CLA 2007-01-30 08:58:14 EST
i havent yet tried to recreate.
Comment 5 Vincenz Braun CLA 2007-02-14 11:03:28 EST
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
Comment 6 Vincenz Braun CLA 2007-02-14 11:05:15 EST
Created attachment 58974 [details]
second testcase
Comment 7 Andrew Clement CLA 2007-02-14 12:03:59 EST
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.
Comment 8 Andrew Clement CLA 2007-02-15 05:53:52 EST
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.
Comment 9 Vincenz Braun CLA 2007-02-15 08:14:41 EST
Thank you very much. Does this also fix the first testcase?
Comment 10 Andrew Clement CLA 2007-02-15 08:35:43 EST
i havent tried that testcase - i will take a look now. 
Comment 11 Andrew Clement CLA 2007-02-15 08:42:18 EST
no it doesnt fix that, that's a different problem.
Comment 12 Andrew Clement CLA 2007-02-20 11:11:24 EST
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
Comment 13 Vincenz Braun CLA 2007-02-20 11:33:16 EST
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.
Comment 14 Andrew Clement CLA 2007-02-21 03:40:24 EST
all fixes available in the latest AJ dev build.
Comment 15 Eclipse Webmaster CLA 2007-07-29 09:21:35 EDT
Changing OS from Mac OS to Mac OS X as per bug 185991