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