Paul King created GROOVY-12482:
----------------------------------

             Summary: YamlSlurper: resolve YAML anchors and aliases rather than 
returning the anchor name
                 Key: GROOVY-12482
                 URL: https://issues.apache.org/jira/browse/GROOVY-12482
             Project: Groovy
          Issue Type: Improvement
          Components: groovy-yaml
            Reporter: Paul King


*NOTE*: For Groovy 6, we closed this to avoid having to handle the security 
implications mentioned below (under Points to settle). Read those before 
starting work on this issue.

{{YamlSlurper}} does not resolve YAML anchors and aliases: an alias is returned 
as a string holding the anchor's *name*, so the document parses without error 
but with wrong values. GROOVY-12358 documented this as a limitation of the 
Jackson YAML backend (FasterXML/jackson-dataformats-text#98, closed as not 
planned). This issue proposes resolving aliases in Groovy instead.

h3. Current behaviour

{code:groovy}
def y = new groovy.yaml.YamlSlurper()
assert y.parseText('defaults: &d {timeout: 30}\nprod: *d') == [defaults: 
[timeout: 30], prod: 'd']
assert y.parseText('host: &h db.example.com\nreplica: *h') == [host: 
'db.example.com', replica: 'h']
assert y.parseText('base: &l [1, 2]\nmore: *l') == [base: [1, 2], more: 'l']
assert y.parseText('- &x {a: 1}\n- *x') == [[a: 1], 'x']
{code}

The typed API is affected the same way: {{parseTextAs(Cfg, 'host: &h 
db.example.com\nreplica: *h')}} gives {{replica == 'h'}}. The documented 
{{@JsonIdentityInfo}} workaround only covers mappings bound to annotated 
classes.

Seen on 4.0.32, 5.0.6 and 6.0.0, and on master both with Jackson 2 and with the 
Jackson 3 port in PR #2949. Anchors are common in real-world YAML (CI 
pipelines, compose files, Kubernetes/Helm values), and the silently wrong value 
is worse than an error.

h3. Proposed approach

Jackson's {{YAMLParser}} reports enough to do this ourselves, in both Jackson 
2.x and 3.x:
* {{getObjectId()}} returns the anchor name on the anchored token
* {{isCurrentAlias()}} is true on an alias token, whose text is the anchor name

{{YamlConverter.convertYamlToJson}} currently binds each document to {{Object}} 
in one step. It could instead build the tree from the token stream: record the 
value completed at each anchor, and substitute it when an alias is met. An 
alias to an undefined anchor should be an error.

Points to settle:
* *Expansion limit.* Resolving aliases makes the "billion laughs" document 
possible: a few lines whose aliases expand exponentially. Jackson never 
expanded aliases, so it never needed a guard. We need one, either a cap on 
alias uses per collection (SnakeYAML's {{maxAliasesForCollections}}, default 
50) or on total expanded nodes, with an exception when it is exceeded.
* *Copies or shared instances.* The slurper output goes via JSON text to 
{{JsonSlurper}}, so a resolved alias naturally becomes an independent copy. 
That is probably the least surprising choice for mutable maps and lists.
* *Typed {{parseAs}}.* This currently uses {{readValue(reader, type)}} 
directly, so it would need to read the resolved tree and then {{convertValue}}. 
It's worth deciding whether that should take precedence over 
{{@JsonIdentityInfo}} handling or be opt-in.
* *Merge keys ({{<<: *d}}).* These are a YAML 1.1 type rather than part of YAML 
1.2, but widely used. They could follow as a separate step once aliases 
resolve, or be left documented as unsupported.

When done, the "Anchors and aliases" section of the YAML user guide and the 
{{alias_behaviour}} tests in {{YamlParserTest}} need updating.



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

Reply via email to