2020-06-24 06:32:23 +00:00
|
|
|
// Copyright 2018 The gVisor Authors.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
// Package pretty is a pretty-printer for state streams.
|
|
|
|
package pretty
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"reflect"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"gvisor.dev/gvisor/pkg/state"
|
|
|
|
"gvisor.dev/gvisor/pkg/state/wire"
|
|
|
|
)
|
|
|
|
|
2020-08-31 19:01:46 +00:00
|
|
|
type printer struct {
|
|
|
|
html bool
|
|
|
|
typeSpecs map[string]*wire.Type
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *printer) formatRef(x *wire.Ref, graph uint64) string {
|
2020-06-24 06:32:23 +00:00
|
|
|
baseRef := fmt.Sprintf("g%dr%d", graph, x.Root)
|
|
|
|
fullRef := baseRef
|
|
|
|
if len(x.Dots) > 0 {
|
|
|
|
// See wire.Ref; Type valid if Dots non-zero.
|
2020-08-31 19:01:46 +00:00
|
|
|
typ, _ := p.formatType(x.Type, graph)
|
2020-06-24 06:32:23 +00:00
|
|
|
var buf strings.Builder
|
|
|
|
buf.WriteString("(*")
|
|
|
|
buf.WriteString(typ)
|
|
|
|
buf.WriteString(")(")
|
|
|
|
buf.WriteString(baseRef)
|
2020-10-23 19:51:29 +00:00
|
|
|
buf.WriteString(")")
|
2020-06-24 06:32:23 +00:00
|
|
|
for _, component := range x.Dots {
|
|
|
|
switch v := component.(type) {
|
|
|
|
case *wire.FieldName:
|
|
|
|
buf.WriteString(".")
|
|
|
|
buf.WriteString(string(*v))
|
|
|
|
case wire.Index:
|
|
|
|
buf.WriteString(fmt.Sprintf("[%d]", v))
|
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("unreachable: switch should be exhaustive, unhandled case %v", reflect.TypeOf(component)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fullRef = buf.String()
|
|
|
|
}
|
2020-08-31 19:01:46 +00:00
|
|
|
if p.html {
|
2020-06-24 06:32:23 +00:00
|
|
|
return fmt.Sprintf("<a href=\"#%s\">%s</a>", baseRef, fullRef)
|
|
|
|
}
|
|
|
|
return fullRef
|
|
|
|
}
|
|
|
|
|
2020-08-31 19:01:46 +00:00
|
|
|
func (p *printer) formatType(t wire.TypeSpec, graph uint64) (string, bool) {
|
2020-06-24 06:32:23 +00:00
|
|
|
switch x := t.(type) {
|
|
|
|
case wire.TypeID:
|
2020-08-31 19:01:46 +00:00
|
|
|
tag := fmt.Sprintf("g%dt%d", graph, x)
|
|
|
|
desc := tag
|
|
|
|
if spec, ok := p.typeSpecs[tag]; ok {
|
|
|
|
desc += fmt.Sprintf("=%s", spec.Name)
|
|
|
|
} else {
|
|
|
|
desc += "!missing-type-spec"
|
|
|
|
}
|
|
|
|
if p.html {
|
|
|
|
return fmt.Sprintf("<a href=\"#%s\">%s</a>", tag, desc), true
|
2020-06-24 06:32:23 +00:00
|
|
|
}
|
2020-08-31 19:01:46 +00:00
|
|
|
return desc, true
|
2020-06-24 06:32:23 +00:00
|
|
|
case wire.TypeSpecNil:
|
|
|
|
return "", false // Only nil type.
|
|
|
|
case *wire.TypeSpecPointer:
|
2020-08-31 19:01:46 +00:00
|
|
|
element, _ := p.formatType(x.Type, graph)
|
2020-06-24 06:32:23 +00:00
|
|
|
return fmt.Sprintf("(*%s)", element), true
|
|
|
|
case *wire.TypeSpecArray:
|
2020-08-31 19:01:46 +00:00
|
|
|
element, _ := p.formatType(x.Type, graph)
|
2020-06-24 06:32:23 +00:00
|
|
|
return fmt.Sprintf("[%d](%s)", x.Count, element), true
|
|
|
|
case *wire.TypeSpecSlice:
|
2020-08-31 19:01:46 +00:00
|
|
|
element, _ := p.formatType(x.Type, graph)
|
2020-06-24 06:32:23 +00:00
|
|
|
return fmt.Sprintf("([]%s)", element), true
|
|
|
|
case *wire.TypeSpecMap:
|
2020-08-31 19:01:46 +00:00
|
|
|
key, _ := p.formatType(x.Key, graph)
|
|
|
|
value, _ := p.formatType(x.Value, graph)
|
2020-06-24 06:32:23 +00:00
|
|
|
return fmt.Sprintf("(map[%s]%s)", key, value), true
|
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("unreachable: unknown type %T", t))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// format formats a single object, for pretty-printing. It also returns whether
|
|
|
|
// the value is a non-zero value.
|
2020-08-31 19:01:46 +00:00
|
|
|
func (p *printer) format(graph uint64, depth int, encoded wire.Object) (string, bool) {
|
2020-06-24 06:32:23 +00:00
|
|
|
switch x := encoded.(type) {
|
|
|
|
case wire.Nil:
|
|
|
|
return "nil", false
|
|
|
|
case *wire.String:
|
|
|
|
return fmt.Sprintf("%q", *x), *x != ""
|
|
|
|
case *wire.Complex64:
|
|
|
|
return fmt.Sprintf("%f+%fi", real(*x), imag(*x)), *x != 0.0
|
|
|
|
case *wire.Complex128:
|
|
|
|
return fmt.Sprintf("%f+%fi", real(*x), imag(*x)), *x != 0.0
|
|
|
|
case *wire.Ref:
|
2020-08-31 19:01:46 +00:00
|
|
|
return p.formatRef(x, graph), x.Root != 0
|
2020-06-24 06:32:23 +00:00
|
|
|
case *wire.Type:
|
|
|
|
tabs := "\n" + strings.Repeat("\t", depth)
|
|
|
|
items := make([]string, 0, len(x.Fields)+2)
|
|
|
|
items = append(items, fmt.Sprintf("type %s {", x.Name))
|
|
|
|
for i := 0; i < len(x.Fields); i++ {
|
|
|
|
items = append(items, fmt.Sprintf("\t%d: %s,", i, x.Fields[i]))
|
|
|
|
}
|
|
|
|
items = append(items, "}")
|
|
|
|
return strings.Join(items, tabs), true // No zero value.
|
|
|
|
case *wire.Slice:
|
2020-08-31 19:01:46 +00:00
|
|
|
return fmt.Sprintf("%s{len:%d,cap:%d}", p.formatRef(&x.Ref, graph), x.Length, x.Capacity), x.Capacity != 0
|
2020-06-24 06:32:23 +00:00
|
|
|
case *wire.Array:
|
|
|
|
if len(x.Contents) == 0 {
|
|
|
|
return "[]", false
|
|
|
|
}
|
|
|
|
items := make([]string, 0, len(x.Contents)+2)
|
|
|
|
zeros := make([]string, 0) // used to eliminate zero entries.
|
|
|
|
items = append(items, "[")
|
|
|
|
tabs := "\n" + strings.Repeat("\t", depth)
|
|
|
|
for i := 0; i < len(x.Contents); i++ {
|
2020-08-31 19:01:46 +00:00
|
|
|
item, ok := p.format(graph, depth+1, x.Contents[i])
|
2020-06-24 06:32:23 +00:00
|
|
|
if !ok {
|
|
|
|
zeros = append(zeros, fmt.Sprintf("\t%s,", item))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if len(zeros) > 0 {
|
|
|
|
items = append(items, zeros...)
|
|
|
|
zeros = nil
|
|
|
|
}
|
|
|
|
items = append(items, fmt.Sprintf("\t%s,", item))
|
|
|
|
}
|
|
|
|
if len(zeros) > 0 {
|
|
|
|
items = append(items, fmt.Sprintf("\t... (%d zeros),", len(zeros)))
|
|
|
|
}
|
|
|
|
items = append(items, "]")
|
|
|
|
return strings.Join(items, tabs), len(zeros) < len(x.Contents)
|
|
|
|
case *wire.Struct:
|
2020-08-31 19:01:46 +00:00
|
|
|
tag := fmt.Sprintf("g%dt%d", graph, x.TypeID)
|
|
|
|
spec, _ := p.typeSpecs[tag]
|
|
|
|
typ, _ := p.formatType(x.TypeID, graph)
|
2020-06-24 06:32:23 +00:00
|
|
|
if x.Fields() == 0 {
|
|
|
|
return fmt.Sprintf("struct[%s]{}", typ), false
|
|
|
|
}
|
|
|
|
items := make([]string, 0, 2)
|
|
|
|
items = append(items, fmt.Sprintf("struct[%s]{", typ))
|
|
|
|
tabs := "\n" + strings.Repeat("\t", depth)
|
|
|
|
allZero := true
|
|
|
|
for i := 0; i < x.Fields(); i++ {
|
2020-08-31 19:01:46 +00:00
|
|
|
var name string
|
|
|
|
if spec != nil && i < len(spec.Fields) {
|
|
|
|
name = spec.Fields[i]
|
|
|
|
} else {
|
|
|
|
name = fmt.Sprintf("%d", i)
|
|
|
|
}
|
|
|
|
element, ok := p.format(graph, depth+1, *x.Field(i))
|
2020-06-24 06:32:23 +00:00
|
|
|
allZero = allZero && !ok
|
2020-08-31 19:01:46 +00:00
|
|
|
items = append(items, fmt.Sprintf("\t%s: %s,", name, element))
|
2020-06-24 06:32:23 +00:00
|
|
|
}
|
|
|
|
items = append(items, "}")
|
|
|
|
return strings.Join(items, tabs), !allZero
|
|
|
|
case *wire.Map:
|
|
|
|
if len(x.Keys) == 0 {
|
|
|
|
return "map{}", false
|
|
|
|
}
|
|
|
|
items := make([]string, 0, len(x.Keys)+2)
|
|
|
|
items = append(items, "map{")
|
|
|
|
tabs := "\n" + strings.Repeat("\t", depth)
|
|
|
|
for i := 0; i < len(x.Keys); i++ {
|
2020-08-31 19:01:46 +00:00
|
|
|
key, _ := p.format(graph, depth+1, x.Keys[i])
|
|
|
|
value, _ := p.format(graph, depth+1, x.Values[i])
|
2020-06-24 06:32:23 +00:00
|
|
|
items = append(items, fmt.Sprintf("\t%s: %s,", key, value))
|
|
|
|
}
|
|
|
|
items = append(items, "}")
|
|
|
|
return strings.Join(items, tabs), true
|
|
|
|
case *wire.Interface:
|
2020-08-31 19:01:46 +00:00
|
|
|
typ, typOk := p.formatType(x.Type, graph)
|
|
|
|
element, elementOk := p.format(graph, depth+1, x.Value)
|
2020-06-24 06:32:23 +00:00
|
|
|
return fmt.Sprintf("interface[%s]{%s}", typ, element), typOk || elementOk
|
|
|
|
default:
|
|
|
|
// Must be a primitive; use reflection.
|
|
|
|
return fmt.Sprintf("%v", encoded), true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// printStream is the basic print implementation.
|
2020-08-31 19:01:46 +00:00
|
|
|
func (p *printer) printStream(w io.Writer, r wire.Reader) (err error) {
|
2020-06-24 06:32:23 +00:00
|
|
|
// current graph ID.
|
|
|
|
var graph uint64
|
|
|
|
|
2020-08-31 19:01:46 +00:00
|
|
|
if p.html {
|
2020-06-24 06:32:23 +00:00
|
|
|
fmt.Fprintf(w, "<pre>")
|
|
|
|
defer fmt.Fprintf(w, "</pre>")
|
|
|
|
}
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
if r := recover(); r != nil {
|
|
|
|
if rErr, ok := r.(error); ok {
|
|
|
|
err = rErr // Override return.
|
|
|
|
return
|
|
|
|
}
|
|
|
|
panic(r) // Propagate.
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2020-08-31 19:01:46 +00:00
|
|
|
p.typeSpecs = make(map[string]*wire.Type)
|
|
|
|
|
2020-06-24 06:32:23 +00:00
|
|
|
for {
|
|
|
|
// Find the first object to begin generation.
|
|
|
|
length, object, err := state.ReadHeader(r)
|
|
|
|
if err == io.EOF {
|
|
|
|
// Nothing else to do.
|
|
|
|
break
|
|
|
|
} else if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if !object {
|
|
|
|
graph++ // Increment the graph.
|
|
|
|
if length > 0 {
|
|
|
|
fmt.Fprintf(w, "(%d bytes non-object data)\n", length)
|
|
|
|
io.Copy(ioutil.Discard, &io.LimitedReader{
|
|
|
|
R: r,
|
|
|
|
N: int64(length),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read & unmarshal the object.
|
|
|
|
//
|
|
|
|
// Note that this loop must match the general structure of the
|
|
|
|
// loop in decode.go. But we don't register type information,
|
|
|
|
// etc. and just print the raw structures.
|
2020-10-23 19:51:29 +00:00
|
|
|
type objectAndID struct {
|
|
|
|
id uint64
|
|
|
|
obj wire.Object
|
|
|
|
}
|
2020-06-24 06:32:23 +00:00
|
|
|
var (
|
2020-08-31 19:01:46 +00:00
|
|
|
tid uint64 = 1
|
2020-10-23 19:51:29 +00:00
|
|
|
objects []objectAndID
|
2020-06-24 06:32:23 +00:00
|
|
|
)
|
2020-10-23 19:51:29 +00:00
|
|
|
for i := uint64(0); i < length; {
|
|
|
|
// Unmarshal either a type object or object ID.
|
2020-06-24 06:32:23 +00:00
|
|
|
encoded := wire.Load(r)
|
2020-10-23 19:51:29 +00:00
|
|
|
switch we := encoded.(type) {
|
|
|
|
case *wire.Type:
|
2020-08-31 19:01:46 +00:00
|
|
|
str, _ := p.format(graph, 0, encoded)
|
2020-06-24 06:32:23 +00:00
|
|
|
tag := fmt.Sprintf("g%dt%d", graph, tid)
|
2020-10-23 19:51:29 +00:00
|
|
|
p.typeSpecs[tag] = we
|
2020-08-31 19:01:46 +00:00
|
|
|
if p.html {
|
2020-06-24 06:32:23 +00:00
|
|
|
// See below.
|
|
|
|
tag = fmt.Sprintf("<a name=\"%s\">%s</a><a href=\"#%s\">⚓</a>", tag, tag, tag)
|
|
|
|
}
|
|
|
|
if _, err := fmt.Fprintf(w, "%s = %s\n", tag, str); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
tid++
|
2020-10-23 19:51:29 +00:00
|
|
|
case wire.Uint:
|
|
|
|
// Unmarshal the actual object.
|
|
|
|
objects = append(objects, objectAndID{
|
|
|
|
id: uint64(we),
|
|
|
|
obj: wire.Load(r),
|
|
|
|
})
|
|
|
|
i++
|
|
|
|
default:
|
|
|
|
return fmt.Errorf("wanted type or object ID, got %#v", encoded)
|
2020-06-24 06:32:23 +00:00
|
|
|
}
|
2020-08-31 19:01:46 +00:00
|
|
|
}
|
|
|
|
|
2020-10-23 19:51:29 +00:00
|
|
|
for _, objAndID := range objects {
|
2020-06-24 06:32:23 +00:00
|
|
|
// Format the node.
|
2020-10-23 19:51:29 +00:00
|
|
|
str, _ := p.format(graph, 0, objAndID.obj)
|
|
|
|
tag := fmt.Sprintf("g%dr%d", graph, objAndID.id)
|
2020-08-31 19:01:46 +00:00
|
|
|
if p.html {
|
2020-06-24 06:32:23 +00:00
|
|
|
// Create a little tag with an anchor next to it for linking.
|
|
|
|
tag = fmt.Sprintf("<a name=\"%s\">%s</a><a href=\"#%s\">⚓</a>", tag, tag, tag)
|
|
|
|
}
|
|
|
|
if _, err := fmt.Fprintf(w, "%s = %s\n", tag, str); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// PrintText reads the stream from r and prints text to w.
|
|
|
|
func PrintText(w io.Writer, r wire.Reader) error {
|
2020-08-31 19:01:46 +00:00
|
|
|
return (&printer{}).printStream(w, r)
|
2020-06-24 06:32:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// PrintHTML reads the stream from r and prints html to w.
|
|
|
|
func PrintHTML(w io.Writer, r wire.Reader) error {
|
2020-08-31 19:01:46 +00:00
|
|
|
return (&printer{html: true}).printStream(w, r)
|
2020-06-24 06:32:23 +00:00
|
|
|
}
|