|  | // 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) | 
|  |  | 
|  | // Print statements in unreachable blocks | 
|  | // (in order determined by builder). | 
|  | var buf bytes.Buffer | 
|  | for _, b := range g.Blocks { | 
|  | if !b.Live { | 
|  | 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 | 
|  | } |