Skip to content

Commit 48adb37

Browse files
Bound YAML merge graph indexing work
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 47a3e9d commit 48adb37

10 files changed

Lines changed: 884 additions & 119 deletions
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
function Add-YamlMergeIndexCandidate {
2+
<#
3+
.SYNOPSIS
4+
Adds one mapping key or sequence item to a structural candidate index.
5+
#>
6+
[CmdletBinding()]
7+
param (
8+
[Parameter(Mandatory)]
9+
[pscustomobject] $Index,
10+
11+
[Parameter(Mandatory)]
12+
[pscustomobject] $Candidate,
13+
14+
[Parameter(Mandatory)]
15+
[pscustomobject] $Context
16+
)
17+
18+
$node = if ($Index.Kind -eq 'Mapping') {
19+
$Candidate.Key
20+
} else {
21+
$Candidate
22+
}
23+
Add-YamlMergeWork -State $Context.WorkState -Node $node `
24+
-Operation 'index candidate identity'
25+
$effective = Get-YamlMergeNode -Node $node
26+
if (-not $Index.IndexedEffectiveIds.Add($effective.Id)) {
27+
return
28+
}
29+
30+
$fingerprint = Get-YamlMergeFingerprint -Node $node -State $Context.EqualityState `
31+
-Cache $Index.FingerprintCache -CandidateIndex $Index
32+
33+
Add-YamlMergeWork -State $Context.WorkState -Node $node -Operation 'index bucket visit'
34+
$bucket = $null
35+
if (-not $Index.Buckets.TryGetValue($fingerprint, [ref] $bucket)) {
36+
$bucket = [pscustomobject]@{
37+
Candidates = [System.Collections.Generic.List[object]]::new()
38+
}
39+
$Index.Buckets[$fingerprint] = $bucket
40+
}
41+
42+
Add-YamlMergeWork -State $Context.WorkState -Node $node -Operation 'index candidate visit'
43+
$bucket.Candidates.Add($Candidate)
44+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
function Add-YamlMergeWork {
2+
<#
3+
.SYNOPSIS
4+
Charges one or more deterministic operations to the YAML merge work budget.
5+
#>
6+
[CmdletBinding()]
7+
param (
8+
[Parameter(Mandatory)]
9+
[pscustomobject] $State,
10+
11+
[Parameter()]
12+
[ValidateRange(1, 2147483647)]
13+
[int] $Count = 1,
14+
15+
[Parameter(Mandatory)]
16+
[string] $Operation,
17+
18+
[Parameter()]
19+
[AllowNull()]
20+
[pscustomobject] $Node
21+
)
22+
23+
$State.Count = [long] $State.Count + $Count
24+
if ($State.Count -le $State.MaxNodes) {
25+
return
26+
}
27+
28+
$exception = New-YamlMergeException -Node $Node -ErrorId 'YamlMergeWorkLimitExceeded' `
29+
-Message (
30+
"YAML merge operation '$Operation' exceeded the configured invocation work limit " +
31+
"of $($State.MaxNodes) operations."
32+
)
33+
$exception.Data['YamlMergeWorkCount'] = $State.Count
34+
$exception.Data['YamlMergeWorkLimit'] = $State.MaxNodes
35+
throw $exception
36+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
function Find-YamlMergeIndexMatch {
2+
<#
3+
.SYNOPSIS
4+
Finds a collision-safe structural match in a YAML merge candidate index.
5+
#>
6+
[CmdletBinding()]
7+
[OutputType([pscustomobject])]
8+
param (
9+
[Parameter(Mandatory)]
10+
[pscustomobject] $Index,
11+
12+
[Parameter(Mandatory)]
13+
[pscustomobject] $Node,
14+
15+
[Parameter(Mandatory)]
16+
[pscustomobject] $Context
17+
)
18+
19+
$fingerprint = Get-YamlMergeFingerprint -Node $Node -State $Context.EqualityState `
20+
-Cache $Context.OverlayFingerprintCache
21+
Add-YamlMergeWork -State $Context.WorkState -Node $Node -Operation 'index bucket lookup'
22+
23+
$bucket = $null
24+
if (-not $Index.Buckets.TryGetValue($fingerprint, [ref] $bucket)) {
25+
return
26+
}
27+
28+
foreach ($candidate in $bucket.Candidates) {
29+
$candidateNode = if ($Index.Kind -eq 'Mapping') {
30+
$candidate.Key
31+
} else {
32+
$candidate
33+
}
34+
Add-YamlMergeWork -State $Context.WorkState -Node $Node `
35+
-Operation 'index candidate comparison'
36+
if (Test-YamlMergeNodeEqual -Node $candidateNode -OtherNode $Node `
37+
-State $Context.EqualityState) {
38+
Write-Output -InputObject $candidate -NoEnumerate
39+
return
40+
}
41+
}
42+
}
Lines changed: 239 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
function Get-YamlMergeFingerprint {
22
<#
33
.SYNOPSIS
4-
Creates a deterministic candidate index for structural merge comparisons.
4+
Creates a deterministic structural candidate index for merge comparisons.
55
#>
66
[CmdletBinding()]
77
[OutputType([string])]
@@ -10,21 +10,247 @@ function Get-YamlMergeFingerprint {
1010
[pscustomobject] $Node,
1111

1212
[Parameter(Mandatory)]
13-
[pscustomobject] $State
13+
[pscustomobject] $State,
14+
15+
[Parameter()]
16+
[AllowNull()]
17+
[System.Collections.Generic.Dictionary[int, string]] $Cache,
18+
19+
[Parameter()]
20+
[AllowNull()]
21+
[pscustomobject] $CandidateIndex
1422
)
1523

16-
$effective = Get-YamlMergeNode -Node $Node
17-
$tag = Get-YamlMergeNodeTag -Node $effective
18-
if ($effective.Kind -eq 'Scalar') {
19-
$resolved = (Resolve-YamlScalar -Node $effective).Value
20-
$value = Get-YamlScalarFingerprint -Value $resolved -Hasher $State.FingerprintHasher
21-
return 'scalar:{0}:{1}:{2}' -f $tag.Length, $tag, $value
24+
if ($null -eq $Cache) {
25+
$Cache = [System.Collections.Generic.Dictionary[int, string]]::new()
2226
}
27+
$active = [System.Collections.Generic.HashSet[int]]::new()
28+
$root = [pscustomobject]@{ Value = ''; IsContextual = $false }
29+
$stack = [System.Collections.Generic.Stack[object]]::new()
30+
$stack.Push([pscustomobject]@{
31+
Node = $Node
32+
Holder = $root
33+
State = 'Start'
34+
Index = 0
35+
Parts = $null
36+
Child = $null
37+
KeyHash = ''
38+
Accumulator = $null
39+
IsContextual = $false
40+
})
41+
42+
while ($stack.Count -gt 0) {
43+
$frame = $stack.Peek()
44+
if ($frame.State -eq 'Start') {
45+
$effective = $frame.Node
46+
while ($effective.Kind -eq 'Alias') {
47+
Add-YamlMergeWork -State $State.WorkState -Node $effective `
48+
-Operation 'fingerprint alias traversal'
49+
$effective = $effective.Target
50+
}
51+
Add-YamlMergeWork -State $State.WorkState -Node $effective `
52+
-Operation 'fingerprint node traversal'
53+
$frame.Node = $effective
54+
55+
if ($null -ne $CandidateIndex -and
56+
$CandidateIndex.Dependencies.Add($effective.Id)) {
57+
$dependents = $null
58+
if (-not $State.IndexDependents.TryGetValue(
59+
$effective.Id,
60+
[ref] $dependents
61+
)) {
62+
$dependents = [System.Collections.Generic.List[object]]::new()
63+
$State.IndexDependents[$effective.Id] = $dependents
64+
}
65+
$dependents.Add($CandidateIndex)
66+
}
67+
68+
$cached = ''
69+
if ($Cache.TryGetValue($effective.Id, [ref] $cached)) {
70+
$frame.Holder.Value = $cached
71+
[void] $stack.Pop()
72+
continue
73+
}
2374

24-
$count = if ($effective.Kind -eq 'Sequence') {
25-
$effective.Items.Count
26-
} else {
27-
$effective.Entries.Count
75+
if (-not $active.Add($effective.Id)) {
76+
$tag = Get-YamlMergeNodeTag -Node $effective
77+
$count = if ($effective.Kind -eq 'Sequence') {
78+
$effective.Items.Count
79+
} elseif ($effective.Kind -eq 'Mapping') {
80+
$effective.Entries.Count
81+
} else {
82+
0
83+
}
84+
$frame.Holder.Value = Get-YamlFingerprintHash -Value (
85+
'cycle:{0}:{1}:{2}:{3}' -f @(
86+
$effective.Kind.ToLowerInvariant(),
87+
$tag.Length,
88+
$tag,
89+
$count
90+
)
91+
) -Hasher $State.FingerprintHasher
92+
$frame.Holder.IsContextual = $true
93+
[void] $stack.Pop()
94+
continue
95+
}
96+
97+
if ($effective.Kind -eq 'Scalar') {
98+
$resolved = (Resolve-YamlScalar -Node $effective).Value
99+
$tag = Get-YamlEffectiveTag -Node $effective -Value $resolved
100+
$value = Get-YamlScalarFingerprint -Value $resolved `
101+
-Hasher $State.FingerprintHasher
102+
$fingerprint = Get-YamlFingerprintHash -Value (
103+
'scalar:{0}:{1}:{2}' -f $tag.Length, $tag, $value
104+
) -Hasher $State.FingerprintHasher
105+
$Cache[$effective.Id] = $fingerprint
106+
[void] $active.Remove($effective.Id)
107+
$frame.Holder.Value = $fingerprint
108+
[void] $stack.Pop()
109+
continue
110+
}
111+
112+
if ($effective.Kind -eq 'Sequence') {
113+
$frame.Parts = [System.Collections.Generic.List[string]]::new()
114+
$frame.State = 'Sequence'
115+
} else {
116+
$frame.Accumulator = [int[]]::new(32)
117+
$frame.State = 'MappingKey'
118+
}
119+
continue
120+
}
121+
122+
if ($frame.State -eq 'Sequence') {
123+
if ($frame.Index -ge $frame.Node.Items.Count) {
124+
$tag = Get-YamlEffectiveTag -Node $frame.Node -Value $null
125+
$canonical = if ($frame.IsContextual) {
126+
'cyclic-sequence:{0}:{1}:{2}' -f @(
127+
$tag.Length,
128+
$tag,
129+
$frame.Node.Items.Count
130+
)
131+
} else {
132+
'sequence:{0}:{1}:{2}' -f $tag.Length, $tag, ($frame.Parts -join '|')
133+
}
134+
$fingerprint = Get-YamlFingerprintHash -Value $canonical `
135+
-Hasher $State.FingerprintHasher
136+
if (-not $frame.IsContextual) {
137+
$Cache[$frame.Node.Id] = $fingerprint
138+
}
139+
[void] $active.Remove($frame.Node.Id)
140+
$frame.Holder.Value = $fingerprint
141+
$frame.Holder.IsContextual = $frame.IsContextual
142+
[void] $stack.Pop()
143+
continue
144+
}
145+
Add-YamlMergeWork -State $State.WorkState -Node $frame.Node `
146+
-Operation 'fingerprint sequence entry'
147+
$frame.Child = [pscustomobject]@{ Value = ''; IsContextual = $false }
148+
$frame.State = 'SequenceValue'
149+
$stack.Push([pscustomobject]@{
150+
Node = $frame.Node.Items[$frame.Index]
151+
Holder = $frame.Child
152+
State = 'Start'
153+
Index = 0
154+
Parts = $null
155+
Child = $null
156+
KeyHash = ''
157+
Accumulator = $null
158+
IsContextual = $false
159+
})
160+
continue
161+
}
162+
if ($frame.State -eq 'SequenceValue') {
163+
$frame.Parts.Add($frame.Child.Value)
164+
$frame.IsContextual = $frame.IsContextual -or $frame.Child.IsContextual
165+
$frame.Index++
166+
$frame.State = 'Sequence'
167+
continue
168+
}
169+
170+
if ($frame.State -eq 'MappingKey') {
171+
if ($frame.Index -ge $frame.Node.Entries.Count) {
172+
$tag = Get-YamlEffectiveTag -Node $frame.Node -Value $null
173+
$canonical = if ($frame.IsContextual) {
174+
'cyclic-mapping:{0}:{1}:{2}' -f @(
175+
$tag.Length,
176+
$tag,
177+
$frame.Node.Entries.Count
178+
)
179+
} else {
180+
$aggregate = [byte[]]::new($frame.Accumulator.Count)
181+
for ($index = 0; $index -lt $aggregate.Count; $index++) {
182+
$aggregate[$index] = [byte] $frame.Accumulator[$index]
183+
}
184+
'mapping:{0}:{1}:{2}:{3}' -f @(
185+
$tag.Length,
186+
$tag,
187+
$frame.Node.Entries.Count,
188+
[System.Convert]::ToBase64String($aggregate)
189+
)
190+
}
191+
$fingerprint = Get-YamlFingerprintHash -Value $canonical `
192+
-Hasher $State.FingerprintHasher
193+
if (-not $frame.IsContextual) {
194+
$Cache[$frame.Node.Id] = $fingerprint
195+
}
196+
[void] $active.Remove($frame.Node.Id)
197+
$frame.Holder.Value = $fingerprint
198+
$frame.Holder.IsContextual = $frame.IsContextual
199+
[void] $stack.Pop()
200+
continue
201+
}
202+
Add-YamlMergeWork -State $State.WorkState -Node $frame.Node `
203+
-Operation 'fingerprint mapping entry'
204+
$frame.Child = [pscustomobject]@{ Value = ''; IsContextual = $false }
205+
$frame.State = 'MappingKeyValue'
206+
$stack.Push([pscustomobject]@{
207+
Node = $frame.Node.Entries[$frame.Index].Key
208+
Holder = $frame.Child
209+
State = 'Start'
210+
Index = 0
211+
Parts = $null
212+
Child = $null
213+
KeyHash = ''
214+
Accumulator = $null
215+
IsContextual = $false
216+
})
217+
continue
218+
}
219+
if ($frame.State -eq 'MappingKeyValue') {
220+
$frame.KeyHash = $frame.Child.Value
221+
$frame.IsContextual = $frame.IsContextual -or $frame.Child.IsContextual
222+
$frame.Child = [pscustomobject]@{ Value = ''; IsContextual = $false }
223+
$frame.State = 'MappingValue'
224+
$stack.Push([pscustomobject]@{
225+
Node = $frame.Node.Entries[$frame.Index].Value
226+
Holder = $frame.Child
227+
State = 'Start'
228+
Index = 0
229+
Parts = $null
230+
Child = $null
231+
KeyHash = ''
232+
Accumulator = $null
233+
IsContextual = $false
234+
})
235+
continue
236+
}
237+
if ($frame.State -eq 'MappingValue') {
238+
Add-YamlMergeWork -State $State.WorkState -Node $frame.Node `
239+
-Operation 'fingerprint mapping combination'
240+
$entryHash = Get-YamlFingerprintHash -Value (
241+
"entry:$($frame.KeyHash)=$($frame.Child.Value)"
242+
) -Hasher $State.FingerprintHasher
243+
$entryBytes = [System.Convert]::FromBase64String($entryHash)
244+
for ($index = 0; $index -lt $entryBytes.Count; $index++) {
245+
$frame.Accumulator[$index] = (
246+
$frame.Accumulator[$index] + $entryBytes[$index]
247+
) -band 0xff
248+
}
249+
$frame.IsContextual = $frame.IsContextual -or $frame.Child.IsContextual
250+
$frame.Index++
251+
$frame.State = 'MappingKey'
252+
}
28253
}
29-
return '{0}:{1}:{2}:{3}' -f $effective.Kind.ToLowerInvariant(), $tag.Length, $tag, $count
254+
255+
$root.Value
30256
}

0 commit comments

Comments
 (0)