1

Suppose I have the following recursive function

public class MyClass{
    public int foo(int arg){
        ...
    }
}

I want to throw an exception in an aspect if the initial value for arg is say 10 (it's okay for it to be afterwards). I'm new to AspectJ, but came up with the following which doesn't seem to be working.

public aspect CheckBounds{
    pointcut initialCall(int x):
        call(int MyClass.foo(int))
        && !cflow(call(int MyClass.foo(int)))
        && args(x);

    before(int x) : initialCall(x){
        if(x == 10){
            throw new IllegalArgumentException("x must not be 10");
        }
    }
}

Do you have any suggestions/recommended ways of accomplishing this?

Frank Ibem
  • 828
  • 1
  • 11
  • 19

2 Answers2

1

Firstly, your own solution cannot work because it contains a syntax error: !withincode(call(int MyClass.foo(int))) does not compile because withincode() does not take a pointcut parameter, only a signature. So it should really be: !withincode(int MyClass.foo(int)).

Secondly, I think that what you really want is similar to (but not quite exactly) your initial solution because the solution from your own answer only works for direct recursion, not indirect recursion. Here is an example:

Driver application:

package de.scrum_master.app;

public class Application {
    public static void main(String[] args) {
        Application application = new Application();
        System.out.println("Directly recursive factorials:");
        for (int i = 0; i < 12; i++)
            System.out.printf("%2d! = %10d%n", i, application.factorial(i));
        System.out.println("\nIndirectly recursive factorials:");
        for (int i = 0; i < 12; i++)
            System.out.printf("%2d! = %10d%n", i, application.factorial_indirect(i));
    }

    public int factorial(int i) {
        return i > 1 ? i * factorial(i - 1) : 1;
    }

    public int factorial_indirect(int i) {
        return helper(i);
    }

    public int helper(int i) {
        return i > 1 ? i * factorial_indirect(i - 1) : 1;
    }
}

As you can see, factorial(int) calculates the factorial by direct recursion whereas factorial_indirect(int) does so via indirect recursion because it calls helper(int) which in turn calls the original method again. I am going to present an aspect which works for both situations, only blocking the initial call, no directly or indirectly recursive ones.

Aspect:

The original pointcut from your question was almost correct, it just should have used cflowbelow() instead of cflow().

Please note that I am not really throwing exceptions but only logging them for demo purposes so as not to interrupt the program flow.

package de.scrum_master.aspect;

import de.scrum_master.app.Application;

public aspect CheckBounds {
    pointcut factorialCall() :
        call(int Application.factorial*(int));

    pointcut initialFactorialCall(int i) :
        factorialCall() &&
        !cflowbelow(factorialCall()) &&
        args(i);

    pointcut initialFactorialCall2(int i) :
        factorialCall() &&
        !withincode(int Application.factorial*(int)) &&
        args(i);

    before(int i) : initialFactorialCall(i) {
        if (i < 1 || i == 10) {
            System.out.println(new IllegalArgumentException("x must be >=1 and != 10"));
        }
    }
}

Console log:

Directly recursive factorials:
java.lang.IllegalArgumentException: x must be >=1 and != 10
 0! =          1
 1! =          1
 2! =          2
 3! =          6
 4! =         24
 5! =        120
 6! =        720
 7! =       5040
 8! =      40320
 9! =     362880
java.lang.IllegalArgumentException: x must be >=1 and != 10
10! =    3628800
11! =   39916800

Indirectly recursive factorials:
java.lang.IllegalArgumentException: x must be >=1 and != 10
 0! =          1
 1! =          1
 2! =          2
 3! =          6
 4! =         24
 5! =        120
 6! =        720
 7! =       5040
 8! =      40320
 9! =     362880
java.lang.IllegalArgumentException: x must be >=1 and != 10
10! =    3628800
11! =   39916800

As you can see my test condition logs errors for initial values of 0 and 10 in both direct and indirect recursion cases. Now if we switch to initialFactorialCall2(i) in the before() advice, the log for the indirect case changes to:

Indirectly recursive factorials:
java.lang.IllegalArgumentException: x must be >=1 and != 10
 0! =          1
 1! =          1
 2! =          2
 3! =          6
 4! =         24
 5! =        120
 6! =        720
 7! =       5040
 8! =      40320
 9! =     362880
java.lang.IllegalArgumentException: x must be >=1 and != 10
10! =    3628800
java.lang.IllegalArgumentException: x must be >=1 and != 10
11! =   39916800

Please note the wrong reaction for 11! where an exception is also logged for the internal call of factorial_indirect(10). This is clearly wrong, so you want to go with the cflowbelow() solution instead.

kriegaex
  • 63,017
  • 15
  • 111
  • 202
  • Thanks for the explanation! Strangely though, I didn't get a syntax error with my original solution and it worked for direct recursion. Any ideas? – Frank Ibem Apr 23 '16 at 16:58
  • Impossible. Maybe you used copy & paste and forgot to adjust something here which you corrected in your IDE. I tried both your approaches with the results described in my answer. Just copy it again from here and see for yourself. :-) – kriegaex Apr 23 '16 at 17:33
  • Hello, I was wondering. How would you do it, so if you want the input to always be 1, to change the value in the parameter to 1, only in the first call? – Daniel Medina Sada May 05 '16 at 04:47
  • My answer is lengthy already and this question is a new one. Would you mind asking in a separate topic? I even know the answer. ;-) – kriegaex May 05 '16 at 09:12
0

It turns out that I was using the wrong pointcut. My intention was to ensure that the call to foo() was not done within foo (only a direct call). On changing 'cflow' to 'withincode' the advice is now functioning as expected:

public aspect CheckBounds{
    pointcut initialCall(int x):
        call(int MyClass.foo(int))
        && !withincode(int MyClass.foo(int))
        && args(x);

    before(int x) : initialCall(x){
        if(x == 10){
            throw new IllegalArgumentException("x must not be 10");
        }
    }
}
Frank Ibem
  • 828
  • 1
  • 11
  • 19
  • This solution cannot work because it contains a syntax error: `!withincode(call(int MyClass.foo(int)))` does not compile because `withincode()` does not take a pointcut parameter, only a signature. So it should really be: `!withincode(int MyClass.foo(int))`. Furthermore, this solution only works for direct, not indirect recursion. See my own answer for an illustration and a better solution. – kriegaex Apr 23 '16 at 15:54