loadObjdump reads the objdump output. This records if there is a call any function for every source line. It is used only to remove false positives for escape analysis. The call will be elided if escape analysis is able to put the object on the heap exclusively. Note that the map uses <basename.go
(binary io.Reader)
| 362 | // Note that the map uses <basename.go>:<line> because that is all that is |
| 363 | // provided in the objdump format. Since this is all local, it is sufficient. |
| 364 | func loadObjdump(binary io.Reader) (map[string]map[string]struct{}, error) { |
| 365 | // Do we have a binary? If it's missing, then the nil will simply be |
| 366 | // plumbed all the way down here. |
| 367 | if binary == nil { |
| 368 | return nil, fmt.Errorf("no binary provided") |
| 369 | } |
| 370 | |
| 371 | // Construct & start our command. The 'go tool objdump' command |
| 372 | // requires a seekable input passed on the command line. Therefore, we |
| 373 | // may need to generate a temporary file here. |
| 374 | input, ok := binary.(*os.File) |
| 375 | if ok { |
| 376 | // Ensure that the file is seekable and that the offset is |
| 377 | // zero, since we can't control that. |
| 378 | if offset, err := input.Seek(0, io.SeekCurrent); err != nil || offset != 0 { |
| 379 | ok = false // Not usable. |
| 380 | } |
| 381 | } |
| 382 | if !ok { |
| 383 | // Copy to a temporary path. |
| 384 | f, err := os.CreateTemp("", "") |
| 385 | if err != nil { |
| 386 | return nil, fmt.Errorf("unable to create temp file: %w", err) |
| 387 | } |
| 388 | // Ensure the file is deleted. |
| 389 | defer os.Remove(f.Name()) |
| 390 | // Populate the file contents. |
| 391 | if _, err := io.Copy(f, binary); err != nil { |
| 392 | return nil, fmt.Errorf("unable to populate temp file: %w", err) |
| 393 | } |
| 394 | // Seek to the beginning. |
| 395 | if _, err := f.Seek(0, os.SEEK_SET); err != nil { |
| 396 | return nil, fmt.Errorf("unable to seek in temp file: %w", err) |
| 397 | } |
| 398 | input = f |
| 399 | } |
| 400 | |
| 401 | ctx, cancel := context.WithCancel(context.Background()) |
| 402 | defer cancel() |
| 403 | |
| 404 | // Execute go tool objdump given the input. |
| 405 | cmd := exec.CommandContext(ctx, flags.Go, "tool", "objdump", input.Name()) |
| 406 | if goroot, ok := os.LookupEnv("GOROOT"); ok && strings.HasPrefix(goroot, "bazel-out/") { |
| 407 | // Under Bazel, our nogo machinery sets GOROOT to a stdlib output tree, |
| 408 | // which does not include the prebuilt objdump tool. Some Go versions |
| 409 | // may build objdump on-demand via `go tool objdump`, which requires a |
| 410 | // writable build cache. |
| 411 | // |
| 412 | // See https://go.dev/issue/71867 and |
| 413 | // https://github.com/bazel-contrib/rules_go/issues/4535. |
| 414 | cacheDirRel := filepath.Join("bazel-out", ".checkescape-gocache") |
| 415 | if err := os.MkdirAll(cacheDirRel, 0755); err != nil { |
| 416 | return nil, fmt.Errorf("unable to create build cache dir %q: %w", cacheDirRel, err) |
| 417 | } |
| 418 | // GOCACHE must be an absolute path. |
| 419 | cacheDirAbs, err := filepath.Abs(cacheDirRel) |
| 420 | if err != nil { |
| 421 | return nil, fmt.Errorf("unable to get absolute path of build cache dir %q: %w", cacheDirRel, err) |
no test coverage detected
searching dependent graphs…