Initialize module and dependencies
This commit is contained in:
663
vendor/golang.org/x/tools/go/ast/astutil/enclosing.go
generated
vendored
Normal file
663
vendor/golang.org/x/tools/go/ast/astutil/enclosing.go
generated
vendored
Normal file
@@ -0,0 +1,663 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package astutil
|
||||
|
||||
// This file defines utilities for working with source positions.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// PathEnclosingInterval returns the node that encloses the source
|
||||
// interval [start, end), and all its ancestors up to the AST root.
|
||||
//
|
||||
// The definition of "enclosing" used by this function considers
|
||||
// additional whitespace abutting a node to be enclosed by it.
|
||||
// In this example:
|
||||
//
|
||||
// z := x + y // add them
|
||||
// <-A->
|
||||
// <----B----->
|
||||
//
|
||||
// the ast.BinaryExpr(+) node is considered to enclose interval B
|
||||
// even though its [Pos()..End()) is actually only interval A.
|
||||
// This behaviour makes user interfaces more tolerant of imperfect
|
||||
// input.
|
||||
//
|
||||
// This function treats tokens as nodes, though they are not included
|
||||
// in the result. e.g. PathEnclosingInterval("+") returns the
|
||||
// enclosing ast.BinaryExpr("x + y").
|
||||
//
|
||||
// If start==end, the 1-char interval following start is used instead.
|
||||
//
|
||||
// The 'exact' result is true if the interval contains only path[0]
|
||||
// and perhaps some adjacent whitespace. It is false if the interval
|
||||
// overlaps multiple children of path[0], or if it contains only
|
||||
// interior whitespace of path[0].
|
||||
// In this example:
|
||||
//
|
||||
// z := x + y // add them
|
||||
// <--C--> <---E-->
|
||||
// ^
|
||||
// D
|
||||
//
|
||||
// intervals C, D and E are inexact. C is contained by the
|
||||
// z-assignment statement, because it spans three of its children (:=,
|
||||
// x, +). So too is the 1-char interval D, because it contains only
|
||||
// interior whitespace of the assignment. E is considered interior
|
||||
// whitespace of the BlockStmt containing the assignment.
|
||||
//
|
||||
// The resulting path is never empty; it always contains at least the
|
||||
// 'root' *ast.File. Ideally PathEnclosingInterval would reject
|
||||
// intervals that lie wholly or partially outside the range of the
|
||||
// file, but unfortunately ast.File records only the token.Pos of
|
||||
// the 'package' keyword, but not of the start of the file itself.
|
||||
func PathEnclosingInterval(root *ast.File, start, end token.Pos) (path []ast.Node, exact bool) {
|
||||
// fmt.Printf("EnclosingInterval %d %d\n", start, end) // debugging
|
||||
|
||||
// Precondition: node.[Pos..End) and adjoining whitespace contain [start, end).
|
||||
var visit func(node ast.Node) bool
|
||||
visit = func(node ast.Node) bool {
|
||||
path = append(path, node)
|
||||
|
||||
nodePos := node.Pos()
|
||||
nodeEnd := node.End()
|
||||
|
||||
// fmt.Printf("visit(%T, %d, %d)\n", node, nodePos, nodeEnd) // debugging
|
||||
|
||||
// Intersect [start, end) with interval of node.
|
||||
if start < nodePos {
|
||||
start = nodePos
|
||||
}
|
||||
if end > nodeEnd {
|
||||
end = nodeEnd
|
||||
}
|
||||
|
||||
// Find sole child that contains [start, end).
|
||||
children := childrenOf(node)
|
||||
l := len(children)
|
||||
for i, child := range children {
|
||||
// [childPos, childEnd) is unaugmented interval of child.
|
||||
childPos := child.Pos()
|
||||
childEnd := child.End()
|
||||
|
||||
// [augPos, augEnd) is whitespace-augmented interval of child.
|
||||
augPos := childPos
|
||||
augEnd := childEnd
|
||||
if i > 0 {
|
||||
augPos = children[i-1].End() // start of preceding whitespace
|
||||
}
|
||||
if i < l-1 {
|
||||
nextChildPos := children[i+1].Pos()
|
||||
// Does [start, end) lie between child and next child?
|
||||
if start >= augEnd && end <= nextChildPos {
|
||||
return false // inexact match
|
||||
}
|
||||
augEnd = nextChildPos // end of following whitespace
|
||||
}
|
||||
|
||||
// fmt.Printf("\tchild %d: [%d..%d)\tcontains interval [%d..%d)?\n",
|
||||
// i, augPos, augEnd, start, end) // debugging
|
||||
|
||||
// Does augmented child strictly contain [start, end)?
|
||||
if augPos <= start && end <= augEnd {
|
||||
if is[tokenNode](child) {
|
||||
return true
|
||||
}
|
||||
|
||||
// childrenOf elides the FuncType node beneath FuncDecl.
|
||||
// Add it back here for TypeParams, Params, Results,
|
||||
// all FieldLists). But we don't add it back for the "func" token
|
||||
// even though it is the tree at FuncDecl.Type.Func.
|
||||
if decl, ok := node.(*ast.FuncDecl); ok {
|
||||
if fields, ok := child.(*ast.FieldList); ok && fields != decl.Recv {
|
||||
path = append(path, decl.Type)
|
||||
}
|
||||
}
|
||||
|
||||
return visit(child)
|
||||
}
|
||||
|
||||
// Does [start, end) overlap multiple children?
|
||||
// i.e. left-augmented child contains start
|
||||
// but LR-augmented child does not contain end.
|
||||
if start < childEnd && end > augEnd {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// No single child contained [start, end),
|
||||
// so node is the result. Is it exact?
|
||||
|
||||
// (It's tempting to put this condition before the
|
||||
// child loop, but it gives the wrong result in the
|
||||
// case where a node (e.g. ExprStmt) and its sole
|
||||
// child have equal intervals.)
|
||||
if start == nodePos && end == nodeEnd {
|
||||
return true // exact match
|
||||
}
|
||||
|
||||
return false // inexact: overlaps multiple children
|
||||
}
|
||||
|
||||
// Ensure [start,end) is nondecreasing.
|
||||
if start > end {
|
||||
start, end = end, start
|
||||
}
|
||||
|
||||
if start < root.End() && end > root.Pos() {
|
||||
if start == end {
|
||||
end = start + 1 // empty interval => interval of size 1
|
||||
}
|
||||
exact = visit(root)
|
||||
|
||||
// Reverse the path:
|
||||
for i, l := 0, len(path); i < l/2; i++ {
|
||||
path[i], path[l-1-i] = path[l-1-i], path[i]
|
||||
}
|
||||
} else {
|
||||
// Selection lies within whitespace preceding the
|
||||
// first (or following the last) declaration in the file.
|
||||
// The result nonetheless always includes the ast.File.
|
||||
path = append(path, root)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// tokenNode is a dummy implementation of ast.Node for a single token.
|
||||
// They are used transiently by PathEnclosingInterval but never escape
|
||||
// this package.
|
||||
type tokenNode struct {
|
||||
pos token.Pos
|
||||
end token.Pos
|
||||
}
|
||||
|
||||
func (n tokenNode) Pos() token.Pos {
|
||||
return n.pos
|
||||
}
|
||||
|
||||
func (n tokenNode) End() token.Pos {
|
||||
return n.end
|
||||
}
|
||||
|
||||
func tok(pos token.Pos, len int) ast.Node {
|
||||
return tokenNode{pos, pos + token.Pos(len)}
|
||||
}
|
||||
|
||||
// childrenOf returns the direct non-nil children of ast.Node n.
|
||||
// It may include fake ast.Node implementations for bare tokens.
|
||||
// it is not safe to call (e.g.) ast.Walk on such nodes.
|
||||
func childrenOf(n ast.Node) []ast.Node {
|
||||
var children []ast.Node
|
||||
|
||||
// First add nodes for all true subtrees.
|
||||
ast.Inspect(n, func(node ast.Node) bool {
|
||||
if node == n { // push n
|
||||
return true // recur
|
||||
}
|
||||
if node != nil { // push child
|
||||
children = append(children, node)
|
||||
}
|
||||
return false // no recursion
|
||||
})
|
||||
|
||||
// TODO(adonovan): be more careful about missing (!Pos.Valid)
|
||||
// tokens in trees produced from invalid input.
|
||||
|
||||
// Then add fake Nodes for bare tokens.
|
||||
switch n := n.(type) {
|
||||
case *ast.ArrayType:
|
||||
children = append(children,
|
||||
tok(n.Lbrack, len("[")),
|
||||
tok(n.Elt.End(), len("]")))
|
||||
|
||||
case *ast.AssignStmt:
|
||||
children = append(children,
|
||||
tok(n.TokPos, len(n.Tok.String())))
|
||||
|
||||
case *ast.BasicLit:
|
||||
children = append(children,
|
||||
tok(n.ValuePos, len(n.Value)))
|
||||
|
||||
case *ast.BinaryExpr:
|
||||
children = append(children, tok(n.OpPos, len(n.Op.String())))
|
||||
|
||||
case *ast.BlockStmt:
|
||||
if n.Lbrace.IsValid() {
|
||||
children = append(children, tok(n.Lbrace, len("{")))
|
||||
}
|
||||
if n.Rbrace.IsValid() {
|
||||
children = append(children, tok(n.Rbrace, len("}")))
|
||||
}
|
||||
|
||||
case *ast.BranchStmt:
|
||||
children = append(children,
|
||||
tok(n.TokPos, len(n.Tok.String())))
|
||||
|
||||
case *ast.CallExpr:
|
||||
children = append(children,
|
||||
tok(n.Lparen, len("(")),
|
||||
tok(n.Rparen, len(")")))
|
||||
if n.Ellipsis != 0 {
|
||||
children = append(children, tok(n.Ellipsis, len("...")))
|
||||
}
|
||||
|
||||
case *ast.CaseClause:
|
||||
if n.List == nil {
|
||||
children = append(children,
|
||||
tok(n.Case, len("default")))
|
||||
} else {
|
||||
children = append(children,
|
||||
tok(n.Case, len("case")))
|
||||
}
|
||||
children = append(children, tok(n.Colon, len(":")))
|
||||
|
||||
case *ast.ChanType:
|
||||
switch n.Dir {
|
||||
case ast.RECV:
|
||||
children = append(children, tok(n.Begin, len("<-chan")))
|
||||
case ast.SEND:
|
||||
children = append(children, tok(n.Begin, len("chan<-")))
|
||||
case ast.RECV | ast.SEND:
|
||||
children = append(children, tok(n.Begin, len("chan")))
|
||||
}
|
||||
|
||||
case *ast.CommClause:
|
||||
if n.Comm == nil {
|
||||
children = append(children,
|
||||
tok(n.Case, len("default")))
|
||||
} else {
|
||||
children = append(children,
|
||||
tok(n.Case, len("case")))
|
||||
}
|
||||
children = append(children, tok(n.Colon, len(":")))
|
||||
|
||||
case *ast.Comment:
|
||||
// nop
|
||||
|
||||
case *ast.CommentGroup:
|
||||
// nop
|
||||
|
||||
case *ast.CompositeLit:
|
||||
children = append(children,
|
||||
tok(n.Lbrace, len("{")),
|
||||
tok(n.Rbrace, len("{")))
|
||||
|
||||
case *ast.DeclStmt:
|
||||
// nop
|
||||
|
||||
case *ast.DeferStmt:
|
||||
children = append(children,
|
||||
tok(n.Defer, len("defer")))
|
||||
|
||||
case *ast.Ellipsis:
|
||||
children = append(children,
|
||||
tok(n.Ellipsis, len("...")))
|
||||
|
||||
case *ast.EmptyStmt:
|
||||
// nop
|
||||
|
||||
case *ast.ExprStmt:
|
||||
// nop
|
||||
|
||||
case *ast.Field:
|
||||
// TODO(adonovan): Field.{Doc,Comment,Tag}?
|
||||
|
||||
case *ast.FieldList:
|
||||
if n.Opening.IsValid() {
|
||||
children = append(children, tok(n.Opening, len("(")))
|
||||
}
|
||||
if n.Closing.IsValid() {
|
||||
children = append(children, tok(n.Closing, len(")")))
|
||||
}
|
||||
|
||||
case *ast.File:
|
||||
// TODO test: Doc
|
||||
children = append(children,
|
||||
tok(n.Package, len("package")))
|
||||
|
||||
case *ast.ForStmt:
|
||||
children = append(children,
|
||||
tok(n.For, len("for")))
|
||||
|
||||
case *ast.FuncDecl:
|
||||
// TODO(adonovan): FuncDecl.Comment?
|
||||
|
||||
// Uniquely, FuncDecl breaks the invariant that
|
||||
// preorder traversal yields tokens in lexical order:
|
||||
// in fact, FuncDecl.Recv precedes FuncDecl.Type.Func.
|
||||
//
|
||||
// As a workaround, we inline the case for FuncType
|
||||
// here and order things correctly.
|
||||
// We also need to insert the elided FuncType just
|
||||
// before the 'visit' recursion.
|
||||
//
|
||||
children = nil // discard ast.Walk(FuncDecl) info subtrees
|
||||
children = append(children, tok(n.Type.Func, len("func")))
|
||||
if n.Recv != nil {
|
||||
children = append(children, n.Recv)
|
||||
}
|
||||
children = append(children, n.Name)
|
||||
if tparams := n.Type.TypeParams; tparams != nil {
|
||||
children = append(children, tparams)
|
||||
}
|
||||
if n.Type.Params != nil {
|
||||
children = append(children, n.Type.Params)
|
||||
}
|
||||
if n.Type.Results != nil {
|
||||
children = append(children, n.Type.Results)
|
||||
}
|
||||
if n.Body != nil {
|
||||
children = append(children, n.Body)
|
||||
}
|
||||
|
||||
case *ast.FuncLit:
|
||||
// nop
|
||||
|
||||
case *ast.FuncType:
|
||||
if n.Func != 0 {
|
||||
children = append(children,
|
||||
tok(n.Func, len("func")))
|
||||
}
|
||||
|
||||
case *ast.GenDecl:
|
||||
children = append(children,
|
||||
tok(n.TokPos, len(n.Tok.String())))
|
||||
if n.Lparen != 0 {
|
||||
children = append(children,
|
||||
tok(n.Lparen, len("(")),
|
||||
tok(n.Rparen, len(")")))
|
||||
}
|
||||
|
||||
case *ast.GoStmt:
|
||||
children = append(children,
|
||||
tok(n.Go, len("go")))
|
||||
|
||||
case *ast.Ident:
|
||||
children = append(children,
|
||||
tok(n.NamePos, len(n.Name)))
|
||||
|
||||
case *ast.IfStmt:
|
||||
children = append(children,
|
||||
tok(n.If, len("if")))
|
||||
|
||||
case *ast.ImportSpec:
|
||||
// TODO(adonovan): ImportSpec.{Doc,EndPos}?
|
||||
|
||||
case *ast.IncDecStmt:
|
||||
children = append(children,
|
||||
tok(n.TokPos, len(n.Tok.String())))
|
||||
|
||||
case *ast.IndexExpr:
|
||||
children = append(children,
|
||||
tok(n.Lbrack, len("[")),
|
||||
tok(n.Rbrack, len("]")))
|
||||
|
||||
case *ast.IndexListExpr:
|
||||
children = append(children,
|
||||
tok(n.Lbrack, len("[")),
|
||||
tok(n.Rbrack, len("]")))
|
||||
|
||||
case *ast.InterfaceType:
|
||||
children = append(children,
|
||||
tok(n.Interface, len("interface")))
|
||||
|
||||
case *ast.KeyValueExpr:
|
||||
children = append(children,
|
||||
tok(n.Colon, len(":")))
|
||||
|
||||
case *ast.LabeledStmt:
|
||||
children = append(children,
|
||||
tok(n.Colon, len(":")))
|
||||
|
||||
case *ast.MapType:
|
||||
children = append(children,
|
||||
tok(n.Map, len("map")))
|
||||
|
||||
case *ast.ParenExpr:
|
||||
children = append(children,
|
||||
tok(n.Lparen, len("(")),
|
||||
tok(n.Rparen, len(")")))
|
||||
|
||||
case *ast.RangeStmt:
|
||||
children = append(children,
|
||||
tok(n.For, len("for")),
|
||||
tok(n.TokPos, len(n.Tok.String())))
|
||||
|
||||
case *ast.ReturnStmt:
|
||||
children = append(children,
|
||||
tok(n.Return, len("return")))
|
||||
|
||||
case *ast.SelectStmt:
|
||||
children = append(children,
|
||||
tok(n.Select, len("select")))
|
||||
|
||||
case *ast.SelectorExpr:
|
||||
// nop
|
||||
|
||||
case *ast.SendStmt:
|
||||
children = append(children,
|
||||
tok(n.Arrow, len("<-")))
|
||||
|
||||
case *ast.SliceExpr:
|
||||
children = append(children,
|
||||
tok(n.Lbrack, len("[")),
|
||||
tok(n.Rbrack, len("]")))
|
||||
|
||||
case *ast.StarExpr:
|
||||
children = append(children, tok(n.Star, len("*")))
|
||||
|
||||
case *ast.StructType:
|
||||
children = append(children, tok(n.Struct, len("struct")))
|
||||
|
||||
case *ast.SwitchStmt:
|
||||
children = append(children, tok(n.Switch, len("switch")))
|
||||
|
||||
case *ast.TypeAssertExpr:
|
||||
children = append(children,
|
||||
tok(n.Lparen-1, len(".")),
|
||||
tok(n.Lparen, len("(")),
|
||||
tok(n.Rparen, len(")")))
|
||||
|
||||
case *ast.TypeSpec:
|
||||
// TODO(adonovan): TypeSpec.{Doc,Comment}?
|
||||
|
||||
case *ast.TypeSwitchStmt:
|
||||
children = append(children, tok(n.Switch, len("switch")))
|
||||
|
||||
case *ast.UnaryExpr:
|
||||
children = append(children, tok(n.OpPos, len(n.Op.String())))
|
||||
|
||||
case *ast.ValueSpec:
|
||||
// TODO(adonovan): ValueSpec.{Doc,Comment}?
|
||||
|
||||
case *ast.BadDecl, *ast.BadExpr, *ast.BadStmt:
|
||||
// nop
|
||||
}
|
||||
|
||||
// TODO(adonovan): opt: merge the logic of ast.Inspect() into
|
||||
// the switch above so we can make interleaved callbacks for
|
||||
// both Nodes and Tokens in the right order and avoid the need
|
||||
// to sort.
|
||||
sort.Sort(byPos(children))
|
||||
|
||||
return children
|
||||
}
|
||||
|
||||
type byPos []ast.Node
|
||||
|
||||
func (sl byPos) Len() int {
|
||||
return len(sl)
|
||||
}
|
||||
func (sl byPos) Less(i, j int) bool {
|
||||
return sl[i].Pos() < sl[j].Pos()
|
||||
}
|
||||
func (sl byPos) Swap(i, j int) {
|
||||
sl[i], sl[j] = sl[j], sl[i]
|
||||
}
|
||||
|
||||
// NodeDescription returns a description of the concrete type of n suitable
|
||||
// for a user interface.
|
||||
//
|
||||
// TODO(adonovan): in some cases (e.g. Field, FieldList, Ident,
|
||||
// StarExpr) we could be much more specific given the path to the AST
|
||||
// root. Perhaps we should do that.
|
||||
func NodeDescription(n ast.Node) string {
|
||||
switch n := n.(type) {
|
||||
case *ast.ArrayType:
|
||||
return "array type"
|
||||
case *ast.AssignStmt:
|
||||
return "assignment"
|
||||
case *ast.BadDecl:
|
||||
return "bad declaration"
|
||||
case *ast.BadExpr:
|
||||
return "bad expression"
|
||||
case *ast.BadStmt:
|
||||
return "bad statement"
|
||||
case *ast.BasicLit:
|
||||
return "basic literal"
|
||||
case *ast.BinaryExpr:
|
||||
return fmt.Sprintf("binary %s operation", n.Op)
|
||||
case *ast.BlockStmt:
|
||||
return "block"
|
||||
case *ast.BranchStmt:
|
||||
switch n.Tok {
|
||||
case token.BREAK:
|
||||
return "break statement"
|
||||
case token.CONTINUE:
|
||||
return "continue statement"
|
||||
case token.GOTO:
|
||||
return "goto statement"
|
||||
case token.FALLTHROUGH:
|
||||
return "fall-through statement"
|
||||
}
|
||||
case *ast.CallExpr:
|
||||
if len(n.Args) == 1 && !n.Ellipsis.IsValid() {
|
||||
return "function call (or conversion)"
|
||||
}
|
||||
return "function call"
|
||||
case *ast.CaseClause:
|
||||
return "case clause"
|
||||
case *ast.ChanType:
|
||||
return "channel type"
|
||||
case *ast.CommClause:
|
||||
return "communication clause"
|
||||
case *ast.Comment:
|
||||
return "comment"
|
||||
case *ast.CommentGroup:
|
||||
return "comment group"
|
||||
case *ast.CompositeLit:
|
||||
return "composite literal"
|
||||
case *ast.DeclStmt:
|
||||
return NodeDescription(n.Decl) + " statement"
|
||||
case *ast.DeferStmt:
|
||||
return "defer statement"
|
||||
case *ast.Ellipsis:
|
||||
return "ellipsis"
|
||||
case *ast.EmptyStmt:
|
||||
return "empty statement"
|
||||
case *ast.ExprStmt:
|
||||
return "expression statement"
|
||||
case *ast.Field:
|
||||
// Can be any of these:
|
||||
// struct {x, y int} -- struct field(s)
|
||||
// struct {T} -- anon struct field
|
||||
// interface {I} -- interface embedding
|
||||
// interface {f()} -- interface method
|
||||
// func (A) func(B) C -- receiver, param(s), result(s)
|
||||
return "field/method/parameter"
|
||||
case *ast.FieldList:
|
||||
return "field/method/parameter list"
|
||||
case *ast.File:
|
||||
return "source file"
|
||||
case *ast.ForStmt:
|
||||
return "for loop"
|
||||
case *ast.FuncDecl:
|
||||
return "function declaration"
|
||||
case *ast.FuncLit:
|
||||
return "function literal"
|
||||
case *ast.FuncType:
|
||||
return "function type"
|
||||
case *ast.GenDecl:
|
||||
switch n.Tok {
|
||||
case token.IMPORT:
|
||||
return "import declaration"
|
||||
case token.CONST:
|
||||
return "constant declaration"
|
||||
case token.TYPE:
|
||||
return "type declaration"
|
||||
case token.VAR:
|
||||
return "variable declaration"
|
||||
}
|
||||
case *ast.GoStmt:
|
||||
return "go statement"
|
||||
case *ast.Ident:
|
||||
return "identifier"
|
||||
case *ast.IfStmt:
|
||||
return "if statement"
|
||||
case *ast.ImportSpec:
|
||||
return "import specification"
|
||||
case *ast.IncDecStmt:
|
||||
if n.Tok == token.INC {
|
||||
return "increment statement"
|
||||
}
|
||||
return "decrement statement"
|
||||
case *ast.IndexExpr:
|
||||
return "index expression"
|
||||
case *ast.IndexListExpr:
|
||||
return "index list expression"
|
||||
case *ast.InterfaceType:
|
||||
return "interface type"
|
||||
case *ast.KeyValueExpr:
|
||||
return "key/value association"
|
||||
case *ast.LabeledStmt:
|
||||
return "statement label"
|
||||
case *ast.MapType:
|
||||
return "map type"
|
||||
case *ast.Package:
|
||||
return "package"
|
||||
case *ast.ParenExpr:
|
||||
return "parenthesized " + NodeDescription(n.X)
|
||||
case *ast.RangeStmt:
|
||||
return "range loop"
|
||||
case *ast.ReturnStmt:
|
||||
return "return statement"
|
||||
case *ast.SelectStmt:
|
||||
return "select statement"
|
||||
case *ast.SelectorExpr:
|
||||
return "selector"
|
||||
case *ast.SendStmt:
|
||||
return "channel send"
|
||||
case *ast.SliceExpr:
|
||||
return "slice expression"
|
||||
case *ast.StarExpr:
|
||||
return "*-operation" // load/store expr or pointer type
|
||||
case *ast.StructType:
|
||||
return "struct type"
|
||||
case *ast.SwitchStmt:
|
||||
return "switch statement"
|
||||
case *ast.TypeAssertExpr:
|
||||
return "type assertion"
|
||||
case *ast.TypeSpec:
|
||||
return "type specification"
|
||||
case *ast.TypeSwitchStmt:
|
||||
return "type switch"
|
||||
case *ast.UnaryExpr:
|
||||
return fmt.Sprintf("unary %s operation", n.Op)
|
||||
case *ast.ValueSpec:
|
||||
return "value specification"
|
||||
|
||||
}
|
||||
panic(fmt.Sprintf("unexpected node type: %T", n))
|
||||
}
|
||||
|
||||
func is[T any](x any) bool {
|
||||
_, ok := x.(T)
|
||||
return ok
|
||||
}
|
||||
487
vendor/golang.org/x/tools/go/ast/astutil/imports.go
generated
vendored
Normal file
487
vendor/golang.org/x/tools/go/ast/astutil/imports.go
generated
vendored
Normal file
@@ -0,0 +1,487 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package astutil contains common utilities for working with the Go AST.
|
||||
package astutil // import "golang.org/x/tools/go/ast/astutil"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// AddImport adds the import path to the file f, if absent.
|
||||
func AddImport(fset *token.FileSet, f *ast.File, path string) (added bool) {
|
||||
return AddNamedImport(fset, f, "", path)
|
||||
}
|
||||
|
||||
// AddNamedImport adds the import with the given name and path to the file f, if absent.
|
||||
// If name is not empty, it is used to rename the import.
|
||||
//
|
||||
// For example, calling
|
||||
//
|
||||
// AddNamedImport(fset, f, "pathpkg", "path")
|
||||
//
|
||||
// adds
|
||||
//
|
||||
// import pathpkg "path"
|
||||
func AddNamedImport(fset *token.FileSet, f *ast.File, name, path string) (added bool) {
|
||||
if imports(f, name, path) {
|
||||
return false
|
||||
}
|
||||
|
||||
newImport := &ast.ImportSpec{
|
||||
Path: &ast.BasicLit{
|
||||
Kind: token.STRING,
|
||||
Value: strconv.Quote(path),
|
||||
},
|
||||
}
|
||||
if name != "" {
|
||||
newImport.Name = &ast.Ident{Name: name}
|
||||
}
|
||||
|
||||
// Find an import decl to add to.
|
||||
// The goal is to find an existing import
|
||||
// whose import path has the longest shared
|
||||
// prefix with path.
|
||||
var (
|
||||
bestMatch = -1 // length of longest shared prefix
|
||||
lastImport = -1 // index in f.Decls of the file's final import decl
|
||||
impDecl *ast.GenDecl // import decl containing the best match
|
||||
impIndex = -1 // spec index in impDecl containing the best match
|
||||
|
||||
isThirdPartyPath = isThirdParty(path)
|
||||
)
|
||||
for i, decl := range f.Decls {
|
||||
gen, ok := decl.(*ast.GenDecl)
|
||||
if ok && gen.Tok == token.IMPORT {
|
||||
lastImport = i
|
||||
// Do not add to import "C", to avoid disrupting the
|
||||
// association with its doc comment, breaking cgo.
|
||||
if declImports(gen, "C") {
|
||||
continue
|
||||
}
|
||||
|
||||
// Match an empty import decl if that's all that is available.
|
||||
if len(gen.Specs) == 0 && bestMatch == -1 {
|
||||
impDecl = gen
|
||||
}
|
||||
|
||||
// Compute longest shared prefix with imports in this group and find best
|
||||
// matched import spec.
|
||||
// 1. Always prefer import spec with longest shared prefix.
|
||||
// 2. While match length is 0,
|
||||
// - for stdlib package: prefer first import spec.
|
||||
// - for third party package: prefer first third party import spec.
|
||||
// We cannot use last import spec as best match for third party package
|
||||
// because grouped imports are usually placed last by goimports -local
|
||||
// flag.
|
||||
// See issue #19190.
|
||||
seenAnyThirdParty := false
|
||||
for j, spec := range gen.Specs {
|
||||
impspec := spec.(*ast.ImportSpec)
|
||||
p := importPath(impspec)
|
||||
n := matchLen(p, path)
|
||||
if n > bestMatch || (bestMatch == 0 && !seenAnyThirdParty && isThirdPartyPath) {
|
||||
bestMatch = n
|
||||
impDecl = gen
|
||||
impIndex = j
|
||||
}
|
||||
seenAnyThirdParty = seenAnyThirdParty || isThirdParty(p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no import decl found, add one after the last import.
|
||||
if impDecl == nil {
|
||||
impDecl = &ast.GenDecl{
|
||||
Tok: token.IMPORT,
|
||||
}
|
||||
if lastImport >= 0 {
|
||||
impDecl.TokPos = f.Decls[lastImport].End()
|
||||
} else {
|
||||
// There are no existing imports.
|
||||
// Our new import, preceded by a blank line, goes after the package declaration
|
||||
// and after the comment, if any, that starts on the same line as the
|
||||
// package declaration.
|
||||
impDecl.TokPos = f.Package
|
||||
|
||||
file := fset.File(f.Package)
|
||||
pkgLine := file.Line(f.Package)
|
||||
for _, c := range f.Comments {
|
||||
if file.Line(c.Pos()) > pkgLine {
|
||||
break
|
||||
}
|
||||
// +2 for a blank line
|
||||
impDecl.TokPos = c.End() + 2
|
||||
}
|
||||
}
|
||||
f.Decls = append(f.Decls, nil)
|
||||
copy(f.Decls[lastImport+2:], f.Decls[lastImport+1:])
|
||||
f.Decls[lastImport+1] = impDecl
|
||||
}
|
||||
|
||||
// Insert new import at insertAt.
|
||||
insertAt := 0
|
||||
if impIndex >= 0 {
|
||||
// insert after the found import
|
||||
insertAt = impIndex + 1
|
||||
}
|
||||
impDecl.Specs = append(impDecl.Specs, nil)
|
||||
copy(impDecl.Specs[insertAt+1:], impDecl.Specs[insertAt:])
|
||||
impDecl.Specs[insertAt] = newImport
|
||||
pos := impDecl.Pos()
|
||||
if insertAt > 0 {
|
||||
// If there is a comment after an existing import, preserve the comment
|
||||
// position by adding the new import after the comment.
|
||||
if spec, ok := impDecl.Specs[insertAt-1].(*ast.ImportSpec); ok && spec.Comment != nil {
|
||||
pos = spec.Comment.End()
|
||||
} else {
|
||||
// Assign same position as the previous import,
|
||||
// so that the sorter sees it as being in the same block.
|
||||
pos = impDecl.Specs[insertAt-1].Pos()
|
||||
}
|
||||
}
|
||||
if newImport.Name != nil {
|
||||
newImport.Name.NamePos = pos
|
||||
}
|
||||
updateBasicLitPos(newImport.Path, pos)
|
||||
newImport.EndPos = pos
|
||||
|
||||
// Clean up parens. impDecl contains at least one spec.
|
||||
if len(impDecl.Specs) == 1 {
|
||||
// Remove unneeded parens.
|
||||
impDecl.Lparen = token.NoPos
|
||||
} else if !impDecl.Lparen.IsValid() {
|
||||
// impDecl needs parens added.
|
||||
impDecl.Lparen = impDecl.Specs[0].Pos()
|
||||
}
|
||||
|
||||
f.Imports = append(f.Imports, newImport)
|
||||
|
||||
if len(f.Decls) <= 1 {
|
||||
return true
|
||||
}
|
||||
|
||||
// Merge all the import declarations into the first one.
|
||||
var first *ast.GenDecl
|
||||
for i := 0; i < len(f.Decls); i++ {
|
||||
decl := f.Decls[i]
|
||||
gen, ok := decl.(*ast.GenDecl)
|
||||
if !ok || gen.Tok != token.IMPORT || declImports(gen, "C") {
|
||||
continue
|
||||
}
|
||||
if first == nil {
|
||||
first = gen
|
||||
continue // Don't touch the first one.
|
||||
}
|
||||
// We now know there is more than one package in this import
|
||||
// declaration. Ensure that it ends up parenthesized.
|
||||
first.Lparen = first.Pos()
|
||||
// Move the imports of the other import declaration to the first one.
|
||||
for _, spec := range gen.Specs {
|
||||
updateBasicLitPos(spec.(*ast.ImportSpec).Path, first.Pos())
|
||||
first.Specs = append(first.Specs, spec)
|
||||
}
|
||||
f.Decls = slices.Delete(f.Decls, i, i+1)
|
||||
i--
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func isThirdParty(importPath string) bool {
|
||||
// Third party package import path usually contains "." (".com", ".org", ...)
|
||||
// This logic is taken from golang.org/x/tools/imports package.
|
||||
return strings.Contains(importPath, ".")
|
||||
}
|
||||
|
||||
// DeleteImport deletes the import path from the file f, if present.
|
||||
// If there are duplicate import declarations, all matching ones are deleted.
|
||||
func DeleteImport(fset *token.FileSet, f *ast.File, path string) (deleted bool) {
|
||||
return DeleteNamedImport(fset, f, "", path)
|
||||
}
|
||||
|
||||
// DeleteNamedImport deletes the import with the given name and path from the file f, if present.
|
||||
// If there are duplicate import declarations, all matching ones are deleted.
|
||||
func DeleteNamedImport(fset *token.FileSet, f *ast.File, name, path string) (deleted bool) {
|
||||
var (
|
||||
delspecs = make(map[*ast.ImportSpec]bool)
|
||||
delcomments = make(map[*ast.CommentGroup]bool)
|
||||
)
|
||||
|
||||
// Find the import nodes that import path, if any.
|
||||
for i := 0; i < len(f.Decls); i++ {
|
||||
gen, ok := f.Decls[i].(*ast.GenDecl)
|
||||
if !ok || gen.Tok != token.IMPORT {
|
||||
continue
|
||||
}
|
||||
for j := 0; j < len(gen.Specs); j++ {
|
||||
impspec := gen.Specs[j].(*ast.ImportSpec)
|
||||
if importName(impspec) != name || importPath(impspec) != path {
|
||||
continue
|
||||
}
|
||||
|
||||
// We found an import spec that imports path.
|
||||
// Delete it.
|
||||
delspecs[impspec] = true
|
||||
deleted = true
|
||||
gen.Specs = slices.Delete(gen.Specs, j, j+1)
|
||||
|
||||
// If this was the last import spec in this decl,
|
||||
// delete the decl, too.
|
||||
if len(gen.Specs) == 0 {
|
||||
f.Decls = slices.Delete(f.Decls, i, i+1)
|
||||
i--
|
||||
break
|
||||
} else if len(gen.Specs) == 1 {
|
||||
if impspec.Doc != nil {
|
||||
delcomments[impspec.Doc] = true
|
||||
}
|
||||
if impspec.Comment != nil {
|
||||
delcomments[impspec.Comment] = true
|
||||
}
|
||||
for _, cg := range f.Comments {
|
||||
// Found comment on the same line as the import spec.
|
||||
if cg.End() < impspec.Pos() && fset.Position(cg.End()).Line == fset.Position(impspec.Pos()).Line {
|
||||
delcomments[cg] = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
spec := gen.Specs[0].(*ast.ImportSpec)
|
||||
|
||||
// Move the documentation right after the import decl.
|
||||
if spec.Doc != nil {
|
||||
for fset.Position(gen.TokPos).Line+1 < fset.Position(spec.Doc.Pos()).Line {
|
||||
fset.File(gen.TokPos).MergeLine(fset.Position(gen.TokPos).Line)
|
||||
}
|
||||
}
|
||||
for _, cg := range f.Comments {
|
||||
if cg.End() < spec.Pos() && fset.Position(cg.End()).Line == fset.Position(spec.Pos()).Line {
|
||||
for fset.Position(gen.TokPos).Line+1 < fset.Position(spec.Pos()).Line {
|
||||
fset.File(gen.TokPos).MergeLine(fset.Position(gen.TokPos).Line)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if j > 0 {
|
||||
lastImpspec := gen.Specs[j-1].(*ast.ImportSpec)
|
||||
lastLine := fset.PositionFor(lastImpspec.Path.ValuePos, false).Line
|
||||
line := fset.PositionFor(impspec.Path.ValuePos, false).Line
|
||||
|
||||
// We deleted an entry but now there may be
|
||||
// a blank line-sized hole where the import was.
|
||||
if line-lastLine > 1 || !gen.Rparen.IsValid() {
|
||||
// There was a blank line immediately preceding the deleted import,
|
||||
// so there's no need to close the hole. The right parenthesis is
|
||||
// invalid after AddImport to an import statement without parenthesis.
|
||||
// Do nothing.
|
||||
} else if line != fset.File(gen.Rparen).LineCount() {
|
||||
// There was no blank line. Close the hole.
|
||||
fset.File(gen.Rparen).MergeLine(line)
|
||||
}
|
||||
}
|
||||
j--
|
||||
}
|
||||
}
|
||||
|
||||
// Delete imports from f.Imports.
|
||||
before := len(f.Imports)
|
||||
f.Imports = slices.DeleteFunc(f.Imports, func(imp *ast.ImportSpec) bool {
|
||||
_, ok := delspecs[imp]
|
||||
return ok
|
||||
})
|
||||
if len(f.Imports)+len(delspecs) != before {
|
||||
// This can happen when the AST is invalid (i.e. imports differ between f.Decls and f.Imports).
|
||||
panic(fmt.Sprintf("deleted specs from Decls but not Imports: %v", delspecs))
|
||||
}
|
||||
|
||||
// Delete comments from f.Comments.
|
||||
f.Comments = slices.DeleteFunc(f.Comments, func(cg *ast.CommentGroup) bool {
|
||||
_, ok := delcomments[cg]
|
||||
return ok
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// RewriteImport rewrites any import of path oldPath to path newPath.
|
||||
func RewriteImport(fset *token.FileSet, f *ast.File, oldPath, newPath string) (rewrote bool) {
|
||||
for _, imp := range f.Imports {
|
||||
if importPath(imp) == oldPath {
|
||||
rewrote = true
|
||||
// record old End, because the default is to compute
|
||||
// it using the length of imp.Path.Value.
|
||||
imp.EndPos = imp.End()
|
||||
imp.Path.Value = strconv.Quote(newPath)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// UsesImport reports whether a given import is used.
|
||||
// The provided File must have been parsed with syntactic object resolution
|
||||
// (not using go/parser.SkipObjectResolution).
|
||||
func UsesImport(f *ast.File, path string) (used bool) {
|
||||
if f.Scope == nil {
|
||||
panic("file f was not parsed with syntactic object resolution")
|
||||
}
|
||||
spec := importSpec(f, path)
|
||||
if spec == nil {
|
||||
return
|
||||
}
|
||||
|
||||
name := spec.Name.String()
|
||||
switch name {
|
||||
case "<nil>":
|
||||
// If the package name is not explicitly specified,
|
||||
// make an educated guess. This is not guaranteed to be correct.
|
||||
lastSlash := strings.LastIndex(path, "/")
|
||||
if lastSlash == -1 {
|
||||
name = path
|
||||
} else {
|
||||
name = path[lastSlash+1:]
|
||||
}
|
||||
case "_", ".":
|
||||
// Not sure if this import is used - err on the side of caution.
|
||||
return true
|
||||
}
|
||||
|
||||
ast.Walk(visitFn(func(n ast.Node) {
|
||||
sel, ok := n.(*ast.SelectorExpr)
|
||||
if ok && isTopName(sel.X, name) {
|
||||
used = true
|
||||
}
|
||||
}), f)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type visitFn func(node ast.Node)
|
||||
|
||||
func (fn visitFn) Visit(node ast.Node) ast.Visitor {
|
||||
fn(node)
|
||||
return fn
|
||||
}
|
||||
|
||||
// imports reports whether f has an import with the specified name and path.
|
||||
func imports(f *ast.File, name, path string) bool {
|
||||
for _, s := range f.Imports {
|
||||
if importName(s) == name && importPath(s) == path {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// importSpec returns the import spec if f imports path,
|
||||
// or nil otherwise.
|
||||
func importSpec(f *ast.File, path string) *ast.ImportSpec {
|
||||
for _, s := range f.Imports {
|
||||
if importPath(s) == path {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// importName returns the name of s,
|
||||
// or "" if the import is not named.
|
||||
func importName(s *ast.ImportSpec) string {
|
||||
if s.Name == nil {
|
||||
return ""
|
||||
}
|
||||
return s.Name.Name
|
||||
}
|
||||
|
||||
// importPath returns the unquoted import path of s,
|
||||
// or "" if the path is not properly quoted.
|
||||
func importPath(s *ast.ImportSpec) string {
|
||||
t, err := strconv.Unquote(s.Path.Value)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// declImports reports whether gen contains an import of path.
|
||||
func declImports(gen *ast.GenDecl, path string) bool {
|
||||
if gen.Tok != token.IMPORT {
|
||||
return false
|
||||
}
|
||||
for _, spec := range gen.Specs {
|
||||
impspec := spec.(*ast.ImportSpec)
|
||||
if importPath(impspec) == path {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// matchLen returns the length of the longest path segment prefix shared by x and y.
|
||||
func matchLen(x, y string) int {
|
||||
n := 0
|
||||
for i := 0; i < len(x) && i < len(y) && x[i] == y[i]; i++ {
|
||||
if x[i] == '/' {
|
||||
n++
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// isTopName returns true if n is a top-level unresolved identifier with the given name.
|
||||
func isTopName(n ast.Expr, name string) bool {
|
||||
id, ok := n.(*ast.Ident)
|
||||
return ok && id.Name == name && id.Obj == nil
|
||||
}
|
||||
|
||||
// Imports returns the file imports grouped by paragraph.
|
||||
func Imports(fset *token.FileSet, f *ast.File) [][]*ast.ImportSpec {
|
||||
var groups [][]*ast.ImportSpec
|
||||
|
||||
for _, decl := range f.Decls {
|
||||
genDecl, ok := decl.(*ast.GenDecl)
|
||||
if !ok || genDecl.Tok != token.IMPORT {
|
||||
break
|
||||
}
|
||||
|
||||
group := []*ast.ImportSpec{}
|
||||
|
||||
var lastLine int
|
||||
for _, spec := range genDecl.Specs {
|
||||
importSpec := spec.(*ast.ImportSpec)
|
||||
pos := importSpec.Path.ValuePos
|
||||
line := fset.Position(pos).Line
|
||||
if lastLine > 0 && pos > 0 && line-lastLine > 1 {
|
||||
groups = append(groups, group)
|
||||
group = []*ast.ImportSpec{}
|
||||
}
|
||||
group = append(group, importSpec)
|
||||
lastLine = line
|
||||
}
|
||||
groups = append(groups, group)
|
||||
}
|
||||
|
||||
return groups
|
||||
}
|
||||
|
||||
// updateBasicLitPos updates lit.Pos,
|
||||
// ensuring that lit.End (if set) is displaced by the same amount.
|
||||
// (See https://go.dev/issue/76395.)
|
||||
func updateBasicLitPos(lit *ast.BasicLit, pos token.Pos) {
|
||||
len := lit.End() - lit.Pos()
|
||||
lit.ValuePos = pos
|
||||
// TODO(adonovan): after go1.26, simplify to:
|
||||
// lit.ValueEnd = pos + len
|
||||
v := reflect.ValueOf(lit).Elem().FieldByName("ValueEnd")
|
||||
if v.IsValid() && v.Int() != 0 {
|
||||
v.SetInt(int64(pos + len))
|
||||
}
|
||||
}
|
||||
490
vendor/golang.org/x/tools/go/ast/astutil/rewrite.go
generated
vendored
Normal file
490
vendor/golang.org/x/tools/go/ast/astutil/rewrite.go
generated
vendored
Normal file
@@ -0,0 +1,490 @@
|
||||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package astutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"reflect"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// An ApplyFunc is invoked by Apply for each node n, even if n is nil,
|
||||
// before and/or after the node's children, using a Cursor describing
|
||||
// the current node and providing operations on it.
|
||||
//
|
||||
// The return value of ApplyFunc controls the syntax tree traversal.
|
||||
// See Apply for details.
|
||||
type ApplyFunc func(*Cursor) bool
|
||||
|
||||
// Apply traverses a syntax tree recursively, starting with root,
|
||||
// and calling pre and post for each node as described below.
|
||||
// Apply returns the syntax tree, possibly modified.
|
||||
//
|
||||
// If pre is not nil, it is called for each node before the node's
|
||||
// children are traversed (pre-order). If pre returns false, no
|
||||
// children are traversed, and post is not called for that node.
|
||||
//
|
||||
// If post is not nil, and a prior call of pre didn't return false,
|
||||
// post is called for each node after its children are traversed
|
||||
// (post-order). If post returns false, traversal is terminated and
|
||||
// Apply returns immediately.
|
||||
//
|
||||
// Only fields that refer to AST nodes are considered children;
|
||||
// i.e., token.Pos, Scopes, Objects, and fields of basic types
|
||||
// (strings, etc.) are ignored.
|
||||
//
|
||||
// Children are traversed in the order in which they appear in the
|
||||
// respective node's struct definition. A package's files are
|
||||
// traversed in the filenames' alphabetical order.
|
||||
func Apply(root ast.Node, pre, post ApplyFunc) (result ast.Node) {
|
||||
parent := &struct{ ast.Node }{root}
|
||||
defer func() {
|
||||
if r := recover(); r != nil && r != abort {
|
||||
panic(r)
|
||||
}
|
||||
result = parent.Node
|
||||
}()
|
||||
a := &application{pre: pre, post: post}
|
||||
a.apply(parent, "Node", nil, root)
|
||||
return
|
||||
}
|
||||
|
||||
var abort = new(int) // singleton, to signal termination of Apply
|
||||
|
||||
// A Cursor describes a node encountered during Apply.
|
||||
// Information about the node and its parent is available
|
||||
// from the Node, Parent, Name, and Index methods.
|
||||
//
|
||||
// If p is a variable of type and value of the current parent node
|
||||
// c.Parent(), and f is the field identifier with name c.Name(),
|
||||
// the following invariants hold:
|
||||
//
|
||||
// p.f == c.Node() if c.Index() < 0
|
||||
// p.f[c.Index()] == c.Node() if c.Index() >= 0
|
||||
//
|
||||
// The methods Replace, Delete, InsertBefore, and InsertAfter
|
||||
// can be used to change the AST without disrupting Apply.
|
||||
//
|
||||
// This type is not to be confused with [inspector.Cursor] from
|
||||
// package [golang.org/x/tools/go/ast/inspector], which provides
|
||||
// stateless navigation of immutable syntax trees.
|
||||
type Cursor struct {
|
||||
parent ast.Node
|
||||
name string
|
||||
iter *iterator // valid if non-nil
|
||||
node ast.Node
|
||||
}
|
||||
|
||||
// Node returns the current Node.
|
||||
func (c *Cursor) Node() ast.Node { return c.node }
|
||||
|
||||
// Parent returns the parent of the current Node.
|
||||
func (c *Cursor) Parent() ast.Node { return c.parent }
|
||||
|
||||
// Name returns the name of the parent Node field that contains the current Node.
|
||||
// If the parent is a *ast.Package and the current Node is a *ast.File, Name returns
|
||||
// the filename for the current Node.
|
||||
func (c *Cursor) Name() string { return c.name }
|
||||
|
||||
// Index reports the index >= 0 of the current Node in the slice of Nodes that
|
||||
// contains it, or a value < 0 if the current Node is not part of a slice.
|
||||
// The index of the current node changes if InsertBefore is called while
|
||||
// processing the current node.
|
||||
func (c *Cursor) Index() int {
|
||||
if c.iter != nil {
|
||||
return c.iter.index
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// field returns the current node's parent field value.
|
||||
func (c *Cursor) field() reflect.Value {
|
||||
return reflect.Indirect(reflect.ValueOf(c.parent)).FieldByName(c.name)
|
||||
}
|
||||
|
||||
// Replace replaces the current Node with n.
|
||||
// The replacement node is not walked by Apply.
|
||||
func (c *Cursor) Replace(n ast.Node) {
|
||||
if _, ok := c.node.(*ast.File); ok {
|
||||
file, ok := n.(*ast.File)
|
||||
if !ok {
|
||||
panic("attempt to replace *ast.File with non-*ast.File")
|
||||
}
|
||||
c.parent.(*ast.Package).Files[c.name] = file
|
||||
return
|
||||
}
|
||||
|
||||
v := c.field()
|
||||
if i := c.Index(); i >= 0 {
|
||||
v = v.Index(i)
|
||||
}
|
||||
v.Set(reflect.ValueOf(n))
|
||||
}
|
||||
|
||||
// Delete deletes the current Node from its containing slice.
|
||||
// If the current Node is not part of a slice, Delete panics.
|
||||
// As a special case, if the current node is a package file,
|
||||
// Delete removes it from the package's Files map.
|
||||
func (c *Cursor) Delete() {
|
||||
if _, ok := c.node.(*ast.File); ok {
|
||||
delete(c.parent.(*ast.Package).Files, c.name)
|
||||
return
|
||||
}
|
||||
|
||||
i := c.Index()
|
||||
if i < 0 {
|
||||
panic("Delete node not contained in slice")
|
||||
}
|
||||
v := c.field()
|
||||
l := v.Len()
|
||||
reflect.Copy(v.Slice(i, l), v.Slice(i+1, l))
|
||||
v.Index(l - 1).Set(reflect.Zero(v.Type().Elem()))
|
||||
v.SetLen(l - 1)
|
||||
c.iter.step--
|
||||
}
|
||||
|
||||
// InsertAfter inserts n after the current Node in its containing slice.
|
||||
// If the current Node is not part of a slice, InsertAfter panics.
|
||||
// Apply does not walk n.
|
||||
func (c *Cursor) InsertAfter(n ast.Node) {
|
||||
i := c.Index()
|
||||
if i < 0 {
|
||||
panic("InsertAfter node not contained in slice")
|
||||
}
|
||||
v := c.field()
|
||||
v.Set(reflect.Append(v, reflect.Zero(v.Type().Elem())))
|
||||
l := v.Len()
|
||||
reflect.Copy(v.Slice(i+2, l), v.Slice(i+1, l))
|
||||
v.Index(i + 1).Set(reflect.ValueOf(n))
|
||||
c.iter.step++
|
||||
}
|
||||
|
||||
// InsertBefore inserts n before the current Node in its containing slice.
|
||||
// If the current Node is not part of a slice, InsertBefore panics.
|
||||
// Apply will not walk n.
|
||||
func (c *Cursor) InsertBefore(n ast.Node) {
|
||||
i := c.Index()
|
||||
if i < 0 {
|
||||
panic("InsertBefore node not contained in slice")
|
||||
}
|
||||
v := c.field()
|
||||
v.Set(reflect.Append(v, reflect.Zero(v.Type().Elem())))
|
||||
l := v.Len()
|
||||
reflect.Copy(v.Slice(i+1, l), v.Slice(i, l))
|
||||
v.Index(i).Set(reflect.ValueOf(n))
|
||||
c.iter.index++
|
||||
}
|
||||
|
||||
// application carries all the shared data so we can pass it around cheaply.
|
||||
type application struct {
|
||||
pre, post ApplyFunc
|
||||
cursor Cursor
|
||||
iter iterator
|
||||
}
|
||||
|
||||
func (a *application) apply(parent ast.Node, name string, iter *iterator, n ast.Node) {
|
||||
// convert typed nil into untyped nil
|
||||
if v := reflect.ValueOf(n); v.Kind() == reflect.Pointer && v.IsNil() {
|
||||
n = nil
|
||||
}
|
||||
|
||||
// avoid heap-allocating a new cursor for each apply call; reuse a.cursor instead
|
||||
saved := a.cursor
|
||||
a.cursor.parent = parent
|
||||
a.cursor.name = name
|
||||
a.cursor.iter = iter
|
||||
a.cursor.node = n
|
||||
|
||||
if a.pre != nil && !a.pre(&a.cursor) {
|
||||
a.cursor = saved
|
||||
return
|
||||
}
|
||||
|
||||
// walk children
|
||||
// (the order of the cases matches the order of the corresponding node types in go/ast)
|
||||
switch n := n.(type) {
|
||||
case nil:
|
||||
// nothing to do
|
||||
|
||||
// Comments and fields
|
||||
case *ast.Comment:
|
||||
// nothing to do
|
||||
|
||||
case *ast.CommentGroup:
|
||||
if n != nil {
|
||||
a.applyList(n, "List")
|
||||
}
|
||||
|
||||
case *ast.Field:
|
||||
a.apply(n, "Doc", nil, n.Doc)
|
||||
a.applyList(n, "Names")
|
||||
a.apply(n, "Type", nil, n.Type)
|
||||
a.apply(n, "Tag", nil, n.Tag)
|
||||
a.apply(n, "Comment", nil, n.Comment)
|
||||
|
||||
case *ast.FieldList:
|
||||
a.applyList(n, "List")
|
||||
|
||||
// Expressions
|
||||
case *ast.BadExpr, *ast.Ident, *ast.BasicLit:
|
||||
// nothing to do
|
||||
|
||||
case *ast.Ellipsis:
|
||||
a.apply(n, "Elt", nil, n.Elt)
|
||||
|
||||
case *ast.FuncLit:
|
||||
a.apply(n, "Type", nil, n.Type)
|
||||
a.apply(n, "Body", nil, n.Body)
|
||||
|
||||
case *ast.CompositeLit:
|
||||
a.apply(n, "Type", nil, n.Type)
|
||||
a.applyList(n, "Elts")
|
||||
|
||||
case *ast.ParenExpr:
|
||||
a.apply(n, "X", nil, n.X)
|
||||
|
||||
case *ast.SelectorExpr:
|
||||
a.apply(n, "X", nil, n.X)
|
||||
a.apply(n, "Sel", nil, n.Sel)
|
||||
|
||||
case *ast.IndexExpr:
|
||||
a.apply(n, "X", nil, n.X)
|
||||
a.apply(n, "Index", nil, n.Index)
|
||||
|
||||
case *ast.IndexListExpr:
|
||||
a.apply(n, "X", nil, n.X)
|
||||
a.applyList(n, "Indices")
|
||||
|
||||
case *ast.SliceExpr:
|
||||
a.apply(n, "X", nil, n.X)
|
||||
a.apply(n, "Low", nil, n.Low)
|
||||
a.apply(n, "High", nil, n.High)
|
||||
a.apply(n, "Max", nil, n.Max)
|
||||
|
||||
case *ast.TypeAssertExpr:
|
||||
a.apply(n, "X", nil, n.X)
|
||||
a.apply(n, "Type", nil, n.Type)
|
||||
|
||||
case *ast.CallExpr:
|
||||
a.apply(n, "Fun", nil, n.Fun)
|
||||
a.applyList(n, "Args")
|
||||
|
||||
case *ast.StarExpr:
|
||||
a.apply(n, "X", nil, n.X)
|
||||
|
||||
case *ast.UnaryExpr:
|
||||
a.apply(n, "X", nil, n.X)
|
||||
|
||||
case *ast.BinaryExpr:
|
||||
a.apply(n, "X", nil, n.X)
|
||||
a.apply(n, "Y", nil, n.Y)
|
||||
|
||||
case *ast.KeyValueExpr:
|
||||
a.apply(n, "Key", nil, n.Key)
|
||||
a.apply(n, "Value", nil, n.Value)
|
||||
|
||||
// Types
|
||||
case *ast.ArrayType:
|
||||
a.apply(n, "Len", nil, n.Len)
|
||||
a.apply(n, "Elt", nil, n.Elt)
|
||||
|
||||
case *ast.StructType:
|
||||
a.apply(n, "Fields", nil, n.Fields)
|
||||
|
||||
case *ast.FuncType:
|
||||
if tparams := n.TypeParams; tparams != nil {
|
||||
a.apply(n, "TypeParams", nil, tparams)
|
||||
}
|
||||
a.apply(n, "Params", nil, n.Params)
|
||||
a.apply(n, "Results", nil, n.Results)
|
||||
|
||||
case *ast.InterfaceType:
|
||||
a.apply(n, "Methods", nil, n.Methods)
|
||||
|
||||
case *ast.MapType:
|
||||
a.apply(n, "Key", nil, n.Key)
|
||||
a.apply(n, "Value", nil, n.Value)
|
||||
|
||||
case *ast.ChanType:
|
||||
a.apply(n, "Value", nil, n.Value)
|
||||
|
||||
// Statements
|
||||
case *ast.BadStmt:
|
||||
// nothing to do
|
||||
|
||||
case *ast.DeclStmt:
|
||||
a.apply(n, "Decl", nil, n.Decl)
|
||||
|
||||
case *ast.EmptyStmt:
|
||||
// nothing to do
|
||||
|
||||
case *ast.LabeledStmt:
|
||||
a.apply(n, "Label", nil, n.Label)
|
||||
a.apply(n, "Stmt", nil, n.Stmt)
|
||||
|
||||
case *ast.ExprStmt:
|
||||
a.apply(n, "X", nil, n.X)
|
||||
|
||||
case *ast.SendStmt:
|
||||
a.apply(n, "Chan", nil, n.Chan)
|
||||
a.apply(n, "Value", nil, n.Value)
|
||||
|
||||
case *ast.IncDecStmt:
|
||||
a.apply(n, "X", nil, n.X)
|
||||
|
||||
case *ast.AssignStmt:
|
||||
a.applyList(n, "Lhs")
|
||||
a.applyList(n, "Rhs")
|
||||
|
||||
case *ast.GoStmt:
|
||||
a.apply(n, "Call", nil, n.Call)
|
||||
|
||||
case *ast.DeferStmt:
|
||||
a.apply(n, "Call", nil, n.Call)
|
||||
|
||||
case *ast.ReturnStmt:
|
||||
a.applyList(n, "Results")
|
||||
|
||||
case *ast.BranchStmt:
|
||||
a.apply(n, "Label", nil, n.Label)
|
||||
|
||||
case *ast.BlockStmt:
|
||||
a.applyList(n, "List")
|
||||
|
||||
case *ast.IfStmt:
|
||||
a.apply(n, "Init", nil, n.Init)
|
||||
a.apply(n, "Cond", nil, n.Cond)
|
||||
a.apply(n, "Body", nil, n.Body)
|
||||
a.apply(n, "Else", nil, n.Else)
|
||||
|
||||
case *ast.CaseClause:
|
||||
a.applyList(n, "List")
|
||||
a.applyList(n, "Body")
|
||||
|
||||
case *ast.SwitchStmt:
|
||||
a.apply(n, "Init", nil, n.Init)
|
||||
a.apply(n, "Tag", nil, n.Tag)
|
||||
a.apply(n, "Body", nil, n.Body)
|
||||
|
||||
case *ast.TypeSwitchStmt:
|
||||
a.apply(n, "Init", nil, n.Init)
|
||||
a.apply(n, "Assign", nil, n.Assign)
|
||||
a.apply(n, "Body", nil, n.Body)
|
||||
|
||||
case *ast.CommClause:
|
||||
a.apply(n, "Comm", nil, n.Comm)
|
||||
a.applyList(n, "Body")
|
||||
|
||||
case *ast.SelectStmt:
|
||||
a.apply(n, "Body", nil, n.Body)
|
||||
|
||||
case *ast.ForStmt:
|
||||
a.apply(n, "Init", nil, n.Init)
|
||||
a.apply(n, "Cond", nil, n.Cond)
|
||||
a.apply(n, "Post", nil, n.Post)
|
||||
a.apply(n, "Body", nil, n.Body)
|
||||
|
||||
case *ast.RangeStmt:
|
||||
a.apply(n, "Key", nil, n.Key)
|
||||
a.apply(n, "Value", nil, n.Value)
|
||||
a.apply(n, "X", nil, n.X)
|
||||
a.apply(n, "Body", nil, n.Body)
|
||||
|
||||
// Declarations
|
||||
case *ast.ImportSpec:
|
||||
a.apply(n, "Doc", nil, n.Doc)
|
||||
a.apply(n, "Name", nil, n.Name)
|
||||
a.apply(n, "Path", nil, n.Path)
|
||||
a.apply(n, "Comment", nil, n.Comment)
|
||||
|
||||
case *ast.ValueSpec:
|
||||
a.apply(n, "Doc", nil, n.Doc)
|
||||
a.applyList(n, "Names")
|
||||
a.apply(n, "Type", nil, n.Type)
|
||||
a.applyList(n, "Values")
|
||||
a.apply(n, "Comment", nil, n.Comment)
|
||||
|
||||
case *ast.TypeSpec:
|
||||
a.apply(n, "Doc", nil, n.Doc)
|
||||
a.apply(n, "Name", nil, n.Name)
|
||||
if tparams := n.TypeParams; tparams != nil {
|
||||
a.apply(n, "TypeParams", nil, tparams)
|
||||
}
|
||||
a.apply(n, "Type", nil, n.Type)
|
||||
a.apply(n, "Comment", nil, n.Comment)
|
||||
|
||||
case *ast.BadDecl:
|
||||
// nothing to do
|
||||
|
||||
case *ast.GenDecl:
|
||||
a.apply(n, "Doc", nil, n.Doc)
|
||||
a.applyList(n, "Specs")
|
||||
|
||||
case *ast.FuncDecl:
|
||||
a.apply(n, "Doc", nil, n.Doc)
|
||||
a.apply(n, "Recv", nil, n.Recv)
|
||||
a.apply(n, "Name", nil, n.Name)
|
||||
a.apply(n, "Type", nil, n.Type)
|
||||
a.apply(n, "Body", nil, n.Body)
|
||||
|
||||
// Files and packages
|
||||
case *ast.File:
|
||||
a.apply(n, "Doc", nil, n.Doc)
|
||||
a.apply(n, "Name", nil, n.Name)
|
||||
a.applyList(n, "Decls")
|
||||
// Don't walk n.Comments; they have either been walked already if
|
||||
// they are Doc comments, or they can be easily walked explicitly.
|
||||
|
||||
case *ast.Package:
|
||||
// collect and sort names for reproducible behavior
|
||||
var names []string
|
||||
for name := range n.Files {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
a.apply(n, name, nil, n.Files[name])
|
||||
}
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("Apply: unexpected node type %T", n))
|
||||
}
|
||||
|
||||
if a.post != nil && !a.post(&a.cursor) {
|
||||
panic(abort)
|
||||
}
|
||||
|
||||
a.cursor = saved
|
||||
}
|
||||
|
||||
// An iterator controls iteration over a slice of nodes.
|
||||
type iterator struct {
|
||||
index, step int
|
||||
}
|
||||
|
||||
func (a *application) applyList(parent ast.Node, name string) {
|
||||
// avoid heap-allocating a new iterator for each applyList call; reuse a.iter instead
|
||||
saved := a.iter
|
||||
a.iter.index = 0
|
||||
for {
|
||||
// must reload parent.name each time, since cursor modifications might change it
|
||||
v := reflect.Indirect(reflect.ValueOf(parent)).FieldByName(name)
|
||||
if a.iter.index >= v.Len() {
|
||||
break
|
||||
}
|
||||
|
||||
// element x may be nil in a bad AST - be cautious
|
||||
var x ast.Node
|
||||
if e := v.Index(a.iter.index); e.IsValid() {
|
||||
x = e.Interface().(ast.Node)
|
||||
}
|
||||
|
||||
a.iter.step = 1
|
||||
a.apply(parent, name, &a.iter, x)
|
||||
a.iter.index += a.iter.step
|
||||
}
|
||||
a.iter = saved
|
||||
}
|
||||
13
vendor/golang.org/x/tools/go/ast/astutil/util.go
generated
vendored
Normal file
13
vendor/golang.org/x/tools/go/ast/astutil/util.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package astutil
|
||||
|
||||
import "go/ast"
|
||||
|
||||
// Unparen returns e with any enclosing parentheses stripped.
|
||||
// Deprecated: use [ast.Unparen].
|
||||
//
|
||||
//go:fix inline
|
||||
func Unparen(e ast.Expr) ast.Expr { return ast.Unparen(e) }
|
||||
295
vendor/golang.org/x/tools/go/ast/edge/edge.go
generated
vendored
Normal file
295
vendor/golang.org/x/tools/go/ast/edge/edge.go
generated
vendored
Normal file
@@ -0,0 +1,295 @@
|
||||
// Copyright 2025 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package edge defines identifiers for each field of an ast.Node
|
||||
// struct type that refers to another Node.
|
||||
package edge
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// A Kind describes a field of an ast.Node struct.
|
||||
type Kind uint8
|
||||
|
||||
// String returns a description of the edge kind.
|
||||
func (k Kind) String() string {
|
||||
if k == Invalid {
|
||||
return "<invalid>"
|
||||
}
|
||||
info := fieldInfos[k]
|
||||
return fmt.Sprintf("%v.%s", info.nodeType.Elem().Name(), info.name)
|
||||
}
|
||||
|
||||
// NodeType returns the pointer-to-struct type of the ast.Node implementation.
|
||||
func (k Kind) NodeType() reflect.Type { return fieldInfos[k].nodeType }
|
||||
|
||||
// FieldName returns the name of the field.
|
||||
func (k Kind) FieldName() string { return fieldInfos[k].name }
|
||||
|
||||
// FieldType returns the declared type of the field.
|
||||
func (k Kind) FieldType() reflect.Type { return fieldInfos[k].fieldType }
|
||||
|
||||
// Get returns the direct child of n identified by (k, idx).
|
||||
// n's type must match k.NodeType().
|
||||
// idx must be a valid slice index, or -1 for a non-slice.
|
||||
func (k Kind) Get(n ast.Node, idx int) ast.Node {
|
||||
if k.NodeType() != reflect.TypeOf(n) {
|
||||
panic(fmt.Sprintf("%v.Get(%T): invalid node type", k, n))
|
||||
}
|
||||
v := reflect.ValueOf(n).Elem().Field(fieldInfos[k].index)
|
||||
if idx != -1 {
|
||||
v = v.Index(idx) // asserts valid index
|
||||
} else {
|
||||
// (The type assertion below asserts that v is not a slice.)
|
||||
}
|
||||
return v.Interface().(ast.Node) // may be nil
|
||||
}
|
||||
|
||||
const (
|
||||
Invalid Kind = iota // for nodes at the root of the traversal
|
||||
|
||||
// Kinds are sorted alphabetically.
|
||||
// Numbering is not stable.
|
||||
// Each is named Type_Field, where Type is the
|
||||
// ast.Node struct type and Field is the name of the field
|
||||
|
||||
ArrayType_Elt
|
||||
ArrayType_Len
|
||||
AssignStmt_Lhs
|
||||
AssignStmt_Rhs
|
||||
BinaryExpr_X
|
||||
BinaryExpr_Y
|
||||
BlockStmt_List
|
||||
BranchStmt_Label
|
||||
CallExpr_Args
|
||||
CallExpr_Fun
|
||||
CaseClause_Body
|
||||
CaseClause_List
|
||||
ChanType_Value
|
||||
CommClause_Body
|
||||
CommClause_Comm
|
||||
CommentGroup_List
|
||||
CompositeLit_Elts
|
||||
CompositeLit_Type
|
||||
DeclStmt_Decl
|
||||
DeferStmt_Call
|
||||
Ellipsis_Elt
|
||||
ExprStmt_X
|
||||
FieldList_List
|
||||
Field_Comment
|
||||
Field_Doc
|
||||
Field_Names
|
||||
Field_Tag
|
||||
Field_Type
|
||||
File_Decls
|
||||
File_Doc
|
||||
File_Name
|
||||
ForStmt_Body
|
||||
ForStmt_Cond
|
||||
ForStmt_Init
|
||||
ForStmt_Post
|
||||
FuncDecl_Body
|
||||
FuncDecl_Doc
|
||||
FuncDecl_Name
|
||||
FuncDecl_Recv
|
||||
FuncDecl_Type
|
||||
FuncLit_Body
|
||||
FuncLit_Type
|
||||
FuncType_Params
|
||||
FuncType_Results
|
||||
FuncType_TypeParams
|
||||
GenDecl_Doc
|
||||
GenDecl_Specs
|
||||
GoStmt_Call
|
||||
IfStmt_Body
|
||||
IfStmt_Cond
|
||||
IfStmt_Else
|
||||
IfStmt_Init
|
||||
ImportSpec_Comment
|
||||
ImportSpec_Doc
|
||||
ImportSpec_Name
|
||||
ImportSpec_Path
|
||||
IncDecStmt_X
|
||||
IndexExpr_Index
|
||||
IndexExpr_X
|
||||
IndexListExpr_Indices
|
||||
IndexListExpr_X
|
||||
InterfaceType_Methods
|
||||
KeyValueExpr_Key
|
||||
KeyValueExpr_Value
|
||||
LabeledStmt_Label
|
||||
LabeledStmt_Stmt
|
||||
MapType_Key
|
||||
MapType_Value
|
||||
ParenExpr_X
|
||||
RangeStmt_Body
|
||||
RangeStmt_Key
|
||||
RangeStmt_Value
|
||||
RangeStmt_X
|
||||
ReturnStmt_Results
|
||||
SelectStmt_Body
|
||||
SelectorExpr_Sel
|
||||
SelectorExpr_X
|
||||
SendStmt_Chan
|
||||
SendStmt_Value
|
||||
SliceExpr_High
|
||||
SliceExpr_Low
|
||||
SliceExpr_Max
|
||||
SliceExpr_X
|
||||
StarExpr_X
|
||||
StructType_Fields
|
||||
SwitchStmt_Body
|
||||
SwitchStmt_Init
|
||||
SwitchStmt_Tag
|
||||
TypeAssertExpr_Type
|
||||
TypeAssertExpr_X
|
||||
TypeSpec_Comment
|
||||
TypeSpec_Doc
|
||||
TypeSpec_Name
|
||||
TypeSpec_Type
|
||||
TypeSpec_TypeParams
|
||||
TypeSwitchStmt_Assign
|
||||
TypeSwitchStmt_Body
|
||||
TypeSwitchStmt_Init
|
||||
UnaryExpr_X
|
||||
ValueSpec_Comment
|
||||
ValueSpec_Doc
|
||||
ValueSpec_Names
|
||||
ValueSpec_Type
|
||||
ValueSpec_Values
|
||||
|
||||
maxKind
|
||||
)
|
||||
|
||||
// Assert that the encoding fits in 7 bits,
|
||||
// as the inspector relies on this.
|
||||
// (We are currently at 104.)
|
||||
var _ = [1 << 7]struct{}{}[maxKind]
|
||||
|
||||
type fieldInfo struct {
|
||||
nodeType reflect.Type // pointer-to-struct type of ast.Node implementation
|
||||
name string
|
||||
index int
|
||||
fieldType reflect.Type
|
||||
}
|
||||
|
||||
func info[N ast.Node](fieldName string) fieldInfo {
|
||||
nodePtrType := reflect.TypeFor[N]()
|
||||
f, ok := nodePtrType.Elem().FieldByName(fieldName)
|
||||
if !ok {
|
||||
panic(fieldName)
|
||||
}
|
||||
return fieldInfo{nodePtrType, fieldName, f.Index[0], f.Type}
|
||||
}
|
||||
|
||||
var fieldInfos = [...]fieldInfo{
|
||||
Invalid: {},
|
||||
ArrayType_Elt: info[*ast.ArrayType]("Elt"),
|
||||
ArrayType_Len: info[*ast.ArrayType]("Len"),
|
||||
AssignStmt_Lhs: info[*ast.AssignStmt]("Lhs"),
|
||||
AssignStmt_Rhs: info[*ast.AssignStmt]("Rhs"),
|
||||
BinaryExpr_X: info[*ast.BinaryExpr]("X"),
|
||||
BinaryExpr_Y: info[*ast.BinaryExpr]("Y"),
|
||||
BlockStmt_List: info[*ast.BlockStmt]("List"),
|
||||
BranchStmt_Label: info[*ast.BranchStmt]("Label"),
|
||||
CallExpr_Args: info[*ast.CallExpr]("Args"),
|
||||
CallExpr_Fun: info[*ast.CallExpr]("Fun"),
|
||||
CaseClause_Body: info[*ast.CaseClause]("Body"),
|
||||
CaseClause_List: info[*ast.CaseClause]("List"),
|
||||
ChanType_Value: info[*ast.ChanType]("Value"),
|
||||
CommClause_Body: info[*ast.CommClause]("Body"),
|
||||
CommClause_Comm: info[*ast.CommClause]("Comm"),
|
||||
CommentGroup_List: info[*ast.CommentGroup]("List"),
|
||||
CompositeLit_Elts: info[*ast.CompositeLit]("Elts"),
|
||||
CompositeLit_Type: info[*ast.CompositeLit]("Type"),
|
||||
DeclStmt_Decl: info[*ast.DeclStmt]("Decl"),
|
||||
DeferStmt_Call: info[*ast.DeferStmt]("Call"),
|
||||
Ellipsis_Elt: info[*ast.Ellipsis]("Elt"),
|
||||
ExprStmt_X: info[*ast.ExprStmt]("X"),
|
||||
FieldList_List: info[*ast.FieldList]("List"),
|
||||
Field_Comment: info[*ast.Field]("Comment"),
|
||||
Field_Doc: info[*ast.Field]("Doc"),
|
||||
Field_Names: info[*ast.Field]("Names"),
|
||||
Field_Tag: info[*ast.Field]("Tag"),
|
||||
Field_Type: info[*ast.Field]("Type"),
|
||||
File_Decls: info[*ast.File]("Decls"),
|
||||
File_Doc: info[*ast.File]("Doc"),
|
||||
File_Name: info[*ast.File]("Name"),
|
||||
ForStmt_Body: info[*ast.ForStmt]("Body"),
|
||||
ForStmt_Cond: info[*ast.ForStmt]("Cond"),
|
||||
ForStmt_Init: info[*ast.ForStmt]("Init"),
|
||||
ForStmt_Post: info[*ast.ForStmt]("Post"),
|
||||
FuncDecl_Body: info[*ast.FuncDecl]("Body"),
|
||||
FuncDecl_Doc: info[*ast.FuncDecl]("Doc"),
|
||||
FuncDecl_Name: info[*ast.FuncDecl]("Name"),
|
||||
FuncDecl_Recv: info[*ast.FuncDecl]("Recv"),
|
||||
FuncDecl_Type: info[*ast.FuncDecl]("Type"),
|
||||
FuncLit_Body: info[*ast.FuncLit]("Body"),
|
||||
FuncLit_Type: info[*ast.FuncLit]("Type"),
|
||||
FuncType_Params: info[*ast.FuncType]("Params"),
|
||||
FuncType_Results: info[*ast.FuncType]("Results"),
|
||||
FuncType_TypeParams: info[*ast.FuncType]("TypeParams"),
|
||||
GenDecl_Doc: info[*ast.GenDecl]("Doc"),
|
||||
GenDecl_Specs: info[*ast.GenDecl]("Specs"),
|
||||
GoStmt_Call: info[*ast.GoStmt]("Call"),
|
||||
IfStmt_Body: info[*ast.IfStmt]("Body"),
|
||||
IfStmt_Cond: info[*ast.IfStmt]("Cond"),
|
||||
IfStmt_Else: info[*ast.IfStmt]("Else"),
|
||||
IfStmt_Init: info[*ast.IfStmt]("Init"),
|
||||
ImportSpec_Comment: info[*ast.ImportSpec]("Comment"),
|
||||
ImportSpec_Doc: info[*ast.ImportSpec]("Doc"),
|
||||
ImportSpec_Name: info[*ast.ImportSpec]("Name"),
|
||||
ImportSpec_Path: info[*ast.ImportSpec]("Path"),
|
||||
IncDecStmt_X: info[*ast.IncDecStmt]("X"),
|
||||
IndexExpr_Index: info[*ast.IndexExpr]("Index"),
|
||||
IndexExpr_X: info[*ast.IndexExpr]("X"),
|
||||
IndexListExpr_Indices: info[*ast.IndexListExpr]("Indices"),
|
||||
IndexListExpr_X: info[*ast.IndexListExpr]("X"),
|
||||
InterfaceType_Methods: info[*ast.InterfaceType]("Methods"),
|
||||
KeyValueExpr_Key: info[*ast.KeyValueExpr]("Key"),
|
||||
KeyValueExpr_Value: info[*ast.KeyValueExpr]("Value"),
|
||||
LabeledStmt_Label: info[*ast.LabeledStmt]("Label"),
|
||||
LabeledStmt_Stmt: info[*ast.LabeledStmt]("Stmt"),
|
||||
MapType_Key: info[*ast.MapType]("Key"),
|
||||
MapType_Value: info[*ast.MapType]("Value"),
|
||||
ParenExpr_X: info[*ast.ParenExpr]("X"),
|
||||
RangeStmt_Body: info[*ast.RangeStmt]("Body"),
|
||||
RangeStmt_Key: info[*ast.RangeStmt]("Key"),
|
||||
RangeStmt_Value: info[*ast.RangeStmt]("Value"),
|
||||
RangeStmt_X: info[*ast.RangeStmt]("X"),
|
||||
ReturnStmt_Results: info[*ast.ReturnStmt]("Results"),
|
||||
SelectStmt_Body: info[*ast.SelectStmt]("Body"),
|
||||
SelectorExpr_Sel: info[*ast.SelectorExpr]("Sel"),
|
||||
SelectorExpr_X: info[*ast.SelectorExpr]("X"),
|
||||
SendStmt_Chan: info[*ast.SendStmt]("Chan"),
|
||||
SendStmt_Value: info[*ast.SendStmt]("Value"),
|
||||
SliceExpr_High: info[*ast.SliceExpr]("High"),
|
||||
SliceExpr_Low: info[*ast.SliceExpr]("Low"),
|
||||
SliceExpr_Max: info[*ast.SliceExpr]("Max"),
|
||||
SliceExpr_X: info[*ast.SliceExpr]("X"),
|
||||
StarExpr_X: info[*ast.StarExpr]("X"),
|
||||
StructType_Fields: info[*ast.StructType]("Fields"),
|
||||
SwitchStmt_Body: info[*ast.SwitchStmt]("Body"),
|
||||
SwitchStmt_Init: info[*ast.SwitchStmt]("Init"),
|
||||
SwitchStmt_Tag: info[*ast.SwitchStmt]("Tag"),
|
||||
TypeAssertExpr_Type: info[*ast.TypeAssertExpr]("Type"),
|
||||
TypeAssertExpr_X: info[*ast.TypeAssertExpr]("X"),
|
||||
TypeSpec_Comment: info[*ast.TypeSpec]("Comment"),
|
||||
TypeSpec_Doc: info[*ast.TypeSpec]("Doc"),
|
||||
TypeSpec_Name: info[*ast.TypeSpec]("Name"),
|
||||
TypeSpec_Type: info[*ast.TypeSpec]("Type"),
|
||||
TypeSpec_TypeParams: info[*ast.TypeSpec]("TypeParams"),
|
||||
TypeSwitchStmt_Assign: info[*ast.TypeSwitchStmt]("Assign"),
|
||||
TypeSwitchStmt_Body: info[*ast.TypeSwitchStmt]("Body"),
|
||||
TypeSwitchStmt_Init: info[*ast.TypeSwitchStmt]("Init"),
|
||||
UnaryExpr_X: info[*ast.UnaryExpr]("X"),
|
||||
ValueSpec_Comment: info[*ast.ValueSpec]("Comment"),
|
||||
ValueSpec_Doc: info[*ast.ValueSpec]("Doc"),
|
||||
ValueSpec_Names: info[*ast.ValueSpec]("Names"),
|
||||
ValueSpec_Type: info[*ast.ValueSpec]("Type"),
|
||||
ValueSpec_Values: info[*ast.ValueSpec]("Values"),
|
||||
}
|
||||
517
vendor/golang.org/x/tools/go/ast/inspector/cursor.go
generated
vendored
Normal file
517
vendor/golang.org/x/tools/go/ast/inspector/cursor.go
generated
vendored
Normal file
@@ -0,0 +1,517 @@
|
||||
// Copyright 2025 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package inspector
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"iter"
|
||||
"reflect"
|
||||
|
||||
"golang.org/x/tools/go/ast/edge"
|
||||
)
|
||||
|
||||
// A Cursor represents an [ast.Node]. It is immutable.
|
||||
//
|
||||
// Two Cursors compare equal if they represent the same node.
|
||||
//
|
||||
// Call [Inspector.Root] to obtain a valid cursor for the virtual root
|
||||
// node of the traversal.
|
||||
//
|
||||
// Use the following methods to navigate efficiently around the tree:
|
||||
// - for ancestors, use [Cursor.Parent] and [Cursor.Enclosing];
|
||||
// - for children, use [Cursor.Child], [Cursor.Children],
|
||||
// [Cursor.FirstChild], and [Cursor.LastChild];
|
||||
// - for siblings, use [Cursor.PrevSibling] and [Cursor.NextSibling];
|
||||
// - for descendants, use [Cursor.FindByPos], [Cursor.FindNode],
|
||||
// [Cursor.Inspect], and [Cursor.Preorder].
|
||||
//
|
||||
// Use the [Cursor.ChildAt] and [Cursor.ParentEdge] methods for
|
||||
// information about the edges in a tree: which field (and slice
|
||||
// element) of the parent node holds the child.
|
||||
type Cursor struct {
|
||||
in *Inspector
|
||||
index int32 // index of push node; -1 for virtual root node
|
||||
}
|
||||
|
||||
// Root returns a cursor for the virtual root node,
|
||||
// whose children are the files provided to [New].
|
||||
//
|
||||
// Its [Cursor.Node] method return nil.
|
||||
func (in *Inspector) Root() Cursor {
|
||||
return Cursor{in, -1}
|
||||
}
|
||||
|
||||
// At returns the cursor at the specified index in the traversal,
|
||||
// which must have been obtained from [Cursor.Index] on a Cursor
|
||||
// belonging to the same Inspector (see [Cursor.Inspector]).
|
||||
func (in *Inspector) At(index int32) Cursor {
|
||||
if index < 0 {
|
||||
panic("negative index")
|
||||
}
|
||||
if int(index) >= len(in.events) {
|
||||
panic("index out of range for this inspector")
|
||||
}
|
||||
if in.events[index].index < index {
|
||||
panic("invalid index") // (a push, not a pop)
|
||||
}
|
||||
return Cursor{in, index}
|
||||
}
|
||||
|
||||
// Inspector returns the cursor's Inspector.
|
||||
func (c Cursor) Inspector() *Inspector { return c.in }
|
||||
|
||||
// Index returns the index of this cursor position within the package.
|
||||
//
|
||||
// Clients should not assume anything about the numeric Index value
|
||||
// except that it increases monotonically throughout the traversal.
|
||||
// It is provided for use with [At].
|
||||
//
|
||||
// Index must not be called on the Root node.
|
||||
func (c Cursor) Index() int32 {
|
||||
if c.index < 0 {
|
||||
panic("Index called on Root node")
|
||||
}
|
||||
return c.index
|
||||
}
|
||||
|
||||
// Node returns the node at the current cursor position,
|
||||
// or nil for the cursor returned by [Inspector.Root].
|
||||
func (c Cursor) Node() ast.Node {
|
||||
if c.index < 0 {
|
||||
return nil
|
||||
}
|
||||
return c.in.events[c.index].node
|
||||
}
|
||||
|
||||
// String returns information about the cursor's node, if any.
|
||||
func (c Cursor) String() string {
|
||||
if c.in == nil {
|
||||
return "(invalid)"
|
||||
}
|
||||
if c.index < 0 {
|
||||
return "(root)"
|
||||
}
|
||||
return reflect.TypeOf(c.Node()).String()
|
||||
}
|
||||
|
||||
// indices return the [start, end) half-open interval of event indices.
|
||||
func (c Cursor) indices() (int32, int32) {
|
||||
if c.index < 0 {
|
||||
return 0, int32(len(c.in.events)) // root: all events
|
||||
} else {
|
||||
return c.index, c.in.events[c.index].index + 1 // just one subtree
|
||||
}
|
||||
}
|
||||
|
||||
// Preorder returns an iterator over the nodes of the subtree
|
||||
// represented by c in depth-first order. Each node in the sequence is
|
||||
// represented by a Cursor that allows access to the Node, but may
|
||||
// also be used to start a new traversal, or to obtain the stack of
|
||||
// nodes enclosing the cursor.
|
||||
//
|
||||
// The traversal sequence is determined by [ast.Inspect]. The types
|
||||
// argument, if non-empty, enables type-based filtering of events. The
|
||||
// function f if is called only for nodes whose type matches an
|
||||
// element of the types slice.
|
||||
//
|
||||
// If you need control over descent into subtrees,
|
||||
// or need both pre- and post-order notifications, use [Cursor.Inspect]
|
||||
func (c Cursor) Preorder(types ...ast.Node) iter.Seq[Cursor] {
|
||||
mask := maskOf(types)
|
||||
|
||||
return func(yield func(Cursor) bool) {
|
||||
events := c.in.events
|
||||
|
||||
for i, limit := c.indices(); i < limit; {
|
||||
ev := events[i]
|
||||
if ev.index > i { // push?
|
||||
if ev.typ&mask != 0 && !yield(Cursor{c.in, i}) {
|
||||
break
|
||||
}
|
||||
pop := ev.index
|
||||
if events[pop].typ&mask == 0 {
|
||||
// Subtree does not contain types: skip.
|
||||
i = pop + 1
|
||||
continue
|
||||
}
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Inspect visits the nodes of the subtree represented by c in
|
||||
// depth-first order. It calls f(n) for each node n before it
|
||||
// visits n's children. If f returns true, Inspect invokes f
|
||||
// recursively for each of the non-nil children of the node.
|
||||
//
|
||||
// Each node is represented by a Cursor that allows access to the
|
||||
// Node, but may also be used to start a new traversal, or to obtain
|
||||
// the stack of nodes enclosing the cursor.
|
||||
//
|
||||
// The complete traversal sequence is determined by [ast.Inspect].
|
||||
// The types argument, if non-empty, enables type-based filtering of
|
||||
// events. The function f if is called only for nodes whose type
|
||||
// matches an element of the types slice.
|
||||
func (c Cursor) Inspect(types []ast.Node, f func(c Cursor) (descend bool)) {
|
||||
mask := maskOf(types)
|
||||
events := c.in.events
|
||||
for i, limit := c.indices(); i < limit; {
|
||||
ev := events[i]
|
||||
if ev.index > i {
|
||||
// push
|
||||
pop := ev.index
|
||||
if ev.typ&mask != 0 && !f(Cursor{c.in, i}) ||
|
||||
events[pop].typ&mask == 0 {
|
||||
// The user opted not to descend, or the
|
||||
// subtree does not contain types:
|
||||
// skip past the pop.
|
||||
i = pop + 1
|
||||
continue
|
||||
}
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
// Enclosing returns an iterator over the nodes enclosing the current
|
||||
// current node, starting with the Cursor itself.
|
||||
//
|
||||
// Enclosing must not be called on the Root node (whose [Cursor.Node] returns nil).
|
||||
//
|
||||
// The types argument, if non-empty, enables type-based filtering of
|
||||
// events: the sequence includes only enclosing nodes whose type
|
||||
// matches an element of the types slice.
|
||||
func (c Cursor) Enclosing(types ...ast.Node) iter.Seq[Cursor] {
|
||||
if c.index < 0 {
|
||||
panic("Cursor.Enclosing called on Root node")
|
||||
}
|
||||
|
||||
mask := maskOf(types)
|
||||
|
||||
return func(yield func(Cursor) bool) {
|
||||
events := c.in.events
|
||||
for i := c.index; i >= 0; i = events[i].parent {
|
||||
if events[i].typ&mask != 0 && !yield(Cursor{c.in, i}) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parent returns the parent of the current node.
|
||||
//
|
||||
// Parent must not be called on the Root node (whose [Cursor.Node] returns nil).
|
||||
func (c Cursor) Parent() Cursor {
|
||||
if c.index < 0 {
|
||||
panic("Cursor.Parent called on Root node")
|
||||
}
|
||||
|
||||
return Cursor{c.in, c.in.events[c.index].parent}
|
||||
}
|
||||
|
||||
// ParentEdge returns the identity of the field in the parent node
|
||||
// that holds this cursor's node, and if it is a list, the index within it.
|
||||
//
|
||||
// For example, f(x, y) is a CallExpr whose three children are Idents.
|
||||
// f has edge kind [edge.CallExpr_Fun] and index -1.
|
||||
// x and y have kind [edge.CallExpr_Args] and indices 0 and 1, respectively.
|
||||
//
|
||||
// If called on a child of the Root node, it returns ([edge.Invalid], -1).
|
||||
//
|
||||
// ParentEdge must not be called on the Root node (whose [Cursor.Node] returns nil).
|
||||
func (c Cursor) ParentEdge() (edge.Kind, int) {
|
||||
if c.index < 0 {
|
||||
panic("Cursor.ParentEdge called on Root node")
|
||||
}
|
||||
events := c.in.events
|
||||
pop := events[c.index].index
|
||||
return unpackEdgeKindAndIndex(events[pop].parent)
|
||||
}
|
||||
|
||||
// ChildAt returns the cursor for the child of the
|
||||
// current node identified by its edge and index.
|
||||
// The index must be -1 if the edge.Kind is not a slice.
|
||||
// The indicated child node must exist.
|
||||
//
|
||||
// ChildAt must not be called on the Root node (whose [Cursor.Node] returns nil).
|
||||
//
|
||||
// Invariant: c.Parent().ChildAt(c.ParentEdge()) == c.
|
||||
func (c Cursor) ChildAt(k edge.Kind, idx int) Cursor {
|
||||
target := packEdgeKindAndIndex(k, idx)
|
||||
|
||||
// Unfortunately there's no shortcut to looping.
|
||||
events := c.in.events
|
||||
i := c.index + 1
|
||||
for {
|
||||
pop := events[i].index
|
||||
if pop < i {
|
||||
break
|
||||
}
|
||||
if events[pop].parent == target {
|
||||
return Cursor{c.in, i}
|
||||
}
|
||||
i = pop + 1
|
||||
}
|
||||
panic(fmt.Sprintf("ChildAt(%v, %d): no such child of %v", k, idx, c))
|
||||
}
|
||||
|
||||
// Child returns the cursor for n, which must be a direct child of c's Node.
|
||||
//
|
||||
// Child must not be called on the Root node (whose [Cursor.Node] returns nil).
|
||||
func (c Cursor) Child(n ast.Node) Cursor {
|
||||
if c.index < 0 {
|
||||
panic("Cursor.Child called on Root node")
|
||||
}
|
||||
|
||||
if false {
|
||||
// reference implementation
|
||||
for child := range c.Children() {
|
||||
if child.Node() == n {
|
||||
return child
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// optimized implementation
|
||||
events := c.in.events
|
||||
for i := c.index + 1; events[i].index > i; i = events[i].index + 1 {
|
||||
if events[i].node == n {
|
||||
return Cursor{c.in, i}
|
||||
}
|
||||
}
|
||||
}
|
||||
panic(fmt.Sprintf("Child(%T): not a child of %v", n, c))
|
||||
}
|
||||
|
||||
// NextSibling returns the cursor for the next sibling node in the same list
|
||||
// (for example, of files, decls, specs, statements, fields, or expressions) as
|
||||
// the current node. It returns (zero, false) if the node is the last node in
|
||||
// the list, or is not part of a list.
|
||||
//
|
||||
// NextSibling must not be called on the Root node.
|
||||
//
|
||||
// See note at [Cursor.Children].
|
||||
func (c Cursor) NextSibling() (Cursor, bool) {
|
||||
if c.index < 0 {
|
||||
panic("Cursor.NextSibling called on Root node")
|
||||
}
|
||||
|
||||
events := c.in.events
|
||||
i := events[c.index].index + 1 // after corresponding pop
|
||||
if i < int32(len(events)) {
|
||||
if events[i].index > i { // push?
|
||||
return Cursor{c.in, i}, true
|
||||
}
|
||||
}
|
||||
return Cursor{}, false
|
||||
}
|
||||
|
||||
// PrevSibling returns the cursor for the previous sibling node in the
|
||||
// same list (for example, of files, decls, specs, statements, fields,
|
||||
// or expressions) as the current node. It returns zero if the node is
|
||||
// the first node in the list, or is not part of a list.
|
||||
//
|
||||
// It must not be called on the Root node.
|
||||
//
|
||||
// See note at [Cursor.Children].
|
||||
func (c Cursor) PrevSibling() (Cursor, bool) {
|
||||
if c.index < 0 {
|
||||
panic("Cursor.PrevSibling called on Root node")
|
||||
}
|
||||
|
||||
events := c.in.events
|
||||
i := c.index - 1
|
||||
if i >= 0 {
|
||||
if j := events[i].index; j < i { // pop?
|
||||
return Cursor{c.in, j}, true
|
||||
}
|
||||
}
|
||||
return Cursor{}, false
|
||||
}
|
||||
|
||||
// FirstChild returns the first direct child of the current node,
|
||||
// or zero if it has no children.
|
||||
func (c Cursor) FirstChild() (Cursor, bool) {
|
||||
events := c.in.events
|
||||
i := c.index + 1 // i=0 if c is root
|
||||
if i < int32(len(events)) && events[i].index > i { // push?
|
||||
return Cursor{c.in, i}, true
|
||||
}
|
||||
return Cursor{}, false
|
||||
}
|
||||
|
||||
// LastChild returns the last direct child of the current node,
|
||||
// or zero if it has no children.
|
||||
func (c Cursor) LastChild() (Cursor, bool) {
|
||||
events := c.in.events
|
||||
if c.index < 0 { // root?
|
||||
if len(events) > 0 {
|
||||
// return push of final event (a pop)
|
||||
return Cursor{c.in, events[len(events)-1].index}, true
|
||||
}
|
||||
} else {
|
||||
j := events[c.index].index - 1 // before corresponding pop
|
||||
// Inv: j == c.index if c has no children
|
||||
// or j is last child's pop.
|
||||
if j > c.index { // c has children
|
||||
return Cursor{c.in, events[j].index}, true
|
||||
}
|
||||
}
|
||||
return Cursor{}, false
|
||||
}
|
||||
|
||||
// Children returns an iterator over the direct children of the
|
||||
// current node, if any.
|
||||
//
|
||||
// When using Children, NextChild, and PrevChild, bear in mind that a
|
||||
// Node's children may come from different fields, some of which may
|
||||
// be lists of nodes without a distinguished intervening container
|
||||
// such as [ast.BlockStmt].
|
||||
//
|
||||
// For example, [ast.CaseClause] has a field List of expressions and a
|
||||
// field Body of statements, so the children of a CaseClause are a mix
|
||||
// of expressions and statements. Other nodes that have "uncontained"
|
||||
// list fields include:
|
||||
//
|
||||
// - [ast.ValueSpec] (Names, Values)
|
||||
// - [ast.CompositeLit] (Type, Elts)
|
||||
// - [ast.IndexListExpr] (X, Indices)
|
||||
// - [ast.CallExpr] (Fun, Args)
|
||||
// - [ast.AssignStmt] (Lhs, Rhs)
|
||||
//
|
||||
// So, do not assume that the previous sibling of an ast.Stmt is also
|
||||
// an ast.Stmt, or if it is, that they are executed sequentially,
|
||||
// unless you have established that, say, its parent is a BlockStmt
|
||||
// or its [Cursor.ParentEdge] is [edge.BlockStmt_List].
|
||||
// For example, given "for S1; ; S2 {}", the predecessor of S2 is S1,
|
||||
// even though they are not executed in sequence.
|
||||
func (c Cursor) Children() iter.Seq[Cursor] {
|
||||
return func(yield func(Cursor) bool) {
|
||||
c, ok := c.FirstChild()
|
||||
for ok && yield(c) {
|
||||
c, ok = c.NextSibling()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Contains reports whether c contains or is equal to c2.
|
||||
//
|
||||
// Both Cursors must belong to the same [Inspector];
|
||||
// neither may be its Root node.
|
||||
func (c Cursor) Contains(c2 Cursor) bool {
|
||||
if c.in != c2.in {
|
||||
panic("different inspectors")
|
||||
}
|
||||
events := c.in.events
|
||||
return c.index <= c2.index && events[c2.index].index <= events[c.index].index
|
||||
}
|
||||
|
||||
// FindNode returns the cursor for node n if it belongs to the subtree
|
||||
// rooted at c. It returns zero if n is not found.
|
||||
func (c Cursor) FindNode(n ast.Node) (Cursor, bool) {
|
||||
|
||||
// FindNode is equivalent to this code,
|
||||
// but more convenient and 15-20% faster:
|
||||
if false {
|
||||
for candidate := range c.Preorder(n) {
|
||||
if candidate.Node() == n {
|
||||
return candidate, true
|
||||
}
|
||||
}
|
||||
return Cursor{}, false
|
||||
}
|
||||
|
||||
// TODO(adonovan): opt: should we assume Node.Pos is accurate
|
||||
// and combine type-based filtering with position filtering
|
||||
// like FindByPos?
|
||||
|
||||
mask := maskOf([]ast.Node{n})
|
||||
events := c.in.events
|
||||
|
||||
for i, limit := c.indices(); i < limit; i++ {
|
||||
ev := events[i]
|
||||
if ev.index > i { // push?
|
||||
if ev.typ&mask != 0 && ev.node == n {
|
||||
return Cursor{c.in, i}, true
|
||||
}
|
||||
pop := ev.index
|
||||
if events[pop].typ&mask == 0 {
|
||||
// Subtree does not contain type of n: skip.
|
||||
i = pop
|
||||
}
|
||||
}
|
||||
}
|
||||
return Cursor{}, false
|
||||
}
|
||||
|
||||
// FindByPos returns the cursor for the innermost node n in the tree
|
||||
// rooted at c such that n.Pos() <= start && end <= n.End().
|
||||
// (For an *ast.File, it uses the bounds n.FileStart-n.FileEnd.)
|
||||
//
|
||||
// It returns zero if none is found.
|
||||
// Precondition: start <= end.
|
||||
//
|
||||
// See also [astutil.PathEnclosingInterval], which
|
||||
// tolerates adjoining whitespace.
|
||||
func (c Cursor) FindByPos(start, end token.Pos) (Cursor, bool) {
|
||||
if end < start {
|
||||
panic("end < start")
|
||||
}
|
||||
events := c.in.events
|
||||
|
||||
// This algorithm could be implemented using c.Inspect,
|
||||
// but it is about 2.5x slower.
|
||||
|
||||
// best is the push-index of the latest (=innermost) node containing range.
|
||||
// (Beware: latest is not always innermost because FuncDecl.{Name,Type} overlap.)
|
||||
best := int32(-1)
|
||||
for i, limit := c.indices(); i < limit; i++ {
|
||||
ev := events[i]
|
||||
if ev.index > i { // push?
|
||||
n := ev.node
|
||||
var nodeEnd token.Pos
|
||||
if file, ok := n.(*ast.File); ok {
|
||||
nodeEnd = file.FileEnd
|
||||
// Note: files may be out of Pos order.
|
||||
if file.FileStart > start {
|
||||
i = ev.index // disjoint, after; skip to next file
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
// Edge case: FuncDecl.Name and .Type overlap:
|
||||
// Don't update best from Name to FuncDecl.Type.
|
||||
//
|
||||
// The condition can be read as:
|
||||
// - n is FuncType
|
||||
// - n.parent is FuncDecl
|
||||
// - best is strictly beneath the FuncDecl
|
||||
if ev.typ == 1<<nFuncType &&
|
||||
events[ev.parent].typ == 1<<nFuncDecl &&
|
||||
best > ev.parent {
|
||||
continue
|
||||
}
|
||||
|
||||
nodeEnd = n.End()
|
||||
if n.Pos() > start {
|
||||
break // disjoint, after; stop
|
||||
}
|
||||
}
|
||||
// Inv: node.{Pos,FileStart} <= start
|
||||
if end <= nodeEnd {
|
||||
// node fully contains target range
|
||||
best = i
|
||||
} else if nodeEnd < start {
|
||||
i = ev.index // disjoint, before; skip forward
|
||||
}
|
||||
}
|
||||
}
|
||||
if best >= 0 {
|
||||
return Cursor{c.in, best}, true
|
||||
}
|
||||
return Cursor{}, false
|
||||
}
|
||||
311
vendor/golang.org/x/tools/go/ast/inspector/inspector.go
generated
vendored
Normal file
311
vendor/golang.org/x/tools/go/ast/inspector/inspector.go
generated
vendored
Normal file
@@ -0,0 +1,311 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package inspector provides helper functions for traversal over the
|
||||
// syntax trees of a package, including node filtering by type, and
|
||||
// materialization of the traversal stack.
|
||||
//
|
||||
// During construction, the inspector does a complete traversal and
|
||||
// builds a list of push/pop events and their node type. Subsequent
|
||||
// method calls that request a traversal scan this list, rather than walk
|
||||
// the AST, and perform type filtering using efficient bit sets.
|
||||
// This representation is sometimes called a "balanced parenthesis tree."
|
||||
//
|
||||
// Experiments suggest the inspector's traversals are about 2.5x faster
|
||||
// than [ast.Inspect], but it may take around 5 traversals for this
|
||||
// benefit to amortize the inspector's construction cost.
|
||||
// If efficiency is the primary concern, do not use Inspector for
|
||||
// one-off traversals.
|
||||
//
|
||||
// The [Cursor] type provides a more flexible API for efficient
|
||||
// navigation of syntax trees in all four "cardinal directions". For
|
||||
// example, traversals may be nested, so you can find each node of
|
||||
// type A and then search within it for nodes of type B. Or you can
|
||||
// traverse from a node to its immediate neighbors: its parent, its
|
||||
// previous and next sibling, or its first and last child. We
|
||||
// recommend using methods of Cursor in preference to Inspector where
|
||||
// possible.
|
||||
package inspector
|
||||
|
||||
// There are four orthogonal features in a traversal:
|
||||
// 1 type filtering
|
||||
// 2 pruning
|
||||
// 3 postorder calls to f
|
||||
// 4 stack
|
||||
// Rather than offer all of them in the API,
|
||||
// only a few combinations are exposed:
|
||||
// - Preorder is the fastest and has fewest features,
|
||||
// but is the most commonly needed traversal.
|
||||
// - Nodes and WithStack both provide pruning and postorder calls,
|
||||
// even though few clients need it, because supporting two versions
|
||||
// is not justified.
|
||||
// More combinations could be supported by expressing them as
|
||||
// wrappers around a more generic traversal, but this was measured
|
||||
// and found to degrade performance significantly (30%).
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
|
||||
"golang.org/x/tools/go/ast/edge"
|
||||
)
|
||||
|
||||
// An Inspector provides methods for inspecting
|
||||
// (traversing) the syntax trees of a package.
|
||||
type Inspector struct {
|
||||
events []event
|
||||
}
|
||||
|
||||
func packEdgeKindAndIndex(ek edge.Kind, index int) int32 {
|
||||
return int32(uint32(index+1)<<7 | uint32(ek))
|
||||
}
|
||||
|
||||
// unpackEdgeKindAndIndex unpacks the edge kind and edge index (within
|
||||
// an []ast.Node slice) from the parent field of a pop event.
|
||||
func unpackEdgeKindAndIndex(x int32) (edge.Kind, int) {
|
||||
// The "parent" field of a pop node holds the
|
||||
// edge Kind in the lower 7 bits and the index+1
|
||||
// in the upper 25.
|
||||
return edge.Kind(x & 0x7f), int(x>>7) - 1
|
||||
}
|
||||
|
||||
// New returns an Inspector for the specified syntax trees.
|
||||
func New(files []*ast.File) *Inspector {
|
||||
return &Inspector{traverse(files)}
|
||||
}
|
||||
|
||||
// An event represents a push or a pop
|
||||
// of an ast.Node during a traversal.
|
||||
type event struct {
|
||||
node ast.Node
|
||||
typ uint64 // typeOf(node) on push event, or union of typ strictly between push and pop events on pop events
|
||||
index int32 // index of corresponding push or pop event
|
||||
parent int32 // index of parent's push node (push nodes only), or packed edge kind/index (pop nodes only)
|
||||
}
|
||||
|
||||
// TODO: Experiment with storing only the second word of event.node (unsafe.Pointer).
|
||||
// Type can be recovered from the sole bit in typ.
|
||||
// [Tried this, wasn't faster. --adonovan]
|
||||
|
||||
// Preorder visits all the nodes of the files supplied to New in
|
||||
// depth-first order. It calls f(n) for each node n before it visits
|
||||
// n's children.
|
||||
//
|
||||
// The complete traversal sequence is determined by [ast.Inspect].
|
||||
// The types argument, if non-empty, enables type-based filtering of
|
||||
// events. The function f is called only for nodes whose type
|
||||
// matches an element of the types slice.
|
||||
//
|
||||
// The [Cursor.Preorder] method provides a richer alternative interface.
|
||||
// Example:
|
||||
//
|
||||
// for c := range in.Root().Preorder(types) { ... }
|
||||
func (in *Inspector) Preorder(types []ast.Node, f func(ast.Node)) {
|
||||
// Because it avoids postorder calls to f, and the pruning
|
||||
// check, Preorder is almost twice as fast as Nodes. The two
|
||||
// features seem to contribute similar slowdowns (~1.4x each).
|
||||
|
||||
// This function is equivalent to the PreorderSeq call below,
|
||||
// but to avoid the additional dynamic call (which adds 13-35%
|
||||
// to the benchmarks), we expand it out.
|
||||
//
|
||||
// in.PreorderSeq(types...)(func(n ast.Node) bool {
|
||||
// f(n)
|
||||
// return true
|
||||
// })
|
||||
|
||||
mask := maskOf(types)
|
||||
for i := int32(0); i < int32(len(in.events)); {
|
||||
ev := in.events[i]
|
||||
if ev.index > i {
|
||||
// push
|
||||
if ev.typ&mask != 0 {
|
||||
f(ev.node)
|
||||
}
|
||||
pop := ev.index
|
||||
if in.events[pop].typ&mask == 0 {
|
||||
// Subtrees do not contain types: skip them and pop.
|
||||
i = pop + 1
|
||||
continue
|
||||
}
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
// Nodes visits the nodes of the files supplied to New in depth-first
|
||||
// order. It calls f(n, true) for each node n before it visits n's
|
||||
// children. If f returns true, Nodes invokes f recursively for each
|
||||
// of the non-nil children of the node, followed by a call of
|
||||
// f(n, false).
|
||||
//
|
||||
// The complete traversal sequence is determined by [ast.Inspect].
|
||||
// The types argument, if non-empty, enables type-based filtering of
|
||||
// events. The function f if is called only for nodes whose type
|
||||
// matches an element of the types slice.
|
||||
//
|
||||
// The [Cursor.Inspect] method provides a richer alternative interface.
|
||||
// Example:
|
||||
//
|
||||
// in.Root().Inspect(types, func(c Cursor) bool {
|
||||
// ...
|
||||
// return true
|
||||
// }
|
||||
func (in *Inspector) Nodes(types []ast.Node, f func(n ast.Node, push bool) (proceed bool)) {
|
||||
mask := maskOf(types)
|
||||
for i := int32(0); i < int32(len(in.events)); {
|
||||
ev := in.events[i]
|
||||
if ev.index > i {
|
||||
// push
|
||||
pop := ev.index
|
||||
if ev.typ&mask != 0 {
|
||||
if !f(ev.node, true) {
|
||||
i = pop + 1 // jump to corresponding pop + 1
|
||||
continue
|
||||
}
|
||||
}
|
||||
if in.events[pop].typ&mask == 0 {
|
||||
// Subtrees do not contain types: skip them.
|
||||
i = pop
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
// pop
|
||||
push := ev.index
|
||||
if in.events[push].typ&mask != 0 {
|
||||
f(ev.node, false)
|
||||
}
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
// WithStack visits nodes in a similar manner to Nodes, but it
|
||||
// supplies each call to f an additional argument, the current
|
||||
// traversal stack. The stack's first element is the outermost node,
|
||||
// an *ast.File; its last is the innermost, n.
|
||||
//
|
||||
// The [Cursor.Inspect] method provides a richer alternative interface.
|
||||
// Example:
|
||||
//
|
||||
// in.Root().Inspect(types, func(c Cursor) bool {
|
||||
// stack := slices.Collect(c.Enclosing())
|
||||
// ...
|
||||
// return true
|
||||
// })
|
||||
func (in *Inspector) WithStack(types []ast.Node, f func(n ast.Node, push bool, stack []ast.Node) (proceed bool)) {
|
||||
mask := maskOf(types)
|
||||
var stack []ast.Node
|
||||
for i := int32(0); i < int32(len(in.events)); {
|
||||
ev := in.events[i]
|
||||
if ev.index > i {
|
||||
// push
|
||||
pop := ev.index
|
||||
stack = append(stack, ev.node)
|
||||
if ev.typ&mask != 0 {
|
||||
if !f(ev.node, true, stack) {
|
||||
i = pop + 1
|
||||
stack = stack[:len(stack)-1]
|
||||
continue
|
||||
}
|
||||
}
|
||||
if in.events[pop].typ&mask == 0 {
|
||||
// Subtrees does not contain types: skip them.
|
||||
i = pop
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
// pop
|
||||
push := ev.index
|
||||
if in.events[push].typ&mask != 0 {
|
||||
f(ev.node, false, stack)
|
||||
}
|
||||
stack = stack[:len(stack)-1]
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
// traverse builds the table of events representing a traversal.
|
||||
func traverse(files []*ast.File) []event {
|
||||
// Preallocate approximate number of events
|
||||
// based on source file extent of the declarations.
|
||||
// (We use End-Pos not FileStart-FileEnd to neglect
|
||||
// the effect of long doc comments.)
|
||||
// This makes traverse faster by 4x (!).
|
||||
var extent int
|
||||
for _, f := range files {
|
||||
extent += int(f.End() - f.Pos())
|
||||
}
|
||||
// This estimate is based on the net/http package.
|
||||
capacity := min(extent*33/100, 1e6) // impose some reasonable maximum (1M)
|
||||
|
||||
v := &visitor{
|
||||
events: make([]event, 0, capacity),
|
||||
stack: []item{{index: -1}}, // include an extra event so file nodes have a parent
|
||||
}
|
||||
for _, file := range files {
|
||||
walk(v, edge.Invalid, -1, file)
|
||||
}
|
||||
return v.events
|
||||
}
|
||||
|
||||
type visitor struct {
|
||||
events []event
|
||||
stack []item
|
||||
}
|
||||
|
||||
type item struct {
|
||||
index int32 // index of current node's push event
|
||||
parentIndex int32 // index of parent node's push event
|
||||
typAccum uint64 // accumulated type bits of current node's descendants
|
||||
edgeKindAndIndex int32 // edge.Kind and index, bit packed
|
||||
}
|
||||
|
||||
func (v *visitor) push(ek edge.Kind, eindex int, node ast.Node) {
|
||||
var (
|
||||
index = int32(len(v.events))
|
||||
parentIndex = v.stack[len(v.stack)-1].index
|
||||
)
|
||||
v.events = append(v.events, event{
|
||||
node: node,
|
||||
parent: parentIndex,
|
||||
typ: typeOf(node),
|
||||
index: 0, // (pop index is set later by visitor.pop)
|
||||
})
|
||||
v.stack = append(v.stack, item{
|
||||
index: index,
|
||||
parentIndex: parentIndex,
|
||||
edgeKindAndIndex: packEdgeKindAndIndex(ek, eindex),
|
||||
})
|
||||
|
||||
// 2B nodes ought to be enough for anyone!
|
||||
if int32(len(v.events)) < 0 {
|
||||
panic("event index exceeded int32")
|
||||
}
|
||||
|
||||
// 32M elements in an []ast.Node ought to be enough for anyone!
|
||||
if ek2, eindex2 := unpackEdgeKindAndIndex(packEdgeKindAndIndex(ek, eindex)); ek2 != ek || eindex2 != eindex {
|
||||
panic("Node slice index exceeded uint25")
|
||||
}
|
||||
}
|
||||
|
||||
func (v *visitor) pop(node ast.Node) {
|
||||
top := len(v.stack) - 1
|
||||
current := v.stack[top]
|
||||
|
||||
push := &v.events[current.index]
|
||||
parent := &v.stack[top-1]
|
||||
|
||||
push.index = int32(len(v.events)) // make push event refer to pop
|
||||
parent.typAccum |= current.typAccum | push.typ // accumulate type bits into parent
|
||||
|
||||
v.stack = v.stack[:top]
|
||||
|
||||
v.events = append(v.events, event{
|
||||
node: node,
|
||||
typ: current.typAccum,
|
||||
index: current.index,
|
||||
parent: current.edgeKindAndIndex, // see [unpackEdgeKindAndIndex]
|
||||
})
|
||||
}
|
||||
85
vendor/golang.org/x/tools/go/ast/inspector/iter.go
generated
vendored
Normal file
85
vendor/golang.org/x/tools/go/ast/inspector/iter.go
generated
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
// Copyright 2024 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.23
|
||||
|
||||
package inspector
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"iter"
|
||||
)
|
||||
|
||||
// PreorderSeq returns an iterator that visits all the
|
||||
// nodes of the files supplied to New in depth-first order.
|
||||
// It visits each node n before n's children.
|
||||
// The complete traversal sequence is determined by ast.Inspect.
|
||||
//
|
||||
// The types argument, if non-empty, enables type-based
|
||||
// filtering of events: only nodes whose type matches an
|
||||
// element of the types slice are included in the sequence.
|
||||
func (in *Inspector) PreorderSeq(types ...ast.Node) iter.Seq[ast.Node] {
|
||||
|
||||
// This implementation is identical to Preorder,
|
||||
// except that it supports breaking out of the loop.
|
||||
|
||||
return func(yield func(ast.Node) bool) {
|
||||
mask := maskOf(types)
|
||||
for i := int32(0); i < int32(len(in.events)); {
|
||||
ev := in.events[i]
|
||||
if ev.index > i {
|
||||
// push
|
||||
if ev.typ&mask != 0 {
|
||||
if !yield(ev.node) {
|
||||
break
|
||||
}
|
||||
}
|
||||
pop := ev.index
|
||||
if in.events[pop].typ&mask == 0 {
|
||||
// Subtrees do not contain types: skip them and pop.
|
||||
i = pop + 1
|
||||
continue
|
||||
}
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// All[N] returns an iterator over all the nodes of type N.
|
||||
// N must be a pointer-to-struct type that implements ast.Node.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// for call := range All[*ast.CallExpr](in) { ... }
|
||||
func All[N interface {
|
||||
*S
|
||||
ast.Node
|
||||
}, S any](in *Inspector) iter.Seq[N] {
|
||||
|
||||
// To avoid additional dynamic call overheads,
|
||||
// we duplicate rather than call the logic of PreorderSeq.
|
||||
|
||||
mask := typeOf((N)(nil))
|
||||
return func(yield func(N) bool) {
|
||||
for i := int32(0); i < int32(len(in.events)); {
|
||||
ev := in.events[i]
|
||||
if ev.index > i {
|
||||
// push
|
||||
if ev.typ&mask != 0 {
|
||||
if !yield(ev.node.(N)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
pop := ev.index
|
||||
if in.events[pop].typ&mask == 0 {
|
||||
// Subtrees do not contain types: skip them and pop.
|
||||
i = pop + 1
|
||||
continue
|
||||
}
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
227
vendor/golang.org/x/tools/go/ast/inspector/typeof.go
generated
vendored
Normal file
227
vendor/golang.org/x/tools/go/ast/inspector/typeof.go
generated
vendored
Normal file
@@ -0,0 +1,227 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package inspector
|
||||
|
||||
// This file defines func typeOf(ast.Node) uint64.
|
||||
//
|
||||
// The initial map-based implementation was too slow;
|
||||
// see https://go-review.googlesource.com/c/tools/+/135655/1/go/ast/inspector/inspector.go#196
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"math"
|
||||
)
|
||||
|
||||
const (
|
||||
nArrayType = iota
|
||||
nAssignStmt
|
||||
nBadDecl
|
||||
nBadExpr
|
||||
nBadStmt
|
||||
nBasicLit
|
||||
nBinaryExpr
|
||||
nBlockStmt
|
||||
nBranchStmt
|
||||
nCallExpr
|
||||
nCaseClause
|
||||
nChanType
|
||||
nCommClause
|
||||
nComment
|
||||
nCommentGroup
|
||||
nCompositeLit
|
||||
nDeclStmt
|
||||
nDeferStmt
|
||||
nEllipsis
|
||||
nEmptyStmt
|
||||
nExprStmt
|
||||
nField
|
||||
nFieldList
|
||||
nFile
|
||||
nForStmt
|
||||
nFuncDecl
|
||||
nFuncLit
|
||||
nFuncType
|
||||
nGenDecl
|
||||
nGoStmt
|
||||
nIdent
|
||||
nIfStmt
|
||||
nImportSpec
|
||||
nIncDecStmt
|
||||
nIndexExpr
|
||||
nIndexListExpr
|
||||
nInterfaceType
|
||||
nKeyValueExpr
|
||||
nLabeledStmt
|
||||
nMapType
|
||||
nPackage
|
||||
nParenExpr
|
||||
nRangeStmt
|
||||
nReturnStmt
|
||||
nSelectStmt
|
||||
nSelectorExpr
|
||||
nSendStmt
|
||||
nSliceExpr
|
||||
nStarExpr
|
||||
nStructType
|
||||
nSwitchStmt
|
||||
nTypeAssertExpr
|
||||
nTypeSpec
|
||||
nTypeSwitchStmt
|
||||
nUnaryExpr
|
||||
nValueSpec
|
||||
)
|
||||
|
||||
// typeOf returns a distinct single-bit value that represents the type of n.
|
||||
//
|
||||
// Various implementations were benchmarked with BenchmarkNewInspector:
|
||||
//
|
||||
// GOGC=off
|
||||
// - type switch 4.9-5.5ms 2.1ms
|
||||
// - binary search over a sorted list of types 5.5-5.9ms 2.5ms
|
||||
// - linear scan, frequency-ordered list 5.9-6.1ms 2.7ms
|
||||
// - linear scan, unordered list 6.4ms 2.7ms
|
||||
// - hash table 6.5ms 3.1ms
|
||||
//
|
||||
// A perfect hash seemed like overkill.
|
||||
//
|
||||
// The compiler's switch statement is the clear winner
|
||||
// as it produces a binary tree in code,
|
||||
// with constant conditions and good branch prediction.
|
||||
// (Sadly it is the most verbose in source code.)
|
||||
// Binary search suffered from poor branch prediction.
|
||||
func typeOf(n ast.Node) uint64 {
|
||||
// Fast path: nearly half of all nodes are identifiers.
|
||||
if _, ok := n.(*ast.Ident); ok {
|
||||
return 1 << nIdent
|
||||
}
|
||||
|
||||
// These cases include all nodes encountered by ast.Inspect.
|
||||
switch n.(type) {
|
||||
case *ast.ArrayType:
|
||||
return 1 << nArrayType
|
||||
case *ast.AssignStmt:
|
||||
return 1 << nAssignStmt
|
||||
case *ast.BadDecl:
|
||||
return 1 << nBadDecl
|
||||
case *ast.BadExpr:
|
||||
return 1 << nBadExpr
|
||||
case *ast.BadStmt:
|
||||
return 1 << nBadStmt
|
||||
case *ast.BasicLit:
|
||||
return 1 << nBasicLit
|
||||
case *ast.BinaryExpr:
|
||||
return 1 << nBinaryExpr
|
||||
case *ast.BlockStmt:
|
||||
return 1 << nBlockStmt
|
||||
case *ast.BranchStmt:
|
||||
return 1 << nBranchStmt
|
||||
case *ast.CallExpr:
|
||||
return 1 << nCallExpr
|
||||
case *ast.CaseClause:
|
||||
return 1 << nCaseClause
|
||||
case *ast.ChanType:
|
||||
return 1 << nChanType
|
||||
case *ast.CommClause:
|
||||
return 1 << nCommClause
|
||||
case *ast.Comment:
|
||||
return 1 << nComment
|
||||
case *ast.CommentGroup:
|
||||
return 1 << nCommentGroup
|
||||
case *ast.CompositeLit:
|
||||
return 1 << nCompositeLit
|
||||
case *ast.DeclStmt:
|
||||
return 1 << nDeclStmt
|
||||
case *ast.DeferStmt:
|
||||
return 1 << nDeferStmt
|
||||
case *ast.Ellipsis:
|
||||
return 1 << nEllipsis
|
||||
case *ast.EmptyStmt:
|
||||
return 1 << nEmptyStmt
|
||||
case *ast.ExprStmt:
|
||||
return 1 << nExprStmt
|
||||
case *ast.Field:
|
||||
return 1 << nField
|
||||
case *ast.FieldList:
|
||||
return 1 << nFieldList
|
||||
case *ast.File:
|
||||
return 1 << nFile
|
||||
case *ast.ForStmt:
|
||||
return 1 << nForStmt
|
||||
case *ast.FuncDecl:
|
||||
return 1 << nFuncDecl
|
||||
case *ast.FuncLit:
|
||||
return 1 << nFuncLit
|
||||
case *ast.FuncType:
|
||||
return 1 << nFuncType
|
||||
case *ast.GenDecl:
|
||||
return 1 << nGenDecl
|
||||
case *ast.GoStmt:
|
||||
return 1 << nGoStmt
|
||||
case *ast.Ident:
|
||||
return 1 << nIdent
|
||||
case *ast.IfStmt:
|
||||
return 1 << nIfStmt
|
||||
case *ast.ImportSpec:
|
||||
return 1 << nImportSpec
|
||||
case *ast.IncDecStmt:
|
||||
return 1 << nIncDecStmt
|
||||
case *ast.IndexExpr:
|
||||
return 1 << nIndexExpr
|
||||
case *ast.IndexListExpr:
|
||||
return 1 << nIndexListExpr
|
||||
case *ast.InterfaceType:
|
||||
return 1 << nInterfaceType
|
||||
case *ast.KeyValueExpr:
|
||||
return 1 << nKeyValueExpr
|
||||
case *ast.LabeledStmt:
|
||||
return 1 << nLabeledStmt
|
||||
case *ast.MapType:
|
||||
return 1 << nMapType
|
||||
case *ast.Package:
|
||||
return 1 << nPackage
|
||||
case *ast.ParenExpr:
|
||||
return 1 << nParenExpr
|
||||
case *ast.RangeStmt:
|
||||
return 1 << nRangeStmt
|
||||
case *ast.ReturnStmt:
|
||||
return 1 << nReturnStmt
|
||||
case *ast.SelectStmt:
|
||||
return 1 << nSelectStmt
|
||||
case *ast.SelectorExpr:
|
||||
return 1 << nSelectorExpr
|
||||
case *ast.SendStmt:
|
||||
return 1 << nSendStmt
|
||||
case *ast.SliceExpr:
|
||||
return 1 << nSliceExpr
|
||||
case *ast.StarExpr:
|
||||
return 1 << nStarExpr
|
||||
case *ast.StructType:
|
||||
return 1 << nStructType
|
||||
case *ast.SwitchStmt:
|
||||
return 1 << nSwitchStmt
|
||||
case *ast.TypeAssertExpr:
|
||||
return 1 << nTypeAssertExpr
|
||||
case *ast.TypeSpec:
|
||||
return 1 << nTypeSpec
|
||||
case *ast.TypeSwitchStmt:
|
||||
return 1 << nTypeSwitchStmt
|
||||
case *ast.UnaryExpr:
|
||||
return 1 << nUnaryExpr
|
||||
case *ast.ValueSpec:
|
||||
return 1 << nValueSpec
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func maskOf(nodes []ast.Node) uint64 {
|
||||
if len(nodes) == 0 {
|
||||
return math.MaxUint64 // match all node types
|
||||
}
|
||||
var mask uint64
|
||||
for _, n := range nodes {
|
||||
mask |= typeOf(n)
|
||||
}
|
||||
return mask
|
||||
}
|
||||
341
vendor/golang.org/x/tools/go/ast/inspector/walk.go
generated
vendored
Normal file
341
vendor/golang.org/x/tools/go/ast/inspector/walk.go
generated
vendored
Normal file
@@ -0,0 +1,341 @@
|
||||
// Copyright 2025 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package inspector
|
||||
|
||||
// This file is a fork of ast.Inspect to reduce unnecessary dynamic
|
||||
// calls and to gather edge information.
|
||||
//
|
||||
// Consistency with the original is ensured by TestInspectAllNodes.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
|
||||
"golang.org/x/tools/go/ast/edge"
|
||||
)
|
||||
|
||||
func walkList[N ast.Node](v *visitor, ek edge.Kind, list []N) {
|
||||
for i, node := range list {
|
||||
walk(v, ek, i, node)
|
||||
}
|
||||
}
|
||||
|
||||
func walk(v *visitor, ek edge.Kind, index int, node ast.Node) {
|
||||
v.push(ek, index, node)
|
||||
|
||||
// walk children
|
||||
// (the order of the cases matches the order
|
||||
// of the corresponding node types in ast.go)
|
||||
switch n := node.(type) {
|
||||
// Comments and fields
|
||||
case *ast.Comment:
|
||||
// nothing to do
|
||||
|
||||
case *ast.CommentGroup:
|
||||
walkList(v, edge.CommentGroup_List, n.List)
|
||||
|
||||
case *ast.Field:
|
||||
if n.Doc != nil {
|
||||
walk(v, edge.Field_Doc, -1, n.Doc)
|
||||
}
|
||||
walkList(v, edge.Field_Names, n.Names)
|
||||
if n.Type != nil {
|
||||
walk(v, edge.Field_Type, -1, n.Type)
|
||||
}
|
||||
if n.Tag != nil {
|
||||
walk(v, edge.Field_Tag, -1, n.Tag)
|
||||
}
|
||||
if n.Comment != nil {
|
||||
walk(v, edge.Field_Comment, -1, n.Comment)
|
||||
}
|
||||
|
||||
case *ast.FieldList:
|
||||
walkList(v, edge.FieldList_List, n.List)
|
||||
|
||||
// Expressions
|
||||
case *ast.BadExpr, *ast.Ident, *ast.BasicLit:
|
||||
// nothing to do
|
||||
|
||||
case *ast.Ellipsis:
|
||||
if n.Elt != nil {
|
||||
walk(v, edge.Ellipsis_Elt, -1, n.Elt)
|
||||
}
|
||||
|
||||
case *ast.FuncLit:
|
||||
walk(v, edge.FuncLit_Type, -1, n.Type)
|
||||
walk(v, edge.FuncLit_Body, -1, n.Body)
|
||||
|
||||
case *ast.CompositeLit:
|
||||
if n.Type != nil {
|
||||
walk(v, edge.CompositeLit_Type, -1, n.Type)
|
||||
}
|
||||
walkList(v, edge.CompositeLit_Elts, n.Elts)
|
||||
|
||||
case *ast.ParenExpr:
|
||||
walk(v, edge.ParenExpr_X, -1, n.X)
|
||||
|
||||
case *ast.SelectorExpr:
|
||||
walk(v, edge.SelectorExpr_X, -1, n.X)
|
||||
walk(v, edge.SelectorExpr_Sel, -1, n.Sel)
|
||||
|
||||
case *ast.IndexExpr:
|
||||
walk(v, edge.IndexExpr_X, -1, n.X)
|
||||
walk(v, edge.IndexExpr_Index, -1, n.Index)
|
||||
|
||||
case *ast.IndexListExpr:
|
||||
walk(v, edge.IndexListExpr_X, -1, n.X)
|
||||
walkList(v, edge.IndexListExpr_Indices, n.Indices)
|
||||
|
||||
case *ast.SliceExpr:
|
||||
walk(v, edge.SliceExpr_X, -1, n.X)
|
||||
if n.Low != nil {
|
||||
walk(v, edge.SliceExpr_Low, -1, n.Low)
|
||||
}
|
||||
if n.High != nil {
|
||||
walk(v, edge.SliceExpr_High, -1, n.High)
|
||||
}
|
||||
if n.Max != nil {
|
||||
walk(v, edge.SliceExpr_Max, -1, n.Max)
|
||||
}
|
||||
|
||||
case *ast.TypeAssertExpr:
|
||||
walk(v, edge.TypeAssertExpr_X, -1, n.X)
|
||||
if n.Type != nil {
|
||||
walk(v, edge.TypeAssertExpr_Type, -1, n.Type)
|
||||
}
|
||||
|
||||
case *ast.CallExpr:
|
||||
walk(v, edge.CallExpr_Fun, -1, n.Fun)
|
||||
walkList(v, edge.CallExpr_Args, n.Args)
|
||||
|
||||
case *ast.StarExpr:
|
||||
walk(v, edge.StarExpr_X, -1, n.X)
|
||||
|
||||
case *ast.UnaryExpr:
|
||||
walk(v, edge.UnaryExpr_X, -1, n.X)
|
||||
|
||||
case *ast.BinaryExpr:
|
||||
walk(v, edge.BinaryExpr_X, -1, n.X)
|
||||
walk(v, edge.BinaryExpr_Y, -1, n.Y)
|
||||
|
||||
case *ast.KeyValueExpr:
|
||||
walk(v, edge.KeyValueExpr_Key, -1, n.Key)
|
||||
walk(v, edge.KeyValueExpr_Value, -1, n.Value)
|
||||
|
||||
// Types
|
||||
case *ast.ArrayType:
|
||||
if n.Len != nil {
|
||||
walk(v, edge.ArrayType_Len, -1, n.Len)
|
||||
}
|
||||
walk(v, edge.ArrayType_Elt, -1, n.Elt)
|
||||
|
||||
case *ast.StructType:
|
||||
walk(v, edge.StructType_Fields, -1, n.Fields)
|
||||
|
||||
case *ast.FuncType:
|
||||
if n.TypeParams != nil {
|
||||
walk(v, edge.FuncType_TypeParams, -1, n.TypeParams)
|
||||
}
|
||||
if n.Params != nil {
|
||||
walk(v, edge.FuncType_Params, -1, n.Params)
|
||||
}
|
||||
if n.Results != nil {
|
||||
walk(v, edge.FuncType_Results, -1, n.Results)
|
||||
}
|
||||
|
||||
case *ast.InterfaceType:
|
||||
walk(v, edge.InterfaceType_Methods, -1, n.Methods)
|
||||
|
||||
case *ast.MapType:
|
||||
walk(v, edge.MapType_Key, -1, n.Key)
|
||||
walk(v, edge.MapType_Value, -1, n.Value)
|
||||
|
||||
case *ast.ChanType:
|
||||
walk(v, edge.ChanType_Value, -1, n.Value)
|
||||
|
||||
// Statements
|
||||
case *ast.BadStmt:
|
||||
// nothing to do
|
||||
|
||||
case *ast.DeclStmt:
|
||||
walk(v, edge.DeclStmt_Decl, -1, n.Decl)
|
||||
|
||||
case *ast.EmptyStmt:
|
||||
// nothing to do
|
||||
|
||||
case *ast.LabeledStmt:
|
||||
walk(v, edge.LabeledStmt_Label, -1, n.Label)
|
||||
walk(v, edge.LabeledStmt_Stmt, -1, n.Stmt)
|
||||
|
||||
case *ast.ExprStmt:
|
||||
walk(v, edge.ExprStmt_X, -1, n.X)
|
||||
|
||||
case *ast.SendStmt:
|
||||
walk(v, edge.SendStmt_Chan, -1, n.Chan)
|
||||
walk(v, edge.SendStmt_Value, -1, n.Value)
|
||||
|
||||
case *ast.IncDecStmt:
|
||||
walk(v, edge.IncDecStmt_X, -1, n.X)
|
||||
|
||||
case *ast.AssignStmt:
|
||||
walkList(v, edge.AssignStmt_Lhs, n.Lhs)
|
||||
walkList(v, edge.AssignStmt_Rhs, n.Rhs)
|
||||
|
||||
case *ast.GoStmt:
|
||||
walk(v, edge.GoStmt_Call, -1, n.Call)
|
||||
|
||||
case *ast.DeferStmt:
|
||||
walk(v, edge.DeferStmt_Call, -1, n.Call)
|
||||
|
||||
case *ast.ReturnStmt:
|
||||
walkList(v, edge.ReturnStmt_Results, n.Results)
|
||||
|
||||
case *ast.BranchStmt:
|
||||
if n.Label != nil {
|
||||
walk(v, edge.BranchStmt_Label, -1, n.Label)
|
||||
}
|
||||
|
||||
case *ast.BlockStmt:
|
||||
walkList(v, edge.BlockStmt_List, n.List)
|
||||
|
||||
case *ast.IfStmt:
|
||||
if n.Init != nil {
|
||||
walk(v, edge.IfStmt_Init, -1, n.Init)
|
||||
}
|
||||
walk(v, edge.IfStmt_Cond, -1, n.Cond)
|
||||
walk(v, edge.IfStmt_Body, -1, n.Body)
|
||||
if n.Else != nil {
|
||||
walk(v, edge.IfStmt_Else, -1, n.Else)
|
||||
}
|
||||
|
||||
case *ast.CaseClause:
|
||||
walkList(v, edge.CaseClause_List, n.List)
|
||||
walkList(v, edge.CaseClause_Body, n.Body)
|
||||
|
||||
case *ast.SwitchStmt:
|
||||
if n.Init != nil {
|
||||
walk(v, edge.SwitchStmt_Init, -1, n.Init)
|
||||
}
|
||||
if n.Tag != nil {
|
||||
walk(v, edge.SwitchStmt_Tag, -1, n.Tag)
|
||||
}
|
||||
walk(v, edge.SwitchStmt_Body, -1, n.Body)
|
||||
|
||||
case *ast.TypeSwitchStmt:
|
||||
if n.Init != nil {
|
||||
walk(v, edge.TypeSwitchStmt_Init, -1, n.Init)
|
||||
}
|
||||
walk(v, edge.TypeSwitchStmt_Assign, -1, n.Assign)
|
||||
walk(v, edge.TypeSwitchStmt_Body, -1, n.Body)
|
||||
|
||||
case *ast.CommClause:
|
||||
if n.Comm != nil {
|
||||
walk(v, edge.CommClause_Comm, -1, n.Comm)
|
||||
}
|
||||
walkList(v, edge.CommClause_Body, n.Body)
|
||||
|
||||
case *ast.SelectStmt:
|
||||
walk(v, edge.SelectStmt_Body, -1, n.Body)
|
||||
|
||||
case *ast.ForStmt:
|
||||
if n.Init != nil {
|
||||
walk(v, edge.ForStmt_Init, -1, n.Init)
|
||||
}
|
||||
if n.Cond != nil {
|
||||
walk(v, edge.ForStmt_Cond, -1, n.Cond)
|
||||
}
|
||||
if n.Post != nil {
|
||||
walk(v, edge.ForStmt_Post, -1, n.Post)
|
||||
}
|
||||
walk(v, edge.ForStmt_Body, -1, n.Body)
|
||||
|
||||
case *ast.RangeStmt:
|
||||
if n.Key != nil {
|
||||
walk(v, edge.RangeStmt_Key, -1, n.Key)
|
||||
}
|
||||
if n.Value != nil {
|
||||
walk(v, edge.RangeStmt_Value, -1, n.Value)
|
||||
}
|
||||
walk(v, edge.RangeStmt_X, -1, n.X)
|
||||
walk(v, edge.RangeStmt_Body, -1, n.Body)
|
||||
|
||||
// Declarations
|
||||
case *ast.ImportSpec:
|
||||
if n.Doc != nil {
|
||||
walk(v, edge.ImportSpec_Doc, -1, n.Doc)
|
||||
}
|
||||
if n.Name != nil {
|
||||
walk(v, edge.ImportSpec_Name, -1, n.Name)
|
||||
}
|
||||
walk(v, edge.ImportSpec_Path, -1, n.Path)
|
||||
if n.Comment != nil {
|
||||
walk(v, edge.ImportSpec_Comment, -1, n.Comment)
|
||||
}
|
||||
|
||||
case *ast.ValueSpec:
|
||||
if n.Doc != nil {
|
||||
walk(v, edge.ValueSpec_Doc, -1, n.Doc)
|
||||
}
|
||||
walkList(v, edge.ValueSpec_Names, n.Names)
|
||||
if n.Type != nil {
|
||||
walk(v, edge.ValueSpec_Type, -1, n.Type)
|
||||
}
|
||||
walkList(v, edge.ValueSpec_Values, n.Values)
|
||||
if n.Comment != nil {
|
||||
walk(v, edge.ValueSpec_Comment, -1, n.Comment)
|
||||
}
|
||||
|
||||
case *ast.TypeSpec:
|
||||
if n.Doc != nil {
|
||||
walk(v, edge.TypeSpec_Doc, -1, n.Doc)
|
||||
}
|
||||
walk(v, edge.TypeSpec_Name, -1, n.Name)
|
||||
if n.TypeParams != nil {
|
||||
walk(v, edge.TypeSpec_TypeParams, -1, n.TypeParams)
|
||||
}
|
||||
walk(v, edge.TypeSpec_Type, -1, n.Type)
|
||||
if n.Comment != nil {
|
||||
walk(v, edge.TypeSpec_Comment, -1, n.Comment)
|
||||
}
|
||||
|
||||
case *ast.BadDecl:
|
||||
// nothing to do
|
||||
|
||||
case *ast.GenDecl:
|
||||
if n.Doc != nil {
|
||||
walk(v, edge.GenDecl_Doc, -1, n.Doc)
|
||||
}
|
||||
walkList(v, edge.GenDecl_Specs, n.Specs)
|
||||
|
||||
case *ast.FuncDecl:
|
||||
if n.Doc != nil {
|
||||
walk(v, edge.FuncDecl_Doc, -1, n.Doc)
|
||||
}
|
||||
if n.Recv != nil {
|
||||
walk(v, edge.FuncDecl_Recv, -1, n.Recv)
|
||||
}
|
||||
walk(v, edge.FuncDecl_Name, -1, n.Name)
|
||||
walk(v, edge.FuncDecl_Type, -1, n.Type)
|
||||
if n.Body != nil {
|
||||
walk(v, edge.FuncDecl_Body, -1, n.Body)
|
||||
}
|
||||
|
||||
case *ast.File:
|
||||
if n.Doc != nil {
|
||||
walk(v, edge.File_Doc, -1, n.Doc)
|
||||
}
|
||||
walk(v, edge.File_Name, -1, n.Name)
|
||||
walkList(v, edge.File_Decls, n.Decls)
|
||||
// don't walk n.Comments - they have been
|
||||
// visited already through the individual
|
||||
// nodes
|
||||
|
||||
default:
|
||||
// (includes *ast.Package)
|
||||
panic(fmt.Sprintf("Walk: unexpected node type %T", n))
|
||||
}
|
||||
|
||||
v.pop(node)
|
||||
}
|
||||
193
vendor/golang.org/x/tools/go/buildutil/allpackages.go
generated
vendored
Normal file
193
vendor/golang.org/x/tools/go/buildutil/allpackages.go
generated
vendored
Normal file
@@ -0,0 +1,193 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package buildutil provides utilities related to the go/build
|
||||
// package in the standard library.
|
||||
//
|
||||
// All I/O is done via the build.Context file system interface, which must
|
||||
// be concurrency-safe.
|
||||
package buildutil // import "golang.org/x/tools/go/buildutil"
|
||||
|
||||
import (
|
||||
"go/build"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// AllPackages returns the package path of each Go package in any source
|
||||
// directory of the specified build context (e.g. $GOROOT or an element
|
||||
// of $GOPATH). Errors are ignored. The results are sorted.
|
||||
// All package paths are canonical, and thus may contain "/vendor/".
|
||||
//
|
||||
// The result may include import paths for directories that contain no
|
||||
// *.go files, such as "archive" (in $GOROOT/src).
|
||||
//
|
||||
// All I/O is done via the build.Context file system interface,
|
||||
// which must be concurrency-safe.
|
||||
func AllPackages(ctxt *build.Context) []string {
|
||||
var list []string
|
||||
ForEachPackage(ctxt, func(pkg string, _ error) {
|
||||
list = append(list, pkg)
|
||||
})
|
||||
sort.Strings(list)
|
||||
return list
|
||||
}
|
||||
|
||||
// ForEachPackage calls the found function with the package path of
|
||||
// each Go package it finds in any source directory of the specified
|
||||
// build context (e.g. $GOROOT or an element of $GOPATH).
|
||||
// All package paths are canonical, and thus may contain "/vendor/".
|
||||
//
|
||||
// If the package directory exists but could not be read, the second
|
||||
// argument to the found function provides the error.
|
||||
//
|
||||
// All I/O is done via the build.Context file system interface,
|
||||
// which must be concurrency-safe.
|
||||
func ForEachPackage(ctxt *build.Context, found func(importPath string, err error)) {
|
||||
ch := make(chan item)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for _, root := range ctxt.SrcDirs() {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
allPackages(ctxt, root, ch)
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(ch)
|
||||
}()
|
||||
|
||||
// All calls to found occur in the caller's goroutine.
|
||||
for i := range ch {
|
||||
found(i.importPath, i.err)
|
||||
}
|
||||
}
|
||||
|
||||
type item struct {
|
||||
importPath string
|
||||
err error // (optional)
|
||||
}
|
||||
|
||||
// We use a process-wide counting semaphore to limit
|
||||
// the number of parallel calls to ReadDir.
|
||||
var ioLimit = make(chan bool, 20)
|
||||
|
||||
func allPackages(ctxt *build.Context, root string, ch chan<- item) {
|
||||
root = filepath.Clean(root) + string(os.PathSeparator)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
var walkDir func(dir string)
|
||||
walkDir = func(dir string) {
|
||||
// Avoid .foo, _foo, and testdata directory trees.
|
||||
base := filepath.Base(dir)
|
||||
if base == "" || base[0] == '.' || base[0] == '_' || base == "testdata" {
|
||||
return
|
||||
}
|
||||
|
||||
pkg := filepath.ToSlash(strings.TrimPrefix(dir, root))
|
||||
|
||||
// Prune search if we encounter any of these import paths.
|
||||
switch pkg {
|
||||
case "builtin":
|
||||
return
|
||||
}
|
||||
|
||||
ioLimit <- true
|
||||
files, err := ReadDir(ctxt, dir)
|
||||
<-ioLimit
|
||||
if pkg != "" || err != nil {
|
||||
ch <- item{pkg, err}
|
||||
}
|
||||
for _, fi := range files {
|
||||
if fi.IsDir() {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
walkDir(filepath.Join(dir, fi.Name()))
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
walkDir(root)
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// ExpandPatterns returns the set of packages matched by patterns,
|
||||
// which may have the following forms:
|
||||
//
|
||||
// golang.org/x/tools/cmd/guru # a single package
|
||||
// golang.org/x/tools/... # all packages beneath dir
|
||||
// ... # the entire workspace.
|
||||
//
|
||||
// Order is significant: a pattern preceded by '-' removes matching
|
||||
// packages from the set. For example, these patterns match all encoding
|
||||
// packages except encoding/xml:
|
||||
//
|
||||
// encoding/... -encoding/xml
|
||||
//
|
||||
// A trailing slash in a pattern is ignored. (Path components of Go
|
||||
// package names are separated by slash, not the platform's path separator.)
|
||||
func ExpandPatterns(ctxt *build.Context, patterns []string) map[string]bool {
|
||||
// TODO(adonovan): support other features of 'go list':
|
||||
// - "std"/"cmd"/"all" meta-packages
|
||||
// - "..." not at the end of a pattern
|
||||
// - relative patterns using "./" or "../" prefix
|
||||
|
||||
pkgs := make(map[string]bool)
|
||||
doPkg := func(pkg string, neg bool) {
|
||||
if neg {
|
||||
delete(pkgs, pkg)
|
||||
} else {
|
||||
pkgs[pkg] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Scan entire workspace if wildcards are present.
|
||||
// TODO(adonovan): opt: scan only the necessary subtrees of the workspace.
|
||||
var all []string
|
||||
for _, arg := range patterns {
|
||||
if strings.HasSuffix(arg, "...") {
|
||||
all = AllPackages(ctxt)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for _, arg := range patterns {
|
||||
if arg == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
neg := arg[0] == '-'
|
||||
if neg {
|
||||
arg = arg[1:]
|
||||
}
|
||||
|
||||
if arg == "..." {
|
||||
// ... matches all packages
|
||||
for _, pkg := range all {
|
||||
doPkg(pkg, neg)
|
||||
}
|
||||
} else if dir, ok := strings.CutSuffix(arg, "/..."); ok {
|
||||
// dir/... matches all packages beneath dir
|
||||
for _, pkg := range all {
|
||||
if strings.HasPrefix(pkg, dir) &&
|
||||
(len(pkg) == len(dir) || pkg[len(dir)] == '/') {
|
||||
doPkg(pkg, neg)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// single package
|
||||
doPkg(strings.TrimSuffix(arg, "/"), neg)
|
||||
}
|
||||
}
|
||||
|
||||
return pkgs
|
||||
}
|
||||
111
vendor/golang.org/x/tools/go/buildutil/fakecontext.go
generated
vendored
Normal file
111
vendor/golang.org/x/tools/go/buildutil/fakecontext.go
generated
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package buildutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/build"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// FakeContext returns a build.Context for the fake file tree specified
|
||||
// by pkgs, which maps package import paths to a mapping from file base
|
||||
// names to contents.
|
||||
//
|
||||
// The fake Context has a GOROOT of "/go" and no GOPATH, and overrides
|
||||
// the necessary file access methods to read from memory instead of the
|
||||
// real file system.
|
||||
//
|
||||
// Unlike a real file tree, the fake one has only two levels---packages
|
||||
// and files---so ReadDir("/go/src/") returns all packages under
|
||||
// /go/src/ including, for instance, "math" and "math/big".
|
||||
// ReadDir("/go/src/math/big") would return all the files in the
|
||||
// "math/big" package.
|
||||
func FakeContext(pkgs map[string]map[string]string) *build.Context {
|
||||
clean := func(filename string) string {
|
||||
f := path.Clean(filepath.ToSlash(filename))
|
||||
// Removing "/go/src" while respecting segment
|
||||
// boundaries has this unfortunate corner case:
|
||||
if f == "/go/src" {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimPrefix(f, "/go/src/")
|
||||
}
|
||||
|
||||
ctxt := build.Default // copy
|
||||
ctxt.GOROOT = "/go"
|
||||
ctxt.GOPATH = ""
|
||||
ctxt.Compiler = "gc"
|
||||
ctxt.IsDir = func(dir string) bool {
|
||||
dir = clean(dir)
|
||||
if dir == "" {
|
||||
return true // needed by (*build.Context).SrcDirs
|
||||
}
|
||||
return pkgs[dir] != nil
|
||||
}
|
||||
ctxt.ReadDir = func(dir string) ([]os.FileInfo, error) {
|
||||
dir = clean(dir)
|
||||
var fis []os.FileInfo
|
||||
if dir == "" {
|
||||
// enumerate packages
|
||||
for importPath := range pkgs {
|
||||
fis = append(fis, fakeDirInfo(importPath))
|
||||
}
|
||||
} else {
|
||||
// enumerate files of package
|
||||
for basename := range pkgs[dir] {
|
||||
fis = append(fis, fakeFileInfo(basename))
|
||||
}
|
||||
}
|
||||
sort.Sort(byName(fis))
|
||||
return fis, nil
|
||||
}
|
||||
ctxt.OpenFile = func(filename string) (io.ReadCloser, error) {
|
||||
filename = clean(filename)
|
||||
dir, base := path.Split(filename)
|
||||
content, ok := pkgs[path.Clean(dir)][base]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("file not found: %s", filename)
|
||||
}
|
||||
return io.NopCloser(strings.NewReader(content)), nil
|
||||
}
|
||||
ctxt.IsAbsPath = func(path string) bool {
|
||||
path = filepath.ToSlash(path)
|
||||
// Don't rely on the default (filepath.Path) since on
|
||||
// Windows, it reports virtual paths as non-absolute.
|
||||
return strings.HasPrefix(path, "/")
|
||||
}
|
||||
return &ctxt
|
||||
}
|
||||
|
||||
type byName []os.FileInfo
|
||||
|
||||
func (s byName) Len() int { return len(s) }
|
||||
func (s byName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
func (s byName) Less(i, j int) bool { return s[i].Name() < s[j].Name() }
|
||||
|
||||
type fakeFileInfo string
|
||||
|
||||
func (fi fakeFileInfo) Name() string { return string(fi) }
|
||||
func (fakeFileInfo) Sys() any { return nil }
|
||||
func (fakeFileInfo) ModTime() time.Time { return time.Time{} }
|
||||
func (fakeFileInfo) IsDir() bool { return false }
|
||||
func (fakeFileInfo) Size() int64 { return 0 }
|
||||
func (fakeFileInfo) Mode() os.FileMode { return 0644 }
|
||||
|
||||
type fakeDirInfo string
|
||||
|
||||
func (fd fakeDirInfo) Name() string { return string(fd) }
|
||||
func (fakeDirInfo) Sys() any { return nil }
|
||||
func (fakeDirInfo) ModTime() time.Time { return time.Time{} }
|
||||
func (fakeDirInfo) IsDir() bool { return true }
|
||||
func (fakeDirInfo) Size() int64 { return 0 }
|
||||
func (fakeDirInfo) Mode() os.FileMode { return 0755 }
|
||||
101
vendor/golang.org/x/tools/go/buildutil/overlay.go
generated
vendored
Normal file
101
vendor/golang.org/x/tools/go/buildutil/overlay.go
generated
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package buildutil
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// OverlayContext overlays a build.Context with additional files from
|
||||
// a map. Files in the map take precedence over other files.
|
||||
//
|
||||
// In addition to plain string comparison, two file names are
|
||||
// considered equal if their base names match and their directory
|
||||
// components point at the same directory on the file system. That is,
|
||||
// symbolic links are followed for directories, but not files.
|
||||
//
|
||||
// A common use case for OverlayContext is to allow editors to pass in
|
||||
// a set of unsaved, modified files.
|
||||
//
|
||||
// Currently, only the Context.OpenFile function will respect the
|
||||
// overlay. This may change in the future.
|
||||
func OverlayContext(orig *build.Context, overlay map[string][]byte) *build.Context {
|
||||
// TODO(dominikh): Implement IsDir, HasSubdir and ReadDir
|
||||
|
||||
rc := func(data []byte) (io.ReadCloser, error) {
|
||||
return io.NopCloser(bytes.NewBuffer(data)), nil
|
||||
}
|
||||
|
||||
copy := *orig // make a copy
|
||||
ctxt := ©
|
||||
ctxt.OpenFile = func(path string) (io.ReadCloser, error) {
|
||||
// Fast path: names match exactly.
|
||||
if content, ok := overlay[path]; ok {
|
||||
return rc(content)
|
||||
}
|
||||
|
||||
// Slow path: check for same file under a different
|
||||
// alias, perhaps due to a symbolic link.
|
||||
for filename, content := range overlay {
|
||||
if sameFile(path, filename) {
|
||||
return rc(content)
|
||||
}
|
||||
}
|
||||
|
||||
return OpenFile(orig, path)
|
||||
}
|
||||
return ctxt
|
||||
}
|
||||
|
||||
// ParseOverlayArchive parses an archive containing Go files and their
|
||||
// contents. The result is intended to be used with OverlayContext.
|
||||
//
|
||||
// # Archive format
|
||||
//
|
||||
// The archive consists of a series of files. Each file consists of a
|
||||
// name, a decimal file size and the file contents, separated by
|
||||
// newlines. No newline follows after the file contents.
|
||||
func ParseOverlayArchive(archive io.Reader) (map[string][]byte, error) {
|
||||
overlay := make(map[string][]byte)
|
||||
r := bufio.NewReader(archive)
|
||||
for {
|
||||
// Read file name.
|
||||
filename, err := r.ReadString('\n')
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break // OK
|
||||
}
|
||||
return nil, fmt.Errorf("reading archive file name: %v", err)
|
||||
}
|
||||
filename = filepath.Clean(strings.TrimSpace(filename))
|
||||
|
||||
// Read file size.
|
||||
sz, err := r.ReadString('\n')
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading size of archive file %s: %v", filename, err)
|
||||
}
|
||||
sz = strings.TrimSpace(sz)
|
||||
size, err := strconv.ParseUint(sz, 10, 32)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing size of archive file %s: %v", filename, err)
|
||||
}
|
||||
|
||||
// Read file content.
|
||||
content := make([]byte, size)
|
||||
if _, err := io.ReadFull(r, content); err != nil {
|
||||
return nil, fmt.Errorf("reading archive file %s: %v", filename, err)
|
||||
}
|
||||
overlay[filename] = content
|
||||
}
|
||||
|
||||
return overlay, nil
|
||||
}
|
||||
100
vendor/golang.org/x/tools/go/buildutil/tags.go
generated
vendored
Normal file
100
vendor/golang.org/x/tools/go/buildutil/tags.go
generated
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package buildutil
|
||||
|
||||
// This duplicated logic must be kept in sync with that from go build:
|
||||
// $GOROOT/src/cmd/go/internal/work/build.go (tagsFlag.Set)
|
||||
// $GOROOT/src/cmd/go/internal/base/flag.go (StringsFlag.Set)
|
||||
// $GOROOT/src/cmd/internal/quoted/quoted.go (isSpaceByte, Split)
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const TagsFlagDoc = "a list of `build tags` to consider satisfied during the build. " +
|
||||
"For more information about build tags, see the description of " +
|
||||
"build constraints in the documentation for the go/build package"
|
||||
|
||||
// TagsFlag is an implementation of the flag.Value and flag.Getter interfaces that parses
|
||||
// a flag value the same as go build's -tags flag and populates a []string slice.
|
||||
//
|
||||
// See $GOROOT/src/go/build/doc.go for description of build tags.
|
||||
// See $GOROOT/src/cmd/go/doc.go for description of 'go build -tags' flag.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// flag.Var((*buildutil.TagsFlag)(&build.Default.BuildTags), "tags", buildutil.TagsFlagDoc)
|
||||
type TagsFlag []string
|
||||
|
||||
func (v *TagsFlag) Set(s string) error {
|
||||
// See $GOROOT/src/cmd/go/internal/work/build.go (tagsFlag.Set)
|
||||
// For compatibility with Go 1.12 and earlier, allow "-tags='a b c'" or even just "-tags='a'".
|
||||
if strings.Contains(s, " ") || strings.Contains(s, "'") {
|
||||
var err error
|
||||
*v, err = splitQuotedFields(s)
|
||||
if *v == nil {
|
||||
*v = []string{}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Starting in Go 1.13, the -tags flag is a comma-separated list of build tags.
|
||||
*v = []string{}
|
||||
for s := range strings.SplitSeq(s, ",") {
|
||||
if s != "" {
|
||||
*v = append(*v, s)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *TagsFlag) Get() any { return *v }
|
||||
|
||||
func splitQuotedFields(s string) ([]string, error) {
|
||||
// See $GOROOT/src/cmd/internal/quoted/quoted.go (Split)
|
||||
// This must remain in sync with that logic.
|
||||
var f []string
|
||||
for len(s) > 0 {
|
||||
for len(s) > 0 && isSpaceByte(s[0]) {
|
||||
s = s[1:]
|
||||
}
|
||||
if len(s) == 0 {
|
||||
break
|
||||
}
|
||||
// Accepted quoted string. No unescaping inside.
|
||||
if s[0] == '"' || s[0] == '\'' {
|
||||
quote := s[0]
|
||||
s = s[1:]
|
||||
i := 0
|
||||
for i < len(s) && s[i] != quote {
|
||||
i++
|
||||
}
|
||||
if i >= len(s) {
|
||||
return nil, fmt.Errorf("unterminated %c string", quote)
|
||||
}
|
||||
f = append(f, s[:i])
|
||||
s = s[i+1:]
|
||||
continue
|
||||
}
|
||||
i := 0
|
||||
for i < len(s) && !isSpaceByte(s[i]) {
|
||||
i++
|
||||
}
|
||||
f = append(f, s[:i])
|
||||
s = s[i:]
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func (v *TagsFlag) String() string {
|
||||
return "<tagsFlag>"
|
||||
}
|
||||
|
||||
func isSpaceByte(c byte) bool {
|
||||
// See $GOROOT/src/cmd/internal/quoted/quoted.go (isSpaceByte, Split)
|
||||
// This list must remain in sync with that.
|
||||
return c == ' ' || c == '\t' || c == '\n' || c == '\r'
|
||||
}
|
||||
209
vendor/golang.org/x/tools/go/buildutil/util.go
generated
vendored
Normal file
209
vendor/golang.org/x/tools/go/buildutil/util.go
generated
vendored
Normal file
@@ -0,0 +1,209 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package buildutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ParseFile behaves like parser.ParseFile,
|
||||
// but uses the build context's file system interface, if any.
|
||||
//
|
||||
// If file is not absolute (as defined by IsAbsPath), the (dir, file)
|
||||
// components are joined using JoinPath; dir must be absolute.
|
||||
//
|
||||
// The displayPath function, if provided, is used to transform the
|
||||
// filename that will be attached to the ASTs.
|
||||
//
|
||||
// TODO(adonovan): call this from go/loader.parseFiles when the tree thaws.
|
||||
func ParseFile(fset *token.FileSet, ctxt *build.Context, displayPath func(string) string, dir string, file string, mode parser.Mode) (*ast.File, error) {
|
||||
if !IsAbsPath(ctxt, file) {
|
||||
file = JoinPath(ctxt, dir, file)
|
||||
}
|
||||
rd, err := OpenFile(ctxt, file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rd.Close() // ignore error
|
||||
if displayPath != nil {
|
||||
file = displayPath(file)
|
||||
}
|
||||
return parser.ParseFile(fset, file, rd, mode)
|
||||
}
|
||||
|
||||
// ContainingPackage returns the package containing filename.
|
||||
//
|
||||
// If filename is not absolute, it is interpreted relative to working directory dir.
|
||||
// All I/O is via the build context's file system interface, if any.
|
||||
//
|
||||
// The '...Files []string' fields of the resulting build.Package are not
|
||||
// populated (build.FindOnly mode).
|
||||
func ContainingPackage(ctxt *build.Context, dir, filename string) (*build.Package, error) {
|
||||
if !IsAbsPath(ctxt, filename) {
|
||||
filename = JoinPath(ctxt, dir, filename)
|
||||
}
|
||||
|
||||
// We must not assume the file tree uses
|
||||
// "/" always,
|
||||
// `\` always,
|
||||
// or os.PathSeparator (which varies by platform),
|
||||
// but to make any progress, we are forced to assume that
|
||||
// paths will not use `\` unless the PathSeparator
|
||||
// is also `\`, thus we can rely on filepath.ToSlash for some sanity.
|
||||
|
||||
dirSlash := path.Dir(filepath.ToSlash(filename)) + "/"
|
||||
|
||||
// We assume that no source root (GOPATH[i] or GOROOT) contains any other.
|
||||
for _, srcdir := range ctxt.SrcDirs() {
|
||||
srcdirSlash := filepath.ToSlash(srcdir) + "/"
|
||||
if importPath, ok := HasSubdir(ctxt, srcdirSlash, dirSlash); ok {
|
||||
return ctxt.Import(importPath, dir, build.FindOnly)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("can't find package containing %s", filename)
|
||||
}
|
||||
|
||||
// -- Effective methods of file system interface -------------------------
|
||||
|
||||
// (go/build.Context defines these as methods, but does not export them.)
|
||||
|
||||
// HasSubdir calls ctxt.HasSubdir (if not nil) or else uses
|
||||
// the local file system to answer the question.
|
||||
func HasSubdir(ctxt *build.Context, root, dir string) (rel string, ok bool) {
|
||||
if f := ctxt.HasSubdir; f != nil {
|
||||
return f(root, dir)
|
||||
}
|
||||
|
||||
// Try using paths we received.
|
||||
if rel, ok = hasSubdir(root, dir); ok {
|
||||
return
|
||||
}
|
||||
|
||||
// Try expanding symlinks and comparing
|
||||
// expanded against unexpanded and
|
||||
// expanded against expanded.
|
||||
rootSym, _ := filepath.EvalSymlinks(root)
|
||||
dirSym, _ := filepath.EvalSymlinks(dir)
|
||||
|
||||
if rel, ok = hasSubdir(rootSym, dir); ok {
|
||||
return
|
||||
}
|
||||
if rel, ok = hasSubdir(root, dirSym); ok {
|
||||
return
|
||||
}
|
||||
return hasSubdir(rootSym, dirSym)
|
||||
}
|
||||
|
||||
func hasSubdir(root, dir string) (rel string, ok bool) {
|
||||
const sep = string(filepath.Separator)
|
||||
root = filepath.Clean(root)
|
||||
if !strings.HasSuffix(root, sep) {
|
||||
root += sep
|
||||
}
|
||||
|
||||
dir = filepath.Clean(dir)
|
||||
if !strings.HasPrefix(dir, root) {
|
||||
return "", false
|
||||
}
|
||||
|
||||
return filepath.ToSlash(dir[len(root):]), true
|
||||
}
|
||||
|
||||
// FileExists returns true if the specified file exists,
|
||||
// using the build context's file system interface.
|
||||
func FileExists(ctxt *build.Context, path string) bool {
|
||||
if ctxt.OpenFile != nil {
|
||||
r, err := ctxt.OpenFile(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
r.Close() // ignore error
|
||||
return true
|
||||
}
|
||||
_, err := os.Stat(path)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// OpenFile behaves like os.Open,
|
||||
// but uses the build context's file system interface, if any.
|
||||
func OpenFile(ctxt *build.Context, path string) (io.ReadCloser, error) {
|
||||
if ctxt.OpenFile != nil {
|
||||
return ctxt.OpenFile(path)
|
||||
}
|
||||
return os.Open(path)
|
||||
}
|
||||
|
||||
// IsAbsPath behaves like filepath.IsAbs,
|
||||
// but uses the build context's file system interface, if any.
|
||||
func IsAbsPath(ctxt *build.Context, path string) bool {
|
||||
if ctxt.IsAbsPath != nil {
|
||||
return ctxt.IsAbsPath(path)
|
||||
}
|
||||
return filepath.IsAbs(path)
|
||||
}
|
||||
|
||||
// JoinPath behaves like filepath.Join,
|
||||
// but uses the build context's file system interface, if any.
|
||||
func JoinPath(ctxt *build.Context, path ...string) string {
|
||||
if ctxt.JoinPath != nil {
|
||||
return ctxt.JoinPath(path...)
|
||||
}
|
||||
return filepath.Join(path...)
|
||||
}
|
||||
|
||||
// IsDir behaves like os.Stat plus IsDir,
|
||||
// but uses the build context's file system interface, if any.
|
||||
func IsDir(ctxt *build.Context, path string) bool {
|
||||
if ctxt.IsDir != nil {
|
||||
return ctxt.IsDir(path)
|
||||
}
|
||||
fi, err := os.Stat(path)
|
||||
return err == nil && fi.IsDir()
|
||||
}
|
||||
|
||||
// ReadDir behaves like ioutil.ReadDir,
|
||||
// but uses the build context's file system interface, if any.
|
||||
func ReadDir(ctxt *build.Context, path string) ([]os.FileInfo, error) {
|
||||
if ctxt.ReadDir != nil {
|
||||
return ctxt.ReadDir(path)
|
||||
}
|
||||
return ioutil.ReadDir(path)
|
||||
}
|
||||
|
||||
// SplitPathList behaves like filepath.SplitList,
|
||||
// but uses the build context's file system interface, if any.
|
||||
func SplitPathList(ctxt *build.Context, s string) []string {
|
||||
if ctxt.SplitPathList != nil {
|
||||
return ctxt.SplitPathList(s)
|
||||
}
|
||||
return filepath.SplitList(s)
|
||||
}
|
||||
|
||||
// sameFile returns true if x and y have the same basename and denote
|
||||
// the same file.
|
||||
func sameFile(x, y string) bool {
|
||||
if path.Clean(x) == path.Clean(y) {
|
||||
return true
|
||||
}
|
||||
if filepath.Base(x) == filepath.Base(y) { // (optimisation)
|
||||
if xi, err := os.Stat(x); err == nil {
|
||||
if yi, err := os.Stat(y); err == nil {
|
||||
return os.SameFile(xi, yi)
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
124
vendor/golang.org/x/tools/go/callgraph/callgraph.go
generated
vendored
Normal file
124
vendor/golang.org/x/tools/go/callgraph/callgraph.go
generated
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package callgraph defines the call graph and various algorithms
|
||||
and utilities to operate on it.
|
||||
|
||||
A call graph is a labelled directed graph whose nodes represent
|
||||
functions and whose edge labels represent syntactic function call
|
||||
sites. The presence of a labelled edge (caller, site, callee)
|
||||
indicates that caller may call callee at the specified call site.
|
||||
|
||||
A call graph is a multigraph: it may contain multiple edges (caller,
|
||||
*, callee) connecting the same pair of nodes, so long as the edges
|
||||
differ by label; this occurs when one function calls another function
|
||||
from multiple call sites. Also, it may contain multiple edges
|
||||
(caller, site, *) that differ only by callee; this indicates a
|
||||
polymorphic call.
|
||||
|
||||
A SOUND call graph is one that overapproximates the dynamic calling
|
||||
behaviors of the program in all possible executions. One call graph
|
||||
is more PRECISE than another if it is a smaller overapproximation of
|
||||
the dynamic behavior.
|
||||
|
||||
All call graphs have a synthetic root node which is responsible for
|
||||
calling main() and init().
|
||||
|
||||
Calls to built-in functions (e.g. panic, println) are not represented
|
||||
in the call graph; they are treated like built-in operators of the
|
||||
language.
|
||||
*/
|
||||
package callgraph // import "golang.org/x/tools/go/callgraph"
|
||||
|
||||
// TODO(zpavlinovic): decide how callgraphs handle calls to and from generic function bodies.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
)
|
||||
|
||||
// A Graph represents a call graph.
|
||||
//
|
||||
// A graph may contain nodes that are not reachable from the root.
|
||||
// If the call graph is sound, such nodes indicate unreachable
|
||||
// functions.
|
||||
type Graph struct {
|
||||
Root *Node // the distinguished root node (Root.Func may be nil)
|
||||
Nodes map[*ssa.Function]*Node // all nodes by function
|
||||
}
|
||||
|
||||
// New returns a new Graph with the specified (optional) root node.
|
||||
func New(root *ssa.Function) *Graph {
|
||||
g := &Graph{Nodes: make(map[*ssa.Function]*Node)}
|
||||
g.Root = g.CreateNode(root)
|
||||
return g
|
||||
}
|
||||
|
||||
// CreateNode returns the Node for fn, creating it if not present.
|
||||
// The root node may have fn=nil.
|
||||
func (g *Graph) CreateNode(fn *ssa.Function) *Node {
|
||||
n, ok := g.Nodes[fn]
|
||||
if !ok {
|
||||
n = &Node{Func: fn, ID: len(g.Nodes)}
|
||||
g.Nodes[fn] = n
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// A Node represents a node in a call graph.
|
||||
type Node struct {
|
||||
Func *ssa.Function // the function this node represents
|
||||
ID int // 0-based sequence number
|
||||
In []*Edge // unordered set of incoming call edges (n.In[*].Callee == n)
|
||||
Out []*Edge // unordered set of outgoing call edges (n.Out[*].Caller == n)
|
||||
}
|
||||
|
||||
func (n *Node) String() string {
|
||||
return fmt.Sprintf("n%d:%s", n.ID, n.Func)
|
||||
}
|
||||
|
||||
// A Edge represents an edge in the call graph.
|
||||
//
|
||||
// Site is nil for edges originating in synthetic or intrinsic
|
||||
// functions, e.g. reflect.Value.Call or the root of the call graph.
|
||||
type Edge struct {
|
||||
Caller *Node
|
||||
Site ssa.CallInstruction
|
||||
Callee *Node
|
||||
}
|
||||
|
||||
func (e Edge) String() string {
|
||||
return fmt.Sprintf("%s --> %s", e.Caller, e.Callee)
|
||||
}
|
||||
|
||||
func (e Edge) Description() string {
|
||||
var prefix string
|
||||
switch e.Site.(type) {
|
||||
case nil:
|
||||
return "synthetic call"
|
||||
case *ssa.Go:
|
||||
prefix = "concurrent "
|
||||
case *ssa.Defer:
|
||||
prefix = "deferred "
|
||||
}
|
||||
return prefix + e.Site.Common().Description()
|
||||
}
|
||||
|
||||
func (e Edge) Pos() token.Pos {
|
||||
if e.Site == nil {
|
||||
return token.NoPos
|
||||
}
|
||||
return e.Site.Pos()
|
||||
}
|
||||
|
||||
// AddEdge adds the edge (caller, site, callee) to the call graph.
|
||||
// Elimination of duplicate edges is the caller's responsibility.
|
||||
func AddEdge(caller *Node, site ssa.CallInstruction, callee *Node) {
|
||||
e := &Edge{caller, site, callee}
|
||||
callee.In = append(callee.In, e)
|
||||
caller.Out = append(caller.Out, e)
|
||||
}
|
||||
77
vendor/golang.org/x/tools/go/callgraph/cha/cha.go
generated
vendored
Normal file
77
vendor/golang.org/x/tools/go/callgraph/cha/cha.go
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package cha computes the call graph of a Go program using the Class
|
||||
// Hierarchy Analysis (CHA) algorithm.
|
||||
//
|
||||
// CHA was first described in "Optimization of Object-Oriented Programs
|
||||
// Using Static Class Hierarchy Analysis", Jeffrey Dean, David Grove,
|
||||
// and Craig Chambers, ECOOP'95.
|
||||
//
|
||||
// CHA is related to RTA (see go/callgraph/rta); the difference is that
|
||||
// CHA conservatively computes the entire "implements" relation between
|
||||
// interfaces and concrete types ahead of time, whereas RTA uses dynamic
|
||||
// programming to construct it on the fly as it encounters new functions
|
||||
// reachable from main. CHA may thus include spurious call edges for
|
||||
// types that haven't been instantiated yet, or types that are never
|
||||
// instantiated.
|
||||
//
|
||||
// Since CHA conservatively assumes that all functions are address-taken
|
||||
// and all concrete types are put into interfaces, it is sound to run on
|
||||
// partial programs, such as libraries without a main or test function.
|
||||
package cha // import "golang.org/x/tools/go/callgraph/cha"
|
||||
|
||||
// TODO(zpavlinovic): update CHA for how it handles generic function bodies.
|
||||
|
||||
import (
|
||||
"golang.org/x/tools/go/callgraph"
|
||||
"golang.org/x/tools/go/callgraph/internal/chautil"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/ssa/ssautil"
|
||||
)
|
||||
|
||||
// CallGraph computes the call graph of the specified program using the
|
||||
// Class Hierarchy Analysis algorithm.
|
||||
func CallGraph(prog *ssa.Program) *callgraph.Graph {
|
||||
cg := callgraph.New(nil) // TODO(adonovan) eliminate concept of rooted callgraph
|
||||
|
||||
allFuncs := ssautil.AllFunctions(prog)
|
||||
|
||||
calleesOf := lazyCallees(allFuncs)
|
||||
|
||||
addEdge := func(fnode *callgraph.Node, site ssa.CallInstruction, g *ssa.Function) {
|
||||
gnode := cg.CreateNode(g)
|
||||
callgraph.AddEdge(fnode, site, gnode)
|
||||
}
|
||||
|
||||
addEdges := func(fnode *callgraph.Node, site ssa.CallInstruction, callees []*ssa.Function) {
|
||||
// Because every call to a highly polymorphic and
|
||||
// frequently used abstract method such as
|
||||
// (io.Writer).Write is assumed to call every concrete
|
||||
// Write method in the program, the call graph can
|
||||
// contain a lot of duplication.
|
||||
for _, g := range callees {
|
||||
addEdge(fnode, site, g)
|
||||
}
|
||||
}
|
||||
|
||||
for f := range allFuncs {
|
||||
fnode := cg.CreateNode(f)
|
||||
for _, b := range f.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
if site, ok := instr.(ssa.CallInstruction); ok {
|
||||
if g := site.Common().StaticCallee(); g != nil {
|
||||
addEdge(fnode, site, g)
|
||||
} else {
|
||||
addEdges(fnode, site, calleesOf(site))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cg
|
||||
}
|
||||
|
||||
var lazyCallees = chautil.LazyCallees
|
||||
96
vendor/golang.org/x/tools/go/callgraph/internal/chautil/lazy.go
generated
vendored
Normal file
96
vendor/golang.org/x/tools/go/callgraph/internal/chautil/lazy.go
generated
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
// Copyright 2024 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package chautil provides helper functions related to
|
||||
// class hierarchy analysis (CHA) for use in x/tools.
|
||||
package chautil
|
||||
|
||||
import (
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/types/typeutil"
|
||||
)
|
||||
|
||||
// LazyCallees returns a function that maps a call site (in a function in fns)
|
||||
// to its callees within fns. The set of callees is computed using the CHA algorithm,
|
||||
// i.e., on the entire implements relation between interfaces and concrete types
|
||||
// in fns. Please see golang.org/x/tools/go/callgraph/cha for more information.
|
||||
//
|
||||
// The resulting function is not concurrency safe.
|
||||
func LazyCallees(fns map[*ssa.Function]bool) func(site ssa.CallInstruction) []*ssa.Function {
|
||||
// funcsBySig contains all functions, keyed by signature. It is
|
||||
// the effective set of address-taken functions used to resolve
|
||||
// a dynamic call of a particular signature.
|
||||
var funcsBySig typeutil.Map // value is []*ssa.Function
|
||||
|
||||
// methodsByID contains all methods, grouped by ID for efficient
|
||||
// lookup.
|
||||
//
|
||||
// We must key by ID, not name, for correct resolution of interface
|
||||
// calls to a type with two (unexported) methods spelled the same but
|
||||
// from different packages. The fact that the concrete type implements
|
||||
// the interface does not mean the call dispatches to both methods.
|
||||
methodsByID := make(map[string][]*ssa.Function)
|
||||
|
||||
// An imethod represents an interface method I.m.
|
||||
// (There's no go/types object for it;
|
||||
// a *types.Func may be shared by many interfaces due to interface embedding.)
|
||||
type imethod struct {
|
||||
I *types.Interface
|
||||
id string
|
||||
}
|
||||
// methodsMemo records, for every abstract method call I.m on
|
||||
// interface type I, the set of concrete methods C.m of all
|
||||
// types C that satisfy interface I.
|
||||
//
|
||||
// Abstract methods may be shared by several interfaces,
|
||||
// hence we must pass I explicitly, not guess from m.
|
||||
//
|
||||
// methodsMemo is just a cache, so it needn't be a typeutil.Map.
|
||||
methodsMemo := make(map[imethod][]*ssa.Function)
|
||||
lookupMethods := func(I *types.Interface, m *types.Func) []*ssa.Function {
|
||||
id := m.Id()
|
||||
methods, ok := methodsMemo[imethod{I, id}]
|
||||
if !ok {
|
||||
for _, f := range methodsByID[id] {
|
||||
C := f.Signature.Recv().Type() // named or *named
|
||||
if types.Implements(C, I) {
|
||||
methods = append(methods, f)
|
||||
}
|
||||
}
|
||||
methodsMemo[imethod{I, id}] = methods
|
||||
}
|
||||
return methods
|
||||
}
|
||||
|
||||
for f := range fns {
|
||||
if f.Signature.Recv() == nil {
|
||||
// Package initializers can never be address-taken.
|
||||
if f.Name() == "init" && f.Synthetic == "package initializer" {
|
||||
continue
|
||||
}
|
||||
funcs, _ := funcsBySig.At(f.Signature).([]*ssa.Function)
|
||||
funcs = append(funcs, f)
|
||||
funcsBySig.Set(f.Signature, funcs)
|
||||
} else if obj := f.Object(); obj != nil {
|
||||
id := obj.(*types.Func).Id()
|
||||
methodsByID[id] = append(methodsByID[id], f)
|
||||
}
|
||||
}
|
||||
|
||||
return func(site ssa.CallInstruction) []*ssa.Function {
|
||||
call := site.Common()
|
||||
if call.IsInvoke() {
|
||||
tiface := call.Value.Type().Underlying().(*types.Interface)
|
||||
return lookupMethods(tiface, call.Method)
|
||||
} else if g := call.StaticCallee(); g != nil {
|
||||
return []*ssa.Function{g}
|
||||
} else if _, ok := call.Value.(*ssa.Builtin); !ok {
|
||||
fns, _ := funcsBySig.At(call.Signature()).([]*ssa.Function)
|
||||
return fns
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
563
vendor/golang.org/x/tools/go/callgraph/rta/rta.go
generated
vendored
Normal file
563
vendor/golang.org/x/tools/go/callgraph/rta/rta.go
generated
vendored
Normal file
@@ -0,0 +1,563 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This package provides Rapid Type Analysis (RTA) for Go, a fast
|
||||
// algorithm for call graph construction and discovery of reachable code
|
||||
// (and hence dead code) and runtime types. The algorithm was first
|
||||
// described in:
|
||||
//
|
||||
// David F. Bacon and Peter F. Sweeney. 1996.
|
||||
// Fast static analysis of C++ virtual function calls. (OOPSLA '96)
|
||||
// http://doi.acm.org/10.1145/236337.236371
|
||||
//
|
||||
// The algorithm uses dynamic programming to tabulate the cross-product
|
||||
// of the set of known "address-taken" functions with the set of known
|
||||
// dynamic calls of the same type. As each new address-taken function
|
||||
// is discovered, call graph edges are added from each known callsite,
|
||||
// and as each new call site is discovered, call graph edges are added
|
||||
// from it to each known address-taken function.
|
||||
//
|
||||
// A similar approach is used for dynamic calls via interfaces: it
|
||||
// tabulates the cross-product of the set of known "runtime types",
|
||||
// i.e. types that may appear in an interface value, or may be derived from
|
||||
// one via reflection, with the set of known "invoke"-mode dynamic
|
||||
// calls. As each new runtime type is discovered, call edges are
|
||||
// added from the known call sites, and as each new call site is
|
||||
// discovered, call graph edges are added to each compatible
|
||||
// method.
|
||||
//
|
||||
// In addition, we must consider as reachable all address-taken
|
||||
// functions and all exported methods of any runtime type, since they
|
||||
// may be called via reflection.
|
||||
//
|
||||
// Each time a newly added call edge causes a new function to become
|
||||
// reachable, the code of that function is analyzed for more call sites,
|
||||
// address-taken functions, and runtime types. The process continues
|
||||
// until a fixed point is reached.
|
||||
package rta // import "golang.org/x/tools/go/callgraph/rta"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/types"
|
||||
"hash/crc32"
|
||||
|
||||
"golang.org/x/tools/go/callgraph"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/types/typeutil"
|
||||
)
|
||||
|
||||
// A Result holds the results of Rapid Type Analysis, which includes the
|
||||
// set of reachable functions/methods, runtime types, and the call graph.
|
||||
type Result struct {
|
||||
// CallGraph is the discovered callgraph.
|
||||
// It does not include edges for calls made via reflection.
|
||||
CallGraph *callgraph.Graph
|
||||
|
||||
// Reachable contains the set of reachable functions and methods.
|
||||
// This includes exported methods of runtime types, since
|
||||
// they may be accessed via reflection.
|
||||
// The value indicates whether the function is address-taken.
|
||||
//
|
||||
// (We wrap the bool in a struct to avoid inadvertent use of
|
||||
// "if Reachable[f] {" to test for set membership.)
|
||||
Reachable map[*ssa.Function]struct{ AddrTaken bool }
|
||||
|
||||
// RuntimeTypes contains the set of types that are needed at
|
||||
// runtime, for interfaces or reflection.
|
||||
//
|
||||
// The value indicates whether the type is inaccessible to reflection.
|
||||
// Consider:
|
||||
// type A struct{B}
|
||||
// fmt.Println(new(A))
|
||||
// Types *A, A and B are accessible to reflection, but the unnamed
|
||||
// type struct{B} is not.
|
||||
RuntimeTypes typeutil.Map
|
||||
}
|
||||
|
||||
// Working state of the RTA algorithm.
|
||||
type rta struct {
|
||||
result *Result
|
||||
|
||||
prog *ssa.Program
|
||||
|
||||
reflectValueCall *ssa.Function // (*reflect.Value).Call, iff part of prog
|
||||
|
||||
worklist []*ssa.Function // list of functions to visit
|
||||
|
||||
// addrTakenFuncsBySig contains all address-taken *Functions, grouped by signature.
|
||||
// Keys are *types.Signature, values are map[*ssa.Function]bool sets.
|
||||
addrTakenFuncsBySig typeutil.Map
|
||||
|
||||
// dynCallSites contains all dynamic "call"-mode call sites, grouped by signature.
|
||||
// Keys are *types.Signature, values are unordered []ssa.CallInstruction.
|
||||
dynCallSites typeutil.Map
|
||||
|
||||
// invokeSites contains all "invoke"-mode call sites, grouped by interface.
|
||||
// Keys are *types.Interface (never *types.Named),
|
||||
// Values are unordered []ssa.CallInstruction sets.
|
||||
invokeSites typeutil.Map
|
||||
|
||||
// The following two maps together define the subset of the
|
||||
// m:n "implements" relation needed by the algorithm.
|
||||
|
||||
// concreteTypes maps each concrete type to information about it.
|
||||
// Keys are types.Type, values are *concreteTypeInfo.
|
||||
// Only concrete types used as MakeInterface operands are included.
|
||||
concreteTypes typeutil.Map
|
||||
|
||||
// interfaceTypes maps each interface type to information about it.
|
||||
// Keys are *types.Interface, values are *interfaceTypeInfo.
|
||||
// Only interfaces used in "invoke"-mode CallInstructions are included.
|
||||
interfaceTypes typeutil.Map
|
||||
}
|
||||
|
||||
type concreteTypeInfo struct {
|
||||
C types.Type
|
||||
mset *types.MethodSet
|
||||
fprint uint64 // fingerprint of method set
|
||||
implements []*types.Interface // unordered set of implemented interfaces
|
||||
}
|
||||
|
||||
type interfaceTypeInfo struct {
|
||||
I *types.Interface
|
||||
mset *types.MethodSet
|
||||
fprint uint64
|
||||
implementations []types.Type // unordered set of concrete implementations
|
||||
}
|
||||
|
||||
// addReachable marks a function as potentially callable at run-time,
|
||||
// and ensures that it gets processed.
|
||||
func (r *rta) addReachable(f *ssa.Function, addrTaken bool) {
|
||||
reachable := r.result.Reachable
|
||||
n := len(reachable)
|
||||
v := reachable[f]
|
||||
if addrTaken {
|
||||
v.AddrTaken = true
|
||||
}
|
||||
reachable[f] = v
|
||||
if len(reachable) > n {
|
||||
// First time seeing f. Add it to the worklist.
|
||||
r.worklist = append(r.worklist, f)
|
||||
}
|
||||
}
|
||||
|
||||
// addEdge adds the specified call graph edge, and marks it reachable.
|
||||
// addrTaken indicates whether to mark the callee as "address-taken".
|
||||
// site is nil for calls made via reflection.
|
||||
func (r *rta) addEdge(caller *ssa.Function, site ssa.CallInstruction, callee *ssa.Function, addrTaken bool) {
|
||||
r.addReachable(callee, addrTaken)
|
||||
|
||||
if g := r.result.CallGraph; g != nil {
|
||||
if caller == nil {
|
||||
panic(site)
|
||||
}
|
||||
from := g.CreateNode(caller)
|
||||
to := g.CreateNode(callee)
|
||||
callgraph.AddEdge(from, site, to)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- addrTakenFuncs × dynCallSites ----------
|
||||
|
||||
// visitAddrTakenFunc is called each time we encounter an address-taken function f.
|
||||
func (r *rta) visitAddrTakenFunc(f *ssa.Function) {
|
||||
// Create two-level map (Signature -> Function -> bool).
|
||||
S := f.Signature
|
||||
funcs, _ := r.addrTakenFuncsBySig.At(S).(map[*ssa.Function]bool)
|
||||
if funcs == nil {
|
||||
funcs = make(map[*ssa.Function]bool)
|
||||
r.addrTakenFuncsBySig.Set(S, funcs)
|
||||
}
|
||||
if !funcs[f] {
|
||||
// First time seeing f.
|
||||
funcs[f] = true
|
||||
|
||||
// If we've seen any dyncalls of this type, mark it reachable,
|
||||
// and add call graph edges.
|
||||
sites, _ := r.dynCallSites.At(S).([]ssa.CallInstruction)
|
||||
for _, site := range sites {
|
||||
r.addEdge(site.Parent(), site, f, true)
|
||||
}
|
||||
|
||||
// If the program includes (*reflect.Value).Call,
|
||||
// add a dynamic call edge from it to any address-taken
|
||||
// function, regardless of signature.
|
||||
//
|
||||
// This isn't perfect.
|
||||
// - The actual call comes from an internal function
|
||||
// called reflect.call, but we can't rely on that here.
|
||||
// - reflect.Value.CallSlice behaves similarly,
|
||||
// but we don't bother to create callgraph edges from
|
||||
// it as well as it wouldn't fundamentally change the
|
||||
// reachability but it would add a bunch more edges.
|
||||
// - We assume that if reflect.Value.Call is among
|
||||
// the dependencies of the application, it is itself
|
||||
// reachable. (It would be more accurate to defer
|
||||
// all the addEdges below until r.V.Call itself
|
||||
// becomes reachable.)
|
||||
// - Fake call graph edges are added from r.V.Call to
|
||||
// each address-taken function, but not to every
|
||||
// method reachable through a materialized rtype,
|
||||
// which is a little inconsistent. Still, the
|
||||
// reachable set includes both kinds, which is what
|
||||
// matters for e.g. deadcode detection.)
|
||||
if r.reflectValueCall != nil {
|
||||
var site ssa.CallInstruction = nil // can't find actual call site
|
||||
r.addEdge(r.reflectValueCall, site, f, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// visitDynCall is called each time we encounter a dynamic "call"-mode call.
|
||||
func (r *rta) visitDynCall(site ssa.CallInstruction) {
|
||||
S := site.Common().Signature()
|
||||
|
||||
// Record the call site.
|
||||
sites, _ := r.dynCallSites.At(S).([]ssa.CallInstruction)
|
||||
r.dynCallSites.Set(S, append(sites, site))
|
||||
|
||||
// For each function of signature S that we know is address-taken,
|
||||
// add an edge and mark it reachable.
|
||||
funcs, _ := r.addrTakenFuncsBySig.At(S).(map[*ssa.Function]bool)
|
||||
for g := range funcs {
|
||||
r.addEdge(site.Parent(), site, g, true)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- concrete types × invoke sites ----------
|
||||
|
||||
// addInvokeEdge is called for each new pair (site, C) in the matrix.
|
||||
func (r *rta) addInvokeEdge(site ssa.CallInstruction, C types.Type) {
|
||||
// Ascertain the concrete method of C to be called.
|
||||
imethod := site.Common().Method
|
||||
cmethod := r.prog.LookupMethod(C, imethod.Pkg(), imethod.Name())
|
||||
r.addEdge(site.Parent(), site, cmethod, true)
|
||||
}
|
||||
|
||||
// visitInvoke is called each time the algorithm encounters an "invoke"-mode call.
|
||||
func (r *rta) visitInvoke(site ssa.CallInstruction) {
|
||||
I := site.Common().Value.Type().Underlying().(*types.Interface)
|
||||
|
||||
// Record the invoke site.
|
||||
sites, _ := r.invokeSites.At(I).([]ssa.CallInstruction)
|
||||
r.invokeSites.Set(I, append(sites, site))
|
||||
|
||||
// Add callgraph edge for each existing
|
||||
// address-taken concrete type implementing I.
|
||||
for _, C := range r.implementations(I) {
|
||||
r.addInvokeEdge(site, C)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- main algorithm ----------
|
||||
|
||||
// visitFunc processes function f.
|
||||
func (r *rta) visitFunc(f *ssa.Function) {
|
||||
var space [32]*ssa.Value // preallocate space for common case
|
||||
|
||||
for _, b := range f.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
rands := instr.Operands(space[:0])
|
||||
|
||||
switch instr := instr.(type) {
|
||||
case ssa.CallInstruction:
|
||||
call := instr.Common()
|
||||
if call.IsInvoke() {
|
||||
r.visitInvoke(instr)
|
||||
} else if g := call.StaticCallee(); g != nil {
|
||||
r.addEdge(f, instr, g, false)
|
||||
} else if _, ok := call.Value.(*ssa.Builtin); !ok {
|
||||
r.visitDynCall(instr)
|
||||
}
|
||||
|
||||
// Ignore the call-position operand when
|
||||
// looking for address-taken Functions.
|
||||
// Hack: assume this is rands[0].
|
||||
rands = rands[1:]
|
||||
|
||||
case *ssa.MakeInterface:
|
||||
// Converting a value of type T to an
|
||||
// interface materializes its runtime
|
||||
// type, allowing any of its exported
|
||||
// methods to be called though reflection.
|
||||
r.addRuntimeType(instr.X.Type(), false)
|
||||
}
|
||||
|
||||
// Process all address-taken functions.
|
||||
for _, op := range rands {
|
||||
if g, ok := (*op).(*ssa.Function); ok {
|
||||
r.visitAddrTakenFunc(g)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Analyze performs Rapid Type Analysis, starting at the specified root
|
||||
// functions. It returns nil if no roots were specified.
|
||||
//
|
||||
// The root functions must be one or more entrypoints (main and init
|
||||
// functions) of a complete SSA program, with function bodies for all
|
||||
// dependencies, constructed with the [ssa.InstantiateGenerics] mode
|
||||
// flag.
|
||||
//
|
||||
// If buildCallGraph is true, Result.CallGraph will contain a call
|
||||
// graph; otherwise, only the other fields (reachable functions) are
|
||||
// populated.
|
||||
func Analyze(roots []*ssa.Function, buildCallGraph bool) *Result {
|
||||
if len(roots) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
r := &rta{
|
||||
result: &Result{Reachable: make(map[*ssa.Function]struct{ AddrTaken bool })},
|
||||
prog: roots[0].Prog,
|
||||
}
|
||||
|
||||
if buildCallGraph {
|
||||
// TODO(adonovan): change callgraph API to eliminate the
|
||||
// notion of a distinguished root node. Some callgraphs
|
||||
// have many roots, or none.
|
||||
r.result.CallGraph = callgraph.New(roots[0])
|
||||
}
|
||||
|
||||
// Grab ssa.Function for (*reflect.Value).Call,
|
||||
// if "reflect" is among the dependencies.
|
||||
if reflectPkg := r.prog.ImportedPackage("reflect"); reflectPkg != nil {
|
||||
reflectValue := reflectPkg.Members["Value"].(*ssa.Type)
|
||||
r.reflectValueCall = r.prog.LookupMethod(reflectValue.Object().Type(), reflectPkg.Pkg, "Call")
|
||||
}
|
||||
|
||||
hasher := typeutil.MakeHasher()
|
||||
r.result.RuntimeTypes.SetHasher(hasher)
|
||||
r.addrTakenFuncsBySig.SetHasher(hasher)
|
||||
r.dynCallSites.SetHasher(hasher)
|
||||
r.invokeSites.SetHasher(hasher)
|
||||
r.concreteTypes.SetHasher(hasher)
|
||||
r.interfaceTypes.SetHasher(hasher)
|
||||
|
||||
for _, root := range roots {
|
||||
r.addReachable(root, false)
|
||||
}
|
||||
|
||||
// Visit functions, processing their instructions, and adding
|
||||
// new functions to the worklist, until a fixed point is
|
||||
// reached.
|
||||
var shadow []*ssa.Function // for efficiency, we double-buffer the worklist
|
||||
for len(r.worklist) > 0 {
|
||||
shadow, r.worklist = r.worklist, shadow[:0]
|
||||
for _, f := range shadow {
|
||||
r.visitFunc(f)
|
||||
}
|
||||
}
|
||||
return r.result
|
||||
}
|
||||
|
||||
// interfaces(C) returns all currently known interfaces implemented by C.
|
||||
func (r *rta) interfaces(C types.Type) []*types.Interface {
|
||||
// Create an info for C the first time we see it.
|
||||
var cinfo *concreteTypeInfo
|
||||
if v := r.concreteTypes.At(C); v != nil {
|
||||
cinfo = v.(*concreteTypeInfo)
|
||||
} else {
|
||||
mset := r.prog.MethodSets.MethodSet(C)
|
||||
cinfo = &concreteTypeInfo{
|
||||
C: C,
|
||||
mset: mset,
|
||||
fprint: fingerprint(mset),
|
||||
}
|
||||
r.concreteTypes.Set(C, cinfo)
|
||||
|
||||
// Ascertain set of interfaces C implements
|
||||
// and update the 'implements' relation.
|
||||
r.interfaceTypes.Iterate(func(I types.Type, v any) {
|
||||
iinfo := v.(*interfaceTypeInfo)
|
||||
if I := types.Unalias(I).(*types.Interface); implements(cinfo, iinfo) {
|
||||
iinfo.implementations = append(iinfo.implementations, C)
|
||||
cinfo.implements = append(cinfo.implements, I)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return cinfo.implements
|
||||
}
|
||||
|
||||
// implementations(I) returns all currently known concrete types that implement I.
|
||||
func (r *rta) implementations(I *types.Interface) []types.Type {
|
||||
// Create an info for I the first time we see it.
|
||||
var iinfo *interfaceTypeInfo
|
||||
if v := r.interfaceTypes.At(I); v != nil {
|
||||
iinfo = v.(*interfaceTypeInfo)
|
||||
} else {
|
||||
mset := r.prog.MethodSets.MethodSet(I)
|
||||
iinfo = &interfaceTypeInfo{
|
||||
I: I,
|
||||
mset: mset,
|
||||
fprint: fingerprint(mset),
|
||||
}
|
||||
r.interfaceTypes.Set(I, iinfo)
|
||||
|
||||
// Ascertain set of concrete types that implement I
|
||||
// and update the 'implements' relation.
|
||||
r.concreteTypes.Iterate(func(C types.Type, v any) {
|
||||
cinfo := v.(*concreteTypeInfo)
|
||||
if implements(cinfo, iinfo) {
|
||||
cinfo.implements = append(cinfo.implements, I)
|
||||
iinfo.implementations = append(iinfo.implementations, C)
|
||||
}
|
||||
})
|
||||
}
|
||||
return iinfo.implementations
|
||||
}
|
||||
|
||||
// addRuntimeType is called for each concrete type that can be the
|
||||
// dynamic type of some interface or reflect.Value.
|
||||
// Adapted from needMethods in go/ssa/builder.go
|
||||
func (r *rta) addRuntimeType(T types.Type, skip bool) {
|
||||
// Never record aliases.
|
||||
T = types.Unalias(T)
|
||||
|
||||
if prev, ok := r.result.RuntimeTypes.At(T).(bool); ok {
|
||||
if skip && !prev {
|
||||
r.result.RuntimeTypes.Set(T, skip)
|
||||
}
|
||||
return
|
||||
}
|
||||
r.result.RuntimeTypes.Set(T, skip)
|
||||
|
||||
mset := r.prog.MethodSets.MethodSet(T)
|
||||
|
||||
if _, ok := T.Underlying().(*types.Interface); !ok {
|
||||
// T is a new concrete type.
|
||||
for i, n := 0, mset.Len(); i < n; i++ {
|
||||
sel := mset.At(i)
|
||||
m := sel.Obj()
|
||||
|
||||
if m.Exported() {
|
||||
// Exported methods are always potentially callable via reflection.
|
||||
r.addReachable(r.prog.MethodValue(sel), true)
|
||||
}
|
||||
}
|
||||
|
||||
// Add callgraph edge for each existing dynamic
|
||||
// "invoke"-mode call via that interface.
|
||||
for _, I := range r.interfaces(T) {
|
||||
sites, _ := r.invokeSites.At(I).([]ssa.CallInstruction)
|
||||
for _, site := range sites {
|
||||
r.addInvokeEdge(site, T)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Precondition: T is not a method signature (*Signature with Recv()!=nil).
|
||||
// Recursive case: skip => don't call makeMethods(T).
|
||||
// Each package maintains its own set of types it has visited.
|
||||
|
||||
var n *types.Named
|
||||
switch T := types.Unalias(T).(type) {
|
||||
case *types.Named:
|
||||
n = T
|
||||
case *types.Pointer:
|
||||
n, _ = types.Unalias(T.Elem()).(*types.Named)
|
||||
}
|
||||
if n != nil {
|
||||
owner := n.Obj().Pkg()
|
||||
if owner == nil {
|
||||
return // built-in error type
|
||||
}
|
||||
}
|
||||
|
||||
// Recursion over signatures of each exported method.
|
||||
for method := range mset.Methods() {
|
||||
if method.Obj().Exported() {
|
||||
sig := method.Type().(*types.Signature)
|
||||
r.addRuntimeType(sig.Params(), true) // skip the Tuple itself
|
||||
r.addRuntimeType(sig.Results(), true) // skip the Tuple itself
|
||||
}
|
||||
}
|
||||
|
||||
switch t := T.(type) {
|
||||
case *types.Alias:
|
||||
panic("unreachable")
|
||||
|
||||
case *types.Basic:
|
||||
// nop
|
||||
|
||||
case *types.Interface:
|
||||
// nop---handled by recursion over method set.
|
||||
|
||||
case *types.Pointer:
|
||||
r.addRuntimeType(t.Elem(), false)
|
||||
|
||||
case *types.Slice:
|
||||
r.addRuntimeType(t.Elem(), false)
|
||||
|
||||
case *types.Chan:
|
||||
r.addRuntimeType(t.Elem(), false)
|
||||
|
||||
case *types.Map:
|
||||
r.addRuntimeType(t.Key(), false)
|
||||
r.addRuntimeType(t.Elem(), false)
|
||||
|
||||
case *types.Signature:
|
||||
if t.Recv() != nil {
|
||||
panic(fmt.Sprintf("Signature %s has Recv %s", t, t.Recv()))
|
||||
}
|
||||
r.addRuntimeType(t.Params(), true) // skip the Tuple itself
|
||||
r.addRuntimeType(t.Results(), true) // skip the Tuple itself
|
||||
|
||||
case *types.Named:
|
||||
// A pointer-to-named type can be derived from a named
|
||||
// type via reflection. It may have methods too.
|
||||
r.addRuntimeType(types.NewPointer(T), false)
|
||||
|
||||
// Consider 'type T struct{S}' where S has methods.
|
||||
// Reflection provides no way to get from T to struct{S},
|
||||
// only to S, so the method set of struct{S} is unwanted,
|
||||
// so set 'skip' flag during recursion.
|
||||
r.addRuntimeType(t.Underlying(), true)
|
||||
|
||||
case *types.Array:
|
||||
r.addRuntimeType(t.Elem(), false)
|
||||
|
||||
case *types.Struct:
|
||||
for i, n := 0, t.NumFields(); i < n; i++ {
|
||||
r.addRuntimeType(t.Field(i).Type(), false)
|
||||
}
|
||||
|
||||
case *types.Tuple:
|
||||
for i, n := 0, t.Len(); i < n; i++ {
|
||||
r.addRuntimeType(t.At(i).Type(), false)
|
||||
}
|
||||
|
||||
default:
|
||||
panic(T)
|
||||
}
|
||||
}
|
||||
|
||||
// fingerprint returns a bitmask with one bit set per method id,
|
||||
// enabling 'implements' to quickly reject most candidates.
|
||||
func fingerprint(mset *types.MethodSet) uint64 {
|
||||
var space [64]byte
|
||||
var mask uint64
|
||||
for method := range mset.Methods() {
|
||||
method := method.Obj()
|
||||
sig := method.Type().(*types.Signature)
|
||||
sum := crc32.ChecksumIEEE(fmt.Appendf(space[:], "%s/%d/%d",
|
||||
method.Id(),
|
||||
sig.Params().Len(),
|
||||
sig.Results().Len()))
|
||||
mask |= 1 << (sum % 64)
|
||||
}
|
||||
return mask
|
||||
}
|
||||
|
||||
// implements reports whether types.Implements(cinfo.C, iinfo.I),
|
||||
// but more efficiently.
|
||||
func implements(cinfo *concreteTypeInfo, iinfo *interfaceTypeInfo) (got bool) {
|
||||
// The concrete type must have at least the methods
|
||||
// (bits) of the interface type. Use a bitwise subset
|
||||
// test to reject most candidates quickly.
|
||||
return iinfo.fprint & ^cinfo.fprint == 0 && types.Implements(cinfo.C, iinfo.I)
|
||||
}
|
||||
180
vendor/golang.org/x/tools/go/callgraph/util.go
generated
vendored
Normal file
180
vendor/golang.org/x/tools/go/callgraph/util.go
generated
vendored
Normal file
@@ -0,0 +1,180 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package callgraph
|
||||
|
||||
import "golang.org/x/tools/go/ssa"
|
||||
|
||||
// This file provides various utilities over call graphs, such as
|
||||
// visitation and path search.
|
||||
|
||||
// CalleesOf returns a new set containing all direct callees of the
|
||||
// caller node.
|
||||
func CalleesOf(caller *Node) map[*Node]bool {
|
||||
callees := make(map[*Node]bool)
|
||||
for _, e := range caller.Out {
|
||||
callees[e.Callee] = true
|
||||
}
|
||||
return callees
|
||||
}
|
||||
|
||||
// GraphVisitEdges visits all the edges in graph g in depth-first order.
|
||||
// The edge function is called for each edge in postorder. If it
|
||||
// returns non-nil, visitation stops and GraphVisitEdges returns that
|
||||
// value.
|
||||
func GraphVisitEdges(g *Graph, edge func(*Edge) error) error {
|
||||
seen := make(map[*Node]bool)
|
||||
var visit func(n *Node) error
|
||||
visit = func(n *Node) error {
|
||||
if !seen[n] {
|
||||
seen[n] = true
|
||||
for _, e := range n.Out {
|
||||
if err := visit(e.Callee); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := edge(e); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
for _, n := range g.Nodes {
|
||||
if err := visit(n); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PathSearch finds an arbitrary path starting at node start and
|
||||
// ending at some node for which isEnd() returns true. On success,
|
||||
// PathSearch returns the path as an ordered list of edges; on
|
||||
// failure, it returns nil.
|
||||
func PathSearch(start *Node, isEnd func(*Node) bool) []*Edge {
|
||||
stack := make([]*Edge, 0, 32)
|
||||
seen := make(map[*Node]bool)
|
||||
var search func(n *Node) []*Edge
|
||||
search = func(n *Node) []*Edge {
|
||||
if !seen[n] {
|
||||
seen[n] = true
|
||||
if isEnd(n) {
|
||||
return stack
|
||||
}
|
||||
for _, e := range n.Out {
|
||||
stack = append(stack, e) // push
|
||||
if found := search(e.Callee); found != nil {
|
||||
return found
|
||||
}
|
||||
stack = stack[:len(stack)-1] // pop
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return search(start)
|
||||
}
|
||||
|
||||
// DeleteSyntheticNodes removes from call graph g all nodes for
|
||||
// functions that do not correspond to source syntax. For historical
|
||||
// reasons, nodes for g.Root and package initializers are always
|
||||
// kept.
|
||||
//
|
||||
// As nodes are removed, edges are created to preserve the
|
||||
// reachability relation of the remaining nodes.
|
||||
func (g *Graph) DeleteSyntheticNodes() {
|
||||
// Measurements on the standard library and go.tools show that
|
||||
// resulting graph has ~15% fewer nodes and 4-8% fewer edges
|
||||
// than the input.
|
||||
//
|
||||
// Inlining a wrapper of in-degree m, out-degree n adds m*n
|
||||
// and removes m+n edges. Since most wrappers are monomorphic
|
||||
// (n=1) this results in a slight reduction. Polymorphic
|
||||
// wrappers (n>1), e.g. from embedding an interface value
|
||||
// inside a struct to satisfy some interface, cause an
|
||||
// increase in the graph, but they seem to be uncommon.
|
||||
|
||||
// Hash all existing edges to avoid creating duplicates.
|
||||
edges := make(map[Edge]bool)
|
||||
for _, cgn := range g.Nodes {
|
||||
for _, e := range cgn.Out {
|
||||
edges[*e] = true
|
||||
}
|
||||
}
|
||||
for fn, cgn := range g.Nodes {
|
||||
if cgn == g.Root || isInit(cgn.Func) || fn.Syntax() != nil {
|
||||
continue // keep
|
||||
}
|
||||
for _, eIn := range cgn.In {
|
||||
for _, eOut := range cgn.Out {
|
||||
newEdge := Edge{eIn.Caller, eIn.Site, eOut.Callee}
|
||||
if edges[newEdge] {
|
||||
continue // don't add duplicate
|
||||
}
|
||||
AddEdge(eIn.Caller, eIn.Site, eOut.Callee)
|
||||
edges[newEdge] = true
|
||||
}
|
||||
}
|
||||
g.DeleteNode(cgn)
|
||||
}
|
||||
}
|
||||
|
||||
func isInit(fn *ssa.Function) bool {
|
||||
return fn.Pkg != nil && fn.Pkg.Func("init") == fn
|
||||
}
|
||||
|
||||
// DeleteNode removes node n and its edges from the graph g.
|
||||
// (NB: not efficient for batch deletion.)
|
||||
func (g *Graph) DeleteNode(n *Node) {
|
||||
n.deleteIns()
|
||||
n.deleteOuts()
|
||||
delete(g.Nodes, n.Func)
|
||||
}
|
||||
|
||||
// deleteIns deletes all incoming edges to n.
|
||||
func (n *Node) deleteIns() {
|
||||
for _, e := range n.In {
|
||||
removeOutEdge(e)
|
||||
}
|
||||
n.In = nil
|
||||
}
|
||||
|
||||
// deleteOuts deletes all outgoing edges from n.
|
||||
func (n *Node) deleteOuts() {
|
||||
for _, e := range n.Out {
|
||||
removeInEdge(e)
|
||||
}
|
||||
n.Out = nil
|
||||
}
|
||||
|
||||
// removeOutEdge removes edge.Caller's outgoing edge 'edge'.
|
||||
func removeOutEdge(edge *Edge) {
|
||||
caller := edge.Caller
|
||||
n := len(caller.Out)
|
||||
for i, e := range caller.Out {
|
||||
if e == edge {
|
||||
// Replace it with the final element and shrink the slice.
|
||||
caller.Out[i] = caller.Out[n-1]
|
||||
caller.Out[n-1] = nil // aid GC
|
||||
caller.Out = caller.Out[:n-1]
|
||||
return
|
||||
}
|
||||
}
|
||||
panic("edge not found: " + edge.String())
|
||||
}
|
||||
|
||||
// removeInEdge removes edge.Callee's incoming edge 'edge'.
|
||||
func removeInEdge(edge *Edge) {
|
||||
caller := edge.Callee
|
||||
n := len(caller.In)
|
||||
for i, e := range caller.In {
|
||||
if e == edge {
|
||||
// Replace it with the final element and shrink the slice.
|
||||
caller.In[i] = caller.In[n-1]
|
||||
caller.In[n-1] = nil // aid GC
|
||||
caller.In = caller.In[:n-1]
|
||||
return
|
||||
}
|
||||
}
|
||||
panic("edge not found: " + edge.String())
|
||||
}
|
||||
861
vendor/golang.org/x/tools/go/callgraph/vta/graph.go
generated
vendored
Normal file
861
vendor/golang.org/x/tools/go/callgraph/vta/graph.go
generated
vendored
Normal file
@@ -0,0 +1,861 @@
|
||||
// Copyright 2021 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package vta
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"iter"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/types/typeutil"
|
||||
"golang.org/x/tools/internal/typeparams"
|
||||
)
|
||||
|
||||
// node interface for VTA nodes.
|
||||
type node interface {
|
||||
Type() types.Type
|
||||
String() string
|
||||
}
|
||||
|
||||
// constant node for VTA.
|
||||
type constant struct {
|
||||
typ types.Type
|
||||
}
|
||||
|
||||
func (c constant) Type() types.Type {
|
||||
return c.typ
|
||||
}
|
||||
|
||||
func (c constant) String() string {
|
||||
return fmt.Sprintf("Constant(%v)", c.Type())
|
||||
}
|
||||
|
||||
// pointer node for VTA.
|
||||
type pointer struct {
|
||||
typ *types.Pointer
|
||||
}
|
||||
|
||||
func (p pointer) Type() types.Type {
|
||||
return p.typ
|
||||
}
|
||||
|
||||
func (p pointer) String() string {
|
||||
return fmt.Sprintf("Pointer(%v)", p.Type())
|
||||
}
|
||||
|
||||
// mapKey node for VTA, modeling reachable map key types.
|
||||
type mapKey struct {
|
||||
typ types.Type
|
||||
}
|
||||
|
||||
func (mk mapKey) Type() types.Type {
|
||||
return mk.typ
|
||||
}
|
||||
|
||||
func (mk mapKey) String() string {
|
||||
return fmt.Sprintf("MapKey(%v)", mk.Type())
|
||||
}
|
||||
|
||||
// mapValue node for VTA, modeling reachable map value types.
|
||||
type mapValue struct {
|
||||
typ types.Type
|
||||
}
|
||||
|
||||
func (mv mapValue) Type() types.Type {
|
||||
return mv.typ
|
||||
}
|
||||
|
||||
func (mv mapValue) String() string {
|
||||
return fmt.Sprintf("MapValue(%v)", mv.Type())
|
||||
}
|
||||
|
||||
// sliceElem node for VTA, modeling reachable slice and array element types.
|
||||
type sliceElem struct {
|
||||
typ types.Type
|
||||
}
|
||||
|
||||
func (s sliceElem) Type() types.Type {
|
||||
return s.typ
|
||||
}
|
||||
|
||||
func (s sliceElem) String() string {
|
||||
return fmt.Sprintf("Slice([]%v)", s.Type())
|
||||
}
|
||||
|
||||
// channelElem node for VTA, modeling reachable channel element types.
|
||||
type channelElem struct {
|
||||
typ types.Type
|
||||
}
|
||||
|
||||
func (c channelElem) Type() types.Type {
|
||||
return c.typ
|
||||
}
|
||||
|
||||
func (c channelElem) String() string {
|
||||
return fmt.Sprintf("Channel(chan %v)", c.Type())
|
||||
}
|
||||
|
||||
// field node for VTA.
|
||||
type field struct {
|
||||
StructType types.Type
|
||||
index int // index of the field in the struct
|
||||
}
|
||||
|
||||
func (f field) Type() types.Type {
|
||||
s := typeparams.CoreType(f.StructType).(*types.Struct)
|
||||
return s.Field(f.index).Type()
|
||||
}
|
||||
|
||||
func (f field) String() string {
|
||||
s := typeparams.CoreType(f.StructType).(*types.Struct)
|
||||
return fmt.Sprintf("Field(%v:%s)", f.StructType, s.Field(f.index).Name())
|
||||
}
|
||||
|
||||
// global node for VTA.
|
||||
type global struct {
|
||||
val *ssa.Global
|
||||
}
|
||||
|
||||
func (g global) Type() types.Type {
|
||||
return g.val.Type()
|
||||
}
|
||||
|
||||
func (g global) String() string {
|
||||
return fmt.Sprintf("Global(%s)", g.val.Name())
|
||||
}
|
||||
|
||||
// local node for VTA modeling local variables
|
||||
// and function/method parameters.
|
||||
type local struct {
|
||||
val ssa.Value
|
||||
}
|
||||
|
||||
func (l local) Type() types.Type {
|
||||
return l.val.Type()
|
||||
}
|
||||
|
||||
func (l local) String() string {
|
||||
return fmt.Sprintf("Local(%s)", l.val.Name())
|
||||
}
|
||||
|
||||
// indexedLocal node for VTA node. Models indexed locals
|
||||
// related to the ssa extract instructions.
|
||||
type indexedLocal struct {
|
||||
val ssa.Value
|
||||
index int
|
||||
typ types.Type
|
||||
}
|
||||
|
||||
func (i indexedLocal) Type() types.Type {
|
||||
return i.typ
|
||||
}
|
||||
|
||||
func (i indexedLocal) String() string {
|
||||
return fmt.Sprintf("Local(%s[%d])", i.val.Name(), i.index)
|
||||
}
|
||||
|
||||
// function node for VTA.
|
||||
type function struct {
|
||||
f *ssa.Function
|
||||
}
|
||||
|
||||
func (f function) Type() types.Type {
|
||||
return f.f.Type()
|
||||
}
|
||||
|
||||
func (f function) String() string {
|
||||
return fmt.Sprintf("Function(%s)", f.f.Name())
|
||||
}
|
||||
|
||||
// resultVar represents the result
|
||||
// variable of a function, whether
|
||||
// named or not.
|
||||
type resultVar struct {
|
||||
f *ssa.Function
|
||||
index int // valid index into result var tuple
|
||||
}
|
||||
|
||||
func (o resultVar) Type() types.Type {
|
||||
return o.f.Signature.Results().At(o.index).Type()
|
||||
}
|
||||
|
||||
func (o resultVar) String() string {
|
||||
v := o.f.Signature.Results().At(o.index)
|
||||
if n := v.Name(); n != "" {
|
||||
return fmt.Sprintf("Return(%s[%s])", o.f.Name(), n)
|
||||
}
|
||||
return fmt.Sprintf("Return(%s[%d])", o.f.Name(), o.index)
|
||||
}
|
||||
|
||||
// nestedPtrInterface node represents all references and dereferences
|
||||
// of locals and globals that have a nested pointer to interface type.
|
||||
// We merge such constructs into a single node for simplicity and without
|
||||
// much precision sacrifice as such variables are rare in practice. Both
|
||||
// a and b would be represented as the same PtrInterface(I) node in:
|
||||
//
|
||||
// type I interface
|
||||
// var a ***I
|
||||
// var b **I
|
||||
type nestedPtrInterface struct {
|
||||
typ types.Type
|
||||
}
|
||||
|
||||
func (l nestedPtrInterface) Type() types.Type {
|
||||
return l.typ
|
||||
}
|
||||
|
||||
func (l nestedPtrInterface) String() string {
|
||||
return fmt.Sprintf("PtrInterface(%v)", l.typ)
|
||||
}
|
||||
|
||||
// nestedPtrFunction node represents all references and dereferences of locals
|
||||
// and globals that have a nested pointer to function type. We merge such
|
||||
// constructs into a single node for simplicity and without much precision
|
||||
// sacrifice as such variables are rare in practice. Both a and b would be
|
||||
// represented as the same PtrFunction(func()) node in:
|
||||
//
|
||||
// var a *func()
|
||||
// var b **func()
|
||||
type nestedPtrFunction struct {
|
||||
typ types.Type
|
||||
}
|
||||
|
||||
func (p nestedPtrFunction) Type() types.Type {
|
||||
return p.typ
|
||||
}
|
||||
|
||||
func (p nestedPtrFunction) String() string {
|
||||
return fmt.Sprintf("PtrFunction(%v)", p.typ)
|
||||
}
|
||||
|
||||
// panicArg models types of all arguments passed to panic.
|
||||
type panicArg struct{}
|
||||
|
||||
func (p panicArg) Type() types.Type {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p panicArg) String() string {
|
||||
return "Panic"
|
||||
}
|
||||
|
||||
// recoverReturn models types of all return values of recover().
|
||||
type recoverReturn struct{}
|
||||
|
||||
func (r recoverReturn) Type() types.Type {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r recoverReturn) String() string {
|
||||
return "Recover"
|
||||
}
|
||||
|
||||
type empty = struct{}
|
||||
|
||||
// idx is an index representing a unique node in a vtaGraph.
|
||||
type idx int
|
||||
|
||||
// vtaGraph remembers for each VTA node the set of its successors.
|
||||
// Tailored for VTA, hence does not support singleton (sub)graphs.
|
||||
type vtaGraph struct {
|
||||
m []map[idx]empty // m[i] has the successors for the node with index i.
|
||||
idx map[node]idx // idx[n] is the index for the node n.
|
||||
node []node // node[i] is the node with index i.
|
||||
}
|
||||
|
||||
func (g *vtaGraph) numNodes() int {
|
||||
return len(g.idx)
|
||||
}
|
||||
|
||||
func (g *vtaGraph) successors(x idx) iter.Seq[idx] {
|
||||
return func(yield func(y idx) bool) {
|
||||
for y := range g.m[x] {
|
||||
if !yield(y) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// addEdge adds an edge x->y to the graph.
|
||||
func (g *vtaGraph) addEdge(x, y node) {
|
||||
if g.idx == nil {
|
||||
g.idx = make(map[node]idx)
|
||||
}
|
||||
lookup := func(n node) idx {
|
||||
i, ok := g.idx[n]
|
||||
if !ok {
|
||||
i = idx(len(g.idx))
|
||||
g.m = append(g.m, nil)
|
||||
g.idx[n] = i
|
||||
g.node = append(g.node, n)
|
||||
}
|
||||
return i
|
||||
}
|
||||
a := lookup(x)
|
||||
b := lookup(y)
|
||||
succs := g.m[a]
|
||||
if succs == nil {
|
||||
succs = make(map[idx]empty)
|
||||
g.m[a] = succs
|
||||
}
|
||||
succs[b] = empty{}
|
||||
}
|
||||
|
||||
// typePropGraph builds a VTA graph for a set of `funcs` and initial
|
||||
// `callgraph` needed to establish interprocedural edges. Returns the
|
||||
// graph and a map for unique type representatives.
|
||||
func typePropGraph(funcs map[*ssa.Function]bool, callees calleesFunc) (*vtaGraph, *typeutil.Map) {
|
||||
b := builder{callees: callees}
|
||||
b.visit(funcs)
|
||||
b.callees = nil // ensure callees is not pinned by pointers to other fields of b.
|
||||
return &b.graph, &b.canon
|
||||
}
|
||||
|
||||
// Data structure responsible for linearly traversing the
|
||||
// code and building a VTA graph.
|
||||
type builder struct {
|
||||
graph vtaGraph
|
||||
callees calleesFunc // initial call graph for creating flows at unresolved call sites.
|
||||
|
||||
// Specialized type map for canonicalization of types.Type.
|
||||
// Semantically equivalent types can have different implementations,
|
||||
// i.e., they are different pointer values. The map allows us to
|
||||
// have one unique representative. The keys are fixed and from the
|
||||
// client perspective they are types. The values in our case are
|
||||
// types too, in particular type representatives. Each value is a
|
||||
// pointer so this map is not expected to take much memory.
|
||||
canon typeutil.Map
|
||||
}
|
||||
|
||||
func (b *builder) visit(funcs map[*ssa.Function]bool) {
|
||||
// Add the fixed edge Panic -> Recover
|
||||
b.graph.addEdge(panicArg{}, recoverReturn{})
|
||||
|
||||
for f, in := range funcs {
|
||||
if in {
|
||||
b.fun(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *builder) fun(f *ssa.Function) {
|
||||
for _, bl := range f.Blocks {
|
||||
for _, instr := range bl.Instrs {
|
||||
b.instr(instr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *builder) instr(instr ssa.Instruction) {
|
||||
switch i := instr.(type) {
|
||||
case *ssa.Store:
|
||||
b.addInFlowAliasEdges(b.nodeFromVal(i.Addr), b.nodeFromVal(i.Val))
|
||||
case *ssa.MakeInterface:
|
||||
b.addInFlowEdge(b.nodeFromVal(i.X), b.nodeFromVal(i))
|
||||
case *ssa.MakeClosure:
|
||||
b.closure(i)
|
||||
case *ssa.UnOp:
|
||||
b.unop(i)
|
||||
case *ssa.Phi:
|
||||
b.phi(i)
|
||||
case *ssa.ChangeInterface:
|
||||
// Although in change interface a := A(b) command a and b are
|
||||
// the same object, the only interesting flow happens when A
|
||||
// is an interface. We create flow b -> a, but omit a -> b.
|
||||
// The latter flow is not needed: if a gets assigned concrete
|
||||
// type later on, that cannot be propagated back to b as b
|
||||
// is a separate variable. The a -> b flow can happen when
|
||||
// A is a pointer to interface, but then the command is of
|
||||
// type ChangeType, handled below.
|
||||
b.addInFlowEdge(b.nodeFromVal(i.X), b.nodeFromVal(i))
|
||||
case *ssa.ChangeType:
|
||||
// change type command a := A(b) results in a and b being the
|
||||
// same value. For concrete type A, there is no interesting flow.
|
||||
//
|
||||
// When A is an interface, most interface casts are handled
|
||||
// by the ChangeInterface instruction. The relevant case here is
|
||||
// when converting a pointer to an interface type. This can happen
|
||||
// when the underlying interfaces have the same method set.
|
||||
//
|
||||
// type I interface{ foo() }
|
||||
// type J interface{ foo() }
|
||||
// var b *I
|
||||
// a := (*J)(b)
|
||||
//
|
||||
// When this happens we add flows between a <--> b.
|
||||
b.addInFlowAliasEdges(b.nodeFromVal(i), b.nodeFromVal(i.X))
|
||||
case *ssa.TypeAssert:
|
||||
b.tassert(i)
|
||||
case *ssa.Extract:
|
||||
b.extract(i)
|
||||
case *ssa.Field:
|
||||
b.field(i)
|
||||
case *ssa.FieldAddr:
|
||||
b.fieldAddr(i)
|
||||
case *ssa.Send:
|
||||
b.send(i)
|
||||
case *ssa.Select:
|
||||
b.selekt(i)
|
||||
case *ssa.Index:
|
||||
b.index(i)
|
||||
case *ssa.IndexAddr:
|
||||
b.indexAddr(i)
|
||||
case *ssa.Lookup:
|
||||
b.lookup(i)
|
||||
case *ssa.MapUpdate:
|
||||
b.mapUpdate(i)
|
||||
case *ssa.Next:
|
||||
b.next(i)
|
||||
case ssa.CallInstruction:
|
||||
b.call(i)
|
||||
case *ssa.Panic:
|
||||
b.panic(i)
|
||||
case *ssa.Return:
|
||||
b.rtrn(i)
|
||||
case *ssa.MakeChan, *ssa.MakeMap, *ssa.MakeSlice, *ssa.BinOp,
|
||||
*ssa.Alloc, *ssa.DebugRef, *ssa.Convert, *ssa.Jump, *ssa.If,
|
||||
*ssa.Slice, *ssa.SliceToArrayPointer, *ssa.Range, *ssa.RunDefers:
|
||||
// No interesting flow here.
|
||||
// Notes on individual instructions:
|
||||
// SliceToArrayPointer: t1 = slice to array pointer *[4]T <- []T (t0)
|
||||
// No interesting flow as sliceArrayElem(t1) == sliceArrayElem(t0).
|
||||
return
|
||||
case *ssa.MultiConvert:
|
||||
b.multiconvert(i)
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported instruction %v\n", instr))
|
||||
}
|
||||
}
|
||||
|
||||
func (b *builder) unop(u *ssa.UnOp) {
|
||||
switch u.Op {
|
||||
case token.MUL:
|
||||
// Multiplication operator * is used here as a dereference operator.
|
||||
b.addInFlowAliasEdges(b.nodeFromVal(u), b.nodeFromVal(u.X))
|
||||
case token.ARROW:
|
||||
t := typeparams.CoreType(u.X.Type()).(*types.Chan).Elem()
|
||||
b.addInFlowAliasEdges(b.nodeFromVal(u), channelElem{typ: t})
|
||||
default:
|
||||
// There is no interesting type flow otherwise.
|
||||
}
|
||||
}
|
||||
|
||||
func (b *builder) phi(p *ssa.Phi) {
|
||||
for _, edge := range p.Edges {
|
||||
b.addInFlowAliasEdges(b.nodeFromVal(p), b.nodeFromVal(edge))
|
||||
}
|
||||
}
|
||||
|
||||
func (b *builder) tassert(a *ssa.TypeAssert) {
|
||||
if !a.CommaOk {
|
||||
b.addInFlowEdge(b.nodeFromVal(a.X), b.nodeFromVal(a))
|
||||
return
|
||||
}
|
||||
// The case where a is <a.AssertedType, bool> register so there
|
||||
// is a flow from a.X to a[0]. Here, a[0] is represented as an
|
||||
// indexedLocal: an entry into local tuple register a at index 0.
|
||||
tup := a.Type().(*types.Tuple)
|
||||
t := tup.At(0).Type()
|
||||
|
||||
local := indexedLocal{val: a, typ: t, index: 0}
|
||||
b.addInFlowEdge(b.nodeFromVal(a.X), local)
|
||||
}
|
||||
|
||||
// extract instruction t1 := t2[i] generates flows between t2[i]
|
||||
// and t1 where the source is indexed local representing a value
|
||||
// from tuple register t2 at index i and the target is t1.
|
||||
func (b *builder) extract(e *ssa.Extract) {
|
||||
tup := e.Tuple.Type().(*types.Tuple)
|
||||
t := tup.At(e.Index).Type()
|
||||
|
||||
local := indexedLocal{val: e.Tuple, typ: t, index: e.Index}
|
||||
b.addInFlowAliasEdges(b.nodeFromVal(e), local)
|
||||
}
|
||||
|
||||
func (b *builder) field(f *ssa.Field) {
|
||||
fnode := field{StructType: f.X.Type(), index: f.Field}
|
||||
b.addInFlowEdge(fnode, b.nodeFromVal(f))
|
||||
}
|
||||
|
||||
func (b *builder) fieldAddr(f *ssa.FieldAddr) {
|
||||
t := typeparams.CoreType(f.X.Type()).(*types.Pointer).Elem()
|
||||
|
||||
// Since we are getting pointer to a field, make a bidirectional edge.
|
||||
fnode := field{StructType: t, index: f.Field}
|
||||
b.addInFlowEdge(fnode, b.nodeFromVal(f))
|
||||
b.addInFlowEdge(b.nodeFromVal(f), fnode)
|
||||
}
|
||||
|
||||
func (b *builder) send(s *ssa.Send) {
|
||||
t := typeparams.CoreType(s.Chan.Type()).(*types.Chan).Elem()
|
||||
b.addInFlowAliasEdges(channelElem{typ: t}, b.nodeFromVal(s.X))
|
||||
}
|
||||
|
||||
// selekt generates flows for select statement
|
||||
//
|
||||
// a = select blocking/nonblocking [c_1 <- t_1, c_2 <- t_2, ..., <- o_1, <- o_2, ...]
|
||||
//
|
||||
// between receiving channel registers c_i and corresponding input register t_i. Further,
|
||||
// flows are generated between o_i and a[2 + i]. Note that a is a tuple register of type
|
||||
// <int, bool, r_1, r_2, ...> where the type of r_i is the element type of channel o_i.
|
||||
func (b *builder) selekt(s *ssa.Select) {
|
||||
recvIndex := 0
|
||||
for _, state := range s.States {
|
||||
t := typeparams.CoreType(state.Chan.Type()).(*types.Chan).Elem()
|
||||
|
||||
if state.Dir == types.SendOnly {
|
||||
b.addInFlowAliasEdges(channelElem{typ: t}, b.nodeFromVal(state.Send))
|
||||
} else {
|
||||
// state.Dir == RecvOnly by definition of select instructions.
|
||||
tupEntry := indexedLocal{val: s, typ: t, index: 2 + recvIndex}
|
||||
b.addInFlowAliasEdges(tupEntry, channelElem{typ: t})
|
||||
recvIndex++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// index instruction a := b[c] on slices creates flows between a and
|
||||
// SliceElem(t) flow where t is an interface type of c. Arrays and
|
||||
// slice elements are both modeled as SliceElem.
|
||||
func (b *builder) index(i *ssa.Index) {
|
||||
et := sliceArrayElem(i.X.Type())
|
||||
b.addInFlowAliasEdges(b.nodeFromVal(i), sliceElem{typ: et})
|
||||
}
|
||||
|
||||
// indexAddr instruction a := &b[c] fetches address of a index
|
||||
// into the field so we create bidirectional flow a <-> SliceElem(t)
|
||||
// where t is an interface type of c. Arrays and slice elements are
|
||||
// both modeled as SliceElem.
|
||||
func (b *builder) indexAddr(i *ssa.IndexAddr) {
|
||||
et := sliceArrayElem(i.X.Type())
|
||||
b.addInFlowEdge(sliceElem{typ: et}, b.nodeFromVal(i))
|
||||
b.addInFlowEdge(b.nodeFromVal(i), sliceElem{typ: et})
|
||||
}
|
||||
|
||||
// lookup handles map query commands a := m[b] where m is of type
|
||||
// map[...]V and V is an interface. It creates flows between `a`
|
||||
// and MapValue(V).
|
||||
func (b *builder) lookup(l *ssa.Lookup) {
|
||||
t, ok := l.X.Type().Underlying().(*types.Map)
|
||||
if !ok {
|
||||
// No interesting flows for string lookups.
|
||||
return
|
||||
}
|
||||
|
||||
if !l.CommaOk {
|
||||
b.addInFlowAliasEdges(b.nodeFromVal(l), mapValue{typ: t.Elem()})
|
||||
} else {
|
||||
i := indexedLocal{val: l, typ: t.Elem(), index: 0}
|
||||
b.addInFlowAliasEdges(i, mapValue{typ: t.Elem()})
|
||||
}
|
||||
}
|
||||
|
||||
// mapUpdate handles map update commands m[b] = a where m is of type
|
||||
// map[K]V and K and V are interfaces. It creates flows between `a`
|
||||
// and MapValue(V) as well as between MapKey(K) and `b`.
|
||||
func (b *builder) mapUpdate(u *ssa.MapUpdate) {
|
||||
t, ok := u.Map.Type().Underlying().(*types.Map)
|
||||
if !ok {
|
||||
// No interesting flows for string updates.
|
||||
return
|
||||
}
|
||||
|
||||
b.addInFlowAliasEdges(mapKey{typ: t.Key()}, b.nodeFromVal(u.Key))
|
||||
b.addInFlowAliasEdges(mapValue{typ: t.Elem()}, b.nodeFromVal(u.Value))
|
||||
}
|
||||
|
||||
// next instruction <ok, key, value> := next r, where r
|
||||
// is a range over map or string generates flow between
|
||||
// key and MapKey as well value and MapValue nodes.
|
||||
func (b *builder) next(n *ssa.Next) {
|
||||
if n.IsString {
|
||||
return
|
||||
}
|
||||
tup := n.Type().(*types.Tuple)
|
||||
kt := tup.At(1).Type()
|
||||
vt := tup.At(2).Type()
|
||||
|
||||
b.addInFlowAliasEdges(indexedLocal{val: n, typ: kt, index: 1}, mapKey{typ: kt})
|
||||
b.addInFlowAliasEdges(indexedLocal{val: n, typ: vt, index: 2}, mapValue{typ: vt})
|
||||
}
|
||||
|
||||
// addInFlowAliasEdges adds an edge r -> l to b.graph if l is a node that can
|
||||
// have an inflow, i.e., a node that represents an interface or an unresolved
|
||||
// function value. Similarly for the edge l -> r with an additional condition
|
||||
// of that l and r can potentially alias.
|
||||
func (b *builder) addInFlowAliasEdges(l, r node) {
|
||||
b.addInFlowEdge(r, l)
|
||||
|
||||
if canAlias(l, r) {
|
||||
b.addInFlowEdge(l, r)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *builder) closure(c *ssa.MakeClosure) {
|
||||
f := c.Fn.(*ssa.Function)
|
||||
b.addInFlowEdge(function{f: f}, b.nodeFromVal(c))
|
||||
|
||||
for i, fv := range f.FreeVars {
|
||||
b.addInFlowAliasEdges(b.nodeFromVal(fv), b.nodeFromVal(c.Bindings[i]))
|
||||
}
|
||||
}
|
||||
|
||||
// panic creates a flow from arguments to panic instructions to return
|
||||
// registers of all recover statements in the program. Introduces a
|
||||
// global panic node Panic and
|
||||
// 1. for every panic statement p: add p -> Panic
|
||||
// 2. for every recover statement r: add Panic -> r (handled in call)
|
||||
//
|
||||
// TODO(zpavlinovic): improve precision by explicitly modeling how panic
|
||||
// values flow from callees to callers and into deferred recover instructions.
|
||||
func (b *builder) panic(p *ssa.Panic) {
|
||||
// Panics often have, for instance, strings as arguments which do
|
||||
// not create interesting flows.
|
||||
if !canHaveMethods(p.X.Type()) {
|
||||
return
|
||||
}
|
||||
|
||||
b.addInFlowEdge(b.nodeFromVal(p.X), panicArg{})
|
||||
}
|
||||
|
||||
// call adds flows between arguments/parameters and return values/registers
|
||||
// for both static and dynamic calls, as well as go and defer calls.
|
||||
func (b *builder) call(c ssa.CallInstruction) {
|
||||
// When c is r := recover() call register instruction, we add Recover -> r.
|
||||
if bf, ok := c.Common().Value.(*ssa.Builtin); ok && bf.Name() == "recover" {
|
||||
if v, ok := c.(ssa.Value); ok {
|
||||
b.addInFlowEdge(recoverReturn{}, b.nodeFromVal(v))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
for f := range siteCallees(c, b.callees) {
|
||||
addArgumentFlows(b, c, f)
|
||||
|
||||
site, ok := c.(ssa.Value)
|
||||
if !ok {
|
||||
continue // go or defer
|
||||
}
|
||||
|
||||
results := f.Signature.Results()
|
||||
if results.Len() == 1 {
|
||||
// When there is only one return value, the destination register does not
|
||||
// have a tuple type.
|
||||
b.addInFlowEdge(resultVar{f: f, index: 0}, b.nodeFromVal(site))
|
||||
} else {
|
||||
tup := site.Type().(*types.Tuple)
|
||||
for i := 0; i < results.Len(); i++ {
|
||||
local := indexedLocal{val: site, typ: tup.At(i).Type(), index: i}
|
||||
b.addInFlowEdge(resultVar{f: f, index: i}, local)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func addArgumentFlows(b *builder, c ssa.CallInstruction, f *ssa.Function) {
|
||||
// When f has no parameters (including receiver), there is no type
|
||||
// flow here. Also, f's body and parameters might be missing, such
|
||||
// as when vta is used within the golang.org/x/tools/go/analysis
|
||||
// framework (see github.com/golang/go/issues/50670).
|
||||
if len(f.Params) == 0 {
|
||||
return
|
||||
}
|
||||
cc := c.Common()
|
||||
if cc.Method != nil {
|
||||
// In principle we don't add interprocedural flows for receiver
|
||||
// objects. At a call site, the receiver object is interface
|
||||
// while the callee object is concrete. The flow from interface
|
||||
// to concrete type in general does not make sense. The exception
|
||||
// is when the concrete type is a named function type (see #57756).
|
||||
//
|
||||
// The flow other way around would bake in information from the
|
||||
// initial call graph.
|
||||
if isFunction(f.Params[0].Type()) {
|
||||
b.addInFlowEdge(b.nodeFromVal(cc.Value), b.nodeFromVal(f.Params[0]))
|
||||
}
|
||||
}
|
||||
|
||||
offset := 0
|
||||
if cc.Method != nil {
|
||||
offset = 1
|
||||
}
|
||||
for i, v := range cc.Args {
|
||||
// Parameters of f might not be available, as in the case
|
||||
// when vta is used within the golang.org/x/tools/go/analysis
|
||||
// framework (see github.com/golang/go/issues/50670).
|
||||
//
|
||||
// TODO: investigate other cases of missing body and parameters
|
||||
if len(f.Params) <= i+offset {
|
||||
return
|
||||
}
|
||||
b.addInFlowAliasEdges(b.nodeFromVal(f.Params[i+offset]), b.nodeFromVal(v))
|
||||
}
|
||||
}
|
||||
|
||||
// rtrn creates flow edges from the operands of the return
|
||||
// statement to the result variables of the enclosing function.
|
||||
func (b *builder) rtrn(r *ssa.Return) {
|
||||
for i, rs := range r.Results {
|
||||
b.addInFlowEdge(b.nodeFromVal(rs), resultVar{f: r.Parent(), index: i})
|
||||
}
|
||||
}
|
||||
|
||||
func (b *builder) multiconvert(c *ssa.MultiConvert) {
|
||||
// TODO(zpavlinovic): decide what to do on MultiConvert long term.
|
||||
// TODO(zpavlinovic): add unit tests.
|
||||
typeSetOf := func(typ types.Type) []*types.Term {
|
||||
// This is a adaptation of x/exp/typeparams.NormalTerms which x/tools cannot depend on.
|
||||
var terms []*types.Term
|
||||
var err error
|
||||
switch typ := types.Unalias(typ).(type) {
|
||||
case *types.TypeParam:
|
||||
terms, err = typeparams.StructuralTerms(typ)
|
||||
case *types.Union:
|
||||
terms, err = typeparams.UnionTermSet(typ)
|
||||
case *types.Interface:
|
||||
terms, err = typeparams.InterfaceTermSet(typ)
|
||||
default:
|
||||
// Common case.
|
||||
// Specializing the len=1 case to avoid a slice
|
||||
// had no measurable space/time benefit.
|
||||
terms = []*types.Term{types.NewTerm(false, typ)}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return terms
|
||||
}
|
||||
// isValuePreserving returns true if a conversion from ut_src to
|
||||
// ut_dst is value-preserving, i.e. just a change of type.
|
||||
// Precondition: neither argument is a named or alias type.
|
||||
isValuePreserving := func(ut_src, ut_dst types.Type) bool {
|
||||
// Identical underlying types?
|
||||
if types.IdenticalIgnoreTags(ut_dst, ut_src) {
|
||||
return true
|
||||
}
|
||||
|
||||
switch ut_dst.(type) {
|
||||
case *types.Chan:
|
||||
// Conversion between channel types?
|
||||
_, ok := ut_src.(*types.Chan)
|
||||
return ok
|
||||
|
||||
case *types.Pointer:
|
||||
// Conversion between pointers with identical base types?
|
||||
_, ok := ut_src.(*types.Pointer)
|
||||
return ok
|
||||
}
|
||||
return false
|
||||
}
|
||||
dst_terms := typeSetOf(c.Type())
|
||||
src_terms := typeSetOf(c.X.Type())
|
||||
for _, s := range src_terms {
|
||||
us := s.Type().Underlying()
|
||||
for _, d := range dst_terms {
|
||||
ud := d.Type().Underlying()
|
||||
if isValuePreserving(us, ud) {
|
||||
// This is equivalent to a ChangeType.
|
||||
b.addInFlowAliasEdges(b.nodeFromVal(c), b.nodeFromVal(c.X))
|
||||
return
|
||||
}
|
||||
// This is equivalent to either: SliceToArrayPointer,,
|
||||
// SliceToArrayPointer+Deref, Size 0 Array constant, or a Convert.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// addInFlowEdge adds s -> d to g if d is node that can have an inflow, i.e., a node
|
||||
// that represents an interface or an unresolved function value. Otherwise, there
|
||||
// is no interesting type flow so the edge is omitted.
|
||||
func (b *builder) addInFlowEdge(s, d node) {
|
||||
if hasInFlow(d) {
|
||||
b.graph.addEdge(b.representative(s), b.representative(d))
|
||||
}
|
||||
}
|
||||
|
||||
// Creates const, pointer, global, func, and local nodes based on register instructions.
|
||||
func (b *builder) nodeFromVal(val ssa.Value) node {
|
||||
if p, ok := types.Unalias(val.Type()).(*types.Pointer); ok && !types.IsInterface(p.Elem()) && !isFunction(p.Elem()) {
|
||||
// Nested pointer to interfaces are modeled as a special
|
||||
// nestedPtrInterface node.
|
||||
if i := interfaceUnderPtr(p.Elem()); i != nil {
|
||||
return nestedPtrInterface{typ: i}
|
||||
}
|
||||
// The same goes for nested function types.
|
||||
if f := functionUnderPtr(p.Elem()); f != nil {
|
||||
return nestedPtrFunction{typ: f}
|
||||
}
|
||||
return pointer{typ: p}
|
||||
}
|
||||
|
||||
switch v := val.(type) {
|
||||
case *ssa.Const:
|
||||
return constant{typ: val.Type()}
|
||||
case *ssa.Global:
|
||||
return global{val: v}
|
||||
case *ssa.Function:
|
||||
return function{f: v}
|
||||
case *ssa.Parameter, *ssa.FreeVar, ssa.Instruction:
|
||||
// ssa.Param, ssa.FreeVar, and a specific set of "register" instructions,
|
||||
// satisfying the ssa.Value interface, can serve as local variables.
|
||||
return local{val: v}
|
||||
default:
|
||||
panic(fmt.Errorf("unsupported value %v in node creation", val))
|
||||
}
|
||||
}
|
||||
|
||||
// representative returns a unique representative for node `n`. Since
|
||||
// semantically equivalent types can have different implementations,
|
||||
// this method guarantees the same implementation is always used.
|
||||
func (b *builder) representative(n node) node {
|
||||
if n.Type() == nil {
|
||||
// panicArg and recoverReturn do not have
|
||||
// types and are unique by definition.
|
||||
return n
|
||||
}
|
||||
t := canonicalize(n.Type(), &b.canon)
|
||||
|
||||
switch i := n.(type) {
|
||||
case constant:
|
||||
return constant{typ: t}
|
||||
case pointer:
|
||||
return pointer{typ: t.(*types.Pointer)}
|
||||
case sliceElem:
|
||||
return sliceElem{typ: t}
|
||||
case mapKey:
|
||||
return mapKey{typ: t}
|
||||
case mapValue:
|
||||
return mapValue{typ: t}
|
||||
case channelElem:
|
||||
return channelElem{typ: t}
|
||||
case nestedPtrInterface:
|
||||
return nestedPtrInterface{typ: t}
|
||||
case nestedPtrFunction:
|
||||
return nestedPtrFunction{typ: t}
|
||||
case field:
|
||||
return field{StructType: canonicalize(i.StructType, &b.canon), index: i.index}
|
||||
case indexedLocal:
|
||||
return indexedLocal{typ: t, val: i.val, index: i.index}
|
||||
case local, global, panicArg, recoverReturn, function, resultVar:
|
||||
return n
|
||||
default:
|
||||
panic(fmt.Errorf("canonicalizing unrecognized node %v", n))
|
||||
}
|
||||
}
|
||||
|
||||
// canonicalize returns a type representative of `t` unique subject
|
||||
// to type map `canon`.
|
||||
func canonicalize(t types.Type, canon *typeutil.Map) types.Type {
|
||||
rep := canon.At(t)
|
||||
if rep != nil {
|
||||
return rep.(types.Type)
|
||||
}
|
||||
canon.Set(t, t)
|
||||
return t
|
||||
}
|
||||
37
vendor/golang.org/x/tools/go/callgraph/vta/initial.go
generated
vendored
Normal file
37
vendor/golang.org/x/tools/go/callgraph/vta/initial.go
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright 2024 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package vta
|
||||
|
||||
import (
|
||||
"golang.org/x/tools/go/callgraph"
|
||||
"golang.org/x/tools/go/callgraph/internal/chautil"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
)
|
||||
|
||||
// calleesFunc abstracts call graph in one direction,
|
||||
// from call sites to callees.
|
||||
type calleesFunc func(ssa.CallInstruction) []*ssa.Function
|
||||
|
||||
// makeCalleesFunc returns an initial call graph for vta as a
|
||||
// calleesFunc. If c is not nil, returns callees as given by c.
|
||||
// Otherwise, it returns chautil.LazyCallees over fs.
|
||||
func makeCalleesFunc(fs map[*ssa.Function]bool, c *callgraph.Graph) calleesFunc {
|
||||
if c == nil {
|
||||
return chautil.LazyCallees(fs)
|
||||
}
|
||||
return func(call ssa.CallInstruction) []*ssa.Function {
|
||||
node := c.Nodes[call.Parent()]
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
var cs []*ssa.Function
|
||||
for _, edge := range node.Out {
|
||||
if edge.Site == call {
|
||||
cs = append(cs, edge.Callee.Func)
|
||||
}
|
||||
}
|
||||
return cs
|
||||
}
|
||||
}
|
||||
127
vendor/golang.org/x/tools/go/callgraph/vta/internal/trie/bits.go
generated
vendored
Normal file
127
vendor/golang.org/x/tools/go/callgraph/vta/internal/trie/bits.go
generated
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
// Copyright 2021 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package trie
|
||||
|
||||
import (
|
||||
"math/bits"
|
||||
)
|
||||
|
||||
// This file contains bit twiddling functions for Patricia tries.
|
||||
// Consult this paper for details.
|
||||
// C. Okasaki and A. Gill, “Fast mergeable integer maps,” in ACM SIGPLAN
|
||||
// Workshop on ML, September 1998, pp. 77–86.
|
||||
|
||||
// key is a key in a Map.
|
||||
type key uint64
|
||||
|
||||
// bitpos is the position of a bit. A position is represented by having a 1
|
||||
// bit in that position.
|
||||
// Examples:
|
||||
// - 0b0010 is the position of the `1` bit in 2.
|
||||
// It is the 3rd most specific bit position in big endian encoding
|
||||
// (0b0 and 0b1 are more specific).
|
||||
// - 0b0100 is the position of the bit that 1 and 5 disagree on.
|
||||
// - 0b0 is a special value indicating that all bit agree.
|
||||
type bitpos uint64
|
||||
|
||||
// prefixes represent a set of keys that all agree with the
|
||||
// prefix up to a bitpos m.
|
||||
//
|
||||
// The value for a prefix is determined by the mask(k, m) function.
|
||||
// (See mask for details on the values.)
|
||||
// A `p` prefix for position `m` matches a key `k` iff mask(k, m) == p.
|
||||
// A prefix always mask(p, m) == p.
|
||||
//
|
||||
// A key is its own prefix for the bit position 64,
|
||||
// e.g. seeing a `prefix(key)` is not a problem.
|
||||
//
|
||||
// Prefixes should never be turned into keys.
|
||||
type prefix uint64
|
||||
|
||||
// branchingBit returns the position of the first bit in `x` and `y`
|
||||
// that are not equal.
|
||||
func branchingBit(x, y prefix) bitpos {
|
||||
p := x ^ y
|
||||
if p == 0 {
|
||||
return 0
|
||||
}
|
||||
return bitpos(1) << uint(bits.Len64(uint64(p))-1) // uint conversion needed for go1.12
|
||||
}
|
||||
|
||||
// zeroBit returns true if k has a 0 bit at position `b`.
|
||||
func zeroBit(k prefix, b bitpos) bool {
|
||||
return (uint64(k) & uint64(b)) == 0
|
||||
}
|
||||
|
||||
// matchPrefix returns true if a prefix k matches a prefix p up to position `b`.
|
||||
func matchPrefix(k prefix, p prefix, b bitpos) bool {
|
||||
return mask(k, b) == p
|
||||
}
|
||||
|
||||
// mask returns a prefix of `k` with all bits after and including `b` zeroed out.
|
||||
//
|
||||
// In big endian encoding, this value is the [64-(m-1)] most significant bits of k
|
||||
// followed by a `0` bit at bitpos m, followed m-1 `1` bits.
|
||||
// Examples:
|
||||
//
|
||||
// prefix(0b1011) for a bitpos 0b0100 represents the keys:
|
||||
// 0b1000, 0b1001, 0b1010, 0b1011, 0b1100, 0b1101, 0b1110, 0b1111
|
||||
//
|
||||
// This mask function has the property that if matchPrefix(k, p, b), then
|
||||
// k <= p if and only if zeroBit(k, m). This induces binary search tree tries.
|
||||
// See Okasaki & Gill for more details about this choice of mask function.
|
||||
//
|
||||
// mask is idempotent for a given `b`, i.e. mask(mask(p, b), b) == mask(p,b).
|
||||
func mask(k prefix, b bitpos) prefix {
|
||||
return prefix((uint64(k) | (uint64(b) - 1)) & (^uint64(b)))
|
||||
}
|
||||
|
||||
// ord returns true if m comes before n in the bit ordering.
|
||||
func ord(m, n bitpos) bool {
|
||||
return m > n // big endian encoding
|
||||
}
|
||||
|
||||
// prefixesOverlap returns true if there is some key a prefix `p` for bitpos `m`
|
||||
// can hold that can also be held by a prefix `q` for some bitpos `n`.
|
||||
//
|
||||
// This is equivalent to:
|
||||
//
|
||||
// m ==n && p == q,
|
||||
// higher(m, n) && matchPrefix(q, p, m), or
|
||||
// higher(n, m) && matchPrefix(p, q, n)
|
||||
func prefixesOverlap(p prefix, m bitpos, q prefix, n bitpos) bool {
|
||||
fbb := n
|
||||
if ord(m, n) {
|
||||
fbb = m
|
||||
}
|
||||
return mask(p, fbb) == mask(q, fbb)
|
||||
// Lemma:
|
||||
// mask(p, fbb) == mask(q, fbb)
|
||||
// iff
|
||||
// m > n && matchPrefix(q, p, m) or (note: big endian encoding)
|
||||
// m < n && matchPrefix(p, q, n) or (note: big endian encoding)
|
||||
// m ==n && p == q
|
||||
// Quick-n-dirty proof:
|
||||
// p == mask(p0, m) for some p0 by precondition.
|
||||
// q == mask(q0, n) for some q0 by precondition.
|
||||
// So mask(p, m) == p and mask(q, n) == q as mask(*, n') is idempotent.
|
||||
//
|
||||
// [=> proof]
|
||||
// Suppose mask(p, fbb) == mask(q, fbb).
|
||||
// if m ==n, p == mask(p, m) == mask(p, fbb) == mask(q, fbb) == mask(q, n) == q
|
||||
// if m > n, fbb = firstBranchBit(m, n) = m (big endian).
|
||||
// p == mask(p, m) == mask(p, fbb) == mask(q, fbb) == mask(q, m)
|
||||
// so mask(q, m) == p or matchPrefix(q, p, m)
|
||||
// if m < n, is symmetric to the above.
|
||||
//
|
||||
// [<= proof]
|
||||
// case m ==n && p == q. Then mask(p, fbb) == mask(q, fbb)
|
||||
//
|
||||
// case m > n && matchPrefix(q, p, m).
|
||||
// fbb == firstBranchBit(m, n) == m (by m>n).
|
||||
// mask(q, fbb) == mask(q, m) == p == mask(p, m) == mask(p, fbb)
|
||||
//
|
||||
// case m < n && matchPrefix(p, q, n) is symmetric.
|
||||
}
|
||||
516
vendor/golang.org/x/tools/go/callgraph/vta/internal/trie/builder.go
generated
vendored
Normal file
516
vendor/golang.org/x/tools/go/callgraph/vta/internal/trie/builder.go
generated
vendored
Normal file
@@ -0,0 +1,516 @@
|
||||
// Copyright 2021 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package trie
|
||||
|
||||
// Collision functions combine a left and right hand side (lhs and rhs) values
|
||||
// the two values are associated with the same key and produces the value that
|
||||
// will be stored for the key.
|
||||
//
|
||||
// Collision functions must be idempotent:
|
||||
//
|
||||
// collision(x, x) == x for all x.
|
||||
//
|
||||
// Collisions functions may be applied whenever a value is inserted
|
||||
// or two maps are merged, or intersected.
|
||||
type Collision func(lhs any, rhs any) any
|
||||
|
||||
// TakeLhs always returns the left value in a collision.
|
||||
func TakeLhs(lhs, rhs any) any { return lhs }
|
||||
|
||||
// TakeRhs always returns the right hand side in a collision.
|
||||
func TakeRhs(lhs, rhs any) any { return rhs }
|
||||
|
||||
// Builder creates new Map. Each Builder has a unique Scope.
|
||||
//
|
||||
// IMPORTANT: Nodes are hash-consed internally to reduce memory consumption. To
|
||||
// support hash-consing Builders keep an internal Map of all of the Maps that they
|
||||
// have created. To GC any of the Maps created by the Builder, all references to
|
||||
// the Builder must be dropped. This includes MutMaps.
|
||||
type Builder struct {
|
||||
scope Scope
|
||||
|
||||
// hash-consing maps for each node type.
|
||||
empty *empty
|
||||
leaves map[leaf]*leaf
|
||||
branches map[branch]*branch
|
||||
// It may be possible to support more types of patricia tries
|
||||
// (e.g. non-hash-consed) by making Builder an interface and abstracting
|
||||
// the mkLeaf and mkBranch functions.
|
||||
}
|
||||
|
||||
// NewBuilder creates a new Builder with a unique Scope.
|
||||
func NewBuilder() *Builder {
|
||||
s := newScope()
|
||||
return &Builder{
|
||||
scope: s,
|
||||
empty: &empty{s},
|
||||
leaves: make(map[leaf]*leaf),
|
||||
branches: make(map[branch]*branch),
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Builder) Scope() Scope { return b.scope }
|
||||
|
||||
// Rescope changes the builder's scope to a new unique Scope.
|
||||
//
|
||||
// Any Maps created using the previous scope need to be Cloned
|
||||
// before any operation.
|
||||
//
|
||||
// This makes the old internals of the Builder eligible to be GC'ed.
|
||||
func (b *Builder) Rescope() {
|
||||
s := newScope()
|
||||
b.scope = s
|
||||
b.empty = &empty{s}
|
||||
b.leaves = make(map[leaf]*leaf)
|
||||
b.branches = make(map[branch]*branch)
|
||||
}
|
||||
|
||||
// Empty is the empty map.
|
||||
func (b *Builder) Empty() Map { return Map{b.Scope(), b.empty} }
|
||||
|
||||
// InsertWith inserts a new association from k to v into the Map m to create a new map
|
||||
// in the current scope and handle collisions using the collision function c.
|
||||
//
|
||||
// This is roughly corresponds to updating a map[uint64]interface{} by:
|
||||
//
|
||||
// if _, ok := m[k]; ok { m[k] = c(m[k], v} else { m[k] = v}
|
||||
//
|
||||
// An insertion or update happened whenever Insert(m, ...) != m .
|
||||
func (b *Builder) InsertWith(c Collision, m Map, k uint64, v any) Map {
|
||||
m = b.Clone(m)
|
||||
return Map{b.Scope(), b.insert(c, m.n, b.mkLeaf(key(k), v), false)}
|
||||
}
|
||||
|
||||
// Inserts a new association from key to value into the Map m to create
|
||||
// a new map in the current scope.
|
||||
//
|
||||
// If there was a previous value mapped by key, keep the previously mapped value.
|
||||
// This is roughly corresponds to updating a map[uint64]interface{} by:
|
||||
//
|
||||
// if _, ok := m[k]; ok { m[k] = val }
|
||||
//
|
||||
// This is equivalent to b.Merge(m, b.Create({k: v})).
|
||||
func (b *Builder) Insert(m Map, k uint64, v any) Map {
|
||||
return b.InsertWith(TakeLhs, m, k, v)
|
||||
}
|
||||
|
||||
// Updates a (key, value) in the map. This is roughly corresponds to
|
||||
// updating a map[uint64]interface{} by:
|
||||
//
|
||||
// m[key] = val
|
||||
func (b *Builder) Update(m Map, key uint64, val any) Map {
|
||||
return b.InsertWith(TakeRhs, m, key, val)
|
||||
}
|
||||
|
||||
// Merge two maps lhs and rhs to create a new map in the current scope.
|
||||
//
|
||||
// Whenever there is a key in both maps (a collision), the resulting value mapped by
|
||||
// the key will be `c(lhs[key], rhs[key])`.
|
||||
func (b *Builder) MergeWith(c Collision, lhs, rhs Map) Map {
|
||||
lhs, rhs = b.Clone(lhs), b.Clone(rhs)
|
||||
return Map{b.Scope(), b.merge(c, lhs.n, rhs.n)}
|
||||
}
|
||||
|
||||
// Merge two maps lhs and rhs to create a new map in the current scope.
|
||||
//
|
||||
// Whenever there is a key in both maps (a collision), the resulting value mapped by
|
||||
// the key will be the value in lhs `b.Collision(lhs[key], rhs[key])`.
|
||||
func (b *Builder) Merge(lhs, rhs Map) Map {
|
||||
return b.MergeWith(TakeLhs, lhs, rhs)
|
||||
}
|
||||
|
||||
// Clone returns a Map that contains the same (key, value) elements
|
||||
// within b.Scope(), i.e. return m if m.Scope() == b.Scope() or return
|
||||
// a deep copy of m within b.Scope() otherwise.
|
||||
func (b *Builder) Clone(m Map) Map {
|
||||
if m.Scope() == b.Scope() {
|
||||
return m
|
||||
} else if m.n == nil {
|
||||
return Map{b.Scope(), b.empty}
|
||||
}
|
||||
return Map{b.Scope(), b.clone(m.n)}
|
||||
}
|
||||
func (b *Builder) clone(n node) node {
|
||||
switch n := n.(type) {
|
||||
case *empty:
|
||||
return b.empty
|
||||
case *leaf:
|
||||
return b.mkLeaf(n.k, n.v)
|
||||
case *branch:
|
||||
return b.mkBranch(n.prefix, n.branching, b.clone(n.left), b.clone(n.right))
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
|
||||
// Remove a key from a Map m and return the resulting Map.
|
||||
func (b *Builder) Remove(m Map, k uint64) Map {
|
||||
m = b.Clone(m)
|
||||
return Map{b.Scope(), b.remove(m.n, key(k))}
|
||||
}
|
||||
|
||||
// Intersect Maps lhs and rhs and returns a map with all of the keys in
|
||||
// both lhs and rhs and the value comes from lhs, i.e.
|
||||
//
|
||||
// {(k, lhs[k]) | k in lhs, k in rhs}.
|
||||
func (b *Builder) Intersect(lhs, rhs Map) Map {
|
||||
return b.IntersectWith(TakeLhs, lhs, rhs)
|
||||
}
|
||||
|
||||
// IntersectWith take lhs and rhs and returns the intersection
|
||||
// with the value coming from the collision function, i.e.
|
||||
//
|
||||
// {(k, c(lhs[k], rhs[k]) ) | k in lhs, k in rhs}.
|
||||
//
|
||||
// The elements of the resulting map are always { <k, c(lhs[k], rhs[k]) > }
|
||||
// for each key k that a key in both lhs and rhs.
|
||||
func (b *Builder) IntersectWith(c Collision, lhs, rhs Map) Map {
|
||||
l, r := b.Clone(lhs), b.Clone(rhs)
|
||||
return Map{b.Scope(), b.intersect(c, l.n, r.n)}
|
||||
}
|
||||
|
||||
// MutMap is a convenient wrapper for a Map and a *Builder that will be used to create
|
||||
// new Maps from it.
|
||||
type MutMap struct {
|
||||
B *Builder
|
||||
M Map
|
||||
}
|
||||
|
||||
// MutEmpty is an empty MutMap for a builder.
|
||||
func (b *Builder) MutEmpty() MutMap {
|
||||
return MutMap{b, b.Empty()}
|
||||
}
|
||||
|
||||
// Insert an element into the map using the collision function for the builder.
|
||||
// Returns true if the element was inserted.
|
||||
func (mm *MutMap) Insert(k uint64, v any) bool {
|
||||
old := mm.M
|
||||
mm.M = mm.B.Insert(old, k, v)
|
||||
return old != mm.M
|
||||
}
|
||||
|
||||
// Updates an element in the map. Returns true if the map was updated.
|
||||
func (mm *MutMap) Update(k uint64, v any) bool {
|
||||
old := mm.M
|
||||
mm.M = mm.B.Update(old, k, v)
|
||||
return old != mm.M
|
||||
}
|
||||
|
||||
// Removes a key from the map. Returns true if the element was removed.
|
||||
func (mm *MutMap) Remove(k uint64) bool {
|
||||
old := mm.M
|
||||
mm.M = mm.B.Remove(old, k)
|
||||
return old != mm.M
|
||||
}
|
||||
|
||||
// Merge another map into the current one using the collision function
|
||||
// for the builder. Returns true if the map changed.
|
||||
func (mm *MutMap) Merge(other Map) bool {
|
||||
old := mm.M
|
||||
mm.M = mm.B.Merge(old, other)
|
||||
return old != mm.M
|
||||
}
|
||||
|
||||
// Intersect another map into the current one using the collision function
|
||||
// for the builder. Returns true if the map changed.
|
||||
func (mm *MutMap) Intersect(other Map) bool {
|
||||
old := mm.M
|
||||
mm.M = mm.B.Intersect(old, other)
|
||||
return old != mm.M
|
||||
}
|
||||
|
||||
func (b *Builder) Create(m map[uint64]any) Map {
|
||||
var leaves []*leaf
|
||||
for k, v := range m {
|
||||
leaves = append(leaves, b.mkLeaf(key(k), v))
|
||||
}
|
||||
return Map{b.Scope(), b.create(leaves)}
|
||||
}
|
||||
|
||||
// Merge another map into the current one using the collision function
|
||||
// for the builder. Returns true if the map changed.
|
||||
func (mm *MutMap) MergeWith(c Collision, other Map) bool {
|
||||
old := mm.M
|
||||
mm.M = mm.B.MergeWith(c, old, other)
|
||||
return old != mm.M
|
||||
}
|
||||
|
||||
// creates a map for a collection of leaf nodes.
|
||||
func (b *Builder) create(leaves []*leaf) node {
|
||||
n := len(leaves)
|
||||
if n == 0 {
|
||||
return b.empty
|
||||
} else if n == 1 {
|
||||
return leaves[0]
|
||||
}
|
||||
// Note: we can do a more sophisticated algorithm by:
|
||||
// - sorting the leaves ahead of time,
|
||||
// - taking the prefix and branching bit of the min and max key,
|
||||
// - binary searching for the branching bit,
|
||||
// - splitting exactly where the branch will be, and
|
||||
// - making the branch node for this prefix + branching bit.
|
||||
// Skipping until this is a performance bottleneck.
|
||||
|
||||
m := n / 2 // (n >= 2) ==> 1 <= m < n
|
||||
l, r := leaves[:m], leaves[m:]
|
||||
return b.merge(nil, b.create(l), b.create(r))
|
||||
}
|
||||
|
||||
// mkLeaf returns the hash-consed representative of (k, v) in the current scope.
|
||||
func (b *Builder) mkLeaf(k key, v any) *leaf {
|
||||
rep, ok := b.leaves[leaf{k, v}]
|
||||
if !ok {
|
||||
rep = &leaf{k, v} // heap-allocated copy
|
||||
b.leaves[leaf{k, v}] = rep
|
||||
}
|
||||
return rep
|
||||
}
|
||||
|
||||
// mkBranch returns the hash-consed representative of the tuple
|
||||
//
|
||||
// (prefix, branch, left, right)
|
||||
//
|
||||
// in the current scope.
|
||||
func (b *Builder) mkBranch(p prefix, bp bitpos, left node, right node) *branch {
|
||||
br := branch{
|
||||
sz: left.size() + right.size(),
|
||||
prefix: p,
|
||||
branching: bp,
|
||||
left: left,
|
||||
right: right,
|
||||
}
|
||||
rep, ok := b.branches[br]
|
||||
if !ok {
|
||||
rep = new(branch) // heap-allocated copy
|
||||
*rep = br
|
||||
b.branches[br] = rep
|
||||
}
|
||||
return rep
|
||||
}
|
||||
|
||||
// join two maps with prefixes p0 and p1 that are *known* to disagree.
|
||||
func (b *Builder) join(p0 prefix, t0 node, p1 prefix, t1 node) *branch {
|
||||
m := branchingBit(p0, p1)
|
||||
var left, right node
|
||||
if zeroBit(p0, m) {
|
||||
left, right = t0, t1
|
||||
} else {
|
||||
left, right = t1, t0
|
||||
}
|
||||
prefix := mask(p0, m)
|
||||
return b.mkBranch(prefix, m, left, right)
|
||||
}
|
||||
|
||||
// collide two leaves with the same key to create a leaf
|
||||
// with the collided value.
|
||||
func (b *Builder) collide(c Collision, left, right *leaf) *leaf {
|
||||
if left == right {
|
||||
return left // c is idempotent: c(x, x) == x
|
||||
}
|
||||
val := left.v // keep the left value by default if c is nil
|
||||
if c != nil {
|
||||
val = c(left.v, right.v)
|
||||
}
|
||||
switch val {
|
||||
case left.v:
|
||||
return left
|
||||
case right.v:
|
||||
return right
|
||||
default:
|
||||
return b.mkLeaf(left.k, val)
|
||||
}
|
||||
}
|
||||
|
||||
// inserts a leaf l into a map m and returns the resulting map.
|
||||
// When lhs is true, l is the left hand side in a collision.
|
||||
// Both l and m are in the current scope.
|
||||
func (b *Builder) insert(c Collision, m node, l *leaf, lhs bool) node {
|
||||
switch m := m.(type) {
|
||||
case *empty:
|
||||
return l
|
||||
case *leaf:
|
||||
if m.k == l.k {
|
||||
left, right := l, m
|
||||
if !lhs {
|
||||
left, right = right, left
|
||||
}
|
||||
return b.collide(c, left, right)
|
||||
}
|
||||
return b.join(prefix(l.k), l, prefix(m.k), m)
|
||||
case *branch:
|
||||
// fallthrough
|
||||
}
|
||||
// m is a branch
|
||||
br := m.(*branch)
|
||||
if !matchPrefix(prefix(l.k), br.prefix, br.branching) {
|
||||
return b.join(prefix(l.k), l, br.prefix, br)
|
||||
}
|
||||
var left, right node
|
||||
if zeroBit(prefix(l.k), br.branching) {
|
||||
left, right = b.insert(c, br.left, l, lhs), br.right
|
||||
} else {
|
||||
left, right = br.left, b.insert(c, br.right, l, lhs)
|
||||
}
|
||||
if left == br.left && right == br.right {
|
||||
return m
|
||||
}
|
||||
return b.mkBranch(br.prefix, br.branching, left, right)
|
||||
}
|
||||
|
||||
// merge two maps in the current scope.
|
||||
func (b *Builder) merge(c Collision, lhs, rhs node) node {
|
||||
if lhs == rhs {
|
||||
return lhs
|
||||
}
|
||||
switch lhs := lhs.(type) {
|
||||
case *empty:
|
||||
return rhs
|
||||
case *leaf:
|
||||
return b.insert(c, rhs, lhs, true)
|
||||
case *branch:
|
||||
switch rhs := rhs.(type) {
|
||||
case *empty:
|
||||
return lhs
|
||||
case *leaf:
|
||||
return b.insert(c, lhs, rhs, false)
|
||||
case *branch:
|
||||
// fallthrough
|
||||
}
|
||||
}
|
||||
|
||||
// Last remaining case is branch merging.
|
||||
// For brevity, we adopt the Okasaki and Gill naming conventions
|
||||
// for branching and prefixes.
|
||||
s, t := lhs.(*branch), rhs.(*branch)
|
||||
p, m := s.prefix, s.branching
|
||||
q, n := t.prefix, t.branching
|
||||
|
||||
if m == n && p == q { // prefixes are identical.
|
||||
left, right := b.merge(c, s.left, t.left), b.merge(c, s.right, t.right)
|
||||
return b.mkBranch(p, m, left, right)
|
||||
}
|
||||
if !prefixesOverlap(p, m, q, n) {
|
||||
return b.join(p, s, q, t) // prefixes are disjoint.
|
||||
}
|
||||
// prefixesOverlap(p, m, q, n) && !(m ==n && p == q)
|
||||
// By prefixesOverlap(...), either:
|
||||
// higher(m, n) && matchPrefix(q, p, m), or
|
||||
// higher(n, m) && matchPrefix(p, q, n)
|
||||
// So either s or t may can be merged with one branch or the other.
|
||||
switch {
|
||||
case ord(m, n) && zeroBit(q, m):
|
||||
return b.mkBranch(p, m, b.merge(c, s.left, t), s.right)
|
||||
case ord(m, n) && !zeroBit(q, m):
|
||||
return b.mkBranch(p, m, s.left, b.merge(c, s.right, t))
|
||||
case ord(n, m) && zeroBit(p, n):
|
||||
return b.mkBranch(q, n, b.merge(c, s, t.left), t.right)
|
||||
default:
|
||||
return b.mkBranch(q, n, t.left, b.merge(c, s, t.right))
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Builder) remove(m node, k key) node {
|
||||
switch m := m.(type) {
|
||||
case *empty:
|
||||
return m
|
||||
case *leaf:
|
||||
if m.k == k {
|
||||
return b.empty
|
||||
}
|
||||
return m
|
||||
case *branch:
|
||||
// fallthrough
|
||||
}
|
||||
br := m.(*branch)
|
||||
kp := prefix(k)
|
||||
if !matchPrefix(kp, br.prefix, br.branching) {
|
||||
// The prefix does not match. kp is not in br.
|
||||
return br
|
||||
}
|
||||
// the prefix matches. try to remove from the left or right branch.
|
||||
left, right := br.left, br.right
|
||||
if zeroBit(kp, br.branching) {
|
||||
left = b.remove(left, k) // k may be in the left branch.
|
||||
} else {
|
||||
right = b.remove(right, k) // k may be in the right branch.
|
||||
}
|
||||
if left == br.left && right == br.right {
|
||||
return br // no update
|
||||
} else if _, ok := left.(*empty); ok {
|
||||
return right // left updated and is empty.
|
||||
} else if _, ok := right.(*empty); ok {
|
||||
return left // right updated and is empty.
|
||||
}
|
||||
// Either left or right updated. Both left and right are not empty.
|
||||
// The left and right branches still share the same prefix and disagree
|
||||
// on the same branching bit. It is safe to directly create the branch.
|
||||
return b.mkBranch(br.prefix, br.branching, left, right)
|
||||
}
|
||||
|
||||
func (b *Builder) intersect(c Collision, l, r node) node {
|
||||
if l == r {
|
||||
return l
|
||||
}
|
||||
switch l := l.(type) {
|
||||
case *empty:
|
||||
return b.empty
|
||||
case *leaf:
|
||||
if rleaf := r.find(l.k); rleaf != nil {
|
||||
return b.collide(c, l, rleaf)
|
||||
}
|
||||
return b.empty
|
||||
case *branch:
|
||||
switch r := r.(type) {
|
||||
case *empty:
|
||||
return b.empty
|
||||
case *leaf:
|
||||
if lleaf := l.find(r.k); lleaf != nil {
|
||||
return b.collide(c, lleaf, r)
|
||||
}
|
||||
return b.empty
|
||||
case *branch:
|
||||
// fallthrough
|
||||
}
|
||||
}
|
||||
// Last remaining case is branch intersection.
|
||||
s, t := l.(*branch), r.(*branch)
|
||||
p, m := s.prefix, s.branching
|
||||
q, n := t.prefix, t.branching
|
||||
|
||||
if m == n && p == q {
|
||||
// prefixes are identical.
|
||||
left, right := b.intersect(c, s.left, t.left), b.intersect(c, s.right, t.right)
|
||||
if _, ok := left.(*empty); ok {
|
||||
return right
|
||||
} else if _, ok := right.(*empty); ok {
|
||||
return left
|
||||
}
|
||||
// The left and right branches are both non-empty.
|
||||
// They still share the same prefix and disagree on the same branching bit.
|
||||
// It is safe to directly create the branch.
|
||||
return b.mkBranch(p, m, left, right)
|
||||
}
|
||||
|
||||
if !prefixesOverlap(p, m, q, n) {
|
||||
return b.empty // The prefixes share no keys.
|
||||
}
|
||||
// prefixesOverlap(p, m, q, n) && !(m ==n && p == q)
|
||||
// By prefixesOverlap(...), either:
|
||||
// ord(m, n) && matchPrefix(q, p, m), or
|
||||
// ord(n, m) && matchPrefix(p, q, n)
|
||||
// So either s or t may be a strict subtree of the other.
|
||||
var lhs, rhs node
|
||||
switch {
|
||||
case ord(m, n) && zeroBit(q, m):
|
||||
lhs, rhs = s.left, t
|
||||
case ord(m, n) && !zeroBit(q, m):
|
||||
lhs, rhs = s.right, t
|
||||
case ord(n, m) && zeroBit(p, n):
|
||||
lhs, rhs = s, t.left
|
||||
default:
|
||||
lhs, rhs = s, t.right
|
||||
}
|
||||
return b.intersect(c, lhs, rhs)
|
||||
}
|
||||
28
vendor/golang.org/x/tools/go/callgraph/vta/internal/trie/scope.go
generated
vendored
Normal file
28
vendor/golang.org/x/tools/go/callgraph/vta/internal/trie/scope.go
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright 2021 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package trie
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// Scope represents a distinct collection of maps.
|
||||
// Maps with the same Scope can be equal. Maps in different scopes are distinct.
|
||||
// Each Builder creates maps within a unique Scope.
|
||||
type Scope struct {
|
||||
id int32
|
||||
}
|
||||
|
||||
var nextScopeId int32
|
||||
|
||||
func newScope() Scope {
|
||||
id := atomic.AddInt32(&nextScopeId, 1)
|
||||
return Scope{id: id}
|
||||
}
|
||||
|
||||
func (s Scope) String() string {
|
||||
return strconv.Itoa(int(s.id))
|
||||
}
|
||||
229
vendor/golang.org/x/tools/go/callgraph/vta/internal/trie/trie.go
generated
vendored
Normal file
229
vendor/golang.org/x/tools/go/callgraph/vta/internal/trie/trie.go
generated
vendored
Normal file
@@ -0,0 +1,229 @@
|
||||
// Copyright 2021 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// trie implements persistent Patricia trie maps.
|
||||
//
|
||||
// Each Map is effectively a map from uint64 to interface{}. Patricia tries are
|
||||
// a form of radix tree that are particularly appropriate when many maps will be
|
||||
// created, merged together and large amounts of sharing are expected (e.g.
|
||||
// environment abstract domains in program analysis).
|
||||
//
|
||||
// This implementation closely follows the paper:
|
||||
//
|
||||
// C. Okasaki and A. Gill, “Fast mergeable integer maps,” in ACM SIGPLAN
|
||||
// Workshop on ML, September 1998, pp. 77–86.
|
||||
//
|
||||
// Each Map is immutable and can be read from concurrently. The map does not
|
||||
// guarantee that the value pointed to by the interface{} value is not updated
|
||||
// concurrently.
|
||||
//
|
||||
// These Maps are optimized for situations where there will be many maps created at
|
||||
// with a high degree of sharing and combining of maps together. If you do not expect,
|
||||
// significant amount of sharing, the builtin map[T]U is much better choice!
|
||||
//
|
||||
// Each Map is created by a Builder. Each Builder has a unique Scope and each node is
|
||||
// created within this scope. Maps x and y are == if they contains the same
|
||||
// (key,value) mappings and have equal scopes.
|
||||
//
|
||||
// Internally these are big endian Patricia trie nodes, and the keys are sorted.
|
||||
package trie
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Map is effectively a finite mapping from uint64 keys to interface{} values.
|
||||
// Maps are immutable and can be read from concurrently.
|
||||
//
|
||||
// Notes on concurrency:
|
||||
// - A Map value itself is an interface and assignments to a Map value can race.
|
||||
// - Map does not guarantee that the value pointed to by the interface{} value
|
||||
// is not updated concurrently.
|
||||
type Map struct {
|
||||
s Scope
|
||||
n node
|
||||
}
|
||||
|
||||
func (m Map) Scope() Scope {
|
||||
return m.s
|
||||
}
|
||||
func (m Map) Size() int {
|
||||
if m.n == nil {
|
||||
return 0
|
||||
}
|
||||
return m.n.size()
|
||||
}
|
||||
func (m Map) Lookup(k uint64) (any, bool) {
|
||||
if m.n != nil {
|
||||
if leaf := m.n.find(key(k)); leaf != nil {
|
||||
return leaf.v, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Converts the map into a {<key>: <value>[, ...]} string. This uses the default
|
||||
// %s string conversion for <value>.
|
||||
func (m Map) String() string {
|
||||
var kvs []string
|
||||
m.Range(func(u uint64, i any) bool {
|
||||
kvs = append(kvs, fmt.Sprintf("%d: %s", u, i))
|
||||
return true
|
||||
})
|
||||
return fmt.Sprintf("{%s}", strings.Join(kvs, ", "))
|
||||
}
|
||||
|
||||
// Range over the leaf (key, value) pairs in the map in order and
|
||||
// applies cb(key, value) to each. Stops early if cb returns false.
|
||||
// Returns true if all elements were visited without stopping early.
|
||||
func (m Map) Range(cb func(uint64, any) bool) bool {
|
||||
if m.n != nil {
|
||||
return m.n.visit(cb)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// DeepEqual returns true if m and other contain the same (k, v) mappings
|
||||
// [regardless of Scope].
|
||||
//
|
||||
// Equivalently m.DeepEqual(other) <=> reflect.DeepEqual(Elems(m), Elems(other))
|
||||
func (m Map) DeepEqual(other Map) bool {
|
||||
if m.Scope() == other.Scope() {
|
||||
return m.n == other.n
|
||||
}
|
||||
if (m.n == nil) || (other.n == nil) {
|
||||
return m.Size() == 0 && other.Size() == 0
|
||||
}
|
||||
return m.n.deepEqual(other.n)
|
||||
}
|
||||
|
||||
// Elems are the (k,v) elements in the Map as a map[uint64]interface{}
|
||||
func Elems(m Map) map[uint64]any {
|
||||
dest := make(map[uint64]any, m.Size())
|
||||
m.Range(func(k uint64, v any) bool {
|
||||
dest[k] = v
|
||||
return true
|
||||
})
|
||||
return dest
|
||||
}
|
||||
|
||||
// node is an internal node within a trie map.
|
||||
// A node is either empty, a leaf or a branch.
|
||||
type node interface {
|
||||
size() int
|
||||
|
||||
// visit the leaves (key, value) pairs in the map in order and
|
||||
// applies cb(key, value) to each. Stops early if cb returns false.
|
||||
// Returns true if all elements were visited without stopping early.
|
||||
visit(cb func(uint64, any) bool) bool
|
||||
|
||||
// Two nodes contain the same elements regardless of scope.
|
||||
deepEqual(node) bool
|
||||
|
||||
// find the leaf for the given key value or nil if it is not present.
|
||||
find(k key) *leaf
|
||||
|
||||
// implementations must implement this.
|
||||
nodeImpl()
|
||||
}
|
||||
|
||||
// empty represents the empty map within a scope.
|
||||
//
|
||||
// The current builder ensure
|
||||
type empty struct {
|
||||
s Scope
|
||||
}
|
||||
|
||||
// leaf represents a single <key, value> pair.
|
||||
type leaf struct {
|
||||
k key
|
||||
v any
|
||||
}
|
||||
|
||||
// branch represents a tree node within the Patricia trie.
|
||||
//
|
||||
// All keys within the branch match a `prefix` of the key
|
||||
// up to a `branching` bit, and the left and right nodes
|
||||
// contain keys that disagree on the bit at the `branching` bit.
|
||||
type branch struct {
|
||||
sz int // size. cached for O(1) lookup
|
||||
prefix prefix // == mask(p0, branching) for some p0
|
||||
branching bitpos
|
||||
|
||||
// Invariants:
|
||||
// - neither is nil.
|
||||
// - neither is *empty.
|
||||
// - all keys in left are <= p.
|
||||
// - all keys in right are > p.
|
||||
left, right node
|
||||
}
|
||||
|
||||
// all of these types are Maps.
|
||||
var _ node = &empty{}
|
||||
var _ node = &leaf{}
|
||||
var _ node = &branch{}
|
||||
|
||||
func (*empty) nodeImpl() {}
|
||||
func (*leaf) nodeImpl() {}
|
||||
func (*branch) nodeImpl() {}
|
||||
|
||||
func (*empty) find(k key) *leaf { return nil }
|
||||
func (l *leaf) find(k key) *leaf {
|
||||
if k == l.k {
|
||||
return l
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (br *branch) find(k key) *leaf {
|
||||
kp := prefix(k)
|
||||
if !matchPrefix(kp, br.prefix, br.branching) {
|
||||
return nil
|
||||
}
|
||||
if zeroBit(kp, br.branching) {
|
||||
return br.left.find(k)
|
||||
}
|
||||
return br.right.find(k)
|
||||
}
|
||||
|
||||
func (*empty) size() int { return 0 }
|
||||
func (*leaf) size() int { return 1 }
|
||||
func (br *branch) size() int { return br.sz }
|
||||
|
||||
func (*empty) deepEqual(m node) bool {
|
||||
_, ok := m.(*empty)
|
||||
return ok
|
||||
}
|
||||
func (l *leaf) deepEqual(m node) bool {
|
||||
if m, ok := m.(*leaf); ok {
|
||||
return m == l || (l.k == m.k && l.v == m.v)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (br *branch) deepEqual(m node) bool {
|
||||
if m, ok := m.(*branch); ok {
|
||||
if br == m {
|
||||
return true
|
||||
}
|
||||
return br.sz == m.sz && br.branching == m.branching && br.prefix == m.prefix &&
|
||||
br.left.deepEqual(m.left) && br.right.deepEqual(m.right)
|
||||
}
|
||||
// if m is not a branch, m contains 0 or 1 elem.
|
||||
// br contains at least 2 keys that disagree on a prefix.
|
||||
return false
|
||||
}
|
||||
|
||||
func (*empty) visit(cb func(uint64, any) bool) bool {
|
||||
return true
|
||||
}
|
||||
func (l *leaf) visit(cb func(uint64, any) bool) bool {
|
||||
return cb(uint64(l.k), l.v)
|
||||
}
|
||||
func (br *branch) visit(cb func(uint64, any) bool) bool {
|
||||
if !br.left.visit(cb) {
|
||||
return false
|
||||
}
|
||||
return br.right.visit(cb)
|
||||
}
|
||||
201
vendor/golang.org/x/tools/go/callgraph/vta/propagation.go
generated
vendored
Normal file
201
vendor/golang.org/x/tools/go/callgraph/vta/propagation.go
generated
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
// Copyright 2021 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package vta
|
||||
|
||||
import (
|
||||
"go/types"
|
||||
"iter"
|
||||
"slices"
|
||||
|
||||
"golang.org/x/tools/go/callgraph/vta/internal/trie"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
|
||||
"golang.org/x/tools/go/types/typeutil"
|
||||
)
|
||||
|
||||
// scc computes strongly connected components (SCCs) of `g` using the
|
||||
// classical Tarjan's algorithm for SCCs. The result is two slices:
|
||||
// - sccs: the SCCs, each represented as a slice of node indices
|
||||
// - idxToSccID: the inverse map, from node index to SCC number.
|
||||
//
|
||||
// The SCCs are sorted in reverse topological order: for SCCs
|
||||
// with ids X and Y s.t. X < Y, Y comes before X in the topological order.
|
||||
func scc(g *vtaGraph) (sccs [][]idx, idxToSccID []int) {
|
||||
// standard data structures used by Tarjan's algorithm.
|
||||
type state struct {
|
||||
pre int // preorder of the node (0 if unvisited)
|
||||
lowLink int
|
||||
onStack bool
|
||||
}
|
||||
states := make([]state, g.numNodes())
|
||||
var stack []idx
|
||||
|
||||
idxToSccID = make([]int, g.numNodes())
|
||||
nextPre := 0
|
||||
|
||||
var doSCC func(idx)
|
||||
doSCC = func(n idx) {
|
||||
nextPre++
|
||||
ns := &states[n]
|
||||
*ns = state{pre: nextPre, lowLink: nextPre, onStack: true}
|
||||
stack = append(stack, n)
|
||||
|
||||
for s := range g.successors(n) {
|
||||
if ss := &states[s]; ss.pre == 0 {
|
||||
// Analyze successor s that has not been visited yet.
|
||||
doSCC(s)
|
||||
ns.lowLink = min(ns.lowLink, ss.lowLink)
|
||||
} else if ss.onStack {
|
||||
// The successor is on the stack, meaning it has to be
|
||||
// in the current SCC.
|
||||
ns.lowLink = min(ns.lowLink, ss.pre)
|
||||
}
|
||||
}
|
||||
|
||||
// if n is a root node, pop the stack and generate a new SCC.
|
||||
if ns.lowLink == ns.pre {
|
||||
sccStart := slicesLastIndex(stack, n)
|
||||
scc := slices.Clone(stack[sccStart:])
|
||||
stack = stack[:sccStart]
|
||||
sccID := len(sccs)
|
||||
sccs = append(sccs, scc)
|
||||
for _, w := range scc {
|
||||
states[w].onStack = false
|
||||
idxToSccID[w] = sccID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for n, nn := 0, g.numNodes(); n < nn; n++ {
|
||||
if states[n].pre == 0 {
|
||||
doSCC(idx(n))
|
||||
}
|
||||
}
|
||||
|
||||
return sccs, idxToSccID
|
||||
}
|
||||
|
||||
// slicesLastIndex returns the index of the last occurrence of v in s, or -1 if v is
|
||||
// not present in s.
|
||||
//
|
||||
// slicesLastIndex iterates backwards through the elements of s, stopping when the ==
|
||||
// operator determines an element is equal to v.
|
||||
func slicesLastIndex[S ~[]E, E comparable](s S, v E) int {
|
||||
// TODO: move to / dedup with slices.LastIndex
|
||||
for i := len(s) - 1; i >= 0; i-- {
|
||||
if s[i] == v {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// propType represents type information being propagated
|
||||
// over the vta graph. f != nil only for function nodes
|
||||
// and nodes reachable from function nodes. There, we also
|
||||
// remember the actual *ssa.Function in order to more
|
||||
// precisely model higher-order flow.
|
||||
type propType struct {
|
||||
typ types.Type
|
||||
f *ssa.Function
|
||||
}
|
||||
|
||||
// propTypeMap is an auxiliary structure that serves
|
||||
// the role of a map from nodes to a set of propTypes.
|
||||
type propTypeMap map[node]*trie.MutMap
|
||||
|
||||
// propTypes returns an iterator for the propTypes associated with
|
||||
// node `n` in map `ptm`.
|
||||
func (ptm propTypeMap) propTypes(n node) iter.Seq[propType] {
|
||||
return func(yield func(propType) bool) {
|
||||
if types := ptm[n]; types != nil {
|
||||
types.M.Range(func(_ uint64, elem any) bool {
|
||||
return yield(elem.(propType))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// propagate reduces the `graph` based on its SCCs and
|
||||
// then propagates type information through the reduced
|
||||
// graph. The result is a map from nodes to a set of types
|
||||
// and functions, stemming from higher-order data flow,
|
||||
// reaching the node. `canon` is used for type uniqueness.
|
||||
func propagate(graph *vtaGraph, canon *typeutil.Map) propTypeMap {
|
||||
sccs, idxToSccID := scc(graph)
|
||||
|
||||
// propTypeIds are used to create unique ids for
|
||||
// propType, to be used for trie-based type sets.
|
||||
propTypeIds := make(map[propType]uint64)
|
||||
// Id creation is based on == equality, which works
|
||||
// as types are canonicalized (see getPropType).
|
||||
propTypeId := func(p propType) uint64 {
|
||||
if id, ok := propTypeIds[p]; ok {
|
||||
return id
|
||||
}
|
||||
id := uint64(len(propTypeIds))
|
||||
propTypeIds[p] = id
|
||||
return id
|
||||
}
|
||||
builder := trie.NewBuilder()
|
||||
// Initialize sccToTypes to avoid repeated check
|
||||
// for initialization later.
|
||||
sccToTypes := make([]*trie.MutMap, len(sccs))
|
||||
for sccID, scc := range sccs {
|
||||
typeSet := builder.MutEmpty()
|
||||
for _, idx := range scc {
|
||||
if n := graph.node[idx]; hasInitialTypes(n) {
|
||||
// add the propType for idx to typeSet.
|
||||
pt := getPropType(n, canon)
|
||||
typeSet.Update(propTypeId(pt), pt)
|
||||
}
|
||||
}
|
||||
sccToTypes[sccID] = &typeSet
|
||||
}
|
||||
|
||||
for i := len(sccs) - 1; i >= 0; i-- {
|
||||
nextSccs := make(map[int]empty)
|
||||
for _, n := range sccs[i] {
|
||||
for succ := range graph.successors(n) {
|
||||
nextSccs[idxToSccID[succ]] = empty{}
|
||||
}
|
||||
}
|
||||
// Propagate types to all successor SCCs.
|
||||
for nextScc := range nextSccs {
|
||||
sccToTypes[nextScc].Merge(sccToTypes[i].M)
|
||||
}
|
||||
}
|
||||
nodeToTypes := make(propTypeMap, graph.numNodes())
|
||||
for sccID, scc := range sccs {
|
||||
types := sccToTypes[sccID]
|
||||
for _, idx := range scc {
|
||||
nodeToTypes[graph.node[idx]] = types
|
||||
}
|
||||
}
|
||||
return nodeToTypes
|
||||
}
|
||||
|
||||
// hasInitialTypes check if a node can have initial types.
|
||||
// Returns true iff `n` is not a panic, recover, nestedPtr*
|
||||
// node, nor a node whose type is an interface.
|
||||
func hasInitialTypes(n node) bool {
|
||||
switch n.(type) {
|
||||
case panicArg, recoverReturn, nestedPtrFunction, nestedPtrInterface:
|
||||
return false
|
||||
default:
|
||||
return !types.IsInterface(n.Type())
|
||||
}
|
||||
}
|
||||
|
||||
// getPropType creates a propType for `node` based on its type.
|
||||
// propType.typ is always node.Type(). If node is function, then
|
||||
// propType.val is the underlying function; nil otherwise.
|
||||
func getPropType(node node, canon *typeutil.Map) propType {
|
||||
t := canonicalize(node.Type(), canon)
|
||||
if fn, ok := node.(function); ok {
|
||||
return propType{f: fn.f, typ: t}
|
||||
}
|
||||
return propType{f: nil, typ: t}
|
||||
}
|
||||
188
vendor/golang.org/x/tools/go/callgraph/vta/utils.go
generated
vendored
Normal file
188
vendor/golang.org/x/tools/go/callgraph/vta/utils.go
generated
vendored
Normal file
@@ -0,0 +1,188 @@
|
||||
// Copyright 2021 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package vta
|
||||
|
||||
import (
|
||||
"go/types"
|
||||
"iter"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/internal/typeparams"
|
||||
)
|
||||
|
||||
func canAlias(n1, n2 node) bool {
|
||||
return isReferenceNode(n1) && isReferenceNode(n2)
|
||||
}
|
||||
|
||||
func isReferenceNode(n node) bool {
|
||||
if _, ok := n.(nestedPtrInterface); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := n.(nestedPtrFunction); ok {
|
||||
return true
|
||||
}
|
||||
|
||||
if _, ok := types.Unalias(n.Type()).(*types.Pointer); ok {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// hasInFlow checks if a concrete type can flow to node `n`.
|
||||
// Returns yes iff the type of `n` satisfies one the following:
|
||||
// 1. is an interface
|
||||
// 2. is a (nested) pointer to interface (needed for, say,
|
||||
// slice elements of nested pointers to interface type)
|
||||
// 3. is a function type (needed for higher-order type flow)
|
||||
// 4. is a (nested) pointer to function (needed for, say,
|
||||
// slice elements of nested pointers to function type)
|
||||
// 5. is a global Recover or Panic node
|
||||
func hasInFlow(n node) bool {
|
||||
if _, ok := n.(panicArg); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := n.(recoverReturn); ok {
|
||||
return true
|
||||
}
|
||||
|
||||
t := n.Type()
|
||||
|
||||
if i := interfaceUnderPtr(t); i != nil {
|
||||
return true
|
||||
}
|
||||
if f := functionUnderPtr(t); f != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return types.IsInterface(t) || isFunction(t)
|
||||
}
|
||||
|
||||
func isFunction(t types.Type) bool {
|
||||
_, ok := t.Underlying().(*types.Signature)
|
||||
return ok
|
||||
}
|
||||
|
||||
// interfaceUnderPtr checks if type `t` is a potentially nested
|
||||
// pointer to interface and if yes, returns the interface type.
|
||||
// Otherwise, returns nil.
|
||||
func interfaceUnderPtr(t types.Type) types.Type {
|
||||
seen := make(map[types.Type]bool)
|
||||
var visit func(types.Type) types.Type
|
||||
visit = func(t types.Type) types.Type {
|
||||
if seen[t] {
|
||||
return nil
|
||||
}
|
||||
seen[t] = true
|
||||
|
||||
p, ok := t.Underlying().(*types.Pointer)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if types.IsInterface(p.Elem()) {
|
||||
return p.Elem()
|
||||
}
|
||||
|
||||
return visit(p.Elem())
|
||||
}
|
||||
return visit(t)
|
||||
}
|
||||
|
||||
// functionUnderPtr checks if type `t` is a potentially nested
|
||||
// pointer to function type and if yes, returns the function type.
|
||||
// Otherwise, returns nil.
|
||||
func functionUnderPtr(t types.Type) types.Type {
|
||||
seen := make(map[types.Type]bool)
|
||||
var visit func(types.Type) types.Type
|
||||
visit = func(t types.Type) types.Type {
|
||||
if seen[t] {
|
||||
return nil
|
||||
}
|
||||
seen[t] = true
|
||||
|
||||
p, ok := t.Underlying().(*types.Pointer)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if isFunction(p.Elem()) {
|
||||
return p.Elem()
|
||||
}
|
||||
|
||||
return visit(p.Elem())
|
||||
}
|
||||
return visit(t)
|
||||
}
|
||||
|
||||
// sliceArrayElem returns the element type of type `t` that is
|
||||
// expected to be a (pointer to) array, slice or string, consistent with
|
||||
// the ssa.Index and ssa.IndexAddr instructions. Panics otherwise.
|
||||
func sliceArrayElem(t types.Type) types.Type {
|
||||
switch u := t.Underlying().(type) {
|
||||
case *types.Pointer:
|
||||
switch e := u.Elem().Underlying().(type) {
|
||||
case *types.Array:
|
||||
return e.Elem()
|
||||
case *types.Interface:
|
||||
return sliceArrayElem(e) // e is a type param with matching element types.
|
||||
default:
|
||||
panic(t)
|
||||
}
|
||||
case *types.Array:
|
||||
return u.Elem()
|
||||
case *types.Slice:
|
||||
return u.Elem()
|
||||
case *types.Basic:
|
||||
return types.Typ[types.Byte]
|
||||
case *types.Interface: // type param.
|
||||
terms, err := typeparams.InterfaceTermSet(u)
|
||||
if err != nil || len(terms) == 0 {
|
||||
panic(t)
|
||||
}
|
||||
return sliceArrayElem(terms[0].Type()) // Element types must match.
|
||||
default:
|
||||
panic(t)
|
||||
}
|
||||
}
|
||||
|
||||
// siteCallees returns an iterator for the callees for call site `c`.
|
||||
func siteCallees(c ssa.CallInstruction, callees calleesFunc) iter.Seq[*ssa.Function] {
|
||||
return func(yield func(*ssa.Function) bool) {
|
||||
for _, callee := range callees(c) {
|
||||
if !yield(callee) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func canHaveMethods(t types.Type) bool {
|
||||
t = types.Unalias(t)
|
||||
if _, ok := t.(*types.Named); ok {
|
||||
return true
|
||||
}
|
||||
|
||||
u := t.Underlying()
|
||||
switch u.(type) {
|
||||
case *types.Interface, *types.Signature, *types.Struct:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// calls returns the set of call instructions in `f`.
|
||||
func calls(f *ssa.Function) []ssa.CallInstruction {
|
||||
var calls []ssa.CallInstruction
|
||||
for _, bl := range f.Blocks {
|
||||
for _, instr := range bl.Instrs {
|
||||
if c, ok := instr.(ssa.CallInstruction); ok {
|
||||
calls = append(calls, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
return calls
|
||||
}
|
||||
190
vendor/golang.org/x/tools/go/callgraph/vta/vta.go
generated
vendored
Normal file
190
vendor/golang.org/x/tools/go/callgraph/vta/vta.go
generated
vendored
Normal file
@@ -0,0 +1,190 @@
|
||||
// Copyright 2021 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package vta computes the call graph of a Go program using the Variable
|
||||
// Type Analysis (VTA) algorithm originally described in "Practical Virtual
|
||||
// Method Call Resolution for Java," Vijay Sundaresan, Laurie Hendren,
|
||||
// Chrislain Razafimahefa, Raja Vallée-Rai, Patrick Lam, Etienne Gagnon, and
|
||||
// Charles Godin.
|
||||
//
|
||||
// Note: this package is in experimental phase and its interface is
|
||||
// subject to change.
|
||||
// TODO(zpavlinovic): reiterate on documentation.
|
||||
//
|
||||
// The VTA algorithm overapproximates the set of types (and function literals)
|
||||
// a variable can take during runtime by building a global type propagation
|
||||
// graph and propagating types (and function literals) through the graph.
|
||||
//
|
||||
// A type propagation is a directed, labeled graph. A node can represent
|
||||
// one of the following:
|
||||
// - A field of a struct type.
|
||||
// - A local (SSA) variable of a method/function.
|
||||
// - All pointers to a non-interface type.
|
||||
// - The return value of a method.
|
||||
// - All elements in an array.
|
||||
// - All elements in a slice.
|
||||
// - All elements in a map.
|
||||
// - All elements in a channel.
|
||||
// - A global variable.
|
||||
//
|
||||
// In addition, the implementation used in this package introduces
|
||||
// a few Go specific kinds of nodes:
|
||||
// - (De)references of nested pointers to interfaces are modeled
|
||||
// as a unique nestedPtrInterface node in the type propagation graph.
|
||||
// - Each function literal is represented as a function node whose
|
||||
// internal value is the (SSA) representation of the function. This
|
||||
// is done to precisely infer flow of higher-order functions.
|
||||
//
|
||||
// Edges in the graph represent flow of types (and function literals) through
|
||||
// the program. That is, the model 1) typing constraints that are induced by
|
||||
// assignment statements or function and method calls and 2) higher-order flow
|
||||
// of functions in the program.
|
||||
//
|
||||
// The labeling function maps each node to a set of types and functions that
|
||||
// can intuitively reach the program construct the node represents. Initially,
|
||||
// every node is assigned a type corresponding to the program construct it
|
||||
// represents. Function nodes are also assigned the function they represent.
|
||||
// The labeling function then propagates types and function through the graph.
|
||||
//
|
||||
// The result of VTA is a type propagation graph in which each node is labeled
|
||||
// with a conservative overapproximation of the set of types (and functions)
|
||||
// it may have. This information is then used to construct the call graph.
|
||||
// For each unresolved call site, vta uses the set of types and functions
|
||||
// reaching the node representing the call site to create a set of callees.
|
||||
package vta
|
||||
|
||||
// TODO(zpavlinovic): update VTA for how it handles generic function bodies and instantiation wrappers.
|
||||
|
||||
import (
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/go/callgraph"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
)
|
||||
|
||||
// CallGraph uses the VTA algorithm to compute call graph for all functions
|
||||
// f:true in funcs. VTA refines the results of initial call graph and uses it
|
||||
// to establish interprocedural type flow. If initial is nil, VTA uses a more
|
||||
// efficient approach to construct a CHA call graph.
|
||||
//
|
||||
// The resulting graph does not have a root node.
|
||||
//
|
||||
// CallGraph does not make any assumptions on initial types global variables
|
||||
// and function/method inputs can have. CallGraph is then sound, modulo use of
|
||||
// reflection and unsafe, if the initial call graph is sound.
|
||||
func CallGraph(funcs map[*ssa.Function]bool, initial *callgraph.Graph) *callgraph.Graph {
|
||||
callees := makeCalleesFunc(funcs, initial)
|
||||
vtaG, canon := typePropGraph(funcs, callees)
|
||||
types := propagate(vtaG, canon)
|
||||
|
||||
c := &constructor{types: types, callees: callees, cache: make(methodCache)}
|
||||
return c.construct(funcs)
|
||||
}
|
||||
|
||||
// constructor type linearly traverses the input program
|
||||
// and constructs a callgraph based on the results of the
|
||||
// VTA type propagation phase.
|
||||
type constructor struct {
|
||||
types propTypeMap
|
||||
cache methodCache
|
||||
callees calleesFunc
|
||||
}
|
||||
|
||||
func (c *constructor) construct(funcs map[*ssa.Function]bool) *callgraph.Graph {
|
||||
cg := &callgraph.Graph{Nodes: make(map[*ssa.Function]*callgraph.Node)}
|
||||
for f, in := range funcs {
|
||||
if in {
|
||||
c.constrct(cg, f)
|
||||
}
|
||||
}
|
||||
return cg
|
||||
}
|
||||
|
||||
func (c *constructor) constrct(g *callgraph.Graph, f *ssa.Function) {
|
||||
caller := g.CreateNode(f)
|
||||
for _, call := range calls(f) {
|
||||
for _, c := range c.resolves(call) {
|
||||
callgraph.AddEdge(caller, call, g.CreateNode(c))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// resolves computes the set of functions to which VTA resolves `c`. The resolved
|
||||
// functions are intersected with functions to which `c.initial` resolves `c`.
|
||||
func (c *constructor) resolves(call ssa.CallInstruction) []*ssa.Function {
|
||||
cc := call.Common()
|
||||
if cc.StaticCallee() != nil {
|
||||
return []*ssa.Function{cc.StaticCallee()}
|
||||
}
|
||||
|
||||
// Skip builtins as they are not *ssa.Function.
|
||||
if _, ok := cc.Value.(*ssa.Builtin); ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Cover the case of dynamic higher-order and interface calls.
|
||||
var res []*ssa.Function
|
||||
resolved := resolve(call, c.types, c.cache)
|
||||
for f := range siteCallees(call, c.callees) {
|
||||
if _, ok := resolved[f]; ok {
|
||||
res = append(res, f)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// resolve returns a set of functions `c` resolves to based on the
|
||||
// type propagation results in `types`.
|
||||
func resolve(c ssa.CallInstruction, types propTypeMap, cache methodCache) map[*ssa.Function]empty {
|
||||
fns := make(map[*ssa.Function]empty)
|
||||
n := local{val: c.Common().Value}
|
||||
for p := range types.propTypes(n) {
|
||||
for _, f := range propFunc(p, c, cache) {
|
||||
fns[f] = empty{}
|
||||
}
|
||||
}
|
||||
return fns
|
||||
}
|
||||
|
||||
// propFunc returns the functions modeled with the propagation type `p`
|
||||
// assigned to call site `c`. If no such function exists, nil is returned.
|
||||
func propFunc(p propType, c ssa.CallInstruction, cache methodCache) []*ssa.Function {
|
||||
if p.f != nil {
|
||||
return []*ssa.Function{p.f}
|
||||
}
|
||||
|
||||
if c.Common().Method == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return cache.methods(p.typ, c.Common().Method.Name(), c.Parent().Prog)
|
||||
}
|
||||
|
||||
// methodCache serves as a type -> method name -> methods
|
||||
// cache when computing methods of a type using the
|
||||
// ssa.Program.MethodSets and ssa.Program.MethodValue
|
||||
// APIs. The cache is used to speed up querying of
|
||||
// methods of a type as the mentioned APIs are expensive.
|
||||
type methodCache map[types.Type]map[string][]*ssa.Function
|
||||
|
||||
// methods returns methods of a type `t` named `name`. First consults
|
||||
// `mc` and otherwise queries `prog` for the method. If no such method
|
||||
// exists, nil is returned.
|
||||
func (mc methodCache) methods(t types.Type, name string, prog *ssa.Program) []*ssa.Function {
|
||||
if ms, ok := mc[t]; ok {
|
||||
return ms[name]
|
||||
}
|
||||
|
||||
ms := make(map[string][]*ssa.Function)
|
||||
mset := prog.MethodSets.MethodSet(t)
|
||||
for i, n := 0, mset.Len(); i < n; i++ {
|
||||
// f can be nil when t is an interface or some
|
||||
// other type without any runtime methods.
|
||||
if f := prog.MethodValue(mset.At(i)); f != nil {
|
||||
ms[f.Name()] = append(ms[f.Name()], f)
|
||||
}
|
||||
}
|
||||
mc[t] = ms
|
||||
return ms[name]
|
||||
}
|
||||
236
vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go
generated
vendored
Normal file
236
vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go
generated
vendored
Normal file
@@ -0,0 +1,236 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package gcexportdata provides functions for reading and writing
|
||||
// export data, which is a serialized description of the API of a Go
|
||||
// package including the names, kinds, types, and locations of all
|
||||
// exported declarations.
|
||||
//
|
||||
// The standard Go compiler (cmd/compile) writes an export data file
|
||||
// for each package it compiles, which it later reads when compiling
|
||||
// packages that import the earlier one. The compiler must thus
|
||||
// contain logic to both write and read export data.
|
||||
// (See the "Export" section in the cmd/compile/README file.)
|
||||
//
|
||||
// The [Read] function in this package can read files produced by the
|
||||
// compiler, producing [go/types] data structures. As a matter of
|
||||
// policy, Read supports export data files produced by only the last
|
||||
// two Go releases plus tip; see https://go.dev/issue/68898. The
|
||||
// export data files produced by the compiler contain additional
|
||||
// details related to generics, inlining, and other optimizations that
|
||||
// cannot be decoded by the [Read] function.
|
||||
//
|
||||
// In files written by the compiler, the export data is not at the
|
||||
// start of the file. Before calling Read, use [NewReader] to locate
|
||||
// the desired portion of the file.
|
||||
//
|
||||
// The [Write] function in this package encodes the exported API of a
|
||||
// Go package ([types.Package]) as a file. Such files can be later
|
||||
// decoded by Read, but cannot be consumed by the compiler.
|
||||
//
|
||||
// # Future changes
|
||||
//
|
||||
// Although Read supports the formats written by both Write and the
|
||||
// compiler, the two are quite different, and there is an open
|
||||
// proposal (https://go.dev/issue/69491) to separate these APIs.
|
||||
//
|
||||
// Under that proposal, this package would ultimately provide only the
|
||||
// Read operation for compiler export data, which must be defined in
|
||||
// this module (golang.org/x/tools), not in the standard library, to
|
||||
// avoid version skew for developer tools that need to read compiler
|
||||
// export data both before and after a Go release, such as from Go
|
||||
// 1.23 to Go 1.24. Because this package lives in the tools module,
|
||||
// clients can update their version of the module some time before the
|
||||
// Go 1.24 release and rebuild and redeploy their tools, which will
|
||||
// then be able to consume both Go 1.23 and Go 1.24 export data files,
|
||||
// so they will work before and after the Go update. (See discussion
|
||||
// at https://go.dev/issue/15651.)
|
||||
//
|
||||
// The operations to import and export [go/types] data structures
|
||||
// would be defined in the go/types package as Import and Export.
|
||||
// [Write] would (eventually) delegate to Export,
|
||||
// and [Read], when it detects a file produced by Export,
|
||||
// would delegate to Import.
|
||||
//
|
||||
// # Deprecations
|
||||
//
|
||||
// The [NewImporter] and [Find] functions are deprecated and should
|
||||
// not be used in new code. The [WriteBundle] and [ReadBundle]
|
||||
// functions are experimental, and there is an open proposal to
|
||||
// deprecate them (https://go.dev/issue/69573).
|
||||
package gcexportdata
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"io"
|
||||
"os/exec"
|
||||
|
||||
"golang.org/x/tools/internal/gcimporter"
|
||||
)
|
||||
|
||||
// Find returns the name of an object (.o) or archive (.a) file
|
||||
// containing type information for the specified import path,
|
||||
// using the go command.
|
||||
// If no file was found, an empty filename is returned.
|
||||
//
|
||||
// A relative srcDir is interpreted relative to the current working directory.
|
||||
//
|
||||
// Find also returns the package's resolved (canonical) import path,
|
||||
// reflecting the effects of srcDir and vendoring on importPath.
|
||||
//
|
||||
// Deprecated: Use the higher-level API in golang.org/x/tools/go/packages,
|
||||
// which is more efficient.
|
||||
func Find(importPath, srcDir string) (filename, path string) {
|
||||
cmd := exec.Command("go", "list", "-json", "-export", "--", importPath)
|
||||
cmd.Dir = srcDir
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", ""
|
||||
}
|
||||
var data struct {
|
||||
ImportPath string
|
||||
Export string
|
||||
}
|
||||
json.Unmarshal(out, &data)
|
||||
return data.Export, data.ImportPath
|
||||
}
|
||||
|
||||
// NewReader returns a reader for the export data section of an object
|
||||
// (.o) or archive (.a) file read from r. The new reader may provide
|
||||
// additional trailing data beyond the end of the export data.
|
||||
func NewReader(r io.Reader) (io.Reader, error) {
|
||||
buf := bufio.NewReader(r)
|
||||
size, err := gcimporter.FindExportData(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We were given an archive and found the __.PKGDEF in it.
|
||||
// This tells us the size of the export data, and we don't
|
||||
// need to return the entire file.
|
||||
return &io.LimitedReader{
|
||||
R: buf,
|
||||
N: size,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// readAll works the same way as io.ReadAll, but avoids allocations and copies
|
||||
// by preallocating a byte slice of the necessary size if the size is known up
|
||||
// front. This is always possible when the input is an archive. In that case,
|
||||
// NewReader will return the known size using an io.LimitedReader.
|
||||
func readAll(r io.Reader) ([]byte, error) {
|
||||
if lr, ok := r.(*io.LimitedReader); ok {
|
||||
data := make([]byte, lr.N)
|
||||
_, err := io.ReadFull(lr, data)
|
||||
return data, err
|
||||
}
|
||||
return io.ReadAll(r)
|
||||
}
|
||||
|
||||
// Read reads export data from in, decodes it, and returns type
|
||||
// information for the package.
|
||||
//
|
||||
// Read is capable of reading export data produced by [Write] at the
|
||||
// same source code version, or by the last two Go releases (plus tip)
|
||||
// of the standard Go compiler. Reading files from older compilers may
|
||||
// produce an error.
|
||||
//
|
||||
// The package path (effectively its linker symbol prefix) is
|
||||
// specified by path, since unlike the package name, this information
|
||||
// may not be recorded in the export data.
|
||||
//
|
||||
// File position information is added to fset.
|
||||
//
|
||||
// Read may inspect and add to the imports map to ensure that references
|
||||
// within the export data to other packages are consistent. The caller
|
||||
// must ensure that imports[path] does not exist, or exists but is
|
||||
// incomplete (see types.Package.Complete), and Read inserts the
|
||||
// resulting package into this map entry.
|
||||
//
|
||||
// On return, the state of the reader is undefined.
|
||||
func Read(in io.Reader, fset *token.FileSet, imports map[string]*types.Package, path string) (*types.Package, error) {
|
||||
data, err := readAll(in)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading export data for %q: %v", path, err)
|
||||
}
|
||||
|
||||
if bytes.HasPrefix(data, []byte("!<arch>")) {
|
||||
return nil, fmt.Errorf("can't read export data for %q directly from an archive file (call gcexportdata.NewReader first to extract export data)", path)
|
||||
}
|
||||
|
||||
// The indexed export format starts with an 'i'; the older
|
||||
// binary export format starts with a 'c', 'd', or 'v'
|
||||
// (from "version"). Select appropriate importer.
|
||||
if len(data) > 0 {
|
||||
switch data[0] {
|
||||
case 'v', 'c', 'd':
|
||||
// binary, produced by cmd/compile till go1.10
|
||||
return nil, fmt.Errorf("binary (%c) import format is no longer supported", data[0])
|
||||
|
||||
case 'i':
|
||||
// indexed, produced by cmd/compile till go1.19,
|
||||
// and also by [Write].
|
||||
//
|
||||
// If proposal #69491 is accepted, go/types
|
||||
// serialization will be implemented by
|
||||
// types.Export, to which Write would eventually
|
||||
// delegate (explicitly dropping any pretence at
|
||||
// inter-version Write-Read compatibility).
|
||||
// This [Read] function would delegate to types.Import
|
||||
// when it detects that the file was produced by Export.
|
||||
_, pkg, err := gcimporter.IImportData(fset, imports, data[1:], path)
|
||||
return pkg, err
|
||||
|
||||
case 'u':
|
||||
// unified, produced by cmd/compile since go1.20
|
||||
_, pkg, err := gcimporter.UImportData(fset, imports, data[1:], path)
|
||||
return pkg, err
|
||||
|
||||
default:
|
||||
l := min(len(data), 10)
|
||||
return nil, fmt.Errorf("unexpected export data with prefix %q for path %s", string(data[:l]), path)
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("empty export data for %s", path)
|
||||
}
|
||||
|
||||
// Write writes encoded type information for the specified package to out.
|
||||
// The FileSet provides file position information for named objects.
|
||||
func Write(out io.Writer, fset *token.FileSet, pkg *types.Package) error {
|
||||
if _, err := io.WriteString(out, "i"); err != nil {
|
||||
return err
|
||||
}
|
||||
return gcimporter.IExportData(out, fset, pkg)
|
||||
}
|
||||
|
||||
// ReadBundle reads an export bundle from in, decodes it, and returns type
|
||||
// information for the packages.
|
||||
// File position information is added to fset.
|
||||
//
|
||||
// ReadBundle may inspect and add to the imports map to ensure that references
|
||||
// within the export bundle to other packages are consistent.
|
||||
//
|
||||
// On return, the state of the reader is undefined.
|
||||
//
|
||||
// Experimental: This API is experimental and may change in the future.
|
||||
func ReadBundle(in io.Reader, fset *token.FileSet, imports map[string]*types.Package) ([]*types.Package, error) {
|
||||
data, err := readAll(in)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading export bundle: %v", err)
|
||||
}
|
||||
return gcimporter.IImportBundle(fset, imports, data)
|
||||
}
|
||||
|
||||
// WriteBundle writes encoded type information for the specified packages to out.
|
||||
// The FileSet provides file position information for named objects.
|
||||
//
|
||||
// Experimental: This API is experimental and may change in the future.
|
||||
func WriteBundle(out io.Writer, fset *token.FileSet, pkgs []*types.Package) error {
|
||||
return gcimporter.IExportBundle(out, fset, pkgs)
|
||||
}
|
||||
75
vendor/golang.org/x/tools/go/gcexportdata/importer.go
generated
vendored
Normal file
75
vendor/golang.org/x/tools/go/gcexportdata/importer.go
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gcexportdata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"os"
|
||||
)
|
||||
|
||||
// NewImporter returns a new instance of the types.Importer interface
|
||||
// that reads type information from export data files written by gc.
|
||||
// The Importer also satisfies types.ImporterFrom.
|
||||
//
|
||||
// Export data files are located using "go build" workspace conventions
|
||||
// and the build.Default context.
|
||||
//
|
||||
// Use this importer instead of go/importer.For("gc", ...) to avoid the
|
||||
// version-skew problems described in the documentation of this package,
|
||||
// or to control the FileSet or access the imports map populated during
|
||||
// package loading.
|
||||
//
|
||||
// Deprecated: Use the higher-level API in golang.org/x/tools/go/packages,
|
||||
// which is more efficient.
|
||||
func NewImporter(fset *token.FileSet, imports map[string]*types.Package) types.ImporterFrom {
|
||||
return importer{fset, imports}
|
||||
}
|
||||
|
||||
type importer struct {
|
||||
fset *token.FileSet
|
||||
imports map[string]*types.Package
|
||||
}
|
||||
|
||||
func (imp importer) Import(importPath string) (*types.Package, error) {
|
||||
return imp.ImportFrom(importPath, "", 0)
|
||||
}
|
||||
|
||||
func (imp importer) ImportFrom(importPath, srcDir string, mode types.ImportMode) (_ *types.Package, err error) {
|
||||
filename, path := Find(importPath, srcDir)
|
||||
if filename == "" {
|
||||
if importPath == "unsafe" {
|
||||
// Even for unsafe, call Find first in case
|
||||
// the package was vendored.
|
||||
return types.Unsafe, nil
|
||||
}
|
||||
return nil, fmt.Errorf("can't find import: %s", importPath)
|
||||
}
|
||||
|
||||
if pkg, ok := imp.imports[path]; ok && pkg.Complete() {
|
||||
return pkg, nil // cache hit
|
||||
}
|
||||
|
||||
// open file
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
f.Close()
|
||||
if err != nil {
|
||||
// add file name to error
|
||||
err = fmt.Errorf("reading export data: %s: %v", filename, err)
|
||||
}
|
||||
}()
|
||||
|
||||
r, err := NewReader(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return Read(r, imp.fset, imp.imports, path)
|
||||
}
|
||||
219
vendor/golang.org/x/tools/go/internal/cgo/cgo.go
generated
vendored
Normal file
219
vendor/golang.org/x/tools/go/internal/cgo/cgo.go
generated
vendored
Normal file
@@ -0,0 +1,219 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package cgo handles cgo preprocessing of files containing `import "C"`.
|
||||
//
|
||||
// DESIGN
|
||||
//
|
||||
// The approach taken is to run the cgo processor on the package's
|
||||
// CgoFiles and parse the output, faking the filenames of the
|
||||
// resulting ASTs so that the synthetic file containing the C types is
|
||||
// called "C" (e.g. "~/go/src/net/C") and the preprocessed files
|
||||
// have their original names (e.g. "~/go/src/net/cgo_unix.go"),
|
||||
// not the names of the actual temporary files.
|
||||
//
|
||||
// The advantage of this approach is its fidelity to 'go build'. The
|
||||
// downside is that the token.Position.Offset for each AST node is
|
||||
// incorrect, being an offset within the temporary file. Line numbers
|
||||
// should still be correct because of the //line comments.
|
||||
//
|
||||
// The logic of this file is mostly plundered from the 'go build'
|
||||
// tool, which also invokes the cgo preprocessor.
|
||||
//
|
||||
//
|
||||
// REJECTED ALTERNATIVE
|
||||
//
|
||||
// An alternative approach that we explored is to extend go/types'
|
||||
// Importer mechanism to provide the identity of the importing package
|
||||
// so that each time `import "C"` appears it resolves to a different
|
||||
// synthetic package containing just the objects needed in that case.
|
||||
// The loader would invoke cgo but parse only the cgo_types.go file
|
||||
// defining the package-level objects, discarding the other files
|
||||
// resulting from preprocessing.
|
||||
//
|
||||
// The benefit of this approach would have been that source-level
|
||||
// syntax information would correspond exactly to the original cgo
|
||||
// file, with no preprocessing involved, making source tools like
|
||||
// godoc, guru, and eg happy. However, the approach was rejected
|
||||
// due to the additional complexity it would impose on go/types. (It
|
||||
// made for a beautiful demo, though.)
|
||||
//
|
||||
// cgo files, despite their *.go extension, are not legal Go source
|
||||
// files per the specification since they may refer to unexported
|
||||
// members of package "C" such as C.int. Also, a function such as
|
||||
// C.getpwent has in effect two types, one matching its C type and one
|
||||
// which additionally returns (errno C.int). The cgo preprocessor
|
||||
// uses name mangling to distinguish these two functions in the
|
||||
// processed code, but go/types would need to duplicate this logic in
|
||||
// its handling of function calls, analogous to the treatment of map
|
||||
// lookups in which y=m[k] and y,ok=m[k] are both legal.
|
||||
|
||||
package cgo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ProcessFiles invokes the cgo preprocessor on bp.CgoFiles, parses
|
||||
// the output and returns the resulting ASTs.
|
||||
func ProcessFiles(bp *build.Package, fset *token.FileSet, DisplayPath func(path string) string, mode parser.Mode) ([]*ast.File, error) {
|
||||
tmpdir, err := os.MkdirTemp("", strings.Replace(bp.ImportPath, "/", "_", -1)+"_C")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
pkgdir := bp.Dir
|
||||
if DisplayPath != nil {
|
||||
pkgdir = DisplayPath(pkgdir)
|
||||
}
|
||||
|
||||
cgoFiles, cgoDisplayFiles, err := Run(bp, pkgdir, tmpdir, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var files []*ast.File
|
||||
for i := range cgoFiles {
|
||||
rd, err := os.Open(cgoFiles[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
display := filepath.Join(bp.Dir, cgoDisplayFiles[i])
|
||||
f, err := parser.ParseFile(fset, display, rd, mode)
|
||||
rd.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
files = append(files, f)
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
var cgoRe = regexp.MustCompile(`[/\\:]`)
|
||||
|
||||
// Run invokes the cgo preprocessor on bp.CgoFiles and returns two
|
||||
// lists of files: the resulting processed files (in temporary
|
||||
// directory tmpdir) and the corresponding names of the unprocessed files.
|
||||
//
|
||||
// Run is adapted from (*builder).cgo in
|
||||
// $GOROOT/src/cmd/go/build.go, but these features are unsupported:
|
||||
// Objective C, CGOPKGPATH, CGO_FLAGS.
|
||||
//
|
||||
// If useabs is set to true, absolute paths of the bp.CgoFiles will be passed in
|
||||
// to the cgo preprocessor. This in turn will set the // line comments
|
||||
// referring to those files to use absolute paths. This is needed for
|
||||
// go/packages using the legacy go list support so it is able to find
|
||||
// the original files.
|
||||
func Run(bp *build.Package, pkgdir, tmpdir string, useabs bool) (files, displayFiles []string, err error) {
|
||||
cgoCPPFLAGS, _, _, _ := cflags(bp, true)
|
||||
_, cgoexeCFLAGS, _, _ := cflags(bp, false)
|
||||
|
||||
if len(bp.CgoPkgConfig) > 0 {
|
||||
pcCFLAGS, err := pkgConfigFlags(bp)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
cgoCPPFLAGS = append(cgoCPPFLAGS, pcCFLAGS...)
|
||||
}
|
||||
|
||||
// Allows including _cgo_export.h from .[ch] files in the package.
|
||||
cgoCPPFLAGS = append(cgoCPPFLAGS, "-I", tmpdir)
|
||||
|
||||
// _cgo_gotypes.go (displayed "C") contains the type definitions.
|
||||
files = append(files, filepath.Join(tmpdir, "_cgo_gotypes.go"))
|
||||
displayFiles = append(displayFiles, "C")
|
||||
for _, fn := range bp.CgoFiles {
|
||||
// "foo.cgo1.go" (displayed "foo.go") is the processed Go source.
|
||||
f := cgoRe.ReplaceAllString(fn[:len(fn)-len("go")], "_")
|
||||
files = append(files, filepath.Join(tmpdir, f+"cgo1.go"))
|
||||
displayFiles = append(displayFiles, fn)
|
||||
}
|
||||
|
||||
var cgoflags []string
|
||||
if bp.Goroot && bp.ImportPath == "runtime/cgo" {
|
||||
cgoflags = append(cgoflags, "-import_runtime_cgo=false")
|
||||
}
|
||||
if bp.Goroot && bp.ImportPath == "runtime/race" || bp.ImportPath == "runtime/cgo" {
|
||||
cgoflags = append(cgoflags, "-import_syscall=false")
|
||||
}
|
||||
|
||||
var cgoFiles []string = bp.CgoFiles
|
||||
if useabs {
|
||||
cgoFiles = make([]string, len(bp.CgoFiles))
|
||||
for i := range cgoFiles {
|
||||
cgoFiles[i] = filepath.Join(pkgdir, bp.CgoFiles[i])
|
||||
}
|
||||
}
|
||||
|
||||
args := stringList(
|
||||
"go", "tool", "cgo", "-objdir", tmpdir, cgoflags, "--",
|
||||
cgoCPPFLAGS, cgoexeCFLAGS, cgoFiles,
|
||||
)
|
||||
if false {
|
||||
log.Printf("Running cgo for package %q: %s (dir=%s)", bp.ImportPath, args, pkgdir)
|
||||
}
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
cmd.Dir = pkgdir
|
||||
cmd.Env = append(os.Environ(), "PWD="+pkgdir)
|
||||
cmd.Stdout = os.Stderr
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return nil, nil, fmt.Errorf("cgo failed: %s: %s", args, err)
|
||||
}
|
||||
|
||||
return files, displayFiles, nil
|
||||
}
|
||||
|
||||
// -- unmodified from 'go build' ---------------------------------------
|
||||
|
||||
// Return the flags to use when invoking the C or C++ compilers, or cgo.
|
||||
func cflags(p *build.Package, def bool) (cppflags, cflags, cxxflags, ldflags []string) {
|
||||
var defaults string
|
||||
if def {
|
||||
defaults = "-g -O2"
|
||||
}
|
||||
|
||||
cppflags = stringList(envList("CGO_CPPFLAGS", ""), p.CgoCPPFLAGS)
|
||||
cflags = stringList(envList("CGO_CFLAGS", defaults), p.CgoCFLAGS)
|
||||
cxxflags = stringList(envList("CGO_CXXFLAGS", defaults), p.CgoCXXFLAGS)
|
||||
ldflags = stringList(envList("CGO_LDFLAGS", defaults), p.CgoLDFLAGS)
|
||||
return
|
||||
}
|
||||
|
||||
// envList returns the value of the given environment variable broken
|
||||
// into fields, using the default value when the variable is empty.
|
||||
func envList(key, def string) []string {
|
||||
v := os.Getenv(key)
|
||||
if v == "" {
|
||||
v = def
|
||||
}
|
||||
return strings.Fields(v)
|
||||
}
|
||||
|
||||
// stringList's arguments should be a sequence of string or []string values.
|
||||
// stringList flattens them into a single []string.
|
||||
func stringList(args ...any) []string {
|
||||
var x []string
|
||||
for _, arg := range args {
|
||||
switch arg := arg.(type) {
|
||||
case []string:
|
||||
x = append(x, arg...)
|
||||
case string:
|
||||
x = append(x, arg)
|
||||
default:
|
||||
panic("stringList: invalid argument")
|
||||
}
|
||||
}
|
||||
return x
|
||||
}
|
||||
42
vendor/golang.org/x/tools/go/internal/cgo/cgo_pkgconfig.go
generated
vendored
Normal file
42
vendor/golang.org/x/tools/go/internal/cgo/cgo_pkgconfig.go
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cgo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// pkgConfig runs pkg-config with the specified arguments and returns the flags it prints.
|
||||
func pkgConfig(mode string, pkgs []string) (flags []string, err error) {
|
||||
cmd := exec.Command("pkg-config", append([]string{mode}, pkgs...)...)
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
s := fmt.Sprintf("%s failed: %v", strings.Join(cmd.Args, " "), err)
|
||||
if len(out) > 0 {
|
||||
s = fmt.Sprintf("%s: %s", s, out)
|
||||
}
|
||||
if err, ok := err.(*exec.ExitError); ok && len(err.Stderr) > 0 {
|
||||
s = fmt.Sprintf("%s\nstderr:\n%s", s, err.Stderr)
|
||||
}
|
||||
return nil, errors.New(s)
|
||||
}
|
||||
if len(out) > 0 {
|
||||
flags = strings.Fields(string(out))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// pkgConfigFlags calls pkg-config if needed and returns the cflags
|
||||
// needed to build the package.
|
||||
func pkgConfigFlags(p *build.Package) (cflags []string, err error) {
|
||||
if len(p.CgoPkgConfig) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return pkgConfig("--cflags", p.CgoPkgConfig)
|
||||
}
|
||||
202
vendor/golang.org/x/tools/go/loader/doc.go
generated
vendored
Normal file
202
vendor/golang.org/x/tools/go/loader/doc.go
generated
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package loader loads a complete Go program from source code, parsing
|
||||
// and type-checking the initial packages plus their transitive closure
|
||||
// of dependencies. The ASTs and the derived facts are retained for
|
||||
// later use.
|
||||
//
|
||||
// Deprecated: This is an older API and does not have support
|
||||
// for modules. Use golang.org/x/tools/go/packages instead.
|
||||
//
|
||||
// The package defines two primary types: Config, which specifies a
|
||||
// set of initial packages to load and various other options; and
|
||||
// Program, which is the result of successfully loading the packages
|
||||
// specified by a configuration.
|
||||
//
|
||||
// The configuration can be set directly, but *Config provides various
|
||||
// convenience methods to simplify the common cases, each of which can
|
||||
// be called any number of times. Finally, these are followed by a
|
||||
// call to Load() to actually load and type-check the program.
|
||||
//
|
||||
// var conf loader.Config
|
||||
//
|
||||
// // Use the command-line arguments to specify
|
||||
// // a set of initial packages to load from source.
|
||||
// // See FromArgsUsage for help.
|
||||
// rest, err := conf.FromArgs(os.Args[1:], wantTests)
|
||||
//
|
||||
// // Parse the specified files and create an ad hoc package with path "foo".
|
||||
// // All files must have the same 'package' declaration.
|
||||
// conf.CreateFromFilenames("foo", "foo.go", "bar.go")
|
||||
//
|
||||
// // Create an ad hoc package with path "foo" from
|
||||
// // the specified already-parsed files.
|
||||
// // All ASTs must have the same 'package' declaration.
|
||||
// conf.CreateFromFiles("foo", parsedFiles)
|
||||
//
|
||||
// // Add "runtime" to the set of packages to be loaded.
|
||||
// conf.Import("runtime")
|
||||
//
|
||||
// // Adds "fmt" and "fmt_test" to the set of packages
|
||||
// // to be loaded. "fmt" will include *_test.go files.
|
||||
// conf.ImportWithTests("fmt")
|
||||
//
|
||||
// // Finally, load all the packages specified by the configuration.
|
||||
// prog, err := conf.Load()
|
||||
//
|
||||
// See examples_test.go for examples of API usage.
|
||||
//
|
||||
// # CONCEPTS AND TERMINOLOGY
|
||||
//
|
||||
// The WORKSPACE is the set of packages accessible to the loader. The
|
||||
// workspace is defined by Config.Build, a *build.Context. The
|
||||
// default context treats subdirectories of $GOROOT and $GOPATH as
|
||||
// packages, but this behavior may be overridden.
|
||||
//
|
||||
// An AD HOC package is one specified as a set of source files on the
|
||||
// command line. In the simplest case, it may consist of a single file
|
||||
// such as $GOROOT/src/net/http/triv.go.
|
||||
//
|
||||
// EXTERNAL TEST packages are those comprised of a set of *_test.go
|
||||
// files all with the same 'package foo_test' declaration, all in the
|
||||
// same directory. (go/build.Package calls these files XTestFiles.)
|
||||
//
|
||||
// An IMPORTABLE package is one that can be referred to by some import
|
||||
// spec. Every importable package is uniquely identified by its
|
||||
// PACKAGE PATH or just PATH, a string such as "fmt", "encoding/json",
|
||||
// or "cmd/vendor/golang.org/x/arch/x86/x86asm". A package path
|
||||
// typically denotes a subdirectory of the workspace.
|
||||
//
|
||||
// An import declaration uses an IMPORT PATH to refer to a package.
|
||||
// Most import declarations use the package path as the import path.
|
||||
//
|
||||
// Due to VENDORING (https://golang.org/s/go15vendor), the
|
||||
// interpretation of an import path may depend on the directory in which
|
||||
// it appears. To resolve an import path to a package path, go/build
|
||||
// must search the enclosing directories for a subdirectory named
|
||||
// "vendor".
|
||||
//
|
||||
// ad hoc packages and external test packages are NON-IMPORTABLE. The
|
||||
// path of an ad hoc package is inferred from the package
|
||||
// declarations of its files and is therefore not a unique package key.
|
||||
// For example, Config.CreatePkgs may specify two initial ad hoc
|
||||
// packages, both with path "main".
|
||||
//
|
||||
// An AUGMENTED package is an importable package P plus all the
|
||||
// *_test.go files with same 'package foo' declaration as P.
|
||||
// (go/build.Package calls these files TestFiles.)
|
||||
//
|
||||
// The INITIAL packages are those specified in the configuration. A
|
||||
// DEPENDENCY is a package loaded to satisfy an import in an initial
|
||||
// package or another dependency.
|
||||
package loader
|
||||
|
||||
// IMPLEMENTATION NOTES
|
||||
//
|
||||
// 'go test', in-package test files, and import cycles
|
||||
// ---------------------------------------------------
|
||||
//
|
||||
// An external test package may depend upon members of the augmented
|
||||
// package that are not in the unaugmented package, such as functions
|
||||
// that expose internals. (See bufio/export_test.go for an example.)
|
||||
// So, the loader must ensure that for each external test package
|
||||
// it loads, it also augments the corresponding non-test package.
|
||||
//
|
||||
// The import graph over n unaugmented packages must be acyclic; the
|
||||
// import graph over n-1 unaugmented packages plus one augmented
|
||||
// package must also be acyclic. ('go test' relies on this.) But the
|
||||
// import graph over n augmented packages may contain cycles.
|
||||
//
|
||||
// First, all the (unaugmented) non-test packages and their
|
||||
// dependencies are imported in the usual way; the loader reports an
|
||||
// error if it detects an import cycle.
|
||||
//
|
||||
// Then, each package P for which testing is desired is augmented by
|
||||
// the list P' of its in-package test files, by calling
|
||||
// (*types.Checker).Files. This arrangement ensures that P' may
|
||||
// reference definitions within P, but P may not reference definitions
|
||||
// within P'. Furthermore, P' may import any other package, including
|
||||
// ones that depend upon P, without an import cycle error.
|
||||
//
|
||||
// Consider two packages A and B, both of which have lists of
|
||||
// in-package test files we'll call A' and B', and which have the
|
||||
// following import graph edges:
|
||||
// B imports A
|
||||
// B' imports A
|
||||
// A' imports B
|
||||
// This last edge would be expected to create an error were it not
|
||||
// for the special type-checking discipline above.
|
||||
// Cycles of size greater than two are possible. For example:
|
||||
// compress/bzip2/bzip2_test.go (package bzip2) imports "io/ioutil"
|
||||
// io/ioutil/tempfile_test.go (package ioutil) imports "regexp"
|
||||
// regexp/exec_test.go (package regexp) imports "compress/bzip2"
|
||||
//
|
||||
//
|
||||
// Concurrency
|
||||
// -----------
|
||||
//
|
||||
// Let us define the import dependency graph as follows. Each node is a
|
||||
// list of files passed to (Checker).Files at once. Many of these lists
|
||||
// are the production code of an importable Go package, so those nodes
|
||||
// are labelled by the package's path. The remaining nodes are
|
||||
// ad hoc packages and lists of in-package *_test.go files that augment
|
||||
// an importable package; those nodes have no label.
|
||||
//
|
||||
// The edges of the graph represent import statements appearing within a
|
||||
// file. An edge connects a node (a list of files) to the node it
|
||||
// imports, which is importable and thus always labelled.
|
||||
//
|
||||
// Loading is controlled by this dependency graph.
|
||||
//
|
||||
// To reduce I/O latency, we start loading a package's dependencies
|
||||
// asynchronously as soon as we've parsed its files and enumerated its
|
||||
// imports (scanImports). This performs a preorder traversal of the
|
||||
// import dependency graph.
|
||||
//
|
||||
// To exploit hardware parallelism, we type-check unrelated packages in
|
||||
// parallel, where "unrelated" means not ordered by the partial order of
|
||||
// the import dependency graph.
|
||||
//
|
||||
// We use a concurrency-safe non-blocking cache (importer.imported) to
|
||||
// record the results of type-checking, whether success or failure. An
|
||||
// entry is created in this cache by startLoad the first time the
|
||||
// package is imported. The first goroutine to request an entry becomes
|
||||
// responsible for completing the task and broadcasting completion to
|
||||
// subsequent requesters, which block until then.
|
||||
//
|
||||
// Type checking occurs in (parallel) postorder: we cannot type-check a
|
||||
// set of files until we have loaded and type-checked all of their
|
||||
// immediate dependencies (and thus all of their transitive
|
||||
// dependencies). If the input were guaranteed free of import cycles,
|
||||
// this would be trivial: we could simply wait for completion of the
|
||||
// dependencies and then invoke the typechecker.
|
||||
//
|
||||
// But as we saw in the 'go test' section above, some cycles in the
|
||||
// import graph over packages are actually legal, so long as the
|
||||
// cycle-forming edge originates in the in-package test files that
|
||||
// augment the package. This explains why the nodes of the import
|
||||
// dependency graph are not packages, but lists of files: the unlabelled
|
||||
// nodes avoid the cycles. Consider packages A and B where B imports A
|
||||
// and A's in-package tests AT import B. The naively constructed import
|
||||
// graph over packages would contain a cycle (A+AT) --> B --> (A+AT) but
|
||||
// the graph over lists of files is AT --> B --> A, where AT is an
|
||||
// unlabelled node.
|
||||
//
|
||||
// Awaiting completion of the dependencies in a cyclic graph would
|
||||
// deadlock, so we must materialize the import dependency graph (as
|
||||
// importer.graph) and check whether each import edge forms a cycle. If
|
||||
// x imports y, and the graph already contains a path from y to x, then
|
||||
// there is an import cycle, in which case the processing of x must not
|
||||
// wait for the completion of processing of y.
|
||||
//
|
||||
// When the type-checker makes a callback (doImport) to the loader for a
|
||||
// given import edge, there are two possible cases. In the normal case,
|
||||
// the dependency has already been completely type-checked; doImport
|
||||
// does a cache lookup and returns it. In the cyclic case, the entry in
|
||||
// the cache is still necessarily incomplete, indicating a cycle. We
|
||||
// perform the cycle check again to obtain the error message, and return
|
||||
// the error.
|
||||
//
|
||||
// The result of using concurrency is about a 2.5x speedup for stdlib_test.
|
||||
1059
vendor/golang.org/x/tools/go/loader/loader.go
generated
vendored
Normal file
1059
vendor/golang.org/x/tools/go/loader/loader.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
123
vendor/golang.org/x/tools/go/loader/util.go
generated
vendored
Normal file
123
vendor/golang.org/x/tools/go/loader/util.go
generated
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package loader
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/tools/go/buildutil"
|
||||
)
|
||||
|
||||
// We use a counting semaphore to limit
|
||||
// the number of parallel I/O calls per process.
|
||||
var ioLimit = make(chan bool, 10)
|
||||
|
||||
// parseFiles parses the Go source files within directory dir and
|
||||
// returns the ASTs of the ones that could be at least partially parsed,
|
||||
// along with a list of I/O and parse errors encountered.
|
||||
//
|
||||
// I/O is done via ctxt, which may specify a virtual file system.
|
||||
// displayPath is used to transform the filenames attached to the ASTs.
|
||||
func parseFiles(fset *token.FileSet, ctxt *build.Context, displayPath func(string) string, dir string, files []string, mode parser.Mode) ([]*ast.File, []error) {
|
||||
if displayPath == nil {
|
||||
displayPath = func(path string) string { return path }
|
||||
}
|
||||
var wg sync.WaitGroup
|
||||
n := len(files)
|
||||
parsed := make([]*ast.File, n)
|
||||
errors := make([]error, n)
|
||||
for i, file := range files {
|
||||
if !buildutil.IsAbsPath(ctxt, file) {
|
||||
file = buildutil.JoinPath(ctxt, dir, file)
|
||||
}
|
||||
wg.Add(1)
|
||||
go func(i int, file string) {
|
||||
ioLimit <- true // wait
|
||||
defer func() {
|
||||
wg.Done()
|
||||
<-ioLimit // signal
|
||||
}()
|
||||
var rd io.ReadCloser
|
||||
var err error
|
||||
if ctxt.OpenFile != nil {
|
||||
rd, err = ctxt.OpenFile(file)
|
||||
} else {
|
||||
rd, err = os.Open(file)
|
||||
}
|
||||
if err != nil {
|
||||
errors[i] = err // open failed
|
||||
return
|
||||
}
|
||||
|
||||
// ParseFile may return both an AST and an error.
|
||||
parsed[i], errors[i] = parser.ParseFile(fset, displayPath(file), rd, mode)
|
||||
rd.Close()
|
||||
}(i, file)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
// Eliminate nils, preserving order.
|
||||
var o int
|
||||
for _, f := range parsed {
|
||||
if f != nil {
|
||||
parsed[o] = f
|
||||
o++
|
||||
}
|
||||
}
|
||||
parsed = parsed[:o]
|
||||
|
||||
o = 0
|
||||
for _, err := range errors {
|
||||
if err != nil {
|
||||
errors[o] = err
|
||||
o++
|
||||
}
|
||||
}
|
||||
errors = errors[:o]
|
||||
|
||||
return parsed, errors
|
||||
}
|
||||
|
||||
// scanImports returns the set of all import paths from all
|
||||
// import specs in the specified files.
|
||||
func scanImports(files []*ast.File) map[string]bool {
|
||||
imports := make(map[string]bool)
|
||||
for _, f := range files {
|
||||
for _, decl := range f.Decls {
|
||||
if decl, ok := decl.(*ast.GenDecl); ok && decl.Tok == token.IMPORT {
|
||||
for _, spec := range decl.Specs {
|
||||
spec := spec.(*ast.ImportSpec)
|
||||
|
||||
// NB: do not assume the program is well-formed!
|
||||
path, err := strconv.Unquote(spec.Path.Value)
|
||||
if err != nil {
|
||||
continue // quietly ignore the error
|
||||
}
|
||||
if path == "C" {
|
||||
continue // skip pseudopackage
|
||||
}
|
||||
imports[path] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return imports
|
||||
}
|
||||
|
||||
// ---------- Internal helpers ----------
|
||||
|
||||
// TODO(adonovan): make this a method: func (*token.File) Contains(token.Pos)
|
||||
func tokenFileContainsPos(f *token.File, pos token.Pos) bool {
|
||||
p := int(pos)
|
||||
base := f.Base()
|
||||
return base <= p && p < base+f.Size()
|
||||
}
|
||||
253
vendor/golang.org/x/tools/go/packages/doc.go
generated
vendored
Normal file
253
vendor/golang.org/x/tools/go/packages/doc.go
generated
vendored
Normal file
@@ -0,0 +1,253 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package packages loads Go packages for inspection and analysis.
|
||||
|
||||
The [Load] function takes as input a list of patterns and returns a
|
||||
list of [Package] values describing individual packages matched by those
|
||||
patterns.
|
||||
A [Config] specifies configuration options, the most important of which is
|
||||
the [LoadMode], which controls the amount of detail in the loaded packages.
|
||||
|
||||
Load passes most patterns directly to the underlying build tool.
|
||||
The default build tool is the go command.
|
||||
Its supported patterns are described at
|
||||
https://pkg.go.dev/cmd/go#hdr-Package_lists_and_patterns.
|
||||
Other build systems may be supported by providing a "driver";
|
||||
see [The driver protocol].
|
||||
|
||||
All patterns with the prefix "query=", where query is a
|
||||
non-empty string of letters from [a-z], are reserved and may be
|
||||
interpreted as query operators.
|
||||
|
||||
Two query operators are currently supported: "file" and "pattern".
|
||||
|
||||
The query "file=path/to/file.go" matches the package or packages enclosing
|
||||
the Go source file path/to/file.go. For example "file=~/go/src/fmt/print.go"
|
||||
might return the packages "fmt" and "fmt [fmt.test]".
|
||||
|
||||
The query "pattern=string" causes "string" to be passed directly to
|
||||
the underlying build tool. In most cases this is unnecessary,
|
||||
but an application can use Load("pattern=" + x) as an escaping mechanism
|
||||
to ensure that x is not interpreted as a query operator if it contains '='.
|
||||
|
||||
All other query operators are reserved for future use and currently
|
||||
cause Load to report an error.
|
||||
|
||||
The Package struct provides basic information about the package, including
|
||||
|
||||
- ID, a unique identifier for the package in the returned set;
|
||||
- GoFiles, the names of the package's Go source files;
|
||||
- Imports, a map from source import strings to the Packages they name;
|
||||
- Types, the type information for the package's exported symbols;
|
||||
- Syntax, the parsed syntax trees for the package's source code; and
|
||||
- TypesInfo, the result of a complete type-check of the package syntax trees.
|
||||
|
||||
(See the documentation for type Package for the complete list of fields
|
||||
and more detailed descriptions.)
|
||||
|
||||
For example,
|
||||
|
||||
Load(nil, "bytes", "unicode...")
|
||||
|
||||
returns four Package structs describing the standard library packages
|
||||
bytes, unicode, unicode/utf16, and unicode/utf8. Note that one pattern
|
||||
can match multiple packages and that a package might be matched by
|
||||
multiple patterns: in general it is not possible to determine which
|
||||
packages correspond to which patterns.
|
||||
|
||||
Note that the list returned by Load contains only the packages matched
|
||||
by the patterns. Their dependencies can be found by walking the import
|
||||
graph using the Imports fields.
|
||||
|
||||
The Load function can be configured by passing a pointer to a Config as
|
||||
the first argument. A nil Config is equivalent to the zero Config, which
|
||||
causes Load to run in [LoadFiles] mode, collecting minimal information.
|
||||
See the documentation for type Config for details.
|
||||
|
||||
As noted earlier, the Config.Mode controls the amount of detail
|
||||
reported about the loaded packages. See the documentation for type LoadMode
|
||||
for details.
|
||||
|
||||
Most tools should pass their command-line arguments (after any flags)
|
||||
uninterpreted to Load, so that it can interpret them
|
||||
according to the conventions of the underlying build system.
|
||||
|
||||
See the Example function for typical usage.
|
||||
See also [golang.org/x/tools/go/packages/internal/linecount]
|
||||
for an example application.
|
||||
|
||||
# The driver protocol
|
||||
|
||||
Load may be used to load Go packages even in Go projects that use
|
||||
alternative build systems, by installing an appropriate "driver"
|
||||
program for the build system and specifying its location in the
|
||||
GOPACKAGESDRIVER environment variable.
|
||||
For example,
|
||||
https://github.com/bazelbuild/rules_go/wiki/Editor-and-tool-integration
|
||||
explains how to use the driver for Bazel.
|
||||
|
||||
The driver program is responsible for interpreting patterns in its
|
||||
preferred notation and reporting information about the packages that
|
||||
those patterns identify. Drivers must also support the special "file="
|
||||
and "pattern=" patterns described above.
|
||||
|
||||
The patterns are provided as positional command-line arguments. A
|
||||
JSON-encoded [DriverRequest] message providing additional information
|
||||
is written to the driver's standard input. The driver must write a
|
||||
JSON-encoded [DriverResponse] message to its standard output. (This
|
||||
message differs from the JSON schema produced by 'go list'.)
|
||||
|
||||
The value of the PWD environment variable seen by the driver process
|
||||
is the preferred name of its working directory. (The working directory
|
||||
may have other aliases due to symbolic links; see the comment on the
|
||||
Dir field of [exec.Cmd] for related information.)
|
||||
When the driver process emits in its response the name of a file
|
||||
that is a descendant of this directory, it must use an absolute path
|
||||
that has the value of PWD as a prefix, to ensure that the returned
|
||||
filenames satisfy the original query.
|
||||
*/
|
||||
package packages // import "golang.org/x/tools/go/packages"
|
||||
|
||||
/*
|
||||
|
||||
Motivation and design considerations
|
||||
|
||||
The new package's design solves problems addressed by two existing
|
||||
packages: go/build, which locates and describes packages, and
|
||||
golang.org/x/tools/go/loader, which loads, parses and type-checks them.
|
||||
The go/build.Package structure encodes too much of the 'go build' way
|
||||
of organizing projects, leaving us in need of a data type that describes a
|
||||
package of Go source code independent of the underlying build system.
|
||||
We wanted something that works equally well with go build and vgo, and
|
||||
also other build systems such as Bazel and Blaze, making it possible to
|
||||
construct analysis tools that work in all these environments.
|
||||
Tools such as errcheck and staticcheck were essentially unavailable to
|
||||
the Go community at Google, and some of Google's internal tools for Go
|
||||
are unavailable externally.
|
||||
This new package provides a uniform way to obtain package metadata by
|
||||
querying each of these build systems, optionally supporting their
|
||||
preferred command-line notations for packages, so that tools integrate
|
||||
neatly with users' build environments. The Metadata query function
|
||||
executes an external query tool appropriate to the current workspace.
|
||||
|
||||
Loading packages always returns the complete import graph "all the way down",
|
||||
even if all you want is information about a single package, because the query
|
||||
mechanisms of all the build systems we currently support ({go,vgo} list, and
|
||||
blaze/bazel aspect-based query) cannot provide detailed information
|
||||
about one package without visiting all its dependencies too, so there is
|
||||
no additional asymptotic cost to providing transitive information.
|
||||
(This property might not be true of a hypothetical 5th build system.)
|
||||
|
||||
In calls to TypeCheck, all initial packages, and any package that
|
||||
transitively depends on one of them, must be loaded from source.
|
||||
Consider A->B->C->D->E: if A,C are initial, A,B,C must be loaded from
|
||||
source; D may be loaded from export data, and E may not be loaded at all
|
||||
(though it's possible that D's export data mentions it, so a
|
||||
types.Package may be created for it and exposed.)
|
||||
|
||||
The old loader had a feature to suppress type-checking of function
|
||||
bodies on a per-package basis, primarily intended to reduce the work of
|
||||
obtaining type information for imported packages. Now that imports are
|
||||
satisfied by export data, the optimization no longer seems necessary.
|
||||
|
||||
Despite some early attempts, the old loader did not exploit export data,
|
||||
instead always using the equivalent of WholeProgram mode. This was due
|
||||
to the complexity of mixing source and export data packages (now
|
||||
resolved by the upward traversal mentioned above), and because export data
|
||||
files were nearly always missing or stale. Now that 'go build' supports
|
||||
caching, all the underlying build systems can guarantee to produce
|
||||
export data in a reasonable (amortized) time.
|
||||
|
||||
Test "main" packages synthesized by the build system are now reported as
|
||||
first-class packages, avoiding the need for clients (such as go/ssa) to
|
||||
reinvent this generation logic.
|
||||
|
||||
One way in which go/packages is simpler than the old loader is in its
|
||||
treatment of in-package tests. In-package tests are packages that
|
||||
consist of all the files of the library under test, plus the test files.
|
||||
The old loader constructed in-package tests by a two-phase process of
|
||||
mutation called "augmentation": first it would construct and type check
|
||||
all the ordinary library packages and type-check the packages that
|
||||
depend on them; then it would add more (test) files to the package and
|
||||
type-check again. This two-phase approach had four major problems:
|
||||
1) in processing the tests, the loader modified the library package,
|
||||
leaving no way for a client application to see both the test
|
||||
package and the library package; one would mutate into the other.
|
||||
2) because test files can declare additional methods on types defined in
|
||||
the library portion of the package, the dispatch of method calls in
|
||||
the library portion was affected by the presence of the test files.
|
||||
This should have been a clue that the packages were logically
|
||||
different.
|
||||
3) this model of "augmentation" assumed at most one in-package test
|
||||
per library package, which is true of projects using 'go build',
|
||||
but not other build systems.
|
||||
4) because of the two-phase nature of test processing, all packages that
|
||||
import the library package had to be processed before augmentation,
|
||||
forcing a "one-shot" API and preventing the client from calling Load
|
||||
in several times in sequence as is now possible in WholeProgram mode.
|
||||
(TypeCheck mode has a similar one-shot restriction for a different reason.)
|
||||
|
||||
Early drafts of this package supported "multi-shot" operation.
|
||||
Although it allowed clients to make a sequence of calls (or concurrent
|
||||
calls) to Load, building up the graph of Packages incrementally,
|
||||
it was of marginal value: it complicated the API
|
||||
(since it allowed some options to vary across calls but not others),
|
||||
it complicated the implementation,
|
||||
it cannot be made to work in Types mode, as explained above,
|
||||
and it was less efficient than making one combined call (when this is possible).
|
||||
Among the clients we have inspected, none made multiple calls to load
|
||||
but could not be easily and satisfactorily modified to make only a single call.
|
||||
However, applications changes may be required.
|
||||
For example, the ssadump command loads the user-specified packages
|
||||
and in addition the runtime package. It is tempting to simply append
|
||||
"runtime" to the user-provided list, but that does not work if the user
|
||||
specified an ad-hoc package such as [a.go b.go].
|
||||
Instead, ssadump no longer requests the runtime package,
|
||||
but seeks it among the dependencies of the user-specified packages,
|
||||
and emits an error if it is not found.
|
||||
|
||||
Questions & Tasks
|
||||
|
||||
- Add GOARCH/GOOS?
|
||||
They are not portable concepts, but could be made portable.
|
||||
Our goal has been to allow users to express themselves using the conventions
|
||||
of the underlying build system: if the build system honors GOARCH
|
||||
during a build and during a metadata query, then so should
|
||||
applications built atop that query mechanism.
|
||||
Conversely, if the target architecture of the build is determined by
|
||||
command-line flags, the application can pass the relevant
|
||||
flags through to the build system using a command such as:
|
||||
myapp -query_flag="--cpu=amd64" -query_flag="--os=darwin"
|
||||
However, this approach is low-level, unwieldy, and non-portable.
|
||||
GOOS and GOARCH seem important enough to warrant a dedicated option.
|
||||
|
||||
- How should we handle partial failures such as a mixture of good and
|
||||
malformed patterns, existing and non-existent packages, successful and
|
||||
failed builds, import failures, import cycles, and so on, in a call to
|
||||
Load?
|
||||
|
||||
- Support bazel, blaze, and go1.10 list, not just go1.11 list.
|
||||
|
||||
- Handle (and test) various partial success cases, e.g.
|
||||
a mixture of good packages and:
|
||||
invalid patterns
|
||||
nonexistent packages
|
||||
empty packages
|
||||
packages with malformed package or import declarations
|
||||
unreadable files
|
||||
import cycles
|
||||
other parse errors
|
||||
type errors
|
||||
Make sure we record errors at the correct place in the graph.
|
||||
|
||||
- Missing packages among initial arguments are not reported.
|
||||
Return bogus packages for them, like golist does.
|
||||
|
||||
- "undeclared name" errors (for example) are reported out of source file
|
||||
order. I suspect this is due to the breadth-first resolution now used
|
||||
by go/types. Is that a bug? Discuss with gri.
|
||||
|
||||
*/
|
||||
153
vendor/golang.org/x/tools/go/packages/external.go
generated
vendored
Normal file
153
vendor/golang.org/x/tools/go/packages/external.go
generated
vendored
Normal file
@@ -0,0 +1,153 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package packages
|
||||
|
||||
// This file defines the protocol that enables an external "driver"
|
||||
// tool to supply package metadata in place of 'go list'.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DriverRequest defines the schema of a request for package metadata
|
||||
// from an external driver program. The JSON-encoded DriverRequest
|
||||
// message is provided to the driver program's standard input. The
|
||||
// query patterns are provided as command-line arguments.
|
||||
//
|
||||
// See the package documentation for an overview.
|
||||
type DriverRequest struct {
|
||||
Mode LoadMode `json:"mode"`
|
||||
|
||||
// Env specifies the environment the underlying build system should be run in.
|
||||
Env []string `json:"env"`
|
||||
|
||||
// BuildFlags are flags that should be passed to the underlying build system.
|
||||
BuildFlags []string `json:"build_flags"`
|
||||
|
||||
// Tests specifies whether the patterns should also return test packages.
|
||||
Tests bool `json:"tests"`
|
||||
|
||||
// Overlay maps file paths (relative to the driver's working directory)
|
||||
// to the contents of overlay files (see Config.Overlay).
|
||||
Overlay map[string][]byte `json:"overlay"`
|
||||
}
|
||||
|
||||
// DriverResponse defines the schema of a response from an external
|
||||
// driver program, providing the results of a query for package
|
||||
// metadata. The driver program must write a JSON-encoded
|
||||
// DriverResponse message to its standard output.
|
||||
//
|
||||
// See the package documentation for an overview.
|
||||
type DriverResponse struct {
|
||||
// NotHandled is returned if the request can't be handled by the current
|
||||
// driver. If an external driver returns a response with NotHandled, the
|
||||
// rest of the DriverResponse is ignored, and go/packages will fallback
|
||||
// to the next driver. If go/packages is extended in the future to support
|
||||
// lists of multiple drivers, go/packages will fall back to the next driver.
|
||||
NotHandled bool
|
||||
|
||||
// Compiler and Arch are the arguments pass of types.SizesFor
|
||||
// to get a types.Sizes to use when type checking.
|
||||
Compiler string
|
||||
Arch string
|
||||
|
||||
// Roots is the set of package IDs that make up the root packages.
|
||||
// We have to encode this separately because when we encode a single package
|
||||
// we cannot know if it is one of the roots as that requires knowledge of the
|
||||
// graph it is part of.
|
||||
Roots []string `json:",omitempty"`
|
||||
|
||||
// Packages is the full set of packages in the graph.
|
||||
// The packages are not connected into a graph.
|
||||
// The Imports if populated will be stubs that only have their ID set.
|
||||
// Imports will be connected and then type and syntax information added in a
|
||||
// later pass (see refine).
|
||||
Packages []*Package
|
||||
|
||||
// GoVersion is the minor version number used by the driver
|
||||
// (e.g. the go command on the PATH) when selecting .go files.
|
||||
// Zero means unknown.
|
||||
GoVersion int
|
||||
}
|
||||
|
||||
// driver is the type for functions that query the build system for the
|
||||
// packages named by the patterns.
|
||||
type driver func(cfg *Config, patterns []string) (*DriverResponse, error)
|
||||
|
||||
// findExternalDriver returns the file path of a tool that supplies
|
||||
// the build system package structure, or "" if not found.
|
||||
// If GOPACKAGESDRIVER is set in the environment findExternalTool returns its
|
||||
// value, otherwise it searches for a binary named gopackagesdriver on the PATH.
|
||||
func findExternalDriver(cfg *Config) driver {
|
||||
const toolPrefix = "GOPACKAGESDRIVER="
|
||||
tool := ""
|
||||
for _, env := range cfg.Env {
|
||||
if val, ok := strings.CutPrefix(env, toolPrefix); ok {
|
||||
tool = val
|
||||
}
|
||||
}
|
||||
if tool != "" && tool == "off" {
|
||||
return nil
|
||||
}
|
||||
if tool == "" {
|
||||
var err error
|
||||
tool, err = exec.LookPath("gopackagesdriver")
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return func(cfg *Config, patterns []string) (*DriverResponse, error) {
|
||||
req, err := json.Marshal(DriverRequest{
|
||||
Mode: cfg.Mode,
|
||||
Env: cfg.Env,
|
||||
BuildFlags: cfg.BuildFlags,
|
||||
Tests: cfg.Tests,
|
||||
Overlay: cfg.Overlay,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to encode message to driver tool: %v", err)
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
stderr := new(bytes.Buffer)
|
||||
cmd := exec.CommandContext(cfg.Context, tool, patterns...)
|
||||
cmd.Dir = cfg.Dir
|
||||
// The cwd gets resolved to the real path. On Darwin, where
|
||||
// /tmp is a symlink, this breaks anything that expects the
|
||||
// working directory to keep the original path, including the
|
||||
// go command when dealing with modules.
|
||||
//
|
||||
// os.Getwd stdlib has a special feature where if the
|
||||
// cwd and the PWD are the same node then it trusts
|
||||
// the PWD, so by setting it in the env for the child
|
||||
// process we fix up all the paths returned by the go
|
||||
// command.
|
||||
//
|
||||
// (See similar trick in Invocation.run in ../../internal/gocommand/invoke.go)
|
||||
cmd.Env = append(slices.Clip(cfg.Env), "PWD="+cfg.Dir)
|
||||
cmd.Stdin = bytes.NewReader(req)
|
||||
cmd.Stdout = buf
|
||||
cmd.Stderr = stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return nil, fmt.Errorf("%v: %v: %s", tool, err, cmd.Stderr)
|
||||
}
|
||||
if len(stderr.Bytes()) != 0 && os.Getenv("GOPACKAGESPRINTDRIVERERRORS") != "" {
|
||||
fmt.Fprintf(os.Stderr, "%s stderr: <<%s>>\n", cmdDebugStr(cmd), stderr)
|
||||
}
|
||||
|
||||
var response DriverResponse
|
||||
if err := json.Unmarshal(buf.Bytes(), &response); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &response, nil
|
||||
}
|
||||
}
|
||||
1086
vendor/golang.org/x/tools/go/packages/golist.go
generated
vendored
Normal file
1086
vendor/golang.org/x/tools/go/packages/golist.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
83
vendor/golang.org/x/tools/go/packages/golist_overlay.go
generated
vendored
Normal file
83
vendor/golang.org/x/tools/go/packages/golist_overlay.go
generated
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package packages
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"path/filepath"
|
||||
|
||||
"golang.org/x/tools/internal/gocommand"
|
||||
)
|
||||
|
||||
// determineRootDirs returns a mapping from absolute directories that could
|
||||
// contain code to their corresponding import path prefixes.
|
||||
func (state *golistState) determineRootDirs() (map[string]string, error) {
|
||||
env, err := state.getEnv()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if env["GOMOD"] != "" {
|
||||
state.rootsOnce.Do(func() {
|
||||
state.rootDirs, state.rootDirsError = state.determineRootDirsModules()
|
||||
})
|
||||
} else {
|
||||
state.rootsOnce.Do(func() {
|
||||
state.rootDirs, state.rootDirsError = state.determineRootDirsGOPATH()
|
||||
})
|
||||
}
|
||||
return state.rootDirs, state.rootDirsError
|
||||
}
|
||||
|
||||
func (state *golistState) determineRootDirsModules() (map[string]string, error) {
|
||||
// List all of the modules--the first will be the directory for the main
|
||||
// module. Any replaced modules will also need to be treated as roots.
|
||||
// Editing files in the module cache isn't a great idea, so we don't
|
||||
// plan to ever support that.
|
||||
out, err := state.invokeGo("list", "-m", "-json", "all")
|
||||
if err != nil {
|
||||
// 'go list all' will fail if we're outside of a module and
|
||||
// GO111MODULE=on. Try falling back without 'all'.
|
||||
var innerErr error
|
||||
out, innerErr = state.invokeGo("list", "-m", "-json")
|
||||
if innerErr != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
roots := map[string]string{}
|
||||
modules := map[string]string{}
|
||||
var i int
|
||||
for dec := json.NewDecoder(out); dec.More(); {
|
||||
mod := new(gocommand.ModuleJSON)
|
||||
if err := dec.Decode(mod); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mod.Dir != "" && mod.Path != "" {
|
||||
// This is a valid module; add it to the map.
|
||||
absDir, err := state.cfg.abs(mod.Dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
modules[absDir] = mod.Path
|
||||
// The first result is the main module.
|
||||
if i == 0 || mod.Replace != nil && mod.Replace.Path != "" {
|
||||
roots[absDir] = mod.Path
|
||||
}
|
||||
}
|
||||
i++
|
||||
}
|
||||
return roots, nil
|
||||
}
|
||||
|
||||
func (state *golistState) determineRootDirsGOPATH() (map[string]string, error) {
|
||||
m := map[string]string{}
|
||||
for _, dir := range filepath.SplitList(state.mustGetEnv()["GOPATH"]) {
|
||||
absDir, err := filepath.Abs(dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m[filepath.Join(absDir, "src")] = ""
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
56
vendor/golang.org/x/tools/go/packages/loadmode_string.go
generated
vendored
Normal file
56
vendor/golang.org/x/tools/go/packages/loadmode_string.go
generated
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package packages
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var modes = [...]struct {
|
||||
mode LoadMode
|
||||
name string
|
||||
}{
|
||||
{NeedName, "NeedName"},
|
||||
{NeedFiles, "NeedFiles"},
|
||||
{NeedCompiledGoFiles, "NeedCompiledGoFiles"},
|
||||
{NeedImports, "NeedImports"},
|
||||
{NeedDeps, "NeedDeps"},
|
||||
{NeedExportFile, "NeedExportFile"},
|
||||
{NeedTypes, "NeedTypes"},
|
||||
{NeedSyntax, "NeedSyntax"},
|
||||
{NeedTypesInfo, "NeedTypesInfo"},
|
||||
{NeedTypesSizes, "NeedTypesSizes"},
|
||||
{NeedForTest, "NeedForTest"},
|
||||
{NeedModule, "NeedModule"},
|
||||
{NeedEmbedFiles, "NeedEmbedFiles"},
|
||||
{NeedEmbedPatterns, "NeedEmbedPatterns"},
|
||||
{NeedTarget, "NeedTarget"},
|
||||
}
|
||||
|
||||
func (mode LoadMode) String() string {
|
||||
if mode == 0 {
|
||||
return "LoadMode(0)"
|
||||
}
|
||||
var out []string
|
||||
// named bits
|
||||
for _, item := range modes {
|
||||
if (mode & item.mode) != 0 {
|
||||
mode ^= item.mode
|
||||
out = append(out, item.name)
|
||||
}
|
||||
}
|
||||
// unnamed residue
|
||||
if mode != 0 {
|
||||
if out == nil {
|
||||
return fmt.Sprintf("LoadMode(%#x)", int(mode))
|
||||
}
|
||||
out = append(out, fmt.Sprintf("%#x", int(mode)))
|
||||
}
|
||||
if len(out) == 1 {
|
||||
return out[0]
|
||||
}
|
||||
return "(" + strings.Join(out, "|") + ")"
|
||||
}
|
||||
1568
vendor/golang.org/x/tools/go/packages/packages.go
generated
vendored
Normal file
1568
vendor/golang.org/x/tools/go/packages/packages.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
133
vendor/golang.org/x/tools/go/packages/visit.go
generated
vendored
Normal file
133
vendor/golang.org/x/tools/go/packages/visit.go
generated
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package packages
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"fmt"
|
||||
"iter"
|
||||
"os"
|
||||
"slices"
|
||||
)
|
||||
|
||||
// Visit visits all the packages in the import graph whose roots are
|
||||
// pkgs, calling the optional pre function the first time each package
|
||||
// is encountered (preorder), and the optional post function after a
|
||||
// package's dependencies have been visited (postorder).
|
||||
// The boolean result of pre(pkg) determines whether
|
||||
// the imports of package pkg are visited.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// pkgs, err := Load(...)
|
||||
// if err != nil { ... }
|
||||
// Visit(pkgs, nil, func(pkg *Package) {
|
||||
// log.Println(pkg)
|
||||
// })
|
||||
//
|
||||
// In most cases, it is more convenient to use [Postorder]:
|
||||
//
|
||||
// for pkg := range Postorder(pkgs) {
|
||||
// log.Println(pkg)
|
||||
// }
|
||||
func Visit(pkgs []*Package, pre func(*Package) bool, post func(*Package)) {
|
||||
seen := make(map[*Package]bool)
|
||||
var visit func(*Package)
|
||||
visit = func(pkg *Package) {
|
||||
if !seen[pkg] {
|
||||
seen[pkg] = true
|
||||
|
||||
if pre == nil || pre(pkg) {
|
||||
for _, imp := range sorted(pkg.Imports) { // for determinism
|
||||
visit(imp)
|
||||
}
|
||||
}
|
||||
|
||||
if post != nil {
|
||||
post(pkg)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, pkg := range pkgs {
|
||||
visit(pkg)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintErrors prints to os.Stderr the accumulated errors of all
|
||||
// packages in the import graph rooted at pkgs, dependencies first.
|
||||
// PrintErrors returns the number of errors printed.
|
||||
func PrintErrors(pkgs []*Package) int {
|
||||
var n int
|
||||
errModules := make(map[*Module]bool)
|
||||
for pkg := range Postorder(pkgs) {
|
||||
for _, err := range pkg.Errors {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
n++
|
||||
}
|
||||
|
||||
// Print pkg.Module.Error once if present.
|
||||
mod := pkg.Module
|
||||
if mod != nil && mod.Error != nil && !errModules[mod] {
|
||||
errModules[mod] = true
|
||||
fmt.Fprintln(os.Stderr, mod.Error.Err)
|
||||
n++
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Postorder returns an iterator over the packages in
|
||||
// the import graph whose roots are pkg.
|
||||
// Packages are enumerated in dependencies-first order.
|
||||
func Postorder(pkgs []*Package) iter.Seq[*Package] {
|
||||
return func(yield func(*Package) bool) {
|
||||
seen := make(map[*Package]bool)
|
||||
var visit func(*Package) bool
|
||||
visit = func(pkg *Package) bool {
|
||||
if !seen[pkg] {
|
||||
seen[pkg] = true
|
||||
for _, imp := range sorted(pkg.Imports) { // for determinism
|
||||
if !visit(imp) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if !yield(pkg) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
for _, pkg := range pkgs {
|
||||
if !visit(pkg) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- copied from golang.org.x/tools/gopls/internal/util/moremaps --
|
||||
|
||||
// sorted returns an iterator over the entries of m in key order.
|
||||
func sorted[M ~map[K]V, K cmp.Ordered, V any](m M) iter.Seq2[K, V] {
|
||||
// TODO(adonovan): use maps.Sorted if proposal #68598 is accepted.
|
||||
return func(yield func(K, V) bool) {
|
||||
keys := keySlice(m)
|
||||
slices.Sort(keys)
|
||||
for _, k := range keys {
|
||||
if !yield(k, m[k]) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// KeySlice returns the keys of the map M, like slices.Collect(maps.Keys(m)).
|
||||
func keySlice[M ~map[K]V, K comparable, V any](m M) []K {
|
||||
r := make([]K, 0, len(m))
|
||||
for k := range m {
|
||||
r = append(r, k)
|
||||
}
|
||||
return r
|
||||
}
|
||||
16
vendor/golang.org/x/tools/go/ssa/TODO
generated
vendored
Normal file
16
vendor/golang.org/x/tools/go/ssa/TODO
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
-*- text -*-
|
||||
|
||||
SSA Generics to-do list
|
||||
===========================
|
||||
|
||||
DOCUMENTATION:
|
||||
- Read me for internals
|
||||
|
||||
TYPE PARAMETERIZED GENERIC FUNCTIONS:
|
||||
- sanity.go updates.
|
||||
- Check source functions going to generics.
|
||||
- Tests, tests, tests...
|
||||
|
||||
USAGE:
|
||||
- Back fill users for handling ssa.InstantiateGenerics being off.
|
||||
|
||||
113
vendor/golang.org/x/tools/go/ssa/block.go
generated
vendored
Normal file
113
vendor/golang.org/x/tools/go/ssa/block.go
generated
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssa
|
||||
|
||||
import "fmt"
|
||||
|
||||
// This file implements the BasicBlock type.
|
||||
|
||||
// addEdge adds a control-flow graph edge from from to to.
|
||||
func addEdge(from, to *BasicBlock) {
|
||||
from.Succs = append(from.Succs, to)
|
||||
to.Preds = append(to.Preds, from)
|
||||
}
|
||||
|
||||
// Parent returns the function that contains block b.
|
||||
func (b *BasicBlock) Parent() *Function { return b.parent }
|
||||
|
||||
// String returns a human-readable label of this block.
|
||||
// It is not guaranteed unique within the function.
|
||||
func (b *BasicBlock) String() string {
|
||||
return fmt.Sprintf("%d", b.Index)
|
||||
}
|
||||
|
||||
// emit appends an instruction to the current basic block.
|
||||
// If the instruction defines a Value, it is returned.
|
||||
func (b *BasicBlock) emit(i Instruction) Value {
|
||||
i.setBlock(b)
|
||||
b.Instrs = append(b.Instrs, i)
|
||||
v, _ := i.(Value)
|
||||
return v
|
||||
}
|
||||
|
||||
// predIndex returns the i such that b.Preds[i] == c or panics if
|
||||
// there is none.
|
||||
func (b *BasicBlock) predIndex(c *BasicBlock) int {
|
||||
for i, pred := range b.Preds {
|
||||
if pred == c {
|
||||
return i
|
||||
}
|
||||
}
|
||||
panic(fmt.Sprintf("no edge %s -> %s", c, b))
|
||||
}
|
||||
|
||||
// hasPhi returns true if b.Instrs contains φ-nodes.
|
||||
func (b *BasicBlock) hasPhi() bool {
|
||||
_, ok := b.Instrs[0].(*Phi)
|
||||
return ok
|
||||
}
|
||||
|
||||
// phis returns the prefix of b.Instrs containing all the block's φ-nodes.
|
||||
func (b *BasicBlock) phis() []Instruction {
|
||||
for i, instr := range b.Instrs {
|
||||
if _, ok := instr.(*Phi); !ok {
|
||||
return b.Instrs[:i]
|
||||
}
|
||||
}
|
||||
return nil // unreachable in well-formed blocks
|
||||
}
|
||||
|
||||
// replacePred replaces all occurrences of p in b's predecessor list with q.
|
||||
// Ordinarily there should be at most one.
|
||||
func (b *BasicBlock) replacePred(p, q *BasicBlock) {
|
||||
for i, pred := range b.Preds {
|
||||
if pred == p {
|
||||
b.Preds[i] = q
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// replaceSucc replaces all occurrences of p in b's successor list with q.
|
||||
// Ordinarily there should be at most one.
|
||||
func (b *BasicBlock) replaceSucc(p, q *BasicBlock) {
|
||||
for i, succ := range b.Succs {
|
||||
if succ == p {
|
||||
b.Succs[i] = q
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// removePred removes all occurrences of p in b's
|
||||
// predecessor list and φ-nodes.
|
||||
// Ordinarily there should be at most one.
|
||||
func (b *BasicBlock) removePred(p *BasicBlock) {
|
||||
phis := b.phis()
|
||||
|
||||
// We must preserve edge order for φ-nodes.
|
||||
j := 0
|
||||
for i, pred := range b.Preds {
|
||||
if pred != p {
|
||||
b.Preds[j] = b.Preds[i]
|
||||
// Strike out φ-edge too.
|
||||
for _, instr := range phis {
|
||||
phi := instr.(*Phi)
|
||||
phi.Edges[j] = phi.Edges[i]
|
||||
}
|
||||
j++
|
||||
}
|
||||
}
|
||||
// Nil out b.Preds[j:] and φ-edges[j:] to aid GC.
|
||||
for i := j; i < len(b.Preds); i++ {
|
||||
b.Preds[i] = nil
|
||||
for _, instr := range phis {
|
||||
instr.(*Phi).Edges[i] = nil
|
||||
}
|
||||
}
|
||||
b.Preds = b.Preds[:j]
|
||||
for _, instr := range phis {
|
||||
phi := instr.(*Phi)
|
||||
phi.Edges = phi.Edges[:j]
|
||||
}
|
||||
}
|
||||
183
vendor/golang.org/x/tools/go/ssa/blockopt.go
generated
vendored
Normal file
183
vendor/golang.org/x/tools/go/ssa/blockopt.go
generated
vendored
Normal file
@@ -0,0 +1,183 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssa
|
||||
|
||||
// Simple block optimizations to simplify the control flow graph.
|
||||
|
||||
// TODO(adonovan): opt: instead of creating several "unreachable" blocks
|
||||
// per function in the Builder, reuse a single one (e.g. at Blocks[1])
|
||||
// to reduce garbage.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// If true, perform sanity checking and show progress at each
|
||||
// successive iteration of optimizeBlocks. Very verbose.
|
||||
const debugBlockOpt = false
|
||||
|
||||
// markReachable sets Index=-1 for all blocks reachable from b.
|
||||
func markReachable(b *BasicBlock) {
|
||||
b.Index = -1
|
||||
for _, succ := range b.Succs {
|
||||
if succ.Index == 0 {
|
||||
markReachable(succ)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// deleteUnreachableBlocks marks all reachable blocks of f and
|
||||
// eliminates (nils) all others, including possibly cyclic subgraphs.
|
||||
func deleteUnreachableBlocks(f *Function) {
|
||||
const white, black = 0, -1
|
||||
// We borrow b.Index temporarily as the mark bit.
|
||||
for _, b := range f.Blocks {
|
||||
b.Index = white
|
||||
}
|
||||
markReachable(f.Blocks[0])
|
||||
if f.Recover != nil {
|
||||
markReachable(f.Recover)
|
||||
}
|
||||
for i, b := range f.Blocks {
|
||||
if b.Index == white {
|
||||
for _, c := range b.Succs {
|
||||
if c.Index == black {
|
||||
c.removePred(b) // delete white->black edge
|
||||
}
|
||||
}
|
||||
if debugBlockOpt {
|
||||
fmt.Fprintln(os.Stderr, "unreachable", b)
|
||||
}
|
||||
f.Blocks[i] = nil // delete b
|
||||
}
|
||||
}
|
||||
f.removeNilBlocks()
|
||||
}
|
||||
|
||||
// jumpThreading attempts to apply simple jump-threading to block b,
|
||||
// in which a->b->c become a->c if b is just a Jump.
|
||||
// The result is true if the optimization was applied.
|
||||
func jumpThreading(f *Function, b *BasicBlock) bool {
|
||||
if b.Index == 0 {
|
||||
return false // don't apply to entry block
|
||||
}
|
||||
if b.Instrs == nil {
|
||||
return false
|
||||
}
|
||||
if _, ok := b.Instrs[0].(*Jump); !ok {
|
||||
return false // not just a jump
|
||||
}
|
||||
c := b.Succs[0]
|
||||
if c == b {
|
||||
return false // don't apply to degenerate jump-to-self.
|
||||
}
|
||||
if c.hasPhi() {
|
||||
return false // not sound without more effort
|
||||
}
|
||||
for j, a := range b.Preds {
|
||||
a.replaceSucc(b, c)
|
||||
|
||||
// If a now has two edges to c, replace its degenerate If by Jump.
|
||||
if len(a.Succs) == 2 && a.Succs[0] == c && a.Succs[1] == c {
|
||||
jump := new(Jump)
|
||||
jump.setBlock(a)
|
||||
a.Instrs[len(a.Instrs)-1] = jump
|
||||
a.Succs = a.Succs[:1]
|
||||
c.removePred(b)
|
||||
} else {
|
||||
if j == 0 {
|
||||
c.replacePred(b, a)
|
||||
} else {
|
||||
c.Preds = append(c.Preds, a)
|
||||
}
|
||||
}
|
||||
|
||||
if debugBlockOpt {
|
||||
fmt.Fprintln(os.Stderr, "jumpThreading", a, b, c)
|
||||
}
|
||||
}
|
||||
f.Blocks[b.Index] = nil // delete b
|
||||
return true
|
||||
}
|
||||
|
||||
// fuseBlocks attempts to apply the block fusion optimization to block
|
||||
// a, in which a->b becomes ab if len(a.Succs)==len(b.Preds)==1.
|
||||
// The result is true if the optimization was applied.
|
||||
func fuseBlocks(f *Function, a *BasicBlock) bool {
|
||||
if len(a.Succs) != 1 {
|
||||
return false
|
||||
}
|
||||
b := a.Succs[0]
|
||||
if len(b.Preds) != 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Degenerate &&/|| ops may result in a straight-line CFG
|
||||
// containing φ-nodes. (Ideally we'd replace such them with
|
||||
// their sole operand but that requires Referrers, built later.)
|
||||
if b.hasPhi() {
|
||||
return false // not sound without further effort
|
||||
}
|
||||
|
||||
// Eliminate jump at end of A, then copy all of B across.
|
||||
a.Instrs = append(a.Instrs[:len(a.Instrs)-1], b.Instrs...)
|
||||
for _, instr := range b.Instrs {
|
||||
instr.setBlock(a)
|
||||
}
|
||||
|
||||
// A inherits B's successors
|
||||
a.Succs = append(a.succs2[:0], b.Succs...)
|
||||
|
||||
// Fix up Preds links of all successors of B.
|
||||
for _, c := range b.Succs {
|
||||
c.replacePred(b, a)
|
||||
}
|
||||
|
||||
if debugBlockOpt {
|
||||
fmt.Fprintln(os.Stderr, "fuseBlocks", a, b)
|
||||
}
|
||||
|
||||
f.Blocks[b.Index] = nil // delete b
|
||||
return true
|
||||
}
|
||||
|
||||
// optimizeBlocks() performs some simple block optimizations on a
|
||||
// completed function: dead block elimination, block fusion, jump
|
||||
// threading.
|
||||
func optimizeBlocks(f *Function) {
|
||||
deleteUnreachableBlocks(f)
|
||||
|
||||
// Loop until no further progress.
|
||||
changed := true
|
||||
for changed {
|
||||
changed = false
|
||||
|
||||
if debugBlockOpt {
|
||||
f.WriteTo(os.Stderr)
|
||||
mustSanityCheck(f, nil)
|
||||
}
|
||||
|
||||
for _, b := range f.Blocks {
|
||||
// f.Blocks will temporarily contain nils to indicate
|
||||
// deleted blocks; we remove them at the end.
|
||||
if b == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Fuse blocks. b->c becomes bc.
|
||||
if fuseBlocks(f, b) {
|
||||
changed = true
|
||||
}
|
||||
|
||||
// a->b->c becomes a->c if b contains only a Jump.
|
||||
if jumpThreading(f, b) {
|
||||
changed = true
|
||||
continue // (b was disconnected)
|
||||
}
|
||||
}
|
||||
}
|
||||
f.removeNilBlocks()
|
||||
}
|
||||
3292
vendor/golang.org/x/tools/go/ssa/builder.go
generated
vendored
Normal file
3292
vendor/golang.org/x/tools/go/ssa/builder.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
193
vendor/golang.org/x/tools/go/ssa/const.go
generated
vendored
Normal file
193
vendor/golang.org/x/tools/go/ssa/const.go
generated
vendored
Normal file
@@ -0,0 +1,193 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssa
|
||||
|
||||
// This file defines the Const SSA value type.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/constant"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/tools/internal/typeparams"
|
||||
"golang.org/x/tools/internal/typesinternal"
|
||||
)
|
||||
|
||||
// NewConst returns a new constant of the specified value and type.
|
||||
// val must be valid according to the specification of Const.Value.
|
||||
func NewConst(val constant.Value, typ types.Type) *Const {
|
||||
if val == nil {
|
||||
switch soleTypeKind(typ) {
|
||||
case types.IsBoolean:
|
||||
val = constant.MakeBool(false)
|
||||
case types.IsInteger:
|
||||
val = constant.MakeInt64(0)
|
||||
case types.IsString:
|
||||
val = constant.MakeString("")
|
||||
}
|
||||
}
|
||||
return &Const{typ, val}
|
||||
}
|
||||
|
||||
// soleTypeKind returns a BasicInfo for which constant.Value can
|
||||
// represent all zero values for the types in the type set.
|
||||
//
|
||||
// types.IsBoolean for false is a representative.
|
||||
// types.IsInteger for 0
|
||||
// types.IsString for ""
|
||||
// 0 otherwise.
|
||||
func soleTypeKind(typ types.Type) types.BasicInfo {
|
||||
// State records the set of possible zero values (false, 0, "").
|
||||
// Candidates (perhaps all) are eliminated during the type-set
|
||||
// iteration, which executes at least once.
|
||||
state := types.IsBoolean | types.IsInteger | types.IsString
|
||||
underIs(typ, func(ut types.Type) bool {
|
||||
var c types.BasicInfo
|
||||
if t, ok := ut.(*types.Basic); ok {
|
||||
c = t.Info()
|
||||
}
|
||||
if c&types.IsNumeric != 0 { // int/float/complex
|
||||
c = types.IsInteger
|
||||
}
|
||||
state = state & c
|
||||
return state != 0
|
||||
})
|
||||
return state
|
||||
}
|
||||
|
||||
// intConst returns an 'int' constant that evaluates to i.
|
||||
// (i is an int64 in case the host is narrower than the target.)
|
||||
func intConst(i int64) *Const {
|
||||
return NewConst(constant.MakeInt64(i), tInt)
|
||||
}
|
||||
|
||||
// stringConst returns a 'string' constant that evaluates to s.
|
||||
func stringConst(s string) *Const {
|
||||
return NewConst(constant.MakeString(s), tString)
|
||||
}
|
||||
|
||||
// zeroConst returns a new "zero" constant of the specified type.
|
||||
func zeroConst(t types.Type) *Const {
|
||||
return NewConst(nil, t)
|
||||
}
|
||||
|
||||
func (c *Const) RelString(from *types.Package) string {
|
||||
var s string
|
||||
if c.Value == nil {
|
||||
s, _ = typesinternal.ZeroString(c.typ, types.RelativeTo(from))
|
||||
} else if c.Value.Kind() == constant.String {
|
||||
s = constant.StringVal(c.Value)
|
||||
const max = 20
|
||||
// TODO(adonovan): don't cut a rune in half.
|
||||
if len(s) > max {
|
||||
s = s[:max-3] + "..." // abbreviate
|
||||
}
|
||||
s = strconv.Quote(s)
|
||||
} else {
|
||||
s = c.Value.String()
|
||||
}
|
||||
return s + ":" + relType(c.Type(), from)
|
||||
}
|
||||
|
||||
func (c *Const) Name() string {
|
||||
return c.RelString(nil)
|
||||
}
|
||||
|
||||
func (c *Const) String() string {
|
||||
return c.Name()
|
||||
}
|
||||
|
||||
func (c *Const) Type() types.Type {
|
||||
return c.typ
|
||||
}
|
||||
|
||||
func (c *Const) Referrers() *[]Instruction {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Const) Parent() *Function { return nil }
|
||||
|
||||
func (c *Const) Pos() token.Pos {
|
||||
return token.NoPos
|
||||
}
|
||||
|
||||
// IsNil returns true if this constant is a nil value of
|
||||
// a nillable reference type (pointer, slice, channel, map, or function),
|
||||
// a basic interface type, or
|
||||
// a type parameter all of whose possible instantiations are themselves nillable.
|
||||
func (c *Const) IsNil() bool {
|
||||
return c.Value == nil && nillable(c.typ)
|
||||
}
|
||||
|
||||
// nillable reports whether *new(T) == nil is legal for type T.
|
||||
func nillable(t types.Type) bool {
|
||||
if typeparams.IsTypeParam(t) {
|
||||
return underIs(t, func(u types.Type) bool {
|
||||
// empty type set (u==nil) => any underlying types => not nillable
|
||||
return u != nil && nillable(u)
|
||||
})
|
||||
}
|
||||
switch t.Underlying().(type) {
|
||||
case *types.Pointer, *types.Slice, *types.Chan, *types.Map, *types.Signature:
|
||||
return true
|
||||
case *types.Interface:
|
||||
return true // basic interface.
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(adonovan): move everything below into golang.org/x/tools/go/ssa/interp.
|
||||
|
||||
// Int64 returns the numeric value of this constant truncated to fit
|
||||
// a signed 64-bit integer.
|
||||
func (c *Const) Int64() int64 {
|
||||
switch x := constant.ToInt(c.Value); x.Kind() {
|
||||
case constant.Int:
|
||||
if i, ok := constant.Int64Val(x); ok {
|
||||
return i
|
||||
}
|
||||
return 0
|
||||
case constant.Float:
|
||||
f, _ := constant.Float64Val(x)
|
||||
return int64(f)
|
||||
}
|
||||
panic(fmt.Sprintf("unexpected constant value: %T", c.Value))
|
||||
}
|
||||
|
||||
// Uint64 returns the numeric value of this constant truncated to fit
|
||||
// an unsigned 64-bit integer.
|
||||
func (c *Const) Uint64() uint64 {
|
||||
switch x := constant.ToInt(c.Value); x.Kind() {
|
||||
case constant.Int:
|
||||
if u, ok := constant.Uint64Val(x); ok {
|
||||
return u
|
||||
}
|
||||
return 0
|
||||
case constant.Float:
|
||||
f, _ := constant.Float64Val(x)
|
||||
return uint64(f)
|
||||
}
|
||||
panic(fmt.Sprintf("unexpected constant value: %T", c.Value))
|
||||
}
|
||||
|
||||
// Float64 returns the numeric value of this constant truncated to fit
|
||||
// a float64.
|
||||
func (c *Const) Float64() float64 {
|
||||
x := constant.ToFloat(c.Value) // (c.Value == nil) => x.Kind() == Unknown
|
||||
f, _ := constant.Float64Val(x)
|
||||
return f
|
||||
}
|
||||
|
||||
// Complex128 returns the complex value of this constant truncated to
|
||||
// fit a complex128.
|
||||
func (c *Const) Complex128() complex128 {
|
||||
x := constant.ToComplex(c.Value) // (c.Value == nil) => x.Kind() == Unknown
|
||||
re, _ := constant.Float64Val(constant.Real(x))
|
||||
im, _ := constant.Float64Val(constant.Imag(x))
|
||||
return complex(re, im)
|
||||
}
|
||||
332
vendor/golang.org/x/tools/go/ssa/create.go
generated
vendored
Normal file
332
vendor/golang.org/x/tools/go/ssa/create.go
generated
vendored
Normal file
@@ -0,0 +1,332 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssa
|
||||
|
||||
// This file implements the CREATE phase of SSA construction.
|
||||
// See builder.go for explanation.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/tools/internal/ssainternal"
|
||||
"golang.org/x/tools/internal/versions"
|
||||
)
|
||||
|
||||
// NewProgram returns a new SSA Program.
|
||||
//
|
||||
// mode controls diagnostics and checking during SSA construction.
|
||||
//
|
||||
// To construct an SSA program:
|
||||
//
|
||||
// - Call NewProgram to create an empty Program.
|
||||
// - Call CreatePackage providing typed syntax for each package
|
||||
// you want to build, and call it with types but not
|
||||
// syntax for each of those package's direct dependencies.
|
||||
// - Call [Package.Build] on each syntax package you wish to build,
|
||||
// or [Program.Build] to build all of them.
|
||||
//
|
||||
// See the Example tests for simple examples.
|
||||
func NewProgram(fset *token.FileSet, mode BuilderMode) *Program {
|
||||
return &Program{
|
||||
Fset: fset,
|
||||
imported: make(map[string]*Package),
|
||||
packages: make(map[*types.Package]*Package),
|
||||
mode: mode,
|
||||
canon: newCanonizer(),
|
||||
ctxt: types.NewContext(),
|
||||
}
|
||||
}
|
||||
|
||||
// memberFromObject populates package pkg with a member for the
|
||||
// typechecker object obj.
|
||||
//
|
||||
// For objects from Go source code, syntax is the associated syntax
|
||||
// tree (for funcs and vars only) and goversion defines the
|
||||
// appropriate interpretation; they will be used during the build
|
||||
// phase.
|
||||
func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node, goversion string) {
|
||||
name := obj.Name()
|
||||
switch obj := obj.(type) {
|
||||
case *types.Builtin:
|
||||
if pkg.Pkg != types.Unsafe {
|
||||
panic("unexpected builtin object: " + obj.String())
|
||||
}
|
||||
|
||||
case *types.TypeName:
|
||||
if name != "_" {
|
||||
pkg.Members[name] = &Type{
|
||||
object: obj,
|
||||
pkg: pkg,
|
||||
}
|
||||
}
|
||||
|
||||
case *types.Const:
|
||||
c := &NamedConst{
|
||||
object: obj,
|
||||
Value: NewConst(obj.Val(), obj.Type()),
|
||||
pkg: pkg,
|
||||
}
|
||||
pkg.objects[obj] = c
|
||||
if name != "_" {
|
||||
pkg.Members[name] = c
|
||||
}
|
||||
|
||||
case *types.Var:
|
||||
g := &Global{
|
||||
Pkg: pkg,
|
||||
name: name,
|
||||
object: obj,
|
||||
typ: types.NewPointer(obj.Type()), // address
|
||||
pos: obj.Pos(),
|
||||
}
|
||||
pkg.objects[obj] = g
|
||||
if name != "_" {
|
||||
pkg.Members[name] = g
|
||||
}
|
||||
|
||||
case *types.Func:
|
||||
sig := obj.Type().(*types.Signature)
|
||||
if sig.Recv() == nil && name == "init" {
|
||||
pkg.ninit++
|
||||
name = fmt.Sprintf("init#%d", pkg.ninit)
|
||||
}
|
||||
fn := createFunction(pkg.Prog, obj, name, syntax, pkg.info, goversion)
|
||||
fn.Pkg = pkg
|
||||
pkg.created = append(pkg.created, fn)
|
||||
pkg.objects[obj] = fn
|
||||
if name != "_" && sig.Recv() == nil {
|
||||
pkg.Members[name] = fn // package-level function
|
||||
}
|
||||
|
||||
default: // (incl. *types.Package)
|
||||
panic("unexpected Object type: " + obj.String())
|
||||
}
|
||||
}
|
||||
|
||||
// createFunction creates a function or method. It supports both
|
||||
// CreatePackage (with or without syntax) and the on-demand creation
|
||||
// of methods in non-created packages based on their types.Func.
|
||||
func createFunction(prog *Program, obj *types.Func, name string, syntax ast.Node, info *types.Info, goversion string) *Function {
|
||||
sig := obj.Type().(*types.Signature)
|
||||
|
||||
// Collect type parameters.
|
||||
var tparams *types.TypeParamList
|
||||
if rtparams := sig.RecvTypeParams(); rtparams.Len() > 0 {
|
||||
tparams = rtparams // method of generic type
|
||||
} else if sigparams := sig.TypeParams(); sigparams.Len() > 0 {
|
||||
tparams = sigparams // generic function
|
||||
}
|
||||
|
||||
/* declared function/method (from syntax or export data) */
|
||||
fn := &Function{
|
||||
name: name,
|
||||
object: obj,
|
||||
Signature: sig,
|
||||
build: (*builder).buildFromSyntax,
|
||||
syntax: syntax,
|
||||
info: info,
|
||||
goversion: goversion,
|
||||
pos: obj.Pos(),
|
||||
Pkg: nil, // may be set by caller
|
||||
Prog: prog,
|
||||
typeparams: tparams,
|
||||
}
|
||||
if fn.syntax == nil {
|
||||
fn.Synthetic = "from type information"
|
||||
fn.build = (*builder).buildParamsOnly
|
||||
}
|
||||
if tparams.Len() > 0 {
|
||||
fn.generic = new(generic)
|
||||
}
|
||||
return fn
|
||||
}
|
||||
|
||||
// membersFromDecl populates package pkg with members for each
|
||||
// typechecker object (var, func, const or type) associated with the
|
||||
// specified decl.
|
||||
func membersFromDecl(pkg *Package, decl ast.Decl, goversion string) {
|
||||
switch decl := decl.(type) {
|
||||
case *ast.GenDecl: // import, const, type or var
|
||||
switch decl.Tok {
|
||||
case token.CONST:
|
||||
for _, spec := range decl.Specs {
|
||||
for _, id := range spec.(*ast.ValueSpec).Names {
|
||||
memberFromObject(pkg, pkg.info.Defs[id], nil, "")
|
||||
}
|
||||
}
|
||||
|
||||
case token.VAR:
|
||||
for _, spec := range decl.Specs {
|
||||
for _, rhs := range spec.(*ast.ValueSpec).Values {
|
||||
pkg.initVersion[rhs] = goversion
|
||||
}
|
||||
for _, id := range spec.(*ast.ValueSpec).Names {
|
||||
memberFromObject(pkg, pkg.info.Defs[id], spec, goversion)
|
||||
}
|
||||
}
|
||||
|
||||
case token.TYPE:
|
||||
for _, spec := range decl.Specs {
|
||||
id := spec.(*ast.TypeSpec).Name
|
||||
memberFromObject(pkg, pkg.info.Defs[id], nil, "")
|
||||
}
|
||||
}
|
||||
|
||||
case *ast.FuncDecl:
|
||||
id := decl.Name
|
||||
memberFromObject(pkg, pkg.info.Defs[id], decl, goversion)
|
||||
}
|
||||
}
|
||||
|
||||
// CreatePackage creates and returns an SSA Package from the
|
||||
// specified type-checked, error-free file ASTs, and populates its
|
||||
// Members mapping.
|
||||
//
|
||||
// importable determines whether this package should be returned by a
|
||||
// subsequent call to ImportedPackage(pkg.Path()).
|
||||
//
|
||||
// The real work of building SSA form for each function is not done
|
||||
// until a subsequent call to Package.Build.
|
||||
func (prog *Program) CreatePackage(pkg *types.Package, files []*ast.File, info *types.Info, importable bool) *Package {
|
||||
if pkg == nil {
|
||||
panic("nil pkg") // otherwise pkg.Scope below returns types.Universe!
|
||||
}
|
||||
p := &Package{
|
||||
Prog: prog,
|
||||
Members: make(map[string]Member),
|
||||
objects: make(map[types.Object]Member),
|
||||
Pkg: pkg,
|
||||
syntax: info != nil,
|
||||
// transient values (cleared after Package.Build)
|
||||
info: info,
|
||||
files: files,
|
||||
initVersion: make(map[ast.Expr]string),
|
||||
}
|
||||
|
||||
/* synthesized package initializer */
|
||||
p.init = &Function{
|
||||
name: "init",
|
||||
Signature: new(types.Signature),
|
||||
Synthetic: "package initializer",
|
||||
Pkg: p,
|
||||
Prog: prog,
|
||||
build: (*builder).buildPackageInit,
|
||||
info: p.info,
|
||||
goversion: "", // See Package.build for details.
|
||||
}
|
||||
p.Members[p.init.name] = p.init
|
||||
p.created = append(p.created, p.init)
|
||||
|
||||
// Allocate all package members: vars, funcs, consts and types.
|
||||
if len(files) > 0 {
|
||||
// Go source package.
|
||||
for _, file := range files {
|
||||
goversion := versions.Lang(versions.FileVersion(p.info, file))
|
||||
for _, decl := range file.Decls {
|
||||
membersFromDecl(p, decl, goversion)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// GC-compiled binary package (or "unsafe")
|
||||
// No code.
|
||||
// No position information.
|
||||
scope := p.Pkg.Scope()
|
||||
for _, name := range scope.Names() {
|
||||
obj := scope.Lookup(name)
|
||||
memberFromObject(p, obj, nil, "")
|
||||
if obj, ok := obj.(*types.TypeName); ok {
|
||||
// No Unalias: aliases should not duplicate methods.
|
||||
if named, ok := obj.Type().(*types.Named); ok {
|
||||
for i, n := 0, named.NumMethods(); i < n; i++ {
|
||||
memberFromObject(p, named.Method(i), nil, "")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if prog.mode&BareInits == 0 {
|
||||
// Add initializer guard variable.
|
||||
initguard := &Global{
|
||||
Pkg: p,
|
||||
name: "init$guard",
|
||||
typ: types.NewPointer(tBool),
|
||||
}
|
||||
p.Members[initguard.Name()] = initguard
|
||||
}
|
||||
|
||||
if prog.mode&GlobalDebug != 0 {
|
||||
p.SetDebugMode(true)
|
||||
}
|
||||
|
||||
if prog.mode&PrintPackages != 0 {
|
||||
printMu.Lock()
|
||||
p.WriteTo(os.Stdout)
|
||||
printMu.Unlock()
|
||||
}
|
||||
|
||||
if importable {
|
||||
prog.imported[p.Pkg.Path()] = p
|
||||
}
|
||||
prog.packages[p.Pkg] = p
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
// printMu serializes printing of Packages/Functions to stdout.
|
||||
var printMu sync.Mutex
|
||||
|
||||
// AllPackages returns a new slice containing all packages created by
|
||||
// prog.CreatePackage in unspecified order.
|
||||
func (prog *Program) AllPackages() []*Package {
|
||||
pkgs := make([]*Package, 0, len(prog.packages))
|
||||
for _, pkg := range prog.packages {
|
||||
pkgs = append(pkgs, pkg)
|
||||
}
|
||||
return pkgs
|
||||
}
|
||||
|
||||
// ImportedPackage returns the importable Package whose PkgPath
|
||||
// is path, or nil if no such Package has been created.
|
||||
//
|
||||
// A parameter to CreatePackage determines whether a package should be
|
||||
// considered importable. For example, no import declaration can resolve
|
||||
// to the ad-hoc main package created by 'go build foo.go'.
|
||||
//
|
||||
// TODO(adonovan): rethink this function and the "importable" concept;
|
||||
// most packages are importable. This function assumes that all
|
||||
// types.Package.Path values are unique within the ssa.Program, which is
|
||||
// false---yet this function remains very convenient.
|
||||
// Clients should use (*Program).Package instead where possible.
|
||||
// SSA doesn't really need a string-keyed map of packages.
|
||||
//
|
||||
// Furthermore, the graph of packages may contain multiple variants
|
||||
// (e.g. "p" vs "p as compiled for q.test"), and each has a different
|
||||
// view of its dependencies.
|
||||
func (prog *Program) ImportedPackage(path string) *Package {
|
||||
return prog.imported[path]
|
||||
}
|
||||
|
||||
// setNoReturn sets the predicate used by the SSA builder to decide
|
||||
// whether a call to the specified named function cannot return,
|
||||
// allowing the builder to prune control-flow edges following the
|
||||
// call, thus improving the precision of downstream analysis.
|
||||
//
|
||||
// TODO(adonovan): add (*Program).SetNoReturn to the public API.
|
||||
func (prog *Program) setNoReturn(noReturn func(*types.Func) bool) {
|
||||
prog.noReturn = noReturn
|
||||
}
|
||||
|
||||
func init() {
|
||||
// SetNoReturn exposes Program.setNoReturn to the buildssa analyzer.
|
||||
ssainternal.SetNoReturn = func(prog any, noReturn func(*types.Func) bool) {
|
||||
prog.(*Program).setNoReturn(noReturn)
|
||||
}
|
||||
}
|
||||
122
vendor/golang.org/x/tools/go/ssa/doc.go
generated
vendored
Normal file
122
vendor/golang.org/x/tools/go/ssa/doc.go
generated
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package ssa defines a representation of the elements of Go programs
|
||||
// (packages, types, functions, variables and constants) using a
|
||||
// static single-assignment (SSA) form intermediate representation
|
||||
// (IR) for the bodies of functions.
|
||||
//
|
||||
// For an introduction to SSA form, see
|
||||
// http://en.wikipedia.org/wiki/Static_single_assignment_form.
|
||||
// This page provides a broader reading list:
|
||||
// http://www.dcs.gla.ac.uk/~jsinger/ssa.html.
|
||||
//
|
||||
// The level of abstraction of the SSA form is intentionally close to
|
||||
// the source language to facilitate construction of source analysis
|
||||
// tools. It is not intended for machine code generation.
|
||||
//
|
||||
// All looping, branching and switching constructs are replaced with
|
||||
// unstructured control flow. Higher-level control flow constructs
|
||||
// such as multi-way branch can be reconstructed as needed; see
|
||||
// [golang.org/x/tools/go/ssa/ssautil.Switches] for an example.
|
||||
//
|
||||
// The simplest way to create the SSA representation of a package is
|
||||
// to load typed syntax trees using [golang.org/x/tools/go/packages], then
|
||||
// invoke the [golang.org/x/tools/go/ssa/ssautil.Packages] helper function.
|
||||
// (See the package-level Examples named LoadPackages and LoadWholeProgram.)
|
||||
// The resulting [ssa.Program] contains all the packages and their
|
||||
// members, but SSA code is not created for function bodies until a
|
||||
// subsequent call to [Package.Build] or [Program.Build].
|
||||
//
|
||||
// The builder initially builds a naive SSA form in which all local
|
||||
// variables are addresses of stack locations with explicit loads and
|
||||
// stores. Registerisation of eligible locals and φ-node insertion
|
||||
// using dominance and dataflow are then performed as a second pass
|
||||
// called "lifting" to improve the accuracy and performance of
|
||||
// subsequent analyses; this pass can be skipped by setting the
|
||||
// NaiveForm builder flag.
|
||||
//
|
||||
// The primary interfaces of this package are:
|
||||
//
|
||||
// - [Member]: a named member of a Go package.
|
||||
// - [Value]: an expression that yields a value.
|
||||
// - [Instruction]: a statement that consumes values and performs computation.
|
||||
// - [Node]: a [Value] or [Instruction] (emphasizing its membership in the SSA value graph)
|
||||
//
|
||||
// A computation that yields a result implements both the [Value] and
|
||||
// [Instruction] interfaces. The following table shows for each
|
||||
// concrete type which of these interfaces it implements.
|
||||
//
|
||||
// Value? Instruction? Member?
|
||||
// *Alloc ✔ ✔
|
||||
// *BinOp ✔ ✔
|
||||
// *Builtin ✔
|
||||
// *Call ✔ ✔
|
||||
// *ChangeInterface ✔ ✔
|
||||
// *ChangeType ✔ ✔
|
||||
// *Const ✔
|
||||
// *Convert ✔ ✔
|
||||
// *DebugRef ✔
|
||||
// *Defer ✔
|
||||
// *Extract ✔ ✔
|
||||
// *Field ✔ ✔
|
||||
// *FieldAddr ✔ ✔
|
||||
// *FreeVar ✔
|
||||
// *Function ✔ ✔ (func)
|
||||
// *Global ✔ ✔ (var)
|
||||
// *Go ✔
|
||||
// *If ✔
|
||||
// *Index ✔ ✔
|
||||
// *IndexAddr ✔ ✔
|
||||
// *Jump ✔
|
||||
// *Lookup ✔ ✔
|
||||
// *MakeChan ✔ ✔
|
||||
// *MakeClosure ✔ ✔
|
||||
// *MakeInterface ✔ ✔
|
||||
// *MakeMap ✔ ✔
|
||||
// *MakeSlice ✔ ✔
|
||||
// *MapUpdate ✔
|
||||
// *MultiConvert ✔ ✔
|
||||
// *NamedConst ✔ (const)
|
||||
// *Next ✔ ✔
|
||||
// *Panic ✔
|
||||
// *Parameter ✔
|
||||
// *Phi ✔ ✔
|
||||
// *Range ✔ ✔
|
||||
// *Return ✔
|
||||
// *RunDefers ✔
|
||||
// *Select ✔ ✔
|
||||
// *Send ✔
|
||||
// *Slice ✔ ✔
|
||||
// *SliceToArrayPointer ✔ ✔
|
||||
// *Store ✔
|
||||
// *Type ✔ (type)
|
||||
// *TypeAssert ✔ ✔
|
||||
// *UnOp ✔ ✔
|
||||
//
|
||||
// Other key types in this package include: [Program], [Package], [Function]
|
||||
// and [BasicBlock].
|
||||
//
|
||||
// The program representation constructed by this package is fully
|
||||
// resolved internally, i.e. it does not rely on the names of Values,
|
||||
// Packages, Functions, Types or BasicBlocks for the correct
|
||||
// interpretation of the program. Only the identities of objects and
|
||||
// the topology of the SSA and type graphs are semantically
|
||||
// significant. (There is one exception: [types.Id] values, which identify field
|
||||
// and method names, contain strings.) Avoidance of name-based
|
||||
// operations simplifies the implementation of subsequent passes and
|
||||
// can make them very efficient. Many objects are nonetheless named
|
||||
// to aid in debugging, but it is not essential that the names be
|
||||
// either accurate or unambiguous. The public API exposes a number of
|
||||
// name-based maps for client convenience.
|
||||
//
|
||||
// The [golang.org/x/tools/go/ssa/ssautil] package provides various
|
||||
// helper functions, for example to simplify loading a Go program into
|
||||
// SSA form.
|
||||
//
|
||||
// TODO(adonovan): write a how-to document for all the various cases
|
||||
// of trying to determine corresponding elements across the four
|
||||
// domains of source locations, ast.Nodes, types.Objects,
|
||||
// ssa.Values/Instructions.
|
||||
package ssa // import "golang.org/x/tools/go/ssa"
|
||||
342
vendor/golang.org/x/tools/go/ssa/dom.go
generated
vendored
Normal file
342
vendor/golang.org/x/tools/go/ssa/dom.go
generated
vendored
Normal file
@@ -0,0 +1,342 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssa
|
||||
|
||||
// This file defines algorithms related to dominance.
|
||||
|
||||
// Dominator tree construction ----------------------------------------
|
||||
//
|
||||
// We use the algorithm described in Lengauer & Tarjan. 1979. A fast
|
||||
// algorithm for finding dominators in a flowgraph.
|
||||
// http://doi.acm.org/10.1145/357062.357071
|
||||
//
|
||||
// We also apply the optimizations to SLT described in Georgiadis et
|
||||
// al, Finding Dominators in Practice, JGAA 2006,
|
||||
// http://jgaa.info/accepted/2006/GeorgiadisTarjanWerneck2006.10.1.pdf
|
||||
// to avoid the need for buckets of size > 1.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"slices"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Idom returns the block that immediately dominates b:
|
||||
// its parent in the dominator tree, if any.
|
||||
// Neither the entry node (b.Index==0) nor recover node
|
||||
// (b==b.Parent().Recover()) have a parent.
|
||||
func (b *BasicBlock) Idom() *BasicBlock { return b.dom.idom }
|
||||
|
||||
// Dominees returns the list of blocks that b immediately dominates:
|
||||
// its children in the dominator tree.
|
||||
func (b *BasicBlock) Dominees() []*BasicBlock { return b.dom.children }
|
||||
|
||||
// Dominates reports whether b dominates c.
|
||||
func (b *BasicBlock) Dominates(c *BasicBlock) bool {
|
||||
return b.dom.pre <= c.dom.pre && c.dom.post <= b.dom.post
|
||||
}
|
||||
|
||||
// DomPreorder returns a new slice containing the blocks of f
|
||||
// in a preorder traversal of the dominator tree.
|
||||
func (f *Function) DomPreorder() []*BasicBlock {
|
||||
slice := slices.Clone(f.Blocks)
|
||||
sort.Slice(slice, func(i, j int) bool {
|
||||
return slice[i].dom.pre < slice[j].dom.pre
|
||||
})
|
||||
return slice
|
||||
}
|
||||
|
||||
// DomPostorder returns a new slice containing the blocks of f
|
||||
// in a postorder traversal of the dominator tree.
|
||||
// (This is not the same as a postdominance order.)
|
||||
func (f *Function) DomPostorder() []*BasicBlock {
|
||||
slice := slices.Clone(f.Blocks)
|
||||
sort.Slice(slice, func(i, j int) bool {
|
||||
return slice[i].dom.post < slice[j].dom.post
|
||||
})
|
||||
return slice
|
||||
}
|
||||
|
||||
// domInfo contains a BasicBlock's dominance information.
|
||||
type domInfo struct {
|
||||
idom *BasicBlock // immediate dominator (parent in domtree)
|
||||
children []*BasicBlock // nodes immediately dominated by this one
|
||||
pre, post int32 // pre- and post-order numbering within domtree
|
||||
}
|
||||
|
||||
// ltState holds the working state for Lengauer-Tarjan algorithm
|
||||
// (during which domInfo.pre is repurposed for CFG DFS preorder number).
|
||||
type ltState struct {
|
||||
// Each slice is indexed by b.Index.
|
||||
sdom []*BasicBlock // b's semidominator
|
||||
parent []*BasicBlock // b's parent in DFS traversal of CFG
|
||||
ancestor []*BasicBlock // b's ancestor with least sdom
|
||||
}
|
||||
|
||||
// dfs implements the depth-first search part of the LT algorithm.
|
||||
func (lt *ltState) dfs(v *BasicBlock, i int32, preorder []*BasicBlock) int32 {
|
||||
preorder[i] = v
|
||||
v.dom.pre = i // For now: DFS preorder of spanning tree of CFG
|
||||
i++
|
||||
lt.sdom[v.Index] = v
|
||||
lt.link(nil, v)
|
||||
for _, w := range v.Succs {
|
||||
if lt.sdom[w.Index] == nil {
|
||||
lt.parent[w.Index] = v
|
||||
i = lt.dfs(w, i, preorder)
|
||||
}
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
// eval implements the EVAL part of the LT algorithm.
|
||||
func (lt *ltState) eval(v *BasicBlock) *BasicBlock {
|
||||
// TODO(adonovan): opt: do path compression per simple LT.
|
||||
u := v
|
||||
for ; lt.ancestor[v.Index] != nil; v = lt.ancestor[v.Index] {
|
||||
if lt.sdom[v.Index].dom.pre < lt.sdom[u.Index].dom.pre {
|
||||
u = v
|
||||
}
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
// link implements the LINK part of the LT algorithm.
|
||||
func (lt *ltState) link(v, w *BasicBlock) {
|
||||
lt.ancestor[w.Index] = v
|
||||
}
|
||||
|
||||
// buildDomTree computes the dominator tree of f using the LT algorithm.
|
||||
// Precondition: all blocks are reachable (e.g. optimizeBlocks has been run).
|
||||
func buildDomTree(f *Function) {
|
||||
// The step numbers refer to the original LT paper; the
|
||||
// reordering is due to Georgiadis.
|
||||
|
||||
// Clear any previous domInfo.
|
||||
for _, b := range f.Blocks {
|
||||
b.dom = domInfo{}
|
||||
}
|
||||
|
||||
n := len(f.Blocks)
|
||||
// Allocate space for 5 contiguous [n]*BasicBlock arrays:
|
||||
// sdom, parent, ancestor, preorder, buckets.
|
||||
space := make([]*BasicBlock, 5*n)
|
||||
lt := ltState{
|
||||
sdom: space[0:n],
|
||||
parent: space[n : 2*n],
|
||||
ancestor: space[2*n : 3*n],
|
||||
}
|
||||
|
||||
// Step 1. Number vertices by depth-first preorder.
|
||||
preorder := space[3*n : 4*n]
|
||||
root := f.Blocks[0]
|
||||
prenum := lt.dfs(root, 0, preorder)
|
||||
recover := f.Recover
|
||||
if recover != nil {
|
||||
lt.dfs(recover, prenum, preorder)
|
||||
}
|
||||
|
||||
buckets := space[4*n : 5*n]
|
||||
copy(buckets, preorder)
|
||||
|
||||
// In reverse preorder...
|
||||
for i := int32(n) - 1; i > 0; i-- {
|
||||
w := preorder[i]
|
||||
|
||||
// Step 3. Implicitly define the immediate dominator of each node.
|
||||
for v := buckets[i]; v != w; v = buckets[v.dom.pre] {
|
||||
u := lt.eval(v)
|
||||
if lt.sdom[u.Index].dom.pre < i {
|
||||
v.dom.idom = u
|
||||
} else {
|
||||
v.dom.idom = w
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2. Compute the semidominators of all nodes.
|
||||
lt.sdom[w.Index] = lt.parent[w.Index]
|
||||
for _, v := range w.Preds {
|
||||
u := lt.eval(v)
|
||||
if lt.sdom[u.Index].dom.pre < lt.sdom[w.Index].dom.pre {
|
||||
lt.sdom[w.Index] = lt.sdom[u.Index]
|
||||
}
|
||||
}
|
||||
|
||||
lt.link(lt.parent[w.Index], w)
|
||||
|
||||
if lt.parent[w.Index] == lt.sdom[w.Index] {
|
||||
w.dom.idom = lt.parent[w.Index]
|
||||
} else {
|
||||
buckets[i] = buckets[lt.sdom[w.Index].dom.pre]
|
||||
buckets[lt.sdom[w.Index].dom.pre] = w
|
||||
}
|
||||
}
|
||||
|
||||
// The final 'Step 3' is now outside the loop.
|
||||
for v := buckets[0]; v != root; v = buckets[v.dom.pre] {
|
||||
v.dom.idom = root
|
||||
}
|
||||
|
||||
// Step 4. Explicitly define the immediate dominator of each
|
||||
// node, in preorder.
|
||||
for _, w := range preorder[1:] {
|
||||
if w == root || w == recover {
|
||||
w.dom.idom = nil
|
||||
} else {
|
||||
if w.dom.idom != lt.sdom[w.Index] {
|
||||
w.dom.idom = w.dom.idom.dom.idom
|
||||
}
|
||||
// Calculate Children relation as inverse of Idom.
|
||||
w.dom.idom.dom.children = append(w.dom.idom.dom.children, w)
|
||||
}
|
||||
}
|
||||
|
||||
pre, post := numberDomTree(root, 0, 0)
|
||||
if recover != nil {
|
||||
numberDomTree(recover, pre, post)
|
||||
}
|
||||
|
||||
// printDomTreeDot(os.Stderr, f) // debugging
|
||||
// printDomTreeText(os.Stderr, root, 0) // debugging
|
||||
|
||||
if f.Prog.mode&SanityCheckFunctions != 0 {
|
||||
sanityCheckDomTree(f)
|
||||
}
|
||||
}
|
||||
|
||||
// numberDomTree sets the pre- and post-order numbers of a depth-first
|
||||
// traversal of the dominator tree rooted at v. These are used to
|
||||
// answer dominance queries in constant time.
|
||||
func numberDomTree(v *BasicBlock, pre, post int32) (int32, int32) {
|
||||
v.dom.pre = pre
|
||||
pre++
|
||||
for _, child := range v.dom.children {
|
||||
pre, post = numberDomTree(child, pre, post)
|
||||
}
|
||||
v.dom.post = post
|
||||
post++
|
||||
return pre, post
|
||||
}
|
||||
|
||||
// Testing utilities ----------------------------------------
|
||||
|
||||
// sanityCheckDomTree checks the correctness of the dominator tree
|
||||
// computed by the LT algorithm by comparing against the dominance
|
||||
// relation computed by a naive Kildall-style forward dataflow
|
||||
// analysis (Algorithm 10.16 from the "Dragon" book).
|
||||
func sanityCheckDomTree(f *Function) {
|
||||
n := len(f.Blocks)
|
||||
|
||||
// D[i] is the set of blocks that dominate f.Blocks[i],
|
||||
// represented as a bit-set of block indices.
|
||||
D := make([]big.Int, n)
|
||||
|
||||
one := big.NewInt(1)
|
||||
|
||||
// all is the set of all blocks; constant.
|
||||
var all big.Int
|
||||
all.Set(one).Lsh(&all, uint(n)).Sub(&all, one)
|
||||
|
||||
// Initialization.
|
||||
for i, b := range f.Blocks {
|
||||
if i == 0 || b == f.Recover {
|
||||
// A root is dominated only by itself.
|
||||
D[i].SetBit(&D[0], 0, 1)
|
||||
} else {
|
||||
// All other blocks are (initially) dominated
|
||||
// by every block.
|
||||
D[i].Set(&all)
|
||||
}
|
||||
}
|
||||
|
||||
// Iteration until fixed point.
|
||||
for changed := true; changed; {
|
||||
changed = false
|
||||
for i, b := range f.Blocks {
|
||||
if i == 0 || b == f.Recover {
|
||||
continue
|
||||
}
|
||||
// Compute intersection across predecessors.
|
||||
var x big.Int
|
||||
x.Set(&all)
|
||||
for _, pred := range b.Preds {
|
||||
x.And(&x, &D[pred.Index])
|
||||
}
|
||||
x.SetBit(&x, i, 1) // a block always dominates itself.
|
||||
if D[i].Cmp(&x) != 0 {
|
||||
D[i].Set(&x)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check the entire relation. O(n^2).
|
||||
// The Recover block (if any) must be treated specially so we skip it.
|
||||
ok := true
|
||||
for i := range n {
|
||||
for j := range n {
|
||||
b, c := f.Blocks[i], f.Blocks[j]
|
||||
if c == f.Recover {
|
||||
continue
|
||||
}
|
||||
actual := b.Dominates(c)
|
||||
expected := D[j].Bit(i) == 1
|
||||
if actual != expected {
|
||||
fmt.Fprintf(os.Stderr, "dominates(%s, %s)==%t, want %t\n", b, c, actual, expected)
|
||||
ok = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
preorder := f.DomPreorder()
|
||||
for _, b := range f.Blocks {
|
||||
if got := preorder[b.dom.pre]; got != b {
|
||||
fmt.Fprintf(os.Stderr, "preorder[%d]==%s, want %s\n", b.dom.pre, got, b)
|
||||
ok = false
|
||||
}
|
||||
}
|
||||
|
||||
if !ok {
|
||||
panic("sanityCheckDomTree failed for " + f.String())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Printing functions ----------------------------------------
|
||||
|
||||
// printDomTreeText prints the dominator tree as text, using indentation.
|
||||
func printDomTreeText(buf *bytes.Buffer, v *BasicBlock, indent int) {
|
||||
fmt.Fprintf(buf, "%*s%s\n", 4*indent, "", v)
|
||||
for _, child := range v.dom.children {
|
||||
printDomTreeText(buf, child, indent+1)
|
||||
}
|
||||
}
|
||||
|
||||
// printDomTreeDot prints the dominator tree of f in AT&T GraphViz
|
||||
// (.dot) format.
|
||||
// (unused; retained for debugging)
|
||||
func printDomTreeDot(buf *bytes.Buffer, f *Function) {
|
||||
fmt.Fprintln(buf, "//", f)
|
||||
fmt.Fprintln(buf, "digraph domtree {")
|
||||
for i, b := range f.Blocks {
|
||||
v := b.dom
|
||||
fmt.Fprintf(buf, "\tn%d [label=\"%s (%d, %d)\",shape=\"rectangle\"];\n", v.pre, b, v.pre, v.post)
|
||||
// TODO(adonovan): improve appearance of edges
|
||||
// belonging to both dominator tree and CFG.
|
||||
|
||||
// Dominator tree edge.
|
||||
if i != 0 {
|
||||
fmt.Fprintf(buf, "\tn%d -> n%d [style=\"solid\",weight=100];\n", v.idom.dom.pre, v.pre)
|
||||
}
|
||||
// CFG edges.
|
||||
for _, pred := range b.Preds {
|
||||
fmt.Fprintf(buf, "\tn%d -> n%d [style=\"dotted\",weight=0];\n", pred.dom.pre, v.pre)
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(buf, "}")
|
||||
}
|
||||
629
vendor/golang.org/x/tools/go/ssa/emit.go
generated
vendored
Normal file
629
vendor/golang.org/x/tools/go/ssa/emit.go
generated
vendored
Normal file
@@ -0,0 +1,629 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssa
|
||||
|
||||
// Helpers for emitting SSA instructions.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/internal/typeparams"
|
||||
)
|
||||
|
||||
// emitAlloc emits to f a new Alloc instruction allocating a variable
|
||||
// of type typ.
|
||||
//
|
||||
// The caller must set Alloc.Heap=true (for a heap-allocated variable)
|
||||
// or add the Alloc to f.Locals (for a frame-allocated variable).
|
||||
//
|
||||
// During building, a variable in f.Locals may have its Heap flag
|
||||
// set when it is discovered that its address is taken.
|
||||
// These Allocs are removed from f.Locals at the end.
|
||||
//
|
||||
// The builder should generally call one of the emit{New,Local,LocalVar} wrappers instead.
|
||||
func emitAlloc(f *Function, typ types.Type, pos token.Pos, comment string) *Alloc {
|
||||
v := &Alloc{Comment: comment}
|
||||
v.setType(types.NewPointer(typ))
|
||||
v.setPos(pos)
|
||||
f.emit(v)
|
||||
return v
|
||||
}
|
||||
|
||||
// emitNew emits to f a new Alloc instruction heap-allocating a
|
||||
// variable of type typ. pos is the optional source location.
|
||||
func emitNew(f *Function, typ types.Type, pos token.Pos, comment string) *Alloc {
|
||||
alloc := emitAlloc(f, typ, pos, comment)
|
||||
alloc.Heap = true
|
||||
return alloc
|
||||
}
|
||||
|
||||
// emitLocal creates a local var for (t, pos, comment) and
|
||||
// emits an Alloc instruction for it.
|
||||
//
|
||||
// (Use this function or emitNew for synthetic variables;
|
||||
// for source-level variables in the same function, use emitLocalVar.)
|
||||
func emitLocal(f *Function, t types.Type, pos token.Pos, comment string) *Alloc {
|
||||
local := emitAlloc(f, t, pos, comment)
|
||||
f.Locals = append(f.Locals, local)
|
||||
return local
|
||||
}
|
||||
|
||||
// emitLocalVar creates a local var for v and emits an Alloc instruction for it.
|
||||
// Subsequent calls to f.lookup(v) return it.
|
||||
// It applies the appropriate generic instantiation to the type.
|
||||
func emitLocalVar(f *Function, v *types.Var) *Alloc {
|
||||
alloc := emitLocal(f, f.typ(v.Type()), v.Pos(), v.Name())
|
||||
f.vars[v] = alloc
|
||||
return alloc
|
||||
}
|
||||
|
||||
// emitLoad emits to f an instruction to load the address addr into a
|
||||
// new temporary, and returns the value so defined.
|
||||
func emitLoad(f *Function, addr Value) *UnOp {
|
||||
v := &UnOp{Op: token.MUL, X: addr}
|
||||
v.setType(typeparams.MustDeref(addr.Type()))
|
||||
f.emit(v)
|
||||
return v
|
||||
}
|
||||
|
||||
// emitDebugRef emits to f a DebugRef pseudo-instruction associating
|
||||
// expression e with value v.
|
||||
func emitDebugRef(f *Function, e ast.Expr, v Value, isAddr bool) {
|
||||
if !f.debugInfo() {
|
||||
return // debugging not enabled
|
||||
}
|
||||
if v == nil || e == nil {
|
||||
panic("nil")
|
||||
}
|
||||
var obj types.Object
|
||||
e = ast.Unparen(e)
|
||||
if id, ok := e.(*ast.Ident); ok {
|
||||
if isBlankIdent(id) {
|
||||
return
|
||||
}
|
||||
obj = f.objectOf(id)
|
||||
switch obj.(type) {
|
||||
case *types.Nil, *types.Const, *types.Builtin:
|
||||
return
|
||||
}
|
||||
}
|
||||
f.emit(&DebugRef{
|
||||
X: v,
|
||||
Expr: e,
|
||||
IsAddr: isAddr,
|
||||
object: obj,
|
||||
})
|
||||
}
|
||||
|
||||
// emitArith emits to f code to compute the binary operation op(x, y)
|
||||
// where op is an eager shift, logical or arithmetic operation.
|
||||
// (Use emitCompare() for comparisons and Builder.logicalBinop() for
|
||||
// non-eager operations.)
|
||||
func emitArith(f *Function, op token.Token, x, y Value, t types.Type, pos token.Pos) Value {
|
||||
switch op {
|
||||
case token.SHL, token.SHR:
|
||||
x = emitConv(f, x, t)
|
||||
// y may be signed or an 'untyped' constant.
|
||||
|
||||
// There is a runtime panic if y is signed and <0. Instead of inserting a check for y<0
|
||||
// and converting to an unsigned value (like the compiler) leave y as is.
|
||||
|
||||
if isUntyped(y.Type().Underlying()) {
|
||||
// Untyped conversion:
|
||||
// Spec https://go.dev/ref/spec#Operators:
|
||||
// The right operand in a shift expression must have integer type or be an untyped constant
|
||||
// representable by a value of type uint.
|
||||
y = emitConv(f, y, types.Typ[types.Uint])
|
||||
}
|
||||
|
||||
case token.ADD, token.SUB, token.MUL, token.QUO, token.REM, token.AND, token.OR, token.XOR, token.AND_NOT:
|
||||
x = emitConv(f, x, t)
|
||||
y = emitConv(f, y, t)
|
||||
|
||||
default:
|
||||
panic("illegal op in emitArith: " + op.String())
|
||||
|
||||
}
|
||||
v := &BinOp{
|
||||
Op: op,
|
||||
X: x,
|
||||
Y: y,
|
||||
}
|
||||
v.setPos(pos)
|
||||
v.setType(t)
|
||||
return f.emit(v)
|
||||
}
|
||||
|
||||
// emitCompare emits to f code compute the boolean result of
|
||||
// comparison 'x op y'.
|
||||
func emitCompare(f *Function, op token.Token, x, y Value, pos token.Pos) Value {
|
||||
xt := x.Type().Underlying()
|
||||
yt := y.Type().Underlying()
|
||||
|
||||
// Special case to optimise a tagless SwitchStmt so that
|
||||
// these are equivalent
|
||||
// switch { case e: ...}
|
||||
// switch true { case e: ... }
|
||||
// if e==true { ... }
|
||||
// even in the case when e's type is an interface.
|
||||
// TODO(adonovan): opt: generalise to x==true, false!=y, etc.
|
||||
if x == vTrue && op == token.EQL {
|
||||
if yt, ok := yt.(*types.Basic); ok && yt.Info()&types.IsBoolean != 0 {
|
||||
return y
|
||||
}
|
||||
}
|
||||
|
||||
if types.Identical(xt, yt) {
|
||||
// no conversion necessary
|
||||
} else if isNonTypeParamInterface(x.Type()) {
|
||||
y = emitConv(f, y, x.Type())
|
||||
} else if isNonTypeParamInterface(y.Type()) {
|
||||
x = emitConv(f, x, y.Type())
|
||||
} else if _, ok := x.(*Const); ok {
|
||||
x = emitConv(f, x, y.Type())
|
||||
} else if _, ok := y.(*Const); ok {
|
||||
y = emitConv(f, y, x.Type())
|
||||
} else {
|
||||
// other cases, e.g. channels. No-op.
|
||||
}
|
||||
|
||||
v := &BinOp{
|
||||
Op: op,
|
||||
X: x,
|
||||
Y: y,
|
||||
}
|
||||
v.setPos(pos)
|
||||
v.setType(tBool)
|
||||
return f.emit(v)
|
||||
}
|
||||
|
||||
// isValuePreserving returns true if a conversion from ut_src to
|
||||
// ut_dst is value-preserving, i.e. just a change of type.
|
||||
// Precondition: neither argument is a named or alias type.
|
||||
func isValuePreserving(ut_src, ut_dst types.Type) bool {
|
||||
// Identical underlying types?
|
||||
if types.IdenticalIgnoreTags(ut_dst, ut_src) {
|
||||
return true
|
||||
}
|
||||
|
||||
switch ut_dst.(type) {
|
||||
case *types.Chan:
|
||||
// Conversion between channel types?
|
||||
_, ok := ut_src.(*types.Chan)
|
||||
return ok
|
||||
|
||||
case *types.Pointer:
|
||||
// Conversion between pointers with identical base types?
|
||||
_, ok := ut_src.(*types.Pointer)
|
||||
return ok
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// emitConv emits to f code to convert Value val to exactly type typ,
|
||||
// and returns the converted value. Implicit conversions are required
|
||||
// by language assignability rules in assignments, parameter passing,
|
||||
// etc.
|
||||
func emitConv(f *Function, val Value, typ types.Type) Value {
|
||||
t_src := val.Type()
|
||||
|
||||
// Identical types? Conversion is a no-op.
|
||||
if types.Identical(t_src, typ) {
|
||||
return val
|
||||
}
|
||||
ut_dst := typ.Underlying()
|
||||
ut_src := t_src.Underlying()
|
||||
|
||||
// Conversion to, or construction of a value of, an interface type?
|
||||
if isNonTypeParamInterface(typ) {
|
||||
// Interface name change?
|
||||
if isValuePreserving(ut_src, ut_dst) {
|
||||
c := &ChangeType{X: val}
|
||||
c.setType(typ)
|
||||
return f.emit(c)
|
||||
}
|
||||
|
||||
// Assignment from one interface type to another?
|
||||
if isNonTypeParamInterface(t_src) {
|
||||
c := &ChangeInterface{X: val}
|
||||
c.setType(typ)
|
||||
return f.emit(c)
|
||||
}
|
||||
|
||||
// Untyped nil constant? Return interface-typed nil constant.
|
||||
if ut_src == tUntypedNil {
|
||||
return zeroConst(typ)
|
||||
}
|
||||
|
||||
// Convert (non-nil) "untyped" literals to their default type.
|
||||
if t, ok := ut_src.(*types.Basic); ok && t.Info()&types.IsUntyped != 0 {
|
||||
val = emitConv(f, val, types.Default(ut_src))
|
||||
}
|
||||
|
||||
// Record the types of operands to MakeInterface, if
|
||||
// non-parameterized, as they are the set of runtime types.
|
||||
t := val.Type()
|
||||
if f.typeparams.Len() == 0 || !f.Prog.isParameterized(t) {
|
||||
addMakeInterfaceType(f.Prog, t)
|
||||
}
|
||||
|
||||
mi := &MakeInterface{X: val}
|
||||
mi.setType(typ)
|
||||
return f.emit(mi)
|
||||
}
|
||||
|
||||
// conversionCase describes an instruction pattern that maybe emitted to
|
||||
// model d <- s for d in dst_terms and s in src_terms.
|
||||
// Multiple conversions can match the same pattern.
|
||||
type conversionCase uint8
|
||||
const (
|
||||
changeType conversionCase = 1 << iota
|
||||
sliceToArray
|
||||
sliceToArrayPtr
|
||||
sliceTo0Array
|
||||
sliceTo0ArrayPtr
|
||||
convert
|
||||
)
|
||||
// classify the conversion case of a source type us to a destination type ud.
|
||||
// us and ud are underlying types (not *Named or *Alias)
|
||||
classify := func(us, ud types.Type) conversionCase {
|
||||
// Just a change of type, but not value or representation?
|
||||
if isValuePreserving(us, ud) {
|
||||
return changeType
|
||||
}
|
||||
|
||||
// Conversion from slice to array or slice to array pointer?
|
||||
if slice, ok := us.(*types.Slice); ok {
|
||||
var arr *types.Array
|
||||
var ptr bool
|
||||
// Conversion from slice to array pointer?
|
||||
switch d := ud.(type) {
|
||||
case *types.Array:
|
||||
arr = d
|
||||
case *types.Pointer:
|
||||
arr, _ = d.Elem().Underlying().(*types.Array)
|
||||
ptr = true
|
||||
}
|
||||
if arr != nil && types.Identical(slice.Elem(), arr.Elem()) {
|
||||
if arr.Len() == 0 {
|
||||
if ptr {
|
||||
return sliceTo0ArrayPtr
|
||||
} else {
|
||||
return sliceTo0Array
|
||||
}
|
||||
}
|
||||
if ptr {
|
||||
return sliceToArrayPtr
|
||||
} else {
|
||||
return sliceToArray
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The only remaining case in well-typed code is a representation-
|
||||
// changing conversion of basic types (possibly with []byte/[]rune).
|
||||
if !isBasic(us) && !isBasic(ud) {
|
||||
panic(fmt.Sprintf("in %s: cannot convert term %s (%s [within %s]) to type %s [within %s]", f, val, val.Type(), us, typ, ud))
|
||||
}
|
||||
return convert
|
||||
}
|
||||
|
||||
var classifications conversionCase
|
||||
underIs(ut_src, func(us types.Type) bool {
|
||||
return underIs(ut_dst, func(ud types.Type) bool {
|
||||
if us != nil && ud != nil {
|
||||
classifications |= classify(us, ud)
|
||||
}
|
||||
return classifications != 0
|
||||
})
|
||||
})
|
||||
if classifications == 0 {
|
||||
panic(fmt.Sprintf("in %s: cannot convert %s (%s) to %s", f, val, val.Type(), typ))
|
||||
}
|
||||
|
||||
// Conversion of a compile-time constant value?
|
||||
if c, ok := val.(*Const); ok {
|
||||
// Conversion to a basic type?
|
||||
if isBasic(ut_dst) {
|
||||
// Conversion of a compile-time constant to
|
||||
// another constant type results in a new
|
||||
// constant of the destination type and
|
||||
// (initially) the same abstract value.
|
||||
// We don't truncate the value yet.
|
||||
return NewConst(c.Value, typ)
|
||||
}
|
||||
// Can we always convert from zero value without panicking?
|
||||
const mayPanic = sliceToArray | sliceToArrayPtr
|
||||
if c.Value == nil && classifications&mayPanic == 0 {
|
||||
return NewConst(nil, typ)
|
||||
}
|
||||
|
||||
// We're converting from constant to non-constant type,
|
||||
// e.g. string -> []byte/[]rune.
|
||||
}
|
||||
|
||||
switch classifications {
|
||||
case changeType: // representation-preserving change
|
||||
c := &ChangeType{X: val}
|
||||
c.setType(typ)
|
||||
return f.emit(c)
|
||||
|
||||
case sliceToArrayPtr, sliceTo0ArrayPtr: // slice to array pointer
|
||||
c := &SliceToArrayPointer{X: val}
|
||||
c.setType(typ)
|
||||
return f.emit(c)
|
||||
|
||||
case sliceToArray: // slice to arrays (not zero-length)
|
||||
ptype := types.NewPointer(typ)
|
||||
p := &SliceToArrayPointer{X: val}
|
||||
p.setType(ptype)
|
||||
x := f.emit(p)
|
||||
unOp := &UnOp{Op: token.MUL, X: x}
|
||||
unOp.setType(typ)
|
||||
return f.emit(unOp)
|
||||
|
||||
case sliceTo0Array: // slice to zero-length arrays (constant)
|
||||
return zeroConst(typ)
|
||||
|
||||
case convert: // representation-changing conversion
|
||||
c := &Convert{X: val}
|
||||
c.setType(typ)
|
||||
return f.emit(c)
|
||||
|
||||
default: // The conversion represents a cross product.
|
||||
c := &MultiConvert{X: val, from: t_src, to: typ}
|
||||
c.setType(typ)
|
||||
return f.emit(c)
|
||||
}
|
||||
}
|
||||
|
||||
// emitTypeCoercion emits to f code to coerce the type of a
|
||||
// Value v to exactly type typ, and returns the coerced value.
|
||||
//
|
||||
// Requires that coercing v.Typ() to typ is a value preserving change.
|
||||
//
|
||||
// Currently used only when v.Type() is a type instance of typ or vice versa.
|
||||
// A type v is a type instance of a type t if there exists a
|
||||
// type parameter substitution σ s.t. σ(v) == t. Example:
|
||||
//
|
||||
// σ(func(T) T) == func(int) int for σ == [T ↦ int]
|
||||
//
|
||||
// This happens in instantiation wrappers for conversion
|
||||
// from an instantiation to a parameterized type (and vice versa)
|
||||
// with σ substituting f.typeparams by f.typeargs.
|
||||
func emitTypeCoercion(f *Function, v Value, typ types.Type) Value {
|
||||
if types.Identical(v.Type(), typ) {
|
||||
return v // no coercion needed
|
||||
}
|
||||
// TODO(taking): for instances should we record which side is the instance?
|
||||
c := &ChangeType{
|
||||
X: v,
|
||||
}
|
||||
c.setType(typ)
|
||||
f.emit(c)
|
||||
return c
|
||||
}
|
||||
|
||||
// emitStore emits to f an instruction to store value val at location
|
||||
// addr, applying implicit conversions as required by assignability rules.
|
||||
func emitStore(f *Function, addr, val Value, pos token.Pos) *Store {
|
||||
typ := typeparams.MustDeref(addr.Type())
|
||||
s := &Store{
|
||||
Addr: addr,
|
||||
Val: emitConv(f, val, typ),
|
||||
pos: pos,
|
||||
}
|
||||
f.emit(s)
|
||||
return s
|
||||
}
|
||||
|
||||
// emitJump emits to f a jump to target, and updates the control-flow graph.
|
||||
// Postcondition: f.currentBlock is nil.
|
||||
func emitJump(f *Function, target *BasicBlock) {
|
||||
b := f.currentBlock
|
||||
b.emit(new(Jump))
|
||||
addEdge(b, target)
|
||||
f.currentBlock = nil
|
||||
}
|
||||
|
||||
// emitIf emits to f a conditional jump to tblock or fblock based on
|
||||
// cond, and updates the control-flow graph.
|
||||
// Postcondition: f.currentBlock is nil.
|
||||
func emitIf(f *Function, cond Value, tblock, fblock *BasicBlock) {
|
||||
b := f.currentBlock
|
||||
b.emit(&If{Cond: cond})
|
||||
addEdge(b, tblock)
|
||||
addEdge(b, fblock)
|
||||
f.currentBlock = nil
|
||||
}
|
||||
|
||||
// emitExtract emits to f an instruction to extract the index'th
|
||||
// component of tuple. It returns the extracted value.
|
||||
func emitExtract(f *Function, tuple Value, index int) Value {
|
||||
e := &Extract{Tuple: tuple, Index: index}
|
||||
e.setType(tuple.Type().(*types.Tuple).At(index).Type())
|
||||
return f.emit(e)
|
||||
}
|
||||
|
||||
// emitTypeAssert emits to f a type assertion value := x.(t) and
|
||||
// returns the value. x.Type() must be an interface.
|
||||
func emitTypeAssert(f *Function, x Value, t types.Type, pos token.Pos) Value {
|
||||
a := &TypeAssert{X: x, AssertedType: t}
|
||||
a.setPos(pos)
|
||||
a.setType(t)
|
||||
return f.emit(a)
|
||||
}
|
||||
|
||||
// emitTypeTest emits to f a type test value,ok := x.(t) and returns
|
||||
// a (value, ok) tuple. x.Type() must be an interface.
|
||||
func emitTypeTest(f *Function, x Value, t types.Type, pos token.Pos) Value {
|
||||
a := &TypeAssert{
|
||||
X: x,
|
||||
AssertedType: t,
|
||||
CommaOk: true,
|
||||
}
|
||||
a.setPos(pos)
|
||||
a.setType(types.NewTuple(
|
||||
newVar("value", t),
|
||||
varOk,
|
||||
))
|
||||
return f.emit(a)
|
||||
}
|
||||
|
||||
// emitTailCall emits to f a function call in tail position. The
|
||||
// caller is responsible for all fields of 'call' except its type.
|
||||
// Intended for wrapper methods.
|
||||
// Precondition: f does/will not use deferred procedure calls.
|
||||
// Postcondition: f.currentBlock is nil.
|
||||
func emitTailCall(f *Function, call *Call) {
|
||||
tresults := f.Signature.Results()
|
||||
nr := tresults.Len()
|
||||
if nr == 1 {
|
||||
call.typ = tresults.At(0).Type()
|
||||
} else {
|
||||
call.typ = tresults
|
||||
}
|
||||
tuple := emitCall(f, call)
|
||||
var ret Return
|
||||
switch nr {
|
||||
case 0:
|
||||
// no-op
|
||||
case 1:
|
||||
ret.Results = []Value{tuple}
|
||||
default:
|
||||
for i := range nr {
|
||||
v := emitExtract(f, tuple, i)
|
||||
// TODO(adonovan): in principle, this is required:
|
||||
// v = emitConv(f, o.Type, f.Signature.Results[i].Type)
|
||||
// but in practice emitTailCall is only used when
|
||||
// the types exactly match.
|
||||
ret.Results = append(ret.Results, v)
|
||||
}
|
||||
}
|
||||
f.emit(&ret)
|
||||
f.currentBlock = nil
|
||||
}
|
||||
|
||||
// emitCall emits a call instruction. If the callee is "no return",
|
||||
// it also emits a panic to eliminate infeasible CFG edges.
|
||||
func emitCall(fn *Function, call *Call) Value {
|
||||
res := fn.emit(call)
|
||||
|
||||
callee := call.Call.StaticCallee()
|
||||
if callee != nil &&
|
||||
callee.object != nil &&
|
||||
fn.Prog.noReturn != nil &&
|
||||
fn.Prog.noReturn(callee.object) {
|
||||
// Call cannot return. Insert a panic after it.
|
||||
fn.emit(&Panic{
|
||||
X: emitConv(fn, vNoReturn, tEface),
|
||||
pos: call.Pos(),
|
||||
})
|
||||
fn.currentBlock = fn.newBasicBlock("unreachable.noreturn")
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// emitImplicitSelections emits to f code to apply the sequence of
|
||||
// implicit field selections specified by indices to base value v, and
|
||||
// returns the selected value.
|
||||
//
|
||||
// If v is the address of a struct, the result will be the address of
|
||||
// a field; if it is the value of a struct, the result will be the
|
||||
// value of a field.
|
||||
func emitImplicitSelections(f *Function, v Value, indices []int, pos token.Pos) Value {
|
||||
for _, index := range indices {
|
||||
if isPointerCore(v.Type()) {
|
||||
fld := fieldOf(typeparams.MustDeref(v.Type()), index)
|
||||
instr := &FieldAddr{
|
||||
X: v,
|
||||
Field: index,
|
||||
}
|
||||
instr.setPos(pos)
|
||||
instr.setType(types.NewPointer(fld.Type()))
|
||||
v = f.emit(instr)
|
||||
// Load the field's value iff indirectly embedded.
|
||||
if isPointerCore(fld.Type()) {
|
||||
v = emitLoad(f, v)
|
||||
}
|
||||
} else {
|
||||
fld := fieldOf(v.Type(), index)
|
||||
instr := &Field{
|
||||
X: v,
|
||||
Field: index,
|
||||
}
|
||||
instr.setPos(pos)
|
||||
instr.setType(fld.Type())
|
||||
v = f.emit(instr)
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// emitFieldSelection emits to f code to select the index'th field of v.
|
||||
//
|
||||
// If wantAddr, the input must be a pointer-to-struct and the result
|
||||
// will be the field's address; otherwise the result will be the
|
||||
// field's value.
|
||||
// Ident id is used for position and debug info.
|
||||
func emitFieldSelection(f *Function, v Value, index int, wantAddr bool, id *ast.Ident) Value {
|
||||
if isPointerCore(v.Type()) {
|
||||
fld := fieldOf(typeparams.MustDeref(v.Type()), index)
|
||||
instr := &FieldAddr{
|
||||
X: v,
|
||||
Field: index,
|
||||
}
|
||||
instr.setPos(id.Pos())
|
||||
instr.setType(types.NewPointer(fld.Type()))
|
||||
v = f.emit(instr)
|
||||
// Load the field's value iff we don't want its address.
|
||||
if !wantAddr {
|
||||
v = emitLoad(f, v)
|
||||
}
|
||||
} else {
|
||||
fld := fieldOf(v.Type(), index)
|
||||
instr := &Field{
|
||||
X: v,
|
||||
Field: index,
|
||||
}
|
||||
instr.setPos(id.Pos())
|
||||
instr.setType(fld.Type())
|
||||
v = f.emit(instr)
|
||||
}
|
||||
emitDebugRef(f, id, v, wantAddr)
|
||||
return v
|
||||
}
|
||||
|
||||
// createRecoverBlock emits to f a block of code to return after a
|
||||
// recovered panic, and sets f.Recover to it.
|
||||
//
|
||||
// If f's result parameters are named, the code loads and returns
|
||||
// their current values, otherwise it returns the zero values of their
|
||||
// type.
|
||||
//
|
||||
// Idempotent.
|
||||
func createRecoverBlock(f *Function) {
|
||||
if f.Recover != nil {
|
||||
return // already created
|
||||
}
|
||||
saved := f.currentBlock
|
||||
|
||||
f.Recover = f.newBasicBlock("recover")
|
||||
f.currentBlock = f.Recover
|
||||
|
||||
var results []Value
|
||||
// Reload NRPs to form value tuple.
|
||||
for _, nr := range f.results {
|
||||
results = append(results, emitLoad(f, nr))
|
||||
}
|
||||
|
||||
f.emit(&Return{Results: results})
|
||||
|
||||
f.currentBlock = saved
|
||||
}
|
||||
836
vendor/golang.org/x/tools/go/ssa/func.go
generated
vendored
Normal file
836
vendor/golang.org/x/tools/go/ssa/func.go
generated
vendored
Normal file
@@ -0,0 +1,836 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssa
|
||||
|
||||
// This file implements the Function type.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"io"
|
||||
"iter"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/internal/typeparams"
|
||||
)
|
||||
|
||||
// Like ObjectOf, but panics instead of returning nil.
|
||||
// Only valid during f's create and build phases.
|
||||
func (f *Function) objectOf(id *ast.Ident) types.Object {
|
||||
if o := f.info.ObjectOf(id); o != nil {
|
||||
return o
|
||||
}
|
||||
panic(fmt.Sprintf("no types.Object for ast.Ident %s @ %s",
|
||||
id.Name, f.Prog.Fset.Position(id.Pos())))
|
||||
}
|
||||
|
||||
// Like TypeOf, but panics instead of returning nil.
|
||||
// Only valid during f's create and build phases.
|
||||
func (f *Function) typeOf(e ast.Expr) types.Type {
|
||||
if T := f.info.TypeOf(e); T != nil {
|
||||
return f.typ(T)
|
||||
}
|
||||
panic(fmt.Sprintf("no type for %T @ %s", e, f.Prog.Fset.Position(e.Pos())))
|
||||
}
|
||||
|
||||
// typ is the locally instantiated type of T.
|
||||
// If f is not an instantiation, then f.typ(T)==T.
|
||||
func (f *Function) typ(T types.Type) types.Type {
|
||||
return f.subst.typ(T)
|
||||
}
|
||||
|
||||
// If id is an Instance, returns info.Instances[id].Type.
|
||||
// Otherwise returns f.typeOf(id).
|
||||
func (f *Function) instanceType(id *ast.Ident) types.Type {
|
||||
if t, ok := f.info.Instances[id]; ok {
|
||||
return t.Type
|
||||
}
|
||||
return f.typeOf(id)
|
||||
}
|
||||
|
||||
// selection returns a *selection corresponding to f.info.Selections[selector]
|
||||
// with potential updates for type substitution.
|
||||
func (f *Function) selection(selector *ast.SelectorExpr) *selection {
|
||||
sel := f.info.Selections[selector]
|
||||
if sel == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch sel.Kind() {
|
||||
case types.MethodExpr, types.MethodVal:
|
||||
if recv := f.typ(sel.Recv()); recv != sel.Recv() {
|
||||
// recv changed during type substitution.
|
||||
pkg := f.declaredPackage().Pkg
|
||||
obj, index, indirect := types.LookupFieldOrMethod(recv, true, pkg, sel.Obj().Name())
|
||||
|
||||
// sig replaces sel.Type(). See (types.Selection).Typ() for details.
|
||||
sig := obj.Type().(*types.Signature)
|
||||
sig = changeRecv(sig, newVar(sig.Recv().Name(), recv))
|
||||
if sel.Kind() == types.MethodExpr {
|
||||
sig = recvAsFirstArg(sig)
|
||||
}
|
||||
return &selection{
|
||||
kind: sel.Kind(),
|
||||
recv: recv,
|
||||
typ: sig,
|
||||
obj: obj,
|
||||
index: index,
|
||||
indirect: indirect,
|
||||
}
|
||||
}
|
||||
}
|
||||
return toSelection(sel)
|
||||
}
|
||||
|
||||
// Destinations associated with unlabelled for/switch/select stmts.
|
||||
// We push/pop one of these as we enter/leave each construct and for
|
||||
// each BranchStmt we scan for the innermost target of the right type.
|
||||
type targets struct {
|
||||
tail *targets // rest of stack
|
||||
_break *BasicBlock
|
||||
_continue *BasicBlock
|
||||
_fallthrough *BasicBlock
|
||||
}
|
||||
|
||||
// Destinations associated with a labelled block.
|
||||
// We populate these as labels are encountered in forward gotos or
|
||||
// labelled statements.
|
||||
// Forward gotos are resolved once it is known which statement they
|
||||
// are associated with inside the Function.
|
||||
type lblock struct {
|
||||
label *types.Label // Label targeted by the blocks.
|
||||
resolved bool // _goto block encountered (back jump or resolved fwd jump)
|
||||
_goto *BasicBlock
|
||||
_break *BasicBlock
|
||||
_continue *BasicBlock
|
||||
}
|
||||
|
||||
// label returns the symbol denoted by a label identifier.
|
||||
//
|
||||
// label should be a non-blank identifier (label.Name != "_").
|
||||
func (f *Function) label(label *ast.Ident) *types.Label {
|
||||
return f.objectOf(label).(*types.Label)
|
||||
}
|
||||
|
||||
// lblockOf returns the branch target associated with the
|
||||
// specified label, creating it if needed.
|
||||
func (f *Function) lblockOf(label *types.Label) *lblock {
|
||||
lb := f.lblocks[label]
|
||||
if lb == nil {
|
||||
lb = &lblock{
|
||||
label: label,
|
||||
_goto: f.newBasicBlock(label.Name()),
|
||||
}
|
||||
if f.lblocks == nil {
|
||||
f.lblocks = make(map[*types.Label]*lblock)
|
||||
}
|
||||
f.lblocks[label] = lb
|
||||
}
|
||||
return lb
|
||||
}
|
||||
|
||||
// labelledBlock searches f for the block of the specified label.
|
||||
//
|
||||
// If f is a yield function, it additionally searches ancestor Functions
|
||||
// corresponding to enclosing range-over-func statements within the
|
||||
// same source function, so the returned block may belong to a different Function.
|
||||
func labelledBlock(f *Function, label *types.Label, tok token.Token) *BasicBlock {
|
||||
if lb := f.lblocks[label]; lb != nil {
|
||||
var block *BasicBlock
|
||||
switch tok {
|
||||
case token.BREAK:
|
||||
block = lb._break
|
||||
case token.CONTINUE:
|
||||
block = lb._continue
|
||||
case token.GOTO:
|
||||
block = lb._goto
|
||||
}
|
||||
if block != nil {
|
||||
return block
|
||||
}
|
||||
}
|
||||
// Search ancestors if this is a yield function.
|
||||
if f.jump != nil {
|
||||
return labelledBlock(f.parent, label, tok)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// targetedBlock looks for the nearest block in f.targets
|
||||
// (and f's ancestors) that matches tok's type, and returns
|
||||
// the block and function it was found in.
|
||||
func targetedBlock(f *Function, tok token.Token) *BasicBlock {
|
||||
if f == nil {
|
||||
return nil
|
||||
}
|
||||
for t := f.targets; t != nil; t = t.tail {
|
||||
var block *BasicBlock
|
||||
switch tok {
|
||||
case token.BREAK:
|
||||
block = t._break
|
||||
case token.CONTINUE:
|
||||
block = t._continue
|
||||
case token.FALLTHROUGH:
|
||||
block = t._fallthrough
|
||||
}
|
||||
if block != nil {
|
||||
return block
|
||||
}
|
||||
}
|
||||
// Search f's ancestors (in case f is a yield function).
|
||||
return targetedBlock(f.parent, tok)
|
||||
}
|
||||
|
||||
// instrs returns an iterator that returns each reachable instruction of the SSA function.
|
||||
func (f *Function) instrs() iter.Seq[Instruction] {
|
||||
return func(yield func(i Instruction) bool) {
|
||||
for _, block := range f.Blocks {
|
||||
for _, instr := range block.Instrs {
|
||||
if !yield(instr) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// addResultVar adds a result for a variable v to f.results and v to f.returnVars.
|
||||
func (f *Function) addResultVar(v *types.Var) {
|
||||
result := emitLocalVar(f, v)
|
||||
f.results = append(f.results, result)
|
||||
f.returnVars = append(f.returnVars, v)
|
||||
}
|
||||
|
||||
// addParamVar adds a parameter to f.Params.
|
||||
func (f *Function) addParamVar(v *types.Var) *Parameter {
|
||||
name := v.Name()
|
||||
if name == "" {
|
||||
name = fmt.Sprintf("arg%d", len(f.Params))
|
||||
}
|
||||
param := &Parameter{
|
||||
name: name,
|
||||
object: v,
|
||||
typ: f.typ(v.Type()),
|
||||
parent: f,
|
||||
}
|
||||
f.Params = append(f.Params, param)
|
||||
return param
|
||||
}
|
||||
|
||||
// addSpilledParam declares a parameter that is pre-spilled to the
|
||||
// stack; the function body will load/store the spilled location.
|
||||
// Subsequent lifting will eliminate spills where possible.
|
||||
func (f *Function) addSpilledParam(obj *types.Var) {
|
||||
param := f.addParamVar(obj)
|
||||
spill := emitLocalVar(f, obj)
|
||||
f.emit(&Store{Addr: spill, Val: param})
|
||||
}
|
||||
|
||||
// startBody initializes the function prior to generating SSA code for its body.
|
||||
// Precondition: f.Type() already set.
|
||||
func (f *Function) startBody() {
|
||||
f.currentBlock = f.newBasicBlock("entry")
|
||||
f.vars = make(map[*types.Var]Value) // needed for some synthetics, e.g. init
|
||||
}
|
||||
|
||||
// createSyntacticParams populates f.Params and generates code (spills
|
||||
// and named result locals) for all the parameters declared in the
|
||||
// syntax. In addition it populates the f.objects mapping.
|
||||
//
|
||||
// Preconditions:
|
||||
// f.startBody() was called. f.info != nil.
|
||||
// Postcondition:
|
||||
// len(f.Params) == len(f.Signature.Params) + (f.Signature.Recv() ? 1 : 0)
|
||||
func (f *Function) createSyntacticParams(recv *ast.FieldList, functype *ast.FuncType) {
|
||||
// Receiver (at most one inner iteration).
|
||||
if recv != nil {
|
||||
for _, field := range recv.List {
|
||||
for _, n := range field.Names {
|
||||
f.addSpilledParam(identVar(f, n))
|
||||
}
|
||||
// Anonymous receiver? No need to spill.
|
||||
if field.Names == nil {
|
||||
f.addParamVar(f.Signature.Recv())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parameters.
|
||||
if functype.Params != nil {
|
||||
n := len(f.Params) // 1 if has recv, 0 otherwise
|
||||
for _, field := range functype.Params.List {
|
||||
for _, n := range field.Names {
|
||||
f.addSpilledParam(identVar(f, n))
|
||||
}
|
||||
// Anonymous parameter? No need to spill.
|
||||
if field.Names == nil {
|
||||
f.addParamVar(f.Signature.Params().At(len(f.Params) - n))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Results.
|
||||
if functype.Results != nil {
|
||||
for _, field := range functype.Results.List {
|
||||
// Implicit "var" decl of locals for named results.
|
||||
for _, n := range field.Names {
|
||||
v := identVar(f, n)
|
||||
f.addResultVar(v)
|
||||
}
|
||||
// Implicit "var" decl of local for an unnamed result.
|
||||
if field.Names == nil {
|
||||
v := f.Signature.Results().At(len(f.results))
|
||||
f.addResultVar(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// createDeferStack initializes fn.deferstack to local variable
|
||||
// initialized to a ssa:deferstack() call.
|
||||
func (fn *Function) createDeferStack() {
|
||||
// Each syntactic function makes a call to ssa:deferstack,
|
||||
// which is spilled to a local. Unused ones are later removed.
|
||||
fn.deferstack = newVar("defer$stack", tDeferStack)
|
||||
call := &Call{Call: CallCommon{Value: vDeferStack}}
|
||||
call.setType(tDeferStack)
|
||||
deferstack := fn.emit(call)
|
||||
spill := emitLocalVar(fn, fn.deferstack)
|
||||
emitStore(fn, spill, deferstack, token.NoPos)
|
||||
}
|
||||
|
||||
type setNumable interface {
|
||||
setNum(int)
|
||||
}
|
||||
|
||||
// numberRegisters assigns numbers to all SSA registers
|
||||
// (value-defining Instructions) in f, to aid debugging.
|
||||
// (Non-Instruction Values are named at construction.)
|
||||
func numberRegisters(f *Function) {
|
||||
v := 0
|
||||
for _, b := range f.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
switch instr.(type) {
|
||||
case Value:
|
||||
instr.(setNumable).setNum(v)
|
||||
v++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// buildReferrers populates the def/use information in all non-nil
|
||||
// Value.Referrers slice.
|
||||
// Precondition: all such slices are initially empty.
|
||||
func buildReferrers(f *Function) {
|
||||
var rands []*Value
|
||||
for _, b := range f.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
rands = instr.Operands(rands[:0]) // recycle storage
|
||||
for _, rand := range rands {
|
||||
if r := *rand; r != nil {
|
||||
if ref := r.Referrers(); ref != nil {
|
||||
*ref = append(*ref, instr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// finishBody() finalizes the contents of the function after SSA code generation of its body.
|
||||
//
|
||||
// The function is not done being built until done() is called.
|
||||
func (f *Function) finishBody() {
|
||||
f.currentBlock = nil
|
||||
f.lblocks = nil
|
||||
f.returnVars = nil
|
||||
f.jump = nil
|
||||
f.source = nil
|
||||
f.exits = nil
|
||||
|
||||
// Remove from f.Locals any Allocs that escape to the heap.
|
||||
j := 0
|
||||
for _, l := range f.Locals {
|
||||
if !l.Heap {
|
||||
f.Locals[j] = l
|
||||
j++
|
||||
}
|
||||
}
|
||||
// Nil out f.Locals[j:] to aid GC.
|
||||
for i := j; i < len(f.Locals); i++ {
|
||||
f.Locals[i] = nil
|
||||
}
|
||||
f.Locals = f.Locals[:j]
|
||||
|
||||
optimizeBlocks(f)
|
||||
|
||||
buildReferrers(f)
|
||||
|
||||
buildDomTree(f)
|
||||
|
||||
if f.Prog.mode&NaiveForm == 0 {
|
||||
// For debugging pre-state of lifting pass:
|
||||
// numberRegisters(f)
|
||||
// f.WriteTo(os.Stderr)
|
||||
lift(f)
|
||||
}
|
||||
|
||||
// clear remaining builder state
|
||||
f.results = nil // (used by lifting)
|
||||
f.deferstack = nil // (used by lifting)
|
||||
f.vars = nil // (used by lifting)
|
||||
|
||||
// clear out other function state (keep consistent with buildParamsOnly)
|
||||
f.subst = nil
|
||||
|
||||
numberRegisters(f) // uses f.namedRegisters
|
||||
}
|
||||
|
||||
// done marks the building of f's SSA body complete,
|
||||
// along with any nested functions, and optionally prints them.
|
||||
func (f *Function) done() {
|
||||
assert(f.parent == nil, "done called on an anonymous function")
|
||||
|
||||
var visit func(*Function)
|
||||
visit = func(f *Function) {
|
||||
for _, anon := range f.AnonFuncs {
|
||||
visit(anon) // anon is done building before f.
|
||||
}
|
||||
|
||||
f.uniq = 0 // done with uniq
|
||||
f.build = nil // function is built
|
||||
|
||||
if f.Prog.mode&PrintFunctions != 0 {
|
||||
printMu.Lock()
|
||||
f.WriteTo(os.Stdout)
|
||||
printMu.Unlock()
|
||||
}
|
||||
|
||||
if f.Prog.mode&SanityCheckFunctions != 0 {
|
||||
mustSanityCheck(f, nil)
|
||||
}
|
||||
}
|
||||
visit(f)
|
||||
}
|
||||
|
||||
// removeNilBlocks eliminates nils from f.Blocks and updates each
|
||||
// BasicBlock.Index. Use this after any pass that may delete blocks.
|
||||
func (f *Function) removeNilBlocks() {
|
||||
j := 0
|
||||
for _, b := range f.Blocks {
|
||||
if b != nil {
|
||||
b.Index = j
|
||||
f.Blocks[j] = b
|
||||
j++
|
||||
}
|
||||
}
|
||||
// Nil out f.Blocks[j:] to aid GC.
|
||||
for i := j; i < len(f.Blocks); i++ {
|
||||
f.Blocks[i] = nil
|
||||
}
|
||||
f.Blocks = f.Blocks[:j]
|
||||
}
|
||||
|
||||
// SetDebugMode sets the debug mode for package pkg. If true, all its
|
||||
// functions will include full debug info. This greatly increases the
|
||||
// size of the instruction stream, and causes Functions to depend upon
|
||||
// the ASTs, potentially keeping them live in memory for longer.
|
||||
func (pkg *Package) SetDebugMode(debug bool) {
|
||||
pkg.debug = debug
|
||||
}
|
||||
|
||||
// debugInfo reports whether debug info is wanted for this function.
|
||||
func (f *Function) debugInfo() bool {
|
||||
// debug info for instantiations follows the debug info of their origin.
|
||||
p := f.declaredPackage()
|
||||
return p != nil && p.debug
|
||||
}
|
||||
|
||||
// lookup returns the address of the named variable identified by obj
|
||||
// that is local to function f or one of its enclosing functions.
|
||||
// If escaping, the reference comes from a potentially escaping pointer
|
||||
// expression and the referent must be heap-allocated.
|
||||
// We assume the referent is a *Alloc or *Phi.
|
||||
// (The only Phis at this stage are those created directly by go1.22 "for" loops.)
|
||||
func (f *Function) lookup(obj *types.Var, escaping bool) Value {
|
||||
if v, ok := f.vars[obj]; ok {
|
||||
if escaping {
|
||||
switch v := v.(type) {
|
||||
case *Alloc:
|
||||
v.Heap = true
|
||||
case *Phi:
|
||||
for _, edge := range v.Edges {
|
||||
if alloc, ok := edge.(*Alloc); ok {
|
||||
alloc.Heap = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return v // function-local var (address)
|
||||
}
|
||||
|
||||
// Definition must be in an enclosing function;
|
||||
// plumb it through intervening closures.
|
||||
if f.parent == nil {
|
||||
panic("no ssa.Value for " + obj.String())
|
||||
}
|
||||
outer := f.parent.lookup(obj, true) // escaping
|
||||
v := &FreeVar{
|
||||
name: obj.Name(),
|
||||
typ: outer.Type(),
|
||||
pos: outer.Pos(),
|
||||
outer: outer,
|
||||
parent: f,
|
||||
}
|
||||
f.vars[obj] = v
|
||||
f.FreeVars = append(f.FreeVars, v)
|
||||
return v
|
||||
}
|
||||
|
||||
// emit emits the specified instruction to function f.
|
||||
func (f *Function) emit(instr Instruction) Value {
|
||||
return f.currentBlock.emit(instr)
|
||||
}
|
||||
|
||||
// RelString returns the full name of this function, qualified by
|
||||
// package name, receiver type, etc.
|
||||
//
|
||||
// The specific formatting rules are not guaranteed and may change.
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// "math.IsNaN" // a package-level function
|
||||
// "(*bytes.Buffer).Bytes" // a declared method or a wrapper
|
||||
// "(*bytes.Buffer).Bytes$thunk" // thunk (func wrapping method; receiver is param 0)
|
||||
// "(*bytes.Buffer).Bytes$bound" // bound (func wrapping method; receiver supplied by closure)
|
||||
// "main.main$1" // an anonymous function in main
|
||||
// "main.init#1" // a declared init function
|
||||
// "main.init" // the synthesized package initializer
|
||||
//
|
||||
// When these functions are referred to from within the same package
|
||||
// (i.e. from == f.Pkg.Object), they are rendered without the package path.
|
||||
// For example: "IsNaN", "(*Buffer).Bytes", etc.
|
||||
//
|
||||
// All non-synthetic functions have distinct package-qualified names.
|
||||
// (But two methods may have the same name "(T).f" if one is a synthetic
|
||||
// wrapper promoting a non-exported method "f" from another package; in
|
||||
// that case, the strings are equal but the identifiers "f" are distinct.)
|
||||
func (f *Function) RelString(from *types.Package) string {
|
||||
// Anonymous?
|
||||
if f.parent != nil {
|
||||
// An anonymous function's Name() looks like "parentName$1",
|
||||
// but its String() should include the type/package/etc.
|
||||
parent := f.parent.RelString(from)
|
||||
for i, anon := range f.parent.AnonFuncs {
|
||||
if anon == f {
|
||||
return fmt.Sprintf("%s$%d", parent, 1+i)
|
||||
}
|
||||
}
|
||||
|
||||
return f.name // should never happen
|
||||
}
|
||||
|
||||
// Method (declared or wrapper)?
|
||||
if recv := f.Signature.Recv(); recv != nil {
|
||||
return f.relMethod(from, recv.Type())
|
||||
}
|
||||
|
||||
// Thunk?
|
||||
if f.method != nil {
|
||||
return f.relMethod(from, f.method.recv)
|
||||
}
|
||||
|
||||
// Bound?
|
||||
if len(f.FreeVars) == 1 && strings.HasSuffix(f.name, "$bound") {
|
||||
return f.relMethod(from, f.FreeVars[0].Type())
|
||||
}
|
||||
|
||||
// Package-level function?
|
||||
// Prefix with package name for cross-package references only.
|
||||
if p := f.relPkg(); p != nil && p != from {
|
||||
return fmt.Sprintf("%s.%s", p.Path(), f.name)
|
||||
}
|
||||
|
||||
// Unknown.
|
||||
return f.name
|
||||
}
|
||||
|
||||
func (f *Function) relMethod(from *types.Package, recv types.Type) string {
|
||||
return fmt.Sprintf("(%s).%s", relType(recv, from), f.name)
|
||||
}
|
||||
|
||||
// writeSignature writes to buf the signature sig in declaration syntax.
|
||||
func writeSignature(buf *bytes.Buffer, from *types.Package, name string, sig *types.Signature) {
|
||||
buf.WriteString("func ")
|
||||
if recv := sig.Recv(); recv != nil {
|
||||
buf.WriteString("(")
|
||||
if name := recv.Name(); name != "" {
|
||||
buf.WriteString(name)
|
||||
buf.WriteString(" ")
|
||||
}
|
||||
types.WriteType(buf, recv.Type(), types.RelativeTo(from))
|
||||
buf.WriteString(") ")
|
||||
}
|
||||
buf.WriteString(name)
|
||||
types.WriteSignature(buf, sig, types.RelativeTo(from))
|
||||
}
|
||||
|
||||
// declaredPackage returns the package fn is declared in or nil if the
|
||||
// function is not declared in a package.
|
||||
func (fn *Function) declaredPackage() *Package {
|
||||
switch {
|
||||
case fn.Pkg != nil:
|
||||
return fn.Pkg // non-generic function (does that follow??)
|
||||
case fn.topLevelOrigin != nil:
|
||||
return fn.topLevelOrigin.Pkg // instance of a named generic function
|
||||
case fn.parent != nil:
|
||||
return fn.parent.declaredPackage() // instance of an anonymous [generic] function
|
||||
default:
|
||||
return nil // function is not declared in a package, e.g. a wrapper.
|
||||
}
|
||||
}
|
||||
|
||||
// relPkg returns types.Package fn is printed in relationship to.
|
||||
func (fn *Function) relPkg() *types.Package {
|
||||
if p := fn.declaredPackage(); p != nil {
|
||||
return p.Pkg
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ io.WriterTo = (*Function)(nil) // *Function implements io.Writer
|
||||
|
||||
func (f *Function) WriteTo(w io.Writer) (int64, error) {
|
||||
var buf bytes.Buffer
|
||||
WriteFunction(&buf, f)
|
||||
n, err := w.Write(buf.Bytes())
|
||||
return int64(n), err
|
||||
}
|
||||
|
||||
// WriteFunction writes to buf a human-readable "disassembly" of f.
|
||||
func WriteFunction(buf *bytes.Buffer, f *Function) {
|
||||
fmt.Fprintf(buf, "# Name: %s\n", f.String())
|
||||
if f.Pkg != nil {
|
||||
fmt.Fprintf(buf, "# Package: %s\n", f.Pkg.Pkg.Path())
|
||||
}
|
||||
if syn := f.Synthetic; syn != "" {
|
||||
fmt.Fprintln(buf, "# Synthetic:", syn)
|
||||
}
|
||||
if pos := f.Pos(); pos.IsValid() {
|
||||
fmt.Fprintf(buf, "# Location: %s\n", f.Prog.Fset.Position(pos))
|
||||
}
|
||||
|
||||
if f.parent != nil {
|
||||
fmt.Fprintf(buf, "# Parent: %s\n", f.parent.Name())
|
||||
}
|
||||
|
||||
if f.Recover != nil {
|
||||
fmt.Fprintf(buf, "# Recover: %s\n", f.Recover)
|
||||
}
|
||||
|
||||
from := f.relPkg()
|
||||
|
||||
if f.FreeVars != nil {
|
||||
buf.WriteString("# Free variables:\n")
|
||||
for i, fv := range f.FreeVars {
|
||||
fmt.Fprintf(buf, "# % 3d:\t%s %s\n", i, fv.Name(), relType(fv.Type(), from))
|
||||
}
|
||||
}
|
||||
|
||||
if len(f.Locals) > 0 {
|
||||
buf.WriteString("# Locals:\n")
|
||||
for i, l := range f.Locals {
|
||||
fmt.Fprintf(buf, "# % 3d:\t%s %s\n", i, l.Name(), relType(typeparams.MustDeref(l.Type()), from))
|
||||
}
|
||||
}
|
||||
writeSignature(buf, from, f.Name(), f.Signature)
|
||||
buf.WriteString(":\n")
|
||||
|
||||
if f.Blocks == nil {
|
||||
buf.WriteString("\t(external)\n")
|
||||
}
|
||||
|
||||
// NB. column calculations are confused by non-ASCII
|
||||
// characters and assume 8-space tabs.
|
||||
const punchcard = 80 // for old time's sake.
|
||||
const tabwidth = 8
|
||||
for _, b := range f.Blocks {
|
||||
if b == nil {
|
||||
// Corrupt CFG.
|
||||
fmt.Fprintf(buf, ".nil:\n")
|
||||
continue
|
||||
}
|
||||
n, _ := fmt.Fprintf(buf, "%d:", b.Index)
|
||||
// (|predecessors|, |successors|, immediate dominator)
|
||||
bmsg := fmt.Sprintf("%s P:%d S:%d", b.Comment, len(b.Preds), len(b.Succs))
|
||||
if b.Idom() != nil {
|
||||
bmsg = fmt.Sprintf("%s idom:%d", bmsg, b.Idom().Index)
|
||||
}
|
||||
fmt.Fprintf(buf, "%*s%s\n", punchcard-1-n-len(bmsg), "", bmsg)
|
||||
|
||||
if false { // CFG debugging
|
||||
fmt.Fprintf(buf, "\t# CFG: %s --> %s --> %s\n", b.Preds, b, b.Succs)
|
||||
}
|
||||
for _, instr := range b.Instrs {
|
||||
buf.WriteString("\t")
|
||||
switch v := instr.(type) {
|
||||
case Value:
|
||||
l := punchcard - tabwidth
|
||||
// Left-align the instruction.
|
||||
if name := v.Name(); name != "" {
|
||||
n, _ := fmt.Fprintf(buf, "%s = ", name)
|
||||
l -= n
|
||||
}
|
||||
n, _ := buf.WriteString(instr.String())
|
||||
l -= n
|
||||
// Right-align the type if there's space.
|
||||
if t := v.Type(); t != nil {
|
||||
buf.WriteByte(' ')
|
||||
ts := relType(t, from)
|
||||
l -= len(ts) + len(" ") // (spaces before and after type)
|
||||
if l > 0 {
|
||||
fmt.Fprintf(buf, "%*s", l, "")
|
||||
}
|
||||
buf.WriteString(ts)
|
||||
}
|
||||
case nil:
|
||||
// Be robust against bad transforms.
|
||||
buf.WriteString("<deleted>")
|
||||
default:
|
||||
buf.WriteString(instr.String())
|
||||
}
|
||||
// -mode=S: show line numbers
|
||||
if f.Prog.mode&LogSource != 0 {
|
||||
if pos := instr.Pos(); pos.IsValid() {
|
||||
fmt.Fprintf(buf, " L%d", f.Prog.Fset.Position(pos).Line)
|
||||
}
|
||||
}
|
||||
buf.WriteString("\n")
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(buf, "\n")
|
||||
}
|
||||
|
||||
// newBasicBlock adds to f a new basic block and returns it. It does
|
||||
// not automatically become the current block for subsequent calls to emit.
|
||||
// comment is an optional string for more readable debugging output.
|
||||
func (f *Function) newBasicBlock(comment string) *BasicBlock {
|
||||
b := &BasicBlock{
|
||||
Index: len(f.Blocks),
|
||||
Comment: comment,
|
||||
parent: f,
|
||||
}
|
||||
b.Succs = b.succs2[:0]
|
||||
f.Blocks = append(f.Blocks, b)
|
||||
return b
|
||||
}
|
||||
|
||||
// NewFunction returns a new synthetic Function instance belonging to
|
||||
// prog, with its name and signature fields set as specified.
|
||||
//
|
||||
// The caller is responsible for initializing the remaining fields of
|
||||
// the function object, e.g. Pkg, Params, Blocks.
|
||||
//
|
||||
// It is practically impossible for clients to construct well-formed
|
||||
// SSA functions/packages/programs directly, so we assume this is the
|
||||
// job of the Builder alone. NewFunction exists to provide clients a
|
||||
// little flexibility. For example, analysis tools may wish to
|
||||
// construct fake Functions for the root of the callgraph, a fake
|
||||
// "reflect" package, etc.
|
||||
//
|
||||
// TODO(adonovan): think harder about the API here.
|
||||
func (prog *Program) NewFunction(name string, sig *types.Signature, provenance string) *Function {
|
||||
return &Function{Prog: prog, name: name, Signature: sig, Synthetic: provenance}
|
||||
}
|
||||
|
||||
// Syntax returns the function's syntax (*ast.Func{Decl,Lit})
|
||||
// if it was produced from syntax or an *ast.RangeStmt if
|
||||
// it is a range-over-func yield function.
|
||||
func (f *Function) Syntax() ast.Node { return f.syntax }
|
||||
|
||||
// identVar returns the variable defined by id.
|
||||
func identVar(fn *Function, id *ast.Ident) *types.Var {
|
||||
return fn.info.Defs[id].(*types.Var)
|
||||
}
|
||||
|
||||
// unique returns a unique positive int within the source tree of f.
|
||||
// The source tree of f includes all of f's ancestors by parent and all
|
||||
// of the AnonFuncs contained within these.
|
||||
func unique(f *Function) int64 {
|
||||
f.uniq++
|
||||
return f.uniq
|
||||
}
|
||||
|
||||
// exit is a change of control flow going from a range-over-func
|
||||
// yield function to an ancestor function caused by a break, continue,
|
||||
// goto, or return statement.
|
||||
//
|
||||
// There are 3 types of exits:
|
||||
// * return from the source function (from ReturnStmt),
|
||||
// * jump to a block (from break and continue statements [labelled/unlabelled]),
|
||||
// * go to a label (from goto statements).
|
||||
//
|
||||
// As the builder does one pass over the ast, it is unclear whether
|
||||
// a forward goto statement will leave a range-over-func body.
|
||||
// The function being exited to is unresolved until the end
|
||||
// of building the range-over-func body.
|
||||
type exit struct {
|
||||
id int64 // unique value for exit within from and to
|
||||
from *Function // the function the exit starts from
|
||||
to *Function // the function being exited to (nil if unresolved)
|
||||
pos token.Pos
|
||||
|
||||
block *BasicBlock // basic block within to being jumped to.
|
||||
label *types.Label // forward label being jumped to via goto.
|
||||
// block == nil && label == nil => return
|
||||
}
|
||||
|
||||
// storeVar emits to function f code to store a value v to a *types.Var x.
|
||||
func storeVar(f *Function, x *types.Var, v Value, pos token.Pos) {
|
||||
emitStore(f, f.lookup(x, true), v, pos)
|
||||
}
|
||||
|
||||
// labelExit creates a new exit to a yield fn to exit the function using a label.
|
||||
func labelExit(fn *Function, label *types.Label, pos token.Pos) *exit {
|
||||
e := &exit{
|
||||
id: unique(fn),
|
||||
from: fn,
|
||||
to: nil,
|
||||
pos: pos,
|
||||
label: label,
|
||||
}
|
||||
fn.exits = append(fn.exits, e)
|
||||
return e
|
||||
}
|
||||
|
||||
// blockExit creates a new exit to a yield fn that jumps to a basic block.
|
||||
func blockExit(fn *Function, block *BasicBlock, pos token.Pos) *exit {
|
||||
e := &exit{
|
||||
id: unique(fn),
|
||||
from: fn,
|
||||
to: block.parent,
|
||||
pos: pos,
|
||||
block: block,
|
||||
}
|
||||
fn.exits = append(fn.exits, e)
|
||||
return e
|
||||
}
|
||||
|
||||
// returnExit creates a new exit to a yield fn that returns the source function.
|
||||
func returnExit(fn *Function, pos token.Pos) *exit {
|
||||
e := &exit{
|
||||
id: unique(fn),
|
||||
from: fn,
|
||||
to: fn.source,
|
||||
pos: pos,
|
||||
}
|
||||
fn.exits = append(fn.exits, e)
|
||||
return e
|
||||
}
|
||||
127
vendor/golang.org/x/tools/go/ssa/instantiate.go
generated
vendored
Normal file
127
vendor/golang.org/x/tools/go/ssa/instantiate.go
generated
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssa
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/types"
|
||||
"slices"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// A generic records information about a generic origin function,
|
||||
// including a cache of existing instantiations.
|
||||
type generic struct {
|
||||
instancesMu sync.Mutex
|
||||
instances map[*typeList]*Function // canonical type arguments to an instance.
|
||||
}
|
||||
|
||||
// instance returns a Function that is the instantiation of generic
|
||||
// origin function fn with the type arguments targs.
|
||||
//
|
||||
// Any created instance is added to cr.
|
||||
//
|
||||
// Acquires fn.generic.instancesMu.
|
||||
func (fn *Function) instance(targs []types.Type, b *builder) *Function {
|
||||
key := fn.Prog.canon.List(targs)
|
||||
|
||||
gen := fn.generic
|
||||
|
||||
gen.instancesMu.Lock()
|
||||
defer gen.instancesMu.Unlock()
|
||||
inst, ok := gen.instances[key]
|
||||
if !ok {
|
||||
inst = createInstance(fn, targs)
|
||||
inst.buildshared = b.shared()
|
||||
b.enqueue(inst)
|
||||
|
||||
if gen.instances == nil {
|
||||
gen.instances = make(map[*typeList]*Function)
|
||||
}
|
||||
gen.instances[key] = inst
|
||||
} else {
|
||||
b.waitForSharedFunction(inst)
|
||||
}
|
||||
return inst
|
||||
}
|
||||
|
||||
// createInstance returns the instantiation of generic function fn using targs.
|
||||
//
|
||||
// Requires fn.generic.instancesMu.
|
||||
func createInstance(fn *Function, targs []types.Type) *Function {
|
||||
prog := fn.Prog
|
||||
|
||||
// Compute signature.
|
||||
var sig *types.Signature
|
||||
var obj *types.Func
|
||||
if recv := fn.Signature.Recv(); recv != nil {
|
||||
// method
|
||||
obj = prog.canon.instantiateMethod(fn.object, targs, prog.ctxt)
|
||||
sig = obj.Type().(*types.Signature)
|
||||
} else {
|
||||
// function
|
||||
instSig, err := types.Instantiate(prog.ctxt, fn.Signature, targs, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
instance, ok := instSig.(*types.Signature)
|
||||
if !ok {
|
||||
panic("Instantiate of a Signature returned a non-signature")
|
||||
}
|
||||
obj = fn.object // instantiation does not exist yet
|
||||
sig = prog.canon.Type(instance).(*types.Signature)
|
||||
}
|
||||
|
||||
// Choose strategy (instance or wrapper).
|
||||
var (
|
||||
synthetic string
|
||||
subst *subster
|
||||
build buildFunc
|
||||
)
|
||||
if prog.mode&InstantiateGenerics != 0 && !prog.isParameterized(targs...) {
|
||||
synthetic = fmt.Sprintf("instance of %s", fn.Name())
|
||||
if fn.syntax != nil {
|
||||
subst = makeSubster(prog.ctxt, obj, fn.typeparams, targs)
|
||||
build = (*builder).buildFromSyntax
|
||||
} else {
|
||||
build = (*builder).buildParamsOnly
|
||||
}
|
||||
} else {
|
||||
synthetic = fmt.Sprintf("instantiation wrapper of %s", fn.Name())
|
||||
build = (*builder).buildInstantiationWrapper
|
||||
}
|
||||
|
||||
/* generic instance or instantiation wrapper */
|
||||
return &Function{
|
||||
name: fmt.Sprintf("%s%s", fn.Name(), targs), // may not be unique
|
||||
object: obj,
|
||||
Signature: sig,
|
||||
Synthetic: synthetic,
|
||||
syntax: fn.syntax, // \
|
||||
info: fn.info, // } empty for non-created packages
|
||||
goversion: fn.goversion, // /
|
||||
build: build,
|
||||
topLevelOrigin: fn,
|
||||
pos: obj.Pos(),
|
||||
Pkg: nil,
|
||||
Prog: fn.Prog,
|
||||
typeparams: fn.typeparams, // share with origin
|
||||
typeargs: targs,
|
||||
subst: subst,
|
||||
}
|
||||
}
|
||||
|
||||
// isParameterized reports whether any of the specified types contains
|
||||
// a free type parameter. It is safe to call concurrently.
|
||||
func (prog *Program) isParameterized(ts ...types.Type) bool {
|
||||
prog.hasParamsMu.Lock()
|
||||
defer prog.hasParamsMu.Unlock()
|
||||
|
||||
// TODO(adonovan): profile. If this operation is expensive,
|
||||
// handle the most common but shallow cases such as T, pkg.T,
|
||||
// *T without consulting the cache under the lock.
|
||||
|
||||
return slices.ContainsFunc(ts, prog.hasParams.Has)
|
||||
}
|
||||
671
vendor/golang.org/x/tools/go/ssa/lift.go
generated
vendored
Normal file
671
vendor/golang.org/x/tools/go/ssa/lift.go
generated
vendored
Normal file
@@ -0,0 +1,671 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssa
|
||||
|
||||
// This file defines the lifting pass which tries to "lift" Alloc
|
||||
// cells (new/local variables) into SSA registers, replacing loads
|
||||
// with the dominating stored value, eliminating loads and stores, and
|
||||
// inserting φ-nodes as needed.
|
||||
|
||||
// Cited papers and resources:
|
||||
//
|
||||
// Ron Cytron et al. 1991. Efficiently computing SSA form...
|
||||
// http://doi.acm.org/10.1145/115372.115320
|
||||
//
|
||||
// Cooper, Harvey, Kennedy. 2001. A Simple, Fast Dominance Algorithm.
|
||||
// Software Practice and Experience 2001, 4:1-10.
|
||||
// http://www.hipersoft.rice.edu/grads/publications/dom14.pdf
|
||||
//
|
||||
// Daniel Berlin, llvmdev mailing list, 2012.
|
||||
// http://lists.cs.uiuc.edu/pipermail/llvmdev/2012-January/046638.html
|
||||
// (Be sure to expand the whole thread.)
|
||||
|
||||
// TODO(adonovan): opt: there are many optimizations worth evaluating, and
|
||||
// the conventional wisdom for SSA construction is that a simple
|
||||
// algorithm well engineered often beats those of better asymptotic
|
||||
// complexity on all but the most egregious inputs.
|
||||
//
|
||||
// Danny Berlin suggests that the Cooper et al. algorithm for
|
||||
// computing the dominance frontier is superior to Cytron et al.
|
||||
// Furthermore he recommends that rather than computing the DF for the
|
||||
// whole function then renaming all alloc cells, it may be cheaper to
|
||||
// compute the DF for each alloc cell separately and throw it away.
|
||||
//
|
||||
// Consider exploiting liveness information to avoid creating dead
|
||||
// φ-nodes which we then immediately remove.
|
||||
//
|
||||
// Also see many other "TODO: opt" suggestions in the code.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
"math/big"
|
||||
"os"
|
||||
"slices"
|
||||
|
||||
"golang.org/x/tools/internal/typeparams"
|
||||
)
|
||||
|
||||
// If true, show diagnostic information at each step of lifting.
|
||||
// Very verbose.
|
||||
const debugLifting = false
|
||||
|
||||
// domFrontier maps each block to the set of blocks in its dominance
|
||||
// frontier. The outer slice is conceptually a map keyed by
|
||||
// Block.Index. The inner slice is conceptually a set, possibly
|
||||
// containing duplicates.
|
||||
//
|
||||
// TODO(adonovan): opt: measure impact of dups; consider a packed bit
|
||||
// representation, e.g. big.Int, and bitwise parallel operations for
|
||||
// the union step in the Children loop.
|
||||
//
|
||||
// domFrontier's methods mutate the slice's elements but not its
|
||||
// length, so their receivers needn't be pointers.
|
||||
type domFrontier [][]*BasicBlock
|
||||
|
||||
func (df domFrontier) add(u, v *BasicBlock) {
|
||||
p := &df[u.Index]
|
||||
*p = append(*p, v)
|
||||
}
|
||||
|
||||
// build builds the dominance frontier df for the dominator (sub)tree
|
||||
// rooted at u, using the Cytron et al. algorithm.
|
||||
//
|
||||
// TODO(adonovan): opt: consider Berlin approach, computing pruned SSA
|
||||
// by pruning the entire IDF computation, rather than merely pruning
|
||||
// the DF -> IDF step.
|
||||
func (df domFrontier) build(u *BasicBlock) {
|
||||
// Encounter each node u in postorder of dom tree.
|
||||
for _, child := range u.dom.children {
|
||||
df.build(child)
|
||||
}
|
||||
for _, vb := range u.Succs {
|
||||
if v := vb.dom; v.idom != u {
|
||||
df.add(u, vb)
|
||||
}
|
||||
}
|
||||
for _, w := range u.dom.children {
|
||||
for _, vb := range df[w.Index] {
|
||||
// TODO(adonovan): opt: use word-parallel bitwise union.
|
||||
if v := vb.dom; v.idom != u {
|
||||
df.add(u, vb)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func buildDomFrontier(fn *Function) domFrontier {
|
||||
df := make(domFrontier, len(fn.Blocks))
|
||||
df.build(fn.Blocks[0])
|
||||
if fn.Recover != nil {
|
||||
df.build(fn.Recover)
|
||||
}
|
||||
return df
|
||||
}
|
||||
|
||||
func removeInstr(refs []Instruction, instr Instruction) []Instruction {
|
||||
return slices.DeleteFunc(refs, func(i Instruction) bool { return i == instr })
|
||||
}
|
||||
|
||||
// lift replaces local and new Allocs accessed only with
|
||||
// load/store by SSA registers, inserting φ-nodes where necessary.
|
||||
// The result is a program in classical pruned SSA form.
|
||||
//
|
||||
// Preconditions:
|
||||
// - fn has no dead blocks (blockopt has run).
|
||||
// - Def/use info (Operands and Referrers) is up-to-date.
|
||||
// - The dominator tree is up-to-date.
|
||||
func lift(fn *Function) {
|
||||
// TODO(adonovan): opt: lots of little optimizations may be
|
||||
// worthwhile here, especially if they cause us to avoid
|
||||
// buildDomFrontier. For example:
|
||||
//
|
||||
// - Alloc never loaded? Eliminate.
|
||||
// - Alloc never stored? Replace all loads with a zero constant.
|
||||
// - Alloc stored once? Replace loads with dominating store;
|
||||
// don't forget that an Alloc is itself an effective store
|
||||
// of zero.
|
||||
// - Alloc used only within a single block?
|
||||
// Use degenerate algorithm avoiding φ-nodes.
|
||||
// - Consider synergy with scalar replacement of aggregates (SRA).
|
||||
// e.g. *(&x.f) where x is an Alloc.
|
||||
// Perhaps we'd get better results if we generated this as x.f
|
||||
// i.e. Field(x, .f) instead of Load(FieldIndex(x, .f)).
|
||||
// Unclear.
|
||||
//
|
||||
// But we will start with the simplest correct code.
|
||||
df := buildDomFrontier(fn)
|
||||
|
||||
if debugLifting {
|
||||
title := false
|
||||
for i, blocks := range df {
|
||||
if blocks != nil {
|
||||
if !title {
|
||||
fmt.Fprintf(os.Stderr, "Dominance frontier of %s:\n", fn)
|
||||
title = true
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "\t%s: %s\n", fn.Blocks[i], blocks)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newPhis := make(newPhiMap)
|
||||
|
||||
// During this pass we will replace some BasicBlock.Instrs
|
||||
// (allocs, loads and stores) with nil, keeping a count in
|
||||
// BasicBlock.gaps. At the end we will reset Instrs to the
|
||||
// concatenation of all non-dead newPhis and non-nil Instrs
|
||||
// for the block, reusing the original array if space permits.
|
||||
|
||||
// While we're here, we also eliminate 'rundefers'
|
||||
// instructions and ssa:deferstack() in functions that contain no
|
||||
// 'defer' instructions. For now, we also eliminate
|
||||
// 's = ssa:deferstack()' calls if s doesn't escape, replacing s
|
||||
// with nil in Defer{DeferStack: s}. This has the same meaning,
|
||||
// but allows eliminating the intrinsic function `ssa:deferstack()`
|
||||
// (unless it is needed due to range-over-func instances). This gives
|
||||
// ssa users more time to support range-over-func.
|
||||
usesDefer := false
|
||||
deferstackAlloc, deferstackCall := deferstackPreamble(fn)
|
||||
eliminateDeferStack := deferstackAlloc != nil && !deferstackAlloc.Heap
|
||||
|
||||
// A counter used to generate ~unique ids for Phi nodes, as an
|
||||
// aid to debugging. We use large numbers to make them highly
|
||||
// visible. All nodes are renumbered later.
|
||||
fresh := 1000
|
||||
|
||||
// Determine which allocs we can lift and number them densely.
|
||||
// The renaming phase uses this numbering for compact maps.
|
||||
numAllocs := 0
|
||||
for _, b := range fn.Blocks {
|
||||
b.gaps = 0
|
||||
b.rundefers = 0
|
||||
for _, instr := range b.Instrs {
|
||||
switch instr := instr.(type) {
|
||||
case *Alloc:
|
||||
index := -1
|
||||
if liftAlloc(df, instr, newPhis, &fresh) {
|
||||
index = numAllocs
|
||||
numAllocs++
|
||||
}
|
||||
instr.index = index
|
||||
case *Defer:
|
||||
usesDefer = true
|
||||
if eliminateDeferStack {
|
||||
// Clear DeferStack and remove references to loads
|
||||
if instr.DeferStack != nil {
|
||||
if refs := instr.DeferStack.Referrers(); refs != nil {
|
||||
*refs = removeInstr(*refs, instr)
|
||||
}
|
||||
instr.DeferStack = nil
|
||||
}
|
||||
}
|
||||
case *RunDefers:
|
||||
b.rundefers++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// renaming maps an alloc (keyed by index) to its replacement
|
||||
// value. Initially the renaming contains nil, signifying the
|
||||
// zero constant of the appropriate type; we construct the
|
||||
// Const lazily at most once on each path through the domtree.
|
||||
// TODO(adonovan): opt: cache per-function not per subtree.
|
||||
renaming := make([]Value, numAllocs)
|
||||
|
||||
// Renaming.
|
||||
rename(fn.Blocks[0], renaming, newPhis)
|
||||
|
||||
// Eliminate dead φ-nodes.
|
||||
removeDeadPhis(fn.Blocks, newPhis)
|
||||
|
||||
// Eliminate ssa:deferstack() call.
|
||||
if eliminateDeferStack {
|
||||
b := deferstackCall.block
|
||||
for i, instr := range b.Instrs {
|
||||
if instr == deferstackCall {
|
||||
b.Instrs[i] = nil
|
||||
b.gaps++
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Prepend remaining live φ-nodes to each block.
|
||||
for _, b := range fn.Blocks {
|
||||
nps := newPhis[b]
|
||||
j := len(nps)
|
||||
|
||||
rundefersToKill := b.rundefers
|
||||
if usesDefer {
|
||||
rundefersToKill = 0
|
||||
}
|
||||
|
||||
if j+b.gaps+rundefersToKill == 0 {
|
||||
continue // fast path: no new phis or gaps
|
||||
}
|
||||
|
||||
// Compact nps + non-nil Instrs into a new slice.
|
||||
// TODO(adonovan): opt: compact in situ (rightwards)
|
||||
// if Instrs has sufficient space or slack.
|
||||
dst := make([]Instruction, len(b.Instrs)+j-b.gaps-rundefersToKill)
|
||||
for i, np := range nps {
|
||||
dst[i] = np.phi
|
||||
}
|
||||
for _, instr := range b.Instrs {
|
||||
if instr == nil {
|
||||
continue
|
||||
}
|
||||
if !usesDefer {
|
||||
if _, ok := instr.(*RunDefers); ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
dst[j] = instr
|
||||
j++
|
||||
}
|
||||
b.Instrs = dst
|
||||
}
|
||||
|
||||
// Remove any fn.Locals that were lifted.
|
||||
j := 0
|
||||
for _, l := range fn.Locals {
|
||||
if l.index < 0 {
|
||||
fn.Locals[j] = l
|
||||
j++
|
||||
}
|
||||
}
|
||||
// Nil out fn.Locals[j:] to aid GC.
|
||||
for i := j; i < len(fn.Locals); i++ {
|
||||
fn.Locals[i] = nil
|
||||
}
|
||||
fn.Locals = fn.Locals[:j]
|
||||
}
|
||||
|
||||
// removeDeadPhis removes φ-nodes not transitively needed by a
|
||||
// non-Phi, non-DebugRef instruction.
|
||||
func removeDeadPhis(blocks []*BasicBlock, newPhis newPhiMap) {
|
||||
// First pass: find the set of "live" φ-nodes: those reachable
|
||||
// from some non-Phi instruction.
|
||||
//
|
||||
// We compute reachability in reverse, starting from each φ,
|
||||
// rather than forwards, starting from each live non-Phi
|
||||
// instruction, because this way visits much less of the
|
||||
// Value graph.
|
||||
livePhis := make(map[*Phi]bool)
|
||||
for _, npList := range newPhis {
|
||||
for _, np := range npList {
|
||||
phi := np.phi
|
||||
if !livePhis[phi] && phiHasDirectReferrer(phi) {
|
||||
markLivePhi(livePhis, phi)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Existing φ-nodes due to && and || operators
|
||||
// are all considered live (see Go issue 19622).
|
||||
for _, b := range blocks {
|
||||
for _, phi := range b.phis() {
|
||||
markLivePhi(livePhis, phi.(*Phi))
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass: eliminate unused phis from newPhis.
|
||||
for block, npList := range newPhis {
|
||||
j := 0
|
||||
for _, np := range npList {
|
||||
if livePhis[np.phi] {
|
||||
npList[j] = np
|
||||
j++
|
||||
} else {
|
||||
// discard it, first removing it from referrers
|
||||
for _, val := range np.phi.Edges {
|
||||
if refs := val.Referrers(); refs != nil {
|
||||
*refs = removeInstr(*refs, np.phi)
|
||||
}
|
||||
}
|
||||
np.phi.block = nil
|
||||
}
|
||||
}
|
||||
newPhis[block] = npList[:j]
|
||||
}
|
||||
}
|
||||
|
||||
// markLivePhi marks phi, and all φ-nodes transitively reachable via
|
||||
// its Operands, live.
|
||||
func markLivePhi(livePhis map[*Phi]bool, phi *Phi) {
|
||||
livePhis[phi] = true
|
||||
for _, rand := range phi.Operands(nil) {
|
||||
if q, ok := (*rand).(*Phi); ok {
|
||||
if !livePhis[q] {
|
||||
markLivePhi(livePhis, q)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// phiHasDirectReferrer reports whether phi is directly referred to by
|
||||
// a non-Phi instruction. Such instructions are the
|
||||
// roots of the liveness traversal.
|
||||
func phiHasDirectReferrer(phi *Phi) bool {
|
||||
for _, instr := range *phi.Referrers() {
|
||||
if _, ok := instr.(*Phi); !ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type blockSet struct{ big.Int } // (inherit methods from Int)
|
||||
|
||||
// add adds b to the set and returns true if the set changed.
|
||||
func (s *blockSet) add(b *BasicBlock) bool {
|
||||
i := b.Index
|
||||
if s.Bit(i) != 0 {
|
||||
return false
|
||||
}
|
||||
s.SetBit(&s.Int, i, 1)
|
||||
return true
|
||||
}
|
||||
|
||||
// take removes an arbitrary element from a set s and
|
||||
// returns its index, or returns -1 if empty.
|
||||
func (s *blockSet) take() int {
|
||||
l := s.BitLen()
|
||||
for i := range l {
|
||||
if s.Bit(i) == 1 {
|
||||
s.SetBit(&s.Int, i, 0)
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// newPhi is a pair of a newly introduced φ-node and the lifted Alloc
|
||||
// it replaces.
|
||||
type newPhi struct {
|
||||
phi *Phi
|
||||
alloc *Alloc
|
||||
}
|
||||
|
||||
// newPhiMap records for each basic block, the set of newPhis that
|
||||
// must be prepended to the block.
|
||||
type newPhiMap map[*BasicBlock][]newPhi
|
||||
|
||||
// liftAlloc determines whether alloc can be lifted into registers,
|
||||
// and if so, it populates newPhis with all the φ-nodes it may require
|
||||
// and returns true.
|
||||
//
|
||||
// fresh is a source of fresh ids for phi nodes.
|
||||
func liftAlloc(df domFrontier, alloc *Alloc, newPhis newPhiMap, fresh *int) bool {
|
||||
// Don't lift result values in functions that defer
|
||||
// calls that may recover from panic.
|
||||
if fn := alloc.Parent(); fn.Recover != nil {
|
||||
if slices.Contains(fn.results, alloc) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Compute defblocks, the set of blocks containing a
|
||||
// definition of the alloc cell.
|
||||
var defblocks blockSet
|
||||
for _, instr := range *alloc.Referrers() {
|
||||
// Bail out if we discover the alloc is not liftable;
|
||||
// the only operations permitted to use the alloc are
|
||||
// loads/stores into the cell, and DebugRef.
|
||||
switch instr := instr.(type) {
|
||||
case *Store:
|
||||
if instr.Val == alloc {
|
||||
return false // address used as value
|
||||
}
|
||||
if instr.Addr != alloc {
|
||||
panic("Alloc.Referrers is inconsistent")
|
||||
}
|
||||
defblocks.add(instr.Block())
|
||||
case *UnOp:
|
||||
if instr.Op != token.MUL {
|
||||
return false // not a load
|
||||
}
|
||||
if instr.X != alloc {
|
||||
panic("Alloc.Referrers is inconsistent")
|
||||
}
|
||||
case *DebugRef:
|
||||
// ok
|
||||
default:
|
||||
return false // some other instruction
|
||||
}
|
||||
}
|
||||
// The Alloc itself counts as a (zero) definition of the cell.
|
||||
defblocks.add(alloc.Block())
|
||||
|
||||
if debugLifting {
|
||||
fmt.Fprintln(os.Stderr, "\tlifting ", alloc, alloc.Name())
|
||||
}
|
||||
|
||||
fn := alloc.Parent()
|
||||
|
||||
// Φ-insertion.
|
||||
//
|
||||
// What follows is the body of the main loop of the insert-φ
|
||||
// function described by Cytron et al, but instead of using
|
||||
// counter tricks, we just reset the 'hasAlready' and 'work'
|
||||
// sets each iteration. These are bitmaps so it's pretty cheap.
|
||||
//
|
||||
// TODO(adonovan): opt: recycle slice storage for W,
|
||||
// hasAlready, defBlocks across liftAlloc calls.
|
||||
var hasAlready blockSet
|
||||
|
||||
// Initialize W and work to defblocks.
|
||||
var work blockSet = defblocks // blocks seen
|
||||
var W blockSet // blocks to do
|
||||
W.Set(&defblocks.Int)
|
||||
|
||||
// Traverse iterated dominance frontier, inserting φ-nodes.
|
||||
for i := W.take(); i != -1; i = W.take() {
|
||||
u := fn.Blocks[i]
|
||||
for _, v := range df[u.Index] {
|
||||
if hasAlready.add(v) {
|
||||
// Create φ-node.
|
||||
// It will be prepended to v.Instrs later, if needed.
|
||||
phi := &Phi{
|
||||
Edges: make([]Value, len(v.Preds)),
|
||||
Comment: alloc.Comment,
|
||||
}
|
||||
// This is merely a debugging aid:
|
||||
phi.setNum(*fresh)
|
||||
*fresh++
|
||||
|
||||
phi.pos = alloc.Pos()
|
||||
phi.setType(typeparams.MustDeref(alloc.Type()))
|
||||
phi.block = v
|
||||
if debugLifting {
|
||||
fmt.Fprintf(os.Stderr, "\tplace %s = %s at block %s\n", phi.Name(), phi, v)
|
||||
}
|
||||
newPhis[v] = append(newPhis[v], newPhi{phi, alloc})
|
||||
|
||||
if work.add(v) {
|
||||
W.add(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// replaceAll replaces all intraprocedural uses of x with y,
|
||||
// updating x.Referrers and y.Referrers.
|
||||
// Precondition: x.Referrers() != nil, i.e. x must be local to some function.
|
||||
func replaceAll(x, y Value) {
|
||||
var rands []*Value
|
||||
pxrefs := x.Referrers()
|
||||
pyrefs := y.Referrers()
|
||||
for _, instr := range *pxrefs {
|
||||
rands = instr.Operands(rands[:0]) // recycle storage
|
||||
for _, rand := range rands {
|
||||
if *rand != nil {
|
||||
if *rand == x {
|
||||
*rand = y
|
||||
}
|
||||
}
|
||||
}
|
||||
if pyrefs != nil {
|
||||
*pyrefs = append(*pyrefs, instr) // dups ok
|
||||
}
|
||||
}
|
||||
*pxrefs = nil // x is now unreferenced
|
||||
}
|
||||
|
||||
// renamed returns the value to which alloc is being renamed,
|
||||
// constructing it lazily if it's the implicit zero initialization.
|
||||
func renamed(renaming []Value, alloc *Alloc) Value {
|
||||
v := renaming[alloc.index]
|
||||
if v == nil {
|
||||
v = zeroConst(typeparams.MustDeref(alloc.Type()))
|
||||
renaming[alloc.index] = v
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// rename implements the (Cytron et al) SSA renaming algorithm, a
|
||||
// preorder traversal of the dominator tree replacing all loads of
|
||||
// Alloc cells with the value stored to that cell by the dominating
|
||||
// store instruction. For lifting, we need only consider loads,
|
||||
// stores and φ-nodes.
|
||||
//
|
||||
// renaming is a map from *Alloc (keyed by index number) to its
|
||||
// dominating stored value; newPhis[x] is the set of new φ-nodes to be
|
||||
// prepended to block x.
|
||||
func rename(u *BasicBlock, renaming []Value, newPhis newPhiMap) {
|
||||
// Each φ-node becomes the new name for its associated Alloc.
|
||||
for _, np := range newPhis[u] {
|
||||
phi := np.phi
|
||||
alloc := np.alloc
|
||||
renaming[alloc.index] = phi
|
||||
}
|
||||
|
||||
// Rename loads and stores of allocs.
|
||||
for i, instr := range u.Instrs {
|
||||
switch instr := instr.(type) {
|
||||
case *Alloc:
|
||||
if instr.index >= 0 { // store of zero to Alloc cell
|
||||
// Replace dominated loads by the zero value.
|
||||
renaming[instr.index] = nil
|
||||
if debugLifting {
|
||||
fmt.Fprintf(os.Stderr, "\tkill alloc %s\n", instr)
|
||||
}
|
||||
// Delete the Alloc.
|
||||
u.Instrs[i] = nil
|
||||
u.gaps++
|
||||
}
|
||||
|
||||
case *Store:
|
||||
if alloc, ok := instr.Addr.(*Alloc); ok && alloc.index >= 0 { // store to Alloc cell
|
||||
// Replace dominated loads by the stored value.
|
||||
renaming[alloc.index] = instr.Val
|
||||
if debugLifting {
|
||||
fmt.Fprintf(os.Stderr, "\tkill store %s; new value: %s\n",
|
||||
instr, instr.Val.Name())
|
||||
}
|
||||
// Remove the store from the referrer list of the stored value.
|
||||
if refs := instr.Val.Referrers(); refs != nil {
|
||||
*refs = removeInstr(*refs, instr)
|
||||
}
|
||||
// Delete the Store.
|
||||
u.Instrs[i] = nil
|
||||
u.gaps++
|
||||
}
|
||||
|
||||
case *UnOp:
|
||||
if instr.Op == token.MUL {
|
||||
if alloc, ok := instr.X.(*Alloc); ok && alloc.index >= 0 { // load of Alloc cell
|
||||
newval := renamed(renaming, alloc)
|
||||
if debugLifting {
|
||||
fmt.Fprintf(os.Stderr, "\tupdate load %s = %s with %s\n",
|
||||
instr.Name(), instr, newval.Name())
|
||||
}
|
||||
// Replace all references to
|
||||
// the loaded value by the
|
||||
// dominating stored value.
|
||||
replaceAll(instr, newval)
|
||||
// Delete the Load.
|
||||
u.Instrs[i] = nil
|
||||
u.gaps++
|
||||
}
|
||||
}
|
||||
|
||||
case *DebugRef:
|
||||
if alloc, ok := instr.X.(*Alloc); ok && alloc.index >= 0 { // ref of Alloc cell
|
||||
if instr.IsAddr {
|
||||
instr.X = renamed(renaming, alloc)
|
||||
instr.IsAddr = false
|
||||
|
||||
// Add DebugRef to instr.X's referrers.
|
||||
if refs := instr.X.Referrers(); refs != nil {
|
||||
*refs = append(*refs, instr)
|
||||
}
|
||||
} else {
|
||||
// A source expression denotes the address
|
||||
// of an Alloc that was optimized away.
|
||||
instr.X = nil
|
||||
|
||||
// Delete the DebugRef.
|
||||
u.Instrs[i] = nil
|
||||
u.gaps++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For each φ-node in a CFG successor, rename the edge.
|
||||
for _, v := range u.Succs {
|
||||
phis := newPhis[v]
|
||||
if len(phis) == 0 {
|
||||
continue
|
||||
}
|
||||
i := v.predIndex(u)
|
||||
for _, np := range phis {
|
||||
phi := np.phi
|
||||
alloc := np.alloc
|
||||
newval := renamed(renaming, alloc)
|
||||
if debugLifting {
|
||||
fmt.Fprintf(os.Stderr, "\tsetphi %s edge %s -> %s (#%d) (alloc=%s) := %s\n",
|
||||
phi.Name(), u, v, i, alloc.Name(), newval.Name())
|
||||
}
|
||||
phi.Edges[i] = newval
|
||||
if prefs := newval.Referrers(); prefs != nil {
|
||||
*prefs = append(*prefs, phi)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Continue depth-first recursion over domtree, pushing a
|
||||
// fresh copy of the renaming map for each subtree.
|
||||
for i, v := range u.dom.children {
|
||||
r := renaming
|
||||
if i < len(u.dom.children)-1 {
|
||||
// On all but the final iteration, we must make
|
||||
// a copy to avoid destructive update.
|
||||
r = make([]Value, len(renaming))
|
||||
copy(r, renaming)
|
||||
}
|
||||
rename(v, r, newPhis)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// deferstackPreamble returns the *Alloc and ssa:deferstack() call for fn.deferstack.
|
||||
func deferstackPreamble(fn *Function) (*Alloc, *Call) {
|
||||
if alloc, _ := fn.vars[fn.deferstack].(*Alloc); alloc != nil {
|
||||
for _, ref := range *alloc.Referrers() {
|
||||
if ref, _ := ref.(*Store); ref != nil && ref.Addr == alloc {
|
||||
if call, _ := ref.Val.(*Call); call != nil {
|
||||
return alloc, call
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
155
vendor/golang.org/x/tools/go/ssa/lvalue.go
generated
vendored
Normal file
155
vendor/golang.org/x/tools/go/ssa/lvalue.go
generated
vendored
Normal file
@@ -0,0 +1,155 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssa
|
||||
|
||||
// lvalues are the union of addressable expressions and map-index
|
||||
// expressions.
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/internal/typeparams"
|
||||
)
|
||||
|
||||
// An lvalue represents an assignable location that may appear on the
|
||||
// left-hand side of an assignment. This is a generalization of a
|
||||
// pointer to permit updates to elements of maps.
|
||||
type lvalue interface {
|
||||
store(fn *Function, v Value) // stores v into the location
|
||||
load(fn *Function) Value // loads the contents of the location
|
||||
address(fn *Function) Value // address of the location
|
||||
typ() types.Type // returns the type of the location
|
||||
}
|
||||
|
||||
// An address is an lvalue represented by a true pointer.
|
||||
type address struct {
|
||||
addr Value // must have a pointer core type.
|
||||
pos token.Pos // source position
|
||||
expr ast.Expr // source syntax of the value (not address) [debug mode]
|
||||
}
|
||||
|
||||
func (a *address) load(fn *Function) Value {
|
||||
load := emitLoad(fn, a.addr)
|
||||
load.pos = a.pos
|
||||
return load
|
||||
}
|
||||
|
||||
func (a *address) store(fn *Function, v Value) {
|
||||
store := emitStore(fn, a.addr, v, a.pos)
|
||||
if a.expr != nil {
|
||||
// store.Val is v, converted for assignability.
|
||||
emitDebugRef(fn, a.expr, store.Val, false)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *address) address(fn *Function) Value {
|
||||
if a.expr != nil {
|
||||
emitDebugRef(fn, a.expr, a.addr, true)
|
||||
}
|
||||
return a.addr
|
||||
}
|
||||
|
||||
func (a *address) typ() types.Type {
|
||||
return typeparams.MustDeref(a.addr.Type())
|
||||
}
|
||||
|
||||
// An element is an lvalue represented by m[k], the location of an
|
||||
// element of a map. These locations are not addressable
|
||||
// since pointers cannot be formed from them, but they do support
|
||||
// load() and store().
|
||||
type element struct {
|
||||
m, k Value // map
|
||||
t types.Type // map element type
|
||||
pos token.Pos // source position of colon ({k:v}) or lbrack (m[k]=v)
|
||||
}
|
||||
|
||||
func (e *element) load(fn *Function) Value {
|
||||
l := &Lookup{
|
||||
X: e.m,
|
||||
Index: e.k,
|
||||
}
|
||||
l.setPos(e.pos)
|
||||
l.setType(e.t)
|
||||
return fn.emit(l)
|
||||
}
|
||||
|
||||
func (e *element) store(fn *Function, v Value) {
|
||||
up := &MapUpdate{
|
||||
Map: e.m,
|
||||
Key: e.k,
|
||||
Value: emitConv(fn, v, e.t),
|
||||
}
|
||||
up.pos = e.pos
|
||||
fn.emit(up)
|
||||
}
|
||||
|
||||
func (e *element) address(fn *Function) Value {
|
||||
panic("map elements are not addressable")
|
||||
}
|
||||
|
||||
func (e *element) typ() types.Type {
|
||||
return e.t
|
||||
}
|
||||
|
||||
// A lazyAddress is an lvalue whose address is the result of an instruction.
|
||||
// These work like an *address except a new address.address() Value
|
||||
// is created on each load, store and address call.
|
||||
// A lazyAddress can be used to control when a side effect (nil pointer
|
||||
// dereference, index out of bounds) of using a location happens.
|
||||
type lazyAddress struct {
|
||||
addr func(fn *Function) Value // emit to fn the computation of the address
|
||||
t types.Type // type of the location
|
||||
pos token.Pos // source position
|
||||
expr ast.Expr // source syntax of the value (not address) [debug mode]
|
||||
}
|
||||
|
||||
func (l *lazyAddress) load(fn *Function) Value {
|
||||
load := emitLoad(fn, l.addr(fn))
|
||||
load.pos = l.pos
|
||||
return load
|
||||
}
|
||||
|
||||
func (l *lazyAddress) store(fn *Function, v Value) {
|
||||
store := emitStore(fn, l.addr(fn), v, l.pos)
|
||||
if l.expr != nil {
|
||||
// store.Val is v, converted for assignability.
|
||||
emitDebugRef(fn, l.expr, store.Val, false)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lazyAddress) address(fn *Function) Value {
|
||||
addr := l.addr(fn)
|
||||
if l.expr != nil {
|
||||
emitDebugRef(fn, l.expr, addr, true)
|
||||
}
|
||||
return addr
|
||||
}
|
||||
|
||||
func (l *lazyAddress) typ() types.Type { return l.t }
|
||||
|
||||
// A blank is a dummy variable whose name is "_".
|
||||
// It is not reified: loads are illegal and stores are ignored.
|
||||
type blank struct{}
|
||||
|
||||
func (bl blank) load(fn *Function) Value {
|
||||
panic("blank.load is illegal")
|
||||
}
|
||||
|
||||
func (bl blank) store(fn *Function, v Value) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
func (bl blank) address(fn *Function) Value {
|
||||
panic("blank var is not addressable")
|
||||
}
|
||||
|
||||
func (bl blank) typ() types.Type {
|
||||
// This should be the type of the blank Ident; the typechecker
|
||||
// doesn't provide this yet, but fortunately, we don't need it
|
||||
// yet either.
|
||||
panic("blank.typ is unimplemented")
|
||||
}
|
||||
180
vendor/golang.org/x/tools/go/ssa/methods.go
generated
vendored
Normal file
180
vendor/golang.org/x/tools/go/ssa/methods.go
generated
vendored
Normal file
@@ -0,0 +1,180 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssa
|
||||
|
||||
// This file defines utilities for population of method sets.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/go/types/typeutil"
|
||||
"golang.org/x/tools/internal/typesinternal"
|
||||
)
|
||||
|
||||
// MethodValue returns the Function implementing method sel, building
|
||||
// wrapper methods on demand. It returns nil if sel denotes an
|
||||
// interface or generic method.
|
||||
//
|
||||
// Precondition: sel.Kind() == MethodVal.
|
||||
//
|
||||
// Thread-safe.
|
||||
//
|
||||
// Acquires prog.methodsMu.
|
||||
func (prog *Program) MethodValue(sel *types.Selection) *Function {
|
||||
if sel.Kind() != types.MethodVal {
|
||||
panic(fmt.Sprintf("MethodValue(%s) kind != MethodVal", sel))
|
||||
}
|
||||
T := sel.Recv()
|
||||
if types.IsInterface(T) {
|
||||
return nil // interface method or type parameter
|
||||
}
|
||||
|
||||
if prog.isParameterized(T) {
|
||||
return nil // generic method
|
||||
}
|
||||
|
||||
if prog.mode&LogSource != 0 {
|
||||
defer logStack("MethodValue %s %v", T, sel)()
|
||||
}
|
||||
|
||||
var b builder
|
||||
|
||||
m := func() *Function {
|
||||
prog.methodsMu.Lock()
|
||||
defer prog.methodsMu.Unlock()
|
||||
|
||||
// Get or create SSA method set.
|
||||
mset, ok := prog.methodSets.At(T).(*methodSet)
|
||||
if !ok {
|
||||
mset = &methodSet{mapping: make(map[string]*Function)}
|
||||
prog.methodSets.Set(T, mset)
|
||||
}
|
||||
|
||||
// Get or create SSA method.
|
||||
id := sel.Obj().Id()
|
||||
fn, ok := mset.mapping[id]
|
||||
if !ok {
|
||||
obj := sel.Obj().(*types.Func)
|
||||
needsPromotion := len(sel.Index()) > 1
|
||||
needsIndirection := !isPointer(recvType(obj)) && isPointer(T)
|
||||
if needsPromotion || needsIndirection {
|
||||
fn = createWrapper(prog, toSelection(sel))
|
||||
fn.buildshared = b.shared()
|
||||
b.enqueue(fn)
|
||||
} else {
|
||||
fn = prog.objectMethod(obj, &b)
|
||||
}
|
||||
if fn.Signature.Recv() == nil {
|
||||
panic(fn)
|
||||
}
|
||||
mset.mapping[id] = fn
|
||||
} else {
|
||||
b.waitForSharedFunction(fn)
|
||||
}
|
||||
|
||||
return fn
|
||||
}()
|
||||
|
||||
b.iterate()
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// objectMethod returns the Function for a given method symbol.
|
||||
// The symbol may be an instance of a generic function. It need not
|
||||
// belong to an existing SSA package created by a call to
|
||||
// prog.CreatePackage.
|
||||
//
|
||||
// objectMethod panics if the function is not a method.
|
||||
//
|
||||
// Acquires prog.objectMethodsMu.
|
||||
func (prog *Program) objectMethod(obj *types.Func, b *builder) *Function {
|
||||
sig := obj.Type().(*types.Signature)
|
||||
if sig.Recv() == nil {
|
||||
panic("not a method: " + obj.String())
|
||||
}
|
||||
|
||||
// Belongs to a created package?
|
||||
if fn := prog.FuncValue(obj); fn != nil {
|
||||
return fn
|
||||
}
|
||||
|
||||
// Instantiation of generic?
|
||||
if originObj := obj.Origin(); originObj != obj {
|
||||
origin := prog.objectMethod(originObj, b)
|
||||
assert(origin.typeparams.Len() > 0, "origin is not generic")
|
||||
targs := receiverTypeArgs(obj)
|
||||
return origin.instance(targs, b)
|
||||
}
|
||||
|
||||
// Consult/update cache of methods created from types.Func.
|
||||
prog.objectMethodsMu.Lock()
|
||||
defer prog.objectMethodsMu.Unlock()
|
||||
fn, ok := prog.objectMethods[obj]
|
||||
if !ok {
|
||||
fn = createFunction(prog, obj, obj.Name(), nil, nil, "")
|
||||
fn.Synthetic = "from type information (on demand)"
|
||||
fn.buildshared = b.shared()
|
||||
b.enqueue(fn)
|
||||
|
||||
if prog.objectMethods == nil {
|
||||
prog.objectMethods = make(map[*types.Func]*Function)
|
||||
}
|
||||
prog.objectMethods[obj] = fn
|
||||
} else {
|
||||
b.waitForSharedFunction(fn)
|
||||
}
|
||||
return fn
|
||||
}
|
||||
|
||||
// LookupMethod returns the implementation of the method of type T
|
||||
// identified by (pkg, name). It returns nil if the method exists but
|
||||
// is an interface method or generic method, and panics if T has no such method.
|
||||
func (prog *Program) LookupMethod(T types.Type, pkg *types.Package, name string) *Function {
|
||||
sel := prog.MethodSets.MethodSet(T).Lookup(pkg, name)
|
||||
if sel == nil {
|
||||
panic(fmt.Sprintf("%s has no method %s", T, types.Id(pkg, name)))
|
||||
}
|
||||
return prog.MethodValue(sel)
|
||||
}
|
||||
|
||||
// methodSet contains the (concrete) methods of a concrete type (non-interface, non-parameterized).
|
||||
type methodSet struct {
|
||||
mapping map[string]*Function // populated lazily
|
||||
}
|
||||
|
||||
// RuntimeTypes returns a new unordered slice containing all types in
|
||||
// the program for which a runtime type is required.
|
||||
//
|
||||
// A runtime type is required for any non-parameterized, non-interface
|
||||
// type that is converted to an interface, or for any type (including
|
||||
// interface types) derivable from one through reflection.
|
||||
//
|
||||
// The methods of such types may be reachable through reflection or
|
||||
// interface calls even if they are never called directly.
|
||||
//
|
||||
// Thread-safe.
|
||||
//
|
||||
// Acquires prog.makeInterfaceTypesMu.
|
||||
func (prog *Program) RuntimeTypes() []types.Type {
|
||||
prog.makeInterfaceTypesMu.Lock()
|
||||
defer prog.makeInterfaceTypesMu.Unlock()
|
||||
|
||||
// Compute the derived types on demand, since many SSA clients
|
||||
// never call RuntimeTypes, and those that do typically call
|
||||
// it once (often within ssautil.AllFunctions, which will
|
||||
// eventually not use it; see Go issue #69291.) This
|
||||
// eliminates the need to eagerly compute all the element
|
||||
// types during SSA building.
|
||||
var runtimeTypes []types.Type
|
||||
add := func(t types.Type) { runtimeTypes = append(runtimeTypes, t) }
|
||||
var set typeutil.Map // for de-duping identical types
|
||||
for t := range prog.makeInterfaceTypes {
|
||||
typesinternal.ForEachElement(&set, &prog.MethodSets, t, add)
|
||||
}
|
||||
|
||||
return runtimeTypes
|
||||
}
|
||||
111
vendor/golang.org/x/tools/go/ssa/mode.go
generated
vendored
Normal file
111
vendor/golang.org/x/tools/go/ssa/mode.go
generated
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssa
|
||||
|
||||
// This file defines the BuilderMode type and its command-line flag.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// BuilderMode is a bitmask of options for diagnostics and checking.
|
||||
//
|
||||
// *BuilderMode satisfies the flag.Value interface. Example:
|
||||
//
|
||||
// var mode = ssa.BuilderMode(0)
|
||||
// func init() { flag.Var(&mode, "build", ssa.BuilderModeDoc) }
|
||||
type BuilderMode uint
|
||||
|
||||
const (
|
||||
PrintPackages BuilderMode = 1 << iota // Print package inventory to stdout
|
||||
PrintFunctions // Print function SSA code to stdout
|
||||
LogSource // Log source locations as SSA builder progresses
|
||||
SanityCheckFunctions // Perform sanity checking of function bodies
|
||||
NaiveForm // Build naïve SSA form: don't replace local loads/stores with registers
|
||||
BuildSerially // Build packages serially, not in parallel.
|
||||
GlobalDebug // Enable debug info for all packages
|
||||
BareInits // Build init functions without guards or calls to dependent inits
|
||||
InstantiateGenerics // Instantiate generics functions (monomorphize) while building
|
||||
)
|
||||
|
||||
const BuilderModeDoc = `Options controlling the SSA builder.
|
||||
The value is a sequence of zero or more of these letters:
|
||||
C perform sanity [C]hecking of the SSA form.
|
||||
D include [D]ebug info for every function.
|
||||
P print [P]ackage inventory.
|
||||
F print [F]unction SSA code.
|
||||
S log [S]ource locations as SSA builder progresses.
|
||||
L build distinct packages seria[L]ly instead of in parallel.
|
||||
N build [N]aive SSA form: don't replace local loads/stores with registers.
|
||||
I build bare [I]nit functions: no init guards or calls to dependent inits.
|
||||
G instantiate [G]eneric function bodies via monomorphization
|
||||
`
|
||||
|
||||
func (m BuilderMode) String() string {
|
||||
var buf bytes.Buffer
|
||||
if m&GlobalDebug != 0 {
|
||||
buf.WriteByte('D')
|
||||
}
|
||||
if m&PrintPackages != 0 {
|
||||
buf.WriteByte('P')
|
||||
}
|
||||
if m&PrintFunctions != 0 {
|
||||
buf.WriteByte('F')
|
||||
}
|
||||
if m&LogSource != 0 {
|
||||
buf.WriteByte('S')
|
||||
}
|
||||
if m&SanityCheckFunctions != 0 {
|
||||
buf.WriteByte('C')
|
||||
}
|
||||
if m&NaiveForm != 0 {
|
||||
buf.WriteByte('N')
|
||||
}
|
||||
if m&BuildSerially != 0 {
|
||||
buf.WriteByte('L')
|
||||
}
|
||||
if m&BareInits != 0 {
|
||||
buf.WriteByte('I')
|
||||
}
|
||||
if m&InstantiateGenerics != 0 {
|
||||
buf.WriteByte('G')
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Set parses the flag characters in s and updates *m.
|
||||
func (m *BuilderMode) Set(s string) error {
|
||||
var mode BuilderMode
|
||||
for _, c := range s {
|
||||
switch c {
|
||||
case 'D':
|
||||
mode |= GlobalDebug
|
||||
case 'P':
|
||||
mode |= PrintPackages
|
||||
case 'F':
|
||||
mode |= PrintFunctions
|
||||
case 'S':
|
||||
mode |= LogSource | BuildSerially
|
||||
case 'C':
|
||||
mode |= SanityCheckFunctions
|
||||
case 'N':
|
||||
mode |= NaiveForm
|
||||
case 'L':
|
||||
mode |= BuildSerially
|
||||
case 'I':
|
||||
mode |= BareInits
|
||||
case 'G':
|
||||
mode |= InstantiateGenerics
|
||||
default:
|
||||
return fmt.Errorf("unknown BuilderMode option: %q", c)
|
||||
}
|
||||
}
|
||||
*m = mode
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get returns m.
|
||||
func (m BuilderMode) Get() any { return m }
|
||||
462
vendor/golang.org/x/tools/go/ssa/print.go
generated
vendored
Normal file
462
vendor/golang.org/x/tools/go/ssa/print.go
generated
vendored
Normal file
@@ -0,0 +1,462 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssa
|
||||
|
||||
// This file implements the String() methods for all Value and
|
||||
// Instruction types.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/types"
|
||||
"io"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/types/typeutil"
|
||||
"golang.org/x/tools/internal/typeparams"
|
||||
)
|
||||
|
||||
// relName returns the name of v relative to i.
|
||||
// In most cases, this is identical to v.Name(), but references to
|
||||
// Functions (including methods) and Globals use RelString and
|
||||
// all types are displayed with relType, so that only cross-package
|
||||
// references are package-qualified.
|
||||
func relName(v Value, i Instruction) string {
|
||||
var from *types.Package
|
||||
if i != nil {
|
||||
from = i.Parent().relPkg()
|
||||
}
|
||||
switch v := v.(type) {
|
||||
case Member: // *Function or *Global
|
||||
return v.RelString(from)
|
||||
case *Const:
|
||||
return v.RelString(from)
|
||||
}
|
||||
return v.Name()
|
||||
}
|
||||
|
||||
func relType(t types.Type, from *types.Package) string {
|
||||
return types.TypeString(t, types.RelativeTo(from))
|
||||
}
|
||||
|
||||
func relTerm(term *types.Term, from *types.Package) string {
|
||||
s := relType(term.Type(), from)
|
||||
if term.Tilde() {
|
||||
return "~" + s
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func relString(m Member, from *types.Package) string {
|
||||
// NB: not all globals have an Object (e.g. init$guard),
|
||||
// so use Package().Object not Object.Package().
|
||||
if pkg := m.Package().Pkg; pkg != nil && pkg != from {
|
||||
return fmt.Sprintf("%s.%s", pkg.Path(), m.Name())
|
||||
}
|
||||
return m.Name()
|
||||
}
|
||||
|
||||
// Value.String()
|
||||
//
|
||||
// This method is provided only for debugging.
|
||||
// It never appears in disassembly, which uses Value.Name().
|
||||
|
||||
func (v *Parameter) String() string {
|
||||
from := v.Parent().relPkg()
|
||||
return fmt.Sprintf("parameter %s : %s", v.Name(), relType(v.Type(), from))
|
||||
}
|
||||
|
||||
func (v *FreeVar) String() string {
|
||||
from := v.Parent().relPkg()
|
||||
return fmt.Sprintf("freevar %s : %s", v.Name(), relType(v.Type(), from))
|
||||
}
|
||||
|
||||
func (v *Builtin) String() string {
|
||||
return fmt.Sprintf("builtin %s", v.Name())
|
||||
}
|
||||
|
||||
// Instruction.String()
|
||||
|
||||
func (v *Alloc) String() string {
|
||||
op := "local"
|
||||
if v.Heap {
|
||||
op = "new"
|
||||
}
|
||||
from := v.Parent().relPkg()
|
||||
return fmt.Sprintf("%s %s (%s)", op, relType(typeparams.MustDeref(v.Type()), from), v.Comment)
|
||||
}
|
||||
|
||||
func (v *Phi) String() string {
|
||||
var b bytes.Buffer
|
||||
b.WriteString("phi [")
|
||||
for i, edge := range v.Edges {
|
||||
if i > 0 {
|
||||
b.WriteString(", ")
|
||||
}
|
||||
// Be robust against malformed CFG.
|
||||
if v.block == nil {
|
||||
b.WriteString("??")
|
||||
continue
|
||||
}
|
||||
block := -1
|
||||
if i < len(v.block.Preds) {
|
||||
block = v.block.Preds[i].Index
|
||||
}
|
||||
fmt.Fprintf(&b, "%d: ", block)
|
||||
edgeVal := "<nil>" // be robust
|
||||
if edge != nil {
|
||||
edgeVal = relName(edge, v)
|
||||
}
|
||||
b.WriteString(edgeVal)
|
||||
}
|
||||
b.WriteString("]")
|
||||
if v.Comment != "" {
|
||||
b.WriteString(" #")
|
||||
b.WriteString(v.Comment)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func printCall(v *CallCommon, prefix string, instr Instruction) string {
|
||||
var b bytes.Buffer
|
||||
b.WriteString(prefix)
|
||||
if !v.IsInvoke() {
|
||||
b.WriteString(relName(v.Value, instr))
|
||||
} else {
|
||||
fmt.Fprintf(&b, "invoke %s.%s", relName(v.Value, instr), v.Method.Name())
|
||||
}
|
||||
b.WriteString("(")
|
||||
for i, arg := range v.Args {
|
||||
if i > 0 {
|
||||
b.WriteString(", ")
|
||||
}
|
||||
b.WriteString(relName(arg, instr))
|
||||
}
|
||||
if v.Signature().Variadic() {
|
||||
b.WriteString("...")
|
||||
}
|
||||
b.WriteString(")")
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (c *CallCommon) String() string {
|
||||
return printCall(c, "", nil)
|
||||
}
|
||||
|
||||
func (v *Call) String() string {
|
||||
return printCall(&v.Call, "", v)
|
||||
}
|
||||
|
||||
func (v *BinOp) String() string {
|
||||
return fmt.Sprintf("%s %s %s", relName(v.X, v), v.Op.String(), relName(v.Y, v))
|
||||
}
|
||||
|
||||
func (v *UnOp) String() string {
|
||||
return fmt.Sprintf("%s%s%s", v.Op, relName(v.X, v), commaOk(v.CommaOk))
|
||||
}
|
||||
|
||||
func printConv(prefix string, v, x Value) string {
|
||||
from := v.Parent().relPkg()
|
||||
return fmt.Sprintf("%s %s <- %s (%s)",
|
||||
prefix,
|
||||
relType(v.Type(), from),
|
||||
relType(x.Type(), from),
|
||||
relName(x, v.(Instruction)))
|
||||
}
|
||||
|
||||
func (v *ChangeType) String() string { return printConv("changetype", v, v.X) }
|
||||
func (v *Convert) String() string { return printConv("convert", v, v.X) }
|
||||
func (v *ChangeInterface) String() string { return printConv("change interface", v, v.X) }
|
||||
func (v *SliceToArrayPointer) String() string { return printConv("slice to array pointer", v, v.X) }
|
||||
func (v *MakeInterface) String() string { return printConv("make", v, v.X) }
|
||||
|
||||
func (v *MultiConvert) String() string {
|
||||
from := v.Parent().relPkg()
|
||||
|
||||
var b strings.Builder
|
||||
b.WriteString(printConv("multiconvert", v, v.X))
|
||||
b.WriteString(" [")
|
||||
for i, s := range termListOf(v.from) {
|
||||
for j, d := range termListOf(v.to) {
|
||||
if i != 0 || j != 0 {
|
||||
b.WriteString(" | ")
|
||||
}
|
||||
fmt.Fprintf(&b, "%s <- %s", relTerm(d, from), relTerm(s, from))
|
||||
}
|
||||
}
|
||||
b.WriteString("]")
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (v *MakeClosure) String() string {
|
||||
var b bytes.Buffer
|
||||
fmt.Fprintf(&b, "make closure %s", relName(v.Fn, v))
|
||||
if v.Bindings != nil {
|
||||
b.WriteString(" [")
|
||||
for i, c := range v.Bindings {
|
||||
if i > 0 {
|
||||
b.WriteString(", ")
|
||||
}
|
||||
b.WriteString(relName(c, v))
|
||||
}
|
||||
b.WriteString("]")
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (v *MakeSlice) String() string {
|
||||
from := v.Parent().relPkg()
|
||||
return fmt.Sprintf("make %s %s %s",
|
||||
relType(v.Type(), from),
|
||||
relName(v.Len, v),
|
||||
relName(v.Cap, v))
|
||||
}
|
||||
|
||||
func (v *Slice) String() string {
|
||||
var b bytes.Buffer
|
||||
b.WriteString("slice ")
|
||||
b.WriteString(relName(v.X, v))
|
||||
b.WriteString("[")
|
||||
if v.Low != nil {
|
||||
b.WriteString(relName(v.Low, v))
|
||||
}
|
||||
b.WriteString(":")
|
||||
if v.High != nil {
|
||||
b.WriteString(relName(v.High, v))
|
||||
}
|
||||
if v.Max != nil {
|
||||
b.WriteString(":")
|
||||
b.WriteString(relName(v.Max, v))
|
||||
}
|
||||
b.WriteString("]")
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (v *MakeMap) String() string {
|
||||
res := ""
|
||||
if v.Reserve != nil {
|
||||
res = relName(v.Reserve, v)
|
||||
}
|
||||
from := v.Parent().relPkg()
|
||||
return fmt.Sprintf("make %s %s", relType(v.Type(), from), res)
|
||||
}
|
||||
|
||||
func (v *MakeChan) String() string {
|
||||
from := v.Parent().relPkg()
|
||||
return fmt.Sprintf("make %s %s", relType(v.Type(), from), relName(v.Size, v))
|
||||
}
|
||||
|
||||
func (v *FieldAddr) String() string {
|
||||
// Be robust against a bad index.
|
||||
name := "?"
|
||||
if fld := fieldOf(typeparams.MustDeref(v.X.Type()), v.Field); fld != nil {
|
||||
name = fld.Name()
|
||||
}
|
||||
return fmt.Sprintf("&%s.%s [#%d]", relName(v.X, v), name, v.Field)
|
||||
}
|
||||
|
||||
func (v *Field) String() string {
|
||||
// Be robust against a bad index.
|
||||
name := "?"
|
||||
if fld := fieldOf(v.X.Type(), v.Field); fld != nil {
|
||||
name = fld.Name()
|
||||
}
|
||||
return fmt.Sprintf("%s.%s [#%d]", relName(v.X, v), name, v.Field)
|
||||
}
|
||||
|
||||
func (v *IndexAddr) String() string {
|
||||
return fmt.Sprintf("&%s[%s]", relName(v.X, v), relName(v.Index, v))
|
||||
}
|
||||
|
||||
func (v *Index) String() string {
|
||||
return fmt.Sprintf("%s[%s]", relName(v.X, v), relName(v.Index, v))
|
||||
}
|
||||
|
||||
func (v *Lookup) String() string {
|
||||
return fmt.Sprintf("%s[%s]%s", relName(v.X, v), relName(v.Index, v), commaOk(v.CommaOk))
|
||||
}
|
||||
|
||||
func (v *Range) String() string {
|
||||
return "range " + relName(v.X, v)
|
||||
}
|
||||
|
||||
func (v *Next) String() string {
|
||||
return "next " + relName(v.Iter, v)
|
||||
}
|
||||
|
||||
func (v *TypeAssert) String() string {
|
||||
from := v.Parent().relPkg()
|
||||
return fmt.Sprintf("typeassert%s %s.(%s)", commaOk(v.CommaOk), relName(v.X, v), relType(v.AssertedType, from))
|
||||
}
|
||||
|
||||
func (v *Extract) String() string {
|
||||
return fmt.Sprintf("extract %s #%d", relName(v.Tuple, v), v.Index)
|
||||
}
|
||||
|
||||
func (s *Jump) String() string {
|
||||
// Be robust against malformed CFG.
|
||||
block := -1
|
||||
if s.block != nil && len(s.block.Succs) == 1 {
|
||||
block = s.block.Succs[0].Index
|
||||
}
|
||||
return fmt.Sprintf("jump %d", block)
|
||||
}
|
||||
|
||||
func (s *If) String() string {
|
||||
// Be robust against malformed CFG.
|
||||
tblock, fblock := -1, -1
|
||||
if s.block != nil && len(s.block.Succs) == 2 {
|
||||
tblock = s.block.Succs[0].Index
|
||||
fblock = s.block.Succs[1].Index
|
||||
}
|
||||
return fmt.Sprintf("if %s goto %d else %d", relName(s.Cond, s), tblock, fblock)
|
||||
}
|
||||
|
||||
func (s *Go) String() string {
|
||||
return printCall(&s.Call, "go ", s)
|
||||
}
|
||||
|
||||
func (s *Panic) String() string {
|
||||
return "panic " + relName(s.X, s)
|
||||
}
|
||||
|
||||
func (s *Return) String() string {
|
||||
var b bytes.Buffer
|
||||
b.WriteString("return")
|
||||
for i, r := range s.Results {
|
||||
if i == 0 {
|
||||
b.WriteString(" ")
|
||||
} else {
|
||||
b.WriteString(", ")
|
||||
}
|
||||
b.WriteString(relName(r, s))
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (*RunDefers) String() string {
|
||||
return "rundefers"
|
||||
}
|
||||
|
||||
func (s *Send) String() string {
|
||||
return fmt.Sprintf("send %s <- %s", relName(s.Chan, s), relName(s.X, s))
|
||||
}
|
||||
|
||||
func (s *Defer) String() string {
|
||||
prefix := "defer "
|
||||
if s.DeferStack != nil {
|
||||
prefix += "[" + relName(s.DeferStack, s) + "] "
|
||||
}
|
||||
c := printCall(&s.Call, prefix, s)
|
||||
return c
|
||||
}
|
||||
|
||||
func (s *Select) String() string {
|
||||
var b bytes.Buffer
|
||||
for i, st := range s.States {
|
||||
if i > 0 {
|
||||
b.WriteString(", ")
|
||||
}
|
||||
if st.Dir == types.RecvOnly {
|
||||
b.WriteString("<-")
|
||||
b.WriteString(relName(st.Chan, s))
|
||||
} else {
|
||||
b.WriteString(relName(st.Chan, s))
|
||||
b.WriteString("<-")
|
||||
b.WriteString(relName(st.Send, s))
|
||||
}
|
||||
}
|
||||
non := ""
|
||||
if !s.Blocking {
|
||||
non = "non"
|
||||
}
|
||||
return fmt.Sprintf("select %sblocking [%s]", non, b.String())
|
||||
}
|
||||
|
||||
func (s *Store) String() string {
|
||||
return fmt.Sprintf("*%s = %s", relName(s.Addr, s), relName(s.Val, s))
|
||||
}
|
||||
|
||||
func (s *MapUpdate) String() string {
|
||||
return fmt.Sprintf("%s[%s] = %s", relName(s.Map, s), relName(s.Key, s), relName(s.Value, s))
|
||||
}
|
||||
|
||||
func (s *DebugRef) String() string {
|
||||
p := s.Parent().Prog.Fset.Position(s.Pos())
|
||||
var descr any
|
||||
if s.object != nil {
|
||||
descr = s.object // e.g. "var x int"
|
||||
} else {
|
||||
descr = reflect.TypeOf(s.Expr) // e.g. "*ast.CallExpr"
|
||||
}
|
||||
var addr string
|
||||
if s.IsAddr {
|
||||
addr = "address of "
|
||||
}
|
||||
return fmt.Sprintf("; %s%s @ %d:%d is %s", addr, descr, p.Line, p.Column, s.X.Name())
|
||||
}
|
||||
|
||||
func (p *Package) String() string {
|
||||
return "package " + p.Pkg.Path()
|
||||
}
|
||||
|
||||
var _ io.WriterTo = (*Package)(nil) // *Package implements io.Writer
|
||||
|
||||
func (p *Package) WriteTo(w io.Writer) (int64, error) {
|
||||
var buf bytes.Buffer
|
||||
WritePackage(&buf, p)
|
||||
n, err := w.Write(buf.Bytes())
|
||||
return int64(n), err
|
||||
}
|
||||
|
||||
// WritePackage writes to buf a human-readable summary of p.
|
||||
func WritePackage(buf *bytes.Buffer, p *Package) {
|
||||
fmt.Fprintf(buf, "%s:\n", p)
|
||||
|
||||
var names []string
|
||||
maxname := 0
|
||||
for name := range p.Members {
|
||||
if l := len(name); l > maxname {
|
||||
maxname = l
|
||||
}
|
||||
names = append(names, name)
|
||||
}
|
||||
|
||||
from := p.Pkg
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
switch mem := p.Members[name].(type) {
|
||||
case *NamedConst:
|
||||
fmt.Fprintf(buf, " const %-*s %s = %s\n",
|
||||
maxname, name, mem.Name(), mem.Value.RelString(from))
|
||||
|
||||
case *Function:
|
||||
fmt.Fprintf(buf, " func %-*s %s\n",
|
||||
maxname, name, relType(mem.Type(), from))
|
||||
|
||||
case *Type:
|
||||
fmt.Fprintf(buf, " type %-*s %s\n",
|
||||
maxname, name, relType(mem.Type().Underlying(), from))
|
||||
for _, meth := range typeutil.IntuitiveMethodSet(mem.Type(), &p.Prog.MethodSets) {
|
||||
fmt.Fprintf(buf, " %s\n", types.SelectionString(meth, types.RelativeTo(from)))
|
||||
}
|
||||
|
||||
case *Global:
|
||||
fmt.Fprintf(buf, " var %-*s %s\n",
|
||||
maxname, name, relType(typeparams.MustDeref(mem.Type()), from))
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(buf, "\n")
|
||||
}
|
||||
|
||||
func commaOk(x bool) string {
|
||||
if x {
|
||||
return ",ok"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
637
vendor/golang.org/x/tools/go/ssa/sanity.go
generated
vendored
Normal file
637
vendor/golang.org/x/tools/go/ssa/sanity.go
generated
vendored
Normal file
@@ -0,0 +1,637 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssa
|
||||
|
||||
// An optional pass for sanity-checking invariants of the SSA representation.
|
||||
// Currently it checks CFG invariants but little at the instruction level.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/types"
|
||||
"io"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type sanity struct {
|
||||
reporter io.Writer
|
||||
fn *Function
|
||||
block *BasicBlock
|
||||
instrs map[Instruction]unit
|
||||
insane bool
|
||||
}
|
||||
|
||||
// sanityCheck performs integrity checking of the SSA representation
|
||||
// of the function fn (which must have been "built") and returns true
|
||||
// if it was valid. Diagnostics are written to reporter if non-nil,
|
||||
// os.Stderr otherwise. Some diagnostics are only warnings and do not
|
||||
// imply a negative result.
|
||||
//
|
||||
// Sanity-checking is intended to facilitate the debugging of code
|
||||
// transformation passes.
|
||||
func sanityCheck(fn *Function, reporter io.Writer) bool {
|
||||
if reporter == nil {
|
||||
reporter = os.Stderr
|
||||
}
|
||||
return (&sanity{reporter: reporter}).checkFunction(fn)
|
||||
}
|
||||
|
||||
// mustSanityCheck is like sanityCheck but panics instead of returning
|
||||
// a negative result.
|
||||
func mustSanityCheck(fn *Function, reporter io.Writer) {
|
||||
if !sanityCheck(fn, reporter) {
|
||||
fn.WriteTo(os.Stderr)
|
||||
panic("SanityCheck failed")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sanity) diagnostic(prefix, format string, args ...any) {
|
||||
fmt.Fprintf(s.reporter, "%s: function %s", prefix, s.fn)
|
||||
if s.block != nil {
|
||||
fmt.Fprintf(s.reporter, ", block %s", s.block)
|
||||
}
|
||||
io.WriteString(s.reporter, ": ")
|
||||
fmt.Fprintf(s.reporter, format, args...)
|
||||
io.WriteString(s.reporter, "\n")
|
||||
}
|
||||
|
||||
func (s *sanity) errorf(format string, args ...any) {
|
||||
s.insane = true
|
||||
s.diagnostic("Error", format, args...)
|
||||
}
|
||||
|
||||
func (s *sanity) warnf(format string, args ...any) {
|
||||
s.diagnostic("Warning", format, args...)
|
||||
}
|
||||
|
||||
// findDuplicate returns an arbitrary basic block that appeared more
|
||||
// than once in blocks, or nil if all were unique.
|
||||
func findDuplicate(blocks []*BasicBlock) *BasicBlock {
|
||||
if len(blocks) < 2 {
|
||||
return nil
|
||||
}
|
||||
if blocks[0] == blocks[1] {
|
||||
return blocks[0]
|
||||
}
|
||||
// Slow path:
|
||||
m := make(map[*BasicBlock]bool)
|
||||
for _, b := range blocks {
|
||||
if m[b] {
|
||||
return b
|
||||
}
|
||||
m[b] = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *sanity) checkInstr(idx int, instr Instruction) {
|
||||
switch instr := instr.(type) {
|
||||
case *If, *Jump, *Return, *Panic:
|
||||
s.errorf("control flow instruction not at end of block")
|
||||
case *Phi:
|
||||
if idx == 0 {
|
||||
// It suffices to apply this check to just the first phi node.
|
||||
if dup := findDuplicate(s.block.Preds); dup != nil {
|
||||
s.errorf("phi node in block with duplicate predecessor %s", dup)
|
||||
}
|
||||
} else {
|
||||
prev := s.block.Instrs[idx-1]
|
||||
if _, ok := prev.(*Phi); !ok {
|
||||
s.errorf("Phi instruction follows a non-Phi: %T", prev)
|
||||
}
|
||||
}
|
||||
if ne, np := len(instr.Edges), len(s.block.Preds); ne != np {
|
||||
s.errorf("phi node has %d edges but %d predecessors", ne, np)
|
||||
|
||||
} else {
|
||||
for i, e := range instr.Edges {
|
||||
if e == nil {
|
||||
s.errorf("phi node '%s' has no value for edge #%d from %s", instr.Comment, i, s.block.Preds[i])
|
||||
} else if !types.Identical(instr.typ, e.Type()) {
|
||||
s.errorf("phi node '%s' has a different type (%s) for edge #%d from %s (%s)",
|
||||
instr.Comment, instr.Type(), i, s.block.Preds[i], e.Type())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case *Alloc:
|
||||
if !instr.Heap {
|
||||
found := slices.Contains(s.fn.Locals, instr)
|
||||
if !found {
|
||||
s.errorf("local alloc %s = %s does not appear in Function.Locals", instr.Name(), instr)
|
||||
}
|
||||
}
|
||||
|
||||
case *BinOp:
|
||||
case *Call:
|
||||
if common := instr.Call; common.IsInvoke() {
|
||||
if !types.IsInterface(common.Value.Type()) {
|
||||
s.errorf("invoke on %s (%s) which is not an interface type (or type param)", common.Value, common.Value.Type())
|
||||
}
|
||||
}
|
||||
case *ChangeInterface:
|
||||
case *ChangeType:
|
||||
case *SliceToArrayPointer:
|
||||
case *Convert:
|
||||
if from := instr.X.Type(); !isBasicConvTypes(from) {
|
||||
if to := instr.Type(); !isBasicConvTypes(to) {
|
||||
s.errorf("convert %s -> %s: at least one type must be basic (or all basic, []byte, or []rune)", from, to)
|
||||
}
|
||||
}
|
||||
case *MultiConvert:
|
||||
case *Defer:
|
||||
case *Extract:
|
||||
case *Field:
|
||||
case *FieldAddr:
|
||||
case *Go:
|
||||
case *Index:
|
||||
case *IndexAddr:
|
||||
case *Lookup:
|
||||
case *MakeChan:
|
||||
case *MakeClosure:
|
||||
numFree := len(instr.Fn.(*Function).FreeVars)
|
||||
numBind := len(instr.Bindings)
|
||||
if numFree != numBind {
|
||||
s.errorf("MakeClosure has %d Bindings for function %s with %d free vars",
|
||||
numBind, instr.Fn, numFree)
|
||||
|
||||
}
|
||||
if recv := instr.Type().(*types.Signature).Recv(); recv != nil {
|
||||
s.errorf("MakeClosure's type includes receiver %s", recv.Type())
|
||||
}
|
||||
|
||||
case *MakeInterface:
|
||||
case *MakeMap:
|
||||
case *MakeSlice:
|
||||
case *MapUpdate:
|
||||
case *Next:
|
||||
case *Range:
|
||||
case *RunDefers:
|
||||
case *Select:
|
||||
case *Send:
|
||||
case *Slice:
|
||||
case *Store:
|
||||
case *TypeAssert:
|
||||
case *UnOp:
|
||||
case *DebugRef:
|
||||
// TODO(adonovan): implement checks.
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown instruction type: %T", instr))
|
||||
}
|
||||
|
||||
if call, ok := instr.(CallInstruction); ok {
|
||||
if call.Common().Signature() == nil {
|
||||
s.errorf("nil signature: %s", call)
|
||||
}
|
||||
}
|
||||
|
||||
// Check that value-defining instructions have valid types
|
||||
// and a valid referrer list.
|
||||
if v, ok := instr.(Value); ok {
|
||||
t := v.Type()
|
||||
if t == nil {
|
||||
s.errorf("no type: %s = %s", v.Name(), v)
|
||||
} else if t == tRangeIter || t == tDeferStack {
|
||||
// not a proper type; ignore.
|
||||
} else if b, ok := t.Underlying().(*types.Basic); ok && b.Info()&types.IsUntyped != 0 {
|
||||
s.errorf("instruction has 'untyped' result: %s = %s : %s", v.Name(), v, t)
|
||||
}
|
||||
s.checkReferrerList(v)
|
||||
}
|
||||
|
||||
// Untyped constants are legal as instruction Operands(),
|
||||
// for example:
|
||||
// _ = "foo"[0]
|
||||
// or:
|
||||
// if wordsize==64 {...}
|
||||
|
||||
// All other non-Instruction Values can be found via their
|
||||
// enclosing Function or Package.
|
||||
}
|
||||
|
||||
func (s *sanity) checkFinalInstr(instr Instruction) {
|
||||
switch instr := instr.(type) {
|
||||
case *If:
|
||||
if nsuccs := len(s.block.Succs); nsuccs != 2 {
|
||||
s.errorf("If-terminated block has %d successors; expected 2", nsuccs)
|
||||
return
|
||||
}
|
||||
if s.block.Succs[0] == s.block.Succs[1] {
|
||||
s.errorf("If-instruction has same True, False target blocks: %s", s.block.Succs[0])
|
||||
return
|
||||
}
|
||||
|
||||
case *Jump:
|
||||
if nsuccs := len(s.block.Succs); nsuccs != 1 {
|
||||
s.errorf("Jump-terminated block has %d successors; expected 1", nsuccs)
|
||||
return
|
||||
}
|
||||
|
||||
case *Return:
|
||||
if nsuccs := len(s.block.Succs); nsuccs != 0 {
|
||||
s.errorf("Return-terminated block has %d successors; expected none", nsuccs)
|
||||
return
|
||||
}
|
||||
if na, nf := len(instr.Results), s.fn.Signature.Results().Len(); nf != na {
|
||||
s.errorf("%d-ary return in %d-ary function", na, nf)
|
||||
}
|
||||
|
||||
case *Panic:
|
||||
if nsuccs := len(s.block.Succs); nsuccs != 0 {
|
||||
s.errorf("Panic-terminated block has %d successors; expected none", nsuccs)
|
||||
return
|
||||
}
|
||||
|
||||
default:
|
||||
s.errorf("non-control flow instruction at end of block")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sanity) checkBlock(b *BasicBlock, index int) {
|
||||
s.block = b
|
||||
|
||||
if b.Index != index {
|
||||
s.errorf("block has incorrect Index %d", b.Index)
|
||||
}
|
||||
if b.parent != s.fn {
|
||||
s.errorf("block has incorrect parent %s", b.parent)
|
||||
}
|
||||
|
||||
// Check all blocks are reachable.
|
||||
// (The entry block is always implicitly reachable,
|
||||
// as is the Recover block, if any.)
|
||||
if (index > 0 && b != b.parent.Recover) && len(b.Preds) == 0 {
|
||||
s.warnf("unreachable block")
|
||||
if b.Instrs == nil {
|
||||
// Since this block is about to be pruned,
|
||||
// tolerating transient problems in it
|
||||
// simplifies other optimizations.
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Check predecessor and successor relations are dual,
|
||||
// and that all blocks in CFG belong to same function.
|
||||
for _, a := range b.Preds {
|
||||
found := slices.Contains(a.Succs, b)
|
||||
if !found {
|
||||
s.errorf("expected successor edge in predecessor %s; found only: %s", a, a.Succs)
|
||||
}
|
||||
if a.parent != s.fn {
|
||||
s.errorf("predecessor %s belongs to different function %s", a, a.parent)
|
||||
}
|
||||
}
|
||||
for _, c := range b.Succs {
|
||||
found := slices.Contains(c.Preds, b)
|
||||
if !found {
|
||||
s.errorf("expected predecessor edge in successor %s; found only: %s", c, c.Preds)
|
||||
}
|
||||
if c.parent != s.fn {
|
||||
s.errorf("successor %s belongs to different function %s", c, c.parent)
|
||||
}
|
||||
}
|
||||
|
||||
// Check each instruction is sane.
|
||||
n := len(b.Instrs)
|
||||
if n == 0 {
|
||||
s.errorf("basic block contains no instructions")
|
||||
}
|
||||
var rands [10]*Value // reuse storage
|
||||
for j, instr := range b.Instrs {
|
||||
if instr == nil {
|
||||
s.errorf("nil instruction at index %d", j)
|
||||
continue
|
||||
}
|
||||
if b2 := instr.Block(); b2 == nil {
|
||||
s.errorf("nil Block() for instruction at index %d", j)
|
||||
continue
|
||||
} else if b2 != b {
|
||||
s.errorf("wrong Block() (%s) for instruction at index %d ", b2, j)
|
||||
continue
|
||||
}
|
||||
if j < n-1 {
|
||||
s.checkInstr(j, instr)
|
||||
} else {
|
||||
s.checkFinalInstr(instr)
|
||||
}
|
||||
|
||||
// Check Instruction.Operands.
|
||||
operands:
|
||||
for i, op := range instr.Operands(rands[:0]) {
|
||||
if op == nil {
|
||||
s.errorf("nil operand pointer %d of %s", i, instr)
|
||||
continue
|
||||
}
|
||||
val := *op
|
||||
if val == nil {
|
||||
continue // a nil operand is ok
|
||||
}
|
||||
|
||||
// Check that "untyped" types only appear on constant operands.
|
||||
if _, ok := (*op).(*Const); !ok {
|
||||
if basic, ok := (*op).Type().Underlying().(*types.Basic); ok {
|
||||
if basic.Info()&types.IsUntyped != 0 {
|
||||
s.errorf("operand #%d of %s is untyped: %s", i, instr, basic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check that Operands that are also Instructions belong to same function.
|
||||
// TODO(adonovan): also check their block dominates block b.
|
||||
if val, ok := val.(Instruction); ok {
|
||||
if val.Block() == nil {
|
||||
s.errorf("operand %d of %s is an instruction (%s) that belongs to no block", i, instr, val)
|
||||
} else if val.Parent() != s.fn {
|
||||
s.errorf("operand %d of %s is an instruction (%s) from function %s", i, instr, val, val.Parent())
|
||||
}
|
||||
}
|
||||
|
||||
// Check that each function-local operand of
|
||||
// instr refers back to instr. (NB: quadratic)
|
||||
switch val := val.(type) {
|
||||
case *Const, *Global, *Builtin:
|
||||
continue // not local
|
||||
case *Function:
|
||||
if val.parent == nil {
|
||||
continue // only anon functions are local
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(adonovan): check val.Parent() != nil <=> val.Referrers() is defined.
|
||||
|
||||
if refs := val.Referrers(); refs != nil {
|
||||
for _, ref := range *refs {
|
||||
if ref == instr {
|
||||
continue operands
|
||||
}
|
||||
}
|
||||
s.errorf("operand %d of %s (%s) does not refer to us", i, instr, val)
|
||||
} else {
|
||||
s.errorf("operand %d of %s (%s) has no referrers", i, instr, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sanity) checkReferrerList(v Value) {
|
||||
refs := v.Referrers()
|
||||
if refs == nil {
|
||||
s.errorf("%s has missing referrer list", v.Name())
|
||||
return
|
||||
}
|
||||
for i, ref := range *refs {
|
||||
if _, ok := s.instrs[ref]; !ok {
|
||||
s.errorf("%s.Referrers()[%d] = %s is not an instruction belonging to this function", v.Name(), i, ref)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sanity) checkFunctionParams() {
|
||||
signature := s.fn.Signature
|
||||
params := s.fn.Params
|
||||
|
||||
// startSigParams is the start of signature.Params() within params.
|
||||
startSigParams := 0
|
||||
if signature.Recv() != nil {
|
||||
startSigParams = 1
|
||||
}
|
||||
|
||||
if startSigParams+signature.Params().Len() != len(params) {
|
||||
s.errorf("function has %d parameters in signature but has %d after building",
|
||||
startSigParams+signature.Params().Len(), len(params))
|
||||
return
|
||||
}
|
||||
|
||||
for i, param := range params {
|
||||
var sigType types.Type
|
||||
si := i - startSigParams
|
||||
if si < 0 {
|
||||
sigType = signature.Recv().Type()
|
||||
} else {
|
||||
sigType = signature.Params().At(si).Type()
|
||||
}
|
||||
|
||||
if !types.Identical(sigType, param.Type()) {
|
||||
s.errorf("expect type %s in signature but got type %s in param %d", param.Type(), sigType, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checkTransientFields checks whether all transient fields of Function are cleared.
|
||||
func (s *sanity) checkTransientFields() {
|
||||
fn := s.fn
|
||||
if fn.build != nil {
|
||||
s.errorf("function transient field 'build' is not nil")
|
||||
}
|
||||
if fn.currentBlock != nil {
|
||||
s.errorf("function transient field 'currentBlock' is not nil")
|
||||
}
|
||||
if fn.vars != nil {
|
||||
s.errorf("function transient field 'vars' is not nil")
|
||||
}
|
||||
if fn.results != nil {
|
||||
s.errorf("function transient field 'results' is not nil")
|
||||
}
|
||||
if fn.returnVars != nil {
|
||||
s.errorf("function transient field 'returnVars' is not nil")
|
||||
}
|
||||
if fn.targets != nil {
|
||||
s.errorf("function transient field 'targets' is not nil")
|
||||
}
|
||||
if fn.lblocks != nil {
|
||||
s.errorf("function transient field 'lblocks' is not nil")
|
||||
}
|
||||
if fn.subst != nil {
|
||||
s.errorf("function transient field 'subst' is not nil")
|
||||
}
|
||||
if fn.jump != nil {
|
||||
s.errorf("function transient field 'jump' is not nil")
|
||||
}
|
||||
if fn.deferstack != nil {
|
||||
s.errorf("function transient field 'deferstack' is not nil")
|
||||
}
|
||||
if fn.source != nil {
|
||||
s.errorf("function transient field 'source' is not nil")
|
||||
}
|
||||
if fn.exits != nil {
|
||||
s.errorf("function transient field 'exits' is not nil")
|
||||
}
|
||||
if fn.uniq != 0 {
|
||||
s.errorf("function transient field 'uniq' is not zero")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sanity) checkFunction(fn *Function) bool {
|
||||
s.fn = fn
|
||||
s.checkFunctionParams()
|
||||
s.checkTransientFields()
|
||||
|
||||
// TODO(taking): Sanity check origin, typeparams, and typeargs.
|
||||
if fn.Prog == nil {
|
||||
s.errorf("nil Prog")
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
_ = fn.String() // must not crash
|
||||
_ = fn.RelString(fn.relPkg()) // must not crash
|
||||
WriteFunction(&buf, fn) // must not crash
|
||||
|
||||
// All functions have a package, except delegates (which are
|
||||
// shared across packages, or duplicated as weak symbols in a
|
||||
// separate-compilation model), and error.Error.
|
||||
if fn.Pkg == nil {
|
||||
if strings.HasPrefix(fn.Synthetic, "from type information (on demand)") ||
|
||||
strings.HasPrefix(fn.Synthetic, "wrapper ") ||
|
||||
strings.HasPrefix(fn.Synthetic, "bound ") ||
|
||||
strings.HasPrefix(fn.Synthetic, "thunk ") ||
|
||||
strings.HasSuffix(fn.name, "Error") ||
|
||||
strings.HasPrefix(fn.Synthetic, "instance ") ||
|
||||
strings.HasPrefix(fn.Synthetic, "instantiation ") ||
|
||||
(fn.parent != nil && len(fn.typeargs) > 0) /* anon fun in instance */ {
|
||||
// ok
|
||||
} else {
|
||||
s.errorf("nil Pkg")
|
||||
}
|
||||
}
|
||||
if src, syn := fn.Synthetic == "", fn.Syntax() != nil; src != syn {
|
||||
if len(fn.typeargs) > 0 && fn.Prog.mode&InstantiateGenerics != 0 {
|
||||
// ok (instantiation with InstantiateGenerics on)
|
||||
} else if fn.topLevelOrigin != nil && len(fn.typeargs) > 0 {
|
||||
// ok (we always have the syntax set for instantiation)
|
||||
} else if _, rng := fn.syntax.(*ast.RangeStmt); rng && fn.Synthetic == "range-over-func yield" {
|
||||
// ok (range-func-yields are both synthetic and keep syntax)
|
||||
} else {
|
||||
s.errorf("got fromSource=%t, hasSyntax=%t; want same values", src, syn)
|
||||
}
|
||||
}
|
||||
|
||||
// Build the set of valid referrers.
|
||||
s.instrs = make(map[Instruction]unit)
|
||||
|
||||
// instrs are the instructions that are present in the function.
|
||||
for instr := range fn.instrs() {
|
||||
s.instrs[instr] = unit{}
|
||||
}
|
||||
|
||||
// Check all Locals allocations appear in the function instruction.
|
||||
for i, l := range fn.Locals {
|
||||
if _, present := s.instrs[l]; !present {
|
||||
s.warnf("function doesn't contain Local alloc %s", l.Name())
|
||||
}
|
||||
|
||||
if l.Parent() != fn {
|
||||
s.errorf("Local %s at index %d has wrong parent", l.Name(), i)
|
||||
}
|
||||
if l.Heap {
|
||||
s.errorf("Local %s at index %d has Heap flag set", l.Name(), i)
|
||||
}
|
||||
}
|
||||
for i, p := range fn.Params {
|
||||
if p.Parent() != fn {
|
||||
s.errorf("Param %s at index %d has wrong parent", p.Name(), i)
|
||||
}
|
||||
// Check common suffix of Signature and Params match type.
|
||||
if sig := fn.Signature; sig != nil {
|
||||
j := i - len(fn.Params) + sig.Params().Len() // index within sig.Params
|
||||
if j < 0 {
|
||||
continue
|
||||
}
|
||||
if !types.Identical(p.Type(), sig.Params().At(j).Type()) {
|
||||
s.errorf("Param %s at index %d has wrong type (%s, versus %s in Signature)", p.Name(), i, p.Type(), sig.Params().At(j).Type())
|
||||
|
||||
}
|
||||
}
|
||||
s.checkReferrerList(p)
|
||||
}
|
||||
for i, fv := range fn.FreeVars {
|
||||
if fv.Parent() != fn {
|
||||
s.errorf("FreeVar %s at index %d has wrong parent", fv.Name(), i)
|
||||
}
|
||||
s.checkReferrerList(fv)
|
||||
}
|
||||
|
||||
if fn.Blocks != nil && len(fn.Blocks) == 0 {
|
||||
// Function _had_ blocks (so it's not external) but
|
||||
// they were "optimized" away, even the entry block.
|
||||
s.errorf("Blocks slice is non-nil but empty")
|
||||
}
|
||||
for i, b := range fn.Blocks {
|
||||
if b == nil {
|
||||
s.warnf("nil *BasicBlock at f.Blocks[%d]", i)
|
||||
continue
|
||||
}
|
||||
s.checkBlock(b, i)
|
||||
}
|
||||
if fn.Recover != nil && fn.Blocks[fn.Recover.Index] != fn.Recover {
|
||||
s.errorf("Recover block is not in Blocks slice")
|
||||
}
|
||||
|
||||
s.block = nil
|
||||
for i, anon := range fn.AnonFuncs {
|
||||
if anon.Parent() != fn {
|
||||
s.errorf("AnonFuncs[%d]=%s but %s.Parent()=%s", i, anon, anon, anon.Parent())
|
||||
}
|
||||
if i != int(anon.anonIdx) {
|
||||
s.errorf("AnonFuncs[%d]=%s but %s.anonIdx=%d", i, anon, anon, anon.anonIdx)
|
||||
}
|
||||
}
|
||||
s.fn = nil
|
||||
return !s.insane
|
||||
}
|
||||
|
||||
// sanityCheckPackage checks invariants of packages upon creation.
|
||||
// It does not require that the package is built.
|
||||
// Unlike sanityCheck (for functions), it just panics at the first error.
|
||||
func sanityCheckPackage(pkg *Package) {
|
||||
if pkg.Pkg == nil {
|
||||
panic(fmt.Sprintf("Package %s has no Object", pkg))
|
||||
}
|
||||
if pkg.info != nil {
|
||||
panic(fmt.Sprintf("package %s field 'info' is not cleared", pkg))
|
||||
}
|
||||
if pkg.files != nil {
|
||||
panic(fmt.Sprintf("package %s field 'files' is not cleared", pkg))
|
||||
}
|
||||
if pkg.created != nil {
|
||||
panic(fmt.Sprintf("package %s field 'created' is not cleared", pkg))
|
||||
}
|
||||
if pkg.initVersion != nil {
|
||||
panic(fmt.Sprintf("package %s field 'initVersion' is not cleared", pkg))
|
||||
}
|
||||
|
||||
_ = pkg.String() // must not crash
|
||||
|
||||
for name, mem := range pkg.Members {
|
||||
if name != mem.Name() {
|
||||
panic(fmt.Sprintf("%s: %T.Name() = %s, want %s",
|
||||
pkg.Pkg.Path(), mem, mem.Name(), name))
|
||||
}
|
||||
obj := mem.Object()
|
||||
if obj == nil {
|
||||
// This check is sound because fields
|
||||
// {Global,Function}.object have type
|
||||
// types.Object. (If they were declared as
|
||||
// *types.{Var,Func}, we'd have a non-empty
|
||||
// interface containing a nil pointer.)
|
||||
|
||||
continue // not all members have typechecker objects
|
||||
}
|
||||
if obj.Name() != name {
|
||||
if obj.Name() == "init" && strings.HasPrefix(mem.Name(), "init#") {
|
||||
// Ok. The name of a declared init function varies between
|
||||
// its types.Func ("init") and its ssa.Function ("init#%d").
|
||||
} else {
|
||||
panic(fmt.Sprintf("%s: %T.Object().Name() = %s, want %s",
|
||||
pkg.Pkg.Path(), mem, obj.Name(), name))
|
||||
}
|
||||
}
|
||||
if obj.Pos() != mem.Pos() {
|
||||
panic(fmt.Sprintf("%s Pos=%d obj.Pos=%d", mem, mem.Pos(), obj.Pos()))
|
||||
}
|
||||
}
|
||||
}
|
||||
288
vendor/golang.org/x/tools/go/ssa/source.go
generated
vendored
Normal file
288
vendor/golang.org/x/tools/go/ssa/source.go
generated
vendored
Normal file
@@ -0,0 +1,288 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssa
|
||||
|
||||
// This file defines utilities for working with source positions
|
||||
// or source-level named entities ("objects").
|
||||
|
||||
// TODO(adonovan): test that {Value,Instruction}.Pos() positions match
|
||||
// the originating syntax, as specified.
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
)
|
||||
|
||||
// EnclosingFunction returns the function that contains the syntax
|
||||
// node denoted by path.
|
||||
//
|
||||
// Syntax associated with package-level variable specifications is
|
||||
// enclosed by the package's init() function.
|
||||
//
|
||||
// Returns nil if not found; reasons might include:
|
||||
// - the node is not enclosed by any function.
|
||||
// - the node is within an anonymous function (FuncLit) and
|
||||
// its SSA function has not been created yet
|
||||
// (pkg.Build() has not yet been called).
|
||||
func EnclosingFunction(pkg *Package, path []ast.Node) *Function {
|
||||
// Start with package-level function...
|
||||
fn := findEnclosingPackageLevelFunction(pkg, path)
|
||||
if fn == nil {
|
||||
return nil // not in any function
|
||||
}
|
||||
|
||||
// ...then walk down the nested anonymous functions.
|
||||
n := len(path)
|
||||
outer:
|
||||
for i := range path {
|
||||
if lit, ok := path[n-1-i].(*ast.FuncLit); ok {
|
||||
for _, anon := range fn.AnonFuncs {
|
||||
if anon.Pos() == lit.Type.Func {
|
||||
fn = anon
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
// SSA function not found:
|
||||
// - package not yet built, or maybe
|
||||
// - builder skipped FuncLit in dead block
|
||||
// (in principle; but currently the Builder
|
||||
// generates even dead FuncLits).
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fn
|
||||
}
|
||||
|
||||
// HasEnclosingFunction returns true if the AST node denoted by path
|
||||
// is contained within the declaration of some function or
|
||||
// package-level variable.
|
||||
//
|
||||
// Unlike EnclosingFunction, the behaviour of this function does not
|
||||
// depend on whether SSA code for pkg has been built, so it can be
|
||||
// used to quickly reject check inputs that will cause
|
||||
// EnclosingFunction to fail, prior to SSA building.
|
||||
func HasEnclosingFunction(pkg *Package, path []ast.Node) bool {
|
||||
return findEnclosingPackageLevelFunction(pkg, path) != nil
|
||||
}
|
||||
|
||||
// findEnclosingPackageLevelFunction returns the Function
|
||||
// corresponding to the package-level function enclosing path.
|
||||
func findEnclosingPackageLevelFunction(pkg *Package, path []ast.Node) *Function {
|
||||
if n := len(path); n >= 2 { // [... {Gen,Func}Decl File]
|
||||
switch decl := path[n-2].(type) {
|
||||
case *ast.GenDecl:
|
||||
if decl.Tok == token.VAR && n >= 3 {
|
||||
// Package-level 'var' initializer.
|
||||
return pkg.init
|
||||
}
|
||||
|
||||
case *ast.FuncDecl:
|
||||
if decl.Recv == nil && decl.Name.Name == "init" {
|
||||
// Explicit init() function.
|
||||
for _, b := range pkg.init.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
if instr, ok := instr.(*Call); ok {
|
||||
if callee, ok := instr.Call.Value.(*Function); ok && callee.Pkg == pkg && callee.Pos() == decl.Name.NamePos {
|
||||
return callee
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Hack: return non-nil when SSA is not yet
|
||||
// built so that HasEnclosingFunction works.
|
||||
return pkg.init
|
||||
}
|
||||
// Declared function/method.
|
||||
return findNamedFunc(pkg, decl.Name.NamePos)
|
||||
}
|
||||
}
|
||||
return nil // not in any function
|
||||
}
|
||||
|
||||
// findNamedFunc returns the named function whose FuncDecl.Ident is at
|
||||
// position pos.
|
||||
func findNamedFunc(pkg *Package, pos token.Pos) *Function {
|
||||
// Look at all package members and method sets of named types.
|
||||
// Not very efficient.
|
||||
for _, mem := range pkg.Members {
|
||||
switch mem := mem.(type) {
|
||||
case *Function:
|
||||
if mem.Pos() == pos {
|
||||
return mem
|
||||
}
|
||||
case *Type:
|
||||
mset := pkg.Prog.MethodSets.MethodSet(types.NewPointer(mem.Type()))
|
||||
for i, n := 0, mset.Len(); i < n; i++ {
|
||||
// Don't call Program.Method: avoid creating wrappers.
|
||||
obj := mset.At(i).Obj().(*types.Func)
|
||||
if obj.Pos() == pos {
|
||||
// obj from MethodSet may not be the origin type.
|
||||
m := obj.Origin()
|
||||
return pkg.objects[m].(*Function)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValueForExpr returns the SSA Value that corresponds to non-constant
|
||||
// expression e.
|
||||
//
|
||||
// It returns nil if no value was found, e.g.
|
||||
// - the expression is not lexically contained within f;
|
||||
// - f was not built with debug information; or
|
||||
// - e is a constant expression. (For efficiency, no debug
|
||||
// information is stored for constants. Use
|
||||
// go/types.Info.Types[e].Value instead.)
|
||||
// - e is a reference to nil or a built-in function.
|
||||
// - the value was optimised away.
|
||||
//
|
||||
// If e is an addressable expression used in an lvalue context,
|
||||
// value is the address denoted by e, and isAddr is true.
|
||||
//
|
||||
// The types of e (or &e, if isAddr) and the result are equal
|
||||
// (modulo "untyped" bools resulting from comparisons).
|
||||
//
|
||||
// (Tip: to find the ssa.Value given a source position, use
|
||||
// astutil.PathEnclosingInterval to locate the ast.Node, then
|
||||
// EnclosingFunction to locate the Function, then ValueForExpr to find
|
||||
// the ssa.Value.)
|
||||
func (f *Function) ValueForExpr(e ast.Expr) (value Value, isAddr bool) {
|
||||
if f.debugInfo() { // (opt)
|
||||
e = ast.Unparen(e)
|
||||
for _, b := range f.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
if ref, ok := instr.(*DebugRef); ok {
|
||||
if ref.Expr == e {
|
||||
return ref.X, ref.IsAddr
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// --- Lookup functions for source-level named entities (types.Objects) ---
|
||||
|
||||
// Package returns the SSA Package corresponding to the specified
|
||||
// type-checker package. It returns nil if no such Package was
|
||||
// created by a prior call to prog.CreatePackage.
|
||||
func (prog *Program) Package(pkg *types.Package) *Package {
|
||||
return prog.packages[pkg]
|
||||
}
|
||||
|
||||
// packageLevelMember returns the package-level member corresponding
|
||||
// to the specified symbol, which may be a package-level const
|
||||
// (*NamedConst), var (*Global) or func/method (*Function) of some
|
||||
// package in prog.
|
||||
//
|
||||
// It returns nil if the object belongs to a package that has not been
|
||||
// created by prog.CreatePackage.
|
||||
func (prog *Program) packageLevelMember(obj types.Object) Member {
|
||||
if pkg, ok := prog.packages[obj.Pkg()]; ok {
|
||||
return pkg.objects[obj]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FuncValue returns the SSA function or (non-interface) method
|
||||
// denoted by the specified func symbol. It returns nil if the symbol
|
||||
// denotes an interface method, or belongs to a package that was not
|
||||
// created by prog.CreatePackage.
|
||||
func (prog *Program) FuncValue(obj *types.Func) *Function {
|
||||
fn, _ := prog.packageLevelMember(obj).(*Function)
|
||||
return fn
|
||||
}
|
||||
|
||||
// ConstValue returns the SSA constant denoted by the specified const symbol.
|
||||
func (prog *Program) ConstValue(obj *types.Const) *Const {
|
||||
// TODO(adonovan): opt: share (don't reallocate)
|
||||
// Consts for const objects and constant ast.Exprs.
|
||||
|
||||
// Universal constant? {true,false,nil}
|
||||
if obj.Parent() == types.Universe {
|
||||
return NewConst(obj.Val(), obj.Type())
|
||||
}
|
||||
// Package-level named constant?
|
||||
if v := prog.packageLevelMember(obj); v != nil {
|
||||
return v.(*NamedConst).Value
|
||||
}
|
||||
return NewConst(obj.Val(), obj.Type())
|
||||
}
|
||||
|
||||
// VarValue returns the SSA Value that corresponds to a specific
|
||||
// identifier denoting the specified var symbol.
|
||||
//
|
||||
// VarValue returns nil if a local variable was not found, perhaps
|
||||
// because its package was not built, the debug information was not
|
||||
// requested during SSA construction, or the value was optimized away.
|
||||
//
|
||||
// ref is the path to an ast.Ident (e.g. from PathEnclosingInterval),
|
||||
// and that ident must resolve to obj.
|
||||
//
|
||||
// pkg is the package enclosing the reference. (A reference to a var
|
||||
// always occurs within a function, so we need to know where to find it.)
|
||||
//
|
||||
// If the identifier is a field selector and its base expression is
|
||||
// non-addressable, then VarValue returns the value of that field.
|
||||
// For example:
|
||||
//
|
||||
// func f() struct {x int}
|
||||
// f().x // VarValue(x) returns a *Field instruction of type int
|
||||
//
|
||||
// All other identifiers denote addressable locations (variables).
|
||||
// For them, VarValue may return either the variable's address or its
|
||||
// value, even when the expression is evaluated only for its value; the
|
||||
// situation is reported by isAddr, the second component of the result.
|
||||
//
|
||||
// If !isAddr, the returned value is the one associated with the
|
||||
// specific identifier. For example,
|
||||
//
|
||||
// var x int // VarValue(x) returns Const 0 here
|
||||
// x = 1 // VarValue(x) returns Const 1 here
|
||||
//
|
||||
// It is not specified whether the value or the address is returned in
|
||||
// any particular case, as it may depend upon optimizations performed
|
||||
// during SSA code generation, such as registerization, constant
|
||||
// folding, avoidance of materialization of subexpressions, etc.
|
||||
func (prog *Program) VarValue(obj *types.Var, pkg *Package, ref []ast.Node) (value Value, isAddr bool) {
|
||||
// All references to a var are local to some function, possibly init.
|
||||
fn := EnclosingFunction(pkg, ref)
|
||||
if fn == nil {
|
||||
return // e.g. def of struct field; SSA not built?
|
||||
}
|
||||
|
||||
id := ref[0].(*ast.Ident)
|
||||
|
||||
// Defining ident of a parameter?
|
||||
if id.Pos() == obj.Pos() {
|
||||
for _, param := range fn.Params {
|
||||
if param.Object() == obj {
|
||||
return param, false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Other ident?
|
||||
for _, b := range fn.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
if dr, ok := instr.(*DebugRef); ok {
|
||||
if dr.Pos() == id.Pos() {
|
||||
return dr.X, dr.IsAddr
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Defining ident of package-level var?
|
||||
if v := prog.packageLevelMember(obj); v != nil {
|
||||
return v.(*Global), true
|
||||
}
|
||||
|
||||
return // e.g. debug info not requested, or var optimized away
|
||||
}
|
||||
1873
vendor/golang.org/x/tools/go/ssa/ssa.go
generated
vendored
Normal file
1873
vendor/golang.org/x/tools/go/ssa/ssa.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
36
vendor/golang.org/x/tools/go/ssa/ssautil/deprecated.go
generated
vendored
Normal file
36
vendor/golang.org/x/tools/go/ssa/ssautil/deprecated.go
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssautil
|
||||
|
||||
// This file contains deprecated public APIs.
|
||||
// We discourage their use.
|
||||
|
||||
import (
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
)
|
||||
|
||||
// CreateProgram returns a new program in SSA form, given a program
|
||||
// loaded from source. An SSA package is created for each transitively
|
||||
// error-free package of lprog.
|
||||
//
|
||||
// Code for bodies of functions is not built until Build is called
|
||||
// on the result.
|
||||
//
|
||||
// The mode parameter controls diagnostics and checking during SSA construction.
|
||||
//
|
||||
// Deprecated: Use [golang.org/x/tools/go/packages] and the [Packages]
|
||||
// function instead; see ssa.Example_loadPackages.
|
||||
func CreateProgram(lprog *loader.Program, mode ssa.BuilderMode) *ssa.Program {
|
||||
prog := ssa.NewProgram(lprog.Fset, mode)
|
||||
|
||||
for _, info := range lprog.AllPackages {
|
||||
if info.TransitivelyErrorFree {
|
||||
prog.CreatePackage(info.Pkg, info.Files, &info.Info, info.Importable)
|
||||
}
|
||||
}
|
||||
|
||||
return prog
|
||||
}
|
||||
189
vendor/golang.org/x/tools/go/ssa/ssautil/load.go
generated
vendored
Normal file
189
vendor/golang.org/x/tools/go/ssa/ssautil/load.go
generated
vendored
Normal file
@@ -0,0 +1,189 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssautil
|
||||
|
||||
// This file defines utility functions for constructing programs in SSA form.
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
)
|
||||
|
||||
// Packages creates an SSA program for a set of packages.
|
||||
//
|
||||
// The packages must have been loaded from source syntax using the
|
||||
// [packages.Load] function in [packages.LoadSyntax] or
|
||||
// [packages.LoadAllSyntax] mode.
|
||||
//
|
||||
// Packages creates an SSA package for each well-typed package in the
|
||||
// initial list, plus all their dependencies. The resulting list of
|
||||
// packages corresponds to the list of initial packages, and may contain
|
||||
// a nil if SSA code could not be constructed for the corresponding initial
|
||||
// package due to type errors.
|
||||
//
|
||||
// Code for bodies of functions is not built until [Program.Build] is
|
||||
// called on the resulting Program. SSA code is constructed only for
|
||||
// the initial packages with well-typed syntax trees.
|
||||
//
|
||||
// The mode parameter controls diagnostics and checking during SSA construction.
|
||||
func Packages(initial []*packages.Package, mode ssa.BuilderMode) (*ssa.Program, []*ssa.Package) {
|
||||
// TODO(adonovan): opt: this calls CreatePackage far more than
|
||||
// necessary: for all dependencies, not just the (non-initial)
|
||||
// direct dependencies of the initial packages.
|
||||
//
|
||||
// But can it reasonably be changed without breaking the
|
||||
// spirit and/or letter of the law above? Clients may notice
|
||||
// if we call CreatePackage less, as methods like
|
||||
// Program.FuncValue will return nil. Or must we provide a new
|
||||
// function (and perhaps deprecate this one)? Is it worth it?
|
||||
//
|
||||
// Tim King makes the interesting point that it would be
|
||||
// possible to entirely alleviate the client from the burden
|
||||
// of calling CreatePackage for non-syntax packages, if we
|
||||
// were to treat vars and funcs lazily in the same way we now
|
||||
// treat methods. (In essence, try to move away from the
|
||||
// notion of ssa.Packages, and make the Program answer
|
||||
// all reasonable questions about any types.Object.)
|
||||
|
||||
return doPackages(initial, mode, false)
|
||||
}
|
||||
|
||||
// AllPackages creates an SSA program for a set of packages plus all
|
||||
// their dependencies.
|
||||
//
|
||||
// The packages must have been loaded from source syntax using the
|
||||
// [packages.Load] function in [packages.LoadAllSyntax] mode.
|
||||
//
|
||||
// AllPackages creates an SSA package for each well-typed package in the
|
||||
// initial list, plus all their dependencies. The resulting list of
|
||||
// packages corresponds to the list of initial packages, and may contain
|
||||
// a nil if SSA code could not be constructed for the corresponding
|
||||
// initial package due to type errors.
|
||||
//
|
||||
// Code for bodies of functions is not built until Build is called on
|
||||
// the resulting Program. SSA code is constructed for all packages with
|
||||
// well-typed syntax trees.
|
||||
//
|
||||
// The mode parameter controls diagnostics and checking during SSA construction.
|
||||
func AllPackages(initial []*packages.Package, mode ssa.BuilderMode) (*ssa.Program, []*ssa.Package) {
|
||||
return doPackages(initial, mode, true)
|
||||
}
|
||||
|
||||
func doPackages(initial []*packages.Package, mode ssa.BuilderMode, deps bool) (*ssa.Program, []*ssa.Package) {
|
||||
|
||||
var fset *token.FileSet
|
||||
if len(initial) > 0 {
|
||||
fset = initial[0].Fset
|
||||
}
|
||||
|
||||
prog := ssa.NewProgram(fset, mode)
|
||||
|
||||
isInitial := make(map[*packages.Package]bool, len(initial))
|
||||
for _, p := range initial {
|
||||
isInitial[p] = true
|
||||
}
|
||||
|
||||
ssamap := make(map[*packages.Package]*ssa.Package)
|
||||
packages.Visit(initial, nil, func(p *packages.Package) {
|
||||
if p.Types != nil && !p.IllTyped {
|
||||
var files []*ast.File
|
||||
var info *types.Info
|
||||
if deps || isInitial[p] {
|
||||
files = p.Syntax
|
||||
info = p.TypesInfo
|
||||
}
|
||||
ssamap[p] = prog.CreatePackage(p.Types, files, info, true)
|
||||
}
|
||||
})
|
||||
|
||||
var ssapkgs []*ssa.Package
|
||||
for _, p := range initial {
|
||||
ssapkgs = append(ssapkgs, ssamap[p]) // may be nil
|
||||
}
|
||||
return prog, ssapkgs
|
||||
}
|
||||
|
||||
// BuildPackage builds an SSA program with SSA intermediate
|
||||
// representation (IR) for all functions of a single package.
|
||||
//
|
||||
// It populates pkg by type-checking the specified file syntax trees. All
|
||||
// dependencies are loaded using the importer specified by tc, which
|
||||
// typically loads compiler export data; SSA code cannot be built for
|
||||
// those packages. BuildPackage then constructs an [ssa.Program] with all
|
||||
// dependency packages created, and builds and returns the SSA package
|
||||
// corresponding to pkg.
|
||||
//
|
||||
// The caller must have set pkg.Path to the import path.
|
||||
//
|
||||
// The operation fails if there were any type-checking or import errors.
|
||||
//
|
||||
// See ../example_test.go for an example.
|
||||
func BuildPackage(tc *types.Config, fset *token.FileSet, pkg *types.Package, files []*ast.File, mode ssa.BuilderMode) (*ssa.Package, *types.Info, error) {
|
||||
if fset == nil {
|
||||
panic("no token.FileSet")
|
||||
}
|
||||
if pkg.Path() == "" {
|
||||
panic("package has no import path")
|
||||
}
|
||||
|
||||
info := &types.Info{
|
||||
Types: make(map[ast.Expr]types.TypeAndValue),
|
||||
Defs: make(map[*ast.Ident]types.Object),
|
||||
Uses: make(map[*ast.Ident]types.Object),
|
||||
Implicits: make(map[ast.Node]types.Object),
|
||||
Instances: make(map[*ast.Ident]types.Instance),
|
||||
Scopes: make(map[ast.Node]*types.Scope),
|
||||
Selections: make(map[*ast.SelectorExpr]*types.Selection),
|
||||
FileVersions: make(map[*ast.File]string),
|
||||
}
|
||||
if err := types.NewChecker(tc, fset, pkg, info).Files(files); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
prog := ssa.NewProgram(fset, mode)
|
||||
|
||||
// Create SSA packages for all imports.
|
||||
// Order is not significant.
|
||||
created := make(map[*types.Package]bool)
|
||||
var createAll func(pkgs []*types.Package)
|
||||
createAll = func(pkgs []*types.Package) {
|
||||
for _, p := range pkgs {
|
||||
if !created[p] {
|
||||
created[p] = true
|
||||
prog.CreatePackage(p, nil, nil, true)
|
||||
createAll(p.Imports())
|
||||
}
|
||||
}
|
||||
}
|
||||
createAll(pkg.Imports())
|
||||
|
||||
// TODO(adonovan): we could replace createAll with just:
|
||||
//
|
||||
// // Create SSA packages for all imports.
|
||||
// for _, p := range pkg.Imports() {
|
||||
// prog.CreatePackage(p, nil, nil, true)
|
||||
// }
|
||||
//
|
||||
// (with minor changes to changes to ../builder_test.go as
|
||||
// shown in CL 511715 PS 10.) But this would strictly violate
|
||||
// the letter of the doc comment above, which says "all
|
||||
// dependencies created".
|
||||
//
|
||||
// Tim makes the good point with some extra work we could
|
||||
// remove the need for any CreatePackage calls except the
|
||||
// ones with syntax (i.e. primary packages). Of course
|
||||
// You wouldn't have ssa.Packages and Members for as
|
||||
// many things but no-one really uses that anyway.
|
||||
// I wish I had done this from the outset.
|
||||
|
||||
// Create and build the primary package.
|
||||
ssapkg := prog.CreatePackage(pkg, files, info, false)
|
||||
ssapkg.Build()
|
||||
return ssapkg, info, nil
|
||||
}
|
||||
230
vendor/golang.org/x/tools/go/ssa/ssautil/switch.go
generated
vendored
Normal file
230
vendor/golang.org/x/tools/go/ssa/ssautil/switch.go
generated
vendored
Normal file
@@ -0,0 +1,230 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssautil
|
||||
|
||||
// This file implements discovery of switch and type-switch constructs
|
||||
// from low-level control flow.
|
||||
//
|
||||
// Many techniques exist for compiling a high-level switch with
|
||||
// constant cases to efficient machine code. The optimal choice will
|
||||
// depend on the data type, the specific case values, the code in the
|
||||
// body of each case, and the hardware.
|
||||
// Some examples:
|
||||
// - a lookup table (for a switch that maps constants to constants)
|
||||
// - a computed goto
|
||||
// - a binary tree
|
||||
// - a perfect hash
|
||||
// - a two-level switch (to partition constant strings by their first byte).
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/token"
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
)
|
||||
|
||||
// A ConstCase represents a single constant comparison.
|
||||
// It is part of a Switch.
|
||||
type ConstCase struct {
|
||||
Block *ssa.BasicBlock // block performing the comparison
|
||||
Body *ssa.BasicBlock // body of the case
|
||||
Value *ssa.Const // case comparand
|
||||
}
|
||||
|
||||
// A TypeCase represents a single type assertion.
|
||||
// It is part of a Switch.
|
||||
type TypeCase struct {
|
||||
Block *ssa.BasicBlock // block performing the type assert
|
||||
Body *ssa.BasicBlock // body of the case
|
||||
Type types.Type // case type
|
||||
Binding ssa.Value // value bound by this case
|
||||
}
|
||||
|
||||
// A Switch is a logical high-level control flow operation
|
||||
// (a multiway branch) discovered by analysis of a CFG containing
|
||||
// only if/else chains. It is not part of the ssa.Instruction set.
|
||||
//
|
||||
// One of ConstCases and TypeCases has length >= 2;
|
||||
// the other is nil.
|
||||
//
|
||||
// In a value switch, the list of cases may contain duplicate constants.
|
||||
// A type switch may contain duplicate types, or types assignable
|
||||
// to an interface type also in the list.
|
||||
// TODO(adonovan): eliminate such duplicates.
|
||||
type Switch struct {
|
||||
Start *ssa.BasicBlock // block containing start of if/else chain
|
||||
X ssa.Value // the switch operand
|
||||
ConstCases []ConstCase // ordered list of constant comparisons
|
||||
TypeCases []TypeCase // ordered list of type assertions
|
||||
Default *ssa.BasicBlock // successor if all comparisons fail
|
||||
}
|
||||
|
||||
func (sw *Switch) String() string {
|
||||
// We represent each block by the String() of its
|
||||
// first Instruction, e.g. "print(42:int)".
|
||||
var buf bytes.Buffer
|
||||
if sw.ConstCases != nil {
|
||||
fmt.Fprintf(&buf, "switch %s {\n", sw.X.Name())
|
||||
for _, c := range sw.ConstCases {
|
||||
fmt.Fprintf(&buf, "case %s: %s\n", c.Value, c.Body.Instrs[0])
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintf(&buf, "switch %s.(type) {\n", sw.X.Name())
|
||||
for _, c := range sw.TypeCases {
|
||||
fmt.Fprintf(&buf, "case %s %s: %s\n",
|
||||
c.Binding.Name(), c.Type, c.Body.Instrs[0])
|
||||
}
|
||||
}
|
||||
if sw.Default != nil {
|
||||
fmt.Fprintf(&buf, "default: %s\n", sw.Default.Instrs[0])
|
||||
}
|
||||
fmt.Fprintf(&buf, "}")
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Switches examines the control-flow graph of fn and returns the
|
||||
// set of inferred value and type switches. A value switch tests an
|
||||
// ssa.Value for equality against two or more compile-time constant
|
||||
// values. Switches involving link-time constants (addresses) are
|
||||
// ignored. A type switch type-asserts an ssa.Value against two or
|
||||
// more types.
|
||||
//
|
||||
// The switches are returned in dominance order.
|
||||
//
|
||||
// The resulting switches do not necessarily correspond to uses of the
|
||||
// 'switch' keyword in the source: for example, a single source-level
|
||||
// switch statement with non-constant cases may result in zero, one or
|
||||
// many Switches, one per plural sequence of constant cases.
|
||||
// Switches may even be inferred from if/else- or goto-based control flow.
|
||||
// (In general, the control flow constructs of the source program
|
||||
// cannot be faithfully reproduced from the SSA representation.)
|
||||
func Switches(fn *ssa.Function) []Switch {
|
||||
// Traverse the CFG in dominance order, so we don't
|
||||
// enter an if/else-chain in the middle.
|
||||
var switches []Switch
|
||||
seen := make(map[*ssa.BasicBlock]bool) // TODO(adonovan): opt: use ssa.blockSet
|
||||
for _, b := range fn.DomPreorder() {
|
||||
if x, k := isComparisonBlock(b); x != nil {
|
||||
// Block b starts a switch.
|
||||
sw := Switch{Start: b, X: x}
|
||||
valueSwitch(&sw, k, seen)
|
||||
if len(sw.ConstCases) > 1 {
|
||||
switches = append(switches, sw)
|
||||
}
|
||||
}
|
||||
|
||||
if y, x, T := isTypeAssertBlock(b); y != nil {
|
||||
// Block b starts a type switch.
|
||||
sw := Switch{Start: b, X: x}
|
||||
typeSwitch(&sw, y, T, seen)
|
||||
if len(sw.TypeCases) > 1 {
|
||||
switches = append(switches, sw)
|
||||
}
|
||||
}
|
||||
}
|
||||
return switches
|
||||
}
|
||||
|
||||
func valueSwitch(sw *Switch, k *ssa.Const, seen map[*ssa.BasicBlock]bool) {
|
||||
b := sw.Start
|
||||
x := sw.X
|
||||
for x == sw.X {
|
||||
if seen[b] {
|
||||
break
|
||||
}
|
||||
seen[b] = true
|
||||
|
||||
sw.ConstCases = append(sw.ConstCases, ConstCase{
|
||||
Block: b,
|
||||
Body: b.Succs[0],
|
||||
Value: k,
|
||||
})
|
||||
b = b.Succs[1]
|
||||
if len(b.Instrs) > 2 {
|
||||
// Block b contains not just 'if x == k',
|
||||
// so it may have side effects that
|
||||
// make it unsafe to elide.
|
||||
break
|
||||
}
|
||||
if len(b.Preds) != 1 {
|
||||
// Block b has multiple predecessors,
|
||||
// so it cannot be treated as a case.
|
||||
break
|
||||
}
|
||||
x, k = isComparisonBlock(b)
|
||||
}
|
||||
sw.Default = b
|
||||
}
|
||||
|
||||
func typeSwitch(sw *Switch, y ssa.Value, T types.Type, seen map[*ssa.BasicBlock]bool) {
|
||||
b := sw.Start
|
||||
x := sw.X
|
||||
for x == sw.X {
|
||||
if seen[b] {
|
||||
break
|
||||
}
|
||||
seen[b] = true
|
||||
|
||||
sw.TypeCases = append(sw.TypeCases, TypeCase{
|
||||
Block: b,
|
||||
Body: b.Succs[0],
|
||||
Type: T,
|
||||
Binding: y,
|
||||
})
|
||||
b = b.Succs[1]
|
||||
if len(b.Instrs) > 4 {
|
||||
// Block b contains not just
|
||||
// {TypeAssert; Extract #0; Extract #1; If}
|
||||
// so it may have side effects that
|
||||
// make it unsafe to elide.
|
||||
break
|
||||
}
|
||||
if len(b.Preds) != 1 {
|
||||
// Block b has multiple predecessors,
|
||||
// so it cannot be treated as a case.
|
||||
break
|
||||
}
|
||||
y, x, T = isTypeAssertBlock(b)
|
||||
}
|
||||
sw.Default = b
|
||||
}
|
||||
|
||||
// isComparisonBlock returns the operands (v, k) if a block ends with
|
||||
// a comparison v==k, where k is a compile-time constant.
|
||||
func isComparisonBlock(b *ssa.BasicBlock) (v ssa.Value, k *ssa.Const) {
|
||||
if n := len(b.Instrs); n >= 2 {
|
||||
if i, ok := b.Instrs[n-1].(*ssa.If); ok {
|
||||
if binop, ok := i.Cond.(*ssa.BinOp); ok && binop.Block() == b && binop.Op == token.EQL {
|
||||
if k, ok := binop.Y.(*ssa.Const); ok {
|
||||
return binop.X, k
|
||||
}
|
||||
if k, ok := binop.X.(*ssa.Const); ok {
|
||||
return binop.Y, k
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// isTypeAssertBlock returns the operands (y, x, T) if a block ends with
|
||||
// a type assertion "if y, ok := x.(T); ok {".
|
||||
func isTypeAssertBlock(b *ssa.BasicBlock) (y, x ssa.Value, T types.Type) {
|
||||
if n := len(b.Instrs); n >= 4 {
|
||||
if i, ok := b.Instrs[n-1].(*ssa.If); ok {
|
||||
if ext1, ok := i.Cond.(*ssa.Extract); ok && ext1.Block() == b && ext1.Index == 1 {
|
||||
if ta, ok := ext1.Tuple.(*ssa.TypeAssert); ok && ta.Block() == b {
|
||||
// hack: relies upon instruction ordering.
|
||||
if ext0, ok := b.Instrs[n-3].(*ssa.Extract); ok {
|
||||
return ext0, ta.X, ta.AssertedType
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
157
vendor/golang.org/x/tools/go/ssa/ssautil/visit.go
generated
vendored
Normal file
157
vendor/golang.org/x/tools/go/ssa/ssautil/visit.go
generated
vendored
Normal file
@@ -0,0 +1,157 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssautil // import "golang.org/x/tools/go/ssa/ssautil"
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
|
||||
_ "unsafe" // for linkname hack
|
||||
)
|
||||
|
||||
// This file defines utilities for visiting the SSA representation of
|
||||
// a Program.
|
||||
//
|
||||
// TODO(adonovan): test coverage.
|
||||
|
||||
// AllFunctions finds and returns the set of functions potentially
|
||||
// needed by program prog, as determined by a simple linker-style
|
||||
// reachability algorithm starting from the members and method-sets of
|
||||
// each package. The result may include anonymous functions and
|
||||
// synthetic wrappers.
|
||||
//
|
||||
// Precondition: all packages are built.
|
||||
//
|
||||
// TODO(adonovan): this function is underspecified. It doesn't
|
||||
// actually work like a linker, which computes reachability from main
|
||||
// using something like go/callgraph/cha (without materializing the
|
||||
// call graph). In fact, it treats all public functions and all
|
||||
// methods of public non-parameterized types as roots, even though
|
||||
// they may be unreachable--but only in packages created from syntax.
|
||||
//
|
||||
// I think we should deprecate AllFunctions function in favor of two
|
||||
// clearly defined ones:
|
||||
//
|
||||
// 1. The first would efficiently compute CHA reachability from a set
|
||||
// of main packages, making it suitable for a whole-program
|
||||
// analysis context with InstantiateGenerics, in conjunction with
|
||||
// Program.Build.
|
||||
//
|
||||
// 2. The second would return only the set of functions corresponding
|
||||
// to source Func{Decl,Lit} syntax, like SrcFunctions in
|
||||
// go/analysis/passes/buildssa; this is suitable for
|
||||
// package-at-a-time (or handful of packages) context.
|
||||
// ssa.Package could easily expose it as a field.
|
||||
//
|
||||
// We could add them unexported for now and use them via the linkname hack.
|
||||
func AllFunctions(prog *ssa.Program) map[*ssa.Function]bool {
|
||||
seen := make(map[*ssa.Function]bool)
|
||||
|
||||
var function func(fn *ssa.Function)
|
||||
function = func(fn *ssa.Function) {
|
||||
if !seen[fn] {
|
||||
seen[fn] = true
|
||||
var buf [10]*ssa.Value // avoid alloc in common case
|
||||
for _, b := range fn.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
for _, op := range instr.Operands(buf[:0]) {
|
||||
if fn, ok := (*op).(*ssa.Function); ok {
|
||||
function(fn)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(adonovan): opt: provide a way to share a builder
|
||||
// across a sequence of MethodValue calls.
|
||||
|
||||
methodsOf := func(T types.Type) {
|
||||
if !types.IsInterface(T) {
|
||||
mset := prog.MethodSets.MethodSet(T)
|
||||
for method := range mset.Methods() {
|
||||
function(prog.MethodValue(method))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Historically, Program.RuntimeTypes used to include the type
|
||||
// of any exported member of a package loaded from syntax that
|
||||
// has a non-parameterized type, plus all types
|
||||
// reachable from that type using reflection, even though
|
||||
// these runtime types may not be required for them.
|
||||
//
|
||||
// Rather than break existing programs that rely on
|
||||
// AllFunctions visiting extra methods that are unreferenced
|
||||
// by IR and unreachable via reflection, we moved the logic
|
||||
// here, unprincipled though it is.
|
||||
// (See doc comment for better ideas.)
|
||||
//
|
||||
// Nonetheless, after the move, we no longer visit every
|
||||
// method of any type recursively reachable from T, only the
|
||||
// methods of T and *T themselves, and we only apply this to
|
||||
// named types T, and not to the type of every exported
|
||||
// package member.
|
||||
exportedTypeHack := func(t *ssa.Type) {
|
||||
if isSyntactic(t.Package()) &&
|
||||
ast.IsExported(t.Name()) &&
|
||||
!types.IsInterface(t.Type()) {
|
||||
// Consider only named types.
|
||||
// (Ignore aliases and unsafe.Pointer.)
|
||||
if named, ok := t.Type().(*types.Named); ok {
|
||||
if named.TypeParams() == nil {
|
||||
methodsOf(named) // T
|
||||
methodsOf(types.NewPointer(named)) // *T
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, pkg := range prog.AllPackages() {
|
||||
for _, mem := range pkg.Members {
|
||||
switch mem := mem.(type) {
|
||||
case *ssa.Function:
|
||||
// Visit all package-level declared functions.
|
||||
function(mem)
|
||||
|
||||
case *ssa.Type:
|
||||
exportedTypeHack(mem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Visit all methods of types for which runtime types were
|
||||
// materialized, as they are reachable through reflection.
|
||||
for _, T := range prog.RuntimeTypes() {
|
||||
methodsOf(T)
|
||||
}
|
||||
|
||||
return seen
|
||||
}
|
||||
|
||||
// MainPackages returns the subset of the specified packages
|
||||
// named "main" that define a main function.
|
||||
// The result may include synthetic "testmain" packages.
|
||||
func MainPackages(pkgs []*ssa.Package) []*ssa.Package {
|
||||
var mains []*ssa.Package
|
||||
for _, pkg := range pkgs {
|
||||
if pkg.Pkg.Name() == "main" && pkg.Func("main") != nil {
|
||||
mains = append(mains, pkg)
|
||||
}
|
||||
}
|
||||
return mains
|
||||
}
|
||||
|
||||
// TODO(adonovan): propose a principled API for this. One possibility
|
||||
// is a new field, Package.SrcFunctions []*Function, which would
|
||||
// contain the list of SrcFunctions described in point 2 of the
|
||||
// AllFunctions doc comment, or nil if the package is not from syntax.
|
||||
// But perhaps overloading nil vs empty slice is too subtle.
|
||||
//
|
||||
//go:linkname isSyntactic golang.org/x/tools/go/ssa.isSyntactic
|
||||
func isSyntactic(pkg *ssa.Package) bool
|
||||
567
vendor/golang.org/x/tools/go/ssa/subst.go
generated
vendored
Normal file
567
vendor/golang.org/x/tools/go/ssa/subst.go
generated
vendored
Normal file
@@ -0,0 +1,567 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssa
|
||||
|
||||
import (
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/go/types/typeutil"
|
||||
"golang.org/x/tools/internal/aliases"
|
||||
)
|
||||
|
||||
// subster defines a type substitution operation of a set of type parameters
|
||||
// to type parameter free replacement types. Substitution is done within
|
||||
// the context of a package-level function instantiation. *Named types
|
||||
// declared in the function are unique to the instantiation.
|
||||
//
|
||||
// For example, given a parameterized function F
|
||||
//
|
||||
// func F[S, T any]() any {
|
||||
// type X struct{ s S; next *X }
|
||||
// var p *X
|
||||
// return p
|
||||
// }
|
||||
//
|
||||
// calling the instantiation F[string, int]() returns an interface
|
||||
// value (*X[string,int], nil) where the underlying value of
|
||||
// X[string,int] is a struct{s string; next *X[string,int]}.
|
||||
//
|
||||
// A nil *subster is a valid, empty substitution map. It always acts as
|
||||
// the identity function. This allows for treating parameterized and
|
||||
// non-parameterized functions identically while compiling to ssa.
|
||||
//
|
||||
// Not concurrency-safe.
|
||||
//
|
||||
// Note: Some may find it helpful to think through some of the most
|
||||
// complex substitution cases using lambda calculus inspired notation.
|
||||
// subst.typ() solves evaluating a type expression E
|
||||
// within the body of a function Fn[m] with the type parameters m
|
||||
// once we have applied the type arguments N.
|
||||
// We can succinctly write this as a function application:
|
||||
//
|
||||
// ((λm. E) N)
|
||||
//
|
||||
// go/types does not provide this interface directly.
|
||||
// So what subster provides is a type substitution operation
|
||||
//
|
||||
// E[m:=N]
|
||||
type subster struct {
|
||||
replacements map[*types.TypeParam]types.Type // values should contain no type params
|
||||
cache map[types.Type]types.Type // cache of subst results
|
||||
origin *types.Func // types.Objects declared within this origin function are unique within this context
|
||||
ctxt *types.Context // speeds up repeated instantiations
|
||||
uniqueness typeutil.Map // determines the uniqueness of the instantiations within the function
|
||||
// TODO(taking): consider adding Pos
|
||||
}
|
||||
|
||||
// Returns a subster that replaces tparams[i] with targs[i]. Uses ctxt as a cache.
|
||||
// targs should not contain any types in tparams.
|
||||
// fn is the generic function for which we are substituting.
|
||||
func makeSubster(ctxt *types.Context, fn *types.Func, tparams *types.TypeParamList, targs []types.Type) *subster {
|
||||
assert(tparams.Len() == len(targs), "makeSubster argument count must match")
|
||||
|
||||
subst := &subster{
|
||||
replacements: make(map[*types.TypeParam]types.Type, tparams.Len()),
|
||||
cache: make(map[types.Type]types.Type),
|
||||
origin: fn.Origin(),
|
||||
ctxt: ctxt,
|
||||
}
|
||||
for i := 0; i < tparams.Len(); i++ {
|
||||
subst.replacements[tparams.At(i)] = targs[i]
|
||||
}
|
||||
return subst
|
||||
}
|
||||
|
||||
// typ returns the type of t with the type parameter tparams[i] substituted
|
||||
// for the type targs[i] where subst was created using tparams and targs.
|
||||
func (subst *subster) typ(t types.Type) (res types.Type) {
|
||||
if subst == nil {
|
||||
return t // A nil subst is type preserving.
|
||||
}
|
||||
if r, ok := subst.cache[t]; ok {
|
||||
return r
|
||||
}
|
||||
defer func() {
|
||||
subst.cache[t] = res
|
||||
}()
|
||||
|
||||
switch t := t.(type) {
|
||||
case *types.TypeParam:
|
||||
if r := subst.replacements[t]; r != nil {
|
||||
return r
|
||||
}
|
||||
return t
|
||||
|
||||
case *types.Basic:
|
||||
return t
|
||||
|
||||
case *types.Array:
|
||||
if r := subst.typ(t.Elem()); r != t.Elem() {
|
||||
return types.NewArray(r, t.Len())
|
||||
}
|
||||
return t
|
||||
|
||||
case *types.Slice:
|
||||
if r := subst.typ(t.Elem()); r != t.Elem() {
|
||||
return types.NewSlice(r)
|
||||
}
|
||||
return t
|
||||
|
||||
case *types.Pointer:
|
||||
if r := subst.typ(t.Elem()); r != t.Elem() {
|
||||
return types.NewPointer(r)
|
||||
}
|
||||
return t
|
||||
|
||||
case *types.Tuple:
|
||||
return subst.tuple(t)
|
||||
|
||||
case *types.Struct:
|
||||
return subst.struct_(t)
|
||||
|
||||
case *types.Map:
|
||||
key := subst.typ(t.Key())
|
||||
elem := subst.typ(t.Elem())
|
||||
if key != t.Key() || elem != t.Elem() {
|
||||
return types.NewMap(key, elem)
|
||||
}
|
||||
return t
|
||||
|
||||
case *types.Chan:
|
||||
if elem := subst.typ(t.Elem()); elem != t.Elem() {
|
||||
return types.NewChan(t.Dir(), elem)
|
||||
}
|
||||
return t
|
||||
|
||||
case *types.Signature:
|
||||
return subst.signature(t)
|
||||
|
||||
case *types.Union:
|
||||
return subst.union(t)
|
||||
|
||||
case *types.Interface:
|
||||
return subst.interface_(t)
|
||||
|
||||
case *types.Alias:
|
||||
return subst.alias(t)
|
||||
|
||||
case *types.Named:
|
||||
return subst.named(t)
|
||||
|
||||
case *opaqueType:
|
||||
return t // opaque types are never substituted
|
||||
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
|
||||
// types returns the result of {subst.typ(ts[i])}.
|
||||
func (subst *subster) types(ts []types.Type) []types.Type {
|
||||
res := make([]types.Type, len(ts))
|
||||
for i := range ts {
|
||||
res[i] = subst.typ(ts[i])
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (subst *subster) tuple(t *types.Tuple) *types.Tuple {
|
||||
if t != nil {
|
||||
if vars := subst.varlist(t); vars != nil {
|
||||
return types.NewTuple(vars...)
|
||||
}
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
type varlist interface {
|
||||
At(i int) *types.Var
|
||||
Len() int
|
||||
}
|
||||
|
||||
// fieldlist is an adapter for structs for the varlist interface.
|
||||
type fieldlist struct {
|
||||
str *types.Struct
|
||||
}
|
||||
|
||||
func (fl fieldlist) At(i int) *types.Var { return fl.str.Field(i) }
|
||||
func (fl fieldlist) Len() int { return fl.str.NumFields() }
|
||||
|
||||
func (subst *subster) struct_(t *types.Struct) *types.Struct {
|
||||
if t != nil {
|
||||
if fields := subst.varlist(fieldlist{t}); fields != nil {
|
||||
tags := make([]string, t.NumFields())
|
||||
for i, n := 0, t.NumFields(); i < n; i++ {
|
||||
tags[i] = t.Tag(i)
|
||||
}
|
||||
return types.NewStruct(fields, tags)
|
||||
}
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// varlist returns subst(in[i]) or return nils if subst(v[i]) == v[i] for all i.
|
||||
func (subst *subster) varlist(in varlist) []*types.Var {
|
||||
var out []*types.Var // nil => no updates
|
||||
for i, n := 0, in.Len(); i < n; i++ {
|
||||
v := in.At(i)
|
||||
w := subst.var_(v)
|
||||
if v != w && out == nil {
|
||||
out = make([]*types.Var, n)
|
||||
for j := 0; j < i; j++ {
|
||||
out[j] = in.At(j)
|
||||
}
|
||||
}
|
||||
if out != nil {
|
||||
out[i] = w
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (subst *subster) var_(v *types.Var) *types.Var {
|
||||
if v != nil {
|
||||
if typ := subst.typ(v.Type()); typ != v.Type() {
|
||||
if v.IsField() {
|
||||
return types.NewField(v.Pos(), v.Pkg(), v.Name(), typ, v.Embedded())
|
||||
}
|
||||
return types.NewParam(v.Pos(), v.Pkg(), v.Name(), typ)
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func (subst *subster) union(u *types.Union) *types.Union {
|
||||
var out []*types.Term // nil => no updates
|
||||
|
||||
for i, n := 0, u.Len(); i < n; i++ {
|
||||
t := u.Term(i)
|
||||
r := subst.typ(t.Type())
|
||||
if r != t.Type() && out == nil {
|
||||
out = make([]*types.Term, n)
|
||||
for j := 0; j < i; j++ {
|
||||
out[j] = u.Term(j)
|
||||
}
|
||||
}
|
||||
if out != nil {
|
||||
out[i] = types.NewTerm(t.Tilde(), r)
|
||||
}
|
||||
}
|
||||
|
||||
if out != nil {
|
||||
return types.NewUnion(out)
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
func (subst *subster) interface_(iface *types.Interface) *types.Interface {
|
||||
if iface == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// methods for the interface. Initially nil if there is no known change needed.
|
||||
// Signatures for the method where recv is nil. NewInterfaceType fills in the receivers.
|
||||
var methods []*types.Func
|
||||
initMethods := func(n int) { // copy first n explicit methods
|
||||
methods = make([]*types.Func, iface.NumExplicitMethods())
|
||||
for i := range n {
|
||||
f := iface.ExplicitMethod(i)
|
||||
norecv := changeRecv(f.Type().(*types.Signature), nil)
|
||||
methods[i] = types.NewFunc(f.Pos(), f.Pkg(), f.Name(), norecv)
|
||||
}
|
||||
}
|
||||
for i := 0; i < iface.NumExplicitMethods(); i++ {
|
||||
f := iface.ExplicitMethod(i)
|
||||
// On interfaces, we need to cycle break on anonymous interface types
|
||||
// being in a cycle with their signatures being in cycles with their receivers
|
||||
// that do not go through a Named.
|
||||
norecv := changeRecv(f.Type().(*types.Signature), nil)
|
||||
sig := subst.typ(norecv)
|
||||
if sig != norecv && methods == nil {
|
||||
initMethods(i)
|
||||
}
|
||||
if methods != nil {
|
||||
methods[i] = types.NewFunc(f.Pos(), f.Pkg(), f.Name(), sig.(*types.Signature))
|
||||
}
|
||||
}
|
||||
|
||||
var embeds []types.Type
|
||||
initEmbeds := func(n int) { // copy first n embedded types
|
||||
embeds = make([]types.Type, iface.NumEmbeddeds())
|
||||
for i := range n {
|
||||
embeds[i] = iface.EmbeddedType(i)
|
||||
}
|
||||
}
|
||||
for i := 0; i < iface.NumEmbeddeds(); i++ {
|
||||
e := iface.EmbeddedType(i)
|
||||
r := subst.typ(e)
|
||||
if e != r && embeds == nil {
|
||||
initEmbeds(i)
|
||||
}
|
||||
if embeds != nil {
|
||||
embeds[i] = r
|
||||
}
|
||||
}
|
||||
|
||||
if methods == nil && embeds == nil {
|
||||
return iface
|
||||
}
|
||||
if methods == nil {
|
||||
initMethods(iface.NumExplicitMethods())
|
||||
}
|
||||
if embeds == nil {
|
||||
initEmbeds(iface.NumEmbeddeds())
|
||||
}
|
||||
return types.NewInterfaceType(methods, embeds).Complete()
|
||||
}
|
||||
|
||||
func (subst *subster) alias(t *types.Alias) types.Type {
|
||||
// See subster.named. This follows the same strategy.
|
||||
tparams := aliases.TypeParams(t)
|
||||
targs := aliases.TypeArgs(t)
|
||||
tname := t.Obj()
|
||||
torigin := aliases.Origin(t)
|
||||
|
||||
if !declaredWithin(tname, subst.origin) {
|
||||
// t is declared outside of the function origin. So t is a package level type alias.
|
||||
if targs.Len() == 0 {
|
||||
// No type arguments so no instantiation needed.
|
||||
return t
|
||||
}
|
||||
|
||||
// Instantiate with the substituted type arguments.
|
||||
newTArgs := subst.typelist(targs)
|
||||
return subst.instantiate(torigin, newTArgs)
|
||||
}
|
||||
|
||||
if targs.Len() == 0 {
|
||||
// t is declared within the function origin and has no type arguments.
|
||||
//
|
||||
// Example: This corresponds to A or B in F, but not A[int]:
|
||||
//
|
||||
// func F[T any]() {
|
||||
// type A[S any] = struct{t T, s S}
|
||||
// type B = T
|
||||
// var x A[int]
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
// This is somewhat different than *Named as *Alias cannot be created recursively.
|
||||
|
||||
// Copy and substitute type params.
|
||||
var newTParams []*types.TypeParam
|
||||
for cur := range tparams.TypeParams() {
|
||||
cobj := cur.Obj()
|
||||
cname := types.NewTypeName(cobj.Pos(), cobj.Pkg(), cobj.Name(), nil)
|
||||
ntp := types.NewTypeParam(cname, nil)
|
||||
subst.cache[cur] = ntp // See the comment "Note: Subtle" in subster.named.
|
||||
newTParams = append(newTParams, ntp)
|
||||
}
|
||||
|
||||
// Substitute rhs.
|
||||
rhs := subst.typ(aliases.Rhs(t))
|
||||
|
||||
// Create the fresh alias.
|
||||
//
|
||||
// Until 1.27, the result of aliases.NewAlias(...).Type() cannot guarantee it is a *types.Alias.
|
||||
// However, as t is an *alias.Alias and t is well-typed, then aliases must have been enabled.
|
||||
// Follow this decision, and always enable aliases here.
|
||||
const enabled = true
|
||||
obj := aliases.NewAlias(enabled, tname.Pos(), tname.Pkg(), tname.Name(), rhs, newTParams)
|
||||
|
||||
// Substitute into all of the constraints after they are created.
|
||||
for i, ntp := range newTParams {
|
||||
bound := tparams.At(i).Constraint()
|
||||
ntp.SetConstraint(subst.typ(bound))
|
||||
}
|
||||
return obj.Type()
|
||||
}
|
||||
|
||||
// t is declared within the function origin and has type arguments.
|
||||
//
|
||||
// Example: This corresponds to A[int] in F. Cases A and B are handled above.
|
||||
// func F[T any]() {
|
||||
// type A[S any] = struct{t T, s S}
|
||||
// type B = T
|
||||
// var x A[int]
|
||||
// ...
|
||||
// }
|
||||
subOrigin := subst.typ(torigin)
|
||||
subTArgs := subst.typelist(targs)
|
||||
return subst.instantiate(subOrigin, subTArgs)
|
||||
}
|
||||
|
||||
func (subst *subster) named(t *types.Named) types.Type {
|
||||
// A Named type is a user defined type.
|
||||
// Ignoring generics, Named types are canonical: they are identical if
|
||||
// and only if they have the same defining symbol.
|
||||
// Generics complicate things, both if the type definition itself is
|
||||
// parameterized, and if the type is defined within the scope of a
|
||||
// parameterized function. In this case, two named types are identical if
|
||||
// and only if their identifying symbols are identical, and all type
|
||||
// arguments bindings in scope of the named type definition (including the
|
||||
// type parameters of the definition itself) are equivalent.
|
||||
//
|
||||
// Notably:
|
||||
// 1. For type definition type T[P1 any] struct{}, T[A] and T[B] are identical
|
||||
// only if A and B are identical.
|
||||
// 2. Inside the generic func Fn[m any]() any { type T struct{}; return T{} },
|
||||
// the result of Fn[A] and Fn[B] have identical type if and only if A and
|
||||
// B are identical.
|
||||
// 3. Both 1 and 2 could apply, such as in
|
||||
// func F[m any]() any { type T[x any] struct{}; return T{} }
|
||||
//
|
||||
// A subster replaces type parameters within a function scope, and therefore must
|
||||
// also replace free type parameters in the definitions of local types.
|
||||
//
|
||||
// Note: There are some detailed notes sprinkled throughout that borrow from
|
||||
// lambda calculus notation. These contain some over simplifying math.
|
||||
//
|
||||
// LC: One way to think about subster is that it is a way of evaluating
|
||||
// ((λm. E) N) as E[m:=N].
|
||||
// Each Named type t has an object *TypeName within a scope S that binds an
|
||||
// underlying type expression U. U can refer to symbols within S (+ S's ancestors).
|
||||
// Let x = t.TypeParams() and A = t.TypeArgs().
|
||||
// Each Named type t is then either:
|
||||
// U where len(x) == 0 && len(A) == 0
|
||||
// λx. U where len(x) != 0 && len(A) == 0
|
||||
// ((λx. U) A) where len(x) == len(A)
|
||||
// In each case, we will evaluate t[m:=N].
|
||||
tparams := t.TypeParams() // x
|
||||
targs := t.TypeArgs() // A
|
||||
|
||||
if !declaredWithin(t.Obj(), subst.origin) {
|
||||
// t is declared outside of Fn[m].
|
||||
//
|
||||
// In this case, we can skip substituting t.Underlying().
|
||||
// The underlying type cannot refer to the type parameters.
|
||||
//
|
||||
// LC: Let free(E) be the set of free type parameters in an expression E.
|
||||
// Then whenever m ∉ free(E), then E = E[m:=N].
|
||||
// t ∉ Scope(fn) so therefore m ∉ free(U) and m ∩ x = ∅.
|
||||
if targs.Len() == 0 {
|
||||
// t has no type arguments. So it does not need to be instantiated.
|
||||
//
|
||||
// This is the normal case in real Go code, where t is not parameterized,
|
||||
// declared at some package scope, and m is a TypeParam from a parameterized
|
||||
// function F[m] or method.
|
||||
//
|
||||
// LC: m ∉ free(A) lets us conclude m ∉ free(t). So t=t[m:=N].
|
||||
return t
|
||||
}
|
||||
|
||||
// t is declared outside of Fn[m] and has type arguments.
|
||||
// The type arguments may contain type parameters m so
|
||||
// substitute the type arguments, and instantiate the substituted
|
||||
// type arguments.
|
||||
//
|
||||
// LC: Evaluate this as ((λx. U) A') where A' = A[m := N].
|
||||
newTArgs := subst.typelist(targs)
|
||||
return subst.instantiate(t.Origin(), newTArgs)
|
||||
}
|
||||
|
||||
// t is declared within Fn[m].
|
||||
|
||||
if targs.Len() == 0 { // no type arguments?
|
||||
assert(t == t.Origin(), "local parameterized type abstraction must be an origin type")
|
||||
|
||||
// t has no type arguments.
|
||||
// The underlying type of t may contain the function's type parameters,
|
||||
// replace these, and create a new type.
|
||||
//
|
||||
// Subtle: We short circuit substitution and use a newly created type in
|
||||
// subst, i.e. cache[t]=fresh, to preemptively replace t with fresh
|
||||
// in recursive types during traversal. This both breaks infinite cycles
|
||||
// and allows for constructing types with the replacement applied in
|
||||
// subst.typ(U).
|
||||
//
|
||||
// A new copy of the Named and Typename (and constraints) per function
|
||||
// instantiation matches the semantics of Go, which treats all function
|
||||
// instantiations F[N] as having distinct local types.
|
||||
//
|
||||
// LC: x.Len()=0 can be thought of as a special case of λx. U.
|
||||
// LC: Evaluate (λx. U)[m:=N] as (λx'. U') where U'=U[x:=x',m:=N].
|
||||
tname := t.Obj()
|
||||
obj := types.NewTypeName(tname.Pos(), tname.Pkg(), tname.Name(), nil)
|
||||
fresh := types.NewNamed(obj, nil, nil)
|
||||
var newTParams []*types.TypeParam
|
||||
for cur := range tparams.TypeParams() {
|
||||
cobj := cur.Obj()
|
||||
cname := types.NewTypeName(cobj.Pos(), cobj.Pkg(), cobj.Name(), nil)
|
||||
ntp := types.NewTypeParam(cname, nil)
|
||||
subst.cache[cur] = ntp
|
||||
newTParams = append(newTParams, ntp)
|
||||
}
|
||||
fresh.SetTypeParams(newTParams)
|
||||
subst.cache[t] = fresh
|
||||
subst.cache[fresh] = fresh
|
||||
fresh.SetUnderlying(subst.typ(t.Underlying()))
|
||||
// Substitute into all of the constraints after they are created.
|
||||
for i, ntp := range newTParams {
|
||||
bound := tparams.At(i).Constraint()
|
||||
ntp.SetConstraint(subst.typ(bound))
|
||||
}
|
||||
return fresh
|
||||
}
|
||||
|
||||
// t is defined within Fn[m] and t has type arguments (an instantiation).
|
||||
// We reduce this to the two cases above:
|
||||
// (1) substitute the function's type parameters into t.Origin().
|
||||
// (2) substitute t's type arguments A and instantiate the updated t.Origin() with these.
|
||||
//
|
||||
// LC: Evaluate ((λx. U) A)[m:=N] as (t' A') where t' = (λx. U)[m:=N] and A'=A [m:=N]
|
||||
subOrigin := subst.typ(t.Origin())
|
||||
subTArgs := subst.typelist(targs)
|
||||
return subst.instantiate(subOrigin, subTArgs)
|
||||
}
|
||||
|
||||
func (subst *subster) instantiate(orig types.Type, targs []types.Type) types.Type {
|
||||
i, err := types.Instantiate(subst.ctxt, orig, targs, false)
|
||||
assert(err == nil, "failed to Instantiate named (Named or Alias) type")
|
||||
if c, _ := subst.uniqueness.At(i).(types.Type); c != nil {
|
||||
return c.(types.Type)
|
||||
}
|
||||
subst.uniqueness.Set(i, i)
|
||||
return i
|
||||
}
|
||||
|
||||
func (subst *subster) typelist(l *types.TypeList) []types.Type {
|
||||
res := make([]types.Type, l.Len())
|
||||
for i := 0; i < l.Len(); i++ {
|
||||
res[i] = subst.typ(l.At(i))
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (subst *subster) signature(t *types.Signature) types.Type {
|
||||
tparams := t.TypeParams()
|
||||
|
||||
// We are choosing not to support tparams.Len() > 0 until a need has been observed in practice.
|
||||
//
|
||||
// There are some known usages for types.Types coming from types.{Eval,CheckExpr}.
|
||||
// To support tparams.Len() > 0, we just need to do the following [pseudocode]:
|
||||
// targs := {subst.replacements[tparams[i]]]}; Instantiate(ctxt, t, targs, false)
|
||||
|
||||
assert(tparams.Len() == 0, "Substituting types.Signatures with generic functions are currently unsupported.")
|
||||
|
||||
// Either:
|
||||
// (1)non-generic function.
|
||||
// no type params to substitute
|
||||
// (2)generic method and recv needs to be substituted.
|
||||
|
||||
// Receivers can be either:
|
||||
// named
|
||||
// pointer to named
|
||||
// interface
|
||||
// nil
|
||||
// interface is the problematic case. We need to cycle break there!
|
||||
recv := subst.var_(t.Recv())
|
||||
params := subst.tuple(t.Params())
|
||||
results := subst.tuple(t.Results())
|
||||
if recv != t.Recv() || params != t.Params() || results != t.Results() {
|
||||
return types.NewSignatureType(recv, nil, nil, params, results, t.Variadic())
|
||||
}
|
||||
return t
|
||||
}
|
||||
103
vendor/golang.org/x/tools/go/ssa/task.go
generated
vendored
Normal file
103
vendor/golang.org/x/tools/go/ssa/task.go
generated
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
// Copyright 2024 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssa
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// Each task has two states: it is initially "active",
|
||||
// and transitions to "done".
|
||||
//
|
||||
// tasks form a directed graph. An edge from x to y (with y in x.edges)
|
||||
// indicates that the task x waits on the task y to be done.
|
||||
// Cycles are permitted.
|
||||
//
|
||||
// Calling x.wait() blocks the calling goroutine until task x,
|
||||
// and all the tasks transitively reachable from x are done.
|
||||
//
|
||||
// The nil *task is always considered done.
|
||||
type task struct {
|
||||
done chan unit // close when the task is done.
|
||||
edges map[*task]unit // set of predecessors of this task.
|
||||
transitive atomic.Bool // true once it is known all predecessors are done.
|
||||
}
|
||||
|
||||
func (x *task) isTransitivelyDone() bool { return x == nil || x.transitive.Load() }
|
||||
|
||||
// addEdge creates an edge from x to y, indicating that
|
||||
// x.wait() will not return before y is done.
|
||||
// All calls to x.addEdge(...) should happen before x.markDone().
|
||||
func (x *task) addEdge(y *task) {
|
||||
if x == y || y.isTransitivelyDone() {
|
||||
return // no work remaining
|
||||
}
|
||||
|
||||
// heuristic done check
|
||||
select {
|
||||
case <-x.done:
|
||||
panic("cannot add an edge to a done task")
|
||||
default:
|
||||
}
|
||||
|
||||
if x.edges == nil {
|
||||
x.edges = make(map[*task]unit)
|
||||
}
|
||||
x.edges[y] = unit{}
|
||||
}
|
||||
|
||||
// markDone changes the task's state to markDone.
|
||||
func (x *task) markDone() {
|
||||
if x != nil {
|
||||
close(x.done)
|
||||
}
|
||||
}
|
||||
|
||||
// wait blocks until x and all the tasks it can reach through edges are done.
|
||||
func (x *task) wait() {
|
||||
if x.isTransitivelyDone() {
|
||||
return // already known to be done. Skip allocations.
|
||||
}
|
||||
|
||||
// Use BFS to wait on u.done to be closed, for all u transitively
|
||||
// reachable from x via edges.
|
||||
//
|
||||
// This work can be repeated by multiple workers doing wait().
|
||||
//
|
||||
// Note: Tarjan's SCC algorithm is able to mark SCCs as transitively done
|
||||
// as soon as the SCC has been visited. This is theoretically faster, but is
|
||||
// a more complex algorithm. Until we have evidence, we need the more complex
|
||||
// algorithm, the simpler algorithm BFS is implemented.
|
||||
//
|
||||
// In Go 1.23, ssa/TestStdlib reaches <=3 *tasks per wait() in most schedules
|
||||
// On some schedules, there is a cycle building net/http and internal/trace/testtrace
|
||||
// due to slices functions.
|
||||
work := []*task{x}
|
||||
enqueued := map[*task]unit{x: {}}
|
||||
for i := 0; i < len(work); i++ {
|
||||
u := work[i]
|
||||
if u.isTransitivelyDone() { // already transitively done
|
||||
work[i] = nil
|
||||
continue
|
||||
}
|
||||
<-u.done // wait for u to be marked done.
|
||||
|
||||
for v := range u.edges {
|
||||
if _, ok := enqueued[v]; !ok {
|
||||
enqueued[v] = unit{}
|
||||
work = append(work, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// work is transitively closed over dependencies.
|
||||
// u in work is done (or transitively done and skipped).
|
||||
// u is transitively done.
|
||||
for _, u := range work {
|
||||
if u != nil {
|
||||
x.transitive.Store(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
179
vendor/golang.org/x/tools/go/ssa/typeset.go
generated
vendored
Normal file
179
vendor/golang.org/x/tools/go/ssa/typeset.go
generated
vendored
Normal file
@@ -0,0 +1,179 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssa
|
||||
|
||||
import (
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/internal/typeparams"
|
||||
)
|
||||
|
||||
// Utilities for dealing with type sets.
|
||||
|
||||
const debug = false
|
||||
|
||||
// typeset is an iterator over the (type/underlying type) pairs of the
|
||||
// specific type terms of the type set implied by t.
|
||||
// If t is a type parameter, the implied type set is the type set of t's constraint.
|
||||
// In that case, if there are no specific terms, typeset calls yield with (nil, nil).
|
||||
// If t is not a type parameter, the implied type set consists of just t.
|
||||
// In any case, typeset is guaranteed to call yield at least once.
|
||||
func typeset(typ types.Type, yield func(t, u types.Type) bool) {
|
||||
switch typ := types.Unalias(typ).(type) {
|
||||
case *types.TypeParam, *types.Interface:
|
||||
terms := termListOf(typ)
|
||||
if len(terms) == 0 {
|
||||
yield(nil, nil)
|
||||
return
|
||||
}
|
||||
for _, term := range terms {
|
||||
u := types.Unalias(term.Type())
|
||||
if !term.Tilde() {
|
||||
u = u.Underlying()
|
||||
}
|
||||
if debug {
|
||||
assert(types.Identical(u, u.Underlying()), "Unalias(x) == under(x) for ~x terms")
|
||||
}
|
||||
if !yield(term.Type(), u) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
default:
|
||||
yield(typ, typ.Underlying())
|
||||
}
|
||||
}
|
||||
|
||||
// termListOf returns the type set of typ as a normalized term set. Returns an empty set on an error.
|
||||
func termListOf(typ types.Type) []*types.Term {
|
||||
terms, err := typeparams.NormalTerms(typ)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return terms
|
||||
}
|
||||
|
||||
// typeSetIsEmpty returns true if a typeset is empty.
|
||||
func typeSetIsEmpty(typ types.Type) bool {
|
||||
var empty bool
|
||||
typeset(typ, func(t, _ types.Type) bool {
|
||||
empty = t == nil
|
||||
return false
|
||||
})
|
||||
return empty
|
||||
}
|
||||
|
||||
// isBytestring returns true if T has the same terms as interface{[]byte | string}.
|
||||
// These act like a core type for some operations: slice expressions, append and copy.
|
||||
//
|
||||
// See https://go.dev/ref/spec#Core_types for the details on bytestring.
|
||||
func isBytestring(T types.Type) bool {
|
||||
U := T.Underlying()
|
||||
if _, ok := U.(*types.Interface); !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
hasBytes, hasString := false, false
|
||||
ok := underIs(U, func(t types.Type) bool {
|
||||
switch {
|
||||
case isString(t):
|
||||
hasString = true
|
||||
return true
|
||||
case isByteSlice(t):
|
||||
hasBytes = true
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
})
|
||||
return ok && hasBytes && hasString
|
||||
}
|
||||
|
||||
// underIs calls f with the underlying types of the type terms
|
||||
// of the type set of typ and reports whether all calls to f returned true.
|
||||
// If there are no specific terms, underIs returns the result of f(nil).
|
||||
func underIs(typ types.Type, f func(types.Type) bool) bool {
|
||||
var ok bool
|
||||
typeset(typ, func(t, u types.Type) bool {
|
||||
ok = f(u)
|
||||
return ok
|
||||
})
|
||||
return ok
|
||||
}
|
||||
|
||||
// indexType returns the element type and index mode of a IndexExpr over a type.
|
||||
// It returns an invalid mode if the type is not indexable; this should never occur in a well-typed program.
|
||||
func indexType(typ types.Type) (types.Type, indexMode) {
|
||||
switch U := typ.Underlying().(type) {
|
||||
case *types.Array:
|
||||
return U.Elem(), ixArrVar
|
||||
case *types.Pointer:
|
||||
if arr, ok := U.Elem().Underlying().(*types.Array); ok {
|
||||
return arr.Elem(), ixVar
|
||||
}
|
||||
case *types.Slice:
|
||||
return U.Elem(), ixVar
|
||||
case *types.Map:
|
||||
return U.Elem(), ixMap
|
||||
case *types.Basic:
|
||||
return tByte, ixValue // must be a string
|
||||
case *types.Interface:
|
||||
var elem types.Type
|
||||
mode := ixInvalid
|
||||
typeset(typ, func(t, _ types.Type) bool {
|
||||
if t == nil {
|
||||
return false // empty set
|
||||
}
|
||||
e, m := indexType(t)
|
||||
if elem == nil {
|
||||
elem, mode = e, m
|
||||
}
|
||||
if debug && !types.Identical(elem, e) { // if type checked, just a sanity check
|
||||
mode = ixInvalid
|
||||
return false
|
||||
}
|
||||
// Update the mode to the most constrained address type.
|
||||
mode = mode.meet(m)
|
||||
return mode != ixInvalid
|
||||
})
|
||||
return elem, mode
|
||||
}
|
||||
return nil, ixInvalid
|
||||
}
|
||||
|
||||
// An indexMode specifies the (addressing) mode of an index operand.
|
||||
//
|
||||
// Addressing mode of an index operation is based on the set of
|
||||
// underlying types.
|
||||
// Hasse diagram of the indexMode meet semi-lattice:
|
||||
//
|
||||
// ixVar ixMap
|
||||
// | |
|
||||
// ixArrVar |
|
||||
// | |
|
||||
// ixValue |
|
||||
// \ /
|
||||
// ixInvalid
|
||||
type indexMode byte
|
||||
|
||||
const (
|
||||
ixInvalid indexMode = iota // index is invalid
|
||||
ixValue // index is a computed value (not addressable)
|
||||
ixArrVar // like ixVar, but index operand contains an array
|
||||
ixVar // index is an addressable variable
|
||||
ixMap // index is a map index expression (acts like a variable on lhs, commaok on rhs of an assignment)
|
||||
)
|
||||
|
||||
// meet is the address type that is constrained by both x and y.
|
||||
func (x indexMode) meet(y indexMode) indexMode {
|
||||
if (x == ixMap || y == ixMap) && x != y {
|
||||
return ixInvalid
|
||||
}
|
||||
// Use int representation and return min.
|
||||
if x < y {
|
||||
return y
|
||||
}
|
||||
return x
|
||||
}
|
||||
413
vendor/golang.org/x/tools/go/ssa/util.go
generated
vendored
Normal file
413
vendor/golang.org/x/tools/go/ssa/util.go
generated
vendored
Normal file
@@ -0,0 +1,413 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssa
|
||||
|
||||
// This file defines a number of miscellaneous utility functions.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
_ "unsafe" // for go:linkname hack
|
||||
|
||||
"golang.org/x/tools/go/types/typeutil"
|
||||
"golang.org/x/tools/internal/typeparams"
|
||||
"golang.org/x/tools/internal/typesinternal"
|
||||
)
|
||||
|
||||
type unit struct{}
|
||||
|
||||
//// Sanity checking utilities
|
||||
|
||||
// assert panics with the message msg if p is false.
|
||||
// Avoid combining with expensive string formatting.
|
||||
func assert(p bool, msg string) {
|
||||
if !p {
|
||||
panic(msg)
|
||||
}
|
||||
}
|
||||
|
||||
//// AST utilities
|
||||
|
||||
// isBlankIdent returns true iff e is an Ident with name "_".
|
||||
// They have no associated types.Object, and thus no type.
|
||||
func isBlankIdent(e ast.Expr) bool {
|
||||
id, ok := e.(*ast.Ident)
|
||||
return ok && id.Name == "_"
|
||||
}
|
||||
|
||||
//// Type utilities. Some of these belong in go/types.
|
||||
|
||||
// isNonTypeParamInterface reports whether t is an interface type but not a type parameter.
|
||||
func isNonTypeParamInterface(t types.Type) bool {
|
||||
return !typeparams.IsTypeParam(t) && types.IsInterface(t)
|
||||
}
|
||||
|
||||
// isBasic reports whether t is a basic type.
|
||||
// t is assumed to be an Underlying type (not Named or Alias).
|
||||
func isBasic(t types.Type) bool {
|
||||
_, ok := t.(*types.Basic)
|
||||
return ok
|
||||
}
|
||||
|
||||
// isString reports whether t is exactly a string type.
|
||||
// t is assumed to be an Underlying type (not Named or Alias).
|
||||
func isString(t types.Type) bool {
|
||||
basic, ok := t.(*types.Basic)
|
||||
return ok && basic.Info()&types.IsString != 0
|
||||
}
|
||||
|
||||
// isByteSlice reports whether t is of the form []~bytes.
|
||||
// t is assumed to be an Underlying type (not Named or Alias).
|
||||
func isByteSlice(t types.Type) bool {
|
||||
if b, ok := t.(*types.Slice); ok {
|
||||
e, _ := b.Elem().Underlying().(*types.Basic)
|
||||
return e != nil && e.Kind() == types.Byte
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isRuneSlice reports whether t is of the form []~runes.
|
||||
// t is assumed to be an Underlying type (not Named or Alias).
|
||||
func isRuneSlice(t types.Type) bool {
|
||||
if b, ok := t.(*types.Slice); ok {
|
||||
e, _ := b.Elem().Underlying().(*types.Basic)
|
||||
return e != nil && e.Kind() == types.Rune
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isBasicConvTypes returns true when the type set of a type
|
||||
// can be one side of a Convert operation. This is when:
|
||||
// - All are basic, []byte, or []rune.
|
||||
// - At least 1 is basic.
|
||||
// - At most 1 is []byte or []rune.
|
||||
func isBasicConvTypes(typ types.Type) bool {
|
||||
basics, cnt := 0, 0
|
||||
ok := underIs(typ, func(t types.Type) bool {
|
||||
cnt++
|
||||
if isBasic(t) {
|
||||
basics++
|
||||
return true
|
||||
}
|
||||
return isByteSlice(t) || isRuneSlice(t)
|
||||
})
|
||||
return ok && basics >= 1 && cnt-basics <= 1
|
||||
}
|
||||
|
||||
// isPointer reports whether t's underlying type is a pointer.
|
||||
func isPointer(t types.Type) bool {
|
||||
return is[*types.Pointer](t.Underlying())
|
||||
}
|
||||
|
||||
// isPointerCore reports whether t's core type is a pointer.
|
||||
//
|
||||
// (Most pointer manipulation is related to receivers, in which case
|
||||
// isPointer is appropriate. tecallers can use isPointer(t).
|
||||
func isPointerCore(t types.Type) bool {
|
||||
return is[*types.Pointer](typeparams.CoreType(t))
|
||||
}
|
||||
|
||||
func is[T any](x any) bool {
|
||||
_, ok := x.(T)
|
||||
return ok
|
||||
}
|
||||
|
||||
// recvType returns the receiver type of method obj.
|
||||
func recvType(obj *types.Func) types.Type {
|
||||
return obj.Signature().Recv().Type()
|
||||
}
|
||||
|
||||
// fieldOf returns the index'th field of the (core type of) a struct type;
|
||||
// otherwise returns nil.
|
||||
func fieldOf(typ types.Type, index int) *types.Var {
|
||||
if st, ok := typeparams.CoreType(typ).(*types.Struct); ok {
|
||||
if 0 <= index && index < st.NumFields() {
|
||||
return st.Field(index)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// isUntyped reports whether typ is the type of an untyped constant.
|
||||
func isUntyped(typ types.Type) bool {
|
||||
// No Underlying/Unalias: untyped constant types cannot be Named or Alias.
|
||||
b, ok := typ.(*types.Basic)
|
||||
return ok && b.Info()&types.IsUntyped != 0
|
||||
}
|
||||
|
||||
// declaredWithin reports whether an object is declared within a function.
|
||||
//
|
||||
// obj must not be a method or a field.
|
||||
func declaredWithin(obj types.Object, fn *types.Func) bool {
|
||||
if obj.Pos() != token.NoPos {
|
||||
return fn.Scope().Contains(obj.Pos()) // trust the positions if they exist.
|
||||
}
|
||||
if fn.Pkg() != obj.Pkg() {
|
||||
return false // fast path for different packages
|
||||
}
|
||||
|
||||
// Traverse Parent() scopes for fn.Scope().
|
||||
for p := obj.Parent(); p != nil; p = p.Parent() {
|
||||
if p == fn.Scope() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// logStack prints the formatted "start" message to stderr and
|
||||
// returns a closure that prints the corresponding "end" message.
|
||||
// Call using 'defer logStack(...)()' to show builder stack on panic.
|
||||
// Don't forget trailing parens!
|
||||
func logStack(format string, args ...any) func() {
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
io.WriteString(os.Stderr, msg)
|
||||
io.WriteString(os.Stderr, "\n")
|
||||
return func() {
|
||||
io.WriteString(os.Stderr, msg)
|
||||
io.WriteString(os.Stderr, " end\n")
|
||||
}
|
||||
}
|
||||
|
||||
// newVar creates a 'var' for use in a types.Tuple.
|
||||
func newVar(name string, typ types.Type) *types.Var {
|
||||
return types.NewParam(token.NoPos, nil, name, typ)
|
||||
}
|
||||
|
||||
// anonVar creates an anonymous 'var' for use in a types.Tuple.
|
||||
func anonVar(typ types.Type) *types.Var {
|
||||
return newVar("", typ)
|
||||
}
|
||||
|
||||
var lenResults = types.NewTuple(anonVar(tInt))
|
||||
|
||||
// makeLen returns the len builtin specialized to type func(T)int.
|
||||
func makeLen(T types.Type) *Builtin {
|
||||
lenParams := types.NewTuple(anonVar(T))
|
||||
return &Builtin{
|
||||
name: "len",
|
||||
sig: types.NewSignatureType(nil, nil, nil, lenParams, lenResults, false),
|
||||
}
|
||||
}
|
||||
|
||||
// receiverTypeArgs returns the type arguments to a method's receiver.
|
||||
// Returns an empty list if the receiver does not have type arguments.
|
||||
func receiverTypeArgs(method *types.Func) []types.Type {
|
||||
recv := method.Signature().Recv()
|
||||
_, named := typesinternal.ReceiverNamed(recv)
|
||||
if named == nil {
|
||||
return nil // recv is anonymous struct/interface
|
||||
}
|
||||
ts := named.TypeArgs()
|
||||
if ts.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
targs := make([]types.Type, ts.Len())
|
||||
for i := 0; i < ts.Len(); i++ {
|
||||
targs[i] = ts.At(i)
|
||||
}
|
||||
return targs
|
||||
}
|
||||
|
||||
// recvAsFirstArg takes a method signature and returns a function
|
||||
// signature with receiver as the first parameter.
|
||||
func recvAsFirstArg(sig *types.Signature) *types.Signature {
|
||||
params := make([]*types.Var, 0, 1+sig.Params().Len())
|
||||
params = append(params, sig.Recv())
|
||||
for v := range sig.Params().Variables() {
|
||||
params = append(params, v)
|
||||
}
|
||||
return types.NewSignatureType(nil, nil, nil, types.NewTuple(params...), sig.Results(), sig.Variadic())
|
||||
}
|
||||
|
||||
// instance returns whether an expression is a simple or qualified identifier
|
||||
// that is a generic instantiation.
|
||||
func instance(info *types.Info, expr ast.Expr) bool {
|
||||
// Compare the logic here against go/types.instantiatedIdent,
|
||||
// which also handles *IndexExpr and *IndexListExpr.
|
||||
var id *ast.Ident
|
||||
switch x := expr.(type) {
|
||||
case *ast.Ident:
|
||||
id = x
|
||||
case *ast.SelectorExpr:
|
||||
id = x.Sel
|
||||
default:
|
||||
return false
|
||||
}
|
||||
_, ok := info.Instances[id]
|
||||
return ok
|
||||
}
|
||||
|
||||
// instanceArgs returns the Instance[id].TypeArgs as a slice.
|
||||
func instanceArgs(info *types.Info, id *ast.Ident) []types.Type {
|
||||
targList := info.Instances[id].TypeArgs
|
||||
if targList == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
targs := make([]types.Type, targList.Len())
|
||||
for i, n := 0, targList.Len(); i < n; i++ {
|
||||
targs[i] = targList.At(i)
|
||||
}
|
||||
return targs
|
||||
}
|
||||
|
||||
// Mapping of a type T to a canonical instance C s.t. types.Identical(T, C).
|
||||
// Thread-safe.
|
||||
type canonizer struct {
|
||||
mu sync.Mutex
|
||||
types typeutil.Map // map from type to a canonical instance
|
||||
lists typeListMap // map from a list of types to a canonical instance
|
||||
}
|
||||
|
||||
func newCanonizer() *canonizer {
|
||||
c := &canonizer{}
|
||||
h := typeutil.MakeHasher()
|
||||
c.types.SetHasher(h)
|
||||
c.lists.hasher = h
|
||||
return c
|
||||
}
|
||||
|
||||
// List returns a canonical representative of a list of types.
|
||||
// Representative of the empty list is nil.
|
||||
func (c *canonizer) List(ts []types.Type) *typeList {
|
||||
if len(ts) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
unaliasAll := func(ts []types.Type) []types.Type {
|
||||
// Is there some top level alias?
|
||||
var found bool
|
||||
for _, t := range ts {
|
||||
if _, ok := t.(*types.Alias); ok {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return ts // no top level alias
|
||||
}
|
||||
|
||||
cp := make([]types.Type, len(ts)) // copy with top level aliases removed.
|
||||
for i, t := range ts {
|
||||
cp[i] = types.Unalias(t)
|
||||
}
|
||||
return cp
|
||||
}
|
||||
l := unaliasAll(ts)
|
||||
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
return c.lists.rep(l)
|
||||
}
|
||||
|
||||
// Type returns a canonical representative of type T.
|
||||
// Removes top-level aliases.
|
||||
//
|
||||
// For performance, reasons the canonical instance is order-dependent,
|
||||
// and may contain deeply nested aliases.
|
||||
func (c *canonizer) Type(T types.Type) types.Type {
|
||||
T = types.Unalias(T) // remove the top level alias.
|
||||
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if r := c.types.At(T); r != nil {
|
||||
return r.(types.Type)
|
||||
}
|
||||
c.types.Set(T, T)
|
||||
return T
|
||||
}
|
||||
|
||||
// A type for representing a canonized list of types.
|
||||
type typeList []types.Type
|
||||
|
||||
func (l *typeList) identical(ts []types.Type) bool {
|
||||
if l == nil {
|
||||
return len(ts) == 0
|
||||
}
|
||||
n := len(*l)
|
||||
if len(ts) != n {
|
||||
return false
|
||||
}
|
||||
for i, left := range *l {
|
||||
right := ts[i]
|
||||
if !types.Identical(left, right) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type typeListMap struct {
|
||||
hasher typeutil.Hasher
|
||||
buckets map[uint32][]*typeList
|
||||
}
|
||||
|
||||
// rep returns a canonical representative of a slice of types.
|
||||
func (m *typeListMap) rep(ts []types.Type) *typeList {
|
||||
if m == nil || len(ts) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if m.buckets == nil {
|
||||
m.buckets = make(map[uint32][]*typeList)
|
||||
}
|
||||
|
||||
h := m.hash(ts)
|
||||
bucket := m.buckets[h]
|
||||
for _, l := range bucket {
|
||||
if l.identical(ts) {
|
||||
return l
|
||||
}
|
||||
}
|
||||
|
||||
// not present. create a representative.
|
||||
cp := make(typeList, len(ts))
|
||||
copy(cp, ts)
|
||||
rep := &cp
|
||||
|
||||
m.buckets[h] = append(bucket, rep)
|
||||
return rep
|
||||
}
|
||||
|
||||
func (m *typeListMap) hash(ts []types.Type) uint32 {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
// Some smallish prime far away from typeutil.Hash.
|
||||
n := len(ts)
|
||||
h := uint32(13619) + 2*uint32(n)
|
||||
for i := range n {
|
||||
h += 3 * m.hasher.Hash(ts[i])
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
// instantiateMethod instantiates m with targs and returns a canonical representative for this method.
|
||||
func (canon *canonizer) instantiateMethod(m *types.Func, targs []types.Type, ctxt *types.Context) *types.Func {
|
||||
recv := recvType(m)
|
||||
if p, ok := types.Unalias(recv).(*types.Pointer); ok {
|
||||
recv = p.Elem()
|
||||
}
|
||||
named := types.Unalias(recv).(*types.Named)
|
||||
inst, err := types.Instantiate(ctxt, named.Origin(), targs, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
rep := canon.Type(inst)
|
||||
obj, _, _ := types.LookupFieldOrMethod(rep, true, m.Pkg(), m.Name())
|
||||
return obj.(*types.Func)
|
||||
}
|
||||
|
||||
// Exposed to ssautil using the linkname hack.
|
||||
//
|
||||
//go:linkname isSyntactic golang.org/x/tools/go/ssa.isSyntactic
|
||||
func isSyntactic(pkg *Package) bool { return pkg.syntax }
|
||||
346
vendor/golang.org/x/tools/go/ssa/wrappers.go
generated
vendored
Normal file
346
vendor/golang.org/x/tools/go/ssa/wrappers.go
generated
vendored
Normal file
@@ -0,0 +1,346 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssa
|
||||
|
||||
// This file defines synthesis of Functions that delegate to declared
|
||||
// methods; they come in three kinds:
|
||||
//
|
||||
// (1) wrappers: methods that wrap declared methods, performing
|
||||
// implicit pointer indirections and embedded field selections.
|
||||
//
|
||||
// (2) thunks: funcs that wrap declared methods. Like wrappers,
|
||||
// thunks perform indirections and field selections. The thunk's
|
||||
// first parameter is used as the receiver for the method call.
|
||||
//
|
||||
// (3) bounds: funcs that wrap declared methods. The bound's sole
|
||||
// free variable, supplied by a closure, is used as the receiver
|
||||
// for the method call. No indirections or field selections are
|
||||
// performed since they can be done before the call.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"go/token"
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/internal/typeparams"
|
||||
)
|
||||
|
||||
// -- wrappers -----------------------------------------------------------
|
||||
|
||||
// createWrapper returns a synthetic method that delegates to the
|
||||
// declared method denoted by meth.Obj(), first performing any
|
||||
// necessary pointer indirections or field selections implied by meth.
|
||||
//
|
||||
// The resulting method's receiver type is meth.Recv().
|
||||
//
|
||||
// This function is versatile but quite subtle! Consider the
|
||||
// following axes of variation when making changes:
|
||||
// - optional receiver indirection
|
||||
// - optional implicit field selections
|
||||
// - meth.Obj() may denote a concrete or an interface method
|
||||
// - the result may be a thunk or a wrapper.
|
||||
func createWrapper(prog *Program, sel *selection) *Function {
|
||||
obj := sel.obj.(*types.Func) // the declared function
|
||||
sig := sel.typ.(*types.Signature) // type of this wrapper
|
||||
|
||||
var recv *types.Var // wrapper's receiver or thunk's params[0]
|
||||
name := obj.Name()
|
||||
var description string
|
||||
if sel.kind == types.MethodExpr {
|
||||
name += "$thunk"
|
||||
description = "thunk"
|
||||
recv = sig.Params().At(0)
|
||||
} else {
|
||||
description = "wrapper"
|
||||
recv = sig.Recv()
|
||||
}
|
||||
|
||||
description = fmt.Sprintf("%s for %s", description, sel.obj)
|
||||
if prog.mode&LogSource != 0 {
|
||||
defer logStack("create %s to (%s)", description, recv.Type())()
|
||||
}
|
||||
/* method wrapper */
|
||||
return &Function{
|
||||
name: name,
|
||||
method: sel,
|
||||
object: obj,
|
||||
Signature: sig,
|
||||
Synthetic: description,
|
||||
Prog: prog,
|
||||
pos: obj.Pos(),
|
||||
// wrappers have no syntax
|
||||
build: (*builder).buildWrapper,
|
||||
syntax: nil,
|
||||
info: nil,
|
||||
goversion: "",
|
||||
}
|
||||
}
|
||||
|
||||
// buildWrapper builds fn.Body for a method wrapper.
|
||||
func (b *builder) buildWrapper(fn *Function) {
|
||||
var recv *types.Var // wrapper's receiver or thunk's params[0]
|
||||
var start int // first regular param
|
||||
if fn.method.kind == types.MethodExpr {
|
||||
recv = fn.Signature.Params().At(0)
|
||||
start = 1
|
||||
} else {
|
||||
recv = fn.Signature.Recv()
|
||||
}
|
||||
|
||||
fn.startBody()
|
||||
fn.addSpilledParam(recv)
|
||||
createParams(fn, start)
|
||||
|
||||
indices := fn.method.index
|
||||
|
||||
var v Value = fn.Locals[0] // spilled receiver
|
||||
if isPointer(fn.method.recv) {
|
||||
v = emitLoad(fn, v)
|
||||
|
||||
// For simple indirection wrappers, perform an informative nil-check:
|
||||
// "value method (T).f called using nil *T pointer"
|
||||
if len(indices) == 1 && !isPointer(recvType(fn.object)) {
|
||||
var c Call
|
||||
c.Call.Value = &Builtin{
|
||||
name: "ssa:wrapnilchk",
|
||||
sig: types.NewSignatureType(nil, nil, nil, types.NewTuple(anonVar(fn.method.recv), anonVar(tString), anonVar(tString)), types.NewTuple(anonVar(fn.method.recv)), false),
|
||||
}
|
||||
c.Call.Args = []Value{
|
||||
v,
|
||||
stringConst(typeparams.MustDeref(fn.method.recv).String()),
|
||||
stringConst(fn.method.obj.Name()),
|
||||
}
|
||||
c.setType(v.Type())
|
||||
v = fn.emit(&c)
|
||||
}
|
||||
}
|
||||
|
||||
// Invariant: v is a pointer, either
|
||||
// value of *A receiver param, or
|
||||
// address of A spilled receiver.
|
||||
|
||||
// We use pointer arithmetic (FieldAddr possibly followed by
|
||||
// Load) in preference to value extraction (Field possibly
|
||||
// preceded by Load).
|
||||
|
||||
v = emitImplicitSelections(fn, v, indices[:len(indices)-1], token.NoPos)
|
||||
|
||||
// Invariant: v is a pointer, either
|
||||
// value of implicit *C field, or
|
||||
// address of implicit C field.
|
||||
|
||||
var c Call
|
||||
if r := recvType(fn.object); !types.IsInterface(r) { // concrete method
|
||||
if !isPointer(r) {
|
||||
v = emitLoad(fn, v)
|
||||
}
|
||||
c.Call.Value = fn.Prog.objectMethod(fn.object, b)
|
||||
c.Call.Args = append(c.Call.Args, v)
|
||||
} else {
|
||||
c.Call.Method = fn.object
|
||||
c.Call.Value = emitLoad(fn, v) // interface (possibly a typeparam)
|
||||
}
|
||||
for _, arg := range fn.Params[1:] {
|
||||
c.Call.Args = append(c.Call.Args, arg)
|
||||
}
|
||||
emitTailCall(fn, &c)
|
||||
fn.finishBody()
|
||||
}
|
||||
|
||||
// createParams creates parameters for wrapper method fn based on its
|
||||
// Signature.Params, which do not include the receiver.
|
||||
// start is the index of the first regular parameter to use.
|
||||
func createParams(fn *Function, start int) {
|
||||
tparams := fn.Signature.Params()
|
||||
for i, n := start, tparams.Len(); i < n; i++ {
|
||||
fn.addParamVar(tparams.At(i))
|
||||
}
|
||||
}
|
||||
|
||||
// -- bounds -----------------------------------------------------------
|
||||
|
||||
// createBound returns a bound method wrapper (or "bound"), a synthetic
|
||||
// function that delegates to a concrete or interface method denoted
|
||||
// by obj. The resulting function has no receiver, but has one free
|
||||
// variable which will be used as the method's receiver in the
|
||||
// tail-call.
|
||||
//
|
||||
// Use MakeClosure with such a wrapper to construct a bound method
|
||||
// closure. e.g.:
|
||||
//
|
||||
// type T int or: type T interface { meth() }
|
||||
// func (t T) meth()
|
||||
// var t T
|
||||
// f := t.meth
|
||||
// f() // calls t.meth()
|
||||
//
|
||||
// f is a closure of a synthetic wrapper defined as if by:
|
||||
//
|
||||
// f := func() { return t.meth() }
|
||||
//
|
||||
// Unlike createWrapper, createBound need perform no indirection or field
|
||||
// selections because that can be done before the closure is
|
||||
// constructed.
|
||||
func createBound(prog *Program, obj *types.Func) *Function {
|
||||
description := fmt.Sprintf("bound method wrapper for %s", obj)
|
||||
if prog.mode&LogSource != 0 {
|
||||
defer logStack("%s", description)()
|
||||
}
|
||||
/* bound method wrapper */
|
||||
fn := &Function{
|
||||
name: obj.Name() + "$bound",
|
||||
object: obj,
|
||||
Signature: changeRecv(obj.Type().(*types.Signature), nil), // drop receiver
|
||||
Synthetic: description,
|
||||
Prog: prog,
|
||||
pos: obj.Pos(),
|
||||
// wrappers have no syntax
|
||||
build: (*builder).buildBound,
|
||||
syntax: nil,
|
||||
info: nil,
|
||||
goversion: "",
|
||||
}
|
||||
fn.FreeVars = []*FreeVar{{name: "recv", typ: recvType(obj), parent: fn}} // (cyclic)
|
||||
return fn
|
||||
}
|
||||
|
||||
// buildBound builds fn.Body for a bound method closure.
|
||||
func (b *builder) buildBound(fn *Function) {
|
||||
fn.startBody()
|
||||
createParams(fn, 0)
|
||||
var c Call
|
||||
|
||||
recv := fn.FreeVars[0]
|
||||
if !types.IsInterface(recvType(fn.object)) { // concrete
|
||||
c.Call.Value = fn.Prog.objectMethod(fn.object, b)
|
||||
c.Call.Args = []Value{recv}
|
||||
} else {
|
||||
c.Call.Method = fn.object
|
||||
c.Call.Value = recv // interface (possibly a typeparam)
|
||||
}
|
||||
for _, arg := range fn.Params {
|
||||
c.Call.Args = append(c.Call.Args, arg)
|
||||
}
|
||||
emitTailCall(fn, &c)
|
||||
fn.finishBody()
|
||||
}
|
||||
|
||||
// -- thunks -----------------------------------------------------------
|
||||
|
||||
// createThunk returns a thunk, a synthetic function that delegates to a
|
||||
// concrete or interface method denoted by sel.obj. The resulting
|
||||
// function has no receiver, but has an additional (first) regular
|
||||
// parameter.
|
||||
//
|
||||
// Precondition: sel.kind == types.MethodExpr.
|
||||
//
|
||||
// type T int or: type T interface { meth() }
|
||||
// func (t T) meth()
|
||||
// f := T.meth
|
||||
// var t T
|
||||
// f(t) // calls t.meth()
|
||||
//
|
||||
// f is a synthetic wrapper defined as if by:
|
||||
//
|
||||
// f := func(t T) { return t.meth() }
|
||||
func createThunk(prog *Program, sel *selection) *Function {
|
||||
if sel.kind != types.MethodExpr {
|
||||
panic(sel)
|
||||
}
|
||||
|
||||
fn := createWrapper(prog, sel)
|
||||
if fn.Signature.Recv() != nil {
|
||||
panic(fn) // unexpected receiver
|
||||
}
|
||||
|
||||
return fn
|
||||
}
|
||||
|
||||
func changeRecv(s *types.Signature, recv *types.Var) *types.Signature {
|
||||
return types.NewSignatureType(recv, nil, nil, s.Params(), s.Results(), s.Variadic())
|
||||
}
|
||||
|
||||
// A local version of *types.Selection.
|
||||
// Needed for some additional control, such as creating a MethodExpr for an instantiation.
|
||||
type selection struct {
|
||||
kind types.SelectionKind
|
||||
recv types.Type
|
||||
typ types.Type
|
||||
obj types.Object
|
||||
index []int
|
||||
indirect bool
|
||||
}
|
||||
|
||||
func toSelection(sel *types.Selection) *selection {
|
||||
return &selection{
|
||||
kind: sel.Kind(),
|
||||
recv: sel.Recv(),
|
||||
typ: sel.Type(),
|
||||
obj: sel.Obj(),
|
||||
index: sel.Index(),
|
||||
indirect: sel.Indirect(),
|
||||
}
|
||||
}
|
||||
|
||||
// -- instantiations --------------------------------------------------
|
||||
|
||||
// buildInstantiationWrapper builds the body of an instantiation
|
||||
// wrapper fn. The body calls the original generic function,
|
||||
// bracketed by ChangeType conversions on its arguments and results.
|
||||
func (b *builder) buildInstantiationWrapper(fn *Function) {
|
||||
orig := fn.topLevelOrigin
|
||||
sig := fn.Signature
|
||||
|
||||
fn.startBody()
|
||||
if sig.Recv() != nil {
|
||||
fn.addParamVar(sig.Recv())
|
||||
}
|
||||
createParams(fn, 0)
|
||||
|
||||
// Create body. Add a call to origin generic function
|
||||
// and make type changes between argument and parameters,
|
||||
// as well as return values.
|
||||
var c Call
|
||||
c.Call.Value = orig
|
||||
if res := orig.Signature.Results(); res.Len() == 1 {
|
||||
c.typ = res.At(0).Type()
|
||||
} else {
|
||||
c.typ = res
|
||||
}
|
||||
|
||||
// parameter of instance becomes an argument to the call
|
||||
// to the original generic function.
|
||||
argOffset := 0
|
||||
for i, arg := range fn.Params {
|
||||
var typ types.Type
|
||||
if i == 0 && sig.Recv() != nil {
|
||||
typ = orig.Signature.Recv().Type()
|
||||
argOffset = 1
|
||||
} else {
|
||||
typ = orig.Signature.Params().At(i - argOffset).Type()
|
||||
}
|
||||
c.Call.Args = append(c.Call.Args, emitTypeCoercion(fn, arg, typ))
|
||||
}
|
||||
|
||||
results := fn.emit(&c)
|
||||
var ret Return
|
||||
switch res := sig.Results(); res.Len() {
|
||||
case 0:
|
||||
// no results, do nothing.
|
||||
case 1:
|
||||
ret.Results = []Value{emitTypeCoercion(fn, results, res.At(0).Type())}
|
||||
default:
|
||||
for i := 0; i < sig.Results().Len(); i++ {
|
||||
v := emitExtract(fn, results, i)
|
||||
ret.Results = append(ret.Results, emitTypeCoercion(fn, v, res.At(i).Type()))
|
||||
}
|
||||
}
|
||||
|
||||
fn.emit(&ret)
|
||||
fn.currentBlock = nil
|
||||
|
||||
fn.finishBody()
|
||||
}
|
||||
820
vendor/golang.org/x/tools/go/types/objectpath/objectpath.go
generated
vendored
Normal file
820
vendor/golang.org/x/tools/go/types/objectpath/objectpath.go
generated
vendored
Normal file
@@ -0,0 +1,820 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package objectpath defines a naming scheme for types.Objects
|
||||
// (that is, named entities in Go programs) relative to their enclosing
|
||||
// package.
|
||||
//
|
||||
// Type-checker objects are canonical, so they are usually identified by
|
||||
// their address in memory (a pointer), but a pointer has meaning only
|
||||
// within one address space. By contrast, objectpath names allow the
|
||||
// identity of an object to be sent from one program to another,
|
||||
// establishing a correspondence between types.Object variables that are
|
||||
// distinct but logically equivalent.
|
||||
//
|
||||
// A single object may have multiple paths. In this example,
|
||||
//
|
||||
// type A struct{ X int }
|
||||
// type B A
|
||||
//
|
||||
// the field X has two paths due to its membership of both A and B.
|
||||
// The For(obj) function always returns one of these paths, arbitrarily
|
||||
// but consistently.
|
||||
package objectpath
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/types"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/internal/aliases"
|
||||
"golang.org/x/tools/internal/typesinternal"
|
||||
)
|
||||
|
||||
// TODO(adonovan): think about generic aliases.
|
||||
|
||||
// A Path is an opaque name that identifies a types.Object
|
||||
// relative to its package. Conceptually, the name consists of a
|
||||
// sequence of destructuring operations applied to the package scope
|
||||
// to obtain the original object.
|
||||
// The name does not include the package itself.
|
||||
type Path string
|
||||
|
||||
// Encoding
|
||||
//
|
||||
// An object path is a textual and (with training) human-readable encoding
|
||||
// of a sequence of destructuring operators, starting from a types.Package.
|
||||
// The sequences represent a path through the package/object/type graph.
|
||||
// We classify these operators by their type:
|
||||
//
|
||||
// PO package->object Package.Scope.Lookup
|
||||
// OT object->type Object.Type
|
||||
// TT type->type Type.{Elem,Key,{,{,Recv}Type}Params,Results,Underlying,Rhs} [EKPRUTrCa]
|
||||
// TO type->object Type.{At,Field,Method,Obj} [AFMO]
|
||||
//
|
||||
// All valid paths start with a package and end at an object
|
||||
// and thus may be defined by the regular language:
|
||||
//
|
||||
// objectpath = PO (OT TT* TO)*
|
||||
//
|
||||
// The concrete encoding follows directly:
|
||||
// - The only PO operator is Package.Scope.Lookup, which requires an identifier.
|
||||
// - The only OT operator is Object.Type,
|
||||
// which we encode as '.' because dot cannot appear in an identifier.
|
||||
// - The TT operators are encoded as [EKPRUTrCa];
|
||||
// two of these ({,Recv}TypeParams) require an integer operand,
|
||||
// which is encoded as a string of decimal digits.
|
||||
// - The TO operators are encoded as [AFMO];
|
||||
// three of these (At,Field,Method) require an integer operand,
|
||||
// which is encoded as a string of decimal digits.
|
||||
// These indices are stable across different representations
|
||||
// of the same package, even source and export data.
|
||||
// The indices used are implementation specific and may not correspond to
|
||||
// the argument to the go/types function.
|
||||
//
|
||||
// In the example below,
|
||||
//
|
||||
// package p
|
||||
//
|
||||
// type T interface {
|
||||
// f() (a string, b struct{ X int })
|
||||
// }
|
||||
//
|
||||
// field X has the path "T.UM0.RA1.F0",
|
||||
// representing the following sequence of operations:
|
||||
//
|
||||
// p.Lookup("T") T
|
||||
// .Type().Underlying().Method(0). f
|
||||
// .Type().Results().At(1) b
|
||||
// .Type().Field(0) X
|
||||
//
|
||||
// The encoding is not maximally compact---every R or P is
|
||||
// followed by an A, for example---but this simplifies the
|
||||
// encoder and decoder.
|
||||
const (
|
||||
// object->type operators
|
||||
opType = '.' // .Type() (Object)
|
||||
|
||||
// type->type operators
|
||||
opElem = 'E' // .Elem() (Pointer, Slice, Array, Chan, Map)
|
||||
opKey = 'K' // .Key() (Map)
|
||||
opParams = 'P' // .Params() (Signature)
|
||||
opResults = 'R' // .Results() (Signature)
|
||||
opUnderlying = 'U' // .Underlying() (Named)
|
||||
opTypeParam = 'T' // .TypeParams.At(i) (Named, Signature)
|
||||
opRecvTypeParam = 'r' // .RecvTypeParams.At(i) (Signature)
|
||||
opConstraint = 'C' // .Constraint() (TypeParam)
|
||||
opRhs = 'a' // .Rhs() (Alias)
|
||||
|
||||
// type->object operators
|
||||
opAt = 'A' // .At(i) (Tuple)
|
||||
opField = 'F' // .Field(i) (Struct)
|
||||
opMethod = 'M' // .Method(i) (Named or Interface; not Struct: "promoted" names are ignored)
|
||||
opObj = 'O' // .Obj() (Named, TypeParam)
|
||||
)
|
||||
|
||||
// For is equivalent to new(Encoder).For(obj).
|
||||
//
|
||||
// It may be more efficient to reuse a single Encoder across several calls.
|
||||
func For(obj types.Object) (Path, error) {
|
||||
return new(Encoder).For(obj)
|
||||
}
|
||||
|
||||
// An Encoder amortizes the cost of encoding the paths of multiple objects.
|
||||
// The zero value of an Encoder is ready to use.
|
||||
type Encoder struct {
|
||||
scopeMemo map[*types.Scope][]types.Object // memoization of scopeObjects
|
||||
}
|
||||
|
||||
// For returns the path to an object relative to its package,
|
||||
// or an error if the object is not accessible from the package's Scope.
|
||||
//
|
||||
// The For function guarantees to return a path only for the following objects:
|
||||
// - package-level types
|
||||
// - exported package-level non-types
|
||||
// - methods
|
||||
// - parameter and result variables
|
||||
// - struct fields
|
||||
// These objects are sufficient to define the API of their package.
|
||||
// The objects described by a package's export data are drawn from this set.
|
||||
//
|
||||
// The set of objects accessible from a package's Scope depends on
|
||||
// whether the package was produced by type-checking syntax, or
|
||||
// reading export data; the latter may have a smaller Scope since
|
||||
// export data trims objects that are not reachable from an exported
|
||||
// declaration. For example, the For function will return a path for
|
||||
// an exported method of an unexported type that is not reachable
|
||||
// from any public declaration; this path will cause the Object
|
||||
// function to fail if called on a package loaded from export data.
|
||||
// TODO(adonovan): is this a bug or feature? Should this package
|
||||
// compute accessibility in the same way?
|
||||
//
|
||||
// For does not return a path for predeclared names, imported package
|
||||
// names, local names, and unexported package-level names (except
|
||||
// types).
|
||||
//
|
||||
// Example: given this definition,
|
||||
//
|
||||
// package p
|
||||
//
|
||||
// type T interface {
|
||||
// f() (a string, b struct{ X int })
|
||||
// }
|
||||
//
|
||||
// For(X) would return a path that denotes the following sequence of operations:
|
||||
//
|
||||
// p.Scope().Lookup("T") (TypeName T)
|
||||
// .Type().Underlying().Method(0). (method Func f)
|
||||
// .Type().Results().At(1) (field Var b)
|
||||
// .Type().Field(0) (field Var X)
|
||||
//
|
||||
// where p is the package (*types.Package) to which X belongs.
|
||||
func (enc *Encoder) For(obj types.Object) (Path, error) {
|
||||
pkg := obj.Pkg()
|
||||
|
||||
// This table lists the cases of interest.
|
||||
//
|
||||
// Object Action
|
||||
// ------ ------
|
||||
// nil reject
|
||||
// builtin reject
|
||||
// pkgname reject
|
||||
// label reject
|
||||
// var
|
||||
// package-level accept
|
||||
// func param/result accept
|
||||
// local reject
|
||||
// struct field accept
|
||||
// const
|
||||
// package-level accept
|
||||
// local reject
|
||||
// func
|
||||
// package-level accept
|
||||
// init functions reject
|
||||
// concrete method accept
|
||||
// interface method accept
|
||||
// type
|
||||
// package-level accept
|
||||
// local reject
|
||||
//
|
||||
// The only accessible package-level objects are members of pkg itself.
|
||||
//
|
||||
// The cases are handled in four steps:
|
||||
//
|
||||
// 1. reject nil and builtin
|
||||
// 2. accept package-level objects
|
||||
// 3. reject obviously invalid objects
|
||||
// 4. search the API for the path to the param/result/field/method.
|
||||
|
||||
// 1. reference to nil or builtin?
|
||||
if pkg == nil {
|
||||
return "", fmt.Errorf("predeclared %s has no path", obj)
|
||||
}
|
||||
scope := pkg.Scope()
|
||||
|
||||
// 2. package-level object?
|
||||
if scope.Lookup(obj.Name()) == obj {
|
||||
// Only exported objects (and non-exported types) have a path.
|
||||
// Non-exported types may be referenced by other objects.
|
||||
if _, ok := obj.(*types.TypeName); !ok && !obj.Exported() {
|
||||
return "", fmt.Errorf("no path for non-exported %v", obj)
|
||||
}
|
||||
return Path(obj.Name()), nil
|
||||
}
|
||||
|
||||
// 3. Not a package-level object.
|
||||
// Reject obviously non-viable cases.
|
||||
switch obj := obj.(type) {
|
||||
case *types.TypeName:
|
||||
if _, ok := types.Unalias(obj.Type()).(*types.TypeParam); !ok {
|
||||
// With the exception of type parameters, only package-level type names
|
||||
// have a path.
|
||||
return "", fmt.Errorf("no path for %v", obj)
|
||||
}
|
||||
case *types.Const, // Only package-level constants have a path.
|
||||
*types.Label, // Labels are function-local.
|
||||
*types.PkgName: // PkgNames are file-local.
|
||||
return "", fmt.Errorf("no path for %v", obj)
|
||||
|
||||
case *types.Var:
|
||||
// Could be:
|
||||
// - a field (obj.IsField())
|
||||
// - a func parameter or result
|
||||
// - a local var.
|
||||
// Sadly there is no way to distinguish
|
||||
// a param/result from a local
|
||||
// so we must proceed to the find.
|
||||
|
||||
case *types.Func:
|
||||
// A func, if not package-level, must be a method.
|
||||
if recv := obj.Signature().Recv(); recv == nil {
|
||||
return "", fmt.Errorf("func is not a method: %v", obj)
|
||||
}
|
||||
|
||||
if path, ok := enc.concreteMethod(obj); ok {
|
||||
// Fast path for concrete methods that avoids looping over scope.
|
||||
return path, nil
|
||||
}
|
||||
|
||||
default:
|
||||
panic(obj)
|
||||
}
|
||||
|
||||
// 4. Search the API for the path to the var (field/param/result) or method.
|
||||
|
||||
// First inspect package-level named types.
|
||||
// In the presence of path aliases, these give
|
||||
// the best paths because non-types may
|
||||
// refer to types, but not the reverse.
|
||||
empty := make([]byte, 0, 48) // initial space
|
||||
objs := enc.scopeObjects(scope)
|
||||
for _, o := range objs {
|
||||
tname, ok := o.(*types.TypeName)
|
||||
if !ok {
|
||||
continue // handle non-types in second pass
|
||||
}
|
||||
|
||||
path := append(empty, o.Name()...)
|
||||
path = append(path, opType)
|
||||
|
||||
T := o.Type()
|
||||
if alias, ok := T.(*types.Alias); ok {
|
||||
if r := findTypeParam(obj, aliases.TypeParams(alias), path, opTypeParam); r != nil {
|
||||
return Path(r), nil
|
||||
}
|
||||
if r := find(obj, aliases.Rhs(alias), append(path, opRhs)); r != nil {
|
||||
return Path(r), nil
|
||||
}
|
||||
|
||||
} else if tname.IsAlias() {
|
||||
// legacy alias
|
||||
if r := find(obj, T, path); r != nil {
|
||||
return Path(r), nil
|
||||
}
|
||||
|
||||
} else if named, ok := T.(*types.Named); ok {
|
||||
// defined (named) type
|
||||
if r := findTypeParam(obj, named.TypeParams(), path, opTypeParam); r != nil {
|
||||
return Path(r), nil
|
||||
}
|
||||
if r := find(obj, named.Underlying(), append(path, opUnderlying)); r != nil {
|
||||
return Path(r), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Then inspect everything else:
|
||||
// non-types, and declared methods of defined types.
|
||||
for _, o := range objs {
|
||||
path := append(empty, o.Name()...)
|
||||
if _, ok := o.(*types.TypeName); !ok {
|
||||
if o.Exported() {
|
||||
// exported non-type (const, var, func)
|
||||
if r := find(obj, o.Type(), append(path, opType)); r != nil {
|
||||
return Path(r), nil
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Inspect declared methods of defined types.
|
||||
if T, ok := types.Unalias(o.Type()).(*types.Named); ok {
|
||||
path = append(path, opType)
|
||||
// The method index here is always with respect
|
||||
// to the underlying go/types data structures,
|
||||
// which ultimately derives from source order
|
||||
// and must be preserved by export data.
|
||||
for i := 0; i < T.NumMethods(); i++ {
|
||||
m := T.Method(i)
|
||||
path2 := appendOpArg(path, opMethod, i)
|
||||
if m == obj {
|
||||
return Path(path2), nil // found declared method
|
||||
}
|
||||
if r := find(obj, m.Type(), append(path2, opType)); r != nil {
|
||||
return Path(r), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("can't find path for %v in %s", obj, pkg.Path())
|
||||
}
|
||||
|
||||
func appendOpArg(path []byte, op byte, arg int) []byte {
|
||||
path = append(path, op)
|
||||
path = strconv.AppendInt(path, int64(arg), 10)
|
||||
return path
|
||||
}
|
||||
|
||||
// concreteMethod returns the path for meth, which must have a non-nil receiver.
|
||||
// The second return value indicates success and may be false if the method is
|
||||
// an interface method or if it is an instantiated method.
|
||||
//
|
||||
// This function is just an optimization that avoids the general scope walking
|
||||
// approach. You are expected to fall back to the general approach if this
|
||||
// function fails.
|
||||
func (enc *Encoder) concreteMethod(meth *types.Func) (Path, bool) {
|
||||
// Concrete methods can only be declared on package-scoped named types. For
|
||||
// that reason we can skip the expensive walk over the package scope: the
|
||||
// path will always be package -> named type -> method. We can trivially get
|
||||
// the type name from the receiver, and only have to look over the type's
|
||||
// methods to find the method index.
|
||||
//
|
||||
// Methods on generic types require special consideration, however. Consider
|
||||
// the following package:
|
||||
//
|
||||
// L1: type S[T any] struct{}
|
||||
// L2: func (recv S[A]) Foo() { recv.Bar() }
|
||||
// L3: func (recv S[B]) Bar() { }
|
||||
// L4: type Alias = S[int]
|
||||
// L5: func _[T any]() { var s S[int]; s.Foo() }
|
||||
//
|
||||
// The receivers of methods on generic types are instantiations. L2 and L3
|
||||
// instantiate S with the type-parameters A and B, which are scoped to the
|
||||
// respective methods. L4 and L5 each instantiate S with int. Each of these
|
||||
// instantiations has its own method set, full of methods (and thus objects)
|
||||
// with receivers whose types are the respective instantiations. In other
|
||||
// words, we have
|
||||
//
|
||||
// S[A].Foo, S[A].Bar
|
||||
// S[B].Foo, S[B].Bar
|
||||
// S[int].Foo, S[int].Bar
|
||||
//
|
||||
// We may thus be trying to produce object paths for any of these objects.
|
||||
//
|
||||
// S[A].Foo and S[B].Bar are the origin methods, and their paths are S.Foo
|
||||
// and S.Bar, which are the paths that this function naturally produces.
|
||||
//
|
||||
// S[A].Bar, S[B].Foo, and both methods on S[int] are instantiations that
|
||||
// don't correspond to the origin methods. For S[int], this is significant.
|
||||
// The most precise object path for S[int].Foo, for example, is Alias.Foo,
|
||||
// not S.Foo. Our function, however, would produce S.Foo, which would
|
||||
// resolve to a different object.
|
||||
//
|
||||
// For S[A].Bar and S[B].Foo it could be argued that S.Bar and S.Foo are
|
||||
// still the correct paths, since only the origin methods have meaningful
|
||||
// paths. But this is likely only true for trivial cases and has edge cases.
|
||||
// Since this function is only an optimization, we err on the side of giving
|
||||
// up, deferring to the slower but definitely correct algorithm. Most users
|
||||
// of objectpath will only be giving us origin methods, anyway, as referring
|
||||
// to instantiated methods is usually not useful.
|
||||
|
||||
if meth.Origin() != meth {
|
||||
return "", false
|
||||
}
|
||||
|
||||
_, named := typesinternal.ReceiverNamed(meth.Signature().Recv())
|
||||
if named == nil {
|
||||
return "", false
|
||||
}
|
||||
|
||||
if types.IsInterface(named) {
|
||||
// Named interfaces don't have to be package-scoped
|
||||
//
|
||||
// TODO(dominikh): opt: if scope.Lookup(name) == named, then we can apply this optimization to interface
|
||||
// methods, too, I think.
|
||||
return "", false
|
||||
}
|
||||
|
||||
// Preallocate space for the name, opType, opMethod, and some digits.
|
||||
name := named.Obj().Name()
|
||||
path := make([]byte, 0, len(name)+8)
|
||||
path = append(path, name...)
|
||||
path = append(path, opType)
|
||||
|
||||
// Method indices are w.r.t. the go/types data structures,
|
||||
// ultimately deriving from source order,
|
||||
// which is preserved by export data.
|
||||
for i := 0; i < named.NumMethods(); i++ {
|
||||
if named.Method(i) == meth {
|
||||
path = appendOpArg(path, opMethod, i)
|
||||
return Path(path), true
|
||||
}
|
||||
}
|
||||
|
||||
// Due to golang/go#59944, go/types fails to associate the receiver with
|
||||
// certain methods on cgo types.
|
||||
//
|
||||
// TODO(rfindley): replace this panic once golang/go#59944 is fixed in all Go
|
||||
// versions gopls supports.
|
||||
return "", false
|
||||
// panic(fmt.Sprintf("couldn't find method %s on type %s; methods: %#v", meth, named, enc.namedMethods(named)))
|
||||
}
|
||||
|
||||
// find finds obj within type T, returning the path to it, or nil if not found.
|
||||
//
|
||||
// The seen map is used to short circuit cycles through type parameters. If
|
||||
// nil, it will be allocated as necessary.
|
||||
//
|
||||
// The seenMethods map is used internally to short circuit cycles through
|
||||
// interface methods, such as occur in the following example:
|
||||
//
|
||||
// type I interface { f() interface{I} }
|
||||
//
|
||||
// See golang/go#68046 for details.
|
||||
func find(obj types.Object, T types.Type, path []byte) []byte {
|
||||
return (&finder{obj: obj}).find(T, path)
|
||||
}
|
||||
|
||||
// finder closes over search state for a call to find.
|
||||
type finder struct {
|
||||
obj types.Object // the sought object
|
||||
seenTParamNames map[*types.TypeName]bool // for cycle breaking through type parameters
|
||||
seenMethods map[*types.Func]bool // for cycle breaking through recursive interfaces
|
||||
}
|
||||
|
||||
func (f *finder) find(T types.Type, path []byte) []byte {
|
||||
switch T := T.(type) {
|
||||
case *types.Alias:
|
||||
return f.find(types.Unalias(T), path)
|
||||
case *types.Basic, *types.Named:
|
||||
// Named types belonging to pkg were handled already,
|
||||
// so T must belong to another package. No path.
|
||||
return nil
|
||||
case *types.Pointer:
|
||||
return f.find(T.Elem(), append(path, opElem))
|
||||
case *types.Slice:
|
||||
return f.find(T.Elem(), append(path, opElem))
|
||||
case *types.Array:
|
||||
return f.find(T.Elem(), append(path, opElem))
|
||||
case *types.Chan:
|
||||
return f.find(T.Elem(), append(path, opElem))
|
||||
case *types.Map:
|
||||
if r := f.find(T.Key(), append(path, opKey)); r != nil {
|
||||
return r
|
||||
}
|
||||
return f.find(T.Elem(), append(path, opElem))
|
||||
case *types.Signature:
|
||||
if r := f.findTypeParam(T.RecvTypeParams(), path, opRecvTypeParam); r != nil {
|
||||
return r
|
||||
}
|
||||
if r := f.findTypeParam(T.TypeParams(), path, opTypeParam); r != nil {
|
||||
return r
|
||||
}
|
||||
if r := f.find(T.Params(), append(path, opParams)); r != nil {
|
||||
return r
|
||||
}
|
||||
return f.find(T.Results(), append(path, opResults))
|
||||
case *types.Struct:
|
||||
for i := 0; i < T.NumFields(); i++ {
|
||||
fld := T.Field(i)
|
||||
path2 := appendOpArg(path, opField, i)
|
||||
if fld == f.obj {
|
||||
return path2 // found field var
|
||||
}
|
||||
if r := f.find(fld.Type(), append(path2, opType)); r != nil {
|
||||
return r
|
||||
}
|
||||
}
|
||||
return nil
|
||||
case *types.Tuple:
|
||||
for i := 0; i < T.Len(); i++ {
|
||||
v := T.At(i)
|
||||
path2 := appendOpArg(path, opAt, i)
|
||||
if v == f.obj {
|
||||
return path2 // found param/result var
|
||||
}
|
||||
if r := f.find(v.Type(), append(path2, opType)); r != nil {
|
||||
return r
|
||||
}
|
||||
}
|
||||
return nil
|
||||
case *types.Interface:
|
||||
for i := 0; i < T.NumMethods(); i++ {
|
||||
m := T.Method(i)
|
||||
if f.seenMethods[m] {
|
||||
return nil
|
||||
}
|
||||
path2 := appendOpArg(path, opMethod, i)
|
||||
if m == f.obj {
|
||||
return path2 // found interface method
|
||||
}
|
||||
if f.seenMethods == nil {
|
||||
f.seenMethods = make(map[*types.Func]bool)
|
||||
}
|
||||
f.seenMethods[m] = true
|
||||
if r := f.find(m.Type(), append(path2, opType)); r != nil {
|
||||
return r
|
||||
}
|
||||
}
|
||||
return nil
|
||||
case *types.TypeParam:
|
||||
name := T.Obj()
|
||||
if f.seenTParamNames[name] {
|
||||
return nil
|
||||
}
|
||||
if name == f.obj {
|
||||
return append(path, opObj)
|
||||
}
|
||||
if f.seenTParamNames == nil {
|
||||
f.seenTParamNames = make(map[*types.TypeName]bool)
|
||||
}
|
||||
f.seenTParamNames[name] = true
|
||||
if r := f.find(T.Constraint(), append(path, opConstraint)); r != nil {
|
||||
return r
|
||||
}
|
||||
return nil
|
||||
}
|
||||
panic(T)
|
||||
}
|
||||
|
||||
func findTypeParam(obj types.Object, list *types.TypeParamList, path []byte, op byte) []byte {
|
||||
return (&finder{obj: obj}).findTypeParam(list, path, op)
|
||||
}
|
||||
|
||||
func (f *finder) findTypeParam(list *types.TypeParamList, path []byte, op byte) []byte {
|
||||
for i := 0; i < list.Len(); i++ {
|
||||
tparam := list.At(i)
|
||||
path2 := appendOpArg(path, op, i)
|
||||
if r := f.find(tparam, path2); r != nil {
|
||||
return r
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Object returns the object denoted by path p within the package pkg.
|
||||
func Object(pkg *types.Package, p Path) (types.Object, error) {
|
||||
pathstr := string(p)
|
||||
if pathstr == "" {
|
||||
return nil, fmt.Errorf("empty path")
|
||||
}
|
||||
|
||||
var pkgobj, suffix string
|
||||
if dot := strings.IndexByte(pathstr, opType); dot < 0 {
|
||||
pkgobj = pathstr
|
||||
} else {
|
||||
pkgobj = pathstr[:dot]
|
||||
suffix = pathstr[dot:] // suffix starts with "."
|
||||
}
|
||||
|
||||
obj := pkg.Scope().Lookup(pkgobj)
|
||||
if obj == nil {
|
||||
return nil, fmt.Errorf("package %s does not contain %q", pkg.Path(), pkgobj)
|
||||
}
|
||||
|
||||
// abstraction of *types.{Pointer,Slice,Array,Chan,Map}
|
||||
type hasElem interface {
|
||||
Elem() types.Type
|
||||
}
|
||||
// abstraction of *types.{Named,Signature}
|
||||
type hasTypeParams interface {
|
||||
TypeParams() *types.TypeParamList
|
||||
}
|
||||
// abstraction of *types.{Alias,Named,TypeParam}
|
||||
type hasObj interface {
|
||||
Obj() *types.TypeName
|
||||
}
|
||||
|
||||
// The loop state is the pair (t, obj),
|
||||
// exactly one of which is non-nil, initially obj.
|
||||
// All suffixes start with '.' (the only object->type operation),
|
||||
// followed by optional type->type operations,
|
||||
// then a type->object operation.
|
||||
// The cycle then repeats.
|
||||
var t types.Type
|
||||
for suffix != "" {
|
||||
code := suffix[0]
|
||||
suffix = suffix[1:]
|
||||
|
||||
// Codes [AFMTr] have an integer operand.
|
||||
var index int
|
||||
switch code {
|
||||
case opAt, opField, opMethod, opTypeParam, opRecvTypeParam:
|
||||
rest := strings.TrimLeft(suffix, "0123456789")
|
||||
numerals := suffix[:len(suffix)-len(rest)]
|
||||
suffix = rest
|
||||
i, err := strconv.Atoi(numerals)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid path: bad numeric operand %q for code %q", numerals, code)
|
||||
}
|
||||
index = int(i)
|
||||
case opObj:
|
||||
// no operand
|
||||
default:
|
||||
// The suffix must end with a type->object operation.
|
||||
if suffix == "" {
|
||||
return nil, fmt.Errorf("invalid path: ends with %q, want [AFMO]", code)
|
||||
}
|
||||
}
|
||||
|
||||
if code == opType {
|
||||
if t != nil {
|
||||
return nil, fmt.Errorf("invalid path: unexpected %q in type context", opType)
|
||||
}
|
||||
t = obj.Type()
|
||||
obj = nil
|
||||
continue
|
||||
}
|
||||
|
||||
if t == nil {
|
||||
return nil, fmt.Errorf("invalid path: code %q in object context", code)
|
||||
}
|
||||
|
||||
// Inv: t != nil, obj == nil
|
||||
|
||||
t = types.Unalias(t)
|
||||
switch code {
|
||||
case opElem:
|
||||
hasElem, ok := t.(hasElem) // Pointer, Slice, Array, Chan, Map
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want pointer, slice, array, chan or map)", code, t, t)
|
||||
}
|
||||
t = hasElem.Elem()
|
||||
|
||||
case opKey:
|
||||
mapType, ok := t.(*types.Map)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want map)", code, t, t)
|
||||
}
|
||||
t = mapType.Key()
|
||||
|
||||
case opParams:
|
||||
sig, ok := t.(*types.Signature)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want signature)", code, t, t)
|
||||
}
|
||||
t = sig.Params()
|
||||
|
||||
case opResults:
|
||||
sig, ok := t.(*types.Signature)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want signature)", code, t, t)
|
||||
}
|
||||
t = sig.Results()
|
||||
|
||||
case opUnderlying:
|
||||
named, ok := t.(*types.Named)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want named)", code, t, t)
|
||||
}
|
||||
t = named.Underlying()
|
||||
|
||||
case opRhs:
|
||||
if alias, ok := t.(*types.Alias); ok {
|
||||
t = aliases.Rhs(alias)
|
||||
} else if false && aliases.Enabled() {
|
||||
// The Enabled check is too expensive, so for now we
|
||||
// simply assume that aliases are not enabled.
|
||||
//
|
||||
// Now that go1.24 is assured, we should be able to
|
||||
// replace this with "if true {", but it causes tests
|
||||
// to fail. TODO(adonovan): investigate.
|
||||
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want alias)", code, t, t)
|
||||
}
|
||||
|
||||
case opTypeParam:
|
||||
hasTypeParams, ok := t.(hasTypeParams) // Named, Signature
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want named or signature)", code, t, t)
|
||||
}
|
||||
tparams := hasTypeParams.TypeParams()
|
||||
if n := tparams.Len(); index >= n {
|
||||
return nil, fmt.Errorf("tuple index %d out of range [0-%d)", index, n)
|
||||
}
|
||||
t = tparams.At(index)
|
||||
|
||||
case opRecvTypeParam:
|
||||
sig, ok := t.(*types.Signature) // Signature
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want signature)", code, t, t)
|
||||
}
|
||||
rtparams := sig.RecvTypeParams()
|
||||
if n := rtparams.Len(); index >= n {
|
||||
return nil, fmt.Errorf("tuple index %d out of range [0-%d)", index, n)
|
||||
}
|
||||
t = rtparams.At(index)
|
||||
|
||||
case opConstraint:
|
||||
tparam, ok := t.(*types.TypeParam)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want type parameter)", code, t, t)
|
||||
}
|
||||
t = tparam.Constraint()
|
||||
|
||||
case opAt:
|
||||
tuple, ok := t.(*types.Tuple)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want tuple)", code, t, t)
|
||||
}
|
||||
if n := tuple.Len(); index >= n {
|
||||
return nil, fmt.Errorf("tuple index %d out of range [0-%d)", index, n)
|
||||
}
|
||||
obj = tuple.At(index)
|
||||
t = nil
|
||||
|
||||
case opField:
|
||||
structType, ok := t.(*types.Struct)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want struct)", code, t, t)
|
||||
}
|
||||
if n := structType.NumFields(); index >= n {
|
||||
return nil, fmt.Errorf("field index %d out of range [0-%d)", index, n)
|
||||
}
|
||||
obj = structType.Field(index)
|
||||
t = nil
|
||||
|
||||
case opMethod:
|
||||
switch t := t.(type) {
|
||||
case *types.Interface:
|
||||
if index >= t.NumMethods() {
|
||||
return nil, fmt.Errorf("method index %d out of range [0-%d)", index, t.NumMethods())
|
||||
}
|
||||
obj = t.Method(index) // Id-ordered
|
||||
|
||||
case *types.Named:
|
||||
if index >= t.NumMethods() {
|
||||
return nil, fmt.Errorf("method index %d out of range [0-%d)", index, t.NumMethods())
|
||||
}
|
||||
obj = t.Method(index)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want interface or named)", code, t, t)
|
||||
}
|
||||
t = nil
|
||||
|
||||
case opObj:
|
||||
hasObj, ok := t.(hasObj)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want named or type param)", code, t, t)
|
||||
}
|
||||
obj = hasObj.Obj()
|
||||
t = nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid path: unknown code %q", code)
|
||||
}
|
||||
}
|
||||
|
||||
if obj == nil {
|
||||
panic(p) // path does not end in an object-valued operator
|
||||
}
|
||||
|
||||
if obj.Pkg() != pkg {
|
||||
return nil, fmt.Errorf("path denotes %s, which belongs to a different package", obj)
|
||||
}
|
||||
|
||||
return obj, nil // success
|
||||
}
|
||||
|
||||
// scopeObjects is a memoization of scope objects.
|
||||
// Callers must not modify the result.
|
||||
func (enc *Encoder) scopeObjects(scope *types.Scope) []types.Object {
|
||||
m := enc.scopeMemo
|
||||
if m == nil {
|
||||
m = make(map[*types.Scope][]types.Object)
|
||||
enc.scopeMemo = m
|
||||
}
|
||||
objs, ok := m[scope]
|
||||
if !ok {
|
||||
names := scope.Names() // allocates and sorts
|
||||
objs = make([]types.Object, len(names))
|
||||
for i, name := range names {
|
||||
objs[i] = scope.Lookup(name)
|
||||
}
|
||||
m[scope] = objs
|
||||
}
|
||||
return objs
|
||||
}
|
||||
86
vendor/golang.org/x/tools/go/types/typeutil/callee.go
generated
vendored
Normal file
86
vendor/golang.org/x/tools/go/types/typeutil/callee.go
generated
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package typeutil
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/types"
|
||||
_ "unsafe" // for linkname
|
||||
)
|
||||
|
||||
// Callee returns the named target of a function call, if any:
|
||||
// a function, method, builtin, or variable.
|
||||
// It returns nil for a T(x) conversion.
|
||||
//
|
||||
// Functions and methods may potentially have type parameters.
|
||||
//
|
||||
// Note: for calls of instantiated functions and methods, Callee returns
|
||||
// the corresponding generic function or method on the generic type.
|
||||
func Callee(info *types.Info, call *ast.CallExpr) types.Object {
|
||||
obj := info.Uses[usedIdent(info, call.Fun)]
|
||||
if obj == nil {
|
||||
return nil
|
||||
}
|
||||
if _, ok := obj.(*types.TypeName); ok {
|
||||
return nil
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
// StaticCallee returns the target (function or method) of a static function
|
||||
// call, if any. It returns nil for calls to builtins.
|
||||
//
|
||||
// Note: for calls of instantiated functions and methods, StaticCallee returns
|
||||
// the corresponding generic function or method on the generic type.
|
||||
func StaticCallee(info *types.Info, call *ast.CallExpr) *types.Func {
|
||||
obj := info.Uses[usedIdent(info, call.Fun)]
|
||||
fn, _ := obj.(*types.Func)
|
||||
if fn == nil || interfaceMethod(fn) {
|
||||
return nil
|
||||
}
|
||||
return fn
|
||||
}
|
||||
|
||||
// usedIdent is the implementation of [internal/typesinternal.UsedIdent].
|
||||
// It returns the identifier associated with e.
|
||||
// See typesinternal.UsedIdent for a fuller description.
|
||||
// This function should live in typesinternal, but cannot because it would
|
||||
// create an import cycle.
|
||||
//
|
||||
//go:linkname usedIdent golang.org/x/tools/go/types/typeutil.usedIdent
|
||||
func usedIdent(info *types.Info, e ast.Expr) *ast.Ident {
|
||||
if info.Types == nil || info.Uses == nil {
|
||||
panic("one of info.Types or info.Uses is nil; both must be populated")
|
||||
}
|
||||
// Look through type instantiation if necessary.
|
||||
switch d := ast.Unparen(e).(type) {
|
||||
case *ast.IndexExpr:
|
||||
if info.Types[d.Index].IsType() {
|
||||
e = d.X
|
||||
}
|
||||
case *ast.IndexListExpr:
|
||||
e = d.X
|
||||
}
|
||||
|
||||
switch e := ast.Unparen(e).(type) {
|
||||
// info.Uses always has the object we want, even for selector expressions.
|
||||
// We don't need info.Selections.
|
||||
// See go/types/recording.go:recordSelection.
|
||||
case *ast.Ident:
|
||||
return e
|
||||
case *ast.SelectorExpr:
|
||||
return e.Sel
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// interfaceMethod reports whether its argument is a method of an interface.
|
||||
// This function should live in typesinternal, but cannot because it would create an import cycle.
|
||||
//
|
||||
//go:linkname interfaceMethod golang.org/x/tools/go/types/typeutil.interfaceMethod
|
||||
func interfaceMethod(f *types.Func) bool {
|
||||
recv := f.Signature().Recv()
|
||||
return recv != nil && types.IsInterface(recv.Type())
|
||||
}
|
||||
30
vendor/golang.org/x/tools/go/types/typeutil/imports.go
generated
vendored
Normal file
30
vendor/golang.org/x/tools/go/types/typeutil/imports.go
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package typeutil
|
||||
|
||||
import "go/types"
|
||||
|
||||
// Dependencies returns all dependencies of the specified packages.
|
||||
//
|
||||
// Dependent packages appear in topological order: if package P imports
|
||||
// package Q, Q appears earlier than P in the result.
|
||||
// The algorithm follows import statements in the order they
|
||||
// appear in the source code, so the result is a total order.
|
||||
func Dependencies(pkgs ...*types.Package) []*types.Package {
|
||||
var result []*types.Package
|
||||
seen := make(map[*types.Package]bool)
|
||||
var visit func(pkgs []*types.Package)
|
||||
visit = func(pkgs []*types.Package) {
|
||||
for _, p := range pkgs {
|
||||
if !seen[p] {
|
||||
seen[p] = true
|
||||
visit(p.Imports())
|
||||
result = append(result, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
visit(pkgs)
|
||||
return result
|
||||
}
|
||||
459
vendor/golang.org/x/tools/go/types/typeutil/map.go
generated
vendored
Normal file
459
vendor/golang.org/x/tools/go/types/typeutil/map.go
generated
vendored
Normal file
@@ -0,0 +1,459 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package typeutil defines various utilities for types, such as [Map],
|
||||
// a hash table that maps [types.Type] to any value.
|
||||
package typeutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/types"
|
||||
"hash/maphash"
|
||||
|
||||
"golang.org/x/tools/internal/typeparams"
|
||||
)
|
||||
|
||||
// Map is a hash-table-based mapping from types (types.Type) to
|
||||
// arbitrary values. The concrete types that implement
|
||||
// the Type interface are pointers. Since they are not canonicalized,
|
||||
// == cannot be used to check for equivalence, and thus we cannot
|
||||
// simply use a Go map.
|
||||
//
|
||||
// Just as with map[K]V, a nil *Map is a valid empty map.
|
||||
//
|
||||
// Read-only map operations ([Map.At], [Map.Len], and so on) may
|
||||
// safely be called concurrently.
|
||||
//
|
||||
// TODO(adonovan): deprecate in favor of https://go.dev/issues/69420
|
||||
// and 69559, if the latter proposals for a generic hash-map type and
|
||||
// a types.Hash function are accepted.
|
||||
type Map struct {
|
||||
table map[uint32][]entry // maps hash to bucket; entry.key==nil means unused
|
||||
length int // number of map entries
|
||||
}
|
||||
|
||||
// entry is an entry (key/value association) in a hash bucket.
|
||||
type entry struct {
|
||||
key types.Type
|
||||
value any
|
||||
}
|
||||
|
||||
// SetHasher has no effect.
|
||||
//
|
||||
// It is a relic of an optimization that is no longer profitable. Do
|
||||
// not use [Hasher], [MakeHasher], or [SetHasher] in new code.
|
||||
func (m *Map) SetHasher(Hasher) {}
|
||||
|
||||
// Delete removes the entry with the given key, if any.
|
||||
// It returns true if the entry was found.
|
||||
func (m *Map) Delete(key types.Type) bool {
|
||||
if m != nil && m.table != nil {
|
||||
hash := hash(key)
|
||||
bucket := m.table[hash]
|
||||
for i, e := range bucket {
|
||||
if e.key != nil && types.Identical(key, e.key) {
|
||||
// We can't compact the bucket as it
|
||||
// would disturb iterators.
|
||||
bucket[i] = entry{}
|
||||
m.length--
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// At returns the map entry for the given key.
|
||||
// The result is nil if the entry is not present.
|
||||
func (m *Map) At(key types.Type) any {
|
||||
if m != nil && m.table != nil {
|
||||
for _, e := range m.table[hash(key)] {
|
||||
if e.key != nil && types.Identical(key, e.key) {
|
||||
return e.value
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set sets the map entry for key to val,
|
||||
// and returns the previous entry, if any.
|
||||
func (m *Map) Set(key types.Type, value any) (prev any) {
|
||||
if m.table != nil {
|
||||
hash := hash(key)
|
||||
bucket := m.table[hash]
|
||||
var hole *entry
|
||||
for i, e := range bucket {
|
||||
if e.key == nil {
|
||||
hole = &bucket[i]
|
||||
} else if types.Identical(key, e.key) {
|
||||
prev = e.value
|
||||
bucket[i].value = value
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if hole != nil {
|
||||
*hole = entry{key, value} // overwrite deleted entry
|
||||
} else {
|
||||
m.table[hash] = append(bucket, entry{key, value})
|
||||
}
|
||||
} else {
|
||||
hash := hash(key)
|
||||
m.table = map[uint32][]entry{hash: {entry{key, value}}}
|
||||
}
|
||||
|
||||
m.length++
|
||||
return
|
||||
}
|
||||
|
||||
// Len returns the number of map entries.
|
||||
func (m *Map) Len() int {
|
||||
if m != nil {
|
||||
return m.length
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Iterate calls function f on each entry in the map in unspecified order.
|
||||
//
|
||||
// If f should mutate the map, Iterate provides the same guarantees as
|
||||
// Go maps: if f deletes a map entry that Iterate has not yet reached,
|
||||
// f will not be invoked for it, but if f inserts a map entry that
|
||||
// Iterate has not yet reached, whether or not f will be invoked for
|
||||
// it is unspecified.
|
||||
func (m *Map) Iterate(f func(key types.Type, value any)) {
|
||||
if m != nil {
|
||||
for _, bucket := range m.table {
|
||||
for _, e := range bucket {
|
||||
if e.key != nil {
|
||||
f(e.key, e.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Keys returns a new slice containing the set of map keys.
|
||||
// The order is unspecified.
|
||||
func (m *Map) Keys() []types.Type {
|
||||
keys := make([]types.Type, 0, m.Len())
|
||||
m.Iterate(func(key types.Type, _ any) {
|
||||
keys = append(keys, key)
|
||||
})
|
||||
return keys
|
||||
}
|
||||
|
||||
func (m *Map) toString(values bool) string {
|
||||
if m == nil {
|
||||
return "{}"
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprint(&buf, "{")
|
||||
sep := ""
|
||||
m.Iterate(func(key types.Type, value any) {
|
||||
fmt.Fprint(&buf, sep)
|
||||
sep = ", "
|
||||
fmt.Fprint(&buf, key)
|
||||
if values {
|
||||
fmt.Fprintf(&buf, ": %q", value)
|
||||
}
|
||||
})
|
||||
fmt.Fprint(&buf, "}")
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// String returns a string representation of the map's entries.
|
||||
// Values are printed using fmt.Sprintf("%v", v).
|
||||
// Order is unspecified.
|
||||
func (m *Map) String() string {
|
||||
return m.toString(true)
|
||||
}
|
||||
|
||||
// KeysString returns a string representation of the map's key set.
|
||||
// Order is unspecified.
|
||||
func (m *Map) KeysString() string {
|
||||
return m.toString(false)
|
||||
}
|
||||
|
||||
// -- Hasher --
|
||||
|
||||
// hash returns the hash of type t.
|
||||
// TODO(adonovan): replace by types.Hash when Go proposal #69420 is accepted.
|
||||
func hash(t types.Type) uint32 {
|
||||
return theHasher.Hash(t)
|
||||
}
|
||||
|
||||
// A Hasher provides a [Hasher.Hash] method to map a type to its hash value.
|
||||
// Hashers are stateless, and all are equivalent.
|
||||
type Hasher struct{}
|
||||
|
||||
var theHasher Hasher
|
||||
|
||||
// MakeHasher returns Hasher{}.
|
||||
// Hashers are stateless; all are equivalent.
|
||||
func MakeHasher() Hasher { return theHasher }
|
||||
|
||||
// Hash computes a hash value for the given type t such that
|
||||
// Identical(t, t') => Hash(t) == Hash(t').
|
||||
func (h Hasher) Hash(t types.Type) uint32 {
|
||||
return hasher{inGenericSig: false}.hash(t)
|
||||
}
|
||||
|
||||
// hasher holds the state of a single Hash traversal: whether we are
|
||||
// inside the signature of a generic function; this is used to
|
||||
// optimize [hasher.hashTypeParam].
|
||||
type hasher struct{ inGenericSig bool }
|
||||
|
||||
// hashString computes the Fowler–Noll–Vo hash of s.
|
||||
func hashString(s string) uint32 {
|
||||
var h uint32
|
||||
for i := 0; i < len(s); i++ {
|
||||
h ^= uint32(s[i])
|
||||
h *= 16777619
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
// hash computes the hash of t.
|
||||
func (h hasher) hash(t types.Type) uint32 {
|
||||
// See Identical for rationale.
|
||||
switch t := t.(type) {
|
||||
case *types.Basic:
|
||||
return uint32(t.Kind())
|
||||
|
||||
case *types.Alias:
|
||||
return h.hash(types.Unalias(t))
|
||||
|
||||
case *types.Array:
|
||||
return 9043 + 2*uint32(t.Len()) + 3*h.hash(t.Elem())
|
||||
|
||||
case *types.Slice:
|
||||
return 9049 + 2*h.hash(t.Elem())
|
||||
|
||||
case *types.Struct:
|
||||
var hash uint32 = 9059
|
||||
for i, n := 0, t.NumFields(); i < n; i++ {
|
||||
f := t.Field(i)
|
||||
if f.Anonymous() {
|
||||
hash += 8861
|
||||
}
|
||||
hash += hashString(t.Tag(i))
|
||||
hash += hashString(f.Name()) // (ignore f.Pkg)
|
||||
hash += h.hash(f.Type())
|
||||
}
|
||||
return hash
|
||||
|
||||
case *types.Pointer:
|
||||
return 9067 + 2*h.hash(t.Elem())
|
||||
|
||||
case *types.Signature:
|
||||
var hash uint32 = 9091
|
||||
if t.Variadic() {
|
||||
hash *= 8863
|
||||
}
|
||||
|
||||
tparams := t.TypeParams()
|
||||
if n := tparams.Len(); n > 0 {
|
||||
h.inGenericSig = true // affects constraints, params, and results
|
||||
|
||||
for i := range n {
|
||||
tparam := tparams.At(i)
|
||||
hash += 7 * h.hash(tparam.Constraint())
|
||||
}
|
||||
}
|
||||
|
||||
return hash + 3*h.hashTuple(t.Params()) + 5*h.hashTuple(t.Results())
|
||||
|
||||
case *types.Union:
|
||||
return h.hashUnion(t)
|
||||
|
||||
case *types.Interface:
|
||||
// Interfaces are identical if they have the same set of methods, with
|
||||
// identical names and types, and they have the same set of type
|
||||
// restrictions. See go/types.identical for more details.
|
||||
var hash uint32 = 9103
|
||||
|
||||
// Hash methods.
|
||||
for i, n := 0, t.NumMethods(); i < n; i++ {
|
||||
// Method order is not significant.
|
||||
// Ignore m.Pkg().
|
||||
m := t.Method(i)
|
||||
// Use shallow hash on method signature to
|
||||
// avoid anonymous interface cycles.
|
||||
hash += 3*hashString(m.Name()) + 5*h.shallowHash(m.Type())
|
||||
}
|
||||
|
||||
// Hash type restrictions.
|
||||
terms, err := typeparams.InterfaceTermSet(t)
|
||||
// if err != nil t has invalid type restrictions.
|
||||
if err == nil {
|
||||
hash += h.hashTermSet(terms)
|
||||
}
|
||||
|
||||
return hash
|
||||
|
||||
case *types.Map:
|
||||
return 9109 + 2*h.hash(t.Key()) + 3*h.hash(t.Elem())
|
||||
|
||||
case *types.Chan:
|
||||
return 9127 + 2*uint32(t.Dir()) + 3*h.hash(t.Elem())
|
||||
|
||||
case *types.Named:
|
||||
hash := h.hashTypeName(t.Obj())
|
||||
targs := t.TypeArgs()
|
||||
for targ := range targs.Types() {
|
||||
hash += 2 * h.hash(targ)
|
||||
}
|
||||
return hash
|
||||
|
||||
case *types.TypeParam:
|
||||
return h.hashTypeParam(t)
|
||||
|
||||
case *types.Tuple:
|
||||
return h.hashTuple(t)
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("%T: %v", t, t))
|
||||
}
|
||||
|
||||
func (h hasher) hashTuple(tuple *types.Tuple) uint32 {
|
||||
// See go/types.identicalTypes for rationale.
|
||||
n := tuple.Len()
|
||||
hash := 9137 + 2*uint32(n)
|
||||
for i := range n {
|
||||
hash += 3 * h.hash(tuple.At(i).Type())
|
||||
}
|
||||
return hash
|
||||
}
|
||||
|
||||
func (h hasher) hashUnion(t *types.Union) uint32 {
|
||||
// Hash type restrictions.
|
||||
terms, err := typeparams.UnionTermSet(t)
|
||||
// if err != nil t has invalid type restrictions. Fall back on a non-zero
|
||||
// hash.
|
||||
if err != nil {
|
||||
return 9151
|
||||
}
|
||||
return h.hashTermSet(terms)
|
||||
}
|
||||
|
||||
func (h hasher) hashTermSet(terms []*types.Term) uint32 {
|
||||
hash := 9157 + 2*uint32(len(terms))
|
||||
for _, term := range terms {
|
||||
// term order is not significant.
|
||||
termHash := h.hash(term.Type())
|
||||
if term.Tilde() {
|
||||
termHash *= 9161
|
||||
}
|
||||
hash += 3 * termHash
|
||||
}
|
||||
return hash
|
||||
}
|
||||
|
||||
// hashTypeParam returns the hash of a type parameter.
|
||||
func (h hasher) hashTypeParam(t *types.TypeParam) uint32 {
|
||||
// Within the signature of a generic function, TypeParams are
|
||||
// identical if they have the same index and constraint, so we
|
||||
// hash them based on index.
|
||||
//
|
||||
// When we are outside a generic function, free TypeParams are
|
||||
// identical iff they are the same object, so we can use a
|
||||
// more discriminating hash consistent with object identity.
|
||||
// This optimization saves [Map] about 4% when hashing all the
|
||||
// types.Info.Types in the forward closure of net/http.
|
||||
if !h.inGenericSig {
|
||||
// Optimization: outside a generic function signature,
|
||||
// use a more discrimating hash consistent with object identity.
|
||||
return h.hashTypeName(t.Obj())
|
||||
}
|
||||
return 9173 + 3*uint32(t.Index())
|
||||
}
|
||||
|
||||
var theSeed = maphash.MakeSeed()
|
||||
|
||||
// hashTypeName hashes the pointer of tname.
|
||||
func (hasher) hashTypeName(tname *types.TypeName) uint32 {
|
||||
// Since types.Identical uses == to compare TypeNames,
|
||||
// the Hash function uses maphash.Comparable.
|
||||
hash := maphash.Comparable(theSeed, tname)
|
||||
return uint32(hash ^ (hash >> 32))
|
||||
}
|
||||
|
||||
// shallowHash computes a hash of t without looking at any of its
|
||||
// element Types, to avoid potential anonymous cycles in the types of
|
||||
// interface methods.
|
||||
//
|
||||
// When an unnamed non-empty interface type appears anywhere among the
|
||||
// arguments or results of an interface method, there is a potential
|
||||
// for endless recursion. Consider:
|
||||
//
|
||||
// type X interface { m() []*interface { X } }
|
||||
//
|
||||
// The problem is that the Methods of the interface in m's result type
|
||||
// include m itself; there is no mention of the named type X that
|
||||
// might help us break the cycle.
|
||||
// (See comment in go/types.identical, case *Interface, for more.)
|
||||
func (h hasher) shallowHash(t types.Type) uint32 {
|
||||
// t is the type of an interface method (Signature),
|
||||
// its params or results (Tuples), or their immediate
|
||||
// elements (mostly Slice, Pointer, Basic, Named),
|
||||
// so there's no need to optimize anything else.
|
||||
switch t := t.(type) {
|
||||
case *types.Alias:
|
||||
return h.shallowHash(types.Unalias(t))
|
||||
|
||||
case *types.Signature:
|
||||
var hash uint32 = 604171
|
||||
if t.Variadic() {
|
||||
hash *= 971767
|
||||
}
|
||||
// The Signature/Tuple recursion is always finite
|
||||
// and invariably shallow.
|
||||
return hash + 1062599*h.shallowHash(t.Params()) + 1282529*h.shallowHash(t.Results())
|
||||
|
||||
case *types.Tuple:
|
||||
n := t.Len()
|
||||
hash := 9137 + 2*uint32(n)
|
||||
for i := range n {
|
||||
hash += 53471161 * h.shallowHash(t.At(i).Type())
|
||||
}
|
||||
return hash
|
||||
|
||||
case *types.Basic:
|
||||
return 45212177 * uint32(t.Kind())
|
||||
|
||||
case *types.Array:
|
||||
return 1524181 + 2*uint32(t.Len())
|
||||
|
||||
case *types.Slice:
|
||||
return 2690201
|
||||
|
||||
case *types.Struct:
|
||||
return 3326489
|
||||
|
||||
case *types.Pointer:
|
||||
return 4393139
|
||||
|
||||
case *types.Union:
|
||||
return 562448657
|
||||
|
||||
case *types.Interface:
|
||||
return 2124679 // no recursion here
|
||||
|
||||
case *types.Map:
|
||||
return 9109
|
||||
|
||||
case *types.Chan:
|
||||
return 9127
|
||||
|
||||
case *types.Named:
|
||||
return h.hashTypeName(t.Obj())
|
||||
|
||||
case *types.TypeParam:
|
||||
return h.hashTypeParam(t)
|
||||
}
|
||||
panic(fmt.Sprintf("shallowHash: %T: %v", t, t))
|
||||
}
|
||||
71
vendor/golang.org/x/tools/go/types/typeutil/methodsetcache.go
generated
vendored
Normal file
71
vendor/golang.org/x/tools/go/types/typeutil/methodsetcache.go
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This file implements a cache of method sets.
|
||||
|
||||
package typeutil
|
||||
|
||||
import (
|
||||
"go/types"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// A MethodSetCache records the method set of each type T for which
|
||||
// MethodSet(T) is called so that repeat queries are fast.
|
||||
// The zero value is a ready-to-use cache instance.
|
||||
type MethodSetCache struct {
|
||||
mu sync.Mutex
|
||||
named map[*types.Named]struct{ value, pointer *types.MethodSet } // method sets for named N and *N
|
||||
others map[types.Type]*types.MethodSet // all other types
|
||||
}
|
||||
|
||||
// MethodSet returns the method set of type T. It is thread-safe.
|
||||
//
|
||||
// If cache is nil, this function is equivalent to types.NewMethodSet(T).
|
||||
// Utility functions can thus expose an optional *MethodSetCache
|
||||
// parameter to clients that care about performance.
|
||||
func (cache *MethodSetCache) MethodSet(T types.Type) *types.MethodSet {
|
||||
if cache == nil {
|
||||
return types.NewMethodSet(T)
|
||||
}
|
||||
cache.mu.Lock()
|
||||
defer cache.mu.Unlock()
|
||||
|
||||
switch T := types.Unalias(T).(type) {
|
||||
case *types.Named:
|
||||
return cache.lookupNamed(T).value
|
||||
|
||||
case *types.Pointer:
|
||||
if N, ok := types.Unalias(T.Elem()).(*types.Named); ok {
|
||||
return cache.lookupNamed(N).pointer
|
||||
}
|
||||
}
|
||||
|
||||
// all other types
|
||||
// (The map uses pointer equivalence, not type identity.)
|
||||
mset := cache.others[T]
|
||||
if mset == nil {
|
||||
mset = types.NewMethodSet(T)
|
||||
if cache.others == nil {
|
||||
cache.others = make(map[types.Type]*types.MethodSet)
|
||||
}
|
||||
cache.others[T] = mset
|
||||
}
|
||||
return mset
|
||||
}
|
||||
|
||||
func (cache *MethodSetCache) lookupNamed(named *types.Named) struct{ value, pointer *types.MethodSet } {
|
||||
if cache.named == nil {
|
||||
cache.named = make(map[*types.Named]struct{ value, pointer *types.MethodSet })
|
||||
}
|
||||
// Avoid recomputing mset(*T) for each distinct Pointer
|
||||
// instance whose underlying type is a named type.
|
||||
msets, ok := cache.named[named]
|
||||
if !ok {
|
||||
msets.value = types.NewMethodSet(named)
|
||||
msets.pointer = types.NewMethodSet(types.NewPointer(named))
|
||||
cache.named[named] = msets
|
||||
}
|
||||
return msets
|
||||
}
|
||||
53
vendor/golang.org/x/tools/go/types/typeutil/ui.go
generated
vendored
Normal file
53
vendor/golang.org/x/tools/go/types/typeutil/ui.go
generated
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package typeutil
|
||||
|
||||
// This file defines utilities for user interfaces that display types.
|
||||
|
||||
import (
|
||||
"go/types"
|
||||
)
|
||||
|
||||
// IntuitiveMethodSet returns the intuitive method set of a type T,
|
||||
// which is the set of methods you can call on an addressable value of
|
||||
// that type.
|
||||
//
|
||||
// The result always contains MethodSet(T), and is exactly MethodSet(T)
|
||||
// for interface types and for pointer-to-concrete types.
|
||||
// For all other concrete types T, the result additionally
|
||||
// contains each method belonging to *T if there is no identically
|
||||
// named method on T itself.
|
||||
//
|
||||
// This corresponds to user intuition about method sets;
|
||||
// this function is intended only for user interfaces.
|
||||
//
|
||||
// The order of the result is as for types.MethodSet(T).
|
||||
func IntuitiveMethodSet(T types.Type, msets *MethodSetCache) []*types.Selection {
|
||||
isPointerToConcrete := func(T types.Type) bool {
|
||||
ptr, ok := types.Unalias(T).(*types.Pointer)
|
||||
return ok && !types.IsInterface(ptr.Elem())
|
||||
}
|
||||
|
||||
var result []*types.Selection
|
||||
mset := msets.MethodSet(T)
|
||||
if types.IsInterface(T) || isPointerToConcrete(T) {
|
||||
for i, n := 0, mset.Len(); i < n; i++ {
|
||||
result = append(result, mset.At(i))
|
||||
}
|
||||
} else {
|
||||
// T is some other concrete type.
|
||||
// Report methods of T and *T, preferring those of T.
|
||||
pmset := msets.MethodSet(types.NewPointer(T))
|
||||
for i, n := 0, pmset.Len(); i < n; i++ {
|
||||
meth := pmset.At(i)
|
||||
if m := mset.Lookup(meth.Obj().Pkg(), meth.Obj().Name()); m != nil {
|
||||
meth = m
|
||||
}
|
||||
result = append(result, meth)
|
||||
}
|
||||
|
||||
}
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user