github.com/goplus/gop@v1.2.6/x/jsonrpc2/internal/stack/parse.go (about) 1 // Copyright 2020 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package stack 6 7 import ( 8 "bufio" 9 "io" 10 "regexp" 11 "strconv" 12 ) 13 14 var ( 15 reBlank = regexp.MustCompile(`^\s*$`) 16 reGoroutine = regexp.MustCompile(`^\s*goroutine (\d+) \[([^\]]*)\]:\s*$`) 17 reCall = regexp.MustCompile(`^\s*` + 18 `(created by )?` + //marker 19 `(([\w/.]+/)?[\w]+)\.` + //package 20 `(\(([^:.)]*)\)\.)?` + //optional type 21 `([\w\.]+)` + //function 22 `(\(.*\))?` + // args 23 `\s*$`) 24 rePos = regexp.MustCompile(`^\s*(.*):(\d+)( .*)?$`) 25 ) 26 27 // Scanner splits an input stream into lines in a way that is consumable by 28 // the parser. 29 type Scanner struct { 30 lines *bufio.Scanner 31 done bool 32 } 33 34 // NewScanner creates a scanner on top of a reader. 35 func NewScanner(r io.Reader) *Scanner { 36 s := &Scanner{ 37 lines: bufio.NewScanner(r), 38 } 39 s.Skip() // prefill 40 return s 41 } 42 43 // Peek returns the next line without consuming it. 44 func (s *Scanner) Peek() string { 45 if s.done { 46 return "" 47 } 48 return s.lines.Text() 49 } 50 51 // Skip consumes the next line without looking at it. 52 // Normally used after it has already been looked at using Peek. 53 func (s *Scanner) Skip() { 54 if !s.lines.Scan() { 55 s.done = true 56 } 57 } 58 59 // Next consumes and returns the next line. 60 func (s *Scanner) Next() string { 61 line := s.Peek() 62 s.Skip() 63 return line 64 } 65 66 // Done returns true if the scanner has reached the end of the underlying 67 // stream. 68 func (s *Scanner) Done() bool { 69 return s.done 70 } 71 72 // Err returns true if the scanner has reached the end of the underlying 73 // stream. 74 func (s *Scanner) Err() error { 75 return s.lines.Err() 76 } 77 78 // Match returns the submatchs of the regular expression against the next line. 79 // If it matched the line is also consumed. 80 func (s *Scanner) Match(re *regexp.Regexp) []string { 81 if s.done { 82 return nil 83 } 84 match := re.FindStringSubmatch(s.Peek()) 85 if match != nil { 86 s.Skip() 87 } 88 return match 89 } 90 91 // SkipBlank skips any number of pure whitespace lines. 92 func (s *Scanner) SkipBlank() { 93 for !s.done { 94 line := s.Peek() 95 if len(line) != 0 && !reBlank.MatchString(line) { 96 return 97 } 98 s.Skip() 99 } 100 } 101 102 // Parse the current contiguous block of goroutine stack traces until the 103 // scanned content no longer matches. 104 func Parse(scanner *Scanner) (Dump, error) { 105 dump := Dump{} 106 for { 107 gr, ok := parseGoroutine(scanner) 108 if !ok { 109 return dump, nil 110 } 111 dump = append(dump, gr) 112 } 113 } 114 115 func parseGoroutine(scanner *Scanner) (Goroutine, bool) { 116 match := scanner.Match(reGoroutine) 117 if match == nil { 118 return Goroutine{}, false 119 } 120 id, _ := strconv.ParseInt(match[1], 0, 32) 121 gr := Goroutine{ 122 ID: int(id), 123 State: match[2], 124 } 125 for { 126 frame, ok := parseFrame(scanner) 127 if !ok { 128 scanner.SkipBlank() 129 return gr, true 130 } 131 if frame.Position.Filename != "" { 132 gr.Stack = append(gr.Stack, frame) 133 } 134 } 135 } 136 137 func parseFrame(scanner *Scanner) (Frame, bool) { 138 fun, ok := parseFunction(scanner) 139 if !ok { 140 return Frame{}, false 141 } 142 frame := Frame{ 143 Function: fun, 144 } 145 frame.Position, ok = parsePosition(scanner) 146 // if ok is false, then this is a broken state. 147 // we got the func but not the file that must follow 148 // the consumed line can be recovered from the frame 149 //TODO: push back the fun raw 150 return frame, ok 151 } 152 153 func parseFunction(scanner *Scanner) (Function, bool) { 154 match := scanner.Match(reCall) 155 if match == nil { 156 return Function{}, false 157 } 158 return Function{ 159 Package: match[2], 160 Type: match[5], 161 Name: match[6], 162 }, true 163 } 164 165 func parsePosition(scanner *Scanner) (Position, bool) { 166 match := scanner.Match(rePos) 167 if match == nil { 168 return Position{}, false 169 } 170 line, _ := strconv.ParseInt(match[2], 0, 32) 171 return Position{Filename: match[1], Line: int(line)}, true 172 }