pax_global_header 0000666 0000000 0000000 00000000064 14566144071 0014522 g ustar 00root root 0000000 0000000 52 comment=ca68a6b6843d6b24ac06621634fe432e2cd77c05
golang-honnef-go-tools-2023.1.7/ 0000775 0000000 0000000 00000000000 14566144071 0016241 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/.gitattributes 0000664 0000000 0000000 00000000061 14566144071 0021131 0 ustar 00root root 0000000 0000000 *.golden -text
*.svg binary
**/testdata/** -text
golang-honnef-go-tools-2023.1.7/.github/ 0000775 0000000 0000000 00000000000 14566144071 0017601 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/.github/FUNDING.yml 0000664 0000000 0000000 00000000043 14566144071 0021413 0 ustar 00root root 0000000 0000000 patreon: dominikh
github: dominikh
golang-honnef-go-tools-2023.1.7/.github/ISSUE_TEMPLATE/ 0000775 0000000 0000000 00000000000 14566144071 0021764 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/.github/ISSUE_TEMPLATE/1_false_positive.md 0000664 0000000 0000000 00000001202 14566144071 0025535 0 ustar 00root root 0000000 0000000 ---
name: 💢 False positive in Staticcheck
about: Your code is fine but Staticcheck complains about it, anyway.
labels: false-positive, needs-triage
title: ""
---
golang-honnef-go-tools-2023.1.7/.github/ISSUE_TEMPLATE/2_false_negative.md 0000664 0000000 0000000 00000001202 14566144071 0025476 0 ustar 00root root 0000000 0000000 ---
name: 🦆 False negative in Staticcheck
about: Your code is wrong but Staticcheck doesn't complain about it.
labels: false-negative, needs-triage
title: ""
---
golang-honnef-go-tools-2023.1.7/.github/ISSUE_TEMPLATE/3_bug.md 0000664 0000000 0000000 00000001156 14566144071 0023310 0 ustar 00root root 0000000 0000000 ---
name: 🐞 General bugs with Staticcheck
about: Something in Staticcheck isn't working as it should.
labels: bug, needs-triage
title: ""
---
golang-honnef-go-tools-2023.1.7/.github/ISSUE_TEMPLATE/4_other.md 0000664 0000000 0000000 00000000220 14566144071 0023644 0 ustar 00root root 0000000 0000000 ---
name: 🛠 Other
about: Ideas, feature requests, and all other issues not fitting into another category.
labels: needs-triage
title: ""
---
golang-honnef-go-tools-2023.1.7/.github/ISSUE_TEMPLATE/config.yml 0000664 0000000 0000000 00000000034 14566144071 0023751 0 ustar 00root root 0000000 0000000 blank_issues_enabled: false
golang-honnef-go-tools-2023.1.7/.github/workflows/ 0000775 0000000 0000000 00000000000 14566144071 0021636 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/.github/workflows/ci.yml 0000664 0000000 0000000 00000003514 14566144071 0022757 0 ustar 00root root 0000000 0000000 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.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/.gitignore 0000664 0000000 0000000 00000000573 14566144071 0020236 0 ustar 00root root 0000000 0000000 /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/.gitmodules 0000664 0000000 0000000 00000000147 14566144071 0020420 0 ustar 00root root 0000000 0000000 [submodule "website/themes/docsy"]
path = website/themes/docsy
url = https://github.com/google/docsy
golang-honnef-go-tools-2023.1.7/LICENSE 0000664 0000000 0000000 00000002042 14566144071 0017244 0 ustar 00root root 0000000 0000000 Copyright (c) 2016 Dominik Honnef
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
golang-honnef-go-tools-2023.1.7/LICENSE-THIRD-PARTY 0000664 0000000 0000000 00000014452 14566144071 0021021 0 ustar 00root root 0000000 0000000 Staticcheck and its related tools make use of third party projects,
either by reusing their code, or by statically linking them into
resulting binaries. These projects are:
* The Go Programming Language - https://golang.org/
golang.org/x/mod - https://github.com/golang/mod
golang.org/x/tools - https://github.com/golang/tools
golang.org/x/sys - https://github.com/golang/sys
golang.org/x/xerrors - https://github.com/golang/xerrors
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* github.com/BurntSushi/toml - https://github.com/BurntSushi/toml
The MIT License (MIT)
Copyright (c) 2013 TOML authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
* gogrep - https://github.com/mvdan/gogrep
Copyright (c) 2017, Daniel Martí. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* gosmith - https://github.com/dvyukov/gosmith
Copyright (c) 2014 Dmitry Vyukov. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* The name of Dmitry Vyukov may be used to endorse or promote
products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
golang-honnef-go-tools-2023.1.7/README.md 0000664 0000000 0000000 00000006010 14566144071 0017515 0 ustar 00root root 0000000 0000000
The advanced Go linter
Staticcheck is a state of the art linter for the [Go programming
language](https://go.dev/). Using static analysis, it finds bugs and performance issues,
offers simplifications, and enforces style rules.
**Financial support by [private and corporate sponsors](http://staticcheck.io/sponsors) guarantees the tool's continued development.
Please [become a sponsor](https://github.com/users/dominikh/sponsorship) if you or your company rely on Staticcheck.**
## Documentation
You can find extensive documentation on Staticcheck on [its website](https://staticcheck.io/docs/).
## Installation
### Releases
It is recommended that you run released versions of the tools.
These releases can be found as git tags (e.g. `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/ 0000775 0000000 0000000 00000000000 14566144071 0020515 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/_benchmarks/bench.sh 0000775 0000000 0000000 00000002601 14566144071 0022132 0 ustar 00root root 0000000 0000000 #!/usr/bin/env bash
set -e
declare -A PKGS=(
["strconv"]="strconv"
["net/http"]="net/http"
["image/color"]="image/color"
["std"]="std"
["k8s"]="k8s.io/kubernetes/pkg/..."
)
MIN_CORES=32
MAX_CORES=32
INCR_CORES=2
MIN_GOGC=100
MAX_GOGC=100
SAMPLES=10
WIPE_CACHE=1
FORMAT=bench
BIN=$(realpath ./silent-staticcheck.sh)
runBenchmark() {
local pkg="$1"
local label="$2"
local gc="$3"
local cores="$4"
local wipe="$5"
if [ $wipe -ne 0 ]; then
rm -rf ~/.cache/staticcheck
fi
local out=$(GOGC=$gc GOMAXPROCS=$cores env time -f "%e %M" $BIN $pkg 2>&1)
local t=$(echo "$out" | cut -f1 -d" ")
local m=$(echo "$out" | cut -f2 -d" ")
local ns=$(printf "%s 1000000000 * p" $t | dc)
local b=$((m * 1024))
case $FORMAT in
bench)
printf "BenchmarkStaticcheck-%s-GOGC%d-wiped%d-%d 1 %.0f ns/op %.0f B/op\n" "$label" "$gc" "$wipe" "$cores" "$ns" "$b"
;;
csv)
printf "%s,%d,%d,%d,%.0f,%.0f\n" "$label" "$gc" "$cores" "$wipe" "$ns" "$b"
;;
esac
}
export GO111MODULE=off
if [ "$FORMAT" = "csv" ]; then
printf "packages,gogc,gomaxprocs,wipe-cache,time,memory\n"
fi
for label in "${!PKGS[@]}"; do
pkg=${PKGS[$label]}
for gc in $(seq $MIN_GOGC 10 $MAX_GOGC); do
for cores in $(seq $MIN_CORES $INCR_CORES $MAX_CORES); do
for i in $(seq 1 $SAMPLES); do
runBenchmark "$pkg" "$label" "$gc" "$cores" 1
runBenchmark "$pkg" "$label" "$gc" "$cores" 0
done
done
done
done
golang-honnef-go-tools-2023.1.7/_benchmarks/silent-staticcheck.sh 0000775 0000000 0000000 00000000205 14566144071 0024632 0 ustar 00root root 0000000 0000000 #!/usr/bin/env sh
/home/dominikh/prj/src/honnef.co/go/tools/cmd/staticcheck/staticcheck -checks "all" -fail "" $1 &>/dev/null
exit 0
golang-honnef-go-tools-2023.1.7/analysis/ 0000775 0000000 0000000 00000000000 14566144071 0020064 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/analysis/code/ 0000775 0000000 0000000 00000000000 14566144071 0020776 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/analysis/code/code.go 0000664 0000000 0000000 00000023402 14566144071 0022240 0 ustar 00root root 0000000 0000000 // Package code answers structural and type questions about Go code.
package code
import (
"flag"
"fmt"
"go/ast"
"go/constant"
"go/token"
"go/types"
"strings"
"honnef.co/go/tools/analysis/facts/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.go 0000664 0000000 0000000 00000002577 14566144071 0022476 0 ustar 00root root 0000000 0000000 package code
import (
"bytes"
"go/ast"
"go/format"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
)
func Preorder(pass *analysis.Pass, fn func(ast.Node), types ...ast.Node) {
pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Preorder(types, fn)
}
func PreorderStack(pass *analysis.Pass, fn func(ast.Node, []ast.Node), types ...ast.Node) {
pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).WithStack(types, func(n ast.Node, push bool, stack []ast.Node) (proceed bool) {
if push {
fn(n, stack)
}
return true
})
}
func Match(pass *analysis.Pass, q pattern.Pattern, node ast.Node) (*pattern.Matcher, bool) {
// Note that we ignore q.Relevant – callers of Match usually use
// AST inspectors that already filter on nodes we're interested
// in.
m := &pattern.Matcher{TypesInfo: pass.TypesInfo}
ok := m.Match(q, 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/ 0000775 0000000 0000000 00000000000 14566144071 0021011 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/analysis/edit/edit.go 0000664 0000000 0000000 00000003772 14566144071 0022276 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 14566144071 0021164 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/analysis/facts/deprecated/ 0000775 0000000 0000000 00000000000 14566144071 0023264 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/analysis/facts/deprecated/deprecated.go 0000664 0000000 0000000 00000006774 14566144071 0025731 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000000323 14566144071 0026750 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 14566144071 0025075 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/analysis/facts/deprecated/testdata/src/ 0000775 0000000 0000000 00000000000 14566144071 0025664 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/analysis/facts/deprecated/testdata/src/example.com/ 0000775 0000000 0000000 00000000000 14566144071 0030074 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/analysis/facts/deprecated/testdata/src/example.com/Deprecated/ 0000775 0000000 0000000 00000000000 14566144071 0032134 5 ustar 00root root 0000000 0000000 Deprecated.go 0000664 0000000 0000000 00000002317 14566144071 0034447 0 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/analysis/facts/deprecated/testdata/src/example.com/Deprecated package pkg
// Deprecated: Don't use this.
func fn2() { // want fn2:`Deprecated: Don't use this\.`
}
// This is a function.
//
// Deprecated: Don't use this.
//
// Here is how you might use it instead.
func fn3() { // want fn3:`Deprecated: Don't use this\.`
}
// 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/ 0000775 0000000 0000000 00000000000 14566144071 0023325 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/analysis/facts/directives/directives.go 0000664 0000000 0000000 00000000720 14566144071 0026014 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 14566144071 0023122 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/analysis/facts/generated/generated.go 0000664 0000000 0000000 00000003557 14566144071 0025421 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 14566144071 0022637 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/analysis/facts/nilness/nilness.go 0000664 0000000 0000000 00000014773 14566144071 0024655 0 ustar 00root root 0000000 0000000 package nilness
import (
"fmt"
"go/token"
"go/types"
"reflect"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/types/typeutil"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
)
// neverReturnsNilFact denotes that a function's return value will never
// be nil (typed or untyped). The analysis errs on the side of false
// negatives.
type neverReturnsNilFact struct {
Rets []neverNilness
}
func (*neverReturnsNilFact) AFact() {}
func (fact *neverReturnsNilFact) String() string {
return fmt.Sprintf("never returns nil: %v", fact.Rets)
}
type Result struct {
m map[*types.Func][]neverNilness
}
var Analysis = &analysis.Analyzer{
Name: "nilness",
Doc: "Annotates return values that will never be nil (typed or untyped)",
Run: run,
Requires: []*analysis.Analyzer{buildir.Analyzer},
FactTypes: []analysis.Fact{(*neverReturnsNilFact)(nil)},
ResultType: reflect.TypeOf((*Result)(nil)),
}
// MayReturnNil reports whether the ret's return value of fn might be
// a typed or untyped nil value. The value of ret is zero-based. When
// globalOnly is true, the only possible nil values are global
// variables.
//
// The analysis has false positives: MayReturnNil can incorrectly
// report true, but never incorrectly reports false.
func (r *Result) MayReturnNil(fn *types.Func, ret int) (yes bool, globalOnly bool) {
if !typeutil.IsPointerLike(fn.Type().(*types.Signature).Results().At(ret).Type()) {
return false, false
}
if len(r.m[fn]) == 0 {
return true, false
}
v := r.m[fn][ret]
return v != neverNil, v == onlyGlobal
}
func run(pass *analysis.Pass) (interface{}, error) {
seen := map[*ir.Function]struct{}{}
out := &Result{
m: map[*types.Func][]neverNilness{},
}
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
impl(pass, fn, seen)
}
for _, fact := range pass.AllObjectFacts() {
out.m[fact.Object.(*types.Func)] = fact.Fact.(*neverReturnsNilFact).Rets
}
return out, nil
}
type neverNilness uint8
const (
neverNil neverNilness = 1
onlyGlobal neverNilness = 2
nilly neverNilness = 3
)
func (n neverNilness) String() string {
switch n {
case neverNil:
return "never"
case onlyGlobal:
return "global"
case nilly:
return "nil"
default:
return "BUG"
}
}
func impl(pass *analysis.Pass, fn *ir.Function, seenFns map[*ir.Function]struct{}) []neverNilness {
if fn.Object() == nil {
// TODO(dh): support closures
return nil
}
if fact := new(neverReturnsNilFact); pass.ImportObjectFact(fn.Object(), fact) {
return fact.Rets
}
if fn.Pkg != pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg {
return nil
}
if fn.Blocks == nil {
return nil
}
if _, ok := seenFns[fn]; ok {
// break recursion
return nil
}
seenFns[fn] = struct{}{}
seen := map[ir.Value]struct{}{}
var mightReturnNil func(v ir.Value) neverNilness
mightReturnNil = func(v ir.Value) neverNilness {
if _, ok := seen[v]; ok {
// break cycle
return nilly
}
if !typeutil.IsPointerLike(v.Type()) {
return neverNil
}
seen[v] = struct{}{}
switch v := v.(type) {
case *ir.MakeInterface:
return mightReturnNil(v.X)
case *ir.Convert:
return mightReturnNil(v.X)
case *ir.SliceToArrayPointer:
if 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.go 0000664 0000000 0000000 00000000312 14566144071 0025674 0 ustar 00root root 0000000 0000000 package nilness
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
)
func TestNilness(t *testing.T) {
analysistest.Run(t, analysistest.TestData(), Analysis, "example.com/Nilness")
}
golang-honnef-go-tools-2023.1.7/analysis/facts/nilness/testdata/ 0000775 0000000 0000000 00000000000 14566144071 0024450 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/analysis/facts/nilness/testdata/src/ 0000775 0000000 0000000 00000000000 14566144071 0025237 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/analysis/facts/nilness/testdata/src/example.com/ 0000775 0000000 0000000 00000000000 14566144071 0027447 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/analysis/facts/nilness/testdata/src/example.com/Nilness/ 0000775 0000000 0000000 00000000000 14566144071 0031062 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/analysis/facts/nilness/testdata/src/example.com/Nilness/Nilness.go 0000664 0000000 0000000 00000003007 14566144071 0033024 0 ustar 00root root 0000000 0000000 package pkg
import "errors"
type T struct{ f *int }
type T2 T
func fn1() *T {
if true {
return nil
}
return &T{}
}
func fn2() *T { // want fn2:`never returns nil: \[never\]`
return &T{}
}
func fn3() *T { // want fn3:`never returns nil: \[never\]`
return new(T)
}
func fn4() *T { // want fn4:`never returns nil: \[never\]`
return fn3()
}
func fn5() *T {
return fn1()
}
func fn6() *T2 { // want fn6:`never returns nil: \[never\]`
return (*T2)(fn4())
}
func fn7() interface{} {
return nil
}
func fn8() interface{} { // want fn8:`never returns nil: \[never\]`
return 1
}
func fn9() []int { // want fn9:`never returns nil: \[never\]`
x := []int{}
y := x[:1]
return y
}
func fn10(x []int) []int {
return x[:1]
}
func fn11(x *T) *T {
return x
}
func fn12(x *T) *int {
return x.f
}
func fn13() *int { // want fn13:`never returns nil: \[never\]`
return new(int)
}
func fn14() []int { // want fn14:`never returns nil: \[never\]`
return make([]int, 0)
}
func fn15() []int { // want fn15:`never returns nil: \[never\]`
return []int{}
}
func fn16() []int {
return nil
}
func fn17() error {
if true {
return errors.New("")
}
return nil
}
func fn18() (err error) { // want fn18:`never returns nil: \[never\]`
for {
if err = fn17(); err != nil {
return
}
}
}
var x *int
func fn19() *int { // want fn19:`never returns nil: \[global\]`
return x
}
func fn20() *int {
if true {
return x
}
return nil
}
func fn27[T ~struct{ F int }]() T { // want fn27:`never returns nil: \[never\]`
return T{0}
}
Nilness_go17.go 0000664 0000000 0000000 00000001154 14566144071 0033603 0 ustar 00root root 0000000 0000000 golang-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/ 0000775 0000000 0000000 00000000000 14566144071 0022520 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/analysis/facts/purity/purity.go 0000664 0000000 0000000 00000015675 14566144071 0024421 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000000307 14566144071 0025442 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 14566144071 0024331 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/analysis/facts/purity/testdata/src/ 0000775 0000000 0000000 00000000000 14566144071 0025120 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/analysis/facts/purity/testdata/src/example.com/ 0000775 0000000 0000000 00000000000 14566144071 0027330 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/analysis/facts/purity/testdata/src/example.com/Purity/ 0000775 0000000 0000000 00000000000 14566144071 0030624 5 ustar 00root root 0000000 0000000 CheckPureFunctions.go 0000664 0000000 0000000 00000001273 14566144071 0034641 0 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/analysis/facts/purity/testdata/src/example.com/Purity package pkg
func foo(a, b int) int { return a + b } // want foo:"is pure"
func bar(a, b int) int {
println(a + b)
return a + b
}
func empty() {}
func stubPointer() *int { return nil }
func stubInt() int { return 0 }
func fn3() {
empty()
stubPointer()
stubInt()
}
func ptr1() *int { return new(int) }
func ptr2() *int { var x int; return &x }
func lit() []int { return []int{} }
var X int
func load() int { _ = X; return 0 }
func assign(x int) int { _ = x; return 0 } // want assign:"is pure"
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/ 0000775 0000000 0000000 00000000000 14566144071 0023144 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/analysis/facts/tokenfile/token.go 0000664 0000000 0000000 00000000771 14566144071 0024620 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 14566144071 0023202 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/analysis/facts/typedness/testdata/ 0000775 0000000 0000000 00000000000 14566144071 0025013 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/analysis/facts/typedness/testdata/src/ 0000775 0000000 0000000 00000000000 14566144071 0025602 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/analysis/facts/typedness/testdata/src/example.com/ 0000775 0000000 0000000 00000000000 14566144071 0030012 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/analysis/facts/typedness/testdata/src/example.com/Typedness/ 0000775 0000000 0000000 00000000000 14566144071 0031770 5 ustar 00root root 0000000 0000000 Typedness.go 0000664 0000000 0000000 00000010241 14566144071 0034214 0 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/analysis/facts/typedness/testdata/src/example.com/Typedness package pkg
import (
"errors"
"os/exec"
)
type T struct{ x *int }
func notAStub() {}
func fn1() *int { return nil }
func fn2() (int, *int, int) { return 0, nil, 0 }
func fn3() (out1 int, out2 error) { notAStub(); return 0, nil }
func fn4() error { notAStub(); return nil }
func gen2() (out1 interface{}) { // want gen2:`always typed: 00000001`
return 1
}
func gen3() (out1 interface{}) { // want gen3:`always typed: 00000001`
// flag, always returns a typed value
m := map[int]*int{}
return m[0]
}
func gen4() (out1 int, out2 interface{}, out3 *int) { // want gen4:`always typed: 00000010`
// flag ret[1], always a typed value
m := map[int]*int{}
return 0, m[0], nil
}
func gen5() (out1 interface{}) { // want gen5:`always typed: 00000001`
// flag, propagate gen3
return gen3()
}
func gen6(b bool) interface{} {
// don't flag, sometimes returns untyped nil
if b {
m := map[int]*int{}
return m[0]
} else {
return nil
}
}
func gen7() (out1 interface{}) { // want gen7:`always typed: 00000001`
// flag, always returns a typed value
return fn1()
}
func gen8(x *int) (out1 interface{}) { // want gen8:`always typed: 00000001`
// flag
if x == nil {
return x
}
return x
}
func gen9() (out1 interface{}) { // want gen9:`always typed: 00000001`
// flag
var x *int
return x
}
func gen10() (out1 interface{}) { // want gen10:`always typed: 00000001`
// flag
var x *int
if x == nil {
return x
}
return errors.New("")
}
func gen11() interface{} {
// don't flag, we sometimes return untyped nil
if true {
return nil
} else {
return (*int)(nil)
}
}
func gen12(b bool) (out1 interface{}) { // want gen12:`always typed: 00000001`
// flag, all branches return typed nils
var x interface{}
if b {
x = (*int)(nil)
} else {
x = (*string)(nil)
}
return x
}
func gen13() (out1 interface{}) { // want gen13:`always typed: 00000001`
// flag, always returns a typed value
_, x, _ := fn2()
return x
}
func gen14(ch chan *int) (out1 interface{}) { // want gen14:`always typed: 00000001`
// flag
return <-ch
}
func gen15() (out1 interface{}) { // want gen15:`always typed: 00000001`
// flag
t := &T{}
return t.x
}
var g *int = new(int)
func gen16() (out1 interface{}) { // want gen16:`always typed: 00000001`
return g
}
func gen17(x interface{}) interface{} {
// don't flag
if x != nil {
return x
}
return x
}
func gen18() (int, error) {
// don't flag
_, err := fn3()
if err != nil {
return 0, errors.New("yo")
}
return 0, err
}
func gen19() (out interface{}) {
// don't flag
if true {
return (*int)(nil)
}
return
}
func gen20() (out interface{}) {
// don't flag
if true {
return (*int)(nil)
}
return
}
func gen21() error {
if false {
return (*exec.Error)(nil)
}
return fn4()
}
func gen22() interface{} {
// don't flag, propagate gen6
return gen6(false)
}
func gen23() interface{} {
return gen24()
}
func gen24() interface{} {
return gen23()
}
func gen25(x interface{}) (out1 interface{}) { // want gen25:`always typed: 00000001`
return x.(interface{})
}
func gen26(x interface{}) interface{} {
v, _ := x.(interface{})
return v
}
func gen27(x interface{}) (out1 interface{}) {
defer recover()
out1 = x.(interface{})
return out1
}
type Error struct{}
func (*Error) Error() string { return "" }
func gen28() (out1 interface{}) { // want gen28:`always typed: 00000001`
x := new(Error)
var y error = x
return y
}
func gen29() (out1 interface{}) { // want gen29:`always typed: 00000001`
var x *Error
var y error = x
return y
}
func gen30() (out1, out2 interface{}) { // want gen30:`always typed: 00000011`
return gen29(), gen28()
}
func gen31() (out1 interface{}) { // want gen31:`always typed: 00000001`
a, _ := gen30()
return a
}
func gen32() (out1 interface{}) { // want gen32:`always typed: 00000001`
_, b := gen30()
return b
}
func gen33() (out1 interface{}) { // want gen33:`always typed: 00000001`
a, b := gen30()
_ = a
return b
}
func gen34() (out1, out2 interface{}) { // want gen34:`always typed: 00000010`
return nil, 1
}
func gen35() (out1 interface{}) {
a, _ := gen34()
return a
}
func gen36() (out1 interface{}) { // want gen36:`always typed: 00000001`
_, b := gen34()
return b
}
golang-honnef-go-tools-2023.1.7/analysis/facts/typedness/typedness.go 0000664 0000000 0000000 00000015523 14566144071 0025555 0 ustar 00root root 0000000 0000000 package typedness
import (
"fmt"
"go/token"
"go/types"
"reflect"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/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.go 0000664 0000000 0000000 00000000320 14566144071 0026601 0 ustar 00root root 0000000 0000000 package typedness
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
)
func TestTypedness(t *testing.T) {
analysistest.Run(t, analysistest.TestData(), Analysis, "example.com/Typedness")
}
golang-honnef-go-tools-2023.1.7/analysis/lint/ 0000775 0000000 0000000 00000000000 14566144071 0021032 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/analysis/lint/lint.go 0000664 0000000 0000000 00000015336 14566144071 0022337 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 14566144071 0022707 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/analysis/lint/testutil/check.go 0000664 0000000 0000000 00000024774 14566144071 0024331 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000012175 14566144071 0024221 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 14566144071 0021377 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/analysis/report/report.go 0000664 0000000 0000000 00000012576 14566144071 0023254 0 ustar 00root root 0000000 0000000 package report
import (
"bytes"
"fmt"
"go/ast"
"go/format"
"go/token"
"path/filepath"
"strconv"
"strings"
"honnef.co/go/tools/analysis/facts/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.go 0000664 0000000 0000000 00000001240 14566144071 0024275 0 ustar 00root root 0000000 0000000 package report
import "testing"
func TestOrdinal(t *testing.T) {
tests := []struct {
num int
want string
}{
{0, "0th"}, {1, "1st"}, {2, "2nd"}, {3, "3rd"}, {4, "4th"}, {5, "5th"}, {6, "6th"}, {7, "7th"}, {8, "8th"}, {9, "9th"},
{10, "10th"}, {11, "11th"}, {12, "12th"}, {13, "13th"}, {14, "14th"}, {15, "15th"}, {16, "16th"}, {17, "17th"}, {18, "18th"}, {19, "19th"},
{20, "20th"}, {21, "21st"}, {22, "22nd"}, {23, "23rd"}, {24, "24th"}, {25, "25th"}, {26, "26th"}, {27, "27th"}, {28, "28th"}, {29, "29th"},
}
for _, tt := range tests {
if got := Ordinal(tt.num); got != tt.want {
t.Errorf("Ordinal(%d) = %s, want %s", tt.num, got, tt.want)
}
}
}
golang-honnef-go-tools-2023.1.7/cmd/ 0000775 0000000 0000000 00000000000 14566144071 0017004 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/cmd/keyify/ 0000775 0000000 0000000 00000000000 14566144071 0020304 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/cmd/keyify/README.md 0000664 0000000 0000000 00000002011 14566144071 0021555 0 ustar 00root root 0000000 0000000 Keyify turns unkeyed struct literals (`T{1, 2, 3}`) into keyed
ones (`T{A: 1, B: 2, C: 3}`)
## Installation
See [the main README](https://github.com/dominikh/go-tools#installation) for installation instructions.
## Usage
Call keyify with a position such as `/some/file.go:#5`, where #5 is
the byte offset in the file and has to point at or into a struct
literal.
By default, keyify will print the new literal on stdout, formatted as
Go code. By using the `-json` flag, it will print a JSON object
denoting the start and end of the original literal, and its
replacement. This is useful for integration with editors.
For a description of all available flags, see `keyify -help`.
### Emacs
For Emacs integration, add the following to your `.emacs` file:
```
(add-to-list 'load-path "/your/gopath/src/honnef.co/go/tools/cmd/keyify)
(eval-after-load 'go-mode
(lambda ()
(require 'go-keyify)))
```
With point on or in a struct literal, call the `go-keyify` command.

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

golang-honnef-go-tools-2023.1.7/cmd/structlayout/main.go 0000664 0000000 0000000 00000005571 14566144071 0023051 0 ustar 00root root 0000000 0000000 // structlayout displays the layout (field sizes and padding) of structs.
package main
import (
"encoding/json"
"flag"
"fmt"
"go/build"
"go/types"
"log"
"os"
"honnef.co/go/tools/go/gcsizes"
"honnef.co/go/tools/lintcmd/version"
st "honnef.co/go/tools/structlayout"
"golang.org/x/tools/go/packages"
)
var (
fJSON bool
fVersion bool
)
func init() {
flag.BoolVar(&fJSON, "json", false, "Format data as JSON")
flag.BoolVar(&fVersion, "version", false, "Print version and exit")
}
func main() {
log.SetFlags(0)
flag.Parse()
if fVersion {
version.Print(version.Version, version.MachineVersion)
os.Exit(0)
}
if len(flag.Args()) != 2 {
flag.Usage()
os.Exit(1)
}
cfg := &packages.Config{
Mode: packages.NeedImports | packages.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/ 0000775 0000000 0000000 00000000000 14566144071 0020307 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/cmd/unused/unused.go 0000664 0000000 0000000 00000004600 14566144071 0022141 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 14566144071 0017506 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/config/config.go 0000664 0000000 0000000 00000015063 14566144071 0021307 0 ustar 00root root 0000000 0000000 package config
import (
"bytes"
"fmt"
"go/ast"
"go/token"
"os"
"path/filepath"
"reflect"
"strings"
"github.com/BurntSushi/toml"
"golang.org/x/tools/go/analysis"
)
// Dir looks at a list of absolute file names, which should make up a
// single package, and returns the path of the directory that may
// contain a staticcheck.conf file. It returns the empty string if no
// such directory could be determined, for example because all files
// were located in Go's build cache.
func Dir(files []string) string {
if len(files) == 0 {
return ""
}
cache, err := os.UserCacheDir()
if err != nil {
cache = ""
}
var path string
for _, p := range files {
// FIXME(dh): using strings.HasPrefix isn't technically
// correct, but it should be good enough for now.
if cache != "" && strings.HasPrefix(p, cache) {
// File in the build cache of the standard Go build system
continue
}
path = p
break
}
if path == "" {
// The package only consists of generated files.
return ""
}
dir := filepath.Dir(path)
return dir
}
func dirAST(files []*ast.File, fset *token.FileSet) string {
names := make([]string, len(files))
for i, f := range files {
names[i] = fset.PositionFor(f.Pos(), true).Filename
}
return Dir(names)
}
var Analyzer = &analysis.Analyzer{
Name: "config",
Doc: "loads configuration for the current package tree",
Run: func(pass *analysis.Pass) (interface{}, error) {
dir := dirAST(pass.Files, pass.Fset)
if dir == "" {
cfg := DefaultConfig
return &cfg, nil
}
cfg, err := Load(dir)
if err != nil {
return nil, fmt.Errorf("error loading staticcheck.conf: %s", err)
}
return &cfg, nil
},
RunDespiteErrors: true,
ResultType: reflect.TypeOf((*Config)(nil)),
}
func For(pass *analysis.Pass) *Config {
return pass.ResultOf[Analyzer].(*Config)
}
func mergeLists(a, b []string) []string {
out := make([]string, 0, len(a)+len(b))
for _, el := range b {
if el == "inherit" {
out = append(out, a...)
} else {
out = append(out, el)
}
}
return out
}
func normalizeList(list []string) []string {
if len(list) > 1 {
nlist := make([]string, 0, len(list))
nlist = append(nlist, list[0])
for i, el := range list[1:] {
if el != list[i] {
nlist = append(nlist, el)
}
}
list = nlist
}
for _, el := range list {
if el == "inherit" {
// This should never happen, because the default config
// should not use "inherit"
panic(`unresolved "inherit"`)
}
}
return list
}
func (cfg Config) Merge(ocfg Config) Config {
if ocfg.Checks != nil {
cfg.Checks = mergeLists(cfg.Checks, ocfg.Checks)
}
if ocfg.Initialisms != nil {
cfg.Initialisms = mergeLists(cfg.Initialisms, ocfg.Initialisms)
}
if ocfg.DotImportWhitelist != nil {
cfg.DotImportWhitelist = mergeLists(cfg.DotImportWhitelist, ocfg.DotImportWhitelist)
}
if ocfg.HTTPStatusCodeWhitelist != nil {
cfg.HTTPStatusCodeWhitelist = mergeLists(cfg.HTTPStatusCodeWhitelist, ocfg.HTTPStatusCodeWhitelist)
}
return cfg
}
type Config struct {
// TODO(dh): this implementation makes it impossible for external
// clients to add their own checkers with configuration. At the
// moment, we don't really care about that; we don't encourage
// that people use this package. In the future, we may. The
// obvious solution would be using map[string]interface{}, but
// that's obviously subpar.
Checks []string `toml:"checks"`
Initialisms []string `toml:"initialisms"`
DotImportWhitelist []string `toml:"dot_import_whitelist"`
HTTPStatusCodeWhitelist []string `toml:"http_status_code_whitelist"`
}
func (c Config) String() string {
buf := &bytes.Buffer{}
fmt.Fprintf(buf, "Checks: %#v\n", c.Checks)
fmt.Fprintf(buf, "Initialisms: %#v\n", c.Initialisms)
fmt.Fprintf(buf, "DotImportWhitelist: %#v\n", c.DotImportWhitelist)
fmt.Fprintf(buf, "HTTPStatusCodeWhitelist: %#v", c.HTTPStatusCodeWhitelist)
return buf.String()
}
// 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.conf 0000664 0000000 0000000 00000001156 14566144071 0022013 0 ustar 00root root 0000000 0000000 checks = ["all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022", "-ST1023"]
initialisms = ["ACL", "API", "ASCII", "CPU", "CSS", "DNS",
"EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID",
"IP", "JSON", "QPS", "RAM", "RPC", "SLA",
"SMTP", "SQL", "SSH", "TCP", "TLS", "TTL",
"UDP", "UI", "GID", "UID", "UUID", "URI",
"URL", "UTF8", "VM", "XML", "XMPP", "XSRF",
"XSS", "SIP", "RTP", "AMQP", "DB", "TS"]
dot_import_whitelist = [
"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/ 0000775 0000000 0000000 00000000000 14566144071 0017327 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/debug/debug.go 0000664 0000000 0000000 00000002464 14566144071 0020752 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 14566144071 0017204 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/dist/build.sh 0000775 0000000 0000000 00000003033 14566144071 0020641 0 ustar 00root root 0000000 0000000 #!/bin/sh -e
build() {
ROOT="$GOPATH/src/honnef.co/go/tools"
os="$1"
arch="$2"
echo "Building GOOS=$os GOARCH=$arch..."
exe="staticcheck"
if [ $os = "windows" ]; then
exe="${exe}.exe"
fi
target="staticcheck_${os}_${arch}"
arm=""
case "$arch" in
armv5l)
arm=5
arch=arm
;;
armv6l)
arm=6
arch=arm
;;
armv7l)
arm=7
arch=arm
;;
arm64)
arch=arm64
;;
esac
mkdir "$d/staticcheck"
cp "$ROOT/LICENSE" "$ROOT/LICENSE-THIRD-PARTY" "$d/staticcheck"
CGO_ENABLED=0 GOOS=$os GOARCH=$arch GOARM=$arm GO111MODULE=on go build -trimpath -o "$d/staticcheck/$exe" honnef.co/go/tools/cmd/staticcheck
(
cd "$d"
tar -czf "$target.tar.gz" staticcheck
sha256sum "$target.tar.gz" > "$target.tar.gz.sha256"
)
rm -rf "$d/staticcheck"
}
rev="$1"
if [ -z "$rev" ]; then
echo "Usage: $0 "
exit 1
fi
mkdir "$rev"
d=$(realpath "$rev")
wrk=$(mktemp -d)
trap "{ rm -rf \"$wrk\"; }" EXIT
cd "$wrk"
go mod init foo
GO111MODULE=on go get -d honnef.co/go/tools/cmd/staticcheck@"$rev"
SYSTEMS=(windows linux freebsd)
ARCHS=(amd64 386)
for os in ${SYSTEMS[@]}; do
for arch in ${ARCHS[@]}; do
build "$os" "$arch"
done
done
build "darwin" "amd64"
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/ 0000775 0000000 0000000 00000000000 14566144071 0017006 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/doc/articles/ 0000775 0000000 0000000 00000000000 14566144071 0020614 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/doc/articles/customizing_staticcheck.html 0000664 0000000 0000000 00000000313 14566144071 0026417 0 ustar 00root root 0000000 0000000 - how to customize staticcheck
- tools serve humans
- tools should assist workflows
- don't let tools bully you
- exit status
- which checks run
- ignoring findings
- output format
- go version
- tests
golang-honnef-go-tools-2023.1.7/doc/run.html 0000664 0000000 0000000 00000002732 14566144071 0020504 0 ustar 00root root 0000000 0000000
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.mod 0000664 0000000 0000000 00000000425 14566144071 0017350 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000001744 14566144071 0017402 0 ustar 00root root 0000000 0000000 github.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/ 0000775 0000000 0000000 00000000000 14566144071 0016646 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/go/ast/ 0000775 0000000 0000000 00000000000 14566144071 0017435 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/go/ast/astutil/ 0000775 0000000 0000000 00000000000 14566144071 0021122 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/go/ast/astutil/upstream.go 0000664 0000000 0000000 00000000656 14566144071 0023320 0 ustar 00root root 0000000 0000000 package astutil
import (
"go/ast"
"go/token"
_ "unsafe"
"golang.org/x/tools/go/ast/astutil"
)
type Cursor = astutil.Cursor
type ApplyFunc = astutil.ApplyFunc
func Apply(root ast.Node, pre, post ApplyFunc) (result ast.Node) {
return astutil.Apply(root, pre, post)
}
func PathEnclosingInterval(root *ast.File, start, end token.Pos) (path []ast.Node, exact bool) {
return astutil.PathEnclosingInterval(root, start, end)
}
golang-honnef-go-tools-2023.1.7/go/ast/astutil/util.go 0000664 0000000 0000000 00000020304 14566144071 0022425 0 ustar 00root root 0000000 0000000 package astutil
import (
"fmt"
"go/ast"
"go/token"
"reflect"
"strings"
)
func IsIdent(expr ast.Expr, ident string) bool {
id, ok := expr.(*ast.Ident)
return ok && id.Name == ident
}
// isBlank returns whether id is the blank identifier "_".
// If id == nil, the answer is false.
func IsBlank(id ast.Expr) bool {
ident, _ := id.(*ast.Ident)
return ident != nil && ident.Name == "_"
}
// 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/ 0000775 0000000 0000000 00000000000 14566144071 0020262 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/go/buildid/UPSTREAM 0000664 0000000 0000000 00000000404 14566144071 0021443 0 ustar 00root root 0000000 0000000 This 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.go 0000664 0000000 0000000 00000013510 14566144071 0022225 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000013421 14566144071 0021557 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 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/ 0000775 0000000 0000000 00000000000 14566144071 0020315 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/go/gcsizes/LICENSE 0000664 0000000 0000000 00000002707 14566144071 0021330 0 ustar 00root root 0000000 0000000 Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
golang-honnef-go-tools-2023.1.7/go/gcsizes/sizes.go 0000664 0000000 0000000 00000005330 14566144071 0022002 0 ustar 00root root 0000000 0000000 // Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package gcsizes provides a types.Sizes implementation that adheres
// to the rules used by the gc compiler.
package gcsizes
import (
"go/build"
"go/types"
)
type Sizes struct {
WordSize int64
MaxAlign int64
}
// ForArch returns a correct Sizes for the given architecture.
func ForArch(arch string) *Sizes {
wordSize := int64(8)
maxAlign := int64(8)
switch build.Default.GOARCH {
case "386", "arm":
wordSize, maxAlign = 4, 4
case "amd64p32":
wordSize = 4
}
return &Sizes{WordSize: wordSize, MaxAlign: maxAlign}
}
func (s *Sizes) Alignof(T types.Type) int64 {
switch t := T.Underlying().(type) {
case *types.Array:
return s.Alignof(t.Elem())
case *types.Struct:
max := int64(1)
n := t.NumFields()
var fields []*types.Var
for i := 0; i < n; i++ {
fields = append(fields, t.Field(i))
}
for _, f := range fields {
if a := s.Alignof(f.Type()); a > max {
max = a
}
}
return max
}
a := s.Sizeof(T) // may be 0
if a < 1 {
return 1
}
if a > s.MaxAlign {
return s.MaxAlign
}
return a
}
func (s *Sizes) Offsetsof(fields []*types.Var) []int64 {
offsets := make([]int64, len(fields))
var o int64
for i, f := range fields {
a := s.Alignof(f.Type())
o = align(o, a)
offsets[i] = o
o += s.Sizeof(f.Type())
}
return offsets
}
var basicSizes = [...]byte{
types.Bool: 1,
types.Int8: 1,
types.Int16: 2,
types.Int32: 4,
types.Int64: 8,
types.Uint8: 1,
types.Uint16: 2,
types.Uint32: 4,
types.Uint64: 8,
types.Float32: 4,
types.Float64: 8,
types.Complex64: 8,
types.Complex128: 16,
}
func (s *Sizes) Sizeof(T types.Type) int64 {
switch t := T.Underlying().(type) {
case *types.Basic:
k := t.Kind()
if int(k) < len(basicSizes) {
if s := basicSizes[k]; s > 0 {
return int64(s)
}
}
if k == types.String {
return s.WordSize * 2
}
case *types.Array:
n := t.Len()
if n == 0 {
return 0
}
a := s.Alignof(t.Elem())
z := s.Sizeof(t.Elem())
return align(z, a)*(n-1) + z
case *types.Slice:
return s.WordSize * 3
case *types.Struct:
n := t.NumFields()
if n == 0 {
return 0
}
var fields []*types.Var
for i := 0; i < n; i++ {
fields = append(fields, t.Field(i))
}
offsets := s.Offsetsof(fields)
a := s.Alignof(T)
lsz := s.Sizeof(fields[n-1].Type())
if lsz == 0 {
lsz = 1
}
z := offsets[n-1] + lsz
return align(z, a)
case *types.Interface:
return s.WordSize * 2
}
return s.WordSize // catch-all
}
// align returns the smallest y >= x such that y % a == 0.
func align(x, a int64) int64 {
y := x + a - 1
return y - y%a
}
golang-honnef-go-tools-2023.1.7/go/ir/ 0000775 0000000 0000000 00000000000 14566144071 0017260 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2023.1.7/go/ir/LICENSE 0000664 0000000 0000000 00000002777 14566144071 0020302 0 ustar 00root root 0000000 0000000 Copyright (c) 2009 The Go Authors. All rights reserved.
Copyright (c) 2016 Dominik Honnef. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
golang-honnef-go-tools-2023.1.7/go/ir/UPSTREAM 0000664 0000000 0000000 00000000713 14566144071 0020444 0 ustar 00root root 0000000 0000000 This package started as a copy of golang.org/x/tools/go/ssa, imported from an unknown commit in 2016.
It has since been heavily modified to match our own needs in an IR.
The changes are too many to list here, and it is best to consider this package independent of go/ssa.
Upstream changes still get applied when they address bugs in portions of code we have inherited.
The last upstream commit we've looked at was:
e854e0228e2ef1cc6e42bbfde1951925096a1272
golang-honnef-go-tools-2023.1.7/go/ir/bench_test.go 0000664 0000000 0000000 00000001472 14566144071 0021731 0 ustar 00root root 0000000 0000000 package ir_test
import (
"testing"
"golang.org/x/tools/go/packages"
"honnef.co/go/tools/go/ir"
)
func BenchmarkSSA(b *testing.B) {
cfg := &packages.Config{
Mode: packages.NeedSyntax | packages.NeedTypes | packages.NeedTypesInfo,
Tests: false,
}
pkgs, err := packages.Load(cfg, "std")
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
prog := ir.NewProgram(pkgs[0].Fset, ir.GlobalDebug)
seen := map[*packages.Package]struct{}{}
var create func(pkg *packages.Package)
create = func(pkg *packages.Package) {
if _, ok := seen[pkg]; ok {
return
}
seen[pkg] = struct{}{}
prog.CreatePackage(pkg.Types, pkg.Syntax, pkg.TypesInfo, true)
for _, imp := range pkg.Imports {
create(imp)
}
}
for _, pkg := range pkgs {
create(pkg)
}
prog.Build()
}
}
golang-honnef-go-tools-2023.1.7/go/ir/blockopt.go 0000664 0000000 0000000 00000012027 14566144071 0021426 0 ustar 00root root 0000000 0000000 // Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ir
// Simple block optimizations to simplify the control flow graph.
// TODO(adonovan): opt: instead of creating several "unreachable" blocks
// per function in the Builder, reuse a single one (e.g. at Blocks[1])
// to reduce garbage.
import (
"fmt"
"os"
)
// If true, perform sanity checking and show progress at each
// successive iteration of optimizeBlocks. Very verbose.
const debugBlockOpt = false
// markReachable sets Index=-1 for all blocks reachable from b.
func markReachable(b *BasicBlock) {
b.gaps = -1
for _, succ := range b.Succs {
if succ.gaps == 0 {
markReachable(succ)
}
}
}
// deleteUnreachableBlocks marks all reachable blocks of f and
// eliminates (nils) all others, including possibly cyclic subgraphs.
func deleteUnreachableBlocks(f *Function) {
const white, black = 0, -1
// We borrow b.gaps temporarily as the mark bit.
for _, b := range f.Blocks {
b.gaps = white
}
markReachable(f.Blocks[0])
// In SSI form, we need the exit to be reachable for correct
// post-dominance information. In original form, however, we
// cannot unconditionally mark it reachable because we won't
// be adding fake edges, and this breaks the calculation of
// dominance information.
markReachable(f.Exit)
for i, b := range f.Blocks {
if b.gaps == white {
for _, c := range b.Succs {
if c.gaps == black {
c.removePred(b) // delete white->black edge
}
}
if debugBlockOpt {
fmt.Fprintln(os.Stderr, "unreachable", b)
}
f.Blocks[i] = nil // delete b
}
}
f.removeNilBlocks()
}
// jumpThreading attempts to apply simple jump-threading to block b,
// in which a->b->c become a->c if b is just a Jump.
// The result is true if the optimization was applied.
func jumpThreading(f *Function, b *BasicBlock) bool {
if b.Index == 0 {
return false // don't apply to entry block
}
if b.Instrs == nil {
return false
}
for _, pred := range b.Preds {
switch pred.Control().(type) {
case *ConstantSwitch:
// don't optimize away the head blocks of switch statements
return false
}
}
if _, ok := b.Instrs[0].(*Jump); !ok {
return false // not just a jump
}
c := b.Succs[0]
if c == b {
return false // don't apply to degenerate jump-to-self.
}
if c.hasPhi() {
return false // not sound without more effort
}
for j, a := range b.Preds {
a.replaceSucc(b, c)
// If a now has two edges to c, replace its degenerate If by Jump.
if len(a.Succs) == 2 && a.Succs[0] == c && a.Succs[1] == c {
jump := new(Jump)
jump.setBlock(a)
a.Instrs[len(a.Instrs)-1] = jump
a.Succs = a.Succs[:1]
c.removePred(b)
} else {
if j == 0 {
c.replacePred(b, a)
} else {
c.Preds = append(c.Preds, a)
}
}
if debugBlockOpt {
fmt.Fprintln(os.Stderr, "jumpThreading", a, b, c)
}
}
f.Blocks[b.Index] = nil // delete b
return true
}
// fuseBlocks attempts to apply the block fusion optimization to block
// a, in which a->b becomes ab if len(a.Succs)==len(b.Preds)==1.
// The result is true if the optimization was applied.
func fuseBlocks(f *Function, a *BasicBlock) bool {
if len(a.Succs) != 1 {
return false
}
if a.Succs[0] == f.Exit {
return false
}
b := a.Succs[0]
if len(b.Preds) != 1 {
return false
}
if _, ok := a.Instrs[len(a.Instrs)-1].(*Panic); ok {
// panics aren't simple jumps, they have side effects.
return false
}
// Degenerate &&/|| ops may result in a straight-line CFG
// containing φ-nodes. (Ideally we'd replace such them with
// their sole operand but that requires Referrers, built later.)
if b.hasPhi() {
return false // not sound without further effort
}
// Eliminate jump at end of A, then copy all of B across.
a.Instrs = append(a.Instrs[:len(a.Instrs)-1], b.Instrs...)
for _, instr := range b.Instrs {
instr.setBlock(a)
}
// A inherits B's successors
a.Succs = append(a.succs2[:0], b.Succs...)
// Fix up Preds links of all successors of B.
for _, c := range b.Succs {
c.replacePred(b, a)
}
if debugBlockOpt {
fmt.Fprintln(os.Stderr, "fuseBlocks", a, b)
}
f.Blocks[b.Index] = nil // delete b
return true
}
// optimizeBlocks() performs some simple block optimizations on a
// completed function: dead block elimination, block fusion, jump
// threading.
func optimizeBlocks(f *Function) {
if debugBlockOpt {
f.WriteTo(os.Stderr)
mustSanityCheck(f, nil)
}
deleteUnreachableBlocks(f)
// Loop until no further progress.
changed := true
for changed {
changed = false
if debugBlockOpt {
f.WriteTo(os.Stderr)
mustSanityCheck(f, nil)
}
for _, b := range f.Blocks {
// f.Blocks will temporarily contain nils to indicate
// deleted blocks; we remove them at the end.
if b == nil {
continue
}
// Fuse blocks. b->c becomes bc.
if fuseBlocks(f, b) {
changed = true
}
// a->b->c becomes a->c if b contains only a Jump.
if jumpThreading(f, b) {
changed = true
continue // (b was disconnected)
}
}
}
f.removeNilBlocks()
}
golang-honnef-go-tools-2023.1.7/go/ir/builder.go 0000664 0000000 0000000 00000222714 14566144071 0021245 0 ustar 00root root 0000000 0000000 // Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ir
// This file implements the BUILD phase of IR construction.
//
// IR construction has two phases, CREATE and BUILD. In the CREATE phase
// (create.go), all packages are constructed and type-checked and
// definitions of all package members are created, method-sets are
// computed, and wrapper methods are synthesized.
// ir.Packages are created in arbitrary order.
//
// In the BUILD phase (builder.go), the builder traverses the AST of
// each Go source function and generates IR instructions for the
// function body. Initializer expressions for package-level variables
// are emitted to the package's init() function in the order specified
// by go/types.Info.InitOrder, then code for each function in the
// package is generated in lexical order.
//
// The builder's and Program's indices (maps) are populated and
// mutated during the CREATE phase, but during the BUILD phase they
// remain constant. The sole exception is Prog.methodSets and its
// related maps, which are protected by a dedicated mutex.
import (
"fmt"
"go/ast"
"go/constant"
"go/token"
"go/types"
"os"
"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.go 0000664 0000000 0000000 00000002452 14566144071 0023215 0 ustar 00root root 0000000 0000000 // Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.17
// +build go1.17
package ir_test
import (
"go/ast"
"go/importer"
"go/parser"
"go/token"
"go/types"
"testing"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
)
func TestBuildPackageGo117(t *testing.T) {
tests := []struct {
name string
src string
importer types.Importer
}{
{"slice to array pointer", "package p; var s []byte; var _ = (*[4]byte)(s)", nil},
{"unsafe slice", `package p; import "unsafe"; var _ = unsafe.Add(nil, 0)`, importer.Default()},
{"unsafe add", `package p; import "unsafe"; var _ = unsafe.Slice((*int)(nil), 0)`, importer.Default()},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "p.go", tc.src, parser.ParseComments|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.go 0000664 0000000 0000000 00000031123 14566144071 0022274 0 ustar 00root root 0000000 0000000 // Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//lint:file-ignore SA1019 go/ssa's test suite is built around the deprecated go/loader. We'll leave fixing that to upstream.
package ir_test
import (
"bytes"
"go/ast"
"go/importer"
"go/parser"
"go/token"
"go/types"
"os"
"reflect"
"sort"
"testing"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
"golang.org/x/tools/go/loader"
)
func isEmpty(f *ir.Function) bool { return f.Blocks == nil }
// Tests that programs partially loaded from gc object files contain
// functions with no code for the external portions, but are otherwise ok.
func TestBuildPackage(t *testing.T) {
input := `
package main
import (
"bytes"
"io"
"testing"
)
func main() {
var t testing.T
t.Parallel() // static call to external declared method
t.Fail() // static call to promoted external declared method
testing.Short() // static call to external package-level function
var w io.Writer = new(bytes.Buffer)
w.Write(nil) // interface invoke of external declared method
}
`
// Parse the file.
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "input.go", input, 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.go 0000664 0000000 0000000 00000016021 14566144071 0020735 0 ustar 00root root 0000000 0000000 // Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ir
// This file defines the Const SSA value type.
import (
"fmt"
"go/constant"
"go/types"
"strconv"
"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.go 0000664 0000000 0000000 00000016473 14566144071 0021065 0 ustar 00root root 0000000 0000000 // Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ir
// This file implements the CREATE phase of IR construction.
// See builder.go for explanation.
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"os"
"sync"
"honnef.co/go/tools/go/types/typeutil"
)
// measured on the standard library and rounded up to powers of two,
// on average there are 8 blocks and 16 instructions per block in a
// function.
const avgBlocks = 8
const avgInstructionsPerBlock = 16
// NewProgram returns a new IR Program.
//
// mode controls diagnostics and checking during IR construction.
func NewProgram(fset *token.FileSet, mode BuilderMode) *Program {
prog := &Program{
Fset: fset,
imported: make(map[string]*Package),
packages: make(map[*types.Package]*Package),
thunks: make(map[selectionKey]*Function),
bounds: make(map[*types.Func]*Function),
mode: mode,
}
h := typeutil.MakeHasher() // protected by methodsMu, in effect
prog.methodSets.SetHasher(h)
prog.canon.SetHasher(h)
return prog
}
// memberFromObject populates package pkg with a member for the
// typechecker object obj.
//
// For objects from Go source code, syntax is the associated syntax
// tree (for funcs and vars only); it will be used during the build
// phase.
func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node) {
name := obj.Name()
switch obj := obj.(type) {
case *types.Builtin:
if pkg.Pkg != types.Unsafe {
panic("unexpected builtin object: " + obj.String())
}
case *types.TypeName:
pkg.Members[name] = &Type{
object: obj,
pkg: pkg,
}
case *types.Const:
c := &NamedConst{
object: obj,
Value: NewConst(obj.Val(), obj.Type()),
pkg: pkg,
}
pkg.values[obj] = c.Value
pkg.Members[name] = c
case *types.Var:
g := &Global{
Pkg: pkg,
name: name,
object: obj,
typ: types.NewPointer(obj.Type()), // address
}
pkg.values[obj] = g
pkg.Members[name] = g
case *types.Func:
sig := obj.Type().(*types.Signature)
if sig.Recv() == nil && name == "init" {
pkg.ninit++
name = fmt.Sprintf("init#%d", pkg.ninit)
}
fn := &Function{
name: name,
object: obj,
Signature: sig,
Pkg: pkg,
Prog: pkg.Prog,
}
fn.source = syntax
fn.initHTML(pkg.printFunc)
if syntax == nil {
fn.Synthetic = SyntheticLoadedFromExportData
} else {
// Note: we initialize fn.Blocks in
// (*builder).buildFunction and not here because Blocks
// being nil is used to indicate that building of the
// function hasn't started yet.
fn.functionBody = &functionBody{
scratchInstructions: make([]Instruction, avgBlocks*avgInstructionsPerBlock),
}
}
pkg.values[obj] = fn
pkg.Functions = append(pkg.Functions, fn)
if sig.Recv() == nil {
pkg.Members[name] = fn // package-level function
}
default: // (incl. *types.Package)
panic("unexpected Object type: " + obj.String())
}
}
// membersFromDecl populates package pkg with members for each
// typechecker object (var, func, const or type) associated with the
// specified decl.
func membersFromDecl(pkg *Package, decl ast.Decl) {
switch decl := decl.(type) {
case *ast.GenDecl: // import, const, type or var
switch decl.Tok {
case token.CONST:
for _, spec := range decl.Specs {
for _, id := range spec.(*ast.ValueSpec).Names {
if !isBlankIdent(id) {
memberFromObject(pkg, pkg.info.Defs[id], nil)
}
}
}
case token.VAR:
for _, spec := range decl.Specs {
for _, id := range spec.(*ast.ValueSpec).Names {
if !isBlankIdent(id) {
memberFromObject(pkg, pkg.info.Defs[id], spec)
}
}
}
case token.TYPE:
for _, spec := range decl.Specs {
id := spec.(*ast.TypeSpec).Name
if !isBlankIdent(id) {
memberFromObject(pkg, pkg.info.Defs[id], nil)
}
}
}
case *ast.FuncDecl:
id := decl.Name
if !isBlankIdent(id) {
memberFromObject(pkg, pkg.info.Defs[id], decl)
}
}
}
// CreatePackage constructs and returns an IR Package from the
// specified type-checked, error-free file ASTs, and populates its
// Members mapping.
//
// importable determines whether this package should be returned by a
// subsequent call to ImportedPackage(pkg.Path()).
//
// The real work of building IR form for each function is not done
// until a subsequent call to Package.Build().
func (prog *Program) CreatePackage(pkg *types.Package, files []*ast.File, info *types.Info, importable bool) *Package {
p := &Package{
Prog: prog,
Members: make(map[string]Member),
values: make(map[types.Object]Value),
Pkg: pkg,
info: info, // transient (CREATE and BUILD phases)
files: files, // transient (CREATE and BUILD phases)
printFunc: prog.PrintFunc,
}
// Add init() function.
p.init = &Function{
name: "init",
Signature: new(types.Signature),
Synthetic: SyntheticPackageInitializer,
Pkg: p,
Prog: prog,
functionBody: new(functionBody),
}
p.init.initHTML(prog.PrintFunc)
p.Members[p.init.name] = p.init
p.Functions = append(p.Functions, p.init)
// CREATE phase.
// Allocate all package members: vars, funcs, consts and types.
if len(files) > 0 {
// Go source package.
for _, file := range files {
for _, decl := range file.Decls {
membersFromDecl(p, decl)
}
}
} else {
// GC-compiled binary package (or "unsafe")
// No code.
// No position information.
scope := p.Pkg.Scope()
for _, name := range scope.Names() {
obj := scope.Lookup(name)
memberFromObject(p, obj, nil)
if obj, ok := obj.(*types.TypeName); ok {
if named, ok := obj.Type().(*types.Named); ok {
for i, n := 0, named.NumMethods(); i < n; i++ {
memberFromObject(p, named.Method(i), nil)
}
}
}
}
}
// Add initializer guard variable.
initguard := &Global{
Pkg: p,
name: "init$guard",
typ: types.NewPointer(tBool),
}
p.Members[initguard.Name()] = initguard
if prog.mode&GlobalDebug != 0 {
p.SetDebugMode(true)
}
if prog.mode&PrintPackages != 0 {
printMu.Lock()
p.WriteTo(os.Stdout)
printMu.Unlock()
}
if importable {
prog.imported[p.Pkg.Path()] = p
}
prog.packages[p.Pkg] = p
return p
}
// printMu serializes printing of Packages/Functions to stdout.
var printMu sync.Mutex
// AllPackages returns a new slice containing all packages in the
// program prog in unspecified order.
func (prog *Program) AllPackages() []*Package {
pkgs := make([]*Package, 0, len(prog.packages))
for _, pkg := range prog.packages {
pkgs = append(pkgs, pkg)
}
return pkgs
}
// ImportedPackage returns the importable Package whose PkgPath
// is path, or nil if no such Package has been created.
//
// A parameter to CreatePackage determines whether a package should be
// considered importable. For example, no import declaration can resolve
// to the ad-hoc main package created by 'go build foo.go'.
//
// TODO(adonovan): rethink this function and the "importable" concept;
// most packages are importable. This function assumes that all
// types.Package.Path values are unique within the ir.Program, which is
// false---yet this function remains very convenient.
// Clients should use (*Program).Package instead where possible.
// IR doesn't really need a string-keyed map of packages.
func (prog *Program) ImportedPackage(path string) *Package {
return prog.imported[path]
}
golang-honnef-go-tools-2023.1.7/go/ir/doc.go 0000664 0000000 0000000 00000014034 14566144071 0020356 0 ustar 00root root 0000000 0000000 // Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package ir defines a representation of the elements of Go programs
// (packages, types, functions, variables and constants) using a
// static single-information (SSI) form intermediate representation
// (IR) for the bodies of functions.
//
// THIS INTERFACE IS EXPERIMENTAL AND IS LIKELY TO CHANGE.
//
// For an introduction to SSA form, upon which SSI builds, see
// http://en.wikipedia.org/wiki/Static_single_assignment_form.
// This page provides a broader reading list:
// http://www.dcs.gla.ac.uk/~jsinger/ssa.html.
//
// For an introduction to SSI form, see The static single information
// form by C. Scott Ananian.
//
// The level of abstraction of the IR form is intentionally close to
// the source language to facilitate construction of source analysis
// tools. It is not intended for machine code generation.
//
// The simplest way to create the IR of a package is
// to load typed syntax trees using golang.org/x/tools/go/packages, then
// invoke the irutil.Packages helper function. See ExampleLoadPackages
// and ExampleWholeProgram for examples.
// The resulting ir.Program contains all the packages and their
// members, but IR code is not created for function bodies until a
// subsequent call to (*Package).Build or (*Program).Build.
//
// The builder initially builds a naive IR form in which all local
// variables are addresses of stack locations with explicit loads and
// stores. Registerization of eligible locals and φ-node insertion
// using dominance and dataflow are then performed as a second pass
// called "lifting" to improve the accuracy and performance of
// subsequent analyses; this pass can be skipped by setting the
// NaiveForm builder flag.
//
// The primary interfaces of this package are:
//
// - Member: a named member of a Go package.
// - Value: an expression that yields a value.
// - Instruction: a statement that consumes values and performs computation.
// - Node: a Value or Instruction (emphasizing its membership in the IR value graph)
//
// A computation that yields a result implements both the Value and
// Instruction interfaces. The following table shows for each
// concrete type which of these interfaces it implements.
//
// Value? Instruction? Member?
// *Alloc ✔ ✔
// *BinOp ✔ ✔
// *BlankStore ✔
// *Builtin ✔
// *Call ✔ ✔
// *ChangeInterface ✔ ✔
// *ChangeType ✔ ✔
// *Const ✔ ✔
// *Convert ✔ ✔
// *DebugRef ✔
// *Defer ✔ ✔
// *Extract ✔ ✔
// *Field ✔ ✔
// *FieldAddr ✔ ✔
// *FreeVar ✔
// *Function ✔ ✔ (func)
// *Global ✔ ✔ (var)
// *Go ✔ ✔
// *If ✔
// *Index ✔ ✔
// *IndexAddr ✔ ✔
// *Jump ✔
// *Load ✔ ✔
// *MakeChan ✔ ✔
// *MakeClosure ✔ ✔
// *MakeInterface ✔ ✔
// *MakeMap ✔ ✔
// *MakeSlice ✔ ✔
// *MapLookup ✔ ✔
// *MapUpdate ✔ ✔
// *NamedConst ✔ (const)
// *Next ✔ ✔
// *Panic ✔
// *Parameter ✔ ✔
// *Phi ✔ ✔
// *Range ✔ ✔
// *Recv ✔ ✔
// *Return ✔
// *RunDefers ✔
// *Select ✔ ✔
// *Send ✔ ✔
// *Sigma ✔ ✔
// *Slice ✔ ✔
// *SliceToArrayPointer ✔ ✔
// *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.go 0000664 0000000 0000000 00000027221 14566144071 0020372 0 ustar 00root root 0000000 0000000 // Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ir
// This file defines algorithms related to dominance.
// Dominator tree construction ----------------------------------------
//
// We use the algorithm described in Lengauer & Tarjan. 1979. A fast
// algorithm for finding dominators in a flowgraph.
// http://doi.acm.org/10.1145/357062.357071
//
// We also apply the optimizations to SLT described in Georgiadis et
// al, Finding Dominators in Practice, JGAA 2006,
// http://jgaa.info/accepted/2006/GeorgiadisTarjanWerneck2006.10.1.pdf
// to avoid the need for buckets of size > 1.
import (
"bytes"
"fmt"
"io"
"math/big"
"os"
"sort"
)
// Idom returns the block that immediately dominates b:
// its parent in the dominator tree, if any.
// The entry node (b.Index==0) does not have a parent.
func (b *BasicBlock) Idom() *BasicBlock { return b.dom.idom }
// Dominees returns the list of blocks that b immediately dominates:
// its children in the dominator tree.
func (b *BasicBlock) Dominees() []*BasicBlock { return b.dom.children }
// Dominates reports whether b dominates c.
func (b *BasicBlock) Dominates(c *BasicBlock) bool {
return b.dom.pre <= c.dom.pre && c.dom.post <= b.dom.post
}
type byDomPreorder []*BasicBlock
func (a byDomPreorder) Len() int { return len(a) }
func (a byDomPreorder) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a byDomPreorder) Less(i, j int) bool { return a[i].dom.pre < a[j].dom.pre }
// DomPreorder returns a new slice containing the blocks of f in
// dominator tree preorder.
func (f *Function) DomPreorder() []*BasicBlock {
n := len(f.Blocks)
order := make(byDomPreorder, n)
copy(order, f.Blocks)
sort.Sort(order)
return order
}
// domInfo contains a BasicBlock's dominance information.
type domInfo struct {
idom *BasicBlock // immediate dominator (parent in domtree)
children []*BasicBlock // nodes immediately dominated by this one
pre, post int32 // pre- and post-order numbering within domtree
}
// buildDomTree computes the dominator tree of f using the LT algorithm.
// Precondition: all blocks are reachable (e.g. optimizeBlocks has been run).
func buildDomTree(fn *Function) {
// The step numbers refer to the original LT paper; the
// reordering is due to Georgiadis.
// Clear any previous domInfo.
for _, b := range fn.Blocks {
b.dom = domInfo{}
}
idoms := make([]*BasicBlock, len(fn.Blocks))
order := make([]*BasicBlock, 0, len(fn.Blocks))
seen := fn.blockset(0)
var dfs func(b *BasicBlock)
dfs = func(b *BasicBlock) {
if !seen.Add(b) {
return
}
for _, succ := range b.Succs {
dfs(succ)
}
if fn.fakeExits.Has(b) {
dfs(fn.Exit)
}
order = append(order, b)
b.post = len(order) - 1
}
dfs(fn.Blocks[0])
for i := 0; i < len(order)/2; i++ {
o := len(order) - i - 1
order[i], order[o] = order[o], order[i]
}
idoms[fn.Blocks[0].Index] = fn.Blocks[0]
changed := true
for changed {
changed = false
// iterate over all nodes in reverse postorder, except for the
// entry node
for _, b := range order[1:] {
var newIdom *BasicBlock
do := func(p *BasicBlock) {
if idoms[p.Index] == nil {
return
}
if newIdom == nil {
newIdom = p
} else {
finger1 := p
finger2 := newIdom
for finger1 != finger2 {
for finger1.post < finger2.post {
finger1 = idoms[finger1.Index]
}
for finger2.post < finger1.post {
finger2 = idoms[finger2.Index]
}
}
newIdom = finger1
}
}
for _, p := range b.Preds {
do(p)
}
if b == fn.Exit {
for _, p := range fn.Blocks {
if fn.fakeExits.Has(p) {
do(p)
}
}
}
if idoms[b.Index] != newIdom {
idoms[b.Index] = newIdom
changed = true
}
}
}
for i, b := range idoms {
fn.Blocks[i].dom.idom = b
if b == nil {
// malformed CFG
continue
}
if i == b.Index {
continue
}
b.dom.children = append(b.dom.children, fn.Blocks[i])
}
numberDomTree(fn.Blocks[0], 0, 0)
// printDomTreeDot(os.Stderr, fn) // debugging
// printDomTreeText(os.Stderr, root, 0) // debugging
if fn.Prog.mode&SanityCheckFunctions != 0 {
sanityCheckDomTree(fn)
}
}
// buildPostDomTree is like buildDomTree, but builds the post-dominator tree instead.
func buildPostDomTree(fn *Function) {
// The step numbers refer to the original LT paper; the
// reordering is due to Georgiadis.
// Clear any previous domInfo.
for _, b := range fn.Blocks {
b.pdom = domInfo{}
}
idoms := make([]*BasicBlock, len(fn.Blocks))
order := make([]*BasicBlock, 0, len(fn.Blocks))
seen := fn.blockset(0)
var dfs func(b *BasicBlock)
dfs = func(b *BasicBlock) {
if !seen.Add(b) {
return
}
for _, pred := range b.Preds {
dfs(pred)
}
if b == fn.Exit {
for _, p := range fn.Blocks {
if fn.fakeExits.Has(p) {
dfs(p)
}
}
}
order = append(order, b)
b.post = len(order) - 1
}
dfs(fn.Exit)
for i := 0; i < len(order)/2; i++ {
o := len(order) - i - 1
order[i], order[o] = order[o], order[i]
}
idoms[fn.Exit.Index] = fn.Exit
changed := true
for changed {
changed = false
// iterate over all nodes in reverse postorder, except for the
// exit node
for _, b := range order[1:] {
var newIdom *BasicBlock
do := func(p *BasicBlock) {
if idoms[p.Index] == nil {
return
}
if newIdom == nil {
newIdom = p
} else {
finger1 := p
finger2 := newIdom
for finger1 != finger2 {
for finger1.post < finger2.post {
finger1 = idoms[finger1.Index]
}
for finger2.post < finger1.post {
finger2 = idoms[finger2.Index]
}
}
newIdom = finger1
}
}
for _, p := range b.Succs {
do(p)
}
if fn.fakeExits.Has(b) {
do(fn.Exit)
}
if idoms[b.Index] != newIdom {
idoms[b.Index] = newIdom
changed = true
}
}
}
for i, b := range idoms {
fn.Blocks[i].pdom.idom = b
if b == nil {
// malformed CFG
continue
}
if i == b.Index {
continue
}
b.pdom.children = append(b.pdom.children, fn.Blocks[i])
}
numberPostDomTree(fn.Exit, 0, 0)
// printPostDomTreeDot(os.Stderr, fn) // debugging
// printPostDomTreeText(os.Stderr, fn.Exit, 0) // debugging
if fn.Prog.mode&SanityCheckFunctions != 0 { // XXX
sanityCheckDomTree(fn) // XXX
}
}
// numberDomTree sets the pre- and post-order numbers of a depth-first
// traversal of the dominator tree rooted at v. These are used to
// answer dominance queries in constant time.
func numberDomTree(v *BasicBlock, pre, post int32) (int32, int32) {
v.dom.pre = pre
pre++
for _, child := range v.dom.children {
pre, post = numberDomTree(child, pre, post)
}
v.dom.post = post
post++
return pre, post
}
// numberPostDomTree sets the pre- and post-order numbers of a depth-first
// traversal of the post-dominator tree rooted at v. These are used to
// answer post-dominance queries in constant time.
func numberPostDomTree(v *BasicBlock, pre, post int32) (int32, int32) {
v.pdom.pre = pre
pre++
for _, child := range v.pdom.children {
pre, post = numberPostDomTree(child, pre, post)
}
v.pdom.post = post
post++
return pre, post
}
// Testing utilities ----------------------------------------
// sanityCheckDomTree checks the correctness of the dominator tree
// computed by the LT algorithm by comparing against the dominance
// relation computed by a naive Kildall-style forward dataflow
// analysis (Algorithm 10.16 from the "Dragon" book).
func sanityCheckDomTree(f *Function) {
n := len(f.Blocks)
// D[i] is the set of blocks that dominate f.Blocks[i],
// represented as a bit-set of block indices.
D := make([]big.Int, n)
one := big.NewInt(1)
// all is the set of all blocks; constant.
var all big.Int
all.Set(one).Lsh(&all, uint(n)).Sub(&all, one)
// Initialization.
for i := range f.Blocks {
if i == 0 {
// A root is dominated only by itself.
D[i].SetBit(&D[0], 0, 1)
} else {
// All other blocks are (initially) dominated
// by every block.
D[i].Set(&all)
}
}
// Iteration until fixed point.
for changed := true; changed; {
changed = false
for i, b := range f.Blocks {
if i == 0 {
continue
}
// Compute intersection across predecessors.
var x big.Int
x.Set(&all)
for _, pred := range b.Preds {
x.And(&x, &D[pred.Index])
}
if b == f.Exit {
for _, p := range f.Blocks {
if f.fakeExits.Has(p) {
x.And(&x, &D[p.Index])
}
}
}
x.SetBit(&x, i, 1) // a block always dominates itself.
if D[i].Cmp(&x) != 0 {
D[i].Set(&x)
changed = true
}
}
}
// Check the entire relation. O(n^2).
ok := true
for i := 0; i < n; i++ {
for j := 0; j < n; j++ {
b, c := f.Blocks[i], f.Blocks[j]
actual := b.Dominates(c)
expected := D[j].Bit(i) == 1
if actual != expected {
fmt.Fprintf(os.Stderr, "dominates(%s, %s)==%t, want %t\n", b, c, actual, expected)
ok = false
}
}
}
preorder := f.DomPreorder()
for _, b := range f.Blocks {
if got := preorder[b.dom.pre]; got != b {
fmt.Fprintf(os.Stderr, "preorder[%d]==%s, want %s\n", b.dom.pre, got, b)
ok = false
}
}
if !ok {
panic("sanityCheckDomTree failed for " + f.String())
}
}
// Printing functions ----------------------------------------
// printDomTree prints the dominator tree as text, using indentation.
//
//lint:ignore U1000 used during debugging
func printDomTreeText(buf *bytes.Buffer, v *BasicBlock, indent int) {
fmt.Fprintf(buf, "%*s%s\n", 4*indent, "", v)
for _, child := range v.dom.children {
printDomTreeText(buf, child, indent+1)
}
}
// printDomTreeDot prints the dominator tree of f in AT&T GraphViz
// (.dot) format.
//
//lint:ignore U1000 used during debugging
func printDomTreeDot(buf io.Writer, f *Function) {
fmt.Fprintln(buf, "//", f)
fmt.Fprintln(buf, "digraph domtree {")
for i, b := range f.Blocks {
v := b.dom
fmt.Fprintf(buf, "\tn%d [label=\"%s (%d, %d)\",shape=\"rectangle\"];\n", v.pre, b, v.pre, v.post)
// TODO(adonovan): improve appearance of edges
// belonging to both dominator tree and CFG.
// Dominator tree edge.
if i != 0 {
fmt.Fprintf(buf, "\tn%d -> n%d [style=\"solid\",weight=100];\n", v.idom.dom.pre, v.pre)
}
// CFG edges.
for _, pred := range b.Preds {
fmt.Fprintf(buf, "\tn%d -> n%d [style=\"dotted\",weight=0];\n", pred.dom.pre, v.pre)
}
if f.fakeExits.Has(b) {
fmt.Fprintf(buf, "\tn%d -> n%d [style=\"dotted\",weight=0,color=red];\n", b.dom.pre, f.Exit.dom.pre)
}
}
fmt.Fprintln(buf, "}")
}
// printDomTree prints the dominator tree as text, using indentation.
//
//lint:ignore U1000 used during debugging
func printPostDomTreeText(buf io.Writer, v *BasicBlock, indent int) {
fmt.Fprintf(buf, "%*s%s\n", 4*indent, "", v)
for _, child := range v.pdom.children {
printPostDomTreeText(buf, child, indent+1)
}
}
// printDomTreeDot prints the dominator tree of f in AT&T GraphViz
// (.dot) format.
//
//lint:ignore U1000 used during debugging
func printPostDomTreeDot(buf io.Writer, f *Function) {
fmt.Fprintln(buf, "//", f)
fmt.Fprintln(buf, "digraph pdomtree {")
for _, b := range f.Blocks {
v := b.pdom
fmt.Fprintf(buf, "\tn%d [label=\"%s (%d, %d)\",shape=\"rectangle\"];\n", v.pre, b, v.pre, v.post)
// TODO(adonovan): improve appearance of edges
// belonging to both dominator tree and CFG.
// Dominator tree edge.
if b != f.Exit {
fmt.Fprintf(buf, "\tn%d -> n%d [style=\"solid\",weight=100];\n", v.idom.pdom.pre, v.pre)
}
// CFG edges.
for _, pred := range b.Preds {
fmt.Fprintf(buf, "\tn%d -> n%d [style=\"dotted\",weight=0];\n", pred.pdom.pre, v.pre)
}
if f.fakeExits.Has(b) {
fmt.Fprintf(buf, "\tn%d -> n%d [style=\"dotted\",weight=0,color=red];\n", b.dom.pre, f.Exit.dom.pre)
}
}
fmt.Fprintln(buf, "}")
}
golang-honnef-go-tools-2023.1.7/go/ir/emit.go 0000664 0000000 0000000 00000037514 14566144071 0020557 0 ustar 00root root 0000000 0000000 // Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ir
// Helpers for emitting IR instructions.
import (
"fmt"
"go/ast"
"go/constant"
"go/token"
"go/types"
"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.go 0000664 0000000 0000000 00000012253 14566144071 0022304 0 ustar 00root root 0000000 0000000 // Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ir_test
import (
"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.go 0000664 0000000 0000000 00000024364 14566144071 0020754 0 ustar 00root root 0000000 0000000 package ir
import (
"go/types"
)
func (b *builder) buildExits(fn *Function) {
if obj := fn.Object(); obj != nil {
switch obj.Pkg().Path() {
case "runtime":
switch obj.Name() {
case "exit":
fn.NoReturn = AlwaysExits
return
case "throw":
fn.NoReturn = AlwaysExits
return
case "Goexit":
fn.NoReturn = AlwaysUnwinds
return
}
case "go.uber.org/zap":
switch obj.(*types.Func).FullName() {
case "(*go.uber.org/zap.Logger).Fatal",
"(*go.uber.org/zap.SugaredLogger).Fatal",
"(*go.uber.org/zap.SugaredLogger).Fatalw",
"(*go.uber.org/zap.SugaredLogger).Fatalf":
// Technically, this method does not unconditionally exit
// the process. It dynamically calls a function stored in
// the logger. If the function is nil, it defaults to
// os.Exit.
//
// The main intent of this method is to terminate the
// process, and that's what the vast majority of people
// will use it for. We'll happily accept some false
// negatives to avoid a lot of false positives.
fn.NoReturn = AlwaysExits
case "(*go.uber.org/zap.Logger).Panic",
"(*go.uber.org/zap.SugaredLogger).Panicw",
"(*go.uber.org/zap.SugaredLogger).Panicf":
fn.NoReturn = AlwaysUnwinds
return
case "(*go.uber.org/zap.Logger).DPanic",
"(*go.uber.org/zap.SugaredLogger).DPanicf",
"(*go.uber.org/zap.SugaredLogger).DPanicw":
// These methods will only panic in development.
}
case "github.com/sirupsen/logrus":
switch obj.(*types.Func).FullName() {
case "(*github.com/sirupsen/logrus.Logger).Exit":
// Technically, this method does not unconditionally exit
// the process. It dynamically calls a function stored in
// the logger. If the function is nil, it defaults to
// os.Exit.
//
// The main intent of this method is to terminate the
// process, and that's what the vast majority of people
// will use it for. We'll happily accept some false
// negatives to avoid a lot of false positives.
fn.NoReturn = AlwaysExits
return
case "(*github.com/sirupsen/logrus.Logger).Panic",
"(*github.com/sirupsen/logrus.Logger).Panicf",
"(*github.com/sirupsen/logrus.Logger).Panicln":
// These methods will always panic, but that's not
// statically known from the code alone, because they
// take a detour through the generic Log methods.
fn.NoReturn = AlwaysUnwinds
return
case "(*github.com/sirupsen/logrus.Entry).Panicf",
"(*github.com/sirupsen/logrus.Entry).Panicln":
// Entry.Panic has an explicit panic, but Panicf and
// Panicln do not, relying fully on the generic Log
// method.
fn.NoReturn = AlwaysUnwinds
return
case "(*github.com/sirupsen/logrus.Logger).Log",
"(*github.com/sirupsen/logrus.Logger).Logf",
"(*github.com/sirupsen/logrus.Logger).Logln":
// TODO(dh): we cannot handle these cases. Whether they
// exit or unwind depends on the level, which is set
// via the first argument. We don't currently support
// call-site-specific exit information.
}
case "github.com/golang/glog":
switch obj.(*types.Func).FullName() {
case "github.com/golang/glog.Exit",
"github.com/golang/glog.ExitDepth",
"github.com/golang/glog.Exitf",
"github.com/golang/glog.Exitln",
"github.com/golang/glog.Fatal",
"github.com/golang/glog.FatalDepth",
"github.com/golang/glog.Fatalf",
"github.com/golang/glog.Fatalln":
// all of these call os.Exit after logging
fn.NoReturn = AlwaysExits
}
case "k8s.io/klog":
switch obj.(*types.Func).FullName() {
case "k8s.io/klog.Exit",
"k8s.io/klog.ExitDepth",
"k8s.io/klog.Exitf",
"k8s.io/klog.Exitln",
"k8s.io/klog.Fatal",
"k8s.io/klog.FatalDepth",
"k8s.io/klog.Fatalf",
"k8s.io/klog.Fatalln":
// all of these call os.Exit after logging
fn.NoReturn = AlwaysExits
}
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.go 0000664 0000000 0000000 00000061247 14566144071 0020554 0 ustar 00root root 0000000 0000000 // Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ir
// This file implements the Function and BasicBlock types.
import (
"bytes"
"fmt"
"go/ast"
"go/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.go 0000664 0000000 0000000 00000067077 14566144071 0020574 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Copyright 2019 Dominik Honnef. All rights reserved.
package ir
import (
"bytes"
"fmt"
"go/types"
"html"
"io"
"log"
"os"
"os/exec"
"path/filepath"
"reflect"
"sort"
"strings"
)
func live(f *Function) []bool {
max := 0
var ops []*Value
for _, b := range f.Blocks {
for _, instr := range b.Instrs {
if int(instr.ID()) > max {
max = int(instr.ID())
}
}
}
out := make([]bool, max+1)
var q []Node
for _, b := range f.Blocks {
for _, instr := range b.Instrs {
switch instr.(type) {
case *BlankStore, *Call, *ConstantSwitch, *Defer, *Go, *If, *Jump, *MapUpdate, *Next, *Panic, *Recv, *Return, *RunDefers, *Send, *Store, *Unreachable:
out[instr.ID()] = true
q = append(q, instr)
}
}
}
for len(q) > 0 {
v := q[len(q)-1]
q = q[:len(q)-1]
for _, op := range v.Operands(ops) {
if *op == nil {
continue
}
if !out[(*op).ID()] {
out[(*op).ID()] = true
q = append(q, *op)
}
}
}
return out
}
type funcPrinter interface {
startBlock(b *BasicBlock, reachable bool)
endBlock(b *BasicBlock)
value(v Node, live bool)
startDepCycle()
endDepCycle()
named(n string, vals []Value)
}
func namedValues(f *Function) map[types.Object][]Value {
names := map[types.Object][]Value{}
for _, b := range f.Blocks {
for _, instr := range b.Instrs {
if instr, ok := instr.(*DebugRef); ok {
if obj := instr.object; obj != nil {
names[obj] = append(names[obj], instr.X)
}
}
}
}
// XXX deduplicate values
return names
}
func fprintFunc(p funcPrinter, f *Function) {
// XXX does our IR form preserve unreachable blocks?
// reachable, live := findlive(f)
l := live(f)
for _, b := range f.Blocks {
// XXX
// p.startBlock(b, reachable[b.Index])
p.startBlock(b, true)
end := len(b.Instrs) - 1
if end < 0 {
end = 0
}
for _, v := range b.Instrs[:end] {
if _, ok := v.(*DebugRef); !ok {
p.value(v, l[v.ID()])
}
}
p.endBlock(b)
}
names := namedValues(f)
keys := make([]types.Object, 0, len(names))
for key := range names {
keys = append(keys, key)
}
sort.Slice(keys, func(i, j int) bool {
return keys[i].Pos() < keys[j].Pos()
})
for _, key := range keys {
p.named(key.Name(), names[key])
}
}
func opName(v Node) string {
switch v := v.(type) {
case *Call:
if v.Common().IsInvoke() {
return "Invoke"
}
return "Call"
case *Alloc:
if v.Heap {
return "HeapAlloc"
}
return "StackAlloc"
case *Select:
if v.Blocking {
return "SelectBlocking"
}
return "SelectNonBlocking"
default:
return reflect.ValueOf(v).Type().Elem().Name()
}
}
type HTMLWriter struct {
w io.WriteCloser
path string
dot *dotWriter
}
func NewHTMLWriter(path string, funcname, cfgMask string) *HTMLWriter {
out, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
log.Fatalf("%v", err)
}
pwd, err := os.Getwd()
if err != nil {
log.Fatalf("%v", err)
}
html := HTMLWriter{w: out, path: filepath.Join(pwd, path)}
html.dot = newDotWriter()
html.start(funcname)
return &html
}
func (w *HTMLWriter) start(name string) {
if w == nil {
return
}
w.WriteString("")
w.WriteString(`
`)
w.WriteString("")
w.WriteString("
Click on a value or block to toggle highlighting of that value/block
and its uses. (Values and blocks are highlighted by ID, and IDs of
dead items may be reused, so not all highlights necessarily correspond
to the clicked item.)
Faded out values and blocks are dead code that has not been eliminated.
Values printed in italics have a dependency cycle.
CFG: Dashed edge is for unlikely branches. Blue color is for backward edges.
Edge with a dot means that this edge follows the order in which blocks were laid out.
`)
w.WriteString("
")
w.WriteString("
")
}
func (w *HTMLWriter) Close() {
if w == nil {
return
}
io.WriteString(w.w, "
")
io.WriteString(w.w, "
")
io.WriteString(w.w, "")
io.WriteString(w.w, "")
w.w.Close()
fmt.Printf("dumped IR to %v\n", w.path)
}
// WriteFunc writes f in a column headed by title.
// phase is used for collapsing columns and should be unique across the table.
func (w *HTMLWriter) WriteFunc(phase, title string, f *Function) {
if w == nil {
return
}
w.WriteColumn(phase, title, "", funcHTML(f, phase, w.dot))
}
// WriteColumn writes raw HTML in a column headed by title.
// It is intended for pre- and post-compilation log output.
func (w *HTMLWriter) WriteColumn(phase, title, class, html string) {
if w == nil {
return
}
id := strings.Replace(phase, " ", "-", -1)
// collapsed column
w.Printf("
%v
", id, phase)
if class == "" {
w.Printf("
", id)
} else {
w.Printf("
", id, class)
}
w.WriteString("
" + title + "
")
w.WriteString(html)
w.WriteString("
")
}
func (w *HTMLWriter) Printf(msg string, v ...interface{}) {
if _, err := fmt.Fprintf(w.w, msg, v...); err != nil {
log.Fatalf("%v", err)
}
}
func (w *HTMLWriter) WriteString(s string) {
if _, err := io.WriteString(w.w, s); err != nil {
log.Fatalf("%v", err)
}
}
func valueHTML(v Node) string {
if v == nil {
return "<nil>"
}
// TODO: Using the value ID as the class ignores the fact
// that value IDs get recycled and that some values
// are transmuted into other values.
class := fmt.Sprintf("t%d", v.ID())
var label string
switch v := v.(type) {
case *Function:
label = v.RelString(nil)
case *Builtin:
label = v.Name()
default:
label = class
}
return fmt.Sprintf("%s", class, label)
}
func valueLongHTML(v Node) string {
// TODO: Any intra-value formatting?
// I'm wary of adding too much visual noise,
// but a little bit might be valuable.
// We already have visual noise in the form of punctuation
// maybe we could replace some of that with formatting.
s := fmt.Sprintf("", v.ID())
linenumber := "(?)"
if v.Pos().IsValid() {
line := v.Parent().Prog.Fset.Position(v.Pos()).Line
linenumber = fmt.Sprintf("(%d)", line, line)
}
s += fmt.Sprintf("%s %s = %s", valueHTML(v), linenumber, opName(v))
if v, ok := v.(Value); ok {
s += " <" + html.EscapeString(v.Type().String()) + ">"
}
switch v := v.(type) {
case *Parameter:
s += fmt.Sprintf(" {%s}", html.EscapeString(v.name))
case *BinOp:
s += fmt.Sprintf(" {%s}", html.EscapeString(v.Op.String()))
case *UnOp:
s += fmt.Sprintf(" {%s}", html.EscapeString(v.Op.String()))
case *Extract:
name := v.Tuple.Type().(*types.Tuple).At(v.Index).Name()
s += fmt.Sprintf(" [%d] (%s)", v.Index, name)
case *Field:
st := v.X.Type().Underlying().(*types.Struct)
// Be robust against a bad index.
name := "?"
if 0 <= v.Field && v.Field < st.NumFields() {
name = st.Field(v.Field).Name()
}
s += fmt.Sprintf(" [%d] (%s)", v.Field, name)
case *FieldAddr:
st := deref(v.X.Type()).Underlying().(*types.Struct)
// Be robust against a bad index.
name := "?"
if 0 <= v.Field && v.Field < st.NumFields() {
name = st.Field(v.Field).Name()
}
s += fmt.Sprintf(" [%d] (%s)", v.Field, name)
case *Recv:
s += fmt.Sprintf(" {%t}", v.CommaOk)
case *Call:
if v.Common().IsInvoke() {
s += fmt.Sprintf(" {%s}", html.EscapeString(v.Common().Method.FullName()))
}
case *Const:
if v.Value == nil {
s += " {<nil>}"
} else {
s += fmt.Sprintf(" {%s}", html.EscapeString(v.Value.String()))
}
case *Sigma:
s += fmt.Sprintf(" [#%s]", v.From)
}
for _, a := range v.Operands(nil) {
s += fmt.Sprintf(" %s", valueHTML(*a))
}
// OPT(dh): we're calling namedValues many times on the same function.
allNames := namedValues(v.Parent())
var names []string
for name, values := range allNames {
for _, value := range values {
if v == value {
names = append(names, name.Name())
break
}
}
}
if len(names) != 0 {
s += " (" + strings.Join(names, ", ") + ")"
}
s += ""
return s
}
func blockHTML(b *BasicBlock) string {
// TODO: Using the value ID as the class ignores the fact
// that value IDs get recycled and that some values
// are transmuted into other values.
s := html.EscapeString(b.String())
return fmt.Sprintf("%s", s, s)
}
func blockLongHTML(b *BasicBlock) string {
var kind string
var term Instruction
if len(b.Instrs) > 0 {
term = b.Control()
kind = opName(term)
}
// TODO: improve this for HTML?
s := fmt.Sprintf("%s", b.Index, kind)
if term != nil {
ops := term.Operands(nil)
if len(ops) > 0 {
var ss []string
for _, op := range ops {
ss = append(ss, valueHTML(*op))
}
s += " " + strings.Join(ss, ", ")
}
}
if len(b.Succs) > 0 {
s += " →" // right arrow
for _, c := range b.Succs {
s += " " + blockHTML(c)
}
}
return s
}
func funcHTML(f *Function, phase string, dot *dotWriter) string {
buf := new(bytes.Buffer)
if dot != nil {
dot.writeFuncSVG(buf, phase, f)
}
fmt.Fprint(buf, "")
p := htmlFuncPrinter{w: buf}
fprintFunc(p, f)
// fprintFunc(&buf, f) // TODO: HTML, not text, for line breaks, etc.
fmt.Fprint(buf, "")
return buf.String()
}
type htmlFuncPrinter struct {
w io.Writer
}
func (p htmlFuncPrinter) startBlock(b *BasicBlock, reachable bool) {
var dead string
if !reachable {
dead = "dead-block"
}
fmt.Fprintf(p.w, "
", b, dead)
fmt.Fprintf(p.w, "
%s:", blockHTML(b))
if len(b.Preds) > 0 {
io.WriteString(p.w, " ←") // left arrow
for _, pred := range b.Preds {
fmt.Fprintf(p.w, " %s", blockHTML(pred))
}
}
if len(b.Instrs) > 0 {
io.WriteString(p.w, ``)
}
io.WriteString(p.w, "
")
if len(b.Instrs) > 0 { // start list of values
io.WriteString(p.w, "
")
io.WriteString(p.w, "
")
}
}
func (p htmlFuncPrinter) endBlock(b *BasicBlock) {
if len(b.Instrs) > 0 { // end list of values
io.WriteString(p.w, "