github.com/insolar/vanilla@v0.0.0-20201023172447-248fdf805322/throw/minimizer.go (about) 1 // Copyright 2020 Insolar Network Ltd. 2 // All rights reserved. 3 // This material is licensed under the Insolar License version 1.0, 4 // available at https://github.com/insolar/assured-ledger/blob/master/LICENSE.md. 5 6 package throw 7 8 import ( 9 "bytes" 10 ) 11 12 func MinimizeStackTrace(st StackTrace, boundPackage string, includeBoundary bool) StackTrace { 13 var r stackTrace 14 switch v := st.(type) { 15 case stackTrace: 16 r = v 17 case nil: 18 return nil 19 default: 20 r.data = []byte(v.StackTraceAsText()) 21 r.limit = !v.IsFullStack() 22 } 23 min := MinimizePanicStack(r.data, boundPackage, includeBoundary) 24 if len(min) == len(r.data) { 25 return st 26 } 27 r.data = min 28 return r 29 } 30 31 func MinimizePanicStack(stackTrace []byte, boundPackage string, includeBoundary bool) []byte { 32 s := alignStart(stackTrace) 33 if len(s) == 0 { 34 return stackTrace 35 } 36 pos := skipDebugFrame(s) 37 s = s[pos:] 38 39 prefix := []byte(boundPackage) 40 // check defer section of panic stack - it will likely start with the boundPackage 41 start := skipFramesWithPrefix(s, prefix) 42 if start > 0 { 43 if start == len(s) { 44 return s 45 } 46 n := skipPanicFrame(s[start:]) 47 if n == 0 { 48 if bytes.HasPrefix(s[start:], []byte("created by ")) { 49 return s 50 } 51 return minimizeDebugStack(s[start:], boundPackage, includeBoundary) 52 } 53 54 n += start 55 n += len(minimizeDebugStack(s[n:], boundPackage, includeBoundary)) 56 if n == len(s) { 57 return s 58 } 59 return s[start:n] 60 } 61 62 start = 0 63 pos = 0 64 65 for { 66 n := skipPanicFrame(s[pos:]) 67 if n > 0 { 68 pos += n 69 n = len(minimizeDebugStack(s[pos:], boundPackage, includeBoundary)) 70 return s[start : pos+n] 71 } 72 73 if bytes.HasPrefix(s[pos:], prefix) { 74 break 75 } 76 n = indexOfFrame(s[pos:], 1) 77 if n <= 0 { 78 return s 79 } 80 pos += n 81 } 82 83 startBound := pos 84 pos += skipFramesWithPrefix(s[pos:], prefix) 85 if pos == len(s) { 86 return s[start:] 87 } 88 89 if !bytes.Contains(s[pos:], []byte("runtime/panic.go")) { 90 if startBound == start { 91 return s 92 } 93 if includeBoundary { 94 if n := indexOfFrame(s[startBound:], 1); n > 0 { 95 startBound += n 96 } 97 } 98 return s[start:startBound] 99 } 100 101 return minimizeDebugStack(s[pos:], boundPackage, includeBoundary) 102 } 103 104 func skipPanicFrame(s []byte) int { 105 return skipFrameByMethod(s, "panic", "runtime/panic.go") 106 } 107 108 func skipDebugFrame(s []byte) int { 109 return skipFrameByMethod(s, "runtime/debug.Stack", "debug/stack.go") 110 } 111 112 func MinimizeDebugStack(stackTrace []byte, boundPackage string, includeBoundary bool) []byte { 113 s := alignStart(stackTrace) 114 if len(s) == 0 { 115 return stackTrace 116 } 117 pos := skipDebugFrame(s) 118 return minimizeDebugStack(s[pos:], boundPackage, includeBoundary) 119 } 120 121 func minimizeDebugStack(s []byte, boundPackage string, includeBoundary bool) []byte { 122 end, ok := skipFramesUntilPrefix(s, []byte(boundPackage)) 123 if end == 0 { 124 return s 125 } 126 if ok && includeBoundary { 127 if n := indexOfFrame(s[end:], 1); n > 0 { 128 end += n 129 } 130 } 131 return s[:end] 132 } 133 134 func skipFramesUntilPrefix(s []byte, prefix []byte) (int, bool) { 135 end := 0 136 for { 137 if bytes.HasPrefix(s[end:], prefix) { 138 return end, true 139 } 140 switch n := indexOfFrame(s[end:], 1); { 141 case n > 0: 142 end += n 143 case n == 0: 144 return len(s), false 145 default: 146 return end, false 147 } 148 } 149 } 150 151 func skipFramesWithPrefix(s []byte, prefix []byte) int { 152 end := 0 153 for { 154 if !bytes.HasPrefix(s[end:], prefix) { 155 return end 156 } 157 end += len(prefix) 158 n := indexOfFrame(s[end:], 1) 159 if n <= 0 { 160 return len(s) 161 } 162 end += n 163 } 164 } 165 166 func alignStart(s []byte) []byte { 167 for { 168 eol := bytes.IndexByte(s, '\n') 169 if eol <= 0 || eol == len(s)-1 { 170 return nil 171 } 172 if s[eol+1] == '\t' { 173 return s 174 } 175 s = s[eol+1:] 176 } 177 } 178 179 func skipFrameByMethod(s []byte, methodPrefix, fileSuffix string) int { 180 if !bytes.HasPrefix(s, []byte(methodPrefix)) { 181 return 0 182 } 183 eol := bytes.IndexByte(s, '\n') 184 if eol <= 0 || eol == len(s)-1 { 185 return 0 186 } 187 if s[eol+1] != '\t' { 188 return 0 189 } 190 frameEnd := eol 191 if n := bytes.IndexByte(s[eol+2:], '\n'); n >= 0 { 192 frameEnd += 3 + n 193 s = s[:frameEnd] 194 } else { 195 frameEnd = len(s) 196 } 197 slash := bytes.LastIndexByte(s, '/') 198 if slash < 0 { 199 return 0 200 } 201 if semicolon := bytes.LastIndexByte(s[slash+1:], ':'); semicolon >= 0 { 202 s = s[:slash+semicolon+1] 203 } 204 if !bytes.HasSuffix(s, []byte(fileSuffix)) { 205 return 0 206 } 207 return frameEnd 208 }