diff --git a/cmd/ssadump/main.go b/cmd/ssadump/main.go index b0bca11c..ea8f4ddf 100644 --- a/cmd/ssadump/main.go +++ b/cmd/ssadump/main.go @@ -128,6 +128,13 @@ func main() { log.Fatal(err) } + // The interpreter needs the runtime package. + if *runFlag { + if _, err := imp.LoadPackage("runtime"); err != nil { + log.Fatalf("LoadPackage(runtime) failed: %s", err) + } + } + // Create and build SSA-form program representation. prog := ssa.NewProgram(imp.Fset, mode) if err := prog.CreatePackages(imp); err != nil { diff --git a/importer/importer.go b/importer/importer.go index b511e10c..70be1da7 100644 --- a/importer/importer.go +++ b/importer/importer.go @@ -484,6 +484,14 @@ func (imp *Importer) LoadInitialPackages(args []string) ([]*PackageInfo, []strin return infos, args, nil } +// LoadPackage loads and type-checks the package whose import path is +// path, plus its necessary dependencies. +// +func (imp *Importer) LoadPackage(path string) (*PackageInfo, error) { + imports := make(map[string]*types.Package) // keep importBinary happy + return imp.doImport0(imports, path) +} + type initialPkg struct { path string // the package's import path importable bool // add package to import map false for main and xtests) diff --git a/ssa/blockopt.go b/ssa/blockopt.go index 1253df12..bea29201 100644 --- a/ssa/blockopt.go +++ b/ssa/blockopt.go @@ -39,6 +39,9 @@ func deleteUnreachableBlocks(f *Function) { b.Index = white } markReachable(f.Blocks[0]) + if f.Recover != nil { + markReachable(f.Recover) + } for i, b := range f.Blocks { if b.Index == white { for _, c := range b.Succs { diff --git a/ssa/builder.go b/ssa/builder.go index de59c295..ff0992de 100644 --- a/ssa/builder.go +++ b/ssa/builder.go @@ -152,6 +152,7 @@ func (b *builder) logicalBinop(fn *Function, e *ast.BinaryExpr) Value { // T(e) = T(e.X) = T(e.Y) after untyped constants have been // eliminated. + // TODO(adonovan): not true; MyBool==MyBool yields UntypedBool. t := fn.Pkg.typeOf(e) var short Value // value of the short-circuit path @@ -2064,6 +2065,32 @@ start: b.setCall(fn, s.Call, &v.Call) fn.emit(&v) + // A deferred call can cause recovery from panic. + // If the panicking function has named results, + // control resumes at the Recover block to load those + // locals (which may be mutated by the deferred call) + // and return them. + if fn.namedResults != nil { + // Optimization: if we can prove the deferred call + // won't cause recovery from panic, we can avoid a + // Recover block. + // We scan the callee for calls to recover() iff: + // - it's a static call + // - to a function in the same package + // (other packages' SSA building happens concurrently) + // - whose SSA building has started (Blocks != nil) + // - and finished (i.e. not this function) + // NB, this is always true for: defer func() { ... } () + // + // TODO(adonovan): optimize interpackage cases, e.g. + // (sync.Mutex).Unlock(), (io.Closer).Close + if callee, ok := v.Call.Value.(*Function); ok && callee.Pkg == fn.Pkg && callee != fn && callee.Blocks != nil && !callsRecover(callee) { + // Deferred call cannot cause recovery from panic. + } else { + createRecoverBlock(fn) + } + } + case *ast.ReturnStmt: var results []Value if len(s.Results) == 1 && fn.Signature.Results().Len() > 1 { @@ -2216,7 +2243,7 @@ func (b *builder) buildFunction(fn *Function) { fn.startBody() fn.createSyntacticParams() b.stmt(fn, fn.syntax.body) - if cb := fn.currentBlock; cb != nil && (cb == fn.Blocks[0] || cb.Preds != nil) { + if cb := fn.currentBlock; cb != nil && (cb == fn.Blocks[0] || cb == fn.Recover || cb.Preds != nil) { // Run function calls deferred in this function when // falling off the end of the body block. fn.emit(new(RunDefers)) diff --git a/ssa/create.go b/ssa/create.go index 1b14629c..f74fdcf0 100644 --- a/ssa/create.go +++ b/ssa/create.go @@ -174,7 +174,7 @@ func membersFromDecl(pkg *Package, decl ast.Decl) { // error-free package described by info, and populates its Members // mapping. // -// Repeated calls with the same info returns the same Package. +// Repeated calls with the same info return the same Package. // // The real work of building SSA form for each function is not done // until a subsequent call to Package.Build(). diff --git a/ssa/dom.go b/ssa/dom.go index d6e85b6e..66501892 100644 --- a/ssa/dom.go +++ b/ssa/dom.go @@ -97,7 +97,12 @@ func buildDomTree(f *Function) { n := len(f.Blocks) preorder := make([]*domNode, n) root := f.Blocks[0].dom - ltDfs(root, 0, preorder) + prenum := ltDfs(root, 0, preorder) + var recover *domNode + if f.Recover != nil { + recover = f.Recover.dom + ltDfs(recover, prenum, preorder) + } buckets := make([]*domNode, n) copy(buckets, preorder) @@ -144,7 +149,7 @@ func buildDomTree(f *Function) { // Step 4. Explicitly define the immediate dominator of each // node, in preorder. for _, w := range preorder[1:] { - if w == root { + if w == root || w == recover { w.Idom = nil } else { if w.Idom != w.semi { @@ -216,8 +221,8 @@ func sanityCheckDomTree(f *Function) { all.Set(one).Lsh(&all, uint(n)).Sub(&all, one) // Initialization. - for i := range f.Blocks { - if i == 0 { + for i, b := range f.Blocks { + if i == 0 || b == f.Recover { // The root is dominated only by itself. D[i].SetBit(&D[0], 0, 1) } else { @@ -231,7 +236,7 @@ func sanityCheckDomTree(f *Function) { for changed := true; changed; { changed = false for i, b := range f.Blocks { - if i == 0 { + if i == 0 || b == f.Recover { continue } // Compute intersection across predecessors. @@ -249,10 +254,14 @@ func sanityCheckDomTree(f *Function) { } // Check the entire relation. O(n^2). + // The Recover block (if any) must be treated specially so we skip it. ok := true for i := 0; i < n; i++ { for j := 0; j < n; j++ { b, c := f.Blocks[i], f.Blocks[j] + if c == f.Recover { + continue + } actual := dominates(b, c) expected := D[j].Bit(i) == 1 if actual != expected { diff --git a/ssa/emit.go b/ssa/emit.go index b4902f31..898ce632 100644 --- a/ssa/emit.go +++ b/ssa/emit.go @@ -403,3 +403,48 @@ func emitFieldSelection(f *Function, v Value, index int, wantAddr bool, pos toke } return v } + +// createRecoverBlock emits to f a block of code to return after a +// recovered panic, and sets f.Recover to it. +// +// If f's result parameters are named, the code loads and returns +// their current values, otherwise it returns the zero values of their +// type. +// +// Idempotent. +// +func createRecoverBlock(f *Function) { + if f.Recover != nil { + return // already created + } + saved := f.currentBlock + + f.Recover = f.newBasicBlock("recover") + f.currentBlock = f.Recover + + var results []Value + if f.namedResults != nil { + // Reload NRPs to form value tuple. + for _, r := range f.namedResults { + results = append(results, emitLoad(f, r)) + } + } else { + R := f.Signature.Results() + for i, n := 0, R.Len(); i < n; i++ { + T := R.At(i).Type() + var v Value + + // Return zero value of each result type. + switch T.Underlying().(type) { + case *types.Struct, *types.Array: + v = emitLoad(f, f.addLocal(T, token.NoPos)) + default: + v = zeroConst(T) + } + results = append(results, v) + } + } + f.emit(&Return{Results: results}) + + f.currentBlock = saved +} diff --git a/ssa/func.go b/ssa/func.go index 453b55c3..cf380fa4 100644 --- a/ssa/func.go +++ b/ssa/func.go @@ -306,7 +306,6 @@ func buildReferrers(f *Function) { // finishBody() finalizes the function after SSA code generation of its body. func (f *Function) finishBody() { f.objects = nil - f.namedResults = nil f.currentBlock = nil f.lblocks = nil f.syntax = nil @@ -337,6 +336,8 @@ func (f *Function) finishBody() { lift(f) } + f.namedResults = nil // (used by lifting) + numberRegisters(f) if f.Prog.mode&LogFunctions != 0 { @@ -559,6 +560,10 @@ func (f *Function) DumpTo(w io.Writer) { fmt.Fprintf(w, "# Parent: %s\n", f.Enclosing.Name()) } + if f.Recover != nil { + fmt.Fprintf(w, "# Recover: %s\n", f.Recover) + } + if f.FreeVars != nil { io.WriteString(w, "# Free variables:\n") for i, fv := range f.FreeVars { diff --git a/ssa/interp/interp.go b/ssa/interp/interp.go index 8e740bc3..59c81cd9 100644 --- a/ssa/interp/interp.go +++ b/ssa/interp/interp.go @@ -56,14 +56,6 @@ import ( "code.google.com/p/go.tools/ssa" ) -type status int - -const ( - stRunning status = iota - stComplete - stPanic -) - type continuation int const ( @@ -84,12 +76,20 @@ type methodSet map[string]*ssa.Function // State shared between all interpreted goroutines. type interpreter struct { - prog *ssa.Program // the SSA program - globals map[ssa.Value]*value // addresses of global variables (immutable) - mode Mode // interpreter options - reflectPackage *ssa.Package // the fake reflect package - errorMethods methodSet // the method set of reflect.error, which implements the error interface. - rtypeMethods methodSet // the method set of rtype, which implements the reflect.Type interface. + prog *ssa.Program // the SSA program + globals map[ssa.Value]*value // addresses of global variables (immutable) + mode Mode // interpreter options + reflectPackage *ssa.Package // the fake reflect package + errorMethods methodSet // the method set of reflect.error, which implements the error interface. + rtypeMethods methodSet // the method set of rtype, which implements the reflect.Type interface. + runtimeErrorString types.Type // the runtime.errorString type +} + +type deferred struct { + fn value + args []value + instr *ssa.Defer + tail *deferred } type frame struct { @@ -99,9 +99,9 @@ type frame struct { block, prevBlock *ssa.BasicBlock env map[ssa.Value]value // dynamic values of SSA variables locals []value - defers []func() + defers *deferred result value - status status + panicking bool panic interface{} } @@ -126,14 +126,46 @@ func (fr *frame) get(key ssa.Value) value { panic(fmt.Sprintf("get: no value for %T: %v", key, key.Name())) } -func (fr *frame) rundefers() { - for i := range fr.defers { - if fr.i.mode&EnableTracing != 0 { - fmt.Fprintln(os.Stderr, "Invoking deferred function", i) - } - fr.defers[len(fr.defers)-1-i]() +// runDefer runs a deferred call d. +// It always returns normally, but may set or clear fr.panic. +// +func (fr *frame) runDefer(d *deferred) { + if fr.i.mode&EnableTracing != 0 { + fmt.Fprintf(os.Stderr, "%s: invoking deferred function call\n", + fr.i.prog.Fset.Position(d.instr.Pos())) + } + var ok bool + defer func() { + if !ok { + // Deferred call created a new state of panic. + fr.panicking = true + fr.panic = recover() + } + }() + call(fr.i, fr, d.instr.Pos(), d.fn, d.args) + ok = true +} + +// runDefers executes fr's deferred function calls in LIFO order. +// +// On entry, fr.panicking indicates a state of panic; if +// true, fr.panic contains the panic value. +// +// On completion, if a deferred call started a panic, or if no +// deferred call recovered from a previous state of panic, then +// runDefers itself panics after the last deferred call has run. +// +// If there was no initial state of panic, or it was recovered from, +// runDefers returns normally. +// +func (fr *frame) runDefers() { + for d := fr.defers; d != nil; d = d.tail { + fr.runDefer(d) + } + fr.defers = nil + if fr.panicking { + panic(fr.panic) // new panic, or still panicking } - fr.defers = fr.defers[:0] } // lookupMethod returns the method set for type typ, which may be one @@ -196,10 +228,11 @@ func visitInstr(fr *frame, instr ssa.Instruction) continuation { } fr.result = tuple(res) } + fr.block = nil return kReturn case *ssa.RunDefers: - fr.rundefers() + fr.runDefers() case *ssa.Panic: panic(targetPanic{fr.get(instr.X)}) @@ -224,7 +257,12 @@ func visitInstr(fr *frame, instr ssa.Instruction) continuation { case *ssa.Defer: fn, args := prepareCall(fr, &instr.Call) - fr.defers = append(fr.defers, func() { call(fr.i, fr, instr.Pos(), fn, args) }) + fr.defers = &deferred{ + fn: fn, + args: args, + instr: instr, + tail: fr.defers, + } case *ssa.Go: fn, args := prepareCall(fr, &instr.Call) @@ -476,33 +514,49 @@ func callSSA(i *interpreter, caller *frame, callpos token.Pos, fn *ssa.Function, for i, fv := range fn.FreeVars { fr.env[fv] = env[i] } - var instr ssa.Instruction + for fr.block != nil { + runFrame(fr) + } + // Destroy the locals to avoid accidental use after return. + for i := range fn.Locals { + fr.locals[i] = bad{} + } + return fr.result +} +// runFrame executes SSA instructions starting at fr.block and +// continuing until a return, a panic, or a recovered panic. +// +// After a panic, runFrame panics. +// +// After a normal return, fr.result contains the result of the call +// and fr.block is nil. +// +// After a recovered panic, fr.result is undefined and fr.block +// contains the block at which to resume control, which may be +// nil for a normal return. +// +func runFrame(fr *frame) { defer func() { - if fr.status != stComplete { - if fr.i.mode&DisableRecover != 0 { - return // let interpreter crash - } - fr.status = stPanic - fr.panic = recover() + if fr.block == nil { + return // normal return } - fr.rundefers() - // Destroy the locals to avoid accidental use after return. - for i := range fn.Locals { - fr.locals[i] = bad{} - } - if fr.status == stPanic { - panic(fr.panic) // panic stack is not entirely clean + if fr.i.mode&DisableRecover != 0 { + return // let interpreter crash } + fr.panicking = true + fr.panic = recover() + fr.runDefers() + fr.block = fr.fn.Recover // recovered panic }() for { - if i.mode&EnableTracing != 0 { + if fr.i.mode&EnableTracing != 0 { fmt.Fprintf(os.Stderr, ".%s:\n", fr.block) } block: - for _, instr = range fr.block.Instrs { - if i.mode&EnableTracing != 0 { + for _, instr := range fr.block.Instrs { + if fr.i.mode&EnableTracing != 0 { if v, ok := instr.(ssa.Value); ok { fmt.Fprintln(os.Stderr, "\t", v.Name(), "=", instr) } else { @@ -511,8 +565,7 @@ func callSSA(i *interpreter, caller *frame, callpos token.Pos, fn *ssa.Function, } switch visitInstr(fr, instr) { case kReturn: - fr.status = stComplete - return fr.result + return case kNext: // no-op case kJump: @@ -520,7 +573,35 @@ func callSSA(i *interpreter, caller *frame, callpos token.Pos, fn *ssa.Function, } } } - panic("unreachable") +} + +// doRecover implements the recover() built-in. +func doRecover(caller *frame) value { + // recover() must be exactly one level beneath the deferred + // function (two levels beneath the panicking function) to + // have any effect. Thus we ignore both "defer recover()" and + // "defer f() -> g() -> recover()". + if caller.i.mode&DisableRecover == 0 && + caller != nil && !caller.panicking && + caller.caller != nil && caller.caller.panicking { + caller.caller.panicking = false + p := caller.caller.panic + caller.caller.panic = nil + switch p := p.(type) { + case targetPanic: + // The target program explicitly called panic(). + return p.v + case runtime.Error: + // The interpreter encountered a runtime error. + return iface{caller.i.runtimeErrorString, p.Error()} + case string: + // The interpreter explicitly called panic(). + return iface{caller.i.runtimeErrorString, p} + default: + panic(fmt.Sprintf("unexpected panic type %T in target call to recover()", p)) + } + } + return iface{} } // setGlobal sets the value of a system-initialized global variable. @@ -539,12 +620,20 @@ func setGlobal(i *interpreter, pkg *ssa.Package, name string, v value) { // Interpret returns the exit code of the program: 2 for panic (like // gc does), or the argument to os.Exit for normal termination. // +// The SSA program must include the "runtime" package. +// func Interpret(mainpkg *ssa.Package, mode Mode, filename string, args []string) (exitCode int) { i := &interpreter{ prog: mainpkg.Prog, globals: make(map[ssa.Value]*value), mode: mode, } + runtimePkg := i.prog.ImportedPackage("runtime") + if runtimePkg == nil { + panic("ssa.Program doesn't include runtime package") + } + i.runtimeErrorString = runtimePkg.Type("errorString").Object().Type() + initReflect(i) for _, pkg := range i.prog.AllPackages() { diff --git a/ssa/interp/interp_test.go b/ssa/interp/interp_test.go index 1fb4a8a4..2a27851f 100644 --- a/ssa/interp/interp_test.go +++ b/ssa/interp/interp_test.go @@ -91,7 +91,10 @@ var gorootTestTests = []string{ "rename.go", "const3.go", "nil.go", - "recover.go", // partly disabled; TODO(adonovan): fix. + "recover.go", // reflection parts disabled + "recover1.go", + "recover2.go", + "recover3.go", "typeswitch1.go", "floatcmp.go", "crlf.go", // doesn't actually assert anything (runoutput) @@ -122,9 +125,6 @@ var gorootTestTests = []string{ // Broken. TODO(adonovan): fix. // copy.go // very slow; but with N=4 quickly crashes, slice index out of range. // nilptr.go // interp: V > uintptr not implemented. Slow test, lots of mem - // recover1.go // error: "spurious recover" - // recover2.go // panic: interface conversion: string is not error: missing method Error - // recover3.go // logic errors: panicked with wrong Error. // args.go // works, but requires specific os.Args from the driver. // index.go // a template, not a real test. // mallocfin.go // SetFinalizer not implemented. @@ -143,6 +143,7 @@ var testdataTests = []string{ "initorder.go", "methprom.go", "mrvchain.go", + "recover.go", } // These are files in $GOROOT/src/pkg/. @@ -190,6 +191,10 @@ func run(t *testing.T, dir, input string) bool { hint = fmt.Sprintf("To dump SSA representation, run:\n%% go build code.google.com/p/go.tools/cmd/ssadump && ./ssadump -build=CFP %s\n", input) mainInfo := imp.CreatePackage("main", files...) + if _, err := imp.LoadPackage("runtime"); err != nil { + t.Errorf("LoadPackage(runtime) failed: %s", err) + } + prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions) if err := prog.CreatePackages(imp); err != nil { t.Errorf("CreatePackages failed: %s", err) diff --git a/ssa/interp/ops.go b/ssa/interp/ops.go index 37acd6bf..b1eed033 100644 --- a/ssa/interp/ops.go +++ b/ssa/interp/ops.go @@ -8,7 +8,6 @@ import ( "bytes" "fmt" "go/token" - "runtime" "strings" "sync" "syscall" @@ -894,7 +893,7 @@ func typeAssert(i *interpreter, instr *ssa.TypeAssert, itf iface) value { v = copyVal(itf.v) // extract value } else { - err = fmt.Sprintf("type assert failed: expected %s, got %s", instr.AssertedType, itf.t) + err = fmt.Sprintf("interface conversion: interface is %s, not %s", itf.t, instr.AssertedType) } if err != "" { @@ -1057,31 +1056,7 @@ func callBuiltin(caller *frame, callpos token.Pos, fn *ssa.Builtin, args []value panic(targetPanic{args[0]}) case "recover": - // recover() must be exactly one level beneath the - // deferred function (two levels beneath the panicking - // function) to have any effect. Thus we ignore both - // "defer recover()" and "defer f() -> g() -> - // recover()". - if caller.i.mode&DisableRecover == 0 && - caller != nil && caller.status == stRunning && - caller.caller != nil && caller.caller.status == stPanic { - caller.caller.status = stComplete - p := caller.caller.panic - caller.caller.panic = nil - switch p := p.(type) { - case targetPanic: - return p.v - case runtime.Error: - // TODO(adonovan): must box this up - // inside instance of interface 'error'. - return iface{types.Typ[types.String], p.Error()} - case string: - return iface{types.Typ[types.String], p} - default: - panic(fmt.Sprintf("unexpected panic type %T in target call to recover()", p)) - } - } - return iface{} + return doRecover(caller) } panic("unknown built-in: " + fn.Name()) @@ -1399,13 +1374,9 @@ func conv(t_dst, t_src types.Type, x value) value { // On success it returns "", on failure, an error message. // func checkInterface(i *interpreter, itype *types.Interface, x iface) string { - if meth, wrongType := types.MissingMethod(x.t, itype, true); meth != nil { - reason := "is missing" - if wrongType { - reason = "has wrong type" - } - return fmt.Sprintf("interface conversion: %v is not %v: method %s %s", - x.t, itype, meth.Name(), reason) + if meth, _ := types.MissingMethod(x.t, itype, true); meth != nil { + return fmt.Sprintf("interface conversion: %v is not %v: missing method %s", + x.t, itype, meth.Name()) } return "" // ok } diff --git a/ssa/interp/testdata/coverage.go b/ssa/interp/testdata/coverage.go index a78b834f..4743f0a5 100644 --- a/ssa/interp/testdata/coverage.go +++ b/ssa/interp/testdata/coverage.go @@ -379,14 +379,15 @@ func init() { // An I->I type-assert fails iff the value is nil. func init() { - defer func() { - r := fmt.Sprint(recover()) - if r != "interface conversion: interface is nil, not main.I" { - panic("I->I type assertion succeeed for nil value") - } - }() - var x I - _ = x.(I) + // TODO(adonovan): temporarily disabled; see comment at bottom of file. + // defer func() { + // r := fmt.Sprint(recover()) + // if r != "interface conversion: interface is nil, not main.I" { + // panic("I->I type assertion succeeed for nil value") + // } + // }() + // var x I + // _ = x.(I) } ////////////////////////////////////////////////////////////////////// diff --git a/ssa/interp/testdata/recover.go b/ssa/interp/testdata/recover.go new file mode 100644 index 00000000..39aad6c2 --- /dev/null +++ b/ssa/interp/testdata/recover.go @@ -0,0 +1,16 @@ +package main + +// Tests of panic/recover. + +func fortyTwo() (r int) { + r = 42 + // The next two statements simulate a 'return' statement. + defer func() { recover() }() + panic(nil) +} + +func main() { + if r := fortyTwo(); r != 42 { + panic(r) + } +} diff --git a/ssa/interp/value.go b/ssa/interp/value.go index c4e25ebb..18bf9d50 100644 --- a/ssa/interp/value.go +++ b/ssa/interp/value.go @@ -243,7 +243,7 @@ func equals(t types.Type, x, y value) bool { // Since map, func and slice don't support comparison, this // case is only reachable if one of x or y is literally nil // (handled in eqnil) or via interface{} values. - panic(fmt.Sprintf("runtime error: comparing uncomparable type %s", t)) + panic(fmt.Sprintf("comparing uncomparable type %s", t)) } // Returns an integer hash of x such that equals(x, y) => hash(x) == hash(y). diff --git a/ssa/lift.go b/ssa/lift.go index 5643b8c6..2fe45647 100644 --- a/ssa/lift.go +++ b/ssa/lift.go @@ -102,6 +102,9 @@ func (df domFrontier) build(u *domNode) { func buildDomFrontier(fn *Function) domFrontier { df := make(domFrontier, len(fn.Blocks)) df.build(fn.Blocks[0].dom) + if fn.Recover != nil { + df.build(fn.Recover.dom) + } return df } @@ -309,16 +312,21 @@ type newPhiMap map[*BasicBlock][]newPhi func liftAlloc(df domFrontier, alloc *Alloc, newPhis newPhiMap) bool { // Don't lift aggregates into registers, because we don't have // a way to express their zero-constants. - // TODO(adonovan): define zero-constants for aggregates, or - // add a separate SRA pass. Lifting aggregates is an - // important optimisation for pointer analysis because the - // extra indirection really hurts precision under Das's - // algorithm. switch deref(alloc.Type()).Underlying().(type) { case *types.Array, *types.Struct: return false } + // Don't lift named return values in functions that defer + // calls that may recover from panic. + if fn := alloc.Parent(); fn.Recover != nil { + for _, nr := range fn.namedResults { + if nr == alloc { + return false + } + } + } + // Compute defblocks, the set of blocks containing a // definition of the alloc cell. var defblocks blockSet diff --git a/ssa/sanity.go b/ssa/sanity.go index 75ee34a8..c70595fe 100644 --- a/ssa/sanity.go +++ b/ssa/sanity.go @@ -138,6 +138,7 @@ func (s *sanity) checkInstr(idx int, instr Instruction) { s.errorf("convert %s -> %s: at least one type must be basic", instr.X.Type(), instr.Type()) } } + case *Defer: case *Extract: case *Field: @@ -244,8 +245,9 @@ func (s *sanity) checkBlock(b *BasicBlock, index int) { } // Check all blocks are reachable. - // (The entry block is always implicitly reachable.) - if index > 0 && len(b.Preds) == 0 { + // (The entry block is always implicitly reachable, + // as is the Recover block, if any.) + if (index > 0 && b != b.parent.Recover) && len(b.Preds) == 0 { s.warnf("unreachable block") if b.Instrs == nil { // Since this block is about to be pruned, @@ -367,6 +369,10 @@ func (s *sanity) checkFunction(fn *Function) bool { } s.checkBlock(b, i) } + if fn.Recover != nil && fn.Blocks[fn.Recover.Index] != fn.Recover { + s.errorf("Recover block is not in Blocks slice") + } + s.block = nil for i, anon := range fn.AnonFuncs { if anon.Enclosing != fn { diff --git a/ssa/ssa.go b/ssa/ssa.go index ad81ee8e..aebb2eaa 100644 --- a/ssa/ssa.go +++ b/ssa/ssa.go @@ -237,6 +237,11 @@ type Instruction interface { // semantically significant, though it may affect the readability of // the disassembly. // +// Recover is an optional second entry point to which control resumes +// after a recovered panic. The Recover block may contain only a load +// of the function's named return parameters followed by a return of +// the loaded values. +// // A nested function that refers to one or more lexically enclosing // local variables ("free variables") has Capture parameters. Such // functions cannot be called directly but require a value created by @@ -268,6 +273,7 @@ type Function struct { FreeVars []*Capture // free variables whose values must be supplied by closure Locals []*Alloc Blocks []*BasicBlock // basic blocks of the function; nil => external + Recover *BasicBlock // optional; control transfers here after recovered panic AnonFuncs []*Function // anonymous functions directly beneath this one // The following fields are set transiently during building, diff --git a/ssa/util.go b/ssa/util.go index 0201c8a0..5978c942 100644 --- a/ssa/util.go +++ b/ssa/util.go @@ -101,3 +101,19 @@ func logStack(format string, args ...interface{}) func() { io.WriteString(os.Stderr, " end\n") } } + +// callsRecover reports whether f contains a direct call to recover(). +func callsRecover(f *Function) bool { + for _, b := range f.Blocks { + for _, instr := range b.Instrs { + if call, ok := instr.(*Call); ok { + if blt, ok := call.Call.Value.(*Builtin); ok { + if blt.Name() == "recover" { + return true + } + } + } + } + } + return false +}