go/ssa: make Builtin capable of representing non-spec-defined intrinsics.
Also, define ssa:wrapnilchk intrinsic to check and gracefully fail when a T method is dynamically invoked via a nil *T receiver. + Test. A follow-up CL will add another intrinsic, ssa:memclr. + minor cleanups. LGTM=gri R=gri CC=golang-codereviews https://golang.org/cl/101170044
This commit is contained in:
parent
38cb4c0966
commit
de23e2b0c2
|
@ -531,7 +531,7 @@ func (a *analysis) genAppend(instr *ssa.Call, cgn *cgnode) {
|
||||||
// genBuiltinCall generates contraints for a call to a built-in.
|
// genBuiltinCall generates contraints for a call to a built-in.
|
||||||
func (a *analysis) genBuiltinCall(instr ssa.CallInstruction, cgn *cgnode) {
|
func (a *analysis) genBuiltinCall(instr ssa.CallInstruction, cgn *cgnode) {
|
||||||
call := instr.Common()
|
call := instr.Common()
|
||||||
switch call.Value.(*ssa.Builtin).Object().Name() {
|
switch call.Value.(*ssa.Builtin).Name() {
|
||||||
case "append":
|
case "append":
|
||||||
// Safe cast: append cannot appear in a go or defer statement.
|
// Safe cast: append cannot appear in a go or defer statement.
|
||||||
a.genAppend(instr.(*ssa.Call), cgn)
|
a.genAppend(instr.(*ssa.Call), cgn)
|
||||||
|
@ -553,6 +553,9 @@ func (a *analysis) genBuiltinCall(instr ssa.CallInstruction, cgn *cgnode) {
|
||||||
// to its arg, so make sure we create nodes for it.
|
// to its arg, so make sure we create nodes for it.
|
||||||
a.valueNode(call.Args[0])
|
a.valueNode(call.Args[0])
|
||||||
|
|
||||||
|
case "ssa:wrapnilchk":
|
||||||
|
a.copy(a.valueNode(instr.Value()), a.valueNode(call.Args[0]), 1)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// No-ops: close len cap real imag complex print println delete.
|
// No-ops: close len cap real imag complex print println delete.
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,6 +57,7 @@ var (
|
||||||
tByte = types.Typ[types.Byte]
|
tByte = types.Typ[types.Byte]
|
||||||
tInt = types.Typ[types.Int]
|
tInt = types.Typ[types.Int]
|
||||||
tInvalid = types.Typ[types.Invalid]
|
tInvalid = types.Typ[types.Invalid]
|
||||||
|
tString = types.Typ[types.String]
|
||||||
tUntypedNil = types.Typ[types.UntypedNil]
|
tUntypedNil = types.Typ[types.UntypedNil]
|
||||||
tRangeIter = &opaqueType{nil, "iter"} // the type of all "range" iterators
|
tRangeIter = &opaqueType{nil, "iter"} // the type of all "range" iterators
|
||||||
tEface = new(types.Interface)
|
tEface = new(types.Interface)
|
||||||
|
@ -597,7 +598,7 @@ func (b *builder) expr0(fn *Function, e ast.Expr) Value {
|
||||||
// Universal built-in or nil?
|
// Universal built-in or nil?
|
||||||
switch obj := obj.(type) {
|
switch obj := obj.(type) {
|
||||||
case *types.Builtin:
|
case *types.Builtin:
|
||||||
return &Builtin{object: obj, sig: fn.Pkg.typeOf(e).(*types.Signature)}
|
return &Builtin{name: obj.Name(), sig: fn.Pkg.typeOf(e).(*types.Signature)}
|
||||||
case *types.Nil:
|
case *types.Nil:
|
||||||
return nilConst(fn.Pkg.typeOf(e))
|
return nilConst(fn.Pkg.typeOf(e))
|
||||||
}
|
}
|
||||||
|
@ -1414,7 +1415,7 @@ func (b *builder) selectStmt(fn *Function, s *ast.SelectStmt, label *lblock) {
|
||||||
for _, st := range states {
|
for _, st := range states {
|
||||||
if st.Dir == types.RecvOnly {
|
if st.Dir == types.RecvOnly {
|
||||||
tElem := st.Chan.Type().Underlying().(*types.Chan).Elem()
|
tElem := st.Chan.Type().Underlying().(*types.Chan).Elem()
|
||||||
vars = append(vars, newVar("", tElem))
|
vars = append(vars, anonVar(tElem))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sel.setType(types.NewTuple(vars...))
|
sel.setType(types.NewTuple(vars...))
|
||||||
|
@ -1489,7 +1490,7 @@ func (b *builder) selectStmt(fn *Function, s *ast.SelectStmt, label *lblock) {
|
||||||
// A blocking select must match some case.
|
// A blocking select must match some case.
|
||||||
// (This should really be a runtime.errorString, not a string.)
|
// (This should really be a runtime.errorString, not a string.)
|
||||||
fn.emit(&Panic{
|
fn.emit(&Panic{
|
||||||
X: emitConv(fn, NewConst(exact.MakeString("blocking select matched no case"), types.Typ[types.String]), tEface),
|
X: emitConv(fn, stringConst("blocking select matched no case"), tEface),
|
||||||
})
|
})
|
||||||
fn.currentBlock = fn.newBasicBlock("unreachable")
|
fn.currentBlock = fn.newBasicBlock("unreachable")
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ func NewConst(val exact.Value, typ types.Type) *Const {
|
||||||
// intConst returns an 'int' constant that evaluates to i.
|
// intConst returns an 'int' constant that evaluates to i.
|
||||||
// (i is an int64 in case the host is narrower than the target.)
|
// (i is an int64 in case the host is narrower than the target.)
|
||||||
func intConst(i int64) *Const {
|
func intConst(i int64) *Const {
|
||||||
return NewConst(exact.MakeInt64(i), types.Typ[types.Int])
|
return NewConst(exact.MakeInt64(i), tInt)
|
||||||
}
|
}
|
||||||
|
|
||||||
// nilConst returns a nil constant of the specified type, which may
|
// nilConst returns a nil constant of the specified type, which may
|
||||||
|
@ -35,6 +35,11 @@ func nilConst(typ types.Type) *Const {
|
||||||
return NewConst(nil, typ)
|
return NewConst(nil, typ)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stringConst returns a 'string' constant that evaluates to s.
|
||||||
|
func stringConst(s string) *Const {
|
||||||
|
return NewConst(exact.MakeString(s), tString)
|
||||||
|
}
|
||||||
|
|
||||||
// zeroConst returns a new "zero" constant of the specified type,
|
// zeroConst returns a new "zero" constant of the specified type,
|
||||||
// which must not be an array or struct type: the zero values of
|
// which must not be an array or struct type: the zero values of
|
||||||
// aggregates are well-defined but cannot be represented by Const.
|
// aggregates are well-defined but cannot be represented by Const.
|
||||||
|
|
|
@ -1059,6 +1059,16 @@ func callBuiltin(caller *frame, callpos token.Pos, fn *ssa.Builtin, args []value
|
||||||
|
|
||||||
case "recover":
|
case "recover":
|
||||||
return doRecover(caller)
|
return doRecover(caller)
|
||||||
|
|
||||||
|
case "ssa:wrapnilchk":
|
||||||
|
recv := args[0]
|
||||||
|
if recv.(*value) == nil {
|
||||||
|
recvType := args[1]
|
||||||
|
methodName := args[2]
|
||||||
|
panic(fmt.Sprintf("value method (%s).%s called using nil *%s pointer",
|
||||||
|
recvType, methodName, recvType))
|
||||||
|
}
|
||||||
|
return recv
|
||||||
}
|
}
|
||||||
|
|
||||||
panic("unknown built-in: " + fn.Name())
|
panic("unknown built-in: " + fn.Name())
|
||||||
|
|
|
@ -673,3 +673,20 @@ func init() {
|
||||||
panic(count)
|
panic(count)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test that a nice error is issue by indirection wrappers.
|
||||||
|
func init() {
|
||||||
|
var ptr *T
|
||||||
|
var i I = ptr
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
r := fmt.Sprint(recover())
|
||||||
|
// Exact error varies by toolchain:
|
||||||
|
if r != "runtime error: value method (main.T).f called using nil *main.T pointer" &&
|
||||||
|
r != "value method main.T.f called using nil *T pointer" {
|
||||||
|
panic("want panic from call with nil receiver, got " + r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
i.f()
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
|
@ -264,12 +264,30 @@ func makeWrapper(prog *Program, meth *types.Selection) *Function {
|
||||||
fn.addSpilledParam(recv)
|
fn.addSpilledParam(recv)
|
||||||
createParams(fn, start)
|
createParams(fn, start)
|
||||||
|
|
||||||
|
indices := meth.Index()
|
||||||
|
|
||||||
var v Value = fn.Locals[0] // spilled receiver
|
var v Value = fn.Locals[0] // spilled receiver
|
||||||
if isPointer(meth.Recv()) {
|
if isPointer(meth.Recv()) {
|
||||||
// TODO(adonovan): consider emitting a nil-pointer
|
|
||||||
// check here with a nice error message, like gc does.
|
|
||||||
// We could define a new builtin for the purpose.
|
|
||||||
v = emitLoad(fn, v)
|
v = emitLoad(fn, v)
|
||||||
|
|
||||||
|
// For simple indirection wrappers, perform an informative nil-check:
|
||||||
|
// "value method (T).f called using nil *T pointer"
|
||||||
|
if len(indices) == 1 && !isPointer(recvType(obj)) {
|
||||||
|
var c Call
|
||||||
|
c.Call.Value = &Builtin{
|
||||||
|
name: "ssa:wrapnilchk",
|
||||||
|
sig: types.NewSignature(nil, nil,
|
||||||
|
types.NewTuple(anonVar(meth.Recv()), anonVar(tString), anonVar(tString)),
|
||||||
|
types.NewTuple(anonVar(meth.Recv())), false),
|
||||||
|
}
|
||||||
|
c.Call.Args = []Value{
|
||||||
|
v,
|
||||||
|
stringConst(deref(meth.Recv()).String()),
|
||||||
|
stringConst(meth.Obj().Name()),
|
||||||
|
}
|
||||||
|
c.setType(v.Type())
|
||||||
|
v = fn.emit(&c)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invariant: v is a pointer, either
|
// Invariant: v is a pointer, either
|
||||||
|
@ -280,7 +298,6 @@ func makeWrapper(prog *Program, meth *types.Selection) *Function {
|
||||||
// Load) in preference to value extraction (Field possibly
|
// Load) in preference to value extraction (Field possibly
|
||||||
// preceded by Load).
|
// preceded by Load).
|
||||||
|
|
||||||
indices := meth.Index()
|
|
||||||
v = emitImplicitSelections(fn, v, indices[:len(indices)-1])
|
v = emitImplicitSelections(fn, v, indices[:len(indices)-1])
|
||||||
|
|
||||||
// Invariant: v is a pointer, either
|
// Invariant: v is a pointer, either
|
||||||
|
|
|
@ -440,13 +440,22 @@ type Global struct {
|
||||||
// Builtins are immutable values. Builtins do not have addresses.
|
// Builtins are immutable values. Builtins do not have addresses.
|
||||||
// Builtins can only appear in CallCommon.Func.
|
// Builtins can only appear in CallCommon.Func.
|
||||||
//
|
//
|
||||||
// Object() returns a *types.Builtin.
|
// Name() indicates the function: one of the built-in functions from the
|
||||||
|
// Go spec (excluding "make" and "new") or one of these ssa-defined
|
||||||
|
// intrinsics:
|
||||||
|
//
|
||||||
|
// // wrapnilchk returns ptr if non-nil, panics otherwise.
|
||||||
|
// // (For use in indirection wrappers.)
|
||||||
|
// func ssa:wrapnilchk(ptr *T, recvType, methodName string) *T
|
||||||
|
//
|
||||||
|
// Object() returns a *types.Builtin for built-ins defined by the spec,
|
||||||
|
// nil for others.
|
||||||
//
|
//
|
||||||
// Type() returns a *types.Signature representing the effective
|
// Type() returns a *types.Signature representing the effective
|
||||||
// signature of the built-in for this call.
|
// signature of the built-in for this call.
|
||||||
//
|
//
|
||||||
type Builtin struct {
|
type Builtin struct {
|
||||||
object *types.Builtin // canonical types.Universe object for this built-in
|
name string
|
||||||
sig *types.Signature
|
sig *types.Signature
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1382,10 +1391,10 @@ func (s *Defer) Value() *Call { return nil }
|
||||||
func (s *Go) Value() *Call { return nil }
|
func (s *Go) Value() *Call { return nil }
|
||||||
|
|
||||||
func (v *Builtin) Type() types.Type { return v.sig }
|
func (v *Builtin) Type() types.Type { return v.sig }
|
||||||
func (v *Builtin) Name() string { return v.object.Name() }
|
func (v *Builtin) Name() string { return v.name }
|
||||||
func (*Builtin) Referrers() *[]Instruction { return nil }
|
func (*Builtin) Referrers() *[]Instruction { return nil }
|
||||||
func (v *Builtin) Pos() token.Pos { return token.NoPos }
|
func (v *Builtin) Pos() token.Pos { return token.NoPos }
|
||||||
func (v *Builtin) Object() types.Object { return v.object }
|
func (v *Builtin) Object() types.Object { return types.Universe.Lookup(v.name) }
|
||||||
func (v *Builtin) Parent() *Function { return nil }
|
func (v *Builtin) Parent() *Function { return nil }
|
||||||
|
|
||||||
func (v *FreeVar) Type() types.Type { return v.typ }
|
func (v *FreeVar) Type() types.Type { return v.typ }
|
||||||
|
|
|
@ -14,7 +14,6 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.google.com/p/go.tools/go/exact"
|
|
||||||
"code.google.com/p/go.tools/go/types"
|
"code.google.com/p/go.tools/go/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -167,7 +166,6 @@ func testMainSlice(fn *Function, expfuncs []*Function, prefix string, slice type
|
||||||
return nilConst(slice)
|
return nilConst(slice)
|
||||||
}
|
}
|
||||||
|
|
||||||
tString := types.Typ[types.String]
|
|
||||||
tPtrString := types.NewPointer(tString)
|
tPtrString := types.NewPointer(tString)
|
||||||
tPtrElem := types.NewPointer(tElem)
|
tPtrElem := types.NewPointer(tElem)
|
||||||
tPtrFunc := types.NewPointer(tFunc)
|
tPtrFunc := types.NewPointer(tFunc)
|
||||||
|
@ -188,7 +186,7 @@ func testMainSlice(fn *Function, expfuncs []*Function, prefix string, slice type
|
||||||
pname := fn.emit(fa)
|
pname := fn.emit(fa)
|
||||||
|
|
||||||
// Emit: *pname = "testfunc"
|
// Emit: *pname = "testfunc"
|
||||||
emitStore(fn, pname, NewConst(exact.MakeString(testfunc.Name()), tString))
|
emitStore(fn, pname, stringConst(testfunc.Name()))
|
||||||
|
|
||||||
// Emit: pfunc = &pitem.F
|
// Emit: pfunc = &pitem.F
|
||||||
fa = &FieldAddr{X: pitem, Field: 1} // .F
|
fa = &FieldAddr{X: pitem, Field: 1} // .F
|
||||||
|
|
|
@ -108,16 +108,18 @@ func newVar(name string, typ types.Type) *types.Var {
|
||||||
return types.NewParam(token.NoPos, nil, name, typ)
|
return types.NewParam(token.NoPos, nil, name, typ)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
// anonVar creates an anonymous 'var' for use in a types.Tuple.
|
||||||
lenObject = types.Universe.Lookup("len").(*types.Builtin)
|
func anonVar(typ types.Type) *types.Var {
|
||||||
lenResults = types.NewTuple(newVar("", tInt))
|
return newVar("", typ)
|
||||||
)
|
}
|
||||||
|
|
||||||
|
var lenResults = types.NewTuple(anonVar(tInt))
|
||||||
|
|
||||||
// makeLen returns the len builtin specialized to type func(T)int.
|
// makeLen returns the len builtin specialized to type func(T)int.
|
||||||
func makeLen(T types.Type) *Builtin {
|
func makeLen(T types.Type) *Builtin {
|
||||||
lenParams := types.NewTuple(newVar("", T))
|
lenParams := types.NewTuple(anonVar(T))
|
||||||
return &Builtin{
|
return &Builtin{
|
||||||
object: lenObject,
|
name: "len",
|
||||||
sig: types.NewSignature(nil, nil, lenParams, lenResults, false),
|
sig: types.NewSignature(nil, nil, lenParams, lenResults, false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue