github.com/nya3jp/tast@v0.0.0-20230601000426-85c8e4d83a9b/src/go.chromium.org/tast/core/internal/caller/caller.go (about) 1 // Copyright 2021 The ChromiumOS Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 // Package caller provides utilities to inspect the caller of a function. 6 package caller 7 8 import ( 9 "fmt" 10 "path" 11 "runtime" 12 13 "go.chromium.org/tast/core/internal/packages" 14 ) 15 16 // isRedirect returns true if curFN and nextFN are the same function after 17 // normalization, meaning curFN is a redirect to nextFN. 18 func isRedirect(curFN, nextFN string) bool { 19 return packages.Normalize(curFN) == packages.Normalize(nextFN) 20 } 21 22 // FuncWithIgnore is Get with custom ignore function. If ignore returns true, 23 // the function is ignored on counting the number of skips. 24 // This method is exported for unit testing. 25 func FuncWithIgnore(skip int, ignore func(curFN, nextFN string) bool) (*runtime.Func, uintptr) { 26 var callStack []string 27 skipCount := 0 28 for { 29 pc, _, _, ok := runtime.Caller(len(callStack)) 30 if !ok { 31 panic("Could not decide the caller") 32 } 33 f := runtime.FuncForPC(pc) 34 callStack = append(callStack, f.Name()) 35 36 if n := len(callStack); n >= 2 && ignore(callStack[n-1], callStack[n-2]) { 37 // Ignore the function on counting the number of skips. 38 continue 39 } 40 if skipCount == skip { 41 return f, pc 42 } 43 skipCount++ 44 } 45 } 46 47 // Get is the implementation of the same-name public function. 48 func Get(skip int) string { 49 f, _ := FuncWithIgnore(skip+1, isRedirect) 50 return f.Name() 51 } 52 53 // Func is similar to Get but returns *runtime.Func and program counter of 54 // the caller. 55 func Func(skip int) (*runtime.Func, uintptr) { 56 return FuncWithIgnore(skip+1, isRedirect) 57 } 58 59 // Check is the implementation of the same-name public function. 60 // Check uses packages.Same to compare two packages. 61 func Check(skip int, pkgs []string) { 62 caller := Get(skip + 1) 63 64 callerPkg, _ := packages.SplitFuncName(caller) 65 for _, pkg := range pkgs { 66 if packages.Same(callerPkg, pkg) { 67 return 68 } 69 } 70 callee := Get(skip) 71 panic(fmt.Sprintf( 72 "%s is not allowed to call %s; check the list in %s", 73 caller, path.Base(callee), path.Base(callee))) 74 }