Terminate pointer-only descriptor traversal

This commit is contained in:
2026-10-25 21:18:35 +00:00
parent 7889ebddfb
commit 2736ddfbf2
5 changed files with 165 additions and 30 deletions

View File

@@ -334,8 +334,21 @@ func prependIndex(index int, nested []int) []int {
type schemaPath struct {
descriptor *typeDescriptor
field *fieldDescriptor
dynamic bool
}
// terminalDescriptor follows a pointer-only descriptor chain without entering
// any value. Recursive pointer aliases can refer back to an earlier descriptor,
// so callers must handle a repeated descriptor as an unaddressable chain.
func terminalDescriptor(descriptor *typeDescriptor) (*typeDescriptor, bool) {
visited := make(map[*typeDescriptor]struct{})
for descriptor != nil && descriptor.kind == descriptorPointer {
if _, exists := visited[descriptor]; exists {
return nil, false
}
visited[descriptor] = struct{}{}
descriptor = descriptor.element
}
return descriptor, descriptor != nil
}
func resolveSchemaPath(schema *schemaDescriptor, path Path) (schemaPath, bool) {
@@ -343,10 +356,11 @@ func resolveSchemaPath(schema *schemaDescriptor, path Path) (schemaPath, bool) {
return schemaPath{}, false
}
current := schema.root
var field *fieldDescriptor
for _, segment := range path.segments {
for current.kind == descriptorPointer {
current = current.element
var ok bool
current, ok = terminalDescriptor(current)
if !ok {
return schemaPath{}, false
}
switch current.kind {
case descriptorStruct:
@@ -355,12 +369,10 @@ func resolveSchemaPath(schema *schemaDescriptor, path Path) (schemaPath, bool) {
}
position, ok := current.fieldByName[segment.key]
if ok {
field = &current.fields[position]
current = field.descriptor
current = current.fields[position].descriptor
continue
}
if current.inlineMap != nil {
field = current.inlineMap
current = current.inlineMap.descriptor.element
continue
}
@@ -369,26 +381,20 @@ func resolveSchemaPath(schema *schemaDescriptor, path Path) (schemaPath, bool) {
if segment.kind == pathSegmentIndex {
return schemaPath{}, false
}
field = nil
current = current.element
case descriptorSlice, descriptorArray:
index, ok := schemaIndex(segment)
if !ok || (current.kind == descriptorArray && index >= current.length) {
return schemaPath{}, false
}
field = nil
current = current.element
case descriptorDynamic:
return schemaPath{descriptor: current, field: field, dynamic: true}, true
return schemaPath{descriptor: current}, true
default:
return schemaPath{}, false
}
}
dynamic := current
for dynamic.kind == descriptorPointer {
dynamic = dynamic.element
}
return schemaPath{descriptor: current, field: field, dynamic: dynamic.kind == descriptorDynamic}, true
return schemaPath{descriptor: current}, true
}
func schemaIndex(segment pathSegment) (int, bool) {