diff --git a/src/runtime/crash_test.go b/src/runtime/crash_test.go index 2ed0fd8f07..7fc620ac50 100644 --- a/src/runtime/crash_test.go +++ b/src/runtime/crash_test.go @@ -773,6 +773,16 @@ func init() { // We expect to crash, so exit 0 to indicate failure. os.Exit(0) } + if os.Getenv("GO_TEST_RUNTIME_NPE_READMEMSTATS") == "1" { + runtime.ReadMemStats(nil) + os.Exit(0) + } + if os.Getenv("GO_TEST_RUNTIME_NPE_FUNCMETHOD") == "1" { + var f *runtime.Func + _ = f.Entry() + os.Exit(0) + } + } func TestRuntimePanic(t *testing.T) { @@ -788,6 +798,32 @@ func TestRuntimePanic(t *testing.T) { } } +func TestTracebackRuntimeFunction(t *testing.T) { + testenv.MustHaveExec(t) + cmd := testenv.CleanCmdEnv(exec.Command(os.Args[0], "-test.run=TestTracebackRuntimeFunction")) + cmd.Env = append(cmd.Env, "GO_TEST_RUNTIME_NPE_READMEMSTATS=1") + out, err := cmd.CombinedOutput() + t.Logf("%s", out) + if err == nil { + t.Error("child process did not fail") + } else if want := "runtime.ReadMemStats"; !bytes.Contains(out, []byte(want)) { + t.Errorf("output did not contain expected string %q", want) + } +} + +func TestTracebackRuntimeMethod(t *testing.T) { + testenv.MustHaveExec(t) + cmd := testenv.CleanCmdEnv(exec.Command(os.Args[0], "-test.run=TestTracebackRuntimeMethod")) + cmd.Env = append(cmd.Env, "GO_TEST_RUNTIME_NPE_FUNCMETHOD=1") + out, err := cmd.CombinedOutput() + t.Logf("%s", out) + if err == nil { + t.Error("child process did not fail") + } else if want := "runtime.(*Func).Entry"; !bytes.Contains(out, []byte(want)) { + t.Errorf("output did not contain expected string %q", want) + } +} + // Test that g0 stack overflows are handled gracefully. func TestG0StackOverflow(t *testing.T) { testenv.MustHaveExec(t) diff --git a/src/runtime/traceback.go b/src/runtime/traceback.go index 4ca4ac51ad..1c75c447d2 100644 --- a/src/runtime/traceback.go +++ b/src/runtime/traceback.go @@ -1133,10 +1133,32 @@ func showfuncinfo(sf srcFunc, firstFrame bool, calleeID abi.FuncID) bool { // isExportedRuntime reports whether name is an exported runtime function. // It is only for runtime functions, so ASCII A-Z is fine. -// TODO: this handles exported functions but not exported methods. func isExportedRuntime(name string) bool { - const n = len("runtime.") - return len(name) > n && name[:n] == "runtime." && 'A' <= name[n] && name[n] <= 'Z' + // Check and remove package qualifier. + n := len("runtime.") + if len(name) <= n || name[:n] != "runtime." { + return false + } + name = name[n:] + rcvr := "" + + // Extract receiver type, if any. + // For example, runtime.(*Func).Entry + i := len(name) - 1 + for i >= 0 && name[i] != '.' { + i-- + } + if i >= 0 { + rcvr = name[:i] + name = name[i+1:] + // Remove parentheses and star for pointer receivers. + if len(rcvr) >= 3 && rcvr[0] == '(' && rcvr[1] == '*' && rcvr[len(rcvr)-1] == ')' { + rcvr = rcvr[2 : len(rcvr)-1] + } + } + + // Exported functions and exported methods on exported types. + return len(name) > 0 && 'A' <= name[0] && name[0] <= 'Z' && (len(rcvr) == 0 || 'A' <= rcvr[0] && rcvr[0] <= 'Z') } // elideWrapperCalling reports whether a wrapper function that called