Frank Frank - 7 months ago 13
Java Question

Validate parameters for initial call to recursive function with AspectJ

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?

Answer

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 loggin 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.

Comments