Bug 71377 - Cannot advise private method call in around advice
Summary: Cannot advise private method call in around advice
Status: RESOLVED FIXED
Alias: None
Product: AspectJ
Classification: Tools
Component: Compiler (show other bugs)
Version: 1.2   Edit
Hardware: PC Windows XP
: P3 normal (vote)
Target Milestone: 1.2.1   Edit
Assignee: Adrian Colyer CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2004-08-04 10:21 EDT by Matthew Webster CLA
Modified: 2004-10-21 04:32 EDT (History)
0 users

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Matthew Webster CLA 2004-08-04 10:21:05 EDT
Pointcuts should match call joint points in advice. However AspectJ does not 
match calls to _private_ methods in around advice. The same applies to get/set 
pointcuts for _private fields. This is probably because in the testcase below 
a call to "privateMethod()" in the body of the around advice is a call to an 
accessor method:

        
ajc$inlineAccessMethod$bug_nnnn_JoinPointInAroundAdvice$bug_nnnn_JoinPointInAro
undAdvice$privateMethod("around");

public aspect JoinPointInAroundAdvice {

	private static Set privateCalls = new HashSet();
	private static Set publicCalls = new HashSet();
	
	pointcut internalCall () :
		call(* JoinPointInAroundAdvice.privateMethod(..));
	
	before () : internalCall () {
		privateCalls.add(thisJoinPoint);
	}

	pointcut externalCall () :
		call(* JoinPointInAroundAdvice.publicMethod(..));
	
	before () : externalCall () {
		publicCalls.add(thisJoinPoint);
	}
	
	pointcut execTest () :
		execution(* JoinPointInAroundAdvice.test());
	
	before () : execTest () {
		privateMethod("before");
		publicMethod("before");
	}
	
	void around () : execTest () {
		privateMethod("around");
		publicMethod("around");
		proceed();
	}
	
//	void around () : execTest () {
//		Runnable runnable = new Runnable () {
//			public void run () {
//				privateMethod("around closure");
//				publicMethod;
//				proceed();
//			}
//		};
//		runnable.run();
//	}
	
	after () : execTest () {
		privateMethod("after");
		publicMethod("after");
	}
	
	private static void privateMethod (String from) {
		System.out.println("? privateMethod() " + from);
	}
	
	public static void publicMethod (String from) {
		System.out.println("? publicMethod() " + from);
	}
	
	public static void test () {
		System.out.println("? test()");
		privateMethod("test");
		publicMethod("test");
	}
	
	public static void main (String[] args) {
		test();
		
		if (privateCalls.size() != publicCalls.size()) {
			throw new RuntimeException("Missing join point");
		}
		else {
			System.out.println("Success.");
		}
	}
}
Comment 1 Andrew Clement CLA 2004-08-06 13:04:13 EDT
I better write up where I am before I forget!!!

I thought this was going to be one of those bugs where you start diagnosing it
and discover there is a fatal flaw in AspectJ.  I think like this about most
hard bugs, but every single time (including this one) I discover that there is a
perfectly ideal framework for solving the bug, someone just forgot to fill in
this piece of it.

The compiler has a notion of an effective signature which is used for inter type
declarations - it enables you to do something clever (like call an accessor
method on another class) but pretend it casts an entirely different join point
shadow (like a field access).

As Matthew points out, the problem is that there is a grotty generated accessor
method:

ajc$inlineAccessMethod$bug_nnnn_JoinPointInAroundAdvice$bug_nnnn_JoinPointInAro
undAdvice$privateMethod("around");

that is being called which just delegates to the real 'privateMethod()'.

My first plan of attack was to work purely in the weaver and when I saw
something like this I'd try and undo the generated name (i know, i know) and
create an effective signature of 'privateMethod()' which would then be matched.
Yes, it was insane, I was young and foolish yesterday when I was trying that. 
Little did I know there was a grotty testcase in the suite that would never let
me do it that way.  Look at this:

aspect EnsureShipIsAlive {
    void around (Ship ship): Ship.helmCommandsCut(ship) {
        if ( ship.isAlive() ) {
            proceed(ship);
        }
    }
}

in SpaceWar.  The call 'ship.isAlive()' is another example of a missing shadow
that we haven't been able to match on.  That kind of call created a *ReAlLy*
horrible accessor, and isAlive() isn't even declared on ship, it is on the
superclass of ship, SpaceObject.

So, forget that, what I need to do is remember what the accessor was generated
for at compilation time and use that at weave time when trying to create this
'fake shadow'.  All I have to do to make this work is create an
effectivesignature attribute against the generated accessor method and then the
magic in BcelClassWeaver.matchInvokeInstruction() will pick up the attribute I
stuffed into the file and use it to create a shadow we can match against.

This worked perfectly ...

and then ...

I hit a problem with thisJoinPoint.  Objects that represent TJPs throughout the
code are usually created PRIVATE STATIC FINAL.  The problem is that I had now
surfaced some new join points in code that might get inlined, and if you advised
those join points and used thisJoinPoint then in the advice, we attempted to
reference a PRIVATE STATIC FINAL TJP instance in the class where the shadow
existed before inlining.  Usually resulting in a verify error.

So I modified the code that generates TJP instances to allow them to be public
if the enclosing method for the shadow is inside around advice (I didn't do
before and after advice as well, because that broke more tests and I believe we
don't inline before or after advice).

Was that it?

Well, that covers calls to private methods from around advice...

what it doesnt cover is sets or gets of private fields in around advice.  Aha, I
thought, this will be easy, I just create an effectivesignature at compile time
and the backend will do the right thing.

But no ... at compile time I haven't quite found a way (at the point we generate
the attribute) to know if it is a set or a get.  I know it is a field access but
not what kind.  How about I put in a compile time flag so you can choose which
you want?  ... only joking - I will get this fixed soon as I can.
Comment 2 Andrew Clement CLA 2004-08-09 06:40:59 EDT
Fixed.

I rejiggled the fix in AspectDeclaration.  Rather than generating new attributes
in the generateMethod() method, you can pass an optional set of additional
attributes when you call generateMethod() and these will be attached to the
generated code.

If the method being generated is an inlineAccessMethod for a private method
call, we have a MethodCall effective signature attribute.  For
inlineAccessMethods that represent field accesses we add either the field_set or
field_get attribute.  Creating the attributes outside of the generateMethod()
attribute enabled me to know enough to differentiate between field set and field
get.

Fix checked in, waiting for build before closing.
Comment 3 Andrew Clement CLA 2004-08-09 09:07:14 EDT
Fix available:

BUILD COMPLETE -  build.331
Date of build: 08/09/2004 11:57:04
Time to build: 117 minutes 38 seconds
Last changed: 08/09/2004 11:27:04
Last log entry: Fix for Bug 71377: Cannot advise private method call in around
advice
Latest good AspectJ jar available at:
download.eclipse.org/technology/ajdt/dev/aspectj-DEVELOPMENT.jar
Comment 4 Adrian Colyer CLA 2004-10-21 04:32:54 EDT
Fix released as part of AspectJ 1.2.1