Fix nested inheritance history and budget accounting

This commit is contained in:
2026-09-30 00:04:02 +00:00
parent b45f7a0410
commit 83c59ea7d3
5 changed files with 187 additions and 9 deletions

View File

@@ -8,7 +8,7 @@ import (
)
type inheritanceWalker struct {
root **document.Node
nodes int // logical non-directive nodes in the entire working tree
origins map[string]Origin
limits projectionLimits
retained *retainedNodeCounter
@@ -38,8 +38,12 @@ func expandInheritanceWithBudgets(input *document.Node, inputOrigins map[string]
if inputOrigins == nil {
origins = directInheritanceOrigins(input, Path{}, nil)
}
nodes, within := inheritanceLowerBoundCount(root, limits.nodes)
if !within {
return nil, []Diagnostic{inheritanceBudgetDiagnostic(Path{}, root.Location)}
}
walker := inheritanceWalker{
root: &root, limits: limits, origins: origins,
nodes: nodes, limits: limits, origins: origins,
retained: newRetainedNodeCounter(historyLimit),
}
if problem := walker.visit(&root, Path{}, true); problem != nil {
@@ -79,7 +83,14 @@ func (w *inheritanceWalker) visit(node **document.Node, path Path, unscoped bool
}
}
if scopeHasInheritanceDefinitions(*node) {
resolved, diagnostics := expandSiblingDefinitionsWithOrigins(*node, path, w.limits, w.origins, w.retained)
// Only this scope can change the logical total. Reserve the nodes
// outside it so expansion checks the remaining budget before cloning.
before, _ := inheritanceLowerBoundCount(*node, -1)
limits := w.limits
if limits.nodes >= 0 {
limits.nodes -= w.nodes - before
}
resolved, diagnostics := expandSiblingDefinitionsWithOrigins(*node, path, limits, w.origins, w.retained)
if len(diagnostics) != 0 {
return &diagnostics[0]
}
@@ -92,11 +103,15 @@ func (w *inheritanceWalker) visit(node **document.Node, path Path, unscoped bool
w.controls = append(w.controls, resolved.controls...)
w.replacements = append(w.replacements, resolved.replacements...)
w.operations = append(w.operations, resolved.operations...)
if !inheritanceLowerBoundWithin(*w.root, w.limits.nodes) {
problem := inheritanceBudgetDiagnostic(path, (*node).Location)
return &problem
}
after, _ := inheritanceLowerBoundCount(*node, -1)
w.nodes += after - before
}
// Operations retain immutable enclosing templates. Detach just this
// container before replacing descendants during destination resolution;
// untouched subtrees remain shared with the retained evidence.
working := **node
working.Mapping = slices.Clone(working.Mapping)
*node = &working
indices := make([]int, len((*node).Mapping))
for index := range indices {
indices[index] = index
@@ -111,6 +126,9 @@ func (w *inheritanceWalker) visit(node **document.Node, path Path, unscoped bool
}
}
case document.Sequence:
working := **node
working.Sequence = slices.Clone(working.Sequence)
*node = &working
for index := range (*node).Sequence {
child := &(*node).Sequence[index]
if problem := w.visit(child, path.Index(index), (*child).Kind == document.Mapping); problem != nil {