mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
runtime: replace panic(nil) with panic(new(runtime.PanicNilError))
Long ago we decided that panic(nil) was too unlikely to bother
making a special case for purposes of recover. Unfortunately,
it has turned out not to be a special case. There are many examples
of code in the Go ecosystem where an author has written panic(nil)
because they want to panic and don't care about the panic value.
Using panic(nil) in this case has the unfortunate behavior of
making recover behave as though the goroutine isn't panicking.
As a result, code like:
func f() {
defer func() {
if err := recover(); err != nil {
log.Fatalf("panicked! %v", err)
}
}()
call1()
call2()
}
looks like it guarantees that call2 has been run any time f returns,
but that turns out not to be strictly true. If call1 does panic(nil),
then f returns "successfully", having recovered the panic, but
without calling call2.
Instead you have to write something like:
func f() {
done := false
defer func() {
if err := recover(); !done {
log.Fatalf("panicked! %v", err)
}
}()
call1()
call2()
done = true
}
which defeats nearly the whole point of recover. No one does this,
with the result that almost all uses of recover are subtly broken.
One specific broken use along these lines is in net/http, which
recovers from panics in handlers and sends back an HTTP error.
Users discovered in the early days of Go that panic(nil) was a
convenient way to jump out of a handler up to the serving loop
without sending back an HTTP error. This was a bug, not a feature.
Go 1.8 added panic(http.ErrAbortHandler) as a better way to access the feature.
Any lingering code that uses panic(nil) to abort an HTTP handler
without a failure message should be changed to use http.ErrAbortHandler.
Programs that need the old, unintended behavior from net/http
or other packages can set GODEBUG=panicnil=1 to stop the run-time error.
Uses of recover that want to detect panic(nil) in new programs
can check for recover returning a value of type *runtime.PanicNilError.
Because the new GODEBUG is used inside the runtime, we can't
import internal/godebug, so there is some new machinery to
cross-connect those in this CL, to allow a mutable GODEBUG setting.
That won't be necessary if we add any other mutable GODEBUG settings
in the future. The CL also corrects the handling of defaulted GODEBUG
values in the runtime, for #56986.
Fixes #25448.
Change-Id: I2b39c7e83e4f7aa308777dabf2edae54773e03f5
Reviewed-on: https://go-review.googlesource.com/c/go/+/461956
Reviewed-by: Robert Griesemer <gri@google.com>
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Auto-Submit: Russ Cox <rsc@golang.org>
This commit is contained in:
parent
8d71ae8c77
commit
aa51c40b1c
9 changed files with 208 additions and 68 deletions
|
|
@ -800,8 +800,29 @@ func deferCallSave(p *_panic, fn func()) {
|
|||
}
|
||||
}
|
||||
|
||||
// A PanicNilError happens when code calls panic(nil).
|
||||
//
|
||||
// Before Go 1.21, programs that called panic(nil) observed recover returning nil.
|
||||
// Starting in Go 1.21, programs that call panic(nil) observe recover returning a *PanicNilError.
|
||||
// Programs can change back to the old behavior by setting GODEBUG=panicnil=1.
|
||||
type PanicNilError struct {
|
||||
// This field makes PanicNilError structurally different from
|
||||
// any other struct in this package, and the _ makes it different
|
||||
// from any struct in other packages too.
|
||||
// This avoids any accidental conversions being possible
|
||||
// between this struct and some other struct sharing the same fields,
|
||||
// like happened in go.dev/issue/56603.
|
||||
_ [0]*PanicNilError
|
||||
}
|
||||
|
||||
func (*PanicNilError) Error() string { return "panic called with nil argument" }
|
||||
func (*PanicNilError) RuntimeError() {}
|
||||
|
||||
// The implementation of the predeclared function panic.
|
||||
func gopanic(e any) {
|
||||
if e == nil && debug.panicnil.Load() != 1 {
|
||||
e = new(PanicNilError)
|
||||
}
|
||||
gp := getg()
|
||||
if gp.m.curg != gp {
|
||||
print("panic: ")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue