271 lines
6.5 KiB
Go
271 lines
6.5 KiB
Go
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
|
|
}
|