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  	"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.
    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 ...any) {
    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  	if Flag.LowerU == 0 {
    87  		sort.Stable(byPos(errorMsgs))
    88  	}
    89  	for i, err := range errorMsgs {
    90  		if i == 0 || err.msg != errorMsgs[i-1].msg {
    91  			fmt.Print(err.msg)
    92  		}
    93  	}
    94  	errorMsgs = errorMsgs[:0]
    95  }
    96  
    97  // lasterror keeps track of the most recently issued error,
    98  // to avoid printing multiple error messages on the same line.
    99  var lasterror struct {
   100  	syntax src.XPos // source position of last syntax error
   101  	other  src.XPos // source position of last non-syntax error
   102  	msg    string   // error message of last non-syntax error
   103  }
   104  
   105  // sameline reports whether two positions a, b are on the same line.
   106  func sameline(a, b src.XPos) bool {
   107  	p := Ctxt.PosTable.Pos(a)
   108  	q := Ctxt.PosTable.Pos(b)
   109  	return p.Base() == q.Base() && p.Line() == q.Line()
   110  }
   111  
   112  // Errorf reports a formatted error at the current line.
   113  func Errorf(format string, args ...any) {
   114  	ErrorfAt(Pos, 0, format, args...)
   115  }
   116  
   117  // ErrorfAt reports a formatted error message at pos.
   118  func ErrorfAt(pos src.XPos, code errors.Code, format string, args ...any) {
   119  	msg := fmt.Sprintf(format, args...)
   120  
   121  	if strings.HasPrefix(msg, "syntax error") {
   122  		numSyntaxErrors++
   123  		// only one syntax error per line, no matter what error
   124  		if sameline(lasterror.syntax, pos) {
   125  			return
   126  		}
   127  		lasterror.syntax = pos
   128  	} else {
   129  		// only one of multiple equal non-syntax errors per line
   130  		// (FlushErrors shows only one of them, so we filter them
   131  		// here as best as we can (they may not appear in order)
   132  		// so that we don't count them here and exit early, and
   133  		// then have nothing to show for.)
   134  		if sameline(lasterror.other, pos) && lasterror.msg == msg {
   135  			return
   136  		}
   137  		lasterror.other = pos
   138  		lasterror.msg = msg
   139  	}
   140  
   141  	addErrorMsg(pos, code, "%s", msg)
   142  	numErrors++
   143  
   144  	hcrash()
   145  	if numErrors >= 10 && Flag.LowerE == 0 {
   146  		FlushErrors()
   147  		fmt.Printf("%v: too many errors\n", FmtPos(pos))
   148  		ErrorExit()
   149  	}
   150  }
   151  
   152  // UpdateErrorDot is a clumsy hack that rewrites the last error,
   153  // if it was "LINE: undefined: NAME", to be "LINE: undefined: NAME in EXPR".
   154  // It is used to give better error messages for dot (selector) expressions.
   155  func UpdateErrorDot(line string, name, expr string) {
   156  	if len(errorMsgs) == 0 {
   157  		return
   158  	}
   159  	e := &errorMsgs[len(errorMsgs)-1]
   160  	if strings.HasPrefix(e.msg, line) && e.msg == fmt.Sprintf("%v: undefined: %v\n", line, name) {
   161  		e.msg = fmt.Sprintf("%v: undefined: %v in %v\n", line, name, expr)
   162  	}
   163  }
   164  
   165  // Warn reports a formatted warning at the current line.
   166  // In general the Go compiler does NOT generate warnings,
   167  // so this should be used only when the user has opted in
   168  // to additional output by setting a particular flag.
   169  func Warn(format string, args ...any) {
   170  	WarnfAt(Pos, format, args...)
   171  }
   172  
   173  // WarnfAt reports a formatted warning at pos.
   174  // In general the Go compiler does NOT generate warnings,
   175  // so this should be used only when the user has opted in
   176  // to additional output by setting a particular flag.
   177  func WarnfAt(pos src.XPos, format string, args ...any) {
   178  	addErrorMsg(pos, 0, format, args...)
   179  	if Flag.LowerM != 0 {
   180  		FlushErrors()
   181  	}
   182  }
   183  
   184  // Fatalf reports a fatal error - an internal problem - at the current line and exits.
   185  // If other errors have already been printed, then Fatalf just quietly exits.
   186  // (The internal problem may have been caused by incomplete information
   187  // after the already-reported errors, so best to let users fix those and
   188  // try again without being bothered about a spurious internal error.)
   189  //
   190  // But if no errors have been printed, or if -d panic has been specified,
   191  // Fatalf prints the error as an "internal compiler error". In a released build,
   192  // it prints an error asking to file a bug report. In development builds, it
   193  // prints a stack trace.
   194  //
   195  // If -h has been specified, Fatalf panics to force the usual runtime info dump.
   196  func Fatalf(format string, args ...any) {
   197  	FatalfAt(Pos, format, args...)
   198  }
   199  
   200  var bugStack = counter.NewStack("compile/bug", 16) // 16 is arbitrary; used by gopls and crashmonitor
   201  
   202  // FatalfAt reports a fatal error - an internal problem - at pos and exits.
   203  // If other errors have already been printed, then FatalfAt just quietly exits.
   204  // (The internal problem may have been caused by incomplete information
   205  // after the already-reported errors, so best to let users fix those and
   206  // try again without being bothered about a spurious internal error.)
   207  //
   208  // But if no errors have been printed, or if -d panic has been specified,
   209  // FatalfAt prints the error as an "internal compiler error". In a released build,
   210  // it prints an error asking to file a bug report. In development builds, it
   211  // prints a stack trace.
   212  //
   213  // If -h has been specified, FatalfAt panics to force the usual runtime info dump.
   214  func FatalfAt(pos src.XPos, format string, args ...any) {
   215  	FlushErrors()
   216  
   217  	bugStack.Inc()
   218  
   219  	if Debug.Panic != 0 || numErrors == 0 {
   220  		fmt.Printf("%v: internal compiler error: ", FmtPos(pos))
   221  		fmt.Printf(format, args...)
   222  		fmt.Printf("\n")
   223  
   224  		// If this is a released compiler version, ask for a bug report.
   225  		if Debug.Panic == 0 && strings.HasPrefix(buildcfg.Version, "go") && !strings.Contains(buildcfg.Version, "devel") {
   226  			fmt.Printf("\n")
   227  			fmt.Printf("Please file a bug report including a short program that triggers the error.\n")
   228  			fmt.Printf("https://go.dev/issue/new\n")
   229  		} else {
   230  			// Not a release; dump a stack trace, too.
   231  			fmt.Println()
   232  			os.Stdout.Write(debug.Stack())
   233  			fmt.Println()
   234  		}
   235  	}
   236  
   237  	hcrash()
   238  	ErrorExit()
   239  }
   240  
   241  // Assert reports "assertion failed" with Fatalf, unless b is true.
   242  func Assert(b bool) {
   243  	if !b {
   244  		Fatalf("assertion failed")
   245  	}
   246  }
   247  
   248  // Assertf reports a fatal error with Fatalf, unless b is true.
   249  func Assertf(b bool, format string, args ...any) {
   250  	if !b {
   251  		Fatalf(format, args...)
   252  	}
   253  }
   254  
   255  // AssertfAt reports a fatal error with FatalfAt, unless b is true.
   256  func AssertfAt(b bool, pos src.XPos, format string, args ...any) {
   257  	if !b {
   258  		FatalfAt(pos, format, args...)
   259  	}
   260  }
   261  
   262  // hcrash crashes the compiler when -h is set, to find out where a message is generated.
   263  func hcrash() {
   264  	if Flag.LowerH != 0 {
   265  		FlushErrors()
   266  		if Flag.LowerO != "" {
   267  			os.Remove(Flag.LowerO)
   268  		}
   269  		panic("-h")
   270  	}
   271  }
   272  
   273  // ErrorExit handles an error-status exit.
   274  // It flushes any pending errors, removes the output file, and exits.
   275  func ErrorExit() {
   276  	FlushErrors()
   277  	if Flag.LowerO != "" {
   278  		os.Remove(Flag.LowerO)
   279  	}
   280  	os.Exit(2)
   281  }
   282  
   283  // ExitIfErrors calls ErrorExit if any errors have been reported.
   284  func ExitIfErrors() {
   285  	if Errors() > 0 {
   286  		ErrorExit()
   287  	}
   288  }
   289  
   290  var AutogeneratedPos src.XPos
   291  

View as plain text