...

Source file src/runtime/symtab_test.go

Documentation: runtime

     1  // Copyright 2009 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  package runtime_test
     6  
     7  import (
     8  	"runtime"
     9  	"strings"
    10  	"testing"
    11  	"unsafe"
    12  )
    13  
    14  func TestCaller(t *testing.T) {
    15  	procs := runtime.GOMAXPROCS(-1)
    16  	c := make(chan bool, procs)
    17  	for p := 0; p < procs; p++ {
    18  		go func() {
    19  			for i := 0; i < 1000; i++ {
    20  				testCallerFoo(t)
    21  			}
    22  			c <- true
    23  		}()
    24  		defer func() {
    25  			<-c
    26  		}()
    27  	}
    28  }
    29  
    30  // These are marked noinline so that we can use FuncForPC
    31  // in testCallerBar.
    32  //
    33  //go:noinline
    34  func testCallerFoo(t *testing.T) {
    35  	testCallerBar(t)
    36  }
    37  
    38  //go:noinline
    39  func testCallerBar(t *testing.T) {
    40  	for i := 0; i < 2; i++ {
    41  		pc, file, line, ok := runtime.Caller(i)
    42  		f := runtime.FuncForPC(pc)
    43  		if !ok ||
    44  			!strings.HasSuffix(file, "symtab_test.go") ||
    45  			(i == 0 && !strings.HasSuffix(f.Name(), "testCallerBar")) ||
    46  			(i == 1 && !strings.HasSuffix(f.Name(), "testCallerFoo")) ||
    47  			line < 5 || line > 1000 ||
    48  			f.Entry() >= pc {
    49  			t.Errorf("incorrect symbol info %d: %t %d %d %s %s %d",
    50  				i, ok, f.Entry(), pc, f.Name(), file, line)
    51  		}
    52  	}
    53  }
    54  
    55  func lineNumber() int {
    56  	_, _, line, _ := runtime.Caller(1)
    57  	return line // return 0 for error
    58  }
    59  
    60  // Do not add/remove lines in this block without updating the line numbers.
    61  var firstLine = lineNumber() // 0
    62  var (                        // 1
    63  	lineVar1             = lineNumber()               // 2
    64  	lineVar2a, lineVar2b = lineNumber(), lineNumber() // 3
    65  )                        // 4
    66  var compLit = []struct { // 5
    67  	lineA, lineB int // 6
    68  }{ // 7
    69  	{ // 8
    70  		lineNumber(), lineNumber(), // 9
    71  	}, // 10
    72  	{ // 11
    73  		lineNumber(), // 12
    74  		lineNumber(), // 13
    75  	}, // 14
    76  	{ // 15
    77  		lineB: lineNumber(), // 16
    78  		lineA: lineNumber(), // 17
    79  	}, // 18
    80  }                                     // 19
    81  var arrayLit = [...]int{lineNumber(), // 20
    82  	lineNumber(), lineNumber(), // 21
    83  	lineNumber(), // 22
    84  }                                  // 23
    85  var sliceLit = []int{lineNumber(), // 24
    86  	lineNumber(), lineNumber(), // 25
    87  	lineNumber(), // 26
    88  }                         // 27
    89  var mapLit = map[int]int{ // 28
    90  	29:           lineNumber(), // 29
    91  	30:           lineNumber(), // 30
    92  	lineNumber(): 31,           // 31
    93  	lineNumber(): 32,           // 32
    94  }                           // 33
    95  var intLit = lineNumber() + // 34
    96  	lineNumber() + // 35
    97  	lineNumber() // 36
    98  func trythis() { // 37
    99  	recordLines(lineNumber(), // 38
   100  		lineNumber(), // 39
   101  		lineNumber()) // 40
   102  }
   103  
   104  // Modifications below this line are okay.
   105  
   106  var l38, l39, l40 int
   107  
   108  func recordLines(a, b, c int) {
   109  	l38 = a
   110  	l39 = b
   111  	l40 = c
   112  }
   113  
   114  func TestLineNumber(t *testing.T) {
   115  	trythis()
   116  	for _, test := range []struct {
   117  		name string
   118  		val  int
   119  		want int
   120  	}{
   121  		{"firstLine", firstLine, 0},
   122  		{"lineVar1", lineVar1, 2},
   123  		{"lineVar2a", lineVar2a, 3},
   124  		{"lineVar2b", lineVar2b, 3},
   125  		{"compLit[0].lineA", compLit[0].lineA, 9},
   126  		{"compLit[0].lineB", compLit[0].lineB, 9},
   127  		{"compLit[1].lineA", compLit[1].lineA, 12},
   128  		{"compLit[1].lineB", compLit[1].lineB, 13},
   129  		{"compLit[2].lineA", compLit[2].lineA, 17},
   130  		{"compLit[2].lineB", compLit[2].lineB, 16},
   131  
   132  		{"arrayLit[0]", arrayLit[0], 20},
   133  		{"arrayLit[1]", arrayLit[1], 21},
   134  		{"arrayLit[2]", arrayLit[2], 21},
   135  		{"arrayLit[3]", arrayLit[3], 22},
   136  
   137  		{"sliceLit[0]", sliceLit[0], 24},
   138  		{"sliceLit[1]", sliceLit[1], 25},
   139  		{"sliceLit[2]", sliceLit[2], 25},
   140  		{"sliceLit[3]", sliceLit[3], 26},
   141  
   142  		{"mapLit[29]", mapLit[29], 29},
   143  		{"mapLit[30]", mapLit[30], 30},
   144  		{"mapLit[31]", mapLit[31+firstLine] + firstLine, 31}, // nb it's the key not the value
   145  		{"mapLit[32]", mapLit[32+firstLine] + firstLine, 32}, // nb it's the key not the value
   146  
   147  		{"intLit", intLit - 2*firstLine, 34 + 35 + 36},
   148  
   149  		{"l38", l38, 38},
   150  		{"l39", l39, 39},
   151  		{"l40", l40, 40},
   152  	} {
   153  		if got := test.val - firstLine; got != test.want {
   154  			t.Errorf("%s on firstLine+%d want firstLine+%d (firstLine=%d, val=%d)",
   155  				test.name, got, test.want, firstLine, test.val)
   156  		}
   157  	}
   158  }
   159  
   160  func TestNilName(t *testing.T) {
   161  	defer func() {
   162  		if ex := recover(); ex != nil {
   163  			t.Fatalf("expected no nil panic, got=%v", ex)
   164  		}
   165  	}()
   166  	if got := (*runtime.Func)(nil).Name(); got != "" {
   167  		t.Errorf("Name() = %q, want %q", got, "")
   168  	}
   169  }
   170  
   171  var dummy int
   172  
   173  func inlined() {
   174  	// Side effect to prevent elimination of this entire function.
   175  	dummy = 42
   176  }
   177  
   178  // A function with an InlTree. Returns a PC within the function body.
   179  //
   180  // No inline to ensure this complete function appears in output.
   181  //
   182  //go:noinline
   183  func tracebackFunc(t *testing.T) uintptr {
   184  	// This body must be more complex than a single call to inlined to get
   185  	// an inline tree.
   186  	inlined()
   187  	inlined()
   188  
   189  	// Acquire a PC in this function.
   190  	pc, _, _, ok := runtime.Caller(0)
   191  	if !ok {
   192  		t.Fatalf("Caller(0) got ok false, want true")
   193  	}
   194  
   195  	return pc
   196  }
   197  
   198  // Test that CallersFrames handles PCs in the alignment region between
   199  // functions (int 3 on amd64) without crashing.
   200  //
   201  // Go will never generate a stack trace containing such an address, as it is
   202  // not a valid call site. However, the cgo traceback function passed to
   203  // runtime.SetCgoTraceback may not be completely accurate and may incorrect
   204  // provide PCs in Go code or the alignment region between functions.
   205  //
   206  // Go obviously doesn't easily expose the problematic PCs to running programs,
   207  // so this test is a bit fragile. Some details:
   208  //
   209  //   - tracebackFunc is our target function. We want to get a PC in the
   210  //     alignment region following this function. This function also has other
   211  //     functions inlined into it to ensure it has an InlTree (this was the source
   212  //     of the bug in issue 44971).
   213  //
   214  //   - We acquire a PC in tracebackFunc, walking forwards until FuncForPC says
   215  //     we're in a new function. The last PC of the function according to FuncForPC
   216  //     should be in the alignment region (assuming the function isn't already
   217  //     perfectly aligned).
   218  //
   219  // This is a regression test for issue 44971.
   220  func TestFunctionAlignmentTraceback(t *testing.T) {
   221  	pc := tracebackFunc(t)
   222  
   223  	// Double-check we got the right PC.
   224  	f := runtime.FuncForPC(pc)
   225  	if !strings.HasSuffix(f.Name(), "tracebackFunc") {
   226  		t.Fatalf("Caller(0) = %+v, want tracebackFunc", f)
   227  	}
   228  
   229  	// Iterate forward until we find a different function. Back up one
   230  	// instruction is (hopefully) an alignment instruction.
   231  	for runtime.FuncForPC(pc) == f {
   232  		pc++
   233  	}
   234  	pc--
   235  
   236  	// Is this an alignment region filler instruction? We only check this
   237  	// on amd64 for simplicity. If this function has no filler, then we may
   238  	// get a false negative, but will never get a false positive.
   239  	if runtime.GOARCH == "amd64" {
   240  		code := *(*uint8)(unsafe.Pointer(pc))
   241  		if code != 0xcc { // INT $3
   242  			t.Errorf("PC %v code got %#x want 0xcc", pc, code)
   243  		}
   244  	}
   245  
   246  	// Finally ensure that Frames.Next doesn't crash when processing this
   247  	// PC.
   248  	frames := runtime.CallersFrames([]uintptr{pc})
   249  	frame, _ := frames.Next()
   250  	if frame.Func != f {
   251  		t.Errorf("frames.Next() got %+v want %+v", frame.Func, f)
   252  	}
   253  }
   254  
   255  func BenchmarkFunc(b *testing.B) {
   256  	pc, _, _, ok := runtime.Caller(0)
   257  	if !ok {
   258  		b.Fatal("failed to look up PC")
   259  	}
   260  	f := runtime.FuncForPC(pc)
   261  	b.Run("Name", func(b *testing.B) {
   262  		for i := 0; i < b.N; i++ {
   263  			name := f.Name()
   264  			if name != "runtime_test.BenchmarkFunc" {
   265  				b.Fatalf("unexpected name %q", name)
   266  			}
   267  		}
   268  	})
   269  	b.Run("Entry", func(b *testing.B) {
   270  		for i := 0; i < b.N; i++ {
   271  			pc := f.Entry()
   272  			if pc == 0 {
   273  				b.Fatal("zero PC")
   274  			}
   275  		}
   276  	})
   277  	b.Run("FileLine", func(b *testing.B) {
   278  		for i := 0; i < b.N; i++ {
   279  			file, line := f.FileLine(pc)
   280  			if !strings.HasSuffix(file, "symtab_test.go") || line == 0 {
   281  				b.Fatalf("unexpected file/line %q:%d", file, line)
   282  			}
   283  		}
   284  	})
   285  }
   286  

View as plain text