[
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)