go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/starlark/builtins/stacktrace.go (about) 1 // Copyright 2018 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package builtins 16 17 import ( 18 "errors" 19 "fmt" 20 "regexp" 21 "strings" 22 23 "go.starlark.net/starlark" 24 ) 25 26 // CapturedStacktrace represents a stack trace returned by stacktrace(...). 27 // 28 // At the present time it can only be stringified (via str(...) in Starlark or 29 // via .String() in Go). 30 type CapturedStacktrace struct { 31 backtrace string 32 } 33 34 // CaptureStacktrace captures thread's stack trace, skipping some number of 35 // innermost frames. 36 // 37 // Returns an error if the stack is not deep enough to skip the requested number 38 // of frames. 39 func CaptureStacktrace(th *starlark.Thread, skip int) (*CapturedStacktrace, error) { 40 if th.CallStackDepth() <= skip { 41 return nil, fmt.Errorf("stacktrace: the stack is not deep enough to skip %d levels, has only %d frames", skip, th.CallStackDepth()) 42 } 43 44 stack := th.CallStack()[:th.CallStackDepth()-skip] 45 return &CapturedStacktrace{stack.String()}, nil 46 } 47 48 // Type is part of starlark.Value interface. 49 func (*CapturedStacktrace) Type() string { return "stacktrace" } 50 51 // Freeze is part of starlark.Value interface. 52 func (*CapturedStacktrace) Freeze() {} 53 54 // Truth is part of starlark.Value interface. 55 func (*CapturedStacktrace) Truth() starlark.Bool { return starlark.True } 56 57 // Hash is part of starlark.Value interface. 58 func (*CapturedStacktrace) Hash() (uint32, error) { return 0, errors.New("stacktrace is unhashable") } 59 60 // String is part of starlark.Value interface. 61 // 62 // Renders the stack trace as string. 63 func (s *CapturedStacktrace) String() string { return s.backtrace } 64 65 // Stacktrace is stacktrace(...) builtin. 66 // 67 // def stacktrace(skip=0): 68 // """Capture and returns a stack trace of the caller. 69 // 70 // A captured stacktrace is an opaque object that can be stringified to get a 71 // nice looking trace (e.g. for error messages). 72 // 73 // Args: 74 // skip: how many inner most frames to skip. 75 // """ 76 var Stacktrace = starlark.NewBuiltin("stacktrace", func(th *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 77 skip := starlark.MakeInt(0) 78 if err := starlark.UnpackArgs("stacktrace", args, kwargs, "skip?", &skip); err != nil { 79 return nil, err 80 } 81 switch lvl, err := starlark.AsInt32(skip); { 82 case err != nil: 83 return nil, fmt.Errorf("stacktrace: bad 'skip' value %s - %s", skip, err) 84 case lvl < 0: 85 return nil, fmt.Errorf("stacktrace: bad 'skip' value %d - must be non-negative", lvl) 86 default: 87 return CaptureStacktrace(th, lvl) 88 } 89 }) 90 91 // This matches "<module>:<line>:<col>: in <func>" where <line>:<col> is 92 // optional. 93 var stackLineRe = regexp.MustCompile(`^(\s*)(.*?): in (.*)$`) 94 95 // NormalizeStacktrace removes mentions of line and column numbers from a 96 // rendered stack trace: "main:1:5: in <toplevel>" => "main: in <toplevel>". 97 // 98 // Useful when comparing stack traces in tests to make the comparison less 99 // brittle. 100 func NormalizeStacktrace(trace string) string { 101 lines := strings.Split(trace, "\n") 102 for i := range lines { 103 if m := stackLineRe.FindStringSubmatch(lines[i]); m != nil { 104 space, module, funcname := m[1], m[2], m[3] 105 if idx := strings.IndexRune(module, ':'); idx != -1 { 106 module = module[:idx] 107 } 108 lines[i] = fmt.Sprintf("%s%s: in %s", space, module, funcname) 109 } 110 } 111 return strings.Join(lines, "\n") 112 }