Source file src/runtime/start_line_test.go

     1  // Copyright 2022 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  	"fmt"
     9  	"internal/testenv"
    10  	"runtime"
    11  	"testing"
    12  )
    13  
    14  // The tests in this file test the function start line metadata included in
    15  // _func and inlinedCall. TestStartLine hard-codes the start lines of functions
    16  // in this file. If code moves, the test will need to be updated.
    17  //
    18  // The "start line" of a function should be the line containing the func
    19  // keyword.
    20  
    21  func normalFunc() int {
    22  	return callerStartLine(false)
    23  }
    24  
    25  func multilineDeclarationFunc() int {
    26  	return multilineDeclarationFunc1(0, 0, 0)
    27  }
    28  
    29  //go:noinline
    30  func multilineDeclarationFunc1(
    31  	a, b, c int) int {
    32  	return callerStartLine(false)
    33  }
    34  
    35  func blankLinesFunc() int {
    36  
    37  	// Some
    38  	// lines
    39  	// without
    40  	// code
    41  
    42  	return callerStartLine(false)
    43  }
    44  
    45  func inlineFunc() int {
    46  	return inlineFunc1()
    47  }
    48  
    49  func inlineFunc1() int {
    50  	return callerStartLine(true)
    51  }
    52  
    53  var closureFn func() int
    54  
    55  func normalClosure() int {
    56  	// Assign to global to ensure this isn't inlined.
    57  	closureFn = func() int {
    58  		return callerStartLine(false)
    59  	}
    60  	return closureFn()
    61  }
    62  
    63  func inlineClosure() int {
    64  	return func() int {
    65  		return callerStartLine(true)
    66  	}()
    67  }
    68  
    69  func TestStartLine(t *testing.T) {
    70  	// We test inlined vs non-inlined variants. We can't do that if
    71  	// optimizations are disabled.
    72  	testenv.SkipIfOptimizationOff(t)
    73  
    74  	testCases := []struct {
    75  		name string
    76  		fn   func() int
    77  		want int
    78  	}{
    79  		{
    80  			name: "normal",
    81  			fn:   normalFunc,
    82  			want: 21,
    83  		},
    84  		{
    85  			name: "multiline-declaration",
    86  			fn:   multilineDeclarationFunc,
    87  			want: 30,
    88  		},
    89  		{
    90  			name: "blank-lines",
    91  			fn:   blankLinesFunc,
    92  			want: 35,
    93  		},
    94  		{
    95  			name: "inline",
    96  			fn:   inlineFunc,
    97  			want: 49,
    98  		},
    99  		{
   100  			name: "normal-closure",
   101  			fn:   normalClosure,
   102  			want: 57,
   103  		},
   104  		{
   105  			name: "inline-closure",
   106  			fn:   inlineClosure,
   107  			want: 64,
   108  		},
   109  	}
   110  
   111  	for _, tc := range testCases {
   112  		t.Run(tc.name, func(t *testing.T) {
   113  			got := tc.fn()
   114  			if got != tc.want {
   115  				t.Errorf("start line got %d want %d", got, tc.want)
   116  			}
   117  		})
   118  	}
   119  }
   120  
   121  //go:noinline
   122  func callerStartLine(wantInlined bool) int {
   123  	var pcs [1]uintptr
   124  	n := runtime.Callers(2, pcs[:])
   125  	if n != 1 {
   126  		panic(fmt.Sprintf("no caller of callerStartLine? n = %d", n))
   127  	}
   128  
   129  	frames := runtime.CallersFrames(pcs[:])
   130  	frame, _ := frames.Next()
   131  
   132  	inlined := frame.Func == nil // Func always set to nil for inlined frames
   133  	if wantInlined != inlined {
   134  		panic(fmt.Sprintf("caller %s inlined got %v want %v", frame.Function, inlined, wantInlined))
   135  	}
   136  
   137  	return runtime.FrameStartLine(&frame)
   138  }
   139  

View as plain text