forked from vikunja/vikunja
Update and fix staticcheck
This commit is contained in:
parent
aae1bc3cab
commit
a525787ab7
2
go.mod
2
go.mod
@ -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
3
go.sum
@ -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=
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
2
vendor/github.com/hashicorp/hcl/go.mod
generated
vendored
2
vendor/github.com/hashicorp/hcl/go.mod
generated
vendored
@ -1,3 +1,5 @@
|
||||
module github.com/hashicorp/hcl
|
||||
|
||||
require github.com/davecgh/go-spew v1.1.1
|
||||
|
||||
go 1.13
|
||||
|
2
vendor/github.com/spf13/afero/go.mod
generated
vendored
2
vendor/github.com/spf13/afero/go.mod
generated
vendored
@ -1,3 +1,5 @@
|
||||
module github.com/spf13/afero
|
||||
|
||||
require golang.org/x/text v0.3.0
|
||||
|
||||
go 1.13
|
||||
|
60
vendor/honnef.co/go/tools/LICENSE-THIRD-PARTY
vendored
60
vendor/honnef.co/go/tools/LICENSE-THIRD-PARTY
vendored
@ -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
481
vendor/honnef.co/go/tools/code/code.go
vendored
Normal 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)
|
||||
}
|
79
vendor/honnef.co/go/tools/config/config.go
vendored
79
vendor/honnef.co/go/tools/config/config.go
vendored
@ -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 {
|
||||
|
77
vendor/honnef.co/go/tools/deprecated/stdlib.go
vendored
77
vendor/honnef.co/go/tools/deprecated/stdlib.go
vendored
@ -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
67
vendor/honnef.co/go/tools/edit/edit.go
vendored
Normal 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,
|
||||
}
|
||||
}
|
7
vendor/honnef.co/go/tools/facts/generated.go
vendored
7
vendor/honnef.co/go/tools/facts/generated.go
vendored
@ -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) {
|
||||
|
78
vendor/honnef.co/go/tools/facts/purity.go
vendored
78
vendor/honnef.co/go/tools/facts/purity.go
vendored
@ -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{}
|
||||
|
12
vendor/honnef.co/go/tools/functions/loops.go
vendored
12
vendor/honnef.co/go/tools/functions/loops.go
vendored
@ -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 {
|
||||
|
46
vendor/honnef.co/go/tools/functions/pure.go
vendored
46
vendor/honnef.co/go/tools/functions/pure.go
vendored
@ -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
|
||||
}
|
||||
}
|
32
vendor/honnef.co/go/tools/functions/stub.go
vendored
Normal file
32
vendor/honnef.co/go/tools/functions/stub.go
vendored
Normal 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
|
||||
}
|
@ -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
|
||||
|
@ -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)
|
||||
|
113
vendor/honnef.co/go/tools/internal/passes/buildir/buildir.go
vendored
Normal file
113
vendor/honnef.co/go/tools/internal/passes/buildir/buildir.go
vendored
Normal 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
|
||||
}
|
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
53
vendor/honnef.co/go/tools/internal/robustio/robustio.go
vendored
Normal file
53
vendor/honnef.co/go/tools/internal/robustio/robustio.go
vendored
Normal 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)
|
||||
}
|
29
vendor/honnef.co/go/tools/internal/robustio/robustio_darwin.go
vendored
Normal file
29
vendor/honnef.co/go/tools/internal/robustio/robustio_darwin.go
vendored
Normal 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
|
||||
}
|
93
vendor/honnef.co/go/tools/internal/robustio/robustio_flaky.go
vendored
Normal file
93
vendor/honnef.co/go/tools/internal/robustio/robustio_flaky.go
vendored
Normal 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)
|
||||
})
|
||||
}
|
28
vendor/honnef.co/go/tools/internal/robustio/robustio_other.go
vendored
Normal file
28
vendor/honnef.co/go/tools/internal/robustio/robustio_other.go
vendored
Normal 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
|
||||
}
|
33
vendor/honnef.co/go/tools/internal/robustio/robustio_windows.go
vendored
Normal file
33
vendor/honnef.co/go/tools/internal/robustio/robustio_windows.go
vendored
Normal 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
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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.
|
File diff suppressed because it is too large
Load Diff
@ -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.
|
||||
//
|
@ -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]
|
@ -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"
|
@ -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, "}")
|
||||
}
|
@ -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
271
vendor/honnef.co/go/tools/ir/exits.go
vendored
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
1124
vendor/honnef.co/go/tools/ir/html.go
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
// +build go1.8
|
||||
|
||||
package ssa
|
||||
package ir
|
||||
|
||||
import "go/types"
|
||||
|
@ -1,6 +1,6 @@
|
||||
// +build !go1.8
|
||||
|
||||
package ssa
|
||||
package ir
|
||||
|
||||
import "go/types"
|
||||
|
183
vendor/honnef.co/go/tools/ir/irutil/load.go
vendored
Normal file
183
vendor/honnef.co/go/tools/ir/irutil/load.go
vendored
Normal 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
|
||||
}
|
264
vendor/honnef.co/go/tools/ir/irutil/switch.go
vendored
Normal file
264
vendor/honnef.co/go/tools/ir/irutil/switch.go
vendored
Normal 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
|
||||
}
|
70
vendor/honnef.co/go/tools/ir/irutil/util.go
vendored
Normal file
70
vendor/honnef.co/go/tools/ir/irutil/util.go
vendored
Normal 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
|
||||
}
|
79
vendor/honnef.co/go/tools/ir/irutil/visit.go
vendored
Normal file
79
vendor/honnef.co/go/tools/ir/irutil/visit.go
vendored
Normal 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
1063
vendor/honnef.co/go/tools/ir/lift.go
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@ -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 {
|
@ -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.
|
||||
|
@ -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)
|
||||
}
|
@ -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 ""
|
||||
}
|
@ -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()))
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
File diff suppressed because it is too large
Load Diff
@ -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.
|
@ -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
5
vendor/honnef.co/go/tools/ir/write.go
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
package ir
|
||||
|
||||
func NewJump(parent *BasicBlock) *Jump {
|
||||
return &Jump{anInstruction{block: parent}, ""}
|
||||
}
|
78
vendor/honnef.co/go/tools/lint/lint.go
vendored
78
vendor/honnef.co/go/tools/lint/lint.go
vendored
@ -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{}
|
||||
|
408
vendor/honnef.co/go/tools/lint/lintdsl/lintdsl.go
vendored
408
vendor/honnef.co/go/tools/lint/lintdsl/lintdsl.go
vendored
@ -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
|
||||
}
|
||||