go.tools/ssa: implement correct control flow for recovered panic.
A function such as this: func one() (x int) { defer func() { recover() }() x = 1 panic("return") } that combines named return parameters (NRPs) with deferred calls that call recover, may return non-zero values despite the fact it doesn't even contain a return statement. (!) This requires a change to the SSA API: all functions' control-flow graphs now have a second entry point, called Recover, which is the block at which control flow resumes after a recovered panic. The Recover block simply loads the NRPs and returns them. As an optimization, most functions don't need a Recover block, so it is omitted. In fact it is only needed for functions that have NRPs and defer a call to another function that _may_ call recover. Dataflow analysis of SSA now requires extra work, since every may-panic instruction has an implicit control-flow edge to the Recover block. The only dataflow analysis so far implemented is SSA renaming, for which we make the following simplifying assumption: the Recover block only loads the NRPs and returns. This means we don't really need to analyze it, we can just skip the "lifting" of such NRPs. We also special-case the Recover block in the dominance computation. Rejected alternative approaches: - Specifying a Recover block for every defer instruction (like a traditional exception handler). This seemed like excessive generality, since Go programs only need the same degenerate form of Recover block. - Adding an instruction to set the Recover block immediately after the named return values are set up, so that dominance can be computed without special-casing. This didn't seem worth the effort. Interpreter: - This CL completely reimplements the panic/recover/ defer logic in the interpreter. It's clearer and simpler and closer to the model in the spec. - Some runtime panic messages have been changed to be closer to gc's, since tests depend on it. - The interpreter now requires that the runtime.runtimeError type be part of the SSA program. This requires that clients import this package prior to invoking the interpreter. This in turn requires (Importer).ImportPackage(path string), which this CL adds. - All $GOROOT/test/recover{,1,2,3}.go tests are now passing. NB, the bug described in coverage.go (defer/recover in a concatenated init function) remains. Will be fixed in a follow-up. Fixes golang/go#6381 R=gri CC=crawshaw, golang-dev https://golang.org/cl/13844043
This commit is contained in:
parent
7aabe2e113
commit
2accef29d7
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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().
|
||||
|
|
19
ssa/dom.go
19
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 {
|
||||
|
|
45
ssa/emit.go
45
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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 (
|
||||
|
@ -90,6 +82,14 @@ type interpreter struct {
|
|||
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 {
|
||||
// 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.Fprintln(os.Stderr, "Invoking deferred function", i)
|
||||
fmt.Fprintf(os.Stderr, "%s: invoking deferred function call\n",
|
||||
fr.i.prog.Fset.Position(d.instr.Pos()))
|
||||
}
|
||||
fr.defers[len(fr.defers)-1-i]()
|
||||
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
|
||||
|
||||
defer func() {
|
||||
if fr.status != stComplete {
|
||||
if fr.i.mode&DisableRecover != 0 {
|
||||
return // let interpreter crash
|
||||
for fr.block != nil {
|
||||
runFrame(fr)
|
||||
}
|
||||
fr.status = stPanic
|
||||
fr.panic = recover()
|
||||
}
|
||||
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
|
||||
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.block == nil {
|
||||
return // normal return
|
||||
}
|
||||
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() {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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).
|
||||
|
|
18
ssa/lift.go
18
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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
16
ssa/util.go
16
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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue