javaaopaspectjpointcuts

About Policy Enforcement with AspectJ


I am using Aspectj for project-wide policy enforcement.

One thing I am trying to implement now is that there should be no logic in any setter methods except simple validation with Guava's Preconditions.check* methods.

public pointcut withinSetter() :
    withincode(public void set*(*));
public pointcut inputValidation() :
    call(public void Preconditions.check*(*));
public pointcut setFieldValue() : set(* *);
public pointcut entity() : within(com.mycompany.BaseEntity+);

declare warning :
entity() && withinSetter() && !setFieldValue() && !inputValidation():
"Please don't use Logic in Setters";

This works as expected, generating warnings for any non-setter code. However, it fails for constructs like this:

public void setFoo(final String newFoo) {
    Preconditions.checkNotNull(newFoo); // this is OK
    Preconditions.checkArgument(
                 newFoo.matches("\\p{Alpha}{3}"), // this generates a warning
                                                  // because String.matches()
                                                  // is called
                "Foo must have exactly 3 characters!");
    this.foo = newFoo;
}

So what I am looking for is a construct that would allow any code, as long as it happens inside the parameters to a Preconditions.check* call. Is there such a pointcut?


Solution

  • I know it is an old question, but I just stumbled across it while searching for something else.

    The answer is no, because in JVM bytecode there is no such thing as "logic inside a check* call". For example, newFoo.matches(..) is evaluated before the result is passed to Preconditions.checkArgument(..), very much like this:

    boolean match = newFoo.matches("\\p{Alpha}{3}");
    Preconditions.checkArgument(match, "Foo must have exactly 3 characters!");
    

    If the code was written like this, you would issue a warning anway, so why not if the same Java code, possibly resulting in similar or identical byte code, is written as a nested call? ;-)


    Update: I have created a little example:

    public class Application {
        public static void main(String[] args) {
            String newFoo = "Scrum";
            boolean match = newFoo.matches("\\p{Alpha}{3}");
            checkArgument(
                match,
                "Foo must have exactly 3 characters!"
            );
            checkArgument(
                newFoo.matches("\\p{Alpha}{3}"),
                "Foo must have exactly 3 characters!"
            );
        }
    
        private static void checkArgument(boolean status, String errorMessage) {
            if (!status)
                System.out.println(errorMessage);
        }
    }
    

    If you dump the byte code using javap -c Application you see this:

    Compiled from "Application.java"
    public class Application extends java.lang.Object{
    public Application();
      Code:
       0:   aload_0
       1:   invokespecial   #8; //Method java/lang/Object."<init>":()V
       4:   return
    
    public static void main(java.lang.String[]);
      Code:
       0:   ldc #16; //String Scrum
       2:   astore_1
       3:   aload_1
       4:   ldc #18; //String \p{Alpha}{3}
       6:   invokevirtual   #20; //Method java/lang/String.matches:(Ljava/lang/String;)Z
       9:   istore_2
       10:  iload_2
       11:  ldc #26; //String Foo must have exactly 3 characters!
       13:  invokestatic    #28; //Method checkArgument:(ZLjava/lang/String;)V
       16:  aload_1
       17:  ldc #18; //String \p{Alpha}{3}
       19:  invokevirtual   #20; //Method java/lang/String.matches:(Ljava/lang/String;)Z
       22:  ldc #26; //String Foo must have exactly 3 characters!
       24:  invokestatic    #28; //Method checkArgument:(ZLjava/lang/String;)V
       27:  return
    
    }
    

    As you can see, the byte code of lines 3-13 versus 16-24 in the dump is identical except for the storing and re-loading of the boolean value. Maybe this illustrates what I have said before.