// Copyright 2016 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. package cfg import ( "bytes" "fmt" "go/ast" "go/parser" "go/token" "testing" ) const src = `package main import "log" func f1() { live() return dead() } func f2() { for { live() } dead() } func f3() { if true { // even known values are ignored return } for true { // even known values are ignored live() } for { live() } dead() } func f4(x int) { switch x { case 1: live() fallthrough case 2: live() log.Fatal() default: panic("oops") } dead() } func f4(ch chan int) { select { case <-ch: live() return default: live() panic("oops") } dead() } func f5(unknown bool) { for { if unknown { break } continue dead() } live() } func f6(unknown bool) { outer: for { for { break outer dead() } dead() } live() } func f7() { for { break nosuchlabel dead() } dead() } func f8() { select{} dead() } func f9(ch chan int) { select { case <-ch: return } dead() } func f10(ch chan int) { select { case <-ch: return dead() default: } live() } func f11() { goto; // mustn't crash dead() } ` func TestDeadCode(t *testing.T) { // We'll use dead code detection to verify the CFG. fset := token.NewFileSet() f, err := parser.ParseFile(fset, "dummy.go", src, parser.Mode(0)) if err != nil { t.Fatal(err) } for _, decl := range f.Decls { if decl, ok := decl.(*ast.FuncDecl); ok { g := New(decl.Body, mayReturn) // Mark blocks reachable from entry. live := make(map[*Block]bool) var visit func(*Block) visit = func(b *Block) { if !live[b] { live[b] = true for _, succ := range b.Succs { visit(succ) } } } visit(g.Blocks[0]) // Print statements in unreachable blocks // (in order determined by builder). var buf bytes.Buffer for _, b := range g.Blocks { if !live[b] { for _, n := range b.Nodes { fmt.Fprintf(&buf, "\t%s\n", formatNode(fset, n)) } } } // Check that the result contains "dead" at least once but not "live". if !bytes.Contains(buf.Bytes(), []byte("dead")) || bytes.Contains(buf.Bytes(), []byte("live")) { t.Errorf("unexpected dead statements in function %s:\n%s", decl.Name.Name, &buf) t.Logf("control flow graph:\n%s", g.Format(fset)) } } } } // A trivial mayReturn predicate that looks only at syntax, not types. func mayReturn(call *ast.CallExpr) bool { switch fun := call.Fun.(type) { case *ast.Ident: return fun.Name != "panic" case *ast.SelectorExpr: return fun.Sel.Name != "Fatal" } return true }