github.com/KarpelesLab/contexter@v1.0.2/contexter.go (about)

     1  package contexter
     2  
     3  import (
     4  	"context"
     5  	"reflect"
     6  	"regexp"
     7  	"runtime/debug"
     8  	"strconv"
     9  	"unsafe"
    10  )
    11  
    12  var (
    13  	matchHex    = regexp.MustCompile("[^+]0x([a-fA-F0-9]{2,16})\\??, 0x([a-fA-F0-9]{2,16})\\??")
    14  	intfType    = reflect.TypeOf((*context.Context)(nil)).Elem()
    15  	intfTypeVal = intfValue(intfType)
    16  )
    17  
    18  type itab struct {
    19  	inter uintptr
    20  	typ   uintptr
    21  }
    22  
    23  type interfaceHeader struct {
    24  	tab  *itab
    25  	data unsafe.Pointer
    26  }
    27  
    28  func intfValue(v interface{}) uintptr {
    29  	x := (*interfaceHeader)(unsafe.Pointer(&v))
    30  	return uintptr(x.data)
    31  }
    32  
    33  func testPtr(p1, p2 uintptr) context.Context {
    34  	defer func() {
    35  		recover()
    36  	}()
    37  
    38  	// we expect itab in p1
    39  	tab := (*itab)(unsafe.Pointer(p1))
    40  
    41  	if tab == nil || tab.inter != intfTypeVal {
    42  		// not a context.Context interface
    43  		return nil
    44  	}
    45  
    46  	var res context.Context
    47  	ti := (*interfaceHeader)(unsafe.Pointer(&res))
    48  	ti.data = unsafe.Pointer(p2)
    49  	ti.tab = tab
    50  
    51  	return res
    52  }
    53  
    54  func testTab(tab, expect uintptr) bool {
    55  	defer func() {
    56  		recover()
    57  	}()
    58  
    59  	t := (*itab)(unsafe.Pointer(tab))
    60  	return t.inter == expect
    61  }
    62  
    63  // Context will return the first instance of context.Context found in the
    64  // calling stack
    65  func Context() context.Context {
    66  	// argp := unsafe.Pointer(frame.argp)
    67  	// The "instruction" of argument printing is encoded in _FUNCDATA_ArgInfo.
    68  	// See cmd/compile/internal/ssagen.emitArgInfo for the description of the
    69  	// encoding
    70  	// _FUNCDATA_ArgInfo            = 5
    71  	// ...
    72  	// Unfortunately(?) go provides no way to get this kind of data from the
    73  	// stack except by calling runtime.Stack(), in which case pointers are
    74  	// encoded in hex as string. This is not optimal, but at least it works.
    75  
    76  	v := debug.Stack()
    77  	res := matchHex.FindAllSubmatch(v, -1)
    78  	for _, h := range res {
    79  		i1, _ := strconv.ParseUint(string(h[1]), 16, 64)
    80  		i2, _ := strconv.ParseUint(string(h[2]), 16, 64)
    81  
    82  		if i1 == 0 || i2 == 0 {
    83  			continue
    84  		}
    85  
    86  		res := testPtr(uintptr(i1), uintptr(i2))
    87  		if res != nil {
    88  			return res
    89  		}
    90  	}
    91  
    92  	return nil
    93  }
    94  
    95  func Find(i interface{}) bool {
    96  	// try to find object of type intf
    97  	v := reflect.ValueOf(i).Elem()
    98  	searchType := intfValue(v.Type())
    99  
   100  	// when an interface is called, it passes two pointers
   101  	res := matchHex.FindAllSubmatch(debug.Stack(), -1)
   102  	for _, h := range res {
   103  		i1, _ := strconv.ParseUint(string(h[1]), 16, 64)
   104  		i2, _ := strconv.ParseUint(string(h[2]), 16, 64)
   105  
   106  		if i1 == 0 || i2 == 0 {
   107  			continue
   108  		}
   109  
   110  		if !testTab(uintptr(i1), searchType) {
   111  			continue
   112  		}
   113  		// found our value
   114  		ti := (*interfaceHeader)(unsafe.Pointer(v.UnsafeAddr()))
   115  		ti.tab = (*itab)(unsafe.Pointer(uintptr(i1)))
   116  		ti.data = unsafe.Pointer(uintptr(i2))
   117  		return true
   118  	}
   119  	return false
   120  }