diff --git a/go/pointer/gen.go b/go/pointer/gen.go index 850591fa..bcb7448b 100644 --- a/go/pointer/gen.go +++ b/go/pointer/gen.go @@ -531,7 +531,7 @@ func (a *analysis) genAppend(instr *ssa.Call, cgn *cgnode) { // genBuiltinCall generates contraints for a call to a built-in. func (a *analysis) genBuiltinCall(instr ssa.CallInstruction, cgn *cgnode) { call := instr.Common() - switch call.Value.(*ssa.Builtin).Object().Name() { + switch call.Value.(*ssa.Builtin).Name() { case "append": // Safe cast: append cannot appear in a go or defer statement. 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. a.valueNode(call.Args[0]) + case "ssa:wrapnilchk": + a.copy(a.valueNode(instr.Value()), a.valueNode(call.Args[0]), 1) + default: // No-ops: close len cap real imag complex print println delete. } diff --git a/go/ssa/builder.go b/go/ssa/builder.go index 4007dc1c..3249a728 100644 --- a/go/ssa/builder.go +++ b/go/ssa/builder.go @@ -57,6 +57,7 @@ var ( tByte = types.Typ[types.Byte] tInt = types.Typ[types.Int] tInvalid = types.Typ[types.Invalid] + tString = types.Typ[types.String] tUntypedNil = types.Typ[types.UntypedNil] tRangeIter = &opaqueType{nil, "iter"} // the type of all "range" iterators tEface = new(types.Interface) @@ -597,7 +598,7 @@ func (b *builder) expr0(fn *Function, e ast.Expr) Value { // Universal built-in or nil? switch obj := obj.(type) { 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: 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 { if st.Dir == types.RecvOnly { tElem := st.Chan.Type().Underlying().(*types.Chan).Elem() - vars = append(vars, newVar("", tElem)) + vars = append(vars, anonVar(tElem)) } } 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. // (This should really be a runtime.errorString, not a string.) 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") } diff --git a/go/ssa/const.go b/go/ssa/const.go index 1a4a4d0c..44a1ee78 100644 --- a/go/ssa/const.go +++ b/go/ssa/const.go @@ -25,7 +25,7 @@ func NewConst(val exact.Value, typ types.Type) *Const { // intConst returns an 'int' constant that evaluates to i. // (i is an int64 in case the host is narrower than the target.) 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 @@ -35,6 +35,11 @@ func nilConst(typ types.Type) *Const { 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, // which must not be an array or struct type: the zero values of // aggregates are well-defined but cannot be represented by Const. diff --git a/go/ssa/interp/ops.go b/go/ssa/interp/ops.go index 5452285a..86aaee0e 100644 --- a/go/ssa/interp/ops.go +++ b/go/ssa/interp/ops.go @@ -1059,6 +1059,16 @@ func callBuiltin(caller *frame, callpos token.Pos, fn *ssa.Builtin, args []value case "recover": 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()) diff --git a/go/ssa/interp/testdata/coverage.go b/go/ssa/interp/testdata/coverage.go index ca6b1748..3ad49f20 100644 --- a/go/ssa/interp/testdata/coverage.go +++ b/go/ssa/interp/testdata/coverage.go @@ -673,3 +673,20 @@ func init() { 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") +} diff --git a/go/ssa/promote.go b/go/ssa/promote.go index fa8c755b..f1104361 100644 --- a/go/ssa/promote.go +++ b/go/ssa/promote.go @@ -264,12 +264,30 @@ func makeWrapper(prog *Program, meth *types.Selection) *Function { fn.addSpilledParam(recv) createParams(fn, start) + indices := meth.Index() + var v Value = fn.Locals[0] // spilled receiver 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) + + // 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 @@ -280,7 +298,6 @@ func makeWrapper(prog *Program, meth *types.Selection) *Function { // Load) in preference to value extraction (Field possibly // preceded by Load). - indices := meth.Index() v = emitImplicitSelections(fn, v, indices[:len(indices)-1]) // Invariant: v is a pointer, either diff --git a/go/ssa/ssa.go b/go/ssa/ssa.go index 404bf92e..b1e582f3 100644 --- a/go/ssa/ssa.go +++ b/go/ssa/ssa.go @@ -440,14 +440,23 @@ type Global struct { // Builtins are immutable values. Builtins do not have addresses. // 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 // signature of the built-in for this call. // type Builtin struct { - object *types.Builtin // canonical types.Universe object for this built-in - sig *types.Signature + name string + sig *types.Signature } // Value-defining instructions ---------------------------------------- @@ -1382,10 +1391,10 @@ func (s *Defer) Value() *Call { return nil } func (s *Go) Value() *Call { return nil } 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 (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 *FreeVar) Type() types.Type { return v.typ } diff --git a/go/ssa/testmain.go b/go/ssa/testmain.go index a5d8a826..448338b0 100644 --- a/go/ssa/testmain.go +++ b/go/ssa/testmain.go @@ -14,7 +14,6 @@ import ( "os" "strings" - "code.google.com/p/go.tools/go/exact" "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) } - tString := types.Typ[types.String] tPtrString := types.NewPointer(tString) tPtrElem := types.NewPointer(tElem) tPtrFunc := types.NewPointer(tFunc) @@ -188,7 +186,7 @@ func testMainSlice(fn *Function, expfuncs []*Function, prefix string, slice type pname := fn.emit(fa) // Emit: *pname = "testfunc" - emitStore(fn, pname, NewConst(exact.MakeString(testfunc.Name()), tString)) + emitStore(fn, pname, stringConst(testfunc.Name())) // Emit: pfunc = &pitem.F fa = &FieldAddr{X: pitem, Field: 1} // .F diff --git a/go/ssa/util.go b/go/ssa/util.go index 56220aba..d44ddce3 100644 --- a/go/ssa/util.go +++ b/go/ssa/util.go @@ -108,16 +108,18 @@ func newVar(name string, typ types.Type) *types.Var { return types.NewParam(token.NoPos, nil, name, typ) } -var ( - lenObject = types.Universe.Lookup("len").(*types.Builtin) - lenResults = types.NewTuple(newVar("", tInt)) -) +// anonVar creates an anonymous 'var' for use in a types.Tuple. +func anonVar(typ types.Type) *types.Var { + return newVar("", typ) +} + +var lenResults = types.NewTuple(anonVar(tInt)) // makeLen returns the len builtin specialized to type func(T)int. func makeLen(T types.Type) *Builtin { - lenParams := types.NewTuple(newVar("", T)) + lenParams := types.NewTuple(anonVar(T)) return &Builtin{ - object: lenObject, - sig: types.NewSignature(nil, nil, lenParams, lenResults, false), + name: "len", + sig: types.NewSignature(nil, nil, lenParams, lenResults, false), } }