1
0

Update module spf13/viper to v1.7.0 (#494)

Update module spf13/viper to v1.7.0

Reviewed-on: https://kolaente.dev/vikunja/api/pulls/494
This commit is contained in:
renovate
2020-05-09 13:44:17 +00:00
committed by konrad
parent 18f6e31b54
commit def2362682
89 changed files with 9018 additions and 4502 deletions

View File

@ -32,7 +32,7 @@ package ssa
import (
"fmt"
"go/ast"
exact "go/constant"
"go/constant"
"go/token"
"go/types"
"os"
@ -58,12 +58,12 @@ var (
tString = types.Typ[types.String]
tUntypedNil = types.Typ[types.UntypedNil]
tRangeIter = &opaqueType{nil, "iter"} // the type of all "range" iterators
tEface = types.NewInterface(nil, nil).Complete()
tEface = types.NewInterfaceType(nil, nil).Complete()
// SSA Value constants.
vZero = intConst(0)
vOne = intConst(1)
vTrue = NewConst(exact.MakeBool(true), tBool)
vTrue = NewConst(constant.MakeBool(true), tBool)
)
// builder holds state associated with the package currently being built.
@ -131,11 +131,11 @@ func (b *builder) logicalBinop(fn *Function, e *ast.BinaryExpr) Value {
switch e.Op {
case token.LAND:
b.cond(fn, e.X, rhs, done)
short = NewConst(exact.MakeBool(false), t)
short = NewConst(constant.MakeBool(false), t)
case token.LOR:
b.cond(fn, e.X, done, rhs)
short = NewConst(exact.MakeBool(true), t)
short = NewConst(constant.MakeBool(true), t)
}
// Is rhs unreachable?
@ -969,10 +969,10 @@ func (b *builder) setCall(fn *Function, e *ast.CallExpr, c *CallCommon) {
c.Args = b.emitCallArgs(fn, sig, e, c.Args)
}
// assignOp emits to fn code to perform loc += incr or loc -= incr.
func (b *builder) assignOp(fn *Function, loc lvalue, incr Value, op token.Token, pos token.Pos) {
// assignOp emits to fn code to perform loc <op>= val.
func (b *builder) assignOp(fn *Function, loc lvalue, val Value, op token.Token, pos token.Pos) {
oldv := loc.load(fn)
loc.store(fn, emitArith(fn, op, oldv, emitConv(fn, incr, oldv.Type()), loc.typ(), pos))
loc.store(fn, emitArith(fn, op, oldv, emitConv(fn, val, oldv.Type()), loc.typ(), pos))
}
// localValueSpec emits to fn code to define all of the vars in the
@ -1998,7 +1998,7 @@ start:
op = token.SUB
}
loc := b.addr(fn, s.X, false)
b.assignOp(fn, loc, NewConst(exact.MakeInt64(1), loc.typ()), op, s.Pos())
b.assignOp(fn, loc, NewConst(constant.MakeInt64(1), loc.typ()), op, s.Pos())
case *ast.AssignStmt:
switch s.Tok {

View File

@ -8,7 +8,7 @@ package ssa
import (
"fmt"
exact "go/constant"
"go/constant"
"go/token"
"go/types"
"strconv"
@ -17,14 +17,14 @@ import (
// 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 exact.Value, typ types.Type) *Const {
func NewConst(val constant.Value, typ types.Type) *Const {
return &Const{typ, val}
}
// 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(exact.MakeInt64(i), tInt)
return NewConst(constant.MakeInt64(i), tInt)
}
// nilConst returns a nil constant of the specified type, which may
@ -36,7 +36,7 @@ func nilConst(typ types.Type) *Const {
// stringConst returns a 'string' constant that evaluates to s.
func stringConst(s string) *Const {
return NewConst(exact.MakeString(s), tString)
return NewConst(constant.MakeString(s), tString)
}
// zeroConst returns a new "zero" constant of the specified type,
@ -48,11 +48,11 @@ func zeroConst(t types.Type) *Const {
case *types.Basic:
switch {
case t.Info()&types.IsBoolean != 0:
return NewConst(exact.MakeBool(false), t)
return NewConst(constant.MakeBool(false), t)
case t.Info()&types.IsNumeric != 0:
return NewConst(exact.MakeInt64(0), t)
return NewConst(constant.MakeInt64(0), t)
case t.Info()&types.IsString != 0:
return NewConst(exact.MakeString(""), t)
return NewConst(constant.MakeString(""), t)
case t.Kind() == types.UnsafePointer:
fallthrough
case t.Kind() == types.UntypedNil:
@ -74,8 +74,8 @@ func (c *Const) RelString(from *types.Package) string {
var s string
if c.Value == nil {
s = "nil"
} else if c.Value.Kind() == exact.String {
s = exact.StringVal(c.Value)
} 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 {
@ -121,14 +121,14 @@ func (c *Const) IsNil() bool {
// a signed 64-bit integer.
//
func (c *Const) Int64() int64 {
switch x := exact.ToInt(c.Value); x.Kind() {
case exact.Int:
if i, ok := exact.Int64Val(x); ok {
switch x := constant.ToInt(c.Value); x.Kind() {
case constant.Int:
if i, ok := constant.Int64Val(x); ok {
return i
}
return 0
case exact.Float:
f, _ := exact.Float64Val(x)
case constant.Float:
f, _ := constant.Float64Val(x)
return int64(f)
}
panic(fmt.Sprintf("unexpected constant value: %T", c.Value))
@ -138,14 +138,14 @@ func (c *Const) Int64() int64 {
// an unsigned 64-bit integer.
//
func (c *Const) Uint64() uint64 {
switch x := exact.ToInt(c.Value); x.Kind() {
case exact.Int:
if u, ok := exact.Uint64Val(x); ok {
switch x := constant.ToInt(c.Value); x.Kind() {
case constant.Int:
if u, ok := constant.Uint64Val(x); ok {
return u
}
return 0
case exact.Float:
f, _ := exact.Float64Val(x)
case constant.Float:
f, _ := constant.Float64Val(x)
return uint64(f)
}
panic(fmt.Sprintf("unexpected constant value: %T", c.Value))
@ -155,7 +155,7 @@ func (c *Const) Uint64() uint64 {
// a float64.
//
func (c *Const) Float64() float64 {
f, _ := exact.Float64Val(c.Value)
f, _ := constant.Float64Val(c.Value)
return f
}
@ -163,7 +163,7 @@ func (c *Const) Float64() float64 {
// fit a complex128.
//
func (c *Const) Complex128() complex128 {
re, _ := exact.Float64Val(exact.Real(c.Value))
im, _ := exact.Float64Val(exact.Imag(c.Value))
re, _ := constant.Float64Val(constant.Real(c.Value))
im, _ := constant.Float64Val(constant.Imag(c.Value))
return complex(re, im)
}

View File

@ -251,12 +251,19 @@ func (prog *Program) AllPackages() []*Package {
return pkgs
}
// ImportedPackage returns the importable SSA Package whose import
// path is path, or nil if no such SSA package has been created.
// ImportedPackage returns the importable Package whose PkgPath
// is path, or nil if no such Package has been created.
//
// Not all packages are importable. For example, no import
// declaration can resolve to the x_test package created by 'go test'
// or the ad-hoc main package created 'go build foo.go'.
// 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.
//
func (prog *Program) ImportedPackage(path string) *Package {
return prog.imported[path]

View File

@ -23,11 +23,13 @@
// such as multi-way branch can be reconstructed as needed; see
// ssautil.Switches() for an example.
//
// To construct an SSA-form program, call ssautil.CreateProgram on a
// loader.Program, a set of type-checked packages created from
// parsed Go source files. 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.
// 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 ssautil.Packages helper function. See ExampleLoadPackages
// and ExampleWholeProgram for examples.
// 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

View File

@ -53,7 +53,7 @@ func (a byDomPreorder) Less(i, j int) bool { return a[i].dom.pre < a[j].dom.pre
//
func (f *Function) DomPreorder() []*BasicBlock {
n := len(f.Blocks)
order := make(byDomPreorder, n, n)
order := make(byDomPreorder, n)
copy(order, f.Blocks)
sort.Sort(order)
return order
@ -123,7 +123,7 @@ func buildDomTree(f *Function) {
n := len(f.Blocks)
// Allocate space for 5 contiguous [n]*BasicBlock arrays:
// sdom, parent, ancestor, preorder, buckets.
space := make([]*BasicBlock, 5*n, 5*n)
space := make([]*BasicBlock, 5*n)
lt := ltState{
sdom: space[0:n],
parent: space[n : 2*n],
@ -310,6 +310,7 @@ func sanityCheckDomTree(f *Function) {
// Printing functions ----------------------------------------
// printDomTree prints the dominator tree as text, using indentation.
//lint:ignore U1000 used during debugging
func printDomTreeText(buf *bytes.Buffer, v *BasicBlock, indent int) {
fmt.Fprintf(buf, "%*s%s\n", 4*indent, "", v)
for _, child := range v.dom.children {
@ -319,6 +320,7 @@ func printDomTreeText(buf *bytes.Buffer, v *BasicBlock, indent int) {
// printDomTreeDot prints the dominator tree of f in AT&T GraphViz
// (.dot) format.
//lint:ignore U1000 used during debugging
func printDomTreeDot(buf *bytes.Buffer, f *Function) {
fmt.Fprintln(buf, "//", f)
fmt.Fprintln(buf, "digraph domtree {")

View File

@ -127,6 +127,7 @@ func emitCompare(f *Function, op token.Token, x, y Value, pos token.Pos) Value {
x = emitConv(f, x, y.Type())
} else if _, ok := y.(*Const); ok {
y = emitConv(f, y, x.Type())
//lint:ignore SA9003 no-op
} else {
// other cases, e.g. channels. No-op.
}

View File

@ -328,6 +328,70 @@ func (f *Function) finishBody() {
}
f.Locals = f.Locals[:j]
// comma-ok receiving from a time.Tick channel will never return
// ok == false, so any branching on the value of ok can be
// replaced with an unconditional jump. This will primarily match
// `for range time.Tick(x)` loops, but it can also match
// user-written code.
for _, block := range f.Blocks {
if len(block.Instrs) < 3 {
continue
}
if len(block.Succs) != 2 {
continue
}
var instrs []*Instruction
for i, ins := range block.Instrs {
if _, ok := ins.(*DebugRef); ok {
continue
}
instrs = append(instrs, &block.Instrs[i])
}
for i, ins := range instrs {
unop, ok := (*ins).(*UnOp)
if !ok || unop.Op != token.ARROW {
continue
}
call, ok := unop.X.(*Call)
if !ok {
continue
}
if call.Common().IsInvoke() {
continue
}
// OPT(dh): surely there is a more efficient way of doing
// this, than using FullName. We should already have
// resolved time.Tick somewhere?
v, ok := call.Common().Value.(*Function)
if !ok {
continue
}
t, ok := v.Object().(*types.Func)
if !ok {
continue
}
if t.FullName() != "time.Tick" {
continue
}
ex, ok := (*instrs[i+1]).(*Extract)
if !ok || ex.Tuple != unop || ex.Index != 1 {
continue
}
ifstmt, ok := (*instrs[i+2]).(*If)
if !ok || ifstmt.Cond != ex {
continue
}
*instrs[i+2] = NewJump(block)
succ := block.Succs[1]
block.Succs = block.Succs[0:1]
succ.RemovePred(block)
}
}
optimizeBlocks(f)
buildReferrers(f)

View File

@ -341,10 +341,10 @@ func phiHasDirectReferrer(phi *Phi) bool {
return false
}
type blockSet struct{ big.Int } // (inherit methods from Int)
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 {
func (s *BlockSet) Add(b *BasicBlock) bool {
i := b.Index
if s.Bit(i) != 0 {
return false
@ -353,9 +353,13 @@ func (s *blockSet) add(b *BasicBlock) bool {
return true
}
func (s *BlockSet) Has(b *BasicBlock) bool {
return s.Bit(b.Index) == 1
}
// take removes an arbitrary element from a set s and
// returns its index, or returns -1 if empty.
func (s *blockSet) take() int {
func (s *BlockSet) Take() int {
l := s.BitLen()
for i := 0; i < l; i++ {
if s.Bit(i) == 1 {
@ -403,7 +407,7 @@ func liftAlloc(df domFrontier, alloc *Alloc, newPhis newPhiMap, fresh *int) bool
// Compute defblocks, the set of blocks containing a
// definition of the alloc cell.
var defblocks blockSet
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
@ -416,7 +420,7 @@ func liftAlloc(df domFrontier, alloc *Alloc, newPhis newPhiMap, fresh *int) bool
if instr.Addr != alloc {
panic("Alloc.Referrers is inconsistent")
}
defblocks.add(instr.Block())
defblocks.Add(instr.Block())
case *UnOp:
if instr.Op != token.MUL {
return false // not a load
@ -431,7 +435,7 @@ func liftAlloc(df domFrontier, alloc *Alloc, newPhis newPhiMap, fresh *int) bool
}
}
// The Alloc itself counts as a (zero) definition of the cell.
defblocks.add(alloc.Block())
defblocks.Add(alloc.Block())
if debugLifting {
fmt.Fprintln(os.Stderr, "\tlifting ", alloc, alloc.Name())
@ -448,18 +452,18 @@ func liftAlloc(df domFrontier, alloc *Alloc, newPhis newPhiMap, fresh *int) bool
//
// TODO(adonovan): opt: recycle slice storage for W,
// hasAlready, defBlocks across liftAlloc calls.
var hasAlready blockSet
var hasAlready BlockSet
// Initialize W and work to defblocks.
var work blockSet = defblocks // blocks seen
var W blockSet // blocks to do
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() {
for i := W.Take(); i != -1; i = W.Take() {
u := fn.Blocks[i]
for _, v := range df[u.Index] {
if hasAlready.add(v) {
if hasAlready.Add(v) {
// Create φ-node.
// It will be prepended to v.Instrs later, if needed.
phi := &Phi{
@ -478,8 +482,8 @@ func liftAlloc(df domFrontier, alloc *Alloc, newPhis newPhiMap, fresh *int) bool
}
newPhis[v] = append(newPhis[v], newPhi{phi, alloc})
if work.add(v) {
W.add(v)
if work.Add(v) {
W.Add(v)
}
}
}

View File

@ -23,14 +23,14 @@ import (
//
func (prog *Program) MethodValue(sel *types.Selection) *Function {
if sel.Kind() != types.MethodVal {
panic(fmt.Sprintf("Method(%s) kind != MethodVal", sel))
panic(fmt.Sprintf("MethodValue(%s) kind != MethodVal", sel))
}
T := sel.Recv()
if isInterface(T) {
return nil // abstract method
}
if prog.mode&LogSource != 0 {
defer logStack("Method %s %v", T, sel)()
defer logStack("MethodValue %s %v", T, sel)()
}
prog.methodsMu.Lock()

View File

@ -410,8 +410,8 @@ func (s *sanity) checkFunction(fn *Function) bool {
s.errorf("nil Prog")
}
fn.String() // must not crash
fn.RelString(fn.pkg()) // must not crash
_ = fn.String() // must not crash
_ = fn.RelString(fn.pkg()) // must not crash
// All functions have a package, except delegates (which are
// shared across packages, or duplicated as weak symbols in a
@ -448,6 +448,18 @@ func (s *sanity) checkFunction(fn *Function) bool {
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 {
@ -490,7 +502,7 @@ func sanityCheckPackage(pkg *Package) {
if pkg.Pkg == nil {
panic(fmt.Sprintf("Package %s has no Object", pkg))
}
pkg.String() // must not crash
_ = pkg.String() // must not crash
for name, mem := range pkg.Members {
if name != mem.Name() {

View File

@ -150,7 +150,7 @@ func findNamedFunc(pkg *Package, pos token.Pos) *Function {
// (modulo "untyped" bools resulting from comparisons).
//
// (Tip: to find the ssa.Value given a source position, use
// importer.PathEnclosingInterval to locate the ast.Node, then
// astutil.PathEnclosingInterval to locate the ast.Node, then
// EnclosingFunction to locate the Function, then ValueForExpr to find
// the ssa.Value.)
//

View File

@ -10,7 +10,7 @@ package ssa
import (
"fmt"
"go/ast"
exact "go/constant"
"go/constant"
"go/token"
"go/types"
"sync"
@ -405,7 +405,7 @@ type Parameter struct {
// of the same type and value.
//
// Value holds the exact value of the constant, independent of its
// Type(), using the same representation as package go/exact uses for
// Type(), using the same representation as package go/constant uses for
// constants, or nil for a typed nil value.
//
// Pos() returns token.NoPos.
@ -417,7 +417,7 @@ type Parameter struct {
//
type Const struct {
typ types.Type
Value exact.Value
Value constant.Value
}
// A Global is a named Value holding the address of a package-level
@ -572,8 +572,8 @@ type BinOp struct {
register
// One of:
// ADD SUB MUL QUO REM + - * / %
// AND OR XOR SHL SHR AND_NOT & | ^ << >> &~
// EQL LSS GTR NEQ LEQ GEQ == != < <= < >=
// AND OR XOR SHL SHR AND_NOT & | ^ << >> &^
// EQL NEQ LSS LEQ GTR GEQ == != < <= < >=
Op token.Token
X, Y Value
}
@ -680,10 +680,10 @@ type ChangeInterface struct {
// value of a concrete type.
//
// Use Program.MethodSets.MethodSet(X.Type()) to find the method-set
// of X, and Program.Method(m) to find the implementation of a method.
// of X, and Program.MethodValue(m) to find the implementation of a method.
//
// To construct the zero value of an interface type T, use:
// NewConst(exact.MakeNil(), T, pos)
// NewConst(constant.MakeNil(), T, pos)
//
// Pos() returns the ast.CallExpr.Lparen, if the instruction arose
// from an explicit conversion in the source.
@ -813,7 +813,7 @@ type Slice struct {
type FieldAddr struct {
register
X Value // *struct
Field int // index into X.Type().Deref().(*types.Struct).Fields
Field int // field is X.Type().Underlying().(*types.Pointer).Elem().Underlying().(*types.Struct).Field(Field)
}
// The Field instruction yields the Field of struct X.

View File

@ -1,143 +0,0 @@
// 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/loader"
"golang.org/x/tools/go/packages"
"honnef.co/go/tools/ssa"
)
// Packages creates an SSA program for a set of packages loaded from
// source syntax using the golang.org/x/tools/go/packages.Load function.
// It creates and returns an SSA package for each well-typed package in
// the initial list. The resulting list of packages has the same length
// as initial, and contains a nil if SSA could not be constructed for
// the corresponding initial package.
//
// Code for bodies of functions is not built until Build is called
// on the resulting Program.
//
// The mode parameter controls diagnostics and checking during SSA construction.
//
func Packages(initial []*packages.Package, mode ssa.BuilderMode) (*ssa.Program, []*ssa.Package) {
var fset *token.FileSet
if len(initial) > 0 {
fset = initial[0].Fset
}
prog := ssa.NewProgram(fset, mode)
seen := make(map[*packages.Package]*ssa.Package)
var create func(p *packages.Package) *ssa.Package
create = func(p *packages.Package) *ssa.Package {
ssapkg, ok := seen[p]
if !ok {
if p.Types == nil || p.IllTyped {
// not well typed
seen[p] = nil
return nil
}
ssapkg = prog.CreatePackage(p.Types, p.Syntax, p.TypesInfo, true)
seen[p] = ssapkg
for _, imp := range p.Imports {
create(imp)
}
}
return ssapkg
}
var ssapkgs []*ssa.Package
for _, p := range initial {
ssapkgs = append(ssapkgs, create(p))
}
return prog, ssapkgs
}
// 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.
//
// mode controls diagnostics and checking during SSA construction.
//
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
}
// BuildPackage builds an SSA program with IR for a single package.
//
// It populates pkg by type-checking the specified file ASTs. 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 ../ssa/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),
Scopes: make(map[ast.Node]*types.Scope),
Selections: make(map[*ast.SelectorExpr]*types.Selection),
}
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())
// Create and build the primary package.
ssapkg := prog.CreatePackage(pkg, files, info, false)
ssapkg.Build()
return ssapkg, info, nil
}

View File

@ -1,234 +0,0 @@
// 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"
"honnef.co/go/tools/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
}

View File

@ -1,79 +0,0 @@
// 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 "honnef.co/go/tools/ssa/ssautil"
import "honnef.co/go/tools/ssa"
// 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.
//
func AllFunctions(prog *ssa.Program) map[*ssa.Function]bool {
visit := visitor{
prog: prog,
seen: make(map[*ssa.Function]bool),
}
visit.program()
return visit.seen
}
type visitor struct {
prog *ssa.Program
seen map[*ssa.Function]bool
}
func (visit *visitor) program() {
for _, pkg := range visit.prog.AllPackages() {
for _, mem := range pkg.Members {
if fn, ok := mem.(*ssa.Function); ok {
visit.function(fn)
}
}
}
for _, T := range visit.prog.RuntimeTypes() {
mset := visit.prog.MethodSets.MethodSet(T)
for i, n := 0, mset.Len(); i < n; i++ {
visit.function(visit.prog.MethodValue(mset.At(i)))
}
}
}
func (visit *visitor) function(fn *ssa.Function) {
if !visit.seen[fn] {
visit.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 {
visit.function(fn)
}
}
}
}
}
}
// 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
}

View File

@ -0,0 +1,3 @@
# ssa/... is mostly imported from upstream and we don't want to
# deviate from it too much, hence disabling SA1019
checks = ["inherit", "-SA1019"]

View File

@ -8,8 +8,8 @@ package ssa
// tests of the supplied packages.
// It is closely coupled to $GOROOT/src/cmd/go/test.go and $GOROOT/src/testing.
//
// TODO(adonovan): this file no longer needs to live in the ssa package.
// Move it to ssautil.
// TODO(adonovan): throws this all away now that x/tools/go/packages
// provides access to the actual synthetic test main files.
import (
"bytes"
@ -26,6 +26,8 @@ import (
// FindTests returns the Test, Benchmark, and Example functions
// (as defined by "go test") defined in the specified package,
// and its TestMain function, if any.
//
// Deprecated: use x/tools/go/packages to access synthetic testmain packages.
func FindTests(pkg *Package) (tests, benchmarks, examples []*Function, main *Function) {
prog := pkg.Prog
@ -109,6 +111,8 @@ func isTest(name, prefix string) bool {
//
// Subsequent calls to prog.AllPackages include the new package.
// The package pkg must belong to the program prog.
//
// Deprecated: use x/tools/go/packages to access synthetic testmain packages.
func (prog *Program) CreateTestMainPackage(pkg *Package) *Package {
if pkg.Prog != prog {
log.Fatal("Package does not belong to Program")

View File

@ -141,13 +141,9 @@ func makeWrapper(prog *Program, sel *types.Selection) *Function {
// start is the index of the first regular parameter to use.
//
func createParams(fn *Function, start int) {
var last *Parameter
tparams := fn.Signature.Params()
for i, n := start, tparams.Len(); i < n; i++ {
last = fn.addParamObj(tparams.At(i))
}
if fn.Signature.Variadic() {
last.typ = types.NewSlice(last.typ)
fn.addParamObj(tparams.At(i))
}
}