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