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

Paul King resolved GROOVY-12728.
--------------------------------
    Fix Version/s: 7.0.0-alpha-1
                   6.0.1
         Assignee: Paul King
       Resolution: Fixed

> groovy-contracts: a class literal given to @Requires, @Ensures or @Invariant 
> is silently never checked, and crashes the compiler when the contract is 
> inherited
> ---------------------------------------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: GROOVY-12728
>                 URL: https://issues.apache.org/jira/browse/GROOVY-12728
>             Project: Groovy
>          Issue Type: Bug
>          Components: groovy-contracts
>    Affects Versions: 6.0.0
>            Reporter: Paul King
>            Assignee: Paul King
>            Priority: Major
>             Fix For: 7.0.0-alpha-1, 6.0.1
>
>
> The value of {{@Requires}}, {{@Ensures}} and {{@Invariant}} is declared as 
> {{Class}}, so a class literal passes the annotation checks. The contract 
> transforms then take it for the condition class they generate from a closure:
> * With no inherited contract, the condition is never checked, and nothing 
> reports that.
> * When the method or class also inherits a contract, compilation fails with 
> an internal error.
> h3. Silently never checked
> {code:groovy}
> import groovy.contracts.*
> class A {
>     @Requires(Object) void m(int x) {}
> }
> new A().m(-1)   // returns normally
> {code}
> With {{@Requires(\{ x > 0 \})}} the same call throws 
> {{PreconditionViolation}}.
> The same happens with:
> * {{@Requires(String)}} and {{@Requires(Object.class)}};
> * {{@Ensures(Object) int m(int x) \{ x \}}}, where {{m(-1)}} returns -1 (with 
> {{@Ensures(\{ result > 0 \})}} it throws {{PostconditionViolation}});
> * {{@Invariant(Object)}} on a class whose state breaks the invariant (with 
> {{@Invariant(\{ count >= 0 \})}} it throws {{ClassInvariantViolation}}).
> h3. Compiler crash with an inherited contract
> {code:groovy}
> import groovy.contracts.*
> class B { @Requires({ x > 0 }) void m(int x) {} }
> class C extends B { @Requires(Object) void m(int x) {} }
> {code}
> {noformat}
> BUG! exception in phase 'instruction selection' in source unit 
> 'Script1.groovy' Index 0 out of bounds for length 0
> {noformat}
> Inheritance gives the same crash for:
> * {{@Ensures(Object)}} overriding a method that has {{@Ensures(\{ result > 0 
> \})}};
> * {{@Invariant(Object)}} on a subclass of a class that has {{@Invariant(\{ 
> count >= 0 \})}}.
> h3. Cause
> * The transforms replace each condition closure with a reference to a 
> generated class. Later passes then treat any value that is not a closure as 
> that replacement ({{BaseVisitor.getReplacedCondition}}). So a class literal 
> written by the user goes down the "already generated" path.
> * {{BaseVisitor.asConditionExecution}} builds {{new <value class>(this, 
> this).doCall(...)}} and calls {{getMethods("doCall").get(0)}}. {{Object}} has 
> no {{doCall}}, which is the IndexOutOfBounds above.
> * Without an inherited contract, that code path is never reached, so the 
> condition is silently skipped.
> h3. Suggested fix
> Reject any value that is not a closure when the annotation is first 
> processed, before the closure is replaced. GROOVY-12723 now does this for 
> {{@Modifies}}:
> {noformat}
> @Modifies requires a closure listing the modifiable targets; use 
> @Modifies({}) to declare that nothing is modified; found a class 
> java.lang.Object
> {noformat}
> Today a list or string value is already rejected by the generic annotation 
> check ("Only classes, closures, and method references can be used for 
> attribute 'value'"). Only class literals get through.



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

Reply via email to