Scott Murphy Heiberg created GROOVY-12744:
---------------------------------------------

             Summary: Node metadata snapshots and copies miss changes made 
after the first one
                 Key: GROOVY-12744
                 URL: https://issues.apache.org/jira/browse/GROOVY-12744
             Project: Groovy
          Issue Type: Bug
          Components: Compiler
    Affects Versions: 6.0.0
            Reporter: Scott Murphy Heiberg


Since GROOVY-11966, {{NodeMetaDataHandler.newMetaDataMap()}} wraps a 
{{ListHashMap}} in {{Collections.synchronizedMap}}. The wrapper creates its 
{{entrySet()}} view on the first call and returns that same view afterwards. 
{{ListHashMap.entrySet()}} does not return a view backed by the map, as the 
{{Map}} contract requires: with up to three entries it returns the entries of a 
copy. So once a node's metadata has been read through {{getNodeMetaData()}} or 
copied with {{copyNodeMetaData}}, every later read or copy of that node returns 
the same old entries. Entries added since are missing, removed entries are 
still there, and replaced values keep their old value. {{keySet()}} and 
{{values()}} go stale in the same way.

{code:groovy}
import org.codehaus.groovy.ast.expr.ConstantExpression
def a = new ConstantExpression(1)
a.putNodeMetaData('k1', 1)
println a.nodeMetaData       // [k1:1]
a.putNodeMetaData('k2', 2)
println a.nodeMetaData       // [k1:1], expected [k1:1, k2:2]
def c = new ConstantExpression(3)
c.copyNodeMetaData(a)
println c.nodeMetaData       // [k1:1], expected [k1:1, k2:2]
a.removeNodeMetaData('k1')
println a.nodeMetaData       // [k1:1], expected [k2:2]
{code}

{{getNodeMetaData(key)}} is not affected, because it reads the map directly.

Compilations run into this. In the core test suite, static compilation 
transforms of {{ConstructorCallExpression}} (map-style constructor calls) and 
{{PropertyExpression}} copy a stale {{DIRECT_METHOD_CALL_TARGET}}. In every 
case observed, the old and new values have the same signature, so no wrong 
bytecode was found. Any code that reads all of a node's metadata or copies it 
(AST transforms, tooling) can get old data.

On 5.0.x and 4.0.x, {{getNodeMetaData()}} and {{copyNodeMetaData()}} read the 
{{ListHashMap}} directly, so each call sees the current entries. The 
{{ListHashMap}} views break the {{Map}} contract there too, though: if code 
keeps the map returned by {{getNodeMetaData()}} and reads it after the node 
changes, it gets the old entries.



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

Reply via email to