Skip to content

cmd/compile: incorrect recover behavior due to defer wrapper #73916

Open
@cherrymui

Description

@cherrymui

Go version

tip, and Go 1.22 - 1.24

Output of go env in your module/workspace:

darwin/arm64

What did you do?

Run https://go.dev/play/p/9kYvE9NH8da

package main

func callRecover() {
	if recover() != nil {
		println("recovered")
	}
}

func F(int) { callRecover() }

func main() {
	defer F(1)
	panic("XXX")
}

What did you see happen?

The panic is recovered. The program prints "recovered" and exits normally.

What did you expect to see?

The panic is not recovered, crashing the program.

The spec says "The return value of recover is nil when ... or recover was not called directly by a deferred function." In this case, recover is not directly called by the deferred function F. There is an intermediate function. So recover should return nil, not recovering the panic.

The program panics as expected if inlining is disabled.

$ go run -gcflags=-l x2.go
panic: XXX

goroutine 1 [running]:
main.main()
	/tmp/x2.go:13 +0x48
exit status 2

At least, the program's behavior should not depend on inlining.

Go 1.21 got it right. Go 1.22 and later fail. Bisection points to CL https://go.dev/cl/520260 . Before that CL, defer wrappers are created after inlining, so the wrapper always makes an actual call to the wrapped function. That CL makes the generation of defer wrappers earlier, before inlining. So the wrapped function could be inlined into the wrapper. But we still keep the WRAPPER attribute on the wrapper function, which makes the compiler insert a special prologue that adjusts the panic frame if it is panicking. This causes the panic to be treated one frame too deep.

I think if we inline the wrapped function into the wrapper, the wrapper should not do the panic frame adjustment. We still need to tell the runtime to omit the wrapper frame from traceback. So we can't simply unset the WRAPPER attribute, which has two effects: panic frame adjustment, and omitting the frame from traceback. We may need a separate attribute for just omitting the frame from traceback without the panic adjustment. Or the inlining table could say that if the wrapper inlines the wrapped function, during the inlined part, it doesn't have a virtual caller (which would be the wrapper).

Found while investigating #73747. There is at least another related issue dated back even earlier, which I'm still investigating.

Metadata

Metadata

Assignees

Labels

BugReportIssues describing a possible bug in the Go implementation.compiler/runtimeIssues related to the Go compiler and/or runtime.

Type

No type

Projects

Status

Todo

Relationships

None yet

Development

No branches or pull requests

Issue actions