Update and fix staticcheck

This commit is contained in:
kolaente 2020-05-29 22:15:21 +02:00
parent aae1bc3cab
commit a525787ab7
Signed by untrusted user: konrad
GPG Key ID: F40E70337AB24C9B
100 changed files with 12353 additions and 7912 deletions

2
go.mod
View File

@ -74,7 +74,7 @@ require (
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/d4l3k/messagediff.v1 v1.2.1
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
honnef.co/go/tools v0.0.1-2019.2.3
honnef.co/go/tools v0.0.1-2020.1.4
src.techknowlogick.com/xgo v0.0.0-20200514233805-209a5cf70012
src.techknowlogick.com/xormigrate v1.2.0
xorm.io/builder v0.3.7

3
go.sum
View File

@ -682,6 +682,7 @@ golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7 h1:EBZoQjiKKPaLbPrbpssUfuHtwM6KV/vb4U85g/cigFY=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef h1:RHORRhs540cYZYrzgU2CPUyykkwZM78hGdzocOo9P8A=
@ -773,6 +774,8 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a h1:LJwr7TCTghdatWv40WobzlK
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
src.techknowlogick.com/xgo v0.0.0-20200514233805-209a5cf70012 h1:k1/qGRpsaGka4IHT2UIUdPqM6+oo3O7+8S3ACN2kJZQ=
src.techknowlogick.com/xgo v0.0.0-20200514233805-209a5cf70012/go.mod h1:31CE1YKtDOrKTk9PSnjTpe6YbO6W/0LTYZ1VskL09oU=

View File

@ -35,7 +35,7 @@ func IsErrGenericForbidden(err error) bool {
}
func (err ErrGenericForbidden) Error() string {
return fmt.Sprintf("Forbidden")
return "Forbidden"
}
// ErrorCodeGenericForbidden holds the unique world-error code of this error
@ -60,7 +60,7 @@ func IsErrIDCannotBeZero(err error) bool {
}
func (err ErrIDCannotBeZero) Error() string {
return fmt.Sprintf("ID cannot be empty or 0")
return "ID cannot be empty or 0"
}
// ErrCodeIDCannotBeZero holds the unique world-error code of this error
@ -169,7 +169,7 @@ func IsErrListTitleCannotBeEmpty(err error) bool {
}
func (err ErrListTitleCannotBeEmpty) Error() string {
return fmt.Sprintf("List title cannot be empty.")
return "List title cannot be empty."
}
// ErrCodeListTitleCannotBeEmpty holds the unique world-error code of this error
@ -193,7 +193,7 @@ func IsErrListShareDoesNotExist(err error) bool {
}
func (err ErrListShareDoesNotExist) Error() string {
return fmt.Sprintf("List share does not exist.")
return "List share does not exist."
}
// ErrCodeListShareDoesNotExist holds the unique world-error code of this error
@ -216,7 +216,7 @@ func IsErrListIdentifierIsNotUnique(err error) bool {
}
func (err ErrListIdentifierIsNotUnique) Error() string {
return fmt.Sprintf("List identifier is not unique.")
return "List identifier is not unique."
}
// ErrCodeListIdentifierIsNotUnique holds the unique world-error code of this error
@ -268,7 +268,7 @@ func IsErrTaskCannotBeEmpty(err error) bool {
}
func (err ErrTaskCannotBeEmpty) Error() string {
return fmt.Sprintf("List task title cannot be empty.")
return "List task title cannot be empty."
}
// ErrCodeTaskCannotBeEmpty holds the unique world-error code of this error
@ -336,7 +336,7 @@ func IsErrBulkTasksNeedAtLeastOne(err error) bool {
}
func (err ErrBulkTasksNeedAtLeastOne) Error() string {
return fmt.Sprintf("Need at least one task when bulk editing tasks")
return "Need at least one task when bulk editing tasks"
}
// ErrCodeBulkTasksNeedAtLeastOne holds the unique world-error code of this error

View File

@ -82,7 +82,7 @@ func UserTOTPEnable(c echo.Context) error {
if he, is := err.(*echo.HTTPError); is {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid model provided. Error was: %s", he.Message))
}
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid model provided."))
return echo.NewHTTPError(http.StatusBadRequest, "Invalid model provided.")
}
err = user.EnableTOTP(passcode)
@ -113,7 +113,7 @@ func UserTOTPDisable(c echo.Context) error {
if he, is := err.(*echo.HTTPError); is {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid model provided. Error was: %s", he.Message))
}
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid model provided."))
return echo.NewHTTPError(http.StatusBadRequest, "Invalid model provided.")
}
u, err := user.GetCurrentUser(c)

View File

@ -47,7 +47,7 @@ func UpdateUserEmail(c echo.Context) (err error) {
if he, is := err.(*echo.HTTPError); is {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid model provided. Error was: %s", he.Message))
}
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid model provided."))
return echo.NewHTTPError(http.StatusBadRequest, "Invalid model provided.")
}
emailUpdate.User, err = user.GetCurrentUser(c)

View File

@ -85,7 +85,7 @@ func IsErrNoUsernamePassword(err error) bool {
}
func (err ErrNoUsernamePassword) Error() string {
return fmt.Sprintf("No username and password provided")
return "No username and password provided"
}
// ErrCodeNoUsernamePassword holds the unique world-error code of this error
@ -129,7 +129,7 @@ func IsErrCouldNotGetUserID(err error) bool {
}
func (err ErrCouldNotGetUserID) Error() string {
return fmt.Sprintf("Could not get user ID")
return "Could not get user ID"
}
// ErrCodeCouldNotGetUserID holds the unique world-error code of this error
@ -208,7 +208,7 @@ type ErrWrongUsernameOrPassword struct {
}
func (err ErrWrongUsernameOrPassword) Error() string {
return fmt.Sprintf("Wrong username or password")
return "Wrong username or password"
}
// ErrCodeWrongUsernameOrPassword holds the unique world-error code of this error
@ -258,7 +258,7 @@ func IsErrEmptyNewPassword(err error) bool {
}
func (err ErrEmptyNewPassword) Error() string {
return fmt.Sprintf("New password is empty")
return "New password is empty"
}
// ErrCodeEmptyNewPassword holds the unique world-error code of this error
@ -279,7 +279,7 @@ func IsErrEmptyOldPassword(err error) bool {
}
func (err ErrEmptyOldPassword) Error() string {
return fmt.Sprintf("Old password is empty")
return "Old password is empty"
}
// ErrCodeEmptyOldPassword holds the unique world-error code of this error
@ -300,7 +300,7 @@ func IsErrTOTPAlreadyEnabled(err error) bool {
}
func (err ErrTOTPAlreadyEnabled) Error() string {
return fmt.Sprintf("Totp is already enabled for this user")
return "Totp is already enabled for this user"
}
// ErrCodeTOTPAlreadyEnabled holds the unique world-error code of this error
@ -325,7 +325,7 @@ func IsErrTOTPNotEnabled(err error) bool {
}
func (err ErrTOTPNotEnabled) Error() string {
return fmt.Sprintf("Totp is not enabled for this user")
return "Totp is not enabled for this user"
}
// ErrCodeTOTPNotEnabled holds the unique world-error code of this error
@ -352,7 +352,7 @@ func IsErrInvalidTOTPPasscode(err error) bool {
}
func (err ErrInvalidTOTPPasscode) Error() string {
return fmt.Sprintf("Invalid totp passcode")
return "Invalid totp passcode"
}
// ErrCodeInvalidTOTPPasscode holds the unique world-error code of this error

View File

@ -1,3 +1,5 @@
module github.com/hashicorp/hcl
require github.com/davecgh/go-spew v1.1.1
go 1.13

View File

@ -1,3 +1,5 @@
module github.com/spf13/afero
require golang.org/x/text v0.3.0
go 1.13

View File

@ -75,7 +75,7 @@ resulting binaries. These projects are:
limitations under the License.
* github.com/kisielk/gotool https://github.com/kisielk/gotool
* github.com/kisielk/gotool - https://github.com/kisielk/gotool
Copyright (c) 2013 Kamil Kisiel <kamil@kamilkisiel.net>
@ -224,3 +224,61 @@ resulting binaries. These projects are:
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* gogrep - https://github.com/mvdan/gogrep
Copyright (c) 2017, Daniel Martí. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* gosmith - https://github.com/dvyukov/gosmith
Copyright (c) 2014 Dmitry Vyukov. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* The name of Dmitry Vyukov may be used to endorse or promote
products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

481
vendor/honnef.co/go/tools/code/code.go vendored Normal file
View File

@ -0,0 +1,481 @@
// Package code answers structural and type questions about Go code.
package code
import (
"flag"
"fmt"
"go/ast"
"go/constant"
"go/token"
"go/types"
"strings"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/go/ast/inspector"
"honnef.co/go/tools/facts"
"honnef.co/go/tools/go/types/typeutil"
"honnef.co/go/tools/ir"
"honnef.co/go/tools/lint"
)
type Positioner interface {
Pos() token.Pos
}
func CallName(call *ir.CallCommon) string {
if call.IsInvoke() {
return ""
}
switch v := call.Value.(type) {
case *ir.Function:
fn, ok := v.Object().(*types.Func)
if !ok {
return ""
}
return lint.FuncName(fn)
case *ir.Builtin:
return v.Name()
}
return ""
}
func IsCallTo(call *ir.CallCommon, name string) bool { return CallName(call) == name }
func IsCallToAny(call *ir.CallCommon, names ...string) bool {
q := CallName(call)
for _, name := range names {
if q == name {
return true
}
}
return false
}
func IsType(T types.Type, name string) bool { return types.TypeString(T, nil) == name }
func FilterDebug(instr []ir.Instruction) []ir.Instruction {
var out []ir.Instruction
for _, ins := range instr {
if _, ok := ins.(*ir.DebugRef); !ok {
out = append(out, ins)
}
}
return out
}
func IsExample(fn *ir.Function) bool {
if !strings.HasPrefix(fn.Name(), "Example") {
return false
}
f := fn.Prog.Fset.File(fn.Pos())
if f == nil {
return false
}
return strings.HasSuffix(f.Name(), "_test.go")
}
func IsPointerLike(T types.Type) bool {
switch T := T.Underlying().(type) {
case *types.Interface, *types.Chan, *types.Map, *types.Signature, *types.Pointer:
return true
case *types.Basic:
return T.Kind() == types.UnsafePointer
}
return false
}
func IsIdent(expr ast.Expr, ident string) bool {
id, ok := expr.(*ast.Ident)
return ok && id.Name == ident
}
// isBlank returns whether id is the blank identifier "_".
// If id == nil, the answer is false.
func IsBlank(id ast.Expr) bool {
ident, _ := id.(*ast.Ident)
return ident != nil && ident.Name == "_"
}
func IsIntLiteral(expr ast.Expr, literal string) bool {
lit, ok := expr.(*ast.BasicLit)
return ok && lit.Kind == token.INT && lit.Value == literal
}
// Deprecated: use IsIntLiteral instead
func IsZero(expr ast.Expr) bool {
return IsIntLiteral(expr, "0")
}
func IsOfType(pass *analysis.Pass, expr ast.Expr, name string) bool {
return IsType(pass.TypesInfo.TypeOf(expr), name)
}
func IsInTest(pass *analysis.Pass, node Positioner) bool {
// FIXME(dh): this doesn't work for global variables with
// initializers
f := pass.Fset.File(node.Pos())
return f != nil && strings.HasSuffix(f.Name(), "_test.go")
}
// IsMain reports whether the package being processed is a package
// main.
func IsMain(pass *analysis.Pass) bool {
return pass.Pkg.Name() == "main"
}
// IsMainLike reports whether the package being processed is a
// main-like package. A main-like package is a package that is
// package main, or that is intended to be used by a tool framework
// such as cobra to implement a command.
//
// Note that this function errs on the side of false positives; it may
// return true for packages that aren't main-like. IsMainLike is
// intended for analyses that wish to suppress diagnostics for
// main-like packages to avoid false positives.
func IsMainLike(pass *analysis.Pass) bool {
if pass.Pkg.Name() == "main" {
return true
}
for _, imp := range pass.Pkg.Imports() {
if imp.Path() == "github.com/spf13/cobra" {
return true
}
}
return false
}
func SelectorName(pass *analysis.Pass, expr *ast.SelectorExpr) string {
info := pass.TypesInfo
sel := info.Selections[expr]
if sel == nil {
if x, ok := expr.X.(*ast.Ident); ok {
pkg, ok := info.ObjectOf(x).(*types.PkgName)
if !ok {
// This shouldn't happen
return fmt.Sprintf("%s.%s", x.Name, expr.Sel.Name)
}
return fmt.Sprintf("%s.%s", pkg.Imported().Path(), expr.Sel.Name)
}
panic(fmt.Sprintf("unsupported selector: %v", expr))
}
return fmt.Sprintf("(%s).%s", sel.Recv(), sel.Obj().Name())
}
func IsNil(pass *analysis.Pass, expr ast.Expr) bool {
return pass.TypesInfo.Types[expr].IsNil()
}
func BoolConst(pass *analysis.Pass, expr ast.Expr) bool {
val := pass.TypesInfo.ObjectOf(expr.(*ast.Ident)).(*types.Const).Val()
return constant.BoolVal(val)
}
func IsBoolConst(pass *analysis.Pass, expr ast.Expr) bool {
// We explicitly don't support typed bools because more often than
// not, custom bool types are used as binary enums and the
// explicit comparison is desired.
ident, ok := expr.(*ast.Ident)
if !ok {
return false
}
obj := pass.TypesInfo.ObjectOf(ident)
c, ok := obj.(*types.Const)
if !ok {
return false
}
basic, ok := c.Type().(*types.Basic)
if !ok {
return false
}
if basic.Kind() != types.UntypedBool && basic.Kind() != types.Bool {
return false
}
return true
}
func ExprToInt(pass *analysis.Pass, expr ast.Expr) (int64, bool) {
tv := pass.TypesInfo.Types[expr]
if tv.Value == nil {
return 0, false
}
if tv.Value.Kind() != constant.Int {
return 0, false
}
return constant.Int64Val(tv.Value)
}
func ExprToString(pass *analysis.Pass, expr ast.Expr) (string, bool) {
val := pass.TypesInfo.Types[expr].Value
if val == nil {
return "", false
}
if val.Kind() != constant.String {
return "", false
}
return constant.StringVal(val), true
}
// Dereference returns a pointer's element type; otherwise it returns
// T.
func Dereference(T types.Type) types.Type {
if p, ok := T.Underlying().(*types.Pointer); ok {
return p.Elem()
}
return T
}
// DereferenceR returns a pointer's element type; otherwise it returns
// T. If the element type is itself a pointer, DereferenceR will be
// applied recursively.
func DereferenceR(T types.Type) types.Type {
if p, ok := T.Underlying().(*types.Pointer); ok {
return DereferenceR(p.Elem())
}
return T
}
func CallNameAST(pass *analysis.Pass, call *ast.CallExpr) string {
switch fun := astutil.Unparen(call.Fun).(type) {
case *ast.SelectorExpr:
fn, ok := pass.TypesInfo.ObjectOf(fun.Sel).(*types.Func)
if !ok {
return ""
}
return lint.FuncName(fn)
case *ast.Ident:
obj := pass.TypesInfo.ObjectOf(fun)
switch obj := obj.(type) {
case *types.Func:
return lint.FuncName(obj)
case *types.Builtin:
return obj.Name()
default:
return ""
}
default:
return ""
}
}
func IsCallToAST(pass *analysis.Pass, node ast.Node, name string) bool {
call, ok := node.(*ast.CallExpr)
if !ok {
return false
}
return CallNameAST(pass, call) == name
}
func IsCallToAnyAST(pass *analysis.Pass, node ast.Node, names ...string) bool {
call, ok := node.(*ast.CallExpr)
if !ok {
return false
}
q := CallNameAST(pass, call)
for _, name := range names {
if q == name {
return true
}
}
return false
}
func Preamble(f *ast.File) string {
cutoff := f.Package
if f.Doc != nil {
cutoff = f.Doc.Pos()
}
var out []string
for _, cmt := range f.Comments {
if cmt.Pos() >= cutoff {
break
}
out = append(out, cmt.Text())
}
return strings.Join(out, "\n")
}
func GroupSpecs(fset *token.FileSet, specs []ast.Spec) [][]ast.Spec {
if len(specs) == 0 {
return nil
}
groups := make([][]ast.Spec, 1)
groups[0] = append(groups[0], specs[0])
for _, spec := range specs[1:] {
g := groups[len(groups)-1]
if fset.PositionFor(spec.Pos(), false).Line-1 !=
fset.PositionFor(g[len(g)-1].End(), false).Line {
groups = append(groups, nil)
}
groups[len(groups)-1] = append(groups[len(groups)-1], spec)
}
return groups
}
func IsObject(obj types.Object, name string) bool {
var path string
if pkg := obj.Pkg(); pkg != nil {
path = pkg.Path() + "."
}
return path+obj.Name() == name
}
type Field struct {
Var *types.Var
Tag string
Path []int
}
// FlattenFields recursively flattens T and embedded structs,
// returning a list of fields. If multiple fields with the same name
// exist, all will be returned.
func FlattenFields(T *types.Struct) []Field {
return flattenFields(T, nil, nil)
}
func flattenFields(T *types.Struct, path []int, seen map[types.Type]bool) []Field {
if seen == nil {
seen = map[types.Type]bool{}
}
if seen[T] {
return nil
}
seen[T] = true
var out []Field
for i := 0; i < T.NumFields(); i++ {
field := T.Field(i)
tag := T.Tag(i)
np := append(path[:len(path):len(path)], i)
if field.Anonymous() {
if s, ok := Dereference(field.Type()).Underlying().(*types.Struct); ok {
out = append(out, flattenFields(s, np, seen)...)
}
} else {
out = append(out, Field{field, tag, np})
}
}
return out
}
func File(pass *analysis.Pass, node Positioner) *ast.File {
m := pass.ResultOf[facts.TokenFile].(map[*token.File]*ast.File)
return m[pass.Fset.File(node.Pos())]
}
// IsGenerated reports whether pos is in a generated file, It ignores
// //line directives.
func IsGenerated(pass *analysis.Pass, pos token.Pos) bool {
_, ok := Generator(pass, pos)
return ok
}
// Generator returns the generator that generated the file containing
// pos. It ignores //line directives.
func Generator(pass *analysis.Pass, pos token.Pos) (facts.Generator, bool) {
file := pass.Fset.PositionFor(pos, false).Filename
m := pass.ResultOf[facts.Generated].(map[string]facts.Generator)
g, ok := m[file]
return g, ok
}
// MayHaveSideEffects reports whether expr may have side effects. If
// the purity argument is nil, this function implements a purely
// syntactic check, meaning that any function call may have side
// effects, regardless of the called function's body. Otherwise,
// purity will be consulted to determine the purity of function calls.
func MayHaveSideEffects(pass *analysis.Pass, expr ast.Expr, purity facts.PurityResult) bool {
switch expr := expr.(type) {
case *ast.BadExpr:
return true
case *ast.Ellipsis:
return MayHaveSideEffects(pass, expr.Elt, purity)
case *ast.FuncLit:
// the literal itself cannot have side ffects, only calling it
// might, which is handled by CallExpr.
return false
case *ast.ArrayType, *ast.StructType, *ast.FuncType, *ast.InterfaceType, *ast.MapType, *ast.ChanType:
// types cannot have side effects
return false
case *ast.BasicLit:
return false
case *ast.BinaryExpr:
return MayHaveSideEffects(pass, expr.X, purity) || MayHaveSideEffects(pass, expr.Y, purity)
case *ast.CallExpr:
if purity == nil {
return true
}
switch obj := typeutil.Callee(pass.TypesInfo, expr).(type) {
case *types.Func:
if _, ok := purity[obj]; !ok {
return true
}
case *types.Builtin:
switch obj.Name() {
case "len", "cap":
default:
return true
}
default:
return true
}
for _, arg := range expr.Args {
if MayHaveSideEffects(pass, arg, purity) {
return true
}
}
return false
case *ast.CompositeLit:
if MayHaveSideEffects(pass, expr.Type, purity) {
return true
}
for _, elt := range expr.Elts {
if MayHaveSideEffects(pass, elt, purity) {
return true
}
}
return false
case *ast.Ident:
return false
case *ast.IndexExpr:
return MayHaveSideEffects(pass, expr.X, purity) || MayHaveSideEffects(pass, expr.Index, purity)
case *ast.KeyValueExpr:
return MayHaveSideEffects(pass, expr.Key, purity) || MayHaveSideEffects(pass, expr.Value, purity)
case *ast.SelectorExpr:
return MayHaveSideEffects(pass, expr.X, purity)
case *ast.SliceExpr:
return MayHaveSideEffects(pass, expr.X, purity) ||
MayHaveSideEffects(pass, expr.Low, purity) ||
MayHaveSideEffects(pass, expr.High, purity) ||
MayHaveSideEffects(pass, expr.Max, purity)
case *ast.StarExpr:
return MayHaveSideEffects(pass, expr.X, purity)
case *ast.TypeAssertExpr:
return MayHaveSideEffects(pass, expr.X, purity)
case *ast.UnaryExpr:
if MayHaveSideEffects(pass, expr.X, purity) {
return true
}
return expr.Op == token.ARROW
case *ast.ParenExpr:
return MayHaveSideEffects(pass, expr.X, purity)
case nil:
return false
default:
panic(fmt.Sprintf("internal error: unhandled type %T", expr))
}
}
func IsGoVersion(pass *analysis.Pass, minor int) bool {
version := pass.Analyzer.Flags.Lookup("go").Value.(flag.Getter).Get().(int)
return version >= minor
}
func Preorder(pass *analysis.Pass, fn func(ast.Node), types ...ast.Node) {
pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Preorder(types, fn)
}

View File

@ -3,6 +3,8 @@ package config
import (
"bytes"
"fmt"
"go/ast"
"go/token"
"os"
"path/filepath"
"reflect"
@ -12,38 +14,57 @@ import (
"golang.org/x/tools/go/analysis"
)
// Dir looks at a list of absolute file names, which should make up a
// single package, and returns the path of the directory that may
// contain a staticcheck.conf file. It returns the empty string if no
// such directory could be determined, for example because all files
// were located in Go's build cache.
func Dir(files []string) string {
if len(files) == 0 {
return ""
}
cache, err := os.UserCacheDir()
if err != nil {
cache = ""
}
var path string
for _, p := range files {
// FIXME(dh): using strings.HasPrefix isn't technically
// correct, but it should be good enough for now.
if cache != "" && strings.HasPrefix(p, cache) {
// File in the build cache of the standard Go build system
continue
}
path = p
break
}
if path == "" {
// The package only consists of generated files.
return ""
}
dir := filepath.Dir(path)
return dir
}
func dirAST(files []*ast.File, fset *token.FileSet) string {
names := make([]string, len(files))
for i, f := range files {
names[i] = fset.PositionFor(f.Pos(), true).Filename
}
return Dir(names)
}
var Analyzer = &analysis.Analyzer{
Name: "config",
Doc: "loads configuration for the current package tree",
Run: func(pass *analysis.Pass) (interface{}, error) {
if len(pass.Files) == 0 {
dir := dirAST(pass.Files, pass.Fset)
if dir == "" {
cfg := DefaultConfig
return &cfg, nil
}
cache, err := os.UserCacheDir()
if err != nil {
cache = ""
}
var path string
for _, f := range pass.Files {
p := pass.Fset.PositionFor(f.Pos(), true).Filename
// FIXME(dh): using strings.HasPrefix isn't technically
// correct, but it should be good enough for now.
if cache != "" && strings.HasPrefix(p, cache) {
// File in the build cache of the standard Go build system
continue
}
path = p
break
}
if path == "" {
// The package only consists of generated files.
cfg := DefaultConfig
return &cfg, nil
}
dir := filepath.Dir(path)
cfg, err := Load(dir)
if err != nil {
return nil, fmt.Errorf("error loading staticcheck.conf: %s", err)
@ -136,7 +157,7 @@ func (c Config) String() string {
}
var DefaultConfig = Config{
Checks: []string{"all", "-ST1000", "-ST1003", "-ST1016"},
Checks: []string{"all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022"},
Initialisms: []string{
"ACL", "API", "ASCII", "CPU", "CSS", "DNS",
"EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID",
@ -144,20 +165,20 @@ var DefaultConfig = Config{
"SMTP", "SQL", "SSH", "TCP", "TLS", "TTL",
"UDP", "UI", "GID", "UID", "UUID", "URI",
"URL", "UTF8", "VM", "XML", "XMPP", "XSRF",
"XSS", "SIP", "RTP",
"XSS", "SIP", "RTP", "AMQP", "DB", "TS",
},
DotImportWhitelist: []string{},
HTTPStatusCodeWhitelist: []string{"200", "400", "404", "500"},
}
const configName = "staticcheck.conf"
const ConfigName = "staticcheck.conf"
func parseConfigs(dir string) ([]Config, error) {
var out []Config
// TODO(dh): consider stopping at the GOPATH/module boundary
for dir != "" {
f, err := os.Open(filepath.Join(dir, configName))
f, err := os.Open(filepath.Join(dir, ConfigName))
if os.IsNotExist(err) {
ndir := filepath.Dir(dir)
if ndir == dir {

View File

@ -6,7 +6,6 @@ type Deprecation struct {
}
var Stdlib = map[string]Deprecation{
"image/jpeg.Reader": {4, 0},
// FIXME(dh): AllowBinary isn't being detected as deprecated
// because the comment has a newline right after "Deprecated:"
"go/build.AllowBinary": {7, 7},
@ -73,40 +72,48 @@ var Stdlib = map[string]Deprecation{
// This function has no alternative, but also no purpose.
"(*crypto/rc4.Cipher).Reset": {12, 0},
"(net/http/httptest.ResponseRecorder).HeaderMap": {11, 7},
"image.ZP": {13, 0},
"image.ZR": {13, 0},
"(*debug/gosym.LineTable).LineToPC": {2, 2},
"(*debug/gosym.LineTable).PCToLine": {2, 2},
"crypto/tls.VersionSSL30": {13, 0},
"(crypto/tls.Config).NameToCertificate": {14, 14},
"(*crypto/tls.Config).BuildNameToCertificate": {14, 14},
"image/jpeg.Reader": {4, 0},
// All of these have been deprecated in favour of external libraries
"syscall.AttachLsf": {7, 0},
"syscall.DetachLsf": {7, 0},
"syscall.LsfSocket": {7, 0},
"syscall.SetLsfPromisc": {7, 0},
"syscall.LsfJump": {7, 0},
"syscall.LsfStmt": {7, 0},
"syscall.BpfStmt": {7, 0},
"syscall.BpfJump": {7, 0},
"syscall.BpfBuflen": {7, 0},
"syscall.SetBpfBuflen": {7, 0},
"syscall.BpfDatalink": {7, 0},
"syscall.SetBpfDatalink": {7, 0},
"syscall.SetBpfPromisc": {7, 0},
"syscall.FlushBpf": {7, 0},
"syscall.BpfInterface": {7, 0},
"syscall.SetBpfInterface": {7, 0},
"syscall.BpfTimeout": {7, 0},
"syscall.SetBpfTimeout": {7, 0},
"syscall.BpfStats": {7, 0},
"syscall.SetBpfImmediate": {7, 0},
"syscall.SetBpf": {7, 0},
"syscall.CheckBpfVersion": {7, 0},
"syscall.BpfHeadercmpl": {7, 0},
"syscall.SetBpfHeadercmpl": {7, 0},
"syscall.RouteRIB": {8, 0},
"syscall.RoutingMessage": {8, 0},
"syscall.RouteMessage": {8, 0},
"syscall.InterfaceMessage": {8, 0},
"syscall.InterfaceAddrMessage": {8, 0},
"syscall.ParseRoutingMessage": {8, 0},
"syscall.ParseRoutingSockaddr": {8, 0},
"InterfaceAnnounceMessage": {7, 0},
"InterfaceMulticastAddrMessage": {7, 0},
"syscall.FormatMessage": {5, 0},
"syscall.AttachLsf": {7, 0},
"syscall.DetachLsf": {7, 0},
"syscall.LsfSocket": {7, 0},
"syscall.SetLsfPromisc": {7, 0},
"syscall.LsfJump": {7, 0},
"syscall.LsfStmt": {7, 0},
"syscall.BpfStmt": {7, 0},
"syscall.BpfJump": {7, 0},
"syscall.BpfBuflen": {7, 0},
"syscall.SetBpfBuflen": {7, 0},
"syscall.BpfDatalink": {7, 0},
"syscall.SetBpfDatalink": {7, 0},
"syscall.SetBpfPromisc": {7, 0},
"syscall.FlushBpf": {7, 0},
"syscall.BpfInterface": {7, 0},
"syscall.SetBpfInterface": {7, 0},
"syscall.BpfTimeout": {7, 0},
"syscall.SetBpfTimeout": {7, 0},
"syscall.BpfStats": {7, 0},
"syscall.SetBpfImmediate": {7, 0},
"syscall.SetBpf": {7, 0},
"syscall.CheckBpfVersion": {7, 0},
"syscall.BpfHeadercmpl": {7, 0},
"syscall.SetBpfHeadercmpl": {7, 0},
"syscall.RouteRIB": {8, 0},
"syscall.RoutingMessage": {8, 0},
"syscall.RouteMessage": {8, 0},
"syscall.InterfaceMessage": {8, 0},
"syscall.InterfaceAddrMessage": {8, 0},
"syscall.ParseRoutingMessage": {8, 0},
"syscall.ParseRoutingSockaddr": {8, 0},
"syscall.InterfaceAnnounceMessage": {7, 0},
"syscall.InterfaceMulticastAddrMessage": {7, 0},
"syscall.FormatMessage": {5, 0},
}

67
vendor/honnef.co/go/tools/edit/edit.go vendored Normal file
View File

@ -0,0 +1,67 @@
package edit
import (
"bytes"
"go/ast"
"go/format"
"go/token"
"golang.org/x/tools/go/analysis"
"honnef.co/go/tools/pattern"
)
type Ranger interface {
Pos() token.Pos
End() token.Pos
}
type Range [2]token.Pos
func (r Range) Pos() token.Pos { return r[0] }
func (r Range) End() token.Pos { return r[1] }
func ReplaceWithString(fset *token.FileSet, old Ranger, new string) analysis.TextEdit {
return analysis.TextEdit{
Pos: old.Pos(),
End: old.End(),
NewText: []byte(new),
}
}
func ReplaceWithNode(fset *token.FileSet, old Ranger, new ast.Node) analysis.TextEdit {
buf := &bytes.Buffer{}
if err := format.Node(buf, fset, new); err != nil {
panic("internal error: " + err.Error())
}
return analysis.TextEdit{
Pos: old.Pos(),
End: old.End(),
NewText: buf.Bytes(),
}
}
func ReplaceWithPattern(pass *analysis.Pass, after pattern.Pattern, state pattern.State, node Ranger) analysis.TextEdit {
r := pattern.NodeToAST(after.Root, state)
buf := &bytes.Buffer{}
format.Node(buf, pass.Fset, r)
return analysis.TextEdit{
Pos: node.Pos(),
End: node.End(),
NewText: buf.Bytes(),
}
}
func Delete(old Ranger) analysis.TextEdit {
return analysis.TextEdit{
Pos: old.Pos(),
End: old.End(),
NewText: nil,
}
}
func Fix(msg string, edits ...analysis.TextEdit) analysis.SuggestedFix {
return analysis.SuggestedFix{
Message: msg,
TextEdits: edits,
}
}

View File

@ -19,6 +19,7 @@ const (
Goyacc
Cgo
Stringer
ProtocGenGo
)
var (
@ -51,10 +52,16 @@ func isGenerated(path string) (Generator, bool) {
return Goyacc, true
case "by cmd/cgo;":
return Cgo, true
case "by protoc-gen-go.":
return ProtocGenGo, true
}
if strings.HasPrefix(text, `by "stringer `) {
return Stringer, true
}
if strings.HasPrefix(text, `by goyacc `) {
return Goyacc, true
}
return Unknown, true
}
if bytes.Equal(s, oldCgo) {

View File

@ -1,14 +1,13 @@
package facts
import (
"go/token"
"go/types"
"reflect"
"golang.org/x/tools/go/analysis"
"honnef.co/go/tools/functions"
"honnef.co/go/tools/internal/passes/buildssa"
"honnef.co/go/tools/ssa"
"honnef.co/go/tools/internal/passes/buildir"
"honnef.co/go/tools/ir"
)
type IsPure struct{}
@ -22,7 +21,7 @@ var Purity = &analysis.Analyzer{
Name: "fact_purity",
Doc: "Mark pure functions",
Run: purity,
Requires: []*analysis.Analyzer{buildssa.Analyzer},
Requires: []*analysis.Analyzer{buildir.Analyzer},
FactTypes: []analysis.Fact{(*IsPure)(nil)},
ResultType: reflect.TypeOf(PurityResult{}),
}
@ -56,65 +55,68 @@ var pureStdlib = map[string]struct{}{
}
func purity(pass *analysis.Pass) (interface{}, error) {
seen := map[*ssa.Function]struct{}{}
ssapkg := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA).Pkg
var check func(ssafn *ssa.Function) (ret bool)
check = func(ssafn *ssa.Function) (ret bool) {
if ssafn.Object() == nil {
seen := map[*ir.Function]struct{}{}
irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg
var check func(fn *ir.Function) (ret bool)
check = func(fn *ir.Function) (ret bool) {
if fn.Object() == nil {
// TODO(dh): support closures
return false
}
if pass.ImportObjectFact(ssafn.Object(), new(IsPure)) {
if pass.ImportObjectFact(fn.Object(), new(IsPure)) {
return true
}
if ssafn.Pkg != ssapkg {
if fn.Pkg != irpkg {
// Function is in another package but wasn't marked as
// pure, ergo it isn't pure
return false
}
// Break recursion
if _, ok := seen[ssafn]; ok {
if _, ok := seen[fn]; ok {
return false
}
seen[ssafn] = struct{}{}
seen[fn] = struct{}{}
defer func() {
if ret {
pass.ExportObjectFact(ssafn.Object(), &IsPure{})
pass.ExportObjectFact(fn.Object(), &IsPure{})
}
}()
if functions.IsStub(ssafn) {
if functions.IsStub(fn) {
return false
}
if _, ok := pureStdlib[ssafn.Object().(*types.Func).FullName()]; ok {
if _, ok := pureStdlib[fn.Object().(*types.Func).FullName()]; ok {
return true
}
if ssafn.Signature.Results().Len() == 0 {
if fn.Signature.Results().Len() == 0 {
// A function with no return values is empty or is doing some
// work we cannot see (for example because of build tags);
// don't consider it pure.
return false
}
for _, param := range ssafn.Params {
for _, param := range fn.Params {
// TODO(dh): this may not be strictly correct. pure code
// can, to an extent, operate on non-basic types.
if _, ok := param.Type().Underlying().(*types.Basic); !ok {
return false
}
}
if ssafn.Blocks == nil {
// Don't consider external functions pure.
if fn.Blocks == nil {
return false
}
checkCall := func(common *ssa.CallCommon) bool {
checkCall := func(common *ir.CallCommon) bool {
if common.IsInvoke() {
return false
}
builtin, ok := common.Value.(*ssa.Builtin)
builtin, ok := common.Value.(*ir.Builtin)
if !ok {
if common.StaticCallee() != ssafn {
if common.StaticCallee() != fn {
if common.StaticCallee() == nil {
return false
}
@ -124,47 +126,47 @@ func purity(pass *analysis.Pass) (interface{}, error) {
}
} else {
switch builtin.Name() {
case "len", "cap", "make", "new":
case "len", "cap":
default:
return false
}
}
return true
}
for _, b := range ssafn.Blocks {
for _, b := range fn.Blocks {
for _, ins := range b.Instrs {
switch ins := ins.(type) {
case *ssa.Call:
case *ir.Call:
if !checkCall(ins.Common()) {
return false
}
case *ssa.Defer:
case *ir.Defer:
if !checkCall(&ins.Call) {
return false
}
case *ssa.Select:
case *ir.Select:
return false
case *ssa.Send:
case *ir.Send:
return false
case *ssa.Go:
case *ir.Go:
return false
case *ssa.Panic:
case *ir.Panic:
return false
case *ssa.Store:
case *ir.Store:
return false
case *ssa.FieldAddr:
case *ir.FieldAddr:
return false
case *ir.Alloc:
return false
case *ir.Load:
return false
case *ssa.UnOp:
if ins.Op == token.MUL || ins.Op == token.AND {
return false
}
}
}
}
return true
}
for _, ssafn := range pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA).SrcFuncs {
check(ssafn)
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
check(fn)
}
out := PurityResult{}

View File

@ -1,10 +1,10 @@
package functions
import "honnef.co/go/tools/ssa"
import "honnef.co/go/tools/ir"
type Loop struct{ ssa.BlockSet }
type Loop struct{ *ir.BlockSet }
func FindLoops(fn *ssa.Function) []Loop {
func FindLoops(fn *ir.Function) []Loop {
if fn.Blocks == nil {
return nil
}
@ -18,12 +18,12 @@ func FindLoops(fn *ssa.Function) []Loop {
// n is a back-edge to h
// h is the loop header
if n == h {
set := Loop{}
set := Loop{ir.NewBlockSet(len(fn.Blocks))}
set.Add(n)
sets = append(sets, set)
continue
}
set := Loop{}
set := Loop{ir.NewBlockSet(len(fn.Blocks))}
set.Add(h)
set.Add(n)
for _, b := range allPredsBut(n, h, nil) {
@ -35,7 +35,7 @@ func FindLoops(fn *ssa.Function) []Loop {
return sets
}
func allPredsBut(b, but *ssa.BasicBlock, list []*ssa.BasicBlock) []*ssa.BasicBlock {
func allPredsBut(b, but *ir.BasicBlock, list []*ir.BasicBlock) []*ir.BasicBlock {
outer:
for _, pred := range b.Preds {
if pred == but {

View File

@ -1,46 +0,0 @@
package functions
import (
"honnef.co/go/tools/ssa"
)
func filterDebug(instr []ssa.Instruction) []ssa.Instruction {
var out []ssa.Instruction
for _, ins := range instr {
if _, ok := ins.(*ssa.DebugRef); !ok {
out = append(out, ins)
}
}
return out
}
// IsStub reports whether a function is a stub. A function is
// considered a stub if it has no instructions or exactly one
// instruction, which must be either returning only constant values or
// a panic.
func IsStub(fn *ssa.Function) bool {
if len(fn.Blocks) == 0 {
return true
}
if len(fn.Blocks) > 1 {
return false
}
instrs := filterDebug(fn.Blocks[0].Instrs)
if len(instrs) != 1 {
return false
}
switch instrs[0].(type) {
case *ssa.Return:
// Since this is the only instruction, the return value must
// be a constant. We consider all constants as stubs, not just
// the zero value. This does not, unfortunately, cover zero
// initialised structs, as these cause additional
// instructions.
return true
case *ssa.Panic:
return true
default:
return false
}
}

View File

@ -0,0 +1,32 @@
package functions
import (
"honnef.co/go/tools/ir"
)
// IsStub reports whether a function is a stub. A function is
// considered a stub if it has no instructions or if all it does is
// return a constant value.
func IsStub(fn *ir.Function) bool {
for _, b := range fn.Blocks {
for _, instr := range b.Instrs {
switch instr.(type) {
case *ir.Const:
// const naturally has no side-effects
case *ir.Panic:
// panic is a stub if it only uses constants
case *ir.Return:
// return is a stub if it only uses constants
case *ir.DebugRef:
case *ir.Jump:
// if there are no disallowed instructions, then we're
// only jumping to the exit block (or possibly
// somewhere else that's stubby?)
default:
// all other instructions are assumed to do actual work
return false
}
}
}
return true
}

View File

@ -1,11 +1,15 @@
package functions
import "honnef.co/go/tools/ssa"
import (
"go/types"
"honnef.co/go/tools/ir"
)
// Terminates reports whether fn is supposed to return, that is if it
// has at least one theoretic path that returns from the function.
// Explicit panics do not count as terminating.
func Terminates(fn *ssa.Function) bool {
func Terminates(fn *ir.Function) bool {
if fn.Blocks == nil {
// assuming that a function terminates is the conservative
// choice
@ -13,11 +17,53 @@ func Terminates(fn *ssa.Function) bool {
}
for _, block := range fn.Blocks {
if len(block.Instrs) == 0 {
continue
}
if _, ok := block.Instrs[len(block.Instrs)-1].(*ssa.Return); ok {
return true
if _, ok := block.Control().(*ir.Return); ok {
if len(block.Preds) == 0 {
return true
}
for _, pred := range block.Preds {
switch ctrl := pred.Control().(type) {
case *ir.Panic:
// explicit panics do not count as terminating
case *ir.If:
// Check if we got here by receiving from a closed
// time.Tick channel this cannot happen at
// runtime and thus doesn't constitute termination
iff := ctrl
if !ok {
return true
}
ex, ok := iff.Cond.(*ir.Extract)
if !ok {
return true
}
if ex.Index != 1 {
return true
}
recv, ok := ex.Tuple.(*ir.Recv)
if !ok {
return true
}
call, ok := recv.Chan.(*ir.Call)
if !ok {
return true
}
fn, ok := call.Common().Value.(*ir.Function)
if !ok {
return true
}
fn2, ok := fn.Object().(*types.Func)
if !ok {
return true
}
if fn2.FullName() != "time.Tick" {
return true
}
default:
// we've reached the exit block
return true
}
}
}
}
return false

View File

@ -177,7 +177,7 @@ func (c *Cache) get(id ActionID) (Entry, error) {
i++
}
tm, err := strconv.ParseInt(string(etime[i:]), 10, 64)
if err != nil || size < 0 {
if err != nil || tm < 0 {
return missing()
}
@ -265,7 +265,7 @@ func (c *Cache) Trim() {
// We maintain in dir/trim.txt the time of the last completed cache trim.
// If the cache has been trimmed recently enough, do nothing.
// This is the common case.
data, _ := ioutil.ReadFile(filepath.Join(c.dir, "trim.txt"))
data, _ := renameio.ReadFile(filepath.Join(c.dir, "trim.txt"))
t, err := strconv.ParseInt(strings.TrimSpace(string(data)), 10, 64)
if err == nil && now.Sub(time.Unix(t, 0)) < trimInterval {
return
@ -282,7 +282,7 @@ func (c *Cache) Trim() {
// Ignore errors from here: if we don't write the complete timestamp, the
// cache will appear older than it is, and we'll trim it again next time.
renameio.WriteFile(filepath.Join(c.dir, "trim.txt"), []byte(fmt.Sprintf("%d", now.Unix())))
renameio.WriteFile(filepath.Join(c.dir, "trim.txt"), []byte(fmt.Sprintf("%d", now.Unix())), 0666)
}
// trimSubdir trims a single cache subdirectory.
@ -326,7 +326,8 @@ func (c *Cache) putIndexEntry(id ActionID, out OutputID, size int64, allowVerify
// in verify mode we are double-checking that the cache entries
// are entirely reproducible. As just noted, this may be unrealistic
// in some cases but the check is also useful for shaking out real bugs.
entry := []byte(fmt.Sprintf("v1 %x %x %20d %20d\n", id, out, size, time.Now().UnixNano()))
entry := fmt.Sprintf("v1 %x %x %20d %20d\n", id, out, size, time.Now().UnixNano())
if verify && allowVerify {
old, err := c.get(id)
if err == nil && (old.OutputID != out || old.Size != size) {
@ -336,7 +337,28 @@ func (c *Cache) putIndexEntry(id ActionID, out OutputID, size int64, allowVerify
}
}
file := c.fileName(id, "a")
if err := ioutil.WriteFile(file, entry, 0666); err != nil {
// Copy file to cache directory.
mode := os.O_WRONLY | os.O_CREATE
f, err := os.OpenFile(file, mode, 0666)
if err != nil {
return err
}
_, err = f.WriteString(entry)
if err == nil {
// Truncate the file only *after* writing it.
// (This should be a no-op, but truncate just in case of previous corruption.)
//
// This differs from ioutil.WriteFile, which truncates to 0 *before* writing
// via os.O_TRUNC. Truncating only after writing ensures that a second write
// of the same content to the same file is idempotent, and does not — even
// temporarily! — undo the effect of the first write.
err = f.Truncate(int64(len(entry)))
}
if closeErr := f.Close(); err == nil {
err = closeErr
}
if err != nil {
// TODO(bcmills): This Remove potentially races with another go command writing to file.
// Can we eliminate it?
os.Remove(file)

View File

@ -0,0 +1,113 @@
// 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 buildir defines an Analyzer that constructs the IR
// of an error-free package and returns the set of all
// functions within it. It does not report any diagnostics itself but
// may be used as an input to other analyzers.
//
// THIS INTERFACE IS EXPERIMENTAL AND MAY BE SUBJECT TO INCOMPATIBLE CHANGE.
package buildir
import (
"go/ast"
"go/types"
"reflect"
"golang.org/x/tools/go/analysis"
"honnef.co/go/tools/ir"
)
type willExit struct{}
type willUnwind struct{}
func (*willExit) AFact() {}
func (*willUnwind) AFact() {}
var Analyzer = &analysis.Analyzer{
Name: "buildir",
Doc: "build IR for later passes",
Run: run,
ResultType: reflect.TypeOf(new(IR)),
FactTypes: []analysis.Fact{new(willExit), new(willUnwind)},
}
// IR provides intermediate representation for all the
// non-blank source functions in the current package.
type IR struct {
Pkg *ir.Package
SrcFuncs []*ir.Function
}
func run(pass *analysis.Pass) (interface{}, error) {
// Plundered from ssautil.BuildPackage.
// We must create a new Program for each Package because the
// analysis API provides no place to hang a Program shared by
// all Packages. Consequently, IR Packages and Functions do not
// have a canonical representation across an analysis session of
// multiple packages. This is unlikely to be a problem in
// practice because the analysis API essentially forces all
// packages to be analysed independently, so any given call to
// Analysis.Run on a package will see only IR objects belonging
// to a single Program.
mode := ir.GlobalDebug
prog := ir.NewProgram(pass.Fset, mode)
// Create IR 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
irpkg := prog.CreatePackage(p, nil, nil, true)
for _, fn := range irpkg.Functions {
if ast.IsExported(fn.Name()) {
var exit willExit
var unwind willUnwind
if pass.ImportObjectFact(fn.Object(), &exit) {
fn.WillExit = true
}
if pass.ImportObjectFact(fn.Object(), &unwind) {
fn.WillUnwind = true
}
}
}
createAll(p.Imports())
}
}
}
createAll(pass.Pkg.Imports())
// Create and build the primary package.
irpkg := prog.CreatePackage(pass.Pkg, pass.Files, pass.TypesInfo, false)
irpkg.Build()
// Compute list of source functions, including literals,
// in source order.
var addAnons func(f *ir.Function)
funcs := make([]*ir.Function, len(irpkg.Functions))
copy(funcs, irpkg.Functions)
addAnons = func(f *ir.Function) {
for _, anon := range f.AnonFuncs {
funcs = append(funcs, anon)
addAnons(anon)
}
}
for _, fn := range irpkg.Functions {
addAnons(fn)
if fn.WillExit {
pass.ExportObjectFact(fn.Object(), new(willExit))
}
if fn.WillUnwind {
pass.ExportObjectFact(fn.Object(), new(willUnwind))
}
}
return &IR{Pkg: irpkg, SrcFuncs: funcs}, nil
}

View File

@ -1,116 +0,0 @@
// 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 buildssa defines an Analyzer that constructs the SSA
// representation of an error-free package and returns the set of all
// functions within it. It does not report any diagnostics itself but
// may be used as an input to other analyzers.
//
// THIS INTERFACE IS EXPERIMENTAL AND MAY BE SUBJECT TO INCOMPATIBLE CHANGE.
package buildssa
import (
"go/ast"
"go/types"
"reflect"
"golang.org/x/tools/go/analysis"
"honnef.co/go/tools/ssa"
)
var Analyzer = &analysis.Analyzer{
Name: "buildssa",
Doc: "build SSA-form IR for later passes",
Run: run,
ResultType: reflect.TypeOf(new(SSA)),
}
// SSA provides SSA-form intermediate representation for all the
// non-blank source functions in the current package.
type SSA struct {
Pkg *ssa.Package
SrcFuncs []*ssa.Function
}
func run(pass *analysis.Pass) (interface{}, error) {
// Plundered from ssautil.BuildPackage.
// We must create a new Program for each Package because the
// analysis API provides no place to hang a Program shared by
// all Packages. Consequently, SSA Packages and Functions do not
// have a canonical representation across an analysis session of
// multiple packages. This is unlikely to be a problem in
// practice because the analysis API essentially forces all
// packages to be analysed independently, so any given call to
// Analysis.Run on a package will see only SSA objects belonging
// to a single Program.
mode := ssa.GlobalDebug
prog := ssa.NewProgram(pass.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(pass.Pkg.Imports())
// Create and build the primary package.
ssapkg := prog.CreatePackage(pass.Pkg, pass.Files, pass.TypesInfo, false)
ssapkg.Build()
// Compute list of source functions, including literals,
// in source order.
var funcs []*ssa.Function
var addAnons func(f *ssa.Function)
addAnons = func(f *ssa.Function) {
funcs = append(funcs, f)
for _, anon := range f.AnonFuncs {
addAnons(anon)
}
}
addAnons(ssapkg.Members["init"].(*ssa.Function))
for _, f := range pass.Files {
for _, decl := range f.Decls {
if fdecl, ok := decl.(*ast.FuncDecl); ok {
// SSA will not build a Function
// for a FuncDecl named blank.
// That's arguably too strict but
// relaxing it would break uniqueness of
// names of package members.
if fdecl.Name.Name == "_" {
continue
}
// (init functions have distinct Func
// objects named "init" and distinct
// ssa.Functions named "init#1", ...)
fn := pass.TypesInfo.Defs[fdecl.Name].(*types.Func)
if fn == nil {
panic(fn)
}
f := ssapkg.Prog.FuncValue(fn)
if f == nil {
panic(fn)
}
addAnons(f)
}
}
}
return &SSA{Pkg: ssapkg, SrcFuncs: funcs}, nil
}

View File

@ -8,15 +8,15 @@ package renameio
import (
"bytes"
"io"
"io/ioutil"
"math/rand"
"os"
"path/filepath"
"runtime"
"strings"
"time"
"strconv"
"honnef.co/go/tools/internal/robustio"
)
const patternSuffix = "*.tmp"
const patternSuffix = ".tmp"
// Pattern returns a glob pattern that matches the unrenamed temporary files
// created when writing to filename.
@ -29,14 +29,14 @@ func Pattern(filename string) string {
// final name.
//
// That ensures that the final location, if it exists, is always a complete file.
func WriteFile(filename string, data []byte) (err error) {
return WriteToFile(filename, bytes.NewReader(data))
func WriteFile(filename string, data []byte, perm os.FileMode) (err error) {
return WriteToFile(filename, bytes.NewReader(data), perm)
}
// WriteToFile is a variant of WriteFile that accepts the data as an io.Reader
// instead of a slice.
func WriteToFile(filename string, data io.Reader) (err error) {
f, err := ioutil.TempFile(filepath.Dir(filename), filepath.Base(filename)+patternSuffix)
func WriteToFile(filename string, data io.Reader, perm os.FileMode) (err error) {
f, err := tempFile(filepath.Dir(filename), filepath.Base(filename), perm)
if err != nil {
return err
}
@ -63,21 +63,31 @@ func WriteToFile(filename string, data io.Reader) (err error) {
return err
}
var start time.Time
for {
err := os.Rename(f.Name(), filename)
if err == nil || runtime.GOOS != "windows" || !strings.HasSuffix(err.Error(), "Access is denied.") {
return err
}
// Windows seems to occasionally trigger spurious "Access is denied" errors
// here (see golang.org/issue/31247). We're not sure why. It's probably
// worth a little extra latency to avoid propagating the spurious errors.
if start.IsZero() {
start = time.Now()
} else if time.Since(start) >= 500*time.Millisecond {
return err
}
time.Sleep(5 * time.Millisecond)
}
return robustio.Rename(f.Name(), filename)
}
// tempFile creates a new temporary file with given permission bits.
func tempFile(dir, prefix string, perm os.FileMode) (f *os.File, err error) {
for i := 0; i < 10000; i++ {
name := filepath.Join(dir, prefix+strconv.Itoa(rand.Intn(1000000000))+patternSuffix)
f, err = os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, perm)
if os.IsExist(err) {
continue
}
break
}
return
}
// ReadFile is like ioutil.ReadFile, but on Windows retries spurious errors that
// may occur if the file is concurrently replaced.
//
// Errors are classified heuristically and retries are bounded, so even this
// function may occasionally return a spurious error on Windows.
// If so, the error will likely wrap one of:
// - syscall.ERROR_ACCESS_DENIED
// - syscall.ERROR_FILE_NOT_FOUND
// - internal/syscall/windows.ERROR_SHARING_VIOLATION
func ReadFile(filename string) ([]byte, error) {
return robustio.ReadFile(filename)
}

View File

@ -0,0 +1,53 @@
// 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 robustio wraps I/O functions that are prone to failure on Windows,
// transparently retrying errors up to an arbitrary timeout.
//
// Errors are classified heuristically and retries are bounded, so the functions
// in this package do not completely eliminate spurious errors. However, they do
// significantly reduce the rate of failure in practice.
//
// If so, the error will likely wrap one of:
// The functions in this package do not completely eliminate spurious errors,
// but substantially reduce their rate of occurrence in practice.
package robustio
// Rename is like os.Rename, but on Windows retries errors that may occur if the
// file is concurrently read or overwritten.
//
// (See golang.org/issue/31247 and golang.org/issue/32188.)
func Rename(oldpath, newpath string) error {
return rename(oldpath, newpath)
}
// ReadFile is like ioutil.ReadFile, but on Windows retries errors that may
// occur if the file is concurrently replaced.
//
// (See golang.org/issue/31247 and golang.org/issue/32188.)
func ReadFile(filename string) ([]byte, error) {
return readFile(filename)
}
// RemoveAll is like os.RemoveAll, but on Windows retries errors that may occur
// if an executable file in the directory has recently been executed.
//
// (See golang.org/issue/19491.)
func RemoveAll(path string) error {
return removeAll(path)
}
// IsEphemeralError reports whether err is one of the errors that the functions
// in this package attempt to mitigate.
//
// Errors considered ephemeral include:
// - syscall.ERROR_ACCESS_DENIED
// - syscall.ERROR_FILE_NOT_FOUND
// - internal/syscall/windows.ERROR_SHARING_VIOLATION
//
// This set may be expanded in the future; programs must not rely on the
// non-ephemerality of any given error.
func IsEphemeralError(err error) bool {
return isEphemeralError(err)
}

View File

@ -0,0 +1,29 @@
// 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 robustio
import (
"os"
"syscall"
)
const errFileNotFound = syscall.ENOENT
// isEphemeralError returns true if err may be resolved by waiting.
func isEphemeralError(err error) bool {
switch werr := err.(type) {
case *os.PathError:
err = werr.Err
case *os.LinkError:
err = werr.Err
case *os.SyscallError:
err = werr.Err
}
if errno, ok := err.(syscall.Errno); ok {
return errno == errFileNotFound
}
return false
}

View File

@ -0,0 +1,93 @@
// 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.
// +build windows darwin
package robustio
import (
"io/ioutil"
"math/rand"
"os"
"syscall"
"time"
)
const arbitraryTimeout = 500 * time.Millisecond
const ERROR_SHARING_VIOLATION = 32
// retry retries ephemeral errors from f up to an arbitrary timeout
// to work around filesystem flakiness on Windows and Darwin.
func retry(f func() (err error, mayRetry bool)) error {
var (
bestErr error
lowestErrno syscall.Errno
start time.Time
nextSleep time.Duration = 1 * time.Millisecond
)
for {
err, mayRetry := f()
if err == nil || !mayRetry {
return err
}
if errno, ok := err.(syscall.Errno); ok && (lowestErrno == 0 || errno < lowestErrno) {
bestErr = err
lowestErrno = errno
} else if bestErr == nil {
bestErr = err
}
if start.IsZero() {
start = time.Now()
} else if d := time.Since(start) + nextSleep; d >= arbitraryTimeout {
break
}
time.Sleep(nextSleep)
nextSleep += time.Duration(rand.Int63n(int64(nextSleep)))
}
return bestErr
}
// rename is like os.Rename, but retries ephemeral errors.
//
// On windows it wraps os.Rename, which (as of 2019-06-04) uses MoveFileEx with
// MOVEFILE_REPLACE_EXISTING.
//
// Windows also provides a different system call, ReplaceFile,
// that provides similar semantics, but perhaps preserves more metadata. (The
// documentation on the differences between the two is very sparse.)
//
// Empirical error rates with MoveFileEx are lower under modest concurrency, so
// for now we're sticking with what the os package already provides.
func rename(oldpath, newpath string) (err error) {
return retry(func() (err error, mayRetry bool) {
err = os.Rename(oldpath, newpath)
return err, isEphemeralError(err)
})
}
// readFile is like ioutil.ReadFile, but retries ephemeral errors.
func readFile(filename string) ([]byte, error) {
var b []byte
err := retry(func() (err error, mayRetry bool) {
b, err = ioutil.ReadFile(filename)
// Unlike in rename, we do not retry errFileNotFound here: it can occur
// as a spurious error, but the file may also genuinely not exist, so the
// increase in robustness is probably not worth the extra latency.
return err, isEphemeralError(err) && err != errFileNotFound
})
return b, err
}
func removeAll(path string) error {
return retry(func() (err error, mayRetry bool) {
err = os.RemoveAll(path)
return err, isEphemeralError(err)
})
}

View File

@ -0,0 +1,28 @@
// 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.
//+build !windows,!darwin
package robustio
import (
"io/ioutil"
"os"
)
func rename(oldpath, newpath string) error {
return os.Rename(oldpath, newpath)
}
func readFile(filename string) ([]byte, error) {
return ioutil.ReadFile(filename)
}
func removeAll(path string) error {
return os.RemoveAll(path)
}
func isEphemeralError(err error) bool {
return false
}

View File

@ -0,0 +1,33 @@
// 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 robustio
import (
"os"
"syscall"
)
const errFileNotFound = syscall.ERROR_FILE_NOT_FOUND
// isEphemeralError returns true if err may be resolved by waiting.
func isEphemeralError(err error) bool {
switch werr := err.(type) {
case *os.PathError:
err = werr.Err
case *os.LinkError:
err = werr.Err
case *os.SyscallError:
err = werr.Err
}
if errno, ok := err.(syscall.Errno); ok {
switch errno {
case syscall.ERROR_ACCESS_DENIED,
syscall.ERROR_FILE_NOT_FOUND,
ERROR_SHARING_VIOLATION:
return true
}
}
return false
}

View File

@ -5,23 +5,24 @@ import (
"go/types"
"golang.org/x/tools/go/analysis"
"honnef.co/go/tools/internal/passes/buildssa"
"honnef.co/go/tools/code"
"honnef.co/go/tools/internal/passes/buildir"
"honnef.co/go/tools/ir"
. "honnef.co/go/tools/lint/lintdsl"
"honnef.co/go/tools/ssa"
)
func CheckRangeStringRunes(pass *analysis.Pass) (interface{}, error) {
for _, ssafn := range pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA).SrcFuncs {
fn := func(node ast.Node) bool {
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
cb := func(node ast.Node) bool {
rng, ok := node.(*ast.RangeStmt)
if !ok || !IsBlank(rng.Key) {
if !ok || !code.IsBlank(rng.Key) {
return true
}
v, _ := ssafn.ValueForExpr(rng.X)
v, _ := fn.ValueForExpr(rng.X)
// Check that we're converting from string to []rune
val, _ := v.(*ssa.Convert)
val, _ := v.(*ir.Convert)
if val == nil {
return true
}
@ -47,13 +48,13 @@ func CheckRangeStringRunes(pass *analysis.Pass) (interface{}, error) {
// Expect two refs: one for obtaining the length of the slice,
// one for accessing the elements
if len(FilterDebug(*refs)) != 2 {
if len(code.FilterDebug(*refs)) != 2 {
// TODO(dh): right now, we check that only one place
// refers to our slice. This will miss cases such as
// ranging over the slice twice. Ideally, we'd ensure that
// the slice is only used for ranging over (without
// accessing the key), but that is harder to do because in
// SSA form, ranging over a slice looks like an ordinary
// IR form, ranging over a slice looks like an ordinary
// loop with index increments and slice accesses. We'd
// have to look at the associated AST node to check that
// it's a range statement.
@ -64,7 +65,7 @@ func CheckRangeStringRunes(pass *analysis.Pass) (interface{}, error) {
return true
}
Inspect(ssafn.Syntax(), fn)
Inspect(fn.Source(), cb)
}
return nil, nil
}

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
package ir
// Simple block optimizations to simplify the control flow graph.
@ -21,35 +21,34 @@ const debugBlockOpt = false
// markReachable sets Index=-1 for all blocks reachable from b.
func markReachable(b *BasicBlock) {
b.Index = -1
b.gaps = -1
for _, succ := range b.Succs {
if succ.Index == 0 {
if succ.gaps == 0 {
markReachable(succ)
}
}
}
func DeleteUnreachableBlocks(f *Function) {
deleteUnreachableBlocks(f)
}
// 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.
// We borrow b.gaps temporarily as the mark bit.
for _, b := range f.Blocks {
b.Index = white
b.gaps = white
}
markReachable(f.Blocks[0])
if f.Recover != nil {
markReachable(f.Recover)
}
// In SSI form, we need the exit to be reachable for correct
// post-dominance information. In original form, however, we
// cannot unconditionally mark it reachable because we won't
// be adding fake edges, and this breaks the calculation of
// dominance information.
markReachable(f.Exit)
for i, b := range f.Blocks {
if b.Index == white {
if b.gaps == white {
for _, c := range b.Succs {
if c.Index == black {
if c.gaps == black {
c.removePred(b) // delete white->black edge
}
}
@ -73,6 +72,13 @@ func jumpThreading(f *Function, b *BasicBlock) bool {
if b.Instrs == nil {
return false
}
for _, pred := range b.Preds {
switch pred.Control().(type) {
case *ConstantSwitch:
// don't optimize away the head blocks of switch statements
return false
}
}
if _, ok := b.Instrs[0].(*Jump); !ok {
return false // not just a jump
}
@ -117,10 +123,17 @@ func fuseBlocks(f *Function, a *BasicBlock) bool {
if len(a.Succs) != 1 {
return false
}
if a.Succs[0] == f.Exit {
return false
}
b := a.Succs[0]
if len(b.Preds) != 1 {
return false
}
if _, ok := a.Instrs[len(a.Instrs)-1].(*Panic); ok {
// panics aren't simple jumps, they have side effects.
return false
}
// Degenerate &&/|| ops may result in a straight-line CFG
// containing φ-nodes. (Ideally we'd replace such them with
@ -151,15 +164,16 @@ func fuseBlocks(f *Function, a *BasicBlock) bool {
return true
}
func OptimizeBlocks(f *Function) {
optimizeBlocks(f)
}
// optimizeBlocks() performs some simple block optimizations on a
// completed function: dead block elimination, block fusion, jump
// threading.
//
func optimizeBlocks(f *Function) {
if debugBlockOpt {
f.WriteTo(os.Stderr)
mustSanityCheck(f, nil)
}
deleteUnreachableBlocks(f)
// Loop until no further progress.

View File

@ -2,14 +2,13 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
package ir
// This file defines the Const SSA value type.
import (
"fmt"
"go/constant"
"go/token"
"go/types"
"strconv"
)
@ -18,7 +17,12 @@ import (
// val must be valid according to the specification of Const.Value.
//
func NewConst(val constant.Value, typ types.Type) *Const {
return &Const{typ, val}
return &Const{
register: register{
typ: typ,
},
Value: val,
}
}
// intConst returns an 'int' constant that evaluates to i.
@ -71,43 +75,25 @@ func zeroConst(t types.Type) *Const {
}
func (c *Const) RelString(from *types.Package) string {
var s string
var p string
if c.Value == nil {
s = "nil"
p = "nil"
} else if c.Value.Kind() == constant.String {
s = constant.StringVal(c.Value)
v := 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
if len(v) > max {
v = v[:max-3] + "..." // abbreviate
}
s = strconv.Quote(s)
p = strconv.Quote(v)
} else {
s = c.Value.String()
p = c.Value.String()
}
return s + ":" + relType(c.Type(), from)
}
func (c *Const) Name() string {
return c.RelString(nil)
return fmt.Sprintf("Const <%s> {%s}", relType(c.Type(), from), p)
}
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
return c.RelString(c.Parent().pkg())
}
// IsNil returns true if this constant represents a typed or untyped nil value.
@ -115,8 +101,6 @@ func (c *Const) IsNil() bool {
return c.Value == nil
}
// TODO(adonovan): move everything below into honnef.co/go/tools/ssa/interp.
// Int64 returns the numeric value of this constant truncated to fit
// a signed 64-bit integer.
//

View File

@ -2,9 +2,9 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
package ir
// This file implements the CREATE phase of SSA construction.
// This file implements the CREATE phase of IR construction.
// See builder.go for explanation.
import (
@ -18,9 +18,9 @@ import (
"golang.org/x/tools/go/types/typeutil"
)
// NewProgram returns a new SSA Program.
// NewProgram returns a new IR Program.
//
// mode controls diagnostics and checking during SSA construction.
// mode controls diagnostics and checking during IR construction.
//
func NewProgram(fset *token.FileSet, mode BuilderMode) *Program {
prog := &Program{
@ -75,7 +75,6 @@ func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node) {
name: name,
object: obj,
typ: types.NewPointer(obj.Type()), // address
pos: obj.Pos(),
}
pkg.values[obj] = g
pkg.Members[name] = g
@ -90,16 +89,20 @@ func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node) {
name: name,
object: obj,
Signature: sig,
syntax: syntax,
pos: obj.Pos(),
Pkg: pkg,
Prog: pkg.Prog,
}
fn.source = syntax
fn.initHTML(pkg.printFunc)
if syntax == nil {
fn.Synthetic = "loaded from gc object file"
} else {
fn.functionBody = new(functionBody)
}
pkg.values[obj] = fn
pkg.Functions = append(pkg.Functions, fn)
if sig.Recv() == nil {
pkg.Members[name] = fn // package-level function
}
@ -152,35 +155,39 @@ func membersFromDecl(pkg *Package, decl ast.Decl) {
}
}
// CreatePackage constructs and returns an SSA Package from the
// CreatePackage constructs and returns an IR 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
// The real work of building IR 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 {
p := &Package{
Prog: prog,
Members: make(map[string]Member),
values: make(map[types.Object]Value),
Pkg: pkg,
info: info, // transient (CREATE and BUILD phases)
files: files, // transient (CREATE and BUILD phases)
Prog: prog,
Members: make(map[string]Member),
values: make(map[types.Object]Value),
Pkg: pkg,
info: info, // transient (CREATE and BUILD phases)
files: files, // transient (CREATE and BUILD phases)
printFunc: prog.PrintFunc,
}
// Add init() function.
p.init = &Function{
name: "init",
Signature: new(types.Signature),
Synthetic: "package initializer",
Pkg: p,
Prog: prog,
name: "init",
Signature: new(types.Signature),
Synthetic: "package initializer",
Pkg: p,
Prog: prog,
functionBody: new(functionBody),
}
p.init.initHTML(prog.PrintFunc)
p.Members[p.init.name] = p.init
p.Functions = append(p.Functions, p.init)
// CREATE phase.
// Allocate all package members: vars, funcs, consts and types.
@ -209,15 +216,13 @@ func (prog *Program) CreatePackage(pkg *types.Package, files []*ast.File, info *
}
}
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
// 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)
@ -260,10 +265,10 @@ func (prog *Program) AllPackages() []*Package {
//
// 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
// types.Package.Path values are unique within the ir.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.
// IR doesn't really need a string-keyed map of packages.
//
func (prog *Program) ImportedPackage(path string) *Package {
return prog.imported[path]

View File

@ -2,36 +2,34 @@
// 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
// Package ir defines a representation of the elements of Go programs
// (packages, types, functions, variables and constants) using a
// static single-assignment (SSA) form intermediate representation
// static single-information (SSI) form intermediate representation
// (IR) for the bodies of functions.
//
// THIS INTERFACE IS EXPERIMENTAL AND IS LIKELY TO CHANGE.
//
// For an introduction to SSA form, see
// For an introduction to SSA form, upon which SSI builds, 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
// For an introduction to SSI form, see The static single information
// form by C. Scott Ananian.
//
// The level of abstraction of the IR 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
// ssautil.Switches() for an example.
//
// The simplest way to create the SSA representation of a package is
// The simplest way to create the IR 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
// invoke the irutil.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
// The resulting ir.Program contains all the packages and their
// members, but IR 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
// The builder initially builds a naive IR 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
@ -44,7 +42,7 @@
// - 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)
// - Node: a Value or Instruction (emphasizing its membership in the IR value graph)
//
// A computation that yields a result implements both the Value and
// Instruction interfaces. The following table shows for each
@ -53,47 +51,53 @@
// Value? Instruction? Member?
// *Alloc ✔ ✔
// *BinOp ✔ ✔
// *BlankStore ✔
// *Builtin ✔
// *Call ✔ ✔
// *ChangeInterface ✔ ✔
// *ChangeType ✔ ✔
// *Const ✔
// *Const ✔
// *Convert ✔ ✔
// *DebugRef ✔
// *Defer
// *Defer
// *Extract ✔ ✔
// *Field ✔ ✔
// *FieldAddr ✔ ✔
// *FreeVar ✔
// *Function ✔ ✔ (func)
// *Global ✔ ✔ (var)
// *Go
// *Go
// *If ✔
// *Index ✔ ✔
// *IndexAddr ✔ ✔
// *Jump ✔
// *Lookup ✔ ✔
// *Load ✔ ✔
// *MakeChan ✔ ✔
// *MakeClosure ✔ ✔
// *MakeInterface ✔ ✔
// *MakeMap ✔ ✔
// *MakeSlice ✔ ✔
// *MapUpdate ✔
// *MapLookup ✔ ✔
// *MapUpdate ✔ ✔
// *NamedConst ✔ (const)
// *Next ✔ ✔
// *Panic ✔
// *Parameter ✔
// *Parameter ✔
// *Phi ✔ ✔
// *Range ✔ ✔
// *Recv ✔ ✔
// *Return ✔
// *RunDefers ✔
// *Select ✔ ✔
// *Send ✔
// *Send ✔ ✔
// *Sigma ✔ ✔
// *Slice ✔ ✔
// *Store ✔
// *Store ✔ ✔
// *StringLookup ✔ ✔
// *Type ✔ (type)
// *TypeAssert ✔ ✔
// *UnOp ✔ ✔
// *Unreachable ✔
//
// Other key types in this package include: Program, Package, Function
// and BasicBlock.
@ -102,7 +106,7 @@
// 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
// the topology of the IR and type graphs are semantically
// significant. (There is one exception: Ids, used to identify field
// and method names, contain strings.) Avoidance of name-based
// operations simplifies the implementation of subsequent passes and
@ -111,7 +115,7 @@
// either accurate or unambiguous. The public API exposes a number of
// name-based maps for client convenience.
//
// The ssa/ssautil package provides various utilities that depend only
// The ir/irutil package provides various utilities that depend only
// on the public API of this package.
//
// TODO(adonovan): Consider the exceptional control-flow implications
@ -120,6 +124,6 @@
// 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.
// ir.Values/Instructions.
//
package ssa // import "honnef.co/go/tools/ssa"
package ir // import "honnef.co/go/tools/ir"

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
package ir
// This file defines algorithms related to dominance.
@ -20,6 +20,7 @@ package ssa
import (
"bytes"
"fmt"
"io"
"math/big"
"os"
"sort"
@ -27,8 +28,7 @@ import (
// 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.
// The entry node (b.Index==0) does not have a parent.
//
func (b *BasicBlock) Idom() *BasicBlock { return b.dom.idom }
@ -66,144 +66,211 @@ type domInfo struct {
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) {
func buildDomTree(fn *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 {
for _, b := range fn.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],
idoms := make([]*BasicBlock, len(fn.Blocks))
order := make([]*BasicBlock, 0, len(fn.Blocks))
seen := fn.blockset(0)
var dfs func(b *BasicBlock)
dfs = func(b *BasicBlock) {
if !seen.Add(b) {
return
}
for _, succ := range b.Succs {
dfs(succ)
}
if fn.fakeExits.Has(b) {
dfs(fn.Exit)
}
order = append(order, b)
b.post = len(order) - 1
}
dfs(fn.Blocks[0])
for i := 0; i < len(order)/2; i++ {
o := len(order) - i - 1
order[i], order[o] = order[o], order[i]
}
// 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)
}
idoms[fn.Blocks[0].Index] = fn.Blocks[0]
changed := true
for changed {
changed = false
// iterate over all nodes in reverse postorder, except for the
// entry node
for _, b := range order[1:] {
var newIdom *BasicBlock
do := func(p *BasicBlock) {
if idoms[p.Index] == nil {
return
}
if newIdom == nil {
newIdom = p
} else {
finger1 := p
finger2 := newIdom
for finger1 != finger2 {
for finger1.post < finger2.post {
finger1 = idoms[finger1.Index]
}
for finger2.post < finger1.post {
finger2 = idoms[finger2.Index]
}
}
newIdom = finger1
}
}
for _, p := range b.Preds {
do(p)
}
if b == fn.Exit {
for _, p := range fn.Blocks {
if fn.fakeExits.Has(p) {
do(p)
}
}
}
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
if idoms[b.Index] != newIdom {
idoms[b.Index] = newIdom
changed = true
}
}
}
// 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]
}
for i, b := range idoms {
fn.Blocks[i].dom.idom = b
if b == nil {
// malformed CFG
continue
}
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
if i == b.Index {
continue
}
b.dom.children = append(b.dom.children, fn.Blocks[i])
}
// The final 'Step 3' is now outside the loop.
for v := buckets[0]; v != root; v = buckets[v.dom.pre] {
v.dom.idom = root
}
numberDomTree(fn.Blocks[0], 0, 0)
// 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
// printDomTreeDot(os.Stderr, fn) // debugging
// printDomTreeText(os.Stderr, root, 0) // debugging
if f.Prog.mode&SanityCheckFunctions != 0 {
sanityCheckDomTree(f)
if fn.Prog.mode&SanityCheckFunctions != 0 {
sanityCheckDomTree(fn)
}
}
// buildPostDomTree is like buildDomTree, but builds the post-dominator tree instead.
func buildPostDomTree(fn *Function) {
// The step numbers refer to the original LT paper; the
// reordering is due to Georgiadis.
// Clear any previous domInfo.
for _, b := range fn.Blocks {
b.pdom = domInfo{}
}
idoms := make([]*BasicBlock, len(fn.Blocks))
order := make([]*BasicBlock, 0, len(fn.Blocks))
seen := fn.blockset(0)
var dfs func(b *BasicBlock)
dfs = func(b *BasicBlock) {
if !seen.Add(b) {
return
}
for _, pred := range b.Preds {
dfs(pred)
}
if b == fn.Exit {
for _, p := range fn.Blocks {
if fn.fakeExits.Has(p) {
dfs(p)
}
}
}
order = append(order, b)
b.post = len(order) - 1
}
dfs(fn.Exit)
for i := 0; i < len(order)/2; i++ {
o := len(order) - i - 1
order[i], order[o] = order[o], order[i]
}
idoms[fn.Exit.Index] = fn.Exit
changed := true
for changed {
changed = false
// iterate over all nodes in reverse postorder, except for the
// exit node
for _, b := range order[1:] {
var newIdom *BasicBlock
do := func(p *BasicBlock) {
if idoms[p.Index] == nil {
return
}
if newIdom == nil {
newIdom = p
} else {
finger1 := p
finger2 := newIdom
for finger1 != finger2 {
for finger1.post < finger2.post {
finger1 = idoms[finger1.Index]
}
for finger2.post < finger1.post {
finger2 = idoms[finger2.Index]
}
}
newIdom = finger1
}
}
for _, p := range b.Succs {
do(p)
}
if fn.fakeExits.Has(b) {
do(fn.Exit)
}
if idoms[b.Index] != newIdom {
idoms[b.Index] = newIdom
changed = true
}
}
}
for i, b := range idoms {
fn.Blocks[i].pdom.idom = b
if b == nil {
// malformed CFG
continue
}
if i == b.Index {
continue
}
b.pdom.children = append(b.pdom.children, fn.Blocks[i])
}
numberPostDomTree(fn.Exit, 0, 0)
// printPostDomTreeDot(os.Stderr, fn) // debugging
// printPostDomTreeText(os.Stderr, fn.Exit, 0) // debugging
if fn.Prog.mode&SanityCheckFunctions != 0 { // XXX
sanityCheckDomTree(fn) // XXX
}
}
@ -222,6 +289,21 @@ func numberDomTree(v *BasicBlock, pre, post int32) (int32, int32) {
return pre, post
}
// numberPostDomTree sets the pre- and post-order numbers of a depth-first
// traversal of the post-dominator tree rooted at v. These are used to
// answer post-dominance queries in constant time.
//
func numberPostDomTree(v *BasicBlock, pre, post int32) (int32, int32) {
v.pdom.pre = pre
pre++
for _, child := range v.pdom.children {
pre, post = numberPostDomTree(child, pre, post)
}
v.pdom.post = post
post++
return pre, post
}
// Testing utilities ----------------------------------------
// sanityCheckDomTree checks the correctness of the dominator tree
@ -243,8 +325,8 @@ func sanityCheckDomTree(f *Function) {
all.Set(one).Lsh(&all, uint(n)).Sub(&all, one)
// Initialization.
for i, b := range f.Blocks {
if i == 0 || b == f.Recover {
for i := range f.Blocks {
if i == 0 {
// A root is dominated only by itself.
D[i].SetBit(&D[0], 0, 1)
} else {
@ -258,7 +340,7 @@ func sanityCheckDomTree(f *Function) {
for changed := true; changed; {
changed = false
for i, b := range f.Blocks {
if i == 0 || b == f.Recover {
if i == 0 {
continue
}
// Compute intersection across predecessors.
@ -267,6 +349,13 @@ func sanityCheckDomTree(f *Function) {
for _, pred := range b.Preds {
x.And(&x, &D[pred.Index])
}
if b == f.Exit {
for _, p := range f.Blocks {
if f.fakeExits.Has(p) {
x.And(&x, &D[p.Index])
}
}
}
x.SetBit(&x, i, 1) // a block always dominates itself.
if D[i].Cmp(&x) != 0 {
D[i].Set(&x)
@ -276,14 +365,10 @@ func sanityCheckDomTree(f *Function) {
}
// Check the entire relation. O(n^2).
// The Recover block (if any) must be treated specially so we skip it.
ok := true
for i := 0; i < n; i++ {
for j := 0; j < n; j++ {
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 {
@ -321,7 +406,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) {
func printDomTreeDot(buf io.Writer, f *Function) {
fmt.Fprintln(buf, "//", f)
fmt.Fprintln(buf, "digraph domtree {")
for i, b := range f.Blocks {
@ -341,3 +426,36 @@ func printDomTreeDot(buf *bytes.Buffer, f *Function) {
}
fmt.Fprintln(buf, "}")
}
// printDomTree prints the dominator tree as text, using indentation.
//lint:ignore U1000 used during debugging
func printPostDomTreeText(buf io.Writer, v *BasicBlock, indent int) {
fmt.Fprintf(buf, "%*s%s\n", 4*indent, "", v)
for _, child := range v.pdom.children {
printPostDomTreeText(buf, child, indent+1)
}
}
// printDomTreeDot prints the dominator tree of f in AT&T GraphViz
// (.dot) format.
//lint:ignore U1000 used during debugging
func printPostDomTreeDot(buf io.Writer, f *Function) {
fmt.Fprintln(buf, "//", f)
fmt.Fprintln(buf, "digraph pdomtree {")
for _, b := range f.Blocks {
v := b.pdom
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 b != f.Exit {
fmt.Fprintf(buf, "\tn%d -> n%d [style=\"solid\",weight=100];\n", v.idom.pdom.pre, v.pre)
}
// CFG edges.
for _, pred := range b.Preds {
fmt.Fprintf(buf, "\tn%d -> n%d [style=\"dotted\",weight=0];\n", pred.pdom.pre, v.pre)
}
}
fmt.Fprintln(buf, "}")
}

View File

@ -2,13 +2,14 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
package ir
// Helpers for emitting SSA instructions.
// Helpers for emitting IR instructions.
import (
"fmt"
"go/ast"
"go/constant"
"go/token"
"go/types"
)
@ -16,24 +17,32 @@ import (
// emitNew emits to f a new (heap Alloc) instruction allocating an
// object of type typ. pos is the optional source location.
//
func emitNew(f *Function, typ types.Type, pos token.Pos) *Alloc {
func emitNew(f *Function, typ types.Type, source ast.Node) *Alloc {
v := &Alloc{Heap: true}
v.setType(types.NewPointer(typ))
v.setPos(pos)
f.emit(v)
f.emit(v, source)
return v
}
// 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}
func emitLoad(f *Function, addr Value, source ast.Node) *Load {
v := &Load{X: addr}
v.setType(deref(addr.Type()))
f.emit(v)
f.emit(v, source)
return v
}
func emitRecv(f *Function, ch Value, commaOk bool, typ types.Type, source ast.Node) Value {
recv := &Recv{
Chan: ch,
CommaOk: commaOk,
}
recv.setType(typ)
return f.emit(recv, source)
}
// emitDebugRef emits to f a DebugRef pseudo-instruction associating
// expression e with value v.
//
@ -61,7 +70,7 @@ func emitDebugRef(f *Function, e ast.Expr, v Value, isAddr bool) {
Expr: e,
IsAddr: isAddr,
object: obj,
})
}, nil)
}
// emitArith emits to f code to compute the binary operation op(x, y)
@ -69,19 +78,19 @@ func emitDebugRef(f *Function, e ast.Expr, v Value, isAddr bool) {
// (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 {
func emitArith(f *Function, op token.Token, x, y Value, t types.Type, source ast.Node) Value {
switch op {
case token.SHL, token.SHR:
x = emitConv(f, x, t)
x = emitConv(f, x, t, source)
// y may be signed or an 'untyped' constant.
// TODO(adonovan): whence signed values?
if b, ok := y.Type().Underlying().(*types.Basic); ok && b.Info()&types.IsUnsigned == 0 {
y = emitConv(f, y, types.Typ[types.Uint64])
y = emitConv(f, y, types.Typ[types.Uint64], source)
}
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)
x = emitConv(f, x, t, source)
y = emitConv(f, y, t, source)
default:
panic("illegal op in emitArith: " + op.String())
@ -92,15 +101,14 @@ func emitArith(f *Function, op token.Token, x, y Value, t types.Type, pos token.
X: x,
Y: y,
}
v.setPos(pos)
v.setType(t)
return f.emit(v)
return f.emit(v, source)
}
// emitCompare emits to f code compute the boolean result of
// comparison comparison 'x op y'.
//
func emitCompare(f *Function, op token.Token, x, y Value, pos token.Pos) Value {
func emitCompare(f *Function, op token.Token, x, y Value, source ast.Node) Value {
xt := x.Type().Underlying()
yt := y.Type().Underlying()
@ -111,7 +119,7 @@ func emitCompare(f *Function, op token.Token, x, y Value, pos token.Pos) Value {
// 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 x, ok := x.(*Const); ok && op == token.EQL && x.Value != nil && x.Value.Kind() == constant.Bool && constant.BoolVal(x.Value) {
if yt, ok := yt.(*types.Basic); ok && yt.Info()&types.IsBoolean != 0 {
return y
}
@ -120,13 +128,13 @@ func emitCompare(f *Function, op token.Token, x, y Value, pos token.Pos) Value {
if types.Identical(xt, yt) {
// no conversion necessary
} else if _, ok := xt.(*types.Interface); ok {
y = emitConv(f, y, x.Type())
y = emitConv(f, y, x.Type(), source)
} else if _, ok := yt.(*types.Interface); ok {
x = emitConv(f, x, y.Type())
x = emitConv(f, x, y.Type(), source)
} else if _, ok := x.(*Const); ok {
x = emitConv(f, x, y.Type())
x = emitConv(f, x, y.Type(), source)
} else if _, ok := y.(*Const); ok {
y = emitConv(f, y, x.Type())
y = emitConv(f, y, x.Type(), source)
//lint:ignore SA9003 no-op
} else {
// other cases, e.g. channels. No-op.
@ -137,9 +145,8 @@ func emitCompare(f *Function, op token.Token, x, y Value, pos token.Pos) Value {
X: x,
Y: y,
}
v.setPos(pos)
v.setType(tBool)
return f.emit(v)
return f.emit(v, source)
}
// isValuePreserving returns true if a conversion from ut_src to
@ -171,7 +178,7 @@ func isValuePreserving(ut_src, ut_dst types.Type) bool {
// by language assignability rules in assignments, parameter passing,
// etc. Conversions cannot fail dynamically.
//
func emitConv(f *Function, val Value, typ types.Type) Value {
func emitConv(f *Function, val Value, typ types.Type, source ast.Node) Value {
t_src := val.Type()
// Identical types? Conversion is a no-op.
@ -186,7 +193,7 @@ func emitConv(f *Function, val Value, typ types.Type) Value {
if isValuePreserving(ut_src, ut_dst) {
c := &ChangeType{X: val}
c.setType(typ)
return f.emit(c)
return f.emit(c, source)
}
// Conversion to, or construction of a value of, an interface type?
@ -195,23 +202,23 @@ func emitConv(f *Function, val Value, typ types.Type) Value {
if _, ok := ut_src.(*types.Interface); ok {
c := &ChangeInterface{X: val}
c.setType(typ)
return f.emit(c)
return f.emit(c, source)
}
// Untyped nil constant? Return interface-typed nil constant.
if ut_src == tUntypedNil {
return nilConst(typ)
return emitConst(f, nilConst(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, DefaultType(ut_src))
val = emitConv(f, val, types.Default(ut_src), source)
}
f.Pkg.Prog.needMethodsOf(val.Type())
mi := &MakeInterface{X: val}
mi.setType(typ)
return f.emit(mi)
return f.emit(mi, source)
}
// Conversion of a compile-time constant value?
@ -222,7 +229,7 @@ func emitConv(f *Function, val Value, typ types.Type) Value {
// constant of the destination type and
// (initially) the same abstract value.
// We don't truncate the value yet.
return NewConst(c.Value, typ)
return emitConst(f, NewConst(c.Value, typ))
}
// We're converting from constant to non-constant type,
@ -237,7 +244,7 @@ func emitConv(f *Function, val Value, typ types.Type) Value {
if ok1 || ok2 {
c := &Convert{X: val}
c.setType(typ)
return f.emit(c)
return f.emit(c, source)
}
panic(fmt.Sprintf("in %s: cannot convert %s (%s) to %s", f, val, val.Type(), typ))
@ -246,72 +253,75 @@ func emitConv(f *Function, val Value, typ types.Type) Value {
// 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 {
func emitStore(f *Function, addr, val Value, source ast.Node) *Store {
s := &Store{
Addr: addr,
Val: emitConv(f, val, deref(addr.Type())),
pos: pos,
Val: emitConv(f, val, deref(addr.Type()), source),
}
f.emit(s)
// make sure we call getMem after the call to emitConv, which may
// itself update the memory state
f.emit(s, source)
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) {
func emitJump(f *Function, target *BasicBlock, source ast.Node) *Jump {
b := f.currentBlock
b.emit(new(Jump))
j := new(Jump)
b.emit(j, source)
addEdge(b, target)
f.currentBlock = nil
return j
}
// 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) {
func emitIf(f *Function, cond Value, tblock, fblock *BasicBlock, source ast.Node) *If {
b := f.currentBlock
b.emit(&If{Cond: cond})
stmt := &If{Cond: cond}
b.emit(stmt, source)
addEdge(b, tblock)
addEdge(b, fblock)
f.currentBlock = nil
return stmt
}
// 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 {
func emitExtract(f *Function, tuple Value, index int, source ast.Node) Value {
e := &Extract{Tuple: tuple, Index: index}
e.setType(tuple.Type().(*types.Tuple).At(index).Type())
return f.emit(e)
return f.emit(e, source)
}
// 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 {
func emitTypeAssert(f *Function, x Value, t types.Type, source ast.Node) Value {
a := &TypeAssert{X: x, AssertedType: t}
a.setPos(pos)
a.setType(t)
return f.emit(a)
return f.emit(a, source)
}
// 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 {
func emitTypeTest(f *Function, x Value, t types.Type, source ast.Node) Value {
a := &TypeAssert{
X: x,
AssertedType: t,
CommaOk: true,
}
a.setPos(pos)
a.setType(types.NewTuple(
newVar("value", t),
varOk,
))
return f.emit(a)
return f.emit(a, source)
}
// emitTailCall emits to f a function call in tail position. The
@ -320,7 +330,7 @@ func emitTypeTest(f *Function, x Value, t types.Type, pos token.Pos) Value {
// Precondition: f does/will not use deferred procedure calls.
// Postcondition: f.currentBlock is nil.
//
func emitTailCall(f *Function, call *Call) {
func emitTailCall(f *Function, call *Call, source ast.Node) {
tresults := f.Signature.Results()
nr := tresults.Len()
if nr == 1 {
@ -328,7 +338,7 @@ func emitTailCall(f *Function, call *Call) {
} else {
call.typ = tresults
}
tuple := f.emit(call)
tuple := f.emit(call, source)
var ret Return
switch nr {
case 0:
@ -337,7 +347,7 @@ func emitTailCall(f *Function, call *Call) {
ret.Results = []Value{tuple}
default:
for i := 0; i < nr; i++ {
v := emitExtract(f, tuple, i)
v := emitExtract(f, tuple, i, source)
// 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
@ -345,7 +355,11 @@ func emitTailCall(f *Function, call *Call) {
ret.Results = append(ret.Results, v)
}
}
f.emit(&ret)
f.Exit = f.newBasicBlock("exit")
emitJump(f, f.Exit, source)
f.currentBlock = f.Exit
f.emit(&ret, source)
f.currentBlock = nil
}
@ -357,7 +371,7 @@ func emitTailCall(f *Function, call *Call) {
// 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) Value {
func emitImplicitSelections(f *Function, v Value, indices []int, source ast.Node) Value {
for _, index := range indices {
fld := deref(v.Type()).Underlying().(*types.Struct).Field(index)
@ -367,10 +381,10 @@ func emitImplicitSelections(f *Function, v Value, indices []int) Value {
Field: index,
}
instr.setType(types.NewPointer(fld.Type()))
v = f.emit(instr)
v = f.emit(instr, source)
// Load the field's value iff indirectly embedded.
if isPointer(fld.Type()) {
v = emitLoad(f, v)
v = emitLoad(f, v, source)
}
} else {
instr := &Field{
@ -378,7 +392,7 @@ func emitImplicitSelections(f *Function, v Value, indices []int) Value {
Field: index,
}
instr.setType(fld.Type())
v = f.emit(instr)
v = f.emit(instr, source)
}
}
return v
@ -398,21 +412,21 @@ func emitFieldSelection(f *Function, v Value, index int, wantAddr bool, id *ast.
X: v,
Field: index,
}
instr.setPos(id.Pos())
instr.setSource(id)
instr.setType(types.NewPointer(fld.Type()))
v = f.emit(instr)
v = f.emit(instr, id)
// Load the field's value iff we don't want its address.
if !wantAddr {
v = emitLoad(f, v)
v = emitLoad(f, v, id)
}
} else {
instr := &Field{
X: v,
Field: index,
}
instr.setPos(id.Pos())
instr.setSource(id)
instr.setType(fld.Type())
v = f.emit(instr)
v = f.emit(instr, id)
}
emitDebugRef(f, id, v, wantAddr)
return v
@ -421,49 +435,16 @@ func emitFieldSelection(f *Function, v Value, index int, wantAddr bool, id *ast.
// zeroValue emits to f code to produce a zero value of type t,
// and returns it.
//
func zeroValue(f *Function, t types.Type) Value {
func zeroValue(f *Function, t types.Type, source ast.Node) Value {
switch t.Underlying().(type) {
case *types.Struct, *types.Array:
return emitLoad(f, f.addLocal(t, token.NoPos))
return emitLoad(f, f.addLocal(t, source), source)
default:
return zeroConst(t)
return emitConst(f, zeroConst(t))
}
}
// 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
if f.namedResults != nil {
// Reload NRPs to form value tuple.
for _, r := range f.namedResults {
results = append(results, emitLoad(f, r))
}
} else {
R := f.Signature.Results()
for i, n := 0, R.Len(); i < n; i++ {
T := R.At(i).Type()
// Return zero value of each result type.
results = append(results, zeroValue(f, T))
}
}
f.emit(&Return{Results: results})
f.currentBlock = saved
func emitConst(f *Function, c *Const) *Const {
f.consts = append(f.consts, c)
return c
}

271
vendor/honnef.co/go/tools/ir/exits.go vendored Normal file
View File

@ -0,0 +1,271 @@
package ir
import (
"go/types"
)
func (b *builder) buildExits(fn *Function) {
if obj := fn.Object(); obj != nil {
switch obj.Pkg().Path() {
case "runtime":
switch obj.Name() {
case "exit":
fn.WillExit = true
return
case "throw":
fn.WillExit = true
return
case "Goexit":
fn.WillUnwind = true
return
}
case "github.com/sirupsen/logrus":
switch obj.(*types.Func).FullName() {
case "(*github.com/sirupsen/logrus.Logger).Exit":
// Technically, this method does not unconditionally exit
// the process. It dynamically calls a function stored in
// the logger. If the function is nil, it defaults to
// os.Exit.
//
// The main intent of this method is to terminate the
// process, and that's what the vast majority of people
// will use it for. We'll happily accept some false
// negatives to avoid a lot of false positives.
fn.WillExit = true
return
case "(*github.com/sirupsen/logrus.Logger).Panic",
"(*github.com/sirupsen/logrus.Logger).Panicf",
"(*github.com/sirupsen/logrus.Logger).Panicln":
// These methods will always panic, but that's not
// statically known from the code alone, because they
// take a detour through the generic Log methods.
fn.WillUnwind = true
return
case "(*github.com/sirupsen/logrus.Entry).Panicf",
"(*github.com/sirupsen/logrus.Entry).Panicln":
// Entry.Panic has an explicit panic, but Panicf and
// Panicln do not, relying fully on the generic Log
// method.
fn.WillUnwind = true
return
case "(*github.com/sirupsen/logrus.Logger).Log",
"(*github.com/sirupsen/logrus.Logger).Logf",
"(*github.com/sirupsen/logrus.Logger).Logln":
// TODO(dh): we cannot handle these case. Whether they
// exit or unwind depends on the level, which is set
// via the first argument. We don't currently support
// call-site-specific exit information.
}
}
}
buildDomTree(fn)
isRecoverCall := func(instr Instruction) bool {
if instr, ok := instr.(*Call); ok {
if builtin, ok := instr.Call.Value.(*Builtin); ok {
if builtin.Name() == "recover" {
return true
}
}
}
return false
}
// All panics branch to the exit block, which means that if every
// possible path through the function panics, then all
// predecessors of the exit block must panic.
willPanic := true
for _, pred := range fn.Exit.Preds {
if _, ok := pred.Control().(*Panic); !ok {
willPanic = false
}
}
if willPanic {
recovers := false
recoverLoop:
for _, u := range fn.Blocks {
for _, instr := range u.Instrs {
if instr, ok := instr.(*Defer); ok {
call := instr.Call.StaticCallee()
if call == nil {
// not a static call, so we can't be sure the
// deferred call isn't calling recover
recovers = true
break recoverLoop
}
if len(call.Blocks) == 0 {
// external function, we don't know what's
// happening inside it
//
// TODO(dh): this includes functions from
// imported packages, due to how go/analysis
// works. We could introduce another fact,
// like we've done for exiting and unwinding,
// but it doesn't seem worth it. Virtually all
// uses of recover will be in closures.
recovers = true
break recoverLoop
}
for _, y := range call.Blocks {
for _, instr2 := range y.Instrs {
if isRecoverCall(instr2) {
recovers = true
break recoverLoop
}
}
}
}
}
}
if !recovers {
fn.WillUnwind = true
return
}
}
// TODO(dh): don't check that any specific call dominates the exit
// block. instead, check that all calls combined cover every
// possible path through the function.
exits := NewBlockSet(len(fn.Blocks))
unwinds := NewBlockSet(len(fn.Blocks))
for _, u := range fn.Blocks {
for _, instr := range u.Instrs {
if instr, ok := instr.(CallInstruction); ok {
switch instr.(type) {
case *Defer, *Call:
default:
continue
}
if instr.Common().IsInvoke() {
// give up
return
}
var call *Function
switch instr.Common().Value.(type) {
case *Function, *MakeClosure:
call = instr.Common().StaticCallee()
case *Builtin:
// the only builtins that affect control flow are
// panic and recover, and we've already handled
// those
continue
default:
// dynamic dispatch
return
}
// buildFunction is idempotent. if we're part of a
// (mutually) recursive call chain, then buildFunction
// will immediately return, and fn.WillExit will be false.
if call.Package() == fn.Package() {
b.buildFunction(call)
}
dom := u.Dominates(fn.Exit)
if call.WillExit {
if dom {
fn.WillExit = true
return
}
exits.Add(u)
} else if call.WillUnwind {
if dom {
fn.WillUnwind = true
return
}
unwinds.Add(u)
}
}
}
}
// depth-first search trying to find a path to the exit block that
// doesn't cross any of the blacklisted blocks
seen := NewBlockSet(len(fn.Blocks))
var findPath func(root *BasicBlock, bl *BlockSet) bool
findPath = func(root *BasicBlock, bl *BlockSet) bool {
if root == fn.Exit {
return true
}
if seen.Has(root) {
return false
}
if bl.Has(root) {
return false
}
seen.Add(root)
for _, succ := range root.Succs {
if findPath(succ, bl) {
return true
}
}
return false
}
if exits.Num() > 0 {
if !findPath(fn.Blocks[0], exits) {
fn.WillExit = true
return
}
}
if unwinds.Num() > 0 {
seen.Clear()
if !findPath(fn.Blocks[0], unwinds) {
fn.WillUnwind = true
return
}
}
}
func (b *builder) addUnreachables(fn *Function) {
for _, bb := range fn.Blocks {
for i, instr := range bb.Instrs {
if instr, ok := instr.(*Call); ok {
var call *Function
switch v := instr.Common().Value.(type) {
case *Function:
call = v
case *MakeClosure:
call = v.Fn.(*Function)
}
if call == nil {
continue
}
if call.Package() == fn.Package() {
// make sure we have information on all functions in this package
b.buildFunction(call)
}
if call.WillExit {
// This call will cause the process to terminate.
// Remove remaining instructions in the block and
// replace any control flow with Unreachable.
for _, succ := range bb.Succs {
succ.removePred(bb)
}
bb.Succs = bb.Succs[:0]
bb.Instrs = bb.Instrs[:i+1]
bb.emit(new(Unreachable), instr.Source())
addEdge(bb, fn.Exit)
break
} else if call.WillUnwind {
// This call will cause the goroutine to terminate
// and defers to run (i.e. a panic or
// runtime.Goexit). Remove remaining instructions
// in the block and replace any control flow with
// an unconditional jump to the exit block.
for _, succ := range bb.Succs {
succ.removePred(bb)
}
bb.Succs = bb.Succs[:0]
bb.Instrs = bb.Instrs[:i+1]
bb.emit(new(Jump), instr.Source())
addEdge(bb, fn.Exit)
break
}
}
}
}
}

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
package ir
// This file implements the Function and BasicBlock types.
@ -10,6 +10,8 @@ import (
"bytes"
"fmt"
"go/ast"
"go/constant"
"go/format"
"go/token"
"go/types"
"io"
@ -23,6 +25,29 @@ func addEdge(from, to *BasicBlock) {
to.Preds = append(to.Preds, from)
}
// Control returns the last instruction in the block.
func (b *BasicBlock) Control() Instruction {
if len(b.Instrs) == 0 {
return nil
}
return b.Instrs[len(b.Instrs)-1]
}
// SIgmaFor returns the sigma node for v coming from pred.
func (b *BasicBlock) SigmaFor(v Value, pred *BasicBlock) *Sigma {
for _, instr := range b.Instrs {
sigma, ok := instr.(*Sigma)
if !ok {
// no more sigmas
return nil
}
if sigma.From == pred && sigma.X == v {
return sigma
}
}
return nil
}
// Parent returns the function that contains block b.
func (b *BasicBlock) Parent() *Function { return b.parent }
@ -36,7 +61,8 @@ func (b *BasicBlock) String() string {
// 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 {
func (b *BasicBlock) emit(i Instruction, source ast.Node) Value {
i.setSource(source)
i.setBlock(b)
b.Instrs = append(b.Instrs, i)
v, _ := i.(Value)
@ -54,6 +80,16 @@ func (b *BasicBlock) predIndex(c *BasicBlock) int {
panic(fmt.Sprintf("no edge %s -> %s", c, b))
}
// succIndex returns the i such that b.Succs[i] == c or -1 if there is none.
func (b *BasicBlock) succIndex(c *BasicBlock) int {
for i, succ := range b.Succs {
if succ == c {
return i
}
}
return -1
}
// hasPhi returns true if b.Instrs contains φ-nodes.
func (b *BasicBlock) hasPhi() bool {
_, ok := b.Instrs[0].(*Phi)
@ -96,10 +132,6 @@ func (b *BasicBlock) replaceSucc(p, q *BasicBlock) {
}
}
func (b *BasicBlock) RemovePred(p *BasicBlock) {
b.removePred(p)
}
// removePred removes all occurrences of p in b's
// predecessor list and φ-nodes.
// Ordinarily there should be at most one.
@ -173,23 +205,33 @@ func (f *Function) labelledBlock(label *ast.Ident) *lblock {
// addParam adds a (non-escaping) parameter to f.Params of the
// specified name, type and source position.
//
func (f *Function) addParam(name string, typ types.Type, pos token.Pos) *Parameter {
v := &Parameter{
name: name,
typ: typ,
pos: pos,
parent: f,
func (f *Function) addParam(name string, typ types.Type, source ast.Node) *Parameter {
var b *BasicBlock
if len(f.Blocks) > 0 {
b = f.Blocks[0]
}
v := &Parameter{
name: name,
}
v.setBlock(b)
v.setType(typ)
v.setSource(source)
f.Params = append(f.Params, v)
if b != nil {
// There may be no blocks if this function has no body. We
// still create params, but aren't interested in the
// instruction.
f.Blocks[0].Instrs = append(f.Blocks[0].Instrs, v)
}
return v
}
func (f *Function) addParamObj(obj types.Object) *Parameter {
func (f *Function) addParamObj(obj types.Object, source ast.Node) *Parameter {
name := obj.Name()
if name == "" {
name = fmt.Sprintf("arg%d", len(f.Params))
}
param := f.addParam(name, obj.Type(), obj.Pos())
param := f.addParam(name, obj.Type(), source)
param.object = obj
return param
}
@ -198,25 +240,61 @@ func (f *Function) addParamObj(obj types.Object) *Parameter {
// stack; the function body will load/store the spilled location.
// Subsequent lifting will eliminate spills where possible.
//
func (f *Function) addSpilledParam(obj types.Object) {
param := f.addParamObj(obj)
spill := &Alloc{Comment: obj.Name()}
func (f *Function) addSpilledParam(obj types.Object, source ast.Node) {
param := f.addParamObj(obj, source)
spill := &Alloc{}
spill.setType(types.NewPointer(obj.Type()))
spill.setPos(obj.Pos())
spill.source = source
f.objects[obj] = spill
f.Locals = append(f.Locals, spill)
f.emit(spill)
f.emit(&Store{Addr: spill, Val: param})
f.emit(spill, source)
emitStore(f, spill, param, source)
// f.emit(&Store{Addr: spill, Val: param})
}
// startBody initializes the function prior to generating SSA code for its body.
// startBody initializes the function prior to generating IR code for its body.
// Precondition: f.Type() already set.
//
func (f *Function) startBody() {
f.currentBlock = f.newBasicBlock("entry")
entry := f.newBasicBlock("entry")
f.currentBlock = entry
f.objects = make(map[types.Object]Value) // needed for some synthetics, e.g. init
}
func (f *Function) blockset(i int) *BlockSet {
bs := &f.blocksets[i]
if len(bs.values) != len(f.Blocks) {
if cap(bs.values) >= len(f.Blocks) {
bs.values = bs.values[:len(f.Blocks)]
bs.Clear()
} else {
bs.values = make([]bool, len(f.Blocks))
}
} else {
bs.Clear()
}
return bs
}
func (f *Function) exitBlock() {
old := f.currentBlock
f.Exit = f.newBasicBlock("exit")
f.currentBlock = f.Exit
ret := f.results()
results := make([]Value, len(ret))
// Run function calls deferred in this
// function when explicitly returning from it.
f.emit(new(RunDefers), nil)
for i, r := range ret {
results[i] = emitLoad(f, r, nil)
}
f.emit(&Return{Results: results}, nil)
f.currentBlock = old
}
// 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.
@ -231,11 +309,11 @@ func (f *Function) createSyntacticParams(recv *ast.FieldList, functype *ast.Func
if recv != nil {
for _, field := range recv.List {
for _, n := range field.Names {
f.addSpilledParam(f.Pkg.info.Defs[n])
f.addSpilledParam(f.Pkg.info.Defs[n], n)
}
// Anonymous receiver? No need to spill.
if field.Names == nil {
f.addParamObj(f.Signature.Recv())
f.addParamObj(f.Signature.Recv(), field)
}
}
}
@ -245,11 +323,11 @@ func (f *Function) createSyntacticParams(recv *ast.FieldList, functype *ast.Func
n := len(f.Params) // 1 if has recv, 0 otherwise
for _, field := range functype.Params.List {
for _, n := range field.Names {
f.addSpilledParam(f.Pkg.info.Defs[n])
f.addSpilledParam(f.Pkg.info.Defs[n], n)
}
// Anonymous parameter? No need to spill.
if field.Names == nil {
f.addParamObj(f.Signature.Params().At(len(f.Params) - n))
f.addParamObj(f.Signature.Params().At(len(f.Params)-n), field)
}
}
}
@ -262,24 +340,27 @@ func (f *Function) createSyntacticParams(recv *ast.FieldList, functype *ast.Func
f.namedResults = append(f.namedResults, f.addLocalForIdent(n))
}
}
if len(f.namedResults) == 0 {
sig := f.Signature.Results()
for i := 0; i < sig.Len(); i++ {
// XXX position information
v := f.addLocal(sig.At(i).Type(), nil)
f.implicitResults = append(f.implicitResults, v)
}
}
}
}
// 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
func numberNodes(f *Function) {
var base ID
for _, b := range f.Blocks {
for _, instr := range b.Instrs {
switch instr.(type) {
case Value:
instr.(interface {
setNum(int)
}).setNum(v)
v++
if instr == nil {
continue
}
base++
instr.setID(base)
}
}
}
@ -303,17 +384,164 @@ func buildReferrers(f *Function) {
}
}
// finishBody() finalizes the function after SSA code generation of its body.
func (f *Function) emitConsts() {
if len(f.Blocks) == 0 {
f.consts = nil
return
}
// TODO(dh): our deduplication only works on booleans and
// integers. other constants are represented as pointers to
// things.
if len(f.consts) == 0 {
return
} else if len(f.consts) <= 32 {
f.emitConstsFew()
} else {
f.emitConstsMany()
}
}
func (f *Function) emitConstsFew() {
dedup := make([]*Const, 0, 32)
for _, c := range f.consts {
if len(*c.Referrers()) == 0 {
continue
}
found := false
for _, d := range dedup {
if c.typ == d.typ && c.Value == d.Value {
replaceAll(c, d)
found = true
break
}
}
if !found {
dedup = append(dedup, c)
}
}
instrs := make([]Instruction, len(f.Blocks[0].Instrs)+len(dedup))
for i, c := range dedup {
instrs[i] = c
c.setBlock(f.Blocks[0])
}
copy(instrs[len(dedup):], f.Blocks[0].Instrs)
f.Blocks[0].Instrs = instrs
f.consts = nil
}
func (f *Function) emitConstsMany() {
type constKey struct {
typ types.Type
value constant.Value
}
m := make(map[constKey]Value, len(f.consts))
areNil := 0
for i, c := range f.consts {
if len(*c.Referrers()) == 0 {
f.consts[i] = nil
areNil++
continue
}
k := constKey{
typ: c.typ,
value: c.Value,
}
if dup, ok := m[k]; !ok {
m[k] = c
} else {
f.consts[i] = nil
areNil++
replaceAll(c, dup)
}
}
instrs := make([]Instruction, len(f.Blocks[0].Instrs)+len(f.consts)-areNil)
i := 0
for _, c := range f.consts {
if c != nil {
instrs[i] = c
c.setBlock(f.Blocks[0])
i++
}
}
copy(instrs[i:], f.Blocks[0].Instrs)
f.Blocks[0].Instrs = instrs
f.consts = nil
}
// buildFakeExits ensures that every block in the function is
// reachable in reverse from the Exit block. This is required to build
// a full post-dominator tree, and to ensure the exit block's
// inclusion in the dominator tree.
func buildFakeExits(fn *Function) {
// Find back-edges via forward DFS
fn.fakeExits = BlockSet{values: make([]bool, len(fn.Blocks))}
seen := fn.blockset(0)
backEdges := fn.blockset(1)
var dfs func(b *BasicBlock)
dfs = func(b *BasicBlock) {
if !seen.Add(b) {
backEdges.Add(b)
return
}
for _, pred := range b.Succs {
dfs(pred)
}
}
dfs(fn.Blocks[0])
buildLoop:
for {
seen := fn.blockset(2)
var dfs func(b *BasicBlock)
dfs = func(b *BasicBlock) {
if !seen.Add(b) {
return
}
for _, pred := range b.Preds {
dfs(pred)
}
if b == fn.Exit {
for _, b := range fn.Blocks {
if fn.fakeExits.Has(b) {
dfs(b)
}
}
}
}
dfs(fn.Exit)
for _, b := range fn.Blocks {
if !seen.Has(b) && backEdges.Has(b) {
// Block b is not reachable from the exit block. Add a
// fake jump from b to exit, then try again. Note that we
// only add one fake edge at a time, as it may make
// multiple blocks reachable.
//
// We only consider those blocks that have back edges.
// Any unreachable block that doesn't have a back edge
// must flow into a loop, which by definition has a
// back edge. Thus, by looking for loops, we should
// need fewer fake edges overall.
fn.fakeExits.Add(b)
continue buildLoop
}
}
break
}
}
// finishBody() finalizes the function after IR code generation of its body.
func (f *Function) finishBody() {
f.objects = nil
f.currentBlock = nil
f.lblocks = nil
// Don't pin the AST in memory (except in debug mode).
if n := f.syntax; n != nil && !f.debugInfo() {
f.syntax = extentNode{n.Pos(), n.End()}
}
// Remove from f.Locals any Allocs that escape to the heap.
j := 0
for _, l := range f.Locals {
@ -328,86 +556,25 @@ 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)
buildDomTree(f)
buildPostDomTree(f)
if f.Prog.mode&NaiveForm == 0 {
// For debugging pre-state of lifting pass:
// numberRegisters(f)
// f.WriteTo(os.Stderr)
lift(f)
}
f.namedResults = nil // (used by lifting)
// emit constants after lifting, because lifting may produce new constants.
f.emitConsts()
numberRegisters(f)
f.namedResults = nil // (used by lifting)
f.implicitResults = nil
numberNodes(f)
defer f.wr.Close()
f.wr.WriteFunc("start", "start", f)
if f.Prog.mode&PrintFunctions != 0 {
printMu.Lock()
@ -420,6 +587,29 @@ func (f *Function) finishBody() {
}
}
func isUselessPhi(phi *Phi) (Value, bool) {
var v0 Value
for _, e := range phi.Edges {
if e == phi {
continue
}
if v0 == nil {
v0 = e
}
if v0 != e {
if v0, ok := v0.(*Const); ok {
if e, ok := e.(*Const); ok {
if v0.typ == e.typ && v0.Value == e.Value {
continue
}
}
}
return nil, false
}
}
return v0, true
}
func (f *Function) RemoveNilBlocks() {
f.removeNilBlocks()
}
@ -462,26 +652,24 @@ func (f *Function) debugInfo() bool {
// returns it. Its name and type are taken from obj. Subsequent
// calls to f.lookup(obj) will return the same local.
//
func (f *Function) addNamedLocal(obj types.Object) *Alloc {
l := f.addLocal(obj.Type(), obj.Pos())
l.Comment = obj.Name()
func (f *Function) addNamedLocal(obj types.Object, source ast.Node) *Alloc {
l := f.addLocal(obj.Type(), source)
f.objects[obj] = l
return l
}
func (f *Function) addLocalForIdent(id *ast.Ident) *Alloc {
return f.addNamedLocal(f.Pkg.info.Defs[id])
return f.addNamedLocal(f.Pkg.info.Defs[id], id)
}
// addLocal creates an anonymous local variable of type typ, adds it
// to function f and returns it. pos is the optional source location.
//
func (f *Function) addLocal(typ types.Type, pos token.Pos) *Alloc {
func (f *Function) addLocal(typ types.Type, source ast.Node) *Alloc {
v := &Alloc{}
v.setType(types.NewPointer(typ))
v.setPos(pos)
f.Locals = append(f.Locals, v)
f.emit(v)
f.emit(v, source)
return v
}
@ -501,13 +689,12 @@ func (f *Function) lookup(obj types.Object, escaping bool) Value {
// Definition must be in an enclosing function;
// plumb it through intervening closures.
if f.parent == nil {
panic("no ssa.Value for " + obj.String())
panic("no ir.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,
}
@ -517,8 +704,8 @@ func (f *Function) lookup(obj types.Object, escaping bool) Value {
}
// emit emits the specified instruction to function f.
func (f *Function) emit(instr Instruction) Value {
return f.currentBlock.emit(instr)
func (f *Function) emit(instr Instruction, source ast.Node) Value {
return f.currentBlock.emit(instr, source)
}
// RelString returns the full name of this function, qualified by
@ -637,10 +824,6 @@ func WriteFunction(buf *bytes.Buffer, f *Function) {
fmt.Fprintf(buf, "# Parent: %s\n", f.parent.Name())
}
if f.Recover != nil {
fmt.Fprintf(buf, "# Recover: %s\n", f.Recover)
}
from := f.pkg()
if f.FreeVars != nil {
@ -663,45 +846,38 @@ func WriteFunction(buf *bytes.Buffer, f *Function) {
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)
bmsg := fmt.Sprintf("%s P:%d S:%d", b.Comment, len(b.Preds), len(b.Succs))
fmt.Fprintf(buf, "%*s%s\n", punchcard-1-n-len(bmsg), "", bmsg)
fmt.Fprintf(buf, "b%d:", b.Index)
if len(b.Preds) > 0 {
fmt.Fprint(buf, " ←")
for _, pred := range b.Preds {
fmt.Fprintf(buf, " b%d", pred.Index)
}
}
if b.Comment != "" {
fmt.Fprintf(buf, " # %s", b.Comment)
}
buf.WriteByte('\n')
if false { // CFG debugging
fmt.Fprintf(buf, "\t# CFG: %s --> %s --> %s\n", b.Preds, b, b.Succs)
}
buf2 := &bytes.Buffer{}
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)
fmt.Fprintf(buf, "%s = ", name)
}
buf.WriteString(instr.String())
case nil:
// Be robust against bad transforms.
buf.WriteString("<deleted>")
@ -709,9 +885,30 @@ func WriteFunction(buf *bytes.Buffer, f *Function) {
buf.WriteString(instr.String())
}
buf.WriteString("\n")
if f.Prog.mode&PrintSource != 0 {
if s := instr.Source(); s != nil {
buf2.Reset()
format.Node(buf2, f.Prog.Fset, s)
for {
line, err := buf2.ReadString('\n')
if len(line) == 0 {
break
}
buf.WriteString("\t\t> ")
buf.WriteString(line)
if line[len(line)-1] != '\n' {
buf.WriteString("\n")
}
if err != nil {
break
}
}
}
}
}
buf.WriteString("\n")
}
fmt.Fprintf(buf, "\n")
}
// newBasicBlock adds to f a new basic block and returns it. It does
@ -736,7 +933,7 @@ func (f *Function) newBasicBlock(comment string) *BasicBlock {
// 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
// IR 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
@ -748,18 +945,17 @@ func (prog *Program) NewFunction(name string, sig *types.Signature, provenance s
return &Function{Prog: prog, name: name, Signature: sig, Synthetic: provenance}
}
//lint:ignore U1000 we may make use of this for functions loaded from export data
type extentNode [2]token.Pos
func (n extentNode) Pos() token.Pos { return n[0] }
func (n extentNode) End() token.Pos { return n[1] }
// Syntax returns an ast.Node whose Pos/End methods provide the
// lexical extent of the function if it was defined by Go source code
// (f.Synthetic==""), or nil otherwise.
//
// If f was built with debug information (see Package.SetDebugRef),
// the result is the *ast.FuncDecl or *ast.FuncLit that declared the
// function. Otherwise, it is an opaque Node providing only position
// information; this avoids pinning the AST in memory.
//
func (f *Function) Syntax() ast.Node { return f.syntax }
func (f *Function) initHTML(name string) {
if name == "" {
return
}
if rel := f.RelString(nil); rel == name {
f.wr = NewHTMLWriter("ir.html", rel, "")
}
}

1124
vendor/honnef.co/go/tools/ir/html.go vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
// +build go1.8
package ssa
package ir
import "go/types"

View File

@ -1,6 +1,6 @@
// +build !go1.8
package ssa
package ir
import "go/types"

View File

@ -0,0 +1,183 @@
// 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 irutil
// This file defines utility functions for constructing programs in IR form.
import (
"go/ast"
"go/token"
"go/types"
"golang.org/x/tools/go/loader"
"golang.org/x/tools/go/packages"
"honnef.co/go/tools/ir"
)
type Options struct {
// Which function, if any, to print in HTML form
PrintFunc string
}
// Packages creates an IR program for a set of packages.
//
// The packages must have been loaded from source syntax using the
// golang.org/x/tools/go/packages.Load function in LoadSyntax or
// LoadAllSyntax mode.
//
// Packages creates an IR 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 IR 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. IR code is constructed only for the initial
// packages with well-typed syntax trees.
//
// The mode parameter controls diagnostics and checking during IR construction.
//
func Packages(initial []*packages.Package, mode ir.BuilderMode, opts *Options) (*ir.Program, []*ir.Package) {
return doPackages(initial, mode, false, opts)
}
// AllPackages creates an IR program for a set of packages plus all
// their dependencies.
//
// The packages must have been loaded from source syntax using the
// golang.org/x/tools/go/packages.Load function in LoadAllSyntax mode.
//
// AllPackages creates an IR 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 IR 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. IR code is constructed for all packages with
// well-typed syntax trees.
//
// The mode parameter controls diagnostics and checking during IR construction.
//
func AllPackages(initial []*packages.Package, mode ir.BuilderMode, opts *Options) (*ir.Program, []*ir.Package) {
return doPackages(initial, mode, true, opts)
}
func doPackages(initial []*packages.Package, mode ir.BuilderMode, deps bool, opts *Options) (*ir.Program, []*ir.Package) {
var fset *token.FileSet
if len(initial) > 0 {
fset = initial[0].Fset
}
prog := ir.NewProgram(fset, mode)
if opts != nil {
prog.PrintFunc = opts.PrintFunc
}
isInitial := make(map[*packages.Package]bool, len(initial))
for _, p := range initial {
isInitial[p] = true
}
irmap := make(map[*packages.Package]*ir.Package)
packages.Visit(initial, nil, func(p *packages.Package) {
if p.Types != nil && !p.IllTyped {
var files []*ast.File
if deps || isInitial[p] {
files = p.Syntax
}
irmap[p] = prog.CreatePackage(p.Types, files, p.TypesInfo, true)
}
})
var irpkgs []*ir.Package
for _, p := range initial {
irpkgs = append(irpkgs, irmap[p]) // may be nil
}
return prog, irpkgs
}
// CreateProgram returns a new program in IR form, given a program
// loaded from source. An IR 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 IR construction.
//
// Deprecated: use golang.org/x/tools/go/packages and the Packages
// function instead; see ir.ExampleLoadPackages.
//
func CreateProgram(lprog *loader.Program, mode ir.BuilderMode) *ir.Program {
prog := ir.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 IR 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; IR code cannot be built for
// those packages. BuildPackage then constructs an ir.Program with all
// dependency packages created, and builds and returns the IR 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 ../ir/example_test.go for an example.
//
func BuildPackage(tc *types.Config, fset *token.FileSet, pkg *types.Package, files []*ast.File, mode ir.BuilderMode) (*ir.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 := ir.NewProgram(fset, mode)
// Create IR 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.
irpkg := prog.CreatePackage(pkg, files, info, false)
irpkg.Build()
return irpkg, info, nil
}

View File

@ -0,0 +1,264 @@
// 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 irutil
// 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/ir"
)
// A ConstCase represents a single constant comparison.
// It is part of a Switch.
type ConstCase struct {
Block *ir.BasicBlock // block performing the comparison
Body *ir.BasicBlock // body of the case
Value *ir.Const // case comparand
}
// A TypeCase represents a single type assertion.
// It is part of a Switch.
type TypeCase struct {
Block *ir.BasicBlock // block performing the type assert
Body *ir.BasicBlock // body of the case
Type types.Type // case type
Binding ir.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 ir.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 *ir.BasicBlock // block containing start of if/else chain
X ir.Value // the switch operand
ConstCases []ConstCase // ordered list of constant comparisons
TypeCases []TypeCase // ordered list of type assertions
Default *ir.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.Name(), 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
// ir.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 ir.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 IR.)
//
func Switches(fn *ir.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[*ir.BasicBlock]bool) // TODO(adonovan): opt: use ir.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 isSameX(x1 ir.Value, x2 ir.Value) bool {
if x1 == x2 {
return true
}
if x2, ok := x2.(*ir.Sigma); ok {
return isSameX(x1, x2.X)
}
return false
}
func valueSwitch(sw *Switch, k *ir.Const, seen map[*ir.BasicBlock]bool) {
b := sw.Start
x := sw.X
for isSameX(sw.X, 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]
n := 0
for _, instr := range b.Instrs {
switch instr.(type) {
case *ir.If, *ir.BinOp:
n++
case *ir.Sigma, *ir.Phi, *ir.DebugRef:
default:
n += 1000
}
}
if n != 2 {
// Block b contains not just 'if x == k' and σ/ϕ nodes,
// 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 ir.Value, T types.Type, seen map[*ir.BasicBlock]bool) {
b := sw.Start
x := sw.X
for isSameX(sw.X, 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]
n := 0
for _, instr := range b.Instrs {
switch instr.(type) {
case *ir.TypeAssert, *ir.Extract, *ir.If:
n++
case *ir.Sigma, *ir.Phi:
default:
n += 1000
}
}
if n != 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 *ir.BasicBlock) (v ir.Value, k *ir.Const) {
if n := len(b.Instrs); n >= 2 {
if i, ok := b.Instrs[n-1].(*ir.If); ok {
if binop, ok := i.Cond.(*ir.BinOp); ok && binop.Block() == b && binop.Op == token.EQL {
if k, ok := binop.Y.(*ir.Const); ok {
return binop.X, k
}
if k, ok := binop.X.(*ir.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 *ir.BasicBlock) (y, x ir.Value, T types.Type) {
if n := len(b.Instrs); n >= 4 {
if i, ok := b.Instrs[n-1].(*ir.If); ok {
if ext1, ok := i.Cond.(*ir.Extract); ok && ext1.Block() == b && ext1.Index == 1 {
if ta, ok := ext1.Tuple.(*ir.TypeAssert); ok && ta.Block() == b {
// hack: relies upon instruction ordering.
if ext0, ok := b.Instrs[n-3].(*ir.Extract); ok {
return ext0, ta.X, ta.AssertedType
}
}
}
}
}
return
}

View File

@ -0,0 +1,70 @@
package irutil
import (
"honnef.co/go/tools/ir"
)
func Reachable(from, to *ir.BasicBlock) bool {
if from == to {
return true
}
if from.Dominates(to) {
return true
}
found := false
Walk(from, func(b *ir.BasicBlock) bool {
if b == to {
found = true
return false
}
return true
})
return found
}
func Walk(b *ir.BasicBlock, fn func(*ir.BasicBlock) bool) {
seen := map[*ir.BasicBlock]bool{}
wl := []*ir.BasicBlock{b}
for len(wl) > 0 {
b := wl[len(wl)-1]
wl = wl[:len(wl)-1]
if seen[b] {
continue
}
seen[b] = true
if !fn(b) {
continue
}
wl = append(wl, b.Succs...)
}
}
func Vararg(x *ir.Slice) ([]ir.Value, bool) {
var out []ir.Value
slice, ok := x.X.(*ir.Alloc)
if !ok {
return nil, false
}
for _, ref := range *slice.Referrers() {
if ref == x {
continue
}
if ref.Block() != x.Block() {
return nil, false
}
idx, ok := ref.(*ir.IndexAddr)
if !ok {
return nil, false
}
if len(*idx.Referrers()) != 1 {
return nil, false
}
store, ok := (*idx.Referrers())[0].(*ir.Store)
if !ok {
return nil, false
}
out = append(out, store.Val)
}
return out, true
}

View File

@ -0,0 +1,79 @@
// 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 irutil // import "honnef.co/go/tools/ir/irutil"
import "honnef.co/go/tools/ir"
// This file defines utilities for visiting the IR 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 *ir.Program) map[*ir.Function]bool {
visit := visitor{
prog: prog,
seen: make(map[*ir.Function]bool),
}
visit.program()
return visit.seen
}
type visitor struct {
prog *ir.Program
seen map[*ir.Function]bool
}
func (visit *visitor) program() {
for _, pkg := range visit.prog.AllPackages() {
for _, mem := range pkg.Members {
if fn, ok := mem.(*ir.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 *ir.Function) {
if !visit.seen[fn] {
visit.seen[fn] = true
var buf [10]*ir.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).(*ir.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 []*ir.Package) []*ir.Package {
var mains []*ir.Package
for _, pkg := range pkgs {
if pkg.Pkg.Name() == "main" && pkg.Func("main") != nil {
mains = append(mains, pkg)
}
}
return mains
}

1063
vendor/honnef.co/go/tools/ir/lift.go vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -2,14 +2,13 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
package ir
// lvalues are the union of addressable expressions and map-index
// expressions.
import (
"go/ast"
"go/token"
"go/types"
)
@ -18,27 +17,24 @@ import (
// 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
store(fn *Function, v Value, source ast.Node) // stores v into the location
load(fn *Function, source ast.Node) 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
pos token.Pos // source position
expr ast.Expr // source syntax of the value (not address) [debug mode]
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) load(fn *Function, source ast.Node) Value {
return emitLoad(fn, a.addr, source)
}
func (a *address) store(fn *Function, v Value) {
store := emitStore(fn, a.addr, v, a.pos)
func (a *address) store(fn *Function, v Value, source ast.Node) {
store := emitStore(fn, a.addr, v, source)
if a.expr != nil {
// store.Val is v, converted for assignability.
emitDebugRef(fn, a.expr, store.Val, false)
@ -57,38 +53,35 @@ func (a *address) typ() types.Type {
}
// An element is an lvalue represented by m[k], the location of an
// element of a map or string. These locations are not addressable
// element of a map. These locations are not addressable
// since pointers cannot be formed from them, but they do support
// load(), and in the case of maps, store().
// load() and store().
//
type element struct {
m, k Value // map or string
t types.Type // map element type or string byte type
pos token.Pos // source position of colon ({k:v}) or lbrack (m[k]=v)
m, k Value // map
t types.Type // map element type
}
func (e *element) load(fn *Function) Value {
l := &Lookup{
func (e *element) load(fn *Function, source ast.Node) Value {
l := &MapLookup{
X: e.m,
Index: e.k,
}
l.setPos(e.pos)
l.setType(e.t)
return fn.emit(l)
return fn.emit(l, source)
}
func (e *element) store(fn *Function, v Value) {
func (e *element) store(fn *Function, v Value, source ast.Node) {
up := &MapUpdate{
Map: e.m,
Key: e.k,
Value: emitConv(fn, v, e.t),
Value: emitConv(fn, v, e.t, source),
}
up.pos = e.pos
fn.emit(up)
fn.emit(up, source)
}
func (e *element) address(fn *Function) Value {
panic("map/string elements are not addressable")
panic("map elements are not addressable")
}
func (e *element) typ() types.Type {
@ -100,15 +93,15 @@ func (e *element) typ() types.Type {
//
type blank struct{}
func (bl blank) load(fn *Function) Value {
func (bl blank) load(fn *Function, source ast.Node) Value {
panic("blank.load is illegal")
}
func (bl blank) store(fn *Function, v Value) {
func (bl blank) store(fn *Function, v Value, source ast.Node) {
s := &BlankStore{
Val: v,
}
fn.emit(s)
fn.emit(s, source)
}
func (bl blank) address(fn *Function) Value {

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
package ir
// This file defines utilities for population of method sets.

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
package ir
// This file defines the BuilderMode type and its command-line flag.
@ -15,32 +15,30 @@ import (
//
// *BuilderMode satisfies the flag.Value interface. Example:
//
// var mode = ssa.BuilderMode(0)
// func init() { flag.Var(&mode, "build", ssa.BuilderModeDoc) }
// var mode = ir.BuilderMode(0)
// func init() { flag.Var(&mode, "build", ir.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
PrintFunctions // Print function IR code to stdout
PrintSource // Print source code when printing function IR
LogSource // Log source locations as IR 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.
NaiveForm // Build naïve IR form: don't replace local loads/stores with registers
GlobalDebug // Enable debug info for all packages
BareInits // Build init functions without guards or calls to dependent inits
)
const BuilderModeDoc = `Options controlling the SSA builder.
const BuilderModeDoc = `Options controlling the IR builder.
The value is a sequence of zero or more of these letters:
C perform sanity [C]hecking of the SSA form.
C perform sanity [C]hecking of the IR 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.
F print [F]unction IR code.
A print [A]ST nodes responsible for IR instructions
S log [S]ource locations as IR builder progresses.
N build [N]aive IR form: don't replace local loads/stores with registers.
`
func (m BuilderMode) String() string {
@ -54,6 +52,9 @@ func (m BuilderMode) String() string {
if m&PrintFunctions != 0 {
buf.WriteByte('F')
}
if m&PrintSource != 0 {
buf.WriteByte('A')
}
if m&LogSource != 0 {
buf.WriteByte('S')
}
@ -63,9 +64,6 @@ func (m BuilderMode) String() string {
if m&NaiveForm != 0 {
buf.WriteByte('N')
}
if m&BuildSerially != 0 {
buf.WriteByte('L')
}
return buf.String()
}
@ -80,14 +78,14 @@ func (m *BuilderMode) Set(s string) error {
mode |= PrintPackages
case 'F':
mode |= PrintFunctions
case 'A':
mode |= PrintSource
case 'S':
mode |= LogSource | BuildSerially
mode |= LogSource
case 'C':
mode |= SanityCheckFunctions
case 'N':
mode |= NaiveForm
case 'L':
mode |= BuildSerially
default:
return fmt.Errorf("unknown BuilderMode option: %q", c)
}

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
package ir
// This file implements the String() methods for all Value and
// Instruction types.
@ -25,6 +25,9 @@ import (
// references are package-qualified.
//
func relName(v Value, i Instruction) string {
if v == nil {
return "<nil>"
}
var from *types.Package
if i != nil {
from = i.Parent().pkg()
@ -32,8 +35,6 @@ func relName(v Value, i Instruction) string {
switch v := v.(type) {
case Member: // *Function or *Global
return v.RelString(from)
case *Const:
return v.RelString(from)
}
return v.Name()
}
@ -58,36 +59,40 @@ func relString(m Member, from *types.Package) string {
func (v *Parameter) String() string {
from := v.Parent().pkg()
return fmt.Sprintf("parameter %s : %s", v.Name(), relType(v.Type(), from))
return fmt.Sprintf("Parameter <%s> {%s}", relType(v.Type(), from), v.name)
}
func (v *FreeVar) String() string {
from := v.Parent().pkg()
return fmt.Sprintf("freevar %s : %s", v.Name(), relType(v.Type(), from))
return fmt.Sprintf("FreeVar <%s> %s", relType(v.Type(), from), v.Name())
}
func (v *Builtin) String() string {
return fmt.Sprintf("builtin %s", v.Name())
return fmt.Sprintf("Builtin %s", v.Name())
}
// Instruction.String()
func (v *Alloc) String() string {
op := "local"
if v.Heap {
op = "new"
}
from := v.Parent().pkg()
return fmt.Sprintf("%s %s (%s)", op, relType(deref(v.Type()), from), v.Comment)
storage := "Stack"
if v.Heap {
storage = "Heap"
}
return fmt.Sprintf("%sAlloc <%s>", storage, relType(v.Type(), from))
}
func (v *Sigma) String() string {
from := v.Parent().pkg()
s := fmt.Sprintf("Sigma <%s> [b%d] %s", relType(v.Type(), from), v.From.Index, v.X.Name())
return s
}
func (v *Phi) String() string {
var b bytes.Buffer
b.WriteString("phi [")
fmt.Fprintf(&b, "Phi <%s>", v.Type())
for i, edge := range v.Edges {
if i > 0 {
b.WriteString(", ")
}
b.WriteString(" ")
// Be robust against malformed CFG.
if v.block == nil {
b.WriteString("??")
@ -97,40 +102,35 @@ func (v *Phi) String() string {
if i < len(v.block.Preds) {
block = v.block.Preds[i].Index
}
fmt.Fprintf(&b, "%d: ", block)
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(", ")
if value, ok := instr.(Value); ok {
fmt.Fprintf(&b, "%s <%s> %s", prefix, relType(value.Type(), instr.Parent().pkg()), relName(v.Value, instr))
} else {
fmt.Fprintf(&b, "%s %s", prefix, relName(v.Value, instr))
}
} else {
if value, ok := instr.(Value); ok {
fmt.Fprintf(&b, "%sInvoke <%s> %s.%s", prefix, relType(value.Type(), instr.Parent().pkg()), relName(v.Value, instr), v.Method.Name())
} else {
fmt.Fprintf(&b, "%sInvoke %s.%s", prefix, relName(v.Value, instr), v.Method.Name())
}
}
for _, arg := range v.Args {
b.WriteString(" ")
b.WriteString(relName(arg, instr))
}
if v.Signature().Variadic() {
b.WriteString("...")
}
b.WriteString(")")
return b.String()
}
@ -139,73 +139,59 @@ func (c *CallCommon) String() string {
}
func (v *Call) String() string {
return printCall(&v.Call, "", v)
return printCall(&v.Call, "Call", v)
}
func (v *BinOp) String() string {
return fmt.Sprintf("%s %s %s", relName(v.X, v), v.Op.String(), relName(v.Y, v))
return fmt.Sprintf("BinOp <%s> {%s} %s %s", relType(v.Type(), v.Parent().pkg()), v.Op.String(), relName(v.X, v), relName(v.Y, v))
}
func (v *UnOp) String() string {
return fmt.Sprintf("%s%s%s", v.Op, relName(v.X, v), commaOk(v.CommaOk))
return fmt.Sprintf("UnOp <%s> {%s} %s", relType(v.Type(), v.Parent().pkg()), v.Op.String(), relName(v.X, v))
}
func (v *Load) String() string {
return fmt.Sprintf("Load <%s> %s", relType(v.Type(), v.Parent().pkg()), relName(v.X, v))
}
func printConv(prefix string, v, x Value) string {
from := v.Parent().pkg()
return fmt.Sprintf("%s %s <- %s (%s)",
return fmt.Sprintf("%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 *MakeInterface) String() string { return printConv("make", v, v.X) }
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("ChangeInterface", v, v.X) }
func (v *MakeInterface) String() string { return printConv("MakeInterface", v, v.X) }
func (v *MakeClosure) String() string {
from := v.Parent().pkg()
var b bytes.Buffer
fmt.Fprintf(&b, "make closure %s", relName(v.Fn, v))
fmt.Fprintf(&b, "MakeClosure <%s> %s", relType(v.Type(), from), relName(v.Fn, v))
if v.Bindings != nil {
b.WriteString(" [")
for i, c := range v.Bindings {
if i > 0 {
b.WriteString(", ")
}
for _, c := range v.Bindings {
b.WriteString(" ")
b.WriteString(relName(c, v))
}
b.WriteString("]")
}
return b.String()
}
func (v *MakeSlice) String() string {
from := v.Parent().pkg()
return fmt.Sprintf("make %s %s %s",
return fmt.Sprintf("MakeSlice <%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()
from := v.Parent().pkg()
return fmt.Sprintf("Slice <%s> %s %s %s %s",
relType(v.Type(), from), relName(v.X, v), relName(v.Low, v), relName(v.High, v), relName(v.Max, v))
}
func (v *MakeMap) String() string {
@ -214,22 +200,23 @@ func (v *MakeMap) String() string {
res = relName(v.Reserve, v)
}
from := v.Parent().pkg()
return fmt.Sprintf("make %s %s", relType(v.Type(), from), res)
return fmt.Sprintf("MakeMap <%s> %s", relType(v.Type(), from), res)
}
func (v *MakeChan) String() string {
from := v.Parent().pkg()
return fmt.Sprintf("make %s %s", relType(v.Type(), from), relName(v.Size, v))
return fmt.Sprintf("MakeChan <%s> %s", relType(v.Type(), from), relName(v.Size, v))
}
func (v *FieldAddr) String() string {
from := v.Parent().pkg()
st := deref(v.X.Type()).Underlying().(*types.Struct)
// Be robust against a bad index.
name := "?"
if 0 <= v.Field && v.Field < st.NumFields() {
name = st.Field(v.Field).Name()
}
return fmt.Sprintf("&%s.%s [#%d]", relName(v.X, v), name, v.Field)
return fmt.Sprintf("FieldAddr <%s> [%d] (%s) %s", relType(v.Type(), from), v.Field, name, relName(v.X, v))
}
func (v *Field) String() string {
@ -239,36 +226,49 @@ func (v *Field) String() string {
if 0 <= v.Field && v.Field < st.NumFields() {
name = st.Field(v.Field).Name()
}
return fmt.Sprintf("%s.%s [#%d]", relName(v.X, v), name, v.Field)
from := v.Parent().pkg()
return fmt.Sprintf("Field <%s> [%d] (%s) %s", relType(v.Type(), from), v.Field, name, relName(v.X, v))
}
func (v *IndexAddr) String() string {
return fmt.Sprintf("&%s[%s]", relName(v.X, v), relName(v.Index, v))
from := v.Parent().pkg()
return fmt.Sprintf("IndexAddr <%s> %s %s", relType(v.Type(), from), 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))
from := v.Parent().pkg()
return fmt.Sprintf("Index <%s> %s %s", relType(v.Type(), from), 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 *MapLookup) String() string {
from := v.Parent().pkg()
return fmt.Sprintf("MapLookup <%s> %s %s", relType(v.Type(), from), relName(v.X, v), relName(v.Index, v))
}
func (v *StringLookup) String() string {
from := v.Parent().pkg()
return fmt.Sprintf("StringLookup <%s> %s %s", relType(v.Type(), from), relName(v.X, v), relName(v.Index, v))
}
func (v *Range) String() string {
return "range " + relName(v.X, v)
from := v.Parent().pkg()
return fmt.Sprintf("Range <%s> %s", relType(v.Type(), from), relName(v.X, v))
}
func (v *Next) String() string {
return "next " + relName(v.Iter, v)
from := v.Parent().pkg()
return fmt.Sprintf("Next <%s> %s", relType(v.Type(), from), relName(v.Iter, v))
}
func (v *TypeAssert) String() string {
from := v.Parent().pkg()
return fmt.Sprintf("typeassert%s %s.(%s)", commaOk(v.CommaOk), relName(v.X, v), relType(v.AssertedType, from))
return fmt.Sprintf("TypeAssert <%s> %s", relType(v.Type(), from), relName(v.X, v))
}
func (v *Extract) String() string {
return fmt.Sprintf("extract %s #%d", relName(v.Tuple, v), v.Index)
from := v.Parent().pkg()
name := v.Tuple.Type().(*types.Tuple).At(v.Index).Name()
return fmt.Sprintf("Extract <%s> [%d] (%s) %s", relType(v.Type(), from), v.Index, name, relName(v.Tuple, v))
}
func (s *Jump) String() string {
@ -277,7 +277,20 @@ func (s *Jump) String() string {
if s.block != nil && len(s.block.Succs) == 1 {
block = s.block.Succs[0].Index
}
return fmt.Sprintf("jump %d", block)
str := fmt.Sprintf("Jump → b%d", block)
if s.Comment != "" {
str = fmt.Sprintf("%s # %s", str, s.Comment)
}
return str
}
func (s *Unreachable) 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("Unreachable → b%d", block)
}
func (s *If) String() string {
@ -287,41 +300,70 @@ func (s *If) String() string {
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)
return fmt.Sprintf("If %s → b%d b%d", relName(s.Cond, s), tblock, fblock)
}
func (s *ConstantSwitch) String() string {
var b bytes.Buffer
fmt.Fprintf(&b, "ConstantSwitch %s", relName(s.Tag, s))
for _, cond := range s.Conds {
fmt.Fprintf(&b, " %s", relName(cond, s))
}
fmt.Fprint(&b, " →")
for _, succ := range s.block.Succs {
fmt.Fprintf(&b, " b%d", succ.Index)
}
return b.String()
}
func (s *TypeSwitch) String() string {
from := s.Parent().pkg()
var b bytes.Buffer
fmt.Fprintf(&b, "TypeSwitch <%s> %s", relType(s.typ, from), relName(s.Tag, s))
for _, cond := range s.Conds {
fmt.Fprintf(&b, " %q", relType(cond, s.block.parent.pkg()))
}
return b.String()
}
func (s *Go) String() string {
return printCall(&s.Call, "go ", s)
return printCall(&s.Call, "Go", s)
}
func (s *Panic) String() string {
return "panic " + relName(s.X, s)
// 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("Panic %s → b%d", relName(s.X, s), block)
}
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("Return")
for _, r := range s.Results {
b.WriteString(" ")
b.WriteString(relName(r, s))
}
return b.String()
}
func (*RunDefers) String() string {
return "rundefers"
return "RunDefers"
}
func (s *Send) String() string {
return fmt.Sprintf("send %s <- %s", relName(s.Chan, s), relName(s.X, s))
return fmt.Sprintf("Send %s %s", relName(s.Chan, s), relName(s.X, s))
}
func (recv *Recv) String() string {
from := recv.Parent().pkg()
return fmt.Sprintf("Recv <%s> %s", relType(recv.Type(), from), relName(recv.Chan, recv))
}
func (s *Defer) String() string {
return printCall(&s.Call, "defer ", s)
return printCall(&s.Call, "Defer", s)
}
func (s *Select) String() string {
@ -341,21 +383,23 @@ func (s *Select) String() string {
}
non := ""
if !s.Blocking {
non = "non"
non = "Non"
}
return fmt.Sprintf("select %sblocking [%s]", non, b.String())
from := s.Parent().pkg()
return fmt.Sprintf("Select%sBlocking <%s> [%s]", non, relType(s.Type(), from), b.String())
}
func (s *Store) String() string {
return fmt.Sprintf("*%s = %s", relName(s.Addr, s), relName(s.Val, s))
return fmt.Sprintf("Store {%s} %s %s",
s.Val.Type(), relName(s.Addr, s), relName(s.Val, s))
}
func (s *BlankStore) String() string {
return fmt.Sprintf("_ = %s", relName(s.Val, s))
return fmt.Sprintf("BlankStore %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))
return fmt.Sprintf("MapUpdate %s %s %s", relName(s.Map, s), relName(s.Key, s), relName(s.Value, s))
}
func (s *DebugRef) String() string {
@ -426,10 +470,3 @@ func WritePackage(buf *bytes.Buffer, p *Package) {
fmt.Fprintf(buf, "\n")
}
func commaOk(x bool) string {
if x {
return ",ok"
}
return ""
}

View File

@ -2,9 +2,9 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
package ir
// An optional pass for sanity-checking invariants of the SSA representation.
// An optional pass for sanity-checking invariants of the IR representation.
// Currently it checks CFG invariants but little at the instruction level.
import (
@ -23,7 +23,7 @@ type sanity struct {
insane bool
}
// sanityCheck performs integrity checking of the SSA representation
// sanityCheck performs integrity checking of the IR representation
// of the function fn 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.
@ -89,8 +89,15 @@ func findDuplicate(blocks []*BasicBlock) *BasicBlock {
func (s *sanity) checkInstr(idx int, instr Instruction) {
switch instr := instr.(type) {
case *If, *Jump, *Return, *Panic:
case *If, *Jump, *Return, *Panic, *Unreachable, *ConstantSwitch:
s.errorf("control flow instruction not at end of block")
case *Sigma:
if idx > 0 {
prev := s.block.Instrs[idx-1]
if _, ok := prev.(*Sigma); !ok {
s.errorf("Sigma instruction follows a non-Sigma: %T", prev)
}
}
case *Phi:
if idx == 0 {
// It suffices to apply this check to just the first phi node.
@ -99,8 +106,10 @@ func (s *sanity) checkInstr(idx int, instr Instruction) {
}
} else {
prev := s.block.Instrs[idx-1]
if _, ok := prev.(*Phi); !ok {
s.errorf("Phi instruction follows a non-Phi: %T", prev)
switch prev.(type) {
case *Phi, *Sigma:
default:
s.errorf("Phi instruction follows a non-Phi, non-Sigma: %T", prev)
}
}
if ne, np := len(instr.Edges), len(s.block.Preds); ne != np {
@ -109,7 +118,7 @@ func (s *sanity) checkInstr(idx int, instr Instruction) {
} 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])
s.errorf("phi node '%v' has no value for edge #%d from %s", instr, i, s.block.Preds[i])
}
}
}
@ -146,7 +155,8 @@ func (s *sanity) checkInstr(idx int, instr Instruction) {
case *Go:
case *Index:
case *IndexAddr:
case *Lookup:
case *MapLookup:
case *StringLookup:
case *MakeChan:
case *MakeClosure:
numFree := len(instr.Fn.(*Function).FreeVars)
@ -175,8 +185,11 @@ func (s *sanity) checkInstr(idx int, instr Instruction) {
case *UnOp:
case *DebugRef:
case *BlankStore:
case *Sigma:
// TODO(adonovan): implement checks.
case *Load:
case *Parameter:
case *Const:
case *Recv:
case *TypeSwitch:
default:
panic(fmt.Sprintf("Unknown instruction type: %T", instr))
}
@ -196,7 +209,9 @@ func (s *sanity) checkInstr(idx int, instr Instruction) {
} else if t == tRangeIter {
// 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)
if _, ok := v.(*Const); !ok {
s.errorf("instruction has 'untyped' result: %s = %s : %s", v.Name(), v, t)
}
}
s.checkReferrerList(v)
}
@ -239,11 +254,19 @@ func (s *sanity) checkFinalInstr(instr Instruction) {
}
case *Panic:
if nsuccs := len(s.block.Succs); nsuccs != 0 {
s.errorf("Panic-terminated block has %d successors; expected none", nsuccs)
if nsuccs := len(s.block.Succs); nsuccs != 1 {
s.errorf("Panic-terminated block has %d successors; expected one", nsuccs)
return
}
case *Unreachable:
if nsuccs := len(s.block.Succs); nsuccs != 1 {
s.errorf("Unreachable-terminated block has %d successors; expected one", nsuccs)
return
}
case *ConstantSwitch:
default:
s.errorf("non-control flow instruction at end of block")
}
@ -260,9 +283,8 @@ func (s *sanity) checkBlock(b *BasicBlock, index int) {
}
// 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 {
// (The entry block is always implicitly reachable, the exit block may be unreachable.)
if index > 1 && len(b.Preds) == 0 {
s.warnf("unreachable block")
if b.Instrs == nil {
// Since this block is about to be pruned,
@ -395,7 +417,11 @@ func (s *sanity) checkReferrerList(v Value) {
}
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)
if val, ok := ref.(Value); ok {
s.errorf("%s.Referrers()[%d] = %s = %s is not an instruction belonging to this function", v.Name(), i, val.Name(), val)
} else {
s.errorf("%s.Referrers()[%d] = %s is not an instruction belonging to this function", v.Name(), i, ref)
}
}
}
}
@ -426,7 +452,7 @@ func (s *sanity) checkFunction(fn *Function) bool {
s.errorf("nil Pkg")
}
}
if src, syn := fn.Synthetic == "", fn.Syntax() != nil; src != syn {
if src, syn := fn.Synthetic == "", fn.source != nil; src != syn {
s.errorf("got fromSource=%t, hasSyntax=%t; want same values", src, syn)
}
for i, l := range fn.Locals {
@ -481,9 +507,6 @@ func (s *sanity) checkFunction(fn *Function) bool {
}
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 {
@ -522,14 +545,11 @@ func sanityCheckPackage(pkg *Package) {
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").
// its types.Func ("init") and its ir.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()))
}
}
}

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
package ir
// This file defines utilities for working with source positions
// or source-level named entities ("objects").
@ -25,7 +25,7 @@ import (
// 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
// its IR function has not been created yet
// (pkg.Build() has not yet been called).
//
func EnclosingFunction(pkg *Package, path []ast.Node) *Function {
@ -46,7 +46,7 @@ outer:
continue outer
}
}
// SSA function not found:
// IR function not found:
// - package not yet built, or maybe
// - builder skipped FuncLit in dead block
// (in principle; but currently the Builder
@ -62,9 +62,9 @@ outer:
// 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
// depend on whether IR 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.
// EnclosingFunction to fail, prior to IR building.
//
func HasEnclosingFunction(pkg *Package, path []ast.Node) bool {
return findEnclosingPackageLevelFunction(pkg, path) != nil
@ -83,23 +83,14 @@ func findEnclosingPackageLevelFunction(pkg *Package, path []ast.Node) *Function
}
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
// Declared function/method.
fn := findNamedFunc(pkg, decl.Pos())
if fn == nil && decl.Recv == nil && decl.Name.Name == "init" {
// Hack: return non-nil when IR is not yet
// built so that HasEnclosingFunction works.
return pkg.init
}
// Declared function/method.
return findNamedFunc(pkg, decl.Name.NamePos)
return fn
}
}
return nil // not in any function
@ -109,29 +100,15 @@ func findEnclosingPackageLevelFunction(pkg *Package, path []ast.Node) *Function
// 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 {
return pkg.values[obj].(*Function)
}
}
for _, fn := range pkg.Functions {
if fn.Pos() == pos {
return fn
}
}
return nil
}
// ValueForExpr returns the SSA Value that corresponds to non-constant
// ValueForExpr returns the IR Value that corresponds to non-constant
// expression e.
//
// It returns nil if no value was found, e.g.
@ -149,10 +126,10 @@ func findNamedFunc(pkg *Package, pos token.Pos) *Function {
// 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
// (Tip: to find the ir.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.)
// the ir.Value.)
//
func (f *Function) ValueForExpr(e ast.Expr) (value Value, isAddr bool) {
if f.debugInfo() { // (opt)
@ -172,9 +149,9 @@ func (f *Function) ValueForExpr(e ast.Expr) (value Value, isAddr bool) {
// --- Lookup functions for source-level named entities (types.Objects) ---
// Package returns the SSA Package corresponding to the specified
// Package returns the IR Package corresponding to the specified
// type-checker package object.
// It returns nil if no such SSA package has been created.
// It returns nil if no such IR package has been created.
//
func (prog *Program) Package(obj *types.Package) *Package {
return prog.packages[obj]
@ -203,7 +180,7 @@ func (prog *Program) FuncValue(obj *types.Func) *Function {
return fn
}
// ConstValue returns the SSA Value denoted by the source-level named
// ConstValue returns the IR Value denoted by the source-level named
// constant obj.
//
func (prog *Program) ConstValue(obj *types.Const) *Const {
@ -221,12 +198,12 @@ func (prog *Program) ConstValue(obj *types.Const) *Const {
return NewConst(obj.Val(), obj.Type())
}
// VarValue returns the SSA Value that corresponds to a specific
// VarValue returns the IR Value that corresponds to a specific
// identifier denoting the source-level named variable obj.
//
// 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.
// requested during IR 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.
@ -252,14 +229,14 @@ func (prog *Program) ConstValue(obj *types.Const) *Const {
//
// 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
// during IR 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?
return // e.g. def of struct field; IR not built?
}
id := ref[0].(*ast.Ident)

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
package ir
// This file defines a number of miscellaneous utility functions.
@ -52,36 +52,6 @@ func recvType(obj *types.Func) types.Type {
return obj.Type().(*types.Signature).Recv().Type()
}
// DefaultType returns the default "typed" type for an "untyped" type;
// it returns the incoming type for all other types. The default type
// for untyped nil is untyped nil.
//
// Exported to ssa/interp.
//
// TODO(adonovan): use go/types.DefaultType after 1.8.
//
func DefaultType(typ types.Type) types.Type {
if t, ok := typ.(*types.Basic); ok {
k := t.Kind()
switch k {
case types.UntypedBool:
k = types.Bool
case types.UntypedInt:
k = types.Int
case types.UntypedRune:
k = types.Rune
case types.UntypedFloat:
k = types.Float64
case types.UntypedComplex:
k = types.Complex128
case types.UntypedString:
k = types.String
}
typ = types.Typ[k]
}
return typ
}
// 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.

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
package ir
// This file defines synthesis of Functions that delegate to declared
// methods; they come in three kinds:
@ -65,41 +65,42 @@ func makeWrapper(prog *Program, sel *types.Selection) *Function {
defer logStack("make %s to (%s)", description, recv.Type())()
}
fn := &Function{
name: name,
method: sel,
object: obj,
Signature: sig,
Synthetic: description,
Prog: prog,
pos: obj.Pos(),
name: name,
method: sel,
object: obj,
Signature: sig,
Synthetic: description,
Prog: prog,
functionBody: new(functionBody),
}
fn.initHTML(prog.PrintFunc)
fn.startBody()
fn.addSpilledParam(recv)
fn.addSpilledParam(recv, nil)
createParams(fn, start)
indices := sel.Index()
var v Value = fn.Locals[0] // spilled receiver
if isPointer(sel.Recv()) {
v = emitLoad(fn, v)
v = emitLoad(fn, v, nil)
// For simple indirection wrappers, perform an informative nil-check:
// "value method (T).f called using nil *T pointer"
if len(indices) == 1 && !isPointer(recvType(obj)) {
var c Call
c.Call.Value = &Builtin{
name: "ssa:wrapnilchk",
name: "ir:wrapnilchk",
sig: types.NewSignature(nil,
types.NewTuple(anonVar(sel.Recv()), anonVar(tString), anonVar(tString)),
types.NewTuple(anonVar(sel.Recv())), false),
}
c.Call.Args = []Value{
v,
stringConst(deref(sel.Recv()).String()),
stringConst(sel.Obj().Name()),
emitConst(fn, stringConst(deref(sel.Recv()).String())),
emitConst(fn, stringConst(sel.Obj().Name())),
}
c.setType(v.Type())
v = fn.emit(&c)
v = fn.emit(&c, nil)
}
}
@ -111,7 +112,7 @@ func makeWrapper(prog *Program, sel *types.Selection) *Function {
// Load) in preference to value extraction (Field possibly
// preceded by Load).
v = emitImplicitSelections(fn, v, indices[:len(indices)-1])
v = emitImplicitSelections(fn, v, indices[:len(indices)-1], nil)
// Invariant: v is a pointer, either
// value of implicit *C field, or
@ -120,18 +121,18 @@ func makeWrapper(prog *Program, sel *types.Selection) *Function {
var c Call
if r := recvType(obj); !isInterface(r) { // concrete method
if !isPointer(r) {
v = emitLoad(fn, v)
v = emitLoad(fn, v, nil)
}
c.Call.Value = prog.declaredFunc(obj)
c.Call.Args = append(c.Call.Args, v)
} else {
c.Call.Method = obj
c.Call.Value = emitLoad(fn, v)
c.Call.Value = emitLoad(fn, v, nil)
}
for _, arg := range fn.Params[1:] {
c.Call.Args = append(c.Call.Args, arg)
}
emitTailCall(fn, &c)
emitTailCall(fn, &c, nil)
fn.finishBody()
return fn
}
@ -143,7 +144,7 @@ func makeWrapper(prog *Program, sel *types.Selection) *Function {
func createParams(fn *Function, start int) {
tparams := fn.Signature.Params()
for i, n := start, tparams.Len(); i < n; i++ {
fn.addParamObj(tparams.At(i))
fn.addParamObj(tparams.At(i), nil)
}
}
@ -184,13 +185,14 @@ func makeBound(prog *Program, obj *types.Func) *Function {
defer logStack("%s", description)()
}
fn = &Function{
name: obj.Name() + "$bound",
object: obj,
Signature: changeRecv(obj.Type().(*types.Signature), nil), // drop receiver
Synthetic: description,
Prog: prog,
pos: obj.Pos(),
name: obj.Name() + "$bound",
object: obj,
Signature: changeRecv(obj.Type().(*types.Signature), nil), // drop receiver
Synthetic: description,
Prog: prog,
functionBody: new(functionBody),
}
fn.initHTML(prog.PrintFunc)
fv := &FreeVar{name: "recv", typ: recvType(obj), parent: fn}
fn.FreeVars = []*FreeVar{fv}
@ -208,7 +210,7 @@ func makeBound(prog *Program, obj *types.Func) *Function {
for _, arg := range fn.Params {
c.Call.Args = append(c.Call.Args, arg)
}
emitTailCall(fn, &c)
emitTailCall(fn, &c, nil)
fn.finishBody()
prog.bounds[obj] = fn

5
vendor/honnef.co/go/tools/ir/write.go vendored Normal file
View File

@ -0,0 +1,5 @@
package ir
func NewJump(parent *BasicBlock) *Jump {
return &Jump{anInstruction{block: parent}, ""}
}

View File

@ -3,6 +3,7 @@ package lint // import "honnef.co/go/tools/lint"
import (
"bytes"
"encoding/gob"
"fmt"
"go/scanner"
"go/token"
@ -17,6 +18,7 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/packages"
"honnef.co/go/tools/config"
"honnef.co/go/tools/internal/cache"
)
type Documentation struct {
@ -62,7 +64,7 @@ type LineIgnore struct {
Line int
Checks []string
Matched bool
Pos token.Pos
Pos token.Position
}
func (li *LineIgnore) Match(p Problem) bool {
@ -119,6 +121,21 @@ type Problem struct {
Message string
Check string
Severity Severity
Related []Related
}
type Related struct {
Pos token.Position
End token.Position
Message string
}
func (p Problem) Equal(o Problem) bool {
return p.Pos == o.Pos &&
p.End == o.End &&
p.Message == o.Message &&
p.Check == o.Check &&
p.Severity == o.Severity
}
func (p *Problem) String() string {
@ -132,6 +149,7 @@ type Linter struct {
GoVersion int
Config config.Config
Stats Stats
RepeatAnalyzers uint
}
type CumulativeChecker interface {
@ -184,6 +202,7 @@ func (l *Linter) Lint(cfg *packages.Config, patterns []string) ([]Problem, error
return nil, err
}
r.goVersion = l.GoVersion
r.repeatAnalyzers = l.RepeatAnalyzers
pkgs, err := r.Run(cfg, patterns, allowedAnalyzers, hasCumulative)
if err != nil {
@ -264,10 +283,12 @@ func (l *Linter) Lint(cfg *packages.Config, patterns []string) ([]Problem, error
}
atomic.StoreUint32(&r.stats.State, StateCumulative)
var problems []Problem
for _, cum := range l.CumulativeCheckers {
for _, res := range cum.Result() {
pkg := tpkgToPkg[res.Pkg()]
if pkg == nil {
panic(fmt.Sprintf("analyzer %s flagged object %s in package %s, a package that we aren't tracking", cum.Analyzer(), res, res.Pkg()))
}
allowedChecks := FilterChecks(allowedAnalyzers, pkg.cfg.Merge(l.Config).Checks)
if allowedChecks[cum.Analyzer().Name] {
pos := DisplayPosition(pkg.Fset, res.Pos())
@ -278,21 +299,51 @@ func (l *Linter) Lint(cfg *packages.Config, patterns []string) ([]Problem, error
continue
}
p := cum.ProblemObject(pkg.Fset, res)
problems = append(problems, p)
pkg.problems = append(pkg.problems, p)
}
}
}
for _, pkg := range pkgs {
if !pkg.fromSource {
// Don't cache packages that we loaded from the cache
continue
}
cpkg := cachedPackage{
Problems: pkg.problems,
Ignores: pkg.ignores,
Config: pkg.cfg,
}
buf := &bytes.Buffer{}
if err := gob.NewEncoder(buf).Encode(cpkg); err != nil {
return nil, err
}
id := cache.Subkey(pkg.actionID, "data "+r.problemsCacheKey)
if err := r.cache.PutBytes(id, buf.Bytes()); err != nil {
return nil, err
}
}
var problems []Problem
// Deduplicate line ignores. When U1000 processes a package and
// its test variant, it will only emit a single problem for an
// unused object, not two problems. We will, however, have two
// line ignores, one per package. Without deduplication, one line
// ignore will be marked as matched, while the other one won't,
// subsequently reporting a "this linter directive didn't match
// anything" error.
ignores := map[token.Position]Ignore{}
for _, pkg := range pkgs {
for _, ig := range pkg.ignores {
for i := range pkg.problems {
p := &pkg.problems[i]
if ig.Match(*p) {
p.Severity = Ignored
if lig, ok := ig.(*LineIgnore); ok {
ig = ignores[lig.Pos]
if ig == nil {
ignores[lig.Pos] = lig
ig = lig
}
}
for i := range problems {
p := &problems[i]
for i := range pkg.problems {
p := &pkg.problems[i]
if ig.Match(*p) {
p.Severity = Ignored
}
@ -318,6 +369,7 @@ func (l *Linter) Lint(cfg *packages.Config, patterns []string) ([]Problem, error
if !ok {
continue
}
ig = ignores[ig.Pos].(*LineIgnore)
if ig.Matched {
continue
}
@ -338,7 +390,7 @@ func (l *Linter) Lint(cfg *packages.Config, patterns []string) ([]Problem, error
continue
}
p := Problem{
Pos: DisplayPosition(pkg.Fset, ig.Pos),
Pos: ig.Pos,
Message: "this linter directive didn't match anything; should it be removed?",
Check: "",
}
@ -372,7 +424,7 @@ func (l *Linter) Lint(cfg *packages.Config, patterns []string) ([]Problem, error
for i, p := range problems[1:] {
// We may encounter duplicate problems because one file
// can be part of many packages.
if problems[i] != p {
if !problems[i].Equal(p) {
out = append(out, p)
}
}
@ -422,10 +474,6 @@ func FilterChecks(allChecks []*analysis.Analyzer, checks []string) map[string]bo
return allowedChecks
}
type Positioner interface {
Pos() token.Pos
}
func DisplayPosition(fset *token.FileSet, p token.Pos) token.Position {
if p == token.NoPos {
return token.Position{}

View File

@ -4,283 +4,14 @@ package lintdsl
import (
"bytes"
"flag"
"fmt"
"go/ast"
"go/constant"
"go/printer"
"go/token"
"go/types"
"strings"
"go/format"
"golang.org/x/tools/go/analysis"
"honnef.co/go/tools/facts"
"honnef.co/go/tools/lint"
"honnef.co/go/tools/ssa"
"honnef.co/go/tools/pattern"
)
type packager interface {
Package() *ssa.Package
}
func CallName(call *ssa.CallCommon) string {
if call.IsInvoke() {
return ""
}
switch v := call.Value.(type) {
case *ssa.Function:
fn, ok := v.Object().(*types.Func)
if !ok {
return ""
}
return lint.FuncName(fn)
case *ssa.Builtin:
return v.Name()
}
return ""
}
func IsCallTo(call *ssa.CallCommon, name string) bool { return CallName(call) == name }
func IsType(T types.Type, name string) bool { return types.TypeString(T, nil) == name }
func FilterDebug(instr []ssa.Instruction) []ssa.Instruction {
var out []ssa.Instruction
for _, ins := range instr {
if _, ok := ins.(*ssa.DebugRef); !ok {
out = append(out, ins)
}
}
return out
}
func IsExample(fn *ssa.Function) bool {
if !strings.HasPrefix(fn.Name(), "Example") {
return false
}
f := fn.Prog.Fset.File(fn.Pos())
if f == nil {
return false
}
return strings.HasSuffix(f.Name(), "_test.go")
}
func IsPointerLike(T types.Type) bool {
switch T := T.Underlying().(type) {
case *types.Interface, *types.Chan, *types.Map, *types.Signature, *types.Pointer:
return true
case *types.Basic:
return T.Kind() == types.UnsafePointer
}
return false
}
func IsIdent(expr ast.Expr, ident string) bool {
id, ok := expr.(*ast.Ident)
return ok && id.Name == ident
}
// isBlank returns whether id is the blank identifier "_".
// If id == nil, the answer is false.
func IsBlank(id ast.Expr) bool {
ident, _ := id.(*ast.Ident)
return ident != nil && ident.Name == "_"
}
func IsIntLiteral(expr ast.Expr, literal string) bool {
lit, ok := expr.(*ast.BasicLit)
return ok && lit.Kind == token.INT && lit.Value == literal
}
// Deprecated: use IsIntLiteral instead
func IsZero(expr ast.Expr) bool {
return IsIntLiteral(expr, "0")
}
func IsOfType(pass *analysis.Pass, expr ast.Expr, name string) bool {
return IsType(pass.TypesInfo.TypeOf(expr), name)
}
func IsInTest(pass *analysis.Pass, node lint.Positioner) bool {
// FIXME(dh): this doesn't work for global variables with
// initializers
f := pass.Fset.File(node.Pos())
return f != nil && strings.HasSuffix(f.Name(), "_test.go")
}
func IsInMain(pass *analysis.Pass, node lint.Positioner) bool {
if node, ok := node.(packager); ok {
return node.Package().Pkg.Name() == "main"
}
return pass.Pkg.Name() == "main"
}
func SelectorName(pass *analysis.Pass, expr *ast.SelectorExpr) string {
info := pass.TypesInfo
sel := info.Selections[expr]
if sel == nil {
if x, ok := expr.X.(*ast.Ident); ok {
pkg, ok := info.ObjectOf(x).(*types.PkgName)
if !ok {
// This shouldn't happen
return fmt.Sprintf("%s.%s", x.Name, expr.Sel.Name)
}
return fmt.Sprintf("%s.%s", pkg.Imported().Path(), expr.Sel.Name)
}
panic(fmt.Sprintf("unsupported selector: %v", expr))
}
return fmt.Sprintf("(%s).%s", sel.Recv(), sel.Obj().Name())
}
func IsNil(pass *analysis.Pass, expr ast.Expr) bool {
return pass.TypesInfo.Types[expr].IsNil()
}
func BoolConst(pass *analysis.Pass, expr ast.Expr) bool {
val := pass.TypesInfo.ObjectOf(expr.(*ast.Ident)).(*types.Const).Val()
return constant.BoolVal(val)
}
func IsBoolConst(pass *analysis.Pass, expr ast.Expr) bool {
// We explicitly don't support typed bools because more often than
// not, custom bool types are used as binary enums and the
// explicit comparison is desired.
ident, ok := expr.(*ast.Ident)
if !ok {
return false
}
obj := pass.TypesInfo.ObjectOf(ident)
c, ok := obj.(*types.Const)
if !ok {
return false
}
basic, ok := c.Type().(*types.Basic)
if !ok {
return false
}
if basic.Kind() != types.UntypedBool && basic.Kind() != types.Bool {
return false
}
return true
}
func ExprToInt(pass *analysis.Pass, expr ast.Expr) (int64, bool) {
tv := pass.TypesInfo.Types[expr]
if tv.Value == nil {
return 0, false
}
if tv.Value.Kind() != constant.Int {
return 0, false
}
return constant.Int64Val(tv.Value)
}
func ExprToString(pass *analysis.Pass, expr ast.Expr) (string, bool) {
val := pass.TypesInfo.Types[expr].Value
if val == nil {
return "", false
}
if val.Kind() != constant.String {
return "", false
}
return constant.StringVal(val), true
}
// Dereference returns a pointer's element type; otherwise it returns
// T.
func Dereference(T types.Type) types.Type {
if p, ok := T.Underlying().(*types.Pointer); ok {
return p.Elem()
}
return T
}
// DereferenceR returns a pointer's element type; otherwise it returns
// T. If the element type is itself a pointer, DereferenceR will be
// applied recursively.
func DereferenceR(T types.Type) types.Type {
if p, ok := T.Underlying().(*types.Pointer); ok {
return DereferenceR(p.Elem())
}
return T
}
func IsGoVersion(pass *analysis.Pass, minor int) bool {
version := pass.Analyzer.Flags.Lookup("go").Value.(flag.Getter).Get().(int)
return version >= minor
}
func CallNameAST(pass *analysis.Pass, call *ast.CallExpr) string {
switch fun := call.Fun.(type) {
case *ast.SelectorExpr:
fn, ok := pass.TypesInfo.ObjectOf(fun.Sel).(*types.Func)
if !ok {
return ""
}
return lint.FuncName(fn)
case *ast.Ident:
obj := pass.TypesInfo.ObjectOf(fun)
switch obj := obj.(type) {
case *types.Func:
return lint.FuncName(obj)
case *types.Builtin:
return obj.Name()
default:
return ""
}
default:
return ""
}
}
func IsCallToAST(pass *analysis.Pass, node ast.Node, name string) bool {
call, ok := node.(*ast.CallExpr)
if !ok {
return false
}
return CallNameAST(pass, call) == name
}
func IsCallToAnyAST(pass *analysis.Pass, node ast.Node, names ...string) bool {
for _, name := range names {
if IsCallToAST(pass, node, name) {
return true
}
}
return false
}
func Render(pass *analysis.Pass, x interface{}) string {
var buf bytes.Buffer
if err := printer.Fprint(&buf, pass.Fset, x); err != nil {
panic(err)
}
return buf.String()
}
func RenderArgs(pass *analysis.Pass, args []ast.Expr) string {
var ss []string
for _, arg := range args {
ss = append(ss, Render(pass, arg))
}
return strings.Join(ss, ", ")
}
func Preamble(f *ast.File) string {
cutoff := f.Package
if f.Doc != nil {
cutoff = f.Doc.Pos()
}
var out []string
for _, cmt := range f.Comments {
if cmt.Pos() >= cutoff {
break
}
out = append(out, cmt.Text())
}
return strings.Join(out, "\n")
}
func Inspect(node ast.Node, fn func(node ast.Node) bool) {
if node == nil {
return
@ -288,113 +19,40 @@ func Inspect(node ast.Node, fn func(node ast.Node) bool) {
ast.Inspect(node, fn)
}
func GroupSpecs(fset *token.FileSet, specs []ast.Spec) [][]ast.Spec {
if len(specs) == 0 {
return nil
func Match(pass *analysis.Pass, q pattern.Pattern, node ast.Node) (*pattern.Matcher, bool) {
// Note that we ignore q.Relevant callers of Match usually use
// AST inspectors that already filter on nodes we're interested
// in.
m := &pattern.Matcher{TypesInfo: pass.TypesInfo}
ok := m.Match(q.Root, node)
return m, ok
}
func MatchAndEdit(pass *analysis.Pass, before, after pattern.Pattern, node ast.Node) (*pattern.Matcher, []analysis.TextEdit, bool) {
m, ok := Match(pass, before, node)
if !ok {
return m, nil, false
}
groups := make([][]ast.Spec, 1)
groups[0] = append(groups[0], specs[0])
r := pattern.NodeToAST(after.Root, m.State)
buf := &bytes.Buffer{}
format.Node(buf, pass.Fset, r)
edit := []analysis.TextEdit{{
Pos: node.Pos(),
End: node.End(),
NewText: buf.Bytes(),
}}
return m, edit, true
}
for _, spec := range specs[1:] {
g := groups[len(groups)-1]
if fset.PositionFor(spec.Pos(), false).Line-1 !=
fset.PositionFor(g[len(g)-1].End(), false).Line {
groups = append(groups, nil)
}
groups[len(groups)-1] = append(groups[len(groups)-1], spec)
func Selector(x, sel string) *ast.SelectorExpr {
return &ast.SelectorExpr{
X: &ast.Ident{Name: x},
Sel: &ast.Ident{Name: sel},
}
return groups
}
func IsObject(obj types.Object, name string) bool {
var path string
if pkg := obj.Pkg(); pkg != nil {
path = pkg.Path() + "."
}
return path+obj.Name() == name
}
type Field struct {
Var *types.Var
Tag string
Path []int
}
// FlattenFields recursively flattens T and embedded structs,
// returning a list of fields. If multiple fields with the same name
// exist, all will be returned.
func FlattenFields(T *types.Struct) []Field {
return flattenFields(T, nil, nil)
}
func flattenFields(T *types.Struct, path []int, seen map[types.Type]bool) []Field {
if seen == nil {
seen = map[types.Type]bool{}
}
if seen[T] {
return nil
}
seen[T] = true
var out []Field
for i := 0; i < T.NumFields(); i++ {
field := T.Field(i)
tag := T.Tag(i)
np := append(path[:len(path):len(path)], i)
if field.Anonymous() {
if s, ok := Dereference(field.Type()).Underlying().(*types.Struct); ok {
out = append(out, flattenFields(s, np, seen)...)
}
} else {
out = append(out, Field{field, tag, np})
}
}
return out
}