// 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
}
