package copyx import ( "fmt" "reflect" "sort" "sync" "time" ) const maxDepth = 256 const maxNodes = 200000 const maxCopyBytes = 64 << 20 var timeType = reflect.TypeFor[time.Time]() // Immutable type metadata is shared, never values or converter registrations. var valueTypes sync.Map func simpleValue(t reflect.Type) bool { if known, ok := valueTypes.Load(t); ok { return known.(bool) } result := t == timeType if !result { switch t.Kind() { case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128, reflect.String: result = true case reflect.Array: result = simpleValue(t.Elem()) case reflect.Struct: result = !forbidden(t) for i := 0; result && i < t.NumField(); i++ { result = simpleValue(t.Field(i).Type) } } } valueTypes.Store(t, result) return result } type reference struct { kind reflect.Kind typ reflect.Type ptr uintptr length, capacity int } func identity(v reflect.Value) reference { r := reference{kind: v.Kind(), typ: v.Type(), ptr: uintptr(v.UnsafePointer())} if v.Kind() == reflect.Slice { r.length = v.Len() r.capacity = v.Cap() } return r } type span struct{ start, end uintptr } type graph struct { maps map[uintptr]bool spans []span maxEnd []uintptr } // References are inspected without reading private values through Interface or // using unsafe writes. Slice capacity matters: returning source[:0] is an alias. func scanGraph(v reflect.Value, visit func(reflect.Value, string) error) error { seen := map[reference]bool{} nodes := 0 var walk func(reflect.Value, string, int) error walk = func(v reflect.Value, path string, depth int) error { nodes++ if depth > maxDepth || nodes > maxNodes { return at(path, ErrLimit) } if !v.IsValid() { return nil } if err := visit(v, path); err != nil { return err } if simpleValue(v.Type()) { return nil } // time.Time is a documented immutable value. switch v.Kind() { case reflect.Interface: if !v.IsNil() { return walk(v.Elem(), path, depth+1) } case reflect.Pointer, reflect.Map, reflect.Slice: if v.IsNil() { return nil } id := identity(v) if seen[id] { return nil } seen[id] = true switch v.Kind() { case reflect.Pointer: return walk(v.Elem(), path, depth+1) case reflect.Map: it := v.MapRange() for it.Next() { if err := walk(it.Key(), path+"[key]", depth+1); err != nil { return err } if err := walk(it.Value(), path+"[value]", depth+1); err != nil { return err } } case reflect.Slice: if simpleValue(v.Type().Elem()) { return nil } all := v.Slice(0, v.Cap()) for i := 0; i < all.Len(); i++ { if err := walk(all.Index(i), fmt.Sprintf("%s[%d]", path, i), depth+1); err != nil { return err } } } case reflect.Struct: for i := 0; i < v.NumField(); i++ { if err := walk(v.Field(i), path+"."+v.Type().Field(i).Name, depth+1); err != nil { return err } } case reflect.Array: for i := 0; i < v.Len(); i++ { if err := walk(v.Index(i), fmt.Sprintf("%s[%d]", path, i), depth+1); err != nil { return err } } } return nil } return walk(v, "$", 0) } func mutableSpan(v reflect.Value) (span, bool) { var n uintptr switch v.Kind() { case reflect.Pointer: if v.IsNil() { return span{}, false } n = v.Type().Elem().Size() case reflect.Slice: if v.IsNil() { return span{}, false } n = uintptr(v.Cap()) * v.Type().Elem().Size() default: return span{}, false } if n == 0 { return span{}, false } start := uintptr(v.UnsafePointer()) return span{start, start + n}, true } func sourceGraph(v reflect.Value) (*graph, error) { g := &graph{maps: map[uintptr]bool{}} err := scanGraph(v, func(v reflect.Value, _ string) error { if r, ok := mutableSpan(v); ok { g.spans = append(g.spans, r) } if v.Kind() == reflect.Map && !v.IsNil() { g.maps[uintptr(v.UnsafePointer())] = true } return nil }) sort.Slice(g.spans, func(i, j int) bool { return g.spans[i].start < g.spans[j].start }) var end uintptr for _, r := range g.spans { if r.end > end { end = r.end } g.maxEnd = append(g.maxEnd, end) } return g, err } func (g *graph) audit(v reflect.Value) error { return scanGraph(v, func(v reflect.Value, path string) error { if forbidden(v.Type()) { return at(path, ErrUnsupported) } switch v.Kind() { case reflect.Func, reflect.Chan: if !v.IsNil() { return at(path, ErrUnsupported) } case reflect.UnsafePointer: if v.Pointer() != 0 { return at(path, ErrUnsupported) } } if v.Kind() == reflect.Map && !v.IsNil() && g.maps[uintptr(v.UnsafePointer())] { return at(path, ErrSharedReference) } if r, ok := mutableSpan(v); ok { i := sort.Search(len(g.spans), func(i int) bool { return g.spans[i].start >= r.end }) if i > 0 && g.maxEnd[i-1] > r.start { return at(path, ErrSharedReference) } } return nil }) } func forbidden(t reflect.Type) bool { // Lock/resource implementations are not data. A custom converter may turn // them into a different data type but may not manufacture cloned handles. switch t.PkgPath() { case "sync", "sync/atomic", "os", "net", "database/sql": return t.Kind() == reflect.Struct } return false } // Shallow copies may share handles through pointers, but must not copy lock or // resource state by value, including state inside a containing struct/array. var safeValueTypes sync.Map func shallowSafe(t reflect.Type) bool { if known, ok := safeValueTypes.Load(t); ok { return known.(bool) } result := true if forbidden(t) { safeValueTypes.Store(t, false) return false } switch t.Kind() { case reflect.Struct: for i := 0; i < t.NumField(); i++ { if !shallowSafe(t.Field(i).Type) { result = false break } } case reflect.Array: result = shallowSafe(t.Elem()) } safeValueTypes.Store(t, result) return result } func privateMutable(t reflect.Type) bool { if t == timeType { return false } switch t.Kind() { case reflect.Pointer, reflect.Map, reflect.Slice, reflect.Interface, reflect.Func, reflect.Chan, reflect.UnsafePointer: return true case reflect.Array: return privateMutable(t.Elem()) case reflect.Struct: if forbidden(t) { return true } for i := 0; i < t.NumField(); i++ { if privateMutable(t.Field(i).Type) { return true } } } return false }