mirror of
				https://github.com/go-delve/delve.git
				synced 2025-10-31 18:57:18 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			316 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			316 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package source
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"go/ast"
 | |
| 	"go/parser"
 | |
| 	"go/token"
 | |
| )
 | |
| 
 | |
| type Searcher struct {
 | |
| 	fileset *token.FileSet
 | |
| 	visited map[string]*ast.File
 | |
| }
 | |
| 
 | |
| func New() *Searcher {
 | |
| 	return &Searcher{fileset: token.NewFileSet(), visited: make(map[string]*ast.File)}
 | |
| }
 | |
| 
 | |
| type NoNodeError struct {
 | |
| 	f string
 | |
| 	l int
 | |
| }
 | |
| 
 | |
| func (n NoNodeError) Error() string {
 | |
| 	return fmt.Sprintf("could not find node at %s:%d", n.f, n.l)
 | |
| }
 | |
| 
 | |
| // Returns the first node at the given file:line.
 | |
| func (s *Searcher) FirstNodeAt(fname string, line int) (*ast.File, ast.Node, error) {
 | |
| 	var node ast.Node
 | |
| 	f, err := s.parse(fname)
 | |
| 	if err != nil {
 | |
| 		return nil, nil, err
 | |
| 	}
 | |
| 	ast.Inspect(f, func(n ast.Node) bool {
 | |
| 		if n == nil {
 | |
| 			return true
 | |
| 		}
 | |
| 		position := s.fileset.Position(n.Pos())
 | |
| 		if position.Line == line {
 | |
| 			node = n
 | |
| 			return false
 | |
| 		}
 | |
| 		return true
 | |
| 	})
 | |
| 	if node == nil {
 | |
| 		return nil, nil, NoNodeError{f: fname, l: line}
 | |
| 	}
 | |
| 	return f, node, nil
 | |
| }
 | |
| 
 | |
| type Done string
 | |
| 
 | |
| func (d Done) Error() string {
 | |
| 	return string(d)
 | |
| }
 | |
| 
 | |
| // Returns all possible lines that could be executed after the given file:line,
 | |
| // within the same source file.
 | |
| func (s *Searcher) NextLines(fname string, line int) (lines []int, err error) {
 | |
| 	parsedFile, n, err := s.FirstNodeAt(fname, line)
 | |
| 	if err != nil {
 | |
| 		return lines, nil
 | |
| 	}
 | |
| 
 | |
| 	switch x := n.(type) {
 | |
| 	// Follow if statements
 | |
| 	case *ast.IfStmt:
 | |
| 		lines = removeDuplicateLines(s.parseIfStmtBlock(x, parsedFile))
 | |
| 		return
 | |
| 
 | |
| 	// Follow case statements.
 | |
| 	// Append line for first statement following each 'case' condition.
 | |
| 	case *ast.SwitchStmt:
 | |
| 		var switchEnd int
 | |
| 		ast.Inspect(x, func(n ast.Node) bool {
 | |
| 			if stmt, ok := n.(*ast.SwitchStmt); ok {
 | |
| 				switchEnd = s.fileset.Position(stmt.End()).Line
 | |
| 				return true
 | |
| 			}
 | |
| 			if switchEnd < s.fileset.Position(x.Pos()).Line {
 | |
| 				return false
 | |
| 			}
 | |
| 			if stmt, ok := n.(*ast.CaseClause); ok {
 | |
| 				p := stmt.Body[0].Pos()
 | |
| 				pos := s.fileset.Position(p)
 | |
| 				lines = append(lines, pos.Line)
 | |
| 				return false
 | |
| 			}
 | |
| 			return true
 | |
| 		})
 | |
| 		lines = removeDuplicateLines(lines)
 | |
| 		return
 | |
| 	// Default case - find next source line.
 | |
| 	default:
 | |
| 		lines = removeDuplicateLines(s.parseDefaultBlock(x, parsedFile, line))
 | |
| 		return
 | |
| 	}
 | |
| 	return lines, nil
 | |
| }
 | |
| 
 | |
| // Parses file named by fname, caching files it has already parsed.
 | |
| func (s *Searcher) parse(fname string) (*ast.File, error) {
 | |
| 	if f, ok := s.visited[fname]; ok {
 | |
| 		return f, nil
 | |
| 	}
 | |
| 	f, err := parser.ParseFile(s.fileset, fname, nil, 0)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	s.visited[fname] = f
 | |
| 	return f, nil
 | |
| }
 | |
| 
 | |
| func (s *Searcher) nextLineAfter(parsedFile *ast.File, line int) (nextLine int) {
 | |
| 	var done bool
 | |
| 	ast.Inspect(parsedFile, func(n ast.Node) bool {
 | |
| 		if done || n == nil {
 | |
| 			return false
 | |
| 		}
 | |
| 		pos := s.fileset.Position(n.Pos())
 | |
| 		if line < pos.Line {
 | |
| 			nextLine = pos.Line
 | |
| 			done = true
 | |
| 			return false
 | |
| 		}
 | |
| 		return true
 | |
| 	})
 | |
| 	return
 | |
| }
 | |
| 
 | |
| // If we are at an 'if' statement, employ the following algorithm:
 | |
| //    * Follow all 'else if' statements, appending their line number
 | |
| //    * Follow any 'else' statement if it exists, appending the line
 | |
| //      number of the statement following the 'else'.
 | |
| //    * If there is no 'else' statement, append line of first statement
 | |
| //      following the entire 'if' block.
 | |
| func (s *Searcher) parseIfStmtBlock(ifRoot *ast.IfStmt, parsedFile *ast.File) []int {
 | |
| 	var (
 | |
| 		rbrace     int
 | |
| 		ifStmtLine = s.fileset.Position(ifRoot.Body.List[0].Pos()).Line
 | |
| 		lines      = []int{ifStmtLine}
 | |
| 	)
 | |
| 
 | |
| 	for {
 | |
| 		if ifRoot.Else == nil {
 | |
| 			// Grab first line after entire 'if' block
 | |
| 			rbrace = s.fileset.Position(ifRoot.Body.Rbrace).Line
 | |
| 			return append(lines, s.nextLineAfter(parsedFile, rbrace))
 | |
| 		}
 | |
| 		// Continue following 'else if' branches.
 | |
| 		if elseStmt, ok := ifRoot.Else.(*ast.IfStmt); ok {
 | |
| 			lines = append(lines, s.fileset.Position(elseStmt.Pos()).Line)
 | |
| 			ifRoot = elseStmt
 | |
| 			continue
 | |
| 		}
 | |
| 		// Grab next line after final 'else'.
 | |
| 		pos := s.fileset.Position(ifRoot.Else.Pos())
 | |
| 		return append(lines, s.nextLineAfter(parsedFile, pos.Line))
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // We are not at a branch, employ the following algorithm:
 | |
| //    * Traverse tree, storing any loop as a parent
 | |
| //    * Find next source line after the given line
 | |
| //    * Check and see if we've passed the scope of any parent we've
 | |
| //      stored. If so, pop them off the stack. The last parent that
 | |
| //      is left get's appending to our list of lines since we could
 | |
| //      end up at the top of the loop again.
 | |
| func (s *Searcher) parseDefaultBlock(ifRoot ast.Node, parsedFile *ast.File, line int) []int {
 | |
| 	var (
 | |
| 		found                bool
 | |
| 		lines                []int
 | |
| 		parents              []*ast.BlockStmt
 | |
| 		parentBlockBeginLine int
 | |
| 		deferEndLine         int
 | |
| 	)
 | |
| 	ast.Inspect(parsedFile, func(n ast.Node) bool {
 | |
| 		if found || n == nil {
 | |
| 			return false
 | |
| 		}
 | |
| 
 | |
| 		pos := s.fileset.Position(n.Pos())
 | |
| 		if line < pos.Line && deferEndLine != 0 {
 | |
| 			p := s.fileset.Position(n.Pos())
 | |
| 			if deferEndLine < p.Line {
 | |
| 				found = true
 | |
| 				lines = append(lines, p.Line)
 | |
| 				return false
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if stmt, ok := n.(*ast.ForStmt); ok {
 | |
| 			pos := s.fileset.Position(stmt.Pos())
 | |
| 			if stmt.Cond == nil {
 | |
| 				nextLine := s.fileset.Position(stmt.Body.List[0].Pos()).Line
 | |
| 				if line < nextLine {
 | |
| 					lines = []int{nextLine}
 | |
| 					found = true
 | |
| 					parents = nil
 | |
| 					return false
 | |
| 				}
 | |
| 			}
 | |
| 			parents = append(parents, stmt.Body)
 | |
| 			parentBlockBeginLine = pos.Line
 | |
| 		}
 | |
| 
 | |
| 		if _, ok := n.(*ast.GenDecl); ok {
 | |
| 			return true
 | |
| 		}
 | |
| 
 | |
| 		if dn, ok := n.(*ast.DeferStmt); ok && line < pos.Line {
 | |
| 			endpos := s.fileset.Position(dn.End())
 | |
| 			deferEndLine = endpos.Line
 | |
| 			return false
 | |
| 		}
 | |
| 
 | |
| 		if st, ok := n.(*ast.DeclStmt); ok {
 | |
| 			beginpos := s.fileset.Position(st.Pos())
 | |
| 			endpos := s.fileset.Position(st.End())
 | |
| 			if beginpos.Line < endpos.Line {
 | |
| 				return true
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// Check to see if we've found the "next" line.
 | |
| 		if line < pos.Line {
 | |
| 			if _, ok := n.(*ast.BlockStmt); ok {
 | |
| 				return true
 | |
| 			}
 | |
| 			var (
 | |
| 				parent        *ast.BlockStmt
 | |
| 				parentEndLine int
 | |
| 			)
 | |
| 			for len(parents) > 0 {
 | |
| 				parent = parents[len(parents)-1]
 | |
| 
 | |
| 				// Grab the line number of the right brace of the parent block.
 | |
| 				parentEndLine = s.fileset.Position(parent.Rbrace).Line
 | |
| 
 | |
| 				// Check to see if we're still within the parents block.
 | |
| 				// If we are, we're done and that is our parent.
 | |
| 				if parentEndLine > line {
 | |
| 					parentBlockBeginLine = s.fileset.Position(parent.Pos()).Line
 | |
| 					break
 | |
| 				}
 | |
| 				// If we weren't, and there is only 1 parent, we no longer have one.
 | |
| 				if len(parents) == 1 {
 | |
| 					parent = nil
 | |
| 					break
 | |
| 				}
 | |
| 				// Remove that parent from the stack.
 | |
| 				parents = parents[0 : len(parents)-1]
 | |
| 			}
 | |
| 			if parent != nil {
 | |
| 				var (
 | |
| 					endfound   bool
 | |
| 					beginFound bool
 | |
| 					beginLine  int
 | |
| 				)
 | |
| 
 | |
| 				ast.Inspect(parsedFile, func(n ast.Node) bool {
 | |
| 					if n == nil || endfound {
 | |
| 						return false
 | |
| 					}
 | |
| 					if _, ok := n.(*ast.BlockStmt); ok {
 | |
| 						return true
 | |
| 					}
 | |
| 					pos := s.fileset.Position(n.Pos())
 | |
| 					if parentBlockBeginLine < pos.Line && !beginFound {
 | |
| 						beginFound = true
 | |
| 						beginLine = pos.Line
 | |
| 						return true
 | |
| 					}
 | |
| 					if parentEndLine < pos.Line {
 | |
| 						if _, ok := n.(*ast.FuncDecl); !ok {
 | |
| 							lines = append(lines, beginLine, pos.Line)
 | |
| 						}
 | |
| 						endfound = true
 | |
| 						return false
 | |
| 					}
 | |
| 					return true
 | |
| 				})
 | |
| 				lines = append(lines, parentBlockBeginLine)
 | |
| 			}
 | |
| 			switch n.(type) {
 | |
| 			case *ast.BranchStmt, *ast.FuncDecl:
 | |
| 			default:
 | |
| 				lines = append(lines, pos.Line)
 | |
| 			}
 | |
| 			found = true
 | |
| 			return false
 | |
| 		}
 | |
| 		return true
 | |
| 	})
 | |
| 	if len(lines) == 0 && 0 < len(parents) {
 | |
| 		parent := parents[len(parents)-1]
 | |
| 		lbrace := s.fileset.Position(parent.Lbrace).Line
 | |
| 		pos := s.fileset.Position(parent.List[0].Pos())
 | |
| 		lines = append(lines, lbrace, pos.Line)
 | |
| 	}
 | |
| 	return lines
 | |
| }
 | |
| 
 | |
| func removeDuplicateLines(lines []int) []int {
 | |
| 	nl := make([]int, 0, len(lines))
 | |
| 	fnd := make(map[int]bool)
 | |
| 	for _, l := range lines {
 | |
| 		if _, ok := fnd[l]; !ok {
 | |
| 			fnd[l] = true
 | |
| 			nl = append(nl, l)
 | |
| 		}
 | |
| 	}
 | |
| 	return nl
 | |
| }
 | 
