Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 83 additions & 15 deletions internal/report/source.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"os"
"path/filepath"
"regexp"
"runtime"
"slices"
"sort"
"strconv"
Expand Down Expand Up @@ -1007,28 +1008,95 @@ func openSourceFile(path, searchPath, trim string) (*os.File, error) {
path = trimPath(path, trim, searchPath)
// If file is still absolute, require file to exist.
if filepath.IsAbs(path) {
f, err := os.Open(path)
return f, err
}
// Scan each component of the path.
for _, dir := range filepath.SplitList(searchPath) {
// Search up for every parent of each possible path.
for {
filename := filepath.Join(dir, path)
if f, err := os.Open(filename); err == nil {
return f, nil
}
parent := filepath.Dir(dir)
if parent == dir {
break
if f, err := tryOpenFile(path); err == nil {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All this changes are round allowing to fallback later to other "handlers", for now we are adding the goroot handler, but in other PRs we will add more special handlers like gomodcache or vendor.

return f, nil
}
} else {
// Scan each component of the path.
for _, dir := range filepath.SplitList(searchPath) {
// Search up for every parent of each possible path.
for {
filename := filepath.Join(dir, path)
if f, err := tryOpenFile(filename); err == nil {
return f, nil
}
parent := filepath.Dir(dir)
if parent == dir {
break
}
dir = parent
}
dir = parent
}
}
// Fall back to looking for Go standard library sources under the local
// $GOROOT/src, since profiles from Go programs refer to standard library
// files under the GOROOT of the machine where the program was built.
if f, err := openGorootSourceFile(path, gorootSrc); err == nil {
return f, nil
}

return nil, fmt.Errorf("could not find file %s on path %s", path, searchPath)
}

// tryOpenFile opens the file at filename if it exists and is not a directory,
// which os.Open would also happily open.
func tryOpenFile(filename string) (*os.File, error) {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fail fast when you try to open a directory.

f, err := os.Open(filename)
if err != nil {
return nil, err
}
stat, err := f.Stat()
if err != nil {
f.Close()
return nil, err
}
if stat.IsDir() {
f.Close()
return nil, fmt.Errorf("%s is a directory", filename)
}
return f, nil
}

// gorootSrc is the directory holding the Go standard library sources, if
// known. runtime.GOROOT honors the $GOROOT environment variable and otherwise
// reports the GOROOT this binary was built with, which is the Go installation
// used to `go install` pprof in the common case.
var gorootSrc = func() string {
if goroot := runtime.GOROOT(); goroot != "" {
return filepath.Join(goroot, "src")
}
return ""
}()

// openGorootSourceFile tries to open a Go standard library source file under
// gorootSrc. Binaries built without -trimpath record standard library files
// under the build machine's GOROOT (e.g. /usr/local/go/src/runtime/proc.go),
// which may not exist locally, so resolve the path components after a "/src/"
// component against gorootSrc. Binaries built with -trimpath record them
// relative to $GOROOT/src (e.g. runtime/proc.go), so resolve relative paths
// against gorootSrc directly.
func openGorootSourceFile(path, gorootSrc string) (*os.File, error) {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the special handling for goroot, only executed if the openSourceFile previous machinery doesn't find anything.

if gorootSrc == "" {
return nil, fmt.Errorf("GOROOT is not known")
}
if !filepath.IsAbs(path) {
if f, err := tryOpenFile(filepath.Join(gorootSrc, path)); err == nil {
return f, nil
}
}
sPath := filepath.ToSlash(path)
for {
i := strings.Index(sPath, "/src/")
if i == -1 {
return nil, fmt.Errorf("could not find file %s under %s", path, gorootSrc)
}
sPath = sPath[i+len("/src/"):]
if f, err := tryOpenFile(filepath.Join(gorootSrc, filepath.FromSlash(sPath))); err == nil {
return f, nil
}
}
}

// trimPath cleans up a path by removing prefixes that are commonly
// found on profiles plus configured prefixes.
// TODO(aalexand): Consider optimizing out the redundant work done in this
Expand Down
131 changes: 131 additions & 0 deletions internal/report/source_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,12 @@ func TestOpenSourceFile(t *testing.T) {
desc: "error when not found",
path: "foo.cc",
},
{
desc: "directory is not matched",
searchPath: "$dir",
fs: []string{"foo/bar.cc"},
path: "foo",
},
} {
t.Run(tc.desc, func(t *testing.T) {
defer func() {
Expand Down Expand Up @@ -215,6 +221,131 @@ func TestOpenSourceFile(t *testing.T) {
}
}

func TestOpenGorootSourceFile(t *testing.T) {
gorootSrc := filepath.Join(t.TempDir(), "goroot", "src")
for _, f := range []string{"runtime/proc.go", "fmt/print.go"} {
path := filepath.Join(gorootSrc, filepath.FromSlash(f))
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
t.Fatalf("failed to create dir for %q: %v", path, err)
}
if err := os.WriteFile(path, nil, 0644); err != nil {
t.Fatalf("failed to create file %q: %v", path, err)
}
}
for _, tc := range []struct {
desc string
path string
noGoroot bool
wantPath string // Relative to gorootSrc. If empty, error is wanted.
}{
{
desc: "absolute path under a foreign GOROOT",
path: "/usr/local/go/src/runtime/proc.go",
wantPath: "runtime/proc.go",
},
{
desc: "absolute path with multiple src components",
path: "/home/user/src/go/src/fmt/print.go",
wantPath: "fmt/print.go",
},
{
desc: "relative path from a -trimpath build",
path: "runtime/proc.go",
wantPath: "runtime/proc.go",
},
{
desc: "absolute path without a src component",
path: "/usr/local/go/runtime/proc.go",
},
{
desc: "file missing from GOROOT",
path: "/usr/local/go/src/runtime/missing.go",
},
{
desc: "unknown GOROOT",
path: "/usr/local/go/src/runtime/proc.go",
noGoroot: true,
},
} {
t.Run(tc.desc, func(t *testing.T) {
src := gorootSrc
if tc.noGoroot {
src = ""
}
path := filepath.FromSlash(tc.path)
f, err := openGorootSourceFile(path, src)
if tc.wantPath == "" {
if err == nil {
gotPath := f.Name()
f.Close()
t.Fatalf("openGorootSourceFile(%q) = %q, want error", path, gotPath)
}
return
}
if err != nil {
t.Fatalf("openGorootSourceFile(%q) = err %v, want path %q", path, err, tc.wantPath)
}
defer f.Close()
if want := filepath.Join(src, filepath.FromSlash(tc.wantPath)); f.Name() != want {
t.Errorf("openGorootSourceFile(%q) = %q, want %q", path, f.Name(), want)
}
})
}
}

func TestOpenSourceFileGorootFallback(t *testing.T) {
fakeGorootSrc := filepath.Join(t.TempDir(), "goroot", "src")
path := filepath.Join(fakeGorootSrc, "runtime", "proc.go")
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
t.Fatalf("failed to create dir for %q: %v", path, err)
}
if err := os.WriteFile(path, nil, 0644); err != nil {
t.Fatalf("failed to create file %q: %v", path, err)
}
savedGorootSrc := gorootSrc
gorootSrc = fakeGorootSrc
defer func() { gorootSrc = savedGorootSrc }()

for _, tc := range []struct {
desc string
path string
wantErr bool
}{
{
desc: "absolute path under a foreign GOROOT",
path: "/usr/local/go/src/runtime/proc.go",
},
{
desc: "relative path from a -trimpath build",
path: "runtime/proc.go",
},
{
desc: "not found under GOROOT falls through to error",
path: "runtime/missing.go",
wantErr: true,
},
} {
t.Run(tc.desc, func(t *testing.T) {
f, err := openSourceFile(filepath.FromSlash(tc.path), "", "")
if tc.wantErr {
if err == nil {
gotPath := f.Name()
f.Close()
t.Fatalf("openSourceFile(%q) = %q, want error", tc.path, gotPath)
}
return
}
if err != nil {
t.Fatalf("openSourceFile(%q) = err %v, want path %q", tc.path, err, path)
}
defer f.Close()
if f.Name() != path {
t.Errorf("openSourceFile(%q) = %q, want %q", tc.path, f.Name(), path)
}
})
}
}

func TestIndentation(t *testing.T) {
for _, c := range []struct {
str string
Expand Down