1// Copyright 2023 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//go:build cgo
6
7package runtime_test
8
9import (
10	"bytes"
11	"fmt"
12	"internal/testenv"
13	"internal/trace"
14	"io"
15	"os"
16	"runtime"
17	"strings"
18	"testing"
19)
20
21// TestTraceUnwindCGO verifies that trace events emitted in cgo callbacks
22// produce the same stack traces and don't cause any crashes regardless of
23// tracefpunwindoff being set to 0 or 1.
24func TestTraceUnwindCGO(t *testing.T) {
25	if *flagQuick {
26		t.Skip("-quick")
27	}
28	testenv.MustHaveGoBuild(t)
29	t.Parallel()
30
31	exe, err := buildTestProg(t, "testprogcgo")
32	if err != nil {
33		t.Fatal(err)
34	}
35
36	wantLogs := []string{
37		"goCalledFromC",
38		"goCalledFromCThread",
39	}
40	logs := make(map[string]*trace.Event)
41	for _, category := range wantLogs {
42		logs[category] = nil
43	}
44	for _, tracefpunwindoff := range []int{1, 0} {
45		env := fmt.Sprintf("GODEBUG=tracefpunwindoff=%d", tracefpunwindoff)
46		got := runBuiltTestProg(t, exe, "Trace", env)
47		prefix, tracePath, found := strings.Cut(got, ":")
48		if !found || prefix != "trace path" {
49			t.Fatalf("unexpected output:\n%s\n", got)
50		}
51		defer os.Remove(tracePath)
52
53		traceData, err := os.ReadFile(tracePath)
54		if err != nil {
55			t.Fatalf("failed to read trace: %s", err)
56		}
57		for category := range logs {
58			event := mustFindLogV2(t, bytes.NewReader(traceData), category)
59			if wantEvent := logs[category]; wantEvent == nil {
60				logs[category] = &event
61			} else if got, want := dumpStackV2(&event), dumpStackV2(wantEvent); got != want {
62				t.Errorf("%q: got stack:\n%s\nwant stack:\n%s\n", category, got, want)
63			}
64		}
65	}
66}
67
68func mustFindLogV2(t *testing.T, trc io.Reader, category string) trace.Event {
69	r, err := trace.NewReader(trc)
70	if err != nil {
71		t.Fatalf("bad trace: %v", err)
72	}
73	var candidates []trace.Event
74	for {
75		ev, err := r.ReadEvent()
76		if err == io.EOF {
77			break
78		}
79		if err != nil {
80			t.Fatalf("failed to parse trace: %v", err)
81		}
82		if ev.Kind() == trace.EventLog && ev.Log().Category == category {
83			candidates = append(candidates, ev)
84		}
85	}
86	if len(candidates) == 0 {
87		t.Fatalf("could not find log with category: %q", category)
88	} else if len(candidates) > 1 {
89		t.Fatalf("found more than one log with category: %q", category)
90	}
91	return candidates[0]
92}
93
94// dumpStack returns e.Stack() as a string.
95func dumpStackV2(e *trace.Event) string {
96	var buf bytes.Buffer
97	e.Stack().Frames(func(f trace.StackFrame) bool {
98		file := strings.TrimPrefix(f.File, runtime.GOROOT())
99		fmt.Fprintf(&buf, "%s\n\t%s:%d\n", f.Func, file, f.Line)
100		return true
101	})
102	return buf.String()
103}
104