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