mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-10-31 16:13:45 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			510 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			510 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| /*
 | |
|  * Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
 | |
|  *
 | |
|  * Permission to use, copy, modify, and distribute this software for any
 | |
|  * purpose with or without fee is hereby granted, provided that the above
 | |
|  * copyright notice and this permission notice appear in all copies.
 | |
|  *
 | |
|  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | |
|  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 | |
|  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | |
|  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | |
|  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 | |
|  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | |
|  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | |
|  */
 | |
| 
 | |
| package spew
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"encoding/hex"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"os"
 | |
| 	"reflect"
 | |
| 	"regexp"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	// uint8Type is a reflect.Type representing a uint8.  It is used to
 | |
| 	// convert cgo types to uint8 slices for hexdumping.
 | |
| 	uint8Type = reflect.TypeOf(uint8(0))
 | |
| 
 | |
| 	// cCharRE is a regular expression that matches a cgo char.
 | |
| 	// It is used to detect character arrays to hexdump them.
 | |
| 	cCharRE = regexp.MustCompile(`^.*\._Ctype_char$`)
 | |
| 
 | |
| 	// cUnsignedCharRE is a regular expression that matches a cgo unsigned
 | |
| 	// char.  It is used to detect unsigned character arrays to hexdump
 | |
| 	// them.
 | |
| 	cUnsignedCharRE = regexp.MustCompile(`^.*\._Ctype_unsignedchar$`)
 | |
| 
 | |
| 	// cUint8tCharRE is a regular expression that matches a cgo uint8_t.
 | |
| 	// It is used to detect uint8_t arrays to hexdump them.
 | |
| 	cUint8tCharRE = regexp.MustCompile(`^.*\._Ctype_uint8_t$`)
 | |
| )
 | |
| 
 | |
| // dumpState contains information about the state of a dump operation.
 | |
| type dumpState struct {
 | |
| 	w                io.Writer
 | |
| 	depth            int
 | |
| 	pointers         map[uintptr]int
 | |
| 	ignoreNextType   bool
 | |
| 	ignoreNextIndent bool
 | |
| 	cs               *ConfigState
 | |
| }
 | |
| 
 | |
| // indent performs indentation according to the depth level and cs.Indent
 | |
| // option.
 | |
| func (d *dumpState) indent() {
 | |
| 	if d.ignoreNextIndent {
 | |
| 		d.ignoreNextIndent = false
 | |
| 		return
 | |
| 	}
 | |
| 	d.w.Write(bytes.Repeat([]byte(d.cs.Indent), d.depth))
 | |
| }
 | |
| 
 | |
| // unpackValue returns values inside of non-nil interfaces when possible.
 | |
| // This is useful for data types like structs, arrays, slices, and maps which
 | |
| // can contain varying types packed inside an interface.
 | |
| func (d *dumpState) unpackValue(v reflect.Value) reflect.Value {
 | |
| 	if v.Kind() == reflect.Interface && !v.IsNil() {
 | |
| 		v = v.Elem()
 | |
| 	}
 | |
| 	return v
 | |
| }
 | |
| 
 | |
| // dumpPtr handles formatting of pointers by indirecting them as necessary.
 | |
| func (d *dumpState) dumpPtr(v reflect.Value) {
 | |
| 	// Remove pointers at or below the current depth from map used to detect
 | |
| 	// circular refs.
 | |
| 	for k, depth := range d.pointers {
 | |
| 		if depth >= d.depth {
 | |
| 			delete(d.pointers, k)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Keep list of all dereferenced pointers to show later.
 | |
| 	pointerChain := make([]uintptr, 0)
 | |
| 
 | |
| 	// Figure out how many levels of indirection there are by dereferencing
 | |
| 	// pointers and unpacking interfaces down the chain while detecting circular
 | |
| 	// references.
 | |
| 	nilFound := false
 | |
| 	cycleFound := false
 | |
| 	indirects := 0
 | |
| 	ve := v
 | |
| 	for ve.Kind() == reflect.Ptr {
 | |
| 		if ve.IsNil() {
 | |
| 			nilFound = true
 | |
| 			break
 | |
| 		}
 | |
| 		indirects++
 | |
| 		addr := ve.Pointer()
 | |
| 		pointerChain = append(pointerChain, addr)
 | |
| 		if pd, ok := d.pointers[addr]; ok && pd < d.depth {
 | |
| 			cycleFound = true
 | |
| 			indirects--
 | |
| 			break
 | |
| 		}
 | |
| 		d.pointers[addr] = d.depth
 | |
| 
 | |
| 		ve = ve.Elem()
 | |
| 		if ve.Kind() == reflect.Interface {
 | |
| 			if ve.IsNil() {
 | |
| 				nilFound = true
 | |
| 				break
 | |
| 			}
 | |
| 			ve = ve.Elem()
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Display type information.
 | |
| 	d.w.Write(openParenBytes)
 | |
| 	d.w.Write(bytes.Repeat(asteriskBytes, indirects))
 | |
| 	d.w.Write([]byte(ve.Type().String()))
 | |
| 	d.w.Write(closeParenBytes)
 | |
| 
 | |
| 	// Display pointer information.
 | |
| 	if !d.cs.DisablePointerAddresses && len(pointerChain) > 0 {
 | |
| 		d.w.Write(openParenBytes)
 | |
| 		for i, addr := range pointerChain {
 | |
| 			if i > 0 {
 | |
| 				d.w.Write(pointerChainBytes)
 | |
| 			}
 | |
| 			printHexPtr(d.w, addr)
 | |
| 		}
 | |
| 		d.w.Write(closeParenBytes)
 | |
| 	}
 | |
| 
 | |
| 	// Display dereferenced value.
 | |
| 	d.w.Write(openParenBytes)
 | |
| 	switch {
 | |
| 	case nilFound:
 | |
| 		d.w.Write(nilAngleBytes)
 | |
| 
 | |
| 	case cycleFound:
 | |
| 		d.w.Write(circularBytes)
 | |
| 
 | |
| 	default:
 | |
| 		d.ignoreNextType = true
 | |
| 		d.dump(ve)
 | |
| 	}
 | |
| 	d.w.Write(closeParenBytes)
 | |
| }
 | |
| 
 | |
| // dumpSlice handles formatting of arrays and slices.  Byte (uint8 under
 | |
| // reflection) arrays and slices are dumped in hexdump -C fashion.
 | |
| func (d *dumpState) dumpSlice(v reflect.Value) {
 | |
| 	// Determine whether this type should be hex dumped or not.  Also,
 | |
| 	// for types which should be hexdumped, try to use the underlying data
 | |
| 	// first, then fall back to trying to convert them to a uint8 slice.
 | |
| 	var buf []uint8
 | |
| 	doConvert := false
 | |
| 	doHexDump := false
 | |
| 	numEntries := v.Len()
 | |
| 	if numEntries > 0 {
 | |
| 		vt := v.Index(0).Type()
 | |
| 		vts := vt.String()
 | |
| 		switch {
 | |
| 		// C types that need to be converted.
 | |
| 		case cCharRE.MatchString(vts):
 | |
| 			fallthrough
 | |
| 		case cUnsignedCharRE.MatchString(vts):
 | |
| 			fallthrough
 | |
| 		case cUint8tCharRE.MatchString(vts):
 | |
| 			doConvert = true
 | |
| 
 | |
| 		// Try to use existing uint8 slices and fall back to converting
 | |
| 		// and copying if that fails.
 | |
| 		case vt.Kind() == reflect.Uint8:
 | |
| 			// We need an addressable interface to convert the type
 | |
| 			// to a byte slice.  However, the reflect package won't
 | |
| 			// give us an interface on certain things like
 | |
| 			// unexported struct fields in order to enforce
 | |
| 			// visibility rules.  We use unsafe, when available, to
 | |
| 			// bypass these restrictions since this package does not
 | |
| 			// mutate the values.
 | |
| 			vs := v
 | |
| 			if !vs.CanInterface() || !vs.CanAddr() {
 | |
| 				vs = unsafeReflectValue(vs)
 | |
| 			}
 | |
| 			if !UnsafeDisabled {
 | |
| 				vs = vs.Slice(0, numEntries)
 | |
| 
 | |
| 				// Use the existing uint8 slice if it can be
 | |
| 				// type asserted.
 | |
| 				iface := vs.Interface()
 | |
| 				if slice, ok := iface.([]uint8); ok {
 | |
| 					buf = slice
 | |
| 					doHexDump = true
 | |
| 					break
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			// The underlying data needs to be converted if it can't
 | |
| 			// be type asserted to a uint8 slice.
 | |
| 			doConvert = true
 | |
| 		}
 | |
| 
 | |
| 		// Copy and convert the underlying type if needed.
 | |
| 		if doConvert && vt.ConvertibleTo(uint8Type) {
 | |
| 			// Convert and copy each element into a uint8 byte
 | |
| 			// slice.
 | |
| 			buf = make([]uint8, numEntries)
 | |
| 			for i := 0; i < numEntries; i++ {
 | |
| 				vv := v.Index(i)
 | |
| 				buf[i] = uint8(vv.Convert(uint8Type).Uint())
 | |
| 			}
 | |
| 			doHexDump = true
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Hexdump the entire slice as needed.
 | |
| 	if doHexDump {
 | |
| 		indent := strings.Repeat(d.cs.Indent, d.depth)
 | |
| 		str := indent + hex.Dump(buf)
 | |
| 		str = strings.Replace(str, "\n", "\n"+indent, -1)
 | |
| 		str = strings.TrimRight(str, d.cs.Indent)
 | |
| 		d.w.Write([]byte(str))
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Recursively call dump for each item.
 | |
| 	for i := 0; i < numEntries; i++ {
 | |
| 		d.dump(d.unpackValue(v.Index(i)))
 | |
| 		if i < (numEntries - 1) {
 | |
| 			d.w.Write(commaNewlineBytes)
 | |
| 		} else {
 | |
| 			d.w.Write(newlineBytes)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // dump is the main workhorse for dumping a value.  It uses the passed reflect
 | |
| // value to figure out what kind of object we are dealing with and formats it
 | |
| // appropriately.  It is a recursive function, however circular data structures
 | |
| // are detected and handled properly.
 | |
| func (d *dumpState) dump(v reflect.Value) {
 | |
| 	// Handle invalid reflect values immediately.
 | |
| 	kind := v.Kind()
 | |
| 	if kind == reflect.Invalid {
 | |
| 		d.w.Write(invalidAngleBytes)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Handle pointers specially.
 | |
| 	if kind == reflect.Ptr {
 | |
| 		d.indent()
 | |
| 		d.dumpPtr(v)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Print type information unless already handled elsewhere.
 | |
| 	if !d.ignoreNextType {
 | |
| 		d.indent()
 | |
| 		d.w.Write(openParenBytes)
 | |
| 		d.w.Write([]byte(v.Type().String()))
 | |
| 		d.w.Write(closeParenBytes)
 | |
| 		d.w.Write(spaceBytes)
 | |
| 	}
 | |
| 	d.ignoreNextType = false
 | |
| 
 | |
| 	// Display length and capacity if the built-in len and cap functions
 | |
| 	// work with the value's kind and the len/cap itself is non-zero.
 | |
| 	valueLen, valueCap := 0, 0
 | |
| 	switch v.Kind() {
 | |
| 	case reflect.Array, reflect.Slice, reflect.Chan:
 | |
| 		valueLen, valueCap = v.Len(), v.Cap()
 | |
| 	case reflect.Map, reflect.String:
 | |
| 		valueLen = v.Len()
 | |
| 	}
 | |
| 	if valueLen != 0 || !d.cs.DisableCapacities && valueCap != 0 {
 | |
| 		d.w.Write(openParenBytes)
 | |
| 		if valueLen != 0 {
 | |
| 			d.w.Write(lenEqualsBytes)
 | |
| 			printInt(d.w, int64(valueLen), 10)
 | |
| 		}
 | |
| 		if !d.cs.DisableCapacities && valueCap != 0 {
 | |
| 			if valueLen != 0 {
 | |
| 				d.w.Write(spaceBytes)
 | |
| 			}
 | |
| 			d.w.Write(capEqualsBytes)
 | |
| 			printInt(d.w, int64(valueCap), 10)
 | |
| 		}
 | |
| 		d.w.Write(closeParenBytes)
 | |
| 		d.w.Write(spaceBytes)
 | |
| 	}
 | |
| 
 | |
| 	// Call Stringer/error interfaces if they exist and the handle methods flag
 | |
| 	// is enabled
 | |
| 	if !d.cs.DisableMethods {
 | |
| 		if (kind != reflect.Invalid) && (kind != reflect.Interface) {
 | |
| 			if handled := handleMethods(d.cs, d.w, v); handled {
 | |
| 				return
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	switch kind {
 | |
| 	case reflect.Invalid:
 | |
| 		// Do nothing.  We should never get here since invalid has already
 | |
| 		// been handled above.
 | |
| 
 | |
| 	case reflect.Bool:
 | |
| 		printBool(d.w, v.Bool())
 | |
| 
 | |
| 	case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
 | |
| 		printInt(d.w, v.Int(), 10)
 | |
| 
 | |
| 	case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
 | |
| 		printUint(d.w, v.Uint(), 10)
 | |
| 
 | |
| 	case reflect.Float32:
 | |
| 		printFloat(d.w, v.Float(), 32)
 | |
| 
 | |
| 	case reflect.Float64:
 | |
| 		printFloat(d.w, v.Float(), 64)
 | |
| 
 | |
| 	case reflect.Complex64:
 | |
| 		printComplex(d.w, v.Complex(), 32)
 | |
| 
 | |
| 	case reflect.Complex128:
 | |
| 		printComplex(d.w, v.Complex(), 64)
 | |
| 
 | |
| 	case reflect.Slice:
 | |
| 		if v.IsNil() {
 | |
| 			d.w.Write(nilAngleBytes)
 | |
| 			break
 | |
| 		}
 | |
| 		fallthrough
 | |
| 
 | |
| 	case reflect.Array:
 | |
| 		d.w.Write(openBraceNewlineBytes)
 | |
| 		d.depth++
 | |
| 		if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
 | |
| 			d.indent()
 | |
| 			d.w.Write(maxNewlineBytes)
 | |
| 		} else {
 | |
| 			d.dumpSlice(v)
 | |
| 		}
 | |
| 		d.depth--
 | |
| 		d.indent()
 | |
| 		d.w.Write(closeBraceBytes)
 | |
| 
 | |
| 	case reflect.String:
 | |
| 		d.w.Write([]byte(strconv.Quote(v.String())))
 | |
| 
 | |
| 	case reflect.Interface:
 | |
| 		// The only time we should get here is for nil interfaces due to
 | |
| 		// unpackValue calls.
 | |
| 		if v.IsNil() {
 | |
| 			d.w.Write(nilAngleBytes)
 | |
| 		}
 | |
| 
 | |
| 	case reflect.Ptr:
 | |
| 		// Do nothing.  We should never get here since pointers have already
 | |
| 		// been handled above.
 | |
| 
 | |
| 	case reflect.Map:
 | |
| 		// nil maps should be indicated as different than empty maps
 | |
| 		if v.IsNil() {
 | |
| 			d.w.Write(nilAngleBytes)
 | |
| 			break
 | |
| 		}
 | |
| 
 | |
| 		d.w.Write(openBraceNewlineBytes)
 | |
| 		d.depth++
 | |
| 		if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
 | |
| 			d.indent()
 | |
| 			d.w.Write(maxNewlineBytes)
 | |
| 		} else {
 | |
| 			numEntries := v.Len()
 | |
| 			keys := v.MapKeys()
 | |
| 			if d.cs.SortKeys {
 | |
| 				sortValues(keys, d.cs)
 | |
| 			}
 | |
| 			for i, key := range keys {
 | |
| 				d.dump(d.unpackValue(key))
 | |
| 				d.w.Write(colonSpaceBytes)
 | |
| 				d.ignoreNextIndent = true
 | |
| 				d.dump(d.unpackValue(v.MapIndex(key)))
 | |
| 				if i < (numEntries - 1) {
 | |
| 					d.w.Write(commaNewlineBytes)
 | |
| 				} else {
 | |
| 					d.w.Write(newlineBytes)
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		d.depth--
 | |
| 		d.indent()
 | |
| 		d.w.Write(closeBraceBytes)
 | |
| 
 | |
| 	case reflect.Struct:
 | |
| 		d.w.Write(openBraceNewlineBytes)
 | |
| 		d.depth++
 | |
| 		if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
 | |
| 			d.indent()
 | |
| 			d.w.Write(maxNewlineBytes)
 | |
| 		} else {
 | |
| 			vt := v.Type()
 | |
| 			numFields := v.NumField()
 | |
| 			for i := 0; i < numFields; i++ {
 | |
| 				d.indent()
 | |
| 				vtf := vt.Field(i)
 | |
| 				d.w.Write([]byte(vtf.Name))
 | |
| 				d.w.Write(colonSpaceBytes)
 | |
| 				d.ignoreNextIndent = true
 | |
| 				d.dump(d.unpackValue(v.Field(i)))
 | |
| 				if i < (numFields - 1) {
 | |
| 					d.w.Write(commaNewlineBytes)
 | |
| 				} else {
 | |
| 					d.w.Write(newlineBytes)
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		d.depth--
 | |
| 		d.indent()
 | |
| 		d.w.Write(closeBraceBytes)
 | |
| 
 | |
| 	case reflect.Uintptr:
 | |
| 		printHexPtr(d.w, uintptr(v.Uint()))
 | |
| 
 | |
| 	case reflect.UnsafePointer, reflect.Chan, reflect.Func:
 | |
| 		printHexPtr(d.w, v.Pointer())
 | |
| 
 | |
| 	// There were not any other types at the time this code was written, but
 | |
| 	// fall back to letting the default fmt package handle it in case any new
 | |
| 	// types are added.
 | |
| 	default:
 | |
| 		if v.CanInterface() {
 | |
| 			fmt.Fprintf(d.w, "%v", v.Interface())
 | |
| 		} else {
 | |
| 			fmt.Fprintf(d.w, "%v", v.String())
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // fdump is a helper function to consolidate the logic from the various public
 | |
| // methods which take varying writers and config states.
 | |
| func fdump(cs *ConfigState, w io.Writer, a ...interface{}) {
 | |
| 	for _, arg := range a {
 | |
| 		if arg == nil {
 | |
| 			w.Write(interfaceBytes)
 | |
| 			w.Write(spaceBytes)
 | |
| 			w.Write(nilAngleBytes)
 | |
| 			w.Write(newlineBytes)
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		d := dumpState{w: w, cs: cs}
 | |
| 		d.pointers = make(map[uintptr]int)
 | |
| 		d.dump(reflect.ValueOf(arg))
 | |
| 		d.w.Write(newlineBytes)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Fdump formats and displays the passed arguments to io.Writer w.  It formats
 | |
| // exactly the same as Dump.
 | |
| func Fdump(w io.Writer, a ...interface{}) {
 | |
| 	fdump(&Config, w, a...)
 | |
| }
 | |
| 
 | |
| // Sdump returns a string with the passed arguments formatted exactly the same
 | |
| // as Dump.
 | |
| func Sdump(a ...interface{}) string {
 | |
| 	var buf bytes.Buffer
 | |
| 	fdump(&Config, &buf, a...)
 | |
| 	return buf.String()
 | |
| }
 | |
| 
 | |
| /*
 | |
| Dump displays the passed parameters to standard out with newlines, customizable
 | |
| indentation, and additional debug information such as complete types and all
 | |
| pointer addresses used to indirect to the final value.  It provides the
 | |
| following features over the built-in printing facilities provided by the fmt
 | |
| package:
 | |
| 
 | |
| 	* Pointers are dereferenced and followed
 | |
| 	* Circular data structures are detected and handled properly
 | |
| 	* Custom Stringer/error interfaces are optionally invoked, including
 | |
| 	  on unexported types
 | |
| 	* Custom types which only implement the Stringer/error interfaces via
 | |
| 	  a pointer receiver are optionally invoked when passing non-pointer
 | |
| 	  variables
 | |
| 	* Byte arrays and slices are dumped like the hexdump -C command which
 | |
| 	  includes offsets, byte values in hex, and ASCII output
 | |
| 
 | |
| The configuration options are controlled by an exported package global,
 | |
| spew.Config.  See ConfigState for options documentation.
 | |
| 
 | |
| See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
 | |
| get the formatted result as a string.
 | |
| */
 | |
| func Dump(a ...interface{}) {
 | |
| 	fdump(&Config, os.Stdout, a...)
 | |
| }
 | 
