Source file src/cmd/compile/internal/base/print.go

     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  	"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.
    19  type 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.
    26  var Pos src.XPos
    27  
    28  var (
    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.
    35  func Errors() int {
    36  	return numErrors
    37  }
    38  
    39  // SyntaxErrors returns the number of syntax errors reported
    40  func SyntaxErrors() int {
    41  	return numSyntaxErrors
    42  }
    43  
    44  // addErrorMsg adds a new errorMsg (which may be a warning) to errorMsgs.
    45  func 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.
    59  func 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.
    67  type byPos []errorMsg
    68  
    69  func (x byPos) Len() int           { return len(x) }
    70  func (x byPos) Less(i, j int) bool { return x[i].pos.Before(x[j].pos) }
    71  func (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.
    75  func 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.
    93  var 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.
   100  func 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.
   107  func Errorf(format string, args ...interface{}) {
   108  	ErrorfAt(Pos, format, args...)
   109  }
   110  
   111  // ErrorfAt reports a formatted error message at pos.
   112  func 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.
   147  func 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.
   154  func 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.
   168  func 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.
   176  func 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.
   195  func 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.
   211  func 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 Debug.Panic == 0 && 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://go.dev/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.
   237  func 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.
   244  func 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.
   251  func 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.
   258  func 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.
   270  func 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.
   279  func ExitIfErrors() {
   280  	if Errors() > 0 {
   281  		ErrorExit()
   282  	}
   283  }
   284  
   285  var AutogeneratedPos src.XPos
   286  

View as plain text