178 lines
2.4 KiB
Go
178 lines
2.4 KiB
Go
// 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
|
|
}
|