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  )
    18  
    19  // An errorMsg is a queued error message, waiting to be printed.
    20  type errorMsg struct {
    21  	pos  src.XPos
    22  	msg  string
    23  	code errors.Code
    24  }
    25  
    26  // Pos is the current source position being processed,
    27  // printed by Errorf, ErrorfLang, Fatalf, and Warnf.
    28  var Pos src.XPos
    29  
    30  var (
    31  	errorMsgs       []errorMsg
    32  	numErrors       int // number of entries in errorMsgs that are errors (as opposed to warnings)
    33  	numSyntaxErrors int
    34  )
    35  
    36  // Errors returns the number of errors reported.
    37  func Errors() int {
    38  	return numErrors
    39  }
    40  
    41  // SyntaxErrors returns the number of syntax errors reported.
    42  func SyntaxErrors() int {
    43  	return numSyntaxErrors
    44  }
    45  
    46  // addErrorMsg adds a new errorMsg (which may be a warning) to errorMsgs.
    47  func addErrorMsg(pos src.XPos, code errors.Code, format string, args ...interface{}) {
    48  	msg := fmt.Sprintf(format, args...)
    49  	// Only add the position if know the position.
    50  	// See issue golang.org/issue/11361.
    51  	if pos.IsKnown() {
    52  		msg = fmt.Sprintf("%v: %s", FmtPos(pos), msg)
    53  	}
    54  	errorMsgs = append(errorMsgs, errorMsg{
    55  		pos:  pos,
    56  		msg:  msg + "\n",
    57  		code: code,
    58  	})
    59  }
    60  
    61  // FmtPos formats pos as a file:line string.
    62  func FmtPos(pos src.XPos) string {
    63  	if Ctxt == nil {
    64  		return "???"
    65  	}
    66  	return Ctxt.OutermostPos(pos).Format(Flag.C == 0, Flag.L == 1)
    67  }
    68  
    69  // byPos sorts errors by source position.
    70  type byPos []errorMsg
    71  
    72  func (x byPos) Len() int           { return len(x) }
    73  func (x byPos) Less(i, j int) bool { return x[i].pos.Before(x[j].pos) }
    74  func (x byPos) Swap(i, j int)      { x[i], x[j] = x[j], x[i] }
    75  
    76  // FlushErrors sorts errors seen so far by line number, prints them to stdout,
    77  // and empties the errors array.
    78  func FlushErrors() {
    79  	if Ctxt != nil && Ctxt.Bso != nil {
    80  		Ctxt.Bso.Flush()
    81  	}
    82  	if len(errorMsgs) == 0 {
    83  		return
    84  	}
    85  	sort.Stable(byPos(errorMsgs))
    86  	for i, err := range errorMsgs {
    87  		if i == 0 || err.msg != errorMsgs[i-1].msg {
    88  			fmt.Print(err.msg)
    89  		}
    90  	}
    91  	errorMsgs = errorMsgs[:0]
    92  }
    93  
    94  // lasterror keeps track of the most recently issued error,
    95  // to avoid printing multiple error messages on the same line.
    96  var lasterror struct {
    97  	syntax src.XPos // source position of last syntax error
    98  	other  src.XPos // source position of last non-syntax error
    99  	msg    string   // error message of last non-syntax error
   100  }
   101  
   102  // sameline reports whether two positions a, b are on the same line.
   103  func sameline(a, b src.XPos) bool {
   104  	p := Ctxt.PosTable.Pos(a)
   105  	q := Ctxt.PosTable.Pos(b)
   106  	return p.Base() == q.Base() && p.Line() == q.Line()
   107  }
   108  
   109  // Errorf reports a formatted error at the current line.
   110  func Errorf(format string, args ...interface{}) {
   111  	ErrorfAt(Pos, 0, format, args...)
   112  }
   113  
   114  // ErrorfAt reports a formatted error message at pos.
   115  func ErrorfAt(pos src.XPos, code errors.Code, format string, args ...interface{}) {
   116  	msg := fmt.Sprintf(format, args...)
   117  
   118  	if strings.HasPrefix(msg, "syntax error") {
   119  		numSyntaxErrors++
   120  		// only one syntax error per line, no matter what error
   121  		if sameline(lasterror.syntax, pos) {
   122  			return
   123  		}
   124  		lasterror.syntax = pos
   125  	} else {
   126  		// only one of multiple equal non-syntax errors per line
   127  		// (FlushErrors shows only one of them, so we filter them
   128  		// here as best as we can (they may not appear in order)
   129  		// so that we don't count them here and exit early, and
   130  		// then have nothing to show for.)
   131  		if sameline(lasterror.other, pos) && lasterror.msg == msg {
   132  			return
   133  		}
   134  		lasterror.other = pos
   135  		lasterror.msg = msg
   136  	}
   137  
   138  	addErrorMsg(pos, code, "%s", msg)
   139  	numErrors++
   140  
   141  	hcrash()
   142  	if numErrors >= 10 && Flag.LowerE == 0 {
   143  		FlushErrors()
   144  		fmt.Printf("%v: too many errors\n", FmtPos(pos))
   145  		ErrorExit()
   146  	}
   147  }
   148  
   149  // UpdateErrorDot is a clumsy hack that rewrites the last error,
   150  // if it was "LINE: undefined: NAME", to be "LINE: undefined: NAME in EXPR".
   151  // It is used to give better error messages for dot (selector) expressions.
   152  func UpdateErrorDot(line string, name, expr string) {
   153  	if len(errorMsgs) == 0 {
   154  		return
   155  	}
   156  	e := &errorMsgs[len(errorMsgs)-1]
   157  	if strings.HasPrefix(e.msg, line) && e.msg == fmt.Sprintf("%v: undefined: %v\n", line, name) {
   158  		e.msg = fmt.Sprintf("%v: undefined: %v in %v\n", line, name, expr)
   159  	}
   160  }
   161  
   162  // Warn reports a formatted warning at the current line.
   163  // In general the Go compiler does NOT generate warnings,
   164  // so this should be used only when the user has opted in
   165  // to additional output by setting a particular flag.
   166  func Warn(format string, args ...interface{}) {
   167  	WarnfAt(Pos, format, args...)
   168  }
   169  
   170  // WarnfAt reports a formatted warning at pos.
   171  // In general the Go compiler does NOT generate warnings,
   172  // so this should be used only when the user has opted in
   173  // to additional output by setting a particular flag.
   174  func WarnfAt(pos src.XPos, format string, args ...interface{}) {
   175  	addErrorMsg(pos, 0, format, args...)
   176  	if Flag.LowerM != 0 {
   177  		FlushErrors()
   178  	}
   179  }
   180  
   181  // Fatalf reports a fatal error - an internal problem - at the current line and exits.
   182  // If other errors have already been printed, then Fatalf just quietly exits.
   183  // (The internal problem may have been caused by incomplete information
   184  // after the already-reported errors, so best to let users fix those and
   185  // try again without being bothered about a spurious internal error.)
   186  //
   187  // But if no errors have been printed, or if -d panic has been specified,
   188  // Fatalf prints the error as an "internal compiler error". In a released build,
   189  // it prints an error asking to file a bug report. In development builds, it
   190  // prints a stack trace.
   191  //
   192  // If -h has been specified, Fatalf panics to force the usual runtime info dump.
   193  func Fatalf(format string, args ...interface{}) {
   194  	FatalfAt(Pos, format, args...)
   195  }
   196  
   197  // FatalfAt reports a fatal error - an internal problem - at pos and exits.
   198  // If other errors have already been printed, then FatalfAt just quietly exits.
   199  // (The internal problem may have been caused by incomplete information
   200  // after the already-reported errors, so best to let users fix those and
   201  // try again without being bothered about a spurious internal error.)
   202  //
   203  // But if no errors have been printed, or if -d panic has been specified,
   204  // FatalfAt prints the error as an "internal compiler error". In a released build,
   205  // it prints an error asking to file a bug report. In development builds, it
   206  // prints a stack trace.
   207  //
   208  // If -h has been specified, FatalfAt panics to force the usual runtime info dump.
   209  func FatalfAt(pos src.XPos, format string, args ...interface{}) {
   210  	FlushErrors()
   211  
   212  	if Debug.Panic != 0 || numErrors == 0 {
   213  		fmt.Printf("%v: internal compiler error: ", FmtPos(pos))
   214  		fmt.Printf(format, args...)
   215  		fmt.Printf("\n")
   216  
   217  		// If this is a released compiler version, ask for a bug report.
   218  		if Debug.Panic == 0 && strings.HasPrefix(buildcfg.Version, "go") {
   219  			fmt.Printf("\n")
   220  			fmt.Printf("Please file a bug report including a short program that triggers the error.\n")
   221  			fmt.Printf("https://go.dev/issue/new\n")
   222  		} else {
   223  			// Not a release; dump a stack trace, too.
   224  			fmt.Println()
   225  			os.Stdout.Write(debug.Stack())
   226  			fmt.Println()
   227  		}
   228  	}
   229  
   230  	hcrash()
   231  	ErrorExit()
   232  }
   233  
   234  // Assert reports "assertion failed" with Fatalf, unless b is true.
   235  func Assert(b bool) {
   236  	if !b {
   237  		Fatalf("assertion failed")
   238  	}
   239  }
   240  
   241  // Assertf reports a fatal error with Fatalf, unless b is true.
   242  func Assertf(b bool, format string, args ...interface{}) {
   243  	if !b {
   244  		Fatalf(format, args...)
   245  	}
   246  }
   247  
   248  // AssertfAt reports a fatal error with FatalfAt, unless b is true.
   249  func AssertfAt(b bool, pos src.XPos, format string, args ...interface{}) {
   250  	if !b {
   251  		FatalfAt(pos, format, args...)
   252  	}
   253  }
   254  
   255  // hcrash crashes the compiler when -h is set, to find out where a message is generated.
   256  func hcrash() {
   257  	if Flag.LowerH != 0 {
   258  		FlushErrors()
   259  		if Flag.LowerO != "" {
   260  			os.Remove(Flag.LowerO)
   261  		}
   262  		panic("-h")
   263  	}
   264  }
   265  
   266  // ErrorExit handles an error-status exit.
   267  // It flushes any pending errors, removes the output file, and exits.
   268  func ErrorExit() {
   269  	FlushErrors()
   270  	if Flag.LowerO != "" {
   271  		os.Remove(Flag.LowerO)
   272  	}
   273  	os.Exit(2)
   274  }
   275  
   276  // ExitIfErrors calls ErrorExit if any errors have been reported.
   277  func ExitIfErrors() {
   278  	if Errors() > 0 {
   279  		ErrorExit()
   280  	}
   281  }
   282  
   283  var AutogeneratedPos src.XPos
   284  

View as plain text