pax_global_header 0000666 0000000 0000000 00000000064 14657040722 0014521 g ustar 00root root 0000000 0000000 52 comment=984331f1ee19d6d15173b5d087560edd7b4efe53
golang-honnef-go-tools-2024.1/ 0000775 0000000 0000000 00000000000 14657040722 0016074 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/.gitattributes 0000664 0000000 0000000 00000000061 14657040722 0020764 0 ustar 00root root 0000000 0000000 *.golden -text
*.svg binary
**/testdata/** -text
golang-honnef-go-tools-2024.1/.github/ 0000775 0000000 0000000 00000000000 14657040722 0017434 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/.github/FUNDING.yml 0000664 0000000 0000000 00000000043 14657040722 0021246 0 ustar 00root root 0000000 0000000 patreon: dominikh
github: dominikh
golang-honnef-go-tools-2024.1/.github/ISSUE_TEMPLATE/ 0000775 0000000 0000000 00000000000 14657040722 0021617 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/.github/ISSUE_TEMPLATE/1_false_positive.md 0000664 0000000 0000000 00000001202 14657040722 0025370 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-2024.1/.github/ISSUE_TEMPLATE/2_false_negative.md 0000664 0000000 0000000 00000001202 14657040722 0025331 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-2024.1/.github/ISSUE_TEMPLATE/3_bug.md 0000664 0000000 0000000 00000001156 14657040722 0023143 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-2024.1/.github/ISSUE_TEMPLATE/4_other.md 0000664 0000000 0000000 00000000220 14657040722 0023477 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-2024.1/.github/ISSUE_TEMPLATE/config.yml 0000664 0000000 0000000 00000000034 14657040722 0023604 0 ustar 00root root 0000000 0000000 blank_issues_enabled: false
golang-honnef-go-tools-2024.1/.github/workflows/ 0000775 0000000 0000000 00000000000 14657040722 0021471 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/.github/workflows/ci.yml 0000664 0000000 0000000 00000003361 14657040722 0022612 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.22.x"]
godebug: ["gotypesalias=0", "gotypesalias=1"]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v1
with:
fetch-depth: 1
- uses: WillAbides/setup-go-faster@v1.14.0
with:
go-version: ${{ matrix.go }}
- run: "go test ./..."
env:
GODEBUG: ${{ matrix.godebug }}
- run: "go vet ./..."
- uses: dominikh/staticcheck-action@v1
with:
version: "2023.1.7"
min-go-version: "module"
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.14.0
with:
go-version: "stable"
# 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 newlines 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@v1
with:
install-go: false
merge-files: ${{ steps.glob.outputs.files }}
golang-honnef-go-tools-2024.1/.gitignore 0000664 0000000 0000000 00000000721 14657040722 0020064 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
/website/data/checks.json
/website/content/docs/configuration/default_config/index.md
golang-honnef-go-tools-2024.1/.gitmodules 0000664 0000000 0000000 00000000147 14657040722 0020253 0 ustar 00root root 0000000 0000000 [submodule "website/themes/docsy"]
path = website/themes/docsy
url = https://github.com/google/docsy
golang-honnef-go-tools-2024.1/LICENSE 0000664 0000000 0000000 00000002042 14657040722 0017077 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-2024.1/LICENSE-THIRD-PARTY 0000664 0000000 0000000 00000014452 14657040722 0020654 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-2024.1/README.md 0000664 0000000 0000000 00000005621 14657040722 0017357 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](https://staticcheck.dev/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.dev/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.dev/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 |
|----------------------------------------------------|-------------------------------------------------------------------------|
| [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
Staticcheck can be compiled and run with the latest release of Go. It can analyze code targeting any version of Go upto
the latest release.
golang-honnef-go-tools-2024.1/_benchmarks/ 0000775 0000000 0000000 00000000000 14657040722 0020350 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/_benchmarks/bench.sh 0000775 0000000 0000000 00000002601 14657040722 0021765 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-2024.1/_benchmarks/silent-staticcheck.sh 0000775 0000000 0000000 00000000205 14657040722 0024465 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-2024.1/add-check.go 0000664 0000000 0000000 00000005550 14657040722 0020233 0 ustar 00root root 0000000 0000000 //go:build ignore
package main
import (
"bytes"
"go/format"
"log"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"text/template"
)
var tmpl = `
package {{.lname}}
import (
"honnef.co/go/tools/analysis/lint"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "{{.name}}",
Run: run,
Requires: []*analysis.Analyzer{},
},
Doc: &lint.RawDocumentation{
Title: "",
Text: {{.emptyRaw}},
{{- if .quickfix }}
Before: {{.emptyRaw}},
After: {{.emptyRaw}},
{{- end }}
Since: "Unreleased",
Severity: lint.SeverityWarning,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
return nil, nil
}
`
func main() {
log.SetFlags(0)
var t template.Template
if _, err := t.Parse(tmpl); err != nil {
log.Fatalln("couldn't parse template:", err)
}
if len(os.Args) != 2 {
log.Fatalf("Usage: %s ", os.Args[0])
}
name := os.Args[1]
checkRe := regexp.MustCompile(`^([A-Za-z]+)\d{4}$`)
parts := checkRe.FindStringSubmatch(name)
if parts == nil {
log.Fatalf("invalid check name %q", name)
}
var catDir string
prefix := strings.ToUpper(parts[1])
switch prefix {
case "SA":
catDir = "staticcheck"
case "S":
catDir = "simple"
case "ST":
catDir = "stylecheck"
case "QF":
catDir = "quickfix"
default:
log.Fatalf("unknown check prefix %q", prefix)
}
lname := strings.ToLower(name)
dir := filepath.Join(catDir, lname)
dst := filepath.Join(dir, lname+".go")
mkdirp(dir)
buf := bytes.NewBuffer(nil)
vars := map[string]any{
"name": name,
"lname": lname,
"emptyRaw": "``",
"quickfix": prefix == "QF",
}
if err := t.Execute(buf, vars); err != nil {
log.Fatalf("couldn't generate %s: %s", dst, err)
}
b, err := format.Source(buf.Bytes())
if err != nil {
log.Fatalf("couldn't gofmt %s: %s", dst, err)
}
writeFile(dst, b)
testdata := filepath.Join(dir, "testdata", "src", "example.com", "pkg")
mkdirp(testdata)
writeFile(filepath.Join(testdata, "pkg.go"), []byte("package pkg\n"))
out, err := exec.Command("go", "generate", "./...").CombinedOutput()
if err != nil {
log.Printf("could not run 'go generate ./...': %s", err)
log.Println("Output:")
log.Fatalln(string(out))
}
flags := []string{
"add",
"--intent-to-add",
"--verbose",
filepath.Join(dir, lname+"_test.go"),
filepath.Join(testdata, "pkg.go"),
dst,
}
cmd := exec.Command("git", flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
log.Fatalln("could not run 'git add':", err)
}
}
func writeFile(path string, data []byte) {
if err := os.WriteFile(path, data, 0677); err != nil {
log.Fatalf("couldn't write %s: %s", path, err)
}
}
func mkdirp(path string) {
if err := os.MkdirAll(path, 0777); err != nil {
log.Fatalf("couldn't create directory %s: %s", path, err)
}
}
golang-honnef-go-tools-2024.1/analysis/ 0000775 0000000 0000000 00000000000 14657040722 0017717 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/analysis/callcheck/ 0000775 0000000 0000000 00000000000 14657040722 0021630 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/analysis/callcheck/callcheck.go 0000664 0000000 0000000 00000007142 14657040722 0024074 0 ustar 00root root 0000000 0000000 // Package callcheck provides a framework for validating arguments in function calls.
package callcheck
import (
"fmt"
"go/ast"
"go/constant"
"go/types"
"golang.org/x/tools/go/analysis"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
"honnef.co/go/tools/go/types/typeutil"
"honnef.co/go/tools/internal/passes/buildir"
)
type Call struct {
Pass *analysis.Pass
Instr ir.CallInstruction
Args []*Argument
Parent *ir.Function
invalids []string
}
func (c *Call) Invalid(msg string) {
c.invalids = append(c.invalids, msg)
}
type Argument struct {
Value Value
invalids []string
}
type Value struct {
Value ir.Value
}
func (arg *Argument) Invalid(msg string) {
arg.invalids = append(arg.invalids, msg)
}
type Check func(call *Call)
func Analyzer(rules map[string]Check) func(pass *analysis.Pass) (interface{}, error) {
return func(pass *analysis.Pass) (interface{}, error) {
return checkCalls(pass, rules)
}
}
func checkCalls(pass *analysis.Pass, rules map[string]Check) (interface{}, error) {
cb := func(caller *ir.Function, site ir.CallInstruction, callee *ir.Function) {
obj, ok := callee.Object().(*types.Func)
if !ok {
return
}
r, ok := rules[typeutil.FuncName(obj)]
if !ok {
return
}
var args []*Argument
irargs := site.Common().Args
if callee.Signature.Recv() != nil {
irargs = irargs[1:]
}
for _, arg := range irargs {
if iarg, ok := arg.(*ir.MakeInterface); ok {
arg = iarg.X
}
args = append(args, &Argument{Value: Value{arg}})
}
call := &Call{
Pass: pass,
Instr: site,
Args: args,
Parent: site.Parent(),
}
r(call)
var astcall *ast.CallExpr
switch source := site.Source().(type) {
case *ast.CallExpr:
astcall = source
case *ast.DeferStmt:
astcall = source.Call
case *ast.GoStmt:
astcall = source.Call
case nil:
// TODO(dh): I am not sure this can actually happen. If it
// can't, we should remove this case, and also stop
// checking for astcall == nil in the code that follows.
default:
panic(fmt.Sprintf("unhandled case %T", source))
}
for idx, arg := range call.Args {
for _, e := range arg.invalids {
if astcall != nil {
if idx < len(astcall.Args) {
report.Report(pass, astcall.Args[idx], e)
} else {
// this is an instance of fn1(fn2()) where fn2
// returns multiple values. Report the error
// at the next-best position that we have, the
// first argument. An example of a check that
// triggers this is checkEncodingBinaryRules.
report.Report(pass, astcall.Args[0], e)
}
} else {
report.Report(pass, site, e)
}
}
}
for _, e := range call.invalids {
report.Report(pass, call.Instr, e)
}
}
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
eachCall(fn, cb)
}
return nil, nil
}
func eachCall(fn *ir.Function, cb func(caller *ir.Function, site ir.CallInstruction, callee *ir.Function)) {
for _, b := range fn.Blocks {
for _, instr := range b.Instrs {
if site, ok := instr.(ir.CallInstruction); ok {
if g := site.Common().StaticCallee(); g != nil {
cb(fn, site, g)
}
}
}
}
}
func ExtractConstExpectKind(v Value, kind constant.Kind) *ir.Const {
k := extractConst(v.Value)
if k == nil || k.Value == nil || k.Value.Kind() != kind {
return nil
}
return k
}
func ExtractConst(v Value) *ir.Const {
return extractConst(v.Value)
}
func extractConst(v ir.Value) *ir.Const {
v = irutil.Flatten(v)
switch v := v.(type) {
case *ir.Const:
return v
case *ir.MakeInterface:
return extractConst(v.X)
default:
return nil
}
}
golang-honnef-go-tools-2024.1/analysis/code/ 0000775 0000000 0000000 00000000000 14657040722 0020631 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/analysis/code/code.go 0000664 0000000 0000000 00000046122 14657040722 0022077 0 ustar 00root root 0000000 0000000 // Package code answers structural and type questions about Go code.
package code
import (
"fmt"
"go/ast"
"go/build/constraint"
"go/constant"
"go/token"
"go/types"
"go/version"
"path/filepath"
"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/knowledge"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
type Positioner interface {
Pos() token.Pos
}
func IsOfStringConvertibleByteSlice(pass *analysis.Pass, expr ast.Expr) bool {
typ, ok := pass.TypesInfo.TypeOf(expr).Underlying().(*types.Slice)
if !ok {
return false
}
elem := types.Unalias(typ.Elem())
if version.Compare(LanguageVersion(pass, expr), "go1.18") >= 0 {
// Before Go 1.18, one could not directly convert from []T (where 'type T byte')
// to string. See also https://github.com/golang/go/issues/23536.
elem = elem.Underlying()
}
return types.Identical(elem, types.Typ[types.Byte])
}
func IsOfPointerToTypeWithName(pass *analysis.Pass, expr ast.Expr, name string) bool {
ptr, ok := types.Unalias(pass.TypesInfo.TypeOf(expr)).(*types.Pointer)
if !ok {
return false
}
return typeutil.IsTypeWithName(ptr.Elem(), name)
}
func IsOfTypeWithName(pass *analysis.Pass, expr ast.Expr, name string) bool {
return typeutil.IsTypeWithName(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. We err on the side of false negatives and
// treat aliases like other custom types.
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 {
// See the comment in typeutil.FuncName for why this doesn't require special handling
// of aliases.
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 {
// See the comment in typeutil.FuncName for why this doesn't require special handling
// of aliases.
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 {
// See the comment in typeutil.FuncName for why this doesn't require special handling
// of aliases.
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())]
}
// BuildConstraints returns the build constraints for file f. It considers both //go:build lines as well as
// GOOS and GOARCH in file names.
func BuildConstraints(pass *analysis.Pass, f *ast.File) (constraint.Expr, bool) {
var expr constraint.Expr
for _, cmt := range f.Comments {
if len(cmt.List) == 0 {
continue
}
for _, el := range cmt.List {
if el.Pos() > f.Package {
break
}
if line := el.Text; strings.HasPrefix(line, "//go:build") {
var err error
expr, err = constraint.Parse(line)
if err != nil {
expr = nil
}
break
}
}
}
name := pass.Fset.PositionFor(f.Pos(), false).Filename
oexpr := constraintsFromName(name)
if oexpr != nil {
if expr == nil {
expr = oexpr
} else {
expr = &constraint.AndExpr{X: expr, Y: oexpr}
}
}
return expr, expr != nil
}
func constraintsFromName(name string) constraint.Expr {
name = filepath.Base(name)
name = strings.TrimSuffix(name, ".go")
name = strings.TrimSuffix(name, "_test")
var goos, goarch string
switch strings.Count(name, "_") {
case 0:
// No GOOS or GOARCH in the file name.
case 1:
_, c, _ := strings.Cut(name, "_")
if _, ok := knowledge.KnownGOOS[c]; ok {
goos = c
} else if _, ok := knowledge.KnownGOARCH[c]; ok {
goarch = c
}
default:
n := strings.LastIndex(name, "_")
if _, ok := knowledge.KnownGOOS[name[n+1:]]; ok {
// The file name is *_stuff_GOOS.go
goos = name[n+1:]
} else if _, ok := knowledge.KnownGOARCH[name[n+1:]]; ok {
// The file name is *_GOOS_GOARCH.go or *_stuff_GOARCH.go
goarch = name[n+1:]
_, c, _ := strings.Cut(name[:n], "_")
if _, ok := knowledge.KnownGOOS[c]; ok {
// The file name is *_GOOS_GOARCH.go
goos = c
}
} else {
// The file name could also be something like foo_windows_nonsense.go — and because nonsense
// isn't a known GOARCH, "windows" won't be interpreted as a GOOS, either.
}
}
var expr constraint.Expr
if goos != "" {
expr = &constraint.TagExpr{Tag: goos}
}
if goarch != "" {
if expr == nil {
expr = &constraint.TagExpr{Tag: goarch}
} else {
expr = &constraint.AndExpr{X: expr, Y: &constraint.TagExpr{Tag: goarch}}
}
}
return expr
}
// 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))
}
}
// LanguageVersion returns the version of the Go language that node has access to. This
// might differ from the version of the Go standard library.
func LanguageVersion(pass *analysis.Pass, node Positioner) string {
// As of Go 1.21, two places can specify the minimum Go version:
// - 'go' directives in go.mod and go.work files
// - individual files by using '//go:build'
//
// Individual files can upgrade to a higher version than the module version. Individual files
// can also downgrade to a lower version, but only if the module version is at least Go 1.21.
//
// The restriction on downgrading doesn't matter to us. All language changes before Go 1.22 will
// not type-check on versions that are too old, and thus never reach our analyzes. In practice,
// such ineffective downgrading will always be useless, as the compiler will not restrict the
// language features used, and doesn't ever rely on minimum versions to restrict the use of the
// standard library. However, for us, both choices (respecting or ignoring ineffective
// downgrading) have equal complexity, but only respecting it has a non-zero chance of reducing
// noisy positives.
//
// The minimum Go versions are exposed via go/ast.File.GoVersion and go/types.Package.GoVersion.
// ast.File's version is populated by the parser, whereas types.Package's version is populated
// from the Go version specified in the types.Config, which is set by our package loader, based
// on the module information provided by go/packages, via 'go list -json'.
//
// As of Go 1.21, standard library packages do not present themselves as modules, and thus do
// not have a version set on their types.Package. In this case, we fall back to the version
// provided by our '-go' flag. In most cases, '-go' defaults to 'module', which falls back to
// the Go version that Staticcheck was built with when no module information exists. In the
// future, the standard library will hopefully be a proper module (see
// https://github.com/golang/go/issues/61174#issuecomment-1622471317). In that case, the version
// of standard library packages will match that of the used Go version. At that point,
// Staticcheck will refuse to work with Go versions that are too new, to avoid misinterpreting
// code due to language changes.
//
// We also lack module information when building in GOPATH mode. In this case, the implied
// language version is at most Go 1.21, as per https://github.com/golang/go/issues/60915. We
// don't handle this yet, and it will not matter until Go 1.22.
//
// It is not clear how per-file downgrading behaves in GOPATH mode. On the one hand, no module
// version at all is provided, which should preclude per-file downgrading. On the other hand,
// https://github.com/golang/go/issues/60915 suggests that the language version is at most 1.21
// in GOPATH mode, which would allow per-file downgrading. Again it doesn't affect us, as all
// relevant language changes before Go 1.22 will lead to type-checking failures and never reach
// us.
//
// Per-file upgrading is permitted in GOPATH mode.
// If the file has its own Go version, we will return that. Otherwise, we default to
// the type checker's GoVersion, which is populated from either the Go module, or from
// our '-go' flag.
return pass.TypesInfo.FileVersions[File(pass, node)]
}
// StdlibVersion returns the version of the Go standard library that node can expect to
// have access to. This might differ from the language version for versions of Go older
// than 1.21.
func StdlibVersion(pass *analysis.Pass, node Positioner) string {
// The Go version as specified in go.mod or via the '-go' flag
n := pass.Pkg.GoVersion()
f := File(pass, node)
if f == nil {
panic(fmt.Sprintf("no file found for node with position %s", pass.Fset.PositionFor(node.Pos(), false)))
}
if nf := f.GoVersion; nf != "" {
if version.Compare(n, "go1.21") == -1 {
// Before Go 1.21, the Go version set in go.mod specified the maximum language
// version available to the module. It wasn't uncommon to set the version to
// Go 1.20 but restrict usage of 1.20 functionality (both language and stdlib)
// to files tagged for 1.20, and supporting a lower version overall. As such,
// a file tagged lower than the module version couldn't expect to have access
// to the standard library of the version set in go.mod.
//
// At the same time, a file tagged higher than the module version, while not
// able to use newer language features, would still have been able to use a
// newer standard library.
//
// While Go 1.21's behavior has been backported to 1.19.11 and 1.20.6, users'
// expectations have not.
return nf
} else {
// Go 1.21 and newer refuse to build modules that depend on versions newer
// than the used version of the Go toolchain. This means that in a 1.22 module
// with a file tagged as 1.17, the file can expect to have access to 1.22's
// standard library (but not to 1.22 language features). A file tagged with a
// version higher than the minimum version has access to the newer standard
// library (and language features.)
//
// Do note that strictly speaking we're conflating the Go version and the
// module version in our check. Nothing is stopping a user from using Go 1.17
// (which didn't implement the new rules for versions in go.mod) to build a Go
// 1.22 module, in which case a file tagged with go1.17 will not have acces to the 1.22
// standard library. However, we believe that if a module requires 1.21 or
// newer, then the author clearly expects the new behavior, and doesn't care
// for the old one. Otherwise they would've specified an older version.
//
// In other words, the module version also specifies what it itself actually means, with
// >=1.21 being a minimum version for the toolchain, and <1.21 being a maximum version for
// the language.
if version.Compare(nf, n) == 1 {
return nf
}
}
}
return n
}
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)
}
func RefersTo(pass *analysis.Pass, expr ast.Expr, ident types.Object) bool {
found := false
fn := func(node ast.Node) bool {
ident2, ok := node.(*ast.Ident)
if !ok {
return true
}
if ident == pass.TypesInfo.ObjectOf(ident2) {
found = true
return false
}
return true
}
ast.Inspect(expr, fn)
return found
}
golang-honnef-go-tools-2024.1/analysis/code/code_test.go 0000664 0000000 0000000 00000002006 14657040722 0023127 0 ustar 00root root 0000000 0000000 package code
import "testing"
var constraintsFromNameTests = []struct {
in string
out string
}{
{"foo.go", ""},
{"foo_windows.go", "windows"},
{"foo_unix.go", ""},
{"foo_windows_amd64.go", "windows && amd64"},
{"foo_amd64.go", "amd64"},
{"foo_windows_nonsense.go", ""},
{"foo_nonsense_amd64.go", "amd64"},
{"foo_nonsense_windows.go", "windows"},
{"foo_nonsense_windows_amd64.go", "amd64"},
{"foo_windows_test.go", "windows"},
{"linux.go", ""},
{"linux_amd64.go", "amd64"},
{"amd64_linux.go", "linux"},
{"amd64.go", ""},
}
func TestConstraintsFromName(t *testing.T) {
for _, tc := range constraintsFromNameTests {
expr := constraintsFromName(tc.in)
var out string
if expr != nil {
out = expr.String()
}
if out != tc.out {
t.Errorf("constraintsFromName(%q) == %q, expected %q", tc.in, out, tc.out)
}
}
}
func FuzzConstraintsFromName(f *testing.F) {
for _, tc := range constraintsFromNameTests {
f.Add(tc.in)
}
f.Fuzz(func(t *testing.T, name string) {
constraintsFromName(name)
})
}
golang-honnef-go-tools-2024.1/analysis/code/visit.go 0000664 0000000 0000000 00000002577 14657040722 0022331 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-2024.1/analysis/dfa/ 0000775 0000000 0000000 00000000000 14657040722 0020451 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/analysis/dfa/dfa.el 0000664 0000000 0000000 00000001763 14657040722 0021534 0 ustar 00root root 0000000 0000000 (require 'cl-lib)
(defun format-state (prefix state ⊤ ⊥)
(cond ((string= state "⊥") ⊥)
((string= state "⊤") ⊤)
(t (format "%s%s" prefix state))))
(defun dh/orgtbl-to-dfa-binary-table (table params)
(let* ((table (--filter (not (equal 'hline it)) table))
(rows (1- (length table)))
(cols (1- (length (nth 0 table))))
(prefix (plist-get params :prefix))
(var (plist-get params :var))
(⊤ (plist-get params :⊤))
(⊥ (plist-get params :⊥)))
(concat
(if var (concat "var " var " = ") "")
(format
"dfa.BinaryTable(%s, map[[2]%s]%s{\n"
⊤ prefix prefix)
(mapconcat
(lambda (rowIdx)
(mapconcat
(lambda (colIdx)
(let* ((x (nth 0 (nth rowIdx table)))
(y (nth colIdx (nth 0 table)))
(z (nth colIdx (nth rowIdx table))))
(format "{%s, %s}: %s," (format-state prefix x ⊤ ⊥) (format-state prefix y ⊤ ⊥) (format-state prefix z ⊤ ⊥))))
(number-sequence 1 cols)
"\n"))
(number-sequence 1 rows)
"\n\n")
"\n})")))
golang-honnef-go-tools-2024.1/analysis/dfa/dfa.go 0000664 0000000 0000000 00000024654 14657040722 0021545 0 ustar 00root root 0000000 0000000 // Package dfa provides types and functions for implementing data-flow analyses.
package dfa
import (
"cmp"
"fmt"
"log"
"math/bits"
"slices"
"strings"
"sync"
"golang.org/x/exp/constraints"
"honnef.co/go/tools/go/ir"
)
const debugging = false
func debugf(f string, args ...any) {
if debugging {
log.Printf(f, args...)
}
}
// Join defines the [∨] operation for a [join-semilattice]. It must implement a commutative and associative binary operation
// that returns the least upper bound of two states from S.
//
// Code that calls Join functions is expected to handle the [⊥ and ⊤ elements], as well as implement idempotency. That is,
// the following properties will be enforced:
//
// - x ∨ ⊥ = x
// - x ∨ ⊤ = ⊤
// - x ∨ x = x
//
// Simple table-based join functions can be created using [JoinTable].
//
// [∨]: https://en.wikipedia.org/wiki/Join_and_meet
// [join-semilattice]: https://en.wikipedia.org/wiki/Semilattice
// [⊥ and ⊤ elements]: https://en.wikipedia.org/wiki/Greatest_element_and_least_element#Top_and_bottom
type Join[S comparable] func(S, S) S
// Mapping maps a single [ir.Value] to an abstract state.
type Mapping[S comparable] struct {
Value ir.Value
State S
Decision Decision
}
// Decision describes how a mapping from an [ir.Value] to an abstract state came to be.
// Decisions are provided by transfer functions when they create mappings.
type Decision struct {
// The relevant values that the transfer function used to make the decision.
Inputs []ir.Value
// A human-readable description of the decision.
Description string
// Whether this is the source of an abstract state. For example, in a taint analysis, the call to a function that
// produces a tainted value would be the source of the taint state, and any instructions that operate on
// and propagate tainted values would not be sources.
Source bool
}
func (m Mapping[S]) String() string {
return fmt.Sprintf("%s = %v", m.Value.Name(), m.State)
}
// M is a helper for constructing instances of [Mapping].
func M[S comparable](v ir.Value, s S, d Decision) Mapping[S] {
return Mapping[S]{Value: v, State: s, Decision: d}
}
// Ms is a helper for constructing slices of mappings.
//
// Example:
//
// Ms(M(v1, d1, ...), M(v2, d2, ...))
func Ms[S comparable](ms ...Mapping[S]) []Mapping[S] {
return ms
}
// Framework describes a monotone data-flow framework ⟨S, ∨, Transfer⟩ using a bounded join-semilattice ⟨S, ∨⟩ and a
// monotonic transfer function.
//
// Transfer implements the transfer function. Given an instruction, it should return zero or more mappings from IR
// values to abstract values, i.e. values from the semilattice. Transfer must be monotonic. ϕ instructions are handled
// automatically and do not cause Transfer to be called.
//
// The set S is defined implicitly by the values returned by Join and Transfer and needn't be finite. In addition, it
// contains the elements ⊥ and ⊤ (Bottom and Top) with Join(x, ⊥) = x and Join(x, ⊤) = ⊤. The provided Join function is
// wrapped to handle these elements automatically. All IR values start in the ⊥ state.
//
// Abstract states are associated with IR values. As such, the analysis is sparse and favours the partitioned variable
// lattice (PVL) property.
type Framework[S comparable] struct {
Join Join[S]
Transfer func(*Instance[S], ir.Instruction) []Mapping[S]
Bottom S
Top S
}
// Start returns a new instance of the framework. See also [Framework.Forward].
func (fw *Framework[S]) Start() *Instance[S] {
if fw.Bottom == fw.Top {
panic("framework's ⊥ and ⊤ are identical; did you forget to specify them?")
}
return &Instance[S]{
Framework: fw,
Mapping: map[ir.Value]Mapping[S]{},
}
}
// Forward runs an intraprocedural forward data flow analysis, using an iterative fixed-point algorithm, given the
// functions specified in the framework. It combines [Framework.Start] and [Instance.Forward].
func (fw *Framework[S]) Forward(fn *ir.Function) *Instance[S] {
ins := fw.Start()
ins.Forward(fn)
return ins
}
// Dot returns a directed graph in [Graphviz] format that represents the finite join-semilattice ⟨S, ≤⟩.
// Vertices represent elements in S and edges represent the ≤ relation between elements.
// We map from ⟨S, ∨⟩ to ⟨S, ≤⟩ by computing x ∨ y for all elements in [S]², where x ≤ y iff x ∨ y == y.
//
// The resulting graph can be filtered through [tred] to compute the transitive reduction of the graph, the
// visualisation of which corresponds to the Hasse diagram of the semilattice.
//
// The set of states should not include the ⊥ and ⊤ elements.
//
// [Graphviz]: https://graphviz.org/
// [tred]: https://graphviz.org/docs/cli/tred/
func Dot[S comparable](fn Join[S], states []S, bottom, top S) string {
var sb strings.Builder
sb.WriteString("digraph{\n")
sb.WriteString("rankdir=\"BT\"\n")
for i, v := range states {
if vs, ok := any(v).(fmt.Stringer); ok {
fmt.Fprintf(&sb, "n%d [label=%q]\n", i, vs)
} else {
fmt.Fprintf(&sb, "n%d [label=%q]\n", i, fmt.Sprintf("%v", v))
}
}
for dx, x := range states {
for dy, y := range states {
if dx == dy {
continue
}
if join(fn, x, y, bottom, top) == y {
fmt.Fprintf(&sb, "n%d -> n%d\n", dx, dy)
}
}
}
sb.WriteString("}")
return sb.String()
}
// Instance is an instance of a data-flow analysis. It is created by [Framework.Forward].
type Instance[S comparable] struct {
Framework *Framework[S]
// Mapping is the result of the analysis. Consider using Instance.Value instead of accessing Mapping
// directly, as it correctly returns ⊥ for missing values.
Mapping map[ir.Value]Mapping[S]
}
// Set maps v to the abstract value d. It does not apply any checks. This should only be used before calling [Instance.Forward], to set
// initial states of values.
func (ins *Instance[S]) Set(v ir.Value, d S) {
ins.Mapping[v] = Mapping[S]{Value: v, State: d}
}
// Value returns the abstract value for v. If none was set, it returns ⊥.
func (ins *Instance[S]) Value(v ir.Value) S {
m, ok := ins.Mapping[v]
if ok {
return m.State
} else {
return ins.Framework.Bottom
}
}
// Decision returns the decision of the mapping for v, if any.
func (ins *Instance[S]) Decision(v ir.Value) Decision {
return ins.Mapping[v].Decision
}
var dfsDebugMu sync.Mutex
func join[S comparable](fn Join[S], a, b, bottom, top S) S {
switch {
case a == top || b == top:
return top
case a == bottom:
return b
case b == bottom:
return a
case a == b:
return a
default:
return fn(a, b)
}
}
// Forward runs a forward data-flow analysis on fn.
func (ins *Instance[S]) Forward(fn *ir.Function) {
if debugging {
dfsDebugMu.Lock()
defer dfsDebugMu.Unlock()
}
debugf("Analyzing %s\n", fn)
if ins.Mapping == nil {
ins.Mapping = map[ir.Value]Mapping[S]{}
}
worklist := map[ir.Instruction]struct{}{}
for _, b := range fn.Blocks {
for _, instr := range b.Instrs {
worklist[instr] = struct{}{}
}
}
for len(worklist) > 0 {
var instr ir.Instruction
for instr = range worklist {
break
}
delete(worklist, instr)
var ds []Mapping[S]
if phi, ok := instr.(*ir.Phi); ok {
d := ins.Framework.Bottom
for _, edge := range phi.Edges {
a, b := d, ins.Value(edge)
d = join(ins.Framework.Join, a, b, ins.Framework.Bottom, ins.Framework.Top)
debugf("join(%v, %v) = %v", a, b, d)
}
ds = []Mapping[S]{{Value: phi, State: d, Decision: Decision{Inputs: phi.Edges, Description: "this variable merges the results of multiple branches"}}}
} else {
ds = ins.Framework.Transfer(ins, instr)
}
if len(ds) > 0 {
if v, ok := instr.(ir.Value); ok {
debugf("transfer(%s = %s) = %v", v.Name(), instr, ds)
} else {
debugf("transfer(%s) = %v", instr, ds)
}
}
for i, d := range ds {
old := ins.Value(d.Value)
dd := d.State
if dd != old {
if j := join(ins.Framework.Join, old, dd, ins.Framework.Bottom, ins.Framework.Top); j != dd {
panic(fmt.Sprintf("transfer function isn't monotonic; Transfer(%v)[%d] = %v; join(%v, %v) = %v", instr, i, dd, old, dd, j))
}
ins.Mapping[d.Value] = Mapping[S]{Value: d.Value, State: dd, Decision: d.Decision}
for _, ref := range *instr.Referrers() {
worklist[ref] = struct{}{}
}
}
}
printMapping(fn, ins.Mapping)
}
}
// Propagate is a helper for creating a [Mapping] that propagates the abstract state of src to dst.
// The desc parameter is used as the value of Decision.Description.
func (ins *Instance[S]) Propagate(dst, src ir.Value, desc string) Mapping[S] {
return M(dst, ins.Value(src), Decision{Inputs: []ir.Value{src}, Description: desc})
}
func (ins *Instance[S]) Transform(dst ir.Value, s S, src ir.Value, desc string) Mapping[S] {
return M(dst, s, Decision{Inputs: []ir.Value{src}, Description: desc})
}
func printMapping[S any](fn *ir.Function, m map[ir.Value]S) {
if !debugging {
return
}
debugf("Mapping for %s:\n", fn)
var keys []ir.Value
for k := range m {
keys = append(keys, k)
}
slices.SortFunc(keys, func(a, b ir.Value) int {
return cmp.Compare(a.ID(), b.ID())
})
for _, k := range keys {
v := m[k]
debugf("\t%v\n", v)
}
}
// BinaryTable returns a binary operator based on the provided mapping.
// For missing pairs of values, the default value will be returned.
func BinaryTable[S comparable](default_ S, m map[[2]S]S) func(S, S) S {
return func(a, b S) S {
if d, ok := m[[2]S{a, b}]; ok {
return d
} else if d, ok := m[[2]S{b, a}]; ok {
return d
} else {
return default_
}
}
}
// JoinTable returns a [Join] function based on the provided mapping.
// For missing pairs of values, the default value will be returned.
func JoinTable[S comparable](top S, m map[[2]S]S) Join[S] {
return func(a, b S) S {
if d, ok := m[[2]S{a, b}]; ok {
return d
} else if d, ok := m[[2]S{b, a}]; ok {
return d
} else {
return top
}
}
}
func PowerSet[S constraints.Integer](all S) []S {
out := make([]S, all+1)
for i := range out {
out[i] = S(i)
}
return out
}
func MapSet[S constraints.Integer](set S, fn func(S) S) S {
bits := 64 - bits.LeadingZeros64(uint64(set))
var out S
for i := 0; i < bits; i++ {
if b := (set & (1 << i)); b != 0 {
out |= fn(b)
}
}
return out
}
func MapCartesianProduct[S constraints.Integer](x, y S, fn func(S, S) S) S {
bitsX := 64 - bits.LeadingZeros64(uint64(x))
bitsY := 64 - bits.LeadingZeros64(uint64(y))
var out S
for i := 0; i < bitsX; i++ {
for j := 0; j < bitsY; j++ {
bx := x & (1 << i)
by := y & (1 << j)
if bx != 0 && by != 0 {
out |= fn(bx, by)
}
}
}
return out
}
golang-honnef-go-tools-2024.1/analysis/edit/ 0000775 0000000 0000000 00000000000 14657040722 0020644 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/analysis/edit/edit.go 0000664 0000000 0000000 00000003772 14657040722 0022131 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-2024.1/analysis/facts/ 0000775 0000000 0000000 00000000000 14657040722 0021017 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/analysis/facts/deprecated/ 0000775 0000000 0000000 00000000000 14657040722 0023117 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/analysis/facts/deprecated/deprecated.go 0000664 0000000 0000000 00000006774 14657040722 0025564 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-2024.1/analysis/facts/deprecated/deprecated_test.go 0000664 0000000 0000000 00000000323 14657040722 0026603 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-2024.1/analysis/facts/deprecated/testdata/ 0000775 0000000 0000000 00000000000 14657040722 0024730 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/analysis/facts/deprecated/testdata/src/ 0000775 0000000 0000000 00000000000 14657040722 0025517 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/analysis/facts/deprecated/testdata/src/example.com/ 0000775 0000000 0000000 00000000000 14657040722 0027727 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/analysis/facts/deprecated/testdata/src/example.com/Deprecated/ 0000775 0000000 0000000 00000000000 14657040722 0031767 5 ustar 00root root 0000000 0000000 Deprecated.go 0000664 0000000 0000000 00000002317 14657040722 0034302 0 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/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-2024.1/analysis/facts/directives/ 0000775 0000000 0000000 00000000000 14657040722 0023160 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/analysis/facts/directives/directives.go 0000664 0000000 0000000 00000000720 14657040722 0025647 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-2024.1/analysis/facts/generated/ 0000775 0000000 0000000 00000000000 14657040722 0022755 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/analysis/facts/generated/generated.go 0000664 0000000 0000000 00000003557 14657040722 0025254 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-2024.1/analysis/facts/nilness/ 0000775 0000000 0000000 00000000000 14657040722 0022472 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/analysis/facts/nilness/nilness.go 0000664 0000000 0000000 00000014570 14657040722 0024503 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.MultiConvert:
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
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 {
// OPT(dh): couldn't we check the result type's pointer-likeness early, and skip
// processing the return value altogether?
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-2024.1/analysis/facts/nilness/nilness_test.go 0000664 0000000 0000000 00000000312 14657040722 0025527 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-2024.1/analysis/facts/nilness/testdata/ 0000775 0000000 0000000 00000000000 14657040722 0024303 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/analysis/facts/nilness/testdata/src/ 0000775 0000000 0000000 00000000000 14657040722 0025072 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/analysis/facts/nilness/testdata/src/example.com/ 0000775 0000000 0000000 00000000000 14657040722 0027302 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/analysis/facts/nilness/testdata/src/example.com/Nilness/ 0000775 0000000 0000000 00000000000 14657040722 0030715 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/analysis/facts/nilness/testdata/src/example.com/Nilness/Nilness.go 0000664 0000000 0000000 00000003130 14657040722 0032654 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 {
return T{0}
}
func fn28[T [8]int]() T {
return T{}
}
func fn29[T []int]() T { // want fn29:`never returns nil: \[never\]`
return T{}
}
Nilness_go118.go 0000664 0000000 0000000 00000002163 14657040722 0033521 0 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/analysis/facts/nilness/testdata/src/example.com/Nilness //go:build go1.18
package pkg
// Make sure we don't crash upon seeing a MultiConvert instruction.
func generic1[T []byte | string](s T) T {
switch v := any(s).(type) {
case string:
return T(v)
case []byte:
return T(v)
default:
return s
}
}
// Make sure we don't emit a fact for a function whose return type isn't pointer-like.
func generic2[T [4]byte | string](s T) T {
switch v := any(s).(type) {
case string:
return T([]byte(v))
case [4]byte:
return T(v[:])
default:
return s
}
}
// Make sure we detect that the return value cannot be nil. It is either a string, a
// non-nil slice we got passed, or a non-nil slice we allocate. Note that we don't
// understand that the switch's non-default branches are exhaustive over the type set and
// for the fact to be computed, we have to return something non-nil from the unreachable
// default branch.
func generic3[T []byte | string](s T) T { // want generic3:`never returns nil: \[never\]`
switch v := any(s).(type) {
case string:
return T(v)
case []byte:
if v == nil {
return T([]byte{})
} else {
return T(v)
}
default:
return T([]byte{})
}
}
Nilness_go17.go 0000664 0000000 0000000 00000001154 14657040722 0033436 0 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/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-2024.1/analysis/facts/purity/ 0000775 0000000 0000000 00000000000 14657040722 0022353 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/analysis/facts/purity/purity.go 0000664 0000000 0000000 00000015675 14657040722 0024254 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-2024.1/analysis/facts/purity/purity_test.go 0000664 0000000 0000000 00000000307 14657040722 0025275 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-2024.1/analysis/facts/purity/testdata/ 0000775 0000000 0000000 00000000000 14657040722 0024164 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/analysis/facts/purity/testdata/src/ 0000775 0000000 0000000 00000000000 14657040722 0024753 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/analysis/facts/purity/testdata/src/example.com/ 0000775 0000000 0000000 00000000000 14657040722 0027163 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/analysis/facts/purity/testdata/src/example.com/Purity/ 0000775 0000000 0000000 00000000000 14657040722 0030457 5 ustar 00root root 0000000 0000000 CheckPureFunctions.go 0000664 0000000 0000000 00000001273 14657040722 0034474 0 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/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-2024.1/analysis/facts/tokenfile/ 0000775 0000000 0000000 00000000000 14657040722 0022777 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/analysis/facts/tokenfile/token.go 0000664 0000000 0000000 00000000771 14657040722 0024453 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-2024.1/analysis/facts/typedness/ 0000775 0000000 0000000 00000000000 14657040722 0023035 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/analysis/facts/typedness/testdata/ 0000775 0000000 0000000 00000000000 14657040722 0024646 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/analysis/facts/typedness/testdata/src/ 0000775 0000000 0000000 00000000000 14657040722 0025435 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/analysis/facts/typedness/testdata/src/example.com/ 0000775 0000000 0000000 00000000000 14657040722 0027645 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/analysis/facts/typedness/testdata/src/example.com/Typedness/ 0000775 0000000 0000000 00000000000 14657040722 0031623 5 ustar 00root root 0000000 0000000 Typedness.go 0000664 0000000 0000000 00000010241 14657040722 0034047 0 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/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-2024.1/analysis/facts/typedness/typedness.go 0000664 0000000 0000000 00000015523 14657040722 0025410 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-2024.1/analysis/facts/typedness/typedness_test.go 0000664 0000000 0000000 00000000320 14657040722 0026434 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-2024.1/analysis/lint/ 0000775 0000000 0000000 00000000000 14657040722 0020665 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/analysis/lint/lint.go 0000664 0000000 0000000 00000012304 14657040722 0022162 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 (
"fmt"
"go/ast"
"go/token"
"strings"
"golang.org/x/tools/go/analysis"
"honnef.co/go/tools/analysis/facts/tokenfile"
)
// 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 *RawDocumentation
Analyzer *analysis.Analyzer
}
func InitializeAnalyzer(a *Analyzer) *Analyzer {
a.Analyzer.Doc = a.Doc.Compile().String()
a.Analyzer.URL = "https://staticcheck.dev/docs/checks/#" + a.Analyzer.Name
a.Analyzer.Requires = append(a.Analyzer.Requires, tokenfile.Analyzer)
return a
}
// 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 (doc RawDocumentation) Compile() *Documentation {
return &Documentation{
Title: strings.TrimSpace(stripMarkdown(doc.Title)),
Text: strings.TrimSpace(stripMarkdown(doc.Text)),
TitleMarkdown: strings.TrimSpace(toMarkdown(doc.Title)),
TextMarkdown: strings.TrimSpace(toMarkdown(doc.Text)),
Before: strings.TrimSpace(doc.Before),
After: strings.TrimSpace(doc.After),
Since: doc.Since,
NonDefault: doc.NonDefault,
Options: doc.Options,
Severity: doc.Severity,
MergeIf: doc.MergeIf,
}
}
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)
}
// 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-2024.1/analysis/lint/testutil/ 0000775 0000000 0000000 00000000000 14657040722 0022542 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/analysis/lint/testutil/check.go 0000664 0000000 0000000 00000025305 14657040722 0024153 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)
}
}
for _, vf := range ar.Files {
if _, ok := fixes[vf.Name]; !ok {
t.Errorf("%s.golden has section for suggested fix %q, but we didn't produce any fix by that name", file, vf.Name)
}
}
} 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-2024.1/analysis/lint/testutil/util.go 0000664 0000000 0000000 00000006223 14657040722 0024051 0 ustar 00root root 0000000 0000000 package testutil
import (
"crypto/sha256"
"go/build"
"go/version"
"io"
"os"
"path/filepath"
"strings"
"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 Run(t *testing.T, a *lint.Analyzer) {
dirs, err := filepath.Glob("testdata/*")
if err != nil {
t.Fatalf("couldn't enumerate test data: %s", err)
}
if len(dirs) == 0 {
t.Fatalf("found no tests")
}
c, err := cache.Open(t.TempDir())
if err != nil {
t.Fatal(err)
}
salt, err := computeSalt()
if err != nil {
t.Fatal(err)
}
c.SetSalt(salt)
tags := build.Default.ReleaseTags
maxVersion := tags[len(tags)-1]
for _, dir := range dirs {
vers := filepath.Base(dir)
t.Run(vers, func(t *testing.T) {
if !version.IsValid(vers) {
t.Fatalf("%q is not a valid Go version", dir)
}
if version.Compare(vers, maxVersion) == 1 {
t.Skipf("%s is newer than our Go version (%s), skipping", vers, maxVersion)
}
r, err := runner.New(config.Config{}, c)
if err != nil {
t.Fatal(err)
}
r.TestMode = true
testdata, err := filepath.Abs("testdata")
if err != nil {
t.Fatal(err)
}
cfg := &packages.Config{
Dir: dir,
Tests: true,
Env: append(os.Environ(), "GOPROXY=off", "GOFLAGS=-mod=vendor"),
Overlay: map[string][]byte{
"go.mod": []byte("module example.com\ngo " + strings.TrimPrefix(vers, "go")),
},
}
res, err := r.Run(cfg, []*analysis.Analyzer{a.Analyzer}, []string{"./..."})
if err != nil {
t.Fatal(err)
}
if len(res) == 0 {
t.Fatalf("got no results for %s/...", dir)
}
for _, r := range res {
if r.Failed {
if len(r.Errors) > 0 {
sb := strings.Builder{}
for _, err := range r.Errors {
sb.WriteString(err.Error())
sb.WriteString("\n")
}
t.Fatalf("failed checking %s:\n%s", r.Package.PkgPath, sb.String())
} else {
t.Fatalf("failed processing package %s, but got no errors", r.Package.PkgPath)
}
}
data, err := r.Load()
if err != nil {
t.Fatal(err)
}
tdata, err := r.LoadTest()
if err != nil {
t.Fatal(err)
}
relevantDiags := data.Diagnostics
var relevantFacts []runner.TestFact
for _, fact := range tdata.Facts {
if fact.Analyzer != a.Analyzer.Name {
continue
}
relevantFacts = append(relevantFacts, fact)
}
Check(t, testdata, tdata.Files, relevantDiags, relevantFacts)
CheckSuggestedFixes(t, relevantDiags)
}
})
}
}
golang-honnef-go-tools-2024.1/analysis/report/ 0000775 0000000 0000000 00000000000 14657040722 0021232 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/analysis/report/report.go 0000664 0000000 0000000 00000015016 14657040722 0023077 0 ustar 00root root 0000000 0000000 package report
import (
"bytes"
"fmt"
"go/ast"
"go/format"
"go/token"
"go/version"
"path/filepath"
"strconv"
"strings"
"honnef.co/go/tools/analysis/code"
"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
MinimumLanguageVersion string
MaximumLanguageVersion string
MinimumStdlibVersion string
MaximumStdlibVersion string
}
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)
}
}
func MinimumLanguageVersion(vers string) Option {
return func(opts *Options) { opts.MinimumLanguageVersion = vers }
}
func MaximumLanguageVersion(vers string) Option {
return func(opts *Options) { opts.MinimumLanguageVersion = vers }
}
func MinimumStdlibVersion(vers string) Option {
return func(opts *Options) { opts.MinimumStdlibVersion = vers }
}
func MaximumStdlibVersion(vers string) Option {
return func(opts *Options) { opts.MaximumStdlibVersion = vers }
}
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)
}
langVersion := code.LanguageVersion(pass, node)
stdlibVersion := code.StdlibVersion(pass, node)
if n := cfg.MaximumLanguageVersion; n != "" && version.Compare(n, langVersion) == -1 {
return
}
if n := cfg.MaximumStdlibVersion; n != "" && version.Compare(n, stdlibVersion) == -1 {
return
}
if n := cfg.MinimumLanguageVersion; n != "" && version.Compare(n, langVersion) == 1 {
return
}
if n := cfg.MinimumStdlibVersion; n != "" && version.Compare(n, stdlibVersion) == 1 {
return
}
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-2024.1/analysis/report/report_test.go 0000664 0000000 0000000 00000001240 14657040722 0024130 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-2024.1/cmd/ 0000775 0000000 0000000 00000000000 14657040722 0016637 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/cmd/staticcheck/ 0000775 0000000 0000000 00000000000 14657040722 0021124 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/cmd/staticcheck/README.md 0000664 0000000 0000000 00000000635 14657040722 0022407 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.dev](https://staticcheck.dev/docs/).
golang-honnef-go-tools-2024.1/cmd/staticcheck/staticcheck.go 0000664 0000000 0000000 00000002015 14657040722 0023736 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-2024.1/cmd/structlayout-optimize/ 0000775 0000000 0000000 00000000000 14657040722 0023257 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/cmd/structlayout-optimize/main.go 0000664 0000000 0000000 00000007547 14657040722 0024547 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-2024.1/cmd/structlayout-pretty/ 0000775 0000000 0000000 00000000000 14657040722 0022746 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/cmd/structlayout-pretty/main.go 0000664 0000000 0000000 00000003076 14657040722 0024227 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-2024.1/cmd/structlayout/ 0000775 0000000 0000000 00000000000 14657040722 0021421 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/cmd/structlayout/README.md 0000664 0000000 0000000 00000005427 14657040722 0022710 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-2024.1/cmd/structlayout/main.go 0000664 0000000 0000000 00000005610 14657040722 0022676 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, types.Unalias(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-2024.1/config/ 0000775 0000000 0000000 00000000000 14657040722 0017341 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/config/config.go 0000664 0000000 0000000 00000015063 14657040722 0021142 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-2024.1/config/example.conf 0000664 0000000 0000000 00000001171 14657040722 0021643 0 ustar 00root root 0000000 0000000 checks = ["all", "-SA9003", "-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-2024.1/debug/ 0000775 0000000 0000000 00000000000 14657040722 0017162 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/debug/debug.go 0000664 0000000 0000000 00000003415 14657040722 0020602 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"
"sync"
)
// 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()
}
var aliasesDefaultOnce sync.Once
var gotypesaliasDefault bool
func AliasesEnabled() bool {
// Dynamically check if Aliases will be produced from go/types.
aliasesDefaultOnce.Do(func() {
fset := token.NewFileSet()
f, _ := parser.ParseFile(fset, "a.go", "package p; type A = int", 0)
pkg, _ := new(types.Config).Check("p", fset, []*ast.File{f}, nil)
_, gotypesaliasDefault = pkg.Scope().Lookup("A").Type().(*types.Alias)
})
return gotypesaliasDefault
}
golang-honnef-go-tools-2024.1/dist/ 0000775 0000000 0000000 00000000000 14657040722 0017037 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/dist/build.sh 0000775 0000000 0000000 00000003033 14657040722 0020474 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-2024.1/doc/ 0000775 0000000 0000000 00000000000 14657040722 0016641 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/doc/articles/ 0000775 0000000 0000000 00000000000 14657040722 0020447 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/doc/articles/customizing_staticcheck.html 0000664 0000000 0000000 00000000313 14657040722 0026252 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-2024.1/doc/run.html 0000664 0000000 0000000 00000002733 14657040722 0020340 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.dev/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-2024.1/generate.go 0000664 0000000 0000000 00000004112 14657040722 0020213 0 ustar 00root root 0000000 0000000 //go:build ignore
package main
import (
"bytes"
"go/format"
"log"
"os"
"path/filepath"
"regexp"
"text/template"
)
var tmpl = `
{{define "analyzers"}}
// Code generated by generate.go. DO NOT EDIT.
package {{.dir}}
import (
"honnef.co/go/tools/analysis/lint"
{{- range $check := .checks}}
"honnef.co/go/tools/{{$.dir}}/{{$check}}"
{{- end}}
)
var Analyzers = []*lint.Analyzer{
{{- range $check := .checks}}
{{$check}}.SCAnalyzer,
{{- end}}
}
{{end}}
{{define "tests"}}
// Code generated by generate.go. DO NOT EDIT.
package {{.check}}
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
{{end}}
`
func main() {
log.SetFlags(0)
dir, err := os.Getwd()
if err != nil {
log.Fatalln("couldn't determine current directory:", err)
}
dir = filepath.Base(dir)
var t template.Template
if _, err = t.Parse(tmpl); err != nil {
log.Fatalln("couldn't parse templates:", err)
}
dirs, err := filepath.Glob("*")
if err != nil {
log.Fatalln("couldn't enumerate checks:", err)
}
checkRe := regexp.MustCompile(`^[a-z]+\d{4}$`)
out := dirs[:0]
for _, dir := range dirs {
if checkRe.MatchString(dir) {
out = append(out, dir)
}
}
dirs = out
buf := bytes.NewBuffer(nil)
if err := t.ExecuteTemplate(buf, "analyzers", map[string]any{"checks": dirs, "dir": dir}); err != nil {
log.Fatalln("couldn't generate analysis.go:", err)
}
b, err := format.Source(buf.Bytes())
if err != nil {
log.Fatalln("couldn't gofmt analysis.go:", err)
}
if err := os.WriteFile("analysis.go", b, 0666); err != nil {
log.Fatalln("couldn't write analysis.go:", err)
}
for _, dir := range dirs {
buf.Reset()
dst := filepath.Join(dir, dir+"_test.go")
if err := t.ExecuteTemplate(buf, "tests", map[string]any{"check": dir}); err != nil {
log.Fatalf("couldn't generate %s: %s", dst, err)
}
b, err := format.Source(buf.Bytes())
if err != nil {
log.Fatalf("couldn't gofmt %s: %s", dst, err)
}
if err := os.WriteFile(dst, b, 0666); err != nil {
log.Fatalf("couldn't write %s: %s", dst, err)
}
}
}
golang-honnef-go-tools-2024.1/go.mod 0000664 0000000 0000000 00000000625 14657040722 0017205 0 ustar 00root root 0000000 0000000 module honnef.co/go/tools
go 1.22.1
require (
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa
golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678
golang.org/x/sys v0.20.0
golang.org/x/tools v0.21.1-0.20240531212143-b6235391adb3
)
require (
golang.org/x/mod v0.17.0 // indirect
golang.org/x/sync v0.7.0 // indirect
)
golang-honnef-go-tools-2024.1/go.sum 0000664 0000000 0000000 00000002477 14657040722 0017241 0 ustar 00root root 0000000 0000000 github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs=
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 h1:1P7xPZEwZMoBoz0Yze5Nx2/4pxj6nw9ZqHWXqP0iRgQ=
golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/tools v0.21.1-0.20240531212143-b6235391adb3 h1:SHq4Rl+B7WvyM4XODon1LXtP7gcG49+7Jubt1gWWswY=
golang.org/x/tools v0.21.1-0.20240531212143-b6235391adb3/go.mod h1:bqv7PJ/TtlrzgJKhOAGdDUkUltQapRik/UEHubLVBWo=
golang-honnef-go-tools-2024.1/go/ 0000775 0000000 0000000 00000000000 14657040722 0016501 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/go/ast/ 0000775 0000000 0000000 00000000000 14657040722 0017270 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/go/ast/astutil/ 0000775 0000000 0000000 00000000000 14657040722 0020755 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/go/ast/astutil/upstream.go 0000664 0000000 0000000 00000000656 14657040722 0023153 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-2024.1/go/ast/astutil/util.go 0000664 0000000 0000000 00000024442 14657040722 0022267 0 ustar 00root root 0000000 0000000 package astutil
import (
"fmt"
"go/ast"
"go/token"
"reflect"
"strings"
"golang.org/x/tools/go/ast/astutil"
)
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))
}
}
func NegateDeMorgan(expr ast.Expr, recursive bool) ast.Expr {
switch expr := expr.(type) {
case *ast.BinaryExpr:
var out ast.BinaryExpr
switch expr.Op {
case token.EQL:
out.X = expr.X
out.Op = token.NEQ
out.Y = expr.Y
case token.LSS:
out.X = expr.X
out.Op = token.GEQ
out.Y = expr.Y
case token.GTR:
out.X = expr.X
out.Op = token.LEQ
out.Y = expr.Y
case token.NEQ:
out.X = expr.X
out.Op = token.EQL
out.Y = expr.Y
case token.LEQ:
out.X = expr.X
out.Op = token.GTR
out.Y = expr.Y
case token.GEQ:
out.X = expr.X
out.Op = token.LSS
out.Y = expr.Y
case token.LAND:
out.X = NegateDeMorgan(expr.X, recursive)
out.Op = token.LOR
out.Y = NegateDeMorgan(expr.Y, recursive)
case token.LOR:
out.X = NegateDeMorgan(expr.X, recursive)
out.Op = token.LAND
out.Y = NegateDeMorgan(expr.Y, recursive)
}
return &out
case *ast.ParenExpr:
if recursive {
return &ast.ParenExpr{
X: NegateDeMorgan(expr.X, recursive),
}
} else {
return &ast.UnaryExpr{
Op: token.NOT,
X: expr,
}
}
case *ast.UnaryExpr:
if expr.Op == token.NOT {
return expr.X
} else {
return &ast.UnaryExpr{
Op: token.NOT,
X: expr,
}
}
default:
return &ast.UnaryExpr{
Op: token.NOT,
X: expr,
}
}
}
func SimplifyParentheses(node ast.Expr) ast.Expr {
var changed bool
// XXX accept list of ops to operate on
// XXX copy AST node, don't modify in place
post := func(c *astutil.Cursor) bool {
out := c.Node()
if paren, ok := c.Node().(*ast.ParenExpr); ok {
out = paren.X
}
if binop, ok := out.(*ast.BinaryExpr); ok {
if right, ok := binop.Y.(*ast.BinaryExpr); ok && binop.Op == right.Op {
// XXX also check that Op is associative
root := binop
pivot := root.Y.(*ast.BinaryExpr)
root.Y = pivot.X
pivot.X = root
root = pivot
out = root
}
}
if out != c.Node() {
changed = true
c.Replace(out)
}
return true
}
for changed = true; changed; {
changed = false
node = astutil.Apply(node, nil, post).(ast.Expr)
}
return node
}
golang-honnef-go-tools-2024.1/go/buildid/ 0000775 0000000 0000000 00000000000 14657040722 0020115 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/go/buildid/UPSTREAM 0000664 0000000 0000000 00000000404 14657040722 0021276 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: e8ee1dc4f9e2632ba1018610d1a1187743ae397f
golang-honnef-go-tools-2024.1/go/buildid/buildid.go 0000664 0000000 0000000 00000013510 14657040722 0022060 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-2024.1/go/buildid/note.go 0000664 0000000 0000000 00000013421 14657040722 0021412 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-2024.1/go/gcsizes/ 0000775 0000000 0000000 00000000000 14657040722 0020150 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/go/gcsizes/LICENSE 0000664 0000000 0000000 00000002707 14657040722 0021163 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-2024.1/go/gcsizes/sizes.go 0000664 0000000 0000000 00000005330 14657040722 0021635 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-2024.1/go/ir/ 0000775 0000000 0000000 00000000000 14657040722 0017113 5 ustar 00root root 0000000 0000000 golang-honnef-go-tools-2024.1/go/ir/LICENSE 0000664 0000000 0000000 00000002777 14657040722 0020135 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-2024.1/go/ir/UPSTREAM 0000664 0000000 0000000 00000000713 14657040722 0020277 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:
ac2946029ad3806349fa00546449da9f59320e89
golang-honnef-go-tools-2024.1/go/ir/bench_test.go 0000664 0000000 0000000 00000001472 14657040722 0021564 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-2024.1/go/ir/blockopt.go 0000664 0000000 0000000 00000012027 14657040722 0021261 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-2024.1/go/ir/builder.go 0000664 0000000 0000000 00000300732 14657040722 0021075 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"
"go/version"
"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()
tDeferStack = types.NewPointer(typeutil.NewDeferStack())
vDeferStack = &Builtin{
name: "ssa:deferstack",
sig: types.NewSignatureType(nil, nil, nil, nil, types.NewTuple(anonVar(tDeferStack)), false),
}
)
// range-over-func jump is READY
func jReady() *Const {
c := intConst(0, nil)
c.comment = "rangefunc.exit.ready"
return c
}
// range-over-func jump is BUSY
func jBusy() *Const {
c := intConst(-1, nil)
c.comment = "rangefunc.exit.busy"
return c
}
// range-over-func jump is DONE
func jDone() *Const {
c := intConst(-2, nil)
c.comment = "rangefunc.exit.done"
return c
}
// 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, e))
case token.LOR:
b.cond(fn, e.X, done, rhs)
short = emitConst(fn, NewConst(constant.MakeBool(true), t, e))
}
// 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
phi.comment = e.Op.String()
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)
v := &Slice{
X: emitNew(fn, at, source, "makeslice"),
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, source))
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":
return emitNew(fn, deref(typ), source, "new")
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.
//
// Technically this shouldn't apply to type parameters because their length/capacity is never constant. We still
// choose to treat them as constant so that users of the IR get the practically constant length for free.
t := typeutil.CoreType(deref(fn.Pkg.typeOf(args[0])))
if at, ok := t.(*types.Array); ok {
b.expr(fn, args[0]) // for effects only
return emitConst(fn, intConst(at.Len(), args[0]))
}
// 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, nil)) // 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.(*types.Var), 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, "complit")
} else {
v = emitLocal(fn, t, e, "complit")
}
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 types.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, e))
}
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),
goversion: fn.goversion, // share the parent's goversion
}
fn2.uniq = fn.uniq // start from parent's unique values
fn2.source = e
fn.AnonFuncs = append(fn.AnonFuncs, fn2)
fn2.initHTML(b.printFunc)
b.buildFunction(fn2)
fn.uniq = fn2.uniq // resume after anon's unique values
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 (https://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, e))
}
// 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.(*types.Var), 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 types.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 types.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, nil)))
} else {
// Replace a suffix of args with a slice containing it.
at := types.NewArray(vt, int64(len(varargs)))
a := emitNew(fn, at, e, "varargs")
a.source = e
for i, arg := range varargs {
iaddr := &IndexAddr{
X: a,
Index: emitConst(fn, intConst(int64(i), nil)),
}
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) {
emitLocalVar(fn, identVar(fn, id), 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 := emitLocalVar(fn, identVar(fn, id), 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) {
emitLocalVar(fn, identVar(fn, id), 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, ok := fn.Pkg.info.Defs[lhs.(*ast.Ident)].(*types.Var); ok {
emitLocalVar(fn, 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(), e))
}
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))
array = emitNew(fn, at, e, "slicelit")
case *types.Array:
at = t
array = addr
}
var final Value
if len(e.Elts) == 0 {
if !isZero {
zc := emitConst(fn, zeroConst(at, e))
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(), e))
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, e)).(*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, e)).(*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)), e))}
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, nil))
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 := emitLocal(fn, tagv.Type(), tagSource, "switch.value")
tag.comment = "switch.tag"
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 := emitLocal(fn, 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, ok := fn.Pkg.info.Implicits[cc].(*types.Var); ok {
emitLocalVar(fn, 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), expr)))
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), nil)))
// 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, ok := fn.Pkg.info.Implicits[cc].(*types.Var); ok {
// 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.vars[obj]
if rets[index] == tUntypedNil {
emitStore(fn, l, emitConst(fn, nilConst(tswtch.Tag.Type(), nil)), 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, ok := fn.Pkg.info.Implicits[default_].(*types.Var); ok {
l := fn.vars[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, nil)))
continue
}
swtch.Conds = append(swtch.Conds, emitConst(fn, intConst(int64(state), nil)))
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 {
id := comm.Lhs[0].(*ast.Ident)
emitLocalVar(fn, identVar(fn, id), id)
}
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 {
id := comm.Lhs[1].(*ast.Ident)
emitLocalVar(fn, identVar(fn, id), id)
}
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) {
// Use forStmtGo122 instead if it applies.
if s.Init != nil {
if assign, ok := s.Init.(*ast.AssignStmt); ok && assign.Tok == token.DEFINE {
if version.Compare(fn.goversion, "go1.22") >= 0 {
b.forStmtGo122(fn, s, label)
return
}
}
}
// ...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
}
// forStmtGo122 emits to fn code for the for statement s, optionally
// labelled by label. s must define its variables.
//
// This allocates once per loop iteration. This is only correct in
// GoVersions >= go1.22.
func (b *builder) forStmtGo122(fn *Function, s *ast.ForStmt, label *lblock) {
// i_outer = alloc[T]
// *i_outer = ...init... // under objects[i] = i_outer
// jump loop
// loop:
// i = phi [head: i_outer, loop: i_next]
// ...cond... // under objects[i] = i
// if cond goto body else done
// body:
// ...body... // under objects[i] = i (same as loop)
// jump post
// post:
// tmp = *i
// i_next = alloc[T]
// *i_next = tmp
// ...post... // under objects[i] = i_next
// goto loop
// done:
init := s.Init.(*ast.AssignStmt)
startingBlocks := len(fn.Blocks)
pre := fn.currentBlock // current block before starting
loop := fn.newBasicBlock("for.loop") // target of back-edge
body := fn.newBasicBlock("for.body")
post := fn.newBasicBlock("for.post") // target of 'continue'
done := fn.newBasicBlock("for.done") // target of 'break'
// For each of the n loop variables, we create five SSA values,
// outer, phi, next, load, and store in pre, loop, and post.
// There is no limit on n.
type loopVar struct {
obj *types.Var
outer *Alloc
phi *Phi
load *Load
next *Alloc
store *Store
}
vars := make([]loopVar, len(init.Lhs))
for i, lhs := range init.Lhs {
v := identVar(fn, lhs.(*ast.Ident))
fn.currentBlock = pre
outer := emitLocal(fn, v.Type(), lhs, v.Name())
fn.currentBlock = loop
phi := &Phi{}
phi.comment = v.Name()
phi.typ = outer.Type()
fn.emit(phi, lhs)
fn.currentBlock = post
// If next is is local, it reuses the address and zeroes the old value so
// load before allocating next.
load := emitLoad(fn, phi, init)
next := emitLocal(fn, v.Type(), lhs, v.Name())
store := emitStore(fn, next, load, s)
phi.Edges = []Value{outer, next} // pre edge is emitted before post edge.
vars[i] = loopVar{v, outer, phi, load, next, store}
}
// ...init... under fn.objects[v] = i_outer
fn.currentBlock = pre
for _, v := range vars {
fn.vars[v.obj] = v.outer
}
const isDef = false // assign to already-allocated outers
b.assignStmt(fn, init.Lhs, init.Rhs, isDef, s)
if label != nil {
label._break = done
label._continue = post
}
emitJump(fn, loop, s)
// ...cond... under fn.objects[v] = i
fn.currentBlock = loop
for _, v := range vars {
fn.vars[v.obj] = v.phi
}
if s.Cond != nil {
b.cond(fn, s.Cond, body, done)
} else {
emitJump(fn, body, s)
}
// ...body... under fn.objects[v] = i
fn.currentBlock = body
fn.targets = &targets{
tail: fn.targets,
_break: done,
_continue: post,
}
b.stmt(fn, s.Body)
fn.targets = fn.targets.tail
emitJump(fn, post, s)
// ...post... under fn.objects[v] = i_next
for _, v := range vars {
fn.vars[v.obj] = v.next
}
fn.currentBlock = post
if s.Post != nil {
b.stmt(fn, s.Post)
}
emitJump(fn, loop, s) // back-edge
fn.currentBlock = done
// For each loop variable that does not escape,
// (the common case), fuse its next cells into its
// (local) outer cell as they have disjoint live ranges.
//
// It is sufficient to test whether i_next escapes,
// because its Heap flag will be marked true if either
// the cond or post expression causes i to escape
// (because escape distributes over phi).
var nlocals int
for _, v := range vars {
if !v.next.Heap {
nlocals++
}
}
if nlocals > 0 {
replace := make(map[Value]Value, 2*nlocals)
dead := make(map[Instruction]bool, 4*nlocals)
for _, v := range vars {
if !v.next.Heap {
replace[v.next] = v.outer
replace[v.phi] = v.outer
dead[v.phi], dead[v.next], dead[v.load], dead[v.store] = true, true, true, true
}
}
// Replace all uses of i_next and phi with i_outer.
// Referrers have not been built for fn yet so only update Instruction operands.
// We need only look within the blocks added by the loop.
var operands []*Value // recycle storage
for _, b := range fn.Blocks[startingBlocks:] {
for _, instr := range b.Instrs {
operands = instr.Operands(operands[:0])
for _, ptr := range operands {
k := *ptr
if v := replace[k]; v != nil {
*ptr = v
}
}
}
}
// Remove instructions for phi, load, and store.
// lift() will remove the unused i_next *Alloc.
isDead := func(i Instruction) bool { return dead[i] }
loop.Instrs = removeInstrsIf(loop.Instrs, isDead)
post.Instrs = removeInstrsIf(post.Instrs, isDead)
}
}
// 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 := typeutil.CoreType(deref(x.Type())).(*types.Array); ok {
// For array or *array, the number of iterations is known statically thanks to the type. We avoid a data
// dependence upon x, permitting later dead-code elimination if x is pure, static unrolling, etc. Ranging over a
// nil *array may have >0 iterations. We still generate code for x, in case it has effects.
//
// We use the core type of x, even though the length of type parameters isn't constant as per the language
// specification. Just because len(x) isn't constant doesn't mean we can't emit IR that takes advantage of a
// known length.
length.store(emitConst(fn, intConst(arr.Len(), nil)))
} 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 := emitLocal(fn, tInt, source, "rangeindex")
emitStore(fn, index, emitConst(fn, intConst(-1, nil)), 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, nil)),
}
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
}
// rangeInt emits to fn the header for a range loop with an integer operand.
// tk is the key value's type, or nil if the k result is not wanted.
// pos is the position of the "for" token.
func (b *builder) rangeInt(fn *Function, x Value, tk types.Type, source ast.Node) (k Value, loop, done *BasicBlock) {
//
// iter = 0
// if 0 < x goto body else done
// loop: (target of continue)
// iter++
// if iter < x goto body else done
// body:
// k = x
// ...body...
// jump loop
// done: (target of break)
if b, ok := x.Type().(*types.Basic); ok && b.Info()&types.IsUntyped != 0 {
x = emitConv(fn, x, tInt, source)
}
T := x.Type()
iter := emitLocal(fn, T, source, "rangeint.iter")
// x may be unsigned. Avoid initializing x to -1.
body := fn.newBasicBlock("rangeint.body")
done = fn.newBasicBlock("rangeint.done")
emitIf(fn, emitCompare(fn, token.LSS, emitConst(fn, zeroConst(T, source)), x, source), body, done, source)
loop = fn.newBasicBlock("rangeint.loop")
fn.currentBlock = loop
incr := &BinOp{
Op: token.ADD,
X: emitLoad(fn, iter, source),
Y: emitConv(fn, emitConst(fn, intConst(1, source)), T, source),
}
incr.setType(T)
emitStore(fn, iter, fn.emit(incr, source), source)
emitIf(fn, emitCompare(fn, token.LSS, incr, x, source), body, done, source)
fn.currentBlock = body
if tk != nil {
// Integer types (int, uint8, etc.) are named and
// we know that k is assignable to x when tk != nil.
// This implies tk and T are identical so no conversion is needed.
k = emitLoad(fn, iter, 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)
}
// create locals for s.Key and s.Value
createVars := func() {
// Unlike a short variable declaration, a RangeStmt
// using := never redeclares an existing variable; it
// always creates a new one.
if tk != nil {
id := s.Key.(*ast.Ident)
emitLocalVar(fn, identVar(fn, id), id)
}
if tv != nil {
id := s.Value.(*ast.Ident)
emitLocalVar(fn, identVar(fn, id), id)
}
}
afterGo122 := version.Compare(fn.goversion, "go1.22") >= 0
if s.Tok == token.DEFINE && !afterGo122 {
// pre-go1.22: If iteration variables are defined (:=), this
// occurs once outside the loop.
createVars()
}
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:
k, v, loop, done = b.rangeIter(fn, x, tk, tv, source)
case *types.Basic:
switch {
case rt.Info()&types.IsString != 0:
k, v, loop, done = b.rangeIter(fn, x, tk, tv, source)
case rt.Info()&types.IsInteger != 0:
k, loop, done = b.rangeInt(fn, x, tk, source)
default:
panic("Cannot range over basic type: " + rt.String())
}
case *types.Signature:
// Special case rewrite (fn.goversion >= go1.23):
// for x := range f { ... }
// into
// f(func(x T) bool { ... })
b.rangeFunc(fn, x, tk, tv, s, label)
return
default:
panic("Cannot range over: " + rt.String())
}
if s.Tok == token.DEFINE && afterGo122 {
// go1.22: If iteration variables are defined (:=), this occurs inside the loop.
createVars()
}
// 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
}
// rangeFunc emits to fn code for the range-over-func rng.Body of the iterator
// function x, optionally labelled by label. It creates a new anonymous function
// yield for rng and builds the function.
func (b *builder) rangeFunc(fn *Function, x Value, tk, tv types.Type, rng *ast.RangeStmt, label *lblock) {
// Consider the SSA code for the outermost range-over-func in fn:
//
// func fn(...) (ret R) {
// ...
// for k, v = range x {
// ...
// }
// ...
// }
//
// The code emitted into fn will look something like this.
//
// loop:
// jump := READY
// y := make closure yield [ret, deferstack, jump, k, v]
// x(y)
// switch jump {
// [see resuming execution]
// }
// goto done
// done:
// ...
//
// where yield is a new synthetic yield function:
//
// func yield(_k tk, _v tv) bool
// free variables: [ret, stack, jump, k, v]
// {
// entry:
// if jump != READY then goto invalid else valid
// invalid:
// panic("iterator called when it is not in a ready state")
// valid:
// jump = BUSY
// k = _k
// v = _v
// ...
// cont:
// jump = READY
// return true
// }
//
// Yield state:
//
// Each range loop has an associated jump variable that records
// the state of the iterator. A yield function is initially
// in a READY (0) and callable state. If the yield function is called
// and is not in READY state, it panics. When it is called in a callable
// state, it becomes BUSY. When execution reaches the end of the body
// of the loop (or a continue statement targeting the loop is executed),
// the yield function returns true and resumes being in a READY state.
// After the iterator function x(y) returns, then if the yield function
// is in a READY state, the yield enters the DONE state.
//
// Each lowered control statement (break X, continue X, goto Z, or return)
// that exits the loop sets the variable to a unique positive EXIT value,
// before returning false from the yield function.
//
// If the yield function returns abruptly due to a panic or GoExit,
// it remains in a BUSY state. The generated code asserts that, after
// the iterator call x(y) returns normally, the jump variable state
// is DONE.
//
// Resuming execution:
//
// The code generated for the range statement checks the jump
// variable to determine how to resume execution.
//
// switch jump {
// case BUSY: panic("...")
// case DONE: goto done
// case READY: state = DONE; goto done
// case 123: ... // action for exit 123.
// case 456: ... // action for exit 456.
// ...
// }
//
// Forward goto statements within a yield are jumps to labels that
// have not yet been traversed in fn. They may be in the Body of the
// function. What we emit for these is:
//
// goto target
// target:
// ...
//
// We leave an unresolved exit in yield.exits to check at the end
// of building yield if it encountered target in the body. If it
// encountered target, no additional work is required. Otherwise,
// the yield emits a new early exit in the basic block for target.
// We expect that blockopt will fuse the early exit into the case
// block later. The unresolved exit is then added to yield.parent.exits.
loop := fn.newBasicBlock("rangefunc.loop")
done := fn.newBasicBlock("rangefunc.done")
// These are targets within y.
fn.targets = &targets{
tail: fn.targets,
_break: done,
// _continue is within y.
}
if label != nil {
label._break = done
// _continue is within y
}
emitJump(fn, loop, nil)
fn.currentBlock = loop
// loop:
// jump := READY
anonIdx := len(fn.AnonFuncs)
jump := newVar(fmt.Sprintf("jump$%d", anonIdx+1), tInt)
emitLocalVar(fn, jump, nil) // zero value is READY
xsig := typeutil.CoreType(x.Type()).(*types.Signature)
ysig := typeutil.CoreType(xsig.Params().At(0).Type()).(*types.Signature)
/* synthetic yield function for body of range-over-func loop */
y := &Function{
name: fmt.Sprintf("%s$%d", fn.Name(), anonIdx+1),
Signature: ysig,
Synthetic: SyntheticRangeOverFuncYield,
parent: fn,
Pkg: fn.Pkg,
Prog: fn.Prog,
functionBody: new(functionBody),
}
y.source = rng
y.goversion = fn.goversion
y.jump = jump
y.deferstack = fn.deferstack
y.returnVars = fn.returnVars // use the parent's return variables
y.uniq = fn.uniq // start from parent's unique values
// If the RangeStmt has a label, this is how it is passed to buildYieldFunc.
if label != nil {
y.lblocks = map[*types.Label]*lblock{label.label: nil}
}
fn.AnonFuncs = append(fn.AnonFuncs, y)
// Build y immediately. It may:
// * cause fn's locals to escape, and
// * create new exit nodes in exits.
// (y is not marked 'built' until the end of the enclosing FuncDecl.)
unresolved := len(fn.exits)
b.buildYieldFunc(y)
fn.uniq = y.uniq // resume after y's unique values
// Emit the call of y.
// c := MakeClosure y
// x(c)
c := &MakeClosure{Fn: y}
c.setType(ysig)
c.comment = "yield"
for _, fv := range y.FreeVars {
c.Bindings = append(c.Bindings, fv.outer)
fv.outer = nil
}
fn.emit(c, nil)
call := Call{
Call: CallCommon{
Value: x,
Args: []Value{c},
},
}
call.setType(xsig.Results())
fn.emit(&call, nil)
exits := fn.exits[unresolved:]
b.buildYieldResume(fn, jump, exits, done)
fn.currentBlock = done
}
// buildYieldResume emits to fn code for how to resume execution once a call to
// the iterator function over the yield function returns x(y). It does this by building
// a switch over the value of jump for when it is READY, BUSY, or EXIT(id).
func (b *builder) buildYieldResume(fn *Function, jump *types.Var, exits []*exit, done *BasicBlock) {
// v := *jump
// switch v {
// case BUSY: panic("...")
// case READY: jump = DONE; goto done
// case EXIT(a): ...
// case EXIT(b): ...
// ...
// }
v := emitLoad(fn, fn.lookup(jump, false), nil)
entry := fn.currentBlock
bodies := make([]*BasicBlock, 2, 2+len(exits))
bodies[0] = fn.newBasicBlock("rangefunc.resume.busy")
bodies[1] = fn.newBasicBlock("rangefunc.resume.ready")
conds := make([]Value, 2, 2+len(exits))
conds[0] = emitConst(fn, jBusy())
conds[1] = emitConst(fn, jReady())
fn.currentBlock = bodies[0]
fn.emit(
&Panic{
X: emitConv(fn, emitConst(fn, stringConst("iterator call did not preserve panic", nil)), tEface, nil),
},
nil,
)
addEdge(fn.currentBlock, fn.Exit)
fn.currentBlock = bodies[1]
storeVar(fn, jump, emitConst(fn, jDone()), nil)
emitJump(fn, done, nil)
for _, e := range exits {
body := fn.newBasicBlock(fmt.Sprintf("rangefunc.resume.exit.%d", e.id))
bodies = append(bodies, body)
id_ := intConst(e.id, nil)
id_.comment = fmt.Sprintf("rangefunc.exit.%d", e.id)
id := emitConst(fn, id_)
conds = append(conds, id)
fn.currentBlock = body
switch {
case e.label != nil: // forward goto?
// case EXIT(id): goto lb // label
lb := fn.lblockOf(e.label)
// Do not mark lb as resolved.
// If fn does not contain label, lb remains unresolved and
// fn must itself be a range-over-func function. lb will be:
// lb:
// fn.jump = id
// return false
emitJump(fn, lb._goto, e.source)
case e.to != fn: // e jumps to an ancestor of fn?
// case EXIT(id): { fn.jump = id; return false }
// fn is a range-over-func function.
storeVar(fn, fn.jump, id, e.source)
vFalse := emitConst(fn, NewConst(constant.MakeBool(false), tBool, e.source))
emitReturn(fn, []Value{vFalse}, e.source)
case e.block == nil && e.label == nil: // return from fn?
// case EXIT(id): { return ... }
// The results have already been stored to variables in fn.results, so
// emitReturn doesn't have to do it again.
emitReturn(fn, nil, e.source)
case e.block != nil:
// case EXIT(id): goto block
emitJump(fn, e.block, e.source)
default:
panic("unreachable")
}
}
fn.currentBlock = entry
// Note that this switch does not have an implicit default case. This wouldn't be
// valid for a user-provided switch statement, but for range-over-func we know all
// possible values and we can avoid the impossible branch.
swtch := &ConstantSwitch{
Tag: v,
Conds: conds,
}
fn.emit(swtch, nil)
for _, body := range bodies {
addEdge(entry, body)
}
}
// 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:
if s.Label.Name == "_" {
// Blank labels can't be the target of a goto, break,
// or continue statement, so we don't need a new block.
_s = s.Stmt
goto start
}
label = fn.lblockOf(fn.label(s.Label))
label.resolved = true
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(), s)), 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.
deferstack := emitLoad(fn, fn.lookup(fn.deferstack, false), s)
v := Defer{_DeferStack: deferstack}
b.setCall(fn, s.Call, &v.Call)
fn.hasDefer = true
fn.emit(&v, s)
case *ast.ReturnStmt:
b.returnStmt(fn, s)
case *ast.BranchStmt:
b.branchStmt(fn, s)
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))
}
}
func (b *builder) branchStmt(fn *Function, s *ast.BranchStmt) {
var block *BasicBlock
if s.Label == nil {
block = targetedBlock(fn, s.Tok)
} else {
target := fn.label(s.Label)
block = labelledBlock(fn, target, s.Tok)
if block == nil { // forward goto
lb := fn.lblockOf(target)
block = lb._goto // jump to lb._goto
if fn.jump != nil {
// fn is a range-over-func and the goto may exit fn.
// Create an exit and resolve it at the end of
// builder.buildYieldFunc.
labelExit(fn, target, s)
}
}
}
to := block.parent
if to == fn {
emitJump(fn, block, s)
} else { // break outside of fn.
// fn must be a range-over-func
e := blockExit(fn, block, s)
id_ := intConst(e.id, s)
id_.comment = fmt.Sprintf("rangefunc.exit.%d", e.id)
id := emitConst(fn, id_)
storeVar(fn, fn.jump, id, s)
vFalse := emitConst(fn, NewConst(constant.MakeBool(false), tBool, s))
emitReturn(fn, []Value{vFalse}, s)
}
fn.currentBlock = fn.newBasicBlock("unreachable")
}
func (b *builder) returnStmt(fn *Function, s *ast.ReturnStmt) {
// TODO(dh): we could emit tighter position information by
// using the ith returned expression
var results []Value
sig := fn.sourceFn.Signature // signature of the enclosing source function
// Convert return operands to result type.
if len(s.Results) == 1 && sig.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),
sig.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), sig.Results().At(i).Type(), s)
results = append(results, v)
}
}
// Store the results.
for i, r := range results {
var result Value // fn.sourceFn.result[i] conceptually
if fn == fn.sourceFn {
result = fn.results[i]
} else { // lookup needed?
result = fn.lookup(fn.returnVars[i], false)
}
emitStore(fn, result, r, s)
}
if fn.jump != nil {
// Return from body of a range-over-func.
// The return statement is syntactically within the loop,
// but the generated code is in the 'switch jump {...}' after it.
e := returnExit(fn, s)
id_ := intConst(e.id, e.source)
id_.comment = fmt.Sprintf("rangefunc.exit.%d", e.id)
id := emitConst(fn, id_)
storeVar(fn, fn.jump, id, e.source)
vFalse := emitConst(fn, NewConst(constant.MakeBool(false), tBool, e.source))
emitReturn(fn, []Value{vFalse}, e.source)
return
}
// The results have already been stored to variables in fn.results, so
// emitReturn doesn't have to do it again.
emitReturn(fn, nil, s)
}
func emitReturn(fn *Function, results []Value, source ast.Node) {
for i, r := range results {
emitStore(fn, fn.results[i], r, source)
}
emitJump(fn, fn.Exit, source)
fn.currentBlock = fn.newBasicBlock("unreachable")
}
// 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.addParamVar(recv, nil)
}
params := fn.Signature.Params()
for i, n := 0, params.Len(); i < n; i++ {
// XXX synthesize an ast.Node
fn.addParamVar(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.sourceFn = fn
fn.startBody()
fn.createSyntacticParams(recvField, functype)
fn.createDeferStack()
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
}
// buildYieldFunc builds the body of the yield function created
// from a range-over-func *ast.RangeStmt.
func (b *builder) buildYieldFunc(fn *Function) {
// See builder.rangeFunc for detailed documentation on how fn is set up.
//
// In psuedo-Go this roughly builds:
// func yield(_k tk, _v tv) bool {
// if jump != READY { panic("yield function called after range loop exit") }
// jump = BUSY
// k, v = _k, _v // assign the iterator variable (if needed)
// ... // rng.Body
// continue:
// jump = READY
// return true
// }
s := fn.source.(*ast.RangeStmt)
fn.sourceFn = fn.parent.sourceFn
fn.startBody()
params := fn.Signature.Params()
for i := 0; i < params.Len(); i++ {
fn.addParamVar(params.At(i), nil)
}
fn.addResultVar(fn.Signature.Results().At(0), nil)
fn.exitBlock()
// Initial targets
ycont := fn.newBasicBlock("yield-continue")
// lblocks is either {} or is {label: nil} where label is the label of syntax.
for label := range fn.lblocks {
fn.lblocks[label] = &lblock{
label: label,
resolved: true,
_goto: ycont,
_continue: ycont,
// `break label` statement targets fn.parent.targets._break
}
}
fn.targets = &targets{
_continue: ycont,
// `break` statement targets fn.parent.targets._break.
}
// continue:
// jump = READY
// return true
saved := fn.currentBlock
fn.currentBlock = ycont
storeVar(fn, fn.jump, emitConst(fn, jReady()), s.Body)
vTrue := emitConst(fn, NewConst(constant.MakeBool(true), tBool, nil))
emitReturn(fn, []Value{vTrue}, nil)
// Emit header:
//
// if jump != READY { panic("yield iterator accessed after exit") }
// jump = BUSY
// k, v = _k, _v
fn.currentBlock = saved
yloop := fn.newBasicBlock("yield-loop")
invalid := fn.newBasicBlock("yield-invalid")
jumpVal := emitLoad(fn, fn.lookup(fn.jump, true), nil)
emitIf(fn, emitCompare(fn, token.EQL, jumpVal, emitConst(fn, jReady()), nil), yloop, invalid, nil)
fn.currentBlock = invalid
fn.emit(
&Panic{
X: emitConv(fn, emitConst(fn, stringConst("yield function called after range loop exit", nil)), tEface, nil),
},
nil,
)
addEdge(fn.currentBlock, fn.Exit)
fn.currentBlock = yloop
storeVar(fn, fn.jump, emitConst(fn, jBusy()), s.Body)
// Initialize k and v from params.
var tk, tv types.Type
if s.Key != nil && !isBlankIdent(s.Key) {
tk = fn.Pkg.typeOf(s.Key) // fn.parent.typeOf is identical
}
if s.Value != nil && !isBlankIdent(s.Value) {
tv = fn.Pkg.typeOf(s.Value)
}
if s.Tok == token.DEFINE {
if tk != nil {
emitLocalVar(fn, identVar(fn, s.Key.(*ast.Ident)), s.Key)
}
if tv != nil {
emitLocalVar(fn, identVar(fn, s.Value.(*ast.Ident)), s.Value)
}
}
var k, v Value
if len(fn.Params) > 0 {
k = fn.Params[0]
}
if len(fn.Params) > 1 {
v = fn.Params[1]
}
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.Key)
}
if tv != nil {
vl.store(fn, v, s.Value)
}
// Build the body of the range loop.
b.stmt(fn, s.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.
emitJump(fn, ycont, nil)
}
// Clean up exits and promote any unresolved exits to fn.parent.
for _, e := range fn.exits {
if e.label != nil {
lb := fn.lblocks[e.label]
if lb.resolved {
// label was resolved. Do not turn lb into an exit.
// e does not need to be handled by the parent.
continue
}
// _goto becomes an exit.
// _goto:
// jump = id
// return false
fn.currentBlock = lb._goto
id_ := intConst(e.id, e.source)
id_.comment = fmt.Sprintf("rangefunc.exit.%d", e.id)
id := emitConst(fn, id_)
storeVar(fn, fn.jump, id, e.source)
vFalse := emitConst(fn, NewConst(constant.MakeBool(false), tBool, e.source))
emitReturn(fn, []Value{vFalse}, e.source)
}
if e.to != fn { // e needs to be handled by the parent too.
fn.parent.exits = append(fn.parent.exits, e)
}
}
fn.finishBody()
}
// 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
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)), 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()))
}
// Initializers for global vars are evaluated in dependency
// order, but may come from arbitrary files of the package
// with different versions, so we transiently update
// init.goversion for each one. (Since init is a synthetic
// function it has no syntax of its own that needs a version.)
init.goversion = p.initVersion[varinit.Rhs]
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)
}
}
}
init.goversion = "" // The rest of the init function is synthetic. No syntax => no goversion.
// 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()
// We no longer need ASTs or go/types deductions.
p.info = nil
p.initVersion = nil
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-2024.1/go/ir/builder_test.go 0000664 0000000 0000000 00000047346 14657040722 0022145 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,
}
var seen []string // may contain dups
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
}
seen = append(seen, name)
if wantDescr != fn.Synthetic {
t.Errorf("(%s).Synthetic = %q, want %q", name, fn.Synthetic, wantDescr)
}
}
for _, name := range seen {
delete(want, name)
}
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)
}
}
func TestBuildPackageGo120(t *testing.T) {
tests := []struct {
name string
src string
importer types.Importer
}{
{"slice to array", "package p; var s []byte; var _ = ([4]byte)(s)", nil},
{"slice to zero length array", "package p; var s []byte; var _ = ([0]byte)(s)", nil},
{"slice to zero length array type parameter", "package p; var s []byte; func f[T ~[0]byte]() { tmp := (T)(s); var z T; _ = tmp == z}", nil},
{"slice to non-zero length array type parameter", "package p; var s []byte; func h[T ~[1]byte | [4]byte]() { tmp := T(s); var z T; _ = tmp == z}", nil},
{"slice to maybe-zero length array type parameter", "package p; var s []byte; func g[T ~[0]byte | [4]byte]() { tmp := T(s); var z T; _ = tmp == z}", nil},
{
"rune sequence to sequence cast patterns", `
package p
// Each of fXX functions describes a 1.20 legal cast between sequences of runes
// as []rune, pointers to rune arrays, rune arrays, or strings.
//
// Comments listed given the current emitted instructions [approximately].
// If multiple conversions are needed, these are separated by |.
// rune was selected as it leads to string casts (byte is similar).
// The length 2 is not significant.
// Multiple array lengths may occur in a cast in practice (including 0).
func f00[S string, D string](s S) { _ = D(s) } // ChangeType
func f01[S string, D []rune](s S) { _ = D(s) } // Convert
func f02[S string, D []rune | string](s S) { _ = D(s) } // ChangeType | Convert
func f03[S [2]rune, D [2]rune](s S) { _ = D(s) } // ChangeType
func f04[S *[2]rune, D *[2]rune](s S) { _ = D(s) } // ChangeType
func f05[S []rune, D string](s S) { _ = D(s) } // Convert
func f06[S []rune, D [2]rune](s S) { _ = D(s) } // SliceToArray
func f07[S []rune, D [2]rune | string](s S) { _ = D(s) } // SliceToArray | Convert
func f08[S []rune, D *[2]rune](s S) { _ = D(s) } // SliceToArrayPointer
func f09[S []rune, D *[2]rune | string](s S) { _ = D(s) } // SliceToArray | Convert
func f10[S []rune, D *[2]rune | [2]rune](s S) { _ = D(s) } // SliceToArrayPointer | SliceToArray
func f11[S []rune, D *[2]rune | [2]rune | string](s S) { _ = D(s) } // SliceToArrayPointer | SliceToArray | Convert
func f12[S []rune, D []rune](s S) { _ = D(s) } // ChangeType
func f13[S []rune, D []rune | string](s S) { _ = D(s) } // Convert | ChangeType
func f14[S []rune, D []rune | [2]rune](s S) { _ = D(s) } // ChangeType | SliceToArray
func f15[S []rune, D []rune | [2]rune | string](s S) { _ = D(s) } // ChangeType | SliceToArray | Convert
func f16[S []rune, D []rune | *[2]rune](s S) { _ = D(s) } // ChangeType | SliceToArrayPointer
func f17[S []rune, D []rune | *[2]rune | string](s S) { _ = D(s) } // ChangeType | SliceToArrayPointer | Convert
func f18[S []rune, D []rune | *[2]rune | [2]rune](s S) { _ = D(s) } // ChangeType | SliceToArrayPointer | SliceToArray
func f19[S []rune, D []rune | *[2]rune | [2]rune | string](s S) { _ = D(s) } // ChangeType | SliceToArrayPointer | SliceToArray | Convert
func f20[S []rune | string, D string](s S) { _ = D(s) } // Convert | ChangeType
func f21[S []rune | string, D []rune](s S) { _ = D(s) } // Convert | ChangeType
func f22[S []rune | string, D []rune | string](s S) { _ = D(s) } // ChangeType | Convert | Convert | ChangeType
func f23[S []rune | [2]rune, D [2]rune](s S) { _ = D(s) } // SliceToArray | ChangeType
func f24[S []rune | *[2]rune, D *[2]rune](s S) { _ = D(s) } // SliceToArrayPointer | ChangeType
`, nil,
},
{
"matching named and underlying types", `
package p
type a string
type b string
func g0[S []rune | a | b, D []rune | a | b](s S) { _ = D(s) }
func g1[S []rune | ~string, D []rune | a | b](s S) { _ = D(s) }
func g2[S []rune | a | b, D []rune | ~string](s S) { _ = D(s) }
func g3[S []rune | ~string, D []rune |~string](s S) { _ = D(s) }
`, nil,
},
}
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, 0)
if err != nil {
t.Error(err)
}
files := []*ast.File{f}
pkg := types.NewPackage("p", "")
conf := &types.Config{Importer: tc.importer}
_, _, err = irutil.BuildPackage(conf, fset, pkg, files, ir.SanityCheckFunctions)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
})
}
}
func TestGo117Builtins(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)
}
})
}
}
// TestLabels just tests that anonymous labels are handled.
func TestLabels(t *testing.T) {
tests := []string{
`package main
func main() { _:println(1) }`,
`package main
func main() { _:println(1); _:println(2)}`,
}
for _, test := range tests {
conf := loader.Config{Fset: token.NewFileSet()}
f, err := parser.ParseFile(conf.Fset, "", test, 0)
if err != nil {
t.Errorf("parse error: %s", err)
return
}
conf.CreateFromFiles("main", f)
iprog, err := conf.Load()
if err != nil {
t.Error(err)
continue
}
prog := irutil.CreateProgram(iprog, ir.BuilderMode(0))
pkg := prog.Package(iprog.Created[0].Pkg)
pkg.Build()
}
}
func TestUnreachableExit(t *testing.T) {
tests := []string{
`
package pkg
func foo() (err error) {
if true {
println()
}
println()
for {
err = nil
}
}`,
}
for _, test := range tests {
conf := loader.Config{Fset: token.NewFileSet()}
f, err := parser.ParseFile(conf.Fset, "", test, 0)
if err != nil {
t.Errorf("parse error: %s", err)
return
}
conf.CreateFromFiles("pkg", f)
iprog, err := conf.Load()
if err != nil {
t.Error(err)
continue
}
prog := irutil.CreateProgram(iprog, ir.BuilderMode(0))
pkg := prog.Package(iprog.Created[0].Pkg)
pkg.Build()
}
}
golang-honnef-go-tools-2024.1/go/ir/const.go 0000664 0000000 0000000 00000016706 14657040722 0020602 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/ast"
"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, source ast.Node) *Const {
c := &Const{
register: register{
typ: typ,
},
Value: val,
}
c.setSource(source)
return c
}
// 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, source ast.Node) *Const {
return NewConst(constant.MakeInt64(i), tInt, source)
}
// nilConst returns a nil constant of the specified type, which may
// be any reference type, including interfaces.
func nilConst(typ types.Type, source ast.Node) *Const {
return NewConst(nil, typ, source)
}
// stringConst returns a 'string' constant that evaluates to s.
func stringConst(s string, source ast.Node) *Const {
return NewConst(constant.MakeString(s), tString, source)
}
// zeroConst returns a new "zero" constant of the specified type.
func zeroConst(t types.Type, source ast.Node) Constant {
if _, ok := t.Underlying().(*types.Interface); ok && !typeparams.IsTypeParam(t) {
// Handle non-generic interface early to simplify following code.
return nilConst(t, source)
}
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(), source)
}
ac := &AggregateConst{
register: register{typ: t},
Values: values,
}
ac.setSource(source)
return ac
case *types.Tuple:
values := make([]Value, typ.Len())
for i := 0; i < typ.Len(); i++ {
values[i] = zeroConst(typ.At(i).Type(), source)
}
ac := &AggregateConst{
register: register{typ: t},
Values: values,
}
ac.setSource(source)
return ac
}
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, source)
case tset.All(isInfo(types.IsString)):
return NewConst(constant.MakeString(""), t, source)
case tset.All(isInfo(types.IsBoolean)):
return NewConst(constant.MakeBool(false), t, source)
case tset.All(isNillable):
return nilConst(t, source)
case tset.All(isArray):
var k ArrayConst
k.setType(t)
k.setSource(source)
return &k
default:
var k GenericConst
k.setType(t)
k.setSource(source)
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 && c.source == oc.source
}
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
}
if c.source != oc.source {
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 && c.source == oc.source
}
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 && c.source == oc.source
}
golang-honnef-go-tools-2024.1/go/ir/create.go 0000664 0000000 0000000 00000017547 14657040722 0020723 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"
"go/version"
"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),
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) and goversion defines the appropriate
// interpretation; they will be used during the build phase.
func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node, goversion string) {
name := obj.Name()
switch obj := obj.(type) {
case *types.Builtin:
if pkg.Pkg != types.Unsafe {
panic("unexpected builtin object: " + obj.String())
}
case *types.TypeName:
if name != "_" {
pkg.Members[name] = &Type{
object: obj,
pkg: pkg,
}
}
case *types.Const:
c := &NamedConst{
object: obj,
Value: NewConst(obj.Val(), obj.Type(), syntax),
pkg: pkg,
}
pkg.values[obj] = c.Value
if name != "_" {
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
if name != "_" {
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,
goversion: goversion,
}
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 name != "_" && 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, goversion string) {
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 {
memberFromObject(pkg, pkg.info.Defs[id], nil, "")
}
}
case token.VAR:
for _, spec := range decl.Specs {
for _, rhs := range spec.(*ast.ValueSpec).Values {
pkg.initVersion[rhs] = goversion
}
for _, id := range spec.(*ast.ValueSpec).Names {
memberFromObject(pkg, pkg.info.Defs[id], spec, goversion)
}
}
case token.TYPE:
for _, spec := range decl.Specs {
id := spec.(*ast.TypeSpec).Name
memberFromObject(pkg, pkg.info.Defs[id], nil, "")
}
}
case *ast.FuncDecl:
id := decl.Name
obj, ok := pkg.info.Defs[id]
if !ok {
panic(fmt.Sprintf("couldn't find object for id %q at %s",
id.Name, pkg.Prog.Fset.PositionFor(id.Pos(), false)))
}
if obj == nil {
panic(fmt.Sprintf("found nil object for id %q at %s",
id.Name, pkg.Prog.Fset.PositionFor(id.Pos(), false)))
}
memberFromObject(pkg, obj, decl, goversion)
}
}
// 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,
// transient values (CREATE and BUILD phases)
info: info,
files: files,
printFunc: prog.PrintFunc,
initVersion: make(map[ast.Expr]string),
}
// Add init() function.
p.init = &Function{
name: "init",
Signature: new(types.Signature),
Synthetic: SyntheticPackageInitializer,
Pkg: p,
Prog: prog,
functionBody: new(functionBody),
goversion: "", // See Package.build for details.
}
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 {
goversion := version.Lang(p.info.FileVersions[file])
for _, decl := range file.Decls {
membersFromDecl(p, decl, goversion)
}
}
} 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-2024.1/go/ir/doc.go 0000664 0000000 0000000 00000014115 14657040722 0020211 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
// https://en.wikipedia.org/wiki/Static_single_assignment_form.
// This page provides a broader reading list:
// https://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 ✔ ✔
// *MultiConvert ✔ ✔
// *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-2024.1/go/ir/dom.go 0000664 0000000 0000000 00000027223 14657040722 0020227 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.
// https://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,
// https://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-2024.1/go/ir/emit.go 0000664 0000000 0000000 00000044021 14657040722 0020401 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"
)
// emitAlloc emits to f a new Alloc instruction allocating a variable
// of type typ.
//
// The caller must set Alloc.Heap=true (for a heap-allocated variable)
// or add the Alloc to f.Locals (for a frame-allocated variable).
//
// During building, a variable in f.Locals may have its Heap flag
// set when it is discovered that its address is taken.
// These Allocs are removed from f.Locals at the end.
//
// The builder should generally call one of the emit{New,Local,LocalVar} wrappers instead.
func emitAlloc(f *Function, typ types.Type, source ast.Node, comment string) *Alloc {
v := &Alloc{}
v.comment = comment
v.setType(types.NewPointer(typ))
f.emit(v, source)
return v
}
// emitNew emits to f a new Alloc instruction heap-allocating a
// variable of type typ.
func emitNew(f *Function, typ types.Type, source ast.Node, comment string) *Alloc {
alloc := emitAlloc(f, typ, source, comment)
alloc.Heap = true
return alloc
}
// emitLocal creates a local var for (t, source, comment) and
// emits an Alloc instruction for it.
//
// (Use this function or emitNew for synthetic variables;
// for source-level variables, use emitLocalVar.)
func emitLocal(f *Function, t types.Type, source ast.Node, comment string) *Alloc {
local := emitAlloc(f, t, source, comment)
f.Locals = append(f.Locals, local)
return local
}
// emitLocalVar creates a local var for v and emits an Alloc instruction for it.
// Subsequent calls to f.lookup(v) return it.
func emitLocalVar(f *Function, v *types.Var, source ast.Node) *Alloc {
alloc := emitLocal(f, v.Type(), source, v.Name())
f.vars[v] = alloc
return alloc
}
// 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()
// Conversion to, or construction of a value of, an interface type?
if isNonTypeParamInterface(t_dst) {
// Interface name change?
if isValuePreserving(ut_src, ut_dst) {
c := &ChangeType{X: val}
c.setType(t_dst)
return f.emit(c, source)
}
// Assignment from one interface type to another?
if isNonTypeParamInterface(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, zeroConst(t_dst, source))
}
// 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)
}
// In the common case, the typesets of src and dst are singletons
// and we emit an appropriate conversion. But if either contains
// a type parameter, the conversion may represent a cross product,
// in which case which we emit a MultiConvert.
tset_dst := typeutil.NewTypeSet(ut_dst)
tset_src := typeutil.NewTypeSet(ut_src)
// conversionCase describes an instruction pattern that may be emitted to
// model d <- s for d in dst_terms and s in src_terms.
// Multiple conversions can match the same pattern.
type conversionCase uint8
const (
changeType conversionCase = 1 << iota
sliceToArray
sliceToArrayPtr
sliceTo0Array
sliceTo0ArrayPtr
convert
)
classify := func(s, d types.Type) conversionCase {
// Just a change of type, but not value or representation?
if isValuePreserving(s, d) {
return changeType
}
// Conversion from slice to array or slice to array pointer?
if slice, ok := s.(*types.Slice); ok {
var arr *types.Array
var ptr bool
// Conversion from slice to array pointer?
switch d := d.(type) {
case *types.Array:
arr = d
case *types.Pointer:
arr, _ = d.Elem().Underlying().(*types.Array)
ptr = true
}
if arr != nil && types.Identical(slice.Elem(), arr.Elem()) {
if arr.Len() == 0 {
if ptr {
return sliceTo0ArrayPtr
} else {
return sliceTo0Array
}
}
if ptr {
return sliceToArrayPtr
} else {
return sliceToArray
}
}
}
// The only remaining case in well-typed code is a representation-
// changing conversion of basic types (possibly with []byte/[]rune).
if !isBasic(s) && !isBasic(d) {
panic(fmt.Sprintf("in %s: cannot convert term %s (%s [within %s]) to type %s [within %s]", f, val, val.Type(), s, t_dst, d))
}
return convert
}
var classifications conversionCase
for _, s := range tset_src.Terms {
us := s.Type().Underlying()
for _, d := range tset_dst.Terms {
ud := d.Type().Underlying()
classifications |= classify(us, ud)
}
}
if classifications == 0 {
panic(fmt.Sprintf("in %s: cannot convert %s (%s) to %s", f, val, val.Type(), t_dst))
}
// Conversion of a compile-time constant value?
if c, ok := val.(*Const); ok {
// Conversion to a basic type?
if isBasic(ut_dst) {
// 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, source))
}
// Can we always convert from zero value without panicking?
const mayPanic = sliceToArray | sliceToArrayPtr
if c.Value == nil && classifications&mayPanic == 0 {
return emitConst(f, NewConst(nil, t_dst, source))
}
// We're converting from constant to non-constant type,
// e.g. string -> []byte/[]rune.
}
switch classifications {
case changeType: // representation-preserving change
c := &ChangeType{X: val}
c.setType(t_dst)
return f.emit(c, source)
case sliceToArrayPtr, sliceTo0ArrayPtr: // slice to array pointer
c := &SliceToArrayPointer{X: val}
c.setType(t_dst)
return f.emit(c, source)
case sliceToArray: // slice to arrays (not zero-length)
p := &SliceToArray{X: val}
p.setType(t_dst)
return f.emit(p, source)
case sliceTo0Array: // slice to zero-length arrays (constant)
return emitConst(f, zeroConst(t_dst, source))
case convert: // representation-changing conversion
c := &Convert{X: val}
c.setType(t_dst)
return f.emit(c, source)
default: // multiple conversion
c := &MultiConvert{X: val, from: tset_src, to: tset_dst}
c.setType(t_dst)
return f.emit(c, source)
}
}
// 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, source))
}
type constKey struct {
typ types.Type
value constant.Value
source ast.Node
}
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,
source: c.Source(),
}
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-2024.1/go/ir/example_test.go 0000664 0000000 0000000 00000012265 14657040722 0022142 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> # varargs
// 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-2024.1/go/ir/exits.go 0000664 0000000 0000000 00000024364 14657040722 0020607 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-2024.1/go/ir/func.go 0000664 0000000 0000000 00000071054 14657040722 0020404 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.
// Forward gotos are resolved once it is known which statement they
// are associated with inside the Function.
type lblock struct {
label *types.Label // Label targeted by the blocks.
resolved bool // _goto block encountered (back jump or resolved fwd jump)
_goto *BasicBlock
_break *BasicBlock
_continue *BasicBlock
}
// label returns the symbol denoted by a label identifier.
//
// label should be a non-blank identifier (label.Name != "_").
func (f *Function) label(label *ast.Ident) *types.Label {
return f.Pkg.objectOf(label).(*types.Label)
}
// lblockOf returns the branch target associated with the
// specified label, creating it if needed.
func (f *Function) lblockOf(label *types.Label) *lblock {
lb := f.lblocks[label]
if lb == nil {
lb = &lblock{
label: label,
_goto: f.newBasicBlock(label.Name()),
}
if f.lblocks == nil {
f.lblocks = make(map[*types.Label]*lblock)
}
f.lblocks[label] = lb
}
return lb
}
// labelledBlock searches f for the block of the specified label.
//
// If f is a yield function, it additionally searches ancestor Functions
// corresponding to enclosing range-over-func statements within the
// same source function, so the returned block may belong to a different Function.
func labelledBlock(f *Function, label *types.Label, tok token.Token) *BasicBlock {
if lb := f.lblocks[label]; lb != nil {
var block *BasicBlock
switch tok {
case token.BREAK:
block = lb._break
case token.CONTINUE:
block = lb._continue
case token.GOTO:
block = lb._goto
}
if block != nil {
return block
}
}
// Search ancestors if this is a yield function.
if f.jump != nil {
return labelledBlock(f.parent, label, tok)
}
return nil
}
// targetedBlock looks for the nearest block in f.targets
// (and f's ancestors) that matches tok's type, and returns
// the block and function it was found in.
func targetedBlock(f *Function, tok token.Token) *BasicBlock {
if f == nil {
return nil
}
for t := f.targets; t != nil; t = t.tail {
var block *BasicBlock
switch tok {
case token.BREAK:
block = t._break
case token.CONTINUE:
block = t._continue
case token.FALLTHROUGH:
block = t._fallthrough
}
if block != nil {
return block
}
}
// Search f's ancestors (in case f is a yield function).
return targetedBlock(f.parent, tok)
}
// addResultVar adds a result for a variable v to f.results and v to f.returnVars.
func (f *Function) addResultVar(v *types.Var, source ast.Node) {
name := v.Name()
if name == "" {
name = fmt.Sprintf("res.%d", len(f.results))
}
result := emitLocalVar(f, v, source)
result.comment = name
f.results = append(f.results, result)
f.returnVars = append(f.returnVars, v)
}
func (f *Function) addParamVar(v *types.Var, source ast.Node) *Parameter {
name := v.Name()
if name == "" {
name = fmt.Sprintf("arg%d", len(f.Params))
}
var b *BasicBlock
if len(f.Blocks) > 0 {
b = f.Blocks[0]
}
param := &Parameter{name: name}
param.setBlock(b)
param.setType(v.Type())
param.setSource(source)
param.object = v
f.Params = append(f.Params, param)
if b != nil {
f.Blocks[0].Instrs = append(f.Blocks[0].Instrs, param)
}
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.Var, source ast.Node) {
param := f.addParamVar(obj, source)
spill := emitLocalVar(f, obj, 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.vars = make(map[*types.Var]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
results := make([]Value, len(f.results))
// Run function calls deferred in this
// function when explicitly returning from it.
f.emit(new(RunDefers), nil)
for i, r := range f.results {
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(identVar(f, n), n)
}
// Anonymous receiver? No need to spill.
if field.Names == nil {
f.addParamVar(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(identVar(f, n), n)
}
// Anonymous parameter? No need to spill.
if field.Names == nil {
f.addParamVar(f.Signature.Params().At(len(f.Params)-n), field)
}
}
}
// Results.
if functype.Results != nil {
for _, field := range functype.Results.List {
// Implicit "var" decl of locals for named results.
for _, n := range field.Names {
v := identVar(f, n)
f.addResultVar(v, n)
}
// Implicit "var" decl of local for an unnamed result.
if field.Names == nil {
v := f.Signature.Results().At(len(f.results))
f.addResultVar(v, field.Type)
}
}
}
}
// createDeferStack initializes fn.deferstack to a local variable
// initialized to a ssa:deferstack() call.
func (fn *Function) createDeferStack() {
// Each syntactic function makes a call to ssa:deferstack,
// which is spilled to a local. Unused ones are later removed.
fn.deferstack = newVar("defer$stack", tDeferStack)
call := &Call{Call: CallCommon{Value: vDeferStack}}
call.setType(tDeferStack)
deferstack := fn.emit(call, nil)
spill := emitLocalVar(fn, fn.deferstack, nil)
emitStore(fn, spill, deferstack, nil)
}
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.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{})
}
// clear remaining builder state
f.results = nil // (used by lifting)
f.deferstack = nil // (used by lifting)
f.vars = nil // (used by lifting)
f.goversion = ""
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
}
// 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.
// We assume the referent is a *Alloc or *Phi.
// (The only Phis at this stage are those created directly by go1.22 "for" loops.)
func (f *Function) lookup(obj *types.Var, escaping bool) Value {
if v, ok := f.vars[obj]; ok {
if escaping {
switch v := v.(type) {
case *Alloc:
v.Heap = true
case *Phi:
for _, edge := range v.Edges {
if alloc, ok := edge.(*Alloc); ok {
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.vars[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) {
buf.WriteString("func ")
if recv := sig.Recv(); recv != nil {
buf.WriteString("(")
if name := recv.Name(); name != "" {
buf.WriteString(name)
buf.WriteString(" ")
}
types.WriteType(buf, recv.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)
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)
}
}
}
// identVar returns the variable defined by id.
func identVar(fn *Function, id *ast.Ident) *types.Var {
return fn.Pkg.info.Defs[id].(*types.Var)
}
// unique returns a unique positive int within the source tree of f.
// The source tree of f includes all of f's ancestors by parent and all
// of the AnonFuncs contained within these.
func unique(f *Function) int64 {
f.uniq++
return f.uniq
}
// exit is a change of control flow going from a range-over-func
// yield function to an ancestor function caused by a break, continue,
// goto, or return statement.
//
// There are 3 types of exits:
// * return from the source function (from ReturnStmt),
// * jump to a block (from break and continue statements [labelled/unlabelled]),
// * go to a label (from goto statements).
//
// As the builder does one pass over the ast, it is unclear whether
// a forward goto statement will leave a range-over-func body.
// The function being exited to is unresolved until the end
// of building the range-over-func body.
type exit struct {
id int64 // unique value for exit within from and to
from *Function // the function the exit starts from
to *Function // the function being exited to (nil if unresolved)
source ast.Node
block *BasicBlock // basic block within to being jumped to.
label *types.Label // forward label being jumped to via goto.
// block == nil && label == nil => return
}
// storeVar emits to function f code to store a value v to a *types.Var x.
func storeVar(f *Function, x *types.Var, v Value, source ast.Node) {
emitStore(f, f.lookup(x, true), v, source)
}
// labelExit creates a new exit to a yield fn to exit the function using a label.
func labelExit(fn *Function, label *types.Label, source ast.Node) *exit {
e := &exit{
id: unique(fn),
from: fn,
to: nil,
source: source,
label: label,
}
fn.exits = append(fn.exits, e)
return e
}
// blockExit creates a new exit to a yield fn that jumps to a basic block.
func blockExit(fn *Function, block *BasicBlock, source ast.Node) *exit {
e := &exit{
id: unique(fn),
from: fn,
to: block.parent,
source: source,
block: block,
}
fn.exits = append(fn.exits, e)
return e
}
// returnExit creates a new exit to a yield fn that returns to the source function.
func returnExit(fn *Function, source ast.Node) *exit {
e := &exit{
id: unique(fn),
from: fn,
to: fn.sourceFn,
source: source,
}
fn.exits = append(fn.exits, e)
return e
}
golang-honnef-go-tools-2024.1/go/ir/html.go 0000664 0000000 0000000 00000067216 14657040722 0020422 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))
}
if v, ok := v.(Instruction); ok {
s += fmt.Sprintf(" (%s)", v.Comment())
}
// 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, "