Home > groovy, patterns > Towards Null-Safe Groovy

Towards Null-Safe Groovy

Currently I am implementing some experimental features from which some of them hopefully make it into GContracts next major release.

One of the issue I’ve been playing with is support for null-safe aka void-safe code. The main idea is to let GContracts provide compile-time checks for null-safe code parts. Letting it be ‘code parts’ only is the first restriction which we will have to introduce. We all know null is evil, but: null is not the new goto.

There are use-cases where assigning null to a variable might still be appropriate. Think of a double-linked list data-structure, where null could be needed to mark the first previous and the last next reference. However, the use of null more often leads to errors whenever a method call is executed on a variable which has been assigned to null.

In order to provide compile-time checking of instance variables, local variables and parameters I added a new annotation to GContracts annotation package: @NullSafe:

@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.FIELD, ElementType.LOCAL_VARIABLE, ElementType.PARAMETER})
public @interface NullSafe {}

In addition, I added the NullSafeASTTransformation which is supposed to do the compile-time checking stuff. To do that, we need a set of rules which enforce null-safety for variables annotated with @NullSafe:

Local Variables, Parameters

Whenever a local variable or a parameter is marked as null-safe, only assignments to

  • constants (except null)
  • other null-safe variables

are allowed. The AST transformation done so far checks for local variables marked with @NullSafe and takes care of valid assignments. Whenever an assignment does not conform to the rules stated above, a compile-time error is thrown. E.g. the following source code snippet fails during compilation:

package tests

import org.gcontracts.annotations.*

class A {

  def some_operation(@NullSafe def param1)  {
     def bla = "test"
     param1 = bla
  }
}

BUG! exception in phase 'semantic analysis' in source unit 'script1281039992077748355994.groovy' param1 must be assigned to NullSafe variables!
	at org.gcontracts.ast.visitor.NullSafeVisitor.visitBinaryExpression(NullSafeVisitor.java:62)

The same applies for local variables:

package tests

import org.gcontracts.annotations.*

class A {
  
  def some_operation()  {
     @NullSafe def bla = "test"
     def otherBla = "test2"

     bla = otherBla
  }
}

package tests

import org.gcontracts.annotations.*

class A {

  def some_operation()  {
     @NullSafe def bla = "test"
     def otherBla = "test2"

     bla = otherBla
  }
}

BUG! exception in phase 'semantic analysis' in source unit 'script12810425282531878012710.groovy' bla must be assigned to NullSafe variables!
	at org.gcontracts.ast.visitor.NullSafeVisitor.visitBinaryExpression(NullSafeVisitor.java:31)

Notice, the last code snippet won’t compile according to the rules defined above, as an assignment of a not-null safe variable to a null-safe variable is not valid. If we want to pass compile-time checks, we simply had to annotate otherBla as @NullSafe.

Instance Variables, Fields

Instance variables need to be assigned to a non-null value whenever the construction of the according object is finished, thus the object complies to the explicit class invariant. Whenever a method is called an assignment to null would trigger a compile-time failure:

package tests

import org.gcontracts.annotations.*

class A {

  @NullSafe String myProp

  def A()  {
    myProp = "test"
  }

  def some_op() { myProp = null }
}

BUG! exception in phase 'semantic analysis' in source unit 'script12810407257201736681757.groovy' myProp must be assigned to NullSafe variables!
	at org.gcontracts.ast.visitor.NullSafeVisitor.visitBinaryExpression(NullSafeVisitor.java:59)

Assignments from Non-Void Method Calls

In fact, this is only half the story. Whenever a variable is assigned to the return value of a method, we have no simple way to check at compile-time whether that method returns null. A possible solution for solving this scenario in terms of keeping code null-safe is to specify variable default values via annotation closures (btw: here starts the part I actually did not implement by now, so don’t get confused with the annotation class above):

package tests

import org.gcontracts.annotations.*

class A {

  def some_op() {
      @NullSafe({ "empty" }) def i = "aloha"

      i = some_function()
      // at this point, i will be "empty" not null!
  }

  def some_function() { return null }
}

The default value is assigned only when an assignment to the null-safe variable would result in a null reference.

Annotation closures would provide great flexibility to define arbitrary complex initialization code:


class Address {
    
    @NullSafe({ new City(name: 'dummy') }) City city
    // ... 
}

An alternative approach would be to annotate methods as @NullSafe, which would avoid calling methods not being marked with @NullSafe in null-safe assignments.

What’s next?

That’s it with my experimental @NullSafe feature. I know that this is a naive first approach, but at least its a starting point and I would really appreciate your feedback on this topic either in this article’s comment section or at the Groovy-Dev mailing list [1].

It would be very important to know whether this method works or if it is not applicable in real-world projects from your point of view. One issue is, that although GContracts uses AST transformations, it is not an integral part of the programming language, which could result in e.g. meta- or reflection-calls which make null-safe variables null etc. Just imagine Hibernate injecting null values into @NullSave instance variables – levering null-safe variables immediately . In fact, this is the point I am not completely clear about – does it make sense to introduce such a feature at library/AST transformation level?

[0] GContracts – Project Home Page
[1] Groovy-Dev mailing list @ nabble

Advertisements
Categories: groovy, patterns
  1. August 6, 2010 at 5:54 pm

    Most importantly, don’t make this part of GContracts. It should be a separate project.

    As to whether this would be helpful in the real world, I doubt it. IDEA has sophisticated static code analysis for Groovy (featuring type inference, control flow analysis, and obviously great IDE integration), but even there I’m getting quite a lot of false positives. Consequently I’ve turned off some of the checks. I’m not saying it’s impossible to create something useful, but that it will require a lot of work to cross the “more useful than annoying” threshold – even more so in a dynamic language like Groovy.

    Groovy++ would make a better target for static code analysis (because it’s fully statically typed), and it supports the same AST transforms. Maybe worth a try?

    By the way, here are some ideas as to what could be added to GContracts:
    – Contracts for interfaces
    – Per-parameter pre- and postconditions
    – Pre- and postconditions for properties (somehow attributed to getter/setter)
    – Specialized annotations for frequent cases, e.g. @NotNull for parameters/return values
    – Custom user-defined annotations along the lines of Spring’s custom stereotype annotations (e.g. allow users to define @NotNull on their own by annotating it with a @Precondition({ it != null}) meta-annotation)

    Cheers,
    Peter

    • August 7, 2010 at 4:01 pm

      hi peter, first of all thanks for your comments and sorry for the late reply. I will add your feature suggestions below as issues to gcontracts github page and comment on them there. Back to the discussion on null-safe Groovy code.

      @NullSafe is certainly implemented in a broad way when directly integrated into the programming language itself, I agree with you. I am not sure if this is really an issue of statically vs. dynamically typed code, since the type of the variable does not contribute much. But you’re right, it will be hard to implement this feature from an external library, relying on AST transformations only, so it should be part of standard Groovy/Groovy++ (as Dirk already mentioned in the groovy-dev mailing list) and not depend on GContracts.

      As you’ve mentioned, to get this feature right is pretty hard and it will require more than just starting to work on it and getting it work correctly over some iterations – especially in a dynamic language like Groovy. What I am concerned with is especially meta-programming and reflection, which can make declared null-safe variables virtually useless and that is exactly what makes this feature scream for tight language integration (again). But I guess this would mean a lot of work through various fields in the programming language and I am not sure if this really pays off.

      In a nutshell, maybe it won’t be worth the effort to integrate null-safe checks in the language itself, but GContracts/or some indepedent module could add support for null-safe code under certain conditions (e.g. don’t use meta-programming, reflection etc.).

      I would be interested if you see any major pitfalls with this approach which make it per se rather useless?

  2. August 6, 2010 at 6:36 pm

    Two more thoughts:

    – Have you looked at CodeNarc (http://codenarc.sourceforge.net/)?
    – Another potential GContracts feature: GDoc/JavaDoc integration.

    • August 7, 2010 at 4:08 pm

      Codenarc seems very interesting at first sight – I will have a look.

      javadoc/gdoc integration is already on my list.

  1. August 6, 2010 at 11:19 am

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: