Paul King created GROOVY-12700:
----------------------------------

             Summary: SC: multiple-assignment declaration resolves a name on 
the right-hand side to the variable being declared instead of the field or 
property
                 Key: GROOVY-12700
                 URL: https://issues.apache.org/jira/browse/GROOVY-12700
             Project: Groovy
          Issue Type: Bug
          Components: Static compilation
    Affects Versions: 6.0.0, 5.1.3, 4.0.33
            Reporter: Paul King


h3. Description

Under {{@CompileStatic}}, a multiple-assignment declaration whose right-hand 
side is a list literal is compiled as a sequence of single declarations. When 
an element on the right-hand side uses a name that the left-hand side declares, 
the compiled code reads the newly declared local variable rather than the field 
or property that the name refers to at that point. Dynamic Groovy and 
{{@TypeChecked}} read the field or property.

{code:groovy}
class Foo {
    String a = 'field a'
    static d = 'static d'
    //@groovy.transform.CompileStatic
    def method() {
        def (a, b) = [1, a]
        def (c, d, e) = [1, 2, d]
        println([a, b, c, d, e])
    }
}

def f = new Foo()
f.method()
println f.a
println f.d
{code}

||Mode||Output of {{method()}}||
|dynamic|{{[1, field a, 1, 2, static d]}}|
|{{@TypeChecked}}|{{[1, field a, 1, 2, static d]}}|
|{{@CompileStatic}}|{{[1, 1, 1, 2, 2]}}|

The fields themselves are unchanged in every mode ({{field a}} and {{static d}} 
are printed afterwards), so only the values read on the right-hand side differ.

The same happens with typed declarations. With {{String a = 'field a'}}, {{def 
(int a, String b) = [1, a]}} returns {{[1, field a]}} dynamically and {{[1, 
1]}} under {{@CompileStatic}}: the type checker checks {{String b}} against the 
field, but the generated code reads the local {{int a}}. In every case the code 
compiles without warnings and produces a different value at run time.

Reproduced with 4.0.32, 5.1.3, 6.0.0 and master.

h3. Cause

{{BinaryExpressionTransformer#transformMultipleAssignment}} (static 
compilation, instruction selection phase) rewrites {{def (x, y) = [e1, e2]}} 
into
{code:groovy}
def x = e1
def y = e2
{code}
For a declaration it emits the single declarations in order, so by the time 
{{e2}} is evaluated, {{x}} is already a local variable in scope, and a 
{{VariableExpression}} {{x}} in {{e2}} is generated as a read of that local. In 
the source, and in the dynamic and type-checked code, every element of the list 
is evaluated before any of the variables is declared.

The non-declaration branch of the same method ({{(x, y) = [y, x]}}) already 
avoids the corresponding problem for plain assignments by first storing every 
right-hand value in a temporary and then assigning the temporaries.

h3. Suggested fix

For declarations, evaluate the right-hand elements into temporaries before 
declaring the variables, as the assignment branch does. This could be done only 
when an element refers to one of the names being declared, to keep the current 
bytecode for the common case.



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

Reply via email to