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  }