github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/go/pointer/pointer_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 // No testdata on Android. 6 7 //go:build !android 8 // +build !android 9 10 package pointer_test 11 12 // This test uses 'expectation' comments embedded within testdata/*.go 13 // files to specify the expected pointer analysis behaviour. 14 // See below for grammar. 15 16 import ( 17 "bytes" 18 "errors" 19 "fmt" 20 "go/token" 21 "go/types" 22 "io/ioutil" 23 "os" 24 "path/filepath" 25 "regexp" 26 "strconv" 27 "strings" 28 "testing" 29 "unsafe" 30 31 "github.com/powerman/golang-tools/go/callgraph" 32 "github.com/powerman/golang-tools/go/packages" 33 "github.com/powerman/golang-tools/go/pointer" 34 "github.com/powerman/golang-tools/go/ssa" 35 "github.com/powerman/golang-tools/go/ssa/ssautil" 36 "github.com/powerman/golang-tools/go/types/typeutil" 37 ) 38 39 var inputs = []string{ 40 "testdata/a_test.go", 41 "testdata/another.go", 42 "testdata/arrayreflect.go", 43 "testdata/arrays.go", 44 "testdata/channels.go", 45 "testdata/chanreflect.go", 46 "testdata/context.go", 47 "testdata/conv.go", 48 "testdata/extended.go", 49 "testdata/finalizer.go", 50 "testdata/flow.go", 51 "testdata/fmtexcerpt.go", 52 "testdata/func.go", 53 "testdata/funcreflect.go", 54 "testdata/hello.go", // NB: causes spurious failure of HVN cross-check 55 "testdata/interfaces.go", 56 "testdata/issue9002.go", 57 "testdata/mapreflect.go", 58 "testdata/maps.go", 59 "testdata/panic.go", 60 "testdata/recur.go", 61 "testdata/reflect.go", 62 "testdata/rtti.go", 63 "testdata/structreflect.go", 64 "testdata/structs.go", 65 // "testdata/timer.go", // TODO(adonovan): fix broken assumptions about runtime timers 66 } 67 68 // Expectation grammar: 69 // 70 // @calls f -> g 71 // 72 // A 'calls' expectation asserts that edge (f, g) appears in the 73 // callgraph. f and g are notated as per Function.String(), which 74 // may contain spaces (e.g. promoted method in anon struct). 75 // 76 // @pointsto a | b | c 77 // 78 // A 'pointsto' expectation asserts that the points-to set of its 79 // operand contains exactly the set of labels {a,b,c} notated as per 80 // labelString. 81 // 82 // A 'pointsto' expectation must appear on the same line as a 83 // print(x) statement; the expectation's operand is x. 84 // 85 // If one of the strings is "...", the expectation asserts that the 86 // points-to set at least the other labels. 87 // 88 // We use '|' because label names may contain spaces, e.g. methods 89 // of anonymous structs. 90 // 91 // From a theoretical perspective, concrete types in interfaces are 92 // labels too, but they are represented differently and so have a 93 // different expectation, @types, below. 94 // 95 // @types t | u | v 96 // 97 // A 'types' expectation asserts that the set of possible dynamic 98 // types of its interface operand is exactly {t,u,v}, notated per 99 // go/types.Type.String(). In other words, it asserts that the type 100 // component of the interface may point to that set of concrete type 101 // literals. It also works for reflect.Value, though the types 102 // needn't be concrete in that case. 103 // 104 // A 'types' expectation must appear on the same line as a 105 // print(x) statement; the expectation's operand is x. 106 // 107 // If one of the strings is "...", the expectation asserts that the 108 // interface's type may point to at least the other types. 109 // 110 // We use '|' because type names may contain spaces. 111 // 112 // @warning "regexp" 113 // 114 // A 'warning' expectation asserts that the analysis issues a 115 // warning that matches the regular expression within the string 116 // literal. 117 // 118 // @line id 119 // 120 // A line directive associates the name "id" with the current 121 // file:line. The string form of labels will use this id instead of 122 // a file:line, making @pointsto expectations more robust against 123 // perturbations in the source file. 124 // (NB, anon functions still include line numbers.) 125 // 126 type expectation struct { 127 kind string // "pointsto" | "pointstoquery" | "types" | "calls" | "warning" 128 filepath string 129 linenum int // source line number, 1-based 130 args []string 131 query string // extended query 132 extended *pointer.Pointer // extended query pointer 133 types []types.Type // for types 134 } 135 136 func (e *expectation) String() string { 137 return fmt.Sprintf("@%s[%s]", e.kind, strings.Join(e.args, " | ")) 138 } 139 140 func (e *expectation) errorf(format string, args ...interface{}) { 141 fmt.Printf("%s:%d: ", e.filepath, e.linenum) 142 fmt.Printf(format, args...) 143 fmt.Println() 144 } 145 146 func (e *expectation) needsProbe() bool { 147 return e.kind == "pointsto" || e.kind == "pointstoquery" || e.kind == "types" 148 } 149 150 // Find probe (call to print(x)) of same source file/line as expectation. 151 func findProbe(prog *ssa.Program, probes map[*ssa.CallCommon]bool, queries map[ssa.Value]pointer.Pointer, e *expectation) (site *ssa.CallCommon, pts pointer.PointsToSet) { 152 for call := range probes { 153 pos := prog.Fset.Position(call.Pos()) 154 if pos.Line == e.linenum && pos.Filename == e.filepath { 155 // TODO(adonovan): send this to test log (display only on failure). 156 // fmt.Printf("%s:%d: info: found probe for %s: %s\n", 157 // e.filepath, e.linenum, e, p.arg0) // debugging 158 return call, queries[call.Args[0]].PointsTo() 159 } 160 } 161 return // e.g. analysis didn't reach this call 162 } 163 164 func doOneInput(t *testing.T, input, fpath string) bool { 165 cfg := &packages.Config{ 166 Mode: packages.LoadAllSyntax, 167 Tests: true, 168 } 169 pkgs, err := packages.Load(cfg, fpath) 170 if err != nil { 171 fmt.Println(err) 172 return false 173 } 174 if packages.PrintErrors(pkgs) > 0 { 175 fmt.Println("loaded packages have errors") 176 return false 177 } 178 179 // SSA creation + building. 180 prog, ssaPkgs := ssautil.AllPackages(pkgs, ssa.SanityCheckFunctions) 181 prog.Build() 182 183 // main underlying packages.Package. 184 mainPpkg := pkgs[0] 185 mainpkg := ssaPkgs[0] 186 ptrmain := mainpkg // main package for the pointer analysis 187 if mainpkg.Func("main") == nil { 188 // For test programs without main, such as testdata/a_test.go, 189 // the package with the original code is "main [main.test]" and 190 // the package with the main is "main.test". 191 for i, pkg := range pkgs { 192 if pkg.ID == mainPpkg.ID+".test" { 193 ptrmain = ssaPkgs[i] 194 } else if pkg.ID == fmt.Sprintf("%s [%s.test]", mainPpkg.ID, mainPpkg.ID) { 195 mainpkg = ssaPkgs[i] 196 } 197 } 198 } 199 200 // Find all calls to the built-in print(x). Analytically, 201 // print is a no-op, but it's a convenient hook for testing 202 // the PTS of an expression, so our tests use it. 203 probes := make(map[*ssa.CallCommon]bool) 204 for fn := range ssautil.AllFunctions(prog) { 205 if fn.Pkg == mainpkg { 206 for _, b := range fn.Blocks { 207 for _, instr := range b.Instrs { 208 if instr, ok := instr.(ssa.CallInstruction); ok { 209 call := instr.Common() 210 if b, ok := call.Value.(*ssa.Builtin); ok && b.Name() == "print" && len(call.Args) == 1 { 211 probes[instr.Common()] = true 212 } 213 } 214 } 215 } 216 } 217 } 218 219 ok := true 220 221 lineMapping := make(map[string]string) // maps "file:line" to @line tag 222 223 // Parse expectations in this input. 224 var exps []*expectation 225 re := regexp.MustCompile("// *@([a-z]*) *(.*)$") 226 lines := strings.Split(input, "\n") 227 for linenum, line := range lines { 228 linenum++ // make it 1-based 229 if matches := re.FindAllStringSubmatch(line, -1); matches != nil { 230 match := matches[0] 231 kind, rest := match[1], match[2] 232 e := &expectation{kind: kind, filepath: fpath, linenum: linenum} 233 234 if kind == "line" { 235 if rest == "" { 236 ok = false 237 e.errorf("@%s expectation requires identifier", kind) 238 } else { 239 lineMapping[fmt.Sprintf("%s:%d", fpath, linenum)] = rest 240 } 241 continue 242 } 243 244 if e.needsProbe() && !strings.Contains(line, "print(") { 245 ok = false 246 e.errorf("@%s expectation must follow call to print(x)", kind) 247 continue 248 } 249 250 switch kind { 251 case "pointsto": 252 e.args = split(rest, "|") 253 254 case "pointstoquery": 255 args := strings.SplitN(rest, " ", 2) 256 e.query = args[0] 257 e.args = split(args[1], "|") 258 case "types": 259 for _, typstr := range split(rest, "|") { 260 var t types.Type = types.Typ[types.Invalid] // means "..." 261 if typstr != "..." { 262 tv, err := types.Eval(prog.Fset, mainpkg.Pkg, mainPpkg.Syntax[0].Pos(), typstr) 263 if err != nil { 264 ok = false 265 // Don't print err since its location is bad. 266 e.errorf("'%s' is not a valid type: %s", typstr, err) 267 continue 268 } 269 t = tv.Type 270 } 271 e.types = append(e.types, t) 272 } 273 274 case "calls": 275 e.args = split(rest, "->") 276 // TODO(adonovan): eagerly reject the 277 // expectation if fn doesn't denote 278 // existing function, rather than fail 279 // the expectation after analysis. 280 if len(e.args) != 2 { 281 ok = false 282 e.errorf("@calls expectation wants 'caller -> callee' arguments") 283 continue 284 } 285 286 case "warning": 287 lit, err := strconv.Unquote(strings.TrimSpace(rest)) 288 if err != nil { 289 ok = false 290 e.errorf("couldn't parse @warning operand: %s", err.Error()) 291 continue 292 } 293 e.args = append(e.args, lit) 294 295 default: 296 ok = false 297 e.errorf("unknown expectation kind: %s", e) 298 continue 299 } 300 exps = append(exps, e) 301 } 302 } 303 304 var log bytes.Buffer 305 fmt.Fprintf(&log, "Input: %s\n", fpath) 306 307 // Run the analysis. 308 config := &pointer.Config{ 309 Reflection: true, 310 BuildCallGraph: true, 311 Mains: []*ssa.Package{ptrmain}, 312 Log: &log, 313 } 314 probeLoop: 315 for probe := range probes { 316 v := probe.Args[0] 317 pos := prog.Fset.Position(probe.Pos()) 318 for _, e := range exps { 319 if e.linenum == pos.Line && e.filepath == pos.Filename && e.kind == "pointstoquery" { 320 var err error 321 e.extended, err = config.AddExtendedQuery(v, e.query) 322 if err != nil { 323 panic(err) 324 } 325 continue probeLoop 326 } 327 } 328 if pointer.CanPoint(v.Type()) { 329 config.AddQuery(v) 330 } 331 } 332 333 // Print the log is there was an error or a panic. 334 complete := false 335 defer func() { 336 if !complete || !ok { 337 log.WriteTo(os.Stderr) 338 } 339 }() 340 341 result, err := pointer.Analyze(config) 342 if err != nil { 343 panic(err) // internal error in pointer analysis 344 } 345 346 // Check the expectations. 347 for _, e := range exps { 348 var call *ssa.CallCommon 349 var pts pointer.PointsToSet 350 var tProbe types.Type 351 if e.needsProbe() { 352 if call, pts = findProbe(prog, probes, result.Queries, e); call == nil { 353 ok = false 354 e.errorf("unreachable print() statement has expectation %s", e) 355 continue 356 } 357 if e.extended != nil { 358 pts = e.extended.PointsTo() 359 } 360 tProbe = call.Args[0].Type() 361 if !pointer.CanPoint(tProbe) { 362 ok = false 363 e.errorf("expectation on non-pointerlike operand: %s", tProbe) 364 continue 365 } 366 } 367 368 switch e.kind { 369 case "pointsto", "pointstoquery": 370 if !checkPointsToExpectation(e, pts, lineMapping, prog) { 371 ok = false 372 } 373 374 case "types": 375 if !checkTypesExpectation(e, pts, tProbe) { 376 ok = false 377 } 378 379 case "calls": 380 if !checkCallsExpectation(prog, e, result.CallGraph) { 381 ok = false 382 } 383 384 case "warning": 385 if !checkWarningExpectation(prog, e, result.Warnings) { 386 ok = false 387 } 388 } 389 } 390 391 complete = true 392 393 // ok = false // debugging: uncomment to always see log 394 395 return ok 396 } 397 398 func labelString(l *pointer.Label, lineMapping map[string]string, prog *ssa.Program) string { 399 // Functions and Globals need no pos suffix, 400 // nor do allocations in intrinsic operations 401 // (for which we'll print the function name). 402 switch l.Value().(type) { 403 case nil, *ssa.Function, *ssa.Global: 404 return l.String() 405 } 406 407 str := l.String() 408 if pos := l.Pos(); pos != token.NoPos { 409 // Append the position, using a @line tag instead of a line number, if defined. 410 posn := prog.Fset.Position(pos) 411 s := fmt.Sprintf("%s:%d", posn.Filename, posn.Line) 412 if tag, ok := lineMapping[s]; ok { 413 return fmt.Sprintf("%s@%s:%d", str, tag, posn.Column) 414 } 415 str = fmt.Sprintf("%s@%s", str, posn) 416 } 417 return str 418 } 419 420 func checkPointsToExpectation(e *expectation, pts pointer.PointsToSet, lineMapping map[string]string, prog *ssa.Program) bool { 421 expected := make(map[string]int) 422 surplus := make(map[string]int) 423 exact := true 424 for _, g := range e.args { 425 if g == "..." { 426 exact = false 427 continue 428 } 429 expected[g]++ 430 } 431 // Find the set of labels that the probe's 432 // argument (x in print(x)) may point to. 433 for _, label := range pts.Labels() { 434 name := labelString(label, lineMapping, prog) 435 if expected[name] > 0 { 436 expected[name]-- 437 } else if exact { 438 surplus[name]++ 439 } 440 } 441 // Report multiset difference: 442 ok := true 443 for _, count := range expected { 444 if count > 0 { 445 ok = false 446 e.errorf("value does not alias these expected labels: %s", join(expected)) 447 break 448 } 449 } 450 for _, count := range surplus { 451 if count > 0 { 452 ok = false 453 e.errorf("value may additionally alias these labels: %s", join(surplus)) 454 break 455 } 456 } 457 return ok 458 } 459 460 func checkTypesExpectation(e *expectation, pts pointer.PointsToSet, typ types.Type) bool { 461 var expected typeutil.Map 462 var surplus typeutil.Map 463 exact := true 464 for _, g := range e.types { 465 if g == types.Typ[types.Invalid] { 466 exact = false 467 continue 468 } 469 expected.Set(g, struct{}{}) 470 } 471 472 if !pointer.CanHaveDynamicTypes(typ) { 473 e.errorf("@types expectation requires an interface- or reflect.Value-typed operand, got %s", typ) 474 return false 475 } 476 477 // Find the set of types that the probe's 478 // argument (x in print(x)) may contain. 479 for _, T := range pts.DynamicTypes().Keys() { 480 if expected.At(T) != nil { 481 expected.Delete(T) 482 } else if exact { 483 surplus.Set(T, struct{}{}) 484 } 485 } 486 // Report set difference: 487 ok := true 488 if expected.Len() > 0 { 489 ok = false 490 e.errorf("interface cannot contain these types: %s", expected.KeysString()) 491 } 492 if surplus.Len() > 0 { 493 ok = false 494 e.errorf("interface may additionally contain these types: %s", surplus.KeysString()) 495 } 496 return ok 497 } 498 499 var errOK = errors.New("OK") 500 501 func checkCallsExpectation(prog *ssa.Program, e *expectation, cg *callgraph.Graph) bool { 502 found := make(map[string]int) 503 err := callgraph.GraphVisitEdges(cg, func(edge *callgraph.Edge) error { 504 // Name-based matching is inefficient but it allows us to 505 // match functions whose names that would not appear in an 506 // index ("<root>") or which are not unique ("func@1.2"). 507 if edge.Caller.Func.String() == e.args[0] { 508 calleeStr := edge.Callee.Func.String() 509 if calleeStr == e.args[1] { 510 return errOK // expectation satisfied; stop the search 511 } 512 found[calleeStr]++ 513 } 514 return nil 515 }) 516 if err == errOK { 517 return true 518 } 519 if len(found) == 0 { 520 e.errorf("didn't find any calls from %s", e.args[0]) 521 } 522 e.errorf("found no call from %s to %s, but only to %s", 523 e.args[0], e.args[1], join(found)) 524 return false 525 } 526 527 func checkWarningExpectation(prog *ssa.Program, e *expectation, warnings []pointer.Warning) bool { 528 // TODO(adonovan): check the position part of the warning too? 529 re, err := regexp.Compile(e.args[0]) 530 if err != nil { 531 e.errorf("invalid regular expression in @warning expectation: %s", err.Error()) 532 return false 533 } 534 535 if len(warnings) == 0 { 536 e.errorf("@warning %q expectation, but no warnings", e.args[0]) 537 return false 538 } 539 540 for _, w := range warnings { 541 if re.MatchString(w.Message) { 542 return true 543 } 544 } 545 546 e.errorf("@warning %q expectation not satisfied; found these warnings though:", e.args[0]) 547 for _, w := range warnings { 548 fmt.Printf("%s: warning: %s\n", prog.Fset.Position(w.Pos), w.Message) 549 } 550 return false 551 } 552 553 func TestInput(t *testing.T) { 554 if testing.Short() { 555 t.Skip("skipping in short mode; this test requires tons of memory; https://golang.org/issue/14113") 556 } 557 if unsafe.Sizeof(unsafe.Pointer(nil)) <= 4 { 558 t.Skip("skipping memory-intensive test on platform with small address space; https://golang.org/issue/14113") 559 } 560 ok := true 561 562 wd, err := os.Getwd() 563 if err != nil { 564 t.Errorf("os.Getwd: %s", err) 565 return 566 } 567 568 // 'go test' does a chdir so that relative paths in 569 // diagnostics no longer make sense relative to the invoking 570 // shell's cwd. We print a special marker so that Emacs can 571 // make sense of them. 572 fmt.Fprintf(os.Stderr, "Entering directory `%s'\n", wd) 573 574 for _, filename := range inputs { 575 content, err := ioutil.ReadFile(filename) 576 if err != nil { 577 t.Errorf("couldn't read file '%s': %s", filename, err) 578 continue 579 } 580 581 fpath, err := filepath.Abs(filename) 582 if err != nil { 583 t.Errorf("couldn't get absolute path for '%s': %s", filename, err) 584 } 585 586 if !doOneInput(t, string(content), fpath) { 587 ok = false 588 } 589 } 590 if !ok { 591 t.Fail() 592 } 593 } 594 595 // join joins the elements of multiset with " | "s. 596 func join(set map[string]int) string { 597 var buf bytes.Buffer 598 sep := "" 599 for name, count := range set { 600 for i := 0; i < count; i++ { 601 buf.WriteString(sep) 602 sep = " | " 603 buf.WriteString(name) 604 } 605 } 606 return buf.String() 607 } 608 609 // split returns the list of sep-delimited non-empty strings in s. 610 func split(s, sep string) (r []string) { 611 for _, elem := range strings.Split(s, sep) { 612 elem = strings.TrimSpace(elem) 613 if elem != "" { 614 r = append(r, elem) 615 } 616 } 617 return 618 }