github.com/alexflint/go-memdump@v1.1.0/serialize.go (about)

     1  package memdump
     2  
     3  import (
     4  	"encoding/binary"
     5  	"fmt"
     6  	"io"
     7  	"reflect"
     8  	"sort"
     9  	"sync"
    10  	"unsafe"
    11  )
    12  
    13  // uintptrSize is the size in bytes of uintptr
    14  const uintptrSize = unsafe.Sizeof(uintptr(0))
    15  
    16  // byteType is the reflect.Type of byte
    17  var (
    18  	byteType      = reflect.TypeOf(byte(0))
    19  	typeCache     = make(map[reflect.Type]*typeInfo)
    20  	typeCacheLock sync.Mutex
    21  )
    22  
    23  // block represents a value to be written to the stream
    24  type block struct {
    25  	src  reflect.Value
    26  	dest uintptr
    27  }
    28  
    29  // pointer represents the location of a pointer in a type
    30  type pointer struct {
    31  	offset uintptr
    32  	typ    reflect.Type
    33  }
    34  
    35  // typeInfo represents the location of the pointers in a type
    36  type typeInfo struct {
    37  	pointers []pointer
    38  }
    39  
    40  // asBytes gets a byte slice with data pointer set to the address of the
    41  // assigned value and length set to the sizeof the value.
    42  func asBytes(v reflect.Value) []byte {
    43  	size := int(v.Type().Size())
    44  	hdr := reflect.SliceHeader{
    45  		Data: v.Addr().Pointer(),
    46  		Len:  size,
    47  		Cap:  size,
    48  	}
    49  	return *(*[]byte)(unsafe.Pointer(&hdr))
    50  }
    51  
    52  // isNil determines whether the pointer contained within v is nil.
    53  // This is equivalent to checking x==nil, except for strings, where
    54  // this method checks the data pointer inside the string header.
    55  func isNil(v reflect.Value) bool {
    56  	if v.Kind() == reflect.String {
    57  		hdr := (*reflect.StringHeader)(unsafe.Pointer(v.Addr().Pointer()))
    58  		return hdr.Data == 0
    59  	}
    60  	return v.IsNil()
    61  }
    62  
    63  func readPointer(v reflect.Value) uintptr {
    64  	if v.Kind() == reflect.String {
    65  		hdr := (*reflect.StringHeader)(unsafe.Pointer(v.Addr().Pointer()))
    66  		return hdr.Data
    67  	}
    68  	return v.Pointer()
    69  }
    70  
    71  type byOffset []pointer
    72  
    73  func (xs byOffset) Len() int           { return len(xs) }
    74  func (xs byOffset) Swap(i, j int)      { xs[i], xs[j] = xs[j], xs[i] }
    75  func (xs byOffset) Less(i, j int) bool { return xs[i].offset < xs[j].offset }
    76  
    77  type countingWriter struct {
    78  	w      io.Writer
    79  	offset int
    80  }
    81  
    82  func (w *countingWriter) Write(buf []byte) (int, error) {
    83  	n, err := w.w.Write(buf)
    84  	w.offset += n
    85  	return n, err
    86  }
    87  
    88  // memEncoder writes the in-memory representation of an object, together
    89  // with all referenced objects.
    90  type memEncoder struct {
    91  	w countingWriter
    92  }
    93  
    94  func newMemEncoder(w io.Writer) *memEncoder {
    95  	return &memEncoder{
    96  		w: countingWriter{w: w},
    97  	}
    98  }
    99  
   100  // memEncoderState contains the state that is local to a single Encode() call.
   101  type memEncoderState struct {
   102  	ptrLocs []int64
   103  	next    uintptr
   104  }
   105  
   106  // alloc makes room for N objects of the specified type, and returns the
   107  // base offset for that object. It deals correctly with alignment.
   108  func (e *memEncoderState) alloc(t reflect.Type, n int) uintptr {
   109  	align := uintptr(t.Align())
   110  	if e.next%align != 0 {
   111  		e.next += align - (e.next % align)
   112  	}
   113  	cur := e.next
   114  	e.next += t.Size() * uintptr(n)
   115  	return cur
   116  }
   117  
   118  // arrayFromString gets a fixed-size array representing the bytes pointed to
   119  // by a string.
   120  func arrayFromString(strval reflect.Value) reflect.Value {
   121  	if strval.Kind() != reflect.String {
   122  		panic(fmt.Sprintf("expected string type but got %s", strval.Type()))
   123  	}
   124  
   125  	hdr := (*reflect.StringHeader)(unsafe.Pointer(strval.Addr().Pointer()))
   126  	typ := reflect.ArrayOf(hdr.Len, byteType)
   127  	return reflect.NewAt(typ, unsafe.Pointer(hdr.Data)).Elem()
   128  }
   129  
   130  // arrayFromSlice gets a fixed-size array representing the data pointed to by
   131  // a slice
   132  func arrayFromSlice(sliceval reflect.Value) reflect.Value {
   133  	if sliceval.Kind() != reflect.Slice {
   134  		panic(fmt.Sprintf("expected string type but got %s", sliceval.Type()))
   135  	}
   136  
   137  	hdr := (*reflect.SliceHeader)(unsafe.Pointer(sliceval.Addr().Pointer()))
   138  	typ := reflect.ArrayOf(hdr.Len, sliceval.Type().Elem())
   139  	return reflect.NewAt(typ, unsafe.Pointer(hdr.Data)).Elem()
   140  }
   141  
   142  // Encode writes the in-memory representation of the object pointed to by ptr. It
   143  // returns the offset of each pointer and an error.
   144  func (e *memEncoder) Encode(ptr interface{}) ([]int64, error) {
   145  	var state memEncoderState
   146  
   147  	ptrval := reflect.ValueOf(ptr)
   148  	objval := ptrval.Elem()
   149  	cache := make(map[uintptr]uintptr)
   150  	queue := []block{{
   151  		src:  objval,
   152  		dest: state.alloc(objval.Type(), 1),
   153  	}}
   154  
   155  	for len(queue) > 0 {
   156  		cur := queue[0]
   157  		queue = queue[1:]
   158  
   159  		blockaddr := cur.src.Addr()
   160  		blockbytes := asBytes(cur.src)
   161  
   162  		// check the position of the writer
   163  		if cur.dest < uintptr(e.w.offset) {
   164  			panic(fmt.Sprintf("block.dest=%d but writer is at %d", cur.dest, e.w.offset))
   165  		}
   166  
   167  		// for byte-alignment purposes we may need to fill some bytes
   168  		if fill := cur.dest - uintptr(e.w.offset); fill > 0 {
   169  			_, err := e.w.Write(make([]byte, fill))
   170  			if err != nil {
   171  				return nil, err
   172  			}
   173  		}
   174  
   175  		// look up info about this type
   176  		info := lookupType(cur.src.Type())
   177  
   178  		// add each referenced object to the queue
   179  		var blockpos uintptr
   180  		for _, ptr := range info.pointers {
   181  			_, err := e.w.Write(blockbytes[blockpos:ptr.offset])
   182  			if err != nil {
   183  				return nil, err
   184  			}
   185  
   186  			ptrdata := unsafe.Pointer(blockaddr.Pointer() + ptr.offset)
   187  			ptrval := reflect.NewAt(ptr.typ, ptrdata).Elem()
   188  
   189  			var dest uintptr
   190  			if !isNil(ptrval) {
   191  				state.ptrLocs = append(state.ptrLocs, int64(cur.dest+ptr.offset))
   192  
   193  				var found bool
   194  				dest, found = cache[readPointer(ptrval)]
   195  				if !found {
   196  					switch ptr.typ.Kind() {
   197  					case reflect.Ptr:
   198  						dest = state.alloc(ptr.typ.Elem(), 1)
   199  						queue = append(queue, block{
   200  							src:  ptrval.Elem(),
   201  							dest: dest,
   202  						})
   203  					case reflect.Slice:
   204  						dest = state.alloc(ptr.typ.Elem(), ptrval.Len())
   205  						arr := arrayFromSlice(ptrval)
   206  						queue = append(queue, block{
   207  							src:  arr,
   208  							dest: dest,
   209  						})
   210  					case reflect.String:
   211  						dest = state.alloc(byteType, ptrval.Len())
   212  						arr := arrayFromString(ptrval)
   213  						queue = append(queue, block{
   214  							src:  arr,
   215  							dest: dest,
   216  						})
   217  					}
   218  					cache[readPointer(ptrval)] = dest
   219  				}
   220  			}
   221  
   222  			err = binary.Write(&e.w, binary.LittleEndian, uint64(dest))
   223  			if err != nil {
   224  				return nil, err
   225  			}
   226  			blockpos = ptr.offset + uintptrSize
   227  		}
   228  
   229  		_, err := e.w.Write(blockbytes[blockpos:])
   230  		if err != nil {
   231  			return nil, err
   232  		}
   233  	}
   234  
   235  	return state.ptrLocs, nil
   236  }
   237  
   238  // pointerFinder gets the byte offset of each pointer in an object. It
   239  // only considers the immediate value of an object (i.e. the bytes that
   240  // would be copied in a simple assignment). It does not follow pointers
   241  // to other objects.
   242  type pointerFinder struct {
   243  	pointers []pointer
   244  }
   245  
   246  func (f *pointerFinder) visit(t reflect.Type, base uintptr) {
   247  	switch t.Kind() {
   248  	case reflect.Ptr, reflect.String, reflect.Slice:
   249  		// these four types all store one pointer at offset zero
   250  		f.pointers = append(f.pointers, pointer{
   251  			offset: base,
   252  			typ:    t,
   253  		})
   254  	case reflect.Struct:
   255  		for i := 0; i < t.NumField(); i++ {
   256  			field := t.Field(i)
   257  			f.visit(field.Type, base+field.Offset)
   258  		}
   259  	case reflect.Array:
   260  		elemSize := t.Elem().Size()
   261  		elemPtrs := lookupType(t.Elem()).pointers
   262  		for _, elemPtr := range elemPtrs {
   263  			for i := 0; i < t.Len(); i++ {
   264  				f.pointers = append(f.pointers, pointer{
   265  					offset: base + uintptr(i)*elemSize + elemPtr.offset,
   266  					typ:    elemPtr.typ,
   267  				})
   268  			}
   269  		}
   270  	case reflect.Map, reflect.Chan, reflect.Interface, reflect.UnsafePointer, reflect.Func:
   271  		panic(fmt.Sprintf("cannot serialize objects of %v kind (got %v)", t.Kind(), t))
   272  	}
   273  }
   274  
   275  // lookupType gets the type info for t.
   276  func lookupType(t reflect.Type) *typeInfo {
   277  	typeCacheLock.Lock()
   278  	info, found := typeCache[t]
   279  	typeCacheLock.Unlock()
   280  
   281  	if !found {
   282  		var f pointerFinder
   283  		f.visit(t, 0)
   284  		info = &typeInfo{pointers: f.pointers}
   285  		sort.Sort(byOffset(info.pointers))
   286  
   287  		typeCacheLock.Lock()
   288  		typeCache[t] = info
   289  		typeCacheLock.Unlock()
   290  	}
   291  
   292  	return info
   293  }