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 }