| // 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) |
| } |