package copyx import ( "errors" "fmt" "reflect" "runtime" "sort" "sync" ) var ( ErrType = errors.New("copyx: incompatible types") ErrNil = errors.New("copyx: nil cannot fill a non-nullable target") ErrField = errors.New("copyx: unmatched field") ErrUnsupported = errors.New("copyx: unsupported value; provide an explicit data converter") ErrSharedReference = errors.New("copyx: deep copy retains a mutable source reference") ErrLimit = errors.New("copyx: traversal limit exceeded") ) // Error describes a field/index path without recording the source value. type Error struct { Path string Cause error } func (e *Error) Error() string { return e.Path + ": " + e.Cause.Error() } func (e *Error) Unwrap() error { return e.Cause } func at(path string, err error) error { return &Error{Path: path, Cause: err} } func Copy(target, source any, options ...Option) error { c, err := New(options...) if err != nil { return err } return c.Copy(target, source) } // Clone always makes a deep copy. Nil and empty collections stay distinct. func Clone[T any](source T, options ...Option) (T, error) { var target T options = append(append([]Option(nil), options...), WithDeepCopy()) err := Copy(&target, source, options...) return target, err } // Copy stages the complete result and only assigns target on success. User // converter side effects (including mutation of source) cannot be rolled back. // Caller must synchronize concurrent access to source and target themselves. func (c *Copier) Copy(target, source any) error { // Alias indexes contain addresses only. Keep the original graph alive until // validation and commit finish so the GC cannot recycle any indexed address. defer func() { runtime.KeepAlive(source) }() if c == nil { return errors.New("copyx: nil Copier") } dst := reflect.ValueOf(target) if !dst.IsValid() || dst.Kind() != reflect.Pointer || dst.IsNil() { return errors.New("copyx: target must be a non-nil pointer") } src := reflect.ValueOf(source) s := state{options: &c.options, seen: map[visit]reflect.Value{}} if c.options.deep { var err error s.graph, err = sourceGraph(src) if err != nil { return err } if src.IsValid() && src.Kind() == reflect.Pointer && !src.IsNil() && dst.Elem().Kind() == reflect.Struct { id := identity(src) s.valueRoot = &id } } out, err := s.walk(src, dst.Elem(), dst.Elem().Type(), "$", 0) if err != nil { return err } if s.graph != nil { if err = s.graph.audit(out); err != nil { return err } } dst.Elem().Set(out) return nil } type visit struct { reference target reflect.Type } type state struct { options *settings seen map[visit]reflect.Value graph *graph nodes int allocated uint64 valueRoot *reference checkedTypes map[pair]bool checkedNodes int } func (s *state) reserve(count int, size uintptr, path string) error { if count < 0 || (count > 0 && uint64(size) > maxCopyBytes/uint64(count)) { return at(path, ErrLimit) } s.allocated += uint64(count) * uint64(size) if s.allocated > maxCopyBytes { return at(path, ErrLimit) } return nil } func nullable(k reflect.Kind) bool { switch k { case reflect.Pointer, reflect.Map, reflect.Slice, reflect.Interface, reflect.Func, reflect.Chan: return true } return false } func baseValue(base reflect.Value, t reflect.Type) reflect.Value { if base.IsValid() && base.Type() == t { return base } return reflect.Zero(t) } func (s *state) walk(src, base reflect.Value, t reflect.Type, path string, depth int) (reflect.Value, error) { s.nodes++ if depth > maxDepth || s.nodes > maxNodes { return reflect.Value{}, at(path, ErrLimit) } base = baseValue(base, t) if !shallowSafe(t) { return reflect.Value{}, at(path, ErrUnsupported) } if src.IsValid() && !src.CanInterface() { return reflect.Value{}, at(path, ErrUnsupported) } // Exact converters also receive typed nils and zeros. No method calls, // Scanner/Valuer invocation or implicit numeric conversion is attempted. if src.IsValid() { if convert, ok := s.options.converters[pair{src.Type(), t}]; ok { out, err := convert(src) if err != nil { return reflect.Value{}, at(path, err) } if s.graph != nil { if err = s.graph.audit(out); err != nil { return reflect.Value{}, at(path, err) } } return out, nil } } if !src.IsValid() || (nullable(src.Kind()) && src.IsNil()) { if src.IsValid() && !s.nilCompatible(src.Type(), t, map[pair]bool{}) { return reflect.Value{}, at(path, ErrType) } if s.options.nilPolicy == NilSkip { return base, nil } if nullable(t.Kind()) || s.options.nilPolicy == NilZero { return reflect.Zero(t), nil } return reflect.Value{}, at(path, ErrNil) } if s.options.skipZero && src.IsZero() { if !s.nilCompatible(src.Type(), t, map[pair]bool{}) { return reflect.Value{}, at(path, ErrType) } return base, nil } if src.Type() == t && len(s.options.converters) == 0 && len(s.options.fields) == 0 && !s.options.skipZero && s.options.nilPolicy != NilSkip { if !s.options.deep || simpleValue(t) { return src, nil } } if src.Kind() == reflect.Interface { return s.walk(src.Elem(), base, t, path, depth+1) } if t.Kind() == reflect.Interface { if !src.Type().Implements(t) { return reflect.Value{}, at(path, ErrType) } out, err := s.walk(src, reflect.Value{}, src.Type(), path, depth+1) if err != nil { return reflect.Value{}, err } v := reflect.New(t).Elem() v.Set(out) return v, nil } if s.options.deep && forbidden(t) { return reflect.Value{}, at(path, ErrUnsupported) } if src.Kind() == reflect.Pointer && t.Kind() != reflect.Pointer { return s.walk(src.Elem(), base, t, path, depth+1) } if t.Kind() == reflect.Pointer { if !s.options.deep && src.Type() == t { return src, nil } if src.Kind() == reflect.Pointer { key := visit{identity(src), t} if s.valueRoot != nil && key.reference == *s.valueRoot { return reflect.Value{}, at(path, fmt.Errorf("%w: cyclic root must retain its pointer; use Clone(source) or Copy(&targetPointer, source)", ErrUnsupported)) } if prior, ok := s.seen[key]; ok { return prior, nil } } if err := s.reserve(1, t.Elem().Size(), path); err != nil { return reflect.Value{}, err } out := reflect.New(t.Elem()) if out.Type() != t { out = out.Convert(t) } if src.Kind() == reflect.Pointer { key := visit{identity(src), t} s.seen[key] = out src = src.Elem() } var childBase reflect.Value if !base.IsNil() { childBase = base.Elem() } value, err := s.walk(src, childBase, t.Elem(), path, depth+1) if err != nil { return reflect.Value{}, err } out.Elem().Set(value) return out, nil } if src.Kind() != t.Kind() { return reflect.Value{}, at(path, fmt.Errorf("%w: %s -> %s", ErrType, src.Type(), t)) } if t == timeType && src.Type() == t { return src, nil } switch t.Kind() { case reflect.Struct: return s.structure(src, base, t, path, depth) case reflect.Slice: if src.Len() == 0 { if err := s.checkAbsent(src.Type(), t, path); err != nil { return reflect.Value{}, err } } if !s.options.deep && src.Type() == t { return src, nil } key := visit{identity(src), t} if prior, ok := s.seen[key]; ok { return prior, nil } if err := s.reserve(src.Len(), t.Elem().Size(), path); err != nil { return reflect.Value{}, err } out := reflect.MakeSlice(t, src.Len(), src.Len()) s.seen[key] = out if src.Type() == t && len(s.options.converters) == 0 && len(s.options.fields) == 0 && simpleValue(t.Elem()) { reflect.Copy(out, src) return out, nil } for i := 0; i < src.Len(); i++ { v, err := s.walk(src.Index(i), reflect.Value{}, t.Elem(), fmt.Sprintf("%s[%d]", path, i), depth+1) if err != nil { return reflect.Value{}, err } out.Index(i).Set(v) } return out, nil case reflect.Array: if src.Len() == 0 { if err := s.checkAbsent(src.Type(), t, path); err != nil { return reflect.Value{}, err } } if src.Len() != t.Len() { return reflect.Value{}, at(path, ErrType) } if err := s.reserve(1, t.Size(), path); err != nil { return reflect.Value{}, err } out := reflect.New(t).Elem() for i := 0; i < src.Len(); i++ { v, err := s.walk(src.Index(i), base.Index(i), t.Elem(), fmt.Sprintf("%s[%d]", path, i), depth+1) if err != nil { return reflect.Value{}, err } out.Index(i).Set(v) } return out, nil case reflect.Map: if src.Len() == 0 { if err := s.checkAbsent(src.Type(), t, path); err != nil { return reflect.Value{}, err } } if !s.options.deep && src.Type() == t { return src, nil } key := visit{identity(src), t} if prior, ok := s.seen[key]; ok { return prior, nil } if err := s.reserve(src.Len(), t.Key().Size()+t.Elem().Size()+32, path); err != nil { return reflect.Value{}, err } out := reflect.MakeMapWithSize(t, src.Len()) s.seen[key] = out it := src.MapRange() for it.Next() { k, err := s.walk(it.Key(), reflect.Value{}, t.Key(), path+"[key]", depth+1) if err != nil { return reflect.Value{}, err } if !k.Comparable() { return reflect.Value{}, at(path+"[key]", ErrType) } if out.MapIndex(k).IsValid() { return reflect.Value{}, at(path+"[key]", errors.New("copyx: key conversion collision")) } v, err := s.walk(it.Value(), reflect.Value{}, t.Elem(), path+"[value]", depth+1) if err != nil { return reflect.Value{}, err } out.SetMapIndex(k, v) } return out, nil case reflect.Func, reflect.Chan, reflect.UnsafePointer: if s.options.deep { return reflect.Value{}, at(path, ErrUnsupported) } } if src.Type() != t { return reflect.Value{}, at(path, fmt.Errorf("%w: %s -> %s", ErrType, src.Type(), t)) } return src, nil } var fieldTypes sync.Map func exportedFields(t reflect.Type) map[string]reflect.StructField { if known, ok := fieldTypes.Load(t); ok { return known.(map[string]reflect.StructField) } out := map[string]reflect.StructField{} for _, f := range reflect.VisibleFields(t) { if f.PkgPath != "" { continue } kind := f.Type.Kind() if kind == reflect.Pointer { kind = f.Type.Elem().Kind() } if f.Anonymous && kind == reflect.Struct { continue } out[f.Name] = f } actual, _ := fieldTypes.LoadOrStore(t, out) return actual.(map[string]reflect.StructField) } func (s *state) nilCompatible(from, to reflect.Type, seen map[pair]bool) bool { key := pair{from, to} if seen[key] { return true } seen[key] = true if from == to { return true } if _, ok := s.options.converters[key]; ok { return true } if to.Kind() == reflect.Interface { return from.Implements(to) } if from.Kind() == reflect.Pointer { return s.nilCompatible(from.Elem(), to, seen) } if to.Kind() == reflect.Pointer { return s.nilCompatible(from, to.Elem(), seen) } if from.Kind() != to.Kind() { return false } switch from.Kind() { case reflect.Struct: // Nil must not hide a structurally incompatible type pair. There are no // field values to visit, but coverage and conversion types still apply. for i := 0; i < to.NumField(); i++ { if to.Field(i).PkgPath != "" { return false } } sources, targets := exportedFields(from), exportedFields(to) rules := s.options.fields[key] if rules == nil { rules = &fields{} } reverse, used := map[string]string{}, map[string]bool{} for a, b := range rules.names { reverse[b] = a } for name, target := range targets { if rules.ignoreTarget[name] { continue } srcName := name if renamed, ok := reverse[name]; ok { srcName = renamed } source, ok := sources[srcName] if !ok || rules.ignoreSource[srcName] || !s.nilCompatible(source.Type, target.Type, seen) { return false } used[srcName] = true } if s.options.strict { for name := range sources { if !used[name] && !rules.ignoreSource[name] { return false } } } return true case reflect.Slice: return s.nilCompatible(from.Elem(), to.Elem(), seen) case reflect.Map: return s.nilCompatible(from.Key(), to.Key(), seen) && s.nilCompatible(from.Elem(), to.Elem(), seen) case reflect.Array: return from.Len() == to.Len() && s.nilCompatible(from.Elem(), to.Elem(), seen) } return false } func fieldValue(v reflect.Value, index []int) (reflect.Value, bool) { for _, i := range index { if v.Kind() == reflect.Pointer { if v.IsNil() { return reflect.Value{}, false } v = v.Elem() } v = v.Field(i) } return v, true } func setField(v reflect.Value, index []int, value reflect.Value) error { for j, i := range index { field := v.Field(i) if !field.CanSet() { return ErrUnsupported } if j == len(index)-1 { field.Set(value) return nil } if field.Kind() == reflect.Pointer { // Detach embedded destination pointers before editing promoted fields. fresh := reflect.New(field.Type().Elem()) if !field.IsNil() { fresh.Elem().Set(field.Elem()) } field.Set(fresh) v = fresh.Elem() } else { v = field } } return nil } func (s *state) structure(src, base reflect.Value, t reflect.Type, path string, depth int) (reflect.Value, error) { if err := s.reserve(1, t.Size(), path); err != nil { return reflect.Value{}, err } out := reflect.New(t).Elem() out.Set(base) if src.Type() == t { // Private scalar state may be assigned as part of its whole value; mutable // private state requires a converter, never unsafe writes or silent loss. for i := 0; i < t.NumField(); i++ { f := t.Field(i) if f.PkgPath != "" && s.options.deep && privateMutable(f.Type) { return reflect.Value{}, at(path+"."+f.Name, ErrUnsupported) } } out.Set(src) } else { for i := 0; i < t.NumField(); i++ { if t.Field(i).PkgPath != "" { return reflect.Value{}, at(path, ErrUnsupported) } } } from, to := exportedFields(src.Type()), exportedFields(t) rules := s.options.fields[pair{src.Type(), t}] if rules == nil { rules = &fields{map[string]string{}, map[string]bool{}, map[string]bool{}} } reverse := map[string]string{} for a, b := range rules.names { reverse[b] = a } names := make([]string, 0, len(to)) for n := range to { names = append(names, n) } sort.Strings(names) used := map[string]bool{} for _, name := range names { target := to[name] old, _ := fieldValue(base, target.Index) if rules.ignoreTarget[name] { if !old.IsValid() { old = reflect.Zero(target.Type) } if err := setField(out, target.Index, old); err != nil { return reflect.Value{}, at(path+"."+name, err) } continue } srcName := name if renamed, ok := reverse[name]; ok { srcName = renamed } source, ok := from[srcName] if !ok || rules.ignoreSource[srcName] { return reflect.Value{}, at(path+"."+name, ErrField) } used[srcName] = true v, ok := fieldValue(src, source.Index) if !ok { return reflect.Value{}, at(path+"."+name, ErrNil) } value, err := s.walk(v, old, target.Type, path+"."+name, depth+1) if err != nil { return reflect.Value{}, err } if err = setField(out, target.Index, value); err != nil { return reflect.Value{}, at(path+"."+name, err) } } if s.options.strict { for name := range from { if !used[name] && !rules.ignoreSource[name] { return reflect.Value{}, at(path+"."+name, ErrField) } } } return out, nil }