1// Copyright 2018 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// This file implements textual dumping of arbitrary data structures
6// for debugging purposes. The code is customized for Node graphs
7// and may be used for an alternative view of the node structure.
8
9package ir
10
11import (
12	"fmt"
13	"io"
14	"os"
15	"reflect"
16	"regexp"
17
18	"cmd/compile/internal/base"
19	"cmd/compile/internal/types"
20	"cmd/internal/src"
21)
22
23// DumpAny is like FDumpAny but prints to stderr.
24func DumpAny(root interface{}, filter string, depth int) {
25	FDumpAny(os.Stderr, root, filter, depth)
26}
27
28// FDumpAny prints the structure of a rooted data structure
29// to w by depth-first traversal of the data structure.
30//
31// The filter parameter is a regular expression. If it is
32// non-empty, only struct fields whose names match filter
33// are printed.
34//
35// The depth parameter controls how deep traversal recurses
36// before it returns (higher value means greater depth).
37// If an empty field filter is given, a good depth default value
38// is 4. A negative depth means no depth limit, which may be fine
39// for small data structures or if there is a non-empty filter.
40//
41// In the output, Node structs are identified by their Op name
42// rather than their type; struct fields with zero values or
43// non-matching field names are omitted, and "…" means recursion
44// depth has been reached or struct fields have been omitted.
45func FDumpAny(w io.Writer, root interface{}, filter string, depth int) {
46	if root == nil {
47		fmt.Fprintln(w, "nil")
48		return
49	}
50
51	if filter == "" {
52		filter = ".*" // default
53	}
54
55	p := dumper{
56		output:  w,
57		fieldrx: regexp.MustCompile(filter),
58		ptrmap:  make(map[uintptr]int),
59		last:    '\n', // force printing of line number on first line
60	}
61
62	p.dump(reflect.ValueOf(root), depth)
63	p.printf("\n")
64}
65
66type dumper struct {
67	output  io.Writer
68	fieldrx *regexp.Regexp  // field name filter
69	ptrmap  map[uintptr]int // ptr -> dump line number
70	lastadr string          // last address string printed (for shortening)
71
72	// output
73	indent int  // current indentation level
74	last   byte // last byte processed by Write
75	line   int  // current line number
76}
77
78var indentBytes = []byte(".  ")
79
80func (p *dumper) Write(data []byte) (n int, err error) {
81	var m int
82	for i, b := range data {
83		// invariant: data[0:n] has been written
84		if b == '\n' {
85			m, err = p.output.Write(data[n : i+1])
86			n += m
87			if err != nil {
88				return
89			}
90		} else if p.last == '\n' {
91			p.line++
92			_, err = fmt.Fprintf(p.output, "%6d  ", p.line)
93			if err != nil {
94				return
95			}
96			for j := p.indent; j > 0; j-- {
97				_, err = p.output.Write(indentBytes)
98				if err != nil {
99					return
100				}
101			}
102		}
103		p.last = b
104	}
105	if len(data) > n {
106		m, err = p.output.Write(data[n:])
107		n += m
108	}
109	return
110}
111
112// printf is a convenience wrapper.
113func (p *dumper) printf(format string, args ...interface{}) {
114	if _, err := fmt.Fprintf(p, format, args...); err != nil {
115		panic(err)
116	}
117}
118
119// addr returns the (hexadecimal) address string of the object
120// represented by x (or "?" if x is not addressable), with the
121// common prefix between this and the prior address replaced by
122// "0x…" to make it easier to visually match addresses.
123func (p *dumper) addr(x reflect.Value) string {
124	if !x.CanAddr() {
125		return "?"
126	}
127	adr := fmt.Sprintf("%p", x.Addr().Interface())
128	s := adr
129	if i := commonPrefixLen(p.lastadr, adr); i > 0 {
130		s = "0x…" + adr[i:]
131	}
132	p.lastadr = adr
133	return s
134}
135
136// dump prints the contents of x.
137func (p *dumper) dump(x reflect.Value, depth int) {
138	if depth == 0 {
139		p.printf("…")
140		return
141	}
142
143	if pos, ok := x.Interface().(src.XPos); ok {
144		p.printf("%s", base.FmtPos(pos))
145		return
146	}
147
148	switch x.Kind() {
149	case reflect.String:
150		p.printf("%q", x.Interface()) // print strings in quotes
151
152	case reflect.Interface:
153		if x.IsNil() {
154			p.printf("nil")
155			return
156		}
157		p.dump(x.Elem(), depth-1)
158
159	case reflect.Ptr:
160		if x.IsNil() {
161			p.printf("nil")
162			return
163		}
164
165		p.printf("*")
166		ptr := x.Pointer()
167		if line, exists := p.ptrmap[ptr]; exists {
168			p.printf("(@%d)", line)
169			return
170		}
171		p.ptrmap[ptr] = p.line
172		p.dump(x.Elem(), depth) // don't count pointer indirection towards depth
173
174	case reflect.Slice:
175		if x.IsNil() {
176			p.printf("nil")
177			return
178		}
179		p.printf("%s (%d entries) {", x.Type(), x.Len())
180		if x.Len() > 0 {
181			p.indent++
182			p.printf("\n")
183			for i, n := 0, x.Len(); i < n; i++ {
184				p.printf("%d: ", i)
185				p.dump(x.Index(i), depth-1)
186				p.printf("\n")
187			}
188			p.indent--
189		}
190		p.printf("}")
191
192	case reflect.Struct:
193		typ := x.Type()
194
195		isNode := false
196		if n, ok := x.Interface().(Node); ok {
197			isNode = true
198			p.printf("%s %s {", n.Op().String(), p.addr(x))
199		} else {
200			p.printf("%s {", typ)
201		}
202		p.indent++
203
204		first := true
205		omitted := false
206		for i, n := 0, typ.NumField(); i < n; i++ {
207			// Exclude non-exported fields because their
208			// values cannot be accessed via reflection.
209			if name := typ.Field(i).Name; types.IsExported(name) {
210				if !p.fieldrx.MatchString(name) {
211					omitted = true
212					continue // field name not selected by filter
213				}
214
215				// special cases
216				if isNode && name == "Op" {
217					omitted = true
218					continue // Op field already printed for Nodes
219				}
220				x := x.Field(i)
221				if x.IsZero() {
222					omitted = true
223					continue // exclude zero-valued fields
224				}
225				if n, ok := x.Interface().(Nodes); ok && len(n) == 0 {
226					omitted = true
227					continue // exclude empty Nodes slices
228				}
229
230				if first {
231					p.printf("\n")
232					first = false
233				}
234				p.printf("%s: ", name)
235				p.dump(x, depth-1)
236				p.printf("\n")
237			}
238		}
239		if omitted {
240			p.printf("…\n")
241		}
242
243		p.indent--
244		p.printf("}")
245
246	default:
247		p.printf("%v", x.Interface())
248	}
249}
250
251func commonPrefixLen(a, b string) (i int) {
252	for i < len(a) && i < len(b) && a[i] == b[i] {
253		i++
254	}
255	return
256}
257