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