blob: 473b6fa744b4db53459651abc483e6eef1f4347e [file] [log] [blame]
// Copyright 2017 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
package serializer
import (
"reflect"
"fmt"
"io"
)
// Write writes Go-syntax representation of v into w.
// This is similar to fmt.Fprintf(w, "%#v", v), but properly handles pointers,
// does not write package names before types, omits struct fields with default values,
// omits type names where possible, etc. On the other hand, it currently does not
// support all types (e.g. channels and maps).
func Write(ww io.Writer, i interface{}) {
w := writer{ww}
v := reflect.ValueOf(i)
if v.Kind() == reflect.Slice && (v.IsNil() || v.Len() == 0) {
w.typ(v.Type())
w.string("(nil)")
return
}
w.do(v, false)
}
type writer struct {
w io.Writer
}
func (w *writer) do(v reflect.Value, sliceElem bool) {
switch v.Kind() {
case reflect.Ptr:
w.doPtr(v, sliceElem)
case reflect.Interface:
if v.IsNil() {
w.string("nil")
} else {
w.do(v.Elem(), false)
}
case reflect.Slice:
w.doSlice(v)
case reflect.Struct:
w.doStruct(v, sliceElem)
case reflect.Bool:
if v.Bool() {
w.string("true")
} else {
w.string("false")
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
fmt.Fprintf(w.w, "%v", v.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
fmt.Fprintf(w.w, "%v", v.Uint())
case reflect.String:
fmt.Fprintf(w.w, "%q", v.String())
case reflect.Func:
// Skip, no way to serialize this.
default:
panic(fmt.Sprintf("unsupported type: %#v", v.Type().String()))
}
}
func (w *writer) doPtr(v reflect.Value, sliceElem bool) {
if v.IsNil() {
w.string("nil")
return
}
if !sliceElem {
w.byte('&')
}
if v.Elem().Kind() != reflect.Struct {
panic(fmt.Sprintf("only pointers to structs are supported, got %v",
v.Type().Name()))
}
w.do(v.Elem(), sliceElem)
}
func (w *writer) doSlice(v reflect.Value) {
if v.IsNil() || v.Len() == 0 {
w.string("nil")
return
}
w.typ(v.Type())
sub := v.Type().Elem().Kind()
if sub == reflect.Ptr || sub == reflect.Interface || sub == reflect.Struct {
// Elem per-line.
w.string("{\n")
for i := 0; i < v.Len(); i++ {
w.do(v.Index(i), true)
w.string(",\n")
}
w.byte('}')
return
}
// All on one line.
w.byte('{')
for i := 0; i < v.Len(); i++ {
if i > 0 {
w.byte(',')
}
w.do(v.Index(i), true)
}
w.byte('}')
}
func (w *writer) doStruct(v reflect.Value, sliceElem bool) {
if !sliceElem {
w.string(v.Type().Name())
}
w.byte('{')
needComma := false
for i := 0; i < v.NumField(); i++ {
f := v.Field(i)
if isDefaultValue(f) {
continue
}
if needComma {
w.byte(',')
}
w.string(v.Type().Field(i).Name)
w.byte(':')
w.do(f, false)
needComma = true
}
w.byte('}')
}
func (w *writer) typ(t reflect.Type) {
switch t.Kind() {
case reflect.Ptr:
w.byte('*')
w.typ(t.Elem())
case reflect.Slice:
w.string("[]")
w.typ(t.Elem())
default:
w.string(t.Name())
}
}
func (w *writer) string(v string) {
io.WriteString(w.w, v)
}
func (w *writer) byte(v byte) {
if bw, ok := w.w.(io.ByteWriter); ok {
bw.WriteByte(v)
} else {
w.w.Write([]byte{v})
}
}
func isDefaultValue(v reflect.Value) bool {
switch v.Kind() {
case reflect.Ptr:
return v.IsNil()
case reflect.Interface:
return v.IsNil()
case reflect.Slice:
return v.IsNil() || v.Len() == 0
case reflect.Struct:
for i := 0; i < v.NumField(); i++ {
if !isDefaultValue(v.Field(i)) {
return false
}
}
return true
case reflect.Bool:
return !v.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.String:
return v.String() == ""
case reflect.Func:
return true
default:
return false
}
}