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