1// Copyright 2020 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
5package base
6
7import (
8	"fmt"
9	"internal/buildcfg"
10	"os"
11	"runtime/debug"
12	"sort"
13	"strings"
14
15	"cmd/internal/src"
16)
17
18// An errorMsg is a queued error message, waiting to be printed.
19type errorMsg struct {
20	pos src.XPos
21	msg string
22}
23
24// Pos is the current source position being processed,
25// printed by Errorf, ErrorfLang, Fatalf, and Warnf.
26var Pos src.XPos
27
28var (
29	errorMsgs       []errorMsg
30	numErrors       int // number of entries in errorMsgs that are errors (as opposed to warnings)
31	numSyntaxErrors int
32)
33
34// Errors returns the number of errors reported.
35func Errors() int {
36	return numErrors
37}
38
39// SyntaxErrors returns the number of syntax errors reported
40func SyntaxErrors() int {
41	return numSyntaxErrors
42}
43
44// addErrorMsg adds a new errorMsg (which may be a warning) to errorMsgs.
45func addErrorMsg(pos src.XPos, format string, args ...interface{}) {
46	msg := fmt.Sprintf(format, args...)
47	// Only add the position if know the position.
48	// See issue golang.org/issue/11361.
49	if pos.IsKnown() {
50		msg = fmt.Sprintf("%v: %s", FmtPos(pos), msg)
51	}
52	errorMsgs = append(errorMsgs, errorMsg{
53		pos: pos,
54		msg: msg + "\n",
55	})
56}
57
58// FmtPos formats pos as a file:line string.
59func FmtPos(pos src.XPos) string {
60	if Ctxt == nil {
61		return "???"
62	}
63	return Ctxt.OutermostPos(pos).Format(Flag.C == 0, Flag.L == 1)
64}
65
66// byPos sorts errors by source position.
67type byPos []errorMsg
68
69func (x byPos) Len() int           { return len(x) }
70func (x byPos) Less(i, j int) bool { return x[i].pos.Before(x[j].pos) }
71func (x byPos) Swap(i, j int)      { x[i], x[j] = x[j], x[i] }
72
73// FlushErrors sorts errors seen so far by line number, prints them to stdout,
74// and empties the errors array.
75func FlushErrors() {
76	if Ctxt != nil && Ctxt.Bso != nil {
77		Ctxt.Bso.Flush()
78	}
79	if len(errorMsgs) == 0 {
80		return
81	}
82	sort.Stable(byPos(errorMsgs))
83	for i, err := range errorMsgs {
84		if i == 0 || err.msg != errorMsgs[i-1].msg {
85			fmt.Printf("%s", err.msg)
86		}
87	}
88	errorMsgs = errorMsgs[:0]
89}
90
91// lasterror keeps track of the most recently issued error,
92// to avoid printing multiple error messages on the same line.
93var lasterror struct {
94	syntax src.XPos // source position of last syntax error
95	other  src.XPos // source position of last non-syntax error
96	msg    string   // error message of last non-syntax error
97}
98
99// sameline reports whether two positions a, b are on the same line.
100func sameline(a, b src.XPos) bool {
101	p := Ctxt.PosTable.Pos(a)
102	q := Ctxt.PosTable.Pos(b)
103	return p.Base() == q.Base() && p.Line() == q.Line()
104}
105
106// Errorf reports a formatted error at the current line.
107func Errorf(format string, args ...interface{}) {
108	ErrorfAt(Pos, format, args...)
109}
110
111// ErrorfAt reports a formatted error message at pos.
112func ErrorfAt(pos src.XPos, format string, args ...interface{}) {
113	msg := fmt.Sprintf(format, args...)
114
115	if strings.HasPrefix(msg, "syntax error") {
116		numSyntaxErrors++
117		// only one syntax error per line, no matter what error
118		if sameline(lasterror.syntax, pos) {
119			return
120		}
121		lasterror.syntax = pos
122	} else {
123		// only one of multiple equal non-syntax errors per line
124		// (FlushErrors shows only one of them, so we filter them
125		// here as best as we can (they may not appear in order)
126		// so that we don't count them here and exit early, and
127		// then have nothing to show for.)
128		if sameline(lasterror.other, pos) && lasterror.msg == msg {
129			return
130		}
131		lasterror.other = pos
132		lasterror.msg = msg
133	}
134
135	addErrorMsg(pos, "%s", msg)
136	numErrors++
137
138	hcrash()
139	if numErrors >= 10 && Flag.LowerE == 0 {
140		FlushErrors()
141		fmt.Printf("%v: too many errors\n", FmtPos(pos))
142		ErrorExit()
143	}
144}
145
146// ErrorfVers reports that a language feature (format, args) requires a later version of Go.
147func ErrorfVers(lang string, format string, args ...interface{}) {
148	Errorf("%s requires %s or later (-lang was set to %s; check go.mod)", fmt.Sprintf(format, args...), lang, Flag.Lang)
149}
150
151// UpdateErrorDot is a clumsy hack that rewrites the last error,
152// if it was "LINE: undefined: NAME", to be "LINE: undefined: NAME in EXPR".
153// It is used to give better error messages for dot (selector) expressions.
154func UpdateErrorDot(line string, name, expr string) {
155	if len(errorMsgs) == 0 {
156		return
157	}
158	e := &errorMsgs[len(errorMsgs)-1]
159	if strings.HasPrefix(e.msg, line) && e.msg == fmt.Sprintf("%v: undefined: %v\n", line, name) {
160		e.msg = fmt.Sprintf("%v: undefined: %v in %v\n", line, name, expr)
161	}
162}
163
164// Warnf reports a formatted warning at the current line.
165// In general the Go compiler does NOT generate warnings,
166// so this should be used only when the user has opted in
167// to additional output by setting a particular flag.
168func Warn(format string, args ...interface{}) {
169	WarnfAt(Pos, format, args...)
170}
171
172// WarnfAt reports a formatted warning at pos.
173// In general the Go compiler does NOT generate warnings,
174// so this should be used only when the user has opted in
175// to additional output by setting a particular flag.
176func WarnfAt(pos src.XPos, format string, args ...interface{}) {
177	addErrorMsg(pos, format, args...)
178	if Flag.LowerM != 0 {
179		FlushErrors()
180	}
181}
182
183// Fatalf reports a fatal error - an internal problem - at the current line and exits.
184// If other errors have already been printed, then Fatalf just quietly exits.
185// (The internal problem may have been caused by incomplete information
186// after the already-reported errors, so best to let users fix those and
187// try again without being bothered about a spurious internal error.)
188//
189// But if no errors have been printed, or if -d panic has been specified,
190// Fatalf prints the error as an "internal compiler error". In a released build,
191// it prints an error asking to file a bug report. In development builds, it
192// prints a stack trace.
193//
194// If -h has been specified, Fatalf panics to force the usual runtime info dump.
195func Fatalf(format string, args ...interface{}) {
196	FatalfAt(Pos, format, args...)
197}
198
199// FatalfAt reports a fatal error - an internal problem - at pos and exits.
200// If other errors have already been printed, then FatalfAt just quietly exits.
201// (The internal problem may have been caused by incomplete information
202// after the already-reported errors, so best to let users fix those and
203// try again without being bothered about a spurious internal error.)
204//
205// But if no errors have been printed, or if -d panic has been specified,
206// FatalfAt prints the error as an "internal compiler error". In a released build,
207// it prints an error asking to file a bug report. In development builds, it
208// prints a stack trace.
209//
210// If -h has been specified, FatalfAt panics to force the usual runtime info dump.
211func FatalfAt(pos src.XPos, format string, args ...interface{}) {
212	FlushErrors()
213
214	if Debug.Panic != 0 || numErrors == 0 {
215		fmt.Printf("%v: internal compiler error: ", FmtPos(pos))
216		fmt.Printf(format, args...)
217		fmt.Printf("\n")
218
219		// If this is a released compiler version, ask for a bug report.
220		if strings.HasPrefix(buildcfg.Version, "go") {
221			fmt.Printf("\n")
222			fmt.Printf("Please file a bug report including a short program that triggers the error.\n")
223			fmt.Printf("https://golang.org/issue/new\n")
224		} else {
225			// Not a release; dump a stack trace, too.
226			fmt.Println()
227			os.Stdout.Write(debug.Stack())
228			fmt.Println()
229		}
230	}
231
232	hcrash()
233	ErrorExit()
234}
235
236// Assert reports "assertion failed" with Fatalf, unless b is true.
237func Assert(b bool) {
238	if !b {
239		Fatalf("assertion failed")
240	}
241}
242
243// Assertf reports a fatal error with Fatalf, unless b is true.
244func Assertf(b bool, format string, args ...interface{}) {
245	if !b {
246		Fatalf(format, args...)
247	}
248}
249
250// AssertfAt reports a fatal error with FatalfAt, unless b is true.
251func AssertfAt(b bool, pos src.XPos, format string, args ...interface{}) {
252	if !b {
253		FatalfAt(pos, format, args...)
254	}
255}
256
257// hcrash crashes the compiler when -h is set, to find out where a message is generated.
258func hcrash() {
259	if Flag.LowerH != 0 {
260		FlushErrors()
261		if Flag.LowerO != "" {
262			os.Remove(Flag.LowerO)
263		}
264		panic("-h")
265	}
266}
267
268// ErrorExit handles an error-status exit.
269// It flushes any pending errors, removes the output file, and exits.
270func ErrorExit() {
271	FlushErrors()
272	if Flag.LowerO != "" {
273		os.Remove(Flag.LowerO)
274	}
275	os.Exit(2)
276}
277
278// ExitIfErrors calls ErrorExit if any errors have been reported.
279func ExitIfErrors() {
280	if Errors() > 0 {
281		ErrorExit()
282	}
283}
284
285var AutogeneratedPos src.XPos
286