Skip to content

Commit e87a1cf

Browse files
LukeShujirfag
authored andcommitted
Fix a false-positive from 'unused' (#585)
This false-positive is not present in the upstream stand-alone 'unused' 2019.1.1 program that golangci-lint uses. pkg/lint.ContextLoader.filterPackages() did two things: 1. It removed synthetic "testmain" packages (packages with .Name=="main" and .PkgPath ending with ".test") 2. It removed pruned subsumed copies of packages; if a package with files "a.go" and "a_test.go", it results in packages.Load giving us two packages: - ID=".../a" GoFiles=[a.go] - ID=".../a [.../a.test]" GoFiles=[a.go a_test.go] The first package is subsumed in the second, and leaving it around results in duplicated work, and confuses the 'deadcode' linter. However, the 'unused' linter relies on both the ".../a" and ".../a [.../a.test]" packages being present. Pruning them causes it to panic in some situations, which lead to this workaround: golangci/go-tools@af6baa5 While that workaround got it to not panic, it causes incorrect results. So, split filterPackages() in to two functions: filterTestMainPackages() and filterDuplicatePackages(). The linter.Context.Packages list only gets filterTestMainPackages() called on it, while linter.Context.Program and linter.Context.SSAProgram get both filters applied. With the source of the panic fixed, roll back a few now-unnecessary commits in go-tools.
1 parent 9ae08e9 commit e87a1cf

File tree

12 files changed

+77
-42
lines changed

12 files changed

+77
-42
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ require (
1414
github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a
1515
github.com/golangci/errcheck v0.0.0-20181003203344-ef45e06d44b6
1616
github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613
17-
github.com/golangci/go-tools v0.0.0-20180109140146-af6baa5dc196
17+
github.com/golangci/go-tools v0.0.0-20190318055746-e32c54105b7c
1818
github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3
1919
github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee
2020
github.com/golangci/gofmt v0.0.0-20181105071733-0b8337e80d98

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ github.com/golangci/errcheck v0.0.0-20181003203344-ef45e06d44b6 h1:i2jIkQFb8RG45
5959
github.com/golangci/errcheck v0.0.0-20181003203344-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0=
6060
github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613 h1:9kfjN3AdxcbsZBf8NjltjWihK2QfBBBZuv91cMFfDHw=
6161
github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8=
62-
github.com/golangci/go-tools v0.0.0-20180109140146-af6baa5dc196 h1:9rtVlONXLF1rJZzvLt4tfOXtnAFUEhxCJ64Ibzj6ECo=
63-
github.com/golangci/go-tools v0.0.0-20180109140146-af6baa5dc196/go.mod h1:unzUULGw35sjyOYjUt0jMTXqHlZPpPc6e+xfO4cd6mM=
62+
github.com/golangci/go-tools v0.0.0-20190318055746-e32c54105b7c h1:/7detzz5stiXWPzkTlPTzkBEIIE4WGpppBJYjKqBiPI=
63+
github.com/golangci/go-tools v0.0.0-20190318055746-e32c54105b7c/go.mod h1:unzUULGw35sjyOYjUt0jMTXqHlZPpPc6e+xfO4cd6mM=
6464
github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3 h1:pe9JHs3cHHDQgOFXJJdYkK6fLz2PWyYtP4hthoCMvs8=
6565
github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3/go.mod h1:JXrF4TWy4tXYn62/9x8Wm/K/dm06p8tCKwFRDPZG/1o=
6666
github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee h1:J2XAy40+7yz70uaOiMbNnluTg7gyQhtGqLQncQh+4J8=

pkg/golinters/megacheck.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -237,10 +237,11 @@ func (m megacheck) runMegacheck(workingPkgs []*packages.Package, checkExportedUn
237237
// TODO: support Ignores option
238238
}
239239

240-
return runMegacheckCheckers(checkers, opts, workingPkgs)
240+
return runMegacheckCheckers(checkers, workingPkgs, opts)
241241
}
242242

243-
// parseIgnore is a copy from megacheck code just to not fork megacheck
243+
// parseIgnore is a copy from megacheck honnef.co/go/tools/lint/lintutil.parseIgnore
244+
// just to not fork megacheck.
244245
func parseIgnore(s string) ([]lint.Ignore, error) {
245246
var out []lint.Ignore
246247
if s == "" {
@@ -258,17 +259,28 @@ func parseIgnore(s string) ([]lint.Ignore, error) {
258259
return out, nil
259260
}
260261

261-
func runMegacheckCheckers(cs []lint.Checker, opt *lintutil.Options, workingPkgs []*packages.Package) ([]lint.Problem, error) {
262+
// runMegacheckCheckers is like megacheck honnef.co/go/tools/lint/lintutil.Lint,
263+
// but takes a list of already-parsed packages instead of a list of
264+
// package-paths to parse.
265+
func runMegacheckCheckers(cs []lint.Checker, workingPkgs []*packages.Package, opt *lintutil.Options) ([]lint.Problem, error) {
262266
stats := lint.PerfStats{
263267
CheckerInits: map[string]time.Duration{},
264268
}
265269

270+
if opt == nil {
271+
opt = &lintutil.Options{}
272+
}
266273
ignores, err := parseIgnore(opt.Ignores)
267274
if err != nil {
268275
return nil, err
269276
}
270277

278+
// package-parsing elided here
279+
stats.PackageLoading = 0
280+
271281
var problems []lint.Problem
282+
// populating 'problems' with parser-problems elided here
283+
272284
if len(workingPkgs) == 0 {
273285
return problems, nil
274286
}

pkg/lint/load.go

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,6 @@ func (cl ContextLoader) makeFakeLoaderPackageInfo(pkg *packages.Package) *loader
8888
}
8989
}
9090

91-
func shouldSkipPkg(pkg *packages.Package) bool {
92-
// it's an implicit testmain package
93-
return pkg.Name == "main" && strings.HasSuffix(pkg.PkgPath, ".test")
94-
}
95-
9691
func (cl ContextLoader) makeFakeLoaderProgram(pkgs []*packages.Package) *loader.Program {
9792
var createdPkgs []*loader.PackageInfo
9893
for _, pkg := range pkgs {
@@ -296,7 +291,7 @@ func (cl ContextLoader) loadPackages(ctx context.Context, loadMode packages.Load
296291
return nil, err
297292
}
298293

299-
return cl.filterPackages(pkgs), nil
294+
return cl.filterTestMainPackages(pkgs), nil
300295
}
301296

302297
func (cl ContextLoader) tryParseTestPackage(pkg *packages.Package) (name, testName string, isTest bool) {
@@ -308,7 +303,22 @@ func (cl ContextLoader) tryParseTestPackage(pkg *packages.Package) (name, testNa
308303
return matches[1], matches[2], true
309304
}
310305

311-
func (cl ContextLoader) filterPackages(pkgs []*packages.Package) []*packages.Package {
306+
func (cl ContextLoader) filterTestMainPackages(pkgs []*packages.Package) []*packages.Package {
307+
var retPkgs []*packages.Package
308+
for _, pkg := range pkgs {
309+
if pkg.Name == "main" && strings.HasSuffix(pkg.PkgPath, ".test") {
310+
// it's an implicit testmain package
311+
cl.debugf("skip pkg ID=%s", pkg.ID)
312+
continue
313+
}
314+
315+
retPkgs = append(retPkgs, pkg)
316+
}
317+
318+
return retPkgs
319+
}
320+
321+
func (cl ContextLoader) filterDuplicatePackages(pkgs []*packages.Package) []*packages.Package {
312322
packagesWithTests := map[string]bool{}
313323
for _, pkg := range pkgs {
314324
name, _, isTest := cl.tryParseTestPackage(pkg)
@@ -322,11 +332,6 @@ func (cl ContextLoader) filterPackages(pkgs []*packages.Package) []*packages.Pac
322332

323333
var retPkgs []*packages.Package
324334
for _, pkg := range pkgs {
325-
if shouldSkipPkg(pkg) {
326-
cl.debugf("skip pkg ID=%s", pkg.ID)
327-
continue
328-
}
329-
330335
_, _, isTest := cl.tryParseTestPackage(pkg)
331336
if !isTest && packagesWithTests[pkg.PkgPath] {
332337
// If tests loading is enabled,
@@ -353,22 +358,24 @@ func (cl ContextLoader) Load(ctx context.Context, linters []*linter.Config) (*li
353358
return nil, err
354359
}
355360

356-
if len(pkgs) == 0 {
361+
deduplicatedPkgs := cl.filterDuplicatePackages(pkgs)
362+
363+
if len(deduplicatedPkgs) == 0 {
357364
return nil, exitcodes.ErrNoGoFiles
358365
}
359366

360367
var prog *loader.Program
361368
if loadMode&packages.NeedTypes != 0 {
362-
prog = cl.makeFakeLoaderProgram(pkgs)
369+
prog = cl.makeFakeLoaderProgram(deduplicatedPkgs)
363370
}
364371

365372
var ssaProg *ssa.Program
366373
if loadMode&packages.NeedDeps != 0 {
367-
ssaProg = cl.buildSSAProgram(pkgs)
374+
ssaProg = cl.buildSSAProgram(deduplicatedPkgs)
368375
}
369376

370377
astLog := cl.log.Child("astcache")
371-
astCache, err := astcache.LoadFromPackages(pkgs, astLog)
378+
astCache, err := astcache.LoadFromPackages(deduplicatedPkgs, astLog)
372379
if err != nil {
373380
return nil, err
374381
}

test/run_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,10 @@ func TestIdentifierUsedOnlyInTests(t *testing.T) {
117117
testshared.NewLintRunner(t).Run("--no-config", "--disable-all", "-Eunused", getTestDataDir("used_only_in_tests")).ExpectNoIssues()
118118
}
119119

120+
func TestUnusedCheckExported(t *testing.T) {
121+
testshared.NewLintRunner(t).Run("-c", "testdata_etc/unused_exported/golangci.yml", "testdata_etc/unused_exported/...").ExpectNoIssues()
122+
}
123+
120124
func TestConfigFileIsDetected(t *testing.T) {
121125
checkGotConfig := func(r *testshared.RunResult) {
122126
r.ExpectExitCode(exitcodes.Success).
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
linters:
2+
disable-all: true
3+
enable:
4+
- unused
5+
linters-settings:
6+
unused:
7+
check-exported: true
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package lib
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package lib
2+
3+
import (
4+
"fmt"
5+
)
6+
7+
func PublicFunc() {
8+
privateFunc()
9+
}
10+
11+
func privateFunc() {
12+
fmt.Println("side effect")
13+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package main
2+
3+
import (
4+
"github.com/golangci/golangci-lint/test/testdata_etc/unused_exported/lib"
5+
)
6+
7+
func main() {
8+
lib.PublicFunc()
9+
}

vendor/github.com/golangci/go-tools/lint/lint.go

Lines changed: 1 addition & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/golangci/go-tools/stylecheck/lint.go

Lines changed: 0 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/modules.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ github.com/golangci/errcheck/golangci
6161
github.com/golangci/errcheck/internal/errcheck
6262
# github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613
6363
github.com/golangci/go-misc/deadcode
64-
# github.com/golangci/go-tools v0.0.0-20180109140146-af6baa5dc196
64+
# github.com/golangci/go-tools v0.0.0-20190318055746-e32c54105b7c
6565
github.com/golangci/go-tools/config
6666
github.com/golangci/go-tools/lint
6767
github.com/golangci/go-tools/lint/lintutil

0 commit comments

Comments
 (0)