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

Paul King updated GROOVY-12631:
-------------------------------
    Description: 
h2. Description

Under {{@CompileStatic}}, assigning to an attribute ({{obj.@name = value}}) 
where the receiver class has no field {{name}}, but there is an extension 
getter {{getName()}}, crashes the compiler with an 
{{ArrayIndexOutOfBoundsException}}. A "No such attribute" error is expected 
instead, as for a class with no such field and no extension getter.

{code:groovy}
@groovy.transform.CompileStatic
void update(File file) {
    file.@text = 'b'     // Internal compiler error: 
ArrayIndexOutOfBoundsException
}
{code}

The same crash occurs for the compound form ({{file.@text += 'b'}}), and since 
5.0 for {{sb.@length = 1}} on a {{StringBuilder}}, where the GDK's extension 
{{getLength()}} matches.

For comparison, a class whose accessors are real methods is rejected correctly:

{code:groovy}
class B { int getX() { 1 }; void setX(int v) {} }

@groovy.transform.CompileStatic
void update(B b) {
    b.@x = 1    // [Static type checking] - No such attribute: x for class: B
}
{code}

Under {{@TypeChecked}}, {{file.@text = 'b'}} compiles and then fails at run 
time with {{MissingFieldException: No such field: text for class: 
java.io.File}}, as dynamic code does.

h2. Versions

Seen on 4.0.32, 5.0.6, 6.0.0 and master. The {{StringBuilder}} variant gives 
the expected error on 4.0.32, because the extension {{getLength()}} did not 
exist there.

h2. Cause

The type checker resolves the attribute expression to the extension *getter* 
and records it as the {{DIRECT_METHOD_CALL_TARGET}} of the 
{{AttributeExpression}}:

{noformat}
file.@text  ->  java.io.File#getText()  [extension]
sb.@length  ->  java.lang.StringBuilder#getLength()  [extension]
{noformat}

For an assignment, static compilation then writes a direct call to that 
zero-argument method and passes the assigned value as an argument. It fails 
while loading arguments:

{noformat}
java.lang.ArrayIndexOutOfBoundsException
    at 
org.codehaus.groovy.classgen.asm.OperandStack.doConvertAndCast(OperandStack.java:378)
    at 
org.codehaus.groovy.classgen.asm.OperandStack.doGroovyCast(OperandStack.java:331)
    at 
org.codehaus.groovy.classgen.asm.sc.StaticInvocationWriter.visitArgument(StaticInvocationWriter.java:579)
    at 
org.codehaus.groovy.classgen.asm.sc.StaticInvocationWriter.loadArguments(StaticInvocationWriter.java:522)
    at 
org.codehaus.groovy.classgen.asm.sc.StaticInvocationWriter.writeDirectMethodCall(StaticInvocationWriter.java:335)
    ...
    at 
org.codehaus.groovy.classgen.asm.BinaryExpressionHelper.evaluateEqual(BinaryExpressionHelper.java:665)
{noformat}

Attribute access is field access, so an accessor method, extension or 
otherwise, should never satisfy it.

h2. Suggested fix

When resolving an {{AttributeExpression}}, the type checker should not fall 
back to extension accessors. With no field, it should report {{No such 
attribute: <name> for class: <type>}}, as it already does when there is neither 
a field nor an extension getter. That turns this crash into a compile error 
under {{@CompileStatic}}, and the run-time {{MissingFieldException}} under 
{{@TypeChecked}} into a compile error too.

The read side of the same resolution is tracked in GROOVY-12632: {{sb.@length}} 
compiles under {{@CompileStatic}} and calls {{getLength()}}, while dynamic code 
throws {{MissingFieldException}}. A fix in the type checker would likely cover 
both.

h2. Tests

{{bugs.Groovy12524.testAttributeCompoundAssignOfExtensionPropertyIsRejected}} 
passes today only because of this crash. Once fixed, it should assert the "No 
such attribute" message.

  was:
h2. Description

Under {{@CompileStatic}}, assigning to an attribute ({{obj.@name = value}}) 
where the receiver class has no field {{name}}, but there is an extension 
getter {{getName()}}, crashes the compiler with an 
{{ArrayIndexOutOfBoundsException}}. A "No such attribute" error is expected 
instead, as for a class with no such field and no extension getter.

{code:groovy}
@groovy.transform.CompileStatic
void update(File file) {
    file.@text = 'b'     // Internal compiler error: 
ArrayIndexOutOfBoundsException
}
{code}

The same crash occurs for the compound form ({{file.@text += 'b'}}), and since 
5.0 for {{sb.@length = 1}} on a {{StringBuilder}}, where the GDK's extension 
{{getLength()}} matches.

For comparison, a class whose accessors are real methods is rejected correctly:

{code:groovy}
class B { int getX() { 1 }; void setX(int v) {} }

@groovy.transform.CompileStatic
void update(B b) {
    b.@x = 1    // [Static type checking] - No such attribute: x for class: B
}
{code}

Under {{@TypeChecked}}, {{file.@text = 'b'}} compiles and then fails at run 
time with {{MissingFieldException: No such field: text for class: 
java.io.File}}, as dynamic code does.

h2. Versions

Seen on 4.0.32, 5.0.6, 6.0.0 and master. The {{StringBuilder}} variant gives 
the expected error on 4.0.32, because the extension {{getLength()}} did not 
exist there.

h2. Cause

The type checker resolves the attribute expression to the extension *getter* 
and records it as the {{DIRECT_METHOD_CALL_TARGET}} of the 
{{AttributeExpression}}:

{noformat}
file.@text  ->  java.io.File#getText()  [extension]
sb.@length  ->  java.lang.StringBuilder#getLength()  [extension]
{noformat}

For an assignment, static compilation then writes a direct call to that 
zero-argument method and passes the assigned value as an argument. It fails 
while loading arguments:

{noformat}
java.lang.ArrayIndexOutOfBoundsException
    at 
org.codehaus.groovy.classgen.asm.OperandStack.doConvertAndCast(OperandStack.java:378)
    at 
org.codehaus.groovy.classgen.asm.OperandStack.doGroovyCast(OperandStack.java:331)
    at 
org.codehaus.groovy.classgen.asm.sc.StaticInvocationWriter.visitArgument(StaticInvocationWriter.java:579)
    at 
org.codehaus.groovy.classgen.asm.sc.StaticInvocationWriter.loadArguments(StaticInvocationWriter.java:522)
    at 
org.codehaus.groovy.classgen.asm.sc.StaticInvocationWriter.writeDirectMethodCall(StaticInvocationWriter.java:335)
    ...
    at 
org.codehaus.groovy.classgen.asm.BinaryExpressionHelper.evaluateEqual(BinaryExpressionHelper.java:665)
{noformat}

Attribute access is field access, so an accessor method, extension or 
otherwise, should never satisfy it.

h2. Suggested fix

When resolving an {{AttributeExpression}}, the type checker should not fall 
back to extension accessors. With no field, it should report {{No such 
attribute: <name> for class: <type>}}, as it already does when there is neither 
a field nor an extension getter. That turns this crash into a compile error 
under {{@CompileStatic}}, and the run-time {{MissingFieldException}} under 
{{@TypeChecked}} into a compile error too.

The read side of the same resolution is tracked in GROOVY-XXXXX: {{sb.@length}} 
compiles under {{@CompileStatic}} and calls {{getLength()}}, while dynamic code 
throws {{MissingFieldException}}. A fix in the type checker would likely cover 
both.

h2. Tests

{{bugs.Groovy12524.testAttributeCompoundAssignOfExtensionPropertyIsRejected}} 
passes today only because of this crash. Once fixed, it should assert the "No 
such attribute" message.


> CS: assigning to an attribute obj.@name = value where the receiver class has 
> no field name, but there is an extension getter getName(), crashes the 
> compiler with an ArrayIndexOutOfBoundsException
> ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: GROOVY-12631
>                 URL: https://issues.apache.org/jira/browse/GROOVY-12631
>             Project: Groovy
>          Issue Type: Bug
>          Components: Static compilation, Static Type Checker
>    Affects Versions: 6.0.0
>            Reporter: Paul King
>            Priority: Major
>
> h2. Description
> Under {{@CompileStatic}}, assigning to an attribute ({{obj.@name = value}}) 
> where the receiver class has no field {{name}}, but there is an extension 
> getter {{getName()}}, crashes the compiler with an 
> {{ArrayIndexOutOfBoundsException}}. A "No such attribute" error is expected 
> instead, as for a class with no such field and no extension getter.
> {code:groovy}
> @groovy.transform.CompileStatic
> void update(File file) {
>     file.@text = 'b'     // Internal compiler error: 
> ArrayIndexOutOfBoundsException
> }
> {code}
> The same crash occurs for the compound form ({{file.@text += 'b'}}), and 
> since 5.0 for {{sb.@length = 1}} on a {{StringBuilder}}, where the GDK's 
> extension {{getLength()}} matches.
> For comparison, a class whose accessors are real methods is rejected 
> correctly:
> {code:groovy}
> class B { int getX() { 1 }; void setX(int v) {} }
> @groovy.transform.CompileStatic
> void update(B b) {
>     b.@x = 1    // [Static type checking] - No such attribute: x for class: B
> }
> {code}
> Under {{@TypeChecked}}, {{file.@text = 'b'}} compiles and then fails at run 
> time with {{MissingFieldException: No such field: text for class: 
> java.io.File}}, as dynamic code does.
> h2. Versions
> Seen on 4.0.32, 5.0.6, 6.0.0 and master. The {{StringBuilder}} variant gives 
> the expected error on 4.0.32, because the extension {{getLength()}} did not 
> exist there.
> h2. Cause
> The type checker resolves the attribute expression to the extension *getter* 
> and records it as the {{DIRECT_METHOD_CALL_TARGET}} of the 
> {{AttributeExpression}}:
> {noformat}
> file.@text  ->  java.io.File#getText()  [extension]
> sb.@length  ->  java.lang.StringBuilder#getLength()  [extension]
> {noformat}
> For an assignment, static compilation then writes a direct call to that 
> zero-argument method and passes the assigned value as an argument. It fails 
> while loading arguments:
> {noformat}
> java.lang.ArrayIndexOutOfBoundsException
>     at 
> org.codehaus.groovy.classgen.asm.OperandStack.doConvertAndCast(OperandStack.java:378)
>     at 
> org.codehaus.groovy.classgen.asm.OperandStack.doGroovyCast(OperandStack.java:331)
>     at 
> org.codehaus.groovy.classgen.asm.sc.StaticInvocationWriter.visitArgument(StaticInvocationWriter.java:579)
>     at 
> org.codehaus.groovy.classgen.asm.sc.StaticInvocationWriter.loadArguments(StaticInvocationWriter.java:522)
>     at 
> org.codehaus.groovy.classgen.asm.sc.StaticInvocationWriter.writeDirectMethodCall(StaticInvocationWriter.java:335)
>     ...
>     at 
> org.codehaus.groovy.classgen.asm.BinaryExpressionHelper.evaluateEqual(BinaryExpressionHelper.java:665)
> {noformat}
> Attribute access is field access, so an accessor method, extension or 
> otherwise, should never satisfy it.
> h2. Suggested fix
> When resolving an {{AttributeExpression}}, the type checker should not fall 
> back to extension accessors. With no field, it should report {{No such 
> attribute: <name> for class: <type>}}, as it already does when there is 
> neither a field nor an extension getter. That turns this crash into a compile 
> error under {{@CompileStatic}}, and the run-time {{MissingFieldException}} 
> under {{@TypeChecked}} into a compile error too.
> The read side of the same resolution is tracked in GROOVY-12632: 
> {{sb.@length}} compiles under {{@CompileStatic}} and calls {{getLength()}}, 
> while dynamic code throws {{MissingFieldException}}. A fix in the type 
> checker would likely cover both.
> h2. Tests
> {{bugs.Groovy12524.testAttributeCompoundAssignOfExtensionPropertyIsRejected}} 
> passes today only because of this crash. Once fixed, it should assert the "No 
> such attribute" message.



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

Reply via email to