package engine import "reflect" type projectionLimits struct { depth int nodes int } func defaultProjectionLimits() projectionLimits { return projectionLimits{depth: maxDocumentDepth, nodes: maxDocumentNodes} } type projectionIssue struct { code Code path Path message string } type valueVisit struct { typeOf reflect.Type kind reflect.Kind ptr uintptr length int } // valueOperation owns one traversal's cycle and budget state and reuses the // current load's descriptor cache for dynamic values. type valueOperation struct { cloneMode secretCloneMode schema *schemaDescriptor limits projectionLimits active map[valueVisit]bool nodes int nodeCredits int } func (o *valueOperation) dynamicDescriptor(current reflect.Type, path Path) (*typeDescriptor, *projectionIssue) { if o.schema.secrets.Type != nil && current == o.schema.secrets.Type { return nil, &projectionIssue{code: CodeSchemaDynamicType, path: path.clone(), message: "dynamic configuration values cannot establish secret-reference semantics"} } builder := schemaBuilder{cache: o.schema.types, building: make(map[reflect.Type]bool), secrets: o.schema.secrets} descriptor, issue := builder.describe(current, path) if issue != nil { builder.rollback() return nil, &projectionIssue{code: CodeSchemaDynamicType, path: path.clone(), message: "dynamic configuration value has an unsupported type"} } if descriptorContainsSecret(descriptor, make(map[*typeDescriptor]bool)) { builder.rollback() return nil, &projectionIssue{code: CodeSchemaDynamicType, path: path.clone(), message: "dynamic configuration values cannot establish secret-reference semantics"} } return descriptor, nil } func descriptorContainsSecret(descriptor *typeDescriptor, visited map[*typeDescriptor]bool) bool { if descriptor == nil || visited[descriptor] { return false } if descriptor.kind == descriptorSecret { return true } visited[descriptor] = true if descriptorContainsSecret(descriptor.element, visited) { return true } for _, member := range descriptor.members { if descriptorContainsSecret(member.descriptor, visited) { return true } } return false } func (o *valueOperation) charge(path Path) *projectionIssue { o.nodes++ if o.limits.nodes >= 0 && o.nodes-o.nodeCredits > o.limits.nodes { return &projectionIssue{code: CodeSchemaProjectionLimit, path: path.clone(), message: "configuration projection exceeds the node limit"} } return nil } func (o *valueOperation) checkMinimumChildren(path Path, count, nodesPerChild int) *projectionIssue { if o.limits.nodes < 0 { return nil } remaining := o.limits.nodes - (o.nodes - o.nodeCredits) if remaining < 0 || count > remaining/nodesPerChild { return &projectionIssue{code: CodeSchemaProjectionLimit, path: path.clone(), message: "configuration projection exceeds the node limit"} } return nil } func (o *valueOperation) checkDepth(path Path, depth int) *projectionIssue { if depth > o.limits.depth { return &projectionIssue{code: CodeSchemaProjectionLimit, path: path.clone(), message: "configuration projection exceeds the depth limit"} } return nil } func (o *valueOperation) enter(value reflect.Value, path Path) (func(), *projectionIssue) { visit, ok := visitFor(value) if !ok { return func() {}, nil } if o.active[visit] { return nil, &projectionIssue{code: CodeSchemaCycle, path: path.clone(), message: "configuration values must not contain cycles"} } o.active[visit] = true return func() { delete(o.active, visit) }, nil } func visitFor(value reflect.Value) (valueVisit, bool) { if isNilValue(value) { return valueVisit{}, false } visit := valueVisit{typeOf: value.Type(), kind: value.Kind()} switch value.Kind() { case reflect.Pointer, reflect.Slice: if value.Kind() == reflect.Slice && value.Len() == 0 { return valueVisit{}, false } visit.ptr = value.Pointer() if value.Kind() == reflect.Slice { visit.length = value.Len() } case reflect.Map: if value.Len() == 0 { return valueVisit{}, false } visit.ptr = uintptr(value.UnsafePointer()) default: return valueVisit{}, false } return visit, visit.ptr != 0 } func isNilValue(value reflect.Value) bool { switch value.Kind() { case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice: return value.IsNil() default: return false } }