summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthew Dempsky <mdempsky@google.com>2023-02-14 15:13:50 -0800
committerGopher Robot <gobot@golang.org>2023-02-28 00:07:00 +0000
commit0f4483cfdcf303ac5cbe4f4536e23750423137d9 (patch)
tree7005494a6e945aa6b95825befd5ede2f86b856e6
parent1362737f50ad2cb827eb1b8bc93d8d78e45a1371 (diff)
downloadgo-git-0f4483cfdcf303ac5cbe4f4536e23750423137d9.tar.gz
[release-branch.go1.20] cmd/compile/internal/noder: correct positions for synthetic closures
When inlining functions that contain function literals, we need to be careful about position information. The OCLOSURE node should use the inline-adjusted position, but the ODCLFUNC and its body should use the original positions. However, the same problem can arise with certain generic constructs, which require the compiler to synthesize function literals to insert dictionary arguments. go.dev/cl/425395 fixed the issue with user-written function literals in a somewhat kludgy way; this CL extends the same solution to synthetic function literals. This is all quite subtle and the solutions aren't terribly robust, so longer term it's probably desirable to revisit how we track inlining context for positions. But for now, this seems to be the least bad solution, esp. for backporting to 1.20. Updates #54625. Fixes #58531. Change-Id: Icc43a70dbb11a0e665cbc9e6a64ef274ad8253d1 Reviewed-on: https://go-review.googlesource.com/c/go/+/468415 TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com> Reviewed-by: Than McIntosh <thanm@google.com> Run-TryBot: Matthew Dempsky <mdempsky@google.com> (cherry picked from commit 873c14cec730ee278848f7cc58d2b4d89ab52288) Reviewed-on: https://go-review.googlesource.com/c/go/+/471677 Auto-Submit: Dmitri Shuralyov <dmitshur@google.com> Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org> Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
-rw-r--r--src/cmd/compile/internal/noder/reader.go53
-rw-r--r--test/typeparam/issue58513.go60
2 files changed, 95 insertions, 18 deletions
diff --git a/src/cmd/compile/internal/noder/reader.go b/src/cmd/compile/internal/noder/reader.go
index 189531959e..591d09ba78 100644
--- a/src/cmd/compile/internal/noder/reader.go
+++ b/src/cmd/compile/internal/noder/reader.go
@@ -231,6 +231,16 @@ func (r *reader) pos() src.XPos {
return base.Ctxt.PosTable.XPos(r.pos0())
}
+// origPos reads a position from the bitstream, and returns both the
+// original raw position and an inlining-adjusted position.
+func (r *reader) origPos() (origPos, inlPos src.XPos) {
+ r.suppressInlPos++
+ origPos = r.pos()
+ r.suppressInlPos--
+ inlPos = r.inlPos(origPos)
+ return
+}
+
func (r *reader) pos0() src.Pos {
r.Sync(pkgbits.SyncPos)
if !r.Bool() {
@@ -2117,12 +2127,12 @@ func (r *reader) expr() (res ir.Node) {
return typecheck.Callee(r.obj())
case exprFuncInst:
- pos := r.pos()
+ origPos, pos := r.origPos()
wrapperFn, baseFn, dictPtr := r.funcInst(pos)
if wrapperFn != nil {
return wrapperFn
}
- return r.curry(pos, false, baseFn, dictPtr, nil)
+ return r.curry(origPos, false, baseFn, dictPtr, nil)
case exprConst:
pos := r.pos()
@@ -2152,7 +2162,7 @@ func (r *reader) expr() (res ir.Node) {
case exprMethodVal:
recv := r.expr()
- pos := r.pos()
+ origPos, pos := r.origPos()
wrapperFn, baseFn, dictPtr := r.methodExpr()
// For simple wrapperFn values, the existing machinery for creating
@@ -2201,7 +2211,7 @@ func (r *reader) expr() (res ir.Node) {
// For more complicated method expressions, we construct a
// function literal wrapper.
- return r.curry(pos, true, baseFn, recv, dictPtr)
+ return r.curry(origPos, true, baseFn, recv, dictPtr)
case exprMethodExpr:
recv := r.typ()
@@ -2217,7 +2227,7 @@ func (r *reader) expr() (res ir.Node) {
addr = true
}
- pos := r.pos()
+ origPos, pos := r.origPos()
wrapperFn, baseFn, dictPtr := r.methodExpr()
// If we already have a wrapper and don't need to do anything with
@@ -2240,7 +2250,7 @@ func (r *reader) expr() (res ir.Node) {
return typecheck.Expr(ir.NewSelectorExpr(pos, ir.OXDOT, ir.TypeNode(recv), method.Sel)).(*ir.SelectorExpr)
}
- return r.methodExprWrap(pos, recv, implicits, deref, addr, baseFn, dictPtr)
+ return r.methodExprWrap(origPos, recv, implicits, deref, addr, baseFn, dictPtr)
case exprIndex:
x := r.expr()
@@ -2570,7 +2580,7 @@ func (pr *pkgReader) objDictName(idx pkgbits.Index, implicits, explicits []*type
// If nilCheck is true and arg0 is an interface value, then it's
// checked to be non-nil as an initial step at the point of evaluating
// the function literal itself.
-func (r *reader) curry(pos src.XPos, ifaceHack bool, fun ir.Node, arg0, arg1 ir.Node) ir.Node {
+func (r *reader) curry(origPos src.XPos, ifaceHack bool, fun ir.Node, arg0, arg1 ir.Node) ir.Node {
var captured ir.Nodes
captured.Append(fun, arg0)
if arg1 != nil {
@@ -2594,13 +2604,13 @@ func (r *reader) curry(pos src.XPos, ifaceHack bool, fun ir.Node, arg0, arg1 ir.
r.syntheticTailCall(pos, fun, args)
}
- return r.syntheticClosure(pos, typ, ifaceHack, captured, addBody)
+ return r.syntheticClosure(origPos, typ, ifaceHack, captured, addBody)
}
// methodExprWrap returns a function literal that changes method's
// first parameter's type to recv, and uses implicits/deref/addr to
// select the appropriate receiver parameter to pass to method.
-func (r *reader) methodExprWrap(pos src.XPos, recv *types.Type, implicits []int, deref, addr bool, method, dictPtr ir.Node) ir.Node {
+func (r *reader) methodExprWrap(origPos src.XPos, recv *types.Type, implicits []int, deref, addr bool, method, dictPtr ir.Node) ir.Node {
var captured ir.Nodes
captured.Append(method)
@@ -2651,12 +2661,13 @@ func (r *reader) methodExprWrap(pos src.XPos, recv *types.Type, implicits []int,
r.syntheticTailCall(pos, fn, args)
}
- return r.syntheticClosure(pos, typ, false, captured, addBody)
+ return r.syntheticClosure(origPos, typ, false, captured, addBody)
}
// syntheticClosure constructs a synthetic function literal for
-// currying dictionary arguments. pos is the position used for the
-// closure. typ is the function literal's signature type.
+// currying dictionary arguments. origPos is the position used for the
+// closure, which must be a non-inlined position. typ is the function
+// literal's signature type.
//
// captures is a list of expressions that need to be evaluated at the
// point of function literal evaluation and captured by the function
@@ -2667,7 +2678,7 @@ func (r *reader) methodExprWrap(pos src.XPos, recv *types.Type, implicits []int,
// list of captured values passed back has the captured variables for
// use within the function literal, corresponding to the expressions
// in captures.
-func (r *reader) syntheticClosure(pos src.XPos, typ *types.Type, ifaceHack bool, captures ir.Nodes, addBody func(pos src.XPos, r *reader, captured []ir.Node)) ir.Node {
+func (r *reader) syntheticClosure(origPos src.XPos, typ *types.Type, ifaceHack bool, captures ir.Nodes, addBody func(pos src.XPos, r *reader, captured []ir.Node)) ir.Node {
// isSafe reports whether n is an expression that we can safely
// defer to evaluating inside the closure instead, to avoid storing
// them into the closure.
@@ -2684,9 +2695,15 @@ func (r *reader) syntheticClosure(pos src.XPos, typ *types.Type, ifaceHack bool,
return false
}
- fn := ir.NewClosureFunc(pos, r.curfn != nil)
+ // The ODCLFUNC and its body need to use the original position, but
+ // the OCLOSURE node and any Init statements should use the inlined
+ // position instead. See also the explanation in reader.funcLit.
+ inlPos := r.inlPos(origPos)
+
+ fn := ir.NewClosureFunc(origPos, r.curfn != nil)
fn.SetWrapper(true)
clo := fn.OClosure
+ clo.SetPos(inlPos)
ir.NameClosure(clo, r.curfn)
setType(fn.Nname, typ)
@@ -2699,13 +2716,13 @@ func (r *reader) syntheticClosure(pos src.XPos, typ *types.Type, ifaceHack bool,
continue // skip capture; can reference directly
}
- tmp := r.tempCopy(pos, n, &init)
- ir.NewClosureVar(pos, fn, tmp)
+ tmp := r.tempCopy(inlPos, n, &init)
+ ir.NewClosureVar(origPos, fn, tmp)
// We need to nil check interface receivers at the point of method
// value evaluation, ugh.
if ifaceHack && i == 1 && n.Type().IsInterface() {
- check := ir.NewUnaryExpr(pos, ir.OCHECKNIL, ir.NewUnaryExpr(pos, ir.OITAB, tmp))
+ check := ir.NewUnaryExpr(inlPos, ir.OCHECKNIL, ir.NewUnaryExpr(inlPos, ir.OITAB, tmp))
init.Append(typecheck.Stmt(check))
}
}
@@ -2723,7 +2740,7 @@ func (r *reader) syntheticClosure(pos src.XPos, typ *types.Type, ifaceHack bool,
}
assert(next == len(r.closureVars))
- addBody(pos, r, captured)
+ addBody(origPos, r, captured)
}}
bodyReader[fn] = pri
pri.funcBody(fn)
diff --git a/test/typeparam/issue58513.go b/test/typeparam/issue58513.go
new file mode 100644
index 0000000000..37cb5725ca
--- /dev/null
+++ b/test/typeparam/issue58513.go
@@ -0,0 +1,60 @@
+// run
+
+// Copyright 2023 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.
+
+// Some derived-type expressions require the compiler to synthesize
+// function literals to plumb sub-dictionaries appropriately.
+// However, when these expressions are inlined, we were constructing
+// the function literal bodies with the inline-adjusted positions
+// instead of the original (inline-free) positions, which could lead
+// to infinite loops when unwinding the stack.
+
+package main
+
+import "runtime"
+
+func assert[_ any]() {
+ panic(0)
+}
+
+func Assert[To any]() func() {
+ return assert[To]
+}
+
+type asserter[_ any] struct{}
+
+func (asserter[_]) assert() {
+ panic(0)
+}
+
+func AssertMV[To any]() func() {
+ return asserter[To]{}.assert
+}
+
+func AssertME[To any]() func(asserter[To]) {
+ return asserter[To].assert
+}
+
+var me = AssertME[string]()
+
+var tests = []func(){
+ Assert[int](),
+ AssertMV[int](),
+ func() { me(asserter[string]{}) },
+}
+
+func main() {
+ for _, test := range tests {
+ func() {
+ defer func() {
+ recover()
+
+ // Check that we can unwind the stack without infinite looping.
+ runtime.Caller(1000)
+ }()
+ test()
+ }()
+ }
+}