| // 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 | |
| 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 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 | |
| } |