pax_global_header00006660000000000000000000000064141432231310014504gustar00rootroot0000000000000052 comment=c8caa92bad8c27ae734c6725b8a04932d54a147b go-tools-2021.1.2/000077500000000000000000000000001414322313100134745ustar00rootroot00000000000000go-tools-2021.1.2/.gitattributes000066400000000000000000000000171414322313100163650ustar00rootroot00000000000000*.golden -text go-tools-2021.1.2/.github/000077500000000000000000000000001414322313100150345ustar00rootroot00000000000000go-tools-2021.1.2/.github/FUNDING.yml000066400000000000000000000000431414322313100166460ustar00rootroot00000000000000patreon: dominikh github: dominikh go-tools-2021.1.2/.github/ISSUE_TEMPLATE/000077500000000000000000000000001414322313100172175ustar00rootroot00000000000000go-tools-2021.1.2/.github/ISSUE_TEMPLATE/1_false_positive.md000066400000000000000000000012021414322313100227700ustar00rootroot00000000000000--- 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.md000066400000000000000000000012021414322313100227310ustar00rootroot00000000000000--- 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.md000066400000000000000000000011561414322313100205430ustar00rootroot00000000000000--- 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.md000066400000000000000000000002201414322313100210770ustar00rootroot00000000000000--- 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/000077500000000000000000000000001414322313100170715ustar00rootroot00000000000000go-tools-2021.1.2/.github/workflows/ci.yml000066400000000000000000000017431414322313100202140ustar00rootroot00000000000000name: "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/LICENSE000066400000000000000000000020421414322313100144770ustar00rootroot00000000000000Copyright (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-PARTY000066400000000000000000000144521414322313100162540ustar00rootroot00000000000000Staticcheck 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.md000066400000000000000000000056331414322313100147620ustar00rootroot00000000000000

Staticcheck logo
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/000077500000000000000000000000001414322313100157505ustar00rootroot00000000000000go-tools-2021.1.2/_benchmarks/bench.sh000077500000000000000000000026011414322313100173650ustar00rootroot00000000000000#!/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.sh000077500000000000000000000002051414322313100220650ustar00rootroot00000000000000#!/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/000077500000000000000000000000001414322313100153175ustar00rootroot00000000000000go-tools-2021.1.2/analysis/code/000077500000000000000000000000001414322313100162315ustar00rootroot00000000000000go-tools-2021.1.2/analysis/code/code.go000066400000000000000000000174721414322313100175050ustar00rootroot00000000000000// 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.go000066400000000000000000000026041414322313100177200ustar00rootroot00000000000000package 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/000077500000000000000000000000001414322313100162445ustar00rootroot00000000000000go-tools-2021.1.2/analysis/edit/edit.go000066400000000000000000000030341414322313100175200ustar00rootroot00000000000000package 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/000077500000000000000000000000001414322313100164175ustar00rootroot00000000000000go-tools-2021.1.2/analysis/facts/deprecated.go000066400000000000000000000064731414322313100210600ustar00rootroot00000000000000package 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.go000066400000000000000000000007151414322313100211120ustar00rootroot00000000000000package 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.go000066400000000000000000000004471414322313100211120ustar00rootroot00000000000000package 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.go000066400000000000000000000035541414322313100207130ustar00rootroot00000000000000package 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/000077500000000000000000000000001414322313100200725ustar00rootroot00000000000000go-tools-2021.1.2/analysis/facts/nilness/nilness.go000066400000000000000000000141601414322313100220760ustar00rootroot00000000000000package 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.go000066400000000000000000000002761414322313100231400ustar00rootroot00000000000000package 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/000077500000000000000000000000001414322313100217035ustar00rootroot00000000000000go-tools-2021.1.2/analysis/facts/nilness/testdata/src/000077500000000000000000000000001414322313100224725ustar00rootroot00000000000000go-tools-2021.1.2/analysis/facts/nilness/testdata/src/Nilness/000077500000000000000000000000001414322313100241055ustar00rootroot00000000000000go-tools-2021.1.2/analysis/facts/nilness/testdata/src/Nilness/Nilness.go000066400000000000000000000026471414322313100260600ustar00rootroot00000000000000package 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.go000066400000000000000000000103561414322313100203070ustar00rootroot00000000000000package 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/000077500000000000000000000000001414322313100202305ustar00rootroot00000000000000go-tools-2021.1.2/analysis/facts/testdata/src/000077500000000000000000000000001414322313100210175ustar00rootroot00000000000000go-tools-2021.1.2/analysis/facts/testdata/src/Deprecated/000077500000000000000000000000001414322313100230575ustar00rootroot00000000000000go-tools-2021.1.2/analysis/facts/testdata/src/Deprecated/Deprecated.go000066400000000000000000000004061414322313100254460ustar00rootroot00000000000000package 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/000077500000000000000000000000001414322313100223135ustar00rootroot00000000000000go-tools-2021.1.2/analysis/facts/testdata/src/Purity/CheckPureFunctions.go000066400000000000000000000010161414322313100264020ustar00rootroot00000000000000package 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.go000066400000000000000000000007661414322313100200770ustar00rootroot00000000000000package 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/000077500000000000000000000000001414322313100204355ustar00rootroot00000000000000go-tools-2021.1.2/analysis/facts/typedness/testdata/000077500000000000000000000000001414322313100222465ustar00rootroot00000000000000go-tools-2021.1.2/analysis/facts/typedness/testdata/src/000077500000000000000000000000001414322313100230355ustar00rootroot00000000000000go-tools-2021.1.2/analysis/facts/typedness/testdata/src/Typedness/000077500000000000000000000000001414322313100250135ustar00rootroot00000000000000go-tools-2021.1.2/analysis/facts/typedness/testdata/src/Typedness/Typedness.go000066400000000000000000000102411414322313100273160ustar00rootroot00000000000000package 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.go000066400000000000000000000144641414322313100230130ustar00rootroot00000000000000package 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.go000066400000000000000000000003041414322313100240360ustar00rootroot00000000000000package 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/000077500000000000000000000000001414322313100162655ustar00rootroot00000000000000go-tools-2021.1.2/analysis/lint/lint.go000066400000000000000000000103131414322313100175600ustar00rootroot00000000000000// 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/000077500000000000000000000000001414322313100166325ustar00rootroot00000000000000go-tools-2021.1.2/analysis/report/report.go000066400000000000000000000125551414322313100205040ustar00rootroot00000000000000package 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.go000066400000000000000000000012401414322313100215300ustar00rootroot00000000000000package 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/000077500000000000000000000000001414322313100142375ustar00rootroot00000000000000go-tools-2021.1.2/cmd/keyify/000077500000000000000000000000001414322313100155375ustar00rootroot00000000000000go-tools-2021.1.2/cmd/keyify/README.md000066400000000000000000000020111414322313100170100ustar00rootroot00000000000000Keyify 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. ![gif of keyify](http://stuff.fork-bomb.org/keyify.gif) go-tools-2021.1.2/cmd/keyify/go-keyify.el000066400000000000000000000030041414322313100177610ustar00rootroot00000000000000;;; 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.go000066400000000000000000000217561414322313100174010ustar00rootroot00000000000000// 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.go000066400000000000000000000032441414322313100177350ustar00rootroot00000000000000// 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/000077500000000000000000000000001414322313100165245ustar00rootroot00000000000000go-tools-2021.1.2/cmd/staticcheck/README.md000066400000000000000000000006331414322313100200050ustar00rootroot00000000000000# 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.go000066400000000000000000000020151414322313100213360ustar00rootroot00000000000000// 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/000077500000000000000000000000001414322313100206575ustar00rootroot00000000000000go-tools-2021.1.2/cmd/structlayout-optimize/main.go000066400000000000000000000075471414322313100221470ustar00rootroot00000000000000// 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/000077500000000000000000000000001414322313100203465ustar00rootroot00000000000000go-tools-2021.1.2/cmd/structlayout-pretty/main.go000066400000000000000000000030761414322313100216270ustar00rootroot00000000000000// 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/000077500000000000000000000000001414322313100170215ustar00rootroot00000000000000go-tools-2021.1.2/cmd/structlayout/README.md000066400000000000000000000054271414322313100203100ustar00rootroot00000000000000# 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 ``` ![memory layout of bytes.Buffer](/images/screenshots/struct.png) go-tools-2021.1.2/cmd/structlayout/main.go000066400000000000000000000055721414322313100203050ustar00rootroot00000000000000// 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/000077500000000000000000000000001414322313100147415ustar00rootroot00000000000000go-tools-2021.1.2/config/config.go000066400000000000000000000136601414322313100165430ustar00rootroot00000000000000package 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.conf000066400000000000000000000007651414322313100172530ustar00rootroot00000000000000checks = ["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/000077500000000000000000000000001414322313100145625ustar00rootroot00000000000000go-tools-2021.1.2/debug/debug.go000066400000000000000000000003631414322313100162010ustar00rootroot00000000000000package 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/000077500000000000000000000000001414322313100144375ustar00rootroot00000000000000go-tools-2021.1.2/dist/build.sh000077500000000000000000000030041414322313100160720ustar00rootroot00000000000000#!/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/000077500000000000000000000000001414322313100142415ustar00rootroot00000000000000go-tools-2021.1.2/doc/2017.2.html000066400000000000000000000133601414322313100156630ustar00rootroot00000000000000

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.
go-tools-2021.1.2/doc/2019.1.html000066400000000000000000000202161414322313100156620ustar00rootroot00000000000000

Big restructuring

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.

Details on all of the command-line flags can be found in the documentation.

Configuration files

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.
go-tools-2021.1.2/doc/2019.2.html000066400000000000000000000441541414322313100156720ustar00rootroot00000000000000

Performance improvements

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.


Packages: 37/619 initial, 38/1011 total; Workers: 8/8; Problems: 73

Explaining checks

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 "SA1027" }} flags incorrectly aligned atomic accesses.
  • {{ 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 "ST1018" }} flags string literals containing zero-width characters.

Changed checks

Several checks have been improved:

  • {{ 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.

go-tools-2021.1.2/doc/2020.1.html000066400000000000000000000270161414322313100156570ustar00rootroot00000000000000

Introduction to Staticcheck 2020.1

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 "SA5011" }} flags potential nil pointer dereferences.
  • {{ check "ST1019" }} flags duplicate imports.
  • {{ 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.

Staticcheck 2020.1.5 release notes

This release fixes a crash in the pattern matching engine and a false positive in SA4006.

Staticcheck 2020.1.6 release notes

This release makes the following fixes and improvements:

  • Staticcheck no longer panics when encountering files that have the following comment: // Code generated DO NOT EDIT.
  • {{ check "SA4016" }} no longer panics when checking bitwise operations that involve dot-imported identifiers.
  • Fixed the suggested fix offered by {{ check "S1004" }}.
  • Fixed a false positive involving byte arrays in {{ check "SA5009" }}.
  • Fixed a false positive involving named byte slice types in {{ check "SA5009" }}.
  • Added another heuristic to avoid flagging function names in error messages in {{ check "ST1005" }}.
  • {{ check "SA3000" }} will no longer flag missing calls to os.Exit in TestMain functions if targeting Go 1.15 or newer.
go-tools-2021.1.2/doc/2020.2.html000066400000000000000000000427401414322313100156610ustar00rootroot00000000000000

Performance improvements

The primary focus of this release is a major improvement in performance, significantly reducing memory usage while also reducing runtimes.

Benchmarks comparing the previous and current releases of Staticcheck
Uncached, GOMAXPROCS=1
Package 2020.1.6 2020.2 Delta Stats
image/color 2.41s Β±19% 2.00s Β±14% -17.08% p=0.000, n=10+10
k8s.io/kubernetes/pkg/... 276s Β± 1% 219s Β± 1% -20.62% p=0.000, n=10+10
net/http 6.18s Β± 1% 5.61s Β± 5% -9.21% p=0.000, n=8+10
std 49.5s Β± 1% 42.5s Β± 1% -14.04% p=0.000, n=9+10
strconv 2.49s Β± 9% 2.19s Β±12% -12.08% p=0.001, n=10+10
image/color 167MB Β±26% 146MB Β±19% -12.62% p=0.043, n=10+10
k8s.io/kubernetes/pkg/... 2.14GB Β± 1% 0.45GB Β±13% -79.09% p=0.000, n=10+10
net/http 216MB Β± 6% 166MB Β±18% -23.11% p=0.000, n=10+10
std 972MB Β± 3% 284MB Β± 9% -70.82% p=0.000, n=8+10
strconv 155MB Β±21% 139MB Β±29% ~ p=0.063, n=10+10
Cached, GOMAXPROCS=1
Package 2020.1.6 2020.2 Delta Stats
image/color 160ms Β± 0% 107ms Β± 7% -33.13% p=0.000, n=8+10
k8s.io/kubernetes/pkg/... 12.7s Β± 1% 6.9s Β± 1% -45.26% p=0.000, n=9+10
net/http 370ms Β± 0% 230ms Β± 0% -37.84% p=0.000, n=8+8
std 2.52s Β± 1% 1.31s Β± 1% -48.13% p=0.000, n=10+9
strconv 164ms Β± 4% 110ms Β± 0% -32.93% p=0.000, n=10+10
image/color 38.6MB Β± 4% 20.8MB Β± 1% -45.96% p=0.000, n=9+10
k8s.io/kubernetes/pkg/... 863MB Β± 4% 283MB Β± 2% -67.28% p=0.000, n=10+10
net/http 70.5MB Β± 5% 25.8MB Β± 2% -63.48% p=0.000, n=10+9
std 243MB Β±16% 73MB Β± 8% -70.00% p=0.000, n=10+10
strconv 37.2MB Β± 2% 21.3MB Β± 1% -42.76% p=0.000, n=9+10
Uncached, GOMAXPROCS=32
Package 2020.1.6 2020.2 Delta Stats
image/color 1.19s Β±21% 1.06s Β±12% ~ p=0.115, n=10+8
k8s.io/kubernetes/pkg/... 27.0s Β± 2% 22.4s Β± 2% -16.96% p=0.000, n=10+10
net/http 2.24s Β±11% 2.23s Β±10% ~ p=0.870, n=10+10
std 7.14s Β± 5% 5.10s Β± 9% -28.56% p=0.000, n=10+9
strconv 1.24s Β±26% 1.18s Β±21% ~ p=0.753, n=10+10
image/color 143MB Β± 7% 141MB Β± 6% ~ p=0.515, n=8+10
k8s.io/kubernetes/pkg/... 5.77GB Β± 6% 2.76GB Β± 4% -52.25% p=0.000, n=10+10
net/http 284MB Β±10% 226MB Β±14% -20.38% p=0.000, n=10+10
std 1.74GB Β±10% 1.15GB Β±14% -34.11% p=0.000, n=10+10
strconv 148MB Β±18% 144MB Β±16% ~ p=0.579, n=10+10
Cached, GOMAXPROCS=32
Package 2020.1.6 2020.2 Delta Stats
image/color 96.0ms Β± 6% 80.0ms Β± 0% -16.67% p=0.000, n=10+9
k8s.io/kubernetes/pkg/... 4.64s Β± 1% 3.88s Β± 0% -16.22% p=0.000, n=9+8
net/http 216ms Β± 3% 167ms Β± 4% -22.69% p=0.000, n=10+10
std 1.09s Β± 2% 0.96s Β± 2% -12.20% p=0.000, n=10+10
strconv 100ms Β± 0% 87ms Β± 8% -13.00% p=0.000, n=9+10
image/color 46.4MB Β± 3% 24.1MB Β± 5% -48.08% p=0.000, n=8+10
k8s.io/kubernetes/pkg/... 1.38GB Β± 9% 0.27GB Β± 1% -80.29% p=0.000, n=10+10
net/http 80.7MB Β±12% 31.4MB Β± 2% -61.16% p=0.000, n=10+8
std 363MB Β±12% 75MB Β± 7% -79.30% p=0.000, n=10+10
strconv 48.5MB Β± 6% 24.4MB Β± 3% -49.72% p=0.000, n=10+10

See commit 5cfc85b70e7b778eb76fd7338e538d7c9af21e4e for details on how these improvements have been achieved.

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 aforementioned performance improvements necessitate some changes to the U1000 check (also known as unused).

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)
  • A rare crash (see issue 972 for more information)
go-tools-2021.1.2/doc/2021.1.html000066400000000000000000000136531414322313100156620ustar00rootroot00000000000000

UI improvements

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/000077500000000000000000000000001414322313100160475ustar00rootroot00000000000000go-tools-2021.1.2/doc/articles/customizing_staticcheck.html000066400000000000000000000003131414322313100236520ustar00rootroot00000000000000- 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.html000066400000000000000000000231461414322313100200040ustar00rootroot00000000000000

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:

.
β”œβ”€β”€ net
β”‚   β”œβ”€β”€ cgi
β”‚   β”œβ”€β”€ http
β”‚   β”‚   β”œβ”€β”€ parser
β”‚   β”‚   └── staticcheck.conf // config 3
β”‚   └── staticcheck.conf     // config 2
β”œβ”€β”€ staticcheck.conf         // config 1
└── strconv

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.

{{ option "checks" }} = ["all", "-{{ check "ST1000" }}", "-{{ check "ST1003" }}", "-{{ check "ST1016" }}", "-{{ check "ST1020" }}", "-{{ check "ST1021" }}", "-{{ check "ST1022" }}", "-{{ check "ST1023" }}"]
{{ option "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"]
{{ option "dot_import_whitelist" }} = []
{{ option "http_status_code_whitelist" }} = ["200", "400", "404", "500"]

Ignoring problems with linter directives

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:

func TestNewEqual(t *testing.T) {
  if errors.New("abc") == errors.New("abc") {
    t.Errorf(`New("abc") == New("abc")`)
  }
}

{{ 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.

go-tools-2021.1.2/doc/formatters.html000066400000000000000000000045151414322313100173220ustar00rootroot00000000000000

Text

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"
}
go-tools-2021.1.2/doc/index.html000066400000000000000000000012501414322313100162340ustar00rootroot00000000000000

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.

The following articles will help you get started:

go-tools-2021.1.2/doc/install.html000066400000000000000000000041051414322313100165750ustar00rootroot00000000000000

Installing Staticcheck

tl;dr

Install honnef.co/go/tools/cmd/staticcheck@latest.

From source

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.

If you'd like to be notified of new releases, you can use GitHub's Releases only watches.

Binary releases

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.

Arch Linux
staticcheck
Debian
golang-honnef-go-tools-dev
Fedora
golang-honnef-tools
Homebrew
staticcheck
MacPorts
staticcheck
NixOS
go-tools
go-tools-2021.1.2/doc/options.html000066400000000000000000000040711414322313100166240ustar00rootroot00000000000000

checks

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.

Default value: ["all", "-ST1003"]

initialisms

ST1003 checks, among other things, for the correct capitalization of initialisms. The set of known initialisms can be configured with this option.

Default value: ["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"]

dot_import_whitelist

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.

Default value: ["200", "400", "404", "500"]

go-tools-2021.1.2/doc/run.html000066400000000000000000000027321414322313100157370ustar00rootroot00000000000000

Running Staticcheck

Checking packages

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.mod000066400000000000000000000001561414322313100146040ustar00rootroot00000000000000module 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.sum000066400000000000000000000053261414322313100146350ustar00rootroot00000000000000github.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/000077500000000000000000000000001414322313100141015ustar00rootroot00000000000000go-tools-2021.1.2/go/ast/000077500000000000000000000000001414322313100146705ustar00rootroot00000000000000go-tools-2021.1.2/go/ast/astutil/000077500000000000000000000000001414322313100163555ustar00rootroot00000000000000go-tools-2021.1.2/go/ast/astutil/upstream.go000066400000000000000000000006561414322313100205530ustar00rootroot00000000000000package 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.go000066400000000000000000000153251414322313100176670ustar00rootroot00000000000000package 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/000077500000000000000000000000001414322313100160365ustar00rootroot00000000000000go-tools-2021.1.2/go/callgraph/callgraph.go000066400000000000000000000073121414322313100203250ustar00rootroot00000000000000// 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/000077500000000000000000000000001414322313100165715ustar00rootroot00000000000000go-tools-2021.1.2/go/callgraph/cha/cha.go000066400000000000000000000102701414322313100176530ustar00rootroot00000000000000// 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.go000066400000000000000000000047621414322313100207230ustar00rootroot00000000000000// 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/000077500000000000000000000000001414322313100204025ustar00rootroot00000000000000go-tools-2021.1.2/go/callgraph/cha/testdata/func.go000066400000000000000000000004641414322313100216700ustar00rootroot00000000000000//+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.go000066400000000000000000000017031414322313100220010ustar00rootroot00000000000000//+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.go000066400000000000000000000006561414322313100216770ustar00rootroot00000000000000//+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/000077500000000000000000000000001414322313100166245ustar00rootroot00000000000000go-tools-2021.1.2/go/callgraph/rta/rta.go000066400000000000000000000343331414322313100177470ustar00rootroot00000000000000// 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.go000066400000000000000000000063711414322313100210070ustar00rootroot00000000000000// 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/000077500000000000000000000000001414322313100204355ustar00rootroot00000000000000go-tools-2021.1.2/go/callgraph/rta/testdata/func.go000066400000000000000000000007621414322313100217240ustar00rootroot00000000000000//+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.go000066400000000000000000000023751414322313100220420ustar00rootroot00000000000000//+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.go000066400000000000000000000011471414322313100221320ustar00rootroot00000000000000//+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/000077500000000000000000000000001414322313100173255ustar00rootroot00000000000000go-tools-2021.1.2/go/callgraph/static/static.go000066400000000000000000000017631414322313100211520ustar00rootroot00000000000000// 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.go000066400000000000000000000030471414322313100222060ustar00rootroot00000000000000// 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.go000066400000000000000000000111561414322313100173460ustar00rootroot00000000000000// 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/000077500000000000000000000000001414322313100155505ustar00rootroot00000000000000go-tools-2021.1.2/go/gcsizes/LICENSE000066400000000000000000000027071414322313100165630ustar00rootroot00000000000000Copyright (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.go000066400000000000000000000053301414322313100172350ustar00rootroot00000000000000// 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/000077500000000000000000000000001414322313100145135ustar00rootroot00000000000000go-tools-2021.1.2/go/ir/LICENSE000066400000000000000000000027771414322313100155350ustar00rootroot00000000000000Copyright (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/UPSTREAM000066400000000000000000000007131414322313100156770ustar00rootroot00000000000000This 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.go000066400000000000000000000014721414322313100171640ustar00rootroot00000000000000package 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.go000066400000000000000000000120431414322313100166570ustar00rootroot00000000000000// 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.go000066400000000000000000002045111414322313100164730ustar00rootroot00000000000000// 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.go000066400000000000000000000024161414322313100204500ustar00rootroot00000000000000// 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.go000066400000000000000000000310411414322313100175260ustar00rootroot00000000000000// 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.go000066400000000000000000000100161414322313100161660ustar00rootroot00000000000000// 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.go000066400000000000000000000165151414322313100163150ustar00rootroot00000000000000// 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.go000066400000000000000000000141321414322313100156100ustar00rootroot00000000000000// 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.go000066400000000000000000000272321414322313100156270ustar00rootroot00000000000000// 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.go000066400000000000000000000305031414322313100160010ustar00rootroot00000000000000// 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.go000066400000000000000000000110551414322313100175360ustar00rootroot00000000000000// 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.go000066400000000000000000000235421414322313100162040ustar00rootroot00000000000000package 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.go000066400000000000000000000606151414322313100160050ustar00rootroot00000000000000// 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.go000066400000000000000000000670771414322313100160270ustar00rootroot00000000000000// 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("

") w.WriteString(html.EscapeString(name)) w.WriteString("

") w.WriteString(` help

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, "
    ") io.WriteString(p.w, "
  • ") } io.WriteString(p.w, "
  • ") fmt.Fprint(p.w, blockLongHTML(b)) io.WriteString(p.w, "
  • ") io.WriteString(p.w, "
") } func (p htmlFuncPrinter) value(v Node, live bool) { var dead string if !live { dead = "dead-value" } fmt.Fprintf(p.w, "
  • ", dead) fmt.Fprint(p.w, valueLongHTML(v)) io.WriteString(p.w, "
  • ") } func (p htmlFuncPrinter) startDepCycle() { fmt.Fprintln(p.w, "") } func (p htmlFuncPrinter) endDepCycle() { fmt.Fprintln(p.w, "") } func (p htmlFuncPrinter) named(n string, vals []Value) { fmt.Fprintf(p.w, "
  • name %s: ", n) for _, val := range vals { fmt.Fprintf(p.w, "%s ", valueHTML(val)) } fmt.Fprintf(p.w, "
  • ") } type dotWriter struct { path string broken bool } // newDotWriter returns non-nil value when mask is valid. // dotWriter will generate SVGs only for the phases specified in the mask. // mask can contain following patterns and combinations of them: // * - all of them; // x-y - x through y, inclusive; // x,y - x and y, but not the passes between. func newDotWriter() *dotWriter { path, err := exec.LookPath("dot") if err != nil { fmt.Println(err) return nil } return &dotWriter{path: path} } func (d *dotWriter) writeFuncSVG(w io.Writer, phase string, f *Function) { if d.broken { return } cmd := exec.Command(d.path, "-Tsvg") pipe, err := cmd.StdinPipe() if err != nil { d.broken = true fmt.Println(err) return } buf := new(bytes.Buffer) cmd.Stdout = buf bufErr := new(bytes.Buffer) cmd.Stderr = bufErr err = cmd.Start() if err != nil { d.broken = true fmt.Println(err) return } fmt.Fprint(pipe, `digraph "" { margin=0; size="4,40"; ranksep=.2; `) id := strings.Replace(phase, " ", "-", -1) fmt.Fprintf(pipe, `id="g_graph_%s";`, id) fmt.Fprintf(pipe, `node [style=filled,fillcolor=white,fontsize=16,fontname="Menlo,Times,serif",margin="0.01,0.03"];`) fmt.Fprintf(pipe, `edge [fontsize=16,fontname="Menlo,Times,serif"];`) for _, b := range f.Blocks { layout := "" fmt.Fprintf(pipe, `%v [label="%v%s\n%v",id="graph_node_%v_%v"];`, b, b, layout, b.Control().String(), id, b) } indexOf := make([]int, len(f.Blocks)) for i, b := range f.Blocks { indexOf[b.Index] = i } // XXX /* ponums := make([]int32, len(f.Blocks)) _ = postorderWithNumbering(f, ponums) isBackEdge := func(from, to int) bool { return ponums[from] <= ponums[to] } */ isBackEdge := func(from, to int) bool { return false } for _, b := range f.Blocks { for i, s := range b.Succs { style := "solid" color := "black" arrow := "vee" if isBackEdge(b.Index, s.Index) { color = "blue" } fmt.Fprintf(pipe, `%v -> %v [label=" %d ",style="%s",color="%s",arrowhead="%s"];`, b, s, i, style, color, arrow) } } fmt.Fprint(pipe, "}") pipe.Close() err = cmd.Wait() if err != nil { d.broken = true fmt.Printf("dot: %v\n%v\n", err, bufErr.String()) return } svgID := "svg_graph_" + id fmt.Fprintf(w, `
    `, svgID, svgID) // For now, an awful hack: edit the html as it passes through // our fingers, finding ' 0 { fset = initial[0].Fset } prog := ir.NewProgram(fset, mode) if opts != nil { prog.PrintFunc = opts.PrintFunc } isInitial := make(map[*packages.Package]bool, len(initial)) for _, p := range initial { isInitial[p] = true } irmap := make(map[*packages.Package]*ir.Package) packages.Visit(initial, nil, func(p *packages.Package) { if p.Types != nil && !p.IllTyped { var files []*ast.File if deps || isInitial[p] { files = p.Syntax } irmap[p] = prog.CreatePackage(p.Types, files, p.TypesInfo, true) } }) var irpkgs []*ir.Package for _, p := range initial { irpkgs = append(irpkgs, irmap[p]) // may be nil } return prog, irpkgs } // CreateProgram returns a new program in IR form, given a program // loaded from source. An IR package is created for each transitively // error-free package of lprog. // // Code for bodies of functions is not built until Build is called // on the result. // // The mode parameter controls diagnostics and checking during IR construction. // // Deprecated: use golang.org/x/tools/go/packages and the Packages // function instead; see ir.ExampleLoadPackages. // func CreateProgram(lprog *loader.Program, mode ir.BuilderMode) *ir.Program { prog := ir.NewProgram(lprog.Fset, mode) for _, info := range lprog.AllPackages { if info.TransitivelyErrorFree { prog.CreatePackage(info.Pkg, info.Files, &info.Info, info.Importable) } } return prog } // BuildPackage builds an IR program with IR for a single package. // // It populates pkg by type-checking the specified file ASTs. All // dependencies are loaded using the importer specified by tc, which // typically loads compiler export data; IR code cannot be built for // those packages. BuildPackage then constructs an ir.Program with all // dependency packages created, and builds and returns the IR package // corresponding to pkg. // // The caller must have set pkg.Path() to the import path. // // The operation fails if there were any type-checking or import errors. // // See ../ir/example_test.go for an example. // func BuildPackage(tc *types.Config, fset *token.FileSet, pkg *types.Package, files []*ast.File, mode ir.BuilderMode) (*ir.Package, *types.Info, error) { if fset == nil { panic("no token.FileSet") } if pkg.Path() == "" { panic("package has no import path") } info := &types.Info{ Types: make(map[ast.Expr]types.TypeAndValue), Defs: make(map[*ast.Ident]types.Object), Uses: make(map[*ast.Ident]types.Object), Implicits: make(map[ast.Node]types.Object), Scopes: make(map[ast.Node]*types.Scope), Selections: make(map[*ast.SelectorExpr]*types.Selection), } if err := types.NewChecker(tc, fset, pkg, info).Files(files); err != nil { return nil, nil, err } prog := ir.NewProgram(fset, mode) // Create IR packages for all imports. // Order is not significant. created := make(map[*types.Package]bool) var createAll func(pkgs []*types.Package) createAll = func(pkgs []*types.Package) { for _, p := range pkgs { if !created[p] { created[p] = true prog.CreatePackage(p, nil, nil, true) createAll(p.Imports()) } } } createAll(pkg.Imports()) // Create and build the primary package. irpkg := prog.CreatePackage(pkg, files, info, false) irpkg.Build() return irpkg, info, nil } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������go-tools-2021.1.2/go/ir/irutil/load_test.go���������������������������������������������������������0000664�0000000�0000000�00000006011�14143223131�0020326�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// Copyright 2015 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package irutil_test import ( "bytes" "go/ast" "go/importer" "go/parser" "go/token" "go/types" "os" "strings" "testing" "honnef.co/go/tools/go/ir/irutil" "golang.org/x/tools/go/packages" ) const hello = `package main import "fmt" func main() { fmt.Println("Hello, world") } ` func TestBuildPackage(t *testing.T) { // There is a more substantial test of BuildPackage and the // IR program it builds in ../ir/builder_test.go. fset := token.NewFileSet() f, err := parser.ParseFile(fset, "hello.go", hello, 0) if err != nil { t.Fatal(err) } pkg := types.NewPackage("hello", "") irpkg, _, err := irutil.BuildPackage(&types.Config{Importer: importer.Default()}, fset, pkg, []*ast.File{f}, 0) if err != nil { t.Fatal(err) } if pkg.Name() != "main" { t.Errorf("pkg.Name() = %s, want main", pkg.Name()) } if irpkg.Func("main") == nil { irpkg.WriteTo(os.Stderr) t.Errorf("irpkg has no main function") } } func TestPackages(t *testing.T) { cfg := &packages.Config{Mode: packages.LoadSyntax} initial, err := packages.Load(cfg, "bytes") if err != nil { t.Fatal(err) } if packages.PrintErrors(initial) > 0 { t.Fatal("there were errors") } prog, pkgs := irutil.Packages(initial, 0, nil) bytesNewBuffer := pkgs[0].Func("NewBuffer") bytesNewBuffer.Pkg.Build() // We'll dump the IR of bytes.NewBuffer because it is small and stable. out := new(bytes.Buffer) bytesNewBuffer.WriteTo(out) // For determinism, sanitize the location. location := prog.Fset.Position(bytesNewBuffer.Pos()).String() got := strings.Replace(out.String(), location, "$GOROOT/src/bytes/buffer.go:1", -1) want := ` # Name: bytes.NewBuffer # Package: bytes # Location: $GOROOT/src/bytes/buffer.go:1 func NewBuffer(buf []byte) *Buffer: b0: # entry t1 = Parameter <[]byte> {buf} t2 = HeapAlloc <*Buffer> t3 = FieldAddr <*[]byte> [0] (buf) t2 Store {[]byte} t3 t1 Jump β†’ b1 b1: ← b0 # exit Return t2 `[1:] if got != want { t.Errorf("bytes.NewBuffer IR = <<%s>>, want <<%s>>", got, want) } } func TestBuildPackage_MissingImport(t *testing.T) { fset := token.NewFileSet() f, err := parser.ParseFile(fset, "bad.go", `package bad; import "missing"`, 0) if err != nil { t.Fatal(err) } pkg := types.NewPackage("bad", "") irpkg, _, err := irutil.BuildPackage(new(types.Config), fset, pkg, []*ast.File{f}, 0) if err == nil || irpkg != nil { t.Fatal("BuildPackage succeeded unexpectedly") } } func TestIssue28106(t *testing.T) { // In go1.10, go/packages loads all packages from source, not // export data, but does not type check function bodies of // imported packages. This test ensures that we do not attempt // to run the IR builder on functions without type information. cfg := &packages.Config{Mode: packages.LoadSyntax} pkgs, err := packages.Load(cfg, "runtime") if err != nil { t.Fatal(err) } prog, _ := irutil.Packages(pkgs, 0, nil) prog.Build() // no crash } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������go-tools-2021.1.2/go/ir/irutil/loops.go�������������������������������������������������������������0000664�0000000�0000000�00000002024�14143223131�0017504�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������package irutil import "honnef.co/go/tools/go/ir" type Loop struct{ *ir.BlockSet } func FindLoops(fn *ir.Function) []Loop { if fn.Blocks == nil { return nil } tree := fn.DomPreorder() var sets []Loop for _, h := range tree { for _, n := range h.Preds { if !h.Dominates(n) { continue } // n is a back-edge to h // h is the loop header if n == h { set := Loop{ir.NewBlockSet(len(fn.Blocks))} set.Add(n) sets = append(sets, set) continue } set := Loop{ir.NewBlockSet(len(fn.Blocks))} set.Add(h) set.Add(n) for _, b := range allPredsBut(n, h, nil) { set.Add(b) } sets = append(sets, set) } } return sets } func allPredsBut(b, but *ir.BasicBlock, list []*ir.BasicBlock) []*ir.BasicBlock { outer: for _, pred := range b.Preds { if pred == but { continue } for _, p := range list { // TODO improve big-o complexity of this function if pred == p { continue outer } } list = append(list, pred) list = allPredsBut(pred, but, list) } return list } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������go-tools-2021.1.2/go/ir/irutil/stub.go��������������������������������������������������������������0000664�0000000�0000000�00000001515�14143223131�0017331�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������package irutil import ( "honnef.co/go/tools/go/ir" ) // IsStub reports whether a function is a stub. A function is // considered a stub if it has no instructions or if all it does is // return a constant value. func IsStub(fn *ir.Function) bool { for _, b := range fn.Blocks { for _, instr := range b.Instrs { switch instr.(type) { case *ir.Const: // const naturally has no side-effects case *ir.Panic: // panic is a stub if it only uses constants case *ir.Return: // return is a stub if it only uses constants case *ir.DebugRef: case *ir.Jump: // if there are no disallowed instructions, then we're // only jumping to the exit block (or possibly // somewhere else that's stubby?) default: // all other instructions are assumed to do actual work return false } } } return true } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������go-tools-2021.1.2/go/ir/irutil/switch.go������������������������������������������������������������0000664�0000000�0000000�00000016437�14143223131�0017666�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 irutil // This file implements discovery of switch and type-switch constructs // from low-level control flow. // // Many techniques exist for compiling a high-level switch with // constant cases to efficient machine code. The optimal choice will // depend on the data type, the specific case values, the code in the // body of each case, and the hardware. // Some examples: // - a lookup table (for a switch that maps constants to constants) // - a computed goto // - a binary tree // - a perfect hash // - a two-level switch (to partition constant strings by their first byte). import ( "bytes" "fmt" "go/token" "go/types" "honnef.co/go/tools/go/ir" ) // A ConstCase represents a single constant comparison. // It is part of a Switch. type ConstCase struct { Block *ir.BasicBlock // block performing the comparison Body *ir.BasicBlock // body of the case Value *ir.Const // case comparand } // A TypeCase represents a single type assertion. // It is part of a Switch. type TypeCase struct { Block *ir.BasicBlock // block performing the type assert Body *ir.BasicBlock // body of the case Type types.Type // case type Binding ir.Value // value bound by this case } // A Switch is a logical high-level control flow operation // (a multiway branch) discovered by analysis of a CFG containing // only if/else chains. It is not part of the ir.Instruction set. // // One of ConstCases and TypeCases has length >= 2; // the other is nil. // // In a value switch, the list of cases may contain duplicate constants. // A type switch may contain duplicate types, or types assignable // to an interface type also in the list. // TODO(adonovan): eliminate such duplicates. // type Switch struct { Start *ir.BasicBlock // block containing start of if/else chain X ir.Value // the switch operand ConstCases []ConstCase // ordered list of constant comparisons TypeCases []TypeCase // ordered list of type assertions Default *ir.BasicBlock // successor if all comparisons fail } func (sw *Switch) String() string { // We represent each block by the String() of its // first Instruction, e.g. "print(42:int)". var buf bytes.Buffer if sw.ConstCases != nil { fmt.Fprintf(&buf, "switch %s {\n", sw.X.Name()) for _, c := range sw.ConstCases { fmt.Fprintf(&buf, "case %s: %s\n", c.Value.Name(), c.Body.Instrs[0]) } } else { fmt.Fprintf(&buf, "switch %s.(type) {\n", sw.X.Name()) for _, c := range sw.TypeCases { fmt.Fprintf(&buf, "case %s %s: %s\n", c.Binding.Name(), c.Type, c.Body.Instrs[0]) } } if sw.Default != nil { fmt.Fprintf(&buf, "default: %s\n", sw.Default.Instrs[0]) } fmt.Fprintf(&buf, "}") return buf.String() } // Switches examines the control-flow graph of fn and returns the // set of inferred value and type switches. A value switch tests an // ir.Value for equality against two or more compile-time constant // values. Switches involving link-time constants (addresses) are // ignored. A type switch type-asserts an ir.Value against two or // more types. // // The switches are returned in dominance order. // // The resulting switches do not necessarily correspond to uses of the // 'switch' keyword in the source: for example, a single source-level // switch statement with non-constant cases may result in zero, one or // many Switches, one per plural sequence of constant cases. // Switches may even be inferred from if/else- or goto-based control flow. // (In general, the control flow constructs of the source program // cannot be faithfully reproduced from the IR.) // func Switches(fn *ir.Function) []Switch { // Traverse the CFG in dominance order, so we don't // enter an if/else-chain in the middle. var switches []Switch seen := make(map[*ir.BasicBlock]bool) // TODO(adonovan): opt: use ir.blockSet for _, b := range fn.DomPreorder() { if x, k := isComparisonBlock(b); x != nil { // Block b starts a switch. sw := Switch{Start: b, X: x} valueSwitch(&sw, k, seen) if len(sw.ConstCases) > 1 { switches = append(switches, sw) } } if y, x, T := isTypeAssertBlock(b); y != nil { // Block b starts a type switch. sw := Switch{Start: b, X: x} typeSwitch(&sw, y, T, seen) if len(sw.TypeCases) > 1 { switches = append(switches, sw) } } } return switches } func isSameX(x1 ir.Value, x2 ir.Value) bool { if x1 == x2 { return true } if x2, ok := x2.(*ir.Sigma); ok { return isSameX(x1, x2.X) } return false } func valueSwitch(sw *Switch, k *ir.Const, seen map[*ir.BasicBlock]bool) { b := sw.Start x := sw.X for isSameX(sw.X, x) { if seen[b] { break } seen[b] = true sw.ConstCases = append(sw.ConstCases, ConstCase{ Block: b, Body: b.Succs[0], Value: k, }) b = b.Succs[1] n := 0 for _, instr := range b.Instrs { switch instr.(type) { case *ir.If, *ir.BinOp: n++ case *ir.Sigma, *ir.Phi, *ir.DebugRef: default: n += 1000 } } if n != 2 { // Block b contains not just 'if x == k' and Οƒ/Ο• nodes, // so it may have side effects that // make it unsafe to elide. break } if len(b.Preds) != 1 { // Block b has multiple predecessors, // so it cannot be treated as a case. break } x, k = isComparisonBlock(b) } sw.Default = b } func typeSwitch(sw *Switch, y ir.Value, T types.Type, seen map[*ir.BasicBlock]bool) { b := sw.Start x := sw.X for isSameX(sw.X, x) { if seen[b] { break } seen[b] = true sw.TypeCases = append(sw.TypeCases, TypeCase{ Block: b, Body: b.Succs[0], Type: T, Binding: y, }) b = b.Succs[1] n := 0 for _, instr := range b.Instrs { switch instr.(type) { case *ir.TypeAssert, *ir.Extract, *ir.If: n++ case *ir.Sigma, *ir.Phi: default: n += 1000 } } if n != 4 { // Block b contains not just // {TypeAssert; Extract #0; Extract #1; If} // so it may have side effects that // make it unsafe to elide. break } if len(b.Preds) != 1 { // Block b has multiple predecessors, // so it cannot be treated as a case. break } y, x, T = isTypeAssertBlock(b) } sw.Default = b } // isComparisonBlock returns the operands (v, k) if a block ends with // a comparison v==k, where k is a compile-time constant. // func isComparisonBlock(b *ir.BasicBlock) (v ir.Value, k *ir.Const) { if n := len(b.Instrs); n >= 2 { if i, ok := b.Instrs[n-1].(*ir.If); ok { if binop, ok := i.Cond.(*ir.BinOp); ok && binop.Block() == b && binop.Op == token.EQL { if k, ok := binop.Y.(*ir.Const); ok { return binop.X, k } if k, ok := binop.X.(*ir.Const); ok { return binop.Y, k } } } } return } // isTypeAssertBlock returns the operands (y, x, T) if a block ends with // a type assertion "if y, ok := x.(T); ok {". // func isTypeAssertBlock(b *ir.BasicBlock) (y, x ir.Value, T types.Type) { if n := len(b.Instrs); n >= 4 { if i, ok := b.Instrs[n-1].(*ir.If); ok { if ext1, ok := i.Cond.(*ir.Extract); ok && ext1.Block() == b && ext1.Index == 1 { if ta, ok := ext1.Tuple.(*ir.TypeAssert); ok && ta.Block() == b { // hack: relies upon instruction ordering. if ext0, ok := b.Instrs[n-3].(*ir.Extract); ok { return ext0, ta.X, ta.AssertedType } } } } } return } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������go-tools-2021.1.2/go/ir/irutil/switch_test.go�������������������������������������������������������0000664�0000000�0000000�00000005107�14143223131�0020715�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. // No testdata on Android. // +build !android package irutil import ( "bytes" "fmt" "go/parser" "strings" "testing" "honnef.co/go/tools/go/ir" "golang.org/x/tools/go/loader" ) func TestSwitches(t *testing.T) { conf := loader.Config{ParserMode: parser.ParseComments} f, err := conf.ParseFile("testdata/switches.go", nil) if err != nil { t.Error(err) return } conf.CreateFromFiles("main", f) iprog, err := conf.Load() if err != nil { t.Error(err) return } prog := CreateProgram(iprog, 0) mainPkg := prog.Package(iprog.Created[0].Pkg) mainPkg.Build() for _, mem := range mainPkg.Members { if fn, ok := mem.(*ir.Function); ok { if fn.Synthetic != 0 { continue // e.g. init() } // Each (multi-line) "switch" comment within // this function must match the printed form // of a ConstSwitch. var wantSwitches []string for _, c := range f.Comments { if fn.Source().Pos() <= c.Pos() && c.Pos() < fn.Source().End() { text := strings.TrimSpace(c.Text()) if strings.HasPrefix(text, "switch ") { wantSwitches = append(wantSwitches, text) } } } switches := Switches(fn) if len(switches) != len(wantSwitches) { t.Errorf("in %s, found %d switches, want %d", fn, len(switches), len(wantSwitches)) } for i, sw := range switches { got := sw.testString() if i >= len(wantSwitches) { continue } want := wantSwitches[i] if got != want { t.Errorf("in %s, found switch %d: got <<%s>>, want <<%s>>", fn, i, got, want) } } } } } func (sw *Switch) testString() string { // same as the actual String method, but use the second to last // instruction instead, to skip over all the phi and sigma nodes // that SSI produces. var buf bytes.Buffer if sw.ConstCases != nil { fmt.Fprintf(&buf, "switch %s {\n", sw.X.Name()) for _, c := range sw.ConstCases { n := len(c.Body.Instrs) - 2 if n < 0 { n = 0 } fmt.Fprintf(&buf, "case %s: %s\n", c.Value.Name(), c.Body.Instrs[n]) } } else { fmt.Fprintf(&buf, "switch %s.(type) {\n", sw.X.Name()) for _, c := range sw.TypeCases { n := len(c.Body.Instrs) - 2 if n < 0 { n = 0 } fmt.Fprintf(&buf, "case %s %s: %s\n", c.Binding.Name(), c.Type, c.Body.Instrs[n]) } } if sw.Default != nil { n := len(sw.Default.Instrs) - 2 if n < 0 { n = 0 } fmt.Fprintf(&buf, "default: %s\n", sw.Default.Instrs[n]) } fmt.Fprintf(&buf, "}") return buf.String() } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������go-tools-2021.1.2/go/ir/irutil/terminates.go��������������������������������������������������������0000664�0000000�0000000�00000003014�14143223131�0020523�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������package irutil import ( "go/types" "honnef.co/go/tools/go/ir" ) // Terminates reports whether fn is supposed to return, that is if it // has at least one theoretic path that returns from the function. // Explicit panics do not count as terminating. func Terminates(fn *ir.Function) bool { if fn.Blocks == nil { // assuming that a function terminates is the conservative // choice return true } for _, block := range fn.Blocks { if _, ok := block.Control().(*ir.Return); ok { if len(block.Preds) == 0 { return true } for _, pred := range block.Preds { switch ctrl := pred.Control().(type) { case *ir.Panic: // explicit panics do not count as terminating case *ir.If: // Check if we got here by receiving from a closed // time.Tick channel – this cannot happen at // runtime and thus doesn't constitute termination iff := ctrl if !ok { return true } ex, ok := iff.Cond.(*ir.Extract) if !ok { return true } if ex.Index != 1 { return true } recv, ok := ex.Tuple.(*ir.Recv) if !ok { return true } call, ok := recv.Chan.(*ir.Call) if !ok { return true } fn, ok := call.Common().Value.(*ir.Function) if !ok { return true } fn2, ok := fn.Object().(*types.Func) if !ok { return true } if fn2.FullName() != "time.Tick" { return true } default: // we've reached the exit block return true } } } } return false } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������go-tools-2021.1.2/go/ir/irutil/testdata/������������������������������������������������������������0000775�0000000�0000000�00000000000�14143223131�0017634�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������go-tools-2021.1.2/go/ir/irutil/testdata/switches.go�������������������������������������������������0000664�0000000�0000000�00000010766�14143223131�0022026�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// +build ignore package main // This file is the input to TestSwitches in switch_test.go. // Each multiway conditional with constant or type cases (Switch) // discovered by Switches is printed, and compared with the // comments. // // The body of each case is printed as the value of its first // instruction. // -------- Value switches -------- func four() int { return 4 } // A non-constant case makes a switch "impure", but its pure // cases form two separate switches. func SwitchWithNonConstantCase(x int) { // switch t8 { // case t1: Call <()> print t1 // case t2: Call <()> print t4 // case t3: Call <()> print t4 // default: BinOp {==} t26 t27 // } // switch t32 { // case t5: Call <()> print t5 // case t6: Call <()> print t6 // default: Call <()> print t7 // } switch x { case 1: print(1) case 2, 3: print(23) case four(): print(3) case 5: print(5) case 6: print(6) } print("done") } // Switches may be found even where the source // program doesn't have a switch statement. func ImplicitSwitches(x, y int) { // switch t12 { // case t1: Call <()> print t4 // case t2: Call <()> print t4 // default: BinOp {<} t27 t3 // } if x == 1 || 2 == x || x < 5 { print(12) } // switch t24 { // case t5: Call <()> print t7 // case t6: Call <()> print t7 // default: BinOp {==} t49 t50 // } if x == 3 || 4 == x || x == y { print(34) } // Not a switch: no consistent variable. if x == 5 || y == 6 { print(56) } // Not a switch: only one constant comparison. if x == 7 || x == y { print(78) } } func IfElseBasedSwitch(x int) { // switch t4 { // case t1: Call <()> print t1 // case t2: Call <()> print t2 // default: Call <()> print t3 // } if x == 1 { print(1) } else if x == 2 { print(2) } else { print("else") } } func GotoBasedSwitch(x int) { // switch t4 { // case t1: Call <()> print t1 // case t2: Call <()> print t2 // default: Call <()> print t3 // } if x == 1 { goto L1 } if x == 2 { goto L2 } print("else") L1: print(1) goto end L2: print(2) end: } func SwitchInAForLoop(x, y int) { // switch t11 { // case t2: Call <()> print t2 // case t3: Call <()> print t3 // default: BinOp {==} t29 t28 // } loop: for { print("head") switch x { case 1: print(1) break loop case 2: print(2) break loop case y: print(3) break loop } } } // This case is a switch in a for-loop, both constructed using goto. // As before, the default case points back to the block containing the // switch, but that's ok. func SwitchInAForLoopUsingGoto(x int) { // switch t8 { // case t2: Call <()> print t2 // case t3: Call <()> print t3 // default: BinOp {==} t8 t2 // } loop: print("head") if x == 1 { goto L1 } if x == 2 { goto L2 } goto loop L1: print(1) goto end L2: print(2) end: } func UnstructuredSwitchInAForLoop(x int) { // switch t8 { // case t1: Call <()> print t1 // case t2: BinOp {==} t8 t1 // default: Call <()> print t3 // } for { if x == 1 { print(1) return } if x == 2 { continue } break } print("end") } func CaseWithMultiplePreds(x int) { for { if x == 1 { print(1) return } loop: // This block has multiple predecessors, // so can't be treated as a switch case. if x == 2 { goto loop } break } print("end") } func DuplicateConstantsAreNotEliminated(x int) { // switch t4 { // case t1: Call <()> print t1 // case t1: Call <()> print t2 // case t3: Call <()> print t3 // default: Return // } if x == 1 { print(1) } else if x == 1 { // duplicate => unreachable print("1a") } else if x == 2 { print(2) } } // Interface values (created by comparisons) are not constants, // so ConstSwitch.X is never of interface type. func MakeInterfaceIsNotAConstant(x interface{}) { if x == "foo" { print("foo") } else if x == 1 { print(1) } } func ZeroInitializedVarsAreConstants(x int) { // switch t5 { // case t4: Call <()> print t1 // case t2: Call <()> print t2 // default: Call <()> print t3 // } var zero int // SSA construction replaces zero with 0 if x == zero { print(1) } else if x == 2 { print(2) } print("end") } // -------- Type switches -------- // NB, potentially fragile reliance on register number. func AdHocTypeSwitch(x interface{}) { // switch t2.(type) { // case t4 int: Call <()> println t8 // case t13 string: Call <()> println t16 // default: Call <()> print t1 // } if i, ok := x.(int); ok { println(i) } else if s, ok := x.(string); ok { println(s) } else { print("default") } } ����������go-tools-2021.1.2/go/ir/irutil/util.go��������������������������������������������������������������0000664�0000000�0000000�00000005572�14143223131�0017340�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������package irutil import ( "go/types" "strings" "honnef.co/go/tools/go/ir" "honnef.co/go/tools/go/types/typeutil" ) func Reachable(from, to *ir.BasicBlock) bool { if from == to { return true } if from.Dominates(to) { return true } found := false Walk(from, func(b *ir.BasicBlock) bool { if b == to { found = true return false } return true }) return found } func Walk(b *ir.BasicBlock, fn func(*ir.BasicBlock) bool) { seen := map[*ir.BasicBlock]bool{} wl := []*ir.BasicBlock{b} for len(wl) > 0 { b := wl[len(wl)-1] wl = wl[:len(wl)-1] if seen[b] { continue } seen[b] = true if !fn(b) { continue } wl = append(wl, b.Succs...) } } func Vararg(x *ir.Slice) ([]ir.Value, bool) { var out []ir.Value slice, ok := x.X.(*ir.Alloc) if !ok { return nil, false } for _, ref := range *slice.Referrers() { if ref == x { continue } if ref.Block() != x.Block() { return nil, false } idx, ok := ref.(*ir.IndexAddr) if !ok { return nil, false } if len(*idx.Referrers()) != 1 { return nil, false } store, ok := (*idx.Referrers())[0].(*ir.Store) if !ok { return nil, false } out = append(out, store.Val) } return out, true } func CallName(call *ir.CallCommon) string { if call.IsInvoke() { return "" } switch v := call.Value.(type) { case *ir.Function: fn, ok := v.Object().(*types.Func) if !ok { return "" } return typeutil.FuncName(fn) case *ir.Builtin: return v.Name() } return "" } func IsCallTo(call *ir.CallCommon, name string) bool { return CallName(call) == name } func IsCallToAny(call *ir.CallCommon, names ...string) bool { q := CallName(call) for _, name := range names { if q == name { return true } } return false } func FilterDebug(instr []ir.Instruction) []ir.Instruction { var out []ir.Instruction for _, ins := range instr { if _, ok := ins.(*ir.DebugRef); !ok { out = append(out, ins) } } return out } func IsExample(fn *ir.Function) bool { if !strings.HasPrefix(fn.Name(), "Example") { return false } f := fn.Prog.Fset.File(fn.Pos()) if f == nil { return false } return strings.HasSuffix(f.Name(), "_test.go") } // Flatten recursively returns the underlying value of an ir.Sigma or // ir.Phi node. If all edges in an ir.Phi node are the same (after // flattening), the flattened edge will get returned. If flattening is // not possible, nil is returned. func Flatten(v ir.Value) ir.Value { failed := false seen := map[ir.Value]struct{}{} var out ir.Value var dfs func(v ir.Value) dfs = func(v ir.Value) { if failed { return } if _, ok := seen[v]; ok { return } seen[v] = struct{}{} switch v := v.(type) { case *ir.Sigma: dfs(v.X) case *ir.Phi: for _, e := range v.Edges { dfs(e) } default: if out == nil { out = v } else if out != v { failed = true } } } dfs(v) if failed { return nil } return out } ��������������������������������������������������������������������������������������������������������������������������������������go-tools-2021.1.2/go/ir/irutil/visit.go�������������������������������������������������������������0000664�0000000�0000000�00000003766�14143223131�0017524�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 irutil import "honnef.co/go/tools/go/ir" // This file defines utilities for visiting the IR of // a Program. // // TODO(adonovan): test coverage. // AllFunctions finds and returns the set of functions potentially // needed by program prog, as determined by a simple linker-style // reachability algorithm starting from the members and method-sets of // each package. The result may include anonymous functions and // synthetic wrappers. // // Precondition: all packages are built. // func AllFunctions(prog *ir.Program) map[*ir.Function]bool { visit := visitor{ prog: prog, seen: make(map[*ir.Function]bool), } visit.program() return visit.seen } type visitor struct { prog *ir.Program seen map[*ir.Function]bool } func (visit *visitor) program() { for _, pkg := range visit.prog.AllPackages() { for _, mem := range pkg.Members { if fn, ok := mem.(*ir.Function); ok { visit.function(fn) } } } for _, T := range visit.prog.RuntimeTypes() { mset := visit.prog.MethodSets.MethodSet(T) for i, n := 0, mset.Len(); i < n; i++ { visit.function(visit.prog.MethodValue(mset.At(i))) } } } func (visit *visitor) function(fn *ir.Function) { if !visit.seen[fn] { visit.seen[fn] = true var buf [10]*ir.Value // avoid alloc in common case for _, b := range fn.Blocks { for _, instr := range b.Instrs { for _, op := range instr.Operands(buf[:0]) { if fn, ok := (*op).(*ir.Function); ok { visit.function(fn) } } } } } } // MainPackages returns the subset of the specified packages // named "main" that define a main function. // The result may include synthetic "testmain" packages. func MainPackages(pkgs []*ir.Package) []*ir.Package { var mains []*ir.Package for _, pkg := range pkgs { if pkg.Pkg.Name() == "main" && pkg.Func("main") != nil { mains = append(mains, pkg) } } return mains } ����������go-tools-2021.1.2/go/ir/lift.go���������������������������������������������������������������������0000664�0000000�0000000�00000065314�14143223131�0016011�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 lifting pass which tries to "lift" Alloc // cells (new/local variables) into SSA registers, replacing loads // with the dominating stored value, eliminating loads and stores, and // inserting Ο†- and Οƒ-nodes as needed. // Cited papers and resources: // // Ron Cytron et al. 1991. Efficiently computing SSA form... // http://doi.acm.org/10.1145/115372.115320 // // Cooper, Harvey, Kennedy. 2001. A Simple, Fast Dominance Algorithm. // Software Practice and Experience 2001, 4:1-10. // http://www.hipersoft.rice.edu/grads/publications/dom14.pdf // // Daniel Berlin, llvmdev mailing list, 2012. // http://lists.cs.uiuc.edu/pipermail/llvmdev/2012-January/046638.html // (Be sure to expand the whole thread.) // // C. Scott Ananian. 1997. The static single information form. // // Jeremy Singer. 2006. Static program analysis based on virtual register renaming. // TODO(adonovan): opt: there are many optimizations worth evaluating, and // the conventional wisdom for SSA construction is that a simple // algorithm well engineered often beats those of better asymptotic // complexity on all but the most egregious inputs. // // Danny Berlin suggests that the Cooper et al. algorithm for // computing the dominance frontier is superior to Cytron et al. // Furthermore he recommends that rather than computing the DF for the // whole function then renaming all alloc cells, it may be cheaper to // compute the DF for each alloc cell separately and throw it away. // // Consider exploiting liveness information to avoid creating dead // Ο†-nodes which we then immediately remove. // // Also see many other "TODO: opt" suggestions in the code. import ( "fmt" "go/types" "os" ) // If true, show diagnostic information at each step of lifting. // Very verbose. const debugLifting = false // domFrontier maps each block to the set of blocks in its dominance // frontier. The outer slice is conceptually a map keyed by // Block.Index. The inner slice is conceptually a set, possibly // containing duplicates. // // TODO(adonovan): opt: measure impact of dups; consider a packed bit // representation, e.g. big.Int, and bitwise parallel operations for // the union step in the Children loop. // // domFrontier's methods mutate the slice's elements but not its // length, so their receivers needn't be pointers. // type domFrontier [][]*BasicBlock func (df domFrontier) add(u, v *BasicBlock) { df[u.Index] = append(df[u.Index], v) } // build builds the dominance frontier df for the dominator tree of // fn, using the algorithm found in A Simple, Fast Dominance // Algorithm, Figure 5. // // TODO(adonovan): opt: consider Berlin approach, computing pruned SSA // by pruning the entire IDF computation, rather than merely pruning // the DF -> IDF step. func (df domFrontier) build(fn *Function) { for _, b := range fn.Blocks { preds := b.Preds[0:len(b.Preds):len(b.Preds)] if b == fn.Exit { for i, v := range fn.fakeExits.values { if v { preds = append(preds, fn.Blocks[i]) } } } if len(preds) >= 2 { for _, p := range preds { runner := p for runner != b.dom.idom { df.add(runner, b) runner = runner.dom.idom } } } } } func buildDomFrontier(fn *Function) domFrontier { df := make(domFrontier, len(fn.Blocks)) df.build(fn) return df } type postDomFrontier [][]*BasicBlock func (rdf postDomFrontier) add(u, v *BasicBlock) { rdf[u.Index] = append(rdf[u.Index], v) } func (rdf postDomFrontier) build(fn *Function) { for _, b := range fn.Blocks { succs := b.Succs[0:len(b.Succs):len(b.Succs)] if fn.fakeExits.Has(b) { succs = append(succs, fn.Exit) } if len(succs) >= 2 { for _, s := range succs { runner := s for runner != b.pdom.idom { rdf.add(runner, b) runner = runner.pdom.idom } } } } } func buildPostDomFrontier(fn *Function) postDomFrontier { rdf := make(postDomFrontier, len(fn.Blocks)) rdf.build(fn) return rdf } func removeInstr(refs []Instruction, instr Instruction) []Instruction { i := 0 for _, ref := range refs { if ref == instr { continue } refs[i] = ref i++ } for j := i; j != len(refs); j++ { refs[j] = nil // aid GC } return refs[:i] } func clearInstrs(instrs []Instruction) { for i := range instrs { instrs[i] = nil } } // lift replaces local and new Allocs accessed only with // load/store by IR registers, inserting Ο†- and Οƒ-nodes where necessary. // The result is a program in pruned SSI form. // // Preconditions: // - fn has no dead blocks (blockopt has run). // - Def/use info (Operands and Referrers) is up-to-date. // - The dominator tree is up-to-date. // func lift(fn *Function) { // TODO(adonovan): opt: lots of little optimizations may be // worthwhile here, especially if they cause us to avoid // buildDomFrontier. For example: // // - Alloc never loaded? Eliminate. // - Alloc never stored? Replace all loads with a zero constant. // - Alloc stored once? Replace loads with dominating store; // don't forget that an Alloc is itself an effective store // of zero. // - Alloc used only within a single block? // Use degenerate algorithm avoiding Ο†-nodes. // - Consider synergy with scalar replacement of aggregates (SRA). // e.g. *(&x.f) where x is an Alloc. // Perhaps we'd get better results if we generated this as x.f // i.e. Field(x, .f) instead of Load(FieldIndex(x, .f)). // Unclear. // // But we will start with the simplest correct code. var df domFrontier var rdf postDomFrontier var closure *closure var newPhis newPhiMap var newSigmas newSigmaMap // During this pass we will replace some BasicBlock.Instrs // (allocs, loads and stores) with nil, keeping a count in // BasicBlock.gaps. At the end we will reset Instrs to the // concatenation of all non-dead newPhis and non-nil Instrs // for the block, reusing the original array if space permits. // While we're here, we also eliminate 'rundefers' // instructions in functions that contain no 'defer' // instructions. usesDefer := false // Determine which allocs we can lift and number them densely. // The renaming phase uses this numbering for compact maps. numAllocs := 0 for _, b := range fn.Blocks { b.gaps = 0 b.rundefers = 0 for _, instr := range b.Instrs { switch instr := instr.(type) { case *Alloc: if !liftable(instr) { instr.index = -1 continue } index := -1 if numAllocs == 0 { df = buildDomFrontier(fn) rdf = buildPostDomFrontier(fn) if len(fn.Blocks) > 2 { closure = transitiveClosure(fn) } newPhis = make(newPhiMap, len(fn.Blocks)) newSigmas = make(newSigmaMap, len(fn.Blocks)) if debugLifting { title := false for i, blocks := range df { if blocks != nil { if !title { fmt.Fprintf(os.Stderr, "Dominance frontier of %s:\n", fn) title = true } fmt.Fprintf(os.Stderr, "\t%s: %s\n", fn.Blocks[i], blocks) } } } } liftAlloc(closure, df, rdf, instr, newPhis, newSigmas) index = numAllocs numAllocs++ instr.index = index case *Defer: usesDefer = true case *RunDefers: b.rundefers++ } } } if numAllocs > 0 { // renaming maps an alloc (keyed by index) to its replacement // value. Initially the renaming contains nil, signifying the // zero constant of the appropriate type; we construct the // Const lazily at most once on each path through the domtree. // TODO(adonovan): opt: cache per-function not per subtree. renaming := make([]Value, numAllocs) // Renaming. rename(fn.Blocks[0], renaming, newPhis, newSigmas) simplifyPhis(newPhis) // Eliminate dead Ο†- and Οƒ-nodes. markLiveNodes(fn.Blocks, newPhis, newSigmas) } // Prepend remaining live Ο†-nodes to each block and possibly kill rundefers. for _, b := range fn.Blocks { var head []Instruction if numAllocs > 0 { nps := newPhis[b.Index] head = make([]Instruction, 0, len(nps)) for _, pred := range b.Preds { nss := newSigmas[pred.Index] idx := pred.succIndex(b) for _, newSigma := range nss { if sigma := newSigma.sigmas[idx]; sigma != nil && sigma.live { head = append(head, sigma) // we didn't populate referrers before, as most // sigma nodes will be killed if refs := sigma.X.Referrers(); refs != nil { *refs = append(*refs, sigma) } } else if sigma != nil { sigma.block = nil } } } for _, np := range nps { if np.phi.live { head = append(head, np.phi) } else { for _, edge := range np.phi.Edges { if refs := edge.Referrers(); refs != nil { *refs = removeInstr(*refs, np.phi) } } np.phi.block = nil } } } rundefersToKill := b.rundefers if usesDefer { rundefersToKill = 0 } j := len(head) if j+b.gaps+rundefersToKill == 0 { continue // fast path: no new phis or gaps } // We could do straight copies instead of element-wise copies // when both b.gaps and rundefersToKill are zero. However, // that seems to only be the case ~1% of the time, which // doesn't seem worth the extra branch. // Remove dead instructions, add phis and sigmas ns := len(b.Instrs) + j - b.gaps - rundefersToKill if ns <= cap(b.Instrs) { // b.Instrs has enough capacity to store all instructions // OPT(dh): check cap vs the actually required space; if // there is a big enough difference, it may be worth // allocating a new slice, to avoid pinning memory. dst := b.Instrs[:cap(b.Instrs)] i := len(dst) - 1 for n := len(b.Instrs) - 1; n >= 0; n-- { instr := dst[n] if instr == nil { continue } if !usesDefer { if _, ok := instr.(*RunDefers); ok { continue } } dst[i] = instr i-- } off := i + 1 - len(head) // aid GC clearInstrs(dst[:off]) dst = dst[off:] copy(dst, head) b.Instrs = dst } else { // not enough space, so allocate a new slice and copy // over. dst := make([]Instruction, ns) copy(dst, head) for _, instr := range b.Instrs { if instr == nil { continue } if !usesDefer { if _, ok := instr.(*RunDefers); ok { continue } } dst[j] = instr j++ } b.Instrs = dst } } // Remove any fn.Locals that were lifted. j := 0 for _, l := range fn.Locals { if l.index < 0 { fn.Locals[j] = l j++ } } // Nil out fn.Locals[j:] to aid GC. for i := j; i < len(fn.Locals); i++ { fn.Locals[i] = nil } fn.Locals = fn.Locals[:j] } func hasDirectReferrer(instr Instruction) bool { for _, instr := range *instr.Referrers() { switch instr.(type) { case *Phi, *Sigma: // ignore default: return true } } return false } func markLiveNodes(blocks []*BasicBlock, newPhis newPhiMap, newSigmas newSigmaMap) { // Phi and sigma nodes are considered live if a non-phi, non-sigma // node uses them. Once we find a node that is live, we mark all // of its operands as used, too. for _, npList := range newPhis { for _, np := range npList { phi := np.phi if !phi.live && hasDirectReferrer(phi) { markLivePhi(phi) } } } for _, npList := range newSigmas { for _, np := range npList { for _, sigma := range np.sigmas { if sigma != nil && !sigma.live && hasDirectReferrer(sigma) { markLiveSigma(sigma) } } } } // Existing Ο†-nodes due to && and || operators // are all considered live (see Go issue 19622). for _, b := range blocks { for _, phi := range b.phis() { markLivePhi(phi.(*Phi)) } } } func markLivePhi(phi *Phi) { phi.live = true for _, rand := range phi.Edges { switch rand := rand.(type) { case *Phi: if !rand.live { markLivePhi(rand) } case *Sigma: if !rand.live { markLiveSigma(rand) } } } } func markLiveSigma(sigma *Sigma) { sigma.live = true switch rand := sigma.X.(type) { case *Phi: if !rand.live { markLivePhi(rand) } case *Sigma: if !rand.live { markLiveSigma(rand) } } } // simplifyPhis replaces trivial phis with non-phi alternatives. Phi // nodes where all edges are identical, or consist of only the phi // itself and one other value, may be replaced with the value. func simplifyPhis(newPhis newPhiMap) { // find all phis that are trivial and can be replaced with a // non-phi value. run until we reach a fixpoint, because replacing // a phi may make other phis trivial. for changed := true; changed; { changed = false for _, npList := range newPhis { for _, np := range npList { if np.phi.live { // we're reusing 'live' to mean 'dead' in the context of simplifyPhis continue } if r, ok := isUselessPhi(np.phi); ok { // useless phi, replace its uses with the // replacement value. the dead phi pass will clean // up the phi afterwards. replaceAll(np.phi, r) np.phi.live = true changed = true } } } } for _, npList := range newPhis { for _, np := range npList { np.phi.live = false } } } type BlockSet struct { idx int values []bool count int } func NewBlockSet(size int) *BlockSet { return &BlockSet{values: make([]bool, size)} } func (s *BlockSet) Set(s2 *BlockSet) { copy(s.values, s2.values) s.count = 0 for _, v := range s.values { if v { s.count++ } } } func (s *BlockSet) Num() int { return s.count } func (s *BlockSet) Has(b *BasicBlock) bool { if b.Index >= len(s.values) { return false } return s.values[b.Index] } // add adds b to the set and returns true if the set changed. func (s *BlockSet) Add(b *BasicBlock) bool { if s.values[b.Index] { return false } s.count++ s.values[b.Index] = true s.idx = b.Index return true } func (s *BlockSet) Clear() { for j := range s.values { s.values[j] = false } s.count = 0 } // take removes an arbitrary element from a set s and // returns its index, or returns -1 if empty. func (s *BlockSet) Take() int { // [i, end] for i := s.idx; i < len(s.values); i++ { if s.values[i] { s.values[i] = false s.idx = i s.count-- return i } } // [start, i) for i := 0; i < s.idx; i++ { if s.values[i] { s.values[i] = false s.idx = i s.count-- return i } } return -1 } type closure struct { span []uint32 reachables []interval } type interval uint32 const ( flagMask = 1 << 31 numBits = 20 lengthBits = 32 - numBits - 1 lengthMask = (1<>numBits } else { // large interval i++ start = uint32(inv & numMask) end = uint32(r[i]) } if idx >= start && idx <= end { return true } } return false } func (c closure) reachable(id int) []interval { return c.reachables[c.span[id]:c.span[id+1]] } func (c closure) walk(current *BasicBlock, b *BasicBlock, visited []bool) { visited[b.Index] = true for _, succ := range b.Succs { if visited[succ.Index] { continue } visited[succ.Index] = true c.walk(current, succ, visited) } } func transitiveClosure(fn *Function) *closure { reachable := make([]bool, len(fn.Blocks)) c := &closure{} c.span = make([]uint32, len(fn.Blocks)+1) addInterval := func(start, end uint32) { if l := end - start; l <= 1<= 0 { // store of zero to Alloc cell // Replace dominated loads by the zero value. renaming[instr.index] = nil if debugLifting { fmt.Fprintf(os.Stderr, "\tkill alloc %s\n", instr) } // Delete the Alloc. u.Instrs[i] = nil u.gaps++ } case *Store: if alloc, ok := instr.Addr.(*Alloc); ok && alloc.index >= 0 { // store to Alloc cell // Replace dominated loads by the stored value. renaming[alloc.index] = instr.Val if debugLifting { fmt.Fprintf(os.Stderr, "\tkill store %s; new value: %s\n", instr, instr.Val.Name()) } if refs := instr.Addr.Referrers(); refs != nil { *refs = removeInstr(*refs, instr) } if refs := instr.Val.Referrers(); refs != nil { *refs = removeInstr(*refs, instr) } // Delete the Store. u.Instrs[i] = nil u.gaps++ } case *Load: if alloc, ok := instr.X.(*Alloc); ok && alloc.index >= 0 { // load of Alloc cell // In theory, we wouldn't be able to replace loads // directly, because a loaded value could be used in // different branches, in which case it should be // replaced with different sigma nodes. But we can't // simply defer replacement, either, because then // later stores might incorrectly affect this load. // // To avoid doing renaming on _all_ values (instead of // just loads and stores like we're doing), we make // sure during code generation that each load is only // used in one block. For example, in constant switch // statements, where the tag is only evaluated once, // we store it in a temporary and load it for each // comparison, so that we have individual loads to // replace. newval := renamed(u.Parent(), renaming, alloc) if debugLifting { fmt.Fprintf(os.Stderr, "\tupdate load %s = %s with %s\n", instr.Name(), instr, newval) } replaceAll(instr, newval) u.Instrs[i] = nil u.gaps++ } case *DebugRef: if x, ok := instr.X.(*Alloc); ok && x.index >= 0 { if instr.IsAddr { instr.X = renamed(u.Parent(), renaming, x) instr.IsAddr = false // Add DebugRef to instr.X's referrers. if refs := instr.X.Referrers(); refs != nil { *refs = append(*refs, instr) } } else { // A source expression denotes the address // of an Alloc that was optimized away. instr.X = nil // Delete the DebugRef. u.Instrs[i] = nil u.gaps++ } } } } // update all outgoing sigma nodes with the dominating store for _, sigmas := range newSigmas[u.Index] { for _, sigma := range sigmas.sigmas { if sigma == nil { continue } sigma.X = renamed(u.Parent(), renaming, sigmas.alloc) } } // For each Ο†-node in a CFG successor, rename the edge. for succi, v := range u.Succs { phis := newPhis[v.Index] if len(phis) == 0 { continue } i := v.predIndex(u) for _, np := range phis { phi := np.phi alloc := np.alloc // if there's a sigma node, use it, else use the dominating value var newval Value for _, sigmas := range newSigmas[u.Index] { if sigmas.alloc == alloc && sigmas.sigmas[succi] != nil { newval = sigmas.sigmas[succi] break } } if newval == nil { newval = renamed(u.Parent(), renaming, alloc) } if debugLifting { fmt.Fprintf(os.Stderr, "\tsetphi %s edge %s -> %s (#%d) (alloc=%s) := %s\n", phi.Name(), u, v, i, alloc.Name(), newval.Name()) } phi.Edges[i] = newval if prefs := newval.Referrers(); prefs != nil { *prefs = append(*prefs, phi) } } } // Continue depth-first recursion over domtree, pushing a // fresh copy of the renaming map for each subtree. r := make([]Value, len(renaming)) for _, v := range u.dom.children { // XXX add debugging copy(r, renaming) // on entry to a block, the incoming sigma nodes become the new values for their alloc if idx := u.succIndex(v); idx != -1 { for _, sigma := range newSigmas[u.Index] { if sigma.sigmas[idx] != nil { r[sigma.alloc.index] = sigma.sigmas[idx] } } } rename(v, r, newPhis, newSigmas) } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������go-tools-2021.1.2/go/ir/lvalue.go�������������������������������������������������������������������0000664�0000000�0000000�00000005626�14143223131�0016343�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 // lvalues are the union of addressable expressions and map-index // expressions. import ( "go/ast" "go/types" ) // An lvalue represents an assignable location that may appear on the // left-hand side of an assignment. This is a generalization of a // pointer to permit updates to elements of maps. // type lvalue interface { store(fn *Function, v Value, source ast.Node) // stores v into the location load(fn *Function, source ast.Node) Value // loads the contents of the location address(fn *Function) Value // address of the location typ() types.Type // returns the type of the location } // An address is an lvalue represented by a true pointer. type address struct { addr Value expr ast.Expr // source syntax of the value (not address) [debug mode] } func (a *address) load(fn *Function, source ast.Node) Value { return emitLoad(fn, a.addr, source) } func (a *address) store(fn *Function, v Value, source ast.Node) { store := emitStore(fn, a.addr, v, source) if a.expr != nil { // store.Val is v, converted for assignability. emitDebugRef(fn, a.expr, store.Val, false) } } func (a *address) address(fn *Function) Value { if a.expr != nil { emitDebugRef(fn, a.expr, a.addr, true) } return a.addr } func (a *address) typ() types.Type { return deref(a.addr.Type()) } // An element is an lvalue represented by m[k], the location of an // element of a map. These locations are not addressable // since pointers cannot be formed from them, but they do support // load() and store(). // type element struct { m, k Value // map t types.Type // map element type } func (e *element) load(fn *Function, source ast.Node) Value { l := &MapLookup{ X: e.m, Index: e.k, } l.setType(e.t) return fn.emit(l, source) } func (e *element) store(fn *Function, v Value, source ast.Node) { up := &MapUpdate{ Map: e.m, Key: e.k, Value: emitConv(fn, v, e.t, source), } fn.emit(up, source) } func (e *element) address(fn *Function) Value { panic("map elements are not addressable") } func (e *element) typ() types.Type { return e.t } // A blank is a dummy variable whose name is "_". // It is not reified: loads are illegal and stores are ignored. // type blank struct{} func (bl blank) load(fn *Function, source ast.Node) Value { panic("blank.load is illegal") } func (bl blank) store(fn *Function, v Value, source ast.Node) { s := &BlankStore{ Val: v, } fn.emit(s, source) } func (bl blank) address(fn *Function) Value { panic("blank var is not addressable") } func (bl blank) typ() types.Type { // This should be the type of the blank Ident; the typechecker // doesn't provide this yet, but fortunately, we don't need it // yet either. panic("blank.typ is unimplemented") } ����������������������������������������������������������������������������������������������������������go-tools-2021.1.2/go/ir/methods.go������������������������������������������������������������������0000664�0000000�0000000�00000014525�14143223131�0016514�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 utilities for population of method sets. import ( "fmt" "go/types" ) // MethodValue returns the Function implementing method sel, building // wrapper methods on demand. It returns nil if sel denotes an // abstract (interface) method. // // Precondition: sel.Kind() == MethodVal. // // Thread-safe. // // EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu) // func (prog *Program) MethodValue(sel *types.Selection) *Function { if sel.Kind() != types.MethodVal { panic(fmt.Sprintf("MethodValue(%s) kind != MethodVal", sel)) } T := sel.Recv() if isInterface(T) { return nil // abstract method } if prog.mode&LogSource != 0 { defer logStack("MethodValue %s %v", T, sel)() } prog.methodsMu.Lock() defer prog.methodsMu.Unlock() return prog.addMethod(prog.createMethodSet(T), sel) } // LookupMethod returns the implementation of the method of type T // identified by (pkg, name). It returns nil if the method exists but // is abstract, and panics if T has no such method. // func (prog *Program) LookupMethod(T types.Type, pkg *types.Package, name string) *Function { sel := prog.MethodSets.MethodSet(T).Lookup(pkg, name) if sel == nil { panic(fmt.Sprintf("%s has no method %s", T, types.Id(pkg, name))) } return prog.MethodValue(sel) } // methodSet contains the (concrete) methods of a non-interface type. type methodSet struct { mapping map[string]*Function // populated lazily complete bool // mapping contains all methods } // Precondition: !isInterface(T). // EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu) func (prog *Program) createMethodSet(T types.Type) *methodSet { mset, ok := prog.methodSets.At(T).(*methodSet) if !ok { mset = &methodSet{mapping: make(map[string]*Function)} prog.methodSets.Set(T, mset) } return mset } // EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu) func (prog *Program) addMethod(mset *methodSet, sel *types.Selection) *Function { if sel.Kind() == types.MethodExpr { panic(sel) } id := sel.Obj().Id() fn := mset.mapping[id] if fn == nil { obj := sel.Obj().(*types.Func) needsPromotion := len(sel.Index()) > 1 needsIndirection := !isPointer(recvType(obj)) && isPointer(sel.Recv()) if needsPromotion || needsIndirection { fn = makeWrapper(prog, sel) } else { fn = prog.declaredFunc(obj) } if fn.Signature.Recv() == nil { panic(fn) // missing receiver } mset.mapping[id] = fn } return fn } // RuntimeTypes returns a new unordered slice containing all // concrete types in the program for which a complete (non-empty) // method set is required at run-time. // // Thread-safe. // // EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu) // func (prog *Program) RuntimeTypes() []types.Type { prog.methodsMu.Lock() defer prog.methodsMu.Unlock() var res []types.Type prog.methodSets.Iterate(func(T types.Type, v interface{}) { if v.(*methodSet).complete { res = append(res, T) } }) return res } // declaredFunc returns the concrete function/method denoted by obj. // Panic ensues if there is none. // func (prog *Program) declaredFunc(obj *types.Func) *Function { if v := prog.packageLevelValue(obj); v != nil { return v.(*Function) } panic("no concrete method: " + obj.String()) } // needMethodsOf ensures that runtime type information (including the // complete method set) is available for the specified type T and all // its subcomponents. // // needMethodsOf must be called for at least every type that is an // operand of some MakeInterface instruction, and for the type of // every exported package member. // // Precondition: T is not a method signature (*Signature with Recv()!=nil). // // Thread-safe. (Called via emitConv from multiple builder goroutines.) // // TODO(adonovan): make this faster. It accounts for 20% of SSA build time. // // EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu) // func (prog *Program) needMethodsOf(T types.Type) { prog.methodsMu.Lock() prog.needMethods(T, false) prog.methodsMu.Unlock() } // Precondition: T is not a method signature (*Signature with Recv()!=nil). // Recursive case: skip => don't create methods for T. // // EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu) // func (prog *Program) needMethods(T types.Type, skip bool) { // Each package maintains its own set of types it has visited. if prevSkip, ok := prog.runtimeTypes.At(T).(bool); ok { // needMethods(T) was previously called if !prevSkip || skip { return // already seen, with same or false 'skip' value } } prog.runtimeTypes.Set(T, skip) tmset := prog.MethodSets.MethodSet(T) if !skip && !isInterface(T) && tmset.Len() > 0 { // Create methods of T. mset := prog.createMethodSet(T) if !mset.complete { mset.complete = true n := tmset.Len() for i := 0; i < n; i++ { prog.addMethod(mset, tmset.At(i)) } } } // Recursion over signatures of each method. for i := 0; i < tmset.Len(); i++ { sig := tmset.At(i).Type().(*types.Signature) prog.needMethods(sig.Params(), false) prog.needMethods(sig.Results(), false) } switch t := T.(type) { case *types.Basic: // nop case *types.Interface: // nop---handled by recursion over method set. case *types.Pointer: prog.needMethods(t.Elem(), false) case *types.Slice: prog.needMethods(t.Elem(), false) case *types.Chan: prog.needMethods(t.Elem(), false) case *types.Map: prog.needMethods(t.Key(), false) prog.needMethods(t.Elem(), false) case *types.Signature: if t.Recv() != nil { panic(fmt.Sprintf("Signature %s has Recv %s", t, t.Recv())) } prog.needMethods(t.Params(), false) prog.needMethods(t.Results(), false) case *types.Named: // A pointer-to-named type can be derived from a named // type via reflection. It may have methods too. prog.needMethods(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. prog.needMethods(t.Underlying(), true) case *types.Array: prog.needMethods(t.Elem(), false) case *types.Struct: for i, n := 0, t.NumFields(); i < n; i++ { prog.needMethods(t.Field(i).Type(), false) } case *types.Tuple: for i, n := 0, t.Len(); i < n; i++ { prog.needMethods(t.At(i).Type(), false) } default: panic(T) } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������go-tools-2021.1.2/go/ir/mode.go���������������������������������������������������������������������0000664�0000000�0000000�00000005135�14143223131�0015772�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// Copyright 2015 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package ir // This file defines the BuilderMode type and its command-line flag. import ( "bytes" "fmt" ) // BuilderMode is a bitmask of options for diagnostics and checking. // // *BuilderMode satisfies the flag.Value interface. Example: // // var mode = ir.BuilderMode(0) // func init() { flag.Var(&mode, "build", ir.BuilderModeDoc) } // type BuilderMode uint const ( PrintPackages BuilderMode = 1 << iota // Print package inventory to stdout PrintFunctions // Print function IR code to stdout PrintSource // Print source code when printing function IR LogSource // Log source locations as IR builder progresses SanityCheckFunctions // Perform sanity checking of function bodies NaiveForm // Build naΓ―ve IR form: don't replace local loads/stores with registers GlobalDebug // Enable debug info for all packages ) const BuilderModeDoc = `Options controlling the IR builder. The value is a sequence of zero or more of these letters: C perform sanity [C]hecking of the IR form. D include [D]ebug info for every function. P print [P]ackage inventory. F print [F]unction IR code. A print [A]ST nodes responsible for IR instructions S log [S]ource locations as IR builder progresses. N build [N]aive IR form: don't replace local loads/stores with registers. ` func (m BuilderMode) String() string { var buf bytes.Buffer if m&GlobalDebug != 0 { buf.WriteByte('D') } if m&PrintPackages != 0 { buf.WriteByte('P') } if m&PrintFunctions != 0 { buf.WriteByte('F') } if m&PrintSource != 0 { buf.WriteByte('A') } if m&LogSource != 0 { buf.WriteByte('S') } if m&SanityCheckFunctions != 0 { buf.WriteByte('C') } if m&NaiveForm != 0 { buf.WriteByte('N') } return buf.String() } // Set parses the flag characters in s and updates *m. func (m *BuilderMode) Set(s string) error { var mode BuilderMode for _, c := range s { switch c { case 'D': mode |= GlobalDebug case 'P': mode |= PrintPackages case 'F': mode |= PrintFunctions case 'A': mode |= PrintSource case 'S': mode |= LogSource case 'C': mode |= SanityCheckFunctions case 'N': mode |= NaiveForm default: return fmt.Errorf("unknown BuilderMode option: %q", c) } } *m = mode return nil } // Get returns m. func (m BuilderMode) Get() interface{} { return m } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������go-tools-2021.1.2/go/ir/print.go��������������������������������������������������������������������0000664�0000000�0000000�00000030705�14143223131�0016203�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 String() methods for all Value and // Instruction types. import ( "bytes" "fmt" "go/types" "io" "reflect" "sort" "honnef.co/go/tools/go/types/typeutil" ) // relName returns the name of v relative to i. // In most cases, this is identical to v.Name(), but references to // Functions (including methods) and Globals use RelString and // all types are displayed with relType, so that only cross-package // references are package-qualified. // func relName(v Value, i Instruction) string { if v == nil { return "" } var from *types.Package if i != nil { from = i.Parent().pkg() } switch v := v.(type) { case Member: // *Function or *Global return v.RelString(from) } return v.Name() } func relType(t types.Type, from *types.Package) string { return types.TypeString(t, types.RelativeTo(from)) } func relString(m Member, from *types.Package) string { // NB: not all globals have an Object (e.g. init$guard), // so use Package().Object not Object.Package(). if pkg := m.Package().Pkg; pkg != nil && pkg != from { return fmt.Sprintf("%s.%s", pkg.Path(), m.Name()) } return m.Name() } // Value.String() // // This method is provided only for debugging. // It never appears in disassembly, which uses Value.Name(). func (v *Parameter) String() string { from := v.Parent().pkg() return fmt.Sprintf("Parameter <%s> {%s}", relType(v.Type(), from), v.name) } func (v *FreeVar) String() string { from := v.Parent().pkg() return fmt.Sprintf("FreeVar <%s> %s", relType(v.Type(), from), v.Name()) } func (v *Builtin) String() string { return fmt.Sprintf("Builtin %s", v.Name()) } // Instruction.String() func (v *Alloc) String() string { from := v.Parent().pkg() storage := "Stack" if v.Heap { storage = "Heap" } return fmt.Sprintf("%sAlloc <%s>", storage, relType(v.Type(), from)) } func (v *Sigma) String() string { from := v.Parent().pkg() s := fmt.Sprintf("Sigma <%s> [b%d] %s", relType(v.Type(), from), v.From.Index, v.X.Name()) return s } func (v *Phi) String() string { var b bytes.Buffer fmt.Fprintf(&b, "Phi <%s>", v.Type()) for i, edge := range v.Edges { b.WriteString(" ") // Be robust against malformed CFG. if v.block == nil { b.WriteString("??") continue } block := -1 if i < len(v.block.Preds) { block = v.block.Preds[i].Index } fmt.Fprintf(&b, "%d:", block) edgeVal := "" // be robust if edge != nil { edgeVal = relName(edge, v) } b.WriteString(edgeVal) } return b.String() } func printCall(v *CallCommon, prefix string, instr Instruction) string { var b bytes.Buffer if !v.IsInvoke() { if value, ok := instr.(Value); ok { fmt.Fprintf(&b, "%s <%s> %s", prefix, relType(value.Type(), instr.Parent().pkg()), relName(v.Value, instr)) } else { fmt.Fprintf(&b, "%s %s", prefix, relName(v.Value, instr)) } } else { if value, ok := instr.(Value); ok { fmt.Fprintf(&b, "%sInvoke <%s> %s.%s", prefix, relType(value.Type(), instr.Parent().pkg()), relName(v.Value, instr), v.Method.Name()) } else { fmt.Fprintf(&b, "%sInvoke %s.%s", prefix, relName(v.Value, instr), v.Method.Name()) } } for _, arg := range v.Args { b.WriteString(" ") b.WriteString(relName(arg, instr)) } return b.String() } func (c *CallCommon) String() string { return printCall(c, "", nil) } func (v *Call) String() string { return printCall(&v.Call, "Call", v) } func (v *BinOp) String() string { return fmt.Sprintf("BinOp <%s> {%s} %s %s", relType(v.Type(), v.Parent().pkg()), v.Op.String(), relName(v.X, v), relName(v.Y, v)) } func (v *UnOp) String() string { return fmt.Sprintf("UnOp <%s> {%s} %s", relType(v.Type(), v.Parent().pkg()), v.Op.String(), relName(v.X, v)) } func (v *Load) String() string { return fmt.Sprintf("Load <%s> %s", relType(v.Type(), v.Parent().pkg()), relName(v.X, v)) } func printConv(prefix string, v, x Value) string { from := v.Parent().pkg() return fmt.Sprintf("%s <%s> %s", prefix, relType(v.Type(), from), relName(x, v.(Instruction))) } func (v *ChangeType) String() string { return printConv("ChangeType", v, v.X) } func (v *Convert) String() string { return printConv("Convert", v, v.X) } func (v *ChangeInterface) String() string { return printConv("ChangeInterface", v, v.X) } func (v *SliceToArrayPointer) String() string { return printConv("SliceToArrayPointer", v, v.X) } func (v *MakeInterface) String() string { return printConv("MakeInterface", v, v.X) } func (v *MakeClosure) String() string { from := v.Parent().pkg() var b bytes.Buffer fmt.Fprintf(&b, "MakeClosure <%s> %s", relType(v.Type(), from), relName(v.Fn, v)) if v.Bindings != nil { for _, c := range v.Bindings { b.WriteString(" ") b.WriteString(relName(c, v)) } } return b.String() } func (v *MakeSlice) String() string { from := v.Parent().pkg() return fmt.Sprintf("MakeSlice <%s> %s %s", relType(v.Type(), from), relName(v.Len, v), relName(v.Cap, v)) } func (v *Slice) String() string { from := v.Parent().pkg() return fmt.Sprintf("Slice <%s> %s %s %s %s", relType(v.Type(), from), relName(v.X, v), relName(v.Low, v), relName(v.High, v), relName(v.Max, v)) } func (v *MakeMap) String() string { res := "" if v.Reserve != nil { res = relName(v.Reserve, v) } from := v.Parent().pkg() return fmt.Sprintf("MakeMap <%s> %s", relType(v.Type(), from), res) } func (v *MakeChan) String() string { from := v.Parent().pkg() return fmt.Sprintf("MakeChan <%s> %s", relType(v.Type(), from), relName(v.Size, v)) } func (v *FieldAddr) String() string { from := v.Parent().pkg() st := deref(v.X.Type()).Underlying().(*types.Struct) // Be robust against a bad index. name := "?" if 0 <= v.Field && v.Field < st.NumFields() { name = st.Field(v.Field).Name() } return fmt.Sprintf("FieldAddr <%s> [%d] (%s) %s", relType(v.Type(), from), v.Field, name, relName(v.X, v)) } func (v *Field) String() string { 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() } from := v.Parent().pkg() return fmt.Sprintf("Field <%s> [%d] (%s) %s", relType(v.Type(), from), v.Field, name, relName(v.X, v)) } func (v *IndexAddr) String() string { from := v.Parent().pkg() return fmt.Sprintf("IndexAddr <%s> %s %s", relType(v.Type(), from), relName(v.X, v), relName(v.Index, v)) } func (v *Index) String() string { from := v.Parent().pkg() return fmt.Sprintf("Index <%s> %s %s", relType(v.Type(), from), relName(v.X, v), relName(v.Index, v)) } func (v *MapLookup) String() string { from := v.Parent().pkg() return fmt.Sprintf("MapLookup <%s> %s %s", relType(v.Type(), from), relName(v.X, v), relName(v.Index, v)) } func (v *StringLookup) String() string { from := v.Parent().pkg() return fmt.Sprintf("StringLookup <%s> %s %s", relType(v.Type(), from), relName(v.X, v), relName(v.Index, v)) } func (v *Range) String() string { from := v.Parent().pkg() return fmt.Sprintf("Range <%s> %s", relType(v.Type(), from), relName(v.X, v)) } func (v *Next) String() string { from := v.Parent().pkg() return fmt.Sprintf("Next <%s> %s", relType(v.Type(), from), relName(v.Iter, v)) } func (v *TypeAssert) String() string { from := v.Parent().pkg() return fmt.Sprintf("TypeAssert <%s> %s", relType(v.Type(), from), relName(v.X, v)) } func (v *Extract) String() string { from := v.Parent().pkg() name := v.Tuple.Type().(*types.Tuple).At(v.Index).Name() return fmt.Sprintf("Extract <%s> [%d] (%s) %s", relType(v.Type(), from), v.Index, name, relName(v.Tuple, v)) } func (s *Jump) String() string { // Be robust against malformed CFG. block := -1 if s.block != nil && len(s.block.Succs) == 1 { block = s.block.Succs[0].Index } str := fmt.Sprintf("Jump β†’ b%d", block) if s.Comment != "" { str = fmt.Sprintf("%s # %s", str, s.Comment) } return str } func (s *Unreachable) String() string { // Be robust against malformed CFG. block := -1 if s.block != nil && len(s.block.Succs) == 1 { block = s.block.Succs[0].Index } return fmt.Sprintf("Unreachable β†’ b%d", block) } func (s *If) String() string { // Be robust against malformed CFG. tblock, fblock := -1, -1 if s.block != nil && len(s.block.Succs) == 2 { tblock = s.block.Succs[0].Index fblock = s.block.Succs[1].Index } return fmt.Sprintf("If %s β†’ b%d b%d", relName(s.Cond, s), tblock, fblock) } func (s *ConstantSwitch) String() string { var b bytes.Buffer fmt.Fprintf(&b, "ConstantSwitch %s", relName(s.Tag, s)) for _, cond := range s.Conds { fmt.Fprintf(&b, " %s", relName(cond, s)) } fmt.Fprint(&b, " β†’") for _, succ := range s.block.Succs { fmt.Fprintf(&b, " b%d", succ.Index) } return b.String() } func (s *TypeSwitch) String() string { from := s.Parent().pkg() var b bytes.Buffer fmt.Fprintf(&b, "TypeSwitch <%s> %s", relType(s.typ, from), relName(s.Tag, s)) for _, cond := range s.Conds { fmt.Fprintf(&b, " %q", relType(cond, s.block.parent.pkg())) } return b.String() } func (s *Go) String() string { return printCall(&s.Call, "Go", s) } func (s *Panic) String() string { // Be robust against malformed CFG. block := -1 if s.block != nil && len(s.block.Succs) == 1 { block = s.block.Succs[0].Index } return fmt.Sprintf("Panic %s β†’ b%d", relName(s.X, s), block) } func (s *Return) String() string { var b bytes.Buffer b.WriteString("Return") for _, r := range s.Results { b.WriteString(" ") b.WriteString(relName(r, s)) } return b.String() } func (*RunDefers) String() string { return "RunDefers" } func (s *Send) String() string { return fmt.Sprintf("Send %s %s", relName(s.Chan, s), relName(s.X, s)) } func (recv *Recv) String() string { from := recv.Parent().pkg() return fmt.Sprintf("Recv <%s> %s", relType(recv.Type(), from), relName(recv.Chan, recv)) } func (s *Defer) String() string { return printCall(&s.Call, "Defer", s) } func (s *Select) String() string { var b bytes.Buffer for i, st := range s.States { if i > 0 { b.WriteString(", ") } if st.Dir == types.RecvOnly { b.WriteString("<-") b.WriteString(relName(st.Chan, s)) } else { b.WriteString(relName(st.Chan, s)) b.WriteString("<-") b.WriteString(relName(st.Send, s)) } } non := "" if !s.Blocking { non = "Non" } from := s.Parent().pkg() return fmt.Sprintf("Select%sBlocking <%s> [%s]", non, relType(s.Type(), from), b.String()) } func (s *Store) String() string { return fmt.Sprintf("Store {%s} %s %s", s.Val.Type(), relName(s.Addr, s), relName(s.Val, s)) } func (s *BlankStore) String() string { return fmt.Sprintf("BlankStore %s", relName(s.Val, s)) } func (s *MapUpdate) String() string { return fmt.Sprintf("MapUpdate %s %s %s", relName(s.Map, s), relName(s.Key, s), relName(s.Value, s)) } func (s *DebugRef) String() string { p := s.Parent().Prog.Fset.Position(s.Pos()) var descr interface{} if s.object != nil { descr = s.object // e.g. "var x int" } else { descr = reflect.TypeOf(s.Expr) // e.g. "*ast.CallExpr" } var addr string if s.IsAddr { addr = "address of " } return fmt.Sprintf("; %s%s @ %d:%d is %s", addr, descr, p.Line, p.Column, s.X.Name()) } func (p *Package) String() string { return "package " + p.Pkg.Path() } var _ io.WriterTo = (*Package)(nil) // *Package implements io.Writer func (p *Package) WriteTo(w io.Writer) (int64, error) { var buf bytes.Buffer WritePackage(&buf, p) n, err := w.Write(buf.Bytes()) return int64(n), err } // WritePackage writes to buf a human-readable summary of p. func WritePackage(buf *bytes.Buffer, p *Package) { fmt.Fprintf(buf, "%s:\n", p) var names []string maxname := 0 for name := range p.Members { if l := len(name); l > maxname { maxname = l } names = append(names, name) } from := p.Pkg sort.Strings(names) for _, name := range names { switch mem := p.Members[name].(type) { case *NamedConst: fmt.Fprintf(buf, " const %-*s %s = %s\n", maxname, name, mem.Name(), mem.Value.RelString(from)) case *Function: fmt.Fprintf(buf, " func %-*s %s\n", maxname, name, relType(mem.Type(), from)) case *Type: fmt.Fprintf(buf, " type %-*s %s\n", maxname, name, relType(mem.Type().Underlying(), from)) for _, meth := range typeutil.IntuitiveMethodSet(mem.Type(), &p.Prog.MethodSets) { fmt.Fprintf(buf, " %s\n", types.SelectionString(meth, types.RelativeTo(from))) } case *Global: fmt.Fprintf(buf, " var %-*s %s\n", maxname, name, relType(mem.Type().(*types.Pointer).Elem(), from)) } } fmt.Fprintf(buf, "\n") } �����������������������������������������������������������go-tools-2021.1.2/go/ir/sanity.go�������������������������������������������������������������������0000664�0000000�0000000�00000035106�14143223131�0016356�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 // An optional pass for sanity-checking invariants of the IR representation. // Currently it checks CFG invariants but little at the instruction level. import ( "fmt" "go/types" "io" "os" "strings" ) type sanity struct { reporter io.Writer fn *Function block *BasicBlock instrs map[Instruction]struct{} insane bool } // sanityCheck performs integrity checking of the IR representation // of the function fn and returns true if it was valid. Diagnostics // are written to reporter if non-nil, os.Stderr otherwise. Some // diagnostics are only warnings and do not imply a negative result. // // Sanity-checking is intended to facilitate the debugging of code // transformation passes. // func sanityCheck(fn *Function, reporter io.Writer) bool { if reporter == nil { reporter = os.Stderr } return (&sanity{reporter: reporter}).checkFunction(fn) } // mustSanityCheck is like sanityCheck but panics instead of returning // a negative result. // func mustSanityCheck(fn *Function, reporter io.Writer) { if !sanityCheck(fn, reporter) { fn.WriteTo(os.Stderr) panic("SanityCheck failed") } } func (s *sanity) diagnostic(prefix, format string, args ...interface{}) { fmt.Fprintf(s.reporter, "%s: function %s", prefix, s.fn) if s.block != nil { fmt.Fprintf(s.reporter, ", block %s", s.block) } io.WriteString(s.reporter, ": ") fmt.Fprintf(s.reporter, format, args...) io.WriteString(s.reporter, "\n") } func (s *sanity) errorf(format string, args ...interface{}) { s.insane = true s.diagnostic("Error", format, args...) } func (s *sanity) warnf(format string, args ...interface{}) { s.diagnostic("Warning", format, args...) } // findDuplicate returns an arbitrary basic block that appeared more // than once in blocks, or nil if all were unique. func findDuplicate(blocks []*BasicBlock) *BasicBlock { if len(blocks) < 2 { return nil } if blocks[0] == blocks[1] { return blocks[0] } // Slow path: m := make(map[*BasicBlock]bool) for _, b := range blocks { if m[b] { return b } m[b] = true } return nil } func (s *sanity) checkInstr(idx int, instr Instruction) { switch instr := instr.(type) { case *If, *Jump, *Return, *Panic, *Unreachable, *ConstantSwitch: s.errorf("control flow instruction not at end of block") case *Sigma: if idx > 0 { prev := s.block.Instrs[idx-1] if _, ok := prev.(*Sigma); !ok { s.errorf("Sigma instruction follows a non-Sigma: %T", prev) } } case *Phi: if idx == 0 { // It suffices to apply this check to just the first phi node. if dup := findDuplicate(s.block.Preds); dup != nil { s.errorf("phi node in block with duplicate predecessor %s", dup) } } else { prev := s.block.Instrs[idx-1] switch prev.(type) { case *Phi, *Sigma: default: s.errorf("Phi instruction follows a non-Phi, non-Sigma: %T", prev) } } if ne, np := len(instr.Edges), len(s.block.Preds); ne != np { s.errorf("phi node has %d edges but %d predecessors", ne, np) } else { for i, e := range instr.Edges { if e == nil { s.errorf("phi node '%v' has no value for edge #%d from %s", instr, i, s.block.Preds[i]) } } } case *Alloc: if !instr.Heap { found := false for _, l := range s.fn.Locals { if l == instr { found = true break } } if !found { s.errorf("local alloc %s = %s does not appear in Function.Locals", instr.Name(), instr) } } case *BinOp: case *Call: case *ChangeInterface: case *ChangeType: case *SliceToArrayPointer: case *Convert: if _, ok := instr.X.Type().Underlying().(*types.Basic); !ok { if _, ok := instr.Type().Underlying().(*types.Basic); !ok { s.errorf("convert %s -> %s: at least one type must be basic", instr.X.Type(), instr.Type()) } } case *Defer: case *Extract: case *Field: case *FieldAddr: case *Go: case *Index: case *IndexAddr: case *MapLookup: case *StringLookup: case *MakeChan: case *MakeClosure: numFree := len(instr.Fn.(*Function).FreeVars) numBind := len(instr.Bindings) if numFree != numBind { s.errorf("MakeClosure has %d Bindings for function %s with %d free vars", numBind, instr.Fn, numFree) } if recv := instr.Type().(*types.Signature).Recv(); recv != nil { s.errorf("MakeClosure's type includes receiver %s", recv.Type()) } case *MakeInterface: case *MakeMap: case *MakeSlice: case *MapUpdate: case *Next: case *Range: case *RunDefers: case *Select: case *Send: case *Slice: case *Store: case *TypeAssert: case *UnOp: case *DebugRef: case *BlankStore: case *Load: case *Parameter: case *Const: case *Recv: case *TypeSwitch: default: panic(fmt.Sprintf("Unknown instruction type: %T", instr)) } if call, ok := instr.(CallInstruction); ok { if call.Common().Signature() == nil { s.errorf("nil signature: %s", call) } } // Check that value-defining instructions have valid types // and a valid referrer list. if v, ok := instr.(Value); ok { t := v.Type() if t == nil { s.errorf("no type: %s = %s", v.Name(), v) } else if t == tRangeIter { // not a proper type; ignore. } else if b, ok := t.Underlying().(*types.Basic); ok && b.Info()&types.IsUntyped != 0 { if _, ok := v.(*Const); !ok { s.errorf("instruction has 'untyped' result: %s = %s : %s", v.Name(), v, t) } } s.checkReferrerList(v) } // Untyped constants are legal as instruction Operands(), // for example: // _ = "foo"[0] // or: // if wordsize==64 {...} // All other non-Instruction Values can be found via their // enclosing Function or Package. } func (s *sanity) checkFinalInstr(instr Instruction) { switch instr := instr.(type) { case *If: if nsuccs := len(s.block.Succs); nsuccs != 2 { s.errorf("If-terminated block has %d successors; expected 2", nsuccs) return } if s.block.Succs[0] == s.block.Succs[1] { s.errorf("If-instruction has same True, False target blocks: %s", s.block.Succs[0]) return } case *Jump: if nsuccs := len(s.block.Succs); nsuccs != 1 { s.errorf("Jump-terminated block has %d successors; expected 1", nsuccs) return } case *Return: if nsuccs := len(s.block.Succs); nsuccs != 0 { s.errorf("Return-terminated block has %d successors; expected none", nsuccs) return } if na, nf := len(instr.Results), s.fn.Signature.Results().Len(); nf != na { s.errorf("%d-ary return in %d-ary function", na, nf) } case *Panic: if nsuccs := len(s.block.Succs); nsuccs != 1 { s.errorf("Panic-terminated block has %d successors; expected one", nsuccs) return } case *Unreachable: if nsuccs := len(s.block.Succs); nsuccs != 1 { s.errorf("Unreachable-terminated block has %d successors; expected one", nsuccs) return } case *ConstantSwitch: default: s.errorf("non-control flow instruction at end of block") } } func (s *sanity) checkBlock(b *BasicBlock, index int) { s.block = b if b.Index != index { s.errorf("block has incorrect Index %d", b.Index) } if b.parent != s.fn { s.errorf("block has incorrect parent %s", b.parent) } // Check all blocks are reachable. // (The entry block is always implicitly reachable, the exit block may be unreachable.) if index > 1 && len(b.Preds) == 0 { s.warnf("unreachable block") if b.Instrs == nil { // Since this block is about to be pruned, // tolerating transient problems in it // simplifies other optimizations. return } } // Check predecessor and successor relations are dual, // and that all blocks in CFG belong to same function. for _, a := range b.Preds { found := false for _, bb := range a.Succs { if bb == b { found = true break } } if !found { s.errorf("expected successor edge in predecessor %s; found only: %s", a, a.Succs) } if a.parent != s.fn { s.errorf("predecessor %s belongs to different function %s", a, a.parent) } } for _, c := range b.Succs { found := false for _, bb := range c.Preds { if bb == b { found = true break } } if !found { s.errorf("expected predecessor edge in successor %s; found only: %s", c, c.Preds) } if c.parent != s.fn { s.errorf("successor %s belongs to different function %s", c, c.parent) } } // Check each instruction is sane. n := len(b.Instrs) if n == 0 { s.errorf("basic block contains no instructions") } var rands [10]*Value // reuse storage for j, instr := range b.Instrs { if instr == nil { s.errorf("nil instruction at index %d", j) continue } if b2 := instr.Block(); b2 == nil { s.errorf("nil Block() for instruction at index %d", j) continue } else if b2 != b { s.errorf("wrong Block() (%s) for instruction at index %d ", b2, j) continue } if j < n-1 { s.checkInstr(j, instr) } else { s.checkFinalInstr(instr) } // Check Instruction.Operands. operands: for i, op := range instr.Operands(rands[:0]) { if op == nil { s.errorf("nil operand pointer %d of %s", i, instr) continue } val := *op if val == nil { continue // a nil operand is ok } // Check that "untyped" types only appear on constant operands. if _, ok := (*op).(*Const); !ok { if basic, ok := (*op).Type().(*types.Basic); ok { if basic.Info()&types.IsUntyped != 0 { s.errorf("operand #%d of %s is untyped: %s", i, instr, basic) } } } // Check that Operands that are also Instructions belong to same function. // TODO(adonovan): also check their block dominates block b. if val, ok := val.(Instruction); ok { if val.Block() == nil { s.errorf("operand %d of %s is an instruction (%s) that belongs to no block", i, instr, val) } else if val.Parent() != s.fn { s.errorf("operand %d of %s is an instruction (%s) from function %s", i, instr, val, val.Parent()) } } // Check that each function-local operand of // instr refers back to instr. (NB: quadratic) switch val := val.(type) { case *Const, *Global, *Builtin: continue // not local case *Function: if val.parent == nil { continue // only anon functions are local } } // TODO(adonovan): check val.Parent() != nil <=> val.Referrers() is defined. if refs := val.Referrers(); refs != nil { for _, ref := range *refs { if ref == instr { continue operands } } s.errorf("operand %d of %s (%s) does not refer to us", i, instr, val) } else { s.errorf("operand %d of %s (%s) has no referrers", i, instr, val) } } } } func (s *sanity) checkReferrerList(v Value) { refs := v.Referrers() if refs == nil { s.errorf("%s has missing referrer list", v.Name()) return } for i, ref := range *refs { if _, ok := s.instrs[ref]; !ok { if val, ok := ref.(Value); ok { s.errorf("%s.Referrers()[%d] = %s = %s is not an instruction belonging to this function", v.Name(), i, val.Name(), val) } else { s.errorf("%s.Referrers()[%d] = %s is not an instruction belonging to this function", v.Name(), i, ref) } } } } func (s *sanity) checkFunction(fn *Function) bool { // TODO(adonovan): check Function invariants: // - check params match signature // - check transient fields are nil // - warn if any fn.Locals do not appear among block instructions. s.fn = fn if fn.Prog == nil { s.errorf("nil Prog") } _ = fn.String() // must not crash _ = fn.RelString(fn.pkg()) // must not crash // All functions have a package, except delegates (which are // shared across packages, or duplicated as weak symbols in a // separate-compilation model), and error.Error. if fn.Pkg == nil { switch fn.Synthetic { case SyntheticWrapper, SyntheticBound, SyntheticThunk: default: if !strings.HasSuffix(fn.name, "Error") { s.errorf("nil Pkg") } } } if src, syn := fn.Synthetic == 0, fn.source != nil; src != syn { s.errorf("got fromSource=%t, hasSyntax=%t; want same values", src, syn) } for i, l := range fn.Locals { if l.Parent() != fn { s.errorf("Local %s at index %d has wrong parent", l.Name(), i) } if l.Heap { s.errorf("Local %s at index %d has Heap flag set", l.Name(), i) } } // Build the set of valid referrers. s.instrs = make(map[Instruction]struct{}) for _, b := range fn.Blocks { for _, instr := range b.Instrs { s.instrs[instr] = struct{}{} } } for i, p := range fn.Params { if p.Parent() != fn { s.errorf("Param %s at index %d has wrong parent", p.Name(), i) } // Check common suffix of Signature and Params match type. if sig := fn.Signature; sig != nil { j := i - len(fn.Params) + sig.Params().Len() // index within sig.Params if j < 0 { continue } if !types.Identical(p.Type(), sig.Params().At(j).Type()) { s.errorf("Param %s at index %d has wrong type (%s, versus %s in Signature)", p.Name(), i, p.Type(), sig.Params().At(j).Type()) } } s.checkReferrerList(p) } for i, fv := range fn.FreeVars { if fv.Parent() != fn { s.errorf("FreeVar %s at index %d has wrong parent", fv.Name(), i) } s.checkReferrerList(fv) } if fn.Blocks != nil && len(fn.Blocks) == 0 { // Function _had_ blocks (so it's not external) but // they were "optimized" away, even the entry block. s.errorf("Blocks slice is non-nil but empty") } for i, b := range fn.Blocks { if b == nil { s.warnf("nil *BasicBlock at f.Blocks[%d]", i) continue } s.checkBlock(b, i) } s.block = nil for i, anon := range fn.AnonFuncs { if anon.Parent() != fn { s.errorf("AnonFuncs[%d]=%s but %s.Parent()=%s", i, anon, anon, anon.Parent()) } } s.fn = nil return !s.insane } // sanityCheckPackage checks invariants of packages upon creation. // It does not require that the package is built. // Unlike sanityCheck (for functions), it just panics at the first error. func sanityCheckPackage(pkg *Package) { if pkg.Pkg == nil { panic(fmt.Sprintf("Package %s has no Object", pkg)) } _ = pkg.String() // must not crash for name, mem := range pkg.Members { if name != mem.Name() { panic(fmt.Sprintf("%s: %T.Name() = %s, want %s", pkg.Pkg.Path(), mem, mem.Name(), name)) } obj := mem.Object() if obj == nil { // This check is sound because fields // {Global,Function}.object have type // types.Object. (If they were declared as // *types.{Var,Func}, we'd have a non-empty // interface containing a nil pointer.) continue // not all members have typechecker objects } if obj.Name() != name { if obj.Name() == "init" && strings.HasPrefix(mem.Name(), "init#") { // Ok. The name of a declared init function varies between // its types.Func ("init") and its ir.Function ("init#%d"). } else { panic(fmt.Sprintf("%s: %T.Object().Name() = %s, want %s", pkg.Pkg.Path(), mem, obj.Name(), name)) } } } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������go-tools-2021.1.2/go/ir/source.go�������������������������������������������������������������������0000664�0000000�0000000�00000020654�14143223131�0016351�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 utilities for working with source positions // or source-level named entities ("objects"). // TODO(adonovan): test that {Value,Instruction}.Pos() positions match // the originating syntax, as specified. import ( "go/ast" "go/token" "go/types" ) // EnclosingFunction returns the function that contains the syntax // node denoted by path. // // Syntax associated with package-level variable specifications is // enclosed by the package's init() function. // // Returns nil if not found; reasons might include: // - the node is not enclosed by any function. // - the node is within an anonymous function (FuncLit) and // its IR function has not been created yet // (pkg.Build() has not yet been called). // func EnclosingFunction(pkg *Package, path []ast.Node) *Function { // Start with package-level function... fn := findEnclosingPackageLevelFunction(pkg, path) if fn == nil { return nil // not in any function } // ...then walk down the nested anonymous functions. n := len(path) outer: for i := range path { if lit, ok := path[n-1-i].(*ast.FuncLit); ok { for _, anon := range fn.AnonFuncs { if anon.Pos() == lit.Type.Func { fn = anon continue outer } } // IR function not found: // - package not yet built, or maybe // - builder skipped FuncLit in dead block // (in principle; but currently the Builder // generates even dead FuncLits). return nil } } return fn } // HasEnclosingFunction returns true if the AST node denoted by path // is contained within the declaration of some function or // package-level variable. // // Unlike EnclosingFunction, the behaviour of this function does not // depend on whether IR code for pkg has been built, so it can be // used to quickly reject check inputs that will cause // EnclosingFunction to fail, prior to IR building. // func HasEnclosingFunction(pkg *Package, path []ast.Node) bool { return findEnclosingPackageLevelFunction(pkg, path) != nil } // findEnclosingPackageLevelFunction returns the Function // corresponding to the package-level function enclosing path. // func findEnclosingPackageLevelFunction(pkg *Package, path []ast.Node) *Function { if n := len(path); n >= 2 { // [... {Gen,Func}Decl File] switch decl := path[n-2].(type) { case *ast.GenDecl: if decl.Tok == token.VAR && n >= 3 { // Package-level 'var' initializer. return pkg.init } case *ast.FuncDecl: // Declared function/method. fn := findNamedFunc(pkg, decl.Pos()) if fn == nil && decl.Recv == nil && decl.Name.Name == "init" { // Hack: return non-nil when IR is not yet // built so that HasEnclosingFunction works. return pkg.init } return fn } } return nil // not in any function } // findNamedFunc returns the named function whose FuncDecl.Ident is at // position pos. // func findNamedFunc(pkg *Package, pos token.Pos) *Function { for _, fn := range pkg.Functions { if fn.Pos() == pos { return fn } } return nil } // ValueForExpr returns the IR Value that corresponds to non-constant // expression e. // // It returns nil if no value was found, e.g. // - the expression is not lexically contained within f; // - f was not built with debug information; or // - e is a constant expression. (For efficiency, no debug // information is stored for constants. Use // go/types.Info.Types[e].Value instead.) // - e is a reference to nil or a built-in function. // - the value was optimised away. // // If e is an addressable expression used in an lvalue context, // value is the address denoted by e, and isAddr is true. // // The types of e (or &e, if isAddr) and the result are equal // (modulo "untyped" bools resulting from comparisons). // // (Tip: to find the ir.Value given a source position, use // astutil.PathEnclosingInterval to locate the ast.Node, then // EnclosingFunction to locate the Function, then ValueForExpr to find // the ir.Value.) // func (f *Function) ValueForExpr(e ast.Expr) (value Value, isAddr bool) { if f.debugInfo() { // (opt) e = unparen(e) for _, b := range f.Blocks { for _, instr := range b.Instrs { if ref, ok := instr.(*DebugRef); ok { if ref.Expr == e { return ref.X, ref.IsAddr } } } } } return } // --- Lookup functions for source-level named entities (types.Objects) --- // Package returns the IR Package corresponding to the specified // type-checker package object. // It returns nil if no such IR package has been created. // func (prog *Program) Package(obj *types.Package) *Package { return prog.packages[obj] } // packageLevelValue returns the package-level value corresponding to // the specified named object, which may be a package-level const // (*Const), var (*Global) or func (*Function) of some package in // prog. It returns nil if the object is not found. // func (prog *Program) packageLevelValue(obj types.Object) Value { if pkg, ok := prog.packages[obj.Pkg()]; ok { return pkg.values[obj] } return nil } // FuncValue returns the concrete Function denoted by the source-level // named function obj, or nil if obj denotes an interface method. // // TODO(adonovan): check the invariant that obj.Type() matches the // result's Signature, both in the params/results and in the receiver. // func (prog *Program) FuncValue(obj *types.Func) *Function { fn, _ := prog.packageLevelValue(obj).(*Function) return fn } // ConstValue returns the IR Value denoted by the source-level named // constant obj. // func (prog *Program) ConstValue(obj *types.Const) *Const { // TODO(adonovan): opt: share (don't reallocate) // Consts for const objects and constant ast.Exprs. // Universal constant? {true,false,nil} if obj.Parent() == types.Universe { return NewConst(obj.Val(), obj.Type()) } // Package-level named constant? if v := prog.packageLevelValue(obj); v != nil { return v.(*Const) } return NewConst(obj.Val(), obj.Type()) } // VarValue returns the IR Value that corresponds to a specific // identifier denoting the source-level named variable obj. // // VarValue returns nil if a local variable was not found, perhaps // because its package was not built, the debug information was not // requested during IR construction, or the value was optimized away. // // ref is the path to an ast.Ident (e.g. from PathEnclosingInterval), // and that ident must resolve to obj. // // pkg is the package enclosing the reference. (A reference to a var // always occurs within a function, so we need to know where to find it.) // // If the identifier is a field selector and its base expression is // non-addressable, then VarValue returns the value of that field. // For example: // func f() struct {x int} // f().x // VarValue(x) returns a *Field instruction of type int // // All other identifiers denote addressable locations (variables). // For them, VarValue may return either the variable's address or its // value, even when the expression is evaluated only for its value; the // situation is reported by isAddr, the second component of the result. // // If !isAddr, the returned value is the one associated with the // specific identifier. For example, // var x int // VarValue(x) returns Const 0 here // x = 1 // VarValue(x) returns Const 1 here // // It is not specified whether the value or the address is returned in // any particular case, as it may depend upon optimizations performed // during IR code generation, such as registerization, constant // folding, avoidance of materialization of subexpressions, etc. // func (prog *Program) VarValue(obj *types.Var, pkg *Package, ref []ast.Node) (value Value, isAddr bool) { // All references to a var are local to some function, possibly init. fn := EnclosingFunction(pkg, ref) if fn == nil { return // e.g. def of struct field; IR not built? } id := ref[0].(*ast.Ident) // Defining ident of a parameter? if id.Pos() == obj.Pos() { for _, param := range fn.Params { if param.Object() == obj { return param, false } } } // Other ident? for _, b := range fn.Blocks { for _, instr := range b.Instrs { if dr, ok := instr.(*DebugRef); ok { if dr.Pos() == id.Pos() { return dr.X, dr.IsAddr } } } } // Defining ident of package-level var? if v := prog.packageLevelValue(obj); v != nil { return v.(*Global), true } return // e.g. debug info not requested, or var optimized away } ������������������������������������������������������������������������������������go-tools-2021.1.2/go/ir/source_test.go��������������������������������������������������������������0000664�0000000�0000000�00000026021�14143223131�0017402�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 // This file defines tests of source-level debugging utilities. import ( "fmt" "go/ast" "go/constant" "go/parser" "go/token" "go/types" "io/ioutil" "os" "runtime" "strings" "testing" "honnef.co/go/tools/go/ast/astutil" "honnef.co/go/tools/go/ir" "honnef.co/go/tools/go/ir/irutil" "golang.org/x/tools/go/expect" "golang.org/x/tools/go/loader" ) func TestObjValueLookup(t *testing.T) { if runtime.GOOS == "android" { t.Skipf("no testdata directory on %s", runtime.GOOS) } conf := loader.Config{ParserMode: parser.ParseComments} src, err := ioutil.ReadFile("testdata/objlookup.go") if err != nil { t.Fatal(err) } readFile := func(filename string) ([]byte, error) { return src, nil } f, err := conf.ParseFile("testdata/objlookup.go", src) if err != nil { t.Fatal(err) } conf.CreateFromFiles("main", f) // Maps each var Ident (represented "name:linenum") to the // kind of ir.Value we expect (represented "Constant", "&Alloc"). expectations := make(map[string]string) // Each note of the form @ir(x, "BinOp") in testdata/objlookup.go // specifies an expectation that an object named x declared on the // same line is associated with an an ir.Value of type *ir.BinOp. notes, err := expect.ExtractGo(conf.Fset, f) if err != nil { t.Fatal(err) } for _, n := range notes { if n.Name != "ir" { t.Errorf("%v: unexpected note type %q, want \"ir\"", conf.Fset.Position(n.Pos), n.Name) continue } if len(n.Args) != 2 { t.Errorf("%v: ir has %d args, want 2", conf.Fset.Position(n.Pos), len(n.Args)) continue } ident, ok := n.Args[0].(expect.Identifier) if !ok { t.Errorf("%v: got %v for arg 1, want identifier", conf.Fset.Position(n.Pos), n.Args[0]) continue } exp, ok := n.Args[1].(string) if !ok { t.Errorf("%v: got %v for arg 2, want string", conf.Fset.Position(n.Pos), n.Args[1]) continue } p, _, err := expect.MatchBefore(conf.Fset, readFile, n.Pos, string(ident)) if err != nil { t.Error(err) continue } pos := conf.Fset.Position(p) key := fmt.Sprintf("%s:%d", ident, pos.Line) expectations[key] = exp } iprog, err := conf.Load() if err != nil { t.Error(err) return } prog := irutil.CreateProgram(iprog, 0 /*|ir.PrintFunctions*/) mainInfo := iprog.Created[0] mainPkg := prog.Package(mainInfo.Pkg) mainPkg.SetDebugMode(true) mainPkg.Build() var varIds []*ast.Ident var varObjs []*types.Var for id, obj := range mainInfo.Defs { // Check invariants for func and const objects. switch obj := obj.(type) { case *types.Func: checkFuncValue(t, prog, obj) case *types.Const: checkConstValue(t, prog, obj) case *types.Var: if id.Name == "_" { continue } varIds = append(varIds, id) varObjs = append(varObjs, obj) } } for id, obj := range mainInfo.Uses { if obj, ok := obj.(*types.Var); ok { varIds = append(varIds, id) varObjs = append(varObjs, obj) } } // Check invariants for var objects. // The result varies based on the specific Ident. for i, id := range varIds { obj := varObjs[i] ref, _ := astutil.PathEnclosingInterval(f, id.Pos(), id.Pos()) pos := prog.Fset.Position(id.Pos()) exp := expectations[fmt.Sprintf("%s:%d", id.Name, pos.Line)] if exp == "" { t.Errorf("%s: no expectation for var ident %s ", pos, id.Name) continue } wantAddr := false if exp[0] == '&' { wantAddr = true exp = exp[1:] } checkVarValue(t, prog, mainPkg, ref, obj, exp, wantAddr) } } func checkFuncValue(t *testing.T, prog *ir.Program, obj *types.Func) { fn := prog.FuncValue(obj) // fmt.Printf("FuncValue(%s) = %s\n", obj, fn) // debugging if fn == nil { if obj.Name() != "interfaceMethod" { t.Errorf("FuncValue(%s) == nil", obj) } return } if fnobj := fn.Object(); fnobj != obj { t.Errorf("FuncValue(%s).Object() == %s; value was %s", obj, fnobj, fn.Name()) return } if !types.Identical(fn.Type(), obj.Type()) { t.Errorf("FuncValue(%s).Type() == %s", obj, fn.Type()) return } } func checkConstValue(t *testing.T, prog *ir.Program, obj *types.Const) { c := prog.ConstValue(obj) // fmt.Printf("ConstValue(%s) = %s\n", obj, c) // debugging if c == nil { t.Errorf("ConstValue(%s) == nil", obj) return } if !types.Identical(c.Type(), obj.Type()) { t.Errorf("ConstValue(%s).Type() == %s", obj, c.Type()) return } if obj.Name() != "nil" { if !constant.Compare(c.Value, token.EQL, obj.Val()) { t.Errorf("ConstValue(%s).Value (%s) != %s", obj, c.Value, obj.Val()) return } } } func checkVarValue(t *testing.T, prog *ir.Program, pkg *ir.Package, ref []ast.Node, obj *types.Var, expKind string, wantAddr bool) { // The prefix of all assertions messages. prefix := fmt.Sprintf("VarValue(%s @ L%d)", obj, prog.Fset.Position(ref[0].Pos()).Line) v, gotAddr := prog.VarValue(obj, pkg, ref) // Kind is the concrete type of the ir Value. gotKind := "nil" if v != nil { gotKind = fmt.Sprintf("%T", v)[len("*ir."):] } // fmt.Printf("%s = %v (kind %q; expect %q) wantAddr=%t gotAddr=%t\n", prefix, v, gotKind, expKind, wantAddr, gotAddr) // debugging // Check the kinds match. // "nil" indicates expected failure (e.g. optimized away). if expKind != gotKind { t.Errorf("%s concrete type == %s, want %s", prefix, gotKind, expKind) } // Check the types match. // If wantAddr, the expected type is the object's address. if v != nil { expType := obj.Type() if wantAddr { expType = types.NewPointer(expType) if !gotAddr { t.Errorf("%s: got value, want address", prefix) } } else if gotAddr { t.Errorf("%s: got address, want value", prefix) } if !types.Identical(v.Type(), expType) { t.Errorf("%s.Type() == %s, want %s", prefix, v.Type(), expType) } } } // Ensure that, in debug mode, we can determine the ir.Value // corresponding to every ast.Expr. func TestValueForExpr(t *testing.T) { testValueForExpr(t, "testdata/valueforexpr.go") } func testValueForExpr(t *testing.T, testfile string) { if runtime.GOOS == "android" { t.Skipf("no testdata dir on %s", runtime.GOOS) } conf := loader.Config{ParserMode: parser.ParseComments} f, err := conf.ParseFile(testfile, nil) if err != nil { t.Error(err) return } conf.CreateFromFiles("main", f) iprog, err := conf.Load() if err != nil { t.Error(err) return } mainInfo := iprog.Created[0] prog := irutil.CreateProgram(iprog, 0) mainPkg := prog.Package(mainInfo.Pkg) mainPkg.SetDebugMode(true) mainPkg.Build() if false { // debugging for _, mem := range mainPkg.Members { if fn, ok := mem.(*ir.Function); ok { fn.WriteTo(os.Stderr) } } } var parenExprs []*ast.ParenExpr ast.Inspect(f, func(n ast.Node) bool { if n != nil { if e, ok := n.(*ast.ParenExpr); ok { parenExprs = append(parenExprs, e) } } return true }) notes, err := expect.ExtractGo(prog.Fset, f) if err != nil { t.Fatal(err) } for _, n := range notes { want := n.Name if want == "nil" { want = "" } position := prog.Fset.Position(n.Pos) var e ast.Expr for _, paren := range parenExprs { if paren.Pos() > n.Pos { e = paren.X break } } if e == nil { t.Errorf("%s: note doesn't precede ParenExpr: %q", position, want) continue } path, _ := astutil.PathEnclosingInterval(f, n.Pos, n.Pos) if path == nil { t.Errorf("%s: can't find AST path from root to comment: %s", position, want) continue } fn := ir.EnclosingFunction(mainPkg, path) if fn == nil { t.Errorf("%s: can't find enclosing function", position) continue } v, gotAddr := fn.ValueForExpr(e) // (may be nil) got := strings.TrimPrefix(fmt.Sprintf("%T", v), "*ir.") if got != want { t.Errorf("%s: got value %q, want %q", position, got, want) } if v != nil { T := v.Type() if gotAddr { T = T.Underlying().(*types.Pointer).Elem() // deref } if !types.Identical(T, mainInfo.TypeOf(e)) { t.Errorf("%s: got type %s, want %s", position, mainInfo.TypeOf(e), T) } } } } // findInterval parses input and returns the [start, end) positions of // the first occurrence of substr in input. f==nil indicates failure; // an error has already been reported in that case. // func findInterval(t *testing.T, fset *token.FileSet, input, substr string) (f *ast.File, start, end token.Pos) { f, err := parser.ParseFile(fset, "