You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

156 lines
4.7 KiB

// =============================================================================
// Active-GO Framework
// Copyright (c) 2024 ActiveThing (https://activething.com)
// Author: Juan V. Navarro juanvnl@activething.com
// =============================================================================
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// =============================================================================
package stack
import (
"bufio"
"bytes"
"os"
"runtime"
"strings"
)
// Frame represents a single stack frame in a program's execution stack.
// It contains detailed information about the source code location,
// function, and package where the frame was created.
type Frame struct {
// LineNumber is the line number in the source file where this frame was created
LineNumber int
// File is the full path to the source file containing this frame
File string
// FuncName is the name of the function where this frame was created,
// with any receiver types removed
FuncName string
// Package is the full package path where this frame was created
Package string
// Counter is the program counter address for this frame
Counter uintptr
// files is an internal cache of file contents
files map[string][]byte
}
// NewFrame creates a new Frame instance from a program counter address.
// It extracts the source code location, function name, and package information
// from the runtime stack.
//
// Parameters:
// - counter: Program counter address to create the frame from
//
// Returns:
// - *Frame: A new Frame instance, or nil if counter is 0
//
// Example:
//
// pc := runtime.Caller(0)
// frame := NewFrame(pc)
func NewFrame(counter uintptr) *Frame {
if counter == 0 {
return nil
}
sf := &Frame{
Counter: counter,
}
fn := runtime.FuncForPC(counter)
sf.Package, sf.FuncName = packageAndName(fn)
sf.File, sf.LineNumber = fn.FileLine(counter - 1)
return sf
}
// SourceLine retrieves the actual source code line for this stack frame.
// It reads the source file and returns the line of code that created this frame.
//
// Returns:
// - string: The source code line, or "unknown" if the line cannot be found
// - error: Any error encountered while reading the source file
//
// Example:
//
// frame := NewFrame(runtime.Caller(0))
// line, err := frame.SourceLine()
// if err != nil {
// log.Fatal(err)
// }
// fmt.Println("Source line:", line)
func (f Frame) SourceLine() (string, error) {
if f.LineNumber <= 0 {
return stackUnknownInfo, nil
}
file, err := os.Open(f.File)
if err != nil {
return "", err
}
scanner := bufio.NewScanner(file)
currentLine := 1
for scanner.Scan() {
if currentLine == f.LineNumber {
return string(bytes.Trim(scanner.Bytes(), " \t")), nil
}
currentLine++
}
_ = file.Close()
if err := scanner.Err(); err != nil {
return "", err
}
return stackUnknownInfo, nil
}
// packageAndName splits a fully qualified function name into its package and function parts.
// It handles nested packages and removes any receiver type information from the function name.
//
// Parameters:
// - fn: Runtime function information
//
// Returns:
// - string: Package path
// - string: Function name with receiver types removed
//
// Example:
//
// fn := runtime.FuncForPC(pc)
// pkg, name := packageAndName(fn)
// // For "github.com/user/pkg.(*Type).Method"
// // pkg = "github.com/user/pkg"
// // name = "Type.Method"
func packageAndName(fn *runtime.Func) (string, string) {
nm := fn.Name()
pg := ""
if ls := strings.LastIndex(nm, "/"); ls >= 0 {
pg = nm[:ls] + "/"
nm = nm[ls+1:]
}
if pd := strings.Index(nm, "."); pd >= 0 {
pg += nm[:pd]
nm = nm[pd+1:]
}
return pg, strings.Replace(nm, "·", ".", -1)
}

Powered by TurnKey Linux.