1// Copyright 2019 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// Package debug exports debug information for gopls.
6package debug
7
8import (
9	"context"
10	"fmt"
11	"io"
12	"reflect"
13	"runtime/debug"
14	"sort"
15	"strings"
16
17	"golang.org/x/tools/internal/lsp/source"
18)
19
20type PrintMode int
21
22const (
23	PlainText = PrintMode(iota)
24	Markdown
25	HTML
26)
27
28// Version is a manually-updated mechanism for tracking versions.
29const Version = "master"
30
31// ServerVersion is the format used by gopls to report its version to the
32// client. This format is structured so that the client can parse it easily.
33type ServerVersion struct {
34	Module
35	Deps []*Module `json:"deps,omitempty"`
36}
37
38type Module struct {
39	ModuleVersion
40	Replace *ModuleVersion `json:"replace,omitempty"`
41}
42
43type ModuleVersion struct {
44	Path    string `json:"path,omitempty"`
45	Version string `json:"version,omitempty"`
46	Sum     string `json:"sum,omitempty"`
47}
48
49// VersionInfo returns the build info for the gopls process. If it was not
50// built in module mode, we return a GOPATH-specific message with the
51// hardcoded version.
52func VersionInfo() *ServerVersion {
53	if info, ok := debug.ReadBuildInfo(); ok {
54		return getVersion(info)
55	}
56	path := "gopls, built in GOPATH mode"
57	return &ServerVersion{
58		Module: Module{
59			ModuleVersion: ModuleVersion{
60				Path:    path,
61				Version: Version,
62			},
63		},
64	}
65}
66
67func getVersion(info *debug.BuildInfo) *ServerVersion {
68	serverVersion := ServerVersion{
69		Module: Module{
70			ModuleVersion: ModuleVersion{
71				Path:    info.Main.Path,
72				Version: info.Main.Version,
73				Sum:     info.Main.Sum,
74			},
75		},
76	}
77	for _, d := range info.Deps {
78		m := &Module{
79			ModuleVersion: ModuleVersion{
80				Path:    d.Path,
81				Version: d.Version,
82				Sum:     d.Sum,
83			},
84		}
85		if d.Replace != nil {
86			m.Replace = &ModuleVersion{
87				Path:    d.Replace.Path,
88				Version: d.Replace.Version,
89			}
90		}
91		serverVersion.Deps = append(serverVersion.Deps, m)
92	}
93	return &serverVersion
94}
95
96// PrintServerInfo writes HTML debug info to w for the Instance.
97func (i *Instance) PrintServerInfo(ctx context.Context, w io.Writer) {
98	section(w, HTML, "Server Instance", func() {
99		fmt.Fprintf(w, "Start time: %v\n", i.StartTime)
100		fmt.Fprintf(w, "LogFile: %s\n", i.Logfile)
101		fmt.Fprintf(w, "Working directory: %s\n", i.Workdir)
102		fmt.Fprintf(w, "Address: %s\n", i.ServerAddress)
103		fmt.Fprintf(w, "Debug address: %s\n", i.DebugAddress())
104	})
105	PrintVersionInfo(ctx, w, true, HTML)
106	section(w, HTML, "Command Line", func() {
107		fmt.Fprintf(w, "<a href=/debug/pprof/cmdline>cmdline</a>")
108	})
109}
110
111// PrintVersionInfo writes version information to w, using the output format
112// specified by mode. verbose controls whether additional information is
113// written, including section headers.
114func PrintVersionInfo(ctx context.Context, w io.Writer, verbose bool, mode PrintMode) {
115	info := VersionInfo()
116	if !verbose {
117		printBuildInfo(w, info, false, mode)
118		return
119	}
120	section(w, mode, "Build info", func() {
121		printBuildInfo(w, info, true, mode)
122	})
123}
124
125func section(w io.Writer, mode PrintMode, title string, body func()) {
126	switch mode {
127	case PlainText:
128		fmt.Fprintln(w, title)
129		fmt.Fprintln(w, strings.Repeat("-", len(title)))
130		body()
131	case Markdown:
132		fmt.Fprintf(w, "#### %s\n\n```\n", title)
133		body()
134		fmt.Fprintf(w, "```\n")
135	case HTML:
136		fmt.Fprintf(w, "<h3>%s</h3>\n<pre>\n", title)
137		body()
138		fmt.Fprint(w, "</pre>\n")
139	}
140}
141
142func printBuildInfo(w io.Writer, info *ServerVersion, verbose bool, mode PrintMode) {
143	fmt.Fprintf(w, "%v %v\n", info.Path, Version)
144	printModuleInfo(w, &info.Module, mode)
145	if !verbose {
146		return
147	}
148	for _, dep := range info.Deps {
149		printModuleInfo(w, dep, mode)
150	}
151}
152
153func printModuleInfo(w io.Writer, m *Module, mode PrintMode) {
154	fmt.Fprintf(w, "    %s@%s", m.Path, m.Version)
155	if m.Sum != "" {
156		fmt.Fprintf(w, " %s", m.Sum)
157	}
158	if m.Replace != nil {
159		fmt.Fprintf(w, " => %v", m.Replace.Path)
160	}
161	fmt.Fprintf(w, "\n")
162}
163
164type field struct {
165	index []int
166}
167
168var fields []field
169
170// find all the options. The presumption is that the Options are nested structs
171// and that pointers don't need to be dereferenced
172func swalk(t reflect.Type, ix []int, indent string) {
173	switch t.Kind() {
174	case reflect.Struct:
175		for i := 0; i < t.NumField(); i++ {
176			fld := t.Field(i)
177			ixx := append(append([]int{}, ix...), i)
178			swalk(fld.Type, ixx, indent+". ")
179		}
180	default:
181		// everything is either a struct or a field (that's an assumption about Options)
182		fields = append(fields, field{ix})
183	}
184}
185
186func showOptions(o *source.Options) []string {
187	// non-breaking spaces for indenting current and defaults when they are on a separate line
188	const indent = "\u00a0\u00a0\u00a0\u00a0\u00a0"
189	var ans strings.Builder
190	t := reflect.TypeOf(*o)
191	swalk(t, []int{}, "")
192	v := reflect.ValueOf(*o)
193	do := reflect.ValueOf(*source.DefaultOptions())
194	for _, f := range fields {
195		val := v.FieldByIndex(f.index)
196		def := do.FieldByIndex(f.index)
197		tx := t.FieldByIndex(f.index)
198		prefix := fmt.Sprintf("%s (type is %s): ", tx.Name, tx.Type)
199		is := strVal(val)
200		was := strVal(def)
201		if len(is) < 30 && len(was) < 30 {
202			fmt.Fprintf(&ans, "%s current:%s, default:%s\n", prefix, is, was)
203		} else {
204			fmt.Fprintf(&ans, "%s\n%scurrent:%s\n%sdefault:%s\n", prefix, indent, is, indent, was)
205		}
206	}
207	return strings.Split(ans.String(), "\n")
208}
209func strVal(val reflect.Value) string {
210	switch val.Kind() {
211	case reflect.Bool:
212		return fmt.Sprintf("%v", val.Interface())
213	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
214		return fmt.Sprintf("%v", val.Interface())
215	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
216		return fmt.Sprintf("%v", val.Interface())
217	case reflect.Uintptr, reflect.UnsafePointer:
218		return fmt.Sprintf("0x%x", val.Pointer())
219	case reflect.Complex64, reflect.Complex128:
220		return fmt.Sprintf("%v", val.Complex())
221	case reflect.Array, reflect.Slice:
222		ans := []string{}
223		for i := 0; i < val.Len(); i++ {
224			ans = append(ans, strVal(val.Index(i)))
225		}
226		sort.Strings(ans)
227		return fmt.Sprintf("%v", ans)
228	case reflect.Chan, reflect.Func, reflect.Ptr:
229		return val.Kind().String()
230	case reflect.Struct:
231		var x source.Analyzer
232		if val.Type() != reflect.TypeOf(x) {
233			return val.Kind().String()
234		}
235		// this is sort of ugly, but usable
236		str := val.FieldByName("Analyzer").Elem().FieldByName("Doc").String()
237		ix := strings.Index(str, "\n")
238		if ix == -1 {
239			ix = len(str)
240		}
241		return str[:ix]
242	case reflect.String:
243		return fmt.Sprintf("%q", val.Interface())
244	case reflect.Map:
245		ans := []string{}
246		iter := val.MapRange()
247		for iter.Next() {
248			k := iter.Key()
249			v := iter.Value()
250			ans = append(ans, fmt.Sprintf("%s:%s, ", strVal(k), strVal(v)))
251		}
252		sort.Strings(ans)
253		return fmt.Sprintf("%v", ans)
254	}
255	return fmt.Sprintf("??%s??", val.Type())
256}
257