pax_global_header00006660000000000000000000000064145661440710014522gustar00rootroot0000000000000052 comment=ca68a6b6843d6b24ac06621634fe432e2cd77c05 golang-honnef-go-tools-2023.1.7/000077500000000000000000000000001456614407100162415ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/.gitattributes000066400000000000000000000000611456614407100211310ustar00rootroot00000000000000*.golden -text *.svg binary **/testdata/** -text golang-honnef-go-tools-2023.1.7/.github/000077500000000000000000000000001456614407100176015ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/.github/FUNDING.yml000066400000000000000000000000431456614407100214130ustar00rootroot00000000000000patreon: dominikh github: dominikh golang-honnef-go-tools-2023.1.7/.github/ISSUE_TEMPLATE/000077500000000000000000000000001456614407100217645ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/.github/ISSUE_TEMPLATE/1_false_positive.md000066400000000000000000000012021456614407100255350ustar00rootroot00000000000000--- name: 💢 False positive in Staticcheck about: Your code is fine but Staticcheck complains about it, anyway. labels: false-positive, needs-triage title: "" --- golang-honnef-go-tools-2023.1.7/.github/ISSUE_TEMPLATE/2_false_negative.md000066400000000000000000000012021456614407100254760ustar00rootroot00000000000000--- name: 🦆 False negative in Staticcheck about: Your code is wrong but Staticcheck doesn't complain about it. labels: false-negative, needs-triage title: "" --- golang-honnef-go-tools-2023.1.7/.github/ISSUE_TEMPLATE/3_bug.md000066400000000000000000000011561456614407100233100ustar00rootroot00000000000000--- name: 🐞 General bugs with Staticcheck about: Something in Staticcheck isn't working as it should. labels: bug, needs-triage title: "" --- golang-honnef-go-tools-2023.1.7/.github/ISSUE_TEMPLATE/4_other.md000066400000000000000000000002201456614407100236440ustar00rootroot00000000000000--- name: 🛠 Other about: Ideas, feature requests, and all other issues not fitting into another category. labels: needs-triage title: "" --- golang-honnef-go-tools-2023.1.7/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000000341456614407100237510ustar00rootroot00000000000000blank_issues_enabled: false golang-honnef-go-tools-2023.1.7/.github/workflows/000077500000000000000000000000001456614407100216365ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/.github/workflows/ci.yml000066400000000000000000000035141456614407100227570ustar00rootroot00000000000000name: "CI" on: ["push", "pull_request"] jobs: ci: name: "Run CI" strategy: fail-fast: false matrix: os: ["windows-latest", "ubuntu-latest", "macOS-latest"] go: ["1.19.x"] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v1 with: fetch-depth: 1 - uses: WillAbides/setup-go-faster@v1.7.0 with: go-version: ${{ matrix.go }} - run: "go test ./..." - run: "go vet ./..." - uses: dominikh/staticcheck-action@9f77055cca7bfaafb836cbd6720865187f5fbf51 with: version: "3d6c86f0908ab82d6ff280219e2968ee65f83b2e" min-go-version: "1.17" install-go: false cache-key: ${{ matrix.go }} output-format: binary output-file: "./staticcheck.bin" - uses: actions/upload-artifact@v3 with: name: "staticcheck-${{ github.sha }}-${{ matrix.go }}-${{ matrix.os }}.bin" path: "./staticcheck.bin" retention-days: 1 if-no-files-found: warn output: name: "Output Staticcheck findings" needs: ci runs-on: "ubuntu-latest" steps: - uses: WillAbides/setup-go-faster@v1.7.0 with: go-version: "1.19.x" # this downloads all artifacts of the current workflow into the current working directory, creating one directory per artifact - uses: actions/download-artifact@v3 - id: glob run: | # We replace newliens with %0A, which GitHub apparently magically turns back into newlines out=$(ls -1 ./staticcheck-*.bin/*.bin) echo "::set-output name=files::${out//$'\n'/%0A}" - uses: dominikh/staticcheck-action@9f77055cca7bfaafb836cbd6720865187f5fbf51 with: version: "3d6c86f0908ab82d6ff280219e2968ee65f83b2e" min-go-version: "1.19" install-go: false merge-files: ${{ steps.glob.outputs.files }} golang-honnef-go-tools-2023.1.7/.gitignore000066400000000000000000000005731456614407100202360ustar00rootroot00000000000000/cmd/keyify/keyify /cmd/staticcheck/staticcheck /cmd/structlayout-optimize/structlayout-optimize /cmd/structlayout-pretty/structlayout-pretty /cmd/structlayout/structlayout /dist/20??.?.?/ /dist/20??.?/ /internal/cmd/irdump/irdump /website/.hugo_build.lock /website/public /website/resources /website/assets/img/sponsors /website/data/sponsors.toml /website/data/copyrights.toml golang-honnef-go-tools-2023.1.7/.gitmodules000066400000000000000000000001471456614407100204200ustar00rootroot00000000000000[submodule "website/themes/docsy"] path = website/themes/docsy url = https://github.com/google/docsy golang-honnef-go-tools-2023.1.7/LICENSE000066400000000000000000000020421456614407100172440ustar00rootroot00000000000000Copyright (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. golang-honnef-go-tools-2023.1.7/LICENSE-THIRD-PARTY000066400000000000000000000144521456614407100210210ustar00rootroot00000000000000Staticcheck 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. golang-honnef-go-tools-2023.1.7/README.md000066400000000000000000000060101456614407100175150ustar00rootroot00000000000000

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. `2022.1`). The easiest way of installing a release is by using `go install`, for example `go install honnef.co/go/tools/cmd/staticcheck@2022.1`. Alternatively, we also offer [prebuilt binaries](https://github.com/dominikh/go-tools/releases). You can find more information about installation and releases in the [documentation](https://staticcheck.io/docs/getting-started/). ### 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. You'll have to expect semiregular backwards-incompatible changes if you decide to use these libraries. ## System requirements Releases support the current and previous version of Go at the time of release. The master branch supports the current version of Go. golang-honnef-go-tools-2023.1.7/_benchmarks/000077500000000000000000000000001456614407100205155ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/_benchmarks/bench.sh000077500000000000000000000026011456614407100221320ustar00rootroot00000000000000#!/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 golang-honnef-go-tools-2023.1.7/_benchmarks/silent-staticcheck.sh000077500000000000000000000002051456614407100246320ustar00rootroot00000000000000#!/usr/bin/env sh /home/dominikh/prj/src/honnef.co/go/tools/cmd/staticcheck/staticcheck -checks "all" -fail "" $1 &>/dev/null exit 0 golang-honnef-go-tools-2023.1.7/analysis/000077500000000000000000000000001456614407100200645ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/analysis/code/000077500000000000000000000000001456614407100207765ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/analysis/code/code.go000066400000000000000000000234021456614407100222400ustar00rootroot00000000000000// 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/generated" "honnef.co/go/tools/analysis/facts/purity" "honnef.co/go/tools/analysis/facts/tokenfile" "honnef.co/go/tools/go/ast/astutil" "honnef.co/go/tools/go/types/typeutil" "honnef.co/go/tools/pattern" "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)) } if v, ok := sel.Obj().(*types.Var); ok && v.IsField() { return fmt.Sprintf("(%s).%s", typeutil.DereferenceR(sel.Recv()), sel.Obj().Name()) } else { 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 { fun := astutil.Unparen(call.Fun) // Instantiating a function cannot return another generic function, so doing this once is enough switch idx := fun.(type) { case *ast.IndexExpr: fun = idx.X case *ast.IndexListExpr: fun = idx.X } // (foo)[T] is not a valid instantiationg, so no need to unparen again. switch fun := 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[tokenfile.Analyzer].(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) (generated.Generator, bool) { file := pass.Fset.PositionFor(pos, false).Filename m := pass.ResultOf[generated.Analyzer].(map[string]generated.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 purity.Result) 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.IndexListExpr: // In theory, none of the checks are necessary, as IndexListExpr only involves types. But there is no harm in // being safe. if MayHaveSideEffects(pass, expr.X, purity) { return true } for _, idx := range expr.Indices { if MayHaveSideEffects(pass, idx, purity) { return true } } return false 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 || expr.Op == token.AND 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 } var integerLiteralQ = pattern.MustParse(`(IntegerLiteral tv)`) func IntegerLiteral(pass *analysis.Pass, node ast.Node) (types.TypeAndValue, bool) { m, ok := Match(pass, integerLiteralQ, node) if !ok { return types.TypeAndValue{}, false } return m.State["tv"].(types.TypeAndValue), true } func IsIntegerLiteral(pass *analysis.Pass, node ast.Node, value constant.Value) bool { tv, ok := IntegerLiteral(pass, node) if !ok { return false } return constant.Compare(tv.Value, token.EQL, value) } // IsMethod reports whether expr is a method call of a named method with signature meth. // If name is empty, it is not checked. // For now, method expressions (Type.Method(recv, ..)) are not considered method calls. func IsMethod(pass *analysis.Pass, expr *ast.SelectorExpr, name string, meth *types.Signature) bool { if name != "" && expr.Sel.Name != name { return false } sel, ok := pass.TypesInfo.Selections[expr] if !ok || sel.Kind() != types.MethodVal { return false } return types.Identical(sel.Type(), meth) } golang-honnef-go-tools-2023.1.7/analysis/code/visit.go000066400000000000000000000025771456614407100224760ustar00rootroot00000000000000package 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, 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 } golang-honnef-go-tools-2023.1.7/analysis/edit/000077500000000000000000000000001456614407100210115ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/analysis/edit/edit.go000066400000000000000000000037721456614407100222760ustar00rootroot00000000000000// Package edit contains helpers for creating suggested fixes. package edit import ( "bytes" "go/ast" "go/format" "go/token" "golang.org/x/tools/go/analysis" "honnef.co/go/tools/pattern" ) // Ranger describes values that have a start and end position. // In most cases these are either ast.Node or manually constructed ranges. type Ranger interface { Pos() token.Pos End() token.Pos } // Range implements the Ranger interface. type Range [2]token.Pos func (r Range) Pos() token.Pos { return r[0] } func (r Range) End() token.Pos { return r[1] } // ReplaceWithString replaces a range with a string. func ReplaceWithString(old Ranger, new string) analysis.TextEdit { return analysis.TextEdit{ Pos: old.Pos(), End: old.End(), NewText: []byte(new), } } // ReplaceWithNode replaces a range with an AST node. 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(), } } // ReplaceWithPattern replaces a range with the result of executing a pattern. func ReplaceWithPattern(fset *token.FileSet, old Ranger, new pattern.Pattern, state pattern.State) analysis.TextEdit { r := pattern.NodeToAST(new.Root, state) buf := &bytes.Buffer{} format.Node(buf, fset, r) return analysis.TextEdit{ Pos: old.Pos(), End: old.End(), NewText: buf.Bytes(), } } // Delete deletes a range of code. 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, } } // Selector creates a new selector expression. func Selector(x, sel string) *ast.SelectorExpr { return &ast.SelectorExpr{ X: &ast.Ident{Name: x}, Sel: &ast.Ident{Name: sel}, } } golang-honnef-go-tools-2023.1.7/analysis/facts/000077500000000000000000000000001456614407100211645ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/analysis/facts/deprecated/000077500000000000000000000000001456614407100232645ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/analysis/facts/deprecated/deprecated.go000066400000000000000000000067741456614407100257310ustar00rootroot00000000000000package deprecated 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 Result struct { Objects map[types.Object]*IsDeprecated Packages map[*types.Package]*IsDeprecated } var Analyzer = &analysis.Analyzer{ Name: "fact_deprecated", Doc: "Mark deprecated objects", Run: deprecated, FactTypes: []analysis.Fact{(*IsDeprecated)(nil)}, ResultType: reflect.TypeOf(Result{}), } 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) for i := range node.Specs { switch n := node.Specs[i].(type) { case *ast.ValueSpec: names = append(names, n.Names...) case *ast.TypeSpec: names = append(names, n.Name) } } ret = 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 := Result{ 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 } golang-honnef-go-tools-2023.1.7/analysis/facts/deprecated/deprecated_test.go000066400000000000000000000003231456614407100267500ustar00rootroot00000000000000package deprecated import ( "testing" "golang.org/x/tools/go/analysis/analysistest" ) func TestDeprecated(t *testing.T) { analysistest.Run(t, analysistest.TestData(), Analyzer, "example.com/Deprecated") } golang-honnef-go-tools-2023.1.7/analysis/facts/deprecated/testdata/000077500000000000000000000000001456614407100250755ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/analysis/facts/deprecated/testdata/src/000077500000000000000000000000001456614407100256645ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/analysis/facts/deprecated/testdata/src/example.com/000077500000000000000000000000001456614407100300745ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/analysis/facts/deprecated/testdata/src/example.com/Deprecated/000077500000000000000000000000001456614407100321345ustar00rootroot00000000000000Deprecated.go000066400000000000000000000023171456614407100344470ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/analysis/facts/deprecated/testdata/src/example.com/Deprecatedpackage 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\.` } // Handle cases like: // // Taken from "os" package: // // ``` // // Deprecated: Use io.SeekStart, io.SeekCurrent, and io.SeekEnd. // const ( // SEEK_SET int = 0 // seek relative to the origin of the file // SEEK_CUR int = 1 // seek relative to the current offset // SEEK_END int = 2 // seek relative to the end // ) // ``` // // Here all three consts i.e., os.SEEK_SET, os.SEEK_CUR and os.SEEK_END are // deprecated and not just os.SEEK_SET. // Deprecated: Don't use this. var ( SEEK_A = 0 // want SEEK_A:`Deprecated: Don't use this\.` SEEK_B = 1 // want SEEK_B:`Deprecated: Don't use this\.` SEEK_C = 2 // want SEEK_C:`Deprecated: Don't use this\.` ) // Deprecated: Don't use this. type ( pair struct{ x, y int } // want pair:`Deprecated: Don't use this\.` cube struct{ x, y, z int } // want cube:`Deprecated: Don't use this\.` ) // Deprecated: Don't use this. var SEEK_D = 3 // want SEEK_D:`Deprecated: Don't use this\.` var SEEK_E = 4 var SEEK_F = 5 golang-honnef-go-tools-2023.1.7/analysis/facts/directives/000077500000000000000000000000001456614407100233255ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/analysis/facts/directives/directives.go000066400000000000000000000007201456614407100260140ustar00rootroot00000000000000package directives 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 Analyzer = &analysis.Analyzer{ Name: "directives", Doc: "extracts linter directives", Run: directives, RunDespiteErrors: true, ResultType: reflect.TypeOf([]lint.Directive{}), } golang-honnef-go-tools-2023.1.7/analysis/facts/generated/000077500000000000000000000000001456614407100231225ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/analysis/facts/generated/generated.go000066400000000000000000000035571456614407100254210ustar00rootroot00000000000000package generated 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 Analyzer = &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{}), } golang-honnef-go-tools-2023.1.7/analysis/facts/nilness/000077500000000000000000000000001456614407100226375ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/analysis/facts/nilness/nilness.go000066400000000000000000000147731456614407100246550ustar00rootroot00000000000000package 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 typeutil.CoreType(v.Type()).(*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, *ir.Sigma: // 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.AggregateConst: return neverNil case *ir.TypeAssert, *ir.ChangeInterface, *ir.Field, *ir.Const, *ir.GenericConst, *ir.Index, *ir.MapLookup, *ir.Parameter, *ir.Recv, *ir.TypeSwitch: return nilly case *ir.CompositeValue: // We can get here via composite literals of type parameters, for which typeutil.IsPointerLike doesn't // currently return false (see https://staticcheck.io/issues/1364). However, we only emit ir.CompositeValue // for value types, so we know it can't be nil. return neverNil 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 } golang-honnef-go-tools-2023.1.7/analysis/facts/nilness/nilness_test.go000066400000000000000000000003121456614407100256740ustar00rootroot00000000000000package nilness import ( "testing" "golang.org/x/tools/go/analysis/analysistest" ) func TestNilness(t *testing.T) { analysistest.Run(t, analysistest.TestData(), Analysis, "example.com/Nilness") } golang-honnef-go-tools-2023.1.7/analysis/facts/nilness/testdata/000077500000000000000000000000001456614407100244505ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/analysis/facts/nilness/testdata/src/000077500000000000000000000000001456614407100252375ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/analysis/facts/nilness/testdata/src/example.com/000077500000000000000000000000001456614407100274475ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/analysis/facts/nilness/testdata/src/example.com/Nilness/000077500000000000000000000000001456614407100310625ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/analysis/facts/nilness/testdata/src/example.com/Nilness/Nilness.go000066400000000000000000000030071456614407100330240ustar00rootroot00000000000000package 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 } func fn27[T ~struct{ F int }]() T { // want fn27:`never returns nil: \[never\]` return T{0} } Nilness_go17.go000066400000000000000000000011541456614407100336030ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/analysis/facts/nilness/testdata/src/example.com/Nilness//go:build go1.17 // +build go1.17 package pkg func fn21() *[5]int { // want fn21:`never returns nil: \[never\]` var x []int return (*[5]int)(x) } func fn22() *[0]int { var x []int return (*[0]int)(x) } func fn23() *[5]int { // want fn23:`never returns nil: \[never\]` var x []int type T [5]int ret := (*T)(x) return (*[5]int)(ret) } func fn24() *[0]int { var x []int type T [0]int ret := (*T)(x) return (*[0]int)(ret) } func fn25() *[5]int { // want fn25:`never returns nil: \[never\]` var x []int type T *[5]int return (T)(x) } func fn26() *[0]int { var x []int type T *[0]int return (T)(x) } golang-honnef-go-tools-2023.1.7/analysis/facts/purity/000077500000000000000000000000001456614407100225205ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/analysis/facts/purity/purity.go000066400000000000000000000156751456614407100244210ustar00rootroot00000000000000package purity // TODO(dh): we should split this into two facts, one tracking actual purity, and one tracking side-effects. A function // that returns a heap allocation isn't pure, but it may be free of side effects. 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 Result map[*types.Func]*IsPure var Analyzer = &analysis.Analyzer{ Name: "fact_purity", Doc: "Mark pure functions", Run: purity, Requires: []*analysis.Analyzer{buildir.Analyzer}, FactTypes: []analysis.Fact{(*IsPure)(nil)}, ResultType: reflect.TypeOf(Result{}), } 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": {}, "time.Now": {}, "time.Parse": {}, "time.ParseInLocation": {}, "time.Unix": {}, "time.UnixMicro": {}, "time.UnixMilli": {}, "(time.Time).Add": {}, "(time.Time).AddDate": {}, "(time.Time).After": {}, "(time.Time).Before": {}, "(time.Time).Clock": {}, "(time.Time).Compare": {}, "(time.Time).Date": {}, "(time.Time).Day": {}, "(time.Time).Equal": {}, "(time.Time).Format": {}, "(time.Time).GoString": {}, "(time.Time).GobEncode": {}, "(time.Time).Hour": {}, "(time.Time).ISOWeek": {}, "(time.Time).In": {}, "(time.Time).IsDST": {}, "(time.Time).IsZero": {}, "(time.Time).Local": {}, "(time.Time).Location": {}, "(time.Time).MarshalBinary": {}, "(time.Time).MarshalJSON": {}, "(time.Time).MarshalText": {}, "(time.Time).Minute": {}, "(time.Time).Month": {}, "(time.Time).Nanosecond": {}, "(time.Time).Round": {}, "(time.Time).Second": {}, "(time.Time).String": {}, "(time.Time).Sub": {}, "(time.Time).Truncate": {}, "(time.Time).UTC": {}, "(time.Time).Unix": {}, "(time.Time).UnixMicro": {}, "(time.Time).UnixMilli": {}, "(time.Time).UnixNano": {}, "(time.Time).Weekday": {}, "(time.Time).Year": {}, "(time.Time).YearDay": {}, "(time.Time).Zone": {}, "(time.Time).ZoneBounds": {}, } 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 } var isBasic func(typ types.Type) bool isBasic = func(typ types.Type) bool { switch u := typ.Underlying().(type) { case *types.Basic: return true case *types.Struct: for i := 0; i < u.NumFields(); i++ { if !isBasic(u.Field(i).Type()) { return false } } return true default: 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 !isBasic(param.Type()) { 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 } var isStackAddr func(ir.Value) bool isStackAddr = func(v ir.Value) bool { switch v := v.(type) { case *ir.Alloc: return !v.Heap case *ir.FieldAddr: return isStackAddr(v.X) default: return false } } 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: if !isStackAddr(ins.Addr) { return false } case *ir.FieldAddr: if !isStackAddr(ins.X) { return false } case *ir.Alloc: // TODO(dh): make use of proper escape analysis if ins.Heap { return false } case *ir.Load: if !isStackAddr(ins.X) { return false } } } } return true } for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { check(fn) } out := Result{} for _, fact := range pass.AllObjectFacts() { out[fact.Object.(*types.Func)] = fact.Fact.(*IsPure) } return out, nil } golang-honnef-go-tools-2023.1.7/analysis/facts/purity/purity_test.go000066400000000000000000000003071456614407100254420ustar00rootroot00000000000000package purity import ( "testing" "golang.org/x/tools/go/analysis/analysistest" ) func TestPurity(t *testing.T) { analysistest.Run(t, analysistest.TestData(), Analyzer, "example.com/Purity") } golang-honnef-go-tools-2023.1.7/analysis/facts/purity/testdata/000077500000000000000000000000001456614407100243315ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/analysis/facts/purity/testdata/src/000077500000000000000000000000001456614407100251205ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/analysis/facts/purity/testdata/src/example.com/000077500000000000000000000000001456614407100273305ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/analysis/facts/purity/testdata/src/example.com/Purity/000077500000000000000000000000001456614407100306245ustar00rootroot00000000000000CheckPureFunctions.go000066400000000000000000000012731456614407100346410ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/analysis/facts/purity/testdata/src/example.com/Puritypackage 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" type pureStruct1 struct { a int b string pureStruct2 } type pureStruct2 struct { c float64 } func (arg pureStruct1) get() int { // want get:"is pure" return arg.a } golang-honnef-go-tools-2023.1.7/analysis/facts/tokenfile/000077500000000000000000000000001456614407100231445ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/analysis/facts/tokenfile/token.go000066400000000000000000000007711456614407100246200ustar00rootroot00000000000000package tokenfile import ( "go/ast" "go/token" "reflect" "golang.org/x/tools/go/analysis" ) var Analyzer = &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{}), } golang-honnef-go-tools-2023.1.7/analysis/facts/typedness/000077500000000000000000000000001456614407100232025ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/analysis/facts/typedness/testdata/000077500000000000000000000000001456614407100250135ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/analysis/facts/typedness/testdata/src/000077500000000000000000000000001456614407100256025ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/analysis/facts/typedness/testdata/src/example.com/000077500000000000000000000000001456614407100300125ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/analysis/facts/typedness/testdata/src/example.com/Typedness/000077500000000000000000000000001456614407100317705ustar00rootroot00000000000000Typedness.go000066400000000000000000000102411456614407100342140ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/analysis/facts/typedness/testdata/src/example.com/Typednesspackage 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 } golang-honnef-go-tools-2023.1.7/analysis/facts/typedness/typedness.go000066400000000000000000000155231456614407100255550ustar00rootroot00000000000000package 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/exp/typeparams" "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 { typ := fn.Signature.Results().At(i).Type() if _, ok := typ.Underlying().(*types.Interface); ok && !typeparams.IsTypeParam(typ) { if do(v, map[ir.Value]struct{}{}) { out |= 1 << i } } } return out } golang-honnef-go-tools-2023.1.7/analysis/facts/typedness/typedness_test.go000066400000000000000000000003201456614407100266010ustar00rootroot00000000000000package typedness import ( "testing" "golang.org/x/tools/go/analysis/analysistest" ) func TestTypedness(t *testing.T) { analysistest.Run(t, analysistest.TestData(), Analysis, "example.com/Typedness") } golang-honnef-go-tools-2023.1.7/analysis/lint/000077500000000000000000000000001456614407100210325ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/analysis/lint/lint.go000066400000000000000000000153361456614407100223370ustar00rootroot00000000000000// Package lint provides abstractions on top of go/analysis. // These abstractions add extra information to analyzes, such as structured documentation and severities. package lint import ( "flag" "fmt" "go/ast" "go/build" "go/token" "regexp" "strconv" "strings" "golang.org/x/tools/go/analysis" ) // Analyzer wraps a go/analysis.Analyzer and provides structured documentation. 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 } } // InitializeAnalyzers takes a map of documentation and a map of go/analysis.Analyzers and returns a slice of Analyzers. // The map keys are the analyzer names. 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 } // Severity describes the severity of diagnostics reported by an analyzer. type Severity int const ( SeverityNone Severity = iota SeverityError SeverityDeprecated SeverityWarning SeverityInfo SeverityHint ) // MergeStrategy sets how merge mode should behave for diagnostics of an analyzer. type MergeStrategy int const ( MergeIfAny MergeStrategy = iota MergeIfAll ) type RawDocumentation struct { Title string Text string Before string After string Since string NonDefault bool Options []string Severity Severity MergeIf MergeStrategy } type Documentation struct { Title string Text string TitleMarkdown string TextMarkdown string Before string After string Since string NonDefault bool Options []string Severity Severity MergeIf MergeStrategy } func Markdownify(m map[string]*RawDocumentation) map[string]*Documentation { out := make(map[string]*Documentation, len(m)) for k, v := range m { out[k] = &Documentation{ Title: strings.TrimSpace(stripMarkdown(v.Title)), Text: strings.TrimSpace(stripMarkdown(v.Text)), TitleMarkdown: strings.TrimSpace(toMarkdown(v.Title)), TextMarkdown: strings.TrimSpace(toMarkdown(v.Text)), Before: strings.TrimSpace(v.Before), After: strings.TrimSpace(v.After), Since: v.Since, NonDefault: v.NonDefault, Options: v.Options, Severity: v.Severity, MergeIf: v.MergeIf, } } return out } func toMarkdown(s string) string { return strings.NewReplacer(`\'`, "`", `\"`, "`").Replace(s) } func stripMarkdown(s string) string { return strings.NewReplacer(`\'`, "", `\"`, "'").Replace(s) } func (doc *Documentation) Format(metadata bool) string { return doc.format(false, metadata) } func (doc *Documentation) FormatMarkdown(metadata bool) string { return doc.format(true, metadata) } func (doc *Documentation) format(markdown bool, metadata bool) string { b := &strings.Builder{} if markdown { fmt.Fprintf(b, "%s\n\n", doc.TitleMarkdown) if doc.Text != "" { fmt.Fprintf(b, "%s\n\n", doc.TextMarkdown) } } else { fmt.Fprintf(b, "%s\n\n", doc.Title) if doc.Text != "" { fmt.Fprintf(b, "%s\n\n", doc.Text) } } if doc.Before != "" { fmt.Fprintln(b, "Before:") fmt.Fprintln(b, "") for _, line := range strings.Split(doc.Before, "\n") { fmt.Fprint(b, " ", line, "\n") } fmt.Fprintln(b, "") fmt.Fprintln(b, "After:") fmt.Fprintln(b, "") for _, line := range strings.Split(doc.After, "\n") { fmt.Fprint(b, " ", line, "\n") } fmt.Fprintln(b, "") } if metadata { 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 (doc *Documentation) String() string { return doc.Format(true) } 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) } var goVersionRE = regexp.MustCompile(`^(?:go)?1.(\d+).*$`) // ParseGoVersion parses Go versions of the form 1.M, 1.M.N, or 1.M.NrcR, with an optional "go" prefix. It assumes that // versions have already been verified and are valid. func ParseGoVersion(s string) (int, bool) { m := goVersionRE.FindStringSubmatch(s) if m == nil { return 0, false } n, err := strconv.Atoi(m[1]) if err != nil { return 0, false } return n, true } func (v *VersionFlag) Set(s string) error { n, ok := ParseGoVersion(s) if !ok { return fmt.Errorf("invalid Go version: %q", s) } *v = VersionFlag(n) 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:] } // ParseDirectives extracts all directives from a list of Go files. 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 } golang-honnef-go-tools-2023.1.7/analysis/lint/testutil/000077500000000000000000000000001456614407100227075ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/analysis/lint/testutil/check.go000066400000000000000000000247741456614407100243310ustar00rootroot00000000000000// Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // This file is a modified copy of x/tools/go/analysis/analysistest/analysistest.go package testutil import ( "bytes" "fmt" "go/format" "go/token" "os" "path/filepath" "regexp" "sort" "strings" "testing" "honnef.co/go/tools/internal/diff/myers" "honnef.co/go/tools/lintcmd/runner" "golang.org/x/tools/go/expect" "golang.org/x/tools/txtar" ) func CheckSuggestedFixes(t *testing.T, diagnostics []runner.Diagnostic) { // Process each result (package) separately, matching up the suggested // fixes into a diff, which we will compare to the .golden file. We have // to do this per-result in case a file appears in two packages, such as in // packages with tests, where mypkg/a.go will appear in both mypkg and // mypkg.test. In that case, the analyzer may suggest the same set of // changes to a.go for each package. If we merge all the results, those // changes get doubly applied, which will cause conflicts or mismatches. // Validating the results separately means as long as the two analyses // don't produce conflicting suggestions for a single file, everything // should match up. // file -> message -> edits fileEdits := make(map[string]map[string][]runner.TextEdit) fileContents := make(map[string][]byte) // Validate edits, prepare the fileEdits map and read the file contents. for _, diag := range diagnostics { for _, sf := range diag.SuggestedFixes { for _, edit := range sf.TextEdits { // Validate the edit. if edit.Position.Offset > edit.End.Offset { t.Errorf( "diagnostic for analysis %v contains Suggested Fix with malformed edit: pos (%v) > end (%v)", diag.Category, edit.Position.Offset, edit.End.Offset) continue } if edit.Position.Filename != edit.End.Filename { t.Errorf( "diagnostic for analysis %v contains Suggested Fix with malformed edit spanning files %v and %v", diag.Category, edit.Position.Filename, edit.End.Filename) continue } if _, ok := fileContents[edit.Position.Filename]; !ok { contents, err := os.ReadFile(edit.Position.Filename) if err != nil { t.Errorf("error reading %s: %v", edit.Position.Filename, err) } fileContents[edit.Position.Filename] = contents } if _, ok := fileEdits[edit.Position.Filename]; !ok { fileEdits[edit.Position.Filename] = make(map[string][]runner.TextEdit) } fileEdits[edit.Position.Filename][sf.Message] = append(fileEdits[edit.Position.Filename][sf.Message], edit) } } } for file, fixes := range fileEdits { // Get the original file contents. orig, ok := fileContents[file] if !ok { t.Errorf("could not find file contents for %s", file) continue } // Get the golden file and read the contents. ar, err := txtar.ParseFile(file + ".golden") if err != nil { t.Errorf("error reading %s.golden: %v", file, err) continue } if len(ar.Files) > 0 { // one virtual file per kind of suggested fix if len(ar.Comment) != 0 { // we allow either just the comment, or just virtual // files, not both. it is not clear how "both" should // behave. t.Errorf("%s.golden has leading comment; we don't know what to do with it", file) continue } var sfs []string for sf := range fixes { sfs = append(sfs, sf) } sort.Slice(sfs, func(i, j int) bool { return sfs[i] < sfs[j] }) for _, sf := range sfs { edits := fixes[sf] found := false for _, vf := range ar.Files { if vf.Name == sf { found = true out := applyEdits(orig, edits) // the file may contain multiple trailing // newlines if the user places empty lines // between files in the archive. normalize // this to a single newline. want := string(bytes.TrimRight(vf.Data, "\n")) + "\n" formatted, err := format.Source([]byte(out)) if err != nil { t.Errorf("%s: error formatting edited source: %v\n%s", file, err, out) continue } if want != string(formatted) { d := myers.ComputeEdits(want, string(formatted)) diff := "" for _, op := range d { diff += op.String() } t.Errorf("suggested fixes failed for %s[%s]:\n%s", file, sf, diff) } break } } if !found { t.Errorf("no section for suggested fix %q in %s.golden", sf, file) } } } else { // all suggested fixes are represented by a single file var catchallEdits []runner.TextEdit for _, edits := range fixes { catchallEdits = append(catchallEdits, edits...) } out := applyEdits(orig, catchallEdits) want := string(ar.Comment) formatted, err := format.Source([]byte(out)) if err != nil { t.Errorf("%s: error formatting resulting source: %v\n%s", file, err, out) continue } if want != string(formatted) { d := myers.ComputeEdits(want, string(formatted)) diff := "" for _, op := range d { diff += op.String() } t.Errorf("suggested fixes failed for %s:\n%s", file, diff) } } } } func Check(t *testing.T, gopath string, files []string, diagnostics []runner.Diagnostic, facts []runner.TestFact) { relativePath := func(path string) string { cwd, err := os.Getwd() if err != nil { return path } rel, err := filepath.Rel(cwd, path) if err != nil { return path } return rel } type key struct { file string line int } // the 'files' argument contains a list of all files that were part of the tested package want := make(map[key][]*expect.Note) fset := token.NewFileSet() seen := map[string]struct{}{} for _, file := range files { seen[file] = struct{}{} notes, err := expect.Parse(fset, file, nil) if err != nil { t.Fatal(err) } for _, note := range notes { k := key{ file: file, line: fset.PositionFor(note.Pos, false).Line, } want[k] = append(want[k], note) } } for _, diag := range diagnostics { file := diag.Position.Filename if _, ok := seen[file]; !ok { t.Errorf("got diagnostic in file %q, but that file isn't part of the checked package", relativePath(file)) return } } check := func(posn token.Position, message string, kind string, argIdx int, identifier string) { k := key{posn.Filename, posn.Line} expects := want[k] var unmatched []string for i, exp := range expects { if exp.Name == kind { if kind == "fact" && exp.Args[0] != expect.Identifier(identifier) { continue } matched := false switch arg := exp.Args[argIdx].(type) { case string: matched = strings.Contains(message, arg) case *regexp.Regexp: matched = arg.MatchString(message) default: t.Fatalf("unexpected argument type %T", arg) } if matched { // matched: remove the expectation. expects[i] = expects[len(expects)-1] expects = expects[:len(expects)-1] want[k] = expects return } unmatched = append(unmatched, fmt.Sprintf("%q", exp.Args[argIdx])) } } if unmatched == nil { posn.Filename = relativePath(posn.Filename) t.Errorf("%v: unexpected diag: %v", posn, message) } else { posn.Filename = relativePath(posn.Filename) t.Errorf("%v: diag %q does not match pattern %s", posn, message, strings.Join(unmatched, " or ")) } } checkDiag := func(posn token.Position, message string) { check(posn, message, "diag", 0, "") } checkFact := func(posn token.Position, name, message string) { check(posn, message, "fact", 1, name) } // Check the diagnostics match expectations. for _, f := range diagnostics { // TODO(matloob): Support ranges in analysistest. posn := f.Position checkDiag(posn, f.Message) } // Check the facts match expectations. for _, fact := range facts { name := fact.ObjectName posn := fact.Position if name == "" { name = "package" posn.Line = 1 } checkFact(posn, name, fact.FactString) } // Reject surplus expectations. // // Sometimes an Analyzer reports two similar diagnostics on a // line with only one expectation. The reader may be confused by // the error message. // TODO(adonovan): print a better error: // "got 2 diagnostics here; each one needs its own expectation". var surplus []string for key, expects := range want { for _, exp := range expects { surplus = append(surplus, fmt.Sprintf("%s:%d: no %s was reported matching %q", relativePath(key.file), key.line, exp.Name, exp.Args)) } } sort.Strings(surplus) for _, err := range surplus { t.Errorf("%s", err) } } func applyEdits(src []byte, edits []runner.TextEdit) []byte { // This function isn't efficient, but it doesn't have to be. edits = append([]runner.TextEdit(nil), edits...) sort.Slice(edits, func(i, j int) bool { if edits[i].Position.Offset < edits[j].Position.Offset { return true } if edits[i].Position.Offset == edits[j].Position.Offset { return edits[i].End.Offset < edits[j].End.Offset } return false }) out := append([]byte(nil), src...) offset := 0 for _, edit := range edits { start := edit.Position.Offset + offset end := edit.End.Offset + offset if edit.End == (token.Position{}) { end = -1 } if len(edit.NewText) == 0 { // pure deletion copy(out[start:], out[end:]) out = out[:len(out)-(end-start)] offset -= end - start } else if end == -1 || end == start { // pure insertion tmp := make([]byte, len(out)+len(edit.NewText)) copy(tmp, out[:start]) copy(tmp[start:], edit.NewText) copy(tmp[start+len(edit.NewText):], out[start:]) offset += len(edit.NewText) out = tmp } else if end-start == len(edit.NewText) { // exact replacement copy(out[start:], edit.NewText) } else if end-start < len(edit.NewText) { // replace with longer string growth := len(edit.NewText) - (end - start) tmp := make([]byte, len(out)+growth) copy(tmp, out[:start]) copy(tmp[start:], edit.NewText) copy(tmp[start+len(edit.NewText):], out[end:]) offset += growth out = tmp } else if end-start > len(edit.NewText) { // replace with shorter string shrinkage := (end - start) - len(edit.NewText) copy(out[start:], edit.NewText) copy(out[start+len(edit.NewText):], out[end:]) out = out[:len(out)-shrinkage] offset -= shrinkage } } // Debug code if false { fmt.Println("input:") fmt.Println(string(src)) fmt.Println() fmt.Println("edits:") for _, edit := range edits { fmt.Printf("%d:%d - %d:%d <- %q\n", edit.Position.Line, edit.Position.Column, edit.End.Line, edit.End.Column, edit.NewText) } fmt.Println("output:") fmt.Println(string(out)) panic("") } return out } golang-honnef-go-tools-2023.1.7/analysis/lint/testutil/util.go000066400000000000000000000121751456614407100242210ustar00rootroot00000000000000package testutil import ( "crypto/sha256" "go/build" "io" "os" "path/filepath" "testing" "honnef.co/go/tools/analysis/lint" "honnef.co/go/tools/config" "honnef.co/go/tools/go/buildid" "honnef.co/go/tools/lintcmd/cache" "honnef.co/go/tools/lintcmd/runner" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/packages" ) type Test struct { Dir string Version string } func computeSalt() ([]byte, error) { p, err := os.Executable() if err != nil { return nil, err } if id, err := buildid.ReadFile(p); err == nil { return []byte(id), nil } else { // For some reason we couldn't read the build id from the executable. // Fall back to hashing the entire executable. f, err := os.Open(p) if err != nil { return nil, err } defer f.Close() h := sha256.New() if _, err := io.Copy(h, f); err != nil { return nil, err } return h.Sum(nil), nil } } func defaultGoVersion() string { tags := build.Default.ReleaseTags v := tags[len(tags)-1][2:] return v } func Run(t *testing.T, analyzers []*lint.Analyzer, tests map[string][]Test) { analyzersByName := map[string]*lint.Analyzer{} for _, a := range analyzers { analyzersByName[a.Analyzer.Name] = a } analyzersByVersion := map[string]map[*lint.Analyzer]struct{}{} dirsByVersion := map[string][]string{} for analyzerName, ttt := range tests { for _, tt := range ttt { m := analyzersByVersion[tt.Version] if m == nil { m = map[*lint.Analyzer]struct{}{} analyzersByVersion[tt.Version] = m } analyzer, ok := analyzersByName[analyzerName] if !ok { t.Errorf("found tests for analyzer %q, but no such analyzer exists", analyzerName) continue } m[analyzer] = struct{}{} dirsByVersion[tt.Version] = append(dirsByVersion[tt.Version], tt.Dir) } } for v, asm := range analyzersByVersion { dirs := dirsByVersion[v] actualVersion := v if actualVersion == "" { actualVersion = defaultGoVersion() } as := make([]*analysis.Analyzer, 0, len(asm)) for a := range asm { as = append(as, a.Analyzer) if err := a.Analyzer.Flags.Lookup("go").Value.Set(actualVersion); err != nil { t.Fatal(err) } } c, err := cache.Open(t.TempDir()) if err != nil { t.Fatal(err) } salt, err := computeSalt() if err != nil { t.Fatal(err) } c.SetSalt(salt) r, err := runner.New(config.Config{}, c) if err != nil { t.Fatal(err) } r.GoVersion = actualVersion r.TestMode = true testdata, err := filepath.Abs("testdata") if err != nil { t.Fatal(err) } cfg := &packages.Config{ Tests: true, Env: append(os.Environ(), "GOPATH="+testdata, "GO111MODULE=off", "GOPROXY=off"), } if len(dirs) == 0 { t.Fatal("no directories for version", v) } res, err := r.Run(cfg, as, dirs) if err != nil { t.Fatal(err) } // Each result in res contains all diagnostics and facts for all checked packages for all checked analyzers. // For each package, we only care about the diagnostics and facts reported by a single analyzer. // resultByPath maps from import path to results resultByPath := map[string][]runner.Result{} failed := false for _, r := range res { if r.Failed { failed = true if len(r.Errors) > 0 { t.Fatalf("failed checking %s: %v", r.Package.PkgPath, r.Errors) } } // r.Package.PkgPath is not unique. The same path can refer to a package and a package plus its // (non-external) tests. resultByPath[r.Package.PkgPath] = append(resultByPath[r.Package.PkgPath], r) } if failed { t.Fatal("failed processing package, but got no errors") } for a, ttt := range tests { for _, tt := range ttt { if tt.Version != v { continue } any := false for _, suffix := range []string{"", ".test", "_test"} { dir := tt.Dir + suffix rr, ok := resultByPath[dir] if !ok { continue } any = true // Remove this result. We later check that there remain no tests we haven't checked. delete(resultByPath, dir) for _, r := range rr { data, err := r.Load() if err != nil { t.Fatal(err) } tdata, err := r.LoadTest() if err != nil { t.Fatal(err) } // Select those diagnostics made by the analyzer we're currently checking var relevantDiags []runner.Diagnostic for _, diag := range data.Diagnostics { // FIXME(dh): Category might not match analyzer names. it does for Staticcheck, for now if diag.Category != a { continue } relevantDiags = append(relevantDiags, diag) } var relevantFacts []runner.TestFact for _, fact := range tdata.Facts { if fact.Analyzer != a { continue } relevantFacts = append(relevantFacts, fact) } Check(t, testdata, tdata.Files, relevantDiags, relevantFacts) CheckSuggestedFixes(t, relevantDiags) } } if !any { t.Errorf("no result for directory %s", tt.Dir) } } } for key, rr := range resultByPath { for _, r := range rr { data, err := r.Load() if err != nil { t.Fatal(err) } if len(data.Diagnostics) != 0 { t.Errorf("unexpected diagnostics in package %s", key) } } } } } golang-honnef-go-tools-2023.1.7/analysis/report/000077500000000000000000000000001456614407100213775ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/analysis/report/report.go000066400000000000000000000125761456614407100232540ustar00rootroot00000000000000package report import ( "bytes" "fmt" "go/ast" "go/format" "go/token" "path/filepath" "strconv" "strings" "honnef.co/go/tools/analysis/facts/generated" "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[generated.Analyzer].(map[string]generated.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 } golang-honnef-go-tools-2023.1.7/analysis/report/report_test.go000066400000000000000000000012401456614407100242750ustar00rootroot00000000000000package 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) } } } golang-honnef-go-tools-2023.1.7/cmd/000077500000000000000000000000001456614407100170045ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/cmd/keyify/000077500000000000000000000000001456614407100203045ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/cmd/keyify/README.md000066400000000000000000000020111456614407100215550ustar00rootroot00000000000000Keyify 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) golang-honnef-go-tools-2023.1.7/cmd/keyify/go-keyify.el000066400000000000000000000030041456614407100225260ustar00rootroot00000000000000;;; 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 golang-honnef-go-tools-2023.1.7/cmd/keyify/keyify.go000066400000000000000000000217561456614407100221460ustar00rootroot00000000000000// 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)) } } golang-honnef-go-tools-2023.1.7/cmd/keyify/position.go000066400000000000000000000032441456614407100225020ustar00rootroot00000000000000// 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 } golang-honnef-go-tools-2023.1.7/cmd/staticcheck/000077500000000000000000000000001456614407100212715ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/cmd/staticcheck/README.md000066400000000000000000000006331456614407100225520ustar00rootroot00000000000000# 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/). golang-honnef-go-tools-2023.1.7/cmd/staticcheck/staticcheck.go000066400000000000000000000020151456614407100241030ustar00rootroot00000000000000// 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() } golang-honnef-go-tools-2023.1.7/cmd/structlayout-optimize/000077500000000000000000000000001456614407100234245ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/cmd/structlayout-optimize/main.go000066400000000000000000000075471456614407100247140ustar00rootroot00000000000000// 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 } golang-honnef-go-tools-2023.1.7/cmd/structlayout-pretty/000077500000000000000000000000001456614407100231135ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/cmd/structlayout-pretty/main.go000066400000000000000000000030761456614407100243740ustar00rootroot00000000000000// 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 } } golang-honnef-go-tools-2023.1.7/cmd/structlayout/000077500000000000000000000000001456614407100215665ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/cmd/structlayout/README.md000066400000000000000000000054271456614407100230550ustar00rootroot00000000000000# 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) golang-honnef-go-tools-2023.1.7/cmd/structlayout/main.go000066400000000000000000000055711456614407100230510ustar00rootroot00000000000000// 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.NeedExportFile | 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 } golang-honnef-go-tools-2023.1.7/cmd/unused/000077500000000000000000000000001456614407100203075ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/cmd/unused/unused.go000066400000000000000000000046001456614407100221410ustar00rootroot00000000000000package main import ( "flag" "fmt" "log" "os" "golang.org/x/tools/go/packages" "honnef.co/go/tools/go/loader" "honnef.co/go/tools/lintcmd/cache" "honnef.co/go/tools/unused" ) // OPT(dh): we don't need full graph merging if we're not flagging exported objects. In that case, we can reuse the old // list-based merging approach. // OPT(dh): we can either merge graphs as we process packages, or we can merge them all in one go afterwards (then // reloading them from cache). The first approach will likely lead to higher peak memory usage, but the latter may take // more wall time to finish if we had spare CPU resources while processing packages. func main() { opts := unused.DefaultOptions flag.BoolVar(&opts.FieldWritesAreUses, "field-writes-are-uses", opts.FieldWritesAreUses, "") flag.BoolVar(&opts.PostStatementsAreReads, "post-statements-are-reads", opts.PostStatementsAreReads, "") flag.BoolVar(&opts.ExportedIsUsed, "exported-is-used", opts.ExportedIsUsed, "") flag.BoolVar(&opts.ExportedFieldsAreUsed, "exported-fields-are-used", opts.ExportedFieldsAreUsed, "") flag.BoolVar(&opts.ParametersAreUsed, "parameters-are-used", opts.ParametersAreUsed, "") flag.BoolVar(&opts.LocalVariablesAreUsed, "local-variables-are-used", opts.LocalVariablesAreUsed, "") flag.BoolVar(&opts.GeneratedIsUsed, "generated-is-used", opts.GeneratedIsUsed, "") flag.Parse() // pprof.StartCPUProfile(os.Stdout) // defer pprof.StopCPUProfile() // XXX set cache key for this tool c, err := cache.Default() if err != nil { log.Fatal(err) } cfg := &packages.Config{ Tests: true, } specs, err := loader.Graph(c, cfg, flag.Args()...) if err != nil { log.Fatal(err) } var sg unused.SerializedGraph ourPkgs := map[string]struct{}{} for _, spec := range specs { if len(spec.Errors) != 0 { // XXX priunt errors continue } lpkg, _, err := loader.Load(spec) if err != nil { continue } if len(lpkg.Errors) != 0 { continue } // XXX get directives and generated g := unused.Graph(lpkg.Fset, lpkg.Syntax, lpkg.Types, lpkg.TypesInfo, nil, nil, opts) sg.Merge(g) ourPkgs[spec.PkgPath] = struct{}{} } res := sg.Results() for _, obj := range res.Unused { // XXX format paths like staticcheck does if _, ok := ourPkgs[obj.Path.PkgPath]; !ok { continue } fmt.Printf("%s: %s %s is unused\n", obj.DisplayPosition, obj.Kind, obj.Name) } fmt.Fprintln(os.Stderr, sg.Dot()) } golang-honnef-go-tools-2023.1.7/config/000077500000000000000000000000001456614407100175065ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/config/config.go000066400000000000000000000150631456614407100213070ustar00rootroot00000000000000package 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() } // DefaultConfig is the default configuration. // Its initial value describes the majority of the default configuration, // but the Checks field can be updated at runtime based on the analyzers being used, to disable non-default checks. // For cmd/staticcheck, this is handled by (*lintcmd.Command).Run. // // Note that DefaultConfig shouldn't be modified while analyzers are executing. var DefaultConfig = Config{ Checks: []string{"all"}, 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{ "github.com/mmcloughlin/avo/build", "github.com/mmcloughlin/avo/operand", "github.com/mmcloughlin/avo/reg", }, HTTPStatusCodeWhitelist: []string{"200", "400", "404", "500"}, } const ConfigName = "staticcheck.conf" type ParseError struct { Filename string toml.ParseError } 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.NewDecoder(f).Decode(&cfg) f.Close() if err != nil { if err, ok := err.(toml.ParseError); ok { return nil, ParseError{ Filename: filepath.Join(dir, ConfigName), ParseError: err, } } 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 } golang-honnef-go-tools-2023.1.7/config/example.conf000066400000000000000000000011561456614407100220130ustar00rootroot00000000000000checks = ["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 = [ "github.com/mmcloughlin/avo/build", "github.com/mmcloughlin/avo/operand", "github.com/mmcloughlin/avo/reg", ] http_status_code_whitelist = ["200", "400", "404", "500"] golang-honnef-go-tools-2023.1.7/debug/000077500000000000000000000000001456614407100173275ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/debug/debug.go000066400000000000000000000024641456614407100207520ustar00rootroot00000000000000// Package debug contains helpers for debugging static analyses. package debug import ( "bytes" "go/ast" "go/format" "go/importer" "go/parser" "go/token" "go/types" ) // TypeCheck parses and type-checks a single-file Go package from a string. // The package must not have any imports. func TypeCheck(src string) (*ast.File, *types.Package, *types.Info, error) { fset := token.NewFileSet() f, err := parser.ParseFile(fset, "foo.go", src, parser.ParseComments|parser.SkipObjectResolution) if err != nil { return nil, nil, nil, err } pkg := types.NewPackage("foo", f.Name.Name) info := &types.Info{ Types: map[ast.Expr]types.TypeAndValue{}, Defs: map[*ast.Ident]types.Object{}, Uses: map[*ast.Ident]types.Object{}, Implicits: map[ast.Node]types.Object{}, Selections: map[*ast.SelectorExpr]*types.Selection{}, Scopes: map[ast.Node]*types.Scope{}, InitOrder: []*types.Initializer{}, Instances: map[*ast.Ident]types.Instance{}, } tcfg := &types.Config{ Importer: importer.Default(), } if err := types.NewChecker(tcfg, fset, pkg, info).Files([]*ast.File{f}); err != nil { return nil, nil, nil, err } return f, pkg, info, nil } func FormatNode(node ast.Node) string { var buf bytes.Buffer fset := token.NewFileSet() format.Node(&buf, fset, node) return buf.String() } golang-honnef-go-tools-2023.1.7/dist/000077500000000000000000000000001456614407100172045ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/dist/build.sh000077500000000000000000000030331456614407100206410ustar00rootroot00000000000000#!/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" build "darwin" "arm64" for arch in armv5l armv6l armv7l arm64; do build "linux" "$arch" done ( cd "$d" sha256sum -c --strict *.sha256 ) golang-honnef-go-tools-2023.1.7/doc/000077500000000000000000000000001456614407100170065ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/doc/articles/000077500000000000000000000000001456614407100206145ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/doc/articles/customizing_staticcheck.html000066400000000000000000000003131456614407100264170ustar00rootroot00000000000000- 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 golang-honnef-go-tools-2023.1.7/doc/run.html000066400000000000000000000027321456614407100205040ustar00rootroot00000000000000

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.

golang-honnef-go-tools-2023.1.7/go.mod000066400000000000000000000004251456614407100173500ustar00rootroot00000000000000module honnef.co/go/tools go 1.19 require ( github.com/BurntSushi/toml v1.2.1 golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a golang.org/x/sys v0.11.0 golang.org/x/tools v0.12.1-0.20230825192346-2191a27a6dc5 ) require golang.org/x/mod v0.12.0 // indirect golang-honnef-go-tools-2023.1.7/go.sum000066400000000000000000000017441456614407100174020ustar00rootroot00000000000000github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a h1:Jw5wfR+h9mnIYH+OtGT2im5wV1YGGDora5vTv/aa5bE= golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/tools v0.12.1-0.20230825192346-2191a27a6dc5 h1:Vk4mysSz+GqQK2eqgWbo4zEO89wkeAjJiFIr9bpqa8k= golang.org/x/tools v0.12.1-0.20230825192346-2191a27a6dc5/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= golang-honnef-go-tools-2023.1.7/go/000077500000000000000000000000001456614407100166465ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/go/ast/000077500000000000000000000000001456614407100174355ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/go/ast/astutil/000077500000000000000000000000001456614407100211225ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/go/ast/astutil/upstream.go000066400000000000000000000006561456614407100233200ustar00rootroot00000000000000package 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) } golang-honnef-go-tools-2023.1.7/go/ast/astutil/util.go000066400000000000000000000203041456614407100224250ustar00rootroot00000000000000package 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 == "_" } // Deprecated: use code.IsIntegerLiteral instead. 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 } } // CopyExpr creates a deep copy of an expression. // It doesn't support copying FuncLits and returns ok == false when encountering one. func CopyExpr(node ast.Expr) (ast.Expr, bool) { switch node := node.(type) { case *ast.BasicLit: cp := *node return &cp, true case *ast.BinaryExpr: cp := *node var ok1, ok2 bool cp.X, ok1 = CopyExpr(cp.X) cp.Y, ok2 = CopyExpr(cp.Y) return &cp, ok1 && ok2 case *ast.CallExpr: var ok bool cp := *node cp.Fun, ok = CopyExpr(cp.Fun) if !ok { return nil, false } cp.Args = make([]ast.Expr, len(node.Args)) for i, v := range node.Args { cp.Args[i], ok = CopyExpr(v) if !ok { return nil, false } } return &cp, true case *ast.CompositeLit: var ok bool cp := *node cp.Type, ok = CopyExpr(cp.Type) if !ok { return nil, false } cp.Elts = make([]ast.Expr, len(node.Elts)) for i, v := range node.Elts { cp.Elts[i], ok = CopyExpr(v) if !ok { return nil, false } } return &cp, true case *ast.Ident: cp := *node return &cp, true case *ast.IndexExpr: var ok1, ok2 bool cp := *node cp.X, ok1 = CopyExpr(cp.X) cp.Index, ok2 = CopyExpr(cp.Index) return &cp, ok1 && ok2 case *ast.IndexListExpr: var ok bool cp := *node cp.X, ok = CopyExpr(cp.X) if !ok { return nil, false } for i, v := range node.Indices { cp.Indices[i], ok = CopyExpr(v) if !ok { return nil, false } } return &cp, true case *ast.KeyValueExpr: var ok1, ok2 bool cp := *node cp.Key, ok1 = CopyExpr(cp.Key) cp.Value, ok2 = CopyExpr(cp.Value) return &cp, ok1 && ok2 case *ast.ParenExpr: var ok bool cp := *node cp.X, ok = CopyExpr(cp.X) return &cp, ok case *ast.SelectorExpr: var ok bool cp := *node cp.X, ok = CopyExpr(cp.X) if !ok { return nil, false } sel, ok := CopyExpr(cp.Sel) if !ok { // this is impossible return nil, false } cp.Sel = sel.(*ast.Ident) return &cp, true case *ast.SliceExpr: var ok1, ok2, ok3, ok4 bool cp := *node cp.X, ok1 = CopyExpr(cp.X) cp.Low, ok2 = CopyExpr(cp.Low) cp.High, ok3 = CopyExpr(cp.High) cp.Max, ok4 = CopyExpr(cp.Max) return &cp, ok1 && ok2 && ok3 && ok4 case *ast.StarExpr: var ok bool cp := *node cp.X, ok = CopyExpr(cp.X) return &cp, ok case *ast.TypeAssertExpr: var ok1, ok2 bool cp := *node cp.X, ok1 = CopyExpr(cp.X) cp.Type, ok2 = CopyExpr(cp.Type) return &cp, ok1 && ok2 case *ast.UnaryExpr: var ok bool cp := *node cp.X, ok = CopyExpr(cp.X) return &cp, ok case *ast.MapType: var ok1, ok2 bool cp := *node cp.Key, ok1 = CopyExpr(cp.Key) cp.Value, ok2 = CopyExpr(cp.Value) return &cp, ok1 && ok2 case *ast.ArrayType: var ok1, ok2 bool cp := *node cp.Len, ok1 = CopyExpr(cp.Len) cp.Elt, ok2 = CopyExpr(cp.Elt) return &cp, ok1 && ok2 case *ast.Ellipsis: var ok bool cp := *node cp.Elt, ok = CopyExpr(cp.Elt) return &cp, ok case *ast.InterfaceType: cp := *node return &cp, true case *ast.StructType: cp := *node return &cp, true case *ast.FuncLit, *ast.FuncType: // TODO(dh): implement copying of function literals and types. return nil, false case *ast.ChanType: var ok bool cp := *node cp.Value, ok = CopyExpr(cp.Value) return &cp, ok case nil: return nil, true 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.IndexListExpr: b := b.(*ast.IndexListExpr) if len(a.Indices) != len(b.Indices) { return false } for i, v := range a.Indices { if !Equal(v, b.Indices[i]) { return false } } return Equal(a.X, b.X) 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)) } } golang-honnef-go-tools-2023.1.7/go/buildid/000077500000000000000000000000001456614407100202625ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/go/buildid/UPSTREAM000066400000000000000000000004041456614407100214430ustar00rootroot00000000000000This package extracts buildid.go and note.go from cmd/internal/buildid/. We have modified it to remove support for AIX big archive files, to cut down on our dependencies. The last upstream commit we've looked at was: 639acdc833bfd12b7edd43092d1b380d70cb2874 golang-honnef-go-tools-2023.1.7/go/buildid/buildid.go000066400000000000000000000135101456614407100222250ustar00rootroot00000000000000// Copyright 2017 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 buildid import ( "bytes" "debug/elf" "errors" "fmt" "io" "os" "strconv" "strings" ) var errBuildIDMalformed = fmt.Errorf("malformed object file") var ( bangArch = []byte("!") pkgdef = []byte("__.PKGDEF") goobject = []byte("go object ") buildid = []byte("build id ") ) // ReadFile reads the build ID from an archive or executable file. func ReadFile(name string) (id string, err error) { f, err := os.Open(name) if err != nil { return "", err } defer f.Close() buf := make([]byte, 8) if _, err := f.ReadAt(buf, 0); err != nil { return "", err } if string(buf) != "!\n" { if string(buf) == "\n" { return "", errors.New("unsupported") } return readBinary(name, f) } // Read just enough of the target to fetch the build ID. // The archive is expected to look like: // // ! // __.PKGDEF 0 0 0 644 7955 ` // go object darwin amd64 devel X:none // build id "b41e5c45250e25c9fd5e9f9a1de7857ea0d41224" // // The variable-sized strings are GOOS, GOARCH, and the experiment list (X:none). // Reading the first 1024 bytes should be plenty. data := make([]byte, 1024) n, err := io.ReadFull(f, data) if err != nil && n == 0 { return "", err } tryGccgo := func() (string, error) { return readGccgoArchive(name, f) } // Archive header. for i := 0; ; i++ { // returns during i==3 j := bytes.IndexByte(data, '\n') if j < 0 { return tryGccgo() } line := data[:j] data = data[j+1:] switch i { case 0: if !bytes.Equal(line, bangArch) { return tryGccgo() } case 1: if !bytes.HasPrefix(line, pkgdef) { return tryGccgo() } case 2: if !bytes.HasPrefix(line, goobject) { return tryGccgo() } case 3: if !bytes.HasPrefix(line, buildid) { // Found the object header, just doesn't have a build id line. // Treat as successful, with empty build id. return "", nil } id, err := strconv.Unquote(string(line[len(buildid):])) if err != nil { return tryGccgo() } return id, nil } } } // readGccgoArchive tries to parse the archive as a standard Unix // archive file, and fetch the build ID from the _buildid.o entry. // The _buildid.o entry is written by (*Builder).gccgoBuildIDELFFile // in cmd/go/internal/work/exec.go. func readGccgoArchive(name string, f *os.File) (string, error) { bad := func() (string, error) { return "", &os.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed} } off := int64(8) for { if _, err := f.Seek(off, io.SeekStart); err != nil { return "", err } // TODO(iant): Make a debug/ar package, and use it // here and in cmd/link. var hdr [60]byte if _, err := io.ReadFull(f, hdr[:]); err != nil { if err == io.EOF { // No more entries, no build ID. return "", nil } return "", err } off += 60 sizeStr := strings.TrimSpace(string(hdr[48:58])) size, err := strconv.ParseInt(sizeStr, 0, 64) if err != nil { return bad() } name := strings.TrimSpace(string(hdr[:16])) if name == "_buildid.o/" { sr := io.NewSectionReader(f, off, size) e, err := elf.NewFile(sr) if err != nil { return bad() } s := e.Section(".go.buildid") if s == nil { return bad() } data, err := s.Data() if err != nil { return bad() } return string(data), nil } off += size if off&1 != 0 { off++ } } } var ( goBuildPrefix = []byte("\xff Go build ID: \"") goBuildEnd = []byte("\"\n \xff") elfPrefix = []byte("\x7fELF") machoPrefixes = [][]byte{ {0xfe, 0xed, 0xfa, 0xce}, {0xfe, 0xed, 0xfa, 0xcf}, {0xce, 0xfa, 0xed, 0xfe}, {0xcf, 0xfa, 0xed, 0xfe}, } ) var readSize = 32 * 1024 // changed for testing // readBinary reads the build ID from a binary. // // ELF binaries store the build ID in a proper PT_NOTE section. // // Other binary formats are not so flexible. For those, the linker // stores the build ID as non-instruction bytes at the very beginning // of the text segment, which should appear near the beginning // of the file. This is clumsy but fairly portable. Custom locations // can be added for other binary types as needed, like we did for ELF. func readBinary(name string, f *os.File) (id string, err error) { // Read the first 32 kB of the binary file. // That should be enough to find the build ID. // In ELF files, the build ID is in the leading headers, // which are typically less than 4 kB, not to mention 32 kB. // In Mach-O files, there's no limit, so we have to parse the file. // On other systems, we're trying to read enough that // we get the beginning of the text segment in the read. // The offset where the text segment begins in a hello // world compiled for each different object format today: // // Plan 9: 0x20 // Windows: 0x600 // data := make([]byte, readSize) _, err = io.ReadFull(f, data) if err == io.ErrUnexpectedEOF { err = nil } if err != nil { return "", err } if bytes.HasPrefix(data, elfPrefix) { return readELF(name, f, data) } for _, m := range machoPrefixes { if bytes.HasPrefix(data, m) { return readMacho(name, f, data) } } return readRaw(name, data) } // readRaw finds the raw build ID stored in text segment data. func readRaw(name string, data []byte) (id string, err error) { i := bytes.Index(data, goBuildPrefix) if i < 0 { // Missing. Treat as successful but build ID empty. return "", nil } j := bytes.Index(data[i+len(goBuildPrefix):], goBuildEnd) if j < 0 { return "", &os.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed} } quoted := data[i+len(goBuildPrefix)-1 : i+len(goBuildPrefix)+j+1] id, err = strconv.Unquote(string(quoted)) if err != nil { return "", &os.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed} } return id, nil } golang-honnef-go-tools-2023.1.7/go/buildid/note.go000066400000000000000000000134211456614407100215570ustar00rootroot00000000000000// 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 buildid import ( "bytes" "debug/elf" "debug/macho" "encoding/binary" "fmt" "io" "os" ) func readAligned4(r io.Reader, sz int32) ([]byte, error) { full := (sz + 3) &^ 3 data := make([]byte, full) _, err := io.ReadFull(r, data) if err != nil { return nil, err } data = data[:sz] return data, nil } func ReadELFNote(filename, name string, typ int32) ([]byte, error) { f, err := elf.Open(filename) if err != nil { return nil, err } defer f.Close() for _, sect := range f.Sections { if sect.Type != elf.SHT_NOTE { continue } r := sect.Open() for { var namesize, descsize, noteType int32 err = binary.Read(r, f.ByteOrder, &namesize) if err != nil { if err == io.EOF { break } return nil, fmt.Errorf("read namesize failed: %v", err) } err = binary.Read(r, f.ByteOrder, &descsize) if err != nil { return nil, fmt.Errorf("read descsize failed: %v", err) } err = binary.Read(r, f.ByteOrder, ¬eType) if err != nil { return nil, fmt.Errorf("read type failed: %v", err) } noteName, err := readAligned4(r, namesize) if err != nil { return nil, fmt.Errorf("read name failed: %v", err) } desc, err := readAligned4(r, descsize) if err != nil { return nil, fmt.Errorf("read desc failed: %v", err) } if name == string(noteName) && typ == noteType { return desc, nil } } } return nil, nil } var elfGoNote = []byte("Go\x00\x00") var elfGNUNote = []byte("GNU\x00") // The Go build ID is stored in a note described by an ELF PT_NOTE prog // header. The caller has already opened filename, to get f, and read // at least 4 kB out, in data. func readELF(name string, f *os.File, data []byte) (buildid string, err error) { // Assume the note content is in the data, already read. // Rewrite the ELF header to set shnum to 0, so that we can pass // the data to elf.NewFile and it will decode the Prog list but not // try to read the section headers and the string table from disk. // That's a waste of I/O when all we care about is the Prog list // and the one ELF note. switch elf.Class(data[elf.EI_CLASS]) { case elf.ELFCLASS32: data[48] = 0 data[49] = 0 case elf.ELFCLASS64: data[60] = 0 data[61] = 0 } const elfGoBuildIDTag = 4 const gnuBuildIDTag = 3 ef, err := elf.NewFile(bytes.NewReader(data)) if err != nil { return "", &os.PathError{Path: name, Op: "parse", Err: err} } var gnu string for _, p := range ef.Progs { if p.Type != elf.PT_NOTE || p.Filesz < 16 { continue } var note []byte if p.Off+p.Filesz < uint64(len(data)) { note = data[p.Off : p.Off+p.Filesz] } else { // For some linkers, such as the Solaris linker, // the buildid may not be found in data (which // likely contains the first 16kB of the file) // or even the first few megabytes of the file // due to differences in note segment placement; // in that case, extract the note data manually. _, err = f.Seek(int64(p.Off), io.SeekStart) if err != nil { return "", err } note = make([]byte, p.Filesz) _, err = io.ReadFull(f, note) if err != nil { return "", err } } filesz := p.Filesz off := p.Off for filesz >= 16 { nameSize := ef.ByteOrder.Uint32(note) valSize := ef.ByteOrder.Uint32(note[4:]) tag := ef.ByteOrder.Uint32(note[8:]) nname := note[12:16] if nameSize == 4 && 16+valSize <= uint32(len(note)) && tag == elfGoBuildIDTag && bytes.Equal(nname, elfGoNote) { return string(note[16 : 16+valSize]), nil } if nameSize == 4 && 16+valSize <= uint32(len(note)) && tag == gnuBuildIDTag && bytes.Equal(nname, elfGNUNote) { gnu = string(note[16 : 16+valSize]) } nameSize = (nameSize + 3) &^ 3 valSize = (valSize + 3) &^ 3 notesz := uint64(12 + nameSize + valSize) if filesz <= notesz { break } off += notesz align := p.Align alignedOff := (off + align - 1) &^ (align - 1) notesz += alignedOff - off off = alignedOff filesz -= notesz note = note[notesz:] } } // If we didn't find a Go note, use a GNU note if available. // This is what gccgo uses. if gnu != "" { return gnu, nil } // No note. Treat as successful but build ID empty. return "", nil } // The Go build ID is stored at the beginning of the Mach-O __text segment. // The caller has already opened filename, to get f, and read a few kB out, in data. // Sadly, that's not guaranteed to hold the note, because there is an arbitrary amount // of other junk placed in the file ahead of the main text. func readMacho(name string, f *os.File, data []byte) (buildid string, err error) { // If the data we want has already been read, don't worry about Mach-O parsing. // This is both an optimization and a hedge against the Mach-O parsing failing // in the future due to, for example, the name of the __text section changing. if b, err := readRaw(name, data); b != "" && err == nil { return b, err } mf, err := macho.NewFile(f) if err != nil { return "", &os.PathError{Path: name, Op: "parse", Err: err} } sect := mf.Section("__text") if sect == nil { // Every binary has a __text section. Something is wrong. return "", &os.PathError{Path: name, Op: "parse", Err: fmt.Errorf("cannot find __text section")} } // It should be in the first few bytes, but read a lot just in case, // especially given our past problems on OS X with the build ID moving. // There shouldn't be much difference between reading 4kB and 32kB: // the hard part is getting to the data, not transferring it. n := sect.Size if n > uint64(readSize) { n = uint64(readSize) } buf := make([]byte, n) if _, err := f.ReadAt(buf, int64(sect.Offset)); err != nil { return "", err } return readRaw(name, buf) } golang-honnef-go-tools-2023.1.7/go/gcsizes/000077500000000000000000000000001456614407100203155ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/go/gcsizes/LICENSE000066400000000000000000000027071456614407100213300ustar00rootroot00000000000000Copyright (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. golang-honnef-go-tools-2023.1.7/go/gcsizes/sizes.go000066400000000000000000000053301456614407100220020ustar00rootroot00000000000000// 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 } golang-honnef-go-tools-2023.1.7/go/ir/000077500000000000000000000000001456614407100172605ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/go/ir/LICENSE000066400000000000000000000027771456614407100203020ustar00rootroot00000000000000Copyright (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. golang-honnef-go-tools-2023.1.7/go/ir/UPSTREAM000066400000000000000000000007131456614407100204440ustar00rootroot00000000000000This 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: e854e0228e2ef1cc6e42bbfde1951925096a1272 golang-honnef-go-tools-2023.1.7/go/ir/bench_test.go000066400000000000000000000014721456614407100217310ustar00rootroot00000000000000package 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() } } golang-honnef-go-tools-2023.1.7/go/ir/blockopt.go000066400000000000000000000120271456614407100214260ustar00rootroot00000000000000// 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() } golang-honnef-go-tools-2023.1.7/go/ir/builder.go000066400000000000000000002227141456614407100212450ustar00rootroot00000000000000// 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" "honnef.co/go/tools/analysis/lint" "honnef.co/go/tools/go/types/typeutil" "golang.org/x/exp/typeparams" ) var ( varOk = newVar("ok", tBool) varIndex = newVar("index", tInt) // Type constants. tBool = types.Typ[types.Bool] tInt = types.Typ[types.Int] tInvalid = types.Typ[types.Invalid] tString = types.Typ[types.String] tUntypedNil = types.Typ[types.UntypedNil] 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 := typeutil.CoreType(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": styp := typ.Underlying() if _, ok := typ.Underlying().(*types.Interface); ok { // This must be a type parameter with a core type. // Set styp to the core type and generate instructions based on it. assert(typeparams.IsTypeParam(typ)) styp = typeutil.CoreType(typ) assert(styp != nil) } switch styp.(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(styp.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) default: lint.ExhaustiveTypeSwitch(typ.Underlying()) } 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.) // // For example, for len(gen()), we need to evaluate gen() for its side-effects, but don't need the returned // value to determine the length of the array, which is constant. // // This never applies to type parameters. Even if the constraint has a structural type, len/cap on a type // parameter aren't constant. 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) (RET 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) index := sel.Index()[len(sel.Index())-1] vut := typeutil.CoreType(deref(v.Type())).Underlying().(*types.Struct) fld := vut.Field(index) // Due to the two phases of resolving AssignStmt, a panic from x.f = p() // when x is nil is required to come after the side-effects of // evaluating x and p(). emit := func(fn *Function) Value { return emitFieldSelection(fn, v, index, true, e.Sel) } return &lazyAddress{addr: emit, t: fld.Type(), expr: e.Sel} case *ast.IndexExpr: var x Value var et types.Type xt := fn.Pkg.typeOf(e.X) // Indexing doesn't need a core type, it only requires all types to be similar enough. For example, []int64 | // [5]int64 can be indexed. The element types do have to match though. terms, err := typeparams.NormalTerms(xt) if err != nil { panic(fmt.Sprintf("unexpected error: %s", err)) } isArrayLike := func() (types.Type, bool) { for _, term := range terms { arr, ok := term.Type().Underlying().(*types.Array) if ok { return arr.Elem(), true } } return nil, false } isSliceLike := func() (types.Type, bool) { for _, term := range terms { switch t := term.Type().Underlying().(type) { case *types.Slice: return t.Elem(), true case *types.Pointer: return t.Elem().Underlying().(*types.Array).Elem(), true } } return nil, false } if elem, ok := isArrayLike(); ok { // array x = b.addr(fn, e.X, escaping).address(fn) et = types.NewPointer(elem) } else if elem, ok := isSliceLike(); ok { // slice or *array x = b.expr(fn, e.X) et = types.NewPointer(elem) } else if t, ok := typeutil.CoreType(xt).Underlying().(*types.Map); ok { return &element{ m: b.expr(fn, e.X), k: emitConv(fn, b.expr(fn, e.Index), t.Key(), e.Index), t: t.Elem(), } } else { panic("unexpected container type in IndexExpr: " + t.String()) } // Due to the two phases of resolving AssignStmt, a panic from x[i] = p() // when x is nil or i is out-of-bounds is required to come after the // side-effects of evaluating x, i and p(). index := b.expr(fn, e.Index) emit := func(fn *Function) Value { v := &IndexAddr{ X: x, Index: index, } v.setType(et) return fn.emit(v, e) } return &lazyAddress{addr: emit, t: deref(et), 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 // if debugRef is set no other fields will be set debugRef *DebugRef } 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, nil}) } func (sb *storebuf) storeDebugRef(ref *DebugRef) { sb.stores = append(sb.stores, store{debugRef: ref}) } func (sb *storebuf) emit(fn *Function) { for _, s := range sb.stores { if s.debugRef == nil { s.lhs.store(fn, s.rhs, s.source) } else { fn.emit(s.debugRef, nil) } } } // 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()) { // Example input that hits this code: // // type S1 struct{ X int } // x := []*S1{ // {1}, // <-- & is implied // } // _ = x 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()) && !typeparams.IsTypeParam(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 typeutil.CoreType(loc.typ()).Underlying().(type) { case *types.Struct, *types.Array: if sb != nil { // Make sure we don't emit DebugRefs before the store has actually occurred if ref := makeDebugRef(fn, e, addr, true); ref != nil { sb.storeDebugRef(ref) } } else { 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 x Value if core := typeutil.CoreType(fn.Pkg.typeOf(e.X)); core != nil { switch core.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") } } else { // We're indexing a string | []byte. Note that other combinations such as []byte | [4]byte are currently not // allowed by the language. x = b.expr(fn, e.X) } var low, high, max Value if e.Low != nil { low = b.expr(fn, e.Low) } if e.High != nil { high = b.expr(fn, e.High) } 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) } if instance, ok := fn.Pkg.info.Instances[e]; ok { // Instantiated generic function return makeInstance(fn.Prog, v.(*Function), instance.Type.(*types.Signature), instance.TypeArgs) } 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: // IndexExpr might either be an actual indexing operation, or an instantiation xt := fn.Pkg.typeOf(e.X) terms, err := typeparams.NormalTerms(xt) if err != nil { panic(fmt.Sprintf("unexpected error: %s", err)) } isNonAddressableIndexable := func() (types.Type, bool) { for _, term := range terms { switch t := term.Type().Underlying().(type) { case *types.Array: return t.Elem(), true case *types.Basic: // a string return types.Universe.Lookup("byte").Type(), true } } return nil, false } isAddressableIndexable := func() (types.Type, bool) { for _, term := range terms { switch t := term.Type().Underlying().(type) { case *types.Slice: return t.Elem(), true case *types.Pointer: return t.Elem().Underlying().(*types.Array).Elem(), true } } return nil, false } if elem, ok := isNonAddressableIndexable(); ok { // At least one of the types is non-addressable v := &Index{ X: b.expr(fn, e.X), Index: b.expr(fn, e.Index), } v.setType(elem) return fn.emit(v, e) } else if _, ok := isAddressableIndexable(); ok { // All types are addressable (otherwise the previous branch would've fired) return b.addr(fn, e, false).load(fn, e) } else if t, ok := typeutil.CoreType(xt).Underlying().(*types.Map); ok { // Maps are not addressable. v := &MapLookup{ X: b.expr(fn, e.X), Index: emitConv(fn, b.expr(fn, e.Index), t.Key(), e.Index), } v.setType(t.Elem()) return fn.emit(v, e) } else if _, ok := xt.Underlying().(*types.Signature); ok { // Instantiating a generic function return b.expr(fn, e.X) } else { panic("unexpected container type in IndexExpr: " + t.String()) } case *ast.IndexListExpr: // Instantiating a generic function return b.expr(fn, e.X) 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. // Methods in interfaces cannot have their own type parameters, so we needn't do anything for type // parameters. c.Value = v c.Method = obj } else { // "Call"-mode call. // declaredFunc takes care of creating wrappers for functions with type parameters. 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. // // Code in expr takes care of creating wrappers for functions with type parameters. 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, _ := typeutil.CoreType(fn.Pkg.typeOf(e.Fun)).(*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) { loc.store(fn, emitArith(fn, op, loc.load(fn, source), val, 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. This is implicitly handled by the write buffering effected by // compositeElement and explicitly by the storebuf for when we don't use CompositeValue. // // 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 := typeutil.CoreType(typ).(type) { case *types.Struct: lvalue := &address{addr: addr, expr: e} if len(e.Elts) == 0 { if !isZero { sb.store(lvalue, zeroValue(fn, deref(addr.Type()), e), e) } } else { v := &CompositeValue{ Values: make([]Value, t.NumFields()), } for i := 0; i < t.NumFields(); i++ { v.Values[i] = emitConst(fn, zeroConst(t.Field(i).Type())) } v.setType(typ) 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 } } } ce := &compositeElement{ cv: v, idx: fieldIndex, t: t.Field(fieldIndex).Type(), expr: e, } b.assign(fn, ce, e, isZero, sb, e) v.Bitmap.SetBit(&v.Bitmap, fieldIndex, 1) v.NumSet++ } fn.emit(v, e) sb.store(lvalue, v, 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 } var final Value if len(e.Elts) == 0 { if !isZero { zc := emitConst(fn, zeroConst(at)) final = zc } } else { if at.Len() == int64(len(e.Elts)) { // The literal specifies all elements, so we can use a composite value v := &CompositeValue{ Values: make([]Value, at.Len()), } zc := emitConst(fn, zeroConst(at.Elem())) for i := range v.Values { v.Values[i] = zc } v.setType(at) 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)).(*Const) } iaddr := &compositeElement{ cv: v, idx: int(idx.Int64()), t: at.Elem(), expr: e, } b.assign(fn, iaddr, e, true, sb, e) v.Bitmap.SetBit(&v.Bitmap, int(idx.Int64()), 1) v.NumSet++ } final = v fn.emit(v, e) } else { // Not all elements are specified. Populate the array with a series of stores, to guard against literals // like []int{1<<62: 1}. if !isZero { // 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)).(*Const) } 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 if final != nil { sb.store(&address{addr: array}, final, e) } s := &Slice{X: array} s.setType(typ) sb.store(&address{addr: addr, expr: e}, fn.emit(s, e), e) } else if final != nil { sb.store(&address{addr: array, expr: e}, final, 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), typeutil.CoreType(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 := typeutil.CoreType(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) // We store in an Alloc and load it on each iteration so that lifting produces the necessary σ nodes xAlloc := newVariable(fn, x.Type(), source) xAlloc.store(x) // Determine number of iterations. // // We store the length in an Alloc and load it on each iteration so that lifting produces the necessary σ nodes length := newVariable(fn, tInt, source) if arr, ok := deref(x.Type()).Underlying().(*types.Array); ok && !typeparams.IsTypeParam(x.Type()) { // 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. // // This intentionally misses type parameters with core types, because their length isn't technically constant. length.store(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.store(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.load(), source), body, done, source) fn.currentBlock = body k = emitLoad(fn, index, source) if tv != nil { x := xAlloc.load() switch t := typeutil.CoreType(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(typeutil.NewIterator(types.NewTuple( varOk, newVar("k", tk), newVar("v", tv), ))) it := newVariable(fn, rng.typ, source) it.store(fn.emit(rng, source)) loop = fn.newBasicBlock("rangeiter.loop") emitJump(fn, loop, source) fn.currentBlock = loop // Go doesn't currently allow ranging over string|[]byte, so isString is decidable. _, isString := typeutil.CoreType(x.Type()).Underlying().(*types.Basic) okvInstr := &Next{ Iter: it.load(), IsString: isString, } okvInstr.setType(rng.typ.(*typeutil.Iterator).Elem()) fn.emit(okvInstr, source) okv := newVariable(fn, okvInstr.Type(), source) okv.store(okvInstr) body := fn.newBasicBlock("rangeiter.body") done = fn.newBasicBlock("rangeiter.done") emitIf(fn, emitExtract(fn, okv.load(), 0, source), body, done, source) fn.currentBlock = body if tk != tInvalid { k = emitExtract(fn, okv.load(), 1, source) } if tv != tInvalid { v = emitExtract(fn, okv.load(), 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 recv := emitRecv(fn, x, true, types.NewTuple(newVar("k", typeutil.CoreType(x.Type()).Underlying().(*types.Chan).Elem()), varOk), source) retv := newVariable(fn, recv.Type(), source) retv.store(recv) body := fn.newBasicBlock("rangechan.body") done = fn.newBasicBlock("rangechan.done") emitIf(fn, emitExtract(fn, retv.load(), 1, source), body, done, source) fn.currentBlock = body if tk != nil { k = emitExtract(fn, retv.load(), 0, source) } return } type variable struct { alloc *Alloc fn *Function source ast.Node } func newVariable(fn *Function, typ types.Type, source ast.Node) *variable { alloc := &Alloc{} alloc.setType(types.NewPointer(typ)) fn.emit(alloc, source) fn.Locals = append(fn.Locals, alloc) return &variable{ alloc: alloc, fn: fn, source: source, } } func (v *variable) store(sv Value) { emitStore(v.fn, v.alloc, sv, v.source) } func (v *variable) load() Value { return emitLoad(v.fn, v.alloc, v.source) } // 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 := typeutil.CoreType(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), typeutil.CoreType(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()))) } golang-honnef-go-tools-2023.1.7/go/ir/builder_go117_test.go000066400000000000000000000024521456614407100232150ustar00rootroot00000000000000// 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|parser.SkipObjectResolution) 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) } }) } } golang-honnef-go-tools-2023.1.7/go/ir/builder_test.go000066400000000000000000000311231456614407100222740ustar00rootroot00000000000000// 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, parser.SkipObjectResolution) 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 := 4; phis != expected { g.WriteTo(os.Stderr) t.Errorf("expected %d Phi nodes (for the range index, slice length and slice), got %d", expected, phis) } } golang-honnef-go-tools-2023.1.7/go/ir/const.go000066400000000000000000000160211456614407100207350ustar00rootroot00000000000000// 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" "strings" "golang.org/x/exp/typeparams" "honnef.co/go/tools/go/types/typeutil" ) // 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. func zeroConst(t types.Type) Constant { if _, ok := t.Underlying().(*types.Interface); ok && !typeparams.IsTypeParam(t) { // Handle non-generic interface early to simplify following code. return nilConst(t) } tset := typeutil.NewTypeSet(t) switch typ := tset.CoreType().(type) { case *types.Struct: values := make([]Value, typ.NumFields()) for i := 0; i < typ.NumFields(); i++ { values[i] = zeroConst(typ.Field(i).Type()) } return &AggregateConst{ register: register{typ: t}, Values: values, } case *types.Tuple: values := make([]Value, typ.Len()) for i := 0; i < typ.Len(); i++ { values[i] = zeroConst(typ.At(i).Type()) } return &AggregateConst{ register: register{typ: t}, Values: values, } } isNillable := func(term *types.Term) bool { switch typ := term.Type().Underlying().(type) { case *types.Pointer, *types.Slice, *types.Interface, *types.Chan, *types.Map, *types.Signature, *typeutil.Iterator: return true case *types.Basic: switch typ.Kind() { case types.UnsafePointer, types.UntypedNil: return true default: return false } default: return false } } isInfo := func(info types.BasicInfo) func(*types.Term) bool { return func(term *types.Term) bool { basic, ok := term.Type().Underlying().(*types.Basic) if !ok { return false } return (basic.Info() & info) != 0 } } isArray := func(term *types.Term) bool { _, ok := term.Type().Underlying().(*types.Array) return ok } switch { case tset.All(isInfo(types.IsNumeric)): return NewConst(constant.MakeInt64(0), t) case tset.All(isInfo(types.IsString)): return NewConst(constant.MakeString(""), t) case tset.All(isInfo(types.IsBoolean)): return NewConst(constant.MakeBool(false), t) case tset.All(isNillable): return nilConst(t) case tset.All(isArray): var k ArrayConst k.setType(t) return &k default: var k GenericConst k.setType(t) return &k } } 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 { if c.block == nil { // Constants don't have a block till late in the compilation process. But we want to print consts during // debugging. return c.RelString(nil) } return c.RelString(c.Parent().pkg()) } func (v *ArrayConst) RelString(pkg *types.Package) string { return fmt.Sprintf("ArrayConst <%s>", relType(v.Type(), pkg)) } func (v *ArrayConst) String() string { return v.RelString(v.Parent().pkg()) } func (v *AggregateConst) RelString(pkg *types.Package) string { values := make([]string, len(v.Values)) for i, v := range v.Values { if v != nil { values[i] = v.Name() } else { values[i] = "nil" } } return fmt.Sprintf("AggregateConst <%s> (%s)", relType(v.Type(), pkg), strings.Join(values, ", ")) } func (v *AggregateConst) String() string { if v.block == nil { return v.RelString(nil) } return v.RelString(v.Parent().pkg()) } func (v *GenericConst) RelString(pkg *types.Package) string { return fmt.Sprintf("GenericConst <%s>", relType(v.Type(), pkg)) } func (v *GenericConst) String() string { return v.RelString(v.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) } func (c *Const) equal(o Constant) bool { // TODO(dh): don't use == for types, this will miss identical pointer types, among others oc, ok := o.(*Const) if !ok { return false } return c.typ == oc.typ && c.Value == oc.Value } func (c *AggregateConst) equal(o Constant) bool { oc, ok := o.(*AggregateConst) if !ok { return false } // TODO(dh): don't use == for types, this will miss identical pointer types, among others if c.typ != oc.typ { return false } for i, v := range c.Values { if !v.(Constant).equal(oc.Values[i].(Constant)) { return false } } return true } func (c *ArrayConst) equal(o Constant) bool { oc, ok := o.(*ArrayConst) if !ok { return false } // TODO(dh): don't use == for types, this will miss identical pointer types, among others return c.typ == oc.typ } func (c *GenericConst) equal(o Constant) bool { oc, ok := o.(*GenericConst) if !ok { return false } // TODO(dh): don't use == for types, this will miss identical pointer types, among others return c.typ == oc.typ } golang-honnef-go-tools-2023.1.7/go/ir/create.go000066400000000000000000000164731456614407100210650ustar00rootroot00000000000000// 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] } golang-honnef-go-tools-2023.1.7/go/ir/doc.go000066400000000000000000000140341456614407100203560ustar00rootroot00000000000000// 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 ✔ ✔ // *SliceToArray ✔ ✔ // *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 golang-honnef-go-tools-2023.1.7/go/ir/dom.go000066400000000000000000000272211456614407100203720ustar00rootroot00000000000000// 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, "}") } golang-honnef-go-tools-2023.1.7/go/ir/emit.go000066400000000000000000000375141456614407100205570ustar00rootroot00000000000000// 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" "honnef.co/go/tools/go/types/typeutil" "golang.org/x/exp/typeparams" ) // 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) { ref := makeDebugRef(f, e, v, isAddr) if ref == nil { return } f.emit(ref, nil) } func makeDebugRef(f *Function, e ast.Expr, v Value, isAddr bool) *DebugRef { if !f.debugInfo() { return nil // 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 nil } obj = f.Pkg.objectOf(id) switch obj.(type) { case *types.Nil, *types.Const, *types.Builtin: return nil } } return &DebugRef{ X: v, Expr: e, IsAddr: isAddr, object: obj, } } // 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. // There is a runtime panic if y is signed and <0. Instead of inserting a check for y<0 // and converting to an unsigned value (like the compiler) leave y as is. if b, ok := y.Type().Underlying().(*types.Basic); ok && b.Info()&types.IsUntyped != 0 { // Untyped conversion: // Spec https://go.dev/ref/spec#Operators: // The right operand in a shift expression must have integer type or be an untyped constant // representable by a value of type uint. y = emitConv(f, y, types.Typ[types.Uint], 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 && !typeparams.IsTypeParam(x.Type()) { y = emitConv(f, y, x.Type(), source) } else if _, ok := yt.(*types.Interface); ok && !typeparams.IsTypeParam(y.Type()) { 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 types.IdenticalIgnoreTags(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, t_dst types.Type, source ast.Node) Value { t_src := val.Type() // Identical types? Conversion is a no-op. if types.Identical(t_src, t_dst) { return val } ut_dst := t_dst.Underlying() ut_src := t_src.Underlying() tset_dst := typeutil.NewTypeSet(ut_dst) tset_src := typeutil.NewTypeSet(ut_src) // Just a change of type, but not value or representation? if tset_src.All(func(termSrc *types.Term) bool { return tset_dst.All(func(termDst *types.Term) bool { return isValuePreserving(termSrc.Type().Underlying(), termDst.Type().Underlying()) }) }) { c := &ChangeType{X: val} c.setType(t_dst) return f.emit(c, source) } // Conversion to, or construction of a value of, an interface type? if _, ok := ut_dst.(*types.Interface); ok && !typeparams.IsTypeParam(t_dst) { // Assignment from one interface type to another? if _, ok := ut_src.(*types.Interface); ok && !typeparams.IsTypeParam(t_src) { c := &ChangeInterface{X: val} c.setType(t_dst) return f.emit(c, source) } // Untyped nil constant? Return interface-typed nil constant. if ut_src == tUntypedNil { return emitConst(f, nilConst(t_dst)) } // 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(t_dst) return f.emit(mi, source) } // Conversion of a compile-time constant value? Note that converting a constant to a type parameter never results in // a 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, t_dst)) } // We're converting from constant to non-constant type, // e.g. string -> []byte/[]rune. } // Conversion from slice to array pointer? if tset_src.All(func(termSrc *types.Term) bool { return tset_dst.All(func(termDst *types.Term) bool { if slice, ok := termSrc.Type().Underlying().(*types.Slice); ok { if ptr, ok := termDst.Type().Underlying().(*types.Pointer); ok { if arr, ok := ptr.Elem().Underlying().(*types.Array); ok && types.Identical(slice.Elem(), arr.Elem()) { return true } } } return false }) }) { c := &SliceToArrayPointer{X: val} c.setType(t_dst) return f.emit(c, source) } // Conversion from slice to array. This is almost the same as converting from slice to array pointer, then // dereferencing the pointer. Except that a nil slice can be converted to [0]T, whereas converting a nil slice to // (*[0]T) results in a nil pointer, dereferencing which would panic. To hide the extra branching we use a dedicated // instruction, SliceToArray. if tset_src.All(func(termSrc *types.Term) bool { return tset_dst.All(func(termDst *types.Term) bool { if slice, ok := termSrc.Type().Underlying().(*types.Slice); ok { if arr, ok := termDst.Type().Underlying().(*types.Array); ok && types.Identical(slice.Elem(), arr.Elem()) { return true } } return false }) }) { c := &SliceToArray{X: val} c.setType(t_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 := tset_src.Any(func(term *types.Term) bool { _, ok := term.Type().Underlying().(*types.Basic); return ok }) ok2 := tset_dst.Any(func(term *types.Term) bool { _, ok := term.Type().Underlying().(*types.Basic); return ok }) if ok1 || ok2 { c := &Convert{X: val} c.setType(t_dst) return f.emit(c, source) } panic(fmt.Sprintf("in %s: cannot convert %s (%s) to %s", f, val, val.Type(), t_dst)) } // 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), } 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 { // We may have a generic type containing a pointer, or a pointer to a generic type containing a struct. A // pointer to a generic containing a pointer to a struct shouldn't be possible because the outer pointer gets // dereferenced implicitly before we get here. fld := typeutil.CoreType(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 { // We may have a generic type containing a pointer, or a pointer to a generic type containing a struct. A // pointer to a generic containing a pointer to a struct shouldn't be possible because the outer pointer gets // dereferenced implicitly before we get here. vut := typeutil.CoreType(deref(v.Type())).Underlying().(*types.Struct) fld := vut.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 { return emitConst(f, zeroConst(t)) } type constKey struct { typ types.Type value constant.Value } func emitConst(f *Function, c Constant) Constant { if f.consts == nil { f.consts = map[constKey]constValue{} } typ := c.Type() var val constant.Value switch c := c.(type) { case *Const: val = c.Value case *ArrayConst, *GenericConst: // These can only represent zero values, so all we need is the type case *AggregateConst: candidates, _ := f.aggregateConsts.At(c.typ) for _, candidate := range candidates { if c.equal(candidate) { return candidate } } for i := range c.Values { c.Values[i] = emitConst(f, c.Values[i].(Constant)) } c.setBlock(f.Blocks[0]) rands := c.Operands(nil) updateOperandsReferrers(c, rands) candidates = append(candidates, c) f.aggregateConsts.Set(c.typ, candidates) return c default: panic(fmt.Sprintf("unexpected type %T", c)) } k := constKey{ typ: typ, value: val, } dup, ok := f.consts[k] if ok { return dup.c } else { c.setBlock(f.Blocks[0]) f.consts[k] = constValue{ c: c, idx: len(f.consts), } rands := c.Operands(nil) updateOperandsReferrers(c, rands) return c } } golang-honnef-go-tools-2023.1.7/go/ir/example_test.go000066400000000000000000000122531456614407100223040ustar00rootroot00000000000000// 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 ( "bytes" "fmt" "go/ast" "go/importer" "go/parser" "go/token" "go/types" "log" "os" "strings" "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|parser.SkipObjectResolution) 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. // Replace interface{} with any so the tests work for Go 1.17 and Go 1.18. { var buf bytes.Buffer ir.WriteFunction(&buf, hello.Func("init")) fmt.Print(strings.ReplaceAll(buf.String(), "interface{}", "any")) } { var buf bytes.Buffer ir.WriteFunction(&buf, hello.Func("main")) fmt.Print(strings.ReplaceAll(buf.String(), "interface{}", "any")) } // 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]any> // t4 = IndexAddr <*any> t3 t2 // t5 = MakeInterface t1 // Store {any} t4 t5 // t7 = Slice <[]any> 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.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles | packages.NeedImports | packages.NeedTypes | packages.NeedTypesSizes | packages.NeedSyntax | packages.NeedTypesInfo} 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.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles | packages.NeedImports | packages.NeedTypes | packages.NeedTypesSizes | packages.NeedSyntax | packages.NeedTypesInfo | packages.NeedDeps} 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() } golang-honnef-go-tools-2023.1.7/go/ir/exits.go000066400000000000000000000243641456614407100207540ustar00rootroot00000000000000package 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 } case "k8s.io/klog/v2": switch obj.(*types.Func).FullName() { case "k8s.io/klog/v2.Exit", "k8s.io/klog/v2.ExitDepth", "k8s.io/klog/v2.Exitf", "k8s.io/klog/v2.Exitln", "k8s.io/klog/v2.Fatal", "k8s.io/klog/v2.FatalDepth", "k8s.io/klog/v2.Fatalf", "k8s.io/klog/v2.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.NewSignatureType(nil, nil, 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 } } } } } golang-honnef-go-tools-2023.1.7/go/ir/func.go000066400000000000000000000612471456614407100205540ustar00rootroot00000000000000// 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/format" "go/token" "go/types" "io" "os" "sort" "strings" "honnef.co/go/tools/go/types/typeutil" ) // 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 { obj := f.Pkg.info.ObjectOf(label) if obj == nil { // Blank label, as in '_:' - don't store to f.lblocks, this label can never be referred to; just return a fresh // lbock. return &lblock{_goto: f.newBasicBlock(label.Name)} } lb := f.lblocks[obj] if lb == nil { lb = &lblock{_goto: f.newBasicBlock(label.Name)} if f.lblocks == nil { f.lblocks = make(map[types.Object]*lblock) } f.lblocks[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) } } } func updateOperandsReferrers(instr Instruction, ops []*Value) { for _, op := range ops { if r := *op; r != nil { if refs := (*op).Referrers(); refs != nil { if len(*refs) == 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 *refs = make([]Instruction, 1, 2) (*refs)[0] = instr } else { *refs = append(*refs, instr) } } } } } // 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 updateOperandsReferrers(instr, rands) } } for _, c := range f.consts { rands = c.c.Operands(rands[:0]) updateOperandsReferrers(c.c, rands) } } func (f *Function) emitConsts() { defer func() { f.consts = nil f.aggregateConsts = typeutil.Map[[]*AggregateConst]{} }() if len(f.Blocks) == 0 { return } // TODO(dh): our deduplication only works on booleans and // integers. other constants are represented as pointers to // things. head := make([]constValue, 0, len(f.consts)) for _, c := range f.consts { if len(*c.c.Referrers()) == 0 { // TODO(dh): killing a const may make other consts dead, too killInstruction(c.c) } else { head = append(head, c) } } sort.Slice(head, func(i, j int) bool { return head[i].idx < head[j].idx }) entry := f.Blocks[0] instrs := make([]Instruction, 0, len(entry.Instrs)+len(head)) for _, c := range head { instrs = append(instrs, c.c) } f.aggregateConsts.Iterate(func(key types.Type, value []*AggregateConst) { for _, c := range value { instrs = append(instrs, c) } }) instrs = append(instrs, entry.Instrs...) entry.Instrs = instrs } // 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 { for lift(f) { } if doSimplifyConstantCompositeValues { for simplifyConstantCompositeValues(f) { } } } // emit constants after lifting, because lifting may produce new constants, but before other variable splitting, // because it expects constants to have been deduplicated. f.emitConsts() if f.Prog.mode&SplitAfterNewInformation != 0 { splitOnNewInformation(f.Blocks[0], &StackMap{}) } 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()) } if instr != nil && instr.Comment() != "" { buf.WriteString(" # ") buf.WriteString(instr.Comment()) } 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, "") } } func killInstruction(instr Instruction) { ops := instr.Operands(nil) for _, op := range ops { if refs := (*op).Referrers(); refs != nil { *refs = removeInstr(*refs, instr) } } } golang-honnef-go-tools-2023.1.7/go/ir/html.go000066400000000000000000000670771456614407100205740ustar00rootroot00000000000000// 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 } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-honnef-go-tools-2023.1.7/go/ir/irutil/load_test.go�������������������������������������������0000664�0000000�0000000�00000006706�14566144071�0023106�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, parser.SkipObjectResolution) 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.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles | packages.NeedImports | packages.NeedTypes | packages.NeedTypesSizes | packages.NeedSyntax | packages.NeedTypesInfo} 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 = Const {0} t2 = Const {0} t3 = Parameter <[]byte> {buf} t4 = HeapAlloc <*Buffer> t5 = CompositeValue [100] t3 t1 t2 Store {bytes.Buffer} t4 t5 Jump → b1 b1: ← b0 # exit Return t4 `[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"`, parser.SkipObjectResolution) 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.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles | packages.NeedImports | packages.NeedTypes | packages.NeedTypesSizes | packages.NeedSyntax | packages.NeedTypesInfo} pkgs, err := packages.Load(cfg, "runtime") if err != nil { t.Fatal(err) } prog, _ := irutil.Packages(pkgs, 0, nil) prog.Build() // no crash } ����������������������������������������������������������golang-honnef-go-tools-2023.1.7/go/ir/irutil/loops.go�����������������������������������������������0000664�0000000�0000000�00000002024�14566144071�0022251�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 } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-honnef-go-tools-2023.1.7/go/ir/irutil/stub.go������������������������������������������������0000664�0000000�0000000�00000001515�14566144071�0022076�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 } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-honnef-go-tools-2023.1.7/go/ir/irutil/switch.go����������������������������������������������0000664�0000000�0000000�00000016423�14566144071�0022426�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 } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-honnef-go-tools-2023.1.7/go/ir/irutil/switch_test.go�����������������������������������������0000664�0000000�0000000�00000005406�14566144071�0023464�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. //go:build !android // +build !android package irutil import ( "bytes" "fmt" "go/parser" "path/filepath" "strings" "testing" "honnef.co/go/tools/go/ir" "golang.org/x/tools/go/analysis/analysistest" //lint:ignore SA1019 go/loader is deprecated, but works fine for our tests "golang.org/x/tools/go/loader" ) func TestSwitches(t *testing.T) { conf := loader.Config{ParserMode: parser.ParseComments} f, err := conf.ParseFile(filepath.Join(analysistest.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() } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-honnef-go-tools-2023.1.7/go/ir/irutil/terminates.go������������������������������������������0000664�0000000�0000000�00000003014�14566144071�0023270�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 } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-honnef-go-tools-2023.1.7/go/ir/irutil/testdata/����������������������������������������������0000775�0000000�0000000�00000000000�14566144071�0022401�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-honnef-go-tools-2023.1.7/go/ir/irutil/testdata/switches.go�����������������������������������0000664�0000000�0000000�00000010766�14566144071�0024573�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") } } ����������golang-honnef-go-tools-2023.1.7/go/ir/irutil/util.go������������������������������������������������0000664�0000000�0000000�00000006210�14566144071�0022073�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 alloc, ok := ir.Unwrap(x.X).(*ir.Alloc) if !ok { return nil, false } var checkAlloc func(alloc ir.Value) bool checkAlloc = func(alloc ir.Value) bool { for _, ref := range *alloc.Referrers() { if ref == x { continue } if ref.Block() != x.Block() { return false } switch ref := ref.(type) { case *ir.IndexAddr: idx := ref if len(*idx.Referrers()) != 1 { return false } store, ok := (*idx.Referrers())[0].(*ir.Store) if !ok { return false } out = append(out, store.Val) case *ir.Copy: if !checkAlloc(ref) { return false } default: return false } } return true } if !checkAlloc(alloc) { return nil, false } 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 } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-honnef-go-tools-2023.1.7/go/ir/irutil/visit.go�����������������������������������������������0000664�0000000�0000000�00000003763�14566144071�0022266�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 } �������������golang-honnef-go-tools-2023.1.7/go/ir/lift.go�������������������������������������������������������0000664�0000000�0000000�00000133744�14566144071�0020561�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 ( "encoding/binary" "fmt" "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 BlockMap[[]*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 BlockMap[[]*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 } } func numberNodesPerBlock(f *Function) { for _, b := range f.Blocks { var base ID for _, instr := range b.Instrs { if instr == nil { continue } instr.setID(base) base++ } } } // 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) bool { // 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 BlockMap[[]newPhi] var newSigmas BlockMap[[]newSigma] // 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 instructions := make(BlockMap[liftInstructions], len(fn.Blocks)) for i := range instructions { instructions[i].insertInstructions = map[Instruction][]Instruction{} } // Number nodes, for liftable numberNodesPerBlock(fn) 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, instructions) { instr.index = -1 continue } if numAllocs == 0 { df = buildDomFrontier(fn) rdf = buildPostDomFrontier(fn) if len(fn.Blocks) > 2 { closure = transitiveClosure(fn) } newPhis = make(BlockMap[[]newPhi], len(fn.Blocks)) newSigmas = make(BlockMap[[]newSigma], 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) } } } } instr.index = numAllocs numAllocs++ case *Defer: usesDefer = true case *RunDefers: b.rundefers++ } } } if numAllocs > 0 { for _, b := range fn.Blocks { work := instructions[b.Index] for _, rename := range work.renameAllocs { for _, instr_ := range b.Instrs[rename.startingAt:] { replace(instr_, rename.from, rename.to) } } } for _, b := range fn.Blocks { work := instructions[b.Index] if len(work.insertInstructions) != 0 { newInstrs := make([]Instruction, 0, len(fn.Blocks)+len(work.insertInstructions)*3) for _, instr := range b.Instrs { if add, ok := work.insertInstructions[instr]; ok { newInstrs = append(newInstrs, add...) } newInstrs = append(newInstrs, instr) } b.Instrs = newInstrs } } // TODO(dh): remove inserted allocs that end up unused after lifting. for _, b := range fn.Blocks { for _, instr := range b.Instrs { if instr, ok := instr.(*Alloc); ok && instr.index >= 0 { liftAlloc(closure, df, rdf, instr, newPhis, newSigmas) } } } // 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) simplifyPhisAndSigmas(newPhis, newSigmas) // 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] return numAllocs > 0 } 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 BlockMap[[]newPhi], newSigmas BlockMap[[]newSigma]) { // Phis and sigmas may become dead due to optimization passes. We may also insert more nodes than strictly // necessary, e.g. sigma nodes for constants, which will never be used. // 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) } } } // simplifyPhisAndSigmas removes duplicate phi and sigma nodes, // and 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 simplifyPhisAndSigmas(newPhis BlockMap[[]newPhi], newSigmas BlockMap[[]newSigma]) { // temporary numbering of values used in phis so that we can build map keys var id ID for _, npList := range newPhis { for _, np := range npList { for _, edge := range np.phi.Edges { edge.setID(id) id++ } } } // 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 simplifyPhisAndSigmas 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 } } } // Replace duplicate sigma nodes with a single node. These nodes exist when multiple allocs get replaced with the // same dominating store. for _, sigmaList := range newSigmas { primarySigmas := map[struct { succ int v Value }]*Sigma{} for _, sigmas := range sigmaList { for succ, sigma := range sigmas.sigmas { if sigma == nil { continue } if sigma.live { // we're reusing 'live' to mean 'dead' in the context of simplifyPhisAndSigmas continue } key := struct { succ int v Value }{succ, sigma.X} if alt, ok := primarySigmas[key]; ok { replaceAll(sigma, alt) sigma.live = true changed = true } else { primarySigmas[key] = sigma } } } } // Replace duplicate phi nodes with a single node. As far as we know, these duplicate nodes only ever exist // because of the previous sigma deduplication. keyb := make([]byte, 0, 4*8) for _, npList := range newPhis { primaryPhis := map[string]*Phi{} for _, np := range npList { if np.phi.live { continue } if n := len(np.phi.Edges) * 8; cap(keyb) >= n { keyb = keyb[:n] } else { keyb = make([]byte, n, n*2) } for i, e := range np.phi.Edges { binary.LittleEndian.PutUint64(keyb[i*8:i*8+8], uint64(e.ID())) } if alt, ok := primaryPhis[string(keyb)]; ok { replaceAll(np.phi, alt) np.phi.live = true changed = true } else { primaryPhis[string(keyb)] = np.phi } } } } for _, npList := range newPhis { for _, np := range npList { np.phi.live = false for _, edge := range np.phi.Edges { edge.setID(0) } } } for _, sigmaList := range newSigmas { for _, sigmas := range sigmaList { for _, sigma := range sigmas.sigmas { if sigma != nil { sigma.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 BlockMap[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) { // TODO(dh): the 'current' argument seems to be unused // TODO(dh): there's no reason for this to be a method 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(BlockMap[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<= desc.firstUnliftable { continue } hasLiftable := false switch instr := instr.(type) { case *Store: if instr.Val != alloc { desc.hasLiftableOther = true hasLiftable = true } case *Load: desc.hasLiftableLoad = true hasLiftable = true case *DebugRef: desc.hasLiftableOther = true } if hasLiftable { if int(instr.ID()) > desc.lastLiftable { desc.lastLiftable = int(instr.ID()) } } } for i := range blocks { // Update firstUnliftable to be one after lastLiftable. We do this to include the unliftable's preceding // DebugRefs in the renaming. if blocks[i].lastLiftable == -1 && !blocks[i].storeInPreds { // There are no liftable instructions (for this alloc) in this block. Set firstUnliftable to the // first non-head instruction to avoid inserting the store before phi instructions, which would // fail validation. first := -1 instrLoop: for i, instr := range fn.Blocks[i].Instrs { switch instr.(type) { case *Phi, *Sigma: default: first = i break instrLoop } } blocks[i].firstUnliftable = first } else { blocks[i].firstUnliftable = blocks[i].lastLiftable + 1 } } // If a block is reachable by a (partially) unliftable block, then the entirety of the block is unliftable. In that // case, stores have to be inserted in the predecessors. // // TODO(dh): this isn't always necessary. If the block is reachable by itself, i.e. part of a loop, then if the // Alloc instruction is itself part of that loop, then there is a subset of instructions in the loop that can be // lifted. For example: // // for { // x := 42 // println(x) // escape(&x) // } // // The x that escapes in one iteration of the loop isn't the same x that we read from on the next iteration. seen := make(BlockMap[bool], len(fn.Blocks)) var dfs func(b *BasicBlock) dfs = func(b *BasicBlock) { if seen[b.Index] { return } seen[b.Index] = true desc := &blocks[b.Index] desc.hasLiftableLoad = false desc.hasLiftableOther = false desc.isUnliftable = true desc.firstUnliftable = 0 desc.storeInPreds = true for _, succ := range b.Succs { dfs(succ) } } for _, b := range fn.Blocks { if blocks[b.Index].isUnliftable { for _, succ := range b.Succs { dfs(succ) } } } hasLiftableLoad := false hasLiftableOther := false hasUnliftable := false for _, b := range fn.Blocks { desc := blocks[b.Index] hasLiftableLoad = hasLiftableLoad || desc.hasLiftableLoad hasLiftableOther = hasLiftableOther || desc.hasLiftableOther if desc.isUnliftable { hasUnliftable = true } } if !hasLiftableLoad && !hasLiftableOther { // There are no liftable uses return false } else if !hasUnliftable { // The alloc is entirely liftable without splitting return true } else if !hasLiftableLoad { // The alloc is not entirely liftable, and the only liftable uses are stores. While some of those stores could // get lifted away, it would also lead to an infinite loop when lifting to a fixpoint, because the newly created // allocs also get stored into repeatable and that's their only liftable uses. return false } // We need to insert stores for the new alloc. If a (partially) unliftable block has no unliftable // predecessors and the use isn't in a phi node, then the store can be inserted right before the unliftable use. // Otherwise, stores have to be inserted at the end of all liftable predecessors. newAlloc := &Alloc{Heap: true} newAlloc.setBlock(alloc.block) newAlloc.setType(alloc.typ) newAlloc.setSource(alloc.source) newAlloc.index = -1 newAlloc.comment = "split alloc" { work := instructions[alloc.block.Index] work.insertInstructions[alloc] = append(work.insertInstructions[alloc], newAlloc) } predHasStore := make(BlockMap[bool], len(fn.Blocks)) for _, b := range fn.Blocks { desc := &blocks[b.Index] bWork := &instructions[b.Index] if desc.isUnliftable { bWork.renameAllocs = append(bWork.renameAllocs, struct { from *Alloc to *Alloc startingAt int }{ alloc, newAlloc, int(desc.firstUnliftable), }) } if !desc.isUnliftable { continue } propagate := func(in *BasicBlock, before Instruction) { load := &Load{ X: alloc, } store := &Store{ Addr: newAlloc, Val: load, } load.setType(deref(alloc.typ)) load.setBlock(in) load.comment = "split alloc" store.setBlock(in) updateOperandReferrers(load) updateOperandReferrers(store) store.comment = "split alloc" entry := &instructions[in.Index] entry.insertInstructions[before] = append(entry.insertInstructions[before], load, store) } if desc.storeInPreds { // emit stores at the end of liftable preds for _, pred := range b.Preds { if blocks[pred.Index].isUnliftable { continue } if !alloc.block.Dominates(pred) { // Consider this cfg: // // 1 // /| // / | // ↙ ↓ // 2--→3 // // with an Alloc in block 2. It doesn't make sense to insert a store in block 1 for the jump to // block 3, because 1 can never see the Alloc in the first place. // // Ignoring phi nodes, an Alloc always dominates all of its uses, and phi nodes don't matter here, // because for the incoming edges that do matter, we do emit the stores. continue } if predHasStore[pred.Index] { // Don't generate redundant propagations. Not only is it unnecessary, it can lead to infinite loops // when trying to lift to a fix point, because redundant stores are liftable. continue } predHasStore[pred.Index] = true before := pred.Instrs[len(pred.Instrs)-1] propagate(pred, before) } } else { // emit store before the first unliftable use before := b.Instrs[desc.firstUnliftable] propagate(b, before) } } return true } // liftAlloc lifts alloc into registers and populates newPhis and newSigmas with all the φ- and σ-nodes it may require. func liftAlloc(closure *closure, df domFrontier, rdf postDomFrontier, alloc *Alloc, newPhis BlockMap[[]newPhi], newSigmas BlockMap[[]newSigma]) { fn := alloc.Parent() defblocks := fn.blockset(0) useblocks := fn.blockset(1) Aphi := fn.blockset(2) Asigma := fn.blockset(3) W := fn.blockset(4) // Compute defblocks, the set of blocks containing a // definition of the alloc cell. for _, instr := range *alloc.Referrers() { switch instr := instr.(type) { case *Store: defblocks.Add(instr.Block()) case *Load: useblocks.Add(instr.Block()) for _, ref := range *instr.Referrers() { useblocks.Add(ref.Block()) } } } // The Alloc itself counts as a (zero) definition of the cell. defblocks.Add(alloc.Block()) if debugLifting { fmt.Fprintln(os.Stderr, "\tlifting ", alloc, alloc.Name()) } // Φ-insertion. // // What follows is the body of the main loop of the insert-φ // function described by Cytron et al, but instead of using // counter tricks, we just reset the 'hasAlready' and 'work' // sets each iteration. These are bitmaps so it's pretty cheap. // Initialize W and work to defblocks. for change := true; change; { change = false { // Traverse iterated dominance frontier, inserting φ-nodes. W.Set(defblocks) for i := W.Take(); i != -1; i = W.Take() { n := fn.Blocks[i] for _, y := range df[n.Index] { if Aphi.Add(y) { if len(*alloc.Referrers()) == 0 { continue } live := false if closure == nil { live = true } else { for _, ref := range *alloc.Referrers() { if _, ok := ref.(*Load); ok { if closure.has(y, ref.Block()) { live = true break } } } } if !live { continue } // Create φ-node. // It will be prepended to v.Instrs later, if needed. phi := &Phi{ Edges: make([]Value, len(y.Preds)), } phi.source = alloc.source phi.setType(deref(alloc.Type())) phi.block = y if debugLifting { fmt.Fprintf(os.Stderr, "\tplace %s = %s at block %s\n", phi.Name(), phi, y) } newPhis[y.Index] = append(newPhis[y.Index], newPhi{phi, alloc}) for _, p := range y.Preds { useblocks.Add(p) } change = true if defblocks.Add(y) { W.Add(y) } } } } } { W.Set(useblocks) for i := W.Take(); i != -1; i = W.Take() { n := fn.Blocks[i] for _, y := range rdf[n.Index] { if Asigma.Add(y) { sigmas := make([]*Sigma, 0, len(y.Succs)) anyLive := false for _, succ := range y.Succs { live := false for _, ref := range *alloc.Referrers() { if closure == nil || closure.has(succ, ref.Block()) { live = true anyLive = true break } } if live { sigma := &Sigma{ From: y, X: alloc, } sigma.source = alloc.source sigma.setType(deref(alloc.Type())) sigma.block = succ sigmas = append(sigmas, sigma) } else { sigmas = append(sigmas, nil) } } if anyLive { newSigmas[y.Index] = append(newSigmas[y.Index], newSigma{alloc, sigmas}) for _, s := range y.Succs { defblocks.Add(s) } change = true if useblocks.Add(y) { W.Add(y) } } } } } } } } // replaceAll replaces all intraprocedural uses of x with y, // updating x.Referrers and y.Referrers. // Precondition: x.Referrers() != nil, i.e. x must be local to some function. func replaceAll(x, y Value) { var rands []*Value pxrefs := x.Referrers() pyrefs := y.Referrers() for _, instr := range *pxrefs { switch instr := instr.(type) { case *CompositeValue: // Special case CompositeValue because it might have very large lists of operands // // OPT(dh): this loop is still expensive for large composite values for i, rand := range instr.Values { if rand == x { instr.Values[i] = y } } default: rands = instr.Operands(rands[:0]) // recycle storage for _, rand := range rands { if *rand != nil { if *rand == x { *rand = y } } } } if pyrefs != nil { *pyrefs = append(*pyrefs, instr) // dups ok } } *pxrefs = nil // x is now unreferenced } func replace(instr Instruction, x, y Value) { args := instr.Operands(nil) matched := false for _, arg := range args { if *arg == x { *arg = y matched = true } } if matched { yrefs := y.Referrers() if yrefs != nil { *yrefs = append(*yrefs, instr) } xrefs := x.Referrers() if xrefs != nil { *xrefs = removeInstr(*xrefs, instr) } } } // renamed returns the value to which alloc is being renamed, // constructing it lazily if it's the implicit zero initialization. func renamed(fn *Function, renaming []Value, alloc *Alloc) Value { v := renaming[alloc.index] if v == nil { v = emitConst(fn, zeroConst(deref(alloc.Type()))) renaming[alloc.index] = v } return v } func copyValue(v Value, why Instruction, info CopyInfo) *Copy { c := &Copy{ X: v, Why: why, Info: info, } if refs := v.Referrers(); refs != nil { *refs = append(*refs, c) } c.setType(v.Type()) c.setSource(v.Source()) return c } func splitOnNewInformation(u *BasicBlock, renaming *StackMap) { renaming.Push() defer renaming.Pop() rename := func(v Value, why Instruction, info CopyInfo, i int) { c := copyValue(v, why, info) c.setBlock(u) renaming.Set(v, c) u.Instrs = append(u.Instrs, nil) copy(u.Instrs[i+2:], u.Instrs[i+1:]) u.Instrs[i+1] = c } replacement := func(v Value) (Value, bool) { r, ok := renaming.Get(v) if !ok { return nil, false } for { rr, ok := renaming.Get(r) if !ok { // Store replacement in the map so that future calls to replacement(v) don't have to go through the // iterative process again. renaming.Set(v, r) return r, true } r = rr } } var hasInfo func(v Value, info CopyInfo) bool hasInfo = func(v Value, info CopyInfo) bool { switch v := v.(type) { case *Copy: return (v.Info&info) == info || hasInfo(v.X, info) case *FieldAddr, *IndexAddr, *TypeAssert, *MakeChan, *MakeMap, *MakeSlice, *Alloc: return info == CopyInfoNotNil case Member, *Builtin: return info == CopyInfoNotNil case *Sigma: return hasInfo(v.X, info) default: return false } } var args []*Value for i := 0; i < len(u.Instrs); i++ { instr := u.Instrs[i] if instr == nil { continue } args = instr.Operands(args[:0]) for _, arg := range args { if *arg == nil { continue } if r, ok := replacement(*arg); ok { *arg = r replace(instr, *arg, r) } } // TODO write some bits on why we copy values instead of encoding the actual control flow and panics switch instr := instr.(type) { case *IndexAddr: // Note that we rename instr.Index and instr.X even if they're already copies, because unique combinations // of X and Index may lead to unique information. // OPT we should rename both variables at once and avoid one memmove rename(instr.Index, instr, CopyInfoNotNegative, i) rename(instr.X, instr, CopyInfoNotNil, i) i += 2 // skip over instructions we just inserted case *FieldAddr: if !hasInfo(instr.X, CopyInfoNotNil) { rename(instr.X, instr, CopyInfoNotNil, i) i++ } case *TypeAssert: // If we've already type asserted instr.X without comma-ok before, then it can only contain a single type, // and successive type assertions, no matter the type, don't tell us anything new. if !hasInfo(instr.X, CopyInfoNotNil|CopyInfoSingleConcreteType) { rename(instr.X, instr, CopyInfoNotNil|CopyInfoSingleConcreteType, i) i++ // skip over instruction we just inserted } case *Load: if !hasInfo(instr.X, CopyInfoNotNil) { rename(instr.X, instr, CopyInfoNotNil, i) i++ } case *Store: if !hasInfo(instr.Addr, CopyInfoNotNil) { rename(instr.Addr, instr, CopyInfoNotNil, i) i++ } case *MapUpdate: if !hasInfo(instr.Map, CopyInfoNotNil) { rename(instr.Map, instr, CopyInfoNotNil, i) i++ } case CallInstruction: off := 0 if !instr.Common().IsInvoke() && !hasInfo(instr.Common().Value, CopyInfoNotNil) { rename(instr.Common().Value, instr, CopyInfoNotNil, i) off++ } if f, ok := instr.Common().Value.(*Builtin); ok { switch f.name { case "close": arg := instr.Common().Args[0] if !hasInfo(arg, CopyInfoNotNil|CopyInfoClosed) { rename(arg, instr, CopyInfoNotNil|CopyInfoClosed, i) off++ } } } i += off case *SliceToArrayPointer: // A slice to array pointer conversion tells us the minimum length of the slice rename(instr.X, instr, CopyInfoUnspecified, i) i++ case *SliceToArray: // A slice to array conversion tells us the minimum length of the slice rename(instr.X, instr, CopyInfoUnspecified, i) i++ case *Slice: // Slicing tells us about some of the bounds off := 0 if instr.Low == nil && instr.High == nil && instr.Max == nil { // If all indices are unspecified, then we can only learn something about instr.X if it might've been // nil. if !hasInfo(instr.X, CopyInfoNotNil) { rename(instr.X, instr, CopyInfoUnspecified, i) off++ } } else { rename(instr.X, instr, CopyInfoUnspecified, i) off++ } // We copy the indices even if we already know they are not negative, because we can associate numeric // ranges with them. if instr.Low != nil { rename(instr.Low, instr, CopyInfoNotNegative, i) off++ } if instr.High != nil { rename(instr.High, instr, CopyInfoNotNegative, i) off++ } if instr.Max != nil { rename(instr.Max, instr, CopyInfoNotNegative, i) off++ } i += off case *StringLookup: rename(instr.X, instr, CopyInfoUnspecified, i) rename(instr.Index, instr, CopyInfoNotNegative, i) i += 2 case *Recv: if !hasInfo(instr.Chan, CopyInfoNotNil) { // Receiving from a nil channel never completes rename(instr.Chan, instr, CopyInfoNotNil, i) i++ } case *Send: if !hasInfo(instr.Chan, CopyInfoNotNil) { // Sending to a nil channel never completes. Sending to a closed channel panics, but whether a channel // is closed isn't local to this function, so we didn't learn anything. rename(instr.Chan, instr, CopyInfoNotNil, i) i++ } } } for _, v := range u.dom.children { splitOnNewInformation(v, renaming) } } // rename implements the Cytron et al-based SSI renaming algorithm, a // preorder traversal of the dominator tree replacing all loads of // Alloc cells with the value stored to that cell by the dominating // store instruction. // // renaming is a map from *Alloc (keyed by index number) to its // dominating stored value; newPhis[x] is the set of new φ-nodes to be // prepended to block x. func rename(u *BasicBlock, renaming []Value, newPhis BlockMap[[]newPhi], newSigmas BlockMap[[]newSigma]) { // Each φ-node becomes the new name for its associated Alloc. for _, np := range newPhis[u.Index] { phi := np.phi alloc := np.alloc renaming[alloc.index] = phi } // Rename loads and stores of allocs. for i, instr := range u.Instrs { switch instr := instr.(type) { case *Alloc: if instr.index >= 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. // // Because we only rename stores and loads, the end result will not contain sigma nodes for all // constants. Some constants may be used directly, e.g. in comparisons such as 'x == 5'. We may still // end up inserting dead sigma nodes in branches, but these will never get used in renaming and will be // cleaned up when we remove dead phis and sigmas. 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 { 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) } } func simplifyConstantCompositeValues(fn *Function) bool { changed := false for _, b := range fn.Blocks { n := 0 for _, instr := range b.Instrs { replaced := false if cv, ok := instr.(*CompositeValue); ok { ac := &AggregateConst{} ac.typ = cv.typ replaced = true for _, v := range cv.Values { if c, ok := v.(Constant); ok { ac.Values = append(ac.Values, c) } else { replaced = false break } } if replaced { replaceAll(cv, emitConst(fn, ac)) killInstruction(cv) } } if replaced { changed = true } else { b.Instrs[n] = instr n++ } } clearInstrs(b.Instrs[n:]) b.Instrs = b.Instrs[:n] } return changed } func updateOperandReferrers(instr Instruction) { for _, op := range instr.Operands(nil) { refs := (*op).Referrers() if refs != nil { *refs = append(*refs, instr) } } } ����������������������������golang-honnef-go-tools-2023.1.7/go/ir/lvalue.go�����������������������������������������������������0000664�0000000�0000000�00000011144�14566144071�0021100�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()) } type compositeElement struct { cv *CompositeValue idx int t types.Type expr ast.Expr } func (ce *compositeElement) load(fn *Function, source ast.Node) Value { panic("not implemented") } func (ce *compositeElement) store(fn *Function, v Value, source ast.Node) { v = emitConv(fn, v, ce.t, source) ce.cv.Values[ce.idx] = v if ce.expr != nil { // store.Val is v, converted for assignability. emitDebugRef(fn, ce.expr, v, false) } } func (ce *compositeElement) address(fn *Function) Value { panic("not implemented") } func (ce *compositeElement) typ() types.Type { return ce.t } // 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 lazyAddress is an lvalue whose address is the result of an instruction. // These work like an *address except a new address.address() Value // is created on each load, store and address call. // A lazyAddress can be used to control when a side effect (nil pointer // dereference, index out of bounds) of using a location happens. type lazyAddress struct { addr func(fn *Function) Value // emit to fn the computation of the address t types.Type // type of the location expr ast.Expr // source syntax of the value (not address) [debug mode] } func (l *lazyAddress) load(fn *Function, source ast.Node) Value { load := emitLoad(fn, l.addr(fn), source) return load } func (l *lazyAddress) store(fn *Function, v Value, source ast.Node) { store := emitStore(fn, l.addr(fn), v, source) if l.expr != nil { // store.Val is v, converted for assignability. emitDebugRef(fn, l.expr, store.Val, false) } } func (l *lazyAddress) address(fn *Function) Value { addr := l.addr(fn) if l.expr != nil { emitDebugRef(fn, l.expr, addr, true) } return addr } func (l *lazyAddress) typ() types.Type { return l.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") } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-honnef-go-tools-2023.1.7/go/ir/methods.go����������������������������������������������������0000664�0000000�0000000�00000015203�14566144071�0021253�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" "honnef.co/go/tools/analysis/lint" ) // 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) 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 *methodSet) { if v.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 origin := obj.Origin(); origin != obj { // Calling method on instantiated type, create a wrapper that calls the generic type's method base := prog.packageLevelValue(origin) return makeInstance(prog, base.(*Function), obj.Type().(*types.Signature), nil) } else { 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); 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, *types.TypeParam: // 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: lint.ExhaustiveTypeSwitch(T) } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-honnef-go-tools-2023.1.7/go/ir/mode.go�������������������������������������������������������0000664�0000000�0000000�00000005624�14566144071�0020542�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 SplitAfterNewInformation // Split live range after we learn something new about a value ) const BuilderModeDoc = `Options controlling the IR builder. The value is a sequence of zero or more of these symbols: 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. I Split live range after a value is used as slice or array index ` 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') } if m&SplitAfterNewInformation != 0 { buf.WriteByte('I') } 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 case 'I': mode |= SplitAfterNewInformation default: return fmt.Errorf("unknown BuilderMode option: %q", c) } } *m = mode return nil } // Get returns m. func (m BuilderMode) Get() interface{} { return m } ������������������������������������������������������������������������������������������������������������golang-honnef-go-tools-2023.1.7/go/ir/print.go������������������������������������������������������0000664�0000000�0000000�00000033062�14566144071�0020747�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.TypeArgs { b.WriteString(" ") b.WriteString(relType(arg, instr.Parent().pkg())) } 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 (v *Copy) String() string { return fmt.Sprintf("Copy <%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 *SliceToArray) String() string { return printConv("SliceToArray", 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() // v.X.Type() might be a pointer to a type parameter whose core type is a pointer to a struct st := deref(typeutil.CoreType(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 := typeutil.CoreType(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 (v *CompositeValue) String() string { var b bytes.Buffer from := v.Parent().pkg() fmt.Fprintf(&b, "CompositeValue <%s>", relType(v.Type(), from)) if v.NumSet >= len(v.Values) { // All values provided fmt.Fprint(&b, " [all]") } else if v.Bitmap.BitLen() == 0 { // No values provided fmt.Fprint(&b, " [none]") } else { // Some values provided bits := []byte(fmt.Sprintf("%0*b", len(v.Values), &v.Bitmap)) for i := 0; i < len(bits)/2; i++ { o := len(bits) - 1 - i bits[i], bits[o] = bits[o], bits[i] } fmt.Fprintf(&b, " [%s]", bits) } for _, vv := range v.Values { fmt.Fprintf(&b, " %s", relName(vv, v)) } 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") } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-honnef-go-tools-2023.1.7/go/ir/sanity.go�����������������������������������������������������0000664�0000000�0000000�00000035654�14566144071�0021133�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" "honnef.co/go/tools/go/types/typeutil" ) 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 *SliceToArray: case *Convert: tsetInstrX := typeutil.NewTypeSet(instr.X.Type().Underlying()) tsetInstr := typeutil.NewTypeSet(instr.Type().Underlying()) ok1 := tsetInstr.Any(func(term *types.Term) bool { _, ok := term.Type().Underlying().(*types.Basic); return ok }) ok2 := tsetInstrX.Any(func(term *types.Term) bool { _, ok := term.Type().Underlying().(*types.Basic); return ok }) if !ok1 && !ok2 { s.errorf("convert %s -> %s: at least one type set must contain basic type", 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 *AggregateConst: case *ArrayConst: case *GenericConst: case *Recv: case *TypeSwitch: case *CompositeValue: 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 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, SyntheticGeneric: 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)) } } } } ������������������������������������������������������������������������������������golang-honnef-go-tools-2023.1.7/go/ir/source.go�����������������������������������������������������0000664�0000000�0000000�00000020613�14566144071�0021111�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 { obj = obj.Origin() 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 } ���������������������������������������������������������������������������������������������������������������������golang-honnef-go-tools-2023.1.7/go/ir/source_test.go������������������������������������������������0000664�0000000�0000000�00000026305�14566144071�0022154�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" "path/filepath" "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/analysis/analysistest" "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(filepath.Join(analysistest.TestData(), "objlookup.go")) if err != nil { t.Fatal(err) } readFile := func(filename string) ([]byte, error) { return src, nil } f, err := conf.ParseFile(filepath.Join(analysistest.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, filepath.Join(analysistest.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, "" url = 'https://github.com/dominikh/go-tools' weight = 999 [markup] [markup.highlight] style = "tango" [params] description = 'Staticcheck is a state of the art linter for the Go programming language' images = ['/img/logo.webp'] title = 'Staticcheck' golang-honnef-go-tools-2023.1.7/website/content/000077500000000000000000000000001456614407100213555ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/website/content/_index.md000066400000000000000000000045541456614407100231550ustar00rootroot00000000000000--- title: Staticcheck --- {{< blocks/lead type="section" color="primary" >}} Our mascot, the engineer 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. {{< /blocks/lead >}} {{< blocks/section color="white" >}} {{% blocks/feature title="Correctness" icon="fa-check" %}} Code obviously has to be correct. Unfortunately, it never is. Some code is more correct than other, but there'll always be bugs. Tests catch some, your peers catch others, and Staticcheck catches some more. {{% /blocks/feature %}} {{% blocks/feature title="Simplicity" icon="fa-circle" %}} After correctness comes simplicity. There are many ways to skin a cat (but please don't), but some are unnecessarily elaborate. Staticcheck helps you replace complex code with simple code. {{% /blocks/feature %}} {{% blocks/feature title="Maintainability" icon="fa-screwdriver" %}} Code is a living thing. If it's not maintained regularly, it will become stale and unreliable. It won't catch up when its dependencies move on or guidelines change. Staticcheck is like a sitter for your code for when you don't have time. {{% /blocks/feature %}} {{% blocks/feature title="Exhaustiveness" icon="fas fa-tasks" url="/docs/checks" %}} More than {{< numchecks.inline >}} {{- $c := len $.Site.Data.checks.Checks -}} {{- sub $c (mod $c 25) -}} {{< /numchecks.inline >}} checks ensure that your code is in tip-top shape. {{% /blocks/feature %}} {{% blocks/feature title="Integration" icon="fab fa-github" %}} Staticcheck can easily be integrated with your code review and CI systems, preventing buggy code from ever getting committed. {{% /blocks/feature %}} {{% blocks/feature title="Editor integration" icon="fa-i-cursor" url="https://github.com/golang/tools/blob/master/gopls/doc/settings.md#staticcheck-bool" %}} Staticcheck is part of most major Go editors and has been integrated with gopls, finding bugs and offering automatic fixes. {{% /blocks/feature %}} {{< /blocks/section >}} golang-honnef-go-tools-2023.1.7/website/content/changes/000077500000000000000000000000001456614407100227655ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/website/content/changes/2017.2.md000066400000000000000000000116061456614407100240440ustar00rootroot00000000000000--- title: Staticcheck 2017.2 release notes linkTitle: 2017.2 weight: -1 --- 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]({{< relref "/docs/configuration#ignoring-problems" >}}). 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: ```json { "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 {{< check "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 {#2017.2.1} 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 {#2017.2.2} 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. golang-honnef-go-tools-2023.1.7/website/content/changes/2019.1.md000066400000000000000000000163421456614407100240470ustar00rootroot00000000000000--- title: Staticcheck 2019.1 release notes linkTitle: 2019.1 weight: -2 --- ## 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 `-.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 – `-.enabled` flags. Details on all of the command-line flags can be found [in the documentation.]({{< relref "/docs/#cli" >}}) ## 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]({{< relref "/docs/#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](https://github.com/golang/lint) 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 `-.enabled` and `-.exit-non-zero` have been replaced by `-checks` and `-fail`. Finally, the `-ignore` flag has been replaced by [linter directives]({{< relref "/docs/#ignoring-problems" >}}). ## Binary releases Beginning with this release, we're publishing [prebuilt binaries to GitHub](https://github.com/dominikh/go-tools/releases). 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]({{< relref "/docs/running-staticcheck/cli/formatters" >}}) (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 {#2019.1.1} 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. golang-honnef-go-tools-2023.1.7/website/content/changes/2019.2.md000066400000000000000000000367541456614407100240610ustar00rootroot00000000000000--- title: Staticcheck 2019.2 release notes linkTitle: 2019.2 weight: -3 --- ## Performance improvements {#performance} 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 {#go-analysis} Part of the redesign of Staticcheck involved porting our code to the [go/analysis](https://godoc.org/golang.org/x/tools/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 {#cli} We've made several minor improvements to the command-line interface of `staticcheck` that improve usability and debuggability. ### SIGINFO handler {#cli-siginfo} 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. ```text Packages: 37/619 initial, 38/1011 total; Workers: 8/8; Problems: 73 ``` ### Explaining checks {#cli-explain} Using the new `-explain` flag, a check's documentation can be displayed right in the terminal, eliminating the need to visit the website. ```text $ 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 {#cli-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. ```text $ 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 {#cli-unused} 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 {#cli-ranges} 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 {#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@`, 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 {#deprecated} 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]({{< relref "/docs/#ignoring-problems" >}}). This release no longer includes these deprecated utilities, nor does it provide the deprecated flag. ## Checks {#checks} ### New checks {#checks-new} 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](https://en.wikipedia.org/wiki/Yoda_conditions), which take the form of `if 42 == x`. - {{< check "ST1018" >}} flags string literals containing zero-width characters. ### Changed checks {#checks-changed} 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 {#sustainable-open-source} 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](https://www.patreon.com/dominikh) 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](https://github.com/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.](https://github.com/users/dominikh/sponsorship) 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](https://staticcheck.io/)** for [$250 USD a month](https://github.com/users/dominikh/sponsorship?utf8=%E2%9C%93&tier_id=MDIyOk1hcmtldHBsYWNlTGlzdGluZ1BsYW4yNTAy&editing=false), 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](mailto:dominik@honnef.co) 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 {#2019.2.1} 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 {#2019.2.2} 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-.` instead of `v0.0.0-.`. 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 {#2019.2.3} 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. golang-honnef-go-tools-2023.1.7/website/content/changes/2020.1.md000066400000000000000000000225521456614407100240370ustar00rootroot00000000000000--- title: Staticcheck 2020.1 release notes linkTitle: 2020.1 weight: -4 --- ## Introduction to Staticcheck 2020.1 {#introduction} Staticcheck 2020.1 introduces UI improvements, speed enhancements, and a number of [new](#checks-new) as well as [improved](#checks-changed) checks. Additionally, it is the first release to support the upcoming Go 1.14. ## UI improvements {#ui-improvements} We've improved the output of the `staticcheck` command as well as Staticcheck's integration with [gopls](https://github.com/golang/tools/tree/master/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: ```go 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: ```text 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`]({{< relref "/docs/running-staticcheck/cli/formatters#text" >}}) or [`stylish`]({{< relref "/docs/running-staticcheck/cli/formatters#stylish" >}}) formatters, related information will appear as indented lines. The [`json`]({{< relref "/docs/running-staticcheck/cli/formatters#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 {#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 {#checks} ### New checks {#checks-new} 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 {#checks-changed} 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](https://github.com/spf13/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 {#bugs} 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 {#2020.1.1} 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 {#2020.1.2} The 2020.1.1 release incorrectly identified itself as version 2020.1. ## Staticcheck 2020.1.3 release notes {#2020.1.3} 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 {#2020.1.4} This release adds special handling for imports of the deprecated `github.com/golang/protobuf/proto` package. [github.com/golang/protobuf](https://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](https://github.com/golang/protobuf/issues/1077). ## Staticcheck 2020.1.5 release notes {#2020.1.5} This release fixes a [crash in the pattern matching engine]({{< issueref 806 >}}) and a [false positive in SA4006]({{< issueref 733 >}}). ## Staticcheck 2020.1.6 release notes {#2020.1.6} 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. golang-honnef-go-tools-2023.1.7/website/content/changes/2020.2.md000066400000000000000000000410301456614407100240300ustar00rootroot00000000000000--- title: Staticcheck 2020.2 release notes linkTitle: "2020.2 (v0.1.0)" weight: -5 --- ## Performance improvements {#performance-improvements} The primary focus of this release is a major improvement in performance, significantly reducing memory usage while also reducing runtimes.
    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](https://github.com/dominikh/go-tools/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 {#unused} ### Removal of whole-program mode and changes to the handling of exported identifiers {#unused-whole-program} The [aforementioned performance improvements](#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 {#unused-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 ```go package pkg //lint:ignore U1000 This is fine. func fn1() { fn2() } func fn2() {} ``` Staticcheck would emit the following output: ```text 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 {#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 {#versioning-improvements} 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-` 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 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]({{< issueref 777 >}}). ## Checks {#checks} ### New checks {#checks-new} 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 {#checks-changed} 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 {#2020.2.1} 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](https://blog.golang.org/path-security) article by the Go authors for more information on this potential vulnerability. ## Staticcheck 2020.2.2 release notes {#2020.2.2} 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 {#2020.2.3} This release fixes a false positive in U1000. See [issue 942]({{< issueref 942 >}}) for an example. ## Staticcheck 2020.2.4 release notes {#2020.2.4} 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]({{< issueref 949 >}}) and [981]({{< issueref 981 >}}) for more information) - A rare crash (see issue [972]({{< issueref 972 >}}) for more information) golang-honnef-go-tools-2023.1.7/website/content/changes/2021.1.md000066400000000000000000000135471456614407100240440ustar00rootroot00000000000000--- title: Staticcheck 2021.1 release notes linkTitle: "2021.1 (v0.2.0)" weight: -6 --- ## UI improvements {#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 {#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 {#checks} ### New checks {#checks-new} 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](https://en.wikipedia.org/wiki/Signed_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 {#checks-changed} 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 {#documentation} The documentation on [the website](https://staticcheck.io) 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 {#gopls} Several behind the scenes changes prepare this release for better integration with [gopls](https://github.com/golang/tools/blob/master/gopls/README.md). 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 {#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](https://github.com/loov/goda)) has made rdeps redundant. ## Staticcheck 2021.1.1 release notes {#2021.1.1} 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]({{< issueref 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. ## Staticcheck 2021.1.2 release notes {#2021.1.2} This release fixes the following false positives: - {{< check "SA4000" >}} no longer flags operations involving the `math/rand` package. This is to allow patterns such as `rand.Intn(2) - rand.Intn(2)`. - {{< check "S1019" >}} no longer recommends replacing `make(map[X]Y, 0)` with `make(map[X]Y)` – the latter may allocate a small starting size. - {{< check "SA1026" >}} now more accurately implements the behavior of `encoding/json` and `encoding/xml`, which will lead to fewer false positives involving method sets or embedding. It might also find bugs that it previously missed. - Checks that are sensitive to control flow, such as {{< check "SA5011" >}}, now recognize functions from `k8s.io/klog` and `go.uber.org/zap` that affect control flow (`Panic` and `Fatal` related functions.) Furthermore, it fixes the following crashes: - Using `//lint:ignore U1000` on a type alias would crash. - Converting a slice to an array pointer, where the array type is a named type, would crash. golang-honnef-go-tools-2023.1.7/website/content/changes/2022.1.md000066400000000000000000000143211456614407100240340ustar00rootroot00000000000000--- title: Staticcheck 2022.1 release notes linkTitle: "2022.1 (v0.3.0)" weight: -7 --- ## Improvements {#improvements} This release adds support for Go 1.18 and type parameters (generics). Furthermore, it adds two new flags for handling build tags, `-matrix` and `-merge`. Their use is extensively documented on the new [Build tags]({{< relref "/docs/running-staticcheck/cli/build-tags" >}}) page. Their intended use is for avoiding false positives when dealing with different build tags. Not tied directly to this release, but worth mentioning regardless: Staticcheck has an [official GitHub Action](https://github.com/dominikh/staticcheck-action) now, which may simplify your CI pipeline. Minor changes include slightly nicer output from `staticcheck -explain`, better error messages, and allowing whitespace in flags like `-checks`. ## Checks {#checks} ### New checks {#checks-new} The following new checks have been added: - {{< check "SA4028" >}} flags `x % 1`, which always yields zero, and is sometimes accidentally used instead of `x % 2` - {{< check "SA4029" >}} flags misuses of `sort.IntSlice` and related types - {{< check "SA4030" >}} flags misuses of `math/rand` that always generate zeros - {{< check "SA4031" >}} flags comparisons of never-nil values against nil - {{< check "SA9007" >}} flags attempts at deleting system directories - {{< check "SA9008" >}} flags accidental shadowing in the else branches of type assertions ### Changed checks {#checks-changed} The following checks have been improved: - {{< check "S1001" >}} now simplifies more loops - {{< check "S1038" >}} now simplifies formatted printing in `log` and `testing`, in addition to `fmt` - {{< check "SA1019" >}} no longer flags deprecated API in the Go standard library if it doesn't know when the API was deprecated. This is to avoid false positives when using older versions of Staticcheck on newer versions of Go, in particular Go's `master` branch. - {{< check "SA1020" >}} no longer flags `net/http.ListenAndServe` with a completely empty address - {{< check "ST1001" >}} various packages of `github.com/mmcloughlin/avo` have been whitelisted by default - {{< check "ST1008" >}} no longer flags functions that return `(..., error, bool)` - {{< check "ST1018" >}} no longer flags emoji sequences - {{< check "ST1023" >}} no longer makes erroneous suggestions - Numerous checks have a better understanding of integer literals and can detect mistakes involving unconventional literals such as `---1` instead of `-1` - Some runtime crashes have been fixed ## Staticcheck 2022.1.1 release notes {#2022.1.1} This release addresses the following false positives, crashes, and infinite loops: - {{< check "SA1026" >}} and {{< check "SA5008" >}} no longer get stuck in infinite loops when code attempts to marshal cyclic pointer types ({{< issue "1202" >}}) - U1000 no longer crashes when code contains mutually recursive type instantiations ({{< issue "1247" >}}) - U1000 no longer crashes when generic functions use composite literals of type parameter types ({{< commit "0ccdb5c9dad7e96a8e3a3136738192491b37dbdb" >}}) - {{< check "ST1021" >}} now understands type names that are also English articles ({{< issue "1187" >}}) - {{< check "SA4023" >}} no longer gets confused by the nilness of type parameters ({{< issue "1242" >}}) - Some checks no longer crash when trying to generate automated code fixes that involve function literals ({{< issue "1134" >}}) - {{< check "SA1026" >}} no longer claims that `encoding/json` cannot marshal generic maps ([golang/go#52467](https://golang.org/issue/52467)) - The `binary` format has been improved to handle OS-specific file paths correctly, in turn making the `-merge` flag work more reliably ({{< commit "1846305a946b13d350894512c7ac1e5ed71dc331" >}}) - When using the `-merge` or `-matrix` flags, diagnostics reported by {{< check "SA4008" >}} now have to occur in all runs to be reported, reducing the number of false positives ({{< commit "0e678cbe1c8b3f09ac481673453886b1afc9906a" >}}) - U1000 now understands struct type conversions involving type parameters, reducing the number of false positives ({{< commit "90804df0287d9265e565bcabbe19568efbe374fa" >}}) ## Staticcheck 2022.1.2 release notes {#2022.1.2} This release addresses the following false positives, crashes, infinite loops, and performance issues: - For certain packages that contain tens of thousands of types and methods, such as those generated by [ygot](https://github.com/openconfig/ygot), Staticcheck now finishes [much faster](https://github.com/openconfig/featureprofiles/pull/181#issuecomment-1119250596). - Several infinite loops when handling recursive type parameters have been fixed - {{< check "S1009" >}} no longer mistakes user-defined functions named `len` for the builtin ({{< issue "1181" >}}) - {{< check "ST1015" >}} no longer reorders `switch` statements if their order is significant due to the use of `fallthrough` ({{< issue "1188" >}}) - {{< check "SA1013" >}} now detects constants more robustly, avoiding both false negatives and false positives. Furthermore, it makes sure that offending methods implement io.Seeker and doesn't just rely on the name `Seek` ({{< issue "1213" >}}). - {{< check "SA5008" >}} now understands more third-party extensions to `json` struct tags - A crash involving functions named `_` has been fixed ({{< issue "1268" >}}) - A crash involving slicing type parameters of type `string | []byte` has been fixed ({{< issue "1270" >}}) - {{< check "SA1019" >}} now handles imports of deprecated standard library packages in the same way it handles other deprecated API, taking the targeted Go version into consideration ({{< issue "1117" >}}) Additionally it is strongly recommended to use Go 1.18.2 for building Staticcheck, as it fixes further generics-related bugs in the type checker. ## Staticcheck 2022.1.3 release notes {#2022.1.3} This release addresses the following issues: - Staticcheck maintains a cache to speed up repeated runs. This cache needs to be pruned regularly to keep its size in check. This is meant to happen automatically, but it hasn't since Staticcheck 2020.2. This release corrects that ({{< issue "1283" >}}.) - Some type sets containing both bidirectional and unidirectional channels would lead to panics ({{< issue "1304" >}}) golang-honnef-go-tools-2023.1.7/website/content/changes/2023.1.md000066400000000000000000000116571456614407100240460ustar00rootroot00000000000000--- title: Staticcheck 2023.1 release notes linkTitle: "2023.1 (v0.4.0)" weight: -8 --- Staticcheck 2023.1 adds support for Go 1.20, brings minor improvements to various checks, and replaces U1000 with a new implementation. ## Checks {#checks} ### Changed checks {#checks-changed} The following checks have been improved: - The wording of {{< check "S1001" >}} has been made clearer for cases involving arrays. Furthermore, it no longer suggests using `copy` when the function has been shadowed. - {{< check "S1011" >}} now recognizes index-based loops ({{< issue "881" >}}). - {{< check "SA1019" >}} no longer flags tests (internal or external) that use deprecated API from the package under test ({{< issue "1285" >}}). Furthermore, entire declaration groups (such as groups of constants) can now be marked as deprecated ({{< issue "1313" >}}). - {{< check "SA4017" >}} now detects more functions, including those in the `time` package ({{< issue "1353" >}}). Additionally, its wording has been made clearer. - {{< check "SA5010" >}} no longer gets confused by type assertions involving generic types ({{< issue "1354" >}}). - {{< check "ST1005" >}} no longer flags errors that start with alpha-numeric acronyms such as `P384`. - Improvements to our intermediate representation may allow various checks to find more problems. Staticcheck now knows about version 2 of the `k8s.io/klog` package, in particular which functions abort control flow ({{< issue "1307" >}}). In addition to these minor improvements, U1000 has been rewritten from the ground up, operating on a program representation more suited to the task. In practice this means that there will be fewer false positives and more true positives. Overall, the rewrite fixes at least eight known bugs, both ones that have been a nuisance for a while, as well as ones newly introduced by generics ({{< issue "507" >}}, {{< issue "633" >}}, {{< issue "810" >}}, {{< issue "812" >}}, {{< issue "1199" >}}, {{< issue "1249" >}}, {{< issue "1282" >}}, {{< issue "1333" >}}). ## Staticcheck 2023.1.1 release notes {#2023.1.1} This release fixes a crash, a false positive in U1000 ({{< issue "1360" >}}) and improves the way deprecated API is flagged ({{< issue "1318" >}}). When targeting a Go version that is older than the version that deprecated an API, {{< check "SA1019" >}} will no longer flag the use even if there is already an alternative available in the targeted Go version. For example, `math/rand.Seed` has been deprecated in Go 1.20, but an alternative has existed since Go 1.0. In the past, we would flag uses of `Seed` even if targeting e.g. Go 1.19, to encourage better forwards compatibility. This can lead to unnecessary churn, however, because the correct change may depend on the Go version in use. For example, for `Seed` before Go 1.20, the alternative is to use a separate instance of `math/rand.Rand`, whereas in Go 1.20, a possible alternative is to simply drop the call to `Seed`. ## Staticcheck 2023.1.2 release notes {#2023.1.2} This release fixes a bug that prevented the `binary` formatter from working ({{< issue "1370" >}}). ## Staticcheck 2023.1.3 release notes {#2023.1.3} This release fixes the following bugs: - A crash when embedding type aliases of unnamed types ({{< issue "1361" >}}) - A false positive in U1000, claiming that type aliases are unused ({{< issue "1365" >}}) - A bug in the `binary` formatter that prevented correct merging behavior for some checks ({{< issue "1372" >}}) ## Staticcheck 2023.1.4 release notes {#2023.1.4} This release **adds support for Go 1.21** and fixes the following bugs: - Three crashes when encountering unnecessarily parenthesized statements ({{< issue "1393" >}}, {{< issue "1400" >}}) - Unnecessarily high memory usage when analyzing composite literals such as `[]int{1<<31: 1}` ({{< issue "1393" >}}) - A false positive in {{< check "S1011" >}} when appending to a dynamic left-hand side ({{< issue "1399" >}}) - A crash involving generics ({{< issue "1410" >}}) - A false positive in {{< check "SA9001" >}} involving control flow statements ({{< issue "488" >}}) - A false positive in {{< check "ST1003" >}}, complaining about the names of fuzz functions ({{< issue "1420" >}})) ## Staticcheck 2023.1.5 release notes {#2023.1.5} This release fixes the following bug: - A crash involving methods named `_` ## Staticcheck 2023.1.6 release notes {#2023.1.6} This release fixes the following bugs: - A crash when using the upcoming Go 1.22 ({{< issue "1442" >}}) - A false positive in {{< check "SA9005" >}} when embedding basic types ({{< issue "1443" >}}) ## Staticcheck 2023.1.7 release notes {#2023.1.7} This release fixes some minor issues in Staticcheck's intermediate representation. Furthermore, it improves the way {{< check "QF1003" >}} generates suggested fixes, working around constraints in the language server protocol. The released binaries for this version have been built with Go 1.22 and should no longer panic when checking code targeting Go 1.22. golang-honnef-go-tools-2023.1.7/website/content/changes/_index.md000066400000000000000000000001311456614407100245500ustar00rootroot00000000000000--- title: Release notes type: docs outputs: - html - RSS cascade: type: docs --- golang-honnef-go-tools-2023.1.7/website/content/contact.md000066400000000000000000000010411456614407100233260ustar00rootroot00000000000000--- title: "Contact" menu: main: weight: 3 pre: --- {{< blocks/section type="section" color="white" >}} Please contact me at dominik@honnef.co should you have any questions regarding this website or the services offered. The following information are provided to comply with § 5 Telemediengesetz. Dominik Honnef
    Bahnhofstraße 65
    45770 Marl
    Germany

    Email: dominik@honnef.co
    Phone: +49 151 178 067 90

    USt-IdNr: DE298968211 {{< /blocks/section >}} golang-honnef-go-tools-2023.1.7/website/content/docs/000077500000000000000000000000001456614407100223055ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/website/content/docs/_index.md000066400000000000000000000032101456614407100240710ustar00rootroot00000000000000--- title: "Welcome to Staticcheck" linkTitle: "Documentation" menu: main: weight: 1 pre: --- 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. Each of the [ {{< numchecks.inline >}} {{- $c := len $.Site.Data.checks.Checks -}} {{- sub $c (mod $c 25) -}} {{< /numchecks.inline >}}+ ]({{< relref "/docs/checks" >}}) checks has been designed to be fast, precise and useful. When Staticcheck flags code, you can be sure that it isn't wasting your time with unactionable warnings. Unlike many other linters, Staticcheck focuses on checks that produce few to no false positives. It's the ideal candidate for running in CI without risking spurious failures. Staticcheck aims to be trivial to adopt. It behaves just like the official `go` tool and requires no learning to get started with. Just run `staticcheck ./...` on your code in addition to `go vet ./...`. While checks have been designed to be useful out of the box, they still provide [configuration]({{< relref "/docs/configuration" >}}) where necessary, to fine-tune to your needs, without overwhelming you with hundreds of options. Staticcheck can be used from the command line, in CI, and even [directly from your editor](https://github.com/golang/tools/blob/master/gopls/doc/settings.md#staticcheck-bool). Staticcheck is open source and offered completely free of charge. [Sponsors]({{< relref "/sponsors" >}}) guarantee its continued development. }}"> golang-honnef-go-tools-2023.1.7/website/content/docs/changes.md000066400000000000000000000000721456614407100242360ustar00rootroot00000000000000--- title: Release notes manualLinkRelref: "/changes" --- golang-honnef-go-tools-2023.1.7/website/content/docs/checks.html000066400000000000000000000150201456614407100244310ustar00rootroot00000000000000--- title: "Checks" description: "Explanations for all checks in Staticcheck" menu: main: weight: 2 pre: --- {{< all-checks.inline >}} {{ define "category-list" }} {{ range $name := index $.p.Site.Data.checks.ByCategory $.cat }} {{ $check := index $.p.Site.Data.checks.Checks $name }} {{ $name }} {{ $check.TitleMarkdown | markdownify }} {{ end }} {{ end }} {{ define "category" }} {{ range $name := index $.p.Site.Data.checks.ByCategory .cat }} {{ $check := index $.p.Site.Data.checks.Checks $name }}

    {{ $name }} - {{ $check.TitleMarkdown | markdownify }}{{ if $check.NonDefault }} non-default{{ end }}

    {{ $check.TextMarkdown | $.p.Page.RenderString (dict "display" "block") }} {{ if $check.Before }}

    Before:

    {{ highlight $check.Before "go" "" }} {{ end }} {{ if $check.After }}

    After:

    {{ highlight $check.After "go" "" }} {{ end }}
    Available since
    {{ $check.Since }}
    {{ if $check.Options }}
    Options
      {{ range $opt := $check.Options -}}
    • {{ $opt }}
    • {{ end }}
    {{ end }}
    {{ end }} {{ end }} {{ $categoryNames := slice "SA" "SA1" "SA2" "SA3" "SA4" "SA5" "SA6" "SA9" "S" "S1" "ST" "ST1" "QF" "QF1" }} {{ $categories := dict "SA" (dict "name" "SA" "title" "staticcheck") "SA1" (dict "name" "SA1" "title" "Various misuses of the standard library") "SA2" (dict "name" "SA2" "title" "Concurrency issues") "SA3" (dict "name" "SA3" "title" "Testing issues") "SA4" (dict "name" "SA4" "title" "Code that isn't really doing anything") "SA5" (dict "name" "SA5" "title" "Correctness issues") "SA6" (dict "name" "SA6" "title" "Performance issues") "SA9" (dict "name" "SA9" "title" "Dubious code constructs that have a high probability of being wrong") "S" (dict "name" "S" "title" "simple") "S1" (dict "name" "S1" "title" "Code simplifications") "ST" (dict "name" "ST" "title" "stylecheck") "ST1" (dict "name" "ST1" "title" "Stylistic issues") "QF" (dict "name" "QF" "title" "quickfix") "QF1" (dict "name" "QF1" "title" "Quickfixes") }} {{ range $name := $categoryNames }} {{ $cat := index $categories $name }} {{ template "category-list" (dict "p" $ "cat" $cat.name) }} {{ end }}
    Check Short description
    {{ $cat.name }}{{ $cat.title }}
    {{ define "category-header" }}

    {{ .name }} – {{ .title }}

    {{ end }} {{ define "subcategory-header" }}

    {{ .name }} – {{ .title }}

    {{ end }} {{ template "category-header" (index $categories "SA") }}

    The SA category of checks, codenamed staticcheck, contains all checks that are concerned with the correctness of code.

    {{ template "subcategory-header" (index $categories "SA1") }}

    Checks in this category deal with misuses of the standard library. This tends to involve incorrect function arguments or violating other invariants laid out by the standard library's documentation.

    {{ template "category" (dict "p" $ "cat" "SA1") }} {{ template "subcategory-header" (index $categories "SA2") }}

    Checks in this category find concurrency bugs.

    {{ template "category" (dict "p" $ "cat" "SA2") }} {{ template "subcategory-header" (index $categories "SA3") }}

    Checks in this category find issues in tests and benchmarks.

    {{ template "category" (dict "p" $ "cat" "SA3") }} {{ template "subcategory-header" (index $categories "SA4") }}

    Checks in this category point out code that doesn't have any meaningful effect on a program's execution. Usually this means that the programmer thought the code would do one thing while in reality it does something else.

    {{ template "category" (dict "p" $ "cat" "SA4") }} {{ template "subcategory-header" (index $categories "SA5") }}

    Checks in this category find assorted bugs and crashes.

    {{ template "category" (dict "p" $ "cat" "SA5") }} {{ template "subcategory-header" (index $categories "SA6") }}

    Checks in this category find code that can be trivially made faster.

    {{ template "category" (dict "p" $ "cat" "SA6") }} {{ template "subcategory-header" (index $categories "SA9") }}

    Checks in this category find code that is probably wrong. Unlike checks in the other SA categories, checks in SA9 have a slight chance of reporting false positives. However, even false positives will point at code that is confusing and that should probably be refactored.

    {{ template "category" (dict "p" $ "cat" "SA9") }} {{ template "category-header" (index $categories "S") }}

    The S category of checks, codenamed simple, contains all checks that are concerned with simplifying code.

    {{ template "subcategory-header" (index $categories "S1") }}

    Checks in this category find code that is unnecessarily complex and that can be trivially simplified.

    {{ template "category" (dict "p" $ "cat" "S1") }} {{ template "category-header" (index $categories "ST") }}

    The ST category of checks, codenamed stylecheck, contains all checks that are concerned with stylistic issues.

    {{ template "subcategory-header" (index $categories "ST1") }}

    The rules contained in this category are primarily derived from the Go wiki and represent community consensus.

    Some checks are very pedantic and disabled by default. You may want to tweak which checks from this category run, based on your project's needs.

    {{ template "category" (dict "p" $ "cat" "ST1") }} {{ template "category-header" (index $categories "QF") }}

    The QF category of checks, codenamed quickfix, contains checks that are used as part of gopls for automatic refactorings. In the context of gopls, diagnostics of these checks will usually show up as hints, sometimes as information-level diagnostics.

    {{ template "subcategory-header" (index $categories "QF1") }} {{ template "category" (dict "p" $ "cat" "QF1") }} {{< /all-checks.inline >}} golang-honnef-go-tools-2023.1.7/website/content/docs/configuration/000077500000000000000000000000001456614407100251545ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/website/content/docs/configuration/_index.md000066400000000000000000000142221456614407100267450ustar00rootroot00000000000000--- title: "Configuration" description: "Tweak Staticcheck to your requirements" weight: 3 --- 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 {#cli-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). This includes which build tags to set and which output format to use. All of the CLI flags are explained in the [Command-line interface]({{< relref "/docs/running-staticcheck/cli" >}}) article. ## Configuration files {#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: ```plain . ├── 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 {#configuration-format} Staticcheck configuration files are named `staticcheck.conf` and contain [TOML](https://github.com/toml-lang/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. this is used when enabling or disabling 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 {#configuration-options} A list of all options and their explanations can be found on the [Options]({{< relref "/docs/configuration/options" >}}) page. ### Example configuration {#example-configuration} The following example configuration is the textual representation of Staticcheck's default configuration. {{% content "default_config.md" %}} ## Ignoring problems with linter directives {#ignoring-problems} 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 {#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: ```go 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: ```go 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 {#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 ```go //lint:ignore SA1000 we love invalid regular expressions! regexp.Compile(".+") ``` Staticcheck will report the following: ```plain 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 {#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: ```go //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. golang-honnef-go-tools-2023.1.7/website/content/docs/configuration/default_config/000077500000000000000000000000001456614407100301255ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/website/content/docs/configuration/default_config/index.md000066400000000000000000000014601456614407100315570ustar00rootroot00000000000000--- headless: true --- ```toml {{< 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` >}} = ["github.com/mmcloughlin/avo/build", "github.com/mmcloughlin/avo/operand", "github.com/mmcloughlin/avo/reg"] {{< option `http_status_code_whitelist` >}} = ["200", "400", "404", "500"] ``` golang-honnef-go-tools-2023.1.7/website/content/docs/configuration/options.md000066400000000000000000000037221456614407100271750ustar00rootroot00000000000000--- title: "Options" description: "Explanations for all options" aliases: - /docs/options --- ## checks {#checks} This option sets which [checks]({{< relref "/docs/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", "-{{< check "ST1000" >}}", "-{{< check "ST1003" >}}", "-{{< check "ST1016" >}}", "-{{< check "ST1020" >}}", "-{{< check "ST1021" >}}", "-{{< check "ST1022" >}}"]` ## initialisms {#initialisms} {{< check "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", "SIP", "RTP", "AMQP", "DB", "TS"]` ## dot_import_whitelist {#dot_import_whitelist} By default, {{< check "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 {#http_status_code_whitelist} {{< check "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"]` golang-honnef-go-tools-2023.1.7/website/content/docs/faq.md000066400000000000000000000031711456614407100234000ustar00rootroot00000000000000--- title: "Frequently Asked Questions" --- {{% faq/list %}} {{% faq/question id="false-positives" question="Staticcheck is wrong, what should I do?" %}} First, make sure that Staticcheck is actually wrong. It can find very subtle bugs, and what may look like a false positive at first glance is usually a genuine bug. There is a long list of competent programmers [who got it wrong before.](https://github.com/dominikh/go-tools/issues?q=is%3Aissue+label%3Afalse-positive+label%3Ainvalid+is%3Aclosed) However, sometimes Staticcheck _is_ wrong and you want to suppress a warning to get on with your work. In that case, you can use [ignore directives to ignore specific problems]({{< relref "/docs/configuration/#ignoring-problems" >}}). You should also [report the false positive](https://github.com/dominikh/go-tools/issues/new?assignees=&labels=false-positive%2C+needs-triage&template=1_false_positive.md&title=) so that we can fix it. We don't expect users to have to ignore many problems, and we always aim to avoid false positives. Some checks, particularly those in the `ST` (stylecheck) category, may not be applicable to your code base at all. In that case, you should disable the check using the [`checks` option]({{< relref "/docs/configuration/options#checks" >}}) in your [configuration]({{< relref "/docs/configuration/#configuration-files" >}}). {{% /faq/question %}} {{% faq/question id="go-version" question="Staticcheck's suggestions don't apply to my version of Go" %}} You can [specify the version of Go your code should work with.]({{< relref "/docs/configuration/#targeting-go-versions" >}}) {{% /faq/question %}} {{% /faq/list %}} golang-honnef-go-tools-2023.1.7/website/content/docs/getting-started.md000066400000000000000000000047001456614407100257350ustar00rootroot00000000000000--- title: "Getting started" description: "Quickly get started using Staticcheck" weight: 1 aliases: - /docs/install --- ## Installation Beginning with Go 1.17, 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](https://docs.github.com/en/github/managing-subscriptions-and-notifications-on-github/viewing-your-subscriptions#configuring-your-watch-settings-for-an-individual-repository). ### Binary releases We publish binary releases for the most common operating systems and CPU architectures. These can be downloaded from [GitHub](https://github.com/dominikh/go-tools/releases). ### 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 new releases in a timely manner. What follows is a non-exhaustive list of the package names in various package repositories.
    Arch Linux : [staticcheck](https://archlinux.org/packages/community/x86_64/staticcheck/) Debian : [go-staticcheck](https://packages.debian.org/go-staticcheck) Fedora : [golang-honnef-tools](https://fedora.pkgs.org/33/fedora-x86_64/golang-honnef-tools-2020.1.5-2.fc33.x86_64.rpm.html) Homebrew : [staticcheck](https://formulae.brew.sh/formula/staticcheck) MacPorts : [staticcheck](https://ports.macports.org/port/staticcheck/summary) NixOS : go-tools Scoop : [staticcheck](https://github.com/ScoopInstaller/Main/blob/master/bucket/staticcheck.json)
    ## Running Staticcheck 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`. Therefore, to start using Staticcheck, just run it on your code: `staticcheck ./...`. It will print any issues it finds, or nothing at all if your code is squeaky clean. Read the [Running Staticcheck]({{< relref "/docs/running-staticcheck" >}}) articles to learn more about running Staticcheck. golang-honnef-go-tools-2023.1.7/website/content/docs/running-staticcheck/000077500000000000000000000000001456614407100262505ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/website/content/docs/running-staticcheck/_index.md000066400000000000000000000001041456614407100300330ustar00rootroot00000000000000--- title: Running Staticcheck weight: 2 aliases: - /docs/run --- golang-honnef-go-tools-2023.1.7/website/content/docs/running-staticcheck/ci/000077500000000000000000000000001456614407100266435ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/website/content/docs/running-staticcheck/ci/_index.md000066400000000000000000000001201456614407100304240ustar00rootroot00000000000000--- title: Continuous integration description: How to run Staticcheck in CI --- golang-honnef-go-tools-2023.1.7/website/content/docs/running-staticcheck/ci/github-actions/000077500000000000000000000000001456614407100315635ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/website/content/docs/running-staticcheck/ci/github-actions/index.md000066400000000000000000000072741456614407100332260ustar00rootroot00000000000000--- title: GitHub Actions description: Running Staticcheck in GitHub Actions aliases: - /docs/running-staticcheck/github-actions --- We publish [our own action](https://github.com/marketplace/actions/staticcheck) for [GitHub Actions](https://github.com/features/actions), which makes it very simple to run Staticcheck in CI on GitHub. ## Examples At its simplest, just add `dominikh/staticcheck-action` as a step in your existing workflow. A minimal workflow might look like this: ```yaml name: "CI" on: ["push", "pull_request"] jobs: ci: name: "Run CI" runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 with: fetch-depth: 1 - uses: dominikh/staticcheck-action@v1.2.0 with: version: "2022.1.1" ``` A more advanced example that runs tests, go vet and Staticcheck on multiple OSs and Go versions looks like this: ```yaml name: "CI" on: ["push", "pull_request"] jobs: ci: name: "Run CI" strategy: fail-fast: false matrix: os: ["windows-latest", "ubuntu-latest", "macOS-latest"] go: ["1.16.x", "1.17.x"] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v1 with: fetch-depth: 1 - uses: WillAbides/setup-go-faster@v1.7.0 with: go-version: ${{ matrix.go }} - run: "go test ./..." - run: "go vet ./..." - uses: dominikh/staticcheck-action@v1.2.0 with: version: "2022.1.1" install-go: false cache-key: ${{ matrix.go }} ``` Note that this example could benefit from further improvements, such as caching of Go's build cache. ## Managing Go By default, `staticcheck-action` installs Go so that it can install and run Staticcheck. It also saves and restores Go's build cache (in addition to Staticcheck's own cache) to speed up future runs. This is intended for trivial jobs that only run Staticcheck, not other steps such as `go test`. For more complicated jobs, it is strongly recommended that you set `install-go` to `false`, install Go yourself (e.g. by using [`actions/setup-go`](https://github.com/actions/setup-go) or [`WillAbides/setup-go-faster`](https://github.com/WillAbides/setup-go-faster)), and save and restore the Go build cache, for improved performance. When installing Go, make sure the version meets Staticcheck's minimum requirements. A given Staticcheck release supports the last two versions of Go (such as Go 1.16 and Go 1.17) at the time of release. The action itself requires at least Go 1.16. ## Options ### `version` Which version of Staticcheck to use. Because new versions of Staticcheck introduce new checks that may break your build, it is recommended to pin to a specific version and to update Staticheck consciously. It defaults to `latest`, which installs the latest released version of Staticcheck. ### `min-go-version` Minimum version of Go to support. This affects the diagnostics reported by Staticcheck, avoiding suggestions that are not applicable to older versions of Go. If unset, this will default to the Go version specified in your go.mod. See https://staticcheck.io/docs/running-staticcheck/cli/#go for more information. ### `build-tags` Go build tags that get passed to Staticcheck via the `-tags` flag. ### `install-go` Whether the action should install a suitable version of Go to install and run Staticcheck. If Staticcheck is the only action in your job, this option can usually be left on its default value of `true`. If your job already installs Go prior to running Staticcheck, for example to run unit tests, it is best to set this option to `false`. The latest release of Staticcheck works with the last two minor releases of Go. The action itself requires at least Go 1.16. ### `cache-key` String to include in the cache key, in addition to the default, which is `runner.os`. This is useful when using multiple Go versions. golang-honnef-go-tools-2023.1.7/website/content/docs/running-staticcheck/cli/000077500000000000000000000000001456614407100270175ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/website/content/docs/running-staticcheck/cli/_index.md000066400000000000000000000057501456614407100306160ustar00rootroot00000000000000--- title: Command-line interface description: How to use the `staticcheck` command --- The `staticcheck` command is the primary way of running Staticcheck. At its core, the `staticcheck` command works a lot like `go vet` or `go build`. It accepts the same package patterns (see `go help packages` for details), it outputs problems in the same format, it supports a `-tags` flag for specifying which build tags to use, and so on. Overall, it is meant to feel like another `go` command. However, it also comes with several of its own flags to support some of its unique functionality. This article will focus on explaining that unique functionality. ## Explaining checks {#explain} You can use `staticcheck -explain ` 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 ```text foo.go:1248:4: unnecessary use of fmt.Sprintf (S1039) ``` the check's identifier is S1039. Running `staticcheck -explain S1039` will output the following: ```text 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`. ## Selecting an output format {#format} Staticcheck can format its output in a number of ways, by using the `-f` flag. See this [list of formatters]({{< relref "/docs/running-staticcheck/cli/formatters" >}}) for a list of all formatters. ## Targeting Go versions {#go} 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 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 a good estimate. You can manually overwrite the targeted Go version by using the `-go` command line flag. For example, `staticcheck -go 1.0 ./...` will only make suggestions that work with Go 1.0. The targeted Go version limits both language features and parts of the standard library that will be recommended. ## Excluding tests {#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. golang-honnef-go-tools-2023.1.7/website/content/docs/running-staticcheck/cli/build-tags/000077500000000000000000000000001456614407100310525ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/website/content/docs/running-staticcheck/cli/build-tags/index.md000066400000000000000000000150571456614407100325130ustar00rootroot00000000000000--- title: Build tags description: How to correctly check code that uses build tags --- ## Introduction In Go, files can have build tags, which control when said files will be part of a package. For example, two files might contain alternate implementations of the same function, targeting Linux and Windows respectively. Due to this, a single import path really refers to a collection of packages, with any particular package being chosen by a combination of build tags. Even if your code doesn't make use of build tags, any of your transitive dependencies might. Therefore, running `staticcheck my/package` really only checks one variant of `my/package`. For more information on Go's build tags, see [`go help buildconstraint`](https://pkg.go.dev/cmd/go#hdr-Build_constraints). ## Implications Checking packages using a single set of build tags can lead to both false positives and false negatives. The reason for false negatives is straightforward: if some code is being excluded by build tags, then we won't check it. False positives can be a bit more involved. Consider the following package, in [txtar format](https://pkg.go.dev/golang.org/x/tools/txtar#hdr-Txtar_format): ```go -- api_linux.go -- package pkg func Entry() { foo() } -- api_windows.go -- package pkg func Entry() { bar() } -- shared.go -- package pkg func foo() {} func bar() {} ``` If we don't check the Windows build, then the function `bar` seems unused. Similarly, if we don't check the Linux build, `foo` seems unused. ```terminal $ GOOS=linux staticcheck shared.go:4:6: func bar is unused (U1000) $ GOOS=windows staticcheck shared.go:3:6: func foo is unused (U1000) ``` Only when we check both builds do we see that both functions are in fact used. Arguably, `foo` and `bar` should live in files with matching build tags to avoid this. However, in reality, code bases can be complex, and it isn't always clear which sets of build tags make use of what code. After all, if we always knew, we wouldn't have dead code to begin with. Another example involves control flow. Consider this package, again in txtar format: ```go -- api_linux.go -- package pkg func dieIfUnsupported() { panic("unsupported") } -- api_windows.go -- package pkg func dieIfUnsupported() {} -- shared.go -- package pkg func foo() {} func Entry() { dieIfUnsupported() foo() } ``` Here, `dieIfUnsupported` panics unconditionally on Linux, but not on Windows. Because Staticcheck takes control flow into consideration, this means that `foo` is unused on Linux but used on Windows. Several checks have this sort of false positive, not just U1000. ## Solution The solution to this problem is to run Staticcheck multiple times with different build tags and to merge the results. At first glance, one might think that Staticcheck should be able to do this fully automatically: look at all build tags, find all unique combinations, and check them all. However, this doesn't scale. To be correct, Staticcheck would have to take dependencies and their tags into consideration, too. Virtually all code depends on the Go standard library, and the Go standard library supports a plethora of operating systems, architectures, and a number of tags such as `netgo`. All in all, there are thousands of unique combinations. Checking all of these would take far too long. However, the number of build configurations you care about is probably much smaller. Your software probably supports 2-3 operating systems on 1-2 architectures, and maybe has a debug and a release build. This makes for a lot fewer combinations that need to be checked. These are probably the same combinations you're already checking in CI, too, by running their tests. This will become useful in a bit. ### The `-merge` flag Using the `-merge` flag, Staticcheck can merge the results of multiple runs. It decides on a per-check basis whether any run or all runs have to have reported an issue for it to be valid. It also takes into consideration which files were checked by which run, to reduce false negatives. In order to use `-merge`, the runs to be merged have to use the `-f binary` flag. This outputs results in a binary format containing all information required by `-merge`. When using `-merge`, arguments are interpreted as file names instead of import paths, so that `staticcheck -merge file1 file2` will read the files `file1` and `file2`, which must contain the output of `staticcheck -f binary` runs, and merge them. ```terminal $ GOOS=linux staticcheck -f binary >file1 $ GOOS=windows staticcheck -f binary >file2 $ staticcheck -merge file1 file2 ... ``` Alternatively, if no arguments are passed, `staticcheck -merge` will read from standard input instead. This allows for workflows like ``` ( GOOS=linux staticcheck -f binary GOOS=windows staticcheck -f binary ) | staticcheck -merge ``` This multi-step workflow of generating per-run output and merging it makes it possible to run Staticcheck on different systems before merging the results, which might be especially required when using cgo. ### The `-matrix` flag With the `-matrix` flag, you can instruct Staticcheck to check multiple build configurations at once and merge the results. In other words, it automates running Staticcheck multiple times and merging results afterwards. This is useful when all configurations can be checked on a single system, for example because you don't use cgo. When using the `-matrix` flag, Staticcheck reads a build matrix from standard input. The build matrix uses a line-based format, where each non-empty line specifies a build name, environment variables and command-line flags. A line is of the format `: [environment variables] [flags]`, for example `linux_debug: GOOS=linux -tags=debug -some-flag="some value"`. Environment variables and flags get passed to `go` when Staticcheck analyzes code, so you can use all flags that `go` supports, such as `-tags` or `-gcflags`, although few flags other than `-tags` are really useful. Valid build names consist of letters, numbers and underscores. Here is an example of using a build matrix: ```terminal $ staticcheck -matrix < --- {{< blocks/section type="section" color="white" >}} # Supporting Staticcheck's open source development Staticcheck is an open source project that is provided free of charge and without any expectations. Nevertheless, working on it requires a considerable time investment. Some very generous people choose to support its development by donating money on [GitHub Sponsors](https://github.com/users/dominikh/sponsorship) or [Patreon](https://www.patreon.com/dominikh). While this is in no way expected of them, it is tremendously appreciated. In addition to these individuals, a number of companies also decide to support Staticcheck through monetary means. Their contributions to open source ensure the viability and future development of Staticcheck. Their support, too, is greatly appreciated. {{< sponsors.inline >}} {{ with $sponsors := $.Site.Data.sponsors.sponsors }} The companies supporting Staticcheck are, in alphabetical order:
      {{ range $sponsor := sort $sponsors "name" "asc" }} {{ if $sponsor.enabled }}
    • {{ $sponsor.name }}
    • {{ end }} {{ end }}
    {{ end }} {{< /sponsors.inline >}} If your company would like to support Staticcheck, please check out [GitHub Sponsors](https://github.com/users/dominikh/sponsorship) or [get in touch with me directly.](mailto:dominik@honnef.co) For [$250 USD a month](https://github.com/users/dominikh/sponsorship?utf8=%E2%9C%93&tier_id=MDIyOk1hcmtldHBsYWNlTGlzdGluZ1BsYW4yNTAy&editing=false), we will proudly display your logo on the project's homepage, showing the world that you truly care about code quality and open source. Finally, every single user, individual and company alike, is to be thanked for using Staticcheck, providing feedback, requesting features and in general caring about code quality. Without its users, there would be no Staticcheck. {{< /blocks/section >}} golang-honnef-go-tools-2023.1.7/website/data/000077500000000000000000000000001456614407100206145ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/website/data/checks.json000066400000000000000000003677441456614407100227740ustar00rootroot00000000000000{ "Checks": { "QF1001": { "Title": "Apply De Morgan's law", "Text": "", "TitleMarkdown": "Apply De Morgan's law", "TextMarkdown": "", "Before": "", "After": "", "Since": "2021.1", "NonDefault": false, "Options": null, "Severity": 5, "MergeIf": 0 }, "QF1002": { "Title": "Convert untagged switch to tagged switch", "Text": "An untagged switch that compares a single variable against a series of\nvalues can be replaced with a tagged switch.", "TitleMarkdown": "Convert untagged switch to tagged switch", "TextMarkdown": "An untagged switch that compares a single variable against a series of\nvalues can be replaced with a tagged switch.", "Before": "switch {\ncase x == 1 || x == 2, x == 3:\n ...\ncase x == 4:\n ...\ndefault:\n ...\n}", "After": "switch x {\ncase 1, 2, 3:\n ...\ncase 4:\n ...\ndefault:\n ...\n}", "Since": "2021.1", "NonDefault": false, "Options": null, "Severity": 5, "MergeIf": 0 }, "QF1003": { "Title": "Convert if/else-if chain to tagged switch", "Text": "A series of if/else-if checks comparing the same variable against\nvalues can be replaced with a tagged switch.", "TitleMarkdown": "Convert if/else-if chain to tagged switch", "TextMarkdown": "A series of if/else-if checks comparing the same variable against\nvalues can be replaced with a tagged switch.", "Before": "if x == 1 || x == 2 {\n ...\n} else if x == 3 {\n ...\n} else {\n ...\n}", "After": "switch x {\ncase 1, 2:\n ...\ncase 3:\n ...\ndefault:\n ...\n}", "Since": "2021.1", "NonDefault": false, "Options": null, "Severity": 4, "MergeIf": 0 }, "QF1004": { "Title": "Use strings.ReplaceAll instead of strings.Replace with n == -1", "Text": "", "TitleMarkdown": "Use `strings.ReplaceAll` instead of `strings.Replace` with `n == -1`", "TextMarkdown": "", "Before": "", "After": "", "Since": "2021.1", "NonDefault": false, "Options": null, "Severity": 5, "MergeIf": 0 }, "QF1005": { "Title": "Expand call to math.Pow", "Text": "Some uses of math.Pow can be simplified to basic multiplication.", "TitleMarkdown": "Expand call to `math.Pow`", "TextMarkdown": "Some uses of `math.Pow` can be simplified to basic multiplication.", "Before": "math.Pow(x, 2)", "After": "x * x", "Since": "2021.1", "NonDefault": false, "Options": null, "Severity": 5, "MergeIf": 0 }, "QF1006": { "Title": "Lift if+break into loop condition", "Text": "", "TitleMarkdown": "Lift `if`+`break` into loop condition", "TextMarkdown": "", "Before": "for {\n if done {\n break\n }\n ...\n}", "After": "for !done {\n ...\n}", "Since": "2021.1", "NonDefault": false, "Options": null, "Severity": 5, "MergeIf": 0 }, "QF1007": { "Title": "Merge conditional assignment into variable declaration", "Text": "", "TitleMarkdown": "Merge conditional assignment into variable declaration", "TextMarkdown": "", "Before": "x := false\nif someCondition {\n x = true\n}", "After": "x := someCondition", "Since": "2021.1", "NonDefault": false, "Options": null, "Severity": 5, "MergeIf": 0 }, "QF1008": { "Title": "Omit embedded fields from selector expression", "Text": "", "TitleMarkdown": "Omit embedded fields from selector expression", "TextMarkdown": "", "Before": "", "After": "", "Since": "2021.1", "NonDefault": false, "Options": null, "Severity": 5, "MergeIf": 0 }, "QF1009": { "Title": "Use time.Time.Equal instead of == operator", "Text": "", "TitleMarkdown": "Use `time.Time.Equal` instead of `==` operator", "TextMarkdown": "", "Before": "", "After": "", "Since": "2021.1", "NonDefault": false, "Options": null, "Severity": 4, "MergeIf": 0 }, "QF1010": { "Title": "Convert slice of bytes to string when printing it", "Text": "", "TitleMarkdown": "Convert slice of bytes to string when printing it", "TextMarkdown": "", "Before": "", "After": "", "Since": "2021.1", "NonDefault": false, "Options": null, "Severity": 5, "MergeIf": 0 }, "QF1011": { "Title": "Omit redundant type from variable declaration", "Text": "", "TitleMarkdown": "Omit redundant type from variable declaration", "TextMarkdown": "", "Before": "", "After": "", "Since": "2021.1", "NonDefault": false, "Options": null, "Severity": 5, "MergeIf": 0 }, "QF1012": { "Title": "Use fmt.Fprintf(x, ...) instead of x.Write(fmt.Sprintf(...))", "Text": "", "TitleMarkdown": "Use `fmt.Fprintf(x, ...)` instead of `x.Write(fmt.Sprintf(...))`", "TextMarkdown": "", "Before": "", "After": "", "Since": "2022.1", "NonDefault": false, "Options": null, "Severity": 5, "MergeIf": 0 }, "S1000": { "Title": "Use plain channel send or receive instead of single-case select", "Text": "Select statements with a single case can be replaced with a simple\nsend or receive.", "TitleMarkdown": "Use plain channel send or receive instead of single-case select", "TextMarkdown": "Select statements with a single case can be replaced with a simple\nsend or receive.", "Before": "select {\ncase x := \u003c-ch:\n fmt.Println(x)\n}", "After": "x := \u003c-ch\nfmt.Println(x)", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 0 }, "S1001": { "Title": "Replace for loop with call to copy", "Text": "Use copy() for copying elements from one slice to another. For\narrays of identical size, you can use simple assignment.", "TitleMarkdown": "Replace for loop with call to copy", "TextMarkdown": "Use `copy()` for copying elements from one slice to another. For\narrays of identical size, you can use simple assignment.", "Before": "for i, x := range src {\n dst[i] = x\n}", "After": "copy(dst, src)", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 1 }, "S1002": { "Title": "Omit comparison with boolean constant", "Text": "", "TitleMarkdown": "Omit comparison with boolean constant", "TextMarkdown": "", "Before": "if x == true {}", "After": "if x {}", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 1 }, "S1003": { "Title": "Replace call to strings.Index with strings.Contains", "Text": "", "TitleMarkdown": "Replace call to `strings.Index` with `strings.Contains`", "TextMarkdown": "", "Before": "if strings.Index(x, y) != -1 {}", "After": "if strings.Contains(x, y) {}", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 0 }, "S1004": { "Title": "Replace call to bytes.Compare with bytes.Equal", "Text": "", "TitleMarkdown": "Replace call to `bytes.Compare` with `bytes.Equal`", "TextMarkdown": "", "Before": "if bytes.Compare(x, y) == 0 {}", "After": "if bytes.Equal(x, y) {}", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 0 }, "S1005": { "Title": "Drop unnecessary use of the blank identifier", "Text": "In many cases, assigning to the blank identifier is unnecessary.", "TitleMarkdown": "Drop unnecessary use of the blank identifier", "TextMarkdown": "In many cases, assigning to the blank identifier is unnecessary.", "Before": "for _ = range s {}\nx, _ = someMap[key]\n_ = \u003c-ch", "After": "for range s{}\nx = someMap[key]\n\u003c-ch", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 0 }, "S1006": { "Title": "Use 'for { ... }' for infinite loops", "Text": "For infinite loops, using for { ... } is the most idiomatic choice.", "TitleMarkdown": "Use `for { ... }` for infinite loops", "TextMarkdown": "For infinite loops, using `for { ... }` is the most idiomatic choice.", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 0 }, "S1007": { "Title": "Simplify regular expression by using raw string literal", "Text": "Raw string literals use backticks instead of quotation marks and do not support\nany escape sequences. This means that the backslash can be used\nfreely, without the need of escaping.\n\nSince regular expressions have their own escape sequences, raw strings\ncan improve their readability.", "TitleMarkdown": "Simplify regular expression by using raw string literal", "TextMarkdown": "Raw string literals use backticks instead of quotation marks and do not support\nany escape sequences. This means that the backslash can be used\nfreely, without the need of escaping.\n\nSince regular expressions have their own escape sequences, raw strings\ncan improve their readability.", "Before": "regexp.Compile(\"\\\\A(\\\\w+) profile: total \\\\d+\\\\n\\\\z\")", "After": "regexp.Compile(`\\A(\\w+) profile: total \\d+\\n\\z`)", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 0 }, "S1008": { "Title": "Simplify returning boolean expression", "Text": "", "TitleMarkdown": "Simplify returning boolean expression", "TextMarkdown": "", "Before": "if \u003cexpr\u003e {\n return true\n}\nreturn false", "After": "return \u003cexpr\u003e", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 0 }, "S1009": { "Title": "Omit redundant nil check on slices", "Text": "The len function is defined for all slices, even nil ones, which have\na length of zero. It is not necessary to check if a slice is not nil\nbefore checking that its length is not zero.", "TitleMarkdown": "Omit redundant nil check on slices", "TextMarkdown": "The `len` function is defined for all slices, even nil ones, which have\na length of zero. It is not necessary to check if a slice is not nil\nbefore checking that its length is not zero.", "Before": "if x != nil \u0026\u0026 len(x) != 0 {}", "After": "if len(x) != 0 {}", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 0 }, "S1010": { "Title": "Omit default slice index", "Text": "When slicing, the second index defaults to the length of the value,\nmaking s[n:len(s)] and s[n:] equivalent.", "TitleMarkdown": "Omit default slice index", "TextMarkdown": "When slicing, the second index defaults to the length of the value,\nmaking `s[n:len(s)]` and `s[n:]` equivalent.", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 0 }, "S1011": { "Title": "Use a single append to concatenate two slices", "Text": "", "TitleMarkdown": "Use a single `append` to concatenate two slices", "TextMarkdown": "", "Before": "for _, e := range y {\n x = append(x, e)\n}\n\nfor i := range y {\n x = append(x, y[i])\n}\n\nfor i := range y {\n v := y[i]\n x = append(x, v)\n}", "After": "x = append(x, y...)\nx = append(x, y...)\nx = append(x, y...)", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 1 }, "S1012": { "Title": "Replace time.Now().Sub(x) with time.Since(x)", "Text": "The time.Since helper has the same effect as using time.Now().Sub(x)\nbut is easier to read.", "TitleMarkdown": "Replace `time.Now().Sub(x)` with `time.Since(x)`", "TextMarkdown": "The `time.Since` helper has the same effect as using `time.Now().Sub(x)`\nbut is easier to read.", "Before": "time.Now().Sub(x)", "After": "time.Since(x)", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 0 }, "S1016": { "Title": "Use a type conversion instead of manually copying struct fields", "Text": "Two struct types with identical fields can be converted between each\nother. In older versions of Go, the fields had to have identical\nstruct tags. Since Go 1.8, however, struct tags are ignored during\nconversions. It is thus not necessary to manually copy every field\nindividually.", "TitleMarkdown": "Use a type conversion instead of manually copying struct fields", "TextMarkdown": "Two struct types with identical fields can be converted between each\nother. In older versions of Go, the fields had to have identical\nstruct tags. Since Go 1.8, however, struct tags are ignored during\nconversions. It is thus not necessary to manually copy every field\nindividually.", "Before": "var x T1\ny := T2{\n Field1: x.Field1,\n Field2: x.Field2,\n}", "After": "var x T1\ny := T2(x)", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 1 }, "S1017": { "Title": "Replace manual trimming with strings.TrimPrefix", "Text": "Instead of using strings.HasPrefix and manual slicing, use the\nstrings.TrimPrefix function. If the string doesn't start with the\nprefix, the original string will be returned. Using strings.TrimPrefix\nreduces complexity, and avoids common bugs, such as off-by-one\nmistakes.", "TitleMarkdown": "Replace manual trimming with `strings.TrimPrefix`", "TextMarkdown": "Instead of using `strings.HasPrefix` and manual slicing, use the\n`strings.TrimPrefix` function. If the string doesn't start with the\nprefix, the original string will be returned. Using `strings.TrimPrefix`\nreduces complexity, and avoids common bugs, such as off-by-one\nmistakes.", "Before": "if strings.HasPrefix(str, prefix) {\n str = str[len(prefix):]\n}", "After": "str = strings.TrimPrefix(str, prefix)", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 0 }, "S1018": { "Title": "Use 'copy' for sliding elements", "Text": "copy() permits using the same source and destination slice, even with\noverlapping ranges. This makes it ideal for sliding elements in a\nslice.", "TitleMarkdown": "Use `copy` for sliding elements", "TextMarkdown": "`copy()` permits using the same source and destination slice, even with\noverlapping ranges. This makes it ideal for sliding elements in a\nslice.", "Before": "for i := 0; i \u003c n; i++ {\n bs[i] = bs[offset+i]\n}", "After": "copy(bs[:n], bs[offset:])", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 0 }, "S1019": { "Title": "Simplify 'make' call by omitting redundant arguments", "Text": "The 'make' function has default values for the length and capacity\narguments. For channels, the length defaults to zero, and for slices,\nthe capacity defaults to the length.", "TitleMarkdown": "Simplify `make` call by omitting redundant arguments", "TextMarkdown": "The `make` function has default values for the length and capacity\narguments. For channels, the length defaults to zero, and for slices,\nthe capacity defaults to the length.", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 1 }, "S1020": { "Title": "Omit redundant nil check in type assertion", "Text": "", "TitleMarkdown": "Omit redundant nil check in type assertion", "TextMarkdown": "", "Before": "if _, ok := i.(T); ok \u0026\u0026 i != nil {}", "After": "if _, ok := i.(T); ok {}", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 0 }, "S1021": { "Title": "Merge variable declaration and assignment", "Text": "", "TitleMarkdown": "Merge variable declaration and assignment", "TextMarkdown": "", "Before": "var x uint\nx = 1", "After": "var x uint = 1", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 0 }, "S1023": { "Title": "Omit redundant control flow", "Text": "Functions that have no return value do not need a return statement as\nthe final statement of the function.\n\nSwitches in Go do not have automatic fallthrough, unlike languages\nlike C. It is not necessary to have a break statement as the final\nstatement in a case block.", "TitleMarkdown": "Omit redundant control flow", "TextMarkdown": "Functions that have no return value do not need a return statement as\nthe final statement of the function.\n\nSwitches in Go do not have automatic fallthrough, unlike languages\nlike C. It is not necessary to have a break statement as the final\nstatement in a case block.", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 0 }, "S1024": { "Title": "Replace x.Sub(time.Now()) with time.Until(x)", "Text": "The time.Until helper has the same effect as using x.Sub(time.Now())\nbut is easier to read.", "TitleMarkdown": "Replace `x.Sub(time.Now())` with `time.Until(x)`", "TextMarkdown": "The `time.Until` helper has the same effect as using `x.Sub(time.Now())`\nbut is easier to read.", "Before": "x.Sub(time.Now())", "After": "time.Until(x)", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 0 }, "S1025": { "Title": "Don't use fmt.Sprintf(\"%s\", x) unnecessarily", "Text": "In many instances, there are easier and more efficient ways of getting\na value's string representation. Whenever a value's underlying type is\na string already, or the type has a String method, they should be used\ndirectly.\n\nGiven the following shared definitions\n\n```go\ntype T1 string\ntype T2 int\n\nfunc (T2) String() string { return \"Hello, world\" }\n\nvar x string\nvar y T1\nvar z T2\n```\n\nwe can simplify\n\n```go\nfmt.Sprintf(\"%s\", x)\nfmt.Sprintf(\"%s\", y)\nfmt.Sprintf(\"%s\", z)\n```\n\nto\n\n```go\nx\nstring(y)\nz.String()\n```", "TitleMarkdown": "Don't use `fmt.Sprintf(\"%s\", x)` unnecessarily", "TextMarkdown": "In many instances, there are easier and more efficient ways of getting\na value's string representation. Whenever a value's underlying type is\na string already, or the type has a String method, they should be used\ndirectly.\n\nGiven the following shared definitions\n\n```go\ntype T1 string\ntype T2 int\n\nfunc (T2) String() string { return \"Hello, world\" }\n\nvar x string\nvar y T1\nvar z T2\n```\n\nwe can simplify\n\n```go\nfmt.Sprintf(\"%s\", x)\nfmt.Sprintf(\"%s\", y)\nfmt.Sprintf(\"%s\", z)\n```\n\nto\n\n```go\nx\nstring(y)\nz.String()\n```", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 1 }, "S1028": { "Title": "Simplify error construction with fmt.Errorf", "Text": "", "TitleMarkdown": "Simplify error construction with `fmt.Errorf`", "TextMarkdown": "", "Before": "errors.New(fmt.Sprintf(...))", "After": "fmt.Errorf(...)", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 0 }, "S1029": { "Title": "Range over the string directly", "Text": "Ranging over a string will yield byte offsets and runes. If the offset\nisn't used, this is functionally equivalent to converting the string\nto a slice of runes and ranging over that. Ranging directly over the\nstring will be more performant, however, as it avoids allocating a new\nslice, the size of which depends on the length of the string.", "TitleMarkdown": "Range over the string directly", "TextMarkdown": "Ranging over a string will yield byte offsets and runes. If the offset\nisn't used, this is functionally equivalent to converting the string\nto a slice of runes and ranging over that. Ranging directly over the\nstring will be more performant, however, as it avoids allocating a new\nslice, the size of which depends on the length of the string.", "Before": "for _, r := range []rune(s) {}", "After": "for _, r := range s {}", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 0 }, "S1030": { "Title": "Use bytes.Buffer.String or bytes.Buffer.Bytes", "Text": "bytes.Buffer has both a String and a Bytes method. It is almost never\nnecessary to use string(buf.Bytes()) or []byte(buf.String()) – simply\nuse the other method.\n\nThe only exception to this are map lookups. Due to a compiler optimization,\nm[string(buf.Bytes())] is more efficient than m[buf.String()].", "TitleMarkdown": "Use `bytes.Buffer.String` or `bytes.Buffer.Bytes`", "TextMarkdown": "`bytes.Buffer` has both a `String` and a `Bytes` method. It is almost never\nnecessary to use `string(buf.Bytes())` or `[]byte(buf.String())` – simply\nuse the other method.\n\nThe only exception to this are map lookups. Due to a compiler optimization,\n`m[string(buf.Bytes())]` is more efficient than `m[buf.String()]`.", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 0 }, "S1031": { "Title": "Omit redundant nil check around loop", "Text": "You can use range on nil slices and maps, the loop will simply never\nexecute. This makes an additional nil check around the loop\nunnecessary.", "TitleMarkdown": "Omit redundant nil check around loop", "TextMarkdown": "You can use range on nil slices and maps, the loop will simply never\nexecute. This makes an additional nil check around the loop\nunnecessary.", "Before": "if s != nil {\n for _, x := range s {\n ...\n }\n}", "After": "for _, x := range s {\n ...\n}", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 1 }, "S1032": { "Title": "Use sort.Ints(x), sort.Float64s(x), and sort.Strings(x)", "Text": "The sort.Ints, sort.Float64s and sort.Strings functions are easier to\nread than sort.Sort(sort.IntSlice(x)), sort.Sort(sort.Float64Slice(x))\nand sort.Sort(sort.StringSlice(x)).", "TitleMarkdown": "Use `sort.Ints(x)`, `sort.Float64s(x)`, and `sort.Strings(x)`", "TextMarkdown": "The `sort.Ints`, `sort.Float64s` and `sort.Strings` functions are easier to\nread than `sort.Sort(sort.IntSlice(x))`, `sort.Sort(sort.Float64Slice(x))`\nand `sort.Sort(sort.StringSlice(x))`.", "Before": "sort.Sort(sort.StringSlice(x))", "After": "sort.Strings(x)", "Since": "2019.1", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 0 }, "S1033": { "Title": "Unnecessary guard around call to 'delete'", "Text": "Calling delete on a nil map is a no-op.", "TitleMarkdown": "Unnecessary guard around call to `delete`", "TextMarkdown": "Calling `delete` on a nil map is a no-op.", "Before": "", "After": "", "Since": "2019.2", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 0 }, "S1034": { "Title": "Use result of type assertion to simplify cases", "Text": "", "TitleMarkdown": "Use result of type assertion to simplify cases", "TextMarkdown": "", "Before": "", "After": "", "Since": "2019.2", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 0 }, "S1035": { "Title": "Redundant call to net/http.CanonicalHeaderKey in method call on net/http.Header", "Text": "The methods on net/http.Header, namely Add, Del, Get\nand Set, already canonicalize the given header name.", "TitleMarkdown": "Redundant call to `net/http.CanonicalHeaderKey` in method call on `net/http.Header`", "TextMarkdown": "The methods on `net/http.Header`, namely `Add`, `Del`, `Get`\nand `Set`, already canonicalize the given header name.", "Before": "", "After": "", "Since": "2020.1", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 0 }, "S1036": { "Title": "Unnecessary guard around map access", "Text": "When accessing a map key that doesn't exist yet, one receives a zero\nvalue. Often, the zero value is a suitable value, for example when\nusing append or doing integer math.\n\nThe following\n\n```go\nif _, ok := m[\"foo\"]; ok {\n m[\"foo\"] = append(m[\"foo\"], \"bar\")\n} else {\n m[\"foo\"] = []string{\"bar\"}\n}\n```\n\ncan be simplified to\n\n```go\nm[\"foo\"] = append(m[\"foo\"], \"bar\")\n```\n\nand\n\n```go\nif _, ok := m2[\"k\"]; ok {\n m2[\"k\"] += 4\n} else {\n m2[\"k\"] = 4\n}\n```\n\ncan be simplified to\n\n```go\nm[\"k\"] += 4\n```", "TitleMarkdown": "Unnecessary guard around map access", "TextMarkdown": "When accessing a map key that doesn't exist yet, one receives a zero\nvalue. Often, the zero value is a suitable value, for example when\nusing append or doing integer math.\n\nThe following\n\n```go\nif _, ok := m[\"foo\"]; ok {\n m[\"foo\"] = append(m[\"foo\"], \"bar\")\n} else {\n m[\"foo\"] = []string{\"bar\"}\n}\n```\n\ncan be simplified to\n\n```go\nm[\"foo\"] = append(m[\"foo\"], \"bar\")\n```\n\nand\n\n```go\nif _, ok := m2[\"k\"]; ok {\n m2[\"k\"] += 4\n} else {\n m2[\"k\"] = 4\n}\n```\n\ncan be simplified to\n\n```go\nm[\"k\"] += 4\n```", "Before": "", "After": "", "Since": "2020.1", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 0 }, "S1037": { "Title": "Elaborate way of sleeping", "Text": "Using a select statement with a single case receiving\nfrom the result of time.After is a very elaborate way of sleeping that\ncan much simpler be expressed with a simple call to time.Sleep.", "TitleMarkdown": "Elaborate way of sleeping", "TextMarkdown": "Using a select statement with a single case receiving\nfrom the result of `time.After` is a very elaborate way of sleeping that\ncan much simpler be expressed with a simple call to time.Sleep.", "Before": "", "After": "", "Since": "2020.1", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 0 }, "S1038": { "Title": "Unnecessarily complex way of printing formatted string", "Text": "Instead of using fmt.Print(fmt.Sprintf(...)), one can use fmt.Printf(...).", "TitleMarkdown": "Unnecessarily complex way of printing formatted string", "TextMarkdown": "Instead of using `fmt.Print(fmt.Sprintf(...))`, one can use `fmt.Printf(...)`.", "Before": "", "After": "", "Since": "2020.1", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 0 }, "S1039": { "Title": "Unnecessary use of fmt.Sprint", "Text": "Calling fmt.Sprint with a single string argument is unnecessary\nand identical to using the string directly.", "TitleMarkdown": "Unnecessary use of `fmt.Sprint`", "TextMarkdown": "Calling `fmt.Sprint` with a single string argument is unnecessary\nand identical to using the string directly.", "Before": "", "After": "", "Since": "2020.1", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 1 }, "S1040": { "Title": "Type assertion to current type", "Text": "The type assertion x.(SomeInterface), when x already has type\nSomeInterface, can only fail if x is nil. Usually, this is\nleft-over code from when x had a different type and you can safely\ndelete the type assertion. If you want to check that x is not nil,\nconsider being explicit and using an actual if x == nil comparison\ninstead of relying on the type assertion panicking.", "TitleMarkdown": "Type assertion to current type", "TextMarkdown": "The type assertion `x.(SomeInterface)`, when `x` already has type\n`SomeInterface`, can only fail if `x` is nil. Usually, this is\nleft-over code from when `x` had a different type and you can safely\ndelete the type assertion. If you want to check that `x` is not nil,\nconsider being explicit and using an actual `if x == nil` comparison\ninstead of relying on the type assertion panicking.", "Before": "", "After": "", "Since": "2021.1", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 1 }, "SA1000": { "Title": "Invalid regular expression", "Text": "", "TitleMarkdown": "Invalid regular expression", "TextMarkdown": "", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 1, "MergeIf": 0 }, "SA1001": { "Title": "Invalid template", "Text": "", "TitleMarkdown": "Invalid template", "TextMarkdown": "", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 1, "MergeIf": 0 }, "SA1002": { "Title": "Invalid format in time.Parse", "Text": "", "TitleMarkdown": "Invalid format in `time.Parse`", "TextMarkdown": "", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 1, "MergeIf": 0 }, "SA1003": { "Title": "Unsupported argument to functions in encoding/binary", "Text": "The encoding/binary package can only serialize types with known sizes.\nThis precludes the use of the int and uint types, as their sizes\ndiffer on different architectures. Furthermore, it doesn't support\nserializing maps, channels, strings, or functions.\n\nBefore Go 1.8, bool wasn't supported, either.", "TitleMarkdown": "Unsupported argument to functions in `encoding/binary`", "TextMarkdown": "The `encoding/binary` package can only serialize types with known sizes.\nThis precludes the use of the `int` and `uint` types, as their sizes\ndiffer on different architectures. Furthermore, it doesn't support\nserializing maps, channels, strings, or functions.\n\nBefore Go 1.8, `bool` wasn't supported, either.", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 1, "MergeIf": 0 }, "SA1004": { "Title": "Suspiciously small untyped constant in time.Sleep", "Text": "The time.Sleep function takes a time.Duration as its only argument.\nDurations are expressed in nanoseconds. Thus, calling time.Sleep(1)\nwill sleep for 1 nanosecond. This is a common source of bugs, as sleep\nfunctions in other languages often accept seconds or milliseconds.\n\nThe time package provides constants such as time.Second to express\nlarge durations. These can be combined with arithmetic to express\narbitrary durations, for example 5 * time.Second for 5 seconds.\n\nIf you truly meant to sleep for a tiny amount of time, use\nn * time.Nanosecond to signal to Staticcheck that you did mean to sleep\nfor some amount of nanoseconds.", "TitleMarkdown": "Suspiciously small untyped constant in `time.Sleep`", "TextMarkdown": "The `time`.Sleep function takes a `time.Duration` as its only argument.\nDurations are expressed in nanoseconds. Thus, calling `time.Sleep(1)`\nwill sleep for 1 nanosecond. This is a common source of bugs, as sleep\nfunctions in other languages often accept seconds or milliseconds.\n\nThe `time` package provides constants such as `time.Second` to express\nlarge durations. These can be combined with arithmetic to express\narbitrary durations, for example `5 * time.Second` for 5 seconds.\n\nIf you truly meant to sleep for a tiny amount of time, use\n`n * time.Nanosecond` to signal to Staticcheck that you did mean to sleep\nfor some amount of nanoseconds.", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA1005": { "Title": "Invalid first argument to exec.Command", "Text": "os/exec runs programs directly (using variants of the fork and exec\nsystem calls on Unix systems). This shouldn't be confused with running\na command in a shell. The shell will allow for features such as input\nredirection, pipes, and general scripting. The shell is also\nresponsible for splitting the user's input into a program name and its\narguments. For example, the equivalent to\n\n```go\nls / /tmp\n```\n\nwould be\n\n```go\nexec.Command(\"ls\", \"/\", \"/tmp\")\n```\n\nIf you want to run a command in a shell, consider using something like\nthe following – but be aware that not all systems, particularly\nWindows, will have a /bin/sh program:\n\n```go\nexec.Command(\"/bin/sh\", \"-c\", \"ls | grep Awesome\")\n```", "TitleMarkdown": "Invalid first argument to `exec.Command`", "TextMarkdown": "`os/exec` runs programs directly (using variants of the fork and exec\nsystem calls on Unix systems). This shouldn't be confused with running\na command in a shell. The shell will allow for features such as input\nredirection, pipes, and general scripting. The shell is also\nresponsible for splitting the user's input into a program name and its\narguments. For example, the equivalent to\n\n```go\nls / /tmp\n```\n\nwould be\n\n```go\nexec.Command(\"ls\", \"/\", \"/tmp\")\n```\n\nIf you want to run a command in a shell, consider using something like\nthe following – but be aware that not all systems, particularly\nWindows, will have a `/bin/sh` program:\n\n```go\nexec.Command(\"/bin/sh\", \"-c\", \"ls | grep Awesome\")\n```", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA1006": { "Title": "Printf with dynamic first argument and no further arguments", "Text": "Using fmt.Printf with a dynamic first argument can lead to unexpected\noutput. The first argument is a format string, where certain character\ncombinations have special meaning. If, for example, a user were to\nenter a string such as\n\n```go\nInterest rate: 5%\n```\n\nand you printed it with\n\n```go\nfmt.Printf(s)\n```\n\nit would lead to the following output:\n\n```go\nInterest rate: 5%!(NOVERB).\n```\n\nSimilarly, forming the first parameter via string concatenation with\nuser input should be avoided for the same reason. When printing user\ninput, either use a variant of fmt.Print, or use the %s Printf verb\nand pass the string as an argument.", "TitleMarkdown": "`Printf` with dynamic first argument and no further arguments", "TextMarkdown": "Using `fmt.Printf` with a dynamic first argument can lead to unexpected\noutput. The first argument is a format string, where certain character\ncombinations have special meaning. If, for example, a user were to\nenter a string such as\n\n```go\nInterest rate: 5%\n```\n\nand you printed it with\n\n```go\nfmt.Printf(s)\n```\n\nit would lead to the following output:\n\n```go\nInterest rate: 5%!(NOVERB).\n```\n\nSimilarly, forming the first parameter via string concatenation with\nuser input should be avoided for the same reason. When printing user\ninput, either use a variant of `fmt.Print`, or use the `%s` Printf verb\nand pass the string as an argument.", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA1007": { "Title": "Invalid URL in net/url.Parse", "Text": "", "TitleMarkdown": "Invalid URL in `net/url.Parse`", "TextMarkdown": "", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 1, "MergeIf": 0 }, "SA1008": { "Title": "Non-canonical key in http.Header map", "Text": "Keys in http.Header maps are canonical, meaning they follow a specific\ncombination of uppercase and lowercase letters. Methods such as\nhttp.Header.Add and http.Header.Del convert inputs into this canonical\nform before manipulating the map.\n\nWhen manipulating http.Header maps directly, as opposed to using the\nprovided methods, care should be taken to stick to canonical form in\norder to avoid inconsistencies. The following piece of code\ndemonstrates one such inconsistency:\n\n```go\nh := http.Header{}\nh[\"etag\"] = []string{\"1234\"}\nh.Add(\"etag\", \"5678\")\nfmt.Println(h)\n\n// Output:\n// map[Etag:[5678] etag:[1234]]\n```\n\nThe easiest way of obtaining the canonical form of a key is to use\nhttp.CanonicalHeaderKey.", "TitleMarkdown": "Non-canonical key in `http.Header` map", "TextMarkdown": "Keys in `http.Header` maps are canonical, meaning they follow a specific\ncombination of uppercase and lowercase letters. Methods such as\n`http.Header.Add` and `http.Header.Del` convert inputs into this canonical\nform before manipulating the map.\n\nWhen manipulating `http.Header` maps directly, as opposed to using the\nprovided methods, care should be taken to stick to canonical form in\norder to avoid inconsistencies. The following piece of code\ndemonstrates one such inconsistency:\n\n```go\nh := http.Header{}\nh[\"etag\"] = []string{\"1234\"}\nh.Add(\"etag\", \"5678\")\nfmt.Println(h)\n\n// Output:\n// map[Etag:[5678] etag:[1234]]\n```\n\nThe easiest way of obtaining the canonical form of a key is to use\n`http.CanonicalHeaderKey`.", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA1010": { "Title": "(*regexp.Regexp).FindAll called with n == 0, which will always return zero results", "Text": "If n \u003e= 0, the function returns at most n matches/submatches. To\nreturn all results, specify a negative number.", "TitleMarkdown": "`(*regexp.Regexp).FindAll` called with `n == 0`, which will always return zero results", "TextMarkdown": "If `n \u003e= 0`, the function returns at most `n` matches/submatches. To\nreturn all results, specify a negative number.", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA1011": { "Title": "Various methods in the 'strings' package expect valid UTF-8, but invalid input is provided", "Text": "", "TitleMarkdown": "Various methods in the `strings` package expect valid UTF-8, but invalid input is provided", "TextMarkdown": "", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 1, "MergeIf": 0 }, "SA1012": { "Title": "A nil context.Context is being passed to a function, consider using context.TODO instead", "Text": "", "TitleMarkdown": "A nil `context.Context` is being passed to a function, consider using `context.TODO` instead", "TextMarkdown": "", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA1013": { "Title": "io.Seeker.Seek is being called with the whence constant as the first argument, but it should be the second", "Text": "", "TitleMarkdown": "`io.Seeker.Seek` is being called with the whence constant as the first argument, but it should be the second", "TextMarkdown": "", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA1014": { "Title": "Non-pointer value passed to Unmarshal or Decode", "Text": "", "TitleMarkdown": "Non-pointer value passed to `Unmarshal` or `Decode`", "TextMarkdown": "", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 1, "MergeIf": 0 }, "SA1015": { "Title": "Using time.Tick in a way that will leak. Consider using time.NewTicker, and only use time.Tick in tests, commands and endless functions", "Text": "", "TitleMarkdown": "Using `time.Tick` in a way that will leak. Consider using `time.NewTicker`, and only use `time.Tick` in tests, commands and endless functions", "TextMarkdown": "", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA1016": { "Title": "Trapping a signal that cannot be trapped", "Text": "Not all signals can be intercepted by a process. Specifically, on\nUNIX-like systems, the syscall.SIGKILL and syscall.SIGSTOP signals are\nnever passed to the process, but instead handled directly by the\nkernel. It is therefore pointless to try and handle these signals.", "TitleMarkdown": "Trapping a signal that cannot be trapped", "TextMarkdown": "Not all signals can be intercepted by a process. Specifically, on\nUNIX-like systems, the `syscall.SIGKILL` and `syscall.SIGSTOP` signals are\nnever passed to the process, but instead handled directly by the\nkernel. It is therefore pointless to try and handle these signals.", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA1017": { "Title": "Channels used with os/signal.Notify should be buffered", "Text": "The os/signal package uses non-blocking channel sends when delivering\nsignals. If the receiving end of the channel isn't ready and the\nchannel is either unbuffered or full, the signal will be dropped. To\navoid missing signals, the channel should be buffered and of the\nappropriate size. For a channel used for notification of just one\nsignal value, a buffer of size 1 is sufficient.", "TitleMarkdown": "Channels used with `os/signal.Notify` should be buffered", "TextMarkdown": "The `os/signal` package uses non-blocking channel sends when delivering\nsignals. If the receiving end of the channel isn't ready and the\nchannel is either unbuffered or full, the signal will be dropped. To\navoid missing signals, the channel should be buffered and of the\nappropriate size. For a channel used for notification of just one\nsignal value, a buffer of size 1 is sufficient.", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA1018": { "Title": "strings.Replace called with n == 0, which does nothing", "Text": "With n == 0, zero instances will be replaced. To replace all\ninstances, use a negative number, or use strings.ReplaceAll.", "TitleMarkdown": "`strings.Replace` called with `n == 0`, which does nothing", "TextMarkdown": "With `n == 0`, zero instances will be replaced. To replace all\ninstances, use a negative number, or use `strings.ReplaceAll`.", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA1019": { "Title": "Using a deprecated function, variable, constant or field", "Text": "", "TitleMarkdown": "Using a deprecated function, variable, constant or field", "TextMarkdown": "", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 2, "MergeIf": 0 }, "SA1020": { "Title": "Using an invalid host:port pair with a net.Listen-related function", "Text": "", "TitleMarkdown": "Using an invalid host:port pair with a `net.Listen`-related function", "TextMarkdown": "", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 1, "MergeIf": 0 }, "SA1021": { "Title": "Using bytes.Equal to compare two net.IP", "Text": "A net.IP stores an IPv4 or IPv6 address as a slice of bytes. The\nlength of the slice for an IPv4 address, however, can be either 4 or\n16 bytes long, using different ways of representing IPv4 addresses. In\norder to correctly compare two net.IPs, the net.IP.Equal method should\nbe used, as it takes both representations into account.", "TitleMarkdown": "Using `bytes.Equal` to compare two `net.IP`", "TextMarkdown": "A `net.IP` stores an IPv4 or IPv6 address as a slice of bytes. The\nlength of the slice for an IPv4 address, however, can be either 4 or\n16 bytes long, using different ways of representing IPv4 addresses. In\norder to correctly compare two `net.IP`s, the `net.IP.Equal` method should\nbe used, as it takes both representations into account.", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA1023": { "Title": "Modifying the buffer in an io.Writer implementation", "Text": "Write must not modify the slice data, even temporarily.", "TitleMarkdown": "Modifying the buffer in an `io.Writer` implementation", "TextMarkdown": "`Write` must not modify the slice data, even temporarily.", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 1, "MergeIf": 0 }, "SA1024": { "Title": "A string cutset contains duplicate characters", "Text": "The strings.TrimLeft and strings.TrimRight functions take cutsets, not\nprefixes. A cutset is treated as a set of characters to remove from a\nstring. For example,\n\n```go\nstrings.TrimLeft(\"42133word\", \"1234\")\n```\n\nwill result in the string \"word\" – any characters that are 1, 2, 3 or\n4 are cut from the left of the string.\n\nIn order to remove one string from another, use strings.TrimPrefix instead.", "TitleMarkdown": "A string cutset contains duplicate characters", "TextMarkdown": "The `strings.TrimLeft` and `strings.TrimRight` functions take cutsets, not\nprefixes. A cutset is treated as a set of characters to remove from a\nstring. For example,\n\n```go\nstrings.TrimLeft(\"42133word\", \"1234\")\n```\n\nwill result in the string `\"word\"` – any characters that are 1, 2, 3 or\n4 are cut from the left of the string.\n\nIn order to remove one string from another, use `strings.TrimPrefix` instead.", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA1025": { "Title": "It is not possible to use (*time.Timer).Reset's return value correctly", "Text": "", "TitleMarkdown": "It is not possible to use `(*time.Timer).Reset`'s return value correctly", "TextMarkdown": "", "Before": "", "After": "", "Since": "2019.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA1026": { "Title": "Cannot marshal channels or functions", "Text": "", "TitleMarkdown": "Cannot marshal channels or functions", "TextMarkdown": "", "Before": "", "After": "", "Since": "2019.2", "NonDefault": false, "Options": null, "Severity": 1, "MergeIf": 0 }, "SA1027": { "Title": "Atomic access to 64-bit variable must be 64-bit aligned", "Text": "On ARM, x86-32, and 32-bit MIPS, it is the caller's responsibility to\narrange for 64-bit alignment of 64-bit words accessed atomically. The\nfirst word in a variable or in an allocated struct, array, or slice\ncan be relied upon to be 64-bit aligned.\n\nYou can use the structlayout tool to inspect the alignment of fields\nin a struct.", "TitleMarkdown": "Atomic access to 64-bit variable must be 64-bit aligned", "TextMarkdown": "On ARM, x86-32, and 32-bit MIPS, it is the caller's responsibility to\narrange for 64-bit alignment of 64-bit words accessed atomically. The\nfirst word in a variable or in an allocated struct, array, or slice\ncan be relied upon to be 64-bit aligned.\n\nYou can use the structlayout tool to inspect the alignment of fields\nin a struct.", "Before": "", "After": "", "Since": "2019.2", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA1028": { "Title": "sort.Slice can only be used on slices", "Text": "The first argument of sort.Slice must be a slice.", "TitleMarkdown": "`sort.Slice` can only be used on slices", "TextMarkdown": "The first argument of `sort.Slice` must be a slice.", "Before": "", "After": "", "Since": "2020.1", "NonDefault": false, "Options": null, "Severity": 1, "MergeIf": 0 }, "SA1029": { "Title": "Inappropriate key in call to context.WithValue", "Text": "The provided key must be comparable and should not be\nof type string or any other built-in type to avoid collisions between\npackages using context. Users of WithValue should define their own\ntypes for keys.\n\nTo avoid allocating when assigning to an interface{},\ncontext keys often have concrete type struct{}. Alternatively,\nexported context key variables' static type should be a pointer or\ninterface.", "TitleMarkdown": "Inappropriate key in call to `context.WithValue`", "TextMarkdown": "The provided key must be comparable and should not be\nof type `string` or any other built-in type to avoid collisions between\npackages using context. Users of `WithValue` should define their own\ntypes for keys.\n\nTo avoid allocating when assigning to an `interface{}`,\ncontext keys often have concrete type `struct{}`. Alternatively,\nexported context key variables' static type should be a pointer or\ninterface.", "Before": "", "After": "", "Since": "2020.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA1030": { "Title": "Invalid argument in call to a strconv function", "Text": "This check validates the format, number base and bit size arguments of\nthe various parsing and formatting functions in strconv.", "TitleMarkdown": "Invalid argument in call to a `strconv` function", "TextMarkdown": "This check validates the format, number base and bit size arguments of\nthe various parsing and formatting functions in `strconv`.", "Before": "", "After": "", "Since": "2021.1", "NonDefault": false, "Options": null, "Severity": 1, "MergeIf": 0 }, "SA2000": { "Title": "sync.WaitGroup.Add called inside the goroutine, leading to a race condition", "Text": "", "TitleMarkdown": "`sync.WaitGroup.Add` called inside the goroutine, leading to a race condition", "TextMarkdown": "", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA2001": { "Title": "Empty critical section, did you mean to defer the unlock?", "Text": "Empty critical sections of the kind\n\n```go\nmu.Lock()\nmu.Unlock()\n```\n\nare very often a typo, and the following was intended instead:\n\n```go\nmu.Lock()\ndefer mu.Unlock()\n```\n\nDo note that sometimes empty critical sections can be useful, as a\nform of signaling to wait on another goroutine. Many times, there are\nsimpler ways of achieving the same effect. When that isn't the case,\nthe code should be amply commented to avoid confusion. Combining such\ncomments with a //lint:ignore directive can be used to suppress this\nrare false positive.", "TitleMarkdown": "Empty critical section, did you mean to defer the unlock?", "TextMarkdown": "Empty critical sections of the kind\n\n```go\nmu.Lock()\nmu.Unlock()\n```\n\nare very often a typo, and the following was intended instead:\n\n```go\nmu.Lock()\ndefer mu.Unlock()\n```\n\nDo note that sometimes empty critical sections can be useful, as a\nform of signaling to wait on another goroutine. Many times, there are\nsimpler ways of achieving the same effect. When that isn't the case,\nthe code should be amply commented to avoid confusion. Combining such\ncomments with a `//lint:ignore` directive can be used to suppress this\nrare false positive.", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA2002": { "Title": "Called testing.T.FailNow or SkipNow in a goroutine, which isn't allowed", "Text": "", "TitleMarkdown": "Called `testing.T.FailNow` or `SkipNow` in a goroutine, which isn't allowed", "TextMarkdown": "", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 1, "MergeIf": 0 }, "SA2003": { "Title": "Deferred Lock right after locking, likely meant to defer Unlock instead", "Text": "", "TitleMarkdown": "Deferred `Lock` right after locking, likely meant to defer `Unlock` instead", "TextMarkdown": "", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA3000": { "Title": "TestMain doesn't call os.Exit, hiding test failures", "Text": "Test executables (and in turn 'go test') exit with a non-zero status\ncode if any tests failed. When specifying your own TestMain function,\nit is your responsibility to arrange for this, by calling os.Exit with\nthe correct code. The correct code is returned by (*testing.M).Run, so\nthe usual way of implementing TestMain is to end it with\nos.Exit(m.Run()).", "TitleMarkdown": "`TestMain` doesn't call `os.Exit`, hiding test failures", "TextMarkdown": "Test executables (and in turn `go test`) exit with a non-zero status\ncode if any tests failed. When specifying your own `TestMain` function,\nit is your responsibility to arrange for this, by calling `os.Exit` with\nthe correct code. The correct code is returned by `(*testing.M).Run`, so\nthe usual way of implementing `TestMain` is to end it with\n`os.Exit(m.Run())`.", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA3001": { "Title": "Assigning to b.N in benchmarks distorts the results", "Text": "The testing package dynamically sets b.N to improve the reliability of\nbenchmarks and uses it in computations to determine the duration of a\nsingle operation. Benchmark code must not alter b.N as this would\nfalsify results.", "TitleMarkdown": "Assigning to `b.N` in benchmarks distorts the results", "TextMarkdown": "The testing package dynamically sets `b.N` to improve the reliability of\nbenchmarks and uses it in computations to determine the duration of a\nsingle operation. Benchmark code must not alter `b.N` as this would\nfalsify results.", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 1, "MergeIf": 0 }, "SA4000": { "Title": "Binary operator has identical expressions on both sides", "Text": "", "TitleMarkdown": "Binary operator has identical expressions on both sides", "TextMarkdown": "", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA4001": { "Title": "\u0026*x gets simplified to x, it does not copy x", "Text": "", "TitleMarkdown": "`\u0026*x` gets simplified to `x`, it does not copy `x`", "TextMarkdown": "", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA4003": { "Title": "Comparing unsigned values against negative values is pointless", "Text": "", "TitleMarkdown": "Comparing unsigned values against negative values is pointless", "TextMarkdown": "", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 1 }, "SA4004": { "Title": "The loop exits unconditionally after one iteration", "Text": "", "TitleMarkdown": "The loop exits unconditionally after one iteration", "TextMarkdown": "", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 1 }, "SA4005": { "Title": "Field assignment that will never be observed. Did you mean to use a pointer receiver?", "Text": "", "TitleMarkdown": "Field assignment that will never be observed. Did you mean to use a pointer receiver?", "TextMarkdown": "", "Before": "", "After": "", "Since": "2021.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA4006": { "Title": "A value assigned to a variable is never read before being overwritten. Forgotten error check or dead code?", "Text": "", "TitleMarkdown": "A value assigned to a variable is never read before being overwritten. Forgotten error check or dead code?", "TextMarkdown": "", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 1 }, "SA4008": { "Title": "The variable in the loop condition never changes, are you incrementing the wrong variable?", "Text": "", "TitleMarkdown": "The variable in the loop condition never changes, are you incrementing the wrong variable?", "TextMarkdown": "", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 1 }, "SA4009": { "Title": "A function argument is overwritten before its first use", "Text": "", "TitleMarkdown": "A function argument is overwritten before its first use", "TextMarkdown": "", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA4010": { "Title": "The result of append will never be observed anywhere", "Text": "", "TitleMarkdown": "The result of `append` will never be observed anywhere", "TextMarkdown": "", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 1 }, "SA4011": { "Title": "Break statement with no effect. Did you mean to break out of an outer loop?", "Text": "", "TitleMarkdown": "Break statement with no effect. Did you mean to break out of an outer loop?", "TextMarkdown": "", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA4012": { "Title": "Comparing a value against NaN even though no value is equal to NaN", "Text": "", "TitleMarkdown": "Comparing a value against NaN even though no value is equal to NaN", "TextMarkdown": "", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA4013": { "Title": "Negating a boolean twice (!!b) is the same as writing b. This is either redundant, or a typo.", "Text": "", "TitleMarkdown": "Negating a boolean twice (`!!b`) is the same as writing `b`. This is either redundant, or a typo.", "TextMarkdown": "", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA4014": { "Title": "An if/else if chain has repeated conditions and no side-effects; if the condition didn't match the first time, it won't match the second time, either", "Text": "", "TitleMarkdown": "An if/else if chain has repeated conditions and no side-effects; if the condition didn't match the first time, it won't match the second time, either", "TextMarkdown": "", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 1 }, "SA4015": { "Title": "Calling functions like math.Ceil on floats converted from integers doesn't do anything useful", "Text": "", "TitleMarkdown": "Calling functions like `math.Ceil` on floats converted from integers doesn't do anything useful", "TextMarkdown": "", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 1 }, "SA4016": { "Title": "Certain bitwise operations, such as x ^ 0, do not do anything useful", "Text": "", "TitleMarkdown": "Certain bitwise operations, such as `x ^ 0`, do not do anything useful", "TextMarkdown": "", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA4017": { "Title": "Discarding the return values of a function without side effects, making the call pointless", "Text": "", "TitleMarkdown": "Discarding the return values of a function without side effects, making the call pointless", "TextMarkdown": "", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 1 }, "SA4018": { "Title": "Self-assignment of variables", "Text": "", "TitleMarkdown": "Self-assignment of variables", "TextMarkdown": "", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA4019": { "Title": "Multiple, identical build constraints in the same file", "Text": "", "TitleMarkdown": "Multiple, identical build constraints in the same file", "TextMarkdown": "", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA4020": { "Title": "Unreachable case clause in a type switch", "Text": "In a type switch like the following\n\n```go\ntype T struct{}\nfunc (T) Read(b []byte) (int, error) { return 0, nil }\n\nvar v interface{} = T{}\n\nswitch v.(type) {\ncase io.Reader:\n // ...\ncase T:\n // unreachable\n}\n```\n\nthe second case clause can never be reached because T implements\nio.Reader and case clauses are evaluated in source order.\n\nAnother example:\n\n```go\ntype T struct{}\nfunc (T) Read(b []byte) (int, error) { return 0, nil }\nfunc (T) Close() error { return nil }\n\nvar v interface{} = T{}\n\nswitch v.(type) {\ncase io.Reader:\n // ...\ncase io.ReadCloser:\n // unreachable\n}\n```\n\nEven though T has a Close method and thus implements io.ReadCloser,\nio.Reader will always match first. The method set of io.Reader is a\nsubset of io.ReadCloser. Thus it is impossible to match the second\ncase without matching the first case.\n\n\n#### Structurally equivalent interfaces\n\nA special case of the previous example are structurally identical\ninterfaces. Given these declarations\n\n```go\ntype T error\ntype V error\n\nfunc doSomething() error {\n err, ok := doAnotherThing()\n if ok {\n return T(err)\n }\n\n return U(err)\n}\n```\n\nthe following type switch will have an unreachable case clause:\n\n```go\nswitch doSomething().(type) {\ncase T:\n // ...\ncase V:\n // unreachable\n}\n```\n\nT will always match before V because they are structurally equivalent\nand therefore doSomething()'s return value implements both.", "TitleMarkdown": "Unreachable case clause in a type switch", "TextMarkdown": "In a type switch like the following\n\n```go\ntype T struct{}\nfunc (T) Read(b []byte) (int, error) { return 0, nil }\n\nvar v interface{} = T{}\n\nswitch v.(type) {\ncase io.Reader:\n // ...\ncase T:\n // unreachable\n}\n```\n\nthe second case clause can never be reached because `T` implements\n`io.Reader` and case clauses are evaluated in source order.\n\nAnother example:\n\n```go\ntype T struct{}\nfunc (T) Read(b []byte) (int, error) { return 0, nil }\nfunc (T) Close() error { return nil }\n\nvar v interface{} = T{}\n\nswitch v.(type) {\ncase io.Reader:\n // ...\ncase io.ReadCloser:\n // unreachable\n}\n```\n\nEven though `T` has a `Close` method and thus implements `io.ReadCloser`,\n`io.Reader` will always match first. The method set of `io.Reader` is a\nsubset of `io.ReadCloser`. Thus it is impossible to match the second\ncase without matching the first case.\n\n\n#### Structurally equivalent interfaces\n\nA special case of the previous example are structurally identical\ninterfaces. Given these declarations\n\n```go\ntype T error\ntype V error\n\nfunc doSomething() error {\n err, ok := doAnotherThing()\n if ok {\n return T(err)\n }\n\n return U(err)\n}\n```\n\nthe following type switch will have an unreachable case clause:\n\n```go\nswitch doSomething().(type) {\ncase T:\n // ...\ncase V:\n // unreachable\n}\n```\n\n`T` will always match before V because they are structurally equivalent\nand therefore `doSomething()`'s return value implements both.", "Before": "", "After": "", "Since": "2019.2", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 1 }, "SA4021": { "Title": "'x = append(y)' is equivalent to 'x = y'", "Text": "", "TitleMarkdown": "`x = append(y)` is equivalent to `x = y`", "TextMarkdown": "", "Before": "", "After": "", "Since": "2019.2", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA4022": { "Title": "Comparing the address of a variable against nil", "Text": "Code such as 'if \u0026x == nil' is meaningless, because taking the address of a variable always yields a non-nil pointer.", "TitleMarkdown": "Comparing the address of a variable against nil", "TextMarkdown": "Code such as `if \u0026x == nil` is meaningless, because taking the address of a variable always yields a non-nil pointer.", "Before": "", "After": "", "Since": "2020.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA4023": { "Title": "Impossible comparison of interface value with untyped nil", "Text": "Under the covers, interfaces are implemented as two elements, a\ntype T and a value V. V is a concrete value such as an int,\nstruct or pointer, never an interface itself, and has type T. For\ninstance, if we store the int value 3 in an interface, the\nresulting interface value has, schematically, (T=int, V=3). The\nvalue V is also known as the interface's dynamic value, since a\ngiven interface variable might hold different values V (and\ncorresponding types T) during the execution of the program.\n\nAn interface value is nil only if the V and T are both\nunset, (T=nil, V is not set), In particular, a nil interface will\nalways hold a nil type. If we store a nil pointer of type *int\ninside an interface value, the inner type will be *int regardless\nof the value of the pointer: (T=*int, V=nil). Such an interface\nvalue will therefore be non-nil even when the pointer value V\ninside is nil.\n\nThis situation can be confusing, and arises when a nil value is\nstored inside an interface value such as an error return:\n\n```go\nfunc returnsError() error {\n var p *MyError = nil\n if bad() {\n p = ErrBad\n }\n return p // Will always return a non-nil error.\n}\n```\n\nIf all goes well, the function returns a nil p, so the return\nvalue is an error interface value holding (T=*MyError, V=nil).\nThis means that if the caller compares the returned error to nil,\nit will always look as if there was an error even if nothing bad\nhappened. To return a proper nil error to the caller, the\nfunction must return an explicit nil:\n\n```go\nfunc returnsError() error {\n if bad() {\n return ErrBad\n }\n return nil\n}\n```\n\nIt's a good idea for functions that return errors always to use\nthe error type in their signature (as we did above) rather than a\nconcrete type such as *MyError, to help guarantee the error is\ncreated correctly. As an example, os.Open returns an error even\nthough, if not nil, it's always of concrete type *os.PathError.\n\nSimilar situations to those described here can arise whenever\ninterfaces are used. Just keep in mind that if any concrete value\nhas been stored in the interface, the interface will not be nil.\nFor more information, see The Laws of\nReflection (https://golang.org/doc/articles/laws_of_reflection.html).\n\nThis text has been copied from\nhttps://golang.org/doc/faq#nil_error, licensed under the Creative\nCommons Attribution 3.0 License.", "TitleMarkdown": "Impossible comparison of interface value with untyped nil", "TextMarkdown": "Under the covers, interfaces are implemented as two elements, a\ntype T and a value V. V is a concrete value such as an int,\nstruct or pointer, never an interface itself, and has type T. For\ninstance, if we store the int value 3 in an interface, the\nresulting interface value has, schematically, (T=int, V=3). The\nvalue V is also known as the interface's dynamic value, since a\ngiven interface variable might hold different values V (and\ncorresponding types T) during the execution of the program.\n\nAn interface value is nil only if the V and T are both\nunset, (T=nil, V is not set), In particular, a nil interface will\nalways hold a nil type. If we store a nil pointer of type *int\ninside an interface value, the inner type will be *int regardless\nof the value of the pointer: (T=*int, V=nil). Such an interface\nvalue will therefore be non-nil even when the pointer value V\ninside is nil.\n\nThis situation can be confusing, and arises when a nil value is\nstored inside an interface value such as an error return:\n\n```go\nfunc returnsError() error {\n var p *MyError = nil\n if bad() {\n p = ErrBad\n }\n return p // Will always return a non-nil error.\n}\n```\n\nIf all goes well, the function returns a nil p, so the return\nvalue is an error interface value holding (T=*MyError, V=nil).\nThis means that if the caller compares the returned error to nil,\nit will always look as if there was an error even if nothing bad\nhappened. To return a proper nil error to the caller, the\nfunction must return an explicit nil:\n\n```go\nfunc returnsError() error {\n if bad() {\n return ErrBad\n }\n return nil\n}\n```\n\nIt's a good idea for functions that return errors always to use\nthe error type in their signature (as we did above) rather than a\nconcrete type such as `*MyError`, to help guarantee the error is\ncreated correctly. As an example, `os.Open` returns an error even\nthough, if not nil, it's always of concrete type *os.PathError.\n\nSimilar situations to those described here can arise whenever\ninterfaces are used. Just keep in mind that if any concrete value\nhas been stored in the interface, the interface will not be nil.\nFor more information, see The Laws of\nReflection (https://golang.org/doc/articles/laws_of_reflection.html).\n\nThis text has been copied from\nhttps://golang.org/doc/faq#nil_error, licensed under the Creative\nCommons Attribution 3.0 License.", "Before": "", "After": "", "Since": "2020.2", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA4024": { "Title": "Checking for impossible return value from a builtin function", "Text": "Return values of the len and cap builtins cannot be negative.\n\nSee https://golang.org/pkg/builtin/#len and https://golang.org/pkg/builtin/#cap.\n\nExample:\n\n```go\nif len(slice) \u003c 0 {\n fmt.Println(\"unreachable code\")\n}\n```", "TitleMarkdown": "Checking for impossible return value from a builtin function", "TextMarkdown": "Return values of the `len` and `cap` builtins cannot be negative.\n\nSee https://golang.org/pkg/builtin/#len and https://golang.org/pkg/builtin/#cap.\n\nExample:\n\n```go\nif len(slice) \u003c 0 {\n fmt.Println(\"unreachable code\")\n}\n```", "Before": "", "After": "", "Since": "2021.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA4025": { "Title": "Integer division of literals that results in zero", "Text": "When dividing two integer constants, the result will\nalso be an integer. Thus, a division such as 2 / 3 results in 0.\nThis is true for all of the following examples:\n\n\t_ = 2 / 3\n\tconst _ = 2 / 3\n\tconst _ float64 = 2 / 3\n\t_ = float64(2 / 3)\n\nStaticcheck will flag such divisions if both sides of the division are\ninteger literals, as it is highly unlikely that the division was\nintended to truncate to zero. Staticcheck will not flag integer\ndivision involving named constants, to avoid noisy positives.", "TitleMarkdown": "Integer division of literals that results in zero", "TextMarkdown": "When dividing two integer constants, the result will\nalso be an integer. Thus, a division such as `2 / 3` results in `0`.\nThis is true for all of the following examples:\n\n\t_ = 2 / 3\n\tconst _ = 2 / 3\n\tconst _ float64 = 2 / 3\n\t_ = float64(2 / 3)\n\nStaticcheck will flag such divisions if both sides of the division are\ninteger literals, as it is highly unlikely that the division was\nintended to truncate to zero. Staticcheck will not flag integer\ndivision involving named constants, to avoid noisy positives.", "Before": "", "After": "", "Since": "2021.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA4026": { "Title": "Go constants cannot express negative zero", "Text": "In IEEE 754 floating point math, zero has a sign and can be positive\nor negative. This can be useful in certain numerical code.\n\nGo constants, however, cannot express negative zero. This means that\nthe literals -0.0 and 0.0 have the same ideal value (zero) and\nwill both represent positive zero at runtime.\n\nTo explicitly and reliably create a negative zero, you can use the\nmath.Copysign function: math.Copysign(0, -1).", "TitleMarkdown": "Go constants cannot express negative zero", "TextMarkdown": "In IEEE 754 floating point math, zero has a sign and can be positive\nor negative. This can be useful in certain numerical code.\n\nGo constants, however, cannot express negative zero. This means that\nthe literals `-0.0` and `0.0` have the same ideal value (zero) and\nwill both represent positive zero at runtime.\n\nTo explicitly and reliably create a negative zero, you can use the\n`math.Copysign` function: `math.Copysign(0, -1)`.", "Before": "", "After": "", "Since": "2021.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA4027": { "Title": "(*net/url.URL).Query returns a copy, modifying it doesn't change the URL", "Text": "(*net/url.URL).Query parses the current value of net/url.URL.RawQuery\nand returns it as a map of type net/url.Values. Subsequent changes to\nthis map will not affect the URL unless the map gets encoded and\nassigned to the URL's RawQuery.\n\nAs a consequence, the following code pattern is an expensive no-op:\nu.Query().Add(key, value).", "TitleMarkdown": "`(*net/url.URL).Query` returns a copy, modifying it doesn't change the URL", "TextMarkdown": "`(*net/url.URL).Query` parses the current value of `net/url.URL.RawQuery`\nand returns it as a map of type `net/url.Values`. Subsequent changes to\nthis map will not affect the URL unless the map gets encoded and\nassigned to the URL's `RawQuery`.\n\nAs a consequence, the following code pattern is an expensive no-op:\n`u.Query().Add(key, value)`.", "Before": "", "After": "", "Since": "2021.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA4028": { "Title": "x % 1 is always zero", "Text": "", "TitleMarkdown": "`x % 1` is always zero", "TextMarkdown": "", "Before": "", "After": "", "Since": "2022.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA4029": { "Title": "Ineffective attempt at sorting slice", "Text": "sort.Float64Slice, sort.IntSlice, and sort.StringSlice are\ntypes, not functions. Doing x = sort.StringSlice(x) does nothing,\nespecially not sort any values. The correct usage is\nsort.Sort(sort.StringSlice(x)) or sort.StringSlice(x).Sort(),\nbut there are more convenient helpers, namely sort.Float64s,\nsort.Ints, and sort.Strings.", "TitleMarkdown": "Ineffective attempt at sorting slice", "TextMarkdown": "`sort.Float64Slice`, `sort.IntSlice`, and `sort.StringSlice` are\ntypes, not functions. Doing `x = sort.StringSlice(x)` does nothing,\nespecially not sort any values. The correct usage is\n`sort.Sort(sort.StringSlice(x))` or `sort.StringSlice(x).Sort()`,\nbut there are more convenient helpers, namely `sort.Float64s`,\n`sort.Ints`, and `sort.Strings`.", "Before": "", "After": "", "Since": "2022.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA4030": { "Title": "Ineffective attempt at generating random number", "Text": "Functions in the math/rand package that accept upper limits, such\nas Intn, generate random numbers in the half-open interval [0,n). In\nother words, the generated numbers will be \u003e= 0 and \u003c n – they\ndon't include n. rand.Intn(1) therefore doesn't generate 0\nor 1, it always generates 0.", "TitleMarkdown": "Ineffective attempt at generating random number", "TextMarkdown": "Functions in the `math/rand` package that accept upper limits, such\nas `Intn`, generate random numbers in the half-open interval [0,n). In\nother words, the generated numbers will be `\u003e= 0` and `\u003c n` – they\ndon't include `n`. `rand.Intn(1)` therefore doesn't generate `0`\nor `1`, it always generates `0`.", "Before": "", "After": "", "Since": "2022.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA4031": { "Title": "Checking never-nil value against nil", "Text": "", "TitleMarkdown": "Checking never-nil value against nil", "TextMarkdown": "", "Before": "", "After": "", "Since": "2022.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA5000": { "Title": "Assignment to nil map", "Text": "", "TitleMarkdown": "Assignment to nil map", "TextMarkdown": "", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 1, "MergeIf": 0 }, "SA5001": { "Title": "Deferring Close before checking for a possible error", "Text": "", "TitleMarkdown": "Deferring `Close` before checking for a possible error", "TextMarkdown": "", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA5002": { "Title": "The empty for loop ('for {}') spins and can block the scheduler", "Text": "", "TitleMarkdown": "The empty for loop (`for {}`) spins and can block the scheduler", "TextMarkdown": "", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA5003": { "Title": "Defers in infinite loops will never execute", "Text": "Defers are scoped to the surrounding function, not the surrounding\nblock. In a function that never returns, i.e. one containing an\ninfinite loop, defers will never execute.", "TitleMarkdown": "Defers in infinite loops will never execute", "TextMarkdown": "Defers are scoped to the surrounding function, not the surrounding\nblock. In a function that never returns, i.e. one containing an\ninfinite loop, defers will never execute.", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA5004": { "Title": "'for { select { ...' with an empty default branch spins", "Text": "", "TitleMarkdown": "`for { select { ...` with an empty default branch spins", "TextMarkdown": "", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA5005": { "Title": "The finalizer references the finalized object, preventing garbage collection", "Text": "A finalizer is a function associated with an object that runs when the\ngarbage collector is ready to collect said object, that is when the\nobject is no longer referenced by anything.\n\nIf the finalizer references the object, however, it will always remain\nas the final reference to that object, preventing the garbage\ncollector from collecting the object. The finalizer will never run,\nand the object will never be collected, leading to a memory leak. That\nis why the finalizer should instead use its first argument to operate\non the object. That way, the number of references can temporarily go\nto zero before the object is being passed to the finalizer.", "TitleMarkdown": "The finalizer references the finalized object, preventing garbage collection", "TextMarkdown": "A finalizer is a function associated with an object that runs when the\ngarbage collector is ready to collect said object, that is when the\nobject is no longer referenced by anything.\n\nIf the finalizer references the object, however, it will always remain\nas the final reference to that object, preventing the garbage\ncollector from collecting the object. The finalizer will never run,\nand the object will never be collected, leading to a memory leak. That\nis why the finalizer should instead use its first argument to operate\non the object. That way, the number of references can temporarily go\nto zero before the object is being passed to the finalizer.", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA5007": { "Title": "Infinite recursive call", "Text": "A function that calls itself recursively needs to have an exit\ncondition. Otherwise it will recurse forever, until the system runs\nout of memory.\n\nThis issue can be caused by simple bugs such as forgetting to add an\nexit condition. It can also happen \"on purpose\". Some languages have\ntail call optimization which makes certain infinite recursive calls\nsafe to use. Go, however, does not implement TCO, and as such a loop\nshould be used instead.", "TitleMarkdown": "Infinite recursive call", "TextMarkdown": "A function that calls itself recursively needs to have an exit\ncondition. Otherwise it will recurse forever, until the system runs\nout of memory.\n\nThis issue can be caused by simple bugs such as forgetting to add an\nexit condition. It can also happen \"on purpose\". Some languages have\ntail call optimization which makes certain infinite recursive calls\nsafe to use. Go, however, does not implement TCO, and as such a loop\nshould be used instead.", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA5008": { "Title": "Invalid struct tag", "Text": "", "TitleMarkdown": "Invalid struct tag", "TextMarkdown": "", "Before": "", "After": "", "Since": "2019.2", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA5009": { "Title": "Invalid Printf call", "Text": "", "TitleMarkdown": "Invalid Printf call", "TextMarkdown": "", "Before": "", "After": "", "Since": "2019.2", "NonDefault": false, "Options": null, "Severity": 1, "MergeIf": 0 }, "SA5010": { "Title": "Impossible type assertion", "Text": "Some type assertions can be statically proven to be\nimpossible. This is the case when the method sets of both\narguments of the type assertion conflict with each other, for\nexample by containing the same method with different\nsignatures.\n\nThe Go compiler already applies this check when asserting from an\ninterface value to a concrete type. If the concrete type misses\nmethods from the interface, or if function signatures don't match,\nthen the type assertion can never succeed.\n\nThis check applies the same logic when asserting from one interface to\nanother. If both interface types contain the same method but with\ndifferent signatures, then the type assertion can never succeed,\neither.", "TitleMarkdown": "Impossible type assertion", "TextMarkdown": "Some type assertions can be statically proven to be\nimpossible. This is the case when the method sets of both\narguments of the type assertion conflict with each other, for\nexample by containing the same method with different\nsignatures.\n\nThe Go compiler already applies this check when asserting from an\ninterface value to a concrete type. If the concrete type misses\nmethods from the interface, or if function signatures don't match,\nthen the type assertion can never succeed.\n\nThis check applies the same logic when asserting from one interface to\nanother. If both interface types contain the same method but with\ndifferent signatures, then the type assertion can never succeed,\neither.", "Before": "", "After": "", "Since": "2020.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA5011": { "Title": "Possible nil pointer dereference", "Text": "A pointer is being dereferenced unconditionally, while\nalso being checked against nil in another place. This suggests that\nthe pointer may be nil and dereferencing it may panic. This is\ncommonly a result of improperly ordered code or missing return\nstatements. Consider the following examples:\n\n```go\nfunc fn(x *int) {\n fmt.Println(*x)\n\n // This nil check is equally important for the previous dereference\n if x != nil {\n foo(*x)\n }\n}\n\nfunc TestFoo(t *testing.T) {\n x := compute()\n if x == nil {\n t.Errorf(\"nil pointer received\")\n }\n\n // t.Errorf does not abort the test, so if x is nil, the next line will panic.\n foo(*x)\n}\n```\n\nStaticcheck tries to deduce which functions abort control flow.\nFor example, it is aware that a function will not continue\nexecution after a call to panic or log.Fatal. However, sometimes\nthis detection fails, in particular in the presence of\nconditionals. Consider the following example:\n\n```go\nfunc Log(msg string, level int) {\n fmt.Println(msg)\n if level == levelFatal {\n os.Exit(1)\n }\n}\n\nfunc Fatal(msg string) {\n Log(msg, levelFatal)\n}\n\nfunc fn(x *int) {\n if x == nil {\n Fatal(\"unexpected nil pointer\")\n }\n fmt.Println(*x)\n}\n```\n\nStaticcheck will flag the dereference of x, even though it is perfectly\nsafe. Staticcheck is not able to deduce that a call to\nFatal will exit the program. For the time being, the easiest\nworkaround is to modify the definition of Fatal like so:\n\n```go\nfunc Fatal(msg string) {\n Log(msg, levelFatal)\n panic(\"unreachable\")\n}\n```\n\nWe also hard-code functions from common logging packages such as\nlogrus. Please file an issue if we're missing support for a\npopular package.", "TitleMarkdown": "Possible nil pointer dereference", "TextMarkdown": "A pointer is being dereferenced unconditionally, while\nalso being checked against nil in another place. This suggests that\nthe pointer may be nil and dereferencing it may panic. This is\ncommonly a result of improperly ordered code or missing return\nstatements. Consider the following examples:\n\n```go\nfunc fn(x *int) {\n fmt.Println(*x)\n\n // This nil check is equally important for the previous dereference\n if x != nil {\n foo(*x)\n }\n}\n\nfunc TestFoo(t *testing.T) {\n x := compute()\n if x == nil {\n t.Errorf(\"nil pointer received\")\n }\n\n // t.Errorf does not abort the test, so if x is nil, the next line will panic.\n foo(*x)\n}\n```\n\nStaticcheck tries to deduce which functions abort control flow.\nFor example, it is aware that a function will not continue\nexecution after a call to `panic` or `log.Fatal`. However, sometimes\nthis detection fails, in particular in the presence of\nconditionals. Consider the following example:\n\n```go\nfunc Log(msg string, level int) {\n fmt.Println(msg)\n if level == levelFatal {\n os.Exit(1)\n }\n}\n\nfunc Fatal(msg string) {\n Log(msg, levelFatal)\n}\n\nfunc fn(x *int) {\n if x == nil {\n Fatal(\"unexpected nil pointer\")\n }\n fmt.Println(*x)\n}\n```\n\nStaticcheck will flag the dereference of `x`, even though it is perfectly\nsafe. Staticcheck is not able to deduce that a call to\nFatal will exit the program. For the time being, the easiest\nworkaround is to modify the definition of Fatal like so:\n\n```go\nfunc Fatal(msg string) {\n Log(msg, levelFatal)\n panic(\"unreachable\")\n}\n```\n\nWe also hard-code functions from common logging packages such as\nlogrus. Please file an issue if we're missing support for a\npopular package.", "Before": "", "After": "", "Since": "2020.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA5012": { "Title": "Passing odd-sized slice to function expecting even size", "Text": "Some functions that take slices as parameters expect the slices to have an even number of elements. \nOften, these functions treat elements in a slice as pairs. \nFor example, strings.NewReplacer takes pairs of old and new strings, \nand calling it with an odd number of elements would be an error.", "TitleMarkdown": "Passing odd-sized slice to function expecting even size", "TextMarkdown": "Some functions that take slices as parameters expect the slices to have an even number of elements. \nOften, these functions treat elements in a slice as pairs. \nFor example, `strings.NewReplacer` takes pairs of old and new strings, \nand calling it with an odd number of elements would be an error.", "Before": "", "After": "", "Since": "2020.2", "NonDefault": false, "Options": null, "Severity": 1, "MergeIf": 0 }, "SA6000": { "Title": "Using regexp.Match or related in a loop, should use regexp.Compile", "Text": "", "TitleMarkdown": "Using `regexp.Match` or related in a loop, should use `regexp.Compile`", "TextMarkdown": "", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA6001": { "Title": "Missing an optimization opportunity when indexing maps by byte slices", "Text": "Map keys must be comparable, which precludes the use of byte slices.\nThis usually leads to using string keys and converting byte slices to\nstrings.\n\nNormally, a conversion of a byte slice to a string needs to copy the data and\ncauses allocations. The compiler, however, recognizes m[string(b)] and\nuses the data of b directly, without copying it, because it knows that\nthe data can't change during the map lookup. This leads to the\ncounter-intuitive situation that\n\n```go\nk := string(b)\nprintln(m[k])\nprintln(m[k])\n```\n\nwill be less efficient than\n\n```go\nprintln(m[string(b)])\nprintln(m[string(b)])\n```\n\nbecause the first version needs to copy and allocate, while the second\none does not.\n\nFor some history on this optimization, check out commit\nf5f5a8b6209f84961687d993b93ea0d397f5d5bf in the Go repository.", "TitleMarkdown": "Missing an optimization opportunity when indexing maps by byte slices", "TextMarkdown": "Map keys must be comparable, which precludes the use of byte slices.\nThis usually leads to using string keys and converting byte slices to\nstrings.\n\nNormally, a conversion of a byte slice to a string needs to copy the data and\ncauses allocations. The compiler, however, recognizes `m[string(b)]` and\nuses the data of `b` directly, without copying it, because it knows that\nthe data can't change during the map lookup. This leads to the\ncounter-intuitive situation that\n\n```go\nk := string(b)\nprintln(m[k])\nprintln(m[k])\n```\n\nwill be less efficient than\n\n```go\nprintln(m[string(b)])\nprintln(m[string(b)])\n```\n\nbecause the first version needs to copy and allocate, while the second\none does not.\n\nFor some history on this optimization, check out commit\nf5f5a8b6209f84961687d993b93ea0d397f5d5bf in the Go repository.", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA6002": { "Title": "Storing non-pointer values in sync.Pool allocates memory", "Text": "A sync.Pool is used to avoid unnecessary allocations and reduce the\namount of work the garbage collector has to do.\n\nWhen passing a value that is not a pointer to a function that accepts\nan interface, the value needs to be placed on the heap, which means an\nadditional allocation. Slices are a common thing to put in sync.Pools,\nand they're structs with 3 fields (length, capacity, and a pointer to\nan array). In order to avoid the extra allocation, one should store a\npointer to the slice instead.\n\nSee the comments on https://go-review.googlesource.com/c/go/+/24371\nthat discuss this problem.", "TitleMarkdown": "Storing non-pointer values in `sync.Pool` allocates memory", "TextMarkdown": "A `sync.Pool` is used to avoid unnecessary allocations and reduce the\namount of work the garbage collector has to do.\n\nWhen passing a value that is not a pointer to a function that accepts\nan interface, the value needs to be placed on the heap, which means an\nadditional allocation. Slices are a common thing to put in sync.Pools,\nand they're structs with 3 fields (length, capacity, and a pointer to\nan array). In order to avoid the extra allocation, one should store a\npointer to the slice instead.\n\nSee the comments on https://go-review.googlesource.com/c/go/+/24371\nthat discuss this problem.", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA6003": { "Title": "Converting a string to a slice of runes before ranging over it", "Text": "You may want to loop over the runes in a string. Instead of converting\nthe string to a slice of runes and looping over that, you can loop\nover the string itself. That is,\n\n```go\nfor _, r := range s {}\n```\n\nand\n\n```go\nfor _, r := range []rune(s) {}\n```\n\nwill yield the same values. The first version, however, will be faster\nand avoid unnecessary memory allocations.\n\nDo note that if you are interested in the indices, ranging over a\nstring and over a slice of runes will yield different indices. The\nfirst one yields byte offsets, while the second one yields indices in\nthe slice of runes.", "TitleMarkdown": "Converting a string to a slice of runes before ranging over it", "TextMarkdown": "You may want to loop over the runes in a string. Instead of converting\nthe string to a slice of runes and looping over that, you can loop\nover the string itself. That is,\n\n```go\nfor _, r := range s {}\n```\n\nand\n\n```go\nfor _, r := range []rune(s) {}\n```\n\nwill yield the same values. The first version, however, will be faster\nand avoid unnecessary memory allocations.\n\nDo note that if you are interested in the indices, ranging over a\nstring and over a slice of runes will yield different indices. The\nfirst one yields byte offsets, while the second one yields indices in\nthe slice of runes.", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA6005": { "Title": "Inefficient string comparison with strings.ToLower or strings.ToUpper", "Text": "Converting two strings to the same case and comparing them like so\n\n```go\nif strings.ToLower(s1) == strings.ToLower(s2) {\n ...\n}\n```\n\nis significantly more expensive than comparing them with\nstrings.EqualFold(s1, s2). This is due to memory usage as well as\ncomputational complexity.\n\nstrings.ToLower will have to allocate memory for the new strings, as\nwell as convert both strings fully, even if they differ on the very\nfirst byte. strings.EqualFold, on the other hand, compares the strings\none character at a time. It doesn't need to create two intermediate\nstrings and can return as soon as the first non-matching character has\nbeen found.\n\nFor a more in-depth explanation of this issue, see\nhttps://blog.digitalocean.com/how-to-efficiently-compare-strings-in-go/", "TitleMarkdown": "Inefficient string comparison with `strings.ToLower` or `strings.ToUpper`", "TextMarkdown": "Converting two strings to the same case and comparing them like so\n\n```go\nif strings.ToLower(s1) == strings.ToLower(s2) {\n ...\n}\n```\n\nis significantly more expensive than comparing them with\n`strings.EqualFold(s1, s2)`. This is due to memory usage as well as\ncomputational complexity.\n\n`strings.ToLower` will have to allocate memory for the new strings, as\nwell as convert both strings fully, even if they differ on the very\nfirst byte. strings.EqualFold, on the other hand, compares the strings\none character at a time. It doesn't need to create two intermediate\nstrings and can return as soon as the first non-matching character has\nbeen found.\n\nFor a more in-depth explanation of this issue, see\nhttps://blog.digitalocean.com/how-to-efficiently-compare-strings-in-go/", "Before": "", "After": "", "Since": "2019.2", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA9001": { "Title": "Defers in range loops may not run when you expect them to", "Text": "", "TitleMarkdown": "Defers in range loops may not run when you expect them to", "TextMarkdown": "", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA9002": { "Title": "Using a non-octal os.FileMode that looks like it was meant to be in octal.", "Text": "", "TitleMarkdown": "Using a non-octal `os.FileMode` that looks like it was meant to be in octal.", "TextMarkdown": "", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA9003": { "Title": "Empty body in an if or else branch", "Text": "", "TitleMarkdown": "Empty body in an if or else branch", "TextMarkdown": "", "Before": "", "After": "", "Since": "2017.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA9004": { "Title": "Only the first constant has an explicit type", "Text": "In a constant declaration such as the following:\n\n```go\nconst (\n First byte = 1\n Second = 2\n)\n```\n\nthe constant Second does not have the same type as the constant First.\nThis construct shouldn't be confused with\n\n```go\nconst (\n First byte = iota\n Second\n)\n```\n\nwhere First and Second do indeed have the same type. The type is only\npassed on when no explicit value is assigned to the constant.\n\nWhen declaring enumerations with explicit values it is therefore\nimportant not to write\n\n```go\nconst (\n EnumFirst EnumType = 1\n EnumSecond = 2\n EnumThird = 3\n)\n```\n\nThis discrepancy in types can cause various confusing behaviors and\nbugs.\n\n\n#### Wrong type in variable declarations\n\nThe most obvious issue with such incorrect enumerations expresses\nitself as a compile error:\n\n```go\npackage pkg\n\nconst (\n EnumFirst uint8 = 1\n EnumSecond = 2\n)\n\nfunc fn(useFirst bool) {\n x := EnumSecond\n if useFirst {\n x = EnumFirst\n }\n}\n```\n\nfails to compile with\n\n```go\n./const.go:11:5: cannot use EnumFirst (type uint8) as type int in assignment\n```\n\n\n#### Losing method sets\n\nA more subtle issue occurs with types that have methods and optional\ninterfaces. Consider the following:\n\n```go\npackage main\n\nimport \"fmt\"\n\ntype Enum int\n\nfunc (e Enum) String() string {\n return \"an enum\"\n}\n\nconst (\n EnumFirst Enum = 1\n EnumSecond = 2\n)\n\nfunc main() {\n fmt.Println(EnumFirst)\n fmt.Println(EnumSecond)\n}\n```\n\nThis code will output\n\n```go\nan enum\n2\n```\n\nas EnumSecond has no explicit type, and thus defaults to int.", "TitleMarkdown": "Only the first constant has an explicit type", "TextMarkdown": "In a constant declaration such as the following:\n\n```go\nconst (\n First byte = 1\n Second = 2\n)\n```\n\nthe constant Second does not have the same type as the constant First.\nThis construct shouldn't be confused with\n\n```go\nconst (\n First byte = iota\n Second\n)\n```\n\nwhere `First` and `Second` do indeed have the same type. The type is only\npassed on when no explicit value is assigned to the constant.\n\nWhen declaring enumerations with explicit values it is therefore\nimportant not to write\n\n```go\nconst (\n EnumFirst EnumType = 1\n EnumSecond = 2\n EnumThird = 3\n)\n```\n\nThis discrepancy in types can cause various confusing behaviors and\nbugs.\n\n\n#### Wrong type in variable declarations\n\nThe most obvious issue with such incorrect enumerations expresses\nitself as a compile error:\n\n```go\npackage pkg\n\nconst (\n EnumFirst uint8 = 1\n EnumSecond = 2\n)\n\nfunc fn(useFirst bool) {\n x := EnumSecond\n if useFirst {\n x = EnumFirst\n }\n}\n```\n\nfails to compile with\n\n```go\n./const.go:11:5: cannot use EnumFirst (type uint8) as type int in assignment\n```\n\n\n#### Losing method sets\n\nA more subtle issue occurs with types that have methods and optional\ninterfaces. Consider the following:\n\n```go\npackage main\n\nimport \"fmt\"\n\ntype Enum int\n\nfunc (e Enum) String() string {\n return \"an enum\"\n}\n\nconst (\n EnumFirst Enum = 1\n EnumSecond = 2\n)\n\nfunc main() {\n fmt.Println(EnumFirst)\n fmt.Println(EnumSecond)\n}\n```\n\nThis code will output\n\n```go\nan enum\n2\n```\n\nas `EnumSecond` has no explicit type, and thus defaults to `int`.", "Before": "", "After": "", "Since": "2019.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA9005": { "Title": "Trying to marshal a struct with no public fields nor custom marshaling", "Text": "The encoding/json and encoding/xml packages only operate on exported\nfields in structs, not unexported ones. It is usually an error to try\nto (un)marshal structs that only consist of unexported fields.\n\nThis check will not flag calls involving types that define custom\nmarshaling behavior, e.g. via MarshalJSON methods. It will also not\nflag empty structs.", "TitleMarkdown": "Trying to marshal a struct with no public fields nor custom marshaling", "TextMarkdown": "The `encoding/json` and `encoding/xml` packages only operate on exported\nfields in structs, not unexported ones. It is usually an error to try\nto (un)marshal structs that only consist of unexported fields.\n\nThis check will not flag calls involving types that define custom\nmarshaling behavior, e.g. via `MarshalJSON` methods. It will also not\nflag empty structs.", "Before": "", "After": "", "Since": "2019.2", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 1 }, "SA9006": { "Title": "Dubious bit shifting of a fixed size integer value", "Text": "Bit shifting a value past its size will always clear the value.\n\nFor instance:\n\n```go\nv := int8(42)\nv \u003e\u003e= 8\n```\n\nwill always result in 0.\n\nThis check flags bit shifting operations on fixed size integer values only.\nThat is, int, uint and uintptr are never flagged to avoid potential false\npositives in somewhat exotic but valid bit twiddling tricks:\n\n```go\n// Clear any value above 32 bits if integers are more than 32 bits.\nfunc f(i int) int {\n v := i \u003e\u003e 32\n v = v \u003c\u003c 32\n return i-v\n}\n```", "TitleMarkdown": "Dubious bit shifting of a fixed size integer value", "TextMarkdown": "Bit shifting a value past its size will always clear the value.\n\nFor instance:\n\n```go\nv := int8(42)\nv \u003e\u003e= 8\n```\n\nwill always result in 0.\n\nThis check flags bit shifting operations on fixed size integer values only.\nThat is, int, uint and uintptr are never flagged to avoid potential false\npositives in somewhat exotic but valid bit twiddling tricks:\n\n```go\n// Clear any value above 32 bits if integers are more than 32 bits.\nfunc f(i int) int {\n v := i \u003e\u003e 32\n v = v \u003c\u003c 32\n return i-v\n}\n```", "Before": "", "After": "", "Since": "2020.2", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA9007": { "Title": "Deleting a directory that shouldn't be deleted", "Text": "It is virtually never correct to delete system directories such as\n/tmp or the user's home directory. However, it can be fairly easy to\ndo by mistake, for example by mistakingly using os.TempDir instead\nof ioutil.TempDir, or by forgetting to add a suffix to the result\nof os.UserHomeDir.\n\nWriting\n\n```go\nd := os.TempDir()\ndefer os.RemoveAll(d)\n```\n\nin your unit tests will have a devastating effect on the stability of your system.\n\nThis check flags attempts at deleting the following directories:\n\n- os.TempDir\n- os.UserCacheDir\n- os.UserConfigDir\n- os.UserHomeDir", "TitleMarkdown": "Deleting a directory that shouldn't be deleted", "TextMarkdown": "It is virtually never correct to delete system directories such as\n/tmp or the user's home directory. However, it can be fairly easy to\ndo by mistake, for example by mistakingly using `os.TempDir` instead\nof `ioutil.TempDir`, or by forgetting to add a suffix to the result\nof `os.UserHomeDir`.\n\nWriting\n\n```go\nd := os.TempDir()\ndefer os.RemoveAll(d)\n```\n\nin your unit tests will have a devastating effect on the stability of your system.\n\nThis check flags attempts at deleting the following directories:\n\n- os.TempDir\n- os.UserCacheDir\n- os.UserConfigDir\n- os.UserHomeDir", "Before": "", "After": "", "Since": "2022.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "SA9008": { "Title": "else branch of a type assertion is probably not reading the right value", "Text": "When declaring variables as part of an if statement (like in 'if\nfoo := ...; foo {'), the same variables will also be in the scope of\nthe else branch. This means that in the following example\n\n```go\nif x, ok := x.(int); ok {\n // ...\n} else {\n fmt.Printf(\"unexpected type %T\", x)\n}\n```\n\nx in the else branch will refer to the x from x, ok\n:=; it will not refer to the x that is being type-asserted. The\nresult of a failed type assertion is the zero value of the type that\nis being asserted to, so x in the else branch will always have the\nvalue 0 and the type int.", "TitleMarkdown": "`else` branch of a type assertion is probably not reading the right value", "TextMarkdown": "When declaring variables as part of an `if` statement (like in `if\nfoo := ...; foo {`), the same variables will also be in the scope of\nthe `else` branch. This means that in the following example\n\n```go\nif x, ok := x.(int); ok {\n // ...\n} else {\n fmt.Printf(\"unexpected type %T\", x)\n}\n```\n\n`x` in the `else` branch will refer to the `x` from `x, ok\n:=`; it will not refer to the `x` that is being type-asserted. The\nresult of a failed type assertion is the zero value of the type that\nis being asserted to, so `x` in the else branch will always have the\nvalue `0` and the type `int`.", "Before": "", "After": "", "Since": "2022.1", "NonDefault": false, "Options": null, "Severity": 3, "MergeIf": 0 }, "ST1000": { "Title": "Incorrect or missing package comment", "Text": "Packages must have a package comment that is formatted according to\nthe guidelines laid out in\nhttps://github.com/golang/go/wiki/CodeReviewComments#package-comments.", "TitleMarkdown": "Incorrect or missing package comment", "TextMarkdown": "Packages must have a package comment that is formatted according to\nthe guidelines laid out in\nhttps://github.com/golang/go/wiki/CodeReviewComments#package-comments.", "Before": "", "After": "", "Since": "2019.1", "NonDefault": true, "Options": null, "Severity": 0, "MergeIf": 0 }, "ST1001": { "Title": "Dot imports are discouraged", "Text": "Dot imports that aren't in external test packages are discouraged.\n\nThe dot_import_whitelist option can be used to whitelist certain\nimports.\n\nQuoting Go Code Review Comments:\n\n\u003e The import . form can be useful in tests that, due to circular\n\u003e dependencies, cannot be made part of the package being tested:\n\u003e \n\u003e package foo_test\n\u003e \n\u003e import (\n\u003e \"bar/testutil\" // also imports \"foo\"\n\u003e . \"foo\"\n\u003e )\n\u003e \n\u003e In this case, the test file cannot be in package foo because it\n\u003e uses bar/testutil, which imports foo. So we use the import .\n\u003e form to let the file pretend to be part of package foo even though\n\u003e it is not. Except for this one case, do not use import . in your\n\u003e programs. It makes the programs much harder to read because it is\n\u003e unclear whether a name like Quux is a top-level identifier in the\n\u003e current package or in an imported package.", "TitleMarkdown": "Dot imports are discouraged", "TextMarkdown": "Dot imports that aren't in external test packages are discouraged.\n\nThe `dot_import_whitelist` option can be used to whitelist certain\nimports.\n\nQuoting Go Code Review Comments:\n\n\u003e The `import .` form can be useful in tests that, due to circular\n\u003e dependencies, cannot be made part of the package being tested:\n\u003e \n\u003e package foo_test\n\u003e \n\u003e import (\n\u003e \"bar/testutil\" // also imports \"foo\"\n\u003e . \"foo\"\n\u003e )\n\u003e \n\u003e In this case, the test file cannot be in package foo because it\n\u003e uses `bar/testutil`, which imports `foo`. So we use the `import .`\n\u003e form to let the file pretend to be part of package foo even though\n\u003e it is not. Except for this one case, do not use `import .` in your\n\u003e programs. It makes the programs much harder to read because it is\n\u003e unclear whether a name like `Quux` is a top-level identifier in the\n\u003e current package or in an imported package.", "Before": "", "After": "", "Since": "2019.1", "NonDefault": false, "Options": [ "dot_import_whitelist" ], "Severity": 0, "MergeIf": 0 }, "ST1003": { "Title": "Poorly chosen identifier", "Text": "Identifiers, such as variable and package names, follow certain rules.\n\nSee the following links for details:\n\n- https://golang.org/doc/effective_go.html#package-names\n- https://golang.org/doc/effective_go.html#mixed-caps\n- https://github.com/golang/go/wiki/CodeReviewComments#initialisms\n- https://github.com/golang/go/wiki/CodeReviewComments#variable-names", "TitleMarkdown": "Poorly chosen identifier", "TextMarkdown": "Identifiers, such as variable and package names, follow certain rules.\n\nSee the following links for details:\n\n- https://golang.org/doc/effective_go.html#package-names\n- https://golang.org/doc/effective_go.html#mixed-caps\n- https://github.com/golang/go/wiki/CodeReviewComments#initialisms\n- https://github.com/golang/go/wiki/CodeReviewComments#variable-names", "Before": "", "After": "", "Since": "2019.1", "NonDefault": true, "Options": [ "initialisms" ], "Severity": 0, "MergeIf": 0 }, "ST1005": { "Title": "Incorrectly formatted error string", "Text": "Error strings follow a set of guidelines to ensure uniformity and good\ncomposability.\n\nQuoting Go Code Review Comments:\n\n\u003e Error strings should not be capitalized (unless beginning with\n\u003e proper nouns or acronyms) or end with punctuation, since they are\n\u003e usually printed following other context. That is, use\n\u003e fmt.Errorf(\"something bad\") not fmt.Errorf(\"Something bad\"), so\n\u003e that log.Printf(\"Reading %s: %v\", filename, err) formats without a\n\u003e spurious capital letter mid-message.", "TitleMarkdown": "Incorrectly formatted error string", "TextMarkdown": "Error strings follow a set of guidelines to ensure uniformity and good\ncomposability.\n\nQuoting Go Code Review Comments:\n\n\u003e Error strings should not be capitalized (unless beginning with\n\u003e proper nouns or acronyms) or end with punctuation, since they are\n\u003e usually printed following other context. That is, use\n\u003e `fmt.Errorf(\"something bad\")` not `fmt.Errorf(\"Something bad\")`, so\n\u003e that `log.Printf(\"Reading %s: %v\", filename, err)` formats without a\n\u003e spurious capital letter mid-message.", "Before": "", "After": "", "Since": "2019.1", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 0 }, "ST1006": { "Title": "Poorly chosen receiver name", "Text": "Quoting Go Code Review Comments:\n\n\u003e The name of a method's receiver should be a reflection of its\n\u003e identity; often a one or two letter abbreviation of its type\n\u003e suffices (such as \"c\" or \"cl\" for \"Client\"). Don't use generic\n\u003e names such as \"me\", \"this\" or \"self\", identifiers typical of\n\u003e object-oriented languages that place more emphasis on methods as\n\u003e opposed to functions. The name need not be as descriptive as that\n\u003e of a method argument, as its role is obvious and serves no\n\u003e documentary purpose. It can be very short as it will appear on\n\u003e almost every line of every method of the type; familiarity admits\n\u003e brevity. Be consistent, too: if you call the receiver \"c\" in one\n\u003e method, don't call it \"cl\" in another.", "TitleMarkdown": "Poorly chosen receiver name", "TextMarkdown": "Quoting Go Code Review Comments:\n\n\u003e The name of a method's receiver should be a reflection of its\n\u003e identity; often a one or two letter abbreviation of its type\n\u003e suffices (such as \"c\" or \"cl\" for \"Client\"). Don't use generic\n\u003e names such as \"me\", \"this\" or \"self\", identifiers typical of\n\u003e object-oriented languages that place more emphasis on methods as\n\u003e opposed to functions. The name need not be as descriptive as that\n\u003e of a method argument, as its role is obvious and serves no\n\u003e documentary purpose. It can be very short as it will appear on\n\u003e almost every line of every method of the type; familiarity admits\n\u003e brevity. Be consistent, too: if you call the receiver \"c\" in one\n\u003e method, don't call it \"cl\" in another.", "Before": "", "After": "", "Since": "2019.1", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 0 }, "ST1008": { "Title": "A function's error value should be its last return value", "Text": "A function's error value should be its last return value.", "TitleMarkdown": "A function's error value should be its last return value", "TextMarkdown": "A function's error value should be its last return value.", "Before": "", "After": "", "Since": "2019.1", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 0 }, "ST1011": { "Title": "Poorly chosen name for variable of type time.Duration", "Text": "time.Duration values represent an amount of time, which is represented\nas a count of nanoseconds. An expression like 5 * time.Microsecond\nyields the value 5000. It is therefore not appropriate to suffix a\nvariable of type time.Duration with any time unit, such as Msec or\nMilli.", "TitleMarkdown": "Poorly chosen name for variable of type `time.Duration`", "TextMarkdown": "`time.Duration` values represent an amount of time, which is represented\nas a count of nanoseconds. An expression like `5 * time.Microsecond`\nyields the value `5000`. It is therefore not appropriate to suffix a\nvariable of type `time.Duration` with any time unit, such as `Msec` or\n`Milli`.", "Before": "", "After": "", "Since": "2019.1", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 0 }, "ST1012": { "Title": "Poorly chosen name for error variable", "Text": "Error variables that are part of an API should be called errFoo or\nErrFoo.", "TitleMarkdown": "Poorly chosen name for error variable", "TextMarkdown": "Error variables that are part of an API should be called `errFoo` or\n`ErrFoo`.", "Before": "", "After": "", "Since": "2019.1", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 0 }, "ST1013": { "Title": "Should use constants for HTTP error codes, not magic numbers", "Text": "HTTP has a tremendous number of status codes. While some of those are\nwell known (200, 400, 404, 500), most of them are not. The net/http\npackage provides constants for all status codes that are part of the\nvarious specifications. It is recommended to use these constants\ninstead of hard-coding magic numbers, to vastly improve the\nreadability of your code.", "TitleMarkdown": "Should use constants for HTTP error codes, not magic numbers", "TextMarkdown": "HTTP has a tremendous number of status codes. While some of those are\nwell known (200, 400, 404, 500), most of them are not. The `net/http`\npackage provides constants for all status codes that are part of the\nvarious specifications. It is recommended to use these constants\ninstead of hard-coding magic numbers, to vastly improve the\nreadability of your code.", "Before": "", "After": "", "Since": "2019.1", "NonDefault": false, "Options": [ "http_status_code_whitelist" ], "Severity": 0, "MergeIf": 0 }, "ST1015": { "Title": "A switch's default case should be the first or last case", "Text": "", "TitleMarkdown": "A switch's default case should be the first or last case", "TextMarkdown": "", "Before": "", "After": "", "Since": "2019.1", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 0 }, "ST1016": { "Title": "Use consistent method receiver names", "Text": "", "TitleMarkdown": "Use consistent method receiver names", "TextMarkdown": "", "Before": "", "After": "", "Since": "2019.1", "NonDefault": true, "Options": null, "Severity": 0, "MergeIf": 0 }, "ST1017": { "Title": "Don't use Yoda conditions", "Text": "Yoda conditions are conditions of the kind 'if 42 == x', where the\nliteral is on the left side of the comparison. These are a common\nidiom in languages in which assignment is an expression, to avoid bugs\nof the kind 'if (x = 42)'. In Go, which doesn't allow for this kind of\nbug, we prefer the more idiomatic 'if x == 42'.", "TitleMarkdown": "Don't use Yoda conditions", "TextMarkdown": "Yoda conditions are conditions of the kind `if 42 == x`, where the\nliteral is on the left side of the comparison. These are a common\nidiom in languages in which assignment is an expression, to avoid bugs\nof the kind `if (x = 42)`. In Go, which doesn't allow for this kind of\nbug, we prefer the more idiomatic `if x == 42`.", "Before": "", "After": "", "Since": "2019.2", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 0 }, "ST1018": { "Title": "Avoid zero-width and control characters in string literals", "Text": "", "TitleMarkdown": "Avoid zero-width and control characters in string literals", "TextMarkdown": "", "Before": "", "After": "", "Since": "2019.2", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 0 }, "ST1019": { "Title": "Importing the same package multiple times", "Text": "Go allows importing the same package multiple times, as long as\ndifferent import aliases are being used. That is, the following\nbit of code is valid:\n\n```go\nimport (\n \"fmt\"\n fumpt \"fmt\"\n format \"fmt\"\n _ \"fmt\"\n)\n```\n\nHowever, this is very rarely done on purpose. Usually, it is a\nsign of code that got refactored, accidentally adding duplicate\nimport statements. It is also a rarely known feature, which may\ncontribute to confusion.\n\nDo note that sometimes, this feature may be used\nintentionally (see for example\nhttps://github.com/golang/go/commit/3409ce39bfd7584523b7a8c150a310cea92d879d)\n– if you want to allow this pattern in your code base, you're\nadvised to disable this check.", "TitleMarkdown": "Importing the same package multiple times", "TextMarkdown": "Go allows importing the same package multiple times, as long as\ndifferent import aliases are being used. That is, the following\nbit of code is valid:\n\n```go\nimport (\n \"fmt\"\n fumpt \"fmt\"\n format \"fmt\"\n _ \"fmt\"\n)\n```\n\nHowever, this is very rarely done on purpose. Usually, it is a\nsign of code that got refactored, accidentally adding duplicate\nimport statements. It is also a rarely known feature, which may\ncontribute to confusion.\n\nDo note that sometimes, this feature may be used\nintentionally (see for example\nhttps://github.com/golang/go/commit/3409ce39bfd7584523b7a8c150a310cea92d879d)\n– if you want to allow this pattern in your code base, you're\nadvised to disable this check.", "Before": "", "After": "", "Since": "2020.1", "NonDefault": false, "Options": null, "Severity": 0, "MergeIf": 0 }, "ST1020": { "Title": "The documentation of an exported function should start with the function's name", "Text": "Doc comments work best as complete sentences, which\nallow a wide variety of automated presentations. The first sentence\nshould be a one-sentence summary that starts with the name being\ndeclared.\n\nIf every doc comment begins with the name of the item it describes,\nyou can use the doc subcommand of the go tool and run the output\nthrough grep.\n\nSee https://golang.org/doc/effective_go.html#commentary for more\ninformation on how to write good documentation.", "TitleMarkdown": "The documentation of an exported function should start with the function's name", "TextMarkdown": "Doc comments work best as complete sentences, which\nallow a wide variety of automated presentations. The first sentence\nshould be a one-sentence summary that starts with the name being\ndeclared.\n\nIf every doc comment begins with the name of the item it describes,\nyou can use the `doc` subcommand of the `go` tool and run the output\nthrough grep.\n\nSee https://golang.org/doc/effective_go.html#commentary for more\ninformation on how to write good documentation.", "Before": "", "After": "", "Since": "2020.1", "NonDefault": true, "Options": null, "Severity": 0, "MergeIf": 0 }, "ST1021": { "Title": "The documentation of an exported type should start with type's name", "Text": "Doc comments work best as complete sentences, which\nallow a wide variety of automated presentations. The first sentence\nshould be a one-sentence summary that starts with the name being\ndeclared.\n\nIf every doc comment begins with the name of the item it describes,\nyou can use the doc subcommand of the go tool and run the output\nthrough grep.\n\nSee https://golang.org/doc/effective_go.html#commentary for more\ninformation on how to write good documentation.", "TitleMarkdown": "The documentation of an exported type should start with type's name", "TextMarkdown": "Doc comments work best as complete sentences, which\nallow a wide variety of automated presentations. The first sentence\nshould be a one-sentence summary that starts with the name being\ndeclared.\n\nIf every doc comment begins with the name of the item it describes,\nyou can use the `doc` subcommand of the `go` tool and run the output\nthrough grep.\n\nSee https://golang.org/doc/effective_go.html#commentary for more\ninformation on how to write good documentation.", "Before": "", "After": "", "Since": "2020.1", "NonDefault": true, "Options": null, "Severity": 0, "MergeIf": 0 }, "ST1022": { "Title": "The documentation of an exported variable or constant should start with variable's name", "Text": "Doc comments work best as complete sentences, which\nallow a wide variety of automated presentations. The first sentence\nshould be a one-sentence summary that starts with the name being\ndeclared.\n\nIf every doc comment begins with the name of the item it describes,\nyou can use the doc subcommand of the go tool and run the output\nthrough grep.\n\nSee https://golang.org/doc/effective_go.html#commentary for more\ninformation on how to write good documentation.", "TitleMarkdown": "The documentation of an exported variable or constant should start with variable's name", "TextMarkdown": "Doc comments work best as complete sentences, which\nallow a wide variety of automated presentations. The first sentence\nshould be a one-sentence summary that starts with the name being\ndeclared.\n\nIf every doc comment begins with the name of the item it describes,\nyou can use the `doc` subcommand of the `go` tool and run the output\nthrough grep.\n\nSee https://golang.org/doc/effective_go.html#commentary for more\ninformation on how to write good documentation.", "Before": "", "After": "", "Since": "2020.1", "NonDefault": true, "Options": null, "Severity": 0, "MergeIf": 0 }, "ST1023": { "Title": "Redundant type in variable declaration", "Text": "", "TitleMarkdown": "Redundant type in variable declaration", "TextMarkdown": "", "Before": "", "After": "", "Since": "2021.1", "NonDefault": true, "Options": null, "Severity": 0, "MergeIf": 1 } }, "ByCategory": { "QF1": [ "QF1001", "QF1002", "QF1003", "QF1004", "QF1005", "QF1006", "QF1007", "QF1008", "QF1009", "QF1010", "QF1011", "QF1012" ], "S1": [ "S1000", "S1001", "S1002", "S1003", "S1004", "S1005", "S1006", "S1007", "S1008", "S1009", "S1010", "S1011", "S1012", "S1016", "S1017", "S1018", "S1019", "S1020", "S1021", "S1023", "S1024", "S1025", "S1028", "S1029", "S1030", "S1031", "S1032", "S1033", "S1034", "S1035", "S1036", "S1037", "S1038", "S1039", "S1040" ], "SA1": [ "SA1000", "SA1001", "SA1002", "SA1003", "SA1004", "SA1005", "SA1006", "SA1007", "SA1008", "SA1010", "SA1011", "SA1012", "SA1013", "SA1014", "SA1015", "SA1016", "SA1017", "SA1018", "SA1019", "SA1020", "SA1021", "SA1023", "SA1024", "SA1025", "SA1026", "SA1027", "SA1028", "SA1029", "SA1030" ], "SA2": [ "SA2000", "SA2001", "SA2002", "SA2003" ], "SA3": [ "SA3000", "SA3001" ], "SA4": [ "SA4000", "SA4001", "SA4003", "SA4004", "SA4005", "SA4006", "SA4008", "SA4009", "SA4010", "SA4011", "SA4012", "SA4013", "SA4014", "SA4015", "SA4016", "SA4017", "SA4018", "SA4019", "SA4020", "SA4021", "SA4022", "SA4023", "SA4024", "SA4025", "SA4026", "SA4027", "SA4028", "SA4029", "SA4030", "SA4031" ], "SA5": [ "SA5000", "SA5001", "SA5002", "SA5003", "SA5004", "SA5005", "SA5007", "SA5008", "SA5009", "SA5010", "SA5011", "SA5012" ], "SA6": [ "SA6000", "SA6001", "SA6002", "SA6003", "SA6005" ], "SA9": [ "SA9001", "SA9002", "SA9003", "SA9004", "SA9005", "SA9006", "SA9007", "SA9008" ], "ST1": [ "ST1000", "ST1001", "ST1003", "ST1005", "ST1006", "ST1008", "ST1011", "ST1012", "ST1013", "ST1015", "ST1016", "ST1017", "ST1018", "ST1019", "ST1020", "ST1021", "ST1022", "ST1023" ] } } golang-honnef-go-tools-2023.1.7/website/go.mod000066400000000000000000000006401456614407100210110ustar00rootroot00000000000000module honnef.co/go/tools/website go 1.17 replace honnef.co/go/tools => ../ require ( github.com/BurntSushi/toml v1.2.1 honnef.co/go/tools v0.0.0-00010101000000-000000000000 ) require ( golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a // indirect golang.org/x/mod v0.12.0 // indirect golang.org/x/sys v0.11.0 // indirect golang.org/x/tools v0.12.1-0.20230825192346-2191a27a6dc5 // indirect ) golang-honnef-go-tools-2023.1.7/website/go.sum000066400000000000000000000110361456614407100210370ustar00rootroot00000000000000github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a h1:Jw5wfR+h9mnIYH+OtGT2im5wV1YGGDora5vTv/aa5bE= golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= 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/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 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.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.12.1-0.20230825192346-2191a27a6dc5 h1:Vk4mysSz+GqQK2eqgWbo4zEO89wkeAjJiFIr9bpqa8k= golang.org/x/tools v0.12.1-0.20230825192346-2191a27a6dc5/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang-honnef-go-tools-2023.1.7/website/layouts/000077500000000000000000000000001456614407100214035ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/website/layouts/_internal/000077500000000000000000000000001456614407100233565ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/website/layouts/_internal/twitter_cards.html000066400000000000000000000023231456614407100271220ustar00rootroot00000000000000 {{- with $.Params.images -}} {{ else -}} {{- $images := $.Resources.ByType "image" -}} {{- $featured := $images.GetMatch "*feature*" -}} {{- if not $featured }}{{ $featured = $images.GetMatch "{*cover*,*thumbnail*}" }}{{ end -}} {{- with $featured -}} {{- else -}} {{- with $.Site.Params.images -}} {{- end -}} {{- end -}} {{- end }} {{ with .Site.Social.twitter -}} {{ end -}} golang-honnef-go-tools-2023.1.7/website/layouts/changes/000077500000000000000000000000001456614407100230135ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/website/layouts/changes/list.rss.xml000066400000000000000000000043141456614407100253200ustar00rootroot00000000000000 {{ .Site.Title }} – {{ .Title }} {{ .Permalink }} Recent content {{ if ne .Title .Site.Title }}{{ with .Title }}in {{.}} {{ end }}{{ end }}on {{ .Site.Title }} Hugo -- gohugo.io {{ with .Site.LanguageCode }} {{.}} {{end}} {{ with .Site.Author.email }} {{.}}{{ with $.Site.Author.name }} ({{.}}){{end}} {{end}} {{ with .Site.Author.email }} {{.}}{{ with $.Site.Author.name }} ({{.}}){{end}} {{end}} {{ with .Site.Copyright }} {{.}} {{end}} {{ if not .Date.IsZero }} {{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }} {{ end }} {{ with .OutputFormats.Get "RSS" }} {{ printf "" .Permalink .MediaType | safeHTML }} {{ end }} {{ if not $.Section }} {{ $sections := .Site.Params.rss_sections | default (slice "blog") }} {{ .Scratch.Set "rss_pages" (first 50 (where $.Site.RegularPages "Type" "in" $sections )) }} {{ else }} {{ .Scratch.Set "rss_pages" (first 50 $.Pages) }} {{ end }} {{ range (.Scratch.Get "rss_pages") }} {{ $.Page.Title }}: {{ .Title }} {{ .Permalink }} {{ if not .Date.IsZero }} {{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" }} {{ end }} {{ with .Site.Author.email }} {{.}}{{ with $.Site.Author.name }} ({{.}}){{end}} {{end}} {{ .Permalink }} {{ .Content | html }} {{ end }} golang-honnef-go-tools-2023.1.7/website/layouts/index.redir000066400000000000000000000004071456614407100235420ustar00rootroot00000000000000{{ range $p := .Site.Pages }} {{- range .Aliases -}} {{ . }} {{ $p.RelPermalink }} {{ end }} {{- end -}} /pricing /sponsors 301 /docs/staticcheck -> /docs/checks 301 /docs/gosimple /docs/checks 301 /issues/* https://github.com/dominikh/go-tools/issues/:splat 301 golang-honnef-go-tools-2023.1.7/website/layouts/partials/000077500000000000000000000000001456614407100232225ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/website/layouts/partials/breadcrumb.html000066400000000000000000000035401456614407100262200ustar00rootroot00000000000000 {{ $isSingle := true -}} {{ with .Parent -}} {{ $isSingle = .IsHome -}} {{ end -}} {{ .Scratch.Set "breadcrumbMaxDepth" 0 }} {{ template "breadcrumbcount" (dict "p1" . "scratch" .Scratch) }} {{ define "breadcrumbcount" }} {{ .scratch.Set "breadcrumbMaxDepth" (add 1 (.scratch.Get "breadcrumbMaxDepth")) }} {{ if .p1.Parent }} {{ if not .p1.Parent.IsHome }} {{ template "breadcrumbcount" (dict "p1" .p1.Parent "scratch" .scratch) }} {{ end }} {{ else if not .IsHome }} {{ template "breadcrumbcount" (dict "p1" .p1.Site.Home "scratch" .scratch) }} {{ end }} {{ end }} {{- define "breadcrumbnav" -}} {{ if .p1.Parent -}} {{ if not .p1.Parent.IsHome -}} {{ template "breadcrumbnav" (dict "p1" .p1.Parent "p2" .p2 "depth" (add .depth 1) "scratch" .scratch) -}} {{ end -}} {{ else if not .p1.IsHome -}} {{ template "breadcrumbnav" (dict "p1" .p1.Site.Home "p2" .p2 "depth" (add .depth 1) "scratch" .scratch) -}} {{ end -}} {{ $isActive := eq .p1 .p2 }} {{- end -}} golang-honnef-go-tools-2023.1.7/website/layouts/partials/footer.html000066400000000000000000000060561456614407100254150ustar00rootroot00000000000000 {{ define "partials/sponsor-logo-resize" }} {{- if gt .img.Width .width -}} {{- $rimg := .img.Resize (printf "%dx" .width) -}} {{- $rimg.RelPermalink }} {{ $rimg.Width -}}w, {{- end -}} {{ end }} {{ define "partials/sponsor-logo" }} {{ $img := resources.Get .logo }} {{ end }} {{ with $sponsors := $.Site.Data.sponsors.sponsors }}

    Sponsors

    {{ range $sponsor := sort $sponsors "name" "asc" }} {{ if $sponsor.enabled }} {{ partial "sponsor-logo" (dict "name" $sponsor.name "url" $sponsor.url "logo" $sponsor.logo) }} {{ end }} {{ end }}
    {{ end }} {{ $links := .Site.Params.links }}
    {{ range $cp := .Site.Data.copyrights.copyrights }} {{ $cp | safeHTML }}
    {{ end }} {{ with .Site.Params.privacy_policy }}{{ T "footer_privacy_policy" }}{{ end }} {{ if not .Site.Params.ui.footer_about_disable }} {{ with .Site.GetPage "about" }}

    {{ .Title }}

    {{ end }} {{ end }}
    {{ define "footer-links-block" }}
      {{ range . }}
    • {{ end }}
    {{ end }} golang-honnef-go-tools-2023.1.7/website/layouts/partials/hooks/000077500000000000000000000000001456614407100243455ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/website/layouts/partials/hooks/head-end.html000066400000000000000000000012211456614407100266740ustar00rootroot00000000000000 golang-honnef-go-tools-2023.1.7/website/layouts/partials/navbar.html000066400000000000000000000036041456614407100253640ustar00rootroot00000000000000 {{ $cover := and (.HasShortcode "blocks/cover") (not .Site.Params.ui.navbar_translucent_over_cover_disable) }} golang-honnef-go-tools-2023.1.7/website/layouts/shortcodes/000077500000000000000000000000001456614407100235605ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/website/layouts/shortcodes/check.html000066400000000000000000000003061456614407100255220ustar00rootroot00000000000000{{ $name := .Get 0 -}} {{ $check := index $.Site.Data.checks.Checks $name -}} {{ with .Get 0 }}{{$name}}{{ end -}} golang-honnef-go-tools-2023.1.7/website/layouts/shortcodes/commit.html000066400000000000000000000001741456614407100257400ustar00rootroot00000000000000{{ with .Get 0 }}{{ . }}{{ end -}} golang-honnef-go-tools-2023.1.7/website/layouts/shortcodes/content.html000066400000000000000000000001301456614407100261120ustar00rootroot00000000000000{{$file := .Get 0}} {{ with .Site.GetPage $file }}{{ .Content | markdownify }}{{ end }} golang-honnef-go-tools-2023.1.7/website/layouts/shortcodes/details.html000066400000000000000000000001241456614407100260700ustar00rootroot00000000000000
    {{ .Get 0 }} {{ .Inner | markdownify }}
    golang-honnef-go-tools-2023.1.7/website/layouts/shortcodes/faq/000077500000000000000000000000001456614407100243275ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/website/layouts/shortcodes/faq/list.html000066400000000000000000000001141456614407100261640ustar00rootroot00000000000000
    {{ .Inner }}
    golang-honnef-go-tools-2023.1.7/website/layouts/shortcodes/faq/question.md000066400000000000000000000004371456614407100265240ustar00rootroot00000000000000
    ## {{ .Get "question" }} {#{{ .Get "id" }}}
    {{ .Inner }}
    golang-honnef-go-tools-2023.1.7/website/layouts/shortcodes/issue.html000066400000000000000000000001631456614407100255760ustar00rootroot00000000000000{{ with .Get 0 }}issue {{ . }}{{ end -}} golang-honnef-go-tools-2023.1.7/website/layouts/shortcodes/issueref.html000066400000000000000000000000531456614407100262710ustar00rootroot00000000000000https://staticcheck.io/issues/{{ .Get 0 }} golang-honnef-go-tools-2023.1.7/website/layouts/shortcodes/option.html000066400000000000000000000001321456614407100257520ustar00rootroot00000000000000{{ .Get 0 }}{{"" -}} golang-honnef-go-tools-2023.1.7/website/shell.nix000066400000000000000000000002771456614407100215400ustar00rootroot00000000000000{ pkgs ? import {} }: pkgs.mkShell { # nativeBuildInputs is usually what you want -- tools you need to run nativeBuildInputs = with pkgs; [ go_1_19 hugo postcss-cli nodejs ]; } golang-honnef-go-tools-2023.1.7/website/static/000077500000000000000000000000001456614407100211725ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/website/static/_headers000066400000000000000000000005671456614407100226770ustar00rootroot00000000000000/img/* cache-control: public cache-control: max-age=604800 cache-control: immutable /webfonts/* cache-control: public cache-control: max-age=604800 cache-control: immutable /scss/*.css cache-control: public cache-control: max-age=604800 cache-control: immutable /js/*.js cache-control: public cache-control: max-age=604800 cache-control: immutable golang-honnef-go-tools-2023.1.7/website/static/img/000077500000000000000000000000001456614407100217465ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/website/static/img/logo.svg000066400000000000000000001762221456614407100234410ustar00rootroot00000000000000golang-honnef-go-tools-2023.1.7/website/static/img/logo.webp000066400000000000000000003160201456614407100235670ustar00rootroot00000000000000RIFFWEBPVP8L/U#Ir ŦpG:(ekdMA-߁IxٰYӊGs5e?dUMHAxFsJ#t OVxVaGֶU͈[-@6?u;;K@-򇧶 lUDgixLaSD :f_-v;^+܁3JqF`pdfcX+`i)2zRl1C`)ƒU´'<0%3c|0be,4\,`6 ْ m0.mۦ2mS,$0!r$1HRr|C,yBw}}H0V,D2$,!103H@JF_sLƿݵm m۔}m$EY= 7咸X}7;&4Q/Ҩ$Une2e[,Z ||ϯ+e' Y ),Y"b髷,! &$ҫ\R˥3oMRYCj"իwz 0fڴ\RB,=\DY"){lxg;EN[}q5*2E ,AHՀv"BoKI)8,9aBeJ#zK}$Lu:8L'HoAL,zs@.bB9k@ RA _W)Y[Y.,ZjLUwVB4\/Cf](3XTruS26em:ːl*WKo,ESnKe!ٛ2..xru)4Ŭ3a2 e()܎ ZSY;SѤ^Sr,3'eM&[YU`/؋eEO7 PډRK[۔ity+ֶ\_RՔǬ3E`6ed9)]$m1<B=@dm[$֐D@uu \$g^Hgzz2lc0e[=iFضWZkNƶqkm0w> m<2Olv2+v9sjm۶Q6^x4msFlglƶ3Qlضm۶m3Gu4:ٶm6r$_Ǜ m$Gr3mݿ00ɶm۶m۶m۶m۶z5$Y$IJómӏl@ݕO<񼿄f*Tw_`""Dy' 2P`'pc:0~+`ːlͮ[e?l=H @f#8$)3=A\0bi30'erZkۛM9rI ;4ՙsu.H۶)*=m;c։N*n{͵\sm۶mkm^sss1~1ʶm{eʜ m:eҎضm;e۶m;m[/ZmTLvĶmNҶmUOz۶mNZnض<YMm[}mnM슈!lVӊ0DJ /XIRDp.D3m#5>$m$I}F*kv/aŎ]h,)B%!0 hp]N>]8[:;8e($9DRhL {тbWQ WH.? S*B|o %#NGG a6!tz󖾜I4;5u0:_F@ c5v`/auR1a'Wv@stE@3 M:GYa߅Gp`[B98l?Xqw>nNG!OS sBK 1¼ ktI 0>pt\8+jDGa9^8>*! r0vZПL+>!sp,5p:Z[uAٻ>BGv>w07'/"OmqcOGƈ+ǘBCv sEx =EiUxX@@$0˿4N@)~ 9GE 1▜}]xЇ{\* ʟ˟= ƓMEwht{+|ѩaf# tkaА pf\(]}LAJ%}p$;GA趉l9Nݾ>9; Emx-+<8špŋ.`>%qIw7jNI#oFmbEKNGp^]uC>0|T?D'(a5#0k)F׎ÿMe)j%NCƤި`>"}Uz_-&~*m>e9m&#v^5îq3jR7c|DT{?ؗ$$5o7ZL:i5æޚYTx.U "NIk:/ [PQ5zp= ]iLmBTOμ9’[MAD淚֢([HG|H+w?lx67OF({(-/T6HoBE9Ԧ|^͢%EƷj]qIqNQ-Ko]]¾'s:t\^ CP{0l:Ιe QkP5 O6r!ɥ4RÏ5KLOw-{ y:?R9SE-&$j o#MX!&{Rot]:Px|&1b79xz}Dsjsv!OҜuwƦdQ 42Ӣb}ꩺQyyX'3n$:Ṗv˧' x=0-A5YeIe6(y{΃p@)̕GBc;V细Gdk1ciZ"¢ Vz@'%@w6Wt^iQ>))XV.v'@h.lr_=5dMNXt ;( VlC a!qTQtQ%"eik^/]'"n%U%($N NnGx38!cF$}?8esTɫ*(bco ~NG0Jq1,ũ]WuiQjDͦ%zM#.^ϊk$"h$> -T¦xYx.(aYA \fU36ǡ}/迃.b2*ӆ:io_jj@FKUyE*ڞޣA /F5js^ɫzhXbdQ.Ri,LTUXvHS!R=-(VDU Z%B m=52DO zq0ݬݼւ2S6[ j10:[!k ڣ:P[a!6 *0xlE\4:)(JtzEkH7*qzE(YSfQh$^S5Tha۷UmEH-Vn@# ޳74h<O4;lu]@o=aD+BXS롭6#JVAU:a>B\OoMQ8s'Gl^VM&S3WyOG 배63J?:yqqjopex p$t*7(difB/ޓ)jP7謏gp06=O6ΒFRf1'hcڳ_ޫ~~GMֳA9lQ~պ2U vm6KdcF!o.G&k8x"E*ؐ؞|Ȋqoԕmfjy@]ȒCx\[0VOv]rvOQ :_Ҩ'4ӦXRT̩t;ݘ )8lZN oX*@叢tӟ. H6cJYMQ /^% VR(T /!Ӭ@xF~J756gJf1~5r$j[kDFJ,.Ͷ:)^su)!4 4XiSh7mlV+~|I>qZ7Knq0)ee4JO=Uu/md.WXZoz`VavC.\^ͦ>=}3a7iůGsI^{V"'5$؜7'EN` @CN!٦z22j]}i:dwtl?$Jrל)h1{I ?CU& ulr.'@WC;̞d~>(T Nf>y|R]eMo* (~[T_o Ke4 %Qٝ3ƜhlAs Vn*N5Lji.c80^aȬlC_'E'Pփ?6J~%L+ʍ=ή Wyв:x0S 6U}/9`/`qqq?WP_+JwITlA5e->*[RL:zmQ~ӈliӄbg_OOIfYgl萾wSHŸBۊ6!n~&(B- \IPb!DQ%w pLh6Zlp3Yp.bk0A_6+B=@Aq0Nؼ!еYt'۔*p~饵`Tj dCIIu˔,TV{[x #V}]6љyaUU1ajeZ([t~B:F\ Wmޯ.lpn45gMRUc}Sޣ eB0 xk\qR~xxl,\`4_H)lӫ¢ M#ٰy@03|:] ^i Ld$¢T<KPAOn6ӺF H"/8,Aմ e ~C\]T Z+6T..mKh]xKp&,OkyBD:֤/:N]=T;{Olsng꠻]f366Neus$ǵM~괦U0v{c3x)@eFm.$iH' װ5*enmajA5ܲ5S8B# N| ZL9,b8٪wl@#ȗҳm)D"B(=Wuh#vtLAq ѝw!4'XF{#rf;p[jhW*Ntz $ UҺġ/PIѰѼ΀j͹RVd!hW|8\ǒ!yjo,`hK5M+vRV*ǯ@j #uQМ/&uׅPY pMN)3l6fl93 "72lUpj\۵Np/g#*ެ>Z;c2:>-ZCLREw6zL8#=m 3`LVq@_])^ DoW~iTf^#vLF;(i@={~3(8Ц5zIBwr1yVlnLg^_up<+l҃~E|J7 ؓͦd>W(#6;PYmfS 5 Ѩ >N? ;%oj3,e&4HZNM?v4Ք9@M@ ,냀QfLg+K-QC# Ҽ9m 4m&hځmc N PHw0aPHK&̶:(W1ˢEC5٩lF7AE="\}8epy6DDݿ8_jum 3 :S9-l!ڷURnciUݠ^zl`k i6tm\VS@,x]zKDl,VQHͪgѫJl Y+:4"[8Q E|BwQP[TdP@θYXi$0*"ՊvcM6{ꛦqml!@fSBæ|6\ׂm1a[yPbb`mDqeI~t:g}3ǗZpqnT r*0* #as,A5UoƏ0bO$Whz/|p>m(tj{ZM$\ljI8` R _Q9G3# Jn;P} Un{sCwJq:l[mڸ?;мAM"E7:֑R %&砎)htH H2"i$ eS|)s~Dn ϥF#tUDb|~yuEd]Mko tA㺨[kxF mCv߈LG2,aZ\ah?C)2M  (X3vD(z!uTpaH/JG$E%NAQt:j:iR(/# w-)AL|~cq!KV7f[I9'L>/7JaRPAt]dxpq9h8 2.e*N\b#МYZM~qj5A >VE@Vƥ3(W&pBm؃Ph,(ӝH6` tC! s./ L|+Rc8UztGތ[BuHFx+EE0^yXV\=KD9 ([n`Yq 1**s(V.NK`N@tͪ:H2/_VA`VN^+bi.dG9whNGcbî2r4(UuIY F<|;jR-^-6L*<0Y0T'ҭ-mKU"V R``҅g!V. +J " aügڀ`>*ݬIn] Zʔ9D"? ]= fӋbeݱB͊7%5&p$#uF5nfݖba|`a¯\",{+: ?$!2}v'ķRX?ڭp> &` ֵ"ԢGnaB2*!w F3 \xG;BC&k+*pe }`aͦ>&xXc:uȝTS̔TCX<@mK⹤'^Yin }o09h'-K堦KO ހ`^NTzz% nw> 0eh f8ހ,Ƨ)A?nLAn0֩>,d$i~0 >J.%yqm,&"f*VVR55 8EX,6|XiE V]$(v cQ'yD`2aM+j2Y?"ӌ hHzn. v :qՖH"JQC.z&B˝LMq !44 ? 8`*Y rUd WAZ`A (p\yQȺ6 o%6z1C \ހ˧nY)!q}0ٕX2 :TPm(ހk Cg}ͭP wPE։Q|^AXu tXgZLւEOsFv̅]*` &$2_Ѐji<{Qj[p:i].:nfEI6)LTQV% 4KQ?4~"8 jY2(+qa$<Ս$-K_ZKbѲ?A3 Y L}ZBh.Jl@pme`fPyVp kE[Rⶵ߬Ն٨#p<O0VFBv#X^<EEr`a GI'-YF#_ݗP4Rb'RRAe4$ P{GP ydꮺA3̢%j(,"e^B Df8t5XĒ+ } XͭpaDz%u%xS2-K*$ԶnUBa6ƏBOfB׵n"x,\hO? GJ.նrT5`qEs*ScR/a:V>udɠ6Զt)Lh k͉cQr. =R#KrWEp]GC/mU|(ib Y | K :>n(l$U/{-!GKQe ր\ |:Н],n)q=a[$tzz؋TƶMћ,p5̶Ve ?&y)j[TRYڠ[(*4x0<e`"8dm9,c0/0@ApeL  $rHl{ITBX@ e&BpEwyտ2#H@e_| E@Ǚoo%"%BUPkb%Sn\T^ih>XFK!``3\: NH4 jU#re$ϟd٠,>)ZVހm1SaV4mTOK'y)7V3>>Ť b ҆;6l5sTwꡢD ;r(8RAG_Dm9 RYim@g8V*i׶ fV4ޝzp QZlf>rvԿMR k"} ]@ri+dU!8-P_ ۊ& |C~BAi ZБ/ڒ&LxlRI\EA.XqAX`\L=w=q"KG]qHyT Y Ap)P19P w[4ǂDzI!oOևqv$޶)7 /+y0u.Rj38|G} 뚖1D|ȺYyf=GtКZm?q!>k&3Xηu8Lj]W m:: 1qJ~=hi~sLWRP{ViDũD愸C ) ޓaRՠӬ̙˫ 36KrP(xhGiTe#+/WYT(lNhӆ0(;7-Pn{b/ rYeMdjx[XHs~^ߎ/W?zM X&;]QNk%n J$\b^:S9-$W3 bsiiW1FN1hq jA+p(=3yUjd1z(NTR NW JC4.+r'5']GQs$j> ѥ.ĽbvaNcFQڃh^`wf|~Zsi4eTRŔ#f)`ܼ_~gl d={@;4 sPWן"x*3)4)^VS}G"Wц=!=y&3ig }Eg4_#}V|"C["uΑcOwwsr9[2EOgK4Y:̅u[;l6dy>Em΢6҉#.8{5^7Ԯև1-E.-.:Io3'YI zV~H>8f/YSY t52DD'fZTiÌD-W[ țKq!̝ymxХq Ct\b??Ųբuoe?kˮ{OW",j gV=].%dQdRp!Iֳڈ@o9.<ـg6jbλ4Qƒ?7ĽHv\AQvkI Q V@PJY.5cxV0j-My̤cY@}@vLiC|}Qܦ$9TХ: }P&O-L,cw;8?yL,mJc :Yjוm9^~եV*K$!d?tO-l5 LGJ,`m;A[+.hYh~cπ5ŲI}n @^oyzʜ>fN:,RXuwo&8. hCL<8V~OZya1w9b4Z.*Њs :|t"z\t5zW%Gԫ䗄w| As]m CVp(r7vˈ/*p=zPΰ?yY}{̲g LԤQ> `a4p¹̱J|qgˌ*LGUD6~Yho'˄7D4aȀ q`]ptQdː8S򏑏Rw˥];,=CV<%!s~wDZi&uRiwΑQg]qS4Y_ypC_O!f#:qeLnrj^KNM=!TWql(# _dX.oӠP2yQs*}]Le86';Qz>#59K C}Z)j[/;ԀL7M}dVwmTG4Qжkr".zGw8J4+QRf6\zas@`egmnDGoq.,9h ޴Y`C7kva\QGU&Ve(qхo(T:Sx,L=uљI>z­(" v XpcΊG#+7dkۜ-|ÝRNc4U* o']d%{&A@#$$fC&q \,h8&jǎ13S;jf!C ltu)nbF%kl8 &Q P}X@DUgԣf<}DCgE8 N ݏujx`/8 ѭ.ɬPdJ@λ4- uQNԨX,)j\nt"mYcƦے,{, 0Й,$]ĞPF3Rr>#{r?}$*ux@JD阀wlM*uer+ ,hx e۴CqPE"9oo؜.tqax'G:p7؀d~%h"i+2]UbxyI58y\9%.Ìt$DCu]+WU=&t)[~ׄdv oR!wxUU^i[~FoR{Pd0zȥr,.w! 25 EIye3@P%+8J +ncG/MmB7ѻ|:9WH* oDp 2ÚNɯ+gʑ,joQ&IS؄]3z_1| tP`l͛q"W^l=mOĈȖad`t2Lo=RHWX jH [ȇ% 4耝VYyi{8"*5n( vn+j3D.dSeg*C!aХx B"GQR/%P1ߙ80 J; DqPɦS 4=U>3&Ù1Q13Sӳ*/#8:Tʣ˗,"jӓqcPn/OC g(_u<$+hPLTXLlH*>5x-BBAr84 TlˋzM G wQ-٨DOSٓ uzLNW,};r⫖;s/;i@_su^>'% (7ƣW'֕L+|+u-pyohHdˏ֙)p-p<qԁgtQ*]T h8ui027\Fe٠cJ?=1y>]E#l-ɚ)魔&\򸲡=7q ] 5̐Te#X}? gEeuT)X(/Z[}8]\0H=P9~ʡޑ- _7bGeRWJ9s]y/COŢt˅ڮH ꆄm,kr)s$0 =/;`1:4M1%YWaUQYpr(:_@q@ː>i\&tYK =YC6Г)-zƣ KG]yQ󔭅98&h!VPieGC. T+>3l:eD27'>|$tC yIl=+ЕYU(vO&20'˱i ^na $?AXqxi]Uki. = b@5,,> Q?q{EWL컮*-Ԙd~r}EG8,=%;ʙ 5ku^4Ldt9K$(H_J\CH3[50C1C BiZb5l[!7ZM>[fZ72P?ϩJ7\ ^^!@1`Ά"Qԕ^u'md(;˽j`G2(ỳqpeTQW~ KORfA~.wJF@q~ؼVb8Qu;Йyf-] 2][=3Lr6τ|6OMBrBW:Wd-Q|ȕ^W:f)m$'- JD4D(U>iKcf,$8dte8XΔط.l!槟ỏ /vuvD{\G>fN! V/`v-Z oq8]-`"ܱ4F~ E]96MHls8# / wR-t@X"ެO9x-/ltEQ ̔gB:baV7f0ЊG\A!̈́J=S4'Ф$^lteQ͌E~~b.lIInd `rjZH@"tQJAJʹ1㤮3 g/9.MU\ $dFixhy%F눀{z\kCF@r#i z8YEDžԊwu!O[I"l Jn314H%7Gș1]ԠcJDBxM Eִ:WDkdJU,'ۜҪ.hP>T~ 4-2p֊f r Rr4b([ǢMY5D ' C һ18it^mێ@nwsrc2b5VmFPc냣 2"E uBWDUdՒ\+*;.^CrCะBH/zh!K%t"[muq eoU:NjL2P-3@Ɂ YRj7qiTgB.\4(z*܏$ rs Q؜VuU."ϖHW>UӜn <; ނ8V^SVI'UPӼ(?9P6nPUE\CMhN'4q-=B&+!(tOr*(Xο̫ {qP#u`E"]J4Ȥ{O1ljTb u *'ZMK[Y]`iefYJWHUeӅP+@X64TfufV˒"zSL[їr%)pe'TfA ̥A_Cw!JepI_|g6 xKMª&J|`Z↸t뫆{QxaFHz }V8OAK,](fё1:m )E'|R'&c'YULaM܄AT !%ȉgL8H}Ӝl\azV|w} 9p .*PV roֲtb?NeeyUx[-J Nvf!hxHB,T+CD2"V9UOx`e{ D+!J͠bN9av,XQZld|ҔE <[Z#iVfᔡ*p"eSTQ @U8hI L HW5 FeIR^]U͏GS럂jb}kkݐ1PC_ZTr(PMG%i&{P3砺< teZ0 dfA*Tpӄ$t`u .x+ٯDfF<xP0C몇ȁs|@Qd:#Wt 2 IZ3IɐCD1X*Q7s0ZNE@QlaҴu9R#l5CL4c| GRT EF<[ 2G IDOr*WTxL3!f.p(ҸTzCFb{\נDŽo㙧4oS-_xnh ż*=tD^<e75äD\\rCr\jFة!Qh"zS08Τyj,O Cf`↌!lxNv,oyx|+ `DV<:,!B5C(gHszay I}$Rr0 d5#B#BE yxm5=ISy VXĖ0LrPƂM2:m=K Nf:):0̢>thFl:¿fC$#Xb*TF##v>Pj޵6dd4J٘|V$9T`ip(0L$CE"N* {c3i}%'3ŏLh@84-I@8ȵp. BfcUcr@4?; Э*p.rGo h=Z"fl8IeV+4P]Sy?*W9{6LYiFWM8U4/bda9,W4R(t]hPkH]bL5b} P(usCF7=C?!DDc-P􃳘H71 ?4 BT[OaL3jsWcEKӌCy`/&%4o38D/`>NķQO5Y 'N[]=ޘUGxL7M5|w/;' aUԁ} b9dX Zh@4B&upa\$  ud'FƄu(\Rcwd#=Zޥh.-xwTؙW L]E`<!y&YP}hܕ"s ]s!A}.t1UΆa흽BpxRg*| {FB Zv* {6rxK&l<=BtSvӠ;Z bX㨫)9"H=a//U(&`+ qNвUʃ`B]b.@#V&yD\ Hd]qհk%: ` `jV#Sp)`\1gTT,c`{ 8$aZ@?tIU@U*Ed6FMP JDDaI!}áAn  'crL 8KWm&v\|F p*nWAIPJua!- .?p[$G"GXhxK MeŸ,`ب*IxbIzY*zd« ,√h ¡k6Ph ;%fiDxKTI~M'"+iȔEK,B**H);8 j1G ,8ԢDYԙa]w$Yr=Ts"J! _7D٪IHH`, z DGO$RH0Ɇ{|nb*--E`F[@ŏJZCфQfTaUD%* A"ctĂ ͫA Uv \JFx9%6ax F-&8Hj2b ߐ{@~,A]EbĔC V "dˈ.d=ɩ@`Îᡯ,N]Y4xt P{$5X;6`Ԃ!.1j3m-;\;Or8"p@9 SCҸC΅ׁáXZ#&dhYTa=q9Ԅhc#D{}PoLc6@J.E"(au)hu ȝjzUdV D* ;eqxLZt[/I |EuGteNBc?طzpA0m`ם=n[e ŭ^kă6JH99@I @ԭSC*@xb빟G%F^%! 7VJ8Lu`D axFrƾ{b :u8:O Q"$'pL]BbN=H}`67(*8| mnMoyǓE'%G;r$@=$$@PЉ( _v7pW2}C7Åإ!~뒚7wʽHxÞFW H:nDd4FH )G kRmC Ipq9N<0YeO#a-&Rz2DX3OS셦$߮C~p`83qQ$57Z&HD6@ ɪpKq5!:7x{9-ɯGhZ\M`Jn%t` 7rv!rg(x Ҟq13h8cn5VXJQka:SAf 43t AF<=tҊT@=KIB=%{T2#фǍ-;|餃ZC*5 7Dc/K[4ǩW R8Lk!gc[p+f souL+JVo4Ά0ArDDƄ=\[ģ#d-n&:9[ t>D> 0֮o2b& >e|L}59VaFHǒ3"s*ZO3%k;v? %M"(naP\]ˌ l <9ӷ%Gk1>tu`-$O/sfA5:j\2A>Hx:o.~?I=zr%Ͱ< k UdTVҞ%~@]:R]aQùg6rpm*SݤhګƐ;CPSĬirt\<u`.8Uɝ!O{*KM2gg OrR^Τ+Z\agS M 4 @qvU5'Q $Pȍ-S/N.yV9_nW6 rE˲/ ?\פhv[J#wjVm9{V۱tږ0Qt9DHR9\'@$y@2KnbGr`ZR1ZIa=2>VE$5%T 𒸍@>KMu4ju'0EpCvsR"'RtŒiqE9B+g"GddCejìG ^ gOEd"}7.A?.d0D]x@ 9ŕ kucҜrIS ԱrCCnVъaW>s$k\>ܝ@k۽Oц8W)8Yp`\0Cg+,vN=H?9&K#  MH| at@ԡR O %I2w?BMl tmk:C9 ]R)i<:I b%a]?k\B:`rmq5,VҸ^&PՄY{yQԠСM@ѪBQ4`%Q6 gjWpB8J8-&^0~kuLwȂH?>Z8<œY`ӣӔ H7X4)ӴBɰn C~[㔦a59h?*~KS^;gg:.4҃2(67O=c%j7> ([Y5 ƂIdHVi a编M(Q  LP5@p5 LK7]LA(nOCt\ioC7$89 q!,F`N|Z#9Xڐxr6tN<2;`7bH>Y涑LKAE4-|QE iXZ3tOf\U#*C*Q&nl"h G-Ku St*La 0Nwv\ReTx;iP}@ZI%b5Q'b]=#r/Ի ~J |gWUsْ˥rCgu{̒3/ܼV F-bhB;%<̈́.oônJ%ĤsG,Q[P{M];dL0G A5Y'!}\r%Iz%8b(GсQ)y2;So`Λ5;!.քUOoCJM|B?e2'tJ8 Ge}-y2'Gޣ2"#%JEscn=ULqZg qW15λ'HbC(K(|ScU[Ӻ0$I@XSa#I@ôvb0uoEPн_inX9Qwӯc~=!x>Iߘ1Қfxtx%tq(´&&:q}sJ6)fh-R&|PR?J{:d)uNwHm0lG:UClarl&YGZrj׎1WC0h\w@#gggi:t{̳A-b fPَeT9`JQ]2 –#c!Y7c7[!$ՐL߅rf8OѩѴ0K;"?b ~g*_Irg.=;nwB `\[`nsV#+4!ٺg_ 9B Q_rsä@4Wh]A!Nh }!N]aFq0! xAi$ B7X>e Cn#ڽ?__m&ݾ$r\IG11dGRoKҞmz00U?WNL>h6.8BgC@v]?F%=@.LeY:m8jHEā6QvR5%W3!u|Tuybbrؔ&5O^c]rΗ*6%*A>OX%ch.aa9K:ƴ%!] ]~?uarlC zxȔyx# 퇷9hY! !M'ïg0-%KBCD=d~sPD_R&YgD|slR~[U1a.XAX{Q6SpRDm7d<_Ijp1욅dؤƔ<-,9< ebűl#X |@ xy90'Laqq dA1'%sJ;iW={_A\9X25|1T0Q_B(N( ]{i6*{}C\d%QĈM7 $<6@ȃ͹< o(.3ߗFV#/ {TBEK/lI-]oƼ8BxcE^I#Yπ91rU4r0!b5tq_Gȵ'Tmlw.ɡu6q1*ɇeKt%'{)yZG'Sag6}PZd7FOD1@:* zQpq6a1&OU Y kv2vH| i4vc,s kO1|"6o0ރv'iBGT85B3]ڷ GiGFjbjntXՠ?{"UjoT b$K]3C)b{VQhSǐO};gK;pq.bv i'+4gyO\^) ;$䪍bxl:3э;d R,A`{=0h ]z[|]Qp{I WoRf9Z{FV:M5!@znj׎& m.τr^bdM r ]7LIyAh3Fo ^<~2 VtlaCW2_\ j^C&%388K~*gس56="I~wz`u(63a\ R@h;{6A`Qqqu)͐V@Σ`:IԻ-:M|:]h95Z<|kB1ǰl}dshH t&GfiO~W᰽KX4B8Hڨ.z6[- nBiZ&ZD 'k0~6 $ PS"$Ёx*ry٦&siJHϠG !T6 >=>A 0V<eHm8hۿǤir64*G1܉­I0ar c:~4IzN$Qa Q}yʂ!}Zm?~_x ԺJ܂fbHD "B?lOt7ӕj%grV85.@ ~K8x)gIBvݿPr'a="ʎ/y FA%,حO~H~5_sPIItq$c˨jnn6*$9 j& aOH&,2_v~^YC*a v,~C^'O k>Wi1l{7ta4HɵŰ7<Fs)sȃk*~\͗x;& F Nж{M6~(">tvE=Wj2Y0ŶjmH\T [MX$FkP CB^GE4qD !;.D<0cHXX}"LOՃԱ2uڣxN͆*thY_dNB=6YoS@v1Lx .I΄} -Xم,fh R Q!;ۋ7$0{VQggůK9u~M Z7~fixp@<͘ vD;d`}bpj*{yPMMN1u'x?pL> 4 Ɔʪ~H)Ըǿݰ)dWk2 E3 vEnht E~-1iBnti /= CjbV[x(-6P1 X+>Nk6)mc% dD QG/4 #FX?Ay;#|h?Pp:Rn. 3,# ͳA\*Q:ͳYCȊHhX /##T՛&-5K&t.Œ_Yb2 [;mr(֣w cx'ImhVcL9 9 ~癈Yx<UWڧDGѤ,* o/ t`} '*{w X+b EVS N;EQc;2u& UoG6:Iz\WUs'`Q19 uG`অ4f$ 6ef+gtBӂH7E,ZǦirb}-u { &qec8ײ=y0႒LȸUiÇ {gDw?̂:`.~zh6c1s/23 *l|U#hjy|Sc#i551/d|Pa! u4bfѓmH <&HH;[q&\fԅJ8\t*fkªe`i_$9\/`[ΡGJ^{8#1Yx;I6~("WͭTBN͔z@DmR#hFh3wf$QD=pHQ cԠ4e*7n_Me :~:"1yk~ȧo!lW4*xrLX% (d$''rR@+xy<Ⱥq; -ˆxKE/-~(Խ󲫬r(]!NP%ÎbwfHz_SI{j?q4t~ًiOS!;0rT;c(0QT`*R@b21n1|C$Ass?=ωP-;ڽLh6^ttȶf7OB.au`3ʶ[0ˢ~!"p\>4 OXBkc(humMf.I/R$Vb+{CPpvsUeϰj wO{rNf zn0CZ[B02<섫g$Q Y6ЏbdMS xuԻiBr܂eQToB uTGBW@֙YP6PݯU伱.H3<6) _R`=G , )kVוw5 ^*ةu5G47GmG@h6k1eMt,kbne:f b&?/Ū{L __Gm8I@ǐGx=Ee "Cik,MCn}A g6YU/04Uo"<H\/[˞y]Yr닂Ƌ 9&qD< tbOcdgO `7pac89:$TVSކ:/7jf exM߅g O7A\)츍Tbƃ 7SP2d7ݢct. ZsĦ&Z56 N5qutrONWHdcxB@1O-; :OK9_Ɵxؘdž鼡B8WPY&*Ѹx0j0 3dP2i=e+sv%,6 V丱i O{Gp&9,zbIǾ6,IU#0QXnvC]c-(QmA%,Pw쳽|}~4J2̔AimSg"7hhHҐbɂ?.8&`!7b/>"b&j}3=U>( /֖5*kĉv, W #5p 2"N 5Jh!X(0ͳ:S$;6n_f:Z2UW%gWcU&歠s./Dj%J(C~Ĭp 1fZ P}wL[&kzT (r_vPEsS.2ޔ%QB2SoD(Ù6 *R5E9Y퓮y g<7i "LEP9sdʿe7vs7v:)`tOQ o{#Az4S!`حK?͎$,ͯe /s2SVİ"؛89]\i+bnĆQ)4 }r( U|Kl+DPO^bop]^,ex&fo8hFք)M~b@x4Qz5hiCs(i&%;;8 bpFjT4x2 t4qXege!2{VozS3 ݎ{Tb'K e:fϸխA% yfkқ̍8GnMl [;RaS`o/tzMV~ ȡFLt4n9 "v64/sCx BXt hO'@.0 9&3襲ab8n%z{\%.Ya^_}hBg1y])&ơ~U݄1y$gU.αL ,g.iH:N"FM5n Ls$9xd7 Jgfqcx/=jn}l{g{y@d<=$V7 Ā7#S^vn'˴p/ %?'\]FBw[}YM`طt2xn N!xcF!L{}Z:ClIZQzfvr;Pkap&V2VT'@֫6f0VCM;lG H:I(U W4Hpr# ;{5*nOQv6d.vKet UׁX_%8]ڻ-X22m">3#^dkz/n͝I7n-g "o@z~8Hw$ O@DCTOX|4#@z6SM{>vaKU9{?Wg ?@?9;EtA#IB):b_UzX߶F'#>*°+aĎnW'h"ׁ`W}:?S@9NRiY&#To,@7I ~7'MJdE&k`l$dZGܾNCe۟'Ȕ-׺~HU-UH䌼!?ߜfw2F瑬7֕ Li0Q9@U a(f,,%w뿼!!% &ߜZ=npEwŰ[ibU ]܈& dT1= W$d ?\~Pӗ"v%}d`.?oSpKt\ 3俙UpW`iNlTTNd MD`#ʬ d-9wtޒ[I~U gVᥧ>B⨪P Ƌ`r$?: -{FH(oL"z /If)k?M>EƗY}BdtӒn$yt!3cѸyРP~ցFmA#H:Vn8ZĘw.F:OںdKcLn1K.y4ZUCC A Zt$hUJ"8o@A+%X L$x.d=_9N4~% ASr˒=h6fG,`N>T$XX#tиlU1V.](u])|*tbO*g3o^@%x`&A]O}m<&˂HyfF*8}ϴ{q»g ,w8݇0U"Yssfb _Mw|C42Ўz-fȥS}ș_x .At"5cWR?_&.#MP^ysv`ٖV%7*`'8 _G >A_O h;CƤv]).{uB¹YJ$X$_yS3\|+UnCYM21}yܪiD՛k :dnb}{#tK2W0UdX=ň1Rʌy[)E',$hBR=@UE$EEc 5m!q8w ߼Cfvיp 1+Gn&c A F-j~H˚\F+aK/ W"b8N/hsGjЬUŤX!Go6c7- r -[!XT -'Ra8zՀ06%_؜R!tŪ :c1)|s pJp`AOC#)=;,bg-_,Bo,Jk x|8jl1=Fmxy`8NphDD:YߞhïСGüUeBUj]T :?#! +zb\s|zg~o6sXoG;ا,C0U[΄Gb f,N0TwKp A/bDLF0z|zğQשWħ# 7hkPuD\,Iz\ShUCpW*"0 v5cڲPNH#RO+jCXIZOɶ[p$H#2Ć 8w2'j8/ҁ[> MJSs(+NrVc{s>x(zPL YlpJp`߁2 ={ q)~?8Ŵ) W&.I?z CTkцEg:^x'{ qؚ{SEMp/mNh>\C(Q)ԩ@J+Q!?t?\%znk"!(DV9lj8F%5铈 ^B:j*3}1tHg>M]Y[Ѷ|WULO0 BB__(E!BES1`|ybV3@c ^=rԭ?R <ö"YȚ;';:Aiw!LNYU*L8&SlSe%Ua()p؉kѬ5mjSs7-8 A;iyTB Y8i`$ v!b\UKGsP]ynjڍ>H;Ha5U´cl ?4VʄgV0bBZuoz>[W؍UeM :HM )t5\adA)Q`?atroCRPbH_ZMŷJPLq%E!pDVc }%lt)}2jٜ}4A'Uծh|C:0Չ0} $)18aV1U`u*9'ltW`I*P+d6^Tp=)9"h!MtDi?ke.4 sUX4H 2$x.qY୧4یJj] +ILʗ)% MrB7ci p9k"F5C5NP%7}U9v&{: CmDf|B,Hc7˓Rx*#eI냔w1Pg5s@' ap %9+" Fqf\[HWE+=fV2HwE܆4T kUU@GR1o?iarADK z; VnV&4uIp/9`r֧M$XD)049:0 |Iq |8AW 2W,K4t6@EN=|F3T6ZpȖkۗ (]\3 ,AY"S8 &xL.y[q9<+b]}|8[bG"rm)ȡC@Lx-=g*&C;!fx=ybd9ֿS1ODVYNݘd"t{zERHFT4j Mx_f rUZ=dn02Φ YuAHtJLIy?!S^[F6cFavj^Ҙ-qO @^܌J`OW+`[ΔVU,mJ̫)z)CdyɗV+{~cy>D/fNCLΔᔦ Qc$>`.đs |i!`?x6=A[c=Z9j­bʍ>\;0ÙVS~&h^ *`Wu2:ڧ0Р5б#}7:m^Y:f=&LeIs,^I z ]f!COvXI嚮A|`&7A8R9mIDIJGiGPY;3@>1&3z"<)@I 0tp`xNRɋ]P\. V~:eB#X5氣7PH㜿T'e%h+i!; TQMm" 8mkiU:@UZ!dks+:x5-qUT4ov(40CWؚA?"X*d|*e3ct:ջG2aCJ0bO0 L땆YS=DFس/d{.!w/ L|V; ZT!8o+[A0fyòާrFfv $!ݜ%,/RWK$/ː_E6{L^pW4Xr աNMuDYr@Я0q>Kʆ=O[ow]P }Hf\ |;jʁ-{MmRt.`K+kGxG$QϦ4g`7`Cՠ< 5uA!Dþ?ZQzwZπv֧4)SKH8pY@Z my0Z|u Pa8i@6S^ ɣ1>auR~ۚ=/ ?. aM-;3<9B>}yƢE` b2/ (5w"]!څxv+KӸ*9| M5g?&эc5Wʦ #}d?V#7},y-ï[quIov(d=B/QS*?F?4T'OIP r9e4*pxH(+ U^taDP`YAq")X<I?b 0o;J^&clR.7ZCƌ"W "gG?s|dU}N։==vIa*}D &2*Z4pWBޗE6~/tN( UKÎikLUSS0t4,[&f,H_ 8>~ZRa:݈IJVHkGӧ0[ ^䅼aY+94?PP3a8"%~nEPr2õqcMQղgϥi{9`c8}򘘉= <5ESt9ߓMURA9J]hSCSP1@j ;[#\r6py $L ͔jg6? .sLzQUބ73Ü"rP:`ѕ!z<ۯt'ѦIWq2 %pnP<ʖN?yҥmc75"tk٣tS^f"z@CwڷTs/jgC pk||p-"ٍcQuSåǝ 15+mB=fÆ hO禎dDjx+Sxǎ ;Ѭ2{ڵeN_8@[ɓ×.7@ۂCzώqlJ(>)HRInn*M!^ֿ=4Jz|Pk  ?Ae!T193~WuҤj{5 A_Q ("X$&nI rA9`P g?0 `l5v5H%*k6d^_A0@套y0y W7?zMj 6_$"<0ͷGx0M}IKkP"YuM~.q>ktMjt D(YQ3CxP`=C7mg.e|Q5hMκghr@ т,J<{ qTiʀ%;,ǰ'[?V_ U*glt{t 90z#Pl ` B]!*Li]ϟRf254AaS@Aסdi7EHLP h+l c2"끮I P.]ZL<-;y$ פ@pɷj߳ˉ y5~$ AT;ŕPЧPTFSvO\)gsz-h)̀Łe>tҡaoo<1,\+ |b۰0yj*}pS吂*YՂVi8?6<$G9J(UK&℡6rk'2<0|IH`O5Yӹ.}Ij,sͻ ά[:HGsSkVCGWk3·Ayޢo($?pΈjT4o0tz8 *v@r[\WCFm=A;\'߽4FbqD+1v1SSvA;5cڎ/I_)`yG pJmߑd - PJ'g=߳nS;pRP9`(25~ҀAe܂6g*ˣJ]#S4F kv {RlÊ`1c] Vk. t>&1!~=u& mr/:g§ao雗2:Tvڤ,q 0xo?PH!U5T)TH0`3bT!toeI&&4f@$n*+X $!dVŒf:lyK|ؔhǸT)T{ T~qFo&"I~sPWAv‡+ %wʿCpU(=@eY4<K4Ç⏩gfZ=P5ĵz>ZRc:&4"Ap%<ryK@MQ \yfo;y~Wkm4;ИWG[t0AFAk M~]߮2҄=Nm | Y7dQt@;к+Ikha*՜)9vhH=~I:PAMÃE1?`;i+ HlW iOQuﳱ.vN☊1W9hJװ~E0` LѭҏV?jT6z}qnTH #=69d/ЦT, 8X@hV7(v>,f< cJ(R[}&@_< )YhW?mˌQ OqP\Kg6BJ4y?R9CAGHMfWZ "@T+`1/I[nvٙw{FBx216LNJWw<1̗g@!.wHR'OTɦ7Fo "q89'm taҼϔFhL04,ͽ @.5$9JhLUURP< ?Wu0;ERC, [PU*Ow~ O$ SN" 0>0gHcd"bć=澾OW_}503GU5ppD7 ?ERe 2rݦ S+J<;rRϋOj3^ٖ0ue* /-%[GD|6K12׷b~}OI>~W>_ 'e=&C+&p#kS5oLXIԈ8"LkgCj9Z$Q ӗ-H$>@L$ (H&.--컔 ICLBoe&ОvtpMEB4#ּpj]۴LHQ39B=? I.,@\ɮ$ x1HZnaZ)ɕTݒ҈ŰBsŔEBL60Ul&"W%ax'`Є3Я)&lx>*Lk0kH2rٌn0,iy|k8 X_ħ"_iֹIKX,=PQBVR\aIa[ "@yiLO `/ Iv%ޖD'YL !OEF:G*{u jTR4m]L A E)N +t=4,[{ʥC'{fumd8``]љΩi1NCJ*(susOA_!A4-P%RɚĂm,8N;"yjnOX Hr ʹ;F_}@eI}=%i/]8148GN a.cxƘ1/}@lB~A Aa.b<ց!vG~[q=>%]|UBD0ɲhϯSj3?3Hɐe*S TP/4y2,UJlYT:r"Ö vx,s,{O| ^5fnLs~PjƨɕTc%s SQ>1x,ožh\1 缥:"ـ#q+3I. CϥEd:]DwxRv;9d#wYbokh1=co7ZJ6mKNddž։,X]qgcr0.`rkjY ,ɦܐ'TDgJytHjusbΤNfRM3x袙L]M]&y<"4CU #*I̮5 |yY$kGfKw٘WAPŞXX3).x ,~8sJjߓ->\[[.wI3?$SzMkXrxZV-ek9cчe<2^Ni/%Ͽ UTvωoצ卧,;fԡ-?L#KvtJ=L639ι7eiґy-&`IÍ$T8&Ĝxza1gzkӞgR͖ 1)f6QlNA.1\ нg 01L7WҨl: @M_+,C9E^VzOSV^{r@@9םRS3ϔߙGzX SmGڋ=_r]i7ܝDD'd𑽜OhRd۝=d$Gc'Nj8bĶB_۠Ƹ$y=L;./I7иIm- I]Y+}ibYüJ+e){7k!Tè >{a/v;|KwYZV9]he0^n-Oj<'c)ԷV$LrzvF!1Aӈq/_z>`L2)1 ČAAaa/BnqS< Owٷ~.1?8noS_; ƖQS+-%ABn>iOg:S3)޷vf(ƆOa}KM¾Ͷ㈼ѕMsYƫKnɓ!>+KJ}+mIِ̙o'?%jHiDE'?560ZbaABByd|շ#y߳,t##rtiOn>r8ļOe`hNԇҵcT20Yα}  97%.\ҵ[! "cKWf9L=Dk1},“Rql6dʃ2GMZSXR&|bYw+NWR[v / v 5)qG_e<ʪߥ$ܕT0 IGJH d[cEf,i8@=pNZJ}Kz3E%zYkl";WRԊUp5~8WHj=07uHc/wk>`2juq~^F="D o>}:wɜiۺۊOi7Q{&rK3ŋϜMWųJM$lcHgg3lM^6%̌̌w*0"%b9,GT<$  L@m6ZAt#߈?ߒ.8]?yȏlג[$Fb>T7B@H' LJ #WK[Z I'}=U|uenO4)e(ǿ-@D?1*vпvsl;}چ5"En\Ӓ˯i\Φ. KDEȋ KԧtK\#\9u( `,<3D @l;@2  FO˧zddUSY='xX9ʋ@j F0c™![y^q _Hh4mx@( $wAH 2: 卒4GϮw2ƭrMe9By|qK$[̾)M)' &ecVիR98\'x Y^Ԯ -g 8щxyti?MqdQ0  -gh8dW{[`WSlDBѭƍ8+9}8Vů@xoK T\[?%n0nNz)f[>֕8BeQc1?8rzLʉ(e'>>)'~zr>rsb+.7ZO03aNtv3'')t` yJm~4_/7{P[b $GU{ 6@ ֝}B8) H8+Nٕ D}hMfJ:5T >8)bdX%҂[(,"hM-Ft*FtjbD]>nGRK1T̿oc?k31oJ8L->~} F A1}2Kyٗa@# |H)#7[*t@Bp3Y`*K" P Oz0{L( rC~sѢ91yAuH/ z)\Svд;BEtmʃvlNc=||,N} oW== 6"mDyKϗۍUCҋLwR@nCSU+/;o+ BGA`lfb_Q(d˄+ͩAgqN #RҦWcfg]Yt^ ؝eˌ܈5O3L *'| ~:yG $C-))%1lƯܽ2X(k9fðAqeGJ@VM 70qB5aק1⃕\q8y]' vɕV?cg7 R{PՁ0݆$e\̴SIv1PAU.4 !*bl6*'g fB(kԥ)NObo-] CJ$I_(iЇ9>%A^.ͥ; tpȘTɕ!]FU~d k"tVBzb>a?ZU8?+n/$W|L8):vB_Cf6%hW[~Ad!v1Qsf#\ cJ/,&30&(!v֪CwO>FH ]_J zRc In7 Io' nVz)^ ۗ12&BuՐb1"ܓԄB!4Lߍ:Gz%4wӘ'L"J Y!o I5[߻18^ʺsQƤ T]0rthmTTsd4y,BUskIsX k鳣512Է8!#3*lXIe(<'u-tTwqD %| "*1aa^B5aA),Kx4kb#б;?ZHܤaC%fPGRjaܸ4&qr΢bv:lʲ4%,G>w ŗмF*XqWlQ!r.BIs1!'8#9Xx}4[ISN!NH혈?{ߨS^w}pIN}R]gڍ7gPr+y3+VdVc;9bn0p IxVxF^֛3u|QRS Sc&KIbNlz%aPί1 mD9XJUŖ+"Fߪ/@?~Z8(2@$g_!ˬ/|d,t̩f{2J}' @y4 ns#G7hgm[PԡdΥm,%7 Op: .EHn5 ܷU-M6cRԽRs CHjeMޒ89&kPSpsarG(Ap^hX㊦X"Vs'B!gɺl."gv\g7=v[K2$SWT}->[~__LɫuZ},(\ !ˎH =a,HjPnU笠b؏e\]AХIV<73zH:1stJsX* BCTgRRG&4d"\05,) ![' f%OR {xE 693M]ub[ؗD<*~F |U W$Q`pc-b߾c6ƿoSV剞:ѽe`VυA(c!r~u:sY@;~+g"ᔑb+},$^#,lf .{w訩d5tf] #+'fV0e fc.L=##Q&.`i߀T@)}5*#t$"U*wL W̯ m@/^B@&ʍ#;:H [r5km$tP%"~R"54hq[&bUb5X)KӇtrKxw-e{P~"|np"nEĞ"2߿4埿J27+?Tf*^D\%4';qM,g;rmz?kJt:g:gVٝ]su;;9Ad%.^#%Ęr.yl/UhCl} i 'tggm8aȭCеc褬7)\BM S둪"b:  (fjWlaSefbṜ:YElKjS͠K"v\X)!~wԆ+}W0Ae"u?ybrZby_3U0Bb?"=uT[c0bգiJEJeYL8SuDkiXLG6cL?%PDO܀,0\Uh j1NAо\n j7'l'.Cۂ/]{+Y-V~i{Mzci!si|? mDif< +i,7H߫;sq9ppLJUފF'WX3j55¸uŰ%1)Ҥ*J5 l#Yjfj Uv+Ny=3) !$9# >/ %; +X+xCikq8Ci8uAIFB j**|XߒS47 ųbARO㘳*n6}MڠK/06 9m*.Ĩ{XM> a=|~;Y-O>7ǹd=aմ`JڃPم"65h6,FQRo's PLjYt-l>5sgJ{P)L9[}/J}˾|sIpr&O|57!tq)BΝ$̌ܨZ2zۏ^1UD ٘|wFh9viSu@6=4p)(z9p5c:VX,J\5OYByhb o:ssʐ]3,.Fǫw:=u,6}eruHg/bNSцBݪMzڹA @a\A~۰Dpv%% TFhЅacC{52 CeM@P{t"Be0H{ {;iF%$[1C6yCkpL/ЂM_"{ B)i?-UJ;g4_vAJ,C" N߄8q,T*WUehKߟ5)*s ЅQUwf Dۀz{9𿪺zEXO7BLIO[wFe>+}w*< 5zg *Fvn+6*.DNYCQF[шK$hcU@jЕqBy#4x JoD!{!ׇ ÷^/B8X*{ԡuBSp[ EhHV=&ẹ&syJ}_!ߑJ2! SRq UQ*ko>=_~/=b$:GhEЏlvk R bT"UQ͈D!U qǘ Q5|xZDP9+59OAP%IhIP_j,09MUG U+NΤbr-8 7NTM@H+@)%@ٔ1ϬRA 4EAyB-z'*Mo{8c*{bү 9*6=k |'xGNXK4e_C[R}4u9e;nkxv@ cV,-ɤe:cFDPi#pF2%D<I!9W ryiK B_))]rAA;c'K؎Rhv (Dqf%HU,.;MsIE׾ЙPt)B|B0FAmC'V[-qboٔt 3ͣe E75ޜYqOS 1ԊnZv\yʐ]OUwl*lBGZoELXZB`r!dd֥dzAEQY\@Z۪2ɿN &vyt>W_ -2KhX/3]}q$Us+bPJ#(z5pn0Pϡ t Bi !j+ـa)5 4$h;ª ҅@[BWPW]".W 2rtJ}4je,5/vHY~ԡ݅:ºO=&@_vb]"D~Xm&/ wyهRXbqVXgNtڑ@]Ve D"?RN9]?ez-=TPyu5 vQU0ߚc~Υ<Ԑ&.c= BhۂZAi @jp/6XSPѝ3BL(5!$B:HB(= C(c,>$SFE$5þ*K"Pr YvtM@ٔUOI{FaZ8c^enӔw;-֚V,kI|+,͝м Ƞˎ,,R˰mɍC$әW+s7]_10C%aXL*kB@6%Bw}??&J QH@% hQغ>vn&iwH#A:RFanKMTqF)*k¹iR*en<6Y*7ZVJc-)\F)7GbS,N~@rM"T}_xN-2H vj?\c n<~Zp#A3Ae,]!SZ@a2:9?dxҀ :y*B}jƜw=cOl. }&(CCy"8vP< '.3ANrڟ{MqDRQ30J>(Ղ~iO0(C$WH-%;2\Gpaj1}B)9&}(u;${fwI<&qBb۹eM<5}v%H>2|\oz_@an P6O0tyηJ=[&"z}֊9nGDp\/U*>b3I7mޜÌcߖF߭C{(^:ҠKHG-| cfgXS'd>Adq LW -tjűc*UFkܪn+KGDlbHK˷$'ԶtDD$tVQ1]>(H)q>1ŅsO2%! IbNS] @a5JEUhm~C޺[UOP3BϙیtU&ɡymLTJ6٤"ފ%~) veR{_UhqNSCfqr>BQl|?SR-p8 Wg rq nlX^,]6ܹb}j4oU=lN aԤc=a_jj8´#us`Wj:ɜ!928n7I' 0jp8 *MIs2dj-U&HR`q Z*hn؁C?p JmDCJI+~Q q9BI-*D1F)hK}OiXm[s^K7m-AgO)0,5Ge# Un.=܅rnn},VJW.61#(lhԷ0 72eڃ8߫ 9“h4ڨ.6CApXާ2k%լqfK:7'fB!dzwaO\UZw 3AE(h6sELX I3Uz8$b?Qw 2Vo㍍]KXl6iMHt@݂Z"jSlqD@31f(G bM-u_1̭%m(U\Mr?Ǭp Ȭ%Tz # 4s(qs7P=D]S[Y]1MPtʺ,ՌU*4C“~L{A<&gNZpF]]16t^I( :N<"&8XY\ #[nL@.wjRuI@xZI}φ"XMQ]~DH2%I]YV*MȌ|D@] @ Q$-HS.{OUN;sM\IlWxS&KnBql:3bi)L|V0#ALB$H4H] +qWGD8:~mIX\#]taaٴO<Kv +.=^!Pb2^as3Lk6gK7vR;CSkfkd'\P=]* @=GYƗUwW(?eIٕF{wlAXk53fT8tS3[fSRM;R/zȞ^QCuT8.Q*2L,8Z>q@*uh:wm1ƻ(̧d!I%?؜.frG$@RF BF9V"vna[PWpHVc-"$aىuqB_ @jFzذ>L=Tb'fd}h{3{VbASPbt@0^12nߨc) @> Yv+dQځ [n3ywY&(xzѮhMN^oRaXNRE I %B(G-c񑛌p ƨsm @3f'UdwJL.`2AM̓ G8YŶ!+j;VJRZap:aD='$~@ pr PBH/. ԘB q؏ ~n$*@oP[p)|Tv0n|).D])u(+/0&ɝ~ݭwU EoZvoD$6MZ"i2hf 6,CQO)D-ĦbX4, A~38V?bXddMXKѳ ڵc#kd:#áDXG$~lBvbL .650 rnsRPXs)hC>T ?-OQIq^OKkYGR=-y)z5XBo4v~ܖ2B3m^Ѻ G}Ta"wׅ "ӽ傖ꖮ9=jCI,5QwX3rYR1P PYyQ|&+\ɴkDDxhVUJ ϰ~|H;LQBX0 __B_˾-]sv&e rF0* _AE/7F mbL|z'x ܹå:YuDkB8шWf/U} -%e=jI GMKd62YKT^\&LL e)`gc@KW?z߻ċ0UWO3ѰY[=PCBFd"JjUjpZL 9-i-ǗtD:J,efyhĤ52$|8faH/a,qa[g\ӊD&y>|`wW)}ͻ+syӞq)rU{jd ".Cԧq,;v:OYuud[y*q3#C) `-6$ŴqH-PL*%R1~qmnOEtÈ_c)Cmtݯ~z,_?wKR;+M ^ppnz~uӣ)-M}+0{9H rى>h6KeL*%WBJzeEFn#? 21UBa !ƵZ cNS}Z8sqh^!ˎXĒ[OAC򲙄rN1s{l)O,f֑8WJ۱ )P3%3EcnQCCOw{JN4QKJ %3s%VAU=ɛ- #4I|>X;Zcs0 @+l@ry"#pTvK YD34B ɤc!HHٱИYѨC/t$vf'8؍~|ڵHjPXPdD70;aͷ1aO5MHi1Ke3v֮7T %!Wq-3J`y;wAe0Oc}Xy=&9ф{ Ļa< ; X.Ob b)KH(̙PCC u-q9ʓ #pJ4(2%v?:OH_ށxŁLfΙ]{x'J >yY nEgl@DG,}9G. k2%$&i%AFB @JXȾk+ '˒-qdBO6D~)Mzpf}f9ƒRYۀ\Nٔ䵤 DXQq`c''/ܭH' ؐ,n}enjN0 ޸֝WTTgM:Bè^1ݥ(1%fWs)hW~qm<=a&&z|10T,"u X. Cr{LXLo\R\2)Xmqkl\dr0y3LGɈ"{DE?6{lN+R 2w@\ֶgw(Y|bmC (<'X,>?BwXQ#Rpd2LϣR*6O )\K;*Dꗷ==,gSe`WWrq;c=BO 4L0\W*¸[5an/o;NvYXP˚x|AU=#<ӂ30{~e6dDA/GLs2M\o ;qfZy*Zd#6H>vH~2_Kզ]ADix[7UBJLgEwA#p%qv{ʍWvc <:Y%z[01Hjqndつ{_UryFadԼ{MEo{e<6d}KrdQFLo%% ײ\9q$vh%;}eV~Z{cN7޳e.cir#RC [̈́t̘Kk`t)'mmky*AzN7񔧌oJ whaὼ0ZEe=2KucfθLMVI* O46Ō2\_]c\Cx I-];i͗Udo4HQ0 -MBw<(z"PkvjCka>fbڌhڗ ͌]jfAcA苲[=ʥDc);1P㸍8e dP<@cIPl@`![{`Sfy> OD*y<&s?zOδ?fMXm>Ga=* F\mO;3|eo=FG5L=rQ*P E!#$LK]K{ۈX)"ƔA' :;ݸ3", YjyCs5;fY m$ĊJOPfgz3P@!Kb>L 0Rm2rIkQ2ҵ]Nl&[&q}G*!2)[ʘ7hHqJs|@@5ݐ!O"{_sϞpUdFX2\X^ɗS\tjy",Q@}2\b)k##sKG*aNJ\ǚ^HqY#'hz/_ ͇7\zbx 7feeHJ3~vɒQ[$emCy[=}G' 1HD/3S1v!sio6IJ1ukqKH$6C ȀR@C0l$i_'QM|`eq *$`4A~RZXrƔ†++e;BT{yKN' SXDžnHY'bKo#׎KŁz.˷8j(d$Hڠ6ӸLH%E1[\r/bquTw!2*~pYk<0/V?,0/ھ/`^ߴ?NZډC$]F(֐f:^ko_|m_:p#}0/YuUh/KtK}~\Q!"92#ث_=6)ƆHn#'zm[_Cu^';s20 Th/r*GjabCrׅ@`=v" ك'iR + ܥkcrAD±d=|Qj[nɸK~r!lVf+7'o[__< ^U ɧ Al!nVҙG2rfHͦ{ *m*r[6~ }4`]\f0mQ/.4_K\c.U=}Tq)"24L1ɻOap#~?e|k ejt8ϔYX1ںЬ 36I_2;wt"6p{Wik{YjW$k<`̚%?m9-S}e _3n2a#ٌ؅>up?|&dۅ}){߶&5 4}~,:BVv@o솰f;(Cد/k@k# X2 Ӱ)3[lhX=ړr䗲_ G^"<ǧxOծU|ҋt>Z<7I )qt䉹h8\uC}Oo p#ZCo9dfjwkoK&faw.Q?u:sU-?և.W?pqrlhu)"%g\+גGo[hy7@:#KFDl5]X+/6eqxӂhyr,вkuJXyA|z.4k˜u<:P@K&2\w}߿'o56ӫJ [j:_kUsGcu|v叻kPWbGcHil!XAblϒ Ɔ[)z{VT\%y3`X_]'Z/jUe!yGOzў/0+_fZ3'}0/E]}m']Ŏ$}R5ȏV,c*!7ۇ+܇#QJ>/%Pz^1P|>Zz;FOX ׍> TT3lڦOՈФbD&aWUv8Tj?L_NkO't9w)%pS1Mg̙_SV+ٸAnHZRqM;Dh ZDe}x5 A~ ʊѴ(:'>tޝm?{Y~}Ehٮet{<_]̞UW3A;R\+8tE]|-@*H*6gi0 S2dۭo<\x+3`)QtiqOoWF4ٓ O,Ǚ41MIG&?އY:PRR >d@Z׸# A T(ֲ:~WGo`#:yede3NYQ_|(=J$+/fM@,Wj<2%|E;& G0%uSm 0CZ:A)Ӥ^g)p|Ʋ6 P@"Cgq {, Is_)Af⿫&he2+Md]flshX*Ph'H2oU05TdO'` &k`-JUEl'sɝ*E ԑߢ)y|}̘`=z zd!Hev[Eε; [''0s,ڇZ(BooCH$o;a3 +pqhNn$njTd掦{L6gBn2.p&Æm[_"-&o1C E~L=,,. ץGǯ,|npCb]:F^!48عYs s3%l!IX^۝8;x*YʕXuH/qO9%NUfI-M.cl9#s_OH6t32fB0 abG24ΘZI:kPXfs ƛ&+PjIJ6hM}$Oe:T~ {WѡnQۈo@5&oKhXGPXy?x.R 'a29J(@41p0g #:n0/"ZA*Ys?# xA @ +n!NF < >kD.ɬ*`43 l*6fqFc.p`!7JSICWЇf9yr#jQݲBiL$X[@m%B`9DcSnAtE@JI!Lb6g`!tm)F0/}B@ S v$󆉆"ᤑ,W,ΈNɟ*.nW`9x7ӓ@?s1|v"ePfe2sh[Sֺ]#g ek1-6o/2HY(T &7 6WR>ŒSq3Њs1i }v<NgeEkUl+S yX`oVY"G6E>0]AؼYY}xb}UJT&񄤜Sj8<`̩<STR ! "'k<j 85f <>> tC H( 8BV@a>֮M-#E*M:0˼];uԃq+fS*_l UHj\t)f#jYњFtUW+ۆi*]ޖ"0;<0 EzQh8M@`KYI3 n8m;V*ps1j4LCH*<5"lrVܐX %UPByayOGDdfwS80 l#hGOqf1LCW?o5$^'2Ё c|=xnygTۋB0>7Xf!Q7n#tSYh0- q3 LK( jE H/o`5- 2O,P8=5~ZkS wmXh}[EݙXF! (> ?:sSl3E֊L n =N, WJU0aB(D1cW0pZ6H3 ^ jDGh, Gag6߄E!g RRFvBGU p5܉ NzrSu uXYnO`y[pD gc5*@y( @${8MH`4G6 p*Sd0HExxIa0{ I&>0ܔl Wh"U*7j)YHBNR)NWpU p鉚/Txq>'|(3R In"&QD?RxRpU pR 0 O` JȄ ŸPK-|R7aQ:)j)hoCg~&Ԋ)Njl+^TMX*E@}yi=޸*)Nk$Uߋ cUu^nQ-/^y7iZT Y}~ߥekW<2C = _%g)Bae ;ZjS|֜d4nƭzY}@PI Jbn"%@hRHRNɨX=݄SC7ߕ`Z/*Ċ_DǟBr)S']&S:?Ǻ5[unS"0r%˩#*t[&GbY%{/unglL2فDᑇ<ٺ^Qш)Ba0ClȨ9J @Y2R~-rԾS`mp<x$^7lX3 ? zRO"W&Qj$%;%M3JK*zee #" Z}-r,̹!=! TeCRMƬT+drCTr䖦:)I'J'R<_#=ȵ^#R/_%Ppa9NCm7)5}'Ҭ&I+?"0heoUH(v RG@n8[ĺ@uo F 1.k/PHyHbD~|%ج0Bh!/SL/$7MGOzC|Sq]vt3u&RS^-eCÀ*A5f`ao!N6 Uv#|Lsg"H ԅQߋs*4G5GCßO=v~DMnDLQABB;2>+9D*~QR* 5 :IW"JF( OT 8N$ 6)5֕Ie+ٲt%IFkG(m2lpKJ* 7a pdJI)_V$Ҋa%9Wꚥ z.Ώ-.>h|Aj#iwH)TmxiL]ީ{)pniiJγp;:'m5 6'+sc|+ ~:8YRjDBOx87mjO*?O1RRP h/.wx7* v"meyؒ$jmPGE\ 3 j83H6 C B`a(WHlCSTnX]H[h/GTmG.e%zq)quأ0?n=TtJ],3yATRXbil &<NIFɮt "m!禫wJ\BzCG +>et1*+"y•e-[˶#=b(: `ZfyA6i{I''˰xm/mEER,cr<ynz]Iƥ2|wt>J5(xYΕ;5 Xet-n*cw6ł+_}PvK Ү^O63ҕ\&sIA(h/؜̝  dGC nv_!8P*oK^ bp2HNeQ5s6Oڷ8c5MͶ?{[9{+uldjmjц"-[^c1.S{{3k}cyw0ӵ7U~l'i+N#`؏Mrꊆ< VQwhr׀<7^d#$7TyRe 9c+N:lDz ֎TrkWdfƎ9r] 5@|`TW!1$ao ʸs)rSX(?u`]&/ \2Q}@f@_;Pq60o;sБZ* ]L`es'ǜ2mDOO=O Ks-.hLSk)?,KR&[O"IڡÛH2xMa#@fc .unǕdߔ ߣ^*48,zwKI%(l@/|yS- (=~l'XrBݰf21ӵT& 1_sRE]CK*MЈ790Wjpm9jW%-TI;l܁9Kt\scqKxx>%]hN)B70wm}b?`]lZ FIa^"{\,lM V_7uea!'5RĄ4کg{nL3ja22P Obn}6H7! ɍh*V:A <,K((LB` OpR`mۮCy>Z‡/3Swq";UoR'A \]ᲀq`&hjuI &VR:7U~FbDXh)7)XGc~߂!%K:X0D`q.Qd@&J>`e r^(+lwYObB-uK MN%.HȆĝ@NΎ)O> 5dmd|/J `Ah y7-K6 ]HBh?d3ԁ!Iv X؉D`no%_*>M(Z0D.ɛt- 纒ُP}45xH0"H*Iڡc]pSF-_a'p>ecMpa9VA;+ǥigcMz1WZ@ XJP]t~7g@g v\&amX:2޲iRHCr׶Hv)-|dY|1܇Cx2[ǹd= MejA) )?umPc2&R nI<+fDXSC}Z%| Έ%utbI|Fo*=%qR}Y>ڨ|"p_ϣӧ77ѐǬiq_-{!GIHNùnI@9H V*"$Cj$^XJ52{B\0׷+)7F} ۹׵6u45ڳLW /-UKmhHGc˺{BbQ>ʑ{&N%2<|9$nƽiz t j qiA4Nz-0 1xt~&MtC^J7fRlpJC_,HIn~=0xq0 ]WI42̽Q~!1>SCU9UTۚe.j0|Bn[YjRyѐa@o̹%֝dXRWO9*3E 6KXj!P mÚB(\uy2򊤴H I+$ dO65o! ̐@AR5vhdf9d l|$pzY?~X0qǕ(0H>Q3X#|r}y$` Ϲݜz+>%Q>O\OȹYi5GR7 ױHt;xr5qH;t&NZeBrPvK>~ӹ}T#,, &6;?>[Vl0zD[!Z{mOINŸ '>"-a h,SA?69aeav-vFA,5>n%L̸M ߺ:*'K؊3LKZs#nD.Nz0v ST"Bx_t0x$32hѤM'>ݸgwC85w=.Te*'lذ[de#~YknBq+#P !‚#RIWnGLB>@+4me&^]nt8PC tSe۸7#f utWta,|F%6벒v.O"w|D]Qu]. uC<fK)*>xgHyTgK>zeO6+ ڙ$XJ" M8g/(8_d~!⑱ ‚oioY@>e9snfTU 8}Rtlz,hpI둋A{OshJvyu@ Q!@JV^̤:uWH4BfFx@acꂇ(&_\kN1TݣPd :Is~T2)ӆe$Mw3G_h%AMS鲙0 ta2"0qoCl* X#l9d`+4aVͽdY-㩰#W(DVAJ"y.sbxz1ԀRdPY.{UPC|$Q@~L#+ -c]TJI,+$Z Uw#Jh?O y#-Ϣƕ_d ŏョ(w>6lΏJUfUt:R0D;FI~ͬОT*TRa, BŊY>U ) \sF ˂uE,Ӻc > h i}C&_i,FMdi2:&  eN cn7cKqiҭ<Ͻ t^G60Kb6N#`Й#x0̓K ]TeI;CkE!J7zmȬ 4DdQy>wW\Kz925-Zfr-eMm L*$uB+ư<>%pǹTn]z$k'~ d1Sm=tw;R-~!*;"pqSKwn&ϠuqCTs6XD6+ A-ƍRڡM#}$3D3w."]%q@FmMjanv~J%?E ;2(FmN]H H]#*RB1vwTP5|tςW9eG>͌s@+$~2[(C3f`^BiGp"WVM>#Իnn_Clr_ViX>\9) H6W:,YOV:+S ex#]sKqjF |GuϏ4D R|]ا6[3NINJ CԪL2bpԒn[dgQJLz3FO]u=6AG:ׅ͐[(9h'D<Az,$ո)l!bz yn[!5[Zn@W\Sg#x"~ ]Z |D_v1W4:TT*b$ԍVV7ͫC?E`ѳDTQh MVSvm[d0aJzJ &͂ܘN)W, @][5=5 K_x{$#v2%(8*HF5(.F/ 6֯Џ?^!_;. AӨǯZx=\x6wF,;:%Y :|MK;t^HI +$"r#4.mk邇H]L;.p,߉t;*$MlCx==%"*K^fffZ{:iP(]uQgk_ltJ;h'E#&-h?i3t$ ˬv_bmhJgZM~h65 }_77"-S9 xz?[3X %޾1ZPHlPK'ڴ&D˳ ۵FgWH YWX8t)wx'[<O|-8|uzrM-MIR+pq huy%5؇&\a /`5& &}3,<'ЮƲ 3'#%) pNr넸]M@6 %Evd5d9A{J]@RoSlFB䄛н5Z:p5t$hj/$c.UBH?K: K$KC!No5wВ2{蓨:4p2F,"O`CH%x;00".#Dcz/^_LD_JLGn O[A5 'pޑ9=-#cͮT nغ0 ΀S<.?@.s`Knf0P4!tMxt&$?<&}Q= CXAybߋ =R"tL0YAlԬpIEAV@`_BΗ(5$Ën!/]`}ZQcKQDUlcIN%o`Yxi^c7?C\d.J%JJ pŀD۩Sۺa x<1YśQ\DUO9MJ |  [qr")BX" iPd h eڀ5UnsMX,zKqTp'RݹNZx<*({ɴCyɰDț6U‹0 t}xiUB$xF :BZcOW+7~fky|d7 N~|K4KI*Tpmz^>; eڷIBٱN={_@@pYQy5: l0\h,\5)6GbјbOsbdoOtN s; }0.iIaxi2>%:QJ W(y- |,qvou5$dR!a $O$ J=WiTȴ"G>xiMЎ)%A<(]+M$i t?76 'IOA(5ʮ<U6TP[tONF!Vcz40|OML3wyi6ҹi3v7`1rdotQ:K]I:K4G `z!3+T(Co{ 0aB+h ɓ|ek[`oO2l˽/KM%!{)삍ћ36O$Ê|DOZrA##q;jP2Ǯ2$ؖ0gv8G2ö}α AG/}e;CxxE2_R1g{6%נ8p^XM;rL/5BB%V ,C.w}jHд! 4VԬȭSQ^CM/2ͯ!|c]S)HBبuk]EQ8xKiz]8q87($OY!b¹ dٓ>Y68ql^C﬐{޿2$p}NvbOal3M~kjZ lJp$0M;tݵLd2r>OVdB^t9<=MVS(쉬o4GR2 P'S#LxFszܷ!MךOX(nefmBgX FR6!?p׌L O/ .5Ƣ!ugI5݀a 0Kk#חi4]$rW1.:H9d5g'kpځxJ=P E^]fD `?jkA)Aa㗭[kRj/̮p0 !Tp>>Z tx5NĔUUMbށ59:͖ 1 +mNU߭fzqMm?YԷMzzzajRIPמl@>4=ܞ&!"6WԌHRFڒMh+B_ A&!8ͼh`Fhp!/^lXnۢth <ؿP&/H?Z jtL>{a&#[ٜm'P^'\OEvʪ,̦ 7r [Ÿ?pMn0XJ0AuI zn [#׉rmnz"-RA&hT8#^/ܖaܛlGQ:\gcD/Ya q' P]|#L86k sjؽ MJdu=)SuO*n I>@sS=?FDo+-ڪ MB d.wQ@פVt:c+c)ZV&in&/R+?X|=(`@e7wD6^j|jVMg͸ tnjlfdj<`v>fCawQUodS8uљ% Ч*0,Ld:!{A%trMj=BT 8V78H7̽L!؍+rs6IPyz1Y*w&IaYՙ:n5]13MHĉ ̠#"&М>/10jP-WVƁjf{-̇?U @|BQ8:X ,8i@(H7c<7YL;tKyb^D藦pfPц%.@e@NWƾwfd\`Wzw":wБ Zt~o }'f` ˡ/~]3E%磄ޏSK?;ٙ +A]STI-hjCm^+In@$2 ˤ}@;ǀ_CB }xz&ֺ05g/=iW#{i4Fk q}-$3M$'>C0=nP `ld˃7<$rOUDd̞5&\()ç~+A %0CZpTDb$#ٞ:'nyv@Ae4ݕ\zqDIJ6@.T^oOJEkx]&F9wXPXv6=ɕߩ>m[$mGw(KSQX5TJJ e%1h92zK| f66QonA{BA .47Ai hLfJj?2J6/ 2C;?mTP#g:J")s#;l`g }9uUk<0uY|D~X%OyG&,"qi0wVv܀6Ɖ΃(L̀3^%$bCOV=v%Hn *8.XBWF X.݉ >^a64 g"B=FXj56Dqy+ɘKʵ9Z5#J;x6@=2Ƌϕ{`[(KSQ7j6TA_7㜐sXa1 8 Y˜3x 2JqocKܓY KZWzk˦8*>;ϲTν8cs-rFp{(݋m(%+{1J,ui [ V5L0,F݃'(^3N>\%b+W^e}cd)Ȟ@1@IH7h~t6Y5^6OVA?5f9Q*UAv&F)>K]3Iw#xW0dN#ŹUl4v@]6Бx `"ږi?aOAf ap!v ЇJ`!駱Xd7He9:^1Jfwٌn/xͬ^5PUo6 /3 ĝ@r]a$0??d@ W-X!|El 0x;n3l)@LL$- V |ǒGOv=} u18Ue(mm r3y=Hئ+=# \,ieN|^.>8:gnNXնkP+ꄭѤ@7Tnk0R:o7f<}jT =`|52 2:| h%Q=~d CŎCJpTt WO(.7M΃pw~ɷ71> (BEsc'NWc陕I /%@܂ _I>ؙέ*U؄'`Cݼ S^XADcp9qs +;EȧF h)$C-ۤL.( V1'ln.B^ֶ&{ziV>mZ_C'DZQ쨃NO ڠhEf#)&[ʖDfj6v+(ݮɮJ5q$ʷ+՚H?ƞWhyn# .0TGL)I ;eaCKyhw]ڮ+'梣i/ bn_Vr)D=Πc Op&z "&Jy!KJ34' ])6жm*WOLDdPw[K<=,uO{A} [o>Y<ؽ19aypUB=ؤVL- 4 ?O\i&77&TY]0R[o mDM"WbP~XHPB"}?/eh΃YU~򐃭R`- W:tʫ+Xy݂Mj C!-O;s{(`"P2z}ZK=*[5 u wHWF4C& !b4^ ! 4 WSsl&)Vz4$SPE09Z uv 쏺L M dfx9ᣎSyܒںV']`!x,nIT37\uWtd.!hZK J'[pٿ_Kfq3. $.uY("1t֖@\thtx N=m4S _7 Uχq TG{|\k8Tz>P@GK:.sXDu6dtW-TIHFŒpStxKwQLIȫv^j~ Iv^k\Mm%tcZ\<rLrӵuh^ 0 ""э}^gX]4W("orPP|>-d޳ڎO" 1KN6W)(f6O^6WHnƍ~ 4(,v;}DZT;Jνܸ 4yt6Ûnjgg,lBoMjEURjjrT3=#&x727P1Q45<' gA4Vz=8?,.σ<ݨ,ќulZ,:Dxx>C ψ2ӣQo"`ۮHe0mgι8kL`y5.7p|lFBTz`s=Р\,s[ fz_4jxYP8aWNJF?HN2hZJNf+.dϧg γdN밊-ı32_6Sϛi$UT`";ZE<'IRӠWPø?_,pck~d=kCaQfct=iNP̹g ؑް@(%u&_an = VIR ;Y[R$-IJ+r97gv0,'_b& 0@WP/ U ܳ\I=UvPX7By@VJ0_M?%H❐mTMsFr%PK&hM?|Fg >]K_=&ep(Y4^eL;DEfJ:lH ',f(/~`-Tb"̨[uIᨅ'r3o\dB6œ{ʭGjGlY0Y!؞_Kh*z,Dqb><-RAPQ CNfܱ-a Bl o["66O_r{HF>V٩қAzI~UuH{C O? ?ʀU!σH。^=v"O4_&Q(m+#T: !#0I@>c*ny&iz$gGB7lyu**.LO*1k-K2[L R"fT v誺Q,\BIb$!"" p 1!D_d.0يS2h`Jc^~I1<j6(\UAޑl` x>`StUIΡ6oB%H cF! p3baW"Zk&JUHL*teU:%[]Iby[0[z}CBP ;ռ/|]#Uks\ Q_: ~g953r9~B2_R 2PKAQ318.7qio,x"QH . zŢpZ[(PAymAH1dE`LmA»X-PɂbB?}sҿ3yrLz:A䛍 Q!P}-j{aYs*kQP?L_o %;W+\ }:*AϾA˳Q[-L  F:YC O9е.(k(*<:lҳzs/5TP}ڧp)F,끴 po½c![|*kg.FP'`xrOL&eݾ] a0scR %] 75B3xKiIB6l9ym ug%p$ {9z@WRP H!]P_ #7Rw:R%8y|16}- Aø#EU8#o´OUD5!LT=v2+'gG˭H!LJg0:s߱]E 8?]*xtC9[_" Cbpuڷ[9sqIMr 5I2o6"NY~H621\$ւs{a .rE)o `"hDa5KsT1.b81 $B{ȏEN;t:hXkRjq fCPw#㞄H38/YY4?=PX&@E+`aIhrLR_p-M4P!- *I t:=}pD۷9&th| %-%[$HZ&R 'pCK܋+'nǺfӯH6*r3bJ%(e. }kb:߁X gvɇ:F-HMZO.;6=}!K;tCu>xi<hӈ"E~_;)cl`GlE;+b[Ӎ<*g:40*0C|YC3r[IQI\)϶A|ej?=^TjP]9B$.H!Y,ƶ=?, ꫿07U">3գ`䐀:2-*/5uِ4tK}ErNj8ʴOSkʌ AaMs2^ֵ8Nd^Ih>2BQ+MaQ'dl^tuϽj姟WEd9GGΘIχѠ|E#lHX+G֑exR nGnOB B6!{xGq"'QSOϦj̋tC3dz9A[ 7jI`Ah{${9+f"oBwWQ'c<s[3w?1}4S'ԷRdFנfFI䈒VLSӯBP:ksx .+ Mæn-c(_&o۾1\áb7'[^%,qWj25%w2uzO"H_%ˌhfEZf:hr2-2\@gwʓ ^l^K%ەLCׇuk+O'6}F;䔷|DcN>ywt.Ќ=۴5e>quvќO=ClHs'Bu5p[. AA2R %8DU)LDǵ߬upkwQ0:jO/O&F`Ƌz4-Eql tAa/F\Cqcת^9cz :M%/!\}ji+V!#̿r+]:@pf:`@o= ;I3!#x^Ο11~k#+Kl@wWb7?;i ZUXex|Mv&DL>I^~4{TDD}"ҼH[-;x51X= ^=~ {K7fMtD2S| |àj`G^`knlln+a/T2(ȝ*߷E0yqܘV< .?BBܩ}IgSK+4Z9yȺQ겱hTI<4A|cfm0O'Jpr['?%_ہ߆.XǗ;~/Qý OA<^w)qFH6hѽNSm^2 -մ÷OErJq3? 8 MlvlC~,,rb\v#\M]9 %4:񽿄m-ï,*8* 8&2~ؖgAݮh_i(Ot|ye>kB۽ ;1-imBXօ2p:n/<Ьd7)VkJ f y@i=ْQd6- la k@lm7F*vWV/C'KM;h`熌0W~Dfh|#뢩b?WКyId['>QnkˆىDى<7*⛀\ ߇>S~Xʂ;S(xy}ޯ? 1 }wX {i^ ){/i5v*#Ja&Tpy(մoAXC屒6BjВD+Ne1߅gkQP/rrմ"Фªȷ2u<>B6J$/Tr.$(=??)(e%!S4k,.uT'PvdiS8OڔoI=ܸ 3t4A'V:Tgq"6ΙTPpw#˖*X<1KEvTiAs`j ;P5){=k+LlJ܉=b΁ej j5XXSdU˂().be/H"s,L{ƾQ]ֵ.x_OJ$cb*PwIcuK(zѩRRÅ_AfXu,x!ǁaρzI $N[B>J3)?uFbUy%i3UhhͼxlݤMeqϦJ^s)f5ow-`%uBFt݇]ў!Vh_rb=u%7b&ߕǻ"q1HMz=~p0x3[4'-EAtR&Z}NG͂e, Np%9?X߫^ގOKǾ{pYv#$E:#gҠ!2u<),xj{yZRʄDF)˝6֍W88JqAHJgbP6)7U}ȷ!h lEB Dv. PT/ج |Ϙb૪niܖ-H滩cdgl6ģSk ??w8 KTC3L}Gs/3vU>g*}Cj2ysDMuBv)iL[Z|Azm4w"08$!t.k76xC$Ln`Tp'>Jv6:G:_)M QbC+Af}ãP7$iz3;_t.\yQZᜁѹ˨Q$)e]#|sEG1׫|~%KCvŸ܆KKgrl'䴽;=ȵ;0'.: .P,x a83>xA={lJmF5h_?N~)~O /$(n9g:& ⡣Yn".B ܹ+7Mҡ2tP @l.r/k &vڿl0ͫ0k'`:,b|I V8&\ Px ob]H)<%G7*ڭ^|{Sfj)#5.vqcKbD"UN0a͋>u]sS p y*3QyERS-'[OEuSem0Y&BĠD0*0yq".v.WY#~|y)r;4Cq&4{°Eb4D:x|$#Zi&+_\ 3@$AXP~Cxd_Z0eFkskߜ&|L^D\SZ [;;I҃B׌V1A,l!@]$ޣ2NB2tlQBIjC v B\ ^T3O hj2olm.MAD AF-ڗOjy EBd@;L=G'^oHq\{WMAD[觥zZF{_s׶kjGgL?[.( 4fawbvՋvgyxQSɆe x&LvZ,o7BhN|Ħ{A`#J!"@{a7B :v Ǭ;wD6ry]\>$V1 Ӽzd9D&xfÂ5 5TGnPV, E&/}dipMh΃y?tE1L 7_1pJ`__83V &\_4-8j`c¼˜Cw -21w6P*zL_'uNCFn4J(pHCꓼ>|@KtZ&9qk%"Z?t/twBCq/ q(l&f 5CZ5О AuNJ#j}d$RxadvrM~LEL v;!gC%AA!n3VJPNCvq4O>}e#A>L׋*?pW[l/^xgz';kiEӆViîΝ(/0# (/kٿ[Qg.HT!DlCT+VdS1C|:5|>zjB" ZG@I_Nvubq4R`H/h.xc(~.}i#% z{B=Yn3vŋ,ô!Nt:,o;d:bpgѴfC#4K̟!0ccj?1zt" hģd[&e]>CqDT=16C/)'uuIJNȄDzdC>|z0Z:NZe EBG ۥs҃$$-McA ߷"5q^W5jq>_bDh.C`ia/ 2d!bĈi5|@N<X\("Z3A w cx4߾2Rv3QIGjI4Iκnt3V\@Ҽ`F|S@.CI(H6ZT&i+9ޝˉX\&rFFDe'HF, oA:.ȓP_4bJtZ+6Jź!>1G4}oF9tʾC }"V~ټX2ziHOU4.V4^Hg%l CC?<. *',4Ws֗TTN9$MA1\= t it ph@%?"ކ X|h(,c(8 a\;tP?@~=}ϱQh1ċNf<#rKn6Xk'GմQ\y qN2R{?'{bšo8Q(F"zLT0"Gx:ԤBXċNӛH7at8픺Nli̷Ls&ŭ2/;QxXIB|*v.Rǿ |8M^[o8Q05':"B' Ö'M{ASo,p Om  Byt-.xșm5uӏh֐j0q0 _U_Xp)eH h ?L-q;Nbbs? W$yE9S0@:C[e^FÄ BΤIRsL1@g,h:>`紡_Aa8>7hraUw2gjRiv w 6ߔ:-yt-/iңxԅ5,w !X5ߐu߷V=y.\靟kIo}5~CeV" 9KAi<>`3wt ws<#z8/>f][.i6=|`ՈB3#PD,eѢ%؜)< vdĢ F \-zC)xÑ> >#T RS"Sۑdn:1W-4]U҃7/ \Rؙ: Bw%P fI楅k*<Q[&EF:ӢLݪ5Gu8lHH~ Zٴ)B_5}}{"֗xa4 a>]K6 6Ș `;dD滝mi]fJw&WӠoi1O6I6mfZꭣi?r!{  M0i <<ݟ&<}6Oiq)8`Oĥ&?ճO + z0Gfd4 |zs+dތsJ[_Rሑ7f*O bF89ʸ`9$&{0٤a^8pP= hSpH%Y|eR)%PiYiK"{ǶDls2Pp5D-nM(UD=B Un֡2E,ϡct"I~E(XԴ@Bj1^]cYnȣ)@n4u E"Ue4^d6t E&,1 ^ QgYzr,35! p=pu1{*%9?} S1B$NP`S8Lt!`f qn A0A$#s1'B>`T.Ei_0Pi9>X%\2q)8Wl** ]B5?s A`ơ! &