1// Copyright 2013 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//lint:file-ignore SA1019 go/ssa's test suite is built around the deprecated go/loader. We'll leave fixing that to upstream. 6 7package ir_test 8 9// This file defines tests of source-level debugging utilities. 10 11import ( 12 "fmt" 13 "go/ast" 14 "go/constant" 15 "go/parser" 16 "go/token" 17 "go/types" 18 "io/ioutil" 19 "os" 20 "runtime" 21 "strings" 22 "testing" 23 24 "golang.org/x/tools/go/ast/astutil" 25 "golang.org/x/tools/go/expect" 26 "golang.org/x/tools/go/loader" 27 "honnef.co/go/tools/ir" 28 "honnef.co/go/tools/ir/irutil" 29) 30 31func TestObjValueLookup(t *testing.T) { 32 if runtime.GOOS == "android" { 33 t.Skipf("no testdata directory on %s", runtime.GOOS) 34 } 35 36 conf := loader.Config{ParserMode: parser.ParseComments} 37 src, err := ioutil.ReadFile("testdata/objlookup.go") 38 if err != nil { 39 t.Fatal(err) 40 } 41 readFile := func(filename string) ([]byte, error) { return src, nil } 42 f, err := conf.ParseFile("testdata/objlookup.go", src) 43 if err != nil { 44 t.Fatal(err) 45 } 46 conf.CreateFromFiles("main", f) 47 48 // Maps each var Ident (represented "name:linenum") to the 49 // kind of ir.Value we expect (represented "Constant", "&Alloc"). 50 expectations := make(map[string]string) 51 52 // Each note of the form @ir(x, "BinOp") in testdata/objlookup.go 53 // specifies an expectation that an object named x declared on the 54 // same line is associated with an an ir.Value of type *ir.BinOp. 55 notes, err := expect.Extract(conf.Fset, f) 56 if err != nil { 57 t.Fatal(err) 58 } 59 for _, n := range notes { 60 if n.Name != "ir" { 61 t.Errorf("%v: unexpected note type %q, want \"ir\"", conf.Fset.Position(n.Pos), n.Name) 62 continue 63 } 64 if len(n.Args) != 2 { 65 t.Errorf("%v: ir has %d args, want 2", conf.Fset.Position(n.Pos), len(n.Args)) 66 continue 67 } 68 ident, ok := n.Args[0].(expect.Identifier) 69 if !ok { 70 t.Errorf("%v: got %v for arg 1, want identifier", conf.Fset.Position(n.Pos), n.Args[0]) 71 continue 72 } 73 exp, ok := n.Args[1].(string) 74 if !ok { 75 t.Errorf("%v: got %v for arg 2, want string", conf.Fset.Position(n.Pos), n.Args[1]) 76 continue 77 } 78 p, _, err := expect.MatchBefore(conf.Fset, readFile, n.Pos, string(ident)) 79 if err != nil { 80 t.Error(err) 81 continue 82 } 83 pos := conf.Fset.Position(p) 84 key := fmt.Sprintf("%s:%d", ident, pos.Line) 85 expectations[key] = exp 86 } 87 88 iprog, err := conf.Load() 89 if err != nil { 90 t.Error(err) 91 return 92 } 93 94 prog := irutil.CreateProgram(iprog, 0 /*|ir.PrintFunctions*/) 95 mainInfo := iprog.Created[0] 96 mainPkg := prog.Package(mainInfo.Pkg) 97 mainPkg.SetDebugMode(true) 98 mainPkg.Build() 99 100 var varIds []*ast.Ident 101 var varObjs []*types.Var 102 for id, obj := range mainInfo.Defs { 103 // Check invariants for func and const objects. 104 switch obj := obj.(type) { 105 case *types.Func: 106 checkFuncValue(t, prog, obj) 107 108 case *types.Const: 109 checkConstValue(t, prog, obj) 110 111 case *types.Var: 112 if id.Name == "_" { 113 continue 114 } 115 varIds = append(varIds, id) 116 varObjs = append(varObjs, obj) 117 } 118 } 119 for id, obj := range mainInfo.Uses { 120 if obj, ok := obj.(*types.Var); ok { 121 varIds = append(varIds, id) 122 varObjs = append(varObjs, obj) 123 } 124 } 125 126 // Check invariants for var objects. 127 // The result varies based on the specific Ident. 128 for i, id := range varIds { 129 obj := varObjs[i] 130 ref, _ := astutil.PathEnclosingInterval(f, id.Pos(), id.Pos()) 131 pos := prog.Fset.Position(id.Pos()) 132 exp := expectations[fmt.Sprintf("%s:%d", id.Name, pos.Line)] 133 if exp == "" { 134 t.Errorf("%s: no expectation for var ident %s ", pos, id.Name) 135 continue 136 } 137 wantAddr := false 138 if exp[0] == '&' { 139 wantAddr = true 140 exp = exp[1:] 141 } 142 checkVarValue(t, prog, mainPkg, ref, obj, exp, wantAddr) 143 } 144} 145 146func checkFuncValue(t *testing.T, prog *ir.Program, obj *types.Func) { 147 fn := prog.FuncValue(obj) 148 // fmt.Printf("FuncValue(%s) = %s\n", obj, fn) // debugging 149 if fn == nil { 150 if obj.Name() != "interfaceMethod" { 151 t.Errorf("FuncValue(%s) == nil", obj) 152 } 153 return 154 } 155 if fnobj := fn.Object(); fnobj != obj { 156 t.Errorf("FuncValue(%s).Object() == %s; value was %s", 157 obj, fnobj, fn.Name()) 158 return 159 } 160 if !types.Identical(fn.Type(), obj.Type()) { 161 t.Errorf("FuncValue(%s).Type() == %s", obj, fn.Type()) 162 return 163 } 164} 165 166func checkConstValue(t *testing.T, prog *ir.Program, obj *types.Const) { 167 c := prog.ConstValue(obj) 168 // fmt.Printf("ConstValue(%s) = %s\n", obj, c) // debugging 169 if c == nil { 170 t.Errorf("ConstValue(%s) == nil", obj) 171 return 172 } 173 if !types.Identical(c.Type(), obj.Type()) { 174 t.Errorf("ConstValue(%s).Type() == %s", obj, c.Type()) 175 return 176 } 177 if obj.Name() != "nil" { 178 if !constant.Compare(c.Value, token.EQL, obj.Val()) { 179 t.Errorf("ConstValue(%s).Value (%s) != %s", 180 obj, c.Value, obj.Val()) 181 return 182 } 183 } 184} 185 186func checkVarValue(t *testing.T, prog *ir.Program, pkg *ir.Package, ref []ast.Node, obj *types.Var, expKind string, wantAddr bool) { 187 // The prefix of all assertions messages. 188 prefix := fmt.Sprintf("VarValue(%s @ L%d)", 189 obj, prog.Fset.Position(ref[0].Pos()).Line) 190 191 v, gotAddr := prog.VarValue(obj, pkg, ref) 192 193 // Kind is the concrete type of the ir Value. 194 gotKind := "nil" 195 if v != nil { 196 gotKind = fmt.Sprintf("%T", v)[len("*ir."):] 197 } 198 199 // fmt.Printf("%s = %v (kind %q; expect %q) wantAddr=%t gotAddr=%t\n", prefix, v, gotKind, expKind, wantAddr, gotAddr) // debugging 200 201 // Check the kinds match. 202 // "nil" indicates expected failure (e.g. optimized away). 203 if expKind != gotKind { 204 t.Errorf("%s concrete type == %s, want %s", prefix, gotKind, expKind) 205 } 206 207 // Check the types match. 208 // If wantAddr, the expected type is the object's address. 209 if v != nil { 210 expType := obj.Type() 211 if wantAddr { 212 expType = types.NewPointer(expType) 213 if !gotAddr { 214 t.Errorf("%s: got value, want address", prefix) 215 } 216 } else if gotAddr { 217 t.Errorf("%s: got address, want value", prefix) 218 } 219 if !types.Identical(v.Type(), expType) { 220 t.Errorf("%s.Type() == %s, want %s", prefix, v.Type(), expType) 221 } 222 } 223} 224 225// Ensure that, in debug mode, we can determine the ir.Value 226// corresponding to every ast.Expr. 227func TestValueForExpr(t *testing.T) { 228 testValueForExpr(t, "testdata/valueforexpr.go") 229} 230 231func testValueForExpr(t *testing.T, testfile string) { 232 if runtime.GOOS == "android" { 233 t.Skipf("no testdata dir on %s", runtime.GOOS) 234 } 235 236 conf := loader.Config{ParserMode: parser.ParseComments} 237 f, err := conf.ParseFile(testfile, nil) 238 if err != nil { 239 t.Error(err) 240 return 241 } 242 conf.CreateFromFiles("main", f) 243 244 iprog, err := conf.Load() 245 if err != nil { 246 t.Error(err) 247 return 248 } 249 250 mainInfo := iprog.Created[0] 251 252 prog := irutil.CreateProgram(iprog, 0) 253 mainPkg := prog.Package(mainInfo.Pkg) 254 mainPkg.SetDebugMode(true) 255 mainPkg.Build() 256 257 if false { 258 // debugging 259 for _, mem := range mainPkg.Members { 260 if fn, ok := mem.(*ir.Function); ok { 261 fn.WriteTo(os.Stderr) 262 } 263 } 264 } 265 266 var parenExprs []*ast.ParenExpr 267 ast.Inspect(f, func(n ast.Node) bool { 268 if n != nil { 269 if e, ok := n.(*ast.ParenExpr); ok { 270 parenExprs = append(parenExprs, e) 271 } 272 } 273 return true 274 }) 275 276 notes, err := expect.Extract(prog.Fset, f) 277 if err != nil { 278 t.Fatal(err) 279 } 280 for _, n := range notes { 281 want := n.Name 282 if want == "nil" { 283 want = "<nil>" 284 } 285 position := prog.Fset.Position(n.Pos) 286 var e ast.Expr 287 for _, paren := range parenExprs { 288 if paren.Pos() > n.Pos { 289 e = paren.X 290 break 291 } 292 } 293 if e == nil { 294 t.Errorf("%s: note doesn't precede ParenExpr: %q", position, want) 295 continue 296 } 297 298 path, _ := astutil.PathEnclosingInterval(f, n.Pos, n.Pos) 299 if path == nil { 300 t.Errorf("%s: can't find AST path from root to comment: %s", position, want) 301 continue 302 } 303 304 fn := ir.EnclosingFunction(mainPkg, path) 305 if fn == nil { 306 t.Errorf("%s: can't find enclosing function", position) 307 continue 308 } 309 310 v, gotAddr := fn.ValueForExpr(e) // (may be nil) 311 got := strings.TrimPrefix(fmt.Sprintf("%T", v), "*ir.") 312 if got != want { 313 t.Errorf("%s: got value %q, want %q", position, got, want) 314 } 315 if v != nil { 316 T := v.Type() 317 if gotAddr { 318 T = T.Underlying().(*types.Pointer).Elem() // deref 319 } 320 if !types.Identical(T, mainInfo.TypeOf(e)) { 321 t.Errorf("%s: got type %s, want %s", position, mainInfo.TypeOf(e), T) 322 } 323 } 324 } 325} 326 327// findInterval parses input and returns the [start, end) positions of 328// the first occurrence of substr in input. f==nil indicates failure; 329// an error has already been reported in that case. 330// 331func findInterval(t *testing.T, fset *token.FileSet, input, substr string) (f *ast.File, start, end token.Pos) { 332 f, err := parser.ParseFile(fset, "<input>", input, 0) 333 if err != nil { 334 t.Errorf("parse error: %s", err) 335 return 336 } 337 338 i := strings.Index(input, substr) 339 if i < 0 { 340 t.Errorf("%q is not a substring of input", substr) 341 f = nil 342 return 343 } 344 345 filePos := fset.File(f.Package) 346 return f, filePos.Pos(i), filePos.Pos(i + len(substr)) 347} 348 349func TestEnclosingFunction(t *testing.T) { 350 tests := []struct { 351 input string // the input file 352 substr string // first occurrence of this string denotes interval 353 fn string // name of expected containing function 354 }{ 355 // We use distinctive numbers as syntactic landmarks. 356 357 // Ordinary function: 358 {`package main 359 func f() { println(1003) }`, 360 "100", "main.f"}, 361 // Methods: 362 {`package main 363 type T int 364 func (t T) f() { println(200) }`, 365 "200", "(main.T).f"}, 366 // Function literal: 367 {`package main 368 func f() { println(func() { print(300) }) }`, 369 "300", "main.f$1"}, 370 // Doubly nested 371 {`package main 372 func f() { println(func() { print(func() { print(350) })})}`, 373 "350", "main.f$1$1"}, 374 // Implicit init for package-level var initializer. 375 {"package main; var a = 400", "400", "main.init"}, 376 // No code for constants: 377 {"package main; const a = 500", "500", "(none)"}, 378 // Explicit init() 379 {"package main; func init() { println(600) }", "600", "main.init#1"}, 380 // Multiple explicit init functions: 381 {`package main 382 func init() { println("foo") } 383 func init() { println(800) }`, 384 "800", "main.init#2"}, 385 // init() containing FuncLit. 386 {`package main 387 func init() { println(func(){print(900)}) }`, 388 "900", "main.init#1$1"}, 389 } 390 for _, test := range tests { 391 conf := loader.Config{Fset: token.NewFileSet()} 392 f, start, end := findInterval(t, conf.Fset, test.input, test.substr) 393 if f == nil { 394 continue 395 } 396 path, exact := astutil.PathEnclosingInterval(f, start, end) 397 if !exact { 398 t.Errorf("EnclosingFunction(%q) not exact", test.substr) 399 continue 400 } 401 402 conf.CreateFromFiles("main", f) 403 404 iprog, err := conf.Load() 405 if err != nil { 406 t.Error(err) 407 continue 408 } 409 prog := irutil.CreateProgram(iprog, 0) 410 pkg := prog.Package(iprog.Created[0].Pkg) 411 pkg.Build() 412 413 name := "(none)" 414 fn := ir.EnclosingFunction(pkg, path) 415 if fn != nil { 416 name = fn.String() 417 } 418 419 if name != test.fn { 420 t.Errorf("EnclosingFunction(%q in %q) got %s, want %s", 421 test.substr, test.input, name, test.fn) 422 continue 423 } 424 425 // While we're here: test HasEnclosingFunction. 426 if has := ir.HasEnclosingFunction(pkg, path); has != (fn != nil) { 427 t.Errorf("HasEnclosingFunction(%q in %q) got %v, want %v", 428 test.substr, test.input, has, fn != nil) 429 continue 430 } 431 } 432} 433