Groovy Quickie: AST Transformations and TypeResolver NPs

I have seen this exception every now and than, but today I write this blog post mainly to remind myself of the error cause (and share it with the reader, of course :-)).
 

Caused by: java.lang.NullPointerException
    at com.sun.beans.x.resolve(TypeResolver.java:203)
    at com.sun.beans.TypeResolver.resolve(TypeResolver.java:218)
    at com.sun.beans.TypeResolver.resolve(TypeResolver.java:169)
    at com.sun.beans.TypeResolver.resolve(TypeResolver.java:218)
    at com.sun.beans.TypeResolver.resolveInClass(TypeResolver.java:96)
    at java.beans.FeatureDescriptor.getParameterTypes(FeatureDescriptor.java:387)
    at java.beans.MethodDescriptor.setMethod(MethodDescriptor.java:114)
    at java.beans.MethodDescriptor.<init>(MethodDescriptor.java:72)
    at java.beans.MethodDescriptor.<init>(MethodDescriptor.java:56)
    at java.beans.Introspector.getTargetMethodInfo(Introspector.java:1150)
    at java.beans.Introspector.getBeanInfo(Introspector.java:416)
    at java.beans.Introspector.getBeanInfo(Introspector.java:163)
    at groovy.lang.MetaClassImpl$15.run(MetaClassImpl.java:2924)
    at java.security.AccessController.doPrivileged(Native Method)
    at groovy.lang.MetaClassImpl.addProperties(MetaClassImpl.java:2922)
    at groovy.lang.MetaClassImpl.initialize(MetaClassImpl.java:2905)

 
GContracts issue #36 [0] mentions this issue occurring on Oracle JRE 7.0 Update 10 with 1.2.9. I located the error cause in the AST transformation that generates a class from the annotation closure expression. The code takes the Parameter instances of the current MethodNode and use them directly to create a new MethodNode.
 

parameters.addAll(new ArrayList<Parameter>(Arrays.asList(methodNode.getParameters())));
// ...


MethodNode method =
                answer.addMethod("doCall", ACC_PUBLIC, ClassHelper.Boolean_TYPE, parameters.toArray(new Parameter[parameters.size()]), ClassNode.EMPTY_ARRAY, expression.getCode());
// ...


 
In most cases this approach works out-of-the-box. But problems occur when generic method parameters come into play:
 
class A {

   @Ensures ({ type.isInstance( result ) })
   <T> T extension( Class<T> type, T value )
   {
     return value
  }
}
 
This caused the generated MethodNode to be equipped with a formal type parameter T, although the newly generated method itself was not generic. That has been the cause for the NullPointerException being thrown during runtime during bean introspection in java.beans.FeatureDescriptor#getParameterTypes():
 

static Class[] getParameterTypes(Class base, Method method) {
        if (base == null) {
            base = method.getDeclaringClass();
        }
        return TypeResolver.erase(TypeResolver.resolveInClass(base, method.getGenericParameterTypes()));
    }

 

Conclusion

If you are writing AST transformations that's definitely a dangerous trap. When reusing Parameter objects from existing MethodNode instances, one needs to be careful and create deep copied instances.

[0] Issue #36 - GContracts