// Func devirtualizes calls within fn where possible.
func Func(fn *ir.Func) {
ir.CurFunc = fn
+
+ // For promoted methods (including value-receiver methods promoted to pointer-receivers),
+ // the interface method wrapper may contain expressions that can panic (e.g., ODEREF, ODOTPTR, ODOTINTER).
+ // Devirtualization involves inlining these expressions (and possible panics) to the call site.
+ // This normally isn't a problem, but for go/defer statements it can move the panic from when/where
+ // the call executes to the go/defer statement itself, which is a visible change in semantics (e.g., #52072).
+ // To prevent this, we skip devirtualizing calls within go/defer statements altogether.
+ goDeferCall := make(map[*ir.CallExpr]bool)
ir.VisitList(fn.Body, func(n ir.Node) {
- if call, ok := n.(*ir.CallExpr); ok {
- Call(call)
+ switch n := n.(type) {
+ case *ir.GoDeferStmt:
+ if call, ok := n.Call.(*ir.CallExpr); ok {
+ goDeferCall[call] = true
+ }
+ return
+ case *ir.CallExpr:
+ if !goDeferCall[n] {
+ Call(n)
+ }
}
})
}
--- /dev/null
+// run
+
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+type I interface{ M() }
+
+type T struct {
+ x int
+}
+
+func (T) M() {}
+
+var pt *T
+
+func f() (r int) {
+ defer func() { recover() }()
+
+ var i I = pt
+ defer i.M()
+ r = 1
+ return
+}
+
+func main() {
+ if got := f(); got != 1 {
+ panic(got)
+ }
+}