Files
gf/debug/gdebug/gdebug_caller.go
John Guo cb7cfa58ab fix: guard os.Args access for wasm which panics when building (#4762)
This pull request improves the reliability and safety of server restart
and reload operations by ensuring the correct retrieval of the current
executable path and process arguments. It replaces direct usage of
`os.Args[0]` with a more robust approach using `gfile.SelfPath()`, and
adds error handling for cases where the executable path cannot be
determined. Additionally, it introduces a helper function to safely
obtain process arguments, and removes an unnecessary import.

**Executable Path Handling and Error Checking:**

- Replaced usage of `os.Args[0]` with `gfile.SelfPath()` throughout the
server admin and process management code to reliably determine the
current executable path; added checks and error responses when the path
cannot be determined.
[[1]](diffhunk://#diff-0d174b149c56c4aa7ffeba2be94d16dc1b8000933f1f2a2e6bf011acdad3272fL122-R134)
[[2]](diffhunk://#diff-0d174b149c56c4aa7ffeba2be94d16dc1b8000933f1f2a2e6bf011acdad3272fL168-R184)
[[3]](diffhunk://#diff-3b4265be7ef0335b832dacfc2fc7ddc0f9dfae5b81340ff57d2b6a526c60d9e1L62-R65)
- Added early returns in initialization functions if `os.Args` is empty,
preventing potential panics or misbehavior when the argument list is
missing.
[[1]](diffhunk://#diff-0aa99f033274ea60b9c466ae4fc98d0816ec13781d8ec787fb3ef106a49a79ecR35-R37)
[[2]](diffhunk://#diff-5782fa47aa858b8e8358fd50353b050ee30418b7844b36e313e9c6d01188c092R47-R49)

**Process Argument Handling:**

- Introduced the `getCurrentProcessArgs()` helper function to safely
return process arguments (excluding the program name), ensuring correct
behavior even if no arguments are provided. Updated process creation
calls to use this helper.

**Code Cleanup:**

- Removed an unused import of the `os` package from
`ghttp_server_admin.go`.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-22 20:44:57 +08:00

209 lines
5.4 KiB
Go

// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gdebug
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"reflect"
"runtime"
"strings"
)
const (
maxCallerDepth = 1000
stackFilterKey = "/debug/gdebug/gdebug"
)
var (
goRootForFilter = runtime.GOROOT() // goRootForFilter is used for stack filtering purpose.
binaryVersion = "" // The version of current running binary(uint64 hex).
binaryVersionMd5 = "" // The version of current running binary(MD5).
selfPath = "" // Current running binary absolute path.
)
func init() {
if goRootForFilter != "" {
goRootForFilter = strings.ReplaceAll(goRootForFilter, "\\", "/")
}
if len(os.Args) == 0 {
return
}
// Initialize internal package variable: selfPath.
selfPath, _ = exec.LookPath(os.Args[0])
if selfPath != "" {
selfPath, _ = filepath.Abs(selfPath)
}
if selfPath == "" {
selfPath, _ = filepath.Abs(os.Args[0])
}
}
// Caller returns the function name and the absolute file path along with its line
// number of the caller.
func Caller(skip ...int) (function string, path string, line int) {
return CallerWithFilter(nil, skip...)
}
// CallerWithFilter returns the function name and the absolute file path along with
// its line number of the caller.
//
// The parameter `filters` is used to filter the path of the caller.
func CallerWithFilter(filters []string, skip ...int) (function string, path string, line int) {
var (
number = 0
ok = true
)
if len(skip) > 0 {
number = skip[0]
}
pc, file, line, start := callerFromIndex(filters)
if start != -1 {
for i := start + number; i < maxCallerDepth; i++ {
if i != start {
pc, file, line, ok = runtime.Caller(i)
}
if ok {
if filterFileByFilters(file, filters) {
continue
}
function = ""
if fn := runtime.FuncForPC(pc); fn == nil {
function = "unknown"
} else {
function = fn.Name()
}
return function, file, line
} else {
break
}
}
}
return "", "", -1
}
// callerFromIndex returns the caller position and according information exclusive of the
// debug package.
//
// VERY NOTE THAT, the returned index value should be `index - 1` as the caller's start point.
func callerFromIndex(filters []string) (pc uintptr, file string, line int, index int) {
var ok bool
for index = 0; index < maxCallerDepth; index++ {
if pc, file, line, ok = runtime.Caller(index); ok {
if filterFileByFilters(file, filters) {
continue
}
if index > 0 {
index--
}
return
}
}
return 0, "", -1, -1
}
func filterFileByFilters(file string, filters []string) (filtered bool) {
// Filter empty file.
if file == "" {
return true
}
// Filter gdebug package callings.
if strings.Contains(file, stackFilterKey) {
return true
}
for _, filter := range filters {
if filter != "" && strings.Contains(file, filter) {
return true
}
}
// GOROOT filter.
if goRootForFilter != "" && len(file) >= len(goRootForFilter) && file[0:len(goRootForFilter)] == goRootForFilter {
// https://github.com/gogf/gf/issues/2047
fileSeparator := file[len(goRootForFilter)]
if fileSeparator == filepath.Separator || fileSeparator == '\\' || fileSeparator == '/' {
return true
}
}
return false
}
// CallerPackage returns the package name of the caller.
func CallerPackage() string {
function, _, _ := Caller()
// it defines a new internal function to retrieve the package name from caller function name,
// which is for unit testing purpose for core logic of this function.
return getPackageFromCallerFunction(function)
}
func getPackageFromCallerFunction(function string) string {
indexSplit := strings.LastIndexByte(function, '/')
if indexSplit == -1 {
return function[:strings.IndexByte(function, '.')]
}
var (
leftPart = function[:indexSplit+1]
rightPart = function[indexSplit+1:]
indexDot = strings.IndexByte(rightPart, '.')
)
if indexDot >= 0 {
rightPart = rightPart[:indexDot]
}
return leftPart + rightPart
}
// CallerFunction returns the function name of the caller.
func CallerFunction() string {
function, _, _ := Caller()
function = function[strings.LastIndexByte(function, '/')+1:]
function = function[strings.IndexByte(function, '.')+1:]
return function
}
// CallerFilePath returns the file path of the caller.
func CallerFilePath() string {
_, path, _ := Caller()
return path
}
// CallerDirectory returns the directory of the caller.
func CallerDirectory() string {
_, path, _ := Caller()
return filepath.Dir(path)
}
// CallerFileLine returns the file path along with the line number of the caller.
func CallerFileLine() string {
_, path, line := Caller()
return fmt.Sprintf(`%s:%d`, path, line)
}
// CallerFileLineShort returns the file name along with the line number of the caller.
func CallerFileLineShort() string {
_, path, line := Caller()
return fmt.Sprintf(`%s:%d`, filepath.Base(path), line)
}
// FuncPath returns the complete function path of given `f`.
func FuncPath(f any) string {
return runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
}
// FuncName returns the function name of given `f`.
func FuncName(f any) string {
path := FuncPath(f)
if path == "" {
return ""
}
index := strings.LastIndexByte(path, '/')
if index < 0 {
index = strings.LastIndexByte(path, '\\')
}
return path[index+1:]
}