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  }