Scott Murphy Heiberg created GROOVY-12742:
---------------------------------------------

             Summary: groovy-contracts: a class invariant makes the trait 
forwarders and trait property accessors of the class return null
                 Key: GROOVY-12742
                 URL: https://issues.apache.org/jira/browse/GROOVY-12742
             Project: Groovy
          Issue Type: Bug
          Components: groovy-contracts
    Affects Versions: 6.0.0
            Reporter: Scott Murphy Heiberg


h3. Problem

A class-level {{@Invariant}} on a class that implements a trait makes every 
method the trait contributes return {{null}}; a primitive return fails with a 
cast exception:

{code:groovy}
import groovy.contracts.*

trait Named {
    String name = 'x'
    String greet() { "hi $name" }
    int one() { 1 }
}

@Invariant({ count >= 0 })
class Counter implements Named {
    int count
}

def c = new Counter()
assert c.greet() == 'hi x'   // greet() returns null
c.one()                       // GroovyCastException: Cannot cast 'null' to 
class 'int'
{code}

Trait properties are affected too ({{getSize()}} of a trait property {{int 
size}} fails the same way). The interface equivalent, where the class writes 
the method bodies itself, is fine. Reproduced on Groovy 6.0.0 and on master 
(df1fe66ed1).

h3. Cause

{{ClassInvariantGenerator.addInvariantAssertionStatement}} weaves the invariant 
call before each {{return}} only when the method body is a block. The 
forwarders and property accessors that {{TraitComposer}} generates in the 
implementing class have a bare expression or return statement as body, so the 
generator fell back to appending the (void) invariant call after it, and the 
forwarder's implicit return value was lost. The same fallback left a 
{{@Synchronized}} body unwrapped, so an explicit {{return}} inside it skipped 
the invariant.

h3. Fix

Normalise the body to a block first (the {{ensureBlockBody}} step the 
postcondition path already performs since GROOVY-12084), so the implicit return 
is made explicit and the invariant is checked before each return. Trait 
forwarders and property accessors keep their values and are checked; the 
{{@Synchronized}} explicit-return case is checked as well. Pull request to 
follow.




--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to