1// Copyright 2018 Marc-Antoine Ruel. All rights reserved.
2// Use of this source code is governed under the Apache License, Version 2.0
3// that can be found in the LICENSE file.
4
5//go:generate go get golang.org/x/tools/cmd/stringer
6//go:generate stringer -type state
7
8package stack
9
10import (
11	"bufio"
12	"bytes"
13	"errors"
14	"fmt"
15	"io"
16	"io/ioutil"
17	"os"
18	"os/user"
19	"path/filepath"
20	"regexp"
21	"runtime"
22	"sort"
23	"strconv"
24	"strings"
25)
26
27// Context is a parsing context.
28//
29// It contains the deduced GOROOT and GOPATH, if guesspaths is true.
30type Context struct {
31	// Goroutines is the Goroutines found.
32	//
33	// They are in the order that they were printed.
34	Goroutines []*Goroutine
35
36	// GOROOT is the GOROOT as detected in the traceback, not the on the host.
37	//
38	// It can be empty if no root was determined, for example the traceback
39	// contains only non-stdlib source references.
40	//
41	// Empty is guesspaths was false.
42	GOROOT string
43	// GOPATHs is the GOPATH as detected in the traceback, with the value being
44	// the corresponding path mapped to the host.
45	//
46	// It can be empty if only stdlib code is in the traceback or if no local
47	// sources were matched up. In the general case there is only one entry in
48	// the map.
49	//
50	// Nil is guesspaths was false.
51	GOPATHs map[string]string
52
53	// localGomoduleRoot is the root directory containing go.mod. It is
54	// considered to be the primary project containing the main executable. It is
55	// initialized by findRoots().
56	//
57	// It only works with stack traces created in the local file system.
58	localGomoduleRoot string
59	// gomodImportPath is set to the relative import path that localGomoduleRoot
60	// represents.
61	gomodImportPath string
62
63	// localgoroot is GOROOT with "/" as path separator. No trailing "/".
64	localgoroot string
65	// localgopaths is GOPATH with "/" as path separator. No trailing "/".
66	localgopaths []string
67}
68
69// ParseDump processes the output from runtime.Stack().
70//
71// Returns nil *Context if no stack trace was detected.
72//
73// It pipes anything not detected as a panic stack trace from r into out. It
74// assumes there is junk before the actual stack trace. The junk is streamed to
75// out.
76//
77// If guesspaths is false, no guessing of GOROOT and GOPATH is done, and Call
78// entites do not have LocalSrcPath and IsStdlib filled in. If true, be warned
79// that file presence is done, which means some level of disk I/O.
80func ParseDump(r io.Reader, out io.Writer, guesspaths bool) (*Context, error) {
81	goroutines, err := parseDump(r, out)
82	if len(goroutines) == 0 {
83		return nil, err
84	}
85	c := &Context{
86		Goroutines:   goroutines,
87		localgoroot:  strings.Replace(runtime.GOROOT(), "\\", "/", -1),
88		localgopaths: getGOPATHs(),
89	}
90	nameArguments(goroutines)
91	// Corresponding local values on the host for Context.
92	if guesspaths {
93		c.findRoots()
94		for _, r := range c.Goroutines {
95			// Note that this is important to call it even if
96			// c.GOROOT == c.localgoroot.
97			r.updateLocations(c.GOROOT, c.localgoroot, c.localGomoduleRoot, c.gomodImportPath, c.GOPATHs)
98		}
99	}
100	return c, err
101}
102
103// Private stuff.
104
105func parseDump(r io.Reader, out io.Writer) ([]*Goroutine, error) {
106	scanner := bufio.NewScanner(r)
107	scanner.Split(scanLines)
108	// Do not enable race detection parsing yet, since it cannot be returned in
109	// Context at the moment.
110	s := scanningState{}
111	for scanner.Scan() {
112		line, err := s.scan(scanner.Text())
113		if line != "" {
114			_, _ = io.WriteString(out, line)
115		}
116		if err != nil {
117			return s.goroutines, err
118		}
119	}
120	return s.goroutines, scanner.Err()
121}
122
123// scanLines is similar to bufio.ScanLines except that it:
124//     - doesn't drop '\n'
125//     - doesn't strip '\r'
126//     - returns when the data is bufio.MaxScanTokenSize bytes
127func scanLines(data []byte, atEOF bool) (advance int, token []byte, err error) {
128	if atEOF && len(data) == 0 {
129		return 0, nil, nil
130	}
131	if i := bytes.IndexByte(data, '\n'); i >= 0 {
132		return i + 1, data[0 : i+1], nil
133	}
134	if atEOF {
135		return len(data), data, nil
136	}
137	if len(data) >= bufio.MaxScanTokenSize {
138		// Returns the line even if it is not at EOF nor has a '\n', otherwise the
139		// scanner will return bufio.ErrTooLong which is definitely not what we
140		// want.
141		return len(data), data, nil
142	}
143	return 0, nil, nil
144}
145
146const (
147	lockedToThread = "locked to thread"
148	elided         = "...additional frames elided..."
149	// gotRaceHeader1, normal
150	raceHeaderFooter = "=================="
151	// gotRaceHeader2
152	raceHeader = "WARNING: DATA RACE"
153)
154
155// These are effectively constants.
156var (
157	// gotRoutineHeader
158	reRoutineHeader = regexp.MustCompile("^([ \t]*)goroutine (\\d+) \\[([^\\]]+)\\]\\:$")
159	reMinutes       = regexp.MustCompile(`^(\d+) minutes$`)
160
161	// gotUnavail
162	reUnavail = regexp.MustCompile("^(?:\t| +)goroutine running on other thread; stack unavailable")
163
164	// gotFileFunc, gotRaceOperationFile, gotRaceGoroutineFile
165	// See gentraceback() in src/runtime/traceback.go for more information.
166	// - Sometimes the source file comes up as "<autogenerated>". It is the
167	//   compiler than generated these, not the runtime.
168	// - The tab may be replaced with spaces when a user copy-paste it, handle
169	//   this transparently.
170	// - "runtime.gopanic" is explicitly replaced with "panic" by gentraceback().
171	// - The +0x123 byte offset is printed when frame.pc > _func.entry. _func is
172	//   generated by the linker.
173	// - The +0x123 byte offset is not included with generated code, e.g. unnamed
174	//   functions "func·006()" which is generally go func() { ... }()
175	//   statements. Since the _func is generated at runtime, it's probably why
176	//   _func.entry is not set.
177	// - C calls may have fp=0x123 sp=0x123 appended. I think it normally happens
178	//   when a signal is not correctly handled. It is printed with m.throwing>0.
179	//   These are discarded.
180	// - For cgo, the source file may be "??".
181	reFile = regexp.MustCompile("^(?:\t| +)(\\?\\?|\\<autogenerated\\>|.+\\.(?:c|go|s))\\:(\\d+)(?:| \\+0x[0-9a-f]+)(?:| fp=0x[0-9a-f]+ sp=0x[0-9a-f]+(?:| pc=0x[0-9a-f]+))$")
182
183	// gotCreated
184	// Sadly, it doesn't note the goroutine number so we could cascade them per
185	// parenthood.
186	reCreated = regexp.MustCompile("^created by (.+)$")
187
188	// gotFunc, gotRaceOperationFunc, gotRaceGoroutineFunc
189	reFunc = regexp.MustCompile(`^(.+)\((.*)\)$`)
190
191	// Race:
192	// See https://github.com/llvm/llvm-project/blob/master/compiler-rt/lib/tsan/rtl/tsan_report.cpp
193	// for the code generating these messages. Please note only the block in
194	//   #else  // #if !SANITIZER_GO
195	// is used.
196	// TODO(maruel): "    [failed to restore the stack]\n\n"
197	// TODO(maruel): "Global var %s of size %zu at %p declared at %s:%zu\n"
198
199	// gotRaceOperationHeader
200	reRaceOperationHeader = regexp.MustCompile(`^(Read|Write) at (0x[0-9a-f]+) by goroutine (\d+):$`)
201
202	// gotRaceOperationHeader
203	reRacePreviousOperationHeader = regexp.MustCompile(`^Previous (read|write) at (0x[0-9a-f]+) by goroutine (\d+):$`)
204
205	// gotRaceGoroutineHeader
206	reRaceGoroutine = regexp.MustCompile(`^Goroutine (\d+) \((running|finished)\) created at:$`)
207
208	// TODO(maruel): Use it.
209	//reRacePreviousOperationMainHeader = regexp.MustCompile("^Previous (read|write) at (0x[0-9a-f]+) by main goroutine:$")
210)
211
212// state is the state of the scan to detect and process a stack trace.
213type state int
214
215// Initial state is normal. Other states are when a stack trace is detected.
216const (
217	// Outside a stack trace.
218	// to: gotRoutineHeader, raceHeader1
219	normal state = iota
220
221	// Panic stack trace:
222
223	// Signature: ""
224	// An empty line between goroutines.
225	// from: gotFileCreated, gotFileFunc
226	// to: gotRoutineHeader, normal
227	betweenRoutine
228	// Regexp: reRoutineHeader
229	// Signature: "goroutine 1 [running]:"
230	// Goroutine header was found.
231	// from: normal
232	// to: gotUnavail, gotFunc
233	gotRoutineHeader
234	// Regexp: reFunc
235	// Signature: "main.main()"
236	// Function call line was found.
237	// from: gotRoutineHeader
238	// to: gotFileFunc
239	gotFunc
240	// Regexp: reCreated
241	// Signature: "created by main.glob..func4"
242	// Goroutine creation line was found.
243	// from: gotFileFunc
244	// to: gotFileCreated
245	gotCreated
246	// Regexp: reFile
247	// Signature: "\t/foo/bar/baz.go:116 +0x35"
248	// File header was found.
249	// from: gotFunc
250	// to: gotFunc, gotCreated, betweenRoutine, normal
251	gotFileFunc
252	// Regexp: reFile
253	// Signature: "\t/foo/bar/baz.go:116 +0x35"
254	// File header was found.
255	// from: gotCreated
256	// to: betweenRoutine, normal
257	gotFileCreated
258	// Regexp: reUnavail
259	// Signature: "goroutine running on other thread; stack unavailable"
260	// State when the goroutine stack is instead is reUnavail.
261	// from: gotRoutineHeader
262	// to: betweenRoutine, gotCreated
263	gotUnavail
264
265	// Race detector:
266
267	// Constant: raceHeaderFooter
268	// Signature: "=================="
269	// from: normal
270	// to: normal, gotRaceHeader2
271	gotRaceHeader1
272	// Constant: raceHeader
273	// Signature: "WARNING: DATA RACE"
274	// from: gotRaceHeader1
275	// to: normal, gotRaceOperationHeader
276	gotRaceHeader2
277	// Regexp: reRaceOperationHeader, reRacePreviousOperationHeader
278	// Signature: "Read at 0x00c0000e4030 by goroutine 7:"
279	// A race operation was found.
280	// from: gotRaceHeader2
281	// to: normal, gotRaceOperationFunc
282	gotRaceOperationHeader
283	// Regexp: reFunc
284	// Signature: "  main.panicRace.func1()"
285	// Function that caused the race.
286	// from: gotRaceOperationHeader
287	// to: normal, gotRaceOperationFile
288	gotRaceOperationFunc
289	// Regexp: reFile
290	// Signature: "\t/foo/bar/baz.go:116 +0x35"
291	// File header that caused the race.
292	// from: gotRaceOperationFunc
293	// to: normal, betweenRaceOperations, gotRaceOperationFunc
294	gotRaceOperationFile
295	// Signature: ""
296	// Empty line between race operations or just after.
297	// from: gotRaceOperationFile
298	// to: normal, gotRaceOperationHeader, gotRaceGoroutineHeader
299	betweenRaceOperations
300
301	// Regexp: reRaceGoroutine
302	// Signature: "Goroutine 7 (running) created at:"
303	// Goroutine header.
304	// from: betweenRaceOperations, betweenRaceGoroutines
305	// to: normal, gotRaceOperationHeader
306	gotRaceGoroutineHeader
307	// Regexp: reFunc
308	// Signature: "  main.panicRace.func1()"
309	// Function that caused the race.
310	// from: gotRaceGoroutineHeader
311	// to: normal, gotRaceGoroutineFile
312	gotRaceGoroutineFunc
313	// Regexp: reFile
314	// Signature: "\t/foo/bar/baz.go:116 +0x35"
315	// File header that caused the race.
316	// from: gotRaceGoroutineFunc
317	// to: normal, betweenRaceGoroutines
318	gotRaceGoroutineFile
319	// Signature: ""
320	// Empty line between race stack traces.
321	// from: gotRaceGoroutineFile
322	// to: normal, gotRaceGoroutineHeader
323	betweenRaceGoroutines
324)
325
326// raceOp is one of the detected data race operation as detected by the race
327// detector.
328type raceOp struct {
329	write  bool
330	addr   uint64
331	id     int
332	create Stack
333}
334
335// scanningState is the state of the scan to detect and process a stack trace
336// and stores the traces found.
337type scanningState struct {
338	// Determines if race detection is enabled. Currently false since scan()
339	// would swallow the race detector output, but the data is not part of
340	// Context yet.
341	raceDetectionEnabled bool
342
343	// goroutines contains all the goroutines found.
344	goroutines []*Goroutine
345
346	state       state
347	prefix      string
348	races       map[int]*raceOp
349	goroutineID int
350}
351
352// scan scans one line, updates goroutines and move to the next state.
353//
354// TODO(maruel): Handle corrupted stack cases:
355// - missed stack barrier
356// - found next stack barrier at 0x123; expected
357// - runtime: unexpected return pc for FUNC_NAME called from 0x123
358func (s *scanningState) scan(line string) (string, error) {
359	/* This is very useful to debug issues in the state machine.
360	defer func() {
361		log.Printf("scan(%q) -> %s", line, s.state)
362	}()
363	//*/
364	var cur *Goroutine
365	if len(s.goroutines) != 0 {
366		cur = s.goroutines[len(s.goroutines)-1]
367	}
368	trimmed := line
369	if strings.HasSuffix(line, "\r\n") {
370		trimmed = line[:len(line)-2]
371	} else if strings.HasSuffix(line, "\n") {
372		trimmed = line[:len(line)-1]
373	} else {
374		// There's two cases:
375		// - It's the end of the stream and it's not terminating with EOL character.
376		// - The line is longer than bufio.MaxScanTokenSize
377		if s.state == normal {
378			return line, nil
379		}
380		// Let it flow. It's possible the last line was trimmed and we still want to parse it.
381	}
382
383	if trimmed != "" && s.prefix != "" {
384		// This can only be the case if s.state != normal or the line is empty.
385		if !strings.HasPrefix(trimmed, s.prefix) {
386			prefix := s.prefix
387			s.state = normal
388			s.prefix = ""
389			return "", fmt.Errorf("inconsistent indentation: %q, expected %q", trimmed, prefix)
390		}
391		trimmed = trimmed[len(s.prefix):]
392	}
393
394	switch s.state {
395	case normal:
396		// We could look for '^panic:' but this is more risky, there can be a lot
397		// of junk between this and the stack dump.
398		fallthrough
399
400	case betweenRoutine:
401		// Look for a goroutine header.
402		if match := reRoutineHeader.FindStringSubmatch(trimmed); match != nil {
403			if id, err := strconv.Atoi(match[2]); err == nil {
404				// See runtime/traceback.go.
405				// "<state>, \d+ minutes, locked to thread"
406				items := strings.Split(match[3], ", ")
407				sleep := 0
408				locked := false
409				for i := 1; i < len(items); i++ {
410					if items[i] == lockedToThread {
411						locked = true
412						continue
413					}
414					// Look for duration, if any.
415					if match2 := reMinutes.FindStringSubmatch(items[i]); match2 != nil {
416						sleep, _ = strconv.Atoi(match2[1])
417					}
418				}
419				g := &Goroutine{
420					Signature: Signature{
421						State:    items[0],
422						SleepMin: sleep,
423						SleepMax: sleep,
424						Locked:   locked,
425					},
426					ID:    id,
427					First: len(s.goroutines) == 0,
428				}
429				// Increase performance by always allocating 4 goroutines minimally.
430				if s.goroutines == nil {
431					s.goroutines = make([]*Goroutine, 0, 4)
432				}
433				s.goroutines = append(s.goroutines, g)
434				s.state = gotRoutineHeader
435				s.prefix = match[1]
436				return "", nil
437			}
438		}
439		// Switch to race detection mode.
440		if s.raceDetectionEnabled && trimmed == raceHeaderFooter {
441			// TODO(maruel): We should buffer it in case the next line is not a
442			// WARNING so we can output it back.
443			s.state = gotRaceHeader1
444			return "", nil
445		}
446		// Fallthrough.
447		s.state = normal
448		s.prefix = ""
449		return line, nil
450
451	case gotRoutineHeader:
452		if reUnavail.MatchString(trimmed) {
453			// Generate a fake stack entry.
454			cur.Stack.Calls = []Call{{SrcPath: "<unavailable>"}}
455			// Next line is expected to be an empty line.
456			s.state = gotUnavail
457			return "", nil
458		}
459		c := Call{}
460		if found, err := parseFunc(&c, trimmed); found {
461			// Increase performance by always allocating 4 calls minimally.
462			if cur.Stack.Calls == nil {
463				cur.Stack.Calls = make([]Call, 0, 4)
464			}
465			cur.Stack.Calls = append(cur.Stack.Calls, c)
466			s.state = gotFunc
467			return "", err
468		}
469		return "", fmt.Errorf("expected a function after a goroutine header, got: %q", strings.TrimSpace(trimmed))
470
471	case gotFunc:
472		// cur.Stack.Calls is guaranteed to have at least one item.
473		if found, err := parseFile(&cur.Stack.Calls[len(cur.Stack.Calls)-1], trimmed); err != nil {
474			return "", err
475		} else if !found {
476			return "", fmt.Errorf("expected a file after a function, got: %q", strings.TrimSpace(trimmed))
477		}
478		s.state = gotFileFunc
479		return "", nil
480
481	case gotCreated:
482		if found, err := parseFile(&cur.CreatedBy, trimmed); err != nil {
483			return "", err
484		} else if !found {
485			return "", fmt.Errorf("expected a file after a created line, got: %q", trimmed)
486		}
487		s.state = gotFileCreated
488		return "", nil
489
490	case gotFileFunc:
491		if match := reCreated.FindStringSubmatch(trimmed); match != nil {
492			cur.CreatedBy.Func.Raw = match[1]
493			s.state = gotCreated
494			return "", nil
495		}
496		if elided == trimmed {
497			cur.Stack.Elided = true
498			// TODO(maruel): New state.
499			return "", nil
500		}
501		c := Call{}
502		if found, err := parseFunc(&c, trimmed); found {
503			// Increase performance by always allocating 4 calls minimally.
504			if cur.Stack.Calls == nil {
505				cur.Stack.Calls = make([]Call, 0, 4)
506			}
507			cur.Stack.Calls = append(cur.Stack.Calls, c)
508			s.state = gotFunc
509			return "", err
510		}
511		if trimmed == "" {
512			s.state = betweenRoutine
513			return "", nil
514		}
515		// Back to normal state.
516		s.state = normal
517		s.prefix = ""
518		return line, nil
519
520	case gotFileCreated:
521		if trimmed == "" {
522			s.state = betweenRoutine
523			return "", nil
524		}
525		s.state = normal
526		s.prefix = ""
527		return line, nil
528
529	case gotUnavail:
530		if trimmed == "" {
531			s.state = betweenRoutine
532			return "", nil
533		}
534		if match := reCreated.FindStringSubmatch(trimmed); match != nil {
535			cur.CreatedBy.Func.Raw = match[1]
536			s.state = gotCreated
537			return "", nil
538		}
539		return "", fmt.Errorf("expected empty line after unavailable stack, got: %q", strings.TrimSpace(trimmed))
540
541		// Race detector.
542
543	case gotRaceHeader1:
544		if raceHeader == trimmed {
545			// TODO(maruel): We should buffer it in case the next line is not a
546			// WARNING so we can output it back.
547			s.state = gotRaceHeader2
548			return "", nil
549		}
550		s.state = normal
551		return line, nil
552
553	case gotRaceHeader2:
554		if match := reRaceOperationHeader.FindStringSubmatch(trimmed); match != nil {
555			w := match[1] == "Write"
556			addr, err := strconv.ParseUint(match[2], 0, 64)
557			if err != nil {
558				return "", fmt.Errorf("failed to parse address on line: %q", strings.TrimSpace(trimmed))
559			}
560			id, err := strconv.Atoi(match[3])
561			if err != nil {
562				return "", fmt.Errorf("failed to parse goroutine id on line: %q", strings.TrimSpace(trimmed))
563			}
564			if s.races != nil {
565				panic("internal failure; expected s.races to be nil")
566			}
567			if s.goroutines != nil {
568				panic("internal failure; expected s.goroutines to be nil")
569			}
570			s.races = make(map[int]*raceOp, 4)
571			s.races[id] = &raceOp{write: w, addr: addr, id: id}
572			s.goroutines = append(make([]*Goroutine, 0, 4), &Goroutine{ID: id, First: true})
573			s.goroutineID = id
574			s.state = gotRaceOperationHeader
575			return "", nil
576		}
577		s.state = normal
578		return line, nil
579
580	case gotRaceOperationHeader:
581		c := Call{}
582		if found, err := parseFunc(&c, strings.TrimLeft(trimmed, "\t ")); found {
583			// Increase performance by always allocating 4 calls minimally.
584			if cur.Stack.Calls == nil {
585				cur.Stack.Calls = make([]Call, 0, 4)
586			}
587			cur.Stack.Calls = append(cur.Stack.Calls, c)
588			s.state = gotRaceOperationFunc
589			return "", err
590		}
591		return "", fmt.Errorf("expected a function after a race operation, got: %q", trimmed)
592
593	case gotRaceOperationFunc:
594		if found, err := parseFile(&cur.Stack.Calls[len(cur.Stack.Calls)-1], trimmed); err != nil {
595			return "", err
596		} else if !found {
597			return "", fmt.Errorf("expected a file after a race function, got: %q", trimmed)
598		}
599		s.state = gotRaceOperationFile
600		return "", nil
601
602	case gotRaceOperationFile:
603		if trimmed == "" {
604			s.state = betweenRaceOperations
605			return "", nil
606		}
607		c := Call{}
608		if found, err := parseFunc(&c, strings.TrimLeft(trimmed, "\t ")); found {
609			cur.Stack.Calls = append(cur.Stack.Calls, c)
610			s.state = gotRaceOperationFunc
611			return "", err
612		}
613		return "", fmt.Errorf("expected an empty line after a race file, got: %q", trimmed)
614
615	case betweenRaceOperations:
616		// Look for other previous race data operations.
617		if match := reRacePreviousOperationHeader.FindStringSubmatch(trimmed); match != nil {
618			w := match[1] == "write"
619			addr, err := strconv.ParseUint(match[2], 0, 64)
620			if err != nil {
621				return "", fmt.Errorf("failed to parse address on line: %q", strings.TrimSpace(trimmed))
622			}
623			id, err := strconv.Atoi(match[3])
624			if err != nil {
625				return "", fmt.Errorf("failed to parse goroutine id on line: %q", strings.TrimSpace(trimmed))
626			}
627			s.goroutineID = id
628			s.races[s.goroutineID] = &raceOp{write: w, addr: addr, id: id}
629			s.goroutines = append(s.goroutines, &Goroutine{ID: id})
630			s.state = gotRaceOperationHeader
631			return "", nil
632		}
633		fallthrough
634
635	case betweenRaceGoroutines:
636		if match := reRaceGoroutine.FindStringSubmatch(trimmed); match != nil {
637			id, err := strconv.Atoi(match[1])
638			if err != nil {
639				return "", fmt.Errorf("failed to parse goroutine id on line: %q", strings.TrimSpace(trimmed))
640			}
641			found := false
642			for _, g := range s.goroutines {
643				if g.ID == id {
644					g.State = match[2]
645					found = true
646					break
647				}
648			}
649			if !found {
650				return "", fmt.Errorf("unexpected goroutine ID on line: %q", strings.TrimSpace(trimmed))
651			}
652			s.goroutineID = id
653			s.state = gotRaceGoroutineHeader
654			return "", nil
655		}
656		return "", fmt.Errorf("expected an operator or goroutine, got: %q", trimmed)
657
658		// Race stack traces
659
660	case gotRaceGoroutineFunc:
661		c := s.races[s.goroutineID].create.Calls
662		if found, err := parseFile(&c[len(c)-1], trimmed); err != nil {
663			return "", err
664		} else if !found {
665			return "", fmt.Errorf("expected a file after a race function, got: %q", trimmed)
666		}
667		// TODO(maruel): Set s.goroutines[].CreatedBy.
668		s.state = gotRaceGoroutineFile
669		return "", nil
670
671	case gotRaceGoroutineFile:
672		if trimmed == "" {
673			s.state = betweenRaceGoroutines
674			return "", nil
675		}
676		if trimmed == raceHeaderFooter {
677			// Done.
678			s.state = normal
679			return "", nil
680		}
681		fallthrough
682
683	case gotRaceGoroutineHeader:
684		c := Call{}
685		if found, err := parseFunc(&c, strings.TrimLeft(trimmed, "\t ")); found {
686			// TODO(maruel): Set s.goroutines[].CreatedBy.
687			s.races[s.goroutineID].create.Calls = append(s.races[s.goroutineID].create.Calls, c)
688			s.state = gotRaceGoroutineFunc
689			return "", err
690		}
691		return "", fmt.Errorf("expected a function after a race operation or a race file, got: %q", trimmed)
692
693	default:
694		return "", errors.New("internal error")
695	}
696}
697
698// parseFunc only return an error if also returning a Call.
699//
700// Uses reFunc.
701func parseFunc(c *Call, line string) (bool, error) {
702	if match := reFunc.FindStringSubmatch(line); match != nil {
703		c.Func.Raw = match[1]
704		for _, a := range strings.Split(match[2], ", ") {
705			if a == "..." {
706				c.Args.Elided = true
707				continue
708			}
709			if a == "" {
710				// Remaining values were dropped.
711				break
712			}
713			v, err := strconv.ParseUint(a, 0, 64)
714			if err != nil {
715				return true, fmt.Errorf("failed to parse int on line: %q", strings.TrimSpace(line))
716			}
717			// Increase performance by always allocating 4 values minimally.
718			if c.Args.Values == nil {
719				c.Args.Values = make([]Arg, 0, 4)
720			}
721			c.Args.Values = append(c.Args.Values, Arg{Value: v})
722		}
723		return true, nil
724	}
725	return false, nil
726}
727
728// parseFile only return an error if also processing a Call.
729//
730// Uses reFile.
731func parseFile(c *Call, line string) (bool, error) {
732	if match := reFile.FindStringSubmatch(line); match != nil {
733		num, err := strconv.Atoi(match[2])
734		if err != nil {
735			return true, fmt.Errorf("failed to parse int on line: %q", strings.TrimSpace(line))
736		}
737		c.SrcPath = match[1]
738		c.Line = num
739		return true, nil
740	}
741	return false, nil
742}
743
744// hasSrcPrefix returns true if any of s is the prefix of p.
745func hasSrcPrefix(p string, s map[string]string) bool {
746	for prefix := range s {
747		if strings.HasPrefix(p, prefix+"/src/") || strings.HasPrefix(p, prefix+"/pkg/mod/") {
748			return true
749		}
750	}
751	return false
752}
753
754// getFiles returns all the source files deduped and ordered.
755func getFiles(goroutines []*Goroutine) []string {
756	files := map[string]struct{}{}
757	for _, g := range goroutines {
758		for _, c := range g.Stack.Calls {
759			files[c.SrcPath] = struct{}{}
760		}
761	}
762	if len(files) == 0 {
763		return nil
764	}
765	out := make([]string, 0, len(files))
766	for f := range files {
767		out = append(out, f)
768	}
769	sort.Strings(out)
770	return out
771}
772
773// splitPath splits a path using "/" as separator into its components.
774//
775// The first item has its initial path separator kept.
776func splitPath(p string) []string {
777	if p == "" {
778		return nil
779	}
780	var out []string
781	s := ""
782	for _, c := range p {
783		if c != '/' || (len(out) == 0 && strings.Count(s, "/") == len(s)) {
784			s += string(c)
785		} else if s != "" {
786			out = append(out, s)
787			s = ""
788		}
789	}
790	if s != "" {
791		out = append(out, s)
792	}
793	return out
794}
795
796// isFile returns true if the path is a valid file.
797func isFile(p string) bool {
798	// TODO(maruel): Is it faster to open the file or to stat it? Worth a perf
799	// test on Windows.
800	i, err := os.Stat(p)
801	return err == nil && !i.IsDir()
802}
803
804// rootedIn returns a root if the file split in parts is rooted in root.
805//
806// Uses "/" as path separator.
807func rootedIn(root string, parts []string) string {
808	//log.Printf("rootIn(%s, %v)", root, parts)
809	for i := 1; i < len(parts); i++ {
810		suffix := pathJoin(parts[i:]...)
811		if isFile(pathJoin(root, suffix)) {
812			return pathJoin(parts[:i]...)
813		}
814	}
815	return ""
816}
817
818// reModule find the module line in a go.mod file. It works even on CRLF file.
819var reModule = regexp.MustCompile(`(?m)^module\s+([^\n\r]+)\r?$`)
820
821// isGoModule returns the string to the directory containing a go.mod/go.sum
822// files pair, and the go import path it represents, if found.
823func isGoModule(parts []string) (string, string) {
824	for i := len(parts); i > 0; i-- {
825		prefix := pathJoin(parts[:i]...)
826		if isFile(pathJoin(prefix, "go.sum")) {
827			b, err := ioutil.ReadFile(pathJoin(prefix, "go.mod"))
828			if err != nil {
829				continue
830			}
831			if match := reModule.FindSubmatch(b); match != nil {
832				return prefix, string(match[1])
833			}
834		}
835	}
836	return "", ""
837}
838
839// findRoots sets member GOROOT, GOPATHs and localGomoduleRoot.
840//
841// This causes disk I/O as it checks for file presence.
842//
843// Returns the number of missing files.
844func (c *Context) findRoots() int {
845	c.GOPATHs = map[string]string{}
846	missing := 0
847	for _, f := range getFiles(c.Goroutines) {
848		// TODO(maruel): Could a stack dump have mixed cases? I think it's
849		// possible, need to confirm and handle.
850		//log.Printf("  Analyzing %s", f)
851
852		// First checks skip file I/O.
853		if c.GOROOT != "" && strings.HasPrefix(f, c.GOROOT+"/src/") {
854			// stdlib.
855			continue
856		}
857		if hasSrcPrefix(f, c.GOPATHs) {
858			// $GOPATH/src or go.mod dependency in $GOPATH/pkg/mod.
859			continue
860		}
861
862		// At this point, disk will be looked up.
863		parts := splitPath(f)
864		if c.GOROOT == "" {
865			if r := rootedIn(c.localgoroot+"/src", parts); r != "" {
866				c.GOROOT = r[:len(r)-4]
867				//log.Printf("Found GOROOT=%s", c.GOROOT)
868				continue
869			}
870		}
871		found := false
872		for _, l := range c.localgopaths {
873			if r := rootedIn(l+"/src", parts); r != "" {
874				//log.Printf("Found GOPATH=%s", r[:len(r)-4])
875				c.GOPATHs[r[:len(r)-4]] = l
876				found = true
877				break
878			}
879			if r := rootedIn(l+"/pkg/mod", parts); r != "" {
880				//log.Printf("Found GOPATH=%s", r[:len(r)-8])
881				c.GOPATHs[r[:len(r)-8]] = l
882				found = true
883				break
884			}
885		}
886		// If the source is not found, it's probably a go module.
887		if !found {
888			if c.localGomoduleRoot == "" && len(parts) > 1 {
889				// Search upward looking for a go.mod/go.sum pair.
890				c.localGomoduleRoot, c.gomodImportPath = isGoModule(parts[:len(parts)-1])
891			}
892			if c.localGomoduleRoot != "" && strings.HasPrefix(f, c.localGomoduleRoot+"/") {
893				continue
894			}
895		}
896		if !found {
897			// If the source is not found, just too bad.
898			//log.Printf("Failed to find locally: %s", f)
899			missing++
900		}
901	}
902	return missing
903}
904
905// getGOPATHs returns parsed GOPATH or its default, using "/" as path separator.
906func getGOPATHs() []string {
907	var out []string
908	if gp := os.Getenv("GOPATH"); gp != "" {
909		for _, v := range filepath.SplitList(gp) {
910			// Disallow non-absolute paths?
911			if v != "" {
912				v = strings.Replace(v, "\\", "/", -1)
913				// Trim trailing "/".
914				if l := len(v); v[l-1] == '/' {
915					v = v[:l-1]
916				}
917				out = append(out, v)
918			}
919		}
920	}
921	if len(out) == 0 {
922		homeDir := ""
923		u, err := user.Current()
924		if err != nil {
925			homeDir = os.Getenv("HOME")
926			if homeDir == "" {
927				panic(fmt.Sprintf("Could not get current user or $HOME: %s\n", err.Error()))
928			}
929		} else {
930			homeDir = u.HomeDir
931		}
932		out = []string{strings.Replace(homeDir+"/go", "\\", "/", -1)}
933	}
934	return out
935}
936