Speed up packages loading (#693)

Don't perform extra go env calls in go/packages.
Load only needed go env vars in golangci-lint.
Stay in sync by enabled analyzers in go vet: remove nilness and
atomicalign analyzers, add errorsas analyzer.
Don't build SSA for govet.

Standalone govet runs 25% faster than before. All runs can be 5-10% faster
than before.
Relates: #208
This commit is contained in:
Isaev Denis 2019-09-14 18:48:18 +03:00 committed by GitHub
parent 4ffe85cae8
commit c9a9255238
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 197 additions and 202 deletions

3
go.mod
View File

@ -50,3 +50,6 @@ require (
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b // indirect
mvdan.cc/unparam v0.0.0-20190209190245-fbb59629db34
)
// https://github.com/golang/tools/pull/160
replace golang.org/x/tools => github.com/golangci/tools v0.0.0-20190914130248-e9260b99c8f1

17
go.sum
View File

@ -106,6 +106,8 @@ github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21 h1:leSNB7iYzLYSS
github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21/go.mod h1:tf5+bzsHdTM0bsB7+8mt0GUMvjCgwLpTapNZHU8AajI=
github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0 h1:HVfrLniijszjS1aiNg8JbBMO2+E1WIQ+j/gL4SQqGPg=
github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4=
github.com/golangci/tools v0.0.0-20190914130248-e9260b99c8f1 h1:8eGJVbBRoAvCh/YZq6n11s9WJkIgWKa/iTNq+R0UrNw=
github.com/golangci/tools v0.0.0-20190914130248-e9260b99c8f1/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 h1:zwtduBRr5SSWhqsYNgcuWO2kFlpdOZbP0+yRjmvPGys=
github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@ -283,21 +285,6 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190121143147-24cd39ecf745/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911022129-16c5e0f7d110/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190912215617-3720d1ec3678 h1:rM1Udd0CgtYI3KUIhu9ROz0QCqjW+n/ODp/hH7c60Xc=
golang.org/x/tools v0.0.0-20190912215617-3720d1ec3678/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=

View File

@ -31,6 +31,7 @@ type Executor struct {
goenv *goutil.Env
fileCache *fsutils.FileCache
lineCache *fsutils.LineCache
debugf logutils.DebugFunc
}
func NewExecutor(version, commit, date string) *Executor {
@ -40,8 +41,10 @@ func NewExecutor(version, commit, date string) *Executor {
commit: commit,
date: date,
DBManager: lintersdb.NewManager(nil),
debugf: logutils.Debug("exec"),
}
e.debugf("Starting execution...")
e.log = report.NewLogWrapper(logutils.NewStderrLog(""), &e.reportData)
// to setup log level early we need to parse config from command line extra time to
@ -100,10 +103,12 @@ func NewExecutor(version, commit, date string) *Executor {
e.fileCache = fsutils.NewFileCache()
e.lineCache = fsutils.NewLineCache(e.fileCache)
e.contextLoader = lint.NewContextLoader(e.cfg, e.log.Child("loader"), e.goenv, e.lineCache, e.fileCache)
e.debugf("Initialized executor")
return e
}
func (e *Executor) Execute() error {
return e.rootCmd.Execute()
err := e.rootCmd.Execute()
e.debugf("Finished execution")
return err
}

View File

@ -46,7 +46,7 @@ func printLinterConfigs(lcs []*linter.Config) {
altNamesStr = fmt.Sprintf(" (%s)", strings.Join(lc.AlternativeNames, ", "))
}
fmt.Fprintf(logutils.StdOut, "%s%s: %s [fast: %t, auto-fix: %t]\n", color.YellowString(lc.Name()),
altNamesStr, lc.Linter.Desc(), !lc.NeedsSSARepr, lc.CanAutoFix)
altNamesStr, lc.Linter.Desc(), !lc.NeedsDepsTypeInfo, lc.CanAutoFix)
}
}

View File

@ -406,7 +406,7 @@ func (e *Executor) executeRun(_ *cobra.Command, args []string) {
defer cancel()
if needTrackResources {
go watchResources(ctx, trackResourcesEndCh, e.log)
go watchResources(ctx, trackResourcesEndCh, e.log, e.debugf)
}
if err := e.runAndPrint(ctx, args); err != nil {
@ -447,11 +447,12 @@ func (e *Executor) setupExitCode(ctx context.Context) {
}
}
func watchResources(ctx context.Context, done chan struct{}, logger logutils.Log) {
func watchResources(ctx context.Context, done chan struct{}, logger logutils.Log, debugf logutils.DebugFunc) {
startedAt := time.Now()
debugf("Started tracking time")
var rssValues []uint64
ticker := time.NewTicker(100 * time.Millisecond)
ticker := time.NewTicker(10 * time.Millisecond)
defer ticker.Stop()
logEveryRecord := os.Getenv("GL_MEM_LOG_EVERY") == "1"
@ -474,6 +475,7 @@ func watchResources(ctx context.Context, done chan struct{}, logger logutils.Log
select {
case <-ctx.Done():
stop = true
debugf("Stopped resources tracking")
case <-ticker.C: // track every second
}

View File

@ -11,12 +11,12 @@ import (
"golang.org/x/tools/go/analysis/passes/asmdecl"
"golang.org/x/tools/go/analysis/passes/assign"
"golang.org/x/tools/go/analysis/passes/atomic"
"golang.org/x/tools/go/analysis/passes/atomicalign"
"golang.org/x/tools/go/analysis/passes/bools"
"golang.org/x/tools/go/analysis/passes/buildtag"
"golang.org/x/tools/go/analysis/passes/cgocall"
"golang.org/x/tools/go/analysis/passes/composite"
"golang.org/x/tools/go/analysis/passes/copylock"
"golang.org/x/tools/go/analysis/passes/errorsas"
"golang.org/x/tools/go/analysis/passes/httpresponse"
"golang.org/x/tools/go/analysis/passes/loopclosure"
"golang.org/x/tools/go/analysis/passes/lostcancel"
@ -31,8 +31,6 @@ import (
"golang.org/x/tools/go/analysis/passes/unreachable"
"golang.org/x/tools/go/analysis/passes/unsafeptr"
"golang.org/x/tools/go/analysis/passes/unusedresult"
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis/passes/nilness"
)
func NewGovet(cfg *config.GovetSettings) *goanalysis.Linter {
@ -41,12 +39,12 @@ func NewGovet(cfg *config.GovetSettings) *goanalysis.Linter {
asmdecl.Analyzer,
assign.Analyzer,
atomic.Analyzer,
atomicalign.Analyzer,
bools.Analyzer,
buildtag.Analyzer,
cgocall.Analyzer,
composite.Analyzer,
copylock.Analyzer,
errorsas.Analyzer,
httpresponse.Analyzer,
loopclosure.Analyzer,
lostcancel.Analyzer,
@ -60,13 +58,6 @@ func NewGovet(cfg *config.GovetSettings) *goanalysis.Linter {
unreachable.Analyzer,
unsafeptr.Analyzer,
unusedresult.Analyzer,
// for debugging:
// findcall.Analyzer,
// pkgfact.Analyzer,
// uses SSA:
nilness.Analyzer,
}
var settings map[string]map[string]interface{}

View File

@ -144,15 +144,16 @@ func (MegacheckMetalinter) BuildLinterConfig(enabledChildren []string) (*linter.
// TODO: merge linter.Config and linter.Linter or refactor it in another way
return &linter.Config{
Linter: m,
EnabledByDefault: false,
NeedsTypeInfo: true,
NeedsSSARepr: true,
InPresets: []string{linter.PresetStyle, linter.PresetBugs, linter.PresetUnused},
Speed: 1,
AlternativeNames: nil,
OriginalURL: "",
ParentLinterName: "",
Linter: m,
EnabledByDefault: false,
NeedsTypeInfo: true,
NeedsDepsTypeInfo: true,
NeedsSSARepr: true,
InPresets: []string{linter.PresetStyle, linter.PresetBugs, linter.PresetUnused},
Speed: 1,
AlternativeNames: nil,
OriginalURL: "",
ParentLinterName: "",
}, nil
}

View File

@ -5,12 +5,21 @@ import (
"encoding/json"
"os"
"os/exec"
"strings"
"time"
"github.com/pkg/errors"
"github.com/golangci/golangci-lint/pkg/logutils"
)
type EnvKey string
const (
EnvGoCache EnvKey = "GOCACHE"
EnvGoRoot EnvKey = "GOROOT"
)
type Env struct {
vars map[string]string
log logutils.Log
@ -26,24 +35,27 @@ func NewEnv(log logutils.Log) *Env {
}
func (e *Env) Discover(ctx context.Context) error {
out, err := exec.CommandContext(ctx, "go", "env", "-json").Output()
startedAt := time.Now()
args := []string{"env", "-json"}
args = append(args, string(EnvGoCache), string(EnvGoRoot))
out, err := exec.CommandContext(ctx, "go", args...).Output()
if err != nil {
return errors.Wrap(err, "failed to run 'go env'")
}
if err = json.Unmarshal(out, &e.vars); err != nil {
return errors.Wrap(err, "failed to parse go env json")
return errors.Wrapf(err, "failed to parse 'go %s' json", strings.Join(args, " "))
}
e.debugf("Read go env: %#v", e.vars)
e.debugf("Read go env for %s: %#v", time.Since(startedAt), e.vars)
return nil
}
func (e Env) Get(k string) string {
envValue := os.Getenv(k)
func (e Env) Get(k EnvKey) string {
envValue := os.Getenv(string(k))
if envValue != "" {
return envValue
}
return e.vars[k]
return e.vars[string(k)]
}

View File

@ -13,8 +13,9 @@ type Config struct {
Linter Linter
EnabledByDefault bool
NeedsTypeInfo bool
NeedsSSARepr bool
NeedsTypeInfo bool
NeedsDepsTypeInfo bool
NeedsSSARepr bool
InPresets []string
Speed int // more value means faster execution of linter
@ -30,6 +31,12 @@ func (lc *Config) WithTypeInfo() *Config {
return lc
}
func (lc *Config) WithDepsTypeInfo() *Config {
lc.NeedsTypeInfo = true
lc.NeedsDepsTypeInfo = true
return lc
}
func (lc *Config) WithSSA() *Config {
lc.NeedsTypeInfo = true
lc.NeedsSSARepr = true

View File

@ -51,7 +51,7 @@ func (es EnabledSet) build(lcfg *config.Linters, enabledByDefaultLinters []*lint
// It should be before --enable and --disable to be able to enable or disable specific linter.
if lcfg.Fast {
for name := range resultLintersSet {
if es.m.GetLinterConfig(name).NeedsSSARepr {
if es.m.GetLinterConfig(name).NeedsDepsTypeInfo {
delete(resultLintersSet, name)
}
}

View File

@ -85,12 +85,13 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
}
lcs := []*linter.Config{
linter.NewConfig(golinters.NewGovet(govetCfg)).
WithSSA(). // TODO: extract from the linter config and don't build SSA, just use LoadAllSyntax mode
WithDepsTypeInfo().
WithPresets(linter.PresetBugs).
WithSpeed(4).
WithAlternativeNames("vet", "vetshadow").
WithURL("https://golang.org/cmd/vet/"),
linter.NewConfig(golinters.NewBodyclose()).
WithDepsTypeInfo().
WithSSA().
WithPresets(linter.PresetPerformance, linter.PresetBugs).
WithSpeed(4).
@ -106,21 +107,25 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
WithURL("https://github.com/golang/lint"),
linter.NewConfig(golinters.NewStaticcheck()).
WithDepsTypeInfo().
WithSSA().
WithPresets(linter.PresetBugs).
WithSpeed(2).
WithURL("https://staticcheck.io/"),
linter.NewConfig(golinters.NewUnused()).
WithDepsTypeInfo().
WithSSA().
WithPresets(linter.PresetUnused).
WithSpeed(5).
WithURL("https://github.com/dominikh/go-tools/tree/master/cmd/unused"),
linter.NewConfig(golinters.NewGosimple()).
WithDepsTypeInfo().
WithSSA().
WithPresets(linter.PresetStyle).
WithSpeed(5).
WithURL("https://github.com/dominikh/go-tools/tree/master/cmd/gosimple"),
linter.NewConfig(golinters.NewStylecheck()).
WithDepsTypeInfo().
WithSSA().
WithPresets(linter.PresetStyle).
WithSpeed(5).
@ -143,6 +148,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
WithSpeed(10).
WithURL("https://github.com/opennota/check"),
linter.NewConfig(golinters.Interfacer{}).
WithDepsTypeInfo().
WithSSA().
WithPresets(linter.PresetStyle).
WithSpeed(6).
@ -211,6 +217,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
linter.NewConfig(golinters.Unparam{}).
WithPresets(linter.PresetUnused).
WithSpeed(3).
WithDepsTypeInfo().
WithSSA().
WithURL("https://github.com/mvdan/unparam"),
linter.NewConfig(golinters.Nakedret{}).

View File

@ -53,7 +53,7 @@ func NewContextLoader(cfg *config.Config, log logutils.Log, goenv *goutil.Env,
func (cl ContextLoader) prepareBuildContext() {
// Set GOROOT to have working cross-compilation: cross-compiled binaries
// have invalid GOROOT. XXX: can't use runtime.GOROOT().
goroot := cl.goenv.Get("GOROOT")
goroot := cl.goenv.Get(goutil.EnvGoRoot)
if goroot == "" {
return
}
@ -149,7 +149,7 @@ func (cl ContextLoader) findLoadMode(linters []*linter.Config) packages.LoadMode
if lc.NeedsTypeInfo {
loadMode |= packages.NeedImports | packages.NeedTypes | packages.NeedTypesSizes | packages.NeedTypesInfo | packages.NeedSyntax
}
if lc.NeedsSSARepr {
if lc.NeedsDepsTypeInfo {
loadMode |= packages.NeedDeps
}
}
@ -349,6 +349,15 @@ func (cl ContextLoader) filterDuplicatePackages(pkgs []*packages.Package) []*pac
return retPkgs
}
func needSSA(linters []*linter.Config) bool {
for _, lc := range linters {
if lc.NeedsSSARepr {
return true
}
}
return false
}
//nolint:gocyclo
func (cl ContextLoader) Load(ctx context.Context, linters []*linter.Config) (*linter.Context, error) {
loadMode := cl.findLoadMode(linters)
@ -369,7 +378,7 @@ func (cl ContextLoader) Load(ctx context.Context, linters []*linter.Config) (*li
}
var ssaProg *ssa.Program
if loadMode&packages.NeedDeps != 0 {
if needSSA(linters) {
ssaProg = cl.buildSSAProgram(deduplicatedPkgs)
}

View File

@ -30,9 +30,10 @@ func Debug(tag string) DebugFunc {
return nopDebugf
}
logger := NewStderrLog(tag)
logger.SetLevel(LogLevelDebug)
return func(format string, args ...interface{}) {
logger := NewStderrLog(tag)
logger.SetLevel(LogLevelDebug)
logger.Debugf(format, args...)
}
}

View File

@ -3,6 +3,7 @@ package logutils
import (
"fmt"
"os"
"time"
"github.com/sirupsen/logrus" //nolint:depguard
@ -36,9 +37,15 @@ func NewStderrLog(name string) *StderrLog {
}
sl.logger.Out = StdErr
sl.logger.Formatter = &logrus.TextFormatter{
formatter := &logrus.TextFormatter{
DisableTimestamp: true, // `INFO[0007] msg` -> `INFO msg`
}
if os.Getenv("LOG_TIMESTAMP") == "1" {
formatter.DisableTimestamp = false
formatter.FullTimestamp = true
formatter.TimestampFormat = time.StampMilli
}
sl.logger.Formatter = formatter
return sl
}

View File

@ -18,7 +18,7 @@ var _ Processor = Cgo{}
func NewCgo(goenv *goutil.Env) *Cgo {
return &Cgo{
goCacheDir: goenv.Get("GOCACHE"),
goCacheDir: goenv.Get(goutil.EnvGoCache),
}
}

View File

@ -25,7 +25,7 @@ func getEnabledByDefaultFastLintersExcept(except ...string) []string {
ebdl := m.GetAllEnabledByDefaultLinters()
ret := []string{}
for _, lc := range ebdl {
if lc.NeedsSSARepr {
if lc.NeedsDepsTypeInfo {
continue
}
@ -41,7 +41,7 @@ func getAllFastLintersWith(with ...string) []string {
linters := lintersdb.NewManager(nil).GetAllSupportedLinterConfigs()
ret := append([]string{}, with...)
for _, lc := range linters {
if lc.NeedsSSARepr {
if lc.NeedsDepsTypeInfo {
continue
}
ret = append(ret, lc.Name())
@ -64,7 +64,7 @@ func getEnabledByDefaultFastLintersWith(with ...string) []string {
ebdl := lintersdb.NewManager(nil).GetAllEnabledByDefaultLinters()
ret := append([]string{}, with...)
for _, lc := range ebdl {
if lc.NeedsSSARepr {
if lc.NeedsDepsTypeInfo {
continue
}

View File

@ -1,126 +0,0 @@
// Copyright 2019 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 atomicalign defines an Analyzer that checks for non-64-bit-aligned
// arguments to sync/atomic functions. On non-32-bit platforms, those functions
// panic if their argument variables are not 64-bit aligned. It is therefore
// the caller's responsibility to arrange for 64-bit alignment of such variables.
// See https://golang.org/pkg/sync/atomic/#pkg-note-BUG
package atomicalign
import (
"go/ast"
"go/token"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
)
var Analyzer = &analysis.Analyzer{
Name: "atomicalign",
Doc: "check for non-64-bits-aligned arguments to sync/atomic functions",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
}
func run(pass *analysis.Pass) (interface{}, error) {
if 8*pass.TypesSizes.Sizeof(types.Typ[types.Uintptr]) == 64 {
return nil, nil // 64-bit platform
}
if imports(pass.Pkg, "sync/atomic") == nil {
return nil, nil // doesn't directly import sync/atomic
}
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
(*ast.CallExpr)(nil),
}
inspect.Preorder(nodeFilter, func(node ast.Node) {
call := node.(*ast.CallExpr)
sel, ok := call.Fun.(*ast.SelectorExpr)
if !ok {
return
}
pkgIdent, ok := sel.X.(*ast.Ident)
if !ok {
return
}
pkgName, ok := pass.TypesInfo.Uses[pkgIdent].(*types.PkgName)
if !ok || pkgName.Imported().Path() != "sync/atomic" {
return
}
switch sel.Sel.Name {
case "AddInt64", "AddUint64",
"LoadInt64", "LoadUint64",
"StoreInt64", "StoreUint64",
"SwapInt64", "SwapUint64",
"CompareAndSwapInt64", "CompareAndSwapUint64":
// For all the listed functions, the expression to check is always the first function argument.
check64BitAlignment(pass, sel.Sel.Name, call.Args[0])
}
})
return nil, nil
}
func check64BitAlignment(pass *analysis.Pass, funcName string, arg ast.Expr) {
// Checks the argument is made of the address operator (&) applied to
// to a struct field (as opposed to a variable as the first word of
// uint64 and int64 variables can be relied upon to be 64-bit aligned.
unary, ok := arg.(*ast.UnaryExpr)
if !ok || unary.Op != token.AND {
return
}
// Retrieve the types.Struct in order to get the offset of the
// atomically accessed field.
sel, ok := unary.X.(*ast.SelectorExpr)
if !ok {
return
}
tvar, ok := pass.TypesInfo.Selections[sel].Obj().(*types.Var)
if !ok || !tvar.IsField() {
return
}
stype, ok := pass.TypesInfo.Types[sel.X].Type.Underlying().(*types.Struct)
if !ok {
return
}
var offset int64
var fields []*types.Var
for i := 0; i < stype.NumFields(); i++ {
f := stype.Field(i)
fields = append(fields, f)
if f == tvar {
// We're done, this is the field we were looking for,
// no need to fill the fields slice further.
offset = pass.TypesSizes.Offsetsof(fields)[i]
break
}
}
if offset&7 == 0 {
return // 64-bit aligned
}
pass.Reportf(arg.Pos(), "address of non 64-bit aligned field .%s passed to atomic.%s", tvar.Name(), funcName)
}
// imports reports whether pkg has path among its direct imports.
// It returns the imported package if so, or nil if not.
// copied from passes/cgocall.
func imports(pkg *types.Package, path string) *types.Package {
for _, imp := range pkg.Imports() {
if imp.Path() == path {
return imp
}
}
return nil
}

View File

@ -0,0 +1,75 @@
// 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.
// The errorsas package defines an Analyzer that checks that the second argument to
// errors.As is a pointer to a type implementing error.
package errorsas
import (
"go/ast"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
)
const doc = `report passing non-pointer or non-error values to errors.As
The errorsas analysis reports calls to errors.As where the type
of the second argument is not a pointer to a type implementing error.`
var Analyzer = &analysis.Analyzer{
Name: "errorsas",
Doc: doc,
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
}
func run(pass *analysis.Pass) (interface{}, error) {
switch pass.Pkg.Path() {
case "errors", "errors_test":
// These packages know how to use their own APIs.
// Sometimes they are testing what happens to incorrect programs.
return nil, nil
}
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
(*ast.CallExpr)(nil),
}
inspect.Preorder(nodeFilter, func(n ast.Node) {
call := n.(*ast.CallExpr)
fn := typeutil.StaticCallee(pass.TypesInfo, call)
if fn == nil {
return // not a static call
}
if len(call.Args) < 2 {
return // not enough arguments, e.g. called with return values of another function
}
if fn.FullName() == "errors.As" && !pointerToInterfaceOrError(pass, call.Args[1]) {
pass.Reportf(call.Pos(), "second argument to errors.As must be a pointer to an interface or a type implementing error")
}
})
return nil, nil
}
var errorType = types.Universe.Lookup("error").Type().Underlying().(*types.Interface)
// pointerToInterfaceOrError reports whether the type of e is a pointer to an interface or a type implementing error,
// or is the empty interface.
func pointerToInterfaceOrError(pass *analysis.Pass, e ast.Expr) bool {
t := pass.TypesInfo.Types[e].Type
if it, ok := t.Underlying().(*types.Interface); ok && it.NumMethods() == 0 {
return true
}
pt, ok := t.Underlying().(*types.Pointer)
if !ok {
return false
}
_, ok = pt.Elem().Underlying().(*types.Interface)
return ok || types.Implements(pt.Elem(), errorType)
}

View File

@ -112,19 +112,33 @@ func goListDriver(cfg *Config, patterns ...string) (*driverResponse, error) {
// start fetching rootDirs
var info goInfo
var rootDirsReady, envReady = make(chan struct{}), make(chan struct{})
go func() {
info.rootDirs = determineRootDirs(cfg)
close(rootDirsReady)
}()
go func() {
info.env = determineEnv(cfg)
close(envReady)
}()
getGoInfo := func() *goInfo {
<-rootDirsReady
<-envReady
return &info
var getGoInfo func() *goInfo
if len(cfg.Overlay) != 0 {
// Both of determineEnv and determineRootDirs calls take 100-200ms on MacBook Pro.
// Optimize: right now they are needed in most cases only for overlay processing.
var rootDirsReady, envReady = make(chan struct{}), make(chan struct{})
go func() {
info.rootDirs = determineRootDirs(cfg)
close(rootDirsReady)
}()
go func() {
info.env = determineEnv(cfg)
close(envReady)
}()
getGoInfo = func() *goInfo {
<-rootDirsReady
<-envReady
return &info
}
} else {
var determineRootDirsOnce sync.Once
getGoInfo = func() *goInfo {
determineRootDirsOnce.Do(func() {
info.rootDirs = determineRootDirs(cfg)
})
return &info
}
}
// always pass getGoInfo to golistDriver

4
vendor/modules.txt vendored
View File

@ -202,12 +202,11 @@ golang.org/x/sys/windows
golang.org/x/text/transform
golang.org/x/text/unicode/norm
golang.org/x/text/width
# golang.org/x/tools v0.0.0-20190912215617-3720d1ec3678
# golang.org/x/tools v0.0.0-20190912215617-3720d1ec3678 => github.com/golangci/tools v0.0.0-20190914130248-e9260b99c8f1
golang.org/x/tools/go/analysis
golang.org/x/tools/go/analysis/passes/asmdecl
golang.org/x/tools/go/analysis/passes/assign
golang.org/x/tools/go/analysis/passes/atomic
golang.org/x/tools/go/analysis/passes/atomicalign
golang.org/x/tools/go/analysis/passes/bools
golang.org/x/tools/go/analysis/passes/buildssa
golang.org/x/tools/go/analysis/passes/buildtag
@ -215,6 +214,7 @@ golang.org/x/tools/go/analysis/passes/cgocall
golang.org/x/tools/go/analysis/passes/composite
golang.org/x/tools/go/analysis/passes/copylock
golang.org/x/tools/go/analysis/passes/ctrlflow
golang.org/x/tools/go/analysis/passes/errorsas
golang.org/x/tools/go/analysis/passes/httpresponse
golang.org/x/tools/go/analysis/passes/inspect
golang.org/x/tools/go/analysis/passes/internal/analysisutil