Paul King created GROOVY-12631:
----------------------------------
Summary: 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
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.
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)