github.com/insolar/vanilla@v0.0.0-20201023172447-248fdf805322/throw/stackcmp.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 "bytes" 9 10 // StackRelation shows relevance of 2 stacks 11 type StackRelation int8 12 13 const ( 14 SubsetStack StackRelation = iota - 2 15 StackTop 16 EqualStack 17 FullStack // vs StackTop 18 SupersetStack // vs SubsetStack 19 DifferentStack 20 ) 21 22 // CompareStackTrace returns a relation between the given stacks. 23 // Only supports StackTrace instances created by this module, otherwise will return DifferentStack 24 func CompareStackTrace(st0, st1 StackTrace) StackRelation { 25 return CompareStackTraceExt(st0, st1, StrictMatch) 26 } 27 28 // StackCompareMode defines precision of stack trace comparison 29 type StackCompareMode uint8 30 31 const ( 32 StrictMatch StackCompareMode = iota 33 SameLine 34 SameMethod 35 ) 36 37 // CompareStackTraceExt returns a relation between the given stacks with the given mode/precision. 38 // Only supports StackTrace instances created by this module, otherwise will return DifferentStack 39 func CompareStackTraceExt(st0, st1 StackTrace, mode StackCompareMode) StackRelation { 40 if bst0, ok := st0.(stackTrace); ok { 41 if bst1, ok := st1.(stackTrace); ok { 42 switch { 43 case bst0.limit == bst1.limit: 44 return CompareDebugStackTrace(bst0.data, bst1.data, mode) 45 case bst0.limit: 46 if isStackTraceTop(bst0.data, bst1.data, mode) { 47 return StackTop 48 } 49 default: 50 if isStackTraceTop(bst1.data, bst0.data, mode) { 51 return FullStack 52 } 53 } 54 } 55 } 56 return DifferentStack 57 } 58 59 // CompareDebugStackTrace returns a relation between the stacks produced by debug.Stack() with the given mode/precision. 60 func CompareDebugStackTrace(bst0, bst1 []byte, mode StackCompareMode) StackRelation { 61 // TODO This method will break on 100+ frames and then debug.Stack may output "...additional frames elided..." 62 // see runtime.traceback1 in runtime/traceback.go 63 if mode == StrictMatch { 64 switch { 65 case len(bst0) == len(bst1): 66 if bytes.Equal(bst0, bst1) { 67 return EqualStack 68 } 69 case len(bst0) > len(bst1): 70 if bytes.HasSuffix(bst0, bst1) { 71 return SupersetStack 72 } 73 case bytes.HasSuffix(bst1, bst0): 74 return SubsetStack 75 } 76 return DifferentStack 77 } 78 79 swapped := false 80 if len(bst0) > len(bst1) { 81 swapped = true 82 bst0, bst1 = bst1, bst0 83 } 84 85 compare := 0 86 for { 87 lastFrameEnd, lastShortestEq := backwardCmpEol(bst0, bst1) 88 89 if lastShortestEq == 0 { 90 switch { 91 case len(bst0) == len(bst1): 92 case len(bst0) < len(bst1): 93 compare = -1 94 default: 95 compare = 1 96 } 97 break 98 } 99 100 sep := byte(':') 101 sepPos := bytes.LastIndexByte(bst0[:lastFrameEnd], sep) 102 103 switch { 104 case sepPos <= 0: 105 case sepPos >= lastShortestEq: 106 sepPos = -1 107 case mode == SameLine: 108 sepPos++ 109 sep = ' ' 110 if sep2 := bytes.IndexByte(bst0[sepPos:lastFrameEnd], sep); sep2 <= 0 { 111 sepPos = -1 112 } else { 113 sepPos += sep2 114 if sepPos >= lastShortestEq { 115 sepPos = -1 116 } 117 } 118 } 119 120 if sepPos <= 0 { 121 return DifferentStack 122 } 123 bst0s := bst0[sepPos+1 : lastFrameEnd] 124 125 d := len(bst1) - len(bst0) 126 lastEq := d + lastShortestEq 127 lastFrameEnd1 := d + lastFrameEnd 128 sepPos1 := bytes.LastIndexByte(bst1[:lastEq], sep) 129 if sepPos1 <= 0 { 130 return DifferentStack 131 } 132 bst1s := bst1[sepPos1+1 : lastFrameEnd1] 133 134 switch { 135 case len(bst0s) == len(bst1s): 136 compare = -bytes.Compare(bst0s, bst1s) 137 case len(bst0s) < len(bst1s): 138 compare = 1 139 default: 140 compare = -1 141 } 142 143 bst0 = bst0[:sepPos] 144 bst1 = bst1[:sepPos1] 145 } 146 147 switch { 148 case compare == 0: 149 return EqualStack 150 case compare > 0 != swapped: 151 return SupersetStack 152 default: 153 return SubsetStack 154 } 155 } 156 157 func backwardCmpEol(bShortest, b2 []byte) (lastFrameEnd int, lastShortestEq int) { 158 lastFrameEnd = len(bShortest) - 1 159 if bShortest[lastFrameEnd] != '\n' { 160 lastFrameEnd++ 161 } 162 163 flip := false 164 for i, j := lastFrameEnd-1, len(b2); i >= 0; i-- { 165 j-- 166 b := bShortest[i] 167 if b != b2[j] { 168 return lastFrameEnd, i + 1 169 } 170 if b == '\n' { 171 if flip { 172 flip = false 173 lastFrameEnd = i 174 } else { 175 flip = true 176 } 177 } 178 } 179 return lastFrameEnd, 0 180 } 181 182 func isStackTraceTop(bstTop, bstFull []byte, mode StackCompareMode) bool { 183 n := len(bstTop) 184 if len(bstFull) <= n { 185 return false 186 } 187 188 i, j := cmpLongestTillEol(bstTop, bstFull), 0 189 switch { 190 case i == n: 191 return true 192 case bstTop[i] != '\n': 193 return false 194 case bstFull[i] == '(': 195 j = bytes.IndexByte(bstFull[i:], '\n') 196 if j < 0 { 197 return false 198 } 199 case bstFull[i] != '\n': 200 return false 201 } 202 203 i++ 204 k := cmpLongestTillEol(bstTop[i:], bstFull[i+j:]) 205 k += i 206 207 switch { 208 case k == n: 209 return true 210 case bstTop[k] == '\n': 211 return true 212 } 213 214 var sep byte 215 switch mode { 216 case SameLine: 217 sep = '+' 218 case SameMethod: 219 sep = ':' 220 default: 221 return false 222 } 223 224 z := bytes.IndexByte(bstTop[k:], '\n') 225 if z < 0 { 226 z = len(bstTop) - 1 227 } else { 228 z += k 229 } 230 m := bytes.LastIndexByte(bstTop[i:z], sep) 231 if m >= 0 && i+m < k { 232 return true 233 } 234 235 return false 236 } 237 238 func cmpLongestTillEol(shortest, s []byte) int { 239 i := 0 240 for n := len(shortest); i < n; i++ { 241 switch shortest[i] { 242 case '\n': 243 return i 244 case s[i]: 245 continue 246 } 247 break 248 } 249 return i 250 }