pax_global_header 0000666 0000000 0000000 00000000064 14143223131 0014504 g ustar 00root root 0000000 0000000 52 comment=c8caa92bad8c27ae734c6725b8a04932d54a147b
go-tools-2021.1.2/ 0000775 0000000 0000000 00000000000 14143223131 0013474 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/.gitattributes 0000664 0000000 0000000 00000000017 14143223131 0016365 0 ustar 00root root 0000000 0000000 *.golden -text
go-tools-2021.1.2/.github/ 0000775 0000000 0000000 00000000000 14143223131 0015034 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/.github/FUNDING.yml 0000664 0000000 0000000 00000000043 14143223131 0016646 0 ustar 00root root 0000000 0000000 patreon: dominikh
github: dominikh
go-tools-2021.1.2/.github/ISSUE_TEMPLATE/ 0000775 0000000 0000000 00000000000 14143223131 0017217 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/.github/ISSUE_TEMPLATE/1_false_positive.md 0000664 0000000 0000000 00000001202 14143223131 0022770 0 ustar 00root root 0000000 0000000 ---
name: π’ False positive in staticcheck
about: Your code is fine but staticcheck complains about it, anyway.
labels: false-positive, needs-triage
title: ""
---
go-tools-2021.1.2/.github/ISSUE_TEMPLATE/2_false_negative.md 0000664 0000000 0000000 00000001202 14143223131 0022731 0 ustar 00root root 0000000 0000000 ---
name: π¦ False negative in staticcheck
about: Your code is wrong but staticcheck doesn't complain about it.
labels: false-negative, needs-triage
title: ""
---
go-tools-2021.1.2/.github/ISSUE_TEMPLATE/3_bug.md 0000664 0000000 0000000 00000001156 14143223131 0020543 0 ustar 00root root 0000000 0000000 ---
name: π General bugs with staticcheck
about: Something in staticcheck isn't working as it should.
labels: bug, needs-triage
title: ""
---
go-tools-2021.1.2/.github/ISSUE_TEMPLATE/4_other.md 0000664 0000000 0000000 00000000220 14143223131 0021077 0 ustar 00root root 0000000 0000000 ---
name: π Other
about: Ideas, feature requests, and all other issues not fitting into another category.
labels: needs-triage
title: ""
---
go-tools-2021.1.2/.github/workflows/ 0000775 0000000 0000000 00000000000 14143223131 0017071 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/.github/workflows/ci.yml 0000664 0000000 0000000 00000001743 14143223131 0020214 0 ustar 00root root 0000000 0000000 name: "CI"
on: ["push", "pull_request"]
jobs:
test:
name: "Run unit tests"
strategy:
matrix:
os: ["windows-latest", "ubuntu-latest", "macOS-latest"]
go: ["1.15.x", "1.16.x"]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v1
with:
fetch-depth: 1
- uses: WillAbides/setup-go-faster@v1.5.0
with:
go-version: ${{ matrix.go }}
- run: "go test ./..."
lint:
name: "Run static analysis"
runs-on: "ubuntu-latest"
steps:
- uses: WillAbides/setup-go-faster@v1.5.0
with:
go-version: "1.15.x"
- run: "GO111MODULE=on go get honnef.co/go/tools/cmd/staticcheck"
- uses: actions/checkout@v1
with:
fetch-depth: 1
- uses: actions/cache@v2
with:
path: ~/.cache/staticcheck
key: staticcheck-${{ github.sha }}
restore-keys: |
staticcheck-
- run: "go vet ./..."
- run: "$(go env GOPATH)/bin/staticcheck -go 1.15 ./..."
go-tools-2021.1.2/LICENSE 0000664 0000000 0000000 00000002042 14143223131 0014477 0 ustar 00root root 0000000 0000000 Copyright (c) 2016 Dominik Honnef
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
go-tools-2021.1.2/LICENSE-THIRD-PARTY 0000664 0000000 0000000 00000014452 14143223131 0016254 0 ustar 00root root 0000000 0000000 Staticcheck and its related tools make use of third party projects,
either by reusing their code, or by statically linking them into
resulting binaries. These projects are:
* The Go Programming Language - https://golang.org/
golang.org/x/mod - https://github.com/golang/mod
golang.org/x/tools - https://github.com/golang/tools
golang.org/x/sys - https://github.com/golang/sys
golang.org/x/xerrors - https://github.com/golang/xerrors
Copyright (c) 2009 The Go Authors. 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 Google Inc. 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.
* github.com/BurntSushi/toml - https://github.com/BurntSushi/toml
The MIT License (MIT)
Copyright (c) 2013 TOML authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
* 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.
go-tools-2021.1.2/README.md 0000664 0000000 0000000 00000005633 14143223131 0014762 0 ustar 00root root 0000000 0000000
The advanced Go linter
Staticcheck is a state of the art linter for the [Go programming
language](https://go.dev/). Using static analysis, it finds bugs and performance issues,
offers simplifications, and enforces style rules.
**Financial support by [private and corporate sponsors](http://staticcheck.io/sponsors) guarantees the tool's continued development.
Please [become a sponsor](https://github.com/users/dominikh/sponsorship) if you or your company rely on Staticcheck.**
## Documentation
You can find extensive documentation on Staticcheck on [its website](https://staticcheck.io/docs/).
## Installation
### Releases
It is recommended that you run released versions of the tools. These
releases can be found as git tags (e.g. `2019.1`) as well as prebuilt
binaries in the [releases tab](https://github.com/dominikh/go-tools/releases).
The easiest way of using the releases from source is to use a Go
package manager such as Godep or Go modules. Alternatively you can use
a combination of `git clone -b` and `go get` to check out the
appropriate tag and download its dependencies.
### Master
You can also run the master branch instead of a release. Note that
while the master branch is usually stable, it may still contain new
checks or backwards incompatible changes that break your build. By
using the master branch you agree to become a beta tester.
## Tools
All of the following tools can be found in the cmd/ directory. Each
tool is accompanied by its own README, describing it in more detail.
| Tool | Description |
|----------------------------------------------------|-------------------------------------------------------------------------|
| [keyify](cmd/keyify/) | Transforms an unkeyed struct literal into a keyed one. |
| [staticcheck](cmd/staticcheck/) | Go static analysis, detecting bugs, performance issues, and much more. |
| [structlayout](cmd/structlayout/) | Displays the layout (field sizes and padding) of structs. |
| [structlayout-optimize](cmd/structlayout-optimize) | Reorders struct fields to minimize the amount of padding. |
| [structlayout-pretty](cmd/structlayout-pretty) | Formats the output of structlayout with ASCII art. |
## Libraries
In addition to the aforementioned tools, this repository contains the
libraries necessary to implement these tools.
Unless otherwise noted, none of these libraries have stable APIs.
Their main purpose is to aid the implementation of the tools. If you
decide to use these libraries, please vendor them and expect regular
backwards-incompatible changes.
## System requirements
We support the last two versions of Go.
go-tools-2021.1.2/_benchmarks/ 0000775 0000000 0000000 00000000000 14143223131 0015750 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/_benchmarks/bench.sh 0000775 0000000 0000000 00000002601 14143223131 0017365 0 ustar 00root root 0000000 0000000 #!/usr/bin/env bash
set -e
declare -A PKGS=(
["strconv"]="strconv"
["net/http"]="net/http"
["image/color"]="image/color"
["std"]="std"
["k8s"]="k8s.io/kubernetes/pkg/..."
)
MIN_CORES=32
MAX_CORES=32
INCR_CORES=2
MIN_GOGC=100
MAX_GOGC=100
SAMPLES=10
WIPE_CACHE=1
FORMAT=bench
BIN=$(realpath ./silent-staticcheck.sh)
runBenchmark() {
local pkg="$1"
local label="$2"
local gc="$3"
local cores="$4"
local wipe="$5"
if [ $wipe -ne 0 ]; then
rm -rf ~/.cache/staticcheck
fi
local out=$(GOGC=$gc GOMAXPROCS=$cores env time -f "%e %M" $BIN $pkg 2>&1)
local t=$(echo "$out" | cut -f1 -d" ")
local m=$(echo "$out" | cut -f2 -d" ")
local ns=$(printf "%s 1000000000 * p" $t | dc)
local b=$((m * 1024))
case $FORMAT in
bench)
printf "BenchmarkStaticcheck-%s-GOGC%d-wiped%d-%d 1 %.0f ns/op %.0f B/op\n" "$label" "$gc" "$wipe" "$cores" "$ns" "$b"
;;
csv)
printf "%s,%d,%d,%d,%.0f,%.0f\n" "$label" "$gc" "$cores" "$wipe" "$ns" "$b"
;;
esac
}
export GO111MODULE=off
if [ "$FORMAT" = "csv" ]; then
printf "packages,gogc,gomaxprocs,wipe-cache,time,memory\n"
fi
for label in "${!PKGS[@]}"; do
pkg=${PKGS[$label]}
for gc in $(seq $MIN_GOGC 10 $MAX_GOGC); do
for cores in $(seq $MIN_CORES $INCR_CORES $MAX_CORES); do
for i in $(seq 1 $SAMPLES); do
runBenchmark "$pkg" "$label" "$gc" "$cores" 1
runBenchmark "$pkg" "$label" "$gc" "$cores" 0
done
done
done
done
go-tools-2021.1.2/_benchmarks/silent-staticcheck.sh 0000775 0000000 0000000 00000000205 14143223131 0022065 0 ustar 00root root 0000000 0000000 #!/usr/bin/env sh
/home/dominikh/prj/src/honnef.co/go/tools/cmd/staticcheck/staticcheck -checks "all" -fail "" $1 &>/dev/null
exit 0
go-tools-2021.1.2/analysis/ 0000775 0000000 0000000 00000000000 14143223131 0015317 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/analysis/code/ 0000775 0000000 0000000 00000000000 14143223131 0016231 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/analysis/code/code.go 0000664 0000000 0000000 00000017472 14143223131 0017505 0 ustar 00root root 0000000 0000000 // Package code answers structural and type questions about Go code.
package code
import (
"flag"
"fmt"
"go/ast"
"go/constant"
"go/token"
"go/types"
"strings"
"honnef.co/go/tools/analysis/facts"
"honnef.co/go/tools/go/ast/astutil"
"honnef.co/go/tools/go/types/typeutil"
"golang.org/x/tools/go/analysis"
)
type Positioner interface {
Pos() token.Pos
}
func IsOfType(pass *analysis.Pass, expr ast.Expr, name string) bool {
return typeutil.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
}
func CallName(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 typeutil.FuncName(fn)
case *ast.Ident:
obj := pass.TypesInfo.ObjectOf(fun)
switch obj := obj.(type) {
case *types.Func:
return typeutil.FuncName(obj)
case *types.Builtin:
return obj.Name()
default:
return ""
}
default:
return ""
}
}
func IsCallTo(pass *analysis.Pass, node ast.Node, name string) bool {
call, ok := node.(*ast.CallExpr)
if !ok {
return false
}
return CallName(pass, call) == name
}
func IsCallToAny(pass *analysis.Pass, node ast.Node, names ...string) bool {
call, ok := node.(*ast.CallExpr)
if !ok {
return false
}
q := CallName(pass, call)
for _, name := range names {
if q == name {
return true
}
}
return false
}
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 effects, 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 {
f, ok := pass.Analyzer.Flags.Lookup("go").Value.(flag.Getter)
if !ok {
panic("requested Go version, but analyzer has no version flag")
}
version := f.Get().(int)
return version >= minor
}
go-tools-2021.1.2/analysis/code/visit.go 0000664 0000000 0000000 00000002604 14143223131 0017720 0 ustar 00root root 0000000 0000000 package code
import (
"bytes"
"go/ast"
"go/format"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
)
func Preorder(pass *analysis.Pass, fn func(ast.Node), types ...ast.Node) {
pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Preorder(types, fn)
}
func PreorderStack(pass *analysis.Pass, fn func(ast.Node, []ast.Node), types ...ast.Node) {
pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).WithStack(types, func(n ast.Node, push bool, stack []ast.Node) (proceed bool) {
if push {
fn(n, stack)
}
return true
})
}
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
}
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
}
go-tools-2021.1.2/analysis/edit/ 0000775 0000000 0000000 00000000000 14143223131 0016244 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/analysis/edit/edit.go 0000664 0000000 0000000 00000003034 14143223131 0017520 0 ustar 00root root 0000000 0000000 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,
}
}
func Selector(x, sel string) *ast.SelectorExpr {
return &ast.SelectorExpr{
X: &ast.Ident{Name: x},
Sel: &ast.Ident{Name: sel},
}
}
go-tools-2021.1.2/analysis/facts/ 0000775 0000000 0000000 00000000000 14143223131 0016417 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/analysis/facts/deprecated.go 0000664 0000000 0000000 00000006473 14143223131 0021060 0 ustar 00root root 0000000 0000000 package facts
import (
"go/ast"
"go/token"
"go/types"
"reflect"
"strings"
"golang.org/x/tools/go/analysis"
)
type IsDeprecated struct{ Msg string }
func (*IsDeprecated) AFact() {}
func (d *IsDeprecated) String() string { return "Deprecated: " + d.Msg }
type DeprecatedResult struct {
Objects map[types.Object]*IsDeprecated
Packages map[*types.Package]*IsDeprecated
}
var Deprecated = &analysis.Analyzer{
Name: "fact_deprecated",
Doc: "Mark deprecated objects",
Run: deprecated,
FactTypes: []analysis.Fact{(*IsDeprecated)(nil)},
ResultType: reflect.TypeOf(DeprecatedResult{}),
}
func deprecated(pass *analysis.Pass) (interface{}, error) {
var names []*ast.Ident
extractDeprecatedMessage := func(docs []*ast.CommentGroup) string {
for _, doc := range docs {
if doc == nil {
continue
}
parts := strings.Split(doc.Text(), "\n\n")
for _, part := range parts {
if !strings.HasPrefix(part, "Deprecated: ") {
continue
}
alt := part[len("Deprecated: "):]
alt = strings.Replace(alt, "\n", " ", -1)
return alt
}
}
return ""
}
doDocs := func(names []*ast.Ident, docs []*ast.CommentGroup) {
alt := extractDeprecatedMessage(docs)
if alt == "" {
return
}
for _, name := range names {
obj := pass.TypesInfo.ObjectOf(name)
pass.ExportObjectFact(obj, &IsDeprecated{alt})
}
}
var docs []*ast.CommentGroup
for _, f := range pass.Files {
docs = append(docs, f.Doc)
}
if alt := extractDeprecatedMessage(docs); alt != "" {
// Don't mark package syscall as deprecated, even though
// it is. A lot of people still use it for simple
// constants like SIGKILL, and I am not comfortable
// telling them to use x/sys for that.
if pass.Pkg.Path() != "syscall" {
pass.ExportPackageFact(&IsDeprecated{alt})
}
}
docs = docs[:0]
for _, f := range pass.Files {
fn := func(node ast.Node) bool {
if node == nil {
return true
}
var ret bool
switch node := node.(type) {
case *ast.GenDecl:
switch node.Tok {
case token.TYPE, token.CONST, token.VAR:
docs = append(docs, node.Doc)
return true
default:
return false
}
case *ast.FuncDecl:
docs = append(docs, node.Doc)
names = []*ast.Ident{node.Name}
ret = false
case *ast.TypeSpec:
docs = append(docs, node.Doc)
names = []*ast.Ident{node.Name}
ret = true
case *ast.ValueSpec:
docs = append(docs, node.Doc)
names = node.Names
ret = false
case *ast.File:
return true
case *ast.StructType:
for _, field := range node.Fields.List {
doDocs(field.Names, []*ast.CommentGroup{field.Doc})
}
return false
case *ast.InterfaceType:
for _, field := range node.Methods.List {
doDocs(field.Names, []*ast.CommentGroup{field.Doc})
}
return false
default:
return false
}
if len(names) == 0 || len(docs) == 0 {
return ret
}
doDocs(names, docs)
docs = docs[:0]
names = nil
return ret
}
ast.Inspect(f, fn)
}
out := DeprecatedResult{
Objects: map[types.Object]*IsDeprecated{},
Packages: map[*types.Package]*IsDeprecated{},
}
for _, fact := range pass.AllObjectFacts() {
out.Objects[fact.Object] = fact.Fact.(*IsDeprecated)
}
for _, fact := range pass.AllPackageFacts() {
out.Packages[fact.Package] = fact.Fact.(*IsDeprecated)
}
return out, nil
}
go-tools-2021.1.2/analysis/facts/directives.go 0000664 0000000 0000000 00000000715 14143223131 0021112 0 ustar 00root root 0000000 0000000 package facts
import (
"reflect"
"golang.org/x/tools/go/analysis"
"honnef.co/go/tools/analysis/lint"
)
func directives(pass *analysis.Pass) (interface{}, error) {
return lint.ParseDirectives(pass.Files, pass.Fset), nil
}
var Directives = &analysis.Analyzer{
Name: "directives",
Doc: "extracts linter directives",
Run: directives,
RunDespiteErrors: true,
ResultType: reflect.TypeOf([]lint.Directive{}),
}
go-tools-2021.1.2/analysis/facts/facts_test.go 0000664 0000000 0000000 00000000447 14143223131 0021112 0 ustar 00root root 0000000 0000000 package facts
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
)
func TestDeprecated(t *testing.T) {
analysistest.Run(t, analysistest.TestData(), Deprecated, "Deprecated")
}
func TestPurity(t *testing.T) {
analysistest.Run(t, analysistest.TestData(), Purity, "Purity")
}
go-tools-2021.1.2/analysis/facts/generated.go 0000664 0000000 0000000 00000003554 14143223131 0020713 0 ustar 00root root 0000000 0000000 package facts
import (
"bufio"
"bytes"
"io"
"os"
"reflect"
"strings"
"golang.org/x/tools/go/analysis"
)
type Generator int
// A list of known generators we can detect
const (
Unknown Generator = iota
Goyacc
Cgo
Stringer
ProtocGenGo
)
var (
// used by cgo before Go 1.11
oldCgo = []byte("// Created by cgo - DO NOT EDIT")
prefix = []byte("// Code generated ")
suffix = []byte(" DO NOT EDIT.")
nl = []byte("\n")
crnl = []byte("\r\n")
)
func isGenerated(path string) (Generator, bool) {
f, err := os.Open(path)
if err != nil {
return 0, false
}
defer f.Close()
br := bufio.NewReader(f)
for {
s, err := br.ReadBytes('\n')
if err != nil && err != io.EOF {
return 0, false
}
s = bytes.TrimSuffix(s, crnl)
s = bytes.TrimSuffix(s, nl)
if bytes.HasPrefix(s, prefix) && bytes.HasSuffix(s, suffix) {
if len(s)-len(suffix) < len(prefix) {
return Unknown, true
}
text := string(s[len(prefix) : len(s)-len(suffix)])
switch text {
case "by goyacc.":
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) {
return Cgo, true
}
if err == io.EOF {
break
}
}
return 0, false
}
var Generated = &analysis.Analyzer{
Name: "isgenerated",
Doc: "annotate file names that have been code generated",
Run: func(pass *analysis.Pass) (interface{}, error) {
m := map[string]Generator{}
for _, f := range pass.Files {
path := pass.Fset.PositionFor(f.Pos(), false).Filename
g, ok := isGenerated(path)
if ok {
m[path] = g
}
}
return m, nil
},
RunDespiteErrors: true,
ResultType: reflect.TypeOf(map[string]Generator{}),
}
go-tools-2021.1.2/analysis/facts/nilness/ 0000775 0000000 0000000 00000000000 14143223131 0020072 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/analysis/facts/nilness/nilness.go 0000664 0000000 0000000 00000014160 14143223131 0022076 0 ustar 00root root 0000000 0000000 package nilness
import (
"fmt"
"go/token"
"go/types"
"reflect"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/types/typeutil"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
)
// neverReturnsNilFact denotes that a function's return value will never
// be nil (typed or untyped). The analysis errs on the side of false
// negatives.
type neverReturnsNilFact struct {
Rets []neverNilness
}
func (*neverReturnsNilFact) AFact() {}
func (fact *neverReturnsNilFact) String() string {
return fmt.Sprintf("never returns nil: %v", fact.Rets)
}
type Result struct {
m map[*types.Func][]neverNilness
}
var Analysis = &analysis.Analyzer{
Name: "nilness",
Doc: "Annotates return values that will never be nil (typed or untyped)",
Run: run,
Requires: []*analysis.Analyzer{buildir.Analyzer},
FactTypes: []analysis.Fact{(*neverReturnsNilFact)(nil)},
ResultType: reflect.TypeOf((*Result)(nil)),
}
// MayReturnNil reports whether the ret's return value of fn might be
// a typed or untyped nil value. The value of ret is zero-based. When
// globalOnly is true, the only possible nil values are global
// variables.
//
// The analysis has false positives: MayReturnNil can incorrectly
// report true, but never incorrectly reports false.
func (r *Result) MayReturnNil(fn *types.Func, ret int) (yes bool, globalOnly bool) {
if !typeutil.IsPointerLike(fn.Type().(*types.Signature).Results().At(ret).Type()) {
return false, false
}
if len(r.m[fn]) == 0 {
return true, false
}
v := r.m[fn][ret]
return v != neverNil, v == onlyGlobal
}
func run(pass *analysis.Pass) (interface{}, error) {
seen := map[*ir.Function]struct{}{}
out := &Result{
m: map[*types.Func][]neverNilness{},
}
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
impl(pass, fn, seen)
}
for _, fact := range pass.AllObjectFacts() {
out.m[fact.Object.(*types.Func)] = fact.Fact.(*neverReturnsNilFact).Rets
}
return out, nil
}
type neverNilness uint8
const (
neverNil neverNilness = 1
onlyGlobal neverNilness = 2
nilly neverNilness = 3
)
func (n neverNilness) String() string {
switch n {
case neverNil:
return "never"
case onlyGlobal:
return "global"
case nilly:
return "nil"
default:
return "BUG"
}
}
func impl(pass *analysis.Pass, fn *ir.Function, seenFns map[*ir.Function]struct{}) []neverNilness {
if fn.Object() == nil {
// TODO(dh): support closures
return nil
}
if fact := new(neverReturnsNilFact); pass.ImportObjectFact(fn.Object(), fact) {
return fact.Rets
}
if fn.Pkg != pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg {
return nil
}
if fn.Blocks == nil {
return nil
}
if _, ok := seenFns[fn]; ok {
// break recursion
return nil
}
seenFns[fn] = struct{}{}
seen := map[ir.Value]struct{}{}
var mightReturnNil func(v ir.Value) neverNilness
mightReturnNil = func(v ir.Value) neverNilness {
if _, ok := seen[v]; ok {
// break cycle
return nilly
}
if !typeutil.IsPointerLike(v.Type()) {
return neverNil
}
seen[v] = struct{}{}
switch v := v.(type) {
case *ir.MakeInterface:
return mightReturnNil(v.X)
case *ir.Convert:
return mightReturnNil(v.X)
case *ir.SliceToArrayPointer:
if v.Type().Underlying().(*types.Pointer).Elem().Underlying().(*types.Array).Len() == 0 {
return mightReturnNil(v.X)
} else {
// converting a slice to an array pointer of length > 0 panics if the slice is nil
return neverNil
}
case *ir.Slice:
return mightReturnNil(v.X)
case *ir.Phi:
ret := neverNil
for _, e := range v.Edges {
if n := mightReturnNil(e); n > ret {
ret = n
}
}
return ret
case *ir.Extract:
switch d := v.Tuple.(type) {
case *ir.Call:
if callee := d.Call.StaticCallee(); callee != nil {
ret := impl(pass, callee, seenFns)
if len(ret) == 0 {
return nilly
}
return ret[v.Index]
} else {
return nilly
}
case *ir.TypeAssert, *ir.Next, *ir.Select, *ir.MapLookup, *ir.TypeSwitch, *ir.Recv:
// we don't need to look at the Extract's index
// because we've already checked its type.
return nilly
default:
panic(fmt.Sprintf("internal error: unhandled type %T", d))
}
case *ir.Call:
if callee := v.Call.StaticCallee(); callee != nil {
ret := impl(pass, callee, seenFns)
if len(ret) == 0 {
return nilly
}
return ret[0]
} else {
return nilly
}
case *ir.BinOp, *ir.UnOp, *ir.Alloc, *ir.FieldAddr, *ir.IndexAddr, *ir.Global, *ir.MakeSlice, *ir.MakeClosure, *ir.Function, *ir.MakeMap, *ir.MakeChan:
return neverNil
case *ir.Sigma:
iff, ok := v.From.Control().(*ir.If)
if !ok {
return nilly
}
binop, ok := iff.Cond.(*ir.BinOp)
if !ok {
return nilly
}
isNil := func(v ir.Value) bool {
k, ok := v.(*ir.Const)
if !ok {
return false
}
return k.Value == nil
}
if binop.X == v.X && isNil(binop.Y) || binop.Y == v.X && isNil(binop.X) {
op := binop.Op
if v.From.Succs[0] != v.Block() {
// we're in the false branch, negate op
switch op {
case token.EQL:
op = token.NEQ
case token.NEQ:
op = token.EQL
default:
panic(fmt.Sprintf("internal error: unhandled token %v", op))
}
}
switch op {
case token.EQL:
return nilly
case token.NEQ:
return neverNil
default:
panic(fmt.Sprintf("internal error: unhandled token %v", op))
}
}
return nilly
case *ir.ChangeType:
return mightReturnNil(v.X)
case *ir.Load:
if _, ok := v.X.(*ir.Global); ok {
return onlyGlobal
}
return nilly
case *ir.TypeAssert, *ir.ChangeInterface, *ir.Field, *ir.Const, *ir.Index, *ir.MapLookup, *ir.Parameter, *ir.Recv, *ir.TypeSwitch:
return nilly
default:
panic(fmt.Sprintf("internal error: unhandled type %T", v))
}
}
ret := fn.Exit.Control().(*ir.Return)
out := make([]neverNilness, len(ret.Results))
export := false
for i, v := range ret.Results {
v := mightReturnNil(v)
out[i] = v
if v != nilly && typeutil.IsPointerLike(fn.Signature.Results().At(i).Type()) {
export = true
}
}
if export {
pass.ExportObjectFact(fn.Object(), &neverReturnsNilFact{out})
}
return out
}
go-tools-2021.1.2/analysis/facts/nilness/nilness_test.go 0000664 0000000 0000000 00000000276 14143223131 0023140 0 ustar 00root root 0000000 0000000 package nilness
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
)
func TestNilness(t *testing.T) {
analysistest.Run(t, analysistest.TestData(), Analysis, "Nilness")
}
go-tools-2021.1.2/analysis/facts/nilness/testdata/ 0000775 0000000 0000000 00000000000 14143223131 0021703 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/analysis/facts/nilness/testdata/src/ 0000775 0000000 0000000 00000000000 14143223131 0022472 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/analysis/facts/nilness/testdata/src/Nilness/ 0000775 0000000 0000000 00000000000 14143223131 0024105 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/analysis/facts/nilness/testdata/src/Nilness/Nilness.go 0000664 0000000 0000000 00000002647 14143223131 0026060 0 ustar 00root root 0000000 0000000 package pkg
import "errors"
type T struct{ f *int }
type T2 T
func fn1() *T {
if true {
return nil
}
return &T{}
}
func fn2() *T { // want fn2:`never returns nil: \[never\]`
return &T{}
}
func fn3() *T { // want fn3:`never returns nil: \[never\]`
return new(T)
}
func fn4() *T { // want fn4:`never returns nil: \[never\]`
return fn3()
}
func fn5() *T {
return fn1()
}
func fn6() *T2 { // want fn6:`never returns nil: \[never\]`
return (*T2)(fn4())
}
func fn7() interface{} {
return nil
}
func fn8() interface{} { // want fn8:`never returns nil: \[never\]`
return 1
}
func fn9() []int { // want fn9:`never returns nil: \[never\]`
x := []int{}
y := x[:1]
return y
}
func fn10(x []int) []int {
return x[:1]
}
func fn11(x *T) *T {
return x
}
func fn12(x *T) *int {
return x.f
}
func fn13() *int { // want fn13:`never returns nil: \[never\]`
return new(int)
}
func fn14() []int { // want fn14:`never returns nil: \[never\]`
return make([]int, 0)
}
func fn15() []int { // want fn15:`never returns nil: \[never\]`
return []int{}
}
func fn16() []int {
return nil
}
func fn17() error {
if true {
return errors.New("")
}
return nil
}
func fn18() (err error) { // want fn18:`never returns nil: \[never\]`
for {
if err = fn17(); err != nil {
return
}
}
}
var x *int
func fn19() *int { // want fn19:`never returns nil: \[global\]`
return x
}
func fn20() *int {
if true {
return x
}
return nil
}
go-tools-2021.1.2/analysis/facts/purity.go 0000664 0000000 0000000 00000010356 14143223131 0020307 0 ustar 00root root 0000000 0000000 package facts
import (
"go/types"
"reflect"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
)
type IsPure struct{}
func (*IsPure) AFact() {}
func (d *IsPure) String() string { return "is pure" }
type PurityResult map[*types.Func]*IsPure
var Purity = &analysis.Analyzer{
Name: "fact_purity",
Doc: "Mark pure functions",
Run: purity,
Requires: []*analysis.Analyzer{buildir.Analyzer},
FactTypes: []analysis.Fact{(*IsPure)(nil)},
ResultType: reflect.TypeOf(PurityResult{}),
}
var pureStdlib = map[string]struct{}{
"errors.New": {},
"fmt.Errorf": {},
"fmt.Sprintf": {},
"fmt.Sprint": {},
"sort.Reverse": {},
"strings.Map": {},
"strings.Repeat": {},
"strings.Replace": {},
"strings.Title": {},
"strings.ToLower": {},
"strings.ToLowerSpecial": {},
"strings.ToTitle": {},
"strings.ToTitleSpecial": {},
"strings.ToUpper": {},
"strings.ToUpperSpecial": {},
"strings.Trim": {},
"strings.TrimFunc": {},
"strings.TrimLeft": {},
"strings.TrimLeftFunc": {},
"strings.TrimPrefix": {},
"strings.TrimRight": {},
"strings.TrimRightFunc": {},
"strings.TrimSpace": {},
"strings.TrimSuffix": {},
"(*net/http.Request).WithContext": {},
}
func purity(pass *analysis.Pass) (interface{}, error) {
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(fn.Object(), new(IsPure)) {
return true
}
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[fn]; ok {
return false
}
seen[fn] = struct{}{}
defer func() {
if ret {
pass.ExportObjectFact(fn.Object(), &IsPure{})
}
}()
if irutil.IsStub(fn) {
return false
}
if _, ok := pureStdlib[fn.Object().(*types.Func).FullName()]; ok {
return true
}
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 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
}
}
// Don't consider external functions pure.
if fn.Blocks == nil {
return false
}
checkCall := func(common *ir.CallCommon) bool {
if common.IsInvoke() {
return false
}
builtin, ok := common.Value.(*ir.Builtin)
if !ok {
if common.StaticCallee() != fn {
if common.StaticCallee() == nil {
return false
}
if !check(common.StaticCallee()) {
return false
}
}
} else {
switch builtin.Name() {
case "len", "cap":
default:
return false
}
}
return true
}
for _, b := range fn.Blocks {
for _, ins := range b.Instrs {
switch ins := ins.(type) {
case *ir.Call:
if !checkCall(ins.Common()) {
return false
}
case *ir.Defer:
if !checkCall(&ins.Call) {
return false
}
case *ir.Select:
return false
case *ir.Send:
return false
case *ir.Go:
return false
case *ir.Panic:
return false
case *ir.Store:
return false
case *ir.FieldAddr:
return false
case *ir.Alloc:
return false
case *ir.Load:
return false
}
}
}
return true
}
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
check(fn)
}
out := PurityResult{}
for _, fact := range pass.AllObjectFacts() {
out[fact.Object.(*types.Func)] = fact.Fact.(*IsPure)
}
return out, nil
}
go-tools-2021.1.2/analysis/facts/testdata/ 0000775 0000000 0000000 00000000000 14143223131 0020230 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/analysis/facts/testdata/src/ 0000775 0000000 0000000 00000000000 14143223131 0021017 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/analysis/facts/testdata/src/Deprecated/ 0000775 0000000 0000000 00000000000 14143223131 0023057 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/analysis/facts/testdata/src/Deprecated/Deprecated.go 0000664 0000000 0000000 00000000406 14143223131 0025446 0 ustar 00root root 0000000 0000000 package pkg
// Deprecated: Don't use this.
func fn2() { // want fn2:`Deprecated: Don't use this\.`
}
// This is a function.
//
// Deprecated: Don't use this.
//
// Here is how you might use it instead.
func fn3() { // want fn3:`Deprecated: Don't use this\.`
}
go-tools-2021.1.2/analysis/facts/testdata/src/Purity/ 0000775 0000000 0000000 00000000000 14143223131 0022313 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/analysis/facts/testdata/src/Purity/CheckPureFunctions.go 0000664 0000000 0000000 00000001016 14143223131 0026402 0 ustar 00root root 0000000 0000000 package pkg
func foo(a, b int) int { return a + b } // want foo:"is pure"
func bar(a, b int) int {
println(a + b)
return a + b
}
func empty() {}
func stubPointer() *int { return nil }
func stubInt() int { return 0 }
func fn3() {
empty()
stubPointer()
stubInt()
}
func ptr1() *int { return new(int) }
func ptr2() *int { var x int; return &x }
func lit() []int { return []int{} }
var X int
func load() int { _ = X; return 0 }
func assign(x int) int { _ = x; return 0 } // want assign:"is pure"
go-tools-2021.1.2/analysis/facts/token.go 0000664 0000000 0000000 00000000766 14143223131 0020077 0 ustar 00root root 0000000 0000000 package facts
import (
"go/ast"
"go/token"
"reflect"
"golang.org/x/tools/go/analysis"
)
var TokenFile = &analysis.Analyzer{
Name: "tokenfileanalyzer",
Doc: "creates a mapping of *token.File to *ast.File",
Run: func(pass *analysis.Pass) (interface{}, error) {
m := map[*token.File]*ast.File{}
for _, af := range pass.Files {
tf := pass.Fset.File(af.Pos())
m[tf] = af
}
return m, nil
},
RunDespiteErrors: true,
ResultType: reflect.TypeOf(map[*token.File]*ast.File{}),
}
go-tools-2021.1.2/analysis/facts/typedness/ 0000775 0000000 0000000 00000000000 14143223131 0020435 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/analysis/facts/typedness/testdata/ 0000775 0000000 0000000 00000000000 14143223131 0022246 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/analysis/facts/typedness/testdata/src/ 0000775 0000000 0000000 00000000000 14143223131 0023035 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/analysis/facts/typedness/testdata/src/Typedness/ 0000775 0000000 0000000 00000000000 14143223131 0025013 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/analysis/facts/typedness/testdata/src/Typedness/Typedness.go 0000664 0000000 0000000 00000010241 14143223131 0027316 0 ustar 00root root 0000000 0000000 package pkg
import (
"errors"
"os/exec"
)
type T struct{ x *int }
func notAStub() {}
func fn1() *int { return nil }
func fn2() (int, *int, int) { return 0, nil, 0 }
func fn3() (out1 int, out2 error) { notAStub(); return 0, nil }
func fn4() error { notAStub(); return nil }
func gen2() (out1 interface{}) { // want gen2:`always typed: 00000001`
return 1
}
func gen3() (out1 interface{}) { // want gen3:`always typed: 00000001`
// flag, always returns a typed value
m := map[int]*int{}
return m[0]
}
func gen4() (out1 int, out2 interface{}, out3 *int) { // want gen4:`always typed: 00000010`
// flag ret[1], always a typed value
m := map[int]*int{}
return 0, m[0], nil
}
func gen5() (out1 interface{}) { // want gen5:`always typed: 00000001`
// flag, propagate gen3
return gen3()
}
func gen6(b bool) interface{} {
// don't flag, sometimes returns untyped nil
if b {
m := map[int]*int{}
return m[0]
} else {
return nil
}
}
func gen7() (out1 interface{}) { // want gen7:`always typed: 00000001`
// flag, always returns a typed value
return fn1()
}
func gen8(x *int) (out1 interface{}) { // want gen8:`always typed: 00000001`
// flag
if x == nil {
return x
}
return x
}
func gen9() (out1 interface{}) { // want gen9:`always typed: 00000001`
// flag
var x *int
return x
}
func gen10() (out1 interface{}) { // want gen10:`always typed: 00000001`
// flag
var x *int
if x == nil {
return x
}
return errors.New("")
}
func gen11() interface{} {
// don't flag, we sometimes return untyped nil
if true {
return nil
} else {
return (*int)(nil)
}
}
func gen12(b bool) (out1 interface{}) { // want gen12:`always typed: 00000001`
// flag, all branches return typed nils
var x interface{}
if b {
x = (*int)(nil)
} else {
x = (*string)(nil)
}
return x
}
func gen13() (out1 interface{}) { // want gen13:`always typed: 00000001`
// flag, always returns a typed value
_, x, _ := fn2()
return x
}
func gen14(ch chan *int) (out1 interface{}) { // want gen14:`always typed: 00000001`
// flag
return <-ch
}
func gen15() (out1 interface{}) { // want gen15:`always typed: 00000001`
// flag
t := &T{}
return t.x
}
var g *int = new(int)
func gen16() (out1 interface{}) { // want gen16:`always typed: 00000001`
return g
}
func gen17(x interface{}) interface{} {
// don't flag
if x != nil {
return x
}
return x
}
func gen18() (int, error) {
// don't flag
_, err := fn3()
if err != nil {
return 0, errors.New("yo")
}
return 0, err
}
func gen19() (out interface{}) {
// don't flag
if true {
return (*int)(nil)
}
return
}
func gen20() (out interface{}) {
// don't flag
if true {
return (*int)(nil)
}
return
}
func gen21() error {
if false {
return (*exec.Error)(nil)
}
return fn4()
}
func gen22() interface{} {
// don't flag, propagate gen6
return gen6(false)
}
func gen23() interface{} {
return gen24()
}
func gen24() interface{} {
return gen23()
}
func gen25(x interface{}) (out1 interface{}) { // want gen25:`always typed: 00000001`
return x.(interface{})
}
func gen26(x interface{}) interface{} {
v, _ := x.(interface{})
return v
}
func gen27(x interface{}) (out1 interface{}) {
defer recover()
out1 = x.(interface{})
return out1
}
type Error struct{}
func (*Error) Error() string { return "" }
func gen28() (out1 interface{}) { // want gen28:`always typed: 00000001`
x := new(Error)
var y error = x
return y
}
func gen29() (out1 interface{}) { // want gen29:`always typed: 00000001`
var x *Error
var y error = x
return y
}
func gen30() (out1, out2 interface{}) { // want gen30:`always typed: 00000011`
return gen29(), gen28()
}
func gen31() (out1 interface{}) { // want gen31:`always typed: 00000001`
a, _ := gen30()
return a
}
func gen32() (out1 interface{}) { // want gen32:`always typed: 00000001`
_, b := gen30()
return b
}
func gen33() (out1 interface{}) { // want gen33:`always typed: 00000001`
a, b := gen30()
_ = a
return b
}
func gen34() (out1, out2 interface{}) { // want gen34:`always typed: 00000010`
return nil, 1
}
func gen35() (out1 interface{}) {
a, _ := gen34()
return a
}
func gen36() (out1 interface{}) { // want gen36:`always typed: 00000001`
_, b := gen34()
return b
}
go-tools-2021.1.2/analysis/facts/typedness/typedness.go 0000664 0000000 0000000 00000014464 14143223131 0023013 0 ustar 00root root 0000000 0000000 package typedness
import (
"fmt"
"go/token"
"go/types"
"reflect"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
)
// alwaysTypedFact denotes that a function's return value will never
// be untyped nil. The analysis errs on the side of false negatives.
type alwaysTypedFact struct {
Rets uint8
}
func (*alwaysTypedFact) AFact() {}
func (fact *alwaysTypedFact) String() string {
return fmt.Sprintf("always typed: %08b", fact.Rets)
}
type Result struct {
m map[*types.Func]uint8
}
var Analysis = &analysis.Analyzer{
Name: "typedness",
Doc: "Annotates return values that are always typed values",
Run: run,
Requires: []*analysis.Analyzer{buildir.Analyzer},
FactTypes: []analysis.Fact{(*alwaysTypedFact)(nil)},
ResultType: reflect.TypeOf((*Result)(nil)),
}
// MustReturnTyped reports whether the ret's return value of fn must
// be a typed value, i.e. an interface value containing a concrete
// type or trivially a concrete type. The value of ret is zero-based.
//
// The analysis has false negatives: MustReturnTyped may incorrectly
// report false, but never incorrectly reports true.
func (r *Result) MustReturnTyped(fn *types.Func, ret int) bool {
if _, ok := fn.Type().(*types.Signature).Results().At(ret).Type().Underlying().(*types.Interface); !ok {
return true
}
return (r.m[fn] & (1 << ret)) != 0
}
func run(pass *analysis.Pass) (interface{}, error) {
seen := map[*ir.Function]struct{}{}
out := &Result{
m: map[*types.Func]uint8{},
}
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
impl(pass, fn, seen)
}
for _, fact := range pass.AllObjectFacts() {
out.m[fact.Object.(*types.Func)] = fact.Fact.(*alwaysTypedFact).Rets
}
return out, nil
}
func impl(pass *analysis.Pass, fn *ir.Function, seenFns map[*ir.Function]struct{}) (out uint8) {
if fn.Signature.Results().Len() > 8 {
return 0
}
if fn.Object() == nil {
// TODO(dh): support closures
return 0
}
if fact := new(alwaysTypedFact); pass.ImportObjectFact(fn.Object(), fact) {
return fact.Rets
}
if fn.Pkg != pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg {
return 0
}
if fn.Blocks == nil {
return 0
}
if irutil.IsStub(fn) {
return 0
}
if _, ok := seenFns[fn]; ok {
// break recursion
return 0
}
seenFns[fn] = struct{}{}
defer func() {
for i := 0; i < fn.Signature.Results().Len(); i++ {
if _, ok := fn.Signature.Results().At(i).Type().Underlying().(*types.Interface); !ok {
// we don't need facts to know that non-interface
// types can't be untyped nil. zeroing out those bits
// may result in all bits being zero, in which case we
// don't have to save any fact.
out &= ^(1 << i)
}
}
if out > 0 {
pass.ExportObjectFact(fn.Object(), &alwaysTypedFact{out})
}
}()
isUntypedNil := func(v ir.Value) bool {
k, ok := v.(*ir.Const)
if !ok {
return false
}
if _, ok := k.Type().Underlying().(*types.Interface); !ok {
return false
}
return k.Value == nil
}
var do func(v ir.Value, seen map[ir.Value]struct{}) bool
do = func(v ir.Value, seen map[ir.Value]struct{}) bool {
if _, ok := seen[v]; ok {
// break cycle
return false
}
seen[v] = struct{}{}
switch v := v.(type) {
case *ir.Const:
// can't be a typed nil, because then we'd be returning the
// result of MakeInterface.
return false
case *ir.ChangeInterface:
return do(v.X, seen)
case *ir.Extract:
call, ok := v.Tuple.(*ir.Call)
if !ok {
// We only care about extracts of function results. For
// everything else (e.g. channel receives and map
// lookups), we can either not deduce any information, or
// will see a MakeInterface.
return false
}
if callee := call.Call.StaticCallee(); callee != nil {
return impl(pass, callee, seenFns)&(1<interface conversions, which
// don't tell us anything about the nilness.
return false
case *ir.MapLookup, *ir.Index, *ir.Recv, *ir.Parameter, *ir.Load, *ir.Field:
// All other instructions that tell us nothing about the
// typedness of interface values.
return false
default:
panic(fmt.Sprintf("internal error: unhandled type %T", v))
}
}
ret := fn.Exit.Control().(*ir.Return)
for i, v := range ret.Results {
if _, ok := fn.Signature.Results().At(i).Type().Underlying().(*types.Interface); ok {
if do(v, map[ir.Value]struct{}{}) {
out |= 1 << i
}
}
}
return out
}
go-tools-2021.1.2/analysis/facts/typedness/typedness_test.go 0000664 0000000 0000000 00000000304 14143223131 0024036 0 ustar 00root root 0000000 0000000 package typedness
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
)
func TestTypedness(t *testing.T) {
analysistest.Run(t, analysistest.TestData(), Analysis, "Typedness")
}
go-tools-2021.1.2/analysis/lint/ 0000775 0000000 0000000 00000000000 14143223131 0016265 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/analysis/lint/lint.go 0000664 0000000 0000000 00000010313 14143223131 0017560 0 ustar 00root root 0000000 0000000 // Package lint provides abstractions on top of go/analysis.
package lint
import (
"flag"
"fmt"
"go/ast"
"go/build"
"go/token"
"strconv"
"strings"
"golang.org/x/tools/go/analysis"
)
type Analyzer struct {
// The analyzer's documentation. Unlike go/analysis.Analyzer.Doc,
// this field is structured, providing access to severity, options
// etc.
Doc *Documentation
Analyzer *analysis.Analyzer
}
func (a *Analyzer) initialize() {
a.Analyzer.Doc = a.Doc.String()
if a.Analyzer.Flags.Usage == nil {
fs := flag.NewFlagSet("", flag.PanicOnError)
fs.Var(newVersionFlag(), "go", "Target Go version")
a.Analyzer.Flags = *fs
}
}
func InitializeAnalyzers(docs map[string]*Documentation, analyzers map[string]*analysis.Analyzer) []*Analyzer {
out := make([]*Analyzer, 0, len(analyzers))
for k, v := range analyzers {
v.Name = k
a := &Analyzer{
Doc: docs[k],
Analyzer: v,
}
a.initialize()
out = append(out, a)
}
return out
}
type Severity int
const (
SeverityNone Severity = iota
SeverityError
SeverityDeprecated
SeverityWarning
SeverityInfo
SeverityHint
)
type Documentation struct {
Title string
Text string
Since string
NonDefault bool
Options []string
Severity Severity
}
func Markdownify(m map[string]*Documentation) map[string]*Documentation {
for _, v := range m {
v.Title = toMarkdown(v.Title)
v.Text = toMarkdown(v.Text)
}
return m
}
func toMarkdown(s string) string {
return strings.ReplaceAll(s, `\'`, "`")
}
func (doc *Documentation) String() string {
if doc == nil {
return "Error: No documentation."
}
b := &strings.Builder{}
fmt.Fprintf(b, "%s\n\n", doc.Title)
if doc.Text != "" {
fmt.Fprintf(b, "%s\n\n", doc.Text)
}
fmt.Fprint(b, "Available since\n ")
if doc.Since == "" {
fmt.Fprint(b, "unreleased")
} else {
fmt.Fprintf(b, "%s", doc.Since)
}
if doc.NonDefault {
fmt.Fprint(b, ", non-default")
}
fmt.Fprint(b, "\n")
if len(doc.Options) > 0 {
fmt.Fprintf(b, "\nOptions\n")
for _, opt := range doc.Options {
fmt.Fprintf(b, " %s", opt)
}
fmt.Fprint(b, "\n")
}
return b.String()
}
func newVersionFlag() flag.Getter {
tags := build.Default.ReleaseTags
v := tags[len(tags)-1][2:]
version := new(VersionFlag)
if err := version.Set(v); err != nil {
panic(fmt.Sprintf("internal error: %s", err))
}
return version
}
type VersionFlag int
func (v *VersionFlag) String() string {
return fmt.Sprintf("1.%d", *v)
}
func (v *VersionFlag) Set(s string) error {
if len(s) < 3 {
return fmt.Errorf("invalid Go version: %q", s)
}
if s[0] != '1' {
return fmt.Errorf("invalid Go version: %q", s)
}
if s[1] != '.' {
return fmt.Errorf("invalid Go version: %q", s)
}
i, err := strconv.Atoi(s[2:])
if err != nil {
return fmt.Errorf("invalid Go version: %q", s)
}
*v = VersionFlag(i)
return nil
}
func (v *VersionFlag) Get() interface{} {
return int(*v)
}
// ExhaustiveTypeSwitch panics when called. It can be used to ensure
// that type switches are exhaustive.
func ExhaustiveTypeSwitch(v interface{}) {
panic(fmt.Sprintf("internal error: unhandled case %T", v))
}
// A directive is a comment of the form '//lint:
// [arguments...]'. It represents instructions to the static analysis
// tool.
type Directive struct {
Command string
Arguments []string
Directive *ast.Comment
Node ast.Node
}
func parseDirective(s string) (cmd string, args []string) {
if !strings.HasPrefix(s, "//lint:") {
return "", nil
}
s = strings.TrimPrefix(s, "//lint:")
fields := strings.Split(s, " ")
return fields[0], fields[1:]
}
func ParseDirectives(files []*ast.File, fset *token.FileSet) []Directive {
var dirs []Directive
for _, f := range files {
// OPT(dh): in our old code, we skip all the comment map work if we
// couldn't find any directives, benchmark if that's actually
// worth doing
cm := ast.NewCommentMap(fset, f, f.Comments)
for node, cgs := range cm {
for _, cg := range cgs {
for _, c := range cg.List {
if !strings.HasPrefix(c.Text, "//lint:") {
continue
}
cmd, args := parseDirective(c.Text)
d := Directive{
Command: cmd,
Arguments: args,
Directive: c,
Node: node,
}
dirs = append(dirs, d)
}
}
}
}
return dirs
}
go-tools-2021.1.2/analysis/report/ 0000775 0000000 0000000 00000000000 14143223131 0016632 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/analysis/report/report.go 0000664 0000000 0000000 00000012555 14143223131 0020504 0 ustar 00root root 0000000 0000000 package report
import (
"bytes"
"fmt"
"go/ast"
"go/format"
"go/token"
"path/filepath"
"strconv"
"strings"
"honnef.co/go/tools/analysis/facts"
"honnef.co/go/tools/go/ast/astutil"
"golang.org/x/tools/go/analysis"
)
type Options struct {
ShortRange bool
FilterGenerated bool
Fixes []analysis.SuggestedFix
Related []analysis.RelatedInformation
}
type Option func(*Options)
func ShortRange() Option {
return func(opts *Options) {
opts.ShortRange = true
}
}
func FilterGenerated() Option {
return func(opts *Options) {
opts.FilterGenerated = true
}
}
func Fixes(fixes ...analysis.SuggestedFix) Option {
return func(opts *Options) {
opts.Fixes = append(opts.Fixes, fixes...)
}
}
func Related(node Positioner, message string) Option {
return func(opts *Options) {
pos, end, ok := getRange(node, opts.ShortRange)
if !ok {
return
}
r := analysis.RelatedInformation{
Pos: pos,
End: end,
Message: message,
}
opts.Related = append(opts.Related, r)
}
}
type Positioner interface {
Pos() token.Pos
}
type fullPositioner interface {
Pos() token.Pos
End() token.Pos
}
type sourcer interface {
Source() ast.Node
}
// shortRange returns the position and end of the main component of an
// AST node. For nodes that have no body, the short range is identical
// to the node's Pos and End. For nodes that do have a body, the short
// range excludes the body.
func shortRange(node ast.Node) (pos, end token.Pos) {
switch node := node.(type) {
case *ast.File:
return node.Pos(), node.Name.End()
case *ast.CaseClause:
return node.Pos(), node.Colon + 1
case *ast.CommClause:
return node.Pos(), node.Colon + 1
case *ast.DeferStmt:
return node.Pos(), node.Defer + token.Pos(len("defer"))
case *ast.ExprStmt:
return shortRange(node.X)
case *ast.ForStmt:
if node.Post != nil {
return node.For, node.Post.End()
} else if node.Cond != nil {
return node.For, node.Cond.End()
} else if node.Init != nil {
// +1 to catch the semicolon, for gofmt'ed code
return node.Pos(), node.Init.End() + 1
} else {
return node.Pos(), node.For + token.Pos(len("for"))
}
case *ast.FuncDecl:
return node.Pos(), node.Type.End()
case *ast.FuncLit:
return node.Pos(), node.Type.End()
case *ast.GoStmt:
if _, ok := astutil.Unparen(node.Call.Fun).(*ast.FuncLit); ok {
return node.Pos(), node.Go + token.Pos(len("go"))
} else {
return node.Pos(), node.End()
}
case *ast.IfStmt:
return node.Pos(), node.Cond.End()
case *ast.RangeStmt:
return node.Pos(), node.X.End()
case *ast.SelectStmt:
return node.Pos(), node.Pos() + token.Pos(len("select"))
case *ast.SwitchStmt:
if node.Tag != nil {
return node.Pos(), node.Tag.End()
} else if node.Init != nil {
// +1 to catch the semicolon, for gofmt'ed code
return node.Pos(), node.Init.End() + 1
} else {
return node.Pos(), node.Pos() + token.Pos(len("switch"))
}
case *ast.TypeSwitchStmt:
return node.Pos(), node.Assign.End()
default:
return node.Pos(), node.End()
}
}
func HasRange(node Positioner) bool {
// we don't know if getRange will be called with shortRange set to
// true, so make sure that both work.
_, _, ok := getRange(node, false)
if !ok {
return false
}
_, _, ok = getRange(node, true)
return ok
}
func getRange(node Positioner, short bool) (pos, end token.Pos, ok bool) {
switch n := node.(type) {
case sourcer:
s := n.Source()
if s == nil {
return 0, 0, false
}
if short {
p, e := shortRange(s)
return p, e, true
}
return s.Pos(), s.End(), true
case fullPositioner:
if short {
p, e := shortRange(n)
return p, e, true
}
return n.Pos(), n.End(), true
default:
return n.Pos(), token.NoPos, true
}
}
func Report(pass *analysis.Pass, node Positioner, message string, opts ...Option) {
cfg := &Options{}
for _, opt := range opts {
opt(cfg)
}
file := DisplayPosition(pass.Fset, node.Pos()).Filename
if cfg.FilterGenerated {
m := pass.ResultOf[facts.Generated].(map[string]facts.Generator)
if _, ok := m[file]; ok {
return
}
}
pos, end, ok := getRange(node, cfg.ShortRange)
if !ok {
panic(fmt.Sprintf("no valid position for reporting node %v", node))
}
d := analysis.Diagnostic{
Pos: pos,
End: end,
Message: message,
SuggestedFixes: cfg.Fixes,
Related: cfg.Related,
}
pass.Report(d)
}
func Render(pass *analysis.Pass, x interface{}) string {
var buf bytes.Buffer
if err := format.Node(&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 DisplayPosition(fset *token.FileSet, p token.Pos) token.Position {
if p == token.NoPos {
return token.Position{}
}
// Only use the adjusted position if it points to another Go file.
// This means we'll point to the original file for cgo files, but
// we won't point to a YACC grammar file.
pos := fset.PositionFor(p, false)
adjPos := fset.PositionFor(p, true)
if filepath.Ext(adjPos.Filename) == ".go" {
return adjPos
}
return pos
}
func Ordinal(n int) string {
suffix := "th"
if n < 10 || n > 20 {
switch n % 10 {
case 0:
suffix = "th"
case 1:
suffix = "st"
case 2:
suffix = "nd"
case 3:
suffix = "rd"
default:
suffix = "th"
}
}
return strconv.Itoa(n) + suffix
}
go-tools-2021.1.2/analysis/report/report_test.go 0000664 0000000 0000000 00000001240 14143223131 0021530 0 ustar 00root root 0000000 0000000 package report
import "testing"
func TestOrdinal(t *testing.T) {
tests := []struct {
num int
want string
}{
{0, "0th"}, {1, "1st"}, {2, "2nd"}, {3, "3rd"}, {4, "4th"}, {5, "5th"}, {6, "6th"}, {7, "7th"}, {8, "8th"}, {9, "9th"},
{10, "10th"}, {11, "11th"}, {12, "12th"}, {13, "13th"}, {14, "14th"}, {15, "15th"}, {16, "16th"}, {17, "17th"}, {18, "18th"}, {19, "19th"},
{20, "20th"}, {21, "21st"}, {22, "22nd"}, {23, "23rd"}, {24, "24th"}, {25, "25th"}, {26, "26th"}, {27, "27th"}, {28, "28th"}, {29, "29th"},
}
for _, tt := range tests {
if got := Ordinal(tt.num); got != tt.want {
t.Errorf("Ordinal(%d) = %s, want %s", tt.num, got, tt.want)
}
}
}
go-tools-2021.1.2/cmd/ 0000775 0000000 0000000 00000000000 14143223131 0014237 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/cmd/keyify/ 0000775 0000000 0000000 00000000000 14143223131 0015537 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/cmd/keyify/README.md 0000664 0000000 0000000 00000002011 14143223131 0017010 0 ustar 00root root 0000000 0000000 Keyify turns unkeyed struct literals (`T{1, 2, 3}`) into keyed
ones (`T{A: 1, B: 2, C: 3}`)
## Installation
See [the main README](https://github.com/dominikh/go-tools#installation) for installation instructions.
## Usage
Call keyify with a position such as `/some/file.go:#5`, where #5 is
the byte offset in the file and has to point at or into a struct
literal.
By default, keyify will print the new literal on stdout, formatted as
Go code. By using the `-json` flag, it will print a JSON object
denoting the start and end of the original literal, and its
replacement. This is useful for integration with editors.
For a description of all available flags, see `keyify -help`.
### Emacs
For Emacs integration, add the following to your `.emacs` file:
```
(add-to-list 'load-path "/your/gopath/src/honnef.co/go/tools/cmd/keyify)
(eval-after-load 'go-mode
(lambda ()
(require 'go-keyify)))
```
With point on or in a struct literal, call the `go-keyify` command.

go-tools-2021.1.2/cmd/keyify/go-keyify.el 0000664 0000000 0000000 00000003004 14143223131 0017761 0 ustar 00root root 0000000 0000000 ;;; go-keyify.el --- keyify integration for Emacs
;; Copyright 2016 Dominik Honnef. All rights reserved.
;; Use of this source code is governed by a BSD-style
;; license that can be found in the LICENSE file.
;; Author: Dominik Honnef
;; Version: 1.0.0
;; Keywords: languages go
;; URL: https://github.com/dominikh/go-keyify
;;
;; This file is not part of GNU Emacs.
;;; Code:
(require 'json)
;;;###autoload
(defun go-keyify ()
"Turn an unkeyed struct literal into a keyed one.
Call with point on or in a struct literal."
(interactive)
(let* ((name (buffer-file-name))
(point (point))
(bpoint (1- (position-bytes point)))
(out (get-buffer-create "*go-keyify-output")))
(with-current-buffer out
(setq buffer-read-only nil)
(erase-buffer))
(with-current-buffer (get-buffer-create "*go-keyify-input*")
(setq buffer-read-only nil)
(erase-buffer)
(go--insert-modified-files)
(call-process-region (point-min) (point-max) "keyify" t out nil
"-modified"
"-json"
(format "%s:#%d" name bpoint)))
(let ((res (with-current-buffer out
(goto-char (point-min))
(json-read))))
(delete-region
(1+ (cdr (assoc 'start res)))
(1+ (cdr (assoc 'end res))))
(insert (cdr (assoc 'replacement res)))
(indent-region (1+ (cdr (assoc 'start res))) (point))
(goto-char point))))
(provide 'go-keyify)
;;; go-keyify.el ends here
go-tools-2021.1.2/cmd/keyify/keyify.go 0000664 0000000 0000000 00000021756 14143223131 0017401 0 ustar 00root root 0000000 0000000 // keyify transforms unkeyed struct literals into a keyed ones.
package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"go/ast"
"go/build"
"go/constant"
"go/printer"
"go/token"
"go/types"
"log"
"os"
"path/filepath"
"honnef.co/go/tools/go/ast/astutil"
"honnef.co/go/tools/lintcmd/version"
"golang.org/x/tools/go/buildutil"
//lint:ignore SA1019 this tool is unmaintained, just keep it working
"golang.org/x/tools/go/loader"
)
var (
fRecursive bool
fOneLine bool
fJSON bool
fMinify bool
fModified bool
fVersion bool
)
func init() {
flag.BoolVar(&fRecursive, "r", false, "keyify struct initializers recursively")
flag.BoolVar(&fOneLine, "o", false, "print new struct initializer on a single line")
flag.BoolVar(&fJSON, "json", false, "print new struct initializer as JSON")
flag.BoolVar(&fMinify, "m", false, "omit fields that are set to their zero value")
flag.BoolVar(&fModified, "modified", false, "read an archive of modified files from standard input")
flag.BoolVar(&fVersion, "version", false, "Print version and exit")
}
func usage() {
fmt.Printf("Usage: %s [flags] \n\n", os.Args[0])
flag.PrintDefaults()
}
func main() {
log.SetFlags(0)
flag.Usage = usage
flag.Parse()
if fVersion {
version.Print(version.Version, version.MachineVersion)
os.Exit(0)
}
if flag.NArg() != 1 {
flag.Usage()
os.Exit(2)
}
pos := flag.Args()[0]
name, start, _, err := parsePos(pos)
if err != nil {
log.Fatal(err)
}
eval, err := filepath.EvalSymlinks(name)
if err != nil {
log.Fatal(err)
}
name, err = filepath.Abs(eval)
if err != nil {
log.Fatal(err)
}
cwd, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
ctx := &build.Default
if fModified {
overlay, err := buildutil.ParseOverlayArchive(os.Stdin)
if err != nil {
log.Fatal(err)
}
ctx = buildutil.OverlayContext(ctx, overlay)
}
bpkg, err := buildutil.ContainingPackage(ctx, cwd, name)
if err != nil {
log.Fatal(err)
}
conf := &loader.Config{
Build: ctx,
}
conf.TypeCheckFuncBodies = func(s string) bool {
return s == bpkg.ImportPath || s == bpkg.ImportPath+"_test"
}
conf.ImportWithTests(bpkg.ImportPath)
lprog, err := conf.Load()
if err != nil {
log.Fatal(err)
}
var tf *token.File
var af *ast.File
var pkg *loader.PackageInfo
outer:
for _, pkg = range lprog.InitialPackages() {
for _, ff := range pkg.Files {
file := lprog.Fset.File(ff.Pos())
if file.Name() == name {
af = ff
tf = file
break outer
}
}
}
if tf == nil {
log.Fatalf("couldn't find file %s", name)
}
tstart, tend, err := fileOffsetToPos(tf, start, start)
if err != nil {
log.Fatal(err)
}
path, _ := astutil.PathEnclosingInterval(af, tstart, tend)
var complit *ast.CompositeLit
for _, p := range path {
if p, ok := p.(*ast.CompositeLit); ok {
complit = p
break
}
}
if complit == nil {
log.Fatal("no composite literal found near point")
}
if len(complit.Elts) == 0 {
printComplit(complit, complit, lprog.Fset, lprog.Fset)
return
}
if _, ok := complit.Elts[0].(*ast.KeyValueExpr); ok {
lit := complit
if fOneLine {
lit = copyExpr(complit, 1).(*ast.CompositeLit)
}
printComplit(complit, lit, lprog.Fset, lprog.Fset)
return
}
_, ok := pkg.TypeOf(complit).Underlying().(*types.Struct)
if !ok {
log.Fatal("not a struct initialiser")
return
}
newComplit, lines := keyify(pkg, complit)
newFset := token.NewFileSet()
newFile := newFset.AddFile("", -1, lines)
for i := 1; i <= lines; i++ {
newFile.AddLine(i)
}
printComplit(complit, newComplit, lprog.Fset, newFset)
}
func keyify(
pkg *loader.PackageInfo,
complit *ast.CompositeLit,
) (*ast.CompositeLit, int) {
var calcPos func(int) token.Pos
if fOneLine {
calcPos = func(int) token.Pos { return token.Pos(1) }
} else {
calcPos = func(i int) token.Pos { return token.Pos(2 + i) }
}
st, _ := pkg.TypeOf(complit).Underlying().(*types.Struct)
newComplit := &ast.CompositeLit{
Type: complit.Type,
Lbrace: 1,
Rbrace: token.Pos(st.NumFields() + 2),
}
if fOneLine {
newComplit.Rbrace = 1
}
numLines := 2 + st.NumFields()
n := 0
for i := 0; i < st.NumFields(); i++ {
field := st.Field(i)
val := complit.Elts[i]
if fRecursive {
if val2, ok := val.(*ast.CompositeLit); ok {
if _, ok := pkg.TypeOf(val2.Type).Underlying().(*types.Struct); ok {
// FIXME(dh): this code is obviously wrong. But
// what were we intending to do here?
var lines int
numLines += lines
//lint:ignore SA4006 See FIXME above.
val, lines = keyify(pkg, val2)
}
}
}
_, isIface := st.Field(i).Type().Underlying().(*types.Interface)
if fMinify && (isNil(val, pkg) || (!isIface && isZero(val, pkg))) {
continue
}
elt := &ast.KeyValueExpr{
Key: &ast.Ident{NamePos: calcPos(n), Name: field.Name()},
Value: copyExpr(val, calcPos(n)),
}
newComplit.Elts = append(newComplit.Elts, elt)
n++
}
return newComplit, numLines
}
func isNil(val ast.Expr, pkg *loader.PackageInfo) bool {
ident, ok := val.(*ast.Ident)
if !ok {
return false
}
if _, ok := pkg.ObjectOf(ident).(*types.Nil); ok {
return true
}
if c, ok := pkg.ObjectOf(ident).(*types.Const); ok {
if c.Val().Kind() != constant.Bool {
return false
}
return !constant.BoolVal(c.Val())
}
return false
}
func isZero(val ast.Expr, pkg *loader.PackageInfo) bool {
switch val := val.(type) {
case *ast.BasicLit:
switch val.Value {
case `""`, "``", "0", "0.0", "0i", "0.":
return true
default:
return false
}
case *ast.Ident:
return isNil(val, pkg)
case *ast.CompositeLit:
typ := pkg.TypeOf(val.Type)
if typ == nil {
return false
}
isIface := false
switch typ := typ.Underlying().(type) {
case *types.Struct:
case *types.Array:
_, isIface = typ.Elem().Underlying().(*types.Interface)
default:
return false
}
for _, elt := range val.Elts {
if isNil(elt, pkg) || (!isIface && !isZero(elt, pkg)) {
return false
}
}
return true
}
return false
}
func printComplit(oldlit, newlit *ast.CompositeLit, oldfset, newfset *token.FileSet) {
buf := &bytes.Buffer{}
cfg := printer.Config{Mode: printer.UseSpaces | printer.TabIndent, Tabwidth: 8}
_ = cfg.Fprint(buf, newfset, newlit)
if fJSON {
output := struct {
Start int `json:"start"`
End int `json:"end"`
Replacement string `json:"replacement"`
}{
oldfset.Position(oldlit.Pos()).Offset,
oldfset.Position(oldlit.End()).Offset,
buf.String(),
}
_ = json.NewEncoder(os.Stdout).Encode(output)
} else {
fmt.Println(buf.String())
}
}
func copyExpr(expr ast.Expr, line token.Pos) ast.Expr {
switch expr := expr.(type) {
case *ast.BasicLit:
cp := *expr
cp.ValuePos = 0
return &cp
case *ast.BinaryExpr:
cp := *expr
cp.X = copyExpr(cp.X, line)
cp.OpPos = 0
cp.Y = copyExpr(cp.Y, line)
return &cp
case *ast.CallExpr:
cp := *expr
cp.Fun = copyExpr(cp.Fun, line)
cp.Lparen = 0
for i, v := range cp.Args {
cp.Args[i] = copyExpr(v, line)
}
if cp.Ellipsis != 0 {
cp.Ellipsis = line
}
cp.Rparen = 0
return &cp
case *ast.CompositeLit:
cp := *expr
cp.Type = copyExpr(cp.Type, line)
cp.Lbrace = 0
for i, v := range cp.Elts {
cp.Elts[i] = copyExpr(v, line)
}
cp.Rbrace = 0
return &cp
case *ast.Ident:
cp := *expr
cp.NamePos = 0
return &cp
case *ast.IndexExpr:
cp := *expr
cp.X = copyExpr(cp.X, line)
cp.Lbrack = 0
cp.Index = copyExpr(cp.Index, line)
cp.Rbrack = 0
return &cp
case *ast.KeyValueExpr:
cp := *expr
cp.Key = copyExpr(cp.Key, line)
cp.Colon = 0
cp.Value = copyExpr(cp.Value, line)
return &cp
case *ast.ParenExpr:
cp := *expr
cp.Lparen = 0
cp.X = copyExpr(cp.X, line)
cp.Rparen = 0
return &cp
case *ast.SelectorExpr:
cp := *expr
cp.X = copyExpr(cp.X, line)
cp.Sel = copyExpr(cp.Sel, line).(*ast.Ident)
return &cp
case *ast.SliceExpr:
cp := *expr
cp.X = copyExpr(cp.X, line)
cp.Lbrack = 0
cp.Low = copyExpr(cp.Low, line)
cp.High = copyExpr(cp.High, line)
cp.Max = copyExpr(cp.Max, line)
cp.Rbrack = 0
return &cp
case *ast.StarExpr:
cp := *expr
cp.Star = 0
cp.X = copyExpr(cp.X, line)
return &cp
case *ast.TypeAssertExpr:
cp := *expr
cp.X = copyExpr(cp.X, line)
cp.Lparen = 0
cp.Type = copyExpr(cp.Type, line)
cp.Rparen = 0
return &cp
case *ast.UnaryExpr:
cp := *expr
cp.OpPos = 0
cp.X = copyExpr(cp.X, line)
return &cp
case *ast.MapType:
cp := *expr
cp.Map = 0
cp.Key = copyExpr(cp.Key, line)
cp.Value = copyExpr(cp.Value, line)
return &cp
case *ast.ArrayType:
cp := *expr
cp.Lbrack = 0
cp.Len = copyExpr(cp.Len, line)
cp.Elt = copyExpr(cp.Elt, line)
return &cp
case *ast.Ellipsis:
cp := *expr
cp.Elt = copyExpr(cp.Elt, line)
cp.Ellipsis = line
return &cp
case *ast.InterfaceType:
cp := *expr
cp.Interface = 0
return &cp
case *ast.StructType:
cp := *expr
cp.Struct = 0
return &cp
case *ast.FuncLit:
return expr
case *ast.ChanType:
cp := *expr
cp.Arrow = 0
cp.Begin = 0
cp.Value = copyExpr(cp.Value, line)
return &cp
case nil:
return nil
default:
panic(fmt.Sprintf("shouldn't happen: unknown ast.Expr of type %T", expr))
}
}
go-tools-2021.1.2/cmd/keyify/position.go 0000664 0000000 0000000 00000003244 14143223131 0017735 0 ustar 00root root 0000000 0000000 // 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 main
import (
"fmt"
"go/token"
"strconv"
"strings"
)
func parseOctothorpDecimal(s string) int {
if s != "" && s[0] == '#' {
if s, err := strconv.ParseInt(s[1:], 10, 32); err == nil {
return int(s)
}
}
return -1
}
func parsePos(pos string) (filename string, startOffset, endOffset int, err error) {
if pos == "" {
err = fmt.Errorf("no source position specified")
return
}
colon := strings.LastIndex(pos, ":")
if colon < 0 {
err = fmt.Errorf("bad position syntax %q", pos)
return
}
filename, offset := pos[:colon], pos[colon+1:]
startOffset = -1
endOffset = -1
if hyphen := strings.Index(offset, ","); hyphen < 0 {
// e.g. "foo.go:#123"
startOffset = parseOctothorpDecimal(offset)
endOffset = startOffset
} else {
// e.g. "foo.go:#123,#456"
startOffset = parseOctothorpDecimal(offset[:hyphen])
endOffset = parseOctothorpDecimal(offset[hyphen+1:])
}
if startOffset < 0 || endOffset < 0 {
err = fmt.Errorf("invalid offset %q in query position", offset)
return
}
return
}
func fileOffsetToPos(file *token.File, startOffset, endOffset int) (start, end token.Pos, err error) {
// Range check [start..end], inclusive of both end-points.
if 0 <= startOffset && startOffset <= file.Size() {
start = file.Pos(int(startOffset))
} else {
err = fmt.Errorf("start position is beyond end of file")
return
}
if 0 <= endOffset && endOffset <= file.Size() {
end = file.Pos(int(endOffset))
} else {
err = fmt.Errorf("end position is beyond end of file")
return
}
return
}
go-tools-2021.1.2/cmd/staticcheck/ 0000775 0000000 0000000 00000000000 14143223131 0016524 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/cmd/staticcheck/README.md 0000664 0000000 0000000 00000000633 14143223131 0020005 0 ustar 00root root 0000000 0000000 # staticcheck
_staticcheck_ offers extensive analysis of Go code, covering a myriad
of categories. It will detect bugs, suggest code simplifications,
point out dead code, and more.
## Installation
See [the main README](https://github.com/dominikh/go-tools#installation) for installation instructions.
## Documentation
Detailed documentation can be found on
[staticcheck.io](https://staticcheck.io/docs/).
go-tools-2021.1.2/cmd/staticcheck/staticcheck.go 0000664 0000000 0000000 00000002015 14143223131 0021336 0 ustar 00root root 0000000 0000000 // staticcheck analyses Go code and makes it better.
package main
import (
"log"
"os"
"honnef.co/go/tools/lintcmd"
"honnef.co/go/tools/lintcmd/version"
"honnef.co/go/tools/quickfix"
"honnef.co/go/tools/simple"
"honnef.co/go/tools/staticcheck"
"honnef.co/go/tools/stylecheck"
"honnef.co/go/tools/unused"
)
func main() {
cmd := lintcmd.NewCommand("staticcheck")
cmd.SetVersion(version.Version, version.MachineVersion)
fs := cmd.FlagSet()
debug := fs.String("debug.unused-graph", "", "Write unused's object graph to `file`")
qf := fs.Bool("debug.run-quickfix-analyzers", false, "Run quickfix analyzers")
cmd.ParseFlags(os.Args[1:])
cmd.AddAnalyzers(simple.Analyzers...)
cmd.AddAnalyzers(staticcheck.Analyzers...)
cmd.AddAnalyzers(stylecheck.Analyzers...)
cmd.AddAnalyzers(unused.Analyzer)
if *qf {
cmd.AddAnalyzers(quickfix.Analyzers...)
}
if *debug != "" {
f, err := os.OpenFile(*debug, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
log.Fatal(err)
}
unused.Debug = f
}
cmd.Run()
}
go-tools-2021.1.2/cmd/structlayout-optimize/ 0000775 0000000 0000000 00000000000 14143223131 0020657 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/cmd/structlayout-optimize/main.go 0000664 0000000 0000000 00000007547 14143223131 0022147 0 ustar 00root root 0000000 0000000 // structlayout-optimize reorders struct fields to minimize the amount
// of padding.
package main
import (
"encoding/json"
"flag"
"fmt"
"log"
"os"
"sort"
"strings"
"honnef.co/go/tools/lintcmd/version"
st "honnef.co/go/tools/structlayout"
)
var (
fJSON bool
fRecurse bool
fVersion bool
)
func init() {
flag.BoolVar(&fJSON, "json", false, "Format data as JSON")
flag.BoolVar(&fRecurse, "r", false, "Break up structs and reorder their fields freely")
flag.BoolVar(&fVersion, "version", false, "Print version and exit")
}
func main() {
log.SetFlags(0)
flag.Parse()
if fVersion {
version.Print(version.Version, version.MachineVersion)
os.Exit(0)
}
var in []st.Field
if err := json.NewDecoder(os.Stdin).Decode(&in); err != nil {
log.Fatal(err)
}
if len(in) == 0 {
return
}
if !fRecurse {
in = combine(in)
}
var fields []st.Field
for _, field := range in {
if field.IsPadding {
continue
}
fields = append(fields, field)
}
optimize(fields)
fields = pad(fields)
if fJSON {
json.NewEncoder(os.Stdout).Encode(fields)
} else {
for _, field := range fields {
fmt.Println(field)
}
}
}
func combine(fields []st.Field) []st.Field {
new := st.Field{}
cur := ""
var out []st.Field
wasPad := true
for _, field := range fields {
var prefix string
if field.IsPadding {
wasPad = true
continue
}
p := strings.Split(field.Name, ".")
prefix = strings.Join(p[:2], ".")
if field.Align > new.Align {
new.Align = field.Align
}
if !wasPad {
new.End = field.Start
new.Size = new.End - new.Start
}
if prefix != cur {
if cur != "" {
out = append(out, new)
}
cur = prefix
new = field
new.Name = prefix
} else {
new.Type = "struct"
}
wasPad = false
}
new.Size = new.End - new.Start
out = append(out, new)
return out
}
func optimize(fields []st.Field) {
sort.Sort(&byAlignAndSize{fields})
}
func pad(fields []st.Field) []st.Field {
if len(fields) == 0 {
return nil
}
var out []st.Field
pos := int64(0)
offsets := offsetsof(fields)
alignment := int64(1)
for i, field := range fields {
if field.Align > alignment {
alignment = field.Align
}
if offsets[i] > pos {
padding := offsets[i] - pos
out = append(out, st.Field{
IsPadding: true,
Start: pos,
End: pos + padding,
Size: padding,
})
pos += padding
}
field.Start = pos
field.End = pos + field.Size
out = append(out, field)
pos += field.Size
}
sz := size(out)
pad := align(sz, alignment) - sz
if pad > 0 {
field := out[len(out)-1]
out = append(out, st.Field{
IsPadding: true,
Start: field.End,
End: field.End + pad,
Size: pad,
})
}
return out
}
func size(fields []st.Field) int64 {
n := int64(0)
for _, field := range fields {
n += field.Size
}
return n
}
type byAlignAndSize struct {
fields []st.Field
}
func (s *byAlignAndSize) Len() int { return len(s.fields) }
func (s *byAlignAndSize) Swap(i, j int) {
s.fields[i], s.fields[j] = s.fields[j], s.fields[i]
}
func (s *byAlignAndSize) Less(i, j int) bool {
// Place zero sized objects before non-zero sized objects.
if s.fields[i].Size == 0 && s.fields[j].Size != 0 {
return true
}
if s.fields[j].Size == 0 && s.fields[i].Size != 0 {
return false
}
// Next, place more tightly aligned objects before less tightly aligned objects.
if s.fields[i].Align != s.fields[j].Align {
return s.fields[i].Align > s.fields[j].Align
}
// Lastly, order by size.
if s.fields[i].Size != s.fields[j].Size {
return s.fields[i].Size > s.fields[j].Size
}
return false
}
func offsetsof(fields []st.Field) []int64 {
offsets := make([]int64, len(fields))
var o int64
for i, f := range fields {
a := f.Align
o = align(o, a)
offsets[i] = o
o += f.Size
}
return offsets
}
// align returns the smallest y >= x such that y % a == 0.
func align(x, a int64) int64 {
y := x + a - 1
return y - y%a
}
go-tools-2021.1.2/cmd/structlayout-pretty/ 0000775 0000000 0000000 00000000000 14143223131 0020346 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/cmd/structlayout-pretty/main.go 0000664 0000000 0000000 00000003076 14143223131 0021627 0 ustar 00root root 0000000 0000000 // structlayout-pretty formats the output of structlayout with ASCII
// art.
package main
import (
"encoding/json"
"flag"
"fmt"
"log"
"os"
"strings"
"honnef.co/go/tools/lintcmd/version"
st "honnef.co/go/tools/structlayout"
)
var (
fVerbose bool
fVersion bool
)
func init() {
flag.BoolVar(&fVerbose, "v", false, "Do not compact consecutive bytes of fields")
flag.BoolVar(&fVersion, "version", false, "Print version and exit")
}
func main() {
log.SetFlags(0)
flag.Parse()
if fVersion {
version.Print(version.Version, version.MachineVersion)
os.Exit(0)
}
var fields []st.Field
if err := json.NewDecoder(os.Stdin).Decode(&fields); err != nil {
log.Fatal(err)
}
if len(fields) == 0 {
return
}
max := fields[len(fields)-1].End
maxLength := len(fmt.Sprintf("%d", max))
padding := strings.Repeat(" ", maxLength+2)
format := fmt.Sprintf(" %%%dd ", maxLength)
pos := int64(0)
fmt.Println(padding + "+--------+")
for _, field := range fields {
name := field.Name + " " + field.Type
if field.IsPadding {
name = "padding"
}
fmt.Printf(format+"| | <- %s (size %d, align %d)\n", pos, name, field.Size, field.Align)
fmt.Println(padding + "+--------+")
if fVerbose {
for i := int64(0); i < field.Size-1; i++ {
fmt.Printf(format+"| |\n", pos+i+1)
fmt.Println(padding + "+--------+")
}
} else {
if field.Size > 2 {
fmt.Println(padding + "-........-")
fmt.Println(padding + "+--------+")
fmt.Printf(format+"| |\n", pos+field.Size-1)
fmt.Println(padding + "+--------+")
}
}
pos += field.Size
}
}
go-tools-2021.1.2/cmd/structlayout/ 0000775 0000000 0000000 00000000000 14143223131 0017021 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/cmd/structlayout/README.md 0000664 0000000 0000000 00000005427 14143223131 0020310 0 ustar 00root root 0000000 0000000 # structlayout
The _structlayout_ utility prints the layout of a struct β that is the
byte offset and size of each field, respecting alignment/padding.
The information is printed in human-readable form by default, but can
be emitted as JSON with the `-json` flag. This makes it easy to
consume this information in other tools.
A utility called _structlayout-pretty_ takes this JSON and prints an
ASCII graphic representing the memory layout.
_structlayout-optimize_ is another tool. Inspired by
[maligned](https://github.com/mdempsky/maligned), it reads
_structlayout_ JSON on stdin and reorders fields to minimize the
amount of padding. The tool can itself emit JSON and feed into e.g.
_structlayout-pretty_.
_structlayout-svg_ is a third-party tool that, similarly to
_structlayout-pretty_, visualises struct layouts. It does so by
generating a fancy-looking SVG graphic. You can install it via
```
go get github.com/ajstarks/svgo/structlayout-svg
```
## Installation
See [the main README](https://github.com/dominikh/go-tools#installation) for installation instructions.
## Examples
```
$ structlayout bufio Reader
Reader.buf []byte: 0-24 (24 bytes)
Reader.rd io.Reader: 24-40 (16 bytes)
Reader.r int: 40-48 (8 bytes)
Reader.w int: 48-56 (8 bytes)
Reader.err error: 56-72 (16 bytes)
Reader.lastByte int: 72-80 (8 bytes)
Reader.lastRuneSize int: 80-88 (8 bytes)
```
```
$ structlayout -json bufio Reader | jq .
[
{
"name": "Reader.buf",
"type": "[]byte",
"start": 0,
"end": 24,
"size": 24,
"is_padding": false
},
{
"name": "Reader.rd",
"type": "io.Reader",
"start": 24,
"end": 40,
"size": 16,
"is_padding": false
},
{
"name": "Reader.r",
"type": "int",
"start": 40,
"end": 48,
"size": 8,
"is_padding": false
},
...
```
```
$ structlayout -json bufio Reader | structlayout-pretty
+--------+
0 | | <- Reader.buf []byte
+--------+
-........-
+--------+
23 | |
+--------+
24 | | <- Reader.rd io.Reader
+--------+
-........-
+--------+
39 | |
+--------+
40 | | <- Reader.r int
+--------+
-........-
+--------+
47 | |
+--------+
48 | | <- Reader.w int
+--------+
-........-
+--------+
55 | |
+--------+
56 | | <- Reader.err error
+--------+
-........-
+--------+
71 | |
+--------+
72 | | <- Reader.lastByte int
+--------+
-........-
+--------+
79 | |
+--------+
80 | | <- Reader.lastRuneSize int
+--------+
-........-
+--------+
87 | |
+--------+
```
```
$ structlayout -json bytes Buffer | structlayout-svg -t "bytes.Buffer" > /tmp/struct.svg
```

go-tools-2021.1.2/cmd/structlayout/main.go 0000664 0000000 0000000 00000005572 14143223131 0020305 0 ustar 00root root 0000000 0000000 // structlayout displays the layout (field sizes and padding) of structs.
package main
import (
"encoding/json"
"flag"
"fmt"
"go/build"
"go/types"
"log"
"os"
"honnef.co/go/tools/go/gcsizes"
"honnef.co/go/tools/lintcmd/version"
st "honnef.co/go/tools/structlayout"
"golang.org/x/tools/go/packages"
)
var (
fJSON bool
fVersion bool
)
func init() {
flag.BoolVar(&fJSON, "json", false, "Format data as JSON")
flag.BoolVar(&fVersion, "version", false, "Print version and exit")
}
func main() {
log.SetFlags(0)
flag.Parse()
if fVersion {
version.Print(version.Version, version.MachineVersion)
os.Exit(0)
}
if len(flag.Args()) != 2 {
flag.Usage()
os.Exit(1)
}
cfg := &packages.Config{
Mode: packages.NeedImports | packages.NeedExportsFile | packages.NeedTypes | packages.NeedSyntax,
Tests: true,
}
pkgs, err := packages.Load(cfg, flag.Args()[0])
if err != nil {
log.Fatal(err)
}
for _, pkg := range pkgs {
typName := flag.Args()[1]
var typ types.Type
obj := pkg.Types.Scope().Lookup(typName)
if obj == nil {
continue
}
typ = obj.Type()
st, ok := typ.Underlying().(*types.Struct)
if !ok {
log.Fatal("identifier is not a struct type")
}
fields := sizes(st, typ.(*types.Named).Obj().Name(), 0, nil)
if fJSON {
emitJSON(fields)
} else {
emitText(fields)
}
return
}
log.Fatal("couldn't find type")
}
func emitJSON(fields []st.Field) {
if fields == nil {
fields = []st.Field{}
}
json.NewEncoder(os.Stdout).Encode(fields)
}
func emitText(fields []st.Field) {
for _, field := range fields {
fmt.Println(field)
}
}
func sizes(typ *types.Struct, prefix string, base int64, out []st.Field) []st.Field {
s := gcsizes.ForArch(build.Default.GOARCH)
n := typ.NumFields()
var fields []*types.Var
for i := 0; i < n; i++ {
fields = append(fields, typ.Field(i))
}
offsets := s.Offsetsof(fields)
for i := range offsets {
offsets[i] += base
}
pos := base
for i, field := range fields {
if offsets[i] > pos {
padding := offsets[i] - pos
out = append(out, st.Field{
IsPadding: true,
Start: pos,
End: pos + padding,
Size: padding,
})
pos += padding
}
size := s.Sizeof(field.Type())
if typ2, ok := field.Type().Underlying().(*types.Struct); ok && typ2.NumFields() != 0 {
out = sizes(typ2, prefix+"."+field.Name(), pos, out)
} else {
out = append(out, st.Field{
Name: prefix + "." + field.Name(),
Type: field.Type().String(),
Start: offsets[i],
End: offsets[i] + size,
Size: size,
Align: s.Alignof(field.Type()),
})
}
pos += size
}
if len(out) == 0 {
return out
}
field := &out[len(out)-1]
if field.Size == 0 {
field.Size = 1
field.End++
}
pad := s.Sizeof(typ) - field.End
if pad > 0 {
out = append(out, st.Field{
IsPadding: true,
Start: field.End,
End: field.End + pad,
Size: pad,
})
}
return out
}
go-tools-2021.1.2/config/ 0000775 0000000 0000000 00000000000 14143223131 0014741 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/config/config.go 0000664 0000000 0000000 00000013660 14143223131 0016543 0 ustar 00root root 0000000 0000000 package config
import (
"bytes"
"fmt"
"go/ast"
"go/token"
"os"
"path/filepath"
"reflect"
"strings"
"github.com/BurntSushi/toml"
"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) {
dir := dirAST(pass.Files, pass.Fset)
if dir == "" {
cfg := DefaultConfig
return &cfg, nil
}
cfg, err := Load(dir)
if err != nil {
return nil, fmt.Errorf("error loading staticcheck.conf: %s", err)
}
return &cfg, nil
},
RunDespiteErrors: true,
ResultType: reflect.TypeOf((*Config)(nil)),
}
func For(pass *analysis.Pass) *Config {
return pass.ResultOf[Analyzer].(*Config)
}
func mergeLists(a, b []string) []string {
out := make([]string, 0, len(a)+len(b))
for _, el := range b {
if el == "inherit" {
out = append(out, a...)
} else {
out = append(out, el)
}
}
return out
}
func normalizeList(list []string) []string {
if len(list) > 1 {
nlist := make([]string, 0, len(list))
nlist = append(nlist, list[0])
for i, el := range list[1:] {
if el != list[i] {
nlist = append(nlist, el)
}
}
list = nlist
}
for _, el := range list {
if el == "inherit" {
// This should never happen, because the default config
// should not use "inherit"
panic(`unresolved "inherit"`)
}
}
return list
}
func (cfg Config) Merge(ocfg Config) Config {
if ocfg.Checks != nil {
cfg.Checks = mergeLists(cfg.Checks, ocfg.Checks)
}
if ocfg.Initialisms != nil {
cfg.Initialisms = mergeLists(cfg.Initialisms, ocfg.Initialisms)
}
if ocfg.DotImportWhitelist != nil {
cfg.DotImportWhitelist = mergeLists(cfg.DotImportWhitelist, ocfg.DotImportWhitelist)
}
if ocfg.HTTPStatusCodeWhitelist != nil {
cfg.HTTPStatusCodeWhitelist = mergeLists(cfg.HTTPStatusCodeWhitelist, ocfg.HTTPStatusCodeWhitelist)
}
return cfg
}
type Config struct {
// TODO(dh): this implementation makes it impossible for external
// clients to add their own checkers with configuration. At the
// moment, we don't really care about that; we don't encourage
// that people use this package. In the future, we may. The
// obvious solution would be using map[string]interface{}, but
// that's obviously subpar.
Checks []string `toml:"checks"`
Initialisms []string `toml:"initialisms"`
DotImportWhitelist []string `toml:"dot_import_whitelist"`
HTTPStatusCodeWhitelist []string `toml:"http_status_code_whitelist"`
}
func (c Config) String() string {
buf := &bytes.Buffer{}
fmt.Fprintf(buf, "Checks: %#v\n", c.Checks)
fmt.Fprintf(buf, "Initialisms: %#v\n", c.Initialisms)
fmt.Fprintf(buf, "DotImportWhitelist: %#v\n", c.DotImportWhitelist)
fmt.Fprintf(buf, "HTTPStatusCodeWhitelist: %#v", c.HTTPStatusCodeWhitelist)
return buf.String()
}
var DefaultConfig = Config{
Checks: []string{"all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022", "-ST1023"},
Initialisms: []string{
"ACL", "API", "ASCII", "CPU", "CSS", "DNS",
"EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID",
"IP", "JSON", "QPS", "RAM", "RPC", "SLA",
"SMTP", "SQL", "SSH", "TCP", "TLS", "TTL",
"UDP", "UI", "GID", "UID", "UUID", "URI",
"URL", "UTF8", "VM", "XML", "XMPP", "XSRF",
"XSS", "SIP", "RTP", "AMQP", "DB", "TS",
},
DotImportWhitelist: []string{},
HTTPStatusCodeWhitelist: []string{"200", "400", "404", "500"},
}
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))
if os.IsNotExist(err) {
ndir := filepath.Dir(dir)
if ndir == dir {
break
}
dir = ndir
continue
}
if err != nil {
return nil, err
}
var cfg Config
_, err = toml.DecodeReader(f, &cfg)
f.Close()
if err != nil {
return nil, err
}
out = append(out, cfg)
ndir := filepath.Dir(dir)
if ndir == dir {
break
}
dir = ndir
}
out = append(out, DefaultConfig)
if len(out) < 2 {
return out, nil
}
for i := 0; i < len(out)/2; i++ {
out[i], out[len(out)-1-i] = out[len(out)-1-i], out[i]
}
return out, nil
}
func mergeConfigs(confs []Config) Config {
if len(confs) == 0 {
// This shouldn't happen because we always have at least a
// default config.
panic("trying to merge zero configs")
}
if len(confs) == 1 {
return confs[0]
}
conf := confs[0]
for _, oconf := range confs[1:] {
conf = conf.Merge(oconf)
}
return conf
}
func Load(dir string) (Config, error) {
confs, err := parseConfigs(dir)
if err != nil {
return Config{}, err
}
conf := mergeConfigs(confs)
conf.Checks = normalizeList(conf.Checks)
conf.Initialisms = normalizeList(conf.Initialisms)
conf.DotImportWhitelist = normalizeList(conf.DotImportWhitelist)
conf.HTTPStatusCodeWhitelist = normalizeList(conf.HTTPStatusCodeWhitelist)
return conf, nil
}
go-tools-2021.1.2/config/example.conf 0000664 0000000 0000000 00000000765 14143223131 0017253 0 ustar 00root root 0000000 0000000 checks = ["all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022", "-ST1023"]
initialisms = ["ACL", "API", "ASCII", "CPU", "CSS", "DNS",
"EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID",
"IP", "JSON", "QPS", "RAM", "RPC", "SLA",
"SMTP", "SQL", "SSH", "TCP", "TLS", "TTL",
"UDP", "UI", "GID", "UID", "UUID", "URI",
"URL", "UTF8", "VM", "XML", "XMPP", "XSRF",
"XSS", "SIP", "RTP", "AMQP", "DB", "TS"]
dot_import_whitelist = []
http_status_code_whitelist = ["200", "400", "404", "500"]
go-tools-2021.1.2/debug/ 0000775 0000000 0000000 00000000000 14143223131 0014562 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/debug/debug.go 0000664 0000000 0000000 00000000363 14143223131 0016201 0 ustar 00root root 0000000 0000000 package debug
import (
"fmt"
"go/token"
"golang.org/x/tools/go/analysis"
)
type Positioner interface {
Pos() token.Pos
}
func PrintPosition(pass *analysis.Pass, obj Positioner) {
fmt.Println(pass.Fset.PositionFor(obj.Pos(), false))
}
go-tools-2021.1.2/dist/ 0000775 0000000 0000000 00000000000 14143223131 0014437 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/dist/build.sh 0000775 0000000 0000000 00000003004 14143223131 0016072 0 ustar 00root root 0000000 0000000 #!/bin/sh -e
build() {
ROOT="$GOPATH/src/honnef.co/go/tools"
os="$1"
arch="$2"
echo "Building GOOS=$os GOARCH=$arch..."
exe="staticcheck"
if [ $os = "windows" ]; then
exe="${exe}.exe"
fi
target="staticcheck_${os}_${arch}"
arm=""
case "$arch" in
armv5l)
arm=5
arch=arm
;;
armv6l)
arm=6
arch=arm
;;
armv7l)
arm=7
arch=arm
;;
arm64)
arch=arm64
;;
esac
mkdir "$d/staticcheck"
cp "$ROOT/LICENSE" "$ROOT/LICENSE-THIRD-PARTY" "$d/staticcheck"
CGO_ENABLED=0 GOOS=$os GOARCH=$arch GOARM=$arm GO111MODULE=on go build -trimpath -o "$d/staticcheck/$exe" honnef.co/go/tools/cmd/staticcheck
(
cd "$d"
tar -czf "$target.tar.gz" staticcheck
sha256sum "$target.tar.gz" > "$target.tar.gz.sha256"
)
rm -rf "$d/staticcheck"
}
rev="$1"
if [ -z "$rev" ]; then
echo "Usage: $0 "
exit 1
fi
mkdir "$rev"
d=$(realpath "$rev")
wrk=$(mktemp -d)
trap "{ rm -rf \"$wrk\"; }" EXIT
cd "$wrk"
go mod init foo
GO111MODULE=on go get -d honnef.co/go/tools/cmd/staticcheck@"$rev"
SYSTEMS=(windows linux freebsd)
ARCHS=(amd64 386)
for os in ${SYSTEMS[@]}; do
for arch in ${ARCHS[@]}; do
build "$os" "$arch"
done
done
build "darwin" "amd64"
for arch in armv5l armv6l armv7l arm64; do
build "linux" "$arch"
done
(
cd "$d"
sha256sum -c --strict *.sha256
)
go-tools-2021.1.2/doc/ 0000775 0000000 0000000 00000000000 14143223131 0014241 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/doc/2017.2.html 0000664 0000000 0000000 00000013360 14143223131 0015663 0 ustar 00root root 0000000 0000000
Introduction to staticcheck 2017.2
The 2017.2 release of the staticcheck suite of tools focuses on
reducing friction β fewer false positives, more tools for suppressing
unwanted output, and JSON output for easier integration with other
tools.
New features
Linter directives for ignoring problems
In the past, the only ways to ignore reported problems was by using
the -ignore flag. This led to overreaching ignore rules
which weren't maintained regularly. Now, //lint:ignore and
//lint:file-ignore comments can be used to ignore
problems, either on specific lines or file-wide. A full description of
these directives, their syntax and their behavior can be found
in the documentation.
A related change adds the -show-ignored command line
flag, which outputs problems that would otherwise be ignored by
directives. This is primarily of use with the JSON output format,
for custom front ends.
Output formats
All staticcheck tools now support multiple output formats, selectable
with the -f flag.
Currently, two formats are supported. The first format is
text, which is the default and uses the existing terminal
output format. The other is json, which emits JSON. The
output is a stream of objects, allowing for a future streaming output
mode. Each object uses the following example schema:
{
"checker": "staticcheck",
"code": "SA4006",
"location": {
"file": "/usr/lib/go/src/database/sql/sql_test.go",
"line": 2701,
"column": 5
},
"message": "this value of err is never used",
"ignored": false
}
Control over the exit code of megacheck
Megacheck, the tool for running multiple checkers at once, now has
per checker flags for controlling the overall exit code. Previously,
megacheck would exit non-zero if any checker found a problem. Now it
is possible to configure for each checker whether it should cause a
non-zero exit, by using the -<checker>.exit-non-zero
flags. This flag defaults to false for gosimple and to true for
the other checkers.
Changes to checks
Support for NoCopy in unused
The unused tool now understands NoCopy sentinel types. The
NoCopy type, which is canonically a struct with no fields and only a
single, empty Lock method, can be used to mark structs as not safe
for copying. By declaring a field of this type, go vet will complain
when it sees instances of the struct being copied.
In the past, unused marked these fields as unused, now it ignores
them.
Detection of deprecated identifiers
SA1019 now
correctly identifies deprecated methods, in addition to fields and
package-level objects. Additionally, staticcheck now keeps track of
when each identifier in the Go standard library was deprecated, so
that using -go <version> can correctly
ignore deprecation warnings that don't apply to the targeted Go
version.
Other
{{ check "SA4017" }} no
longer reports pure functions that are stubs β functions that
immediately panic or return a constant.
{{ check "SA5007" }} no
longer flags infinite recursion when the function call is spawned
as a new goroutine.
{{ check "SA6002" }}
now recognizes that unsafe.Pointer is
a pointer type.
{{ check "S1005" }}
no longer suggests for range when targeting a version
older than Go 1.4.
{{ check "S1026" }} has been
removed. In some rare instances, copying a string is necessary, and
all common ways of doing this were incorrectly flagged by the check.
Other changes
The -ignore flag now supports ignoring checks in all packages,
by using * as the path.
//line directives are now being ignored when
reporting problems. That is, problems will always be reported for
the actual position in the Go files they occur.
From now on, only the first compilation error encountered will be
reported. The tools expect to be run on valid Go code and there
was little (if any) value in reporting all compilation errors
encountered, especially because simple errors can lead to many
follow-up errors.
Staticcheck 2017.2.1 Release Notes
The 2017.2.1 release of the staticcheck suite of tools is the first
bug fix release, fixing one bug.
Fixed bugs
Staticcheck 2017.2 made the detection of deprecated objects
Go-version aware. Unfortunately, this only worked correctly for
fields and methods, but not package-level objects. This release
fixes that.
Staticcheck 2017.2.2 Release Notes
The 2017.2.2 release of the staticcheck suite of tools is the second
bug fix release, fixing several bugs.
Fixed bugs
unused: correctly apply the NoCopy exemption when using the -exported flag.
keyify: support external test packages (package foo_test)
staticcheck: disable {{ check "SA4005" }} β the check, in its current form, is prone to false positives and will be reimplemented in a future release.
At the core of the 2019.1 release lies the grand restructuring of all of the staticcheck tools.
All of the individual checkers, as well as megacheck, have been merged into a single tool,
which is simply called staticcheck.
From this point forward, staticcheck will be the static analyzer for Go code.
It will cover all of the existing categories of checks β bugs, simplifications, performance β
as well as future categories, such as the new style checks.
This change makes a series of simplifications possible.
Per-tool command line flags in megacheck have been replaced with unified flags
(-checks and -fail)
that operate on arbitrary subsets of checks.
Consumers of the JSON output no longer need to know about different checker names
and can instead rely solely on user-controllable severities.
And not to be neglected: gone is the silly name of megacheck.
This change will require some changes to your pipelines.
Even though the gosimple, unused, and megacheck tools still exist, they have been deprecated
and will be removed in the next release of staticcheck.
Additionally, megacheck's -<tool>.exit-non-zero flags have been rendered inoperable.
Instead, you will have to use the -fail flag.
Furthermore,, -fail defaults to all, meaning all checks will cause non-zero exiting.
Previous versions of megacheck had different defaults for different checkers, trying to guess the user's intention.
Instead of guessing, staticcheck expects you to provide the correct flags.
Since all of the tools have been merged into staticcheck, it will no longer run just one group of checks.
This may lead to additional problems being reported.
To restore the old behavior, you can use the new -checks flag.
-checks "SA*" will run the same set of checks that the old staticcheck tool did.
The same flag should be used in place of megacheck's β now deprecated β -<tool>.enabled flags.
Staticcheck 2019.1 adds support for per-project configuration files.
With these it will be possible to standardize and codify linter settings, the set of enabled checks, and more.
Please see the documentation page on configuration for all the details!
Build system integration
Beginning with this release, staticcheck calls out to the tools of the underlying build system
(go for most people) to determine the list of Go files to process.
This change should not affect most people.
It does, however, have some implications:
the system that staticcheck runs on needs access to a full Go toolchain β
just the source code of the standard library no longer suffices.
Furthermore, setting GOROOT to use a different Go installation no longer works as expected.
Instead, PATH has to be modified so that go resolves to the desired Go command.
This change has been necessary to support Go modules.
Additionally, it will allow us to support alternative build systems such as Bazel in the future.
Handling of broken packages
We have redesigned the way staticcheck handles broken packages.
Previously, if you ran staticcheck ... and any package wouldn't compile,
staticcheck would refuse to check any packages whatsoever.
Now, it will skip broken packages, as well as any of their dependents, and check only the remaining packages.
Any build errors that are encountered will be reported as problems.
Checks
New checks
Staticcheck 2019.1 adds a new category of checks, ST1.
ST1 contains checks for common style violations β poor variable naming, incorrectly formatted comments and the like.
It brings the good parts of golint to staticcheck,
and adds some checks of its own.
In addition, some other checks have been added.
{{ check "S1032" }} recommends replacing sort.Sort(sort.StringSlice(...)) with sort.Strings(...);
similarly for other types that have helpers for sorting.
{{ check "SA9004" }} flags groups of constants where only the first one is given an explicit type.
{{ check "SA1025" }} checks for incorrect uses of (*time.Timer).Reset.
Changed checks
Several checks have been tweaked, either making them more accurate or finding more issues.
{{ check "S1002" }} no longer applies to code in tests.
While if aBool == true is usually an anti-pattern,
it can feel more natural in unit tests,
as it mirrors the if got != want pattern.
{{ check "S1005" }} now flags for x, _ := range because of the unnecessary blank assignment.
{{ check "S1007" }} no longer suggests using raw strings for regular expressions containing backquotes.
{{ check "S1016" }} now considers the targeted Go version.
It will no longer suggest type conversions between struct types with different field tags
unless Go 1.8 or later is being targeted.
{{ check "SA1000" }} now checks arguments passed to the regexp.Match class of functions.
{{ check "SA1014" }} now checks arguments passed to (*encoding/xml.Decoder).DecodeElement.
{{ check "SA6002" }} now realizes that unsafe.Pointer is a pointer.
{{ check "U1000" }} has fewer false positives in the presence of embedding.
Removed checks
{{ check "S1013" }} has been removed,
no longer suggesting replacing if err != nil { return err }; return nil with return err.
This check has been the source of contention and more often than not, it reduced the consistency of the surrounding code.
Deprecation notices
This release deprecates various features of staticcheck.
These features will be removed in the next release.
As already explained earlier, the unused, gosimple, and megacheck tools
have been replaced by staticcheck.
Similarly, the flags -<tool>.enabled and -<tool>.exit-non-zero
have been replaced by -checks and -fail.
Finally, the -ignore flag has been replaced
by linter directives.
Binary releases
Beginning with this release, we're publishing
prebuilt binaries to GitHub.
These releases still require a functioning Go installation in order to operate, however.
Other changes
We've removed the -min_confidence flag.
This flag hasn't been doing anything for years.
A new formatter called Stylish
(usable with -f stylish)
provides output that is designed for easier consumption by humans.
Due to the restructuring of checkers, the checker field in JSON output has been replaced
with the severity field.
Staticcheck 2019.1.1 Release Notes
The 2019.1.1 release of Staticcheck is the first bug fix release, fixing several bugs and improving performance.
Changes
The ST category of checks no longer flag style issues of
aliased types when the aliased type exists in a package
we aren't explicitly checking. This avoids crashes and
erratic error reports.
Compiler errors now have correct position information.
A crash in the Stylish reporter has been fixed.
We no longer flag unused objects that belong to cgo internals.
The {{ check "U1000" }} check has been optimized, reducing its memory
usage and runtime.
Staticcheck 2019.2 brings major performance improvements and a
reduction in memory usage.
Staticcheck has been redesigned to only keep those packages in memory that are actively being processed.
This allows for much larger workspaces to be checked in one go.
While previously it may have been necessary to split a list of packages into many invocations of staticcheck,
this is now handled intelligently and efficiently by staticcheck itself.
In particular, memory usage is now closely tied to parallelism:
having more CPU cores available allows for more packages to be processed in parallel,
which increases the number of packages held in memory at any one time.
Not only does this make good use of available resources β
systems with more CPU cores also tend to have more memory available β
it also exposes a single, easy to use knob for trading execution time for memory use.
By setting GOMAXPROCS to a value lower than the number of available cores,
memory usage of staticcheck will be reduced, at the cost of taking longer to complete.
We've observed reductions in memory usage of 2x to 8x when checking large code bases.
Package
2019.1.1
2019.2ΒΉ
Change
net/http
3.543 s / 677 MB
3.747 s / 254 MB
+5.76% / -62.48%
strconv
1.628 s / 294 MB
1.678 s / 118 MB
+3.07% / -59.86%
image/color
1.304 s / 225 MB
1.702 s / 138 MB
+30.52% / -38.67%
std
26.234 s / 3987 MB
19.444 s / 1054 MB
-25.88% / -73.56%
github.com/cockroachdb/cockroach/pkg/...
88.644 s / 15959 MB
93.798 s / 4156 MB
+5.81% / -73.96%
ΒΉ: The fact cache was empty for all benchmarks.
In addition, staticcheck now employs caching to speed up repeated checking of packages.
In the past, when checking a package, all of its dependencies had to be loaded from source and analyzed.
Now, we can make use of Go's build cache, as well as cache our own analysis facts.
This makes staticcheck behave a lot more like go build, where repeated builds are much faster.
Package
Uncached
Cached
Change
net/http
3.747 s / 254 MB
1.545 s / 195 MB
-58.77% / -23.23%
strconv
1.678 s / 118 MB
0.495 s / 57 MB
-70.5% / -51.69%
image/color
1.702 s / 138 MB
0.329 s / 31 MB
-80.67% / -77.54%
std
19.444 s / 1054 MB
15.099 s / 887 MB
-22.35% / -15.84%
github.com/cockroachdb/cockroach/pkg/...
93.798 s / 4156 MB
47.205 s / 2516 MB
-49.67% / -39.46%
This combination of improvements not only compensates for the
increased memory usage that 2019.1 introduced, it also brings the
memory usage and execution times way below the levels of those seen in the
2017.2 release, which had previously been our most efficient
release.
It should be noted that all of these improvements are part of the staticcheck command itself, not the individual checks.
Tools such as golangci-lint will have to replicate our efforts to benefit from these improvements.
The go/analysis framework
Part of the redesign of staticcheck involved porting our code to the go/analysis framework.
The go/analysis framework is a framework for writing static analysis tools such as staticcheck and go vet.
It provides an API that enables interoperability between different analyses and analysis drivers β drivers being the code that actually executes analyses.
The intention is that any driver can trivially use any analysis that is implemented using go/analysis.
With the exception of {{ check "U1000" }}, all of our checks are now go/analysis analyses. Furthermore, the staticcheck command is now a go/analysis driver.
With our move to this framework, we enable other drivers to reuse our checks without having to patch them.
This should be of particular interest to golangci-lint, which previously took to patching staticcheck, sometimes in subtly incorrect ways.
Another high-profile go/analysis driver is gopls, the Go language server. It will now be much easier for gopls to use staticcheck to analyze code, should it so desire.
Theoretically it would also allow us to use third-party analyses as part of staticcheck.
Due to quality control reasons, however, we will likely refrain from doing so.
Nonetheless it would be trivial for users to maintain internal forks of cmd/staticcheck that use third-party analyses.
Improvements to the CLI
We've made several minor improvements to the command-line interface of staticcheck that improve usability and debuggability.
SIGINFO handler
Upon receiving the SIGINFO signal β or SIGUSR1 on platforms that lack
SIGINFO β staticcheck will dump statistics, such as the current phase
and how many packages are left to analyze.
Using the new -explain flag, a check's documentation can be displayed right in the terminal,
eliminating the need to browse to https://staticcheck.io/docs/checks.
$ staticcheck -explain S1007
Simplify regular expression by using raw string literal
Raw string literals use ` instead of " and do not support
any escape sequences. This means that the backslash (\) can be used
freely, without the need of escaping.
Since regular expressions have their own escape sequences, raw strings
can improve their readability.
Before:
regexp.Compile("\\A(\\w+) profile: total \\d+\\n\\z")
After:
regexp.Compile(`\A(\w+) profile: total \d+\n\z`)
Available since
2017.1
-debug.version
The -debug.version flag causes staticcheck to print
detailed version information, such as the Go version used to compile
it, as well as the versions of all dependencies if built using Go
modules. This feature is intended for debugging issues, and we will
ask for its output from users who file issues.
$ staticcheck -debug.version
staticcheck (devel, v0.0.0-20190602125119-5a4a2f4a438d)
Compiled with Go version: go1.12.5
Main module:
honnef.co/go/tools@v0.0.0-20190602125119-5a4a2f4a438d (sum: h1:U5vSGN1Bjr0Yd/4pRcp8iRUCs3S5TIPzoAeTEFV2aiU=)
Dependencies:
github.com/BurntSushi/toml@v0.3.1 (sum: h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=)
golang.org/x/tools@v0.0.0-20190530171427-2b03ca6e44eb (sum: h1:mnQlcVx8Qq8L70HV0DxUGuiuAtiEHTwF1gYJE/EL9nU=)
Enabling unused's whole program mode
When we merged unused into staticcheck, we lost the ability to specify the -exported flag to report unused exported identifiers.
Staticcheck 2019.2 restores this ability with the new -unused.whole-program flag.
Range information in diagnostics
Many of our checks now emit [start, end] ranges for findings instead of just positions.
These ranges can be accessed via the json output formatter, as well as by using go/analysis.Diagnostic directly, such as in gopls.
Note that not all checks are able to emit range information.
Installing staticcheck as a module
As part of the 2019.2 release, we've turned staticcheck into a Go module.
From now on, if using Go modules, you can install specific versions of staticcheck with go get honnef.co/go/tools/cmd/staticcheck@<version>,
though do note that older releases do not have a go.mod file.
You can still download them as modules, but Go will record indirect dependencies in the main module's go.mod file, and no minimum versions are specified.
Staticcheck will not use Semantic Versioning for its releases.
It is our belief that Semver is a poor fit for applications and is more suited towards libraries.
For example, almost every release of staticcheck has backwards incompatible changes to some APIs that aren't meant for public consumption,
but which we expose nevertheless so that tinkerers can use them.
However, we use so-called pre-release versions of the form v0.0.0-2019.2.
These allow us to embed our versioning scheme in that of Semver, with correct sorting and updating of versions.
Furthermore, these versions ensure that go get ...,
if not specifying an explicit version (that is, if using the query latest),
will install the latest released version of staticcheck and not the master branch.
While you can use these pre-release version numbers directly, you can also use the canonical versions of the form 2019.2 instead.
The Go tool will automatically translate these versions to the appropriate pre-releases.
To install the master branch, use go get honnef.co/go/tools/cmd/staticcheck@master
Removal of deprecated functionality
Staticcheck 2019.1 deprecated the unused, gosimple, and megacheck
utilities, as they have been merged into staticcheck. Furthermore, it deprecated the -ignore flag,
which has been replaced by linter directives.
This release no longer includes these deprecated utilities, nor does
it provide the deprecated flag.
Checks
New checks
Numerous new checks have been added in this release:
{{ check "S1033" }} flags unnecessary guards around calls to delete.
{{ check "S1034" }} simplifies type switches involving redundant type assertions.
{{ check "SA1026" }} flags attempts at marshaling invalid types.
{{ check "SA4020" }} flags unreachable case clauses in type switches.
{{ check "SA4021" }} flags calls to append with a single argument, as x = append(y) is equivalent to x = y.
{{ check "SA5008" }} flags certain kinds of invalid struct tags.
{{ check "SA5009" }} verifies the correctness of Printf calls.
{{ check "SA6005" }} flags inefficient string comparisons involving strings.ToLower
or strings.ToUpper when they can be replaced with strings.EqualFold.
{{ check "SA9005" }} flags attempts at marshaling structs with no public fields nor custom marshaling.
{{ check "ST1017" }} flags so-called yoda conditions,
which take the form of if 42 == x.
{{ check "SA1019" }} now flags imports of deprecated packages.
{{ check "SA4000" }} no longer flags comparisons between custom float types. Additionally, it avoids a false positive caused by cgo.
{{ check "SA4006" }} no longer flags unused values in code generated by goyacc. This avoids noise caused by the nature of the generated state machine.
{{ check "ST1005" }} no longer flags error messages that start with capitalized type names.
{{ check "ST1006" }} no longer flags receiver names in generated code.
{{ check "SA5002" }} no longer suggests replacing for false { with for {.
Added "SIP" and "RTP" as default initialisms to {{ check "ST1003" }}.
{{ check "SA1006" }}, {{ check "SA4003" }}, {{ check "S1017" }}, and {{ check "S1020" }} match more code patterns.
{{ check "S1021" }} is less eager to merge declarations and assignments when multiple assignments are involved.
{{ check "U1000" }} has been rewritten, eliminating a variety of false positives.
Sustainable open source and a personal plea
Staticcheck is an open source project developed primarily by me, Dominik Honnef, in my free time.
While this model of software development has gotten increasingly common, it is not very sustainable.
Time has to be split between open source work and paid work to sustain one's life.
This is made especially unfortunate by the fact that hundreds of companies rely on open source each day,
but few consider giving back to it, even though it would directly benefit their businesses,
ensuring that the software they rely on keeps being developed.
I have long been soliciting donations for staticcheck on Patreon to make its development more sustainable.
A fair number of individuals have generously pledged their support and I am very grateful to them.
Unfortunately, only few companies support staticcheck's development, and I'd like for that to change.
To people who are familiar with Patreon, it might've always seemed like an odd choice for a software project.
Patreon focuses on art and creative work, and on individuals supporting said work, not companies.
I am therefore excited to announce my participation in GitHub Sponsors,
a new way of supporting developers, directly on GitHub.
GitHub Sponsors allows you to easily support developers by sponsoring them on a monthly basis, via a few simple clicks.
It is fully integrated with the platform and can use your existing billing information, making it an effortless process.
To encourage more company sponsorships I offer to display your company's logo prominently on
staticcheck's website
for
$250 USD a month,
to show my appreciation for your contribution and to show to the world how much you care about code quality.
Please don't hesitate contacting me directly if neither GitHub Sponsors nor Patreon seem suitable to you but you'd like to support me nevertheless.
I am sure we can work something out.
Staticcheck 2019.2.1 release notes
The 2019.2 release has an unfortunate bug that prevents staticcheck from running on 32-bit architectures, causing it to crash unconditionally.
This release fixes that crash.
Staticcheck 2019.2.2 release notes
Staticcheck 2019.2.2 contains the following user-visible fixes:
{{ check "S1008" }} now skips if/else statements where both branches return the same value.
{{ check "SA4006" }} now considers a value read when a switch statement reads it, even if the switch statement has no branches.
2019.2 introduced a bug that made it impossible to enable non-default checks via configuration files. This is now possible again.
2019.2 introduced a bug that made the -tags command line argument ineffective, making it impossible to pass in build tags. This is now possible again.
From this release onward, we will use pseudo versions of the form
v0.0.1-<year>.<minor> instead of v0.0.0-<year>.<minor>.
This fixes an issue where go get would prefer an older commit over a newer released version due to the way versions sort.
Staticcheck 2019.2.3 release notes
Staticcheck 2019.2.3 is a re-release of 2019.2.2.
Its pre-built binaries, which can be found on GitHub,
have been built with Go 1.13, to enable checking of code that uses language features introduced in Go 1.13.
Staticcheck 2020.1 introduces UI improvements, speed enhancements, and
a number of new as well as improved checks. Additionally, it is the
first release to support the upcoming Go 1.14.
UI improvements
We've improved the output of the staticcheck command as well as
Staticcheck's integration with gopls to make it easier to understand
the problems that are being reported.
Related information describes the source of a problem, or why
Staticcheck believes that there is a problem. Take the following
piece of code for example:
func fn(x *int) {
if x == nil {
log.Println("x is nil, returning")
}
// lots of code here
log.Println(*x)
}
Staticcheck 2020.1 will produce the following output:
foo.go:6:14: possible nil pointer dereference (SA5011)
foo.go:2:5: this check suggests that the pointer can be nil
The actual problem that is being reported is the "possible nil pointer
dereference". Staticcheck also explains why it believes that x might
be nil, namely the comparison on line 2.
When using the text or stylish formatters, related information will
appear as indented lines. The json formatter adds a new field
related to problems, containing position information as well as the
message. Editors that use gopls will also display the related
information.
Related information should make it easier to understand why Staticcheck
is flagging code, and how to fix problems.
Integration with gopls has seen some other improvements as wellΒΉ. We
now emit better position information that more accurately reflects the
true source of a problem. The most obvious example is that a missing
package comment will no longer underline the entire file. Similarly,
invalid function arguments will be highlighted individually, instead
of highlighting the call as a whole. Finally, some problems can now be
automatically fixed by using quick fixes.
ΒΉ: due to the nature of Staticcheck's integration with gopls, gopls
will need to update their dependency on Staticcheck before benefiting
from these changes.
Better caching
The 2019.2 release introduced caching to Staticcheck, greatly speeding
up repeated runs. However, the caching only applied to dependencies;
the packages under analysis still had to be analyzed anew on every
invocation to compute the list of problems. Staticcheck 2020.1
introduces caching of problems found, so that repeat runs for
unchanged packages are virtually instantaneous.
Checks
New checks
Numerous new checks have been added in this release:
{{ check "S1035" }} flags redundant calls to net/http.CanonicalHeaderKey.
{{ check "S1036" }} flags unnecessary guards around map accesses.
{{ check "S1037" }} flags unnecessarily elaborate ways of sleeping.
{{ check "S1038" }} flags unnecessary uses of fmt.Sprintf, such as fmt.Println(fmt.Sprintf(...)).
{{ check "S1039" }} flags uses of fmt.Sprint with single string literals.
{{ check "SA1028" }} flags uses of sort.Slice on non-slices.
{{ check "SA1029" }} flags inappropriate keys in calls to context.WithValue.
{{ check "SA4022" }} flags comparisons of the kind if &x == nil.
{{ check "SA5010" }} flags impossible type assertions.
{{ check "ST1020" }} checks the documentation of exported functions.
{{ check "ST1021" }} checks the documentation of exported types.
{{ check "ST1022" }} checks the documentation of exported variables and constants.
{{ check "ST1020" }}, {{ check "ST1021" }} and {{ check "ST1022" }} are not enabled by default.
Changed checks
Several checks have been improved:
{{ check "S1036" }} detects more kinds of unnecessary guards around map accesses.
{{ check "S1008" }} reports more easily understood diagnostics.
{{ check "S1025" }} no longer suggests using v.String() instead of fmt.Sprintf("%s", v) when v is a reflect.Value. fmt gives special treatment to reflect.Value and the two results differ.
{{ check "SA1015" }} no longer flags uses of time.Tick in packages that implement Cobra commands.
{{ check "SA1019" }} no longer misses references to deprecated packages when said packages have been vendored.
{{ check "SA4000" }} no longer flags comparisons of the kind x == x and x != x when `x` has a compound type involving floats.
{{ check "SA4003" }} no longer flags comparisons of the kind x <= 0 when x is an unsigned integer. While it is true that x <= 0 can be written more specifically as x == 0, this is not a helpful suggestion in reality. A lot of people use x <= 0 as a defensive measure, in case x ever becomes signed. Also, unlike all the other warnings made in the check, x <= 0 is neither a tautology nor a contradiction, it is merely less precise than it could be.
{{ check "SA4016" }} now detects silly bitwise ops of the form x & k where k is defined as const k = iota.
{{ check "SA4018" }} no longer flags self-assignments involving side effects; for example, it won't flag x[fn()] = x[fn()] if fn isn't pure.
{{ check "SA5008" }} now permits duplicate instances of various struct tags used by github.com/jessevdk/go-flags.
{{ check "SA5009" }} now correctly recognizes that unsafe.Pointer is a pointer type that can be used with verbs such as %p. Furthermore, it validates calls to golang.org/x/xerrors.Errorf.
{{ check "SA5009" }} now understands fmt.Printf verbs that were changed and added in Go 1.13. Specifically, it now recognizes the new %O verb, and allows the use of %x and %X on floats and complex numbers.
{{ check "ST1003" }} has learned about several new initialisms.
{{ check "ST1011" }} no longer misses variable declarations with inferred types.
{{ check "ST1016" }} now ignores the names of method receivers of methods declared in generated files.
{{ check "ST1020" }}, {{ check "ST1021" }}, and {{ check "ST1022" }} no longer enforce comment style in generated code.
General bug fixes
The following bugs were fixed:
A race condition in the {{ check "U1000" }} check could occasionally lead to sporadic false positives.
Some files generated by goyacc weren't recognized as being generated.
staticcheck no longer fails to check packages that consist exclusively of tests.
Staticcheck 2020.1.1 release notes
The 2020.1 release neglected to update the version string stored in
the binary, causing staticcheck -version to incorrectly emit (no version).
Staticcheck 2020.1.2 release notes
The 2020.1.1 release incorrectly identified itself as version 2020.1.
Staticcheck 2020.1.3 release notes
This release fixes two bugs involving //lint:ignore directives:
When ignoring U1000 and checking a package that contains tests,
Staticcheck would incorrectly complain that the linter directive
didn't match any problems, even when it did.
On repeated runs, the position information for a this linter directive didn't match anything report
would either be missing, or be wildly incorrect.
Staticcheck 2020.1.4 release notes
This release adds special handling for imports of the
deprecated github.com/golang/protobuf/proto package.
github.com/golang/protobuf
has deprecated the proto package, but
their protoc-gen-go still imports the package and uses
one of its constants, to enforce a weak dependency on a
sufficiently new version of the legacy package.
Staticcheck would flag the import of this deprecated package in all
code generated by protoc-gen-go. Instead of forcing the project to
change their project structure, we choose to ignore such imports in
code generated by protoc-gen-go. The import still gets flagged in code
not generated by protoc-gen-go.
You can find more information about this in the upstream issue.
Furthermore, Staticcheck 2020.2 will skip very large packages (currently packages that are 50 MiB or larger),
under the assumption that these packages contain bundled assets and aren't worth analyzing.
This might further reduce Staticcheck's memory usage in your projects.
Changes to the detection of unused code
Removal of whole-program mode and changes to the handling of exported identifiers
The most visible change is the removal of the whole program mode.
This mode, which analyzed an entire program and reported unused code even if it is exported,
did not work well with the kind of caching that we use in Staticcheck.
Even in previous versions, it didn't always work correctly and may have caused flaky results,
depending on the state of the cache and the order of staticcheck invocations.
The whole-program mode may be revived in the future as a standalone tool,
with the understanding that this mode of operation is inherently more expensive than staticcheck.
In the meantime, if you depend on this functionality and can tolerate its bugs, you should continue using Staticcheck 2020.1.
As part of improving the correctness of U1000, changes were made to the normal mode as well.
In particular, all exported package-level identifiers will be considered used from now on,
even if these identifiers are declared in package main or tests, even if they are otherwise unused.
Exported identifiers in package main can be used in ways invisible to us, for example via the plugin build mode.
For tests, we would run into the same kind of issues as we did with the whole program mode.
Improvements
The //lint:ignore directive now works more intelligently with the U1000 check.
In previous versions, the directive would only suppress the output of a diagnostic. For example, for the following example
package pkg
//lint:ignore U1000 This is fine.
func fn1() { fn2() }
func fn2() {}
Staticcheck would emit the following output:
foo.go:6:6: func fn2 is unused (U1000)
as it would only suppress the diagnostic for fn1.
Beginning with this release, the directive instead actively marks the identifier as used,
which means that any transitively used code will also be considered used, and no diagnostic will be reported for fn2.
Similarly, the //lint:file-ignore directive will consider everything in a file used, which may transitively mark code in other files used, too.
UI improvements
We've made some minor improvements to the output and behavior of the staticcheck command:
the command now prints instructions on how to look up documentation for checks
output of the -explain flag includes a link to the online documentation
a warning is emitted when a package pattern matches no packages
unmatched ignore directives cause staticcheck to exit with a non-zero status code
Changes to versioning scheme
Staticcheck releases have two version numbers: one meant for human consumption and one meant for consumption by machines, via Go modules.
For example, the previous release was both 2020.1.6 (for humans) and v0.0.1-2020.1.6 (for machines).
In previous releases, we've tried to include the human version in the machine version, by using the v0.0.1-<human version> scheme.
However, this scheme had various drawbacks.
For this and future releases we've switched to a more standard scheme for machine versions: v0.<minor>.<patch>.
Minor will increase by one for every feature release of Staticcheck,
and patch will increase by one for every bugfix release of Staticcheck,
resetting to zero on feature releases.
For example, this release is both 2020.2 and v0.1.0.
A hypothetical 2020.2.1 would be v0.1.1, and 2021.1 will be v0.2.0.
This new versioning scheme fixes various issues when trying to use Staticcheck as a Go module.
It will also allow us to make true pre-releases in the future.
Documentation on the website, as well as the output of staticcheck -version, will include both version numbers, to make it easier to associate the two.
For detailed information on how we arrived at this decision, see the discussion on issue 777.
Checks
New checks
The following new checks have been added:
{{ check "SA4023" }} flags impossible comparisons of interface values with untyped nils
{{ check "SA5012" }} flags function calls with slice arguments that aren't the right length
{{ check "SA9006" }} flags dubious bit shifts of fixed size integers
Changed checks
Several checks have been improved:
{{ check "S1030" }} no longer recommends replacing m[string(buf.Bytes())] with m[buf.String()], as the former gets optimized by the compiler
{{ check "S1008" }} no longer incorrectly suggests that the negation of >= is <=
{{ check "S1029" }} and {{ check "SA6003" }} now also check custom types with underlying type string
{{ check "SA1019" }} now recognizes deprecation notices that aren't in the last paragraph of a comment
{{ check "SA1019" }} now emits more precise diagnostics for deprecated code in the standard library
{{ check "SA4006" }} no longer flags assignments where the value is a typed nil
{{ check "SA5011" }} is now able to detect more functions that never return, thus reducing the number of false positives
{{ check "SA9004" }} no longer assumes that constants belong to the same group when they have different types
Automatic fixes for {{ check "SA9004" }} inside gopls no longer incorrectly duplicate comments
{{ check "ST1003" }} no longer complains about ALL_CAPS in variable names that don't contain any letters
Incorrect position information in various checks have been fixed
Crashes in various checks have been fixed
Staticcheck 2020.2.1 release notes
This release eliminates some false negatives as well as false positives, makes the staticcheck command less noisy and fixes a potential security issue.
{{ check "SA4020" }} no longer claims that case nil is an unreachable case in a type switch.
{{ check "S1025" }} no longer marks uses of Printf as unnecessary when the printed types implement the fmt.Formatter interface.
Various checks may now detect bugs in conditional code that were previously missed. This was a regression introduced in Staticcheck 2020.1.
The staticcheck command no longer reminds the user of the -explain flag every time problems are found. This was deemed too noisy.
We've updated our dependency on golang.org/x/tools to guard against arbitrary code execution on Windows.
Note that to be fully safe, you will also have to update your installation of Go.
See the Command PATH security in Go article by the Go authors for more information on this potential vulnerability.
Staticcheck 2020.2.2 release notes
This release fixes a rare crash in Staticcheck, reduces the number of false positives, and adds support for Go 1.16's io/fs.FileMode type.
{{ check "SA9002" }} now supports the new io/fs.FileMode type in addition to os.FileMode.
Various checks no longer crash when analyzing nested function calls involving multiple return values.
{{ check "SA1006" }} no longer flags Printf calls of the form Printf(fn()) when fn has multiple return values.
Staticcheck now understands the effects of github.com/golang/glog on a function's control flow.
Staticcheck 2020.2.3 release notes
This release fixes a false positive in U1000. See issue 942 for an example.
Staticcheck 2020.2.4 release notes
This release fixes the following issues:
A false positive in {{ check "S1017" }} when the len function has been shadowed
A bug in Staticcheck's intermediate representation
that would lead to nonsensical reports claiming that a value isn't being used
when it is definitely being used
(see issues 949 and
981 for more information)
The new -list-checks flag lists all available checks, showing each check's identifier and one-line description.
You can use the existing -explain flag to find out more about each individual check.
Targeted Go version
Some checks in Staticcheck adjust their behavior based on the targeted Go version. For example, the suggestion to use for range instead of for _ = range does not apply to Go 1.3 and earlier.
In the past, the default Go version that was targeted was the version that Staticcheck had been compiled with. For most users, this meant that it targeted the latest Go release.
Going forward, we will default to the Go version declared in go.mod via the go directive.
Even though this value does not exactly correspond to the module's minimum supported Go version, it is still a better apprximation than whatever Go version Staticcheck has been compiled with,
and should work fine for most users.
As before, the targeted Go version can be explicitly set by using the -go flag.
Checks
New checks
The following new checks have been added:
{{ check "S1040" }} flags type assertions from an interface type to itself
{{ check "SA1030" }} flags invalid arguments to various functions in the strconv package
{{ check "SA4005" }} flags assignments to fields on value receivers that intended the receiver to be a pointer instead
{{ check "SA4024" }} flags pointless comparisons of the values of len and cap with zero
{{ check "SA4025" }} flags suspicious integer division that results in zero, such as 2 / 3
{{ check "SA4026" }} flags constant expressions that try to express negative zero
{{ check "SA4027" }} flags no-op attempts at modifying a (*net/url.URL)'s query string
{{ check "ST1023" }} flags variable declarations of the form var x T = v where the type T is redundant; this check is disabled by default
Changed checks
The following checks have been improved:
{{check "S1025" }} now recommends converting byte slices to strings instead of using fmt.Sprintf
{{check "S1008" }} includes fewer unnecessary parentheses and double negations in its suggested fixes
{{check "S1017" }} is now able to flag calls that use string literals and integer literals
{{check "SA9005" }} now includes the value's type in its output
{{check "ST1000" }}, {{check "ST1020" }}, {{check "ST1021" }}, and {{check "ST1022" }} no longer flag effectively empty comments, including those that consist entirely of directives
Restructured documentation
The documentation on the website has been restructured and hopefully made more approachable.
Instead of being one long document, it is now split into multiple smaller articles.
In the future, more articles that look at specific aspects of Staticcheck will be added.
Better integration with gopls
Several behind the scenes changes prepare this release for better integration with gopls.
This will include more accurate severities for diagnostics as well as numerous new refactorings.
These improvements will be part of a future gopls release.
Deletion of rdeps
The rdeps tool has been deleted.
This was a GOPATH-centric tool that allowed finding all reverse dependencies of a Go package.
Both the move to Go modules as well as the emergence of much better tooling for inspecting dependencies (such as goda) has made rdeps redundant.
Staticcheck 2021.1.1 release notes
This release adds support for new language features in Go 1.17,
namely conversions from slices to array pointers,
the unsafe.Add function,
and the unsafe.Slice function.
Additionally, it fixes the following false positives:
{{ check "ST1000" }} no longer flags package docs that start with whitespace if they're otherwise well-formed.
{{ check "SA5002" }} no longer prints one too many percent signs in its message.
{{ check "SA4000" }} no longer flags comparisons between floats.
{{ check "SA4010" }} no longer flags appends to slices that might share their backing array with other slices.
{{ check "SA5011" }} no longer infers possible nil pointer dereferences from comparisons done outside of control flow constructs.
This avoids false positives when using assert-style functions.
See issue 1022 for a concrete example.
{{ check "S1020" }} no longer flags nested if statements when the inner statement has an else branch.
{{ check "SA5011" }} no longer claims that indexing a nil slice will cause a nil pointer dereference.
go-tools-2021.1.2/doc/articles/ 0000775 0000000 0000000 00000000000 14143223131 0016047 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/doc/articles/customizing_staticcheck.html 0000664 0000000 0000000 00000000313 14143223131 0023652 0 ustar 00root root 0000000 0000000 - how to customize staticcheck
- tools serve humans
- tools should assist workflows
- don't let tools bully you
- exit status
- which checks run
- ignoring findings
- output format
- go version
- tests
go-tools-2021.1.2/doc/configuration.html 0000664 0000000 0000000 00000023146 14143223131 0020004 0 ustar 00root root 0000000 0000000
Configuring Staticcheck
Staticcheck tries to provide a good out-of-the-box experience, but it also offers a number of options to fine-tune it to your specific needs.
Command-line flags
Staticcheck uses command-line flags for settings that are specific to single invocations and that may change depending on the context (local development, continuous integration etc).
Targeting Go versions
Some of Staticcheck's analyses adjust their behavior based on the targeted Go version.
For example, the suggestion that one use for range xs instead of for _ = range xs only applies to Go 1.4 and later, as it won't compile with versions of Go older than that.
By default, Staticcheck targets the Go version that it was compiled with.
If your project needs to support older versions of Go, you can use the -go flag to overwrite this, for example by saying staticcheck -go 1.14.
Excluding tests
By default, Staticcheck analyses packages as well as their tests.
By passing -tests=false, one can skip the analysis of tests.
This is primarily useful for the {{ check "U1000" }} check, as it allows finding code that is only used by tests and would otherwise be unused.
Specifying build tags
Much like go, staticcheck accepts the -tags flag to specify the active build tags.
Choosing the output format
Staticcheck can format its output in a number of ways, by using the -f flag.
See this list of formatters for a list of all formatters.
Configuration files
Staticcheck uses configuration files for settings that apply to all users of Staticcheck on a given project.
Configuration files can choose which checks to run as well as tweak the behavior of individual checks.
Configuration files are named staticcheck.conf and apply to subtrees of packages. Consider the following tree of Go packages and configuration files:
Config 1 will apply to all packages, config 2 will apply to ./net/... and config 3 will apply to ./net/http/....
When multiple configuration files apply to a package (for example, all three configs will apply to ./net/http) they will be merged, with settings in files deeper in the package tree overriding rules higher up the tree.
Configuration format
Staticcheck configuration files are named staticcheck.conf and contain TOML.
Any set option will override the same option from further up the package tree,
whereas unset options will inherit their values.
Additionally, the special value "inherit" can be used to inherit values.
This is especially useful for array values, as it allows adding and removing values to the inherited option.
For example, the option checks = ["inherit", "ST1000"] will inherit the enabled checks and additionally enable ST1000.
The special value "all" matches all possible values.
Currently, this is only used when enabling checks.
Values prefixed with a minus sign, such as "-S1000" will exclude values from a list.
This can be used in combination with "all" to express "all but",
or in combination with "inherit" to remove values from the inherited option.
Configuration options
A list of all options and their explanations can be found on the Options page.
Example configuration
The following example configuration is the textual representation of Staticcheck's default configuration.
In general, you shouldn't have to ignore problems reported by Staticcheck.
Great care is taken to minimize the number of false positives and subjective suggestions.
Dubious code should be rewritten and genuine false positives should be reported so that they can be fixed.
The reality of things, however, is that not all corner cases can be taken into consideration.
Sometimes code just has to look weird enough to confuse tools,
and sometimes suggestions, though well-meant, just aren't applicable.
For those rare cases, there are several ways of ignoring unwanted problems.
Line-based linter directives
The most fine-grained way of ignoring reported problems is to annotate the offending lines of code with linter directives.
The //lint:ignore Check1[,Check2,...,CheckN] reason directive
ignores one or more checks on the following line of code.
The reason is a required field
that must describe why the checks should be ignored for that line of code.
This field acts as documentation for other people (including future you) reading the code.
Let's consider the following example,
which intentionally checks that the results of two identical function calls are not equal:
{{ check "SA4000" }} will flag this code,
pointing out that the left and right side of == are identical β
usually indicative of a typo and a bug.
To silence this problem, we can use a linter directive:
func TestNewEqual(t *testing.T) {
//lint:ignore SA4000 we want to make sure that no two results of errors.New are ever the same
if errors.New("abc") == errors.New("abc") {
t.Errorf(`New("abc") == New("abc")`)
}
}
Maintenance of linter directives
It is crucial to update or remove outdated linter directives when code has been changed.
Staticcheck helps you with this by making unnecessary directives a problem of its own.
For example, for this (admittedly contrived) snippet of code
//lint:ignore SA1000 we love invalid regular expressions!
regexp.Compile(".+")
Staticcheck will report the following:
tmp.go:1:2: this linter directive didn't match anything; should it be removed?
Checks that have been disabled via configuration files will not cause directives to be considered unnecessary.
File-based linter directives
In some cases, you may want to disable checks for an entire file.
For example, code generation may leave behind a lot of unused code,
as it simplifies the generation process.
Instead of manually annotating every instance of unused code,
the code generator can inject a single, file-wide ignore directive to ignore the problem.
File-based linter directives look a lot like line-based ones:
//lint:file-ignore U1000 Ignore all unused code, it's generated
The only difference is that these comments aren't associated with any specific line of code.
Conventionally, these comments should be placed near the top of the file.
Unlike line-based directives, file-based ones will not be flagged for being unnecessary.
Text is the default output formatter.
It formats problems using the following format: file:line:col: message.
This format is commonly used by compilers and linters,
and is understood by most editors.
Example output
go/src/fmt/print.go:1069:15: this value of afterIndex is never used (SA4006)
Stylish
Stylish is a formatter designed for human consumption.
It groups results by file name
and breaks up the various pieces of information into columns.
Additionally, it displays a final summary.
This output format is not suited for automatic consumption by tools
and may change between versions.
go/src/fmt/fmt_test.go
(43, 2) S1021 should merge variable declaration with assignment on next line
(1185, 10) SA9003 empty branch
go/src/fmt/print.go
(77, 18) ST1006 methods on the same type should have the same receiver name (seen 3x "b", 1x "bp")
(1069, 15) SA4006 this value of afterIndex is never used
go/src/fmt/scan.go
(465, 5) ST1012 error var complexError should have name of the form errFoo
(466, 5) ST1012 error var boolError should have name of the form errFoo
β 6 problems (6 errors, 0 warnings)
JSON
The JSON formatter emits one JSON object per problem found β
that is, it is a stream of objects, not an array.
Most fields should be self-explanatory.
The severity field may be one of
"error", "warning" or "ignored".
Whether a problem is an error or a warning is determined by the -fail flag.
The value "ignored" is used for problems that were ignored,
if the -show-ignored flag was provided.
Example output
Note that actual output is not formatted nicely.
The example has been formatted to improve readability.
{
"code": "SA4006",
"severity": "error",
"location": {
"file": "/usr/lib/go/src/fmt/print.go",
"line": 1082,
"column": 15
},
"end": {
"file": "/usr/lib/go/src/fmt/print.go",
"line": 1082,
"column": 25
},
"message": "this value of afterIndex is never used"
}
Staticcheck is a state of the art linter for the Go programming language.
Using static analysis, it finds bugs and performance issues, offers simplifications, and enforces style rules.
Beginning with Go 1.16, the simplest way of installing Staticcheck is by running go install honnef.co/go/tools/cmd/staticcheck@latest.
This will install the latest version of Staticcheck to $GOPATH/bin. To find out where $GOPATH is, run go env GOPATH.
Instead of @latest, you can also use a specific version, such as @2020.2.1.
We publish binary releases for the most common operating systems and CPU architectures.
These can be downloaded from GitHub.
Distribution packages
Many package managers include Staticcheck, allowing you to install it with your usual commands, such as apt install.
Note, however, that you might not always get the latest version in a timely manner.
What follows is a non-exhaustive list of the package names in various package repositories.
This option sets which checks should be enabled.
By default, most checks will be enabled, except for those that are too opinionated or that only apply to packages in certain domains.
All supported checks can be enabled with "all".
Subsets of checks can be enabled via prefixes and the * glob; for example, "S*", "SA*" and "SA1*" will
enable all checks in the S, SA and SA1 subgroups respectively.
Individual checks can be enabled by their full IDs.
To disable checks, prefix them with a minus sign. This works on all of the previously mentioned values.
By default, ST1001 forbids
all uses of dot imports in non-test packages. This
setting allows setting a whitelist of import paths that can
be dot-imported anywhere.
Default value: []
http_status_code_whitelist
ST1013 recommends using constants from the net/http package
instead of hard-coding numeric HTTP status codes. This
setting specifies a list of numeric status codes that this
check does not complain about.
The staticcheck command works much like go build or go vet do.
It supports all of the same package patterns.
For example, staticcheck . will check the current package, and staticcheck ./... will check all packages.
For more details on specifying packages to check, see go help packages
Explaining checks
You can use staticcheck -explain <check> to get a helpful description of a check.
Every diagnostic that staticcheck reports is annotated with the identifier of the specific check that found the issue.
For example, in
foo.go:1248:4: unnecessary use of fmt.Sprintf (S1039)
the check's identifier is S1039.
Running staticcheck -explain S1039 will output the following:
Unnecessary use of fmt.Sprint
Calling fmt.Sprint with a single string argument is unnecessary and identical to using the string directly.
Available since
2020.1
Online documentation
https://staticcheck.io/docs/checks#S1039
The output includes a one-line summary,
one or more paragraphs of helpful text,
the first version of Staticcheck that the check appeared in,
and a link to online documentation, which contains the same information as the output of staticcheck -explain.
go-tools-2021.1.2/go.mod 0000664 0000000 0000000 00000000156 14143223131 0014604 0 ustar 00root root 0000000 0000000 module honnef.co/go/tools
go 1.14
require (
github.com/BurntSushi/toml v0.3.1
golang.org/x/tools v0.1.0
)
go-tools-2021.1.2/go.sum 0000664 0000000 0000000 00000005326 14143223131 0014635 0 ustar 00root root 0000000 0000000 github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
go-tools-2021.1.2/go/ 0000775 0000000 0000000 00000000000 14143223131 0014101 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/go/ast/ 0000775 0000000 0000000 00000000000 14143223131 0014670 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/go/ast/astutil/ 0000775 0000000 0000000 00000000000 14143223131 0016355 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/go/ast/astutil/upstream.go 0000664 0000000 0000000 00000000656 14143223131 0020553 0 ustar 00root root 0000000 0000000 package astutil
import (
"go/ast"
"go/token"
_ "unsafe"
"golang.org/x/tools/go/ast/astutil"
)
type Cursor = astutil.Cursor
type ApplyFunc = astutil.ApplyFunc
func Apply(root ast.Node, pre, post ApplyFunc) (result ast.Node) {
return astutil.Apply(root, pre, post)
}
func PathEnclosingInterval(root *ast.File, start, end token.Pos) (path []ast.Node, exact bool) {
return astutil.PathEnclosingInterval(root, start, end)
}
go-tools-2021.1.2/go/ast/astutil/util.go 0000664 0000000 0000000 00000015325 14143223131 0017667 0 ustar 00root root 0000000 0000000 package astutil
import (
"fmt"
"go/ast"
"go/token"
"reflect"
"strings"
)
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 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
}
// Unparen returns e with any enclosing parentheses stripped.
func Unparen(e ast.Expr) ast.Expr {
for {
p, ok := e.(*ast.ParenExpr)
if !ok {
return e
}
e = p.X
}
}
func CopyExpr(node ast.Expr) ast.Expr {
switch node := node.(type) {
case *ast.BasicLit:
cp := *node
return &cp
case *ast.BinaryExpr:
cp := *node
cp.X = CopyExpr(cp.X)
cp.Y = CopyExpr(cp.Y)
return &cp
case *ast.CallExpr:
cp := *node
cp.Fun = CopyExpr(cp.Fun)
cp.Args = make([]ast.Expr, len(node.Args))
for i, v := range node.Args {
cp.Args[i] = CopyExpr(v)
}
return &cp
case *ast.CompositeLit:
cp := *node
cp.Type = CopyExpr(cp.Type)
cp.Elts = make([]ast.Expr, len(node.Elts))
for i, v := range node.Elts {
cp.Elts[i] = CopyExpr(v)
}
return &cp
case *ast.Ident:
cp := *node
return &cp
case *ast.IndexExpr:
cp := *node
cp.X = CopyExpr(cp.X)
cp.Index = CopyExpr(cp.Index)
return &cp
case *ast.KeyValueExpr:
cp := *node
cp.Key = CopyExpr(cp.Key)
cp.Value = CopyExpr(cp.Value)
return &cp
case *ast.ParenExpr:
cp := *node
cp.X = CopyExpr(cp.X)
return &cp
case *ast.SelectorExpr:
cp := *node
cp.X = CopyExpr(cp.X)
cp.Sel = CopyExpr(cp.Sel).(*ast.Ident)
return &cp
case *ast.SliceExpr:
cp := *node
cp.X = CopyExpr(cp.X)
cp.Low = CopyExpr(cp.Low)
cp.High = CopyExpr(cp.High)
cp.Max = CopyExpr(cp.Max)
return &cp
case *ast.StarExpr:
cp := *node
cp.X = CopyExpr(cp.X)
return &cp
case *ast.TypeAssertExpr:
cp := *node
cp.X = CopyExpr(cp.X)
cp.Type = CopyExpr(cp.Type)
return &cp
case *ast.UnaryExpr:
cp := *node
cp.X = CopyExpr(cp.X)
return &cp
case *ast.MapType:
cp := *node
cp.Key = CopyExpr(cp.Key)
cp.Value = CopyExpr(cp.Value)
return &cp
case *ast.ArrayType:
cp := *node
cp.Len = CopyExpr(cp.Len)
cp.Elt = CopyExpr(cp.Elt)
return &cp
case *ast.Ellipsis:
cp := *node
cp.Elt = CopyExpr(cp.Elt)
return &cp
case *ast.InterfaceType:
cp := *node
return &cp
case *ast.StructType:
cp := *node
return &cp
case *ast.FuncLit:
// TODO(dh): implement copying of function literals.
return nil
case *ast.ChanType:
cp := *node
cp.Value = CopyExpr(cp.Value)
return &cp
case nil:
return nil
default:
panic(fmt.Sprintf("unreachable: %T", node))
}
}
func Equal(a, b ast.Node) bool {
if a == b {
return true
}
if a == nil || b == nil {
return false
}
if reflect.TypeOf(a) != reflect.TypeOf(b) {
return false
}
switch a := a.(type) {
case *ast.BasicLit:
b := b.(*ast.BasicLit)
return a.Kind == b.Kind && a.Value == b.Value
case *ast.BinaryExpr:
b := b.(*ast.BinaryExpr)
return Equal(a.X, b.X) && a.Op == b.Op && Equal(a.Y, b.Y)
case *ast.CallExpr:
b := b.(*ast.CallExpr)
if len(a.Args) != len(b.Args) {
return false
}
for i, arg := range a.Args {
if !Equal(arg, b.Args[i]) {
return false
}
}
return Equal(a.Fun, b.Fun) &&
(a.Ellipsis == token.NoPos && b.Ellipsis == token.NoPos || a.Ellipsis != token.NoPos && b.Ellipsis != token.NoPos)
case *ast.CompositeLit:
b := b.(*ast.CompositeLit)
if len(a.Elts) != len(b.Elts) {
return false
}
for i, elt := range b.Elts {
if !Equal(elt, b.Elts[i]) {
return false
}
}
return Equal(a.Type, b.Type) && a.Incomplete == b.Incomplete
case *ast.Ident:
b := b.(*ast.Ident)
return a.Name == b.Name
case *ast.IndexExpr:
b := b.(*ast.IndexExpr)
return Equal(a.X, b.X) && Equal(a.Index, b.Index)
case *ast.KeyValueExpr:
b := b.(*ast.KeyValueExpr)
return Equal(a.Key, b.Key) && Equal(a.Value, b.Value)
case *ast.ParenExpr:
b := b.(*ast.ParenExpr)
return Equal(a.X, b.X)
case *ast.SelectorExpr:
b := b.(*ast.SelectorExpr)
return Equal(a.X, b.X) && Equal(a.Sel, b.Sel)
case *ast.SliceExpr:
b := b.(*ast.SliceExpr)
return Equal(a.X, b.X) && Equal(a.Low, b.Low) && Equal(a.High, b.High) && Equal(a.Max, b.Max) && a.Slice3 == b.Slice3
case *ast.StarExpr:
b := b.(*ast.StarExpr)
return Equal(a.X, b.X)
case *ast.TypeAssertExpr:
b := b.(*ast.TypeAssertExpr)
return Equal(a.X, b.X) && Equal(a.Type, b.Type)
case *ast.UnaryExpr:
b := b.(*ast.UnaryExpr)
return a.Op == b.Op && Equal(a.X, b.X)
case *ast.MapType:
b := b.(*ast.MapType)
return Equal(a.Key, b.Key) && Equal(a.Value, b.Value)
case *ast.ArrayType:
b := b.(*ast.ArrayType)
return Equal(a.Len, b.Len) && Equal(a.Elt, b.Elt)
case *ast.Ellipsis:
b := b.(*ast.Ellipsis)
return Equal(a.Elt, b.Elt)
case *ast.InterfaceType:
b := b.(*ast.InterfaceType)
return a.Incomplete == b.Incomplete && Equal(a.Methods, b.Methods)
case *ast.StructType:
b := b.(*ast.StructType)
return a.Incomplete == b.Incomplete && Equal(a.Fields, b.Fields)
case *ast.FuncLit:
// TODO(dh): support function literals
return false
case *ast.ChanType:
b := b.(*ast.ChanType)
return a.Dir == b.Dir && (a.Arrow == token.NoPos && b.Arrow == token.NoPos || a.Arrow != token.NoPos && b.Arrow != token.NoPos)
case *ast.FieldList:
b := b.(*ast.FieldList)
if len(a.List) != len(b.List) {
return false
}
for i, fieldA := range a.List {
if !Equal(fieldA, b.List[i]) {
return false
}
}
return true
case *ast.Field:
b := b.(*ast.Field)
if len(a.Names) != len(b.Names) {
return false
}
for j, name := range a.Names {
if !Equal(name, b.Names[j]) {
return false
}
}
if !Equal(a.Type, b.Type) || !Equal(a.Tag, b.Tag) {
return false
}
return true
default:
panic(fmt.Sprintf("unreachable: %T", a))
}
}
go-tools-2021.1.2/go/callgraph/ 0000775 0000000 0000000 00000000000 14143223131 0016036 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/go/callgraph/callgraph.go 0000664 0000000 0000000 00000007312 14143223131 0020325 0 ustar 00root root 0000000 0000000 // Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/*
Package callgraph defines the call graph and various algorithms
and utilities to operate on it.
A call graph is a labelled directed graph whose nodes represent
functions and whose edge labels represent syntactic function call
sites. The presence of a labelled edge (caller, site, callee)
indicates that caller may call callee at the specified call site.
A call graph is a multigraph: it may contain multiple edges (caller,
*, callee) connecting the same pair of nodes, so long as the edges
differ by label; this occurs when one function calls another function
from multiple call sites. Also, it may contain multiple edges
(caller, site, *) that differ only by callee; this indicates a
polymorphic call.
A SOUND call graph is one that overapproximates the dynamic calling
behaviors of the program in all possible executions. One call graph
is more PRECISE than another if it is a smaller overapproximation of
the dynamic behavior.
All call graphs have a synthetic root node which is responsible for
calling main() and init().
Calls to built-in functions (e.g. panic, println) are not represented
in the call graph; they are treated like built-in operators of the
language.
*/
package callgraph
// TODO(adonovan): add a function to eliminate wrappers from the
// callgraph, preserving topology.
// More generally, we could eliminate "uninteresting" nodes such as
// nodes from packages we don't care about.
import (
"fmt"
"go/token"
"honnef.co/go/tools/go/ir"
)
// A Graph represents a call graph.
//
// A graph may contain nodes that are not reachable from the root.
// If the call graph is sound, such nodes indicate unreachable
// functions.
//
type Graph struct {
Root *Node // the distinguished root node
Nodes map[*ir.Function]*Node // all nodes by function
}
// New returns a new Graph with the specified root node.
func New(root *ir.Function) *Graph {
g := &Graph{Nodes: make(map[*ir.Function]*Node)}
g.Root = g.CreateNode(root)
return g
}
// CreateNode returns the Node for fn, creating it if not present.
func (g *Graph) CreateNode(fn *ir.Function) *Node {
n, ok := g.Nodes[fn]
if !ok {
n = &Node{Func: fn, ID: len(g.Nodes)}
g.Nodes[fn] = n
}
return n
}
// A Node represents a node in a call graph.
type Node struct {
Func *ir.Function // the function this node represents
ID int // 0-based sequence number
In []*Edge // unordered set of incoming call edges (n.In[*].Callee == n)
Out []*Edge // unordered set of outgoing call edges (n.Out[*].Caller == n)
}
func (n *Node) String() string {
return fmt.Sprintf("n%d:%s", n.ID, n.Func)
}
// A Edge represents an edge in the call graph.
//
// Site is nil for edges originating in synthetic or intrinsic
// functions, e.g. reflect.Call or the root of the call graph.
type Edge struct {
Caller *Node
Site ir.CallInstruction
Callee *Node
}
func (e Edge) String() string {
return fmt.Sprintf("%s --> %s", e.Caller, e.Callee)
}
func (e Edge) Description() string {
var prefix string
switch e.Site.(type) {
case nil:
return "synthetic call"
case *ir.Go:
prefix = "concurrent "
case *ir.Defer:
prefix = "deferred "
}
return prefix + e.Site.Common().Description()
}
func (e Edge) Pos() token.Pos {
if e.Site == nil {
return token.NoPos
}
return e.Site.Pos()
}
// AddEdge adds the edge (caller, site, callee) to the call graph.
// Elimination of duplicate edges is the caller's responsibility.
func AddEdge(caller *Node, site ir.CallInstruction, callee *Node) {
e := &Edge{caller, site, callee}
callee.In = append(callee.In, e)
caller.Out = append(caller.Out, e)
}
go-tools-2021.1.2/go/callgraph/cha/ 0000775 0000000 0000000 00000000000 14143223131 0016571 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/go/callgraph/cha/cha.go 0000664 0000000 0000000 00000010270 14143223131 0017653 0 ustar 00root root 0000000 0000000 // Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package cha computes the call graph of a Go program using the Class
// Hierarchy Analysis (CHA) algorithm.
//
// CHA was first described in "Optimization of Object-Oriented Programs
// Using Static Class Hierarchy Analysis", Jeffrey Dean, David Grove,
// and Craig Chambers, ECOOP'95.
//
// CHA is related to RTA (see go/callgraph/rta); the difference is that
// CHA conservatively computes the entire "implements" relation between
// interfaces and concrete types ahead of time, whereas RTA uses dynamic
// programming to construct it on the fly as it encounters new functions
// reachable from main. CHA may thus include spurious call edges for
// types that haven't been instantiated yet, or types that are never
// instantiated.
//
// Since CHA conservatively assumes that all functions are address-taken
// and all concrete types are put into interfaces, it is sound to run on
// partial programs, such as libraries without a main or test function.
//
package cha
import (
"go/types"
"honnef.co/go/tools/go/callgraph"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
"honnef.co/go/tools/go/types/typeutil"
)
// CallGraph computes the call graph of the specified program using the
// Class Hierarchy Analysis algorithm.
//
func CallGraph(prog *ir.Program) *callgraph.Graph {
cg := callgraph.New(nil) // TODO(adonovan) eliminate concept of rooted callgraph
allFuncs := irutil.AllFunctions(prog)
// funcsBySig contains all functions, keyed by signature. It is
// the effective set of address-taken functions used to resolve
// a dynamic call of a particular signature.
var funcsBySig typeutil.Map // value is []*ir.Function
// methodsByName contains all methods,
// grouped by name for efficient lookup.
methodsByName := make(map[string][]*ir.Function)
// methodsMemo records, for every abstract method call call I.f on
// interface type I, the set of concrete methods C.f of all
// types C that satisfy interface I.
methodsMemo := make(map[*types.Func][]*ir.Function)
lookupMethods := func(m *types.Func) []*ir.Function {
methods, ok := methodsMemo[m]
if !ok {
I := m.Type().(*types.Signature).Recv().Type().Underlying().(*types.Interface)
for _, f := range methodsByName[m.Name()] {
C := f.Signature.Recv().Type() // named or *named
if types.Implements(C, I) {
methods = append(methods, f)
}
}
methodsMemo[m] = methods
}
return methods
}
for f := range allFuncs {
if f.Signature.Recv() == nil {
// Package initializers can never be address-taken.
if f.Name() == "init" && f.Synthetic == ir.SyntheticPackageInitializer {
continue
}
funcs, _ := funcsBySig.At(f.Signature).([]*ir.Function)
funcs = append(funcs, f)
funcsBySig.Set(f.Signature, funcs)
} else {
methodsByName[f.Name()] = append(methodsByName[f.Name()], f)
}
}
addEdge := func(fnode *callgraph.Node, site ir.CallInstruction, g *ir.Function) {
gnode := cg.CreateNode(g)
callgraph.AddEdge(fnode, site, gnode)
}
addEdges := func(fnode *callgraph.Node, site ir.CallInstruction, callees []*ir.Function) {
// Because every call to a highly polymorphic and
// frequently used abstract method such as
// (io.Writer).Write is assumed to call every concrete
// Write method in the program, the call graph can
// contain a lot of duplication.
//
// TODO(adonovan): opt: consider factoring the callgraph
// API so that the Callers component of each edge is a
// slice of nodes, not a singleton.
for _, g := range callees {
addEdge(fnode, site, g)
}
}
for f := range allFuncs {
fnode := cg.CreateNode(f)
for _, b := range f.Blocks {
for _, instr := range b.Instrs {
if site, ok := instr.(ir.CallInstruction); ok {
call := site.Common()
if call.IsInvoke() {
addEdges(fnode, site, lookupMethods(call.Method))
} else if g := call.StaticCallee(); g != nil {
addEdge(fnode, site, g)
} else if _, ok := call.Value.(*ir.Builtin); !ok {
callees, _ := funcsBySig.At(call.Signature()).([]*ir.Function)
addEdges(fnode, site, callees)
}
}
}
}
}
return cg
}
go-tools-2021.1.2/go/callgraph/cha/cha_test.go 0000664 0000000 0000000 00000004762 14143223131 0020723 0 ustar 00root root 0000000 0000000 // Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//lint:file-ignore SA1019 go/callgraph's test suite is built around the deprecated go/loader. We'll leave fixing that to upstream.
// No testdata on Android.
// +build !android
package cha_test
import (
"bytes"
"fmt"
"go/ast"
"go/parser"
"go/token"
"go/types"
"io/ioutil"
"sort"
"strings"
"testing"
"honnef.co/go/tools/go/callgraph"
"honnef.co/go/tools/go/callgraph/cha"
"honnef.co/go/tools/go/ir/irutil"
"golang.org/x/tools/go/loader"
)
var inputs = []string{
"testdata/func.go",
"testdata/iface.go",
"testdata/recv.go",
}
func expectation(f *ast.File) (string, token.Pos) {
for _, c := range f.Comments {
text := strings.TrimSpace(c.Text())
if t := strings.TrimPrefix(text, "WANT:\n"); t != text {
return t, c.Pos()
}
}
return "", token.NoPos
}
// TestCHA runs CHA on each file in inputs, prints the dynamic edges of
// the call graph, and compares it with the golden results embedded in
// the WANT comment at the end of the file.
//
func TestCHA(t *testing.T) {
for _, filename := range inputs {
content, err := ioutil.ReadFile(filename)
if err != nil {
t.Errorf("couldn't read file '%s': %s", filename, err)
continue
}
conf := loader.Config{
ParserMode: parser.ParseComments,
}
f, err := conf.ParseFile(filename, content)
if err != nil {
t.Error(err)
continue
}
want, pos := expectation(f)
if pos == token.NoPos {
t.Errorf("No WANT: comment in %s", filename)
continue
}
conf.CreateFromFiles("main", f)
iprog, err := conf.Load()
if err != nil {
t.Error(err)
continue
}
prog := irutil.CreateProgram(iprog, 0)
mainPkg := prog.Package(iprog.Created[0].Pkg)
prog.Build()
cg := cha.CallGraph(prog)
if got := printGraph(cg, mainPkg.Pkg); got != want {
t.Errorf("%s: got:\n%s\nwant:\n%s",
prog.Fset.Position(pos), got, want)
}
}
}
func printGraph(cg *callgraph.Graph, from *types.Package) string {
var edges []string
callgraph.GraphVisitEdges(cg, func(e *callgraph.Edge) error {
if strings.Contains(e.Description(), "dynamic") {
edges = append(edges, fmt.Sprintf("%s --> %s",
e.Caller.Func.RelString(from),
e.Callee.Func.RelString(from)))
}
return nil
})
sort.Strings(edges)
var buf bytes.Buffer
buf.WriteString("Dynamic calls\n")
for _, edge := range edges {
fmt.Fprintf(&buf, " %s\n", edge)
}
return strings.TrimSpace(buf.String())
}
go-tools-2021.1.2/go/callgraph/cha/testdata/ 0000775 0000000 0000000 00000000000 14143223131 0020402 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/go/callgraph/cha/testdata/func.go 0000664 0000000 0000000 00000000464 14143223131 0021670 0 ustar 00root root 0000000 0000000 //+build ignore
package main
// Test of dynamic function calls; no interfaces.
func A(int) {}
var (
B = func(int) {}
C = func(int) {}
)
func f() {
pfn := B
pfn(0) // calls A, B, C, even though A is not even address-taken
}
// WANT:
// Dynamic calls
// f --> A
// f --> init$1
// f --> init$2
go-tools-2021.1.2/go/callgraph/cha/testdata/iface.go 0000664 0000000 0000000 00000001703 14143223131 0022001 0 ustar 00root root 0000000 0000000 //+build ignore
package main
// Test of interface calls. None of the concrete types are ever
// instantiated or converted to interfaces.
type I interface {
f()
}
type J interface {
f()
g()
}
type C int // implements I
func (*C) f()
type D int // implements I and J
func (*D) f()
func (*D) g()
func one(i I, j J) {
i.f() // calls *C and *D
}
func two(i I, j J) {
j.f() // calls *D (but not *C, even though it defines method f)
}
func three(i I, j J) {
j.g() // calls *D
}
func four(i I, j J) {
Jf := J.f
if unknown {
Jf = nil // suppress IR constant propagation
}
Jf(nil) // calls *D
}
func five(i I, j J) {
jf := j.f
if unknown {
jf = nil // suppress IR constant propagation
}
jf() // calls *D
}
var unknown bool
// WANT:
// Dynamic calls
// (J).f$bound --> (*D).f
// (J).f$thunk --> (*D).f
// five --> (J).f$bound
// four --> (J).f$thunk
// one --> (*C).f
// one --> (*D).f
// three --> (*D).g
// two --> (*D).f
go-tools-2021.1.2/go/callgraph/cha/testdata/recv.go 0000664 0000000 0000000 00000000656 14143223131 0021677 0 ustar 00root root 0000000 0000000 //+build ignore
package main
type I interface {
f()
}
type J interface {
g()
}
type C int // C and *C implement I; *C implements J
func (C) f()
func (*C) g()
type D int // *D implements I and J
func (*D) f()
func (*D) g()
func f(i I) {
i.f() // calls C, *C, *D
}
func g(j J) {
j.g() // calls *C, *D
}
// WANT:
// Dynamic calls
// f --> (*C).f
// f --> (*D).f
// f --> (C).f
// g --> (*C).g
// g --> (*D).g
go-tools-2021.1.2/go/callgraph/rta/ 0000775 0000000 0000000 00000000000 14143223131 0016624 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/go/callgraph/rta/rta.go 0000664 0000000 0000000 00000034333 14143223131 0017747 0 ustar 00root root 0000000 0000000 // Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This package provides Rapid Type Analysis (RTA) for Go, a fast
// algorithm for call graph construction and discovery of reachable code
// (and hence dead code) and runtime types. The algorithm was first
// described in:
//
// David F. Bacon and Peter F. Sweeney. 1996.
// Fast static analysis of C++ virtual function calls. (OOPSLA '96)
// http://doi.acm.org/10.1145/236337.236371
//
// The algorithm uses dynamic programming to tabulate the cross-product
// of the set of known "address taken" functions with the set of known
// dynamic calls of the same type. As each new address-taken function
// is discovered, call graph edges are added from each known callsite,
// and as each new call site is discovered, call graph edges are added
// from it to each known address-taken function.
//
// A similar approach is used for dynamic calls via interfaces: it
// tabulates the cross-product of the set of known "runtime types",
// i.e. types that may appear in an interface value, or be derived from
// one via reflection, with the set of known "invoke"-mode dynamic
// calls. As each new "runtime type" is discovered, call edges are
// added from the known call sites, and as each new call site is
// discovered, call graph edges are added to each compatible
// method.
//
// In addition, we must consider all exported methods of any runtime type
// as reachable, since they may be called via reflection.
//
// Each time a newly added call edge causes a new function to become
// reachable, the code of that function is analyzed for more call sites,
// address-taken functions, and runtime types. The process continues
// until a fixed point is achieved.
//
// The resulting call graph is less precise than one produced by pointer
// analysis, but the algorithm is much faster. For example, running the
// cmd/callgraph tool on its own source takes ~2.1s for RTA and ~5.4s
// for points-to analysis.
//
package rta
// TODO(adonovan): test it by connecting it to the interpreter and
// replacing all "unreachable" functions by a special intrinsic, and
// ensure that that intrinsic is never called.
import (
"fmt"
"go/types"
"honnef.co/go/tools/go/callgraph"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/types/typeutil"
)
// A Result holds the results of Rapid Type Analysis, which includes the
// set of reachable functions/methods, runtime types, and the call graph.
//
type Result struct {
// CallGraph is the discovered callgraph.
// It does not include edges for calls made via reflection.
CallGraph *callgraph.Graph
// Reachable contains the set of reachable functions and methods.
// This includes exported methods of runtime types, since
// they may be accessed via reflection.
// The value indicates whether the function is address-taken.
//
// (We wrap the bool in a struct to avoid inadvertent use of
// "if Reachable[f] {" to test for set membership.)
Reachable map[*ir.Function]struct{ AddrTaken bool }
// RuntimeTypes contains the set of types that are needed at
// runtime, for interfaces or reflection.
//
// The value indicates whether the type is inaccessible to reflection.
// Consider:
// type A struct{B}
// fmt.Println(new(A))
// Types *A, A and B are accessible to reflection, but the unnamed
// type struct{B} is not.
RuntimeTypes typeutil.Map
}
// Working state of the RTA algorithm.
type rta struct {
result *Result
prog *ir.Program
worklist []*ir.Function // list of functions to visit
// addrTakenFuncsBySig contains all address-taken *Functions, grouped by signature.
// Keys are *types.Signature, values are map[*ir.Function]bool sets.
addrTakenFuncsBySig typeutil.Map
// dynCallSites contains all dynamic "call"-mode call sites, grouped by signature.
// Keys are *types.Signature, values are unordered []ir.CallInstruction.
dynCallSites typeutil.Map
// invokeSites contains all "invoke"-mode call sites, grouped by interface.
// Keys are *types.Interface (never *types.Named),
// Values are unordered []ir.CallInstruction sets.
invokeSites typeutil.Map
// The following two maps together define the subset of the
// m:n "implements" relation needed by the algorithm.
// concreteTypes maps each concrete type to the set of interfaces that it implements.
// Keys are types.Type, values are unordered []*types.Interface.
// Only concrete types used as MakeInterface operands are included.
concreteTypes typeutil.Map
// interfaceTypes maps each interface type to
// the set of concrete types that implement it.
// Keys are *types.Interface, values are unordered []types.Type.
// Only interfaces used in "invoke"-mode CallInstructions are included.
interfaceTypes typeutil.Map
}
// addReachable marks a function as potentially callable at run-time,
// and ensures that it gets processed.
func (r *rta) addReachable(f *ir.Function, addrTaken bool) {
reachable := r.result.Reachable
n := len(reachable)
v := reachable[f]
if addrTaken {
v.AddrTaken = true
}
reachable[f] = v
if len(reachable) > n {
// First time seeing f. Add it to the worklist.
r.worklist = append(r.worklist, f)
}
}
// addEdge adds the specified call graph edge, and marks it reachable.
// addrTaken indicates whether to mark the callee as "address-taken".
func (r *rta) addEdge(site ir.CallInstruction, callee *ir.Function, addrTaken bool) {
r.addReachable(callee, addrTaken)
if g := r.result.CallGraph; g != nil {
if site.Parent() == nil {
panic(site)
}
from := g.CreateNode(site.Parent())
to := g.CreateNode(callee)
callgraph.AddEdge(from, site, to)
}
}
// ---------- addrTakenFuncs Γ dynCallSites ----------
// visitAddrTakenFunc is called each time we encounter an address-taken function f.
func (r *rta) visitAddrTakenFunc(f *ir.Function) {
// Create two-level map (Signature -> Function -> bool).
S := f.Signature
funcs, _ := r.addrTakenFuncsBySig.At(S).(map[*ir.Function]bool)
if funcs == nil {
funcs = make(map[*ir.Function]bool)
r.addrTakenFuncsBySig.Set(S, funcs)
}
if !funcs[f] {
// First time seeing f.
funcs[f] = true
// If we've seen any dyncalls of this type, mark it reachable,
// and add call graph edges.
sites, _ := r.dynCallSites.At(S).([]ir.CallInstruction)
for _, site := range sites {
r.addEdge(site, f, true)
}
}
}
// visitDynCall is called each time we encounter a dynamic "call"-mode call.
func (r *rta) visitDynCall(site ir.CallInstruction) {
S := site.Common().Signature()
// Record the call site.
sites, _ := r.dynCallSites.At(S).([]ir.CallInstruction)
r.dynCallSites.Set(S, append(sites, site))
// For each function of signature S that we know is address-taken,
// mark it reachable. We'll add the callgraph edges later.
funcs, _ := r.addrTakenFuncsBySig.At(S).(map[*ir.Function]bool)
for g := range funcs {
r.addEdge(site, g, true)
}
}
// ---------- concrete types Γ invoke sites ----------
// addInvokeEdge is called for each new pair (site, C) in the matrix.
func (r *rta) addInvokeEdge(site ir.CallInstruction, C types.Type) {
// Ascertain the concrete method of C to be called.
imethod := site.Common().Method
cmethod := r.prog.MethodValue(r.prog.MethodSets.MethodSet(C).Lookup(imethod.Pkg(), imethod.Name()))
r.addEdge(site, cmethod, true)
}
// visitInvoke is called each time the algorithm encounters an "invoke"-mode call.
func (r *rta) visitInvoke(site ir.CallInstruction) {
I := site.Common().Value.Type().Underlying().(*types.Interface)
// Record the invoke site.
sites, _ := r.invokeSites.At(I).([]ir.CallInstruction)
r.invokeSites.Set(I, append(sites, site))
// Add callgraph edge for each existing
// address-taken concrete type implementing I.
for _, C := range r.implementations(I) {
r.addInvokeEdge(site, C)
}
}
// ---------- main algorithm ----------
// visitFunc processes function f.
func (r *rta) visitFunc(f *ir.Function) {
var space [32]*ir.Value // preallocate space for common case
for _, b := range f.Blocks {
for _, instr := range b.Instrs {
rands := instr.Operands(space[:0])
switch instr := instr.(type) {
case ir.CallInstruction:
call := instr.Common()
if call.IsInvoke() {
r.visitInvoke(instr)
} else if g := call.StaticCallee(); g != nil {
r.addEdge(instr, g, false)
} else if _, ok := call.Value.(*ir.Builtin); !ok {
r.visitDynCall(instr)
}
// Ignore the call-position operand when
// looking for address-taken Functions.
// Hack: assume this is rands[0].
rands = rands[1:]
case *ir.MakeInterface:
r.addRuntimeType(instr.X.Type(), false)
}
// Process all address-taken functions.
for _, op := range rands {
if g, ok := (*op).(*ir.Function); ok {
r.visitAddrTakenFunc(g)
}
}
}
}
}
// Analyze performs Rapid Type Analysis, starting at the specified root
// functions. It returns nil if no roots were specified.
//
// If buildCallGraph is true, Result.CallGraph will contain a call
// graph; otherwise, only the other fields (reachable functions) are
// populated.
//
func Analyze(roots []*ir.Function, buildCallGraph bool) *Result {
if len(roots) == 0 {
return nil
}
r := &rta{
result: &Result{Reachable: make(map[*ir.Function]struct{ AddrTaken bool })},
prog: roots[0].Prog,
}
if buildCallGraph {
// TODO(adonovan): change callgraph API to eliminate the
// notion of a distinguished root node. Some callgraphs
// have many roots, or none.
r.result.CallGraph = callgraph.New(roots[0])
}
hasher := typeutil.MakeHasher()
r.result.RuntimeTypes.SetHasher(hasher)
r.addrTakenFuncsBySig.SetHasher(hasher)
r.dynCallSites.SetHasher(hasher)
r.invokeSites.SetHasher(hasher)
r.concreteTypes.SetHasher(hasher)
r.interfaceTypes.SetHasher(hasher)
// Visit functions, processing their instructions, and adding
// new functions to the worklist, until a fixed point is
// reached.
var shadow []*ir.Function // for efficiency, we double-buffer the worklist
r.worklist = append(r.worklist, roots...)
for len(r.worklist) > 0 {
shadow, r.worklist = r.worklist, shadow[:0]
for _, f := range shadow {
r.visitFunc(f)
}
}
return r.result
}
// interfaces(C) returns all currently known interfaces implemented by C.
func (r *rta) interfaces(C types.Type) []*types.Interface {
// Ascertain set of interfaces C implements
// and update 'implements' relation.
var ifaces []*types.Interface
r.interfaceTypes.Iterate(func(I types.Type, concs interface{}) {
if I := I.(*types.Interface); types.Implements(C, I) {
concs, _ := concs.([]types.Type)
r.interfaceTypes.Set(I, append(concs, C))
ifaces = append(ifaces, I)
}
})
r.concreteTypes.Set(C, ifaces)
return ifaces
}
// implementations(I) returns all currently known concrete types that implement I.
func (r *rta) implementations(I *types.Interface) []types.Type {
var concs []types.Type
if v := r.interfaceTypes.At(I); v != nil {
concs = v.([]types.Type)
} else {
// First time seeing this interface.
// Update the 'implements' relation.
r.concreteTypes.Iterate(func(C types.Type, ifaces interface{}) {
if types.Implements(C, I) {
ifaces, _ := ifaces.([]*types.Interface)
r.concreteTypes.Set(C, append(ifaces, I))
concs = append(concs, C)
}
})
r.interfaceTypes.Set(I, concs)
}
return concs
}
// addRuntimeType is called for each concrete type that can be the
// dynamic type of some interface or reflect.Value.
// Adapted from needMethods in go/ir/builder.go
//
func (r *rta) addRuntimeType(T types.Type, skip bool) {
if prev, ok := r.result.RuntimeTypes.At(T).(bool); ok {
if skip && !prev {
r.result.RuntimeTypes.Set(T, skip)
}
return
}
r.result.RuntimeTypes.Set(T, skip)
mset := r.prog.MethodSets.MethodSet(T)
if _, ok := T.Underlying().(*types.Interface); !ok {
// T is a new concrete type.
for i, n := 0, mset.Len(); i < n; i++ {
sel := mset.At(i)
m := sel.Obj()
if m.Exported() {
// Exported methods are always potentially callable via reflection.
r.addReachable(r.prog.MethodValue(sel), true)
}
}
// Add callgraph edge for each existing dynamic
// "invoke"-mode call via that interface.
for _, I := range r.interfaces(T) {
sites, _ := r.invokeSites.At(I).([]ir.CallInstruction)
for _, site := range sites {
r.addInvokeEdge(site, T)
}
}
}
// Precondition: T is not a method signature (*Signature with Recv()!=nil).
// Recursive case: skip => don't call makeMethods(T).
// Each package maintains its own set of types it has visited.
var n *types.Named
switch T := T.(type) {
case *types.Named:
n = T
case *types.Pointer:
n, _ = T.Elem().(*types.Named)
}
if n != nil {
owner := n.Obj().Pkg()
if owner == nil {
return // built-in error type
}
}
// Recursion over signatures of each exported method.
for i := 0; i < mset.Len(); i++ {
if mset.At(i).Obj().Exported() {
sig := mset.At(i).Type().(*types.Signature)
r.addRuntimeType(sig.Params(), true) // skip the Tuple itself
r.addRuntimeType(sig.Results(), true) // skip the Tuple itself
}
}
switch t := T.(type) {
case *types.Basic:
// nop
case *types.Interface:
// nop---handled by recursion over method set.
case *types.Pointer:
r.addRuntimeType(t.Elem(), false)
case *types.Slice:
r.addRuntimeType(t.Elem(), false)
case *types.Chan:
r.addRuntimeType(t.Elem(), false)
case *types.Map:
r.addRuntimeType(t.Key(), false)
r.addRuntimeType(t.Elem(), false)
case *types.Signature:
if t.Recv() != nil {
panic(fmt.Sprintf("Signature %s has Recv %s", t, t.Recv()))
}
r.addRuntimeType(t.Params(), true) // skip the Tuple itself
r.addRuntimeType(t.Results(), true) // skip the Tuple itself
case *types.Named:
// A pointer-to-named type can be derived from a named
// type via reflection. It may have methods too.
r.addRuntimeType(types.NewPointer(T), false)
// Consider 'type T struct{S}' where S has methods.
// Reflection provides no way to get from T to struct{S},
// only to S, so the method set of struct{S} is unwanted,
// so set 'skip' flag during recursion.
r.addRuntimeType(t.Underlying(), true)
case *types.Array:
r.addRuntimeType(t.Elem(), false)
case *types.Struct:
for i, n := 0, t.NumFields(); i < n; i++ {
r.addRuntimeType(t.Field(i).Type(), false)
}
case *types.Tuple:
for i, n := 0, t.Len(); i < n; i++ {
r.addRuntimeType(t.At(i).Type(), false)
}
default:
panic(T)
}
}
go-tools-2021.1.2/go/callgraph/rta/rta_test.go 0000664 0000000 0000000 00000006371 14143223131 0021007 0 ustar 00root root 0000000 0000000 // Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//lint:file-ignore SA1019 go/callgraph's test suite is built around the deprecated go/loader. We'll leave fixing that to upstream.
// No testdata on Android.
// +build !android
package rta_test
import (
"bytes"
"fmt"
"go/ast"
"go/parser"
"go/token"
"go/types"
"io/ioutil"
"sort"
"strings"
"testing"
"honnef.co/go/tools/go/callgraph"
"honnef.co/go/tools/go/callgraph/rta"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
"golang.org/x/tools/go/loader"
)
var inputs = []string{
"testdata/func.go",
"testdata/rtype.go",
"testdata/iface.go",
}
func expectation(f *ast.File) (string, token.Pos) {
for _, c := range f.Comments {
text := strings.TrimSpace(c.Text())
if t := strings.TrimPrefix(text, "WANT:\n"); t != text {
return t, c.Pos()
}
}
return "", token.NoPos
}
// TestRTA runs RTA on each file in inputs, prints the results, and
// compares it with the golden results embedded in the WANT comment at
// the end of the file.
//
// The results string consists of two parts: the set of dynamic call
// edges, "f --> g", one per line, and the set of reachable functions,
// one per line. Each set is sorted.
//
func TestRTA(t *testing.T) {
for _, filename := range inputs {
content, err := ioutil.ReadFile(filename)
if err != nil {
t.Errorf("couldn't read file '%s': %s", filename, err)
continue
}
conf := loader.Config{
ParserMode: parser.ParseComments,
}
f, err := conf.ParseFile(filename, content)
if err != nil {
t.Error(err)
continue
}
want, pos := expectation(f)
if pos == token.NoPos {
t.Errorf("No WANT: comment in %s", filename)
continue
}
conf.CreateFromFiles("main", f)
iprog, err := conf.Load()
if err != nil {
t.Error(err)
continue
}
prog := irutil.CreateProgram(iprog, 0)
mainPkg := prog.Package(iprog.Created[0].Pkg)
prog.Build()
res := rta.Analyze([]*ir.Function{
mainPkg.Func("main"),
mainPkg.Func("init"),
}, true)
if got := printResult(res, mainPkg.Pkg); got != want {
t.Errorf("%s: got:\n%s\nwant:\n%s",
prog.Fset.Position(pos), got, want)
}
}
}
func printResult(res *rta.Result, from *types.Package) string {
var buf bytes.Buffer
writeSorted := func(ss []string) {
sort.Strings(ss)
for _, s := range ss {
fmt.Fprintf(&buf, " %s\n", s)
}
}
buf.WriteString("Dynamic calls\n")
var edges []string
callgraph.GraphVisitEdges(res.CallGraph, func(e *callgraph.Edge) error {
if strings.Contains(e.Description(), "dynamic") {
edges = append(edges, fmt.Sprintf("%s --> %s",
e.Caller.Func.RelString(from),
e.Callee.Func.RelString(from)))
}
return nil
})
writeSorted(edges)
buf.WriteString("Reachable functions\n")
var reachable []string
for f := range res.Reachable {
reachable = append(reachable, f.RelString(from))
}
writeSorted(reachable)
buf.WriteString("Reflect types\n")
var rtypes []string
res.RuntimeTypes.Iterate(func(key types.Type, value interface{}) {
if value == false { // accessible to reflection
rtypes = append(rtypes, types.TypeString(key, types.RelativeTo(from)))
}
})
writeSorted(rtypes)
return strings.TrimSpace(buf.String())
}
go-tools-2021.1.2/go/callgraph/rta/testdata/ 0000775 0000000 0000000 00000000000 14143223131 0020435 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/go/callgraph/rta/testdata/func.go 0000664 0000000 0000000 00000000762 14143223131 0021724 0 ustar 00root root 0000000 0000000 //+build ignore
package main
// Test of dynamic function calls.
// No interfaces, so no runtime/reflect types.
func A1() {
A2(0)
}
func A2(int) {} // not address-taken
func B() {} // unreachable
var (
C = func(int) {}
D = func(int) {}
)
func main() {
A1()
pfn := C
pfn(0) // calls C and D but not A2 (same sig but not address-taken)
}
// WANT:
// Dynamic calls
// main --> init$1
// main --> init$2
// Reachable functions
// A1
// A2
// init$1
// init$2
// Reflect types
go-tools-2021.1.2/go/callgraph/rta/testdata/iface.go 0000664 0000000 0000000 00000002375 14143223131 0022042 0 ustar 00root root 0000000 0000000 //+build ignore
package main
// Test of interface calls.
func use(interface{})
type A byte // instantiated but not a reflect type
func (A) f() {} // called directly
func (A) F() {} // unreachable
type B int // a reflect type
func (*B) f() {} // reachable via interface invoke
func (*B) F() {} // reachable: exported method of reflect type
type B2 int // a reflect type, and *B2 also
func (B2) f() {} // reachable via interface invoke
func (B2) g() {} // reachable: exported method of reflect type
type C string // not instantiated
func (C) f() {} // unreachable
func (C) F() {} // unreachable
type D uint // instantiated only in dead code
func (D) f() {} // unreachable
func (D) F() {} // unreachable
func main() {
A(0).f()
use(new(B))
use(B2(0))
var i interface {
f()
}
i.f() // calls (*B).f, (*B2).f and (B2.f)
live()
}
func live() {
var j interface {
f()
g()
}
j.f() // calls (B2).f and (*B2).f but not (*B).f (no g method).
}
func dead() {
use(D(0))
}
// WANT:
// Dynamic calls
// live --> (*B2).f
// live --> (B2).f
// main --> (*B).f
// main --> (*B2).f
// main --> (B2).f
// Reachable functions
// (*B).F
// (*B).f
// (*B2).f
// (A).f
// (B2).f
// live
// use
// Reflect types
// *B
// *B2
// B
// B2
go-tools-2021.1.2/go/callgraph/rta/testdata/rtype.go 0000664 0000000 0000000 00000001147 14143223131 0022132 0 ustar 00root root 0000000 0000000 //+build ignore
package main
// Test of runtime types (types for which descriptors are needed).
func use(interface{})
type A byte // neither A nor byte are runtime types
type B struct{ x uint } // B and uint are runtime types, but not the struct
func main() {
var x int // not a runtime type
print(x)
var y string // runtime type due to interface conversion
use(y)
use(struct{ uint64 }{}) // struct is a runtime type
use(new(B)) // *B is a runtime type
}
// WANT:
// Dynamic calls
// Reachable functions
// use
// Reflect types
// *B
// B
// string
// struct{uint64}
// uint
// uint64
go-tools-2021.1.2/go/callgraph/static/ 0000775 0000000 0000000 00000000000 14143223131 0017325 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/go/callgraph/static/static.go 0000664 0000000 0000000 00000001763 14143223131 0021152 0 ustar 00root root 0000000 0000000 // Package static computes the call graph of a Go program containing
// only static call edges.
package static
import (
"honnef.co/go/tools/go/callgraph"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
)
// CallGraph computes the call graph of the specified program
// considering only static calls.
//
func CallGraph(prog *ir.Program) *callgraph.Graph {
cg := callgraph.New(nil) // TODO(adonovan) eliminate concept of rooted callgraph
// TODO(adonovan): opt: use only a single pass over the ir.Program.
// TODO(adonovan): opt: this is slower than RTA (perhaps because
// the lower precision means so many edges are allocated)!
for f := range irutil.AllFunctions(prog) {
fnode := cg.CreateNode(f)
for _, b := range f.Blocks {
for _, instr := range b.Instrs {
if site, ok := instr.(ir.CallInstruction); ok {
if g := site.Common().StaticCallee(); g != nil {
gnode := cg.CreateNode(g)
callgraph.AddEdge(fnode, site, gnode)
}
}
}
}
}
return cg
}
go-tools-2021.1.2/go/callgraph/static/static_test.go 0000664 0000000 0000000 00000003047 14143223131 0022206 0 ustar 00root root 0000000 0000000 // Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//lint:file-ignore SA1019 go/callgraph's test suite is built around the deprecated go/loader. We'll leave fixing that to upstream.
package static_test
import (
"fmt"
"go/parser"
"reflect"
"sort"
"testing"
"honnef.co/go/tools/go/callgraph"
"honnef.co/go/tools/go/callgraph/static"
"honnef.co/go/tools/go/ir/irutil"
"golang.org/x/tools/go/loader"
)
const input = `package P
type C int
func (C) f()
type I interface{f()}
func f() {
p := func() {}
g()
p() // IR constant propagation => static
if unknown {
p = h
}
p() // dynamic
C(0).f()
}
func g() {
var i I = C(0)
i.f()
}
func h()
var unknown bool
`
func TestStatic(t *testing.T) {
conf := loader.Config{ParserMode: parser.ParseComments}
f, err := conf.ParseFile("P.go", input)
if err != nil {
t.Fatal(err)
}
conf.CreateFromFiles("P", f)
iprog, err := conf.Load()
if err != nil {
t.Fatal(err)
}
P := iprog.Created[0].Pkg
prog := irutil.CreateProgram(iprog, 0)
prog.Build()
cg := static.CallGraph(prog)
var edges []string
callgraph.GraphVisitEdges(cg, func(e *callgraph.Edge) error {
edges = append(edges, fmt.Sprintf("%s -> %s",
e.Caller.Func.RelString(P),
e.Callee.Func.RelString(P)))
return nil
})
sort.Strings(edges)
want := []string{
"(*C).f -> (C).f",
"f -> (C).f",
"f -> f$1",
"f -> g",
}
if !reflect.DeepEqual(edges, want) {
t.Errorf("Got edges %v, want %v", edges, want)
}
}
go-tools-2021.1.2/go/callgraph/util.go 0000664 0000000 0000000 00000011156 14143223131 0017346 0 ustar 00root root 0000000 0000000 // Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package callgraph
import "honnef.co/go/tools/go/ir"
// This file provides various utilities over call graphs, such as
// visitation and path search.
// CalleesOf returns a new set containing all direct callees of the
// caller node.
//
func CalleesOf(caller *Node) map[*Node]bool {
callees := make(map[*Node]bool)
for _, e := range caller.Out {
callees[e.Callee] = true
}
return callees
}
// GraphVisitEdges visits all the edges in graph g in depth-first order.
// The edge function is called for each edge in postorder. If it
// returns non-nil, visitation stops and GraphVisitEdges returns that
// value.
//
func GraphVisitEdges(g *Graph, edge func(*Edge) error) error {
seen := make(map[*Node]bool)
var visit func(n *Node) error
visit = func(n *Node) error {
if !seen[n] {
seen[n] = true
for _, e := range n.Out {
if err := visit(e.Callee); err != nil {
return err
}
if err := edge(e); err != nil {
return err
}
}
}
return nil
}
for _, n := range g.Nodes {
if err := visit(n); err != nil {
return err
}
}
return nil
}
// PathSearch finds an arbitrary path starting at node start and
// ending at some node for which isEnd() returns true. On success,
// PathSearch returns the path as an ordered list of edges; on
// failure, it returns nil.
//
func PathSearch(start *Node, isEnd func(*Node) bool) []*Edge {
stack := make([]*Edge, 0, 32)
seen := make(map[*Node]bool)
var search func(n *Node) []*Edge
search = func(n *Node) []*Edge {
if !seen[n] {
seen[n] = true
if isEnd(n) {
return stack
}
for _, e := range n.Out {
stack = append(stack, e) // push
if found := search(e.Callee); found != nil {
return found
}
stack = stack[:len(stack)-1] // pop
}
}
return nil
}
return search(start)
}
// DeleteSyntheticNodes removes from call graph g all nodes for
// synthetic functions (except g.Root and package initializers),
// preserving the topology. In effect, calls to synthetic wrappers
// are "inlined".
//
func (g *Graph) DeleteSyntheticNodes() {
// Measurements on the standard library and go.tools show that
// resulting graph has ~15% fewer nodes and 4-8% fewer edges
// than the input.
//
// Inlining a wrapper of in-degree m, out-degree n adds m*n
// and removes m+n edges. Since most wrappers are monomorphic
// (n=1) this results in a slight reduction. Polymorphic
// wrappers (n>1), e.g. from embedding an interface value
// inside a struct to satisfy some interface, cause an
// increase in the graph, but they seem to be uncommon.
// Hash all existing edges to avoid creating duplicates.
edges := make(map[Edge]bool)
for _, cgn := range g.Nodes {
for _, e := range cgn.Out {
edges[*e] = true
}
}
for fn, cgn := range g.Nodes {
if cgn == g.Root || fn.Synthetic == 0 || isInit(cgn.Func) {
continue // keep
}
for _, eIn := range cgn.In {
for _, eOut := range cgn.Out {
newEdge := Edge{eIn.Caller, eIn.Site, eOut.Callee}
if edges[newEdge] {
continue // don't add duplicate
}
AddEdge(eIn.Caller, eIn.Site, eOut.Callee)
edges[newEdge] = true
}
}
g.DeleteNode(cgn)
}
}
func isInit(fn *ir.Function) bool {
return fn.Pkg != nil && fn.Pkg.Func("init") == fn
}
// DeleteNode removes node n and its edges from the graph g.
// (NB: not efficient for batch deletion.)
func (g *Graph) DeleteNode(n *Node) {
n.deleteIns()
n.deleteOuts()
delete(g.Nodes, n.Func)
}
// deleteIns deletes all incoming edges to n.
func (n *Node) deleteIns() {
for _, e := range n.In {
removeOutEdge(e)
}
n.In = nil
}
// deleteOuts deletes all outgoing edges from n.
func (n *Node) deleteOuts() {
for _, e := range n.Out {
removeInEdge(e)
}
n.Out = nil
}
// removeOutEdge removes edge.Caller's outgoing edge 'edge'.
func removeOutEdge(edge *Edge) {
caller := edge.Caller
n := len(caller.Out)
for i, e := range caller.Out {
if e == edge {
// Replace it with the final element and shrink the slice.
caller.Out[i] = caller.Out[n-1]
caller.Out[n-1] = nil // aid GC
caller.Out = caller.Out[:n-1]
return
}
}
panic("edge not found: " + edge.String())
}
// removeInEdge removes edge.Callee's incoming edge 'edge'.
func removeInEdge(edge *Edge) {
caller := edge.Callee
n := len(caller.In)
for i, e := range caller.In {
if e == edge {
// Replace it with the final element and shrink the slice.
caller.In[i] = caller.In[n-1]
caller.In[n-1] = nil // aid GC
caller.In = caller.In[:n-1]
return
}
}
panic("edge not found: " + edge.String())
}
go-tools-2021.1.2/go/gcsizes/ 0000775 0000000 0000000 00000000000 14143223131 0015550 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/go/gcsizes/LICENSE 0000664 0000000 0000000 00000002707 14143223131 0016563 0 ustar 00root root 0000000 0000000 Copyright (c) 2009 The Go Authors. 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 Google Inc. 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.
go-tools-2021.1.2/go/gcsizes/sizes.go 0000664 0000000 0000000 00000005330 14143223131 0017235 0 ustar 00root root 0000000 0000000 // 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 gcsizes provides a types.Sizes implementation that adheres
// to the rules used by the gc compiler.
package gcsizes
import (
"go/build"
"go/types"
)
type Sizes struct {
WordSize int64
MaxAlign int64
}
// ForArch returns a correct Sizes for the given architecture.
func ForArch(arch string) *Sizes {
wordSize := int64(8)
maxAlign := int64(8)
switch build.Default.GOARCH {
case "386", "arm":
wordSize, maxAlign = 4, 4
case "amd64p32":
wordSize = 4
}
return &Sizes{WordSize: wordSize, MaxAlign: maxAlign}
}
func (s *Sizes) Alignof(T types.Type) int64 {
switch t := T.Underlying().(type) {
case *types.Array:
return s.Alignof(t.Elem())
case *types.Struct:
max := int64(1)
n := t.NumFields()
var fields []*types.Var
for i := 0; i < n; i++ {
fields = append(fields, t.Field(i))
}
for _, f := range fields {
if a := s.Alignof(f.Type()); a > max {
max = a
}
}
return max
}
a := s.Sizeof(T) // may be 0
if a < 1 {
return 1
}
if a > s.MaxAlign {
return s.MaxAlign
}
return a
}
func (s *Sizes) Offsetsof(fields []*types.Var) []int64 {
offsets := make([]int64, len(fields))
var o int64
for i, f := range fields {
a := s.Alignof(f.Type())
o = align(o, a)
offsets[i] = o
o += s.Sizeof(f.Type())
}
return offsets
}
var basicSizes = [...]byte{
types.Bool: 1,
types.Int8: 1,
types.Int16: 2,
types.Int32: 4,
types.Int64: 8,
types.Uint8: 1,
types.Uint16: 2,
types.Uint32: 4,
types.Uint64: 8,
types.Float32: 4,
types.Float64: 8,
types.Complex64: 8,
types.Complex128: 16,
}
func (s *Sizes) Sizeof(T types.Type) int64 {
switch t := T.Underlying().(type) {
case *types.Basic:
k := t.Kind()
if int(k) < len(basicSizes) {
if s := basicSizes[k]; s > 0 {
return int64(s)
}
}
if k == types.String {
return s.WordSize * 2
}
case *types.Array:
n := t.Len()
if n == 0 {
return 0
}
a := s.Alignof(t.Elem())
z := s.Sizeof(t.Elem())
return align(z, a)*(n-1) + z
case *types.Slice:
return s.WordSize * 3
case *types.Struct:
n := t.NumFields()
if n == 0 {
return 0
}
var fields []*types.Var
for i := 0; i < n; i++ {
fields = append(fields, t.Field(i))
}
offsets := s.Offsetsof(fields)
a := s.Alignof(T)
lsz := s.Sizeof(fields[n-1].Type())
if lsz == 0 {
lsz = 1
}
z := offsets[n-1] + lsz
return align(z, a)
case *types.Interface:
return s.WordSize * 2
}
return s.WordSize // catch-all
}
// align returns the smallest y >= x such that y % a == 0.
func align(x, a int64) int64 {
y := x + a - 1
return y - y%a
}
go-tools-2021.1.2/go/ir/ 0000775 0000000 0000000 00000000000 14143223131 0014513 5 ustar 00root root 0000000 0000000 go-tools-2021.1.2/go/ir/LICENSE 0000664 0000000 0000000 00000002777 14143223131 0015535 0 ustar 00root root 0000000 0000000 Copyright (c) 2009 The Go Authors. All rights reserved.
Copyright (c) 2016 Dominik Honnef. 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 Google Inc. 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.
go-tools-2021.1.2/go/ir/UPSTREAM 0000664 0000000 0000000 00000000713 14143223131 0015677 0 ustar 00root root 0000000 0000000 This package started as a copy of golang.org/x/tools/go/ssa, imported from an unknown commit in 2016.
It has since been heavily modified to match our own needs in an IR.
The changes are too many to list here, and it is best to consider this package independent of go/ssa.
Upstream changes still get applied when they address bugs in portions of code we have inherited.
The last upstream commit we've looked at was:
915f6209478fe61eb90dbe155a8a1c58655b931f
go-tools-2021.1.2/go/ir/bench_test.go 0000664 0000000 0000000 00000001472 14143223131 0017164 0 ustar 00root root 0000000 0000000 package ir_test
import (
"testing"
"golang.org/x/tools/go/packages"
"honnef.co/go/tools/go/ir"
)
func BenchmarkSSA(b *testing.B) {
cfg := &packages.Config{
Mode: packages.NeedSyntax | packages.NeedTypes | packages.NeedTypesInfo,
Tests: false,
}
pkgs, err := packages.Load(cfg, "std")
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
prog := ir.NewProgram(pkgs[0].Fset, ir.GlobalDebug)
seen := map[*packages.Package]struct{}{}
var create func(pkg *packages.Package)
create = func(pkg *packages.Package) {
if _, ok := seen[pkg]; ok {
return
}
seen[pkg] = struct{}{}
prog.CreatePackage(pkg.Types, pkg.Syntax, pkg.TypesInfo, true)
for _, imp := range pkg.Imports {
create(imp)
}
}
for _, pkg := range pkgs {
create(pkg)
}
prog.Build()
}
}
go-tools-2021.1.2/go/ir/blockopt.go 0000664 0000000 0000000 00000012043 14143223131 0016657 0 ustar 00root root 0000000 0000000 // 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 ir
// Simple block optimizations to simplify the control flow graph.
// TODO(adonovan): opt: instead of creating several "unreachable" blocks
// per function in the Builder, reuse a single one (e.g. at Blocks[1])
// to reduce garbage.
import (
"fmt"
"os"
)
// If true, perform sanity checking and show progress at each
// successive iteration of optimizeBlocks. Very verbose.
const debugBlockOpt = false
// markReachable sets Index=-1 for all blocks reachable from b.
func markReachable(b *BasicBlock) {
b.gaps = -1
for _, succ := range b.Succs {
if succ.gaps == 0 {
markReachable(succ)
}
}
}
// deleteUnreachableBlocks marks all reachable blocks of f and
// eliminates (nils) all others, including possibly cyclic subgraphs.
//
func deleteUnreachableBlocks(f *Function) {
const white, black = 0, -1
// We borrow b.gaps temporarily as the mark bit.
for _, b := range f.Blocks {
b.gaps = white
}
markReachable(f.Blocks[0])
// 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.gaps == white {
for _, c := range b.Succs {
if c.gaps == black {
c.removePred(b) // delete white->black edge
}
}
if debugBlockOpt {
fmt.Fprintln(os.Stderr, "unreachable", b)
}
f.Blocks[i] = nil // delete b
}
}
f.removeNilBlocks()
}
// jumpThreading attempts to apply simple jump-threading to block b,
// in which a->b->c become a->c if b is just a Jump.
// The result is true if the optimization was applied.
//
func jumpThreading(f *Function, b *BasicBlock) bool {
if b.Index == 0 {
return false // don't apply to entry block
}
if b.Instrs == nil {
return false
}
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
}
c := b.Succs[0]
if c == b {
return false // don't apply to degenerate jump-to-self.
}
if c.hasPhi() {
return false // not sound without more effort
}
for j, a := range b.Preds {
a.replaceSucc(b, c)
// If a now has two edges to c, replace its degenerate If by Jump.
if len(a.Succs) == 2 && a.Succs[0] == c && a.Succs[1] == c {
jump := new(Jump)
jump.setBlock(a)
a.Instrs[len(a.Instrs)-1] = jump
a.Succs = a.Succs[:1]
c.removePred(b)
} else {
if j == 0 {
c.replacePred(b, a)
} else {
c.Preds = append(c.Preds, a)
}
}
if debugBlockOpt {
fmt.Fprintln(os.Stderr, "jumpThreading", a, b, c)
}
}
f.Blocks[b.Index] = nil // delete b
return true
}
// fuseBlocks attempts to apply the block fusion optimization to block
// a, in which a->b becomes ab if len(a.Succs)==len(b.Preds)==1.
// The result is true if the optimization was applied.
//
func fuseBlocks(f *Function, a *BasicBlock) bool {
if len(a.Succs) != 1 {
return false
}
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
// their sole operand but that requires Referrers, built later.)
if b.hasPhi() {
return false // not sound without further effort
}
// Eliminate jump at end of A, then copy all of B across.
a.Instrs = append(a.Instrs[:len(a.Instrs)-1], b.Instrs...)
for _, instr := range b.Instrs {
instr.setBlock(a)
}
// A inherits B's successors
a.Succs = append(a.succs2[:0], b.Succs...)
// Fix up Preds links of all successors of B.
for _, c := range b.Succs {
c.replacePred(b, a)
}
if debugBlockOpt {
fmt.Fprintln(os.Stderr, "fuseBlocks", a, b)
}
f.Blocks[b.Index] = nil // delete b
return true
}
// optimizeBlocks() performs some simple block optimizations on a
// completed function: dead block elimination, block fusion, jump
// threading.
//
func optimizeBlocks(f *Function) {
if debugBlockOpt {
f.WriteTo(os.Stderr)
mustSanityCheck(f, nil)
}
deleteUnreachableBlocks(f)
// Loop until no further progress.
changed := true
for changed {
changed = false
if debugBlockOpt {
f.WriteTo(os.Stderr)
mustSanityCheck(f, nil)
}
for _, b := range f.Blocks {
// f.Blocks will temporarily contain nils to indicate
// deleted blocks; we remove them at the end.
if b == nil {
continue
}
// Fuse blocks. b->c becomes bc.
if fuseBlocks(f, b) {
changed = true
}
// a->b->c becomes a->c if b contains only a Jump.
if jumpThreading(f, b) {
changed = true
continue // (b was disconnected)
}
}
}
f.removeNilBlocks()
}
go-tools-2021.1.2/go/ir/builder.go 0000664 0000000 0000000 00000204511 14143223131 0016473 0 ustar 00root root 0000000 0000000 // 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 ir
// This file implements the BUILD phase of IR construction.
//
// IR construction has two phases, CREATE and BUILD. In the CREATE phase
// (create.go), all packages are constructed and type-checked and
// definitions of all package members are created, method-sets are
// computed, and wrapper methods are synthesized.
// ir.Packages are created in arbitrary order.
//
// In the BUILD phase (builder.go), the builder traverses the AST of
// each Go source function and generates IR instructions for the
// function body. Initializer expressions for package-level variables
// are emitted to the package's init() function in the order specified
// by go/types.Info.InitOrder, then code for each function in the
// package is generated in lexical order.
//
// The builder's and Program's indices (maps) are populated and
// mutated during the CREATE phase, but during the BUILD phase they
// remain constant. The sole exception is Prog.methodSets and its
// related maps, which are protected by a dedicated mutex.
import (
"fmt"
"go/ast"
"go/constant"
"go/token"
"go/types"
"os"
)
type opaqueType struct {
types.Type
name string
}
func (t *opaqueType) String() string { return t.name }
var (
varOk = newVar("ok", tBool)
varIndex = newVar("index", tInt)
// Type constants.
tBool = types.Typ[types.Bool]
tByte = types.Typ[types.Byte]
tInt = types.Typ[types.Int]
tInvalid = types.Typ[types.Invalid]
tString = types.Typ[types.String]
tUntypedNil = types.Typ[types.UntypedNil]
tRangeIter = &opaqueType{nil, "iter"} // the type of all "range" iterators
tEface = types.NewInterfaceType(nil, nil).Complete()
)
// builder holds state associated with the package currently being built.
// Its methods contain all the logic for AST-to-IR conversion.
type builder struct {
printFunc string
blocksets [5]BlockSet
}
// cond emits to fn code to evaluate boolean condition e and jump
// to t or f depending on its value, performing various simplifications.
//
// Postcondition: fn.currentBlock is nil.
//
func (b *builder) cond(fn *Function, e ast.Expr, t, f *BasicBlock) *If {
switch e := e.(type) {
case *ast.ParenExpr:
return b.cond(fn, e.X, t, f)
case *ast.BinaryExpr:
switch e.Op {
case token.LAND:
ltrue := fn.newBasicBlock("cond.true")
b.cond(fn, e.X, ltrue, f)
fn.currentBlock = ltrue
return b.cond(fn, e.Y, t, f)
case token.LOR:
lfalse := fn.newBasicBlock("cond.false")
b.cond(fn, e.X, t, lfalse)
fn.currentBlock = lfalse
return b.cond(fn, e.Y, t, f)
}
case *ast.UnaryExpr:
if e.Op == token.NOT {
return b.cond(fn, e.X, f, t)
}
}
// A traditional compiler would simplify "if false" (etc) here
// but we do not, for better fidelity to the source code.
//
// The value of a constant condition may be platform-specific,
// and may cause blocks that are reachable in some configuration
// to be hidden from subsequent analyses such as bug-finding tools.
return emitIf(fn, b.expr(fn, e), t, f, e)
}
// logicalBinop emits code to fn to evaluate e, a &&- or
// ||-expression whose reified boolean value is wanted.
// The value is returned.
//
func (b *builder) logicalBinop(fn *Function, e *ast.BinaryExpr) Value {
rhs := fn.newBasicBlock("binop.rhs")
done := fn.newBasicBlock("binop.done")
// T(e) = T(e.X) = T(e.Y) after untyped constants have been
// eliminated.
// TODO(adonovan): not true; MyBool==MyBool yields UntypedBool.
t := fn.Pkg.typeOf(e)
var short Value // value of the short-circuit path
switch e.Op {
case token.LAND:
b.cond(fn, e.X, rhs, done)
short = emitConst(fn, NewConst(constant.MakeBool(false), t))
case token.LOR:
b.cond(fn, e.X, done, rhs)
short = emitConst(fn, NewConst(constant.MakeBool(true), t))
}
// Is rhs unreachable?
if rhs.Preds == nil {
// Simplify false&&y to false, true||y to true.
fn.currentBlock = done
return short
}
// Is done unreachable?
if done.Preds == nil {
// Simplify true&&y (or false||y) to y.
fn.currentBlock = rhs
return b.expr(fn, e.Y)
}
// All edges from e.X to done carry the short-circuit value.
var edges []Value
for range done.Preds {
edges = append(edges, short)
}
// The edge from e.Y to done carries the value of e.Y.
fn.currentBlock = rhs
edges = append(edges, b.expr(fn, e.Y))
emitJump(fn, done, e)
fn.currentBlock = done
phi := &Phi{Edges: edges}
phi.typ = t
return done.emit(phi, e)
}
// exprN lowers a multi-result expression e to IR form, emitting code
// to fn and returning a single Value whose type is a *types.Tuple.
// The caller must access the components via Extract.
//
// Multi-result expressions include CallExprs in a multi-value
// assignment or return statement, and "value,ok" uses of
// TypeAssertExpr, IndexExpr (when X is a map), and Recv.
//
func (b *builder) exprN(fn *Function, e ast.Expr) Value {
typ := fn.Pkg.typeOf(e).(*types.Tuple)
switch e := e.(type) {
case *ast.ParenExpr:
return b.exprN(fn, e.X)
case *ast.CallExpr:
// Currently, no built-in function nor type conversion
// has multiple results, so we can avoid some of the
// cases for single-valued CallExpr.
var c Call
b.setCall(fn, e, &c.Call)
c.typ = typ
return fn.emit(&c, e)
case *ast.IndexExpr:
mapt := fn.Pkg.typeOf(e.X).Underlying().(*types.Map)
lookup := &MapLookup{
X: b.expr(fn, e.X),
Index: emitConv(fn, b.expr(fn, e.Index), mapt.Key(), e),
CommaOk: true,
}
lookup.setType(typ)
return fn.emit(lookup, e)
case *ast.TypeAssertExpr:
return emitTypeTest(fn, b.expr(fn, e.X), typ.At(0).Type(), e)
case *ast.UnaryExpr: // must be receive <-
return emitRecv(fn, b.expr(fn, e.X), true, typ, e)
}
panic(fmt.Sprintf("exprN(%T) in %s", e, fn))
}
// builtin emits to fn IR instructions to implement a call to the
// built-in function obj with the specified arguments
// and return type. It returns the value defined by the result.
//
// The result is nil if no special handling was required; in this case
// the caller should treat this like an ordinary library function
// call.
//
func (b *builder) builtin(fn *Function, obj *types.Builtin, args []ast.Expr, typ types.Type, source ast.Node) Value {
switch obj.Name() {
case "make":
switch typ.Underlying().(type) {
case *types.Slice:
n := b.expr(fn, args[1])
m := n
if len(args) == 3 {
m = b.expr(fn, args[2])
}
if m, ok := m.(*Const); ok {
// treat make([]T, n, m) as new([m]T)[:n]
cap := m.Int64()
at := types.NewArray(typ.Underlying().(*types.Slice).Elem(), cap)
alloc := emitNew(fn, at, source)
v := &Slice{
X: alloc,
High: n,
}
v.setType(typ)
return fn.emit(v, source)
}
v := &MakeSlice{
Len: n,
Cap: m,
}
v.setType(typ)
return fn.emit(v, source)
case *types.Map:
var res Value
if len(args) == 2 {
res = b.expr(fn, args[1])
}
v := &MakeMap{Reserve: res}
v.setType(typ)
return fn.emit(v, source)
case *types.Chan:
var sz Value = emitConst(fn, intConst(0))
if len(args) == 2 {
sz = b.expr(fn, args[1])
}
v := &MakeChan{Size: sz}
v.setType(typ)
return fn.emit(v, source)
}
case "new":
alloc := emitNew(fn, deref(typ), source)
return alloc
case "len", "cap":
// Special case: len or cap of an array or *array is
// based on the type, not the value which may be nil.
// We must still evaluate the value, though. (If it
// was side-effect free, the whole call would have
// been constant-folded.)
t := deref(fn.Pkg.typeOf(args[0])).Underlying()
if at, ok := t.(*types.Array); ok {
b.expr(fn, args[0]) // for effects only
return emitConst(fn, intConst(at.Len()))
}
// Otherwise treat as normal.
case "panic":
fn.emit(&Panic{
X: emitConv(fn, b.expr(fn, args[0]), tEface, source),
}, source)
addEdge(fn.currentBlock, fn.Exit)
fn.currentBlock = fn.newBasicBlock("unreachable")
return emitConst(fn, NewConst(constant.MakeBool(true), tBool)) // any non-nil Value will do
}
return nil // treat all others as a regular function call
}
// addr lowers a single-result addressable expression e to IR form,
// emitting code to fn and returning the location (an lvalue) defined
// by the expression.
//
// If escaping is true, addr marks the base variable of the
// addressable expression e as being a potentially escaping pointer
// value. For example, in this code:
//
// a := A{
// b: [1]B{B{c: 1}}
// }
// return &a.b[0].c
//
// the application of & causes a.b[0].c to have its address taken,
// which means that ultimately the local variable a must be
// heap-allocated. This is a simple but very conservative escape
// analysis.
//
// Operations forming potentially escaping pointers include:
// - &x, including when implicit in method call or composite literals.
// - a[:] iff a is an array (not *array)
// - references to variables in lexically enclosing functions.
//
func (b *builder) addr(fn *Function, e ast.Expr, escaping bool) lvalue {
switch e := e.(type) {
case *ast.Ident:
if isBlankIdent(e) {
return blank{}
}
obj := fn.Pkg.objectOf(e)
v := fn.Prog.packageLevelValue(obj) // var (address)
if v == nil {
v = fn.lookup(obj, escaping)
}
return &address{addr: v, expr: e}
case *ast.CompositeLit:
t := deref(fn.Pkg.typeOf(e))
var v *Alloc
if escaping {
v = emitNew(fn, t, e)
} else {
v = fn.addLocal(t, e)
}
var sb storebuf
b.compLit(fn, v, e, true, &sb)
sb.emit(fn)
return &address{addr: v, expr: e}
case *ast.ParenExpr:
return b.addr(fn, e.X, escaping)
case *ast.SelectorExpr:
sel, ok := fn.Pkg.info.Selections[e]
if !ok {
// qualified identifier
return b.addr(fn, e.Sel, escaping)
}
if sel.Kind() != types.FieldVal {
panic(sel)
}
wantAddr := true
v := b.receiver(fn, e.X, wantAddr, escaping, sel, e)
last := len(sel.Index()) - 1
return &address{
addr: emitFieldSelection(fn, v, sel.Index()[last], true, e.Sel),
expr: e.Sel,
}
case *ast.IndexExpr:
var x Value
var et types.Type
switch t := fn.Pkg.typeOf(e.X).Underlying().(type) {
case *types.Array:
x = b.addr(fn, e.X, escaping).address(fn)
et = types.NewPointer(t.Elem())
case *types.Pointer: // *array
x = b.expr(fn, e.X)
et = types.NewPointer(t.Elem().Underlying().(*types.Array).Elem())
case *types.Slice:
x = b.expr(fn, e.X)
et = types.NewPointer(t.Elem())
case *types.Map:
return &element{
m: b.expr(fn, e.X),
k: emitConv(fn, b.expr(fn, e.Index), t.Key(), e.Index),
t: t.Elem(),
}
default:
panic("unexpected container type in IndexExpr: " + t.String())
}
v := &IndexAddr{
X: x,
Index: emitConv(fn, b.expr(fn, e.Index), tInt, e.Index),
}
v.setType(et)
return &address{addr: fn.emit(v, e), expr: e}
case *ast.StarExpr:
return &address{addr: b.expr(fn, e.X), expr: e}
}
panic(fmt.Sprintf("unexpected address expression: %T", e))
}
type store struct {
lhs lvalue
rhs Value
source ast.Node
}
type storebuf struct{ stores []store }
func (sb *storebuf) store(lhs lvalue, rhs Value, source ast.Node) {
sb.stores = append(sb.stores, store{lhs, rhs, source})
}
func (sb *storebuf) emit(fn *Function) {
for _, s := range sb.stores {
s.lhs.store(fn, s.rhs, s.source)
}
}
// assign emits to fn code to initialize the lvalue loc with the value
// of expression e. If isZero is true, assign assumes that loc holds
// the zero value for its type.
//
// This is equivalent to loc.store(fn, b.expr(fn, e)), but may generate
// better code in some cases, e.g., for composite literals in an
// addressable location.
//
// If sb is not nil, assign generates code to evaluate expression e, but
// not to update loc. Instead, the necessary stores are appended to the
// storebuf sb so that they can be executed later. This allows correct
// in-place update of existing variables when the RHS is a composite
// literal that may reference parts of the LHS.
//
func (b *builder) assign(fn *Function, loc lvalue, e ast.Expr, isZero bool, sb *storebuf, source ast.Node) {
// Can we initialize it in place?
if e, ok := unparen(e).(*ast.CompositeLit); ok {
// A CompositeLit never evaluates to a pointer,
// so if the type of the location is a pointer,
// an &-operation is implied.
if _, ok := loc.(blank); !ok { // avoid calling blank.typ()
if isPointer(loc.typ()) {
ptr := b.addr(fn, e, true).address(fn)
// copy address
if sb != nil {
sb.store(loc, ptr, source)
} else {
loc.store(fn, ptr, source)
}
return
}
}
if _, ok := loc.(*address); ok {
if isInterface(loc.typ()) {
// e.g. var x interface{} = T{...}
// Can't in-place initialize an interface value.
// Fall back to copying.
} else {
// x = T{...} or x := T{...}
addr := loc.address(fn)
if sb != nil {
b.compLit(fn, addr, e, isZero, sb)
} else {
var sb storebuf
b.compLit(fn, addr, e, isZero, &sb)
sb.emit(fn)
}
// Subtle: emit debug ref for aggregate types only;
// slice and map are handled by store ops in compLit.
switch loc.typ().Underlying().(type) {
case *types.Struct, *types.Array:
emitDebugRef(fn, e, addr, true)
}
return
}
}
}
// simple case: just copy
rhs := b.expr(fn, e)
if sb != nil {
sb.store(loc, rhs, source)
} else {
loc.store(fn, rhs, source)
}
}
// expr lowers a single-result expression e to IR form, emitting code
// to fn and returning the Value defined by the expression.
//
func (b *builder) expr(fn *Function, e ast.Expr) Value {
e = unparen(e)
tv := fn.Pkg.info.Types[e]
// Is expression a constant?
if tv.Value != nil {
return emitConst(fn, NewConst(tv.Value, tv.Type))
}
var v Value
if tv.Addressable() {
// Prefer pointer arithmetic ({Index,Field}Addr) followed
// by Load over subelement extraction (e.g. Index, Field),
// to avoid large copies.
v = b.addr(fn, e, false).load(fn, e)
} else {
v = b.expr0(fn, e, tv)
}
if fn.debugInfo() {
emitDebugRef(fn, e, v, false)
}
return v
}
func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value {
switch e := e.(type) {
case *ast.BasicLit:
panic("non-constant BasicLit") // unreachable
case *ast.FuncLit:
fn2 := &Function{
name: fmt.Sprintf("%s$%d", fn.Name(), 1+len(fn.AnonFuncs)),
Signature: fn.Pkg.typeOf(e.Type).Underlying().(*types.Signature),
parent: fn,
Pkg: fn.Pkg,
Prog: fn.Prog,
functionBody: new(functionBody),
}
fn2.source = e
fn.AnonFuncs = append(fn.AnonFuncs, fn2)
fn2.initHTML(b.printFunc)
b.buildFunction(fn2)
if fn2.FreeVars == nil {
return fn2
}
v := &MakeClosure{Fn: fn2}
v.setType(tv.Type)
for _, fv := range fn2.FreeVars {
v.Bindings = append(v.Bindings, fv.outer)
fv.outer = nil
}
return fn.emit(v, e)
case *ast.TypeAssertExpr: // single-result form only
return emitTypeAssert(fn, b.expr(fn, e.X), tv.Type, e)
case *ast.CallExpr:
if fn.Pkg.info.Types[e.Fun].IsType() {
// Explicit type conversion, e.g. string(x) or big.Int(x)
x := b.expr(fn, e.Args[0])
y := emitConv(fn, x, tv.Type, e)
return y
}
// Call to "intrinsic" built-ins, e.g. new, make, panic.
if id, ok := unparen(e.Fun).(*ast.Ident); ok {
if obj, ok := fn.Pkg.info.Uses[id].(*types.Builtin); ok {
if v := b.builtin(fn, obj, e.Args, tv.Type, e); v != nil {
return v
}
}
}
// Regular function call.
var v Call
b.setCall(fn, e, &v.Call)
v.setType(tv.Type)
return fn.emit(&v, e)
case *ast.UnaryExpr:
switch e.Op {
case token.AND: // &X --- potentially escaping.
addr := b.addr(fn, e.X, true)
if _, ok := unparen(e.X).(*ast.StarExpr); ok {
// &*p must panic if p is nil (http://golang.org/s/go12nil).
// For simplicity, we'll just (suboptimally) rely
// on the side effects of a load.
// TODO(adonovan): emit dedicated nilcheck.
addr.load(fn, e)
}
return addr.address(fn)
case token.ADD:
return b.expr(fn, e.X)
case token.NOT, token.SUB, token.XOR: // ! <- - ^
v := &UnOp{
Op: e.Op,
X: b.expr(fn, e.X),
}
v.setType(tv.Type)
return fn.emit(v, e)
case token.ARROW:
return emitRecv(fn, b.expr(fn, e.X), false, tv.Type, e)
default:
panic(e.Op)
}
case *ast.BinaryExpr:
switch e.Op {
case token.LAND, token.LOR:
return b.logicalBinop(fn, e)
case token.SHL, token.SHR:
fallthrough
case token.ADD, token.SUB, token.MUL, token.QUO, token.REM, token.AND, token.OR, token.XOR, token.AND_NOT:
return emitArith(fn, e.Op, b.expr(fn, e.X), b.expr(fn, e.Y), tv.Type, e)
case token.EQL, token.NEQ, token.GTR, token.LSS, token.LEQ, token.GEQ:
cmp := emitCompare(fn, e.Op, b.expr(fn, e.X), b.expr(fn, e.Y), e)
// The type of x==y may be UntypedBool.
return emitConv(fn, cmp, types.Default(tv.Type), e)
default:
panic("illegal op in BinaryExpr: " + e.Op.String())
}
case *ast.SliceExpr:
var low, high, max Value
var x Value
switch fn.Pkg.typeOf(e.X).Underlying().(type) {
case *types.Array:
// Potentially escaping.
x = b.addr(fn, e.X, true).address(fn)
case *types.Basic, *types.Slice, *types.Pointer: // *array
x = b.expr(fn, e.X)
default:
panic("unreachable")
}
if e.High != nil {
high = b.expr(fn, e.High)
}
if e.Low != nil {
low = b.expr(fn, e.Low)
}
if e.Slice3 {
max = b.expr(fn, e.Max)
}
v := &Slice{
X: x,
Low: low,
High: high,
Max: max,
}
v.setType(tv.Type)
return fn.emit(v, e)
case *ast.Ident:
obj := fn.Pkg.info.Uses[e]
// Universal built-in or nil?
switch obj := obj.(type) {
case *types.Builtin:
return &Builtin{name: obj.Name(), sig: tv.Type.(*types.Signature)}
case *types.Nil:
return emitConst(fn, nilConst(tv.Type))
}
// Package-level func or var?
if v := fn.Prog.packageLevelValue(obj); v != nil {
if _, ok := obj.(*types.Var); ok {
return emitLoad(fn, v, e) // var (address)
}
return v // (func)
}
// Local var.
return emitLoad(fn, fn.lookup(obj, false), e) // var (address)
case *ast.SelectorExpr:
sel, ok := fn.Pkg.info.Selections[e]
if !ok {
// builtin unsafe.{Add,Slice}
if obj, ok := fn.Pkg.info.Uses[e.Sel].(*types.Builtin); ok {
return &Builtin{name: "Unsafe" + obj.Name(), sig: tv.Type.(*types.Signature)}
}
// qualified identifier
return b.expr(fn, e.Sel)
}
switch sel.Kind() {
case types.MethodExpr:
// (*T).f or T.f, the method f from the method-set of type T.
// The result is a "thunk".
return emitConv(fn, makeThunk(fn.Prog, sel), tv.Type, e)
case types.MethodVal:
// e.f where e is an expression and f is a method.
// The result is a "bound".
obj := sel.Obj().(*types.Func)
rt := recvType(obj)
wantAddr := isPointer(rt)
escaping := true
v := b.receiver(fn, e.X, wantAddr, escaping, sel, e)
if isInterface(rt) {
// If v has interface type I,
// we must emit a check that v is non-nil.
// We use: typeassert v.(I).
emitTypeAssert(fn, v, rt, e)
}
c := &MakeClosure{
Fn: makeBound(fn.Prog, obj),
Bindings: []Value{v},
}
c.source = e.Sel
c.setType(tv.Type)
return fn.emit(c, e)
case types.FieldVal:
indices := sel.Index()
last := len(indices) - 1
v := b.expr(fn, e.X)
v = emitImplicitSelections(fn, v, indices[:last], e)
v = emitFieldSelection(fn, v, indices[last], false, e.Sel)
return v
}
panic("unexpected expression-relative selector")
case *ast.IndexExpr:
switch t := fn.Pkg.typeOf(e.X).Underlying().(type) {
case *types.Array:
// Non-addressable array (in a register).
v := &Index{
X: b.expr(fn, e.X),
Index: emitConv(fn, b.expr(fn, e.Index), tInt, e.Index),
}
v.setType(t.Elem())
return fn.emit(v, e)
case *types.Map:
// Maps are not addressable.
mapt := fn.Pkg.typeOf(e.X).Underlying().(*types.Map)
v := &MapLookup{
X: b.expr(fn, e.X),
Index: emitConv(fn, b.expr(fn, e.Index), mapt.Key(), e.Index),
}
v.setType(mapt.Elem())
return fn.emit(v, e)
case *types.Basic: // => string
// Strings are not addressable.
v := &StringLookup{
X: b.expr(fn, e.X),
Index: b.expr(fn, e.Index),
}
v.setType(tByte)
return fn.emit(v, e)
case *types.Slice, *types.Pointer: // *array
// Addressable slice/array; use IndexAddr and Load.
return b.addr(fn, e, false).load(fn, e)
default:
panic("unexpected container type in IndexExpr: " + t.String())
}
case *ast.CompositeLit, *ast.StarExpr:
// Addressable types (lvalues)
return b.addr(fn, e, false).load(fn, e)
}
panic(fmt.Sprintf("unexpected expr: %T", e))
}
// stmtList emits to fn code for all statements in list.
func (b *builder) stmtList(fn *Function, list []ast.Stmt) {
for _, s := range list {
b.stmt(fn, s)
}
}
// receiver emits to fn code for expression e in the "receiver"
// position of selection e.f (where f may be a field or a method) and
// returns the effective receiver after applying the implicit field
// selections of sel.
//
// wantAddr requests that the result is an an address. If
// !sel.Indirect(), this may require that e be built in addr() mode; it
// must thus be addressable.
//
// escaping is defined as per builder.addr().
//
func (b *builder) receiver(fn *Function, e ast.Expr, wantAddr, escaping bool, sel *types.Selection, source ast.Node) Value {
var v Value
if wantAddr && !sel.Indirect() && !isPointer(fn.Pkg.typeOf(e)) {
v = b.addr(fn, e, escaping).address(fn)
} else {
v = b.expr(fn, e)
}
last := len(sel.Index()) - 1
v = emitImplicitSelections(fn, v, sel.Index()[:last], source)
if !wantAddr && isPointer(v.Type()) {
v = emitLoad(fn, v, e)
}
return v
}
// setCallFunc populates the function parts of a CallCommon structure
// (Func, Method, Recv, Args[0]) based on the kind of invocation
// occurring in e.
//
func (b *builder) setCallFunc(fn *Function, e *ast.CallExpr, c *CallCommon) {
// Is this a method call?
if selector, ok := unparen(e.Fun).(*ast.SelectorExpr); ok {
sel, ok := fn.Pkg.info.Selections[selector]
if ok && sel.Kind() == types.MethodVal {
obj := sel.Obj().(*types.Func)
recv := recvType(obj)
wantAddr := isPointer(recv)
escaping := true
v := b.receiver(fn, selector.X, wantAddr, escaping, sel, selector)
if isInterface(recv) {
// Invoke-mode call.
c.Value = v
c.Method = obj
} else {
// "Call"-mode call.
c.Value = fn.Prog.declaredFunc(obj)
c.Args = append(c.Args, v)
}
return
}
// sel.Kind()==MethodExpr indicates T.f() or (*T).f():
// a statically dispatched call to the method f in the
// method-set of T or *T. T may be an interface.
//
// e.Fun would evaluate to a concrete method, interface
// wrapper function, or promotion wrapper.
//
// For now, we evaluate it in the usual way.
//
// TODO(adonovan): opt: inline expr() here, to make the
// call static and to avoid generation of wrappers.
// It's somewhat tricky as it may consume the first
// actual parameter if the call is "invoke" mode.
//
// Examples:
// type T struct{}; func (T) f() {} // "call" mode
// type T interface { f() } // "invoke" mode
//
// type S struct{ T }
//
// var s S
// S.f(s)
// (*S).f(&s)
//
// Suggested approach:
// - consume the first actual parameter expression
// and build it with b.expr().
// - apply implicit field selections.
// - use MethodVal logic to populate fields of c.
}
// Evaluate the function operand in the usual way.
c.Value = b.expr(fn, e.Fun)
}
// emitCallArgs emits to f code for the actual parameters of call e to
// a (possibly built-in) function of effective type sig.
// The argument values are appended to args, which is then returned.
//
func (b *builder) emitCallArgs(fn *Function, sig *types.Signature, e *ast.CallExpr, args []Value) []Value {
// f(x, y, z...): pass slice z straight through.
if e.Ellipsis != 0 {
for i, arg := range e.Args {
v := emitConv(fn, b.expr(fn, arg), sig.Params().At(i).Type(), arg)
args = append(args, v)
}
return args
}
offset := len(args) // 1 if call has receiver, 0 otherwise
// Evaluate actual parameter expressions.
//
// If this is a chained call of the form f(g()) where g has
// multiple return values (MRV), they are flattened out into
// args; a suffix of them may end up in a varargs slice.
for _, arg := range e.Args {
v := b.expr(fn, arg)
if ttuple, ok := v.Type().(*types.Tuple); ok { // MRV chain
for i, n := 0, ttuple.Len(); i < n; i++ {
args = append(args, emitExtract(fn, v, i, arg))
}
} else {
args = append(args, v)
}
}
// Actual->formal assignability conversions for normal parameters.
np := sig.Params().Len() // number of normal parameters
if sig.Variadic() {
np--
}
for i := 0; i < np; i++ {
args[offset+i] = emitConv(fn, args[offset+i], sig.Params().At(i).Type(), args[offset+i].Source())
}
// Actual->formal assignability conversions for variadic parameter,
// and construction of slice.
if sig.Variadic() {
varargs := args[offset+np:]
st := sig.Params().At(np).Type().(*types.Slice)
vt := st.Elem()
if len(varargs) == 0 {
args = append(args, emitConst(fn, nilConst(st)))
} else {
// Replace a suffix of args with a slice containing it.
at := types.NewArray(vt, int64(len(varargs)))
a := emitNew(fn, at, e)
a.source = e
for i, arg := range varargs {
iaddr := &IndexAddr{
X: a,
Index: emitConst(fn, intConst(int64(i))),
}
iaddr.setType(types.NewPointer(vt))
fn.emit(iaddr, e)
emitStore(fn, iaddr, arg, arg.Source())
}
s := &Slice{X: a}
s.setType(st)
args[offset+np] = fn.emit(s, args[offset+np].Source())
args = args[:offset+np+1]
}
}
return args
}
// setCall emits to fn code to evaluate all the parameters of a function
// call e, and populates *c with those values.
//
func (b *builder) setCall(fn *Function, e *ast.CallExpr, c *CallCommon) {
// First deal with the f(...) part and optional receiver.
b.setCallFunc(fn, e, c)
// Then append the other actual parameters.
sig, _ := fn.Pkg.typeOf(e.Fun).Underlying().(*types.Signature)
if sig == nil {
panic(fmt.Sprintf("no signature for call of %s", e.Fun))
}
c.Args = b.emitCallArgs(fn, sig, e, c.Args)
}
// assignOp emits to fn code to perform loc = val.
func (b *builder) assignOp(fn *Function, loc lvalue, val Value, op token.Token, source ast.Node) {
oldv := loc.load(fn, source)
loc.store(fn, emitArith(fn, op, oldv, emitConv(fn, val, oldv.Type(), source), loc.typ(), source), source)
}
// localValueSpec emits to fn code to define all of the vars in the
// function-local ValueSpec, spec.
//
func (b *builder) localValueSpec(fn *Function, spec *ast.ValueSpec) {
switch {
case len(spec.Values) == len(spec.Names):
// e.g. var x, y = 0, 1
// 1:1 assignment
for i, id := range spec.Names {
if !isBlankIdent(id) {
fn.addLocalForIdent(id)
}
lval := b.addr(fn, id, false) // non-escaping
b.assign(fn, lval, spec.Values[i], true, nil, spec)
}
case len(spec.Values) == 0:
// e.g. var x, y int
// Locals are implicitly zero-initialized.
for _, id := range spec.Names {
if !isBlankIdent(id) {
lhs := fn.addLocalForIdent(id)
if fn.debugInfo() {
emitDebugRef(fn, id, lhs, true)
}
}
}
default:
// e.g. var x, y = pos()
tuple := b.exprN(fn, spec.Values[0])
for i, id := range spec.Names {
if !isBlankIdent(id) {
fn.addLocalForIdent(id)
lhs := b.addr(fn, id, false) // non-escaping
lhs.store(fn, emitExtract(fn, tuple, i, id), id)
}
}
}
}
// assignStmt emits code to fn for a parallel assignment of rhss to lhss.
// isDef is true if this is a short variable declaration (:=).
//
// Note the similarity with localValueSpec.
//
func (b *builder) assignStmt(fn *Function, lhss, rhss []ast.Expr, isDef bool, source ast.Node) {
// Side effects of all LHSs and RHSs must occur in left-to-right order.
lvals := make([]lvalue, len(lhss))
isZero := make([]bool, len(lhss))
for i, lhs := range lhss {
var lval lvalue = blank{}
if !isBlankIdent(lhs) {
if isDef {
if obj := fn.Pkg.info.Defs[lhs.(*ast.Ident)]; obj != nil {
fn.addNamedLocal(obj, lhs)
isZero[i] = true
}
}
lval = b.addr(fn, lhs, false) // non-escaping
}
lvals[i] = lval
}
if len(lhss) == len(rhss) {
// Simple assignment: x = f() (!isDef)
// Parallel assignment: x, y = f(), g() (!isDef)
// or short var decl: x, y := f(), g() (isDef)
//
// In all cases, the RHSs may refer to the LHSs,
// so we need a storebuf.
var sb storebuf
for i := range rhss {
b.assign(fn, lvals[i], rhss[i], isZero[i], &sb, source)
}
sb.emit(fn)
} else {
// e.g. x, y = pos()
tuple := b.exprN(fn, rhss[0])
emitDebugRef(fn, rhss[0], tuple, false)
for i, lval := range lvals {
lval.store(fn, emitExtract(fn, tuple, i, source), source)
}
}
}
// arrayLen returns the length of the array whose composite literal elements are elts.
func (b *builder) arrayLen(fn *Function, elts []ast.Expr) int64 {
var max int64 = -1
var i int64 = -1
for _, e := range elts {
if kv, ok := e.(*ast.KeyValueExpr); ok {
i = b.expr(fn, kv.Key).(*Const).Int64()
} else {
i++
}
if i > max {
max = i
}
}
return max + 1
}
// compLit emits to fn code to initialize a composite literal e at
// address addr with type typ.
//
// Nested composite literals are recursively initialized in place
// where possible. If isZero is true, compLit assumes that addr
// holds the zero value for typ.
//
// Because the elements of a composite literal may refer to the
// variables being updated, as in the second line below,
// x := T{a: 1}
// x = T{a: x.a}
// all the reads must occur before all the writes. Thus all stores to
// loc are emitted to the storebuf sb for later execution.
//
// A CompositeLit may have pointer type only in the recursive (nested)
// case when the type name is implicit. e.g. in []*T{{}}, the inner
// literal has type *T behaves like &T{}.
// In that case, addr must hold a T, not a *T.
//
func (b *builder) compLit(fn *Function, addr Value, e *ast.CompositeLit, isZero bool, sb *storebuf) {
typ := deref(fn.Pkg.typeOf(e))
switch t := typ.Underlying().(type) {
case *types.Struct:
if !isZero && len(e.Elts) != t.NumFields() {
// memclear
sb.store(&address{addr, nil}, zeroValue(fn, deref(addr.Type()), e), e)
isZero = true
}
for i, e := range e.Elts {
fieldIndex := i
if kv, ok := e.(*ast.KeyValueExpr); ok {
fname := kv.Key.(*ast.Ident).Name
for i, n := 0, t.NumFields(); i < n; i++ {
sf := t.Field(i)
if sf.Name() == fname {
fieldIndex = i
e = kv.Value
break
}
}
}
sf := t.Field(fieldIndex)
faddr := &FieldAddr{
X: addr,
Field: fieldIndex,
}
faddr.setType(types.NewPointer(sf.Type()))
fn.emit(faddr, e)
b.assign(fn, &address{addr: faddr, expr: e}, e, isZero, sb, e)
}
case *types.Array, *types.Slice:
var at *types.Array
var array Value
switch t := t.(type) {
case *types.Slice:
at = types.NewArray(t.Elem(), b.arrayLen(fn, e.Elts))
alloc := emitNew(fn, at, e)
array = alloc
case *types.Array:
at = t
array = addr
if !isZero && int64(len(e.Elts)) != at.Len() {
// memclear
sb.store(&address{array, nil}, zeroValue(fn, deref(array.Type()), e), e)
}
}
var idx *Const
for _, e := range e.Elts {
if kv, ok := e.(*ast.KeyValueExpr); ok {
idx = b.expr(fn, kv.Key).(*Const)
e = kv.Value
} else {
var idxval int64
if idx != nil {
idxval = idx.Int64() + 1
}
idx = emitConst(fn, intConst(idxval))
}
iaddr := &IndexAddr{
X: array,
Index: idx,
}
iaddr.setType(types.NewPointer(at.Elem()))
fn.emit(iaddr, e)
if t != at { // slice
// backing array is unaliased => storebuf not needed.
b.assign(fn, &address{addr: iaddr, expr: e}, e, true, nil, e)
} else {
b.assign(fn, &address{addr: iaddr, expr: e}, e, true, sb, e)
}
}
if t != at { // slice
s := &Slice{X: array}
s.setType(typ)
sb.store(&address{addr: addr, expr: e}, fn.emit(s, e), e)
}
case *types.Map:
m := &MakeMap{Reserve: emitConst(fn, intConst(int64(len(e.Elts))))}
m.setType(typ)
fn.emit(m, e)
for _, e := range e.Elts {
e := e.(*ast.KeyValueExpr)
// If a key expression in a map literal is itself a
// composite literal, the type may be omitted.
// For example:
// map[*struct{}]bool{{}: true}
// An &-operation may be implied:
// map[*struct{}]bool{&struct{}{}: true}
var key Value
if _, ok := unparen(e.Key).(*ast.CompositeLit); ok && isPointer(t.Key()) {
// A CompositeLit never evaluates to a pointer,
// so if the type of the location is a pointer,
// an &-operation is implied.
key = b.addr(fn, e.Key, true).address(fn)
} else {
key = b.expr(fn, e.Key)
}
loc := element{
m: m,
k: emitConv(fn, key, t.Key(), e),
t: t.Elem(),
}
// We call assign() only because it takes care
// of any &-operation required in the recursive
// case, e.g.,
// map[int]*struct{}{0: {}} implies &struct{}{}.
// In-place update is of course impossible,
// and no storebuf is needed.
b.assign(fn, &loc, e.Value, true, nil, e)
}
sb.store(&address{addr: addr, expr: e}, m, e)
default:
panic("unexpected CompositeLit type: " + t.String())
}
}
func (b *builder) switchStmt(fn *Function, s *ast.SwitchStmt, label *lblock) {
if s.Tag == nil {
b.switchStmtDynamic(fn, s, label)
return
}
dynamic := false
for _, iclause := range s.Body.List {
clause := iclause.(*ast.CaseClause)
for _, cond := range clause.List {
if fn.Pkg.info.Types[unparen(cond)].Value == nil {
dynamic = true
break
}
}
}
if dynamic {
b.switchStmtDynamic(fn, s, label)
return
}
if s.Init != nil {
b.stmt(fn, s.Init)
}
entry := fn.currentBlock
tag := b.expr(fn, s.Tag)
heads := make([]*BasicBlock, 0, len(s.Body.List))
bodies := make([]*BasicBlock, len(s.Body.List))
conds := make([]Value, 0, len(s.Body.List))
hasDefault := false
done := fn.newBasicBlock("switch.done")
if label != nil {
label._break = done
}
for i, stmt := range s.Body.List {
body := fn.newBasicBlock(fmt.Sprintf("switch.body.%d", i))
bodies[i] = body
cas := stmt.(*ast.CaseClause)
if cas.List == nil {
// default branch
hasDefault = true
head := fn.newBasicBlock(fmt.Sprintf("switch.head.%d", i))
conds = append(conds, nil)
heads = append(heads, head)
fn.currentBlock = head
emitJump(fn, body, cas)
}
for j, cond := range stmt.(*ast.CaseClause).List {
fn.currentBlock = entry
head := fn.newBasicBlock(fmt.Sprintf("switch.head.%d.%d", i, j))
conds = append(conds, b.expr(fn, cond))
heads = append(heads, head)
fn.currentBlock = head
emitJump(fn, body, cond)
}
}
for i, stmt := range s.Body.List {
clause := stmt.(*ast.CaseClause)
body := bodies[i]
fn.currentBlock = body
fallthru := done
if i+1 < len(bodies) {
fallthru = bodies[i+1]
}
fn.targets = &targets{
tail: fn.targets,
_break: done,
_fallthrough: fallthru,
}
b.stmtList(fn, clause.Body)
fn.targets = fn.targets.tail
emitJump(fn, done, stmt)
}
if !hasDefault {
head := fn.newBasicBlock("switch.head.implicit-default")
body := fn.newBasicBlock("switch.body.implicit-default")
fn.currentBlock = head
emitJump(fn, body, s)
fn.currentBlock = body
emitJump(fn, done, s)
heads = append(heads, head)
conds = append(conds, nil)
}
if len(heads) != len(conds) {
panic(fmt.Sprintf("internal error: %d heads for %d conds", len(heads), len(conds)))
}
for _, head := range heads {
addEdge(entry, head)
}
fn.currentBlock = entry
entry.emit(&ConstantSwitch{
Tag: tag,
Conds: conds,
}, s)
fn.currentBlock = done
}
// switchStmt emits to fn code for the switch statement s, optionally
// labelled by label.
//
func (b *builder) switchStmtDynamic(fn *Function, s *ast.SwitchStmt, label *lblock) {
// We treat SwitchStmt like a sequential if-else chain.
// Multiway dispatch can be recovered later by irutil.Switches()
// to those cases that are free of side effects.
if s.Init != nil {
b.stmt(fn, s.Init)
}
kTrue := emitConst(fn, NewConst(constant.MakeBool(true), tBool))
var tagv Value = kTrue
var tagSource ast.Node = s
if s.Tag != nil {
tagv = b.expr(fn, s.Tag)
tagSource = s.Tag
}
// lifting only considers loads and stores, but we want different
// sigma nodes for the different comparisons. use a temporary and
// load it in every branch.
tag := fn.addLocal(tagv.Type(), tagSource)
emitStore(fn, tag, tagv, tagSource)
done := fn.newBasicBlock("switch.done")
if label != nil {
label._break = done
}
// We pull the default case (if present) down to the end.
// But each fallthrough label must point to the next
// body block in source order, so we preallocate a
// body block (fallthru) for the next case.
// Unfortunately this makes for a confusing block order.
var dfltBody *[]ast.Stmt
var dfltFallthrough *BasicBlock
var fallthru, dfltBlock *BasicBlock
ncases := len(s.Body.List)
for i, clause := range s.Body.List {
body := fallthru
if body == nil {
body = fn.newBasicBlock("switch.body") // first case only
}
// Preallocate body block for the next case.
fallthru = done
if i+1 < ncases {
fallthru = fn.newBasicBlock("switch.body")
}
cc := clause.(*ast.CaseClause)
if cc.List == nil {
// Default case.
dfltBody = &cc.Body
dfltFallthrough = fallthru
dfltBlock = body
continue
}
var nextCond *BasicBlock
for _, cond := range cc.List {
nextCond = fn.newBasicBlock("switch.next")
if tagv == kTrue {
// emit a proper if/else chain instead of a comparison
// of a value against true.
//
// NOTE(dh): adonovan had a todo saying "don't forget
// conversions though". As far as I can tell, there
// aren't any conversions that we need to take care of
// here. `case bool(a) && bool(b)` as well as `case
// bool(a && b)` are being taken care of by b.cond,
// and `case a` where a is not of type bool is
// invalid.
b.cond(fn, cond, body, nextCond)
} else {
cond := emitCompare(fn, token.EQL, emitLoad(fn, tag, cond), b.expr(fn, cond), cond)
emitIf(fn, cond, body, nextCond, cond.Source())
}
fn.currentBlock = nextCond
}
fn.currentBlock = body
fn.targets = &targets{
tail: fn.targets,
_break: done,
_fallthrough: fallthru,
}
b.stmtList(fn, cc.Body)
fn.targets = fn.targets.tail
emitJump(fn, done, s)
fn.currentBlock = nextCond
}
if dfltBlock != nil {
// The lack of a Source for the jump doesn't matter, block
// fusing will get rid of the jump later.
emitJump(fn, dfltBlock, s)
fn.currentBlock = dfltBlock
fn.targets = &targets{
tail: fn.targets,
_break: done,
_fallthrough: dfltFallthrough,
}
b.stmtList(fn, *dfltBody)
fn.targets = fn.targets.tail
}
emitJump(fn, done, s)
fn.currentBlock = done
}
func (b *builder) typeSwitchStmt(fn *Function, s *ast.TypeSwitchStmt, label *lblock) {
if s.Init != nil {
b.stmt(fn, s.Init)
}
var tag Value
switch e := s.Assign.(type) {
case *ast.ExprStmt: // x.(type)
tag = b.expr(fn, unparen(e.X).(*ast.TypeAssertExpr).X)
case *ast.AssignStmt: // y := x.(type)
tag = b.expr(fn, unparen(e.Rhs[0]).(*ast.TypeAssertExpr).X)
default:
panic("unreachable")
}
tagPtr := fn.addLocal(tag.Type(), tag.Source())
emitStore(fn, tagPtr, tag, tag.Source())
// +1 in case there's no explicit default case
heads := make([]*BasicBlock, 0, len(s.Body.List)+1)
entry := fn.currentBlock
done := fn.newBasicBlock("done")
if label != nil {
label._break = done
}
// set up type switch and constant switch, populate their conditions
tswtch := &TypeSwitch{
Tag: emitLoad(fn, tagPtr, tag.Source()),
Conds: make([]types.Type, 0, len(s.Body.List)+1),
}
cswtch := &ConstantSwitch{
Conds: make([]Value, 0, len(s.Body.List)+1),
}
rets := make([]types.Type, 0, len(s.Body.List)+1)
index := 0
var default_ *ast.CaseClause
for _, clause := range s.Body.List {
cc := clause.(*ast.CaseClause)
if obj := fn.Pkg.info.Implicits[cc]; obj != nil {
fn.addNamedLocal(obj, cc)
}
if cc.List == nil {
// default case
default_ = cc
} else {
for _, expr := range cc.List {
tswtch.Conds = append(tswtch.Conds, fn.Pkg.typeOf(expr))
cswtch.Conds = append(cswtch.Conds, emitConst(fn, intConst(int64(index))))
index++
}
if len(cc.List) == 1 {
rets = append(rets, fn.Pkg.typeOf(cc.List[0]))
} else {
for range cc.List {
rets = append(rets, tag.Type())
}
}
}
}
// default branch
rets = append(rets, tag.Type())
var vars []*types.Var
vars = append(vars, varIndex)
for _, typ := range rets {
vars = append(vars, anonVar(typ))
}
tswtch.setType(types.NewTuple(vars...))
// default branch
fn.currentBlock = entry
fn.emit(tswtch, s)
cswtch.Conds = append(cswtch.Conds, emitConst(fn, intConst(int64(-1))))
// in theory we should add a local and stores/loads for tswtch, to
// generate sigma nodes in the branches. however, there isn't any
// useful information we could possibly attach to it.
cswtch.Tag = emitExtract(fn, tswtch, 0, s)
fn.emit(cswtch, s)
// build heads and bodies
index = 0
for _, clause := range s.Body.List {
cc := clause.(*ast.CaseClause)
if cc.List == nil {
continue
}
body := fn.newBasicBlock("typeswitch.body")
for _, expr := range cc.List {
head := fn.newBasicBlock("typeswitch.head")
heads = append(heads, head)
fn.currentBlock = head
if obj := fn.Pkg.info.Implicits[cc]; obj != nil {
// In a switch y := x.(type), each case clause
// implicitly declares a distinct object y.
// In a single-type case, y has that type.
// In multi-type cases, 'case nil' and default,
// y has the same type as the interface operand.
l := fn.objects[obj]
if rets[index] == tUntypedNil {
emitStore(fn, l, emitConst(fn, nilConst(tswtch.Tag.Type())), s.Assign)
} else {
x := emitExtract(fn, tswtch, index+1, s.Assign)
emitStore(fn, l, x, nil)
}
}
emitJump(fn, body, expr)
index++
}
fn.currentBlock = body
fn.targets = &targets{
tail: fn.targets,
_break: done,
}
b.stmtList(fn, cc.Body)
fn.targets = fn.targets.tail
emitJump(fn, done, clause)
}
if default_ == nil {
// implicit default
heads = append(heads, done)
} else {
body := fn.newBasicBlock("typeswitch.default")
heads = append(heads, body)
fn.currentBlock = body
fn.targets = &targets{
tail: fn.targets,
_break: done,
}
if obj := fn.Pkg.info.Implicits[default_]; obj != nil {
l := fn.objects[obj]
x := emitExtract(fn, tswtch, index+1, s.Assign)
emitStore(fn, l, x, s)
}
b.stmtList(fn, default_.Body)
fn.targets = fn.targets.tail
emitJump(fn, done, s)
}
fn.currentBlock = entry
for _, head := range heads {
addEdge(entry, head)
}
fn.currentBlock = done
}
// selectStmt emits to fn code for the select statement s, optionally
// labelled by label.
//
func (b *builder) selectStmt(fn *Function, s *ast.SelectStmt, label *lblock) (noreturn bool) {
if len(s.Body.List) == 0 {
instr := &Select{Blocking: true}
instr.setType(types.NewTuple(varIndex, varOk))
fn.emit(instr, s)
fn.emit(new(Unreachable), s)
addEdge(fn.currentBlock, fn.Exit)
return true
}
// A blocking select of a single case degenerates to a
// simple send or receive.
// TODO(adonovan): opt: is this optimization worth its weight?
if len(s.Body.List) == 1 {
clause := s.Body.List[0].(*ast.CommClause)
if clause.Comm != nil {
b.stmt(fn, clause.Comm)
done := fn.newBasicBlock("select.done")
if label != nil {
label._break = done
}
fn.targets = &targets{
tail: fn.targets,
_break: done,
}
b.stmtList(fn, clause.Body)
fn.targets = fn.targets.tail
emitJump(fn, done, clause)
fn.currentBlock = done
return false
}
}
// First evaluate all channels in all cases, and find
// the directions of each state.
var states []*SelectState
blocking := true
debugInfo := fn.debugInfo()
for _, clause := range s.Body.List {
var st *SelectState
switch comm := clause.(*ast.CommClause).Comm.(type) {
case nil: // default case
blocking = false
continue
case *ast.SendStmt: // ch<- i
ch := b.expr(fn, comm.Chan)
st = &SelectState{
Dir: types.SendOnly,
Chan: ch,
Send: emitConv(fn, b.expr(fn, comm.Value),
ch.Type().Underlying().(*types.Chan).Elem(), comm),
Pos: comm.Arrow,
}
if debugInfo {
st.DebugNode = comm
}
case *ast.AssignStmt: // x := <-ch
recv := unparen(comm.Rhs[0]).(*ast.UnaryExpr)
st = &SelectState{
Dir: types.RecvOnly,
Chan: b.expr(fn, recv.X),
Pos: recv.OpPos,
}
if debugInfo {
st.DebugNode = recv
}
case *ast.ExprStmt: // <-ch
recv := unparen(comm.X).(*ast.UnaryExpr)
st = &SelectState{
Dir: types.RecvOnly,
Chan: b.expr(fn, recv.X),
Pos: recv.OpPos,
}
if debugInfo {
st.DebugNode = recv
}
}
states = append(states, st)
}
// We dispatch on the (fair) result of Select using a
// switch on the returned index.
sel := &Select{
States: states,
Blocking: blocking,
}
sel.source = s
var vars []*types.Var
vars = append(vars, varIndex, varOk)
for _, st := range states {
if st.Dir == types.RecvOnly {
tElem := st.Chan.Type().Underlying().(*types.Chan).Elem()
vars = append(vars, anonVar(tElem))
}
}
sel.setType(types.NewTuple(vars...))
fn.emit(sel, s)
idx := emitExtract(fn, sel, 0, s)
done := fn.newBasicBlock("select.done")
if label != nil {
label._break = done
}
entry := fn.currentBlock
swtch := &ConstantSwitch{
Tag: idx,
// one condition per case
Conds: make([]Value, 0, len(s.Body.List)+1),
}
// note that we don't need heads; a select case can only have a single condition
var bodies []*BasicBlock
state := 0
r := 2 // index in 'sel' tuple of value; increments if st.Dir==RECV
for _, cc := range s.Body.List {
clause := cc.(*ast.CommClause)
if clause.Comm == nil {
body := fn.newBasicBlock("select.default")
fn.currentBlock = body
bodies = append(bodies, body)
fn.targets = &targets{
tail: fn.targets,
_break: done,
}
b.stmtList(fn, clause.Body)
emitJump(fn, done, s)
fn.targets = fn.targets.tail
swtch.Conds = append(swtch.Conds, emitConst(fn, intConst(-1)))
continue
}
swtch.Conds = append(swtch.Conds, emitConst(fn, intConst(int64(state))))
body := fn.newBasicBlock("select.body")
fn.currentBlock = body
bodies = append(bodies, body)
fn.targets = &targets{
tail: fn.targets,
_break: done,
}
switch comm := clause.Comm.(type) {
case *ast.ExprStmt: // <-ch
if debugInfo {
v := emitExtract(fn, sel, r, comm)
emitDebugRef(fn, states[state].DebugNode.(ast.Expr), v, false)
}
r++
case *ast.AssignStmt: // x := <-states[state].Chan
if comm.Tok == token.DEFINE {
fn.addLocalForIdent(comm.Lhs[0].(*ast.Ident))
}
x := b.addr(fn, comm.Lhs[0], false) // non-escaping
v := emitExtract(fn, sel, r, comm)
if debugInfo {
emitDebugRef(fn, states[state].DebugNode.(ast.Expr), v, false)
}
x.store(fn, v, comm)
if len(comm.Lhs) == 2 { // x, ok := ...
if comm.Tok == token.DEFINE {
fn.addLocalForIdent(comm.Lhs[1].(*ast.Ident))
}
ok := b.addr(fn, comm.Lhs[1], false) // non-escaping
ok.store(fn, emitExtract(fn, sel, 1, comm), comm)
}
r++
}
b.stmtList(fn, clause.Body)
fn.targets = fn.targets.tail
emitJump(fn, done, s)
state++
}
fn.currentBlock = entry
fn.emit(swtch, s)
for _, body := range bodies {
addEdge(entry, body)
}
fn.currentBlock = done
return false
}
// forStmt emits to fn code for the for statement s, optionally
// labelled by label.
//
func (b *builder) forStmt(fn *Function, s *ast.ForStmt, label *lblock) {
// ...init...
// jump loop
// loop:
// if cond goto body else done
// body:
// ...body...
// jump post
// post: (target of continue)
// ...post...
// jump loop
// done: (target of break)
if s.Init != nil {
b.stmt(fn, s.Init)
}
body := fn.newBasicBlock("for.body")
done := fn.newBasicBlock("for.done") // target of 'break'
loop := body // target of back-edge
if s.Cond != nil {
loop = fn.newBasicBlock("for.loop")
}
cont := loop // target of 'continue'
if s.Post != nil {
cont = fn.newBasicBlock("for.post")
}
if label != nil {
label._break = done
label._continue = cont
}
emitJump(fn, loop, s)
fn.currentBlock = loop
if loop != body {
b.cond(fn, s.Cond, body, done)
fn.currentBlock = body
}
fn.targets = &targets{
tail: fn.targets,
_break: done,
_continue: cont,
}
b.stmt(fn, s.Body)
fn.targets = fn.targets.tail
emitJump(fn, cont, s)
if s.Post != nil {
fn.currentBlock = cont
b.stmt(fn, s.Post)
emitJump(fn, loop, s) // back-edge
}
fn.currentBlock = done
}
// rangeIndexed emits to fn the header for an integer-indexed loop
// over array, *array or slice value x.
// The v result is defined only if tv is non-nil.
// forPos is the position of the "for" token.
//
func (b *builder) rangeIndexed(fn *Function, x Value, tv types.Type, source ast.Node) (k, v Value, loop, done *BasicBlock) {
//
// length = len(x)
// index = -1
// loop: (target of continue)
// index++
// if index < length goto body else done
// body:
// k = index
// v = x[index]
// ...body...
// jump loop
// done: (target of break)
// Determine number of iterations.
var length Value
if arr, ok := deref(x.Type()).Underlying().(*types.Array); ok {
// For array or *array, the number of iterations is
// known statically thanks to the type. We avoid a
// data dependence upon x, permitting later dead-code
// elimination if x is pure, static unrolling, etc.
// Ranging over a nil *array may have >0 iterations.
// We still generate code for x, in case it has effects.
length = emitConst(fn, intConst(arr.Len()))
} else {
// length = len(x).
var c Call
c.Call.Value = makeLen(x.Type())
c.Call.Args = []Value{x}
c.setType(tInt)
length = fn.emit(&c, source)
}
index := fn.addLocal(tInt, source)
emitStore(fn, index, emitConst(fn, intConst(-1)), source)
loop = fn.newBasicBlock("rangeindex.loop")
emitJump(fn, loop, source)
fn.currentBlock = loop
incr := &BinOp{
Op: token.ADD,
X: emitLoad(fn, index, source),
Y: emitConst(fn, intConst(1)),
}
incr.setType(tInt)
emitStore(fn, index, fn.emit(incr, source), source)
body := fn.newBasicBlock("rangeindex.body")
done = fn.newBasicBlock("rangeindex.done")
emitIf(fn, emitCompare(fn, token.LSS, incr, length, source), body, done, source)
fn.currentBlock = body
k = emitLoad(fn, index, source)
if tv != nil {
switch t := x.Type().Underlying().(type) {
case *types.Array:
instr := &Index{
X: x,
Index: k,
}
instr.setType(t.Elem())
v = fn.emit(instr, source)
case *types.Pointer: // *array
instr := &IndexAddr{
X: x,
Index: k,
}
instr.setType(types.NewPointer(t.Elem().Underlying().(*types.Array).Elem()))
v = emitLoad(fn, fn.emit(instr, source), source)
case *types.Slice:
instr := &IndexAddr{
X: x,
Index: k,
}
instr.setType(types.NewPointer(t.Elem()))
v = emitLoad(fn, fn.emit(instr, source), source)
default:
panic("rangeIndexed x:" + t.String())
}
}
return
}
// rangeIter emits to fn the header for a loop using
// Range/Next/Extract to iterate over map or string value x.
// tk and tv are the types of the key/value results k and v, or nil
// if the respective component is not wanted.
//
func (b *builder) rangeIter(fn *Function, x Value, tk, tv types.Type, source ast.Node) (k, v Value, loop, done *BasicBlock) {
//
// it = range x
// loop: (target of continue)
// okv = next it (ok, key, value)
// ok = extract okv #0
// if ok goto body else done
// body:
// k = extract okv #1
// v = extract okv #2
// ...body...
// jump loop
// done: (target of break)
//
if tk == nil {
tk = tInvalid
}
if tv == nil {
tv = tInvalid
}
rng := &Range{X: x}
rng.setType(tRangeIter)
it := fn.emit(rng, source)
loop = fn.newBasicBlock("rangeiter.loop")
emitJump(fn, loop, source)
fn.currentBlock = loop
_, isString := x.Type().Underlying().(*types.Basic)
okv := &Next{
Iter: it,
IsString: isString,
}
okv.setType(types.NewTuple(
varOk,
newVar("k", tk),
newVar("v", tv),
))
fn.emit(okv, source)
body := fn.newBasicBlock("rangeiter.body")
done = fn.newBasicBlock("rangeiter.done")
emitIf(fn, emitExtract(fn, okv, 0, source), body, done, source)
fn.currentBlock = body
if tk != tInvalid {
k = emitExtract(fn, okv, 1, source)
}
if tv != tInvalid {
v = emitExtract(fn, okv, 2, source)
}
return
}
// rangeChan emits to fn the header for a loop that receives from
// channel x until it fails.
// tk is the channel's element type, or nil if the k result is
// not wanted
// pos is the position of the '=' or ':=' token.
//
func (b *builder) rangeChan(fn *Function, x Value, tk types.Type, source ast.Node) (k Value, loop, done *BasicBlock) {
//
// loop: (target of continue)
// ko = <-x (key, ok)
// ok = extract ko #1
// if ok goto body else done
// body:
// k = extract ko #0
// ...
// goto loop
// done: (target of break)
loop = fn.newBasicBlock("rangechan.loop")
emitJump(fn, loop, source)
fn.currentBlock = loop
retv := emitRecv(fn, x, true, types.NewTuple(newVar("k", x.Type().Underlying().(*types.Chan).Elem()), varOk), source)
body := fn.newBasicBlock("rangechan.body")
done = fn.newBasicBlock("rangechan.done")
emitIf(fn, emitExtract(fn, retv, 1, source), body, done, source)
fn.currentBlock = body
if tk != nil {
k = emitExtract(fn, retv, 0, source)
}
return
}
// rangeStmt emits to fn code for the range statement s, optionally
// labelled by label.
//
func (b *builder) rangeStmt(fn *Function, s *ast.RangeStmt, label *lblock, source ast.Node) {
var tk, tv types.Type
if s.Key != nil && !isBlankIdent(s.Key) {
tk = fn.Pkg.typeOf(s.Key)
}
if s.Value != nil && !isBlankIdent(s.Value) {
tv = fn.Pkg.typeOf(s.Value)
}
// If iteration variables are defined (:=), this
// occurs once outside the loop.
//
// Unlike a short variable declaration, a RangeStmt
// using := never redeclares an existing variable; it
// always creates a new one.
if s.Tok == token.DEFINE {
if tk != nil {
fn.addLocalForIdent(s.Key.(*ast.Ident))
}
if tv != nil {
fn.addLocalForIdent(s.Value.(*ast.Ident))
}
}
x := b.expr(fn, s.X)
var k, v Value
var loop, done *BasicBlock
switch rt := x.Type().Underlying().(type) {
case *types.Slice, *types.Array, *types.Pointer: // *array
k, v, loop, done = b.rangeIndexed(fn, x, tv, source)
case *types.Chan:
k, loop, done = b.rangeChan(fn, x, tk, source)
case *types.Map, *types.Basic: // string
k, v, loop, done = b.rangeIter(fn, x, tk, tv, source)
default:
panic("Cannot range over: " + rt.String())
}
// Evaluate both LHS expressions before we update either.
var kl, vl lvalue
if tk != nil {
kl = b.addr(fn, s.Key, false) // non-escaping
}
if tv != nil {
vl = b.addr(fn, s.Value, false) // non-escaping
}
if tk != nil {
kl.store(fn, k, s)
}
if tv != nil {
vl.store(fn, v, s)
}
if label != nil {
label._break = done
label._continue = loop
}
fn.targets = &targets{
tail: fn.targets,
_break: done,
_continue: loop,
}
b.stmt(fn, s.Body)
fn.targets = fn.targets.tail
emitJump(fn, loop, source) // back-edge
fn.currentBlock = done
}
// stmt lowers statement s to IR form, emitting code to fn.
func (b *builder) stmt(fn *Function, _s ast.Stmt) {
// The label of the current statement. If non-nil, its _goto
// target is always set; its _break and _continue are set only
// within the body of switch/typeswitch/select/for/range.
// It is effectively an additional default-nil parameter of stmt().
var label *lblock
start:
switch s := _s.(type) {
case *ast.EmptyStmt:
// ignore. (Usually removed by gofmt.)
case *ast.DeclStmt: // Con, Var or Typ
d := s.Decl.(*ast.GenDecl)
if d.Tok == token.VAR {
for _, spec := range d.Specs {
if vs, ok := spec.(*ast.ValueSpec); ok {
b.localValueSpec(fn, vs)
}
}
}
case *ast.LabeledStmt:
label = fn.labelledBlock(s.Label)
emitJump(fn, label._goto, s)
fn.currentBlock = label._goto
_s = s.Stmt
goto start // effectively: tailcall stmt(fn, s.Stmt, label)
case *ast.ExprStmt:
b.expr(fn, s.X)
case *ast.SendStmt:
instr := &Send{
Chan: b.expr(fn, s.Chan),
X: emitConv(fn, b.expr(fn, s.Value),
fn.Pkg.typeOf(s.Chan).Underlying().(*types.Chan).Elem(), s),
}
fn.emit(instr, s)
case *ast.IncDecStmt:
op := token.ADD
if s.Tok == token.DEC {
op = token.SUB
}
loc := b.addr(fn, s.X, false)
b.assignOp(fn, loc, emitConst(fn, NewConst(constant.MakeInt64(1), loc.typ())), op, s)
case *ast.AssignStmt:
switch s.Tok {
case token.ASSIGN, token.DEFINE:
b.assignStmt(fn, s.Lhs, s.Rhs, s.Tok == token.DEFINE, _s)
default: // +=, etc.
op := s.Tok + token.ADD - token.ADD_ASSIGN
b.assignOp(fn, b.addr(fn, s.Lhs[0], false), b.expr(fn, s.Rhs[0]), op, s)
}
case *ast.GoStmt:
// The "intrinsics" new/make/len/cap are forbidden here.
// panic is treated like an ordinary function call.
v := Go{}
b.setCall(fn, s.Call, &v.Call)
fn.emit(&v, s)
case *ast.DeferStmt:
// The "intrinsics" new/make/len/cap are forbidden here.
// panic is treated like an ordinary function call.
v := Defer{}
b.setCall(fn, s.Call, &v.Call)
fn.hasDefer = true
fn.emit(&v, s)
case *ast.ReturnStmt:
// TODO(dh): we could emit tighter position information by
// using the ith returned expression
var results []Value
if len(s.Results) == 1 && fn.Signature.Results().Len() > 1 {
// Return of one expression in a multi-valued function.
tuple := b.exprN(fn, s.Results[0])
ttuple := tuple.Type().(*types.Tuple)
for i, n := 0, ttuple.Len(); i < n; i++ {
results = append(results,
emitConv(fn, emitExtract(fn, tuple, i, s),
fn.Signature.Results().At(i).Type(), s))
}
} else {
// 1:1 return, or no-arg return in non-void function.
for i, r := range s.Results {
v := emitConv(fn, b.expr(fn, r), fn.Signature.Results().At(i).Type(), s)
results = append(results, v)
}
}
ret := fn.results()
for i, r := range results {
emitStore(fn, ret[i], r, s)
}
emitJump(fn, fn.Exit, s)
fn.currentBlock = fn.newBasicBlock("unreachable")
case *ast.BranchStmt:
var block *BasicBlock
switch s.Tok {
case token.BREAK:
if s.Label != nil {
block = fn.labelledBlock(s.Label)._break
} else {
for t := fn.targets; t != nil && block == nil; t = t.tail {
block = t._break
}
}
case token.CONTINUE:
if s.Label != nil {
block = fn.labelledBlock(s.Label)._continue
} else {
for t := fn.targets; t != nil && block == nil; t = t.tail {
block = t._continue
}
}
case token.FALLTHROUGH:
for t := fn.targets; t != nil && block == nil; t = t.tail {
block = t._fallthrough
}
case token.GOTO:
block = fn.labelledBlock(s.Label)._goto
}
j := emitJump(fn, block, s)
j.Comment = s.Tok.String()
fn.currentBlock = fn.newBasicBlock("unreachable")
case *ast.BlockStmt:
b.stmtList(fn, s.List)
case *ast.IfStmt:
if s.Init != nil {
b.stmt(fn, s.Init)
}
then := fn.newBasicBlock("if.then")
done := fn.newBasicBlock("if.done")
els := done
if s.Else != nil {
els = fn.newBasicBlock("if.else")
}
instr := b.cond(fn, s.Cond, then, els)
instr.source = s
fn.currentBlock = then
b.stmt(fn, s.Body)
emitJump(fn, done, s)
if s.Else != nil {
fn.currentBlock = els
b.stmt(fn, s.Else)
emitJump(fn, done, s)
}
fn.currentBlock = done
case *ast.SwitchStmt:
b.switchStmt(fn, s, label)
case *ast.TypeSwitchStmt:
b.typeSwitchStmt(fn, s, label)
case *ast.SelectStmt:
if b.selectStmt(fn, s, label) {
// the select has no cases, it blocks forever
fn.currentBlock = fn.newBasicBlock("unreachable")
}
case *ast.ForStmt:
b.forStmt(fn, s, label)
case *ast.RangeStmt:
b.rangeStmt(fn, s, label, s)
default:
panic(fmt.Sprintf("unexpected statement kind: %T", s))
}
}
// buildFunction builds IR code for the body of function fn. Idempotent.
func (b *builder) buildFunction(fn *Function) {
if fn.Blocks != nil {
return // building already started
}
var recvField *ast.FieldList
var body *ast.BlockStmt
var functype *ast.FuncType
switch n := fn.source.(type) {
case nil:
return // not a Go source function. (Synthetic, or from object file.)
case *ast.FuncDecl:
functype = n.Type
recvField = n.Recv
body = n.Body
case *ast.FuncLit:
functype = n.Type
body = n.Body
default:
panic(n)
}
if fn.Package().Pkg.Path() == "syscall" && fn.Name() == "Exit" {
// syscall.Exit is a stub and the way os.Exit terminates the
// process. Note that there are other functions in the runtime
// that also terminate or unwind that we cannot analyze.
// However, they aren't stubs, so buildExits ends up getting
// called on them, so that's where we handle those special
// cases.
fn.NoReturn = AlwaysExits
}
if body == nil {
// External function.
if fn.Params == nil {
// This condition ensures we add a non-empty
// params list once only, but we may attempt
// the degenerate empty case repeatedly.
// TODO(adonovan): opt: don't do that.
// We set Function.Params even though there is no body
// code to reference them. This simplifies clients.
if recv := fn.Signature.Recv(); recv != nil {
// XXX synthesize an ast.Node
fn.addParamObj(recv, nil)
}
params := fn.Signature.Params()
for i, n := 0, params.Len(); i < n; i++ {
// XXX synthesize an ast.Node
fn.addParamObj(params.At(i), nil)
}
}
return
}
if fn.Prog.mode&LogSource != 0 {
defer logStack("build function %s @ %s", fn, fn.Prog.Fset.Position(fn.Pos()))()
}
fn.blocksets = b.blocksets
fn.Blocks = make([]*BasicBlock, 0, avgBlocks)
fn.startBody()
fn.createSyntacticParams(recvField, functype)
fn.exitBlock()
b.stmt(fn, body)
if cb := fn.currentBlock; cb != nil && (cb == fn.Blocks[0] || cb.Preds != nil) {
// Control fell off the end of the function's body block.
//
// Block optimizations eliminate the current block, if
// unreachable. It is a builder invariant that
// if this no-arg return is ill-typed for
// fn.Signature.Results, this block must be
// unreachable. The sanity checker checks this.
// fn.emit(new(RunDefers))
// fn.emit(new(Return))
emitJump(fn, fn.Exit, nil)
}
optimizeBlocks(fn)
buildFakeExits(fn)
b.buildExits(fn)
b.addUnreachables(fn)
fn.finishBody()
b.blocksets = fn.blocksets
fn.functionBody = nil
}
// buildFuncDecl builds IR code for the function or method declared
// by decl in package pkg.
//
func (b *builder) buildFuncDecl(pkg *Package, decl *ast.FuncDecl) {
id := decl.Name
if isBlankIdent(id) {
return // discard
}
fn := pkg.values[pkg.info.Defs[id]].(*Function)
if decl.Recv == nil && id.Name == "init" {
var v Call
v.Call.Value = fn
v.setType(types.NewTuple())
pkg.init.emit(&v, decl)
}
fn.source = decl
b.buildFunction(fn)
}
// Build calls Package.Build for each package in prog.
//
// Build is intended for whole-program analysis; a typical compiler
// need only build a single package.
//
// Build is idempotent and thread-safe.
//
func (prog *Program) Build() {
for _, p := range prog.packages {
p.Build()
}
}
// Build builds IR code for all functions and vars in package p.
//
// Precondition: CreatePackage must have been called for all of p's
// direct imports (and hence its direct imports must have been
// error-free).
//
// Build is idempotent and thread-safe.
//
func (p *Package) Build() { p.buildOnce.Do(p.build) }
func (p *Package) build() {
if p.info == nil {
return // synthetic package, e.g. "testmain"
}
// Ensure we have runtime type info for all exported members.
// TODO(adonovan): ideally belongs in memberFromObject, but
// that would require package creation in topological order.
for name, mem := range p.Members {
if ast.IsExported(name) {
p.Prog.needMethodsOf(mem.Type())
}
}
if p.Prog.mode&LogSource != 0 {
defer logStack("build %s", p)()
}
init := p.init
init.startBody()
init.exitBlock()
var done *BasicBlock
// Make init() skip if package is already initialized.
initguard := p.Var("init$guard")
doinit := init.newBasicBlock("init.start")
done = init.Exit
emitIf(init, emitLoad(init, initguard, nil), done, doinit, nil)
init.currentBlock = doinit
emitStore(init, initguard, emitConst(init, NewConst(constant.MakeBool(true), tBool)), nil)
// Call the init() function of each package we import.
for _, pkg := range p.Pkg.Imports() {
prereq := p.Prog.packages[pkg]
if prereq == nil {
panic(fmt.Sprintf("Package(%q).Build(): unsatisfied import: Program.CreatePackage(%q) was not called", p.Pkg.Path(), pkg.Path()))
}
var v Call
v.Call.Value = prereq.init
v.setType(types.NewTuple())
init.emit(&v, nil)
}
b := builder{
printFunc: p.printFunc,
}
// Initialize package-level vars in correct order.
for _, varinit := range p.info.InitOrder {
if init.Prog.mode&LogSource != 0 {
fmt.Fprintf(os.Stderr, "build global initializer %v @ %s\n",
varinit.Lhs, p.Prog.Fset.Position(varinit.Rhs.Pos()))
}
if len(varinit.Lhs) == 1 {
// 1:1 initialization: var x, y = a(), b()
var lval lvalue
if v := varinit.Lhs[0]; v.Name() != "_" {
lval = &address{addr: p.values[v].(*Global)}
} else {
lval = blank{}
}
// TODO(dh): do emit position information
b.assign(init, lval, varinit.Rhs, true, nil, nil)
} else {
// n:1 initialization: var x, y := f()
tuple := b.exprN(init, varinit.Rhs)
for i, v := range varinit.Lhs {
if v.Name() == "_" {
continue
}
emitStore(init, p.values[v].(*Global), emitExtract(init, tuple, i, nil), nil)
}
}
}
// Build all package-level functions, init functions
// and methods, including unreachable/blank ones.
// We build them in source order, but it's not significant.
for _, file := range p.files {
for _, decl := range file.Decls {
if decl, ok := decl.(*ast.FuncDecl); ok {
b.buildFuncDecl(p, decl)
}
}
}
// Finish up init().
emitJump(init, done, nil)
init.finishBody()
p.info = nil // We no longer need ASTs or go/types deductions.
if p.Prog.mode&SanityCheckFunctions != 0 {
sanityCheckPackage(p)
}
}
// Like ObjectOf, but panics instead of returning nil.
// Only valid during p's create and build phases.
func (p *Package) objectOf(id *ast.Ident) types.Object {
if o := p.info.ObjectOf(id); o != nil {
return o
}
panic(fmt.Sprintf("no types.Object for ast.Ident %s @ %s",
id.Name, p.Prog.Fset.Position(id.Pos())))
}
// Like TypeOf, but panics instead of returning nil.
// Only valid during p's create and build phases.
func (p *Package) typeOf(e ast.Expr) types.Type {
if T := p.info.TypeOf(e); T != nil {
return T
}
panic(fmt.Sprintf("no type for %T @ %s",
e, p.Prog.Fset.Position(e.Pos())))
}
go-tools-2021.1.2/go/ir/builder_go117_test.go 0000664 0000000 0000000 00000002416 14143223131 0020450 0 ustar 00root root 0000000 0000000 // Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.17
// +build go1.17
package ir_test
import (
"go/ast"
"go/importer"
"go/parser"
"go/token"
"go/types"
"testing"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
)
func TestBuildPackageGo117(t *testing.T) {
tests := []struct {
name string
src string
importer types.Importer
}{
{"slice to array pointer", "package p; var s []byte; var _ = (*[4]byte)(s)", nil},
{"unsafe slice", `package p; import "unsafe"; var _ = unsafe.Add(nil, 0)`, importer.Default()},
{"unsafe add", `package p; import "unsafe"; var _ = unsafe.Slice((*int)(nil), 0)`, importer.Default()},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "p.go", tc.src, parser.ParseComments)
if err != nil {
t.Error(err)
}
files := []*ast.File{f}
pkg := types.NewPackage("p", "")
conf := &types.Config{Importer: tc.importer}
if _, _, err := irutil.BuildPackage(conf, fset, pkg, files, ir.SanityCheckFunctions); err != nil {
t.Errorf("unexpected error: %v", err)
}
})
}
}
go-tools-2021.1.2/go/ir/builder_test.go 0000664 0000000 0000000 00000031041 14143223131 0017526 0 ustar 00root root 0000000 0000000 // 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.
//lint:file-ignore SA1019 go/ssa's test suite is built around the deprecated go/loader. We'll leave fixing that to upstream.
package ir_test
import (
"bytes"
"go/ast"
"go/importer"
"go/parser"
"go/token"
"go/types"
"os"
"reflect"
"sort"
"testing"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
"golang.org/x/tools/go/loader"
)
func isEmpty(f *ir.Function) bool { return f.Blocks == nil }
// Tests that programs partially loaded from gc object files contain
// functions with no code for the external portions, but are otherwise ok.
func TestBuildPackage(t *testing.T) {
input := `
package main
import (
"bytes"
"io"
"testing"
)
func main() {
var t testing.T
t.Parallel() // static call to external declared method
t.Fail() // static call to promoted external declared method
testing.Short() // static call to external package-level function
var w io.Writer = new(bytes.Buffer)
w.Write(nil) // interface invoke of external declared method
}
`
// Parse the file.
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "input.go", input, 0)
if err != nil {
t.Error(err)
return
}
// Build an IR program from the parsed file.
// Load its dependencies from gc binary export data.
mainPkg, _, err := irutil.BuildPackage(&types.Config{Importer: importer.Default()}, fset,
types.NewPackage("main", ""), []*ast.File{f}, ir.SanityCheckFunctions)
if err != nil {
t.Error(err)
return
}
// The main package, its direct and indirect dependencies are loaded.
deps := []string{
// directly imported dependencies:
"bytes", "io", "testing",
// indirect dependencies mentioned by
// the direct imports' export data
"sync", "unicode", "time",
}
prog := mainPkg.Prog
all := prog.AllPackages()
if len(all) <= len(deps) {
t.Errorf("unexpected set of loaded packages: %q", all)
}
for _, path := range deps {
pkg := prog.ImportedPackage(path)
if pkg == nil {
t.Errorf("package not loaded: %q", path)
continue
}
// External packages should have no function bodies (except for wrappers).
isExt := pkg != mainPkg
// init()
if isExt && !isEmpty(pkg.Func("init")) {
t.Errorf("external package %s has non-empty init", pkg)
} else if !isExt && isEmpty(pkg.Func("init")) {
t.Errorf("main package %s has empty init", pkg)
}
for _, mem := range pkg.Members {
switch mem := mem.(type) {
case *ir.Function:
// Functions at package level.
if isExt && !isEmpty(mem) {
t.Errorf("external function %s is non-empty", mem)
} else if !isExt && isEmpty(mem) {
t.Errorf("function %s is empty", mem)
}
case *ir.Type:
// Methods of named types T.
// (In this test, all exported methods belong to *T not T.)
if !isExt {
t.Fatalf("unexpected name type in main package: %s", mem)
}
mset := prog.MethodSets.MethodSet(types.NewPointer(mem.Type()))
for i, n := 0, mset.Len(); i < n; i++ {
m := prog.MethodValue(mset.At(i))
// For external types, only synthetic wrappers have code.
expExt := m.Synthetic != ir.SyntheticWrapper
if expExt && !isEmpty(m) {
t.Errorf("external method %s is non-empty: %s",
m, m.Synthetic)
} else if !expExt && isEmpty(m) {
t.Errorf("method function %s is empty: %s",
m, m.Synthetic)
}
}
}
}
}
expectedCallee := []string{
"(*testing.T).Parallel",
"(*testing.common).Fail",
"testing.Short",
"N/A",
}
callNum := 0
for _, b := range mainPkg.Func("main").Blocks {
for _, instr := range b.Instrs {
switch instr := instr.(type) {
case ir.CallInstruction:
call := instr.Common()
if want := expectedCallee[callNum]; want != "N/A" {
got := call.StaticCallee().String()
if want != got {
t.Errorf("call #%d from main.main: got callee %s, want %s",
callNum, got, want)
}
}
callNum++
}
}
}
if callNum != 4 {
t.Errorf("in main.main: got %d calls, want %d", callNum, 4)
}
}
// TestRuntimeTypes tests that (*Program).RuntimeTypes() includes all necessary types.
func TestRuntimeTypes(t *testing.T) {
tests := []struct {
input string
want []string
}{
// An exported package-level type is needed.
{`package A; type T struct{}; func (T) f() {}`,
[]string{"*p.T", "p.T"},
},
// An unexported package-level type is not needed.
{`package B; type t struct{}; func (t) f() {}`,
nil,
},
// Subcomponents of type of exported package-level var are needed.
{`package C; import "bytes"; var V struct {*bytes.Buffer}`,
[]string{"*bytes.Buffer", "*struct{*bytes.Buffer}", "struct{*bytes.Buffer}"},
},
// Subcomponents of type of unexported package-level var are not needed.
{`package D; import "bytes"; var v struct {*bytes.Buffer}`,
nil,
},
// Subcomponents of type of exported package-level function are needed.
{`package E; import "bytes"; func F(struct {*bytes.Buffer}) {}`,
[]string{"*bytes.Buffer", "struct{*bytes.Buffer}"},
},
// Subcomponents of type of unexported package-level function are not needed.
{`package F; import "bytes"; func f(struct {*bytes.Buffer}) {}`,
nil,
},
// Subcomponents of type of exported method of uninstantiated unexported type are not needed.
{`package G; import "bytes"; type x struct{}; func (x) G(struct {*bytes.Buffer}) {}; var v x`,
nil,
},
// ...unless used by MakeInterface.
{`package G2; import "bytes"; type x struct{}; func (x) G(struct {*bytes.Buffer}) {}; var v interface{} = x{}`,
[]string{"*bytes.Buffer", "*p.x", "p.x", "struct{*bytes.Buffer}"},
},
// Subcomponents of type of unexported method are not needed.
{`package I; import "bytes"; type X struct{}; func (X) G(struct {*bytes.Buffer}) {}`,
[]string{"*bytes.Buffer", "*p.X", "p.X", "struct{*bytes.Buffer}"},
},
// Local types aren't needed.
{`package J; import "bytes"; func f() { type T struct {*bytes.Buffer}; var t T; _ = t }`,
nil,
},
// ...unless used by MakeInterface.
{`package K; import "bytes"; func f() { type T struct {*bytes.Buffer}; _ = interface{}(T{}) }`,
[]string{"*bytes.Buffer", "*p.T", "p.T"},
},
// Types used as operand of MakeInterface are needed.
{`package L; import "bytes"; func f() { _ = interface{}(struct{*bytes.Buffer}{}) }`,
[]string{"*bytes.Buffer", "struct{*bytes.Buffer}"},
},
// MakeInterface is optimized away when storing to a blank.
{`package M; import "bytes"; var _ interface{} = struct{*bytes.Buffer}{}`,
nil,
},
}
for _, test := range tests {
// Parse the file.
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "input.go", test.input, 0)
if err != nil {
t.Errorf("test %q: %s", test.input[:15], err)
continue
}
// Create a single-file main package.
// Load dependencies from gc binary export data.
irpkg, _, err := irutil.BuildPackage(&types.Config{Importer: importer.Default()}, fset,
types.NewPackage("p", ""), []*ast.File{f}, ir.SanityCheckFunctions)
if err != nil {
t.Errorf("test %q: %s", test.input[:15], err)
continue
}
var typstrs []string
for _, T := range irpkg.Prog.RuntimeTypes() {
typstrs = append(typstrs, T.String())
}
sort.Strings(typstrs)
if !reflect.DeepEqual(typstrs, test.want) {
t.Errorf("test 'package %s': got %q, want %q",
f.Name.Name, typstrs, test.want)
}
}
}
// TestInit tests that synthesized init functions are correctly formed.
func TestInit(t *testing.T) {
tests := []struct {
mode ir.BuilderMode
input, want string
}{
{0, `package A; import _ "errors"; var i int = 42`,
`# Name: A.init
# Package: A
# Synthetic: package initializer
func init():
b0: # entry
t1 = Const {true}
t2 = Const {42}
t3 = Load init$guard
If t3 β b1 b2
b1: β b0 b2 # exit
Return
b2: β b0 # init.start
Store {bool} init$guard t1
t7 = Call <()> errors.init
Store {int} i t2
Jump β b1
`},
}
for _, test := range tests {
// Create a single-file main package.
var conf loader.Config
f, err := conf.ParseFile("", test.input)
if err != nil {
t.Errorf("test %q: %s", test.input[:15], err)
continue
}
conf.CreateFromFiles(f.Name.Name, f)
lprog, err := conf.Load()
if err != nil {
t.Errorf("test 'package %s': Load: %s", f.Name.Name, err)
continue
}
prog := irutil.CreateProgram(lprog, test.mode)
mainPkg := prog.Package(lprog.Created[0].Pkg)
prog.Build()
initFunc := mainPkg.Func("init")
if initFunc == nil {
t.Errorf("test 'package %s': no init function", f.Name.Name)
continue
}
var initbuf bytes.Buffer
_, err = initFunc.WriteTo(&initbuf)
if err != nil {
t.Errorf("test 'package %s': WriteTo: %s", f.Name.Name, err)
continue
}
if initbuf.String() != test.want {
t.Errorf("test 'package %s': got %s, want %s", f.Name.Name, initbuf.String(), test.want)
}
}
}
// TestSyntheticFuncs checks that the expected synthetic functions are
// created, reachable, and not duplicated.
func TestSyntheticFuncs(t *testing.T) {
const input = `package P
type T int
func (T) f() int
func (*T) g() int
var (
// thunks
a = T.f
b = T.f
c = (struct{T}).f
d = (struct{T}).f
e = (*T).g
f = (*T).g
g = (struct{*T}).g
h = (struct{*T}).g
// bounds
i = T(0).f
j = T(0).f
k = new(T).g
l = new(T).g
// wrappers
m interface{} = struct{T}{}
n interface{} = struct{T}{}
o interface{} = struct{*T}{}
p interface{} = struct{*T}{}
q interface{} = new(struct{T})
r interface{} = new(struct{T})
s interface{} = new(struct{*T})
t interface{} = new(struct{*T})
)
`
// Parse
var conf loader.Config
f, err := conf.ParseFile("", input)
if err != nil {
t.Fatalf("parse: %v", err)
}
conf.CreateFromFiles(f.Name.Name, f)
// Load
lprog, err := conf.Load()
if err != nil {
t.Fatalf("Load: %v", err)
}
// Create and build IR
prog := irutil.CreateProgram(lprog, 0)
prog.Build()
// Enumerate reachable synthetic functions
want := map[string]ir.Synthetic{
"(*P.T).g$bound": ir.SyntheticBound,
"(P.T).f$bound": ir.SyntheticBound,
"(*P.T).g$thunk": ir.SyntheticThunk,
"(P.T).f$thunk": ir.SyntheticThunk,
"(struct{*P.T}).g$thunk": ir.SyntheticThunk,
"(struct{P.T}).f$thunk": ir.SyntheticThunk,
"(*P.T).f": ir.SyntheticWrapper,
"(*struct{*P.T}).f": ir.SyntheticWrapper,
"(*struct{*P.T}).g": ir.SyntheticWrapper,
"(*struct{P.T}).f": ir.SyntheticWrapper,
"(*struct{P.T}).g": ir.SyntheticWrapper,
"(struct{*P.T}).f": ir.SyntheticWrapper,
"(struct{*P.T}).g": ir.SyntheticWrapper,
"(struct{P.T}).f": ir.SyntheticWrapper,
"P.init": ir.SyntheticPackageInitializer,
}
for fn := range irutil.AllFunctions(prog) {
if fn.Synthetic == 0 {
continue
}
name := fn.String()
wantDescr, ok := want[name]
if !ok {
t.Errorf("got unexpected/duplicate func: %q: %q", name, fn.Synthetic)
continue
}
delete(want, name)
if wantDescr != fn.Synthetic {
t.Errorf("(%s).Synthetic = %q, want %q", name, fn.Synthetic, wantDescr)
}
}
for fn, descr := range want {
t.Errorf("want func: %q: %q", fn, descr)
}
}
// TestPhiElimination ensures that dead phis, including those that
// participate in a cycle, are properly eliminated.
func TestPhiElimination(t *testing.T) {
const input = `
package p
func f() error
func g(slice []int) {
for {
for range slice {
// e should not be lifted to a dead Ο-node.
e := f()
h(e)
}
}
}
func h(error)
`
// The IR code for this function should look something like this:
// 0:
// jump 1
// 1:
// t0 = len(slice)
// jump 2
// 2:
// t1 = phi [1: -1:int, 3: t2]
// t2 = t1 + 1:int
// t3 = t2 < t0
// if t3 goto 3 else 1
// 3:
// t4 = f()
// t5 = h(t4)
// jump 2
//
// But earlier versions of the IR construction algorithm would
// additionally generate this cycle of dead phis:
//
// 1:
// t7 = phi [0: nil:error, 2: t8] #e
// ...
// 2:
// t8 = phi [1: t7, 3: t4] #e
// ...
// Parse
var conf loader.Config
f, err := conf.ParseFile("", input)
if err != nil {
t.Fatalf("parse: %v", err)
}
conf.CreateFromFiles("p", f)
// Load
lprog, err := conf.Load()
if err != nil {
t.Fatalf("Load: %v", err)
}
// Create and build IR
prog := irutil.CreateProgram(lprog, 0)
p := prog.Package(lprog.Package("p").Pkg)
p.Build()
g := p.Func("g")
phis := 0
for _, b := range g.Blocks {
for _, instr := range b.Instrs {
if _, ok := instr.(*ir.Phi); ok {
phis++
}
}
}
if expected := 3; phis != expected {
g.WriteTo(os.Stderr)
t.Errorf("expected %d Phi nodes (for the range index), got %d", expected, phis)
}
}
go-tools-2021.1.2/go/ir/const.go 0000664 0000000 0000000 00000010016 14143223131 0016166 0 ustar 00root root 0000000 0000000 // 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 ir
// This file defines the Const SSA value type.
import (
"fmt"
"go/constant"
"go/types"
"strconv"
)
// NewConst returns a new constant of the specified value and type.
// val must be valid according to the specification of Const.Value.
//
func NewConst(val constant.Value, typ types.Type) *Const {
return &Const{
register: register{
typ: typ,
},
Value: val,
}
}
// intConst returns an 'int' constant that evaluates to i.
// (i is an int64 in case the host is narrower than the target.)
func intConst(i int64) *Const {
return NewConst(constant.MakeInt64(i), tInt)
}
// nilConst returns a nil constant of the specified type, which may
// be any reference type, including interfaces.
//
func nilConst(typ types.Type) *Const {
return NewConst(nil, typ)
}
// stringConst returns a 'string' constant that evaluates to s.
func stringConst(s string) *Const {
return NewConst(constant.MakeString(s), tString)
}
// zeroConst returns a new "zero" constant of the specified type,
// which must not be an array or struct type: the zero values of
// aggregates are well-defined but cannot be represented by Const.
//
func zeroConst(t types.Type) *Const {
switch t := t.(type) {
case *types.Basic:
switch {
case t.Info()&types.IsBoolean != 0:
return NewConst(constant.MakeBool(false), t)
case t.Info()&types.IsNumeric != 0:
return NewConst(constant.MakeInt64(0), t)
case t.Info()&types.IsString != 0:
return NewConst(constant.MakeString(""), t)
case t.Kind() == types.UnsafePointer:
fallthrough
case t.Kind() == types.UntypedNil:
return nilConst(t)
default:
panic(fmt.Sprint("zeroConst for unexpected type:", t))
}
case *types.Pointer, *types.Slice, *types.Interface, *types.Chan, *types.Map, *types.Signature:
return nilConst(t)
case *types.Named:
return NewConst(zeroConst(t.Underlying()).Value, t)
case *types.Array, *types.Struct, *types.Tuple:
panic(fmt.Sprint("zeroConst applied to aggregate:", t))
}
panic(fmt.Sprint("zeroConst: unexpected ", t))
}
func (c *Const) RelString(from *types.Package) string {
var p string
if c.Value == nil {
p = "nil"
} else if c.Value.Kind() == constant.String {
v := constant.StringVal(c.Value)
const max = 20
// TODO(adonovan): don't cut a rune in half.
if len(v) > max {
v = v[:max-3] + "..." // abbreviate
}
p = strconv.Quote(v)
} else {
p = c.Value.String()
}
return fmt.Sprintf("Const <%s> {%s}", relType(c.Type(), from), p)
}
func (c *Const) String() string {
return c.RelString(c.Parent().pkg())
}
// IsNil returns true if this constant represents a typed or untyped nil value.
func (c *Const) IsNil() bool {
return c.Value == nil
}
// Int64 returns the numeric value of this constant truncated to fit
// a signed 64-bit integer.
//
func (c *Const) Int64() int64 {
switch x := constant.ToInt(c.Value); x.Kind() {
case constant.Int:
if i, ok := constant.Int64Val(x); ok {
return i
}
return 0
case constant.Float:
f, _ := constant.Float64Val(x)
return int64(f)
}
panic(fmt.Sprintf("unexpected constant value: %T", c.Value))
}
// Uint64 returns the numeric value of this constant truncated to fit
// an unsigned 64-bit integer.
//
func (c *Const) Uint64() uint64 {
switch x := constant.ToInt(c.Value); x.Kind() {
case constant.Int:
if u, ok := constant.Uint64Val(x); ok {
return u
}
return 0
case constant.Float:
f, _ := constant.Float64Val(x)
return uint64(f)
}
panic(fmt.Sprintf("unexpected constant value: %T", c.Value))
}
// Float64 returns the numeric value of this constant truncated to fit
// a float64.
//
func (c *Const) Float64() float64 {
f, _ := constant.Float64Val(c.Value)
return f
}
// Complex128 returns the complex value of this constant truncated to
// fit a complex128.
//
func (c *Const) Complex128() complex128 {
re, _ := constant.Float64Val(constant.Real(c.Value))
im, _ := constant.Float64Val(constant.Imag(c.Value))
return complex(re, im)
}
go-tools-2021.1.2/go/ir/create.go 0000664 0000000 0000000 00000016515 14143223131 0016315 0 ustar 00root root 0000000 0000000 // 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 ir
// This file implements the CREATE phase of IR construction.
// See builder.go for explanation.
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"os"
"sync"
"honnef.co/go/tools/go/types/typeutil"
)
// measured on the standard library and rounded up to powers of two,
// on average there are 8 blocks and 16 instructions per block in a
// function.
const avgBlocks = 8
const avgInstructionsPerBlock = 16
// NewProgram returns a new IR Program.
//
// mode controls diagnostics and checking during IR construction.
//
func NewProgram(fset *token.FileSet, mode BuilderMode) *Program {
prog := &Program{
Fset: fset,
imported: make(map[string]*Package),
packages: make(map[*types.Package]*Package),
thunks: make(map[selectionKey]*Function),
bounds: make(map[*types.Func]*Function),
mode: mode,
}
h := typeutil.MakeHasher() // protected by methodsMu, in effect
prog.methodSets.SetHasher(h)
prog.canon.SetHasher(h)
return prog
}
// memberFromObject populates package pkg with a member for the
// typechecker object obj.
//
// For objects from Go source code, syntax is the associated syntax
// tree (for funcs and vars only); it will be used during the build
// phase.
//
func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node) {
name := obj.Name()
switch obj := obj.(type) {
case *types.Builtin:
if pkg.Pkg != types.Unsafe {
panic("unexpected builtin object: " + obj.String())
}
case *types.TypeName:
pkg.Members[name] = &Type{
object: obj,
pkg: pkg,
}
case *types.Const:
c := &NamedConst{
object: obj,
Value: NewConst(obj.Val(), obj.Type()),
pkg: pkg,
}
pkg.values[obj] = c.Value
pkg.Members[name] = c
case *types.Var:
g := &Global{
Pkg: pkg,
name: name,
object: obj,
typ: types.NewPointer(obj.Type()), // address
}
pkg.values[obj] = g
pkg.Members[name] = g
case *types.Func:
sig := obj.Type().(*types.Signature)
if sig.Recv() == nil && name == "init" {
pkg.ninit++
name = fmt.Sprintf("init#%d", pkg.ninit)
}
fn := &Function{
name: name,
object: obj,
Signature: sig,
Pkg: pkg,
Prog: pkg.Prog,
}
fn.source = syntax
fn.initHTML(pkg.printFunc)
if syntax == nil {
fn.Synthetic = SyntheticLoadedFromExportData
} else {
// Note: we initialize fn.Blocks in
// (*builder).buildFunction and not here because Blocks
// being nil is used to indicate that building of the
// function hasn't started yet.
fn.functionBody = &functionBody{
scratchInstructions: make([]Instruction, avgBlocks*avgInstructionsPerBlock),
}
}
pkg.values[obj] = fn
pkg.Functions = append(pkg.Functions, fn)
if sig.Recv() == nil {
pkg.Members[name] = fn // package-level function
}
default: // (incl. *types.Package)
panic("unexpected Object type: " + obj.String())
}
}
// membersFromDecl populates package pkg with members for each
// typechecker object (var, func, const or type) associated with the
// specified decl.
//
func membersFromDecl(pkg *Package, decl ast.Decl) {
switch decl := decl.(type) {
case *ast.GenDecl: // import, const, type or var
switch decl.Tok {
case token.CONST:
for _, spec := range decl.Specs {
for _, id := range spec.(*ast.ValueSpec).Names {
if !isBlankIdent(id) {
memberFromObject(pkg, pkg.info.Defs[id], nil)
}
}
}
case token.VAR:
for _, spec := range decl.Specs {
for _, id := range spec.(*ast.ValueSpec).Names {
if !isBlankIdent(id) {
memberFromObject(pkg, pkg.info.Defs[id], spec)
}
}
}
case token.TYPE:
for _, spec := range decl.Specs {
id := spec.(*ast.TypeSpec).Name
if !isBlankIdent(id) {
memberFromObject(pkg, pkg.info.Defs[id], nil)
}
}
}
case *ast.FuncDecl:
id := decl.Name
if !isBlankIdent(id) {
memberFromObject(pkg, pkg.info.Defs[id], decl)
}
}
}
// 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 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)
printFunc: prog.PrintFunc,
}
// Add init() function.
p.init = &Function{
name: "init",
Signature: new(types.Signature),
Synthetic: SyntheticPackageInitializer,
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.
if len(files) > 0 {
// Go source package.
for _, file := range files {
for _, decl := range file.Decls {
membersFromDecl(p, decl)
}
}
} else {
// GC-compiled binary package (or "unsafe")
// No code.
// No position information.
scope := p.Pkg.Scope()
for _, name := range scope.Names() {
obj := scope.Lookup(name)
memberFromObject(p, obj, nil)
if obj, ok := obj.(*types.TypeName); ok {
if named, ok := obj.Type().(*types.Named); ok {
for i, n := 0, named.NumMethods(); i < n; i++ {
memberFromObject(p, named.Method(i), nil)
}
}
}
}
}
// Add initializer guard variable.
initguard := &Global{
Pkg: p,
name: "init$guard",
typ: types.NewPointer(tBool),
}
p.Members[initguard.Name()] = initguard
if prog.mode&GlobalDebug != 0 {
p.SetDebugMode(true)
}
if prog.mode&PrintPackages != 0 {
printMu.Lock()
p.WriteTo(os.Stdout)
printMu.Unlock()
}
if importable {
prog.imported[p.Pkg.Path()] = p
}
prog.packages[p.Pkg] = p
return p
}
// printMu serializes printing of Packages/Functions to stdout.
var printMu sync.Mutex
// AllPackages returns a new slice containing all packages in the
// program prog in unspecified order.
//
func (prog *Program) AllPackages() []*Package {
pkgs := make([]*Package, 0, len(prog.packages))
for _, pkg := range prog.packages {
pkgs = append(pkgs, pkg)
}
return pkgs
}
// ImportedPackage returns the importable Package whose PkgPath
// is path, or nil if no such Package has been created.
//
// A parameter to CreatePackage determines whether a package should be
// considered importable. For example, no import declaration can resolve
// to the ad-hoc main package created by 'go build foo.go'.
//
// TODO(adonovan): rethink this function and the "importable" concept;
// most packages are importable. This function assumes that all
// types.Package.Path values are unique within the ir.Program, which is
// false---yet this function remains very convenient.
// Clients should use (*Program).Package instead where possible.
// IR doesn't really need a string-keyed map of packages.
//
func (prog *Program) ImportedPackage(path string) *Package {
return prog.imported[path]
}
go-tools-2021.1.2/go/ir/doc.go 0000664 0000000 0000000 00000014132 14143223131 0015610 0 ustar 00root root 0000000 0000000 // 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 ir defines a representation of the elements of Go programs
// (packages, types, functions, variables and constants) using a
// 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, 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.
//
// 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.
//
// 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 irutil.Packages helper function. See ExampleLoadPackages
// and ExampleWholeProgram for examples.
// 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 IR form in which all local
// variables are addresses of stack locations with explicit loads and
// stores. Registerization of eligible locals and Ο-node insertion
// using dominance and dataflow are then performed as a second pass
// called "lifting" to improve the accuracy and performance of
// subsequent analyses; this pass can be skipped by setting the
// NaiveForm builder flag.
//
// The primary interfaces of this package are:
//
// - Member: a named member of a Go package.
// - Value: an expression that yields a value.
// - Instruction: a statement that consumes values and performs computation.
// - Node: a Value or Instruction (emphasizing its membership in the IR value graph)
//
// A computation that yields a result implements both the Value and
// Instruction interfaces. The following table shows for each
// concrete type which of these interfaces it implements.
//
// Value? Instruction? Member?
// *Alloc β β
// *BinOp β β
// *BlankStore β
// *Builtin β
// *Call β β
// *ChangeInterface β β
// *ChangeType β β
// *Const β β
// *Convert β β
// *DebugRef β
// *Defer β β
// *Extract β β
// *Field β β
// *FieldAddr β β
// *FreeVar β
// *Function β β (func)
// *Global β β (var)
// *Go β β
// *If β
// *Index β β
// *IndexAddr β β
// *Jump β
// *Load β β
// *MakeChan β β
// *MakeClosure β β
// *MakeInterface β β
// *MakeMap β β
// *MakeSlice β β
// *MapLookup β β
// *MapUpdate β β
// *NamedConst β (const)
// *Next β β
// *Panic β
// *Parameter β β
// *Phi β β
// *Range β β
// *Recv β β
// *Return β
// *RunDefers β
// *Select β β
// *Send β β
// *Sigma β β
// *Slice β β
// *SliceToArrayPointer β β
// *Store β β
// *StringLookup β β
// *Type β (type)
// *TypeAssert β β
// *UnOp β β
// *Unreachable β
//
// Other key types in this package include: Program, Package, Function
// and BasicBlock.
//
// The program representation constructed by this package is fully
// resolved internally, i.e. it does not rely on the names of Values,
// Packages, Functions, Types or BasicBlocks for the correct
// interpretation of the program. Only the identities of objects and
// the topology of the 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
// can make them very efficient. Many objects are nonetheless named
// to aid in debugging, but it is not essential that the names be
// either accurate or unambiguous. The public API exposes a number of
// name-based maps for client convenience.
//
// The ir/irutil package provides various utilities that depend only
// on the public API of this package.
//
// TODO(adonovan): Consider the exceptional control-flow implications
// of defer and recover().
//
// 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,
// ir.Values/Instructions.
//
package ir
go-tools-2021.1.2/go/ir/dom.go 0000664 0000000 0000000 00000027232 14143223131 0015627 0 ustar 00root root 0000000 0000000 // 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 ir
// This file defines algorithms related to dominance.
// Dominator tree construction ----------------------------------------
//
// We use the algorithm described in Lengauer & Tarjan. 1979. A fast
// algorithm for finding dominators in a flowgraph.
// http://doi.acm.org/10.1145/357062.357071
//
// We also apply the optimizations to SLT described in Georgiadis et
// al, Finding Dominators in Practice, JGAA 2006,
// http://jgaa.info/accepted/2006/GeorgiadisTarjanWerneck2006.10.1.pdf
// to avoid the need for buckets of size > 1.
import (
"bytes"
"fmt"
"io"
"math/big"
"os"
"sort"
)
// Idom returns the block that immediately dominates b:
// its parent in the dominator tree, if any.
// The entry node (b.Index==0) does not have a parent.
//
func (b *BasicBlock) Idom() *BasicBlock { return b.dom.idom }
// Dominees returns the list of blocks that b immediately dominates:
// its children in the dominator tree.
//
func (b *BasicBlock) Dominees() []*BasicBlock { return b.dom.children }
// Dominates reports whether b dominates c.
func (b *BasicBlock) Dominates(c *BasicBlock) bool {
return b.dom.pre <= c.dom.pre && c.dom.post <= b.dom.post
}
type byDomPreorder []*BasicBlock
func (a byDomPreorder) Len() int { return len(a) }
func (a byDomPreorder) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a byDomPreorder) Less(i, j int) bool { return a[i].dom.pre < a[j].dom.pre }
// DomPreorder returns a new slice containing the blocks of f in
// dominator tree preorder.
//
func (f *Function) DomPreorder() []*BasicBlock {
n := len(f.Blocks)
order := make(byDomPreorder, n)
copy(order, f.Blocks)
sort.Sort(order)
return order
}
// domInfo contains a BasicBlock's dominance information.
type domInfo struct {
idom *BasicBlock // immediate dominator (parent in domtree)
children []*BasicBlock // nodes immediately dominated by this one
pre, post int32 // pre- and post-order numbering within domtree
}
// buildDomTree computes the dominator tree of f using the LT algorithm.
// Precondition: all blocks are reachable (e.g. optimizeBlocks has been run).
//
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 fn.Blocks {
b.dom = 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 _, 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]
}
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)
}
}
}
if idoms[b.Index] != newIdom {
idoms[b.Index] = newIdom
changed = true
}
}
}
for i, b := range idoms {
fn.Blocks[i].dom.idom = b
if b == nil {
// malformed CFG
continue
}
if i == b.Index {
continue
}
b.dom.children = append(b.dom.children, fn.Blocks[i])
}
numberDomTree(fn.Blocks[0], 0, 0)
// printDomTreeDot(os.Stderr, fn) // debugging
// printDomTreeText(os.Stderr, root, 0) // debugging
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
}
}
// numberDomTree sets the pre- and post-order numbers of a depth-first
// traversal of the dominator tree rooted at v. These are used to
// answer dominance queries in constant time.
//
func numberDomTree(v *BasicBlock, pre, post int32) (int32, int32) {
v.dom.pre = pre
pre++
for _, child := range v.dom.children {
pre, post = numberDomTree(child, pre, post)
}
v.dom.post = post
post++
return pre, post
}
// 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
// computed by the LT algorithm by comparing against the dominance
// relation computed by a naive Kildall-style forward dataflow
// analysis (Algorithm 10.16 from the "Dragon" book).
//
func sanityCheckDomTree(f *Function) {
n := len(f.Blocks)
// D[i] is the set of blocks that dominate f.Blocks[i],
// represented as a bit-set of block indices.
D := make([]big.Int, n)
one := big.NewInt(1)
// all is the set of all blocks; constant.
var all big.Int
all.Set(one).Lsh(&all, uint(n)).Sub(&all, one)
// Initialization.
for i := range f.Blocks {
if i == 0 {
// A root is dominated only by itself.
D[i].SetBit(&D[0], 0, 1)
} else {
// All other blocks are (initially) dominated
// by every block.
D[i].Set(&all)
}
}
// Iteration until fixed point.
for changed := true; changed; {
changed = false
for i, b := range f.Blocks {
if i == 0 {
continue
}
// Compute intersection across predecessors.
var x big.Int
x.Set(&all)
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)
changed = true
}
}
}
// Check the entire relation. O(n^2).
ok := true
for i := 0; i < n; i++ {
for j := 0; j < n; j++ {
b, c := f.Blocks[i], f.Blocks[j]
actual := b.Dominates(c)
expected := D[j].Bit(i) == 1
if actual != expected {
fmt.Fprintf(os.Stderr, "dominates(%s, %s)==%t, want %t\n", b, c, actual, expected)
ok = false
}
}
}
preorder := f.DomPreorder()
for _, b := range f.Blocks {
if got := preorder[b.dom.pre]; got != b {
fmt.Fprintf(os.Stderr, "preorder[%d]==%s, want %s\n", b.dom.pre, got, b)
ok = false
}
}
if !ok {
panic("sanityCheckDomTree failed for " + f.String())
}
}
// Printing functions ----------------------------------------
// printDomTree prints the dominator tree as text, using indentation.
//lint:ignore U1000 used during debugging
func printDomTreeText(buf *bytes.Buffer, v *BasicBlock, indent int) {
fmt.Fprintf(buf, "%*s%s\n", 4*indent, "", v)
for _, child := range v.dom.children {
printDomTreeText(buf, child, indent+1)
}
}
// printDomTreeDot prints the dominator tree of f in AT&T GraphViz
// (.dot) format.
//lint:ignore U1000 used during debugging
func printDomTreeDot(buf io.Writer, f *Function) {
fmt.Fprintln(buf, "//", f)
fmt.Fprintln(buf, "digraph domtree {")
for i, b := range f.Blocks {
v := b.dom
fmt.Fprintf(buf, "\tn%d [label=\"%s (%d, %d)\",shape=\"rectangle\"];\n", v.pre, b, v.pre, v.post)
// TODO(adonovan): improve appearance of edges
// belonging to both dominator tree and CFG.
// Dominator tree edge.
if i != 0 {
fmt.Fprintf(buf, "\tn%d -> n%d [style=\"solid\",weight=100];\n", v.idom.dom.pre, v.pre)
}
// CFG edges.
for _, pred := range b.Preds {
fmt.Fprintf(buf, "\tn%d -> n%d [style=\"dotted\",weight=0];\n", pred.dom.pre, v.pre)
}
if f.fakeExits.Has(b) {
fmt.Fprintf(buf, "\tn%d -> n%d [style=\"dotted\",weight=0,color=red];\n", b.dom.pre, f.Exit.dom.pre)
}
}
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)
}
if f.fakeExits.Has(b) {
fmt.Fprintf(buf, "\tn%d -> n%d [style=\"dotted\",weight=0,color=red];\n", b.dom.pre, f.Exit.dom.pre)
}
}
fmt.Fprintln(buf, "}")
}
go-tools-2021.1.2/go/ir/emit.go 0000664 0000000 0000000 00000030503 14143223131 0016001 0 ustar 00root root 0000000 0000000 // 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 ir
// Helpers for emitting IR instructions.
import (
"fmt"
"go/ast"
"go/constant"
"go/token"
"go/types"
)
// 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, source ast.Node) *Alloc {
v := &Alloc{Heap: true}
v.setType(types.NewPointer(typ))
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, source ast.Node) *Load {
v := &Load{X: addr}
v.setType(deref(addr.Type()))
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.
//
func emitDebugRef(f *Function, e ast.Expr, v Value, isAddr bool) {
if !f.debugInfo() {
return // debugging not enabled
}
if v == nil || e == nil {
panic("nil")
}
var obj types.Object
e = unparen(e)
if id, ok := e.(*ast.Ident); ok {
if isBlankIdent(id) {
return
}
obj = f.Pkg.objectOf(id)
switch obj.(type) {
case *types.Nil, *types.Const, *types.Builtin:
return
}
}
f.emit(&DebugRef{
X: v,
Expr: e,
IsAddr: isAddr,
object: obj,
}, nil)
}
// emitArith emits to f code to compute the binary operation op(x, y)
// where op is an eager shift, logical or arithmetic operation.
// (Use emitCompare() for comparisons and Builder.logicalBinop() for
// non-eager operations.)
//
func emitArith(f *Function, op token.Token, x, y Value, t types.Type, source ast.Node) Value {
switch op {
case token.SHL, token.SHR:
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], 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, source)
y = emitConv(f, y, t, source)
default:
panic("illegal op in emitArith: " + op.String())
}
v := &BinOp{
Op: op,
X: x,
Y: y,
}
v.setType(t)
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, source ast.Node) Value {
xt := x.Type().Underlying()
yt := y.Type().Underlying()
// Special case to optimise a tagless SwitchStmt so that
// these are equivalent
// switch { case e: ...}
// switch true { case e: ... }
// if e==true { ... }
// even in the case when e's type is an interface.
// TODO(adonovan): opt: generalise to x==true, false!=y, etc.
if x, 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
}
}
if types.Identical(xt, yt) {
// no conversion necessary
} else if _, ok := xt.(*types.Interface); ok {
y = emitConv(f, y, x.Type(), source)
} else if _, ok := yt.(*types.Interface); ok {
x = emitConv(f, x, y.Type(), source)
} else if _, ok := x.(*Const); ok {
x = emitConv(f, x, y.Type(), source)
} else if _, ok := y.(*Const); ok {
y = emitConv(f, y, x.Type(), source)
//lint:ignore SA9003 no-op
} else {
// other cases, e.g. channels. No-op.
}
v := &BinOp{
Op: op,
X: x,
Y: y,
}
v.setType(tBool)
return f.emit(v, source)
}
// isValuePreserving returns true if a conversion from ut_src to
// ut_dst is value-preserving, i.e. just a change of type.
// Precondition: neither argument is a named type.
//
func isValuePreserving(ut_src, ut_dst types.Type) bool {
// Identical underlying types?
if structTypesIdentical(ut_dst, ut_src) {
return true
}
switch ut_dst.(type) {
case *types.Chan:
// Conversion between channel types?
_, ok := ut_src.(*types.Chan)
return ok
case *types.Pointer:
// Conversion between pointers with identical base types?
_, ok := ut_src.(*types.Pointer)
return ok
}
return false
}
// emitConv emits to f code to convert Value val to exactly type typ,
// and returns the converted value. Implicit conversions are required
// by language assignability rules in assignments, parameter passing,
// etc.
//
func emitConv(f *Function, val Value, typ types.Type, source ast.Node) Value {
t_src := val.Type()
// Identical types? Conversion is a no-op.
if types.Identical(t_src, typ) {
return val
}
ut_dst := typ.Underlying()
ut_src := t_src.Underlying()
// Just a change of type, but not value or representation?
if isValuePreserving(ut_src, ut_dst) {
c := &ChangeType{X: val}
c.setType(typ)
return f.emit(c, source)
}
// Conversion to, or construction of a value of, an interface type?
if _, ok := ut_dst.(*types.Interface); ok {
// Assignment from one interface type to another?
if _, ok := ut_src.(*types.Interface); ok {
c := &ChangeInterface{X: val}
c.setType(typ)
return f.emit(c, source)
}
// Untyped nil constant? Return interface-typed nil constant.
if ut_src == tUntypedNil {
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, types.Default(ut_src), source)
}
f.Pkg.Prog.needMethodsOf(val.Type())
mi := &MakeInterface{X: val}
mi.setType(typ)
return f.emit(mi, source)
}
// Conversion of a compile-time constant value?
if c, ok := val.(*Const); ok {
if _, ok := ut_dst.(*types.Basic); ok || c.IsNil() {
// Conversion of a compile-time constant to
// another constant type results in a new
// constant of the destination type and
// (initially) the same abstract value.
// We don't truncate the value yet.
return emitConst(f, NewConst(c.Value, typ))
}
// We're converting from constant to non-constant type,
// e.g. string -> []byte/[]rune.
}
// Conversion from slice to array pointer?
if slice, ok := ut_src.(*types.Slice); ok {
if ptr, ok := ut_dst.(*types.Pointer); ok {
if arr, ok := ptr.Elem().Underlying().(*types.Array); ok && types.Identical(slice.Elem(), arr.Elem()) {
c := &SliceToArrayPointer{X: val}
c.setType(ut_dst)
return f.emit(c, source)
}
}
}
// A representation-changing conversion?
// At least one of {ut_src,ut_dst} must be *Basic.
// (The other may be []byte or []rune.)
_, ok1 := ut_src.(*types.Basic)
_, ok2 := ut_dst.(*types.Basic)
if ok1 || ok2 {
c := &Convert{X: val}
c.setType(typ)
return f.emit(c, source)
}
panic(fmt.Sprintf("in %s: cannot convert %s (%s) to %s", f, val, val.Type(), typ))
}
// 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, source ast.Node) *Store {
s := &Store{
Addr: addr,
Val: emitConv(f, val, deref(addr.Type()), source),
}
// 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, source ast.Node) *Jump {
b := f.currentBlock
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, source ast.Node) *If {
b := f.currentBlock
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, source ast.Node) Value {
e := &Extract{Tuple: tuple, Index: index}
e.setType(tuple.Type().(*types.Tuple).At(index).Type())
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, source ast.Node) Value {
a := &TypeAssert{X: x, AssertedType: t}
a.setType(t)
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, source ast.Node) Value {
a := &TypeAssert{
X: x,
AssertedType: t,
CommaOk: true,
}
a.setType(types.NewTuple(
newVar("value", t),
varOk,
))
return f.emit(a, source)
}
// emitTailCall emits to f a function call in tail position. The
// caller is responsible for all fields of 'call' except its type.
// Intended for wrapper methods.
// Precondition: f does/will not use deferred procedure calls.
// Postcondition: f.currentBlock is nil.
//
func emitTailCall(f *Function, call *Call, source ast.Node) {
tresults := f.Signature.Results()
nr := tresults.Len()
if nr == 1 {
call.typ = tresults.At(0).Type()
} else {
call.typ = tresults
}
tuple := f.emit(call, source)
var ret Return
switch nr {
case 0:
// no-op
case 1:
ret.Results = []Value{tuple}
default:
for i := 0; i < nr; 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
// the types exactly match.
ret.Results = append(ret.Results, v)
}
}
f.Exit = f.newBasicBlock("exit")
emitJump(f, f.Exit, source)
f.currentBlock = f.Exit
f.emit(&ret, source)
f.currentBlock = nil
}
// emitImplicitSelections emits to f code to apply the sequence of
// implicit field selections specified by indices to base value v, and
// returns the selected value.
//
// If v is the address of a struct, the result will be the address of
// a field; if it is the value of a struct, the result will be the
// value of a field.
//
func emitImplicitSelections(f *Function, v Value, indices []int, source ast.Node) Value {
for _, index := range indices {
fld := deref(v.Type()).Underlying().(*types.Struct).Field(index)
if isPointer(v.Type()) {
instr := &FieldAddr{
X: v,
Field: index,
}
instr.setType(types.NewPointer(fld.Type()))
v = f.emit(instr, source)
// Load the field's value iff indirectly embedded.
if isPointer(fld.Type()) {
v = emitLoad(f, v, source)
}
} else {
instr := &Field{
X: v,
Field: index,
}
instr.setType(fld.Type())
v = f.emit(instr, source)
}
}
return v
}
// emitFieldSelection emits to f code to select the index'th field of v.
//
// If wantAddr, the input must be a pointer-to-struct and the result
// will be the field's address; otherwise the result will be the
// field's value.
// Ident id is used for position and debug info.
//
func emitFieldSelection(f *Function, v Value, index int, wantAddr bool, id *ast.Ident) Value {
fld := deref(v.Type()).Underlying().(*types.Struct).Field(index)
if isPointer(v.Type()) {
instr := &FieldAddr{
X: v,
Field: index,
}
instr.setSource(id)
instr.setType(types.NewPointer(fld.Type()))
v = f.emit(instr, id)
// Load the field's value iff we don't want its address.
if !wantAddr {
v = emitLoad(f, v, id)
}
} else {
instr := &Field{
X: v,
Field: index,
}
instr.setSource(id)
instr.setType(fld.Type())
v = f.emit(instr, id)
}
emitDebugRef(f, id, v, wantAddr)
return v
}
// zeroValue emits to f code to produce a zero value of type t,
// and returns it.
//
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, source), source)
default:
return emitConst(f, zeroConst(t))
}
}
func emitConst(f *Function, c *Const) *Const {
f.consts = append(f.consts, c)
return c
}
go-tools-2021.1.2/go/ir/example_test.go 0000664 0000000 0000000 00000011055 14143223131 0017536 0 ustar 00root root 0000000 0000000 // 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 ir_test
import (
"fmt"
"go/ast"
"go/importer"
"go/parser"
"go/token"
"go/types"
"log"
"os"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
"golang.org/x/tools/go/packages"
)
const hello = `
package main
import "fmt"
const message = "Hello, World!"
func main() {
fmt.Println(message)
}
`
// This program demonstrates how to run the IR builder on a single
// package of one or more already-parsed files. Its dependencies are
// loaded from compiler export data. This is what you'd typically use
// for a compiler; it does not depend on golang.org/x/tools/go/loader.
//
// It shows the printed representation of packages, functions, and
// instructions. Within the function listing, the name of each
// BasicBlock such as ".0.entry" is printed left-aligned, followed by
// the block's Instructions.
//
// For each instruction that defines an IR virtual register
// (i.e. implements Value), the type of that value is shown in the
// right column.
//
// Build and run the irdump.go program if you want a standalone tool
// with similar functionality. It is located at
// honnef.co/go/tools/internal/cmd/irdump.
//
func Example_buildPackage() {
// Parse the source files.
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "hello.go", hello, parser.ParseComments)
if err != nil {
fmt.Print(err) // parse error
return
}
files := []*ast.File{f}
// Create the type-checker's package.
pkg := types.NewPackage("hello", "")
// Type-check the package, load dependencies.
// Create and build the IR program.
hello, _, err := irutil.BuildPackage(
&types.Config{Importer: importer.Default()}, fset, pkg, files, ir.SanityCheckFunctions)
if err != nil {
fmt.Print(err) // type error in some package
return
}
// Print out the package.
hello.WriteTo(os.Stdout)
// Print out the package-level functions.
hello.Func("init").WriteTo(os.Stdout)
hello.Func("main").WriteTo(os.Stdout)
// Output:
// package hello:
// func init func()
// var init$guard bool
// func main func()
// const message message = Const {"Hello, World!"}
//
// # Name: hello.init
// # Package: hello
// # Synthetic: package initializer
// func init():
// b0: # entry
// t1 = Const {true}
// t2 = Load init$guard
// If t2 β b1 b2
//
// b1: β b0 b2 # exit
// Return
//
// b2: β b0 # init.start
// Store {bool} init$guard t1
// t6 = Call <()> fmt.init
// Jump β b1
//
// # Name: hello.main
// # Package: hello
// # Location: hello.go:8:1
// func main():
// b0: # entry
// t1 = Const {"Hello, World!"}
// t2 = Const {0}
// t3 = HeapAlloc <*[1]interface{}>
// t4 = IndexAddr <*interface{}> t3 t2
// t5 = MakeInterface t1
// Store {interface{}} t4 t5
// t7 = Slice <[]interface{}> t3
// t8 = Call <(n int, err error)> fmt.Println t7
// Jump β b1
//
// b1: β b0 # exit
// Return
}
// This example builds IR code for a set of packages using the
// x/tools/go/packages API. This is what you would typically use for a
// analysis capable of operating on a single package.
func Example_loadPackages() {
// Load, parse, and type-check the initial packages.
cfg := &packages.Config{Mode: packages.LoadSyntax}
initial, err := packages.Load(cfg, "fmt", "net/http")
if err != nil {
log.Fatal(err)
}
// Stop if any package had errors.
// This step is optional; without it, the next step
// will create IR for only a subset of packages.
if packages.PrintErrors(initial) > 0 {
log.Fatalf("packages contain errors")
}
// Create IR packages for all well-typed packages.
prog, pkgs := irutil.Packages(initial, ir.PrintPackages, nil)
_ = prog
// Build IR code for the well-typed initial packages.
for _, p := range pkgs {
if p != nil {
p.Build()
}
}
}
// This example builds IR code for a set of packages plus all their dependencies,
// using the x/tools/go/packages API.
// This is what you'd typically use for a whole-program analysis.
func Example_loadWholeProgram() {
// Load, parse, and type-check the whole program.
cfg := packages.Config{Mode: packages.LoadAllSyntax}
initial, err := packages.Load(&cfg, "fmt", "net/http")
if err != nil {
log.Fatal(err)
}
// Create IR packages for well-typed packages and their dependencies.
prog, pkgs := irutil.AllPackages(initial, ir.PrintPackages, nil)
_ = pkgs
// Build IR code for the whole program.
prog.Build()
}
go-tools-2021.1.2/go/ir/exits.go 0000664 0000000 0000000 00000023542 14143223131 0016204 0 ustar 00root root 0000000 0000000 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.NoReturn = AlwaysExits
return
case "throw":
fn.NoReturn = AlwaysExits
return
case "Goexit":
fn.NoReturn = AlwaysUnwinds
return
}
case "go.uber.org/zap":
switch obj.(*types.Func).FullName() {
case "(*go.uber.org/zap.Logger).Fatal",
"(*go.uber.org/zap.SugaredLogger).Fatal",
"(*go.uber.org/zap.SugaredLogger).Fatalw",
"(*go.uber.org/zap.SugaredLogger).Fatalf":
// 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.NoReturn = AlwaysExits
case "(*go.uber.org/zap.Logger).Panic",
"(*go.uber.org/zap.SugaredLogger).Panicw",
"(*go.uber.org/zap.SugaredLogger).Panicf":
fn.NoReturn = AlwaysUnwinds
return
case "(*go.uber.org/zap.Logger).DPanic",
"(*go.uber.org/zap.SugaredLogger).DPanicf",
"(*go.uber.org/zap.SugaredLogger).DPanicw":
// These methods will only panic in development.
}
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.NoReturn = AlwaysExits
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.NoReturn = AlwaysUnwinds
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.NoReturn = AlwaysUnwinds
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 cases. 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.
}
case "github.com/golang/glog":
switch obj.(*types.Func).FullName() {
case "github.com/golang/glog.Exit",
"github.com/golang/glog.ExitDepth",
"github.com/golang/glog.Exitf",
"github.com/golang/glog.Exitln",
"github.com/golang/glog.Fatal",
"github.com/golang/glog.FatalDepth",
"github.com/golang/glog.Fatalf",
"github.com/golang/glog.Fatalln":
// all of these call os.Exit after logging
fn.NoReturn = AlwaysExits
}
case "k8s.io/klog":
switch obj.(*types.Func).FullName() {
case "k8s.io/klog.Exit",
"k8s.io/klog.ExitDepth",
"k8s.io/klog.Exitf",
"k8s.io/klog.Exitln",
"k8s.io/klog.Fatal",
"k8s.io/klog.FatalDepth",
"k8s.io/klog.Fatalf",
"k8s.io/klog.Fatalln":
// all of these call os.Exit after logging
fn.NoReturn = AlwaysExits
}
}
}
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
}
both := NewBlockSet(len(fn.Blocks))
exits := NewBlockSet(len(fn.Blocks))
unwinds := NewBlockSet(len(fn.Blocks))
recovers := false
for _, u := range fn.Blocks {
for _, instr := range u.Instrs {
instrSwitch:
switch instr := instr.(type) {
case *Defer:
if recovers {
// avoid doing extra work, we already know that this function calls recover
continue
}
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
}
if call.Package() == fn.Package() {
b.buildFunction(call)
}
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.
recovers = true
break
}
for _, y := range call.Blocks {
for _, instr2 := range y.Instrs {
if isRecoverCall(instr2) {
recovers = true
break instrSwitch
}
}
}
case *Panic:
both.Add(u)
unwinds.Add(u)
case CallInstruction:
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)
}
switch call.NoReturn {
case AlwaysExits:
both.Add(u)
exits.Add(u)
case AlwaysUnwinds:
both.Add(u)
unwinds.Add(u)
case NeverReturns:
both.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
}
findPathEntry := func(root *BasicBlock, bl *BlockSet) bool {
if bl.Num() == 0 {
return true
}
seen.Clear()
return findPath(root, bl)
}
if !findPathEntry(fn.Blocks[0], exits) {
fn.NoReturn = AlwaysExits
} else if !recovers {
// Only consider unwinding and "never returns" if we don't
// call recover. If we do call recover, then panics don't
// bubble up the stack.
// TODO(dh): the position of the defer matters. If we
// unconditionally terminate before we defer a recover, then
// the recover is ineffective.
if !findPathEntry(fn.Blocks[0], unwinds) {
fn.NoReturn = AlwaysUnwinds
} else if !findPathEntry(fn.Blocks[0], both) {
fn.NoReturn = NeverReturns
}
}
}
func (b *builder) addUnreachables(fn *Function) {
var unreachable *BasicBlock
for _, bb := range fn.Blocks {
instrLoop:
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)
}
switch call.NoReturn {
case AlwaysExits:
// 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 instrLoop
case AlwaysUnwinds:
// 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 instrLoop
case NeverReturns:
// This call will either cause the goroutine to
// terminate, or the process to terminate. Remove
// remaining instructions in the block and replace
// any control flow with a conditional jump to
// either the exit block, or Unreachable.
for _, succ := range bb.Succs {
succ.removePred(bb)
}
bb.Succs = bb.Succs[:0]
bb.Instrs = bb.Instrs[:i+1]
var c Call
c.Call.Value = &Builtin{
name: "ir:noreturnWasPanic",
sig: types.NewSignature(nil,
types.NewTuple(),
types.NewTuple(anonVar(types.Typ[types.Bool])),
false,
),
}
c.setType(types.Typ[types.Bool])
if unreachable == nil {
unreachable = fn.newBasicBlock("unreachable")
unreachable.emit(&Unreachable{}, nil)
addEdge(unreachable, fn.Exit)
}
bb.emit(&c, instr.Source())
bb.emit(&If{Cond: &c}, instr.Source())
addEdge(bb, fn.Exit)
addEdge(bb, unreachable)
break instrLoop
}
}
}
}
}
go-tools-2021.1.2/go/ir/func.go 0000664 0000000 0000000 00000060615 14143223131 0016005 0 ustar 00root root 0000000 0000000 // 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 ir
// This file implements the Function and BasicBlock types.
import (
"bytes"
"fmt"
"go/ast"
"go/constant"
"go/format"
"go/token"
"go/types"
"io"
"os"
"strings"
)
// addEdge adds a control-flow graph edge from from to to.
func addEdge(from, to *BasicBlock) {
from.Succs = append(from.Succs, to)
to.Preds = append(to.Preds, from)
}
// 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 }
// String returns a human-readable label of this block.
// It is not guaranteed unique within the function.
//
func (b *BasicBlock) String() string {
return fmt.Sprintf("%d", b.Index)
}
// emit appends an instruction to the current basic block.
// If the instruction defines a Value, it is returned.
//
func (b *BasicBlock) emit(i Instruction, source ast.Node) Value {
i.setSource(source)
i.setBlock(b)
b.Instrs = append(b.Instrs, i)
v, _ := i.(Value)
return v
}
// predIndex returns the i such that b.Preds[i] == c or panics if
// there is none.
func (b *BasicBlock) predIndex(c *BasicBlock) int {
for i, pred := range b.Preds {
if pred == c {
return i
}
}
panic(fmt.Sprintf("no edge %s -> %s", c, b))
}
// 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)
return ok
}
func (b *BasicBlock) Phis() []Instruction {
return b.phis()
}
// phis returns the prefix of b.Instrs containing all the block's Ο-nodes.
func (b *BasicBlock) phis() []Instruction {
for i, instr := range b.Instrs {
if _, ok := instr.(*Phi); !ok {
return b.Instrs[:i]
}
}
return nil // unreachable in well-formed blocks
}
// replacePred replaces all occurrences of p in b's predecessor list with q.
// Ordinarily there should be at most one.
//
func (b *BasicBlock) replacePred(p, q *BasicBlock) {
for i, pred := range b.Preds {
if pred == p {
b.Preds[i] = q
}
}
}
// replaceSucc replaces all occurrences of p in b's successor list with q.
// Ordinarily there should be at most one.
//
func (b *BasicBlock) replaceSucc(p, q *BasicBlock) {
for i, succ := range b.Succs {
if succ == p {
b.Succs[i] = q
}
}
}
// removePred removes all occurrences of p in b's
// predecessor list and Ο-nodes.
// Ordinarily there should be at most one.
//
func (b *BasicBlock) removePred(p *BasicBlock) {
phis := b.phis()
// We must preserve edge order for Ο-nodes.
j := 0
for i, pred := range b.Preds {
if pred != p {
b.Preds[j] = b.Preds[i]
// Strike out Ο-edge too.
for _, instr := range phis {
phi := instr.(*Phi)
phi.Edges[j] = phi.Edges[i]
}
j++
}
}
// Nil out b.Preds[j:] and Ο-edges[j:] to aid GC.
for i := j; i < len(b.Preds); i++ {
b.Preds[i] = nil
for _, instr := range phis {
instr.(*Phi).Edges[i] = nil
}
}
b.Preds = b.Preds[:j]
for _, instr := range phis {
phi := instr.(*Phi)
phi.Edges = phi.Edges[:j]
}
}
// Destinations associated with unlabelled for/switch/select stmts.
// We push/pop one of these as we enter/leave each construct and for
// each BranchStmt we scan for the innermost target of the right type.
//
type targets struct {
tail *targets // rest of stack
_break *BasicBlock
_continue *BasicBlock
_fallthrough *BasicBlock
}
// Destinations associated with a labelled block.
// We populate these as labels are encountered in forward gotos or
// labelled statements.
//
type lblock struct {
_goto *BasicBlock
_break *BasicBlock
_continue *BasicBlock
}
// labelledBlock returns the branch target associated with the
// specified label, creating it if needed.
//
func (f *Function) labelledBlock(label *ast.Ident) *lblock {
lb := f.lblocks[label.Obj]
if lb == nil {
lb = &lblock{_goto: f.newBasicBlock(label.Name)}
if f.lblocks == nil {
f.lblocks = make(map[*ast.Object]*lblock)
}
f.lblocks[label.Obj] = lb
}
return lb
}
// 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, 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, source ast.Node) *Parameter {
name := obj.Name()
if name == "" {
name = fmt.Sprintf("arg%d", len(f.Params))
}
param := f.addParam(name, obj.Type(), source)
param.object = obj
return param
}
// addSpilledParam declares a parameter that is pre-spilled to the
// stack; the function body will load/store the spilled location.
// Subsequent lifting will eliminate spills where possible.
//
func (f *Function) addSpilledParam(obj types.Object, source ast.Node) {
param := f.addParamObj(obj, source)
spill := &Alloc{}
spill.setType(types.NewPointer(obj.Type()))
spill.source = source
f.objects[obj] = spill
f.Locals = append(f.Locals, spill)
f.emit(spill, source)
emitStore(f, spill, param, source)
// f.emit(&Store{Addr: spill, Val: param})
}
// startBody initializes the function prior to generating IR code for its body.
// Precondition: f.Type() already set.
//
func (f *Function) startBody() {
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.
//
// Preconditions:
// f.startBody() was called.
// Postcondition:
// len(f.Params) == len(f.Signature.Params) + (f.Signature.Recv() ? 1 : 0)
//
func (f *Function) createSyntacticParams(recv *ast.FieldList, functype *ast.FuncType) {
// Receiver (at most one inner iteration).
if recv != nil {
for _, field := range recv.List {
for _, n := range field.Names {
f.addSpilledParam(f.Pkg.info.Defs[n], n)
}
// Anonymous receiver? No need to spill.
if field.Names == nil {
f.addParamObj(f.Signature.Recv(), field)
}
}
}
// Parameters.
if functype.Params != nil {
n := len(f.Params) // 1 if has recv, 0 otherwise
for _, field := range functype.Params.List {
for _, n := range field.Names {
f.addSpilledParam(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), field)
}
}
}
// Named results.
if functype.Results != nil {
for _, field := range functype.Results.List {
// Implicit "var" decl of locals for named results.
for _, n := range field.Names {
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)
}
}
}
}
func numberNodes(f *Function) {
var base ID
for _, b := range f.Blocks {
for _, instr := range b.Instrs {
if instr == nil {
continue
}
base++
instr.setID(base)
}
}
}
// buildReferrers populates the def/use information in all non-nil
// Value.Referrers slice.
// Precondition: all such slices are initially empty.
func buildReferrers(f *Function) {
var rands []*Value
for _, b := range f.Blocks {
for _, instr := range b.Instrs {
rands = instr.Operands(rands[:0]) // recycle storage
for _, rand := range rands {
if r := *rand; r != nil {
if ref := r.Referrers(); ref != nil {
if len(*ref) == 0 {
// per median, each value has two referrers, so we can avoid one call into growslice
//
// Note: we experimented with allocating
// sequential scratch space, but we
// couldn't find a value that gave better
// performance than making many individual
// allocations
*ref = make([]Instruction, 1, 2)
(*ref)[0] = instr
} else {
*ref = append(*ref, instr)
}
}
}
}
}
}
}
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
// Remove from f.Locals any Allocs that escape to the heap.
j := 0
for _, l := range f.Locals {
if !l.Heap {
f.Locals[j] = l
j++
}
}
// Nil out f.Locals[j:] to aid GC.
for i := j; i < len(f.Locals); i++ {
f.Locals[i] = nil
}
f.Locals = f.Locals[:j]
optimizeBlocks(f)
buildFakeExits(f)
buildReferrers(f)
buildDomTree(f)
buildPostDomTree(f)
if f.Prog.mode&NaiveForm == 0 {
lift(f)
}
// emit constants after lifting, because lifting may produce new constants.
f.emitConsts()
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()
f.WriteTo(os.Stdout)
printMu.Unlock()
}
if f.Prog.mode&SanityCheckFunctions != 0 {
mustSanityCheck(f, nil)
}
}
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()
}
// removeNilBlocks eliminates nils from f.Blocks and updates each
// BasicBlock.Index. Use this after any pass that may delete blocks.
//
func (f *Function) removeNilBlocks() {
j := 0
for _, b := range f.Blocks {
if b != nil {
b.Index = j
f.Blocks[j] = b
j++
}
}
// Nil out f.Blocks[j:] to aid GC.
for i := j; i < len(f.Blocks); i++ {
f.Blocks[i] = nil
}
f.Blocks = f.Blocks[:j]
}
// SetDebugMode sets the debug mode for package pkg. If true, all its
// functions will include full debug info. This greatly increases the
// size of the instruction stream, and causes Functions to depend upon
// the ASTs, potentially keeping them live in memory for longer.
//
func (pkg *Package) SetDebugMode(debug bool) {
// TODO(adonovan): do we want ast.File granularity?
pkg.debug = debug
}
// debugInfo reports whether debug info is wanted for this function.
func (f *Function) debugInfo() bool {
return f.Pkg != nil && f.Pkg.debug
}
// addNamedLocal creates a local variable, adds it to function f and
// 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, 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], 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, source ast.Node) *Alloc {
v := &Alloc{}
v.setType(types.NewPointer(typ))
f.Locals = append(f.Locals, v)
f.emit(v, source)
return v
}
// lookup returns the address of the named variable identified by obj
// that is local to function f or one of its enclosing functions.
// If escaping, the reference comes from a potentially escaping pointer
// expression and the referent must be heap-allocated.
//
func (f *Function) lookup(obj types.Object, escaping bool) Value {
if v, ok := f.objects[obj]; ok {
if alloc, ok := v.(*Alloc); ok && escaping {
alloc.Heap = true
}
return v // function-local var (address)
}
// Definition must be in an enclosing function;
// plumb it through intervening closures.
if f.parent == nil {
panic("no ir.Value for " + obj.String())
}
outer := f.parent.lookup(obj, true) // escaping
v := &FreeVar{
name: obj.Name(),
typ: outer.Type(),
outer: outer,
parent: f,
}
f.objects[obj] = v
f.FreeVars = append(f.FreeVars, v)
return v
}
// emit emits the specified instruction to function f.
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
// package name, receiver type, etc.
//
// The specific formatting rules are not guaranteed and may change.
//
// Examples:
// "math.IsNaN" // a package-level function
// "(*bytes.Buffer).Bytes" // a declared method or a wrapper
// "(*bytes.Buffer).Bytes$thunk" // thunk (func wrapping method; receiver is param 0)
// "(*bytes.Buffer).Bytes$bound" // bound (func wrapping method; receiver supplied by closure)
// "main.main$1" // an anonymous function in main
// "main.init#1" // a declared init function
// "main.init" // the synthesized package initializer
//
// When these functions are referred to from within the same package
// (i.e. from == f.Pkg.Object), they are rendered without the package path.
// For example: "IsNaN", "(*Buffer).Bytes", etc.
//
// All non-synthetic functions have distinct package-qualified names.
// (But two methods may have the same name "(T).f" if one is a synthetic
// wrapper promoting a non-exported method "f" from another package; in
// that case, the strings are equal but the identifiers "f" are distinct.)
//
func (f *Function) RelString(from *types.Package) string {
// Anonymous?
if f.parent != nil {
// An anonymous function's Name() looks like "parentName$1",
// but its String() should include the type/package/etc.
parent := f.parent.RelString(from)
for i, anon := range f.parent.AnonFuncs {
if anon == f {
return fmt.Sprintf("%s$%d", parent, 1+i)
}
}
return f.name // should never happen
}
// Method (declared or wrapper)?
if recv := f.Signature.Recv(); recv != nil {
return f.relMethod(from, recv.Type())
}
// Thunk?
if f.method != nil {
return f.relMethod(from, f.method.Recv())
}
// Bound?
if len(f.FreeVars) == 1 && strings.HasSuffix(f.name, "$bound") {
return f.relMethod(from, f.FreeVars[0].Type())
}
// Package-level function?
// Prefix with package name for cross-package references only.
if p := f.pkg(); p != nil && p != from {
return fmt.Sprintf("%s.%s", p.Path(), f.name)
}
// Unknown.
return f.name
}
func (f *Function) relMethod(from *types.Package, recv types.Type) string {
return fmt.Sprintf("(%s).%s", relType(recv, from), f.name)
}
// writeSignature writes to buf the signature sig in declaration syntax.
func writeSignature(buf *bytes.Buffer, from *types.Package, name string, sig *types.Signature, params []*Parameter) {
buf.WriteString("func ")
if recv := sig.Recv(); recv != nil {
buf.WriteString("(")
if n := params[0].Name(); n != "" {
buf.WriteString(n)
buf.WriteString(" ")
}
types.WriteType(buf, params[0].Type(), types.RelativeTo(from))
buf.WriteString(") ")
}
buf.WriteString(name)
types.WriteSignature(buf, sig, types.RelativeTo(from))
}
func (f *Function) pkg() *types.Package {
if f.Pkg != nil {
return f.Pkg.Pkg
}
return nil
}
var _ io.WriterTo = (*Function)(nil) // *Function implements io.Writer
func (f *Function) WriteTo(w io.Writer) (int64, error) {
var buf bytes.Buffer
WriteFunction(&buf, f)
n, err := w.Write(buf.Bytes())
return int64(n), err
}
// WriteFunction writes to buf a human-readable "disassembly" of f.
func WriteFunction(buf *bytes.Buffer, f *Function) {
fmt.Fprintf(buf, "# Name: %s\n", f.String())
if f.Pkg != nil {
fmt.Fprintf(buf, "# Package: %s\n", f.Pkg.Pkg.Path())
}
if syn := f.Synthetic; syn != 0 {
fmt.Fprintln(buf, "# Synthetic:", syn)
}
if pos := f.Pos(); pos.IsValid() {
fmt.Fprintf(buf, "# Location: %s\n", f.Prog.Fset.Position(pos))
}
if f.parent != nil {
fmt.Fprintf(buf, "# Parent: %s\n", f.parent.Name())
}
from := f.pkg()
if f.FreeVars != nil {
buf.WriteString("# Free variables:\n")
for i, fv := range f.FreeVars {
fmt.Fprintf(buf, "# % 3d:\t%s %s\n", i, fv.Name(), relType(fv.Type(), from))
}
}
if len(f.Locals) > 0 {
buf.WriteString("# Locals:\n")
for i, l := range f.Locals {
fmt.Fprintf(buf, "# % 3d:\t%s %s\n", i, l.Name(), relType(deref(l.Type()), from))
}
}
writeSignature(buf, from, f.Name(), f.Signature, f.Params)
buf.WriteString(":\n")
if f.Blocks == nil {
buf.WriteString("\t(external)\n")
}
for _, b := range f.Blocks {
if b == nil {
// Corrupt CFG.
fmt.Fprintf(buf, ".nil:\n")
continue
}
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:
// Left-align the instruction.
if name := v.Name(); name != "" {
fmt.Fprintf(buf, "%s = ", name)
}
buf.WriteString(instr.String())
case nil:
// Be robust against bad transforms.
buf.WriteString("")
default:
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")
}
}
// newBasicBlock adds to f a new basic block and returns it. It does
// not automatically become the current block for subsequent calls to emit.
// comment is an optional string for more readable debugging output.
//
func (f *Function) newBasicBlock(comment string) *BasicBlock {
var instrs []Instruction
if len(f.functionBody.scratchInstructions) > 0 {
instrs = f.functionBody.scratchInstructions[0:0:avgInstructionsPerBlock]
f.functionBody.scratchInstructions = f.functionBody.scratchInstructions[avgInstructionsPerBlock:]
} else {
instrs = make([]Instruction, 0, avgInstructionsPerBlock)
}
b := &BasicBlock{
Index: len(f.Blocks),
Comment: comment,
parent: f,
Instrs: instrs,
}
b.Succs = b.succs2[:0]
f.Blocks = append(f.Blocks, b)
return b
}
// NewFunction returns a new synthetic Function instance belonging to
// prog, with its name and signature fields set as specified.
//
// The caller is responsible for initializing the remaining fields of
// the function object, e.g. Pkg, Params, Blocks.
//
// It is practically impossible for clients to construct well-formed
// 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
// "reflect" package, etc.
//
// TODO(adonovan): think harder about the API here.
//
func (prog *Program) NewFunction(name string, sig *types.Signature, provenance Synthetic) *Function {
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] }
func (f *Function) initHTML(name string) {
if name == "" {
return
}
if rel := f.RelString(nil); rel == name {
f.wr = NewHTMLWriter("ir.html", rel, "")
}
}
go-tools-2021.1.2/go/ir/html.go 0000664 0000000 0000000 00000067077 14143223131 0016027 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Copyright 2019 Dominik Honnef. All rights reserved.
package ir
import (
"bytes"
"fmt"
"go/types"
"html"
"io"
"log"
"os"
"os/exec"
"path/filepath"
"reflect"
"sort"
"strings"
)
func live(f *Function) []bool {
max := 0
var ops []*Value
for _, b := range f.Blocks {
for _, instr := range b.Instrs {
if int(instr.ID()) > max {
max = int(instr.ID())
}
}
}
out := make([]bool, max+1)
var q []Node
for _, b := range f.Blocks {
for _, instr := range b.Instrs {
switch instr.(type) {
case *BlankStore, *Call, *ConstantSwitch, *Defer, *Go, *If, *Jump, *MapUpdate, *Next, *Panic, *Recv, *Return, *RunDefers, *Send, *Store, *Unreachable:
out[instr.ID()] = true
q = append(q, instr)
}
}
}
for len(q) > 0 {
v := q[len(q)-1]
q = q[:len(q)-1]
for _, op := range v.Operands(ops) {
if *op == nil {
continue
}
if !out[(*op).ID()] {
out[(*op).ID()] = true
q = append(q, *op)
}
}
}
return out
}
type funcPrinter interface {
startBlock(b *BasicBlock, reachable bool)
endBlock(b *BasicBlock)
value(v Node, live bool)
startDepCycle()
endDepCycle()
named(n string, vals []Value)
}
func namedValues(f *Function) map[types.Object][]Value {
names := map[types.Object][]Value{}
for _, b := range f.Blocks {
for _, instr := range b.Instrs {
if instr, ok := instr.(*DebugRef); ok {
if obj := instr.object; obj != nil {
names[obj] = append(names[obj], instr.X)
}
}
}
}
// XXX deduplicate values
return names
}
func fprintFunc(p funcPrinter, f *Function) {
// XXX does our IR form preserve unreachable blocks?
// reachable, live := findlive(f)
l := live(f)
for _, b := range f.Blocks {
// XXX
// p.startBlock(b, reachable[b.Index])
p.startBlock(b, true)
end := len(b.Instrs) - 1
if end < 0 {
end = 0
}
for _, v := range b.Instrs[:end] {
if _, ok := v.(*DebugRef); !ok {
p.value(v, l[v.ID()])
}
}
p.endBlock(b)
}
names := namedValues(f)
keys := make([]types.Object, 0, len(names))
for key := range names {
keys = append(keys, key)
}
sort.Slice(keys, func(i, j int) bool {
return keys[i].Pos() < keys[j].Pos()
})
for _, key := range keys {
p.named(key.Name(), names[key])
}
}
func opName(v Node) string {
switch v := v.(type) {
case *Call:
if v.Common().IsInvoke() {
return "Invoke"
}
return "Call"
case *Alloc:
if v.Heap {
return "HeapAlloc"
}
return "StackAlloc"
case *Select:
if v.Blocking {
return "SelectBlocking"
}
return "SelectNonBlocking"
default:
return reflect.ValueOf(v).Type().Elem().Name()
}
}
type HTMLWriter struct {
w io.WriteCloser
path string
dot *dotWriter
}
func NewHTMLWriter(path string, funcname, cfgMask string) *HTMLWriter {
out, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
log.Fatalf("%v", err)
}
pwd, err := os.Getwd()
if err != nil {
log.Fatalf("%v", err)
}
html := HTMLWriter{w: out, path: filepath.Join(pwd, path)}
html.dot = newDotWriter()
html.start(funcname)
return &html
}
func (w *HTMLWriter) start(name string) {
if w == nil {
return
}
w.WriteString("")
w.WriteString(`
`)
w.WriteString("")
w.WriteString("
Click on a value or block to toggle highlighting of that value/block
and its uses. (Values and blocks are highlighted by ID, and IDs of
dead items may be reused, so not all highlights necessarily correspond
to the clicked item.)
Faded out values and blocks are dead code that has not been eliminated.
Values printed in italics have a dependency cycle.
CFG: Dashed edge is for unlikely branches. Blue color is for backward edges.
Edge with a dot means that this edge follows the order in which blocks were laid out.
`)
w.WriteString("
")
w.WriteString("
")
}
func (w *HTMLWriter) Close() {
if w == nil {
return
}
io.WriteString(w.w, "
")
io.WriteString(w.w, "
")
io.WriteString(w.w, "")
io.WriteString(w.w, "")
w.w.Close()
fmt.Printf("dumped IR to %v\n", w.path)
}
// WriteFunc writes f in a column headed by title.
// phase is used for collapsing columns and should be unique across the table.
func (w *HTMLWriter) WriteFunc(phase, title string, f *Function) {
if w == nil {
return
}
w.WriteColumn(phase, title, "", funcHTML(f, phase, w.dot))
}
// WriteColumn writes raw HTML in a column headed by title.
// It is intended for pre- and post-compilation log output.
func (w *HTMLWriter) WriteColumn(phase, title, class, html string) {
if w == nil {
return
}
id := strings.Replace(phase, " ", "-", -1)
// collapsed column
w.Printf("
%v
", id, phase)
if class == "" {
w.Printf("
", id)
} else {
w.Printf("
", id, class)
}
w.WriteString("
" + title + "
")
w.WriteString(html)
w.WriteString("
")
}
func (w *HTMLWriter) Printf(msg string, v ...interface{}) {
if _, err := fmt.Fprintf(w.w, msg, v...); err != nil {
log.Fatalf("%v", err)
}
}
func (w *HTMLWriter) WriteString(s string) {
if _, err := io.WriteString(w.w, s); err != nil {
log.Fatalf("%v", err)
}
}
func valueHTML(v Node) string {
if v == nil {
return "<nil>"
}
// TODO: Using the value ID as the class ignores the fact
// that value IDs get recycled and that some values
// are transmuted into other values.
class := fmt.Sprintf("t%d", v.ID())
var label string
switch v := v.(type) {
case *Function:
label = v.RelString(nil)
case *Builtin:
label = v.Name()
default:
label = class
}
return fmt.Sprintf("%s", class, label)
}
func valueLongHTML(v Node) string {
// TODO: Any intra-value formatting?
// I'm wary of adding too much visual noise,
// but a little bit might be valuable.
// We already have visual noise in the form of punctuation
// maybe we could replace some of that with formatting.
s := fmt.Sprintf("", v.ID())
linenumber := "(?)"
if v.Pos().IsValid() {
line := v.Parent().Prog.Fset.Position(v.Pos()).Line
linenumber = fmt.Sprintf("(%d)", line, line)
}
s += fmt.Sprintf("%s %s = %s", valueHTML(v), linenumber, opName(v))
if v, ok := v.(Value); ok {
s += " <" + html.EscapeString(v.Type().String()) + ">"
}
switch v := v.(type) {
case *Parameter:
s += fmt.Sprintf(" {%s}", html.EscapeString(v.name))
case *BinOp:
s += fmt.Sprintf(" {%s}", html.EscapeString(v.Op.String()))
case *UnOp:
s += fmt.Sprintf(" {%s}", html.EscapeString(v.Op.String()))
case *Extract:
name := v.Tuple.Type().(*types.Tuple).At(v.Index).Name()
s += fmt.Sprintf(" [%d] (%s)", v.Index, name)
case *Field:
st := 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()
}
s += fmt.Sprintf(" [%d] (%s)", v.Field, name)
case *FieldAddr:
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()
}
s += fmt.Sprintf(" [%d] (%s)", v.Field, name)
case *Recv:
s += fmt.Sprintf(" {%t}", v.CommaOk)
case *Call:
if v.Common().IsInvoke() {
s += fmt.Sprintf(" {%s}", html.EscapeString(v.Common().Method.FullName()))
}
case *Const:
if v.Value == nil {
s += " {<nil>}"
} else {
s += fmt.Sprintf(" {%s}", html.EscapeString(v.Value.String()))
}
case *Sigma:
s += fmt.Sprintf(" [#%s]", v.From)
}
for _, a := range v.Operands(nil) {
s += fmt.Sprintf(" %s", valueHTML(*a))
}
// OPT(dh): we're calling namedValues many times on the same function.
allNames := namedValues(v.Parent())
var names []string
for name, values := range allNames {
for _, value := range values {
if v == value {
names = append(names, name.Name())
break
}
}
}
if len(names) != 0 {
s += " (" + strings.Join(names, ", ") + ")"
}
s += ""
return s
}
func blockHTML(b *BasicBlock) string {
// TODO: Using the value ID as the class ignores the fact
// that value IDs get recycled and that some values
// are transmuted into other values.
s := html.EscapeString(b.String())
return fmt.Sprintf("%s", s, s)
}
func blockLongHTML(b *BasicBlock) string {
var kind string
var term Instruction
if len(b.Instrs) > 0 {
term = b.Control()
kind = opName(term)
}
// TODO: improve this for HTML?
s := fmt.Sprintf("%s", b.Index, kind)
if term != nil {
ops := term.Operands(nil)
if len(ops) > 0 {
var ss []string
for _, op := range ops {
ss = append(ss, valueHTML(*op))
}
s += " " + strings.Join(ss, ", ")
}
}
if len(b.Succs) > 0 {
s += " →" // right arrow
for _, c := range b.Succs {
s += " " + blockHTML(c)
}
}
return s
}
func funcHTML(f *Function, phase string, dot *dotWriter) string {
buf := new(bytes.Buffer)
if dot != nil {
dot.writeFuncSVG(buf, phase, f)
}
fmt.Fprint(buf, "")
p := htmlFuncPrinter{w: buf}
fprintFunc(p, f)
// fprintFunc(&buf, f) // TODO: HTML, not text, for line breaks, etc.
fmt.Fprint(buf, "")
return buf.String()
}
type htmlFuncPrinter struct {
w io.Writer
}
func (p htmlFuncPrinter) startBlock(b *BasicBlock, reachable bool) {
var dead string
if !reachable {
dead = "dead-block"
}
fmt.Fprintf(p.w, "
", b, dead)
fmt.Fprintf(p.w, "
%s:", blockHTML(b))
if len(b.Preds) > 0 {
io.WriteString(p.w, " ←") // left arrow
for _, pred := range b.Preds {
fmt.Fprintf(p.w, " %s", blockHTML(pred))
}
}
if len(b.Instrs) > 0 {
io.WriteString(p.w, ``)
}
io.WriteString(p.w, "
")
if len(b.Instrs) > 0 { // start list of values
io.WriteString(p.w, "
")
io.WriteString(p.w, "
")
}
}
func (p htmlFuncPrinter) endBlock(b *BasicBlock) {
if len(b.Instrs) > 0 { // end list of values
io.WriteString(p.w, "