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