github.com/goplus/igop@v0.25.0/runtime.go (about)

     1  /*
     2   * Copyright (c) 2022 The GoPlus Authors (goplus.org). All rights reserved.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package igop
    18  
    19  import (
    20  	"bytes"
    21  	"fmt"
    22  	"go/token"
    23  	"go/types"
    24  	"os"
    25  	"path/filepath"
    26  	"reflect"
    27  	"regexp"
    28  	"runtime"
    29  	"strings"
    30  	"sync/atomic"
    31  	"unsafe"
    32  
    33  	"github.com/visualfc/funcval"
    34  	"golang.org/x/tools/go/ssa"
    35  )
    36  
    37  func init() {
    38  	RegisterExternal("os.Exit", func(fr *frame, code int) {
    39  		interp := fr.interp
    40  		if atomic.LoadInt32(&interp.goexited) == 1 {
    41  			//os.Exit(code)
    42  			interp.chexit <- code
    43  		} else {
    44  			panic(exitPanic(code))
    45  		}
    46  	})
    47  	RegisterExternal("runtime.Goexit", func(fr *frame) {
    48  		interp := fr.interp
    49  		// main goroutine use panic
    50  		if goroutineID() == interp.mainid {
    51  			atomic.StoreInt32(&interp.goexited, 1)
    52  			panic(goexitPanic(0))
    53  		} else {
    54  			runtime.Goexit()
    55  		}
    56  	})
    57  	RegisterExternal("runtime.Caller", runtimeCaller)
    58  	RegisterExternal("runtime.FuncForPC", runtimeFuncForPC)
    59  	RegisterExternal("runtime.Callers", runtimeCallers)
    60  	RegisterExternal("(*runtime.Frames).Next", runtimeFramesNext)
    61  	RegisterExternal("(*runtime.Func).FileLine", runtimeFuncFileLine)
    62  	RegisterExternal("runtime.Stack", runtimeStack)
    63  	RegisterExternal("runtime/debug.Stack", debugStack)
    64  	RegisterExternal("runtime/debug.PrintStack", debugPrintStack)
    65  
    66  	if funcval.IsSupport {
    67  		RegisterExternal("(reflect.Value).Pointer", func(v reflect.Value) uintptr {
    68  			if v.Kind() == reflect.Func {
    69  				if fv, n := funcval.Get(v.Interface()); n == 1 {
    70  					pc := (*makeFuncVal)(unsafe.Pointer(fv)).pfn.base
    71  					return uintptr(pc)
    72  				}
    73  			}
    74  			return v.Pointer()
    75  		})
    76  	}
    77  }
    78  
    79  func runtimeFuncFileLine(fr *frame, f *runtime.Func, pc uintptr) (file string, line int) {
    80  	entry := f.Entry()
    81  	if isInlineFunc(f) && pc > entry {
    82  		interp := fr.interp
    83  		if pfn := findFuncByEntry(interp, int(entry)); pfn != nil {
    84  			// pc-1 : fn.instr.pos
    85  			pos := pfn.PosForPC(int(pc - entry - 1))
    86  			if !pos.IsValid() {
    87  				return "?", 0
    88  			}
    89  			fpos := interp.ctx.FileSet.Position(pos)
    90  			if fpos.Filename == "" {
    91  				return "??", fpos.Line
    92  			}
    93  			file, line = filepath.ToSlash(fpos.Filename), fpos.Line
    94  			return
    95  		}
    96  	}
    97  	return f.FileLine(pc)
    98  }
    99  
   100  func runtimeCaller(fr *frame, skip int) (pc uintptr, file string, line int, ok bool) {
   101  	if skip < 0 {
   102  		return runtime.Caller(skip)
   103  	}
   104  	rpc := make([]uintptr, 1)
   105  	n := runtimeCallers(fr, skip+1, rpc[:])
   106  	if n < 1 {
   107  		return
   108  	}
   109  	frame, _ := runtimeFramesNext(fr, runtime.CallersFrames(rpc))
   110  	return frame.PC, frame.File, frame.Line, frame.PC != 0
   111  }
   112  
   113  //go:linkname runtimePanic runtime.gopanic
   114  func runtimePanic(e interface{})
   115  
   116  // runtime.Callers => runtime.CallersFrames
   117  // 0 = runtime.Caller
   118  // 1 = frame
   119  // 2 = frame.caller
   120  // ...
   121  func runtimeCallers(fr *frame, skip int, pc []uintptr) int {
   122  	if len(pc) == 0 {
   123  		return 0
   124  	}
   125  	pcs := make([]uintptr, 1)
   126  
   127  	// runtime.Caller itself
   128  	runtime.Callers(0, pcs)
   129  	pcs[0] -= 1
   130  
   131  	caller := fr
   132  	for caller.valid() {
   133  		link := caller._panic
   134  		for link != nil {
   135  			pcs = append(pcs, uintptr(reflect.ValueOf(runtimePanic).Pointer()))
   136  			pcs = append(pcs, link.pcs...)
   137  			link = link.link
   138  		}
   139  		pcs = append(pcs, caller.pc())
   140  		caller = caller.caller
   141  	}
   142  	var rpc []uintptr
   143  	for _, pc := range pcs {
   144  		// skip wrapper method func
   145  		if fn := findFuncByPC(fr.interp, int(pc)); fn != nil && isWrapperFuncName(fn.Fn.String()) {
   146  			continue
   147  		}
   148  		rpc = append(rpc, pc)
   149  	}
   150  	if skip < 0 {
   151  		skip = 0
   152  	} else if skip > len(rpc)-1 {
   153  		return 0
   154  	}
   155  	return copy(pc, rpc[skip:])
   156  }
   157  
   158  func runtimeFuncForPC(fr *frame, pc uintptr) *runtime.Func {
   159  	if pfn := findFuncByPC(fr.interp, int(pc)); pfn != nil {
   160  		return runtimeFunc(pfn)
   161  	}
   162  	return runtime.FuncForPC(pc)
   163  }
   164  
   165  func findFuncByPC(interp *Interp, pc int) *function {
   166  	if pc == 0 {
   167  		return nil
   168  	}
   169  	for _, pfn := range interp.funcs {
   170  		if pc >= pfn.base && pc <= pfn.base+len(pfn.ssaInstrs) {
   171  			return pfn
   172  		}
   173  	}
   174  	return nil
   175  }
   176  
   177  func findFuncByEntry(interp *Interp, entry int) *function {
   178  	for _, pfn := range interp.funcs {
   179  		if entry == pfn.base {
   180  			return pfn
   181  		}
   182  	}
   183  	return nil
   184  }
   185  
   186  func isWrapperFuncName(name string) bool {
   187  	return strings.HasSuffix(name, "$bound") || strings.HasSuffix(name, "$thunk")
   188  }
   189  
   190  func runtimeFunc(pfn *function) *runtime.Func {
   191  	fn := pfn.Fn
   192  	f := inlineFunc(uintptr(pfn.base))
   193  	var autogen bool
   194  	f.name, autogen = fixedFuncName(pfn.Fn)
   195  	if autogen {
   196  		f.file = "<autogenerated>"
   197  		f.line = 1
   198  	} else {
   199  		if pos := fn.Pos(); pos != token.NoPos {
   200  			fpos := pfn.Interp.ctx.FileSet.Position(pos)
   201  			f.file = filepath.ToSlash(fpos.Filename)
   202  			f.line = fpos.Line
   203  		}
   204  	}
   205  	return (*runtime.Func)(unsafe.Pointer(f))
   206  }
   207  
   208  /*
   209  	type Frames struct {
   210  		// callers is a slice of PCs that have not yet been expanded to frames.
   211  		callers []uintptr
   212  
   213  		// frames is a slice of Frames that have yet to be returned.
   214  		frames     []Frame
   215  		frameStore [2]Frame
   216  	}
   217  */
   218  type runtimeFrames struct {
   219  	callers    []uintptr
   220  	frames     []runtime.Frame
   221  	frameStore [2]runtime.Frame
   222  }
   223  
   224  func runtimeFramesNext(fr *frame, frames *runtime.Frames) (frame runtime.Frame, more bool) {
   225  	ci := (*runtimeFrames)(unsafe.Pointer(frames))
   226  	for len(ci.frames) < 2 {
   227  		// Find the next frame.
   228  		// We need to look for 2 frames so we know what
   229  		// to return for the "more" result.
   230  		if len(ci.callers) == 0 {
   231  			break
   232  		}
   233  		pc := ci.callers[0]
   234  		ci.callers = ci.callers[1:]
   235  		f := runtimeFuncForPC(fr, pc)
   236  		if f == nil {
   237  			continue
   238  		}
   239  		ci.frames = append(ci.frames, runtime.Frame{
   240  			PC:       pc,
   241  			Func:     f,
   242  			Function: f.Name(),
   243  			Entry:    f.Entry(),
   244  			// Note: File,Line set below
   245  		})
   246  	}
   247  
   248  	// Pop one frame from the frame list. Keep the rest.
   249  	// Avoid allocation in the common case, which is 1 or 2 frames.
   250  	switch len(ci.frames) {
   251  	case 0: // In the rare case when there are no frames at all, we return Frame{}.
   252  		return
   253  	case 1:
   254  		frame = ci.frames[0]
   255  		ci.frames = ci.frameStore[:0]
   256  	case 2:
   257  		frame = ci.frames[0]
   258  		ci.frameStore[0] = ci.frames[1]
   259  		ci.frames = ci.frameStore[:1]
   260  	default:
   261  		frame = ci.frames[0]
   262  		ci.frames = ci.frames[1:]
   263  	}
   264  	more = len(ci.frames) > 0
   265  	if frame.Func != nil {
   266  		frame.File, frame.Line = runtimeFuncFileLine(fr, frame.Func, frame.PC)
   267  	}
   268  	return
   269  }
   270  
   271  func extractGoroutine() (string, bool) {
   272  	buf := make([]byte, 1024)
   273  	n := runtime.Stack(buf, false)
   274  	s := string(buf[:n])
   275  	if strings.HasPrefix(s, "goroutine") {
   276  		if pos := strings.Index(s, "\n"); pos != -1 {
   277  			return s[:pos+1], true
   278  		}
   279  	}
   280  	return "", false
   281  }
   282  
   283  func runtimeStack(fr *frame, buf []byte, all bool) int {
   284  	if len(buf) == 0 {
   285  		return 0
   286  	}
   287  	var w bytes.Buffer
   288  	if s, ok := extractGoroutine(); ok {
   289  		w.WriteString(s)
   290  	} else {
   291  		w.WriteString("goroutine 1 [running]:\n")
   292  	}
   293  	rpc := make([]uintptr, 64)
   294  	n := runtimeCallers(fr, 1, rpc)
   295  	fs := runtime.CallersFrames(rpc[:n])
   296  	for {
   297  		f, more := runtimeFramesNext(fr, fs)
   298  		if f.Function == "runtime.gopanic" {
   299  			w.WriteString("panic()")
   300  		} else {
   301  			w.WriteString(f.Function + "()")
   302  		}
   303  		w.WriteByte('\n')
   304  		w.WriteByte('\t')
   305  		w.WriteString(fmt.Sprintf("%v:%v", f.File, f.Line))
   306  		if f.PC != f.Entry {
   307  			w.WriteString(fmt.Sprintf(" +0x%x", f.PC-f.Entry))
   308  		}
   309  		w.WriteByte('\n')
   310  		if !more {
   311  			break
   312  		}
   313  	}
   314  	return copy(buf, w.Bytes())
   315  }
   316  
   317  // PrintStack prints to standard error the stack trace returned by runtime.Stack.
   318  func debugPrintStack(fr *frame) {
   319  	os.Stderr.Write(debugStack(fr))
   320  }
   321  
   322  // Stack returns a formatted stack trace of the goroutine that calls it.
   323  // It calls runtime.Stack with a large enough buffer to capture the entire trace.
   324  func debugStack(fr *frame) []byte {
   325  	buf := make([]byte, 1024)
   326  	for {
   327  		n := runtimeStack(fr, buf, false)
   328  		if n < len(buf) {
   329  			return buf[:n]
   330  		}
   331  		buf = make([]byte, 2*len(buf))
   332  	}
   333  }
   334  
   335  var (
   336  	reFuncName = regexp.MustCompile("\\$(\\d+)")
   337  )
   338  
   339  func fixedFuncName(fn *ssa.Function) (name string, autogen bool) {
   340  	name = fn.String()
   341  	name = reFuncName.ReplaceAllString(name, ".func$1")
   342  	if strings.HasPrefix(name, "(") {
   343  		if pos := strings.LastIndex(name, ")"); pos != -1 {
   344  			line := name[1:pos]
   345  			if strings.HasPrefix(line, "*") {
   346  				if dot := strings.LastIndex(line, "."); dot != -1 {
   347  					line = line[1:dot+1] + "(*" + line[dot+1:] + ")"
   348  				}
   349  			}
   350  			name = line + name[pos+1:]
   351  		}
   352  	}
   353  	if strings.HasSuffix(name, "$bound") {
   354  		return name[:len(name)-6] + "-fm", bound_is_autogen
   355  	} else if strings.HasSuffix(name, "$thunk") {
   356  		name = name[:len(name)-6]
   357  		if strings.HasPrefix(name, "struct{") {
   358  			return name, true
   359  		}
   360  		if sig, ok := fn.Type().(*types.Signature); ok {
   361  			if types.IsInterface(sig.Params().At(0).Type()) {
   362  				return name, true
   363  			}
   364  		}
   365  	}
   366  	return name, false
   367  }
   368  
   369  func runtimeGC(fr *frame) {
   370  	for fr.valid() {
   371  		fr.gc()
   372  		fr = fr.caller
   373  	}
   374  	runtime.GC()
   375  }