Saturday 13 August 2011

Immutable enforcement with AspectJ

Been messing around with AspectJ and out of curiosity I wondered if I could implement something to enforce immutability.

Came up with this.

If we label a type as @Immutable then the aspect verifies that the class really is immutable.

Primitive types are easy to check for immutability - just make sure they are "final".

However, fields that are objects are more tricky. The approach I've taken here is to allow object fields only if the field's class has also been marked as @Immutable.

There are a bunch of classes that we know to be immutable and we don't want to keep putting @Immutable on fields of this type (eg String). My approach here has been to put a set of trusted immutable types in the aspect. String and Float are the only ones in my example below.


package john.aspects.immutable;

public aspect ImmutableAspect {

    pointcut field_is_marked_immutable_mutation() :  set(@Immutable * *);

    pointcut set_non_final_field() : set(!final * *);

    /*
     * add common known immutable classes here so you can use them without
     * having to label field as Immutable too. worth doing this to avoid
     * excessive use of Immutable at field level. too much local labelling of
     * fields may lead to accidental labelling of a mutable type.
     */
    pointcut known_immutable_type() : set(String *) || set(Float *);

    pointcut is_safe_field_type() : known_immutable_type() 
                                    || field_is_marked_immutable_mutation();
-
    pointcut set_field_where_type_is_not_immutable() : set( (!@Immutable *) *);

    pointcut set_field_type_is_mutable() : set_field_where_type_is_not_immutable() 
                                            && !is_safe_field_type();

    pointcut class_is_immutable() : @within(Immutable);

    declare error : set_field_type_is_mutable() 
                    && class_is_immutable() 
                    : "All fields on Immutable class must be also be Immutable";

    declare error : set_non_final_field() 
                    && class_is_immutable() 
                    : "Illegal mutation of field on Immutable class - field should be final";

}
 
An example usage ..
 
package john.aspects.immutable;

import java.util.Date;

// turn on Immutability checks for this class
@Immutable
public class ImmutableTest {

    // its ok to use an Immutable class as a field
    final ImmutableTest ok = null;

    // but not ok unless it's final
    ImmutableTest bad_nonfinal = null;

    // primitive finals work ok
    final int ok_final_primitive_init = 0;

    // but not ok unless final
    int bad_NONFINAL_primitive_init = 0;

    // a class without the immutable annotation isn't trusted.
    final Integer bad_Integer_doesnt_have_annotation = null;
    
    // however I can tell the Aspect that this instance is trusted
    @Immutable
    final Integer ok_Integer_can_be_overridden = null;

    // however I can tell the Aspect that this instance is trusted
    @Immutable
    Integer bad_because_its_non_final = null;

    // obviously Date is mutable so lying is a bad thing.
    // if you do this then there's nothing the aspect can do to prevent you.
    @Immutable
    final Date ok_final_immutableoverride_class_ctorinit = null;

    // We can add trusted types to the aspect and String is one of them.
    // so I don't need to put Immutable here.
    final String ok_final_immutable_init = "";
    
}



The annotation is trivial.
Can't use the JCIS Immutable annotation because it cannot be applied to a field.


package john.aspects.immutable;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.TYPE})
public @interface Immutable { }




Still trying to work out how to extend the coverage to the superclass. I want to enforce that the superclass is either a type that also carries @Immutable or is Object.

Hardware Hacking

Since the last post here I've taken to a bit of further hardware hacking and most of this is recorded on Hackaday  https://hackaday.io/j...