Bug 77166 - array new pointcut doesn't exist
Summary: array new pointcut doesn't exist
Status: RESOLVED FIXED
Alias: None
Product: AspectJ
Classification: Tools
Component: Compiler (show other bugs)
Version: unspecified   Edit
Hardware: All All
: P3 enhancement (vote)
Target Milestone: 1.5.1   Edit
Assignee: Andrew Clement CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2004-10-27 20:22 EDT by david.pearce CLA
Modified: 2006-09-14 08:07 EDT (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 david.pearce CLA 2004-10-27 20:22:17 EDT
public class test {
 public static void main(String argv[]) {
  Integer i = new Integer(0);
  int arr[] = new int[100];
 }
}

public aspect testAspect {
 before() : call(*.new(..)) {
  System.out.println("GOT ONE");
 }
}

> java test
> GOT ONE

The problem is array allocation does not correspond to any pointcut in AspectJ.
 I think this should be corrected so comes under call(*.new(..)).

Thanks,

David J. Pearce
Comment 1 Adrian Colyer CLA 2005-05-30 12:49:01 EDT
Lets think about the implications of this in the M4 timeframe...
Comment 2 Andrew Clement CLA 2005-11-15 08:31:12 EST
Not going to make it for 1.5.0 - moving to 1.5.1.  Making the join point appear
isnt too tough - but it needs: docs, large amount of testing with
this/target/args etc.  Not to mention repairing the (no-doubt) 100s of testcases
that will start failing due to its appearance (any that use a within that
contain array handling code are likely to behave differently)
Comment 3 Andrew Clement CLA 2006-01-13 09:48:59 EST
I have a prototype of this well.  The new join point currently (could be changed without too much work if someone has other ideas...) looks like this:

for 'new Integer[5]'

constructor-call(void java.lang.Integer[].<init>(int))

for 'new int[3]'

constructor-call(void int[].<init>(int))

for 'new int[3][5][7]'

constructor-call(void int[][][].<init>(int,int,int))

===
what do we think of that, Adrian?

args can be used to pull out the size of the array.  What I need to investigate further is:

- writing 'general' advice on creation of any array - can you reasonably obtain the size/dimensions of the array and the type of the array:
  "before(): call(*[].new(..))"
- what happens for the target PCD when used with this new join point. (probably relates to previous point...)



Comment 4 Andrew Clement CLA 2006-01-16 09:03:14 EST
OK.... the proposed new join point is working well.  Unfortunately in trying to write a nice use case demonstrating it, I hit a new problem - and Matthew and I have been discussing options - and I'm wondering about the value of the newarray joinpoint without a solution to this additional problem.  Consider this code:

line1: String[] strs = new String[5];
line2: strs[3] = "hello";

At line2, there is a 'get' join point for the reference to the 'strs' array object, there is no join point for the actual store of 'hello' into element #3 in the array.  This means you cannot tell how an array is being manipulated - even if you can now watch it being created in the first place.

the relevant instructions are:
- 'aaload' which takes an arrayreference and index off the stack and returns a value.
- 'aastore' which takes an arrayreference, index and value off the stack and stores the value at the specified index.

there are no joinpoints for these operations.

Now, this it the new joinpoint for array construction
   constructor-call(void java.lang.Integer[].<init>(int))

Suppose there are now two others:
  method-call(Integer java.lang.Integer[].get(int))
  method-call(void    java.lang.Integer[].set(int,Integer))

for aaload and aastore respectively.

Aha, you say - what about multidimensional arrays, shouldn't the signature of get() and set() involve int... rather than just int - well, maybe, maybe not.  there is no multidimensional array load/store.  Here is some new code:

line1: String[][][] ps = new String[5][3][2];
line2: ps[1][2][0] = "a";

here is the bytecode:

8:   aload_1                  STACK='ps'      (which is a String[][][] array)
9:   iconst_1                 STACK='ps':'1'
10:  aaload                   STACK='ps[1]'   (which is a String[][] array)
11:  iconst_2                 STACK='ps[1]':'2'
12:  aaload                   STACK='ps[1][2]'(which is a String[] array)
13:  iconst_0                 STACK='ps[1][2]':'0'
14:  ldc     #3; //String a   STACK='ps[1][2]':'0':'a'
16:  aastore                  STACK=

So, you see 'aastore' only ever takes 1 int as an index, relying on the array reference on the stack to be pointing at the right kind of object.

It is quite a stable pattern I think so it would be possible for AspectJ to see that kind of sequence and create a single join point for the entire array store to a multi dimensional array, but tricky to expose context correctly through 'args' so that (for example in this case) 1,2 and 0 are available at the join point for extraction through args.  However, the alternative is for AspectJ weaving to simply say the aastore exposes only with the context it knows about - but this would make it complicated to use - and maybe not very helpful.  For example in the above case, you'd like to detect the set to the 1,2,0'th element, but if you write:

before(int idx,String val): 
  call(void String[][][].set(int,String)) && args(idx,val) && if(idx==0) 

you have no idea that its 1,2,0 - it might be 4,2,0 or 1,0,0 - the information about the other indices that were used has been lost.

i bet someone has written a paper on this that I'm not aware of...

I'm not proposing to implement all this right now - just posting my findings for us to think about.  
- Maybe I'm wrong and I've overlooked something obvious.
- Maybe you can use the new array join point by putting advice on every get against the array and checking whats changed about the array, I dunno (hopefully inefficient).


(ps. I know my example above isn't quite right as my array was a local var rather than a field - but that doesn't change the behaviour of the array operations, it just means the first op in the bytecode would be getfield rather than aload)
Comment 5 Matthew Webster CLA 2006-01-16 10:23:28 EST
A couple of interesting references:
- http://www.cs.nccu.edu.tw/~chenk/Courses/BS-Project/JavaMemoryLeak.pdf: the need to advise array element access for lifecycle analysis
- http://www.cs.mcgill.ca/~iyuen/files/AOCheckpointingLibrary.ppt: ditto for checkpointing
Comment 6 Andrew Clement CLA 2006-01-16 11:50:30 EST
after some further discussion  ... the ability to recover 'the other' indices when storing a multi dimensional array becomes extremely painful if arrays are passed around:

public class P  {
  public void m() {
    String[][] strs = new String[5][10];
    m2(strs[1]);
  }
  public void m2(String[] strs) {
    strs[4]="hello";
  }
}

so the simple solution seems the best, only provide immediate context around the aastore, if the aspect needs to track multi-dimensional array operations it will have to attach appropriate advice to the aaloads.
Comment 7 david.pearce CLA 2006-01-16 15:32:57 EST
Other examples of where the array pointcut is useful include:

1) Heap profiler.  The idea is to track how much storage each method allocates --- including array objects.  This doesn't require advising array get/set methods, only new calls.  Similar to the memory leak example.

2) Object graph profiler.  Similar to above, except you want to track the graph of objects and the links between them for debugging / profiling purposes (or, perhaps, for enforcing dynamic ownership).  This also needs the array get/set pointcut.  Similar to the checkpointing example.



Comment 8 Andrew Clement CLA 2006-01-20 05:59:15 EST
new array join point is in.  Has to be activated with 

  -Xjoinpoints:arrayconstruction


----------8<----------------
public class Six {
  public static void main(String []argv) {
    Integer[] Is = new Integer[5];
  }
}

aspect X {
  before(int n): call(Integer[].new(int)) && args(n) { System.err.println("Array size = "+n);}
}
----------8<----------------

If you want the array itself, write after returning advice:

----------8<----------------
aspect X {

  after() returning(Object o): call(*.new(..)) && within(Twelve) {
	   System.err.println("It is "+o.getClass());
	   System.err.println("Is it an array? "+o.getClass().isArray());
	   System.err.println("Component type is "+o.getClass().getComponentType());
	   System.err.println("--");
  }
  
}
----------8<----------------

no doubt not quite perfect, but enough for users to start testing...
Comment 9 Andrew Clement CLA 2006-04-04 14:05:59 EDT
available in 1.5.1 - if we decide to do array element set/get that should be covered in a separate request.
Comment 10 Matthew Webster CLA 2006-09-14 08:07:36 EDT
(In reply to comment #9)
> available in 1.5.1 - if we decide to do array element set/get that should be
> covered in a separate request.

See Bug 157031.