[ 
https://issues.apache.org/jira/browse/GROOVY-12740?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Scott Murphy Heiberg updated GROOVY-12740:
------------------------------------------
    Description: 
A {{@Requires}}, {{@Ensures}} or {{@Invariant}} closure whose body is not a 
plain expression crashes the compiler with an internal error instead of a 
compile error. A closure that declares an empty parameter list with an explicit 
arrow, which is a valid closure, crashes it too.

{code:groovy}
import groovy.contracts.*

class Account {
    @Requires({ return amount > 0 })  // 1
    // @Requires({ assert amount > 0 })  // 2
    // @Requires({ if (amount <= 0) throw new 
IllegalArgumentException('amount') }) // 3
    // @Requires({ -> amount > 0 })  // 4
    void deposit(BigDecimal amount) {}
}
{code}

Forms 1 to 3 fail with:
{noformat}
BUG! exception in phase 'semantic analysis' in source unit 'Account.groovy' 
Cannot invoke 
"org.codehaus.groovy.ast.expr.BooleanExpression.getNodeMetaData(Object)" 
because "booleanExpression" is null
        at 
org.apache.groovy.contracts.generation.AssertStatementCreationUtility.getAssertionStatement(AssertStatementCreationUtility.java:85)
        at 
org.apache.groovy.contracts.generation.AssertStatementCreationUtility.getAssertionStatements(AssertStatementCreationUtility.java:67)
        at 
org.apache.groovy.contracts.ast.visitor.AnnotationClosureVisitor.replaceWithClosureClassReference(AnnotationClosureVisitor.java:250)
{noformat}

Form 4 fails with:
{noformat}
BUG! exception in phase 'semantic analysis' in source unit 'Account.groovy' null
        at 
org.apache.groovy.contracts.ast.visitor.AnnotationClosureVisitor.replaceWithClosureClassReference(AnnotationClosureVisitor.java:236)
{noformat}

{{@Ensures}} and class-level {{@Invariant}} fail the same way with the same 
bodies, and so does {{@Invariant}} on a loop (reported as "unexpected 
NullPointerException").

h3. Cause

{{ExpressionUtils.getBooleanExpression}} builds the condition list from the 
closure's statements and adds a null entry for every statement that is not an 
expression statement, and {{ClosureExpressionValidator}} never checks statement 
kinds, so the null reaches {{AssertStatementCreationUtility}}. For form 4, 
{{ClosureExpression.getParameters()}} returns null (its documented value for a 
closure with no parameters at all, as opposed to the empty array for an 
implicit {{it}}) and {{AnnotationClosureVisitor}} passes it to 
{{Arrays.asList}}.

h3. Expected

Forms 1 to 3 report a compile error that names the statement kind, for example
{noformat}
[groovy-contracts] Annotation closure may only contain boolean expressions; 
return statements are not supported (e.g. use '@Requires({ argument1 > 0 })').
{noformat}
and form 4 compiles and behaves exactly like the closure without the arrow (a 
{{PreconditionViolation}} for {{deposit(-1)}}). A {{return}} is rejected rather 
than taken as the condition: each statement of the closure is its own 
assertion, so a return has no meaning in that list.

Reproduced on Groovy 6.0.0 and on master (df1fe66ed1). Pull request to follow.


  was:
A {{@Requires}}, {{@Ensures}} or {{@Invariant}} closure whose body is not a 
plain expression crashes the compiler with an internal error instead of a 
compile error. A closure that declares an empty parameter list with an explicit 
arrow, which is a valid closure, crashes it too.

{code:groovy}
import groovy.contracts.*

class Account {
    @Requires({ return amount > 0 })                                            
    // 1
    // @Requires({ assert amount > 0 })                                         
    // 2
    // @Requires({ if (amount <= 0) throw new 
IllegalArgumentException('amount') }) // 3
    // @Requires({ -> amount > 0 })                                             
    // 4
    void deposit(BigDecimal amount) {}
}
{code}

Forms 1 to 3 fail with:
{noformat}
BUG! exception in phase 'semantic analysis' in source unit 'Account.groovy' 
Cannot invoke 
"org.codehaus.groovy.ast.expr.BooleanExpression.getNodeMetaData(Object)" 
because "booleanExpression" is null
        at 
org.apache.groovy.contracts.generation.AssertStatementCreationUtility.getAssertionStatement(AssertStatementCreationUtility.java:85)
        at 
org.apache.groovy.contracts.generation.AssertStatementCreationUtility.getAssertionStatements(AssertStatementCreationUtility.java:67)
        at 
org.apache.groovy.contracts.ast.visitor.AnnotationClosureVisitor.replaceWithClosureClassReference(AnnotationClosureVisitor.java:250)
{noformat}

Form 4 fails with:
{noformat}
BUG! exception in phase 'semantic analysis' in source unit 'Account.groovy' null
        at 
org.apache.groovy.contracts.ast.visitor.AnnotationClosureVisitor.replaceWithClosureClassReference(AnnotationClosureVisitor.java:236)
{noformat}

{{@Ensures}} and class-level {{@Invariant}} fail the same way with the same 
bodies, and so does {{@Invariant}} on a loop (reported as "unexpected 
NullPointerException").

h3. Cause

{{ExpressionUtils.getBooleanExpression}} builds the condition list from the 
closure's statements and adds a null entry for every statement that is not an 
expression statement, and {{ClosureExpressionValidator}} never checks statement 
kinds, so the null reaches {{AssertStatementCreationUtility}}. For form 4, 
{{ClosureExpression.getParameters()}} returns null (its documented value for a 
closure with no parameters at all, as opposed to the empty array for an 
implicit {{it}}) and {{AnnotationClosureVisitor}} passes it to 
{{Arrays.asList}}.

h3. Expected

Forms 1 to 3 report a compile error that names the statement kind, for example
{noformat}
[groovy-contracts] Annotation closure may only contain boolean expressions; 
return statements are not supported (e.g. use '@Requires({ argument1 > 0 })').
{noformat}
and form 4 compiles and behaves exactly like the closure without the arrow (a 
{{PreconditionViolation}} for {{deposit(-1)}}). A {{return}} is rejected rather 
than taken as the condition: each statement of the closure is its own 
assertion, so a return has no meaning in that list.

Reproduced on Groovy 6.0.0 and on master (df1fe66ed1). Pull request to follow.



> groovy-contracts: a contract closure holding a return, assert or if 
> statement, or declaring an empty parameter list, crashes the compiler with a 
> NullPointerException
> ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: GROOVY-12740
>                 URL: https://issues.apache.org/jira/browse/GROOVY-12740
>             Project: Groovy
>          Issue Type: Bug
>          Components: groovy-contracts
>    Affects Versions: 6.0.0
>            Reporter: Scott Murphy Heiberg
>            Priority: Major
>
> A {{@Requires}}, {{@Ensures}} or {{@Invariant}} closure whose body is not a 
> plain expression crashes the compiler with an internal error instead of a 
> compile error. A closure that declares an empty parameter list with an 
> explicit arrow, which is a valid closure, crashes it too.
> {code:groovy}
> import groovy.contracts.*
> class Account {
>     @Requires({ return amount > 0 })  // 1
>     // @Requires({ assert amount > 0 })  // 2
>     // @Requires({ if (amount <= 0) throw new 
> IllegalArgumentException('amount') }) // 3
>     // @Requires({ -> amount > 0 })  // 4
>     void deposit(BigDecimal amount) {}
> }
> {code}
> Forms 1 to 3 fail with:
> {noformat}
> BUG! exception in phase 'semantic analysis' in source unit 'Account.groovy' 
> Cannot invoke 
> "org.codehaus.groovy.ast.expr.BooleanExpression.getNodeMetaData(Object)" 
> because "booleanExpression" is null
>       at 
> org.apache.groovy.contracts.generation.AssertStatementCreationUtility.getAssertionStatement(AssertStatementCreationUtility.java:85)
>       at 
> org.apache.groovy.contracts.generation.AssertStatementCreationUtility.getAssertionStatements(AssertStatementCreationUtility.java:67)
>       at 
> org.apache.groovy.contracts.ast.visitor.AnnotationClosureVisitor.replaceWithClosureClassReference(AnnotationClosureVisitor.java:250)
> {noformat}
> Form 4 fails with:
> {noformat}
> BUG! exception in phase 'semantic analysis' in source unit 'Account.groovy' 
> null
>       at 
> org.apache.groovy.contracts.ast.visitor.AnnotationClosureVisitor.replaceWithClosureClassReference(AnnotationClosureVisitor.java:236)
> {noformat}
> {{@Ensures}} and class-level {{@Invariant}} fail the same way with the same 
> bodies, and so does {{@Invariant}} on a loop (reported as "unexpected 
> NullPointerException").
> h3. Cause
> {{ExpressionUtils.getBooleanExpression}} builds the condition list from the 
> closure's statements and adds a null entry for every statement that is not an 
> expression statement, and {{ClosureExpressionValidator}} never checks 
> statement kinds, so the null reaches {{AssertStatementCreationUtility}}. For 
> form 4, {{ClosureExpression.getParameters()}} returns null (its documented 
> value for a closure with no parameters at all, as opposed to the empty array 
> for an implicit {{it}}) and {{AnnotationClosureVisitor}} passes it to 
> {{Arrays.asList}}.
> h3. Expected
> Forms 1 to 3 report a compile error that names the statement kind, for example
> {noformat}
> [groovy-contracts] Annotation closure may only contain boolean expressions; 
> return statements are not supported (e.g. use '@Requires({ argument1 > 0 })').
> {noformat}
> and form 4 compiles and behaves exactly like the closure without the arrow (a 
> {{PreconditionViolation}} for {{deposit(-1)}}). A {{return}} is rejected 
> rather than taken as the condition: each statement of the closure is its own 
> assertion, so a return has no meaning in that list.
> Reproduced on Groovy 6.0.0 and on master (df1fe66ed1). Pull request to follow.



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

Reply via email to