golang.org/x/tools@v0.21.0/go/cfg/cfg_test.go (about)

     1  // Copyright 2016 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package cfg_test
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"go/ast"
    11  	"go/format"
    12  	"go/parser"
    13  	"go/token"
    14  	"testing"
    15  
    16  	"golang.org/x/tools/go/cfg"
    17  	"golang.org/x/tools/go/packages"
    18  	"golang.org/x/tools/internal/testenv"
    19  )
    20  
    21  const src = `package main
    22  
    23  import "log"
    24  
    25  func f1() {
    26  	live()
    27  	return
    28  	dead()
    29  }
    30  
    31  func f2() {
    32  	for {
    33  		live()
    34  	}
    35  	dead()
    36  }
    37  
    38  func f3() {
    39  	if true { // even known values are ignored
    40  		return
    41  	}
    42  	for true { // even known values are ignored
    43  		live()
    44  	}
    45  	for {
    46  		live()
    47  	}
    48  	dead()
    49  }
    50  
    51  func f4(x int) {
    52  	switch x {
    53  	case 1:
    54  		live()
    55  		fallthrough
    56  	case 2:
    57  		live()
    58  		log.Fatal()
    59  	default:
    60  		panic("oops")
    61  	}
    62  	dead()
    63  }
    64  
    65  func f4(ch chan int) {
    66  	select {
    67  	case <-ch:
    68  		live()
    69  		return
    70  	default:
    71  		live()
    72  		panic("oops")
    73  	}
    74  	dead()
    75  }
    76  
    77  func f5(unknown bool) {
    78  	for {
    79  		if unknown {
    80  			break
    81  		}
    82  		continue
    83  		dead()
    84  	}
    85  	live()
    86  }
    87  
    88  func f6(unknown bool) {
    89  outer:
    90  	for {
    91  		for {
    92  			break outer
    93  			dead()
    94  		}
    95  		dead()
    96  	}
    97  	live()
    98  }
    99  
   100  func f7() {
   101  	for {
   102  		break nosuchlabel
   103  		dead()
   104  	}
   105  	dead()
   106  }
   107  
   108  func f8() {
   109  	select{}
   110  	dead()
   111  }
   112  
   113  func f9(ch chan int) {
   114  	select {
   115  	case <-ch:
   116  		return
   117  	}
   118  	dead()
   119  }
   120  
   121  func f10(ch chan int) {
   122  	select {
   123  	case <-ch:
   124  		return
   125  		dead()
   126  	default:
   127  	}
   128  	live()
   129  }
   130  
   131  func f11() {
   132  	goto; // mustn't crash
   133  	dead()
   134  }
   135  
   136  `
   137  
   138  func TestDeadCode(t *testing.T) {
   139  	// We'll use dead code detection to verify the CFG.
   140  
   141  	fset := token.NewFileSet()
   142  	f, err := parser.ParseFile(fset, "dummy.go", src, parser.Mode(0))
   143  	if err != nil {
   144  		t.Fatal(err)
   145  	}
   146  	for _, decl := range f.Decls {
   147  		if decl, ok := decl.(*ast.FuncDecl); ok {
   148  			g := cfg.New(decl.Body, mayReturn)
   149  
   150  			// Print statements in unreachable blocks
   151  			// (in order determined by builder).
   152  			var buf bytes.Buffer
   153  			for _, b := range g.Blocks {
   154  				if !b.Live {
   155  					for _, n := range b.Nodes {
   156  						fmt.Fprintf(&buf, "\t%s\n", formatNode(fset, n))
   157  					}
   158  				}
   159  			}
   160  
   161  			// Check that the result contains "dead" at least once but not "live".
   162  			if !bytes.Contains(buf.Bytes(), []byte("dead")) ||
   163  				bytes.Contains(buf.Bytes(), []byte("live")) {
   164  				t.Errorf("unexpected dead statements in function %s:\n%s",
   165  					decl.Name.Name,
   166  					&buf)
   167  				t.Logf("control flow graph:\n%s", g.Format(fset))
   168  			}
   169  		}
   170  	}
   171  }
   172  
   173  // TestSmoke runs the CFG builder on every FuncDecl in the standard
   174  // library and x/tools. (This is all well-typed code, but it gives
   175  // some coverage.)
   176  func TestSmoke(t *testing.T) {
   177  	if testing.Short() {
   178  		t.Skip("skipping in short mode")
   179  	}
   180  	testenv.NeedsTool(t, "go")
   181  
   182  	// The Mode API is just hateful.
   183  	// https://github.com/golang/go/issues/48226#issuecomment-1948792315
   184  	mode := packages.NeedDeps | packages.NeedImports | packages.NeedSyntax | packages.NeedTypes
   185  	pkgs, err := packages.Load(&packages.Config{Mode: mode}, "std", "golang.org/x/tools/...")
   186  	if err != nil {
   187  		t.Fatal(err)
   188  	}
   189  
   190  	for _, pkg := range pkgs {
   191  		for _, file := range pkg.Syntax {
   192  			for _, decl := range file.Decls {
   193  				if decl, ok := decl.(*ast.FuncDecl); ok && decl.Body != nil {
   194  					g := cfg.New(decl.Body, mayReturn)
   195  
   196  					// Run a few quick sanity checks.
   197  					failed := false
   198  					for i, b := range g.Blocks {
   199  						errorf := func(format string, args ...any) {
   200  							if !failed {
   201  								t.Errorf("%s\n%s", pkg.Fset.Position(decl.Pos()), g.Format(pkg.Fset))
   202  								failed = true
   203  							}
   204  							msg := fmt.Sprintf(format, args...)
   205  							t.Errorf("block %d: %s", i, msg)
   206  						}
   207  
   208  						if b.Kind == cfg.KindInvalid {
   209  							errorf("invalid Block.Kind %v", b.Kind)
   210  						}
   211  						if b.Stmt == nil && b.Kind != cfg.KindLabel {
   212  							errorf("nil Block.Stmt (Kind=%v)", b.Kind)
   213  						}
   214  						if i != int(b.Index) {
   215  							errorf("invalid Block.Index")
   216  						}
   217  					}
   218  				}
   219  			}
   220  		}
   221  	}
   222  }
   223  
   224  // A trivial mayReturn predicate that looks only at syntax, not types.
   225  func mayReturn(call *ast.CallExpr) bool {
   226  	switch fun := call.Fun.(type) {
   227  	case *ast.Ident:
   228  		return fun.Name != "panic"
   229  	case *ast.SelectorExpr:
   230  		return fun.Sel.Name != "Fatal"
   231  	}
   232  	return true
   233  }
   234  
   235  func formatNode(fset *token.FileSet, n ast.Node) string {
   236  	var buf bytes.Buffer
   237  	format.Node(&buf, fset, n)
   238  	// Indent secondary lines by a tab.
   239  	return string(bytes.Replace(buf.Bytes(), []byte("\n"), []byte("\n\t"), -1))
   240  }