1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
|
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !disable_events
// +build !disable_events
package event
import (
"reflect"
"runtime"
"sort"
"strings"
)
const (
// this is the maximum amount of helpers we scan past to find a non helper
helperDepthLimit = 5
)
type sources struct {
entries []caller
}
type Source struct {
Space string
Owner string
Name string
}
type caller struct {
helper bool
pc uintptr
source Source
}
var globalCallers chan *sources
// RegisterHelper records a function as being an event helper that should not
// be used when capturing the source information on events.
// v should be either a string or a function pointer.
// If v is a string it is of the form
//
// Space.Owner.Name
//
// where Owner and Name cannot contain '/' and Name also cannot contain '.'
func RegisterHelper(v interface{}) {
g := <-globalCallers
defer func() { globalCallers <- g }()
switch v := v.(type) {
case string:
g.entries = append(g.entries, caller{source: splitName(v), helper: true})
default:
g.helperFunction(v)
}
}
func init() {
g := &sources{}
// make all entries in the event package helpers
globalCallers = make(chan *sources, 1)
globalCallers <- g
RegisterHelper("golang.org/x/exp/event")
}
func newCallers() sources {
g := <-globalCallers
defer func() { globalCallers <- g }()
c := sources{}
c.entries = make([]caller, len(g.entries))
copy(c.entries, g.entries)
return c
}
func (c *sources) addCaller(entry caller) {
i := sort.Search(len(c.entries), func(i int) bool {
return c.entries[i].pc >= entry.pc
})
if i >= len(c.entries) {
// add to end
c.entries = append(c.entries, entry)
return
}
if c.entries[i].pc == entry.pc {
// already present
return
}
//expand the array
c.entries = append(c.entries, caller{})
//make a space
copy(c.entries[i+1:], c.entries[i:])
// insert the entry
c.entries[i] = entry
}
func (c *sources) getCaller(pc uintptr) (caller, bool) {
i := sort.Search(len(c.entries), func(i int) bool {
return c.entries[i].pc >= pc
})
if i == len(c.entries) || c.entries[i].pc != pc {
return caller{}, false
}
return c.entries[i], true
}
func scanStack() Source {
g := <-globalCallers
defer func() { globalCallers <- g }()
return g.scanStack()
}
func (c *sources) scanStack() Source {
// first capture the caller stack
var stack [helperDepthLimit]uintptr
// we can skip the first three entries
// runtime.Callers
// event.(*sources).scanStack (this function)
// another function in this package (because scanStack is private)
depth := runtime.Callers(3, stack[:]) // start at 2 to skip Callers and this function
// do a cheap first pass to see if we have an entry for this stack
for i := 0; i < depth; i++ {
pc := stack[i]
e, found := c.getCaller(pc)
if found {
if !e.helper {
// exact non helper match match found, return it
return e.source
}
// helper found, keep scanning
continue
}
// stack entry not found, we need to fill one in
f := runtime.FuncForPC(stack[i])
if f == nil {
// symtab lookup failed, pretend it does not exist
continue
}
e = caller{
source: splitName(f.Name()),
pc: pc,
}
e.helper = c.isHelper(e)
c.addCaller(e)
if !e.helper {
// found a non helper entry, add it and return it
return e.source
}
}
// ran out of stack, was all helpers
return Source{}
}
// we do helper matching by name, if the pc matched we would have already found
// that, but helper registration does not know the call stack pcs
func (c *sources) isHelper(entry caller) bool {
// scan to see if it matches any of the helpers
// we match by name in case of inlining
for _, e := range c.entries {
if !e.helper {
// ignore all the non helper entries
continue
}
if isMatch(entry.source.Space, e.source.Space) &&
isMatch(entry.source.Owner, e.source.Owner) &&
isMatch(entry.source.Name, e.source.Name) {
return true
}
}
return false
}
func isMatch(value, against string) bool {
return len(against) == 0 || value == against
}
func (c *sources) helperFunction(v interface{}) {
r := reflect.ValueOf(v)
pc := r.Pointer()
f := runtime.FuncForPC(pc)
entry := caller{
source: splitName(f.Name()),
pc: f.Entry(),
helper: true,
}
c.addCaller(entry)
if entry.pc != pc {
entry.pc = pc
c.addCaller(entry)
}
}
func splitName(full string) Source {
// Function is the fully-qualified function name. The name itself may
// have dots (for a closure, for instance), but it can't have slashes.
// So the package path ends at the first dot after the last slash.
entry := Source{Space: full}
slash := strings.LastIndexByte(full, '/')
if slash < 0 {
slash = 0
}
if dot := strings.IndexByte(full[slash:], '.'); dot >= 0 {
entry.Space = full[:slash+dot]
entry.Name = full[slash+dot+1:]
if dot = strings.LastIndexByte(entry.Name, '.'); dot >= 0 {
entry.Owner = entry.Name[:dot]
entry.Name = entry.Name[dot+1:]
}
}
return entry
}
|