| // Copyright 2022 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package runtime_test | |
| import ( | |
| "fmt" | |
| "internal/testenv" | |
| "runtime" | |
| "testing" | |
| ) | |
| // The tests in this file test the function start line metadata included in | |
| // _func and inlinedCall. TestStartLine hard-codes the start lines of functions | |
| // in this file. If code moves, the test will need to be updated. | |
| // | |
| // The "start line" of a function should be the line containing the func | |
| // keyword. | |
| func normalFunc() int { | |
| return callerStartLine(false) | |
| } | |
| func multilineDeclarationFunc() int { | |
| return multilineDeclarationFunc1(0, 0, 0) | |
| } | |
| //go:noinline | |
| func multilineDeclarationFunc1( | |
| a, b, c int) int { | |
| return callerStartLine(false) | |
| } | |
| func blankLinesFunc() int { | |
| // Some | |
| // lines | |
| // without | |
| // code | |
| return callerStartLine(false) | |
| } | |
| func inlineFunc() int { | |
| return inlineFunc1() | |
| } | |
| func inlineFunc1() int { | |
| return callerStartLine(true) | |
| } | |
| var closureFn func() int | |
| func normalClosure() int { | |
| // Assign to global to ensure this isn't inlined. | |
| closureFn = func() int { | |
| return callerStartLine(false) | |
| } | |
| return closureFn() | |
| } | |
| func inlineClosure() int { | |
| return func() int { | |
| return callerStartLine(true) | |
| }() | |
| } | |
| func TestStartLine(t *testing.T) { | |
| // We test inlined vs non-inlined variants. We can't do that if | |
| // optimizations are disabled. | |
| testenv.SkipIfOptimizationOff(t) | |
| testCases := []struct { | |
| name string | |
| fn func() int | |
| want int | |
| }{ | |
| { | |
| name: "normal", | |
| fn: normalFunc, | |
| want: 21, | |
| }, | |
| { | |
| name: "multiline-declaration", | |
| fn: multilineDeclarationFunc, | |
| want: 30, | |
| }, | |
| { | |
| name: "blank-lines", | |
| fn: blankLinesFunc, | |
| want: 35, | |
| }, | |
| { | |
| name: "inline", | |
| fn: inlineFunc, | |
| want: 49, | |
| }, | |
| { | |
| name: "normal-closure", | |
| fn: normalClosure, | |
| want: 57, | |
| }, | |
| { | |
| name: "inline-closure", | |
| fn: inlineClosure, | |
| want: 64, | |
| }, | |
| } | |
| for _, tc := range testCases { | |
| t.Run(tc.name, func(t *testing.T) { | |
| got := tc.fn() | |
| if got != tc.want { | |
| t.Errorf("start line got %d want %d", got, tc.want) | |
| } | |
| }) | |
| } | |
| } | |
| //go:noinline | |
| func callerStartLine(wantInlined bool) int { | |
| var pcs [1]uintptr | |
| n := runtime.Callers(2, pcs[:]) | |
| if n != 1 { | |
| panic(fmt.Sprintf("no caller of callerStartLine? n = %d", n)) | |
| } | |
| frames := runtime.CallersFrames(pcs[:]) | |
| frame, _ := frames.Next() | |
| inlined := frame.Func == nil // Func always set to nil for inlined frames | |
| if wantInlined != inlined { | |
| panic(fmt.Sprintf("caller %s inlined got %v want %v", frame.Function, inlined, wantInlined)) | |
| } | |
| return runtime.FrameStartLine(&frame) | |
| } |