| 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 | } |
| 728 |
Members