github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/compile/base/print.go (about) 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 5 package base 6 7 import ( 8 "fmt" 9 "os" 10 "runtime/debug" 11 "sort" 12 "strings" 13 14 "github.com/go-asm/go/buildcfg" 15 "github.com/go-asm/go/types/errors" 16 17 "github.com/go-asm/go/cmd/src" 18 ) 19 20 // An errorMsg is a queued error message, waiting to be printed. 21 type 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. 29 var Pos src.XPos 30 31 var ( 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. 38 func Errors() int { 39 return numErrors 40 } 41 42 // SyntaxErrors returns the number of syntax errors reported. 43 func SyntaxErrors() int { 44 return numSyntaxErrors 45 } 46 47 // addErrorMsg adds a new errorMsg (which may be a warning) to errorMsgs. 48 func 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. 63 func 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. 71 type byPos []errorMsg 72 73 func (x byPos) Len() int { return len(x) } 74 func (x byPos) Less(i, j int) bool { return x[i].pos.Before(x[j].pos) } 75 func (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. 79 func 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. 97 var 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. 104 func 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. 111 func Errorf(format string, args ...interface{}) { 112 ErrorfAt(Pos, 0, format, args...) 113 } 114 115 // ErrorfAt reports a formatted error message at pos. 116 func 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. 153 func 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. 167 func 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. 175 func 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. 194 func Fatalf(format string, args ...interface{}) { 195 FatalfAt(Pos, format, args...) 196 } 197 198 // FatalfAt reports a fatal error - an internal problem - at pos and exits. 199 // If other errors have already been printed, then FatalfAt just quietly exits. 200 // (The internal problem may have been caused by incomplete information 201 // after the already-reported errors, so best to let users fix those and 202 // try again without being bothered about a spurious internal error.) 203 // 204 // But if no errors have been printed, or if -d panic has been specified, 205 // FatalfAt prints the error as an "internal compiler error". In a released build, 206 // it prints an error asking to file a bug report. In development builds, it 207 // prints a stack trace. 208 // 209 // If -h has been specified, FatalfAt panics to force the usual runtime info dump. 210 func FatalfAt(pos src.XPos, format string, args ...interface{}) { 211 FlushErrors() 212 213 if Debug.Panic != 0 || numErrors == 0 { 214 fmt.Printf("%v: internal compiler error: ", FmtPos(pos)) 215 fmt.Printf(format, args...) 216 fmt.Printf("\n") 217 218 // If this is a released compiler version, ask for a bug report. 219 if Debug.Panic == 0 && strings.HasPrefix(buildcfg.Version, "go") { 220 fmt.Printf("\n") 221 fmt.Printf("Please file a bug report including a short program that triggers the error.\n") 222 fmt.Printf("https://go.dev/issue/new\n") 223 } else { 224 // Not a release; dump a stack trace, too. 225 fmt.Println() 226 os.Stdout.Write(debug.Stack()) 227 fmt.Println() 228 } 229 } 230 231 hcrash() 232 ErrorExit() 233 } 234 235 // Assert reports "assertion failed" with Fatalf, unless b is true. 236 func Assert(b bool) { 237 if !b { 238 Fatalf("assertion failed") 239 } 240 } 241 242 // Assertf reports a fatal error with Fatalf, unless b is true. 243 func Assertf(b bool, format string, args ...interface{}) { 244 if !b { 245 Fatalf(format, args...) 246 } 247 } 248 249 // AssertfAt reports a fatal error with FatalfAt, unless b is true. 250 func AssertfAt(b bool, pos src.XPos, format string, args ...interface{}) { 251 if !b { 252 FatalfAt(pos, format, args...) 253 } 254 } 255 256 // hcrash crashes the compiler when -h is set, to find out where a message is generated. 257 func hcrash() { 258 if Flag.LowerH != 0 { 259 FlushErrors() 260 if Flag.LowerO != "" { 261 os.Remove(Flag.LowerO) 262 } 263 panic("-h") 264 } 265 } 266 267 // ErrorExit handles an error-status exit. 268 // It flushes any pending errors, removes the output file, and exits. 269 func ErrorExit() { 270 FlushErrors() 271 if Flag.LowerO != "" { 272 os.Remove(Flag.LowerO) 273 } 274 os.Exit(2) 275 } 276 277 // ExitIfErrors calls ErrorExit if any errors have been reported. 278 func ExitIfErrors() { 279 if Errors() > 0 { 280 ErrorExit() 281 } 282 } 283 284 var AutogeneratedPos src.XPos