// Copyright 2020 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package base import ( "fmt" "internal/buildcfg" "os" "runtime/debug" "sort" "strings" "cmd/internal/src" ) // An errorMsg is a queued error message, waiting to be printed. type errorMsg struct { pos src.XPos msg string } // Pos is the current source position being processed, // printed by Errorf, ErrorfLang, Fatalf, and Warnf. var Pos src.XPos var ( errorMsgs []errorMsg numErrors int // number of entries in errorMsgs that are errors (as opposed to warnings) numSyntaxErrors int ) // Errors returns the number of errors reported. func Errors() int { return numErrors } // SyntaxErrors returns the number of syntax errors reported func SyntaxErrors() int { return numSyntaxErrors } // addErrorMsg adds a new errorMsg (which may be a warning) to errorMsgs. func addErrorMsg(pos src.XPos, format string, args ...interface{}) { msg := fmt.Sprintf(format, args...) // Only add the position if know the position. // See issue golang.org/issue/11361. if pos.IsKnown() { msg = fmt.Sprintf("%v: %s", FmtPos(pos), msg) } errorMsgs = append(errorMsgs, errorMsg{ pos: pos, msg: msg + "\n", }) } // FmtPos formats pos as a file:line string. func FmtPos(pos src.XPos) string { if Ctxt == nil { return "???" } return Ctxt.OutermostPos(pos).Format(Flag.C == 0, Flag.L == 1) } // byPos sorts errors by source position. type byPos []errorMsg func (x byPos) Len() int { return len(x) } func (x byPos) Less(i, j int) bool { return x[i].pos.Before(x[j].pos) } func (x byPos) Swap(i, j int) { x[i], x[j] = x[j], x[i] } // FlushErrors sorts errors seen so far by line number, prints them to stdout, // and empties the errors array. func FlushErrors() { if Ctxt != nil && Ctxt.Bso != nil { Ctxt.Bso.Flush() } if len(errorMsgs) == 0 { return } sort.Stable(byPos(errorMsgs)) for i, err := range errorMsgs { if i == 0 || err.msg != errorMsgs[i-1].msg { fmt.Printf("%s", err.msg) } } errorMsgs = errorMsgs[:0] } // lasterror keeps track of the most recently issued error, // to avoid printing multiple error messages on the same line. var lasterror struct { syntax src.XPos // source position of last syntax error other src.XPos // source position of last non-syntax error msg string // error message of last non-syntax error } // sameline reports whether two positions a, b are on the same line. func sameline(a, b src.XPos) bool { p := Ctxt.PosTable.Pos(a) q := Ctxt.PosTable.Pos(b) return p.Base() == q.Base() && p.Line() == q.Line() } // Errorf reports a formatted error at the current line. func Errorf(format string, args ...interface{}) { ErrorfAt(Pos, format, args...) } // ErrorfAt reports a formatted error message at pos. func ErrorfAt(pos src.XPos, format string, args ...interface{}) { msg := fmt.Sprintf(format, args...) if strings.HasPrefix(msg, "syntax error") { numSyntaxErrors++ // only one syntax error per line, no matter what error if sameline(lasterror.syntax, pos) { return } lasterror.syntax = pos } else { // only one of multiple equal non-syntax errors per line // (FlushErrors shows only one of them, so we filter them // here as best as we can (they may not appear in order) // so that we don't count them here and exit early, and // then have nothing to show for.) if sameline(lasterror.other, pos) && lasterror.msg == msg { return } lasterror.other = pos lasterror.msg = msg } addErrorMsg(pos, "%s", msg) numErrors++ hcrash() if numErrors >= 10 && Flag.LowerE == 0 { FlushErrors() fmt.Printf("%v: too many errors\n", FmtPos(pos)) ErrorExit() } } // ErrorfVers reports that a language feature (format, args) requires a later version of Go. func ErrorfVers(lang string, format string, args ...interface{}) { Errorf("%s requires %s or later (-lang was set to %s; check go.mod)", fmt.Sprintf(format, args...), lang, Flag.Lang) } // UpdateErrorDot is a clumsy hack that rewrites the last error, // if it was "LINE: undefined: NAME", to be "LINE: undefined: NAME in EXPR". // It is used to give better error messages for dot (selector) expressions. func UpdateErrorDot(line string, name, expr string) { if len(errorMsgs) == 0 { return } e := &errorMsgs[len(errorMsgs)-1] if strings.HasPrefix(e.msg, line) && e.msg == fmt.Sprintf("%v: undefined: %v\n", line, name) { e.msg = fmt.Sprintf("%v: undefined: %v in %v\n", line, name, expr) } } // Warnf reports a formatted warning at the current line. // In general the Go compiler does NOT generate warnings, // so this should be used only when the user has opted in // to additional output by setting a particular flag. func Warn(format string, args ...interface{}) { WarnfAt(Pos, format, args...) } // WarnfAt reports a formatted warning at pos. // In general the Go compiler does NOT generate warnings, // so this should be used only when the user has opted in // to additional output by setting a particular flag. func WarnfAt(pos src.XPos, format string, args ...interface{}) { addErrorMsg(pos, format, args...) if Flag.LowerM != 0 { FlushErrors() } } // Fatalf reports a fatal error - an internal problem - at the current line and exits. // If other errors have already been printed, then Fatalf just quietly exits. // (The internal problem may have been caused by incomplete information // after the already-reported errors, so best to let users fix those and // try again without being bothered about a spurious internal error.) // // But if no errors have been printed, or if -d panic has been specified, // Fatalf prints the error as an "internal compiler error". In a released build, // it prints an error asking to file a bug report. In development builds, it // prints a stack trace. // // If -h has been specified, Fatalf panics to force the usual runtime info dump. func Fatalf(format string, args ...interface{}) { FatalfAt(Pos, format, args...) } // FatalfAt reports a fatal error - an internal problem - at pos and exits. // If other errors have already been printed, then FatalfAt just quietly exits. // (The internal problem may have been caused by incomplete information // after the already-reported errors, so best to let users fix those and // try again without being bothered about a spurious internal error.) // // But if no errors have been printed, or if -d panic has been specified, // FatalfAt prints the error as an "internal compiler error". In a released build, // it prints an error asking to file a bug report. In development builds, it // prints a stack trace. // // If -h has been specified, FatalfAt panics to force the usual runtime info dump. func FatalfAt(pos src.XPos, format string, args ...interface{}) { FlushErrors() if Debug.Panic != 0 || numErrors == 0 { fmt.Printf("%v: internal compiler error: ", FmtPos(pos)) fmt.Printf(format, args...) fmt.Printf("\n") // If this is a released compiler version, ask for a bug report. if Debug.Panic == 0 && strings.HasPrefix(buildcfg.Version, "go") { fmt.Printf("\n") fmt.Printf("Please file a bug report including a short program that triggers the error.\n") fmt.Printf("https://go.dev/issue/new\n") } else { // Not a release; dump a stack trace, too. fmt.Println() os.Stdout.Write(debug.Stack()) fmt.Println() } } hcrash() ErrorExit() } // Assert reports "assertion failed" with Fatalf, unless b is true. func Assert(b bool) { if !b { Fatalf("assertion failed") } } // Assertf reports a fatal error with Fatalf, unless b is true. func Assertf(b bool, format string, args ...interface{}) { if !b { Fatalf(format, args...) } } // AssertfAt reports a fatal error with FatalfAt, unless b is true. func AssertfAt(b bool, pos src.XPos, format string, args ...interface{}) { if !b { FatalfAt(pos, format, args...) } } // hcrash crashes the compiler when -h is set, to find out where a message is generated. func hcrash() { if Flag.LowerH != 0 { FlushErrors() if Flag.LowerO != "" { os.Remove(Flag.LowerO) } panic("-h") } } // ErrorExit handles an error-status exit. // It flushes any pending errors, removes the output file, and exits. func ErrorExit() { FlushErrors() if Flag.LowerO != "" { os.Remove(Flag.LowerO) } os.Exit(2) } // ExitIfErrors calls ErrorExit if any errors have been reported. func ExitIfErrors() { if Errors() > 0 { ErrorExit() } } var AutogeneratedPos src.XPos