From c2c311a859b50449d9fe2a147c740eb94a835a21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Balv=C3=ADn?= Date: Sun, 4 Oct 2026 22:09:21 +0200 Subject: [PATCH] fix: catch a cyclic map by its identity on the document walk Assisted-by: DeepSeek V4.1 Flash --- document.go | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/document.go b/document.go index fa9e077..f0db02a 100644 --- a/document.go +++ b/document.go @@ -5,6 +5,7 @@ package interpres import ( "maps" + "reflect" "slices" ) @@ -402,13 +403,24 @@ func (t *Table) Set(key string, value any) { // newOrderedTable builds a table node for a value the caller set, its keys // entered in sorted order, the order Marshal writes maps in. func newOrderedTable(m map[string]any) *Table { - return orderedTable(m, 0) + return orderedTable(m, 0, map[uintptr]bool{}) } // orderedTable is newOrderedTable's recursion. The depth bound is the value // encoder's: a cyclic map stopped here is written by the value writer, which -// reports it instead of running the stack out. -func orderedTable(m map[string]any, depth int) *Table { +// reports it instead of running the stack out. seen carries the maps the walk +// is inside of, so a map that repeats one of them is caught by its identity +// at once and takes the depth limit's shape: its entries are recorded and the +// walk stops, instead of the walk and the emission materialising a chain the +// input does not hold. +func orderedTable(m map[string]any, depth int, seen map[uintptr]bool) *Table { + id := reflect.ValueOf(m).Pointer() + if seen[id] { + depth = maxEncodeDepth + } else { + seen[id] = true + defer delete(seen, id) + } t := newTable(m) for _, k := range slices.Sorted(maps.Keys(m)) { v := m[k] @@ -418,11 +430,11 @@ func orderedTable(m map[string]any, depth int) *Table { } switch val := v.(type) { case map[string]any: - e.child = orderedTable(val, depth+1) + e.child = orderedTable(val, depth+1, seen) case []map[string]any: e.elements = make([]*Table, len(val)) for i, item := range val { - e.elements[i] = orderedTable(item, depth+1) + e.elements[i] = orderedTable(item, depth+1, seen) } } }