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