Skip to content

Commit 81cda2f

Browse files
authored
Allow excluding analyzers globally (#1180)
* This change does not exclude analyzers for inline comment * Changed the expected issues count for G103, G109 samples for test. Previously G115 has been included in the issue count * Show analyzers IDs(G115, G602) in gosec usage help * See #1175
1 parent 18135b4 commit 81cda2f

11 files changed

+301
-26
lines changed

analyzer.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ type Analyzer struct {
182182
showIgnored bool
183183
trackSuppressions bool
184184
concurrency int
185-
analyzerList []*analysis.Analyzer
185+
analyzerSet *analyzers.AnalyzerSet
186186
mu sync.Mutex
187187
}
188188

@@ -213,7 +213,7 @@ func NewAnalyzer(conf Config, tests bool, excludeGenerated bool, trackSuppressio
213213
concurrency: concurrency,
214214
excludeGenerated: excludeGenerated,
215215
trackSuppressions: trackSuppressions,
216-
analyzerList: analyzers.BuildDefaultAnalyzers(),
216+
analyzerSet: analyzers.NewAnalyzerSet(),
217217
}
218218
}
219219

@@ -236,6 +236,15 @@ func (gosec *Analyzer) LoadRules(ruleDefinitions map[string]RuleBuilder, ruleSup
236236
}
237237
}
238238

239+
// LoadAnalyzers instantiates all the analyzers to be used when analyzing source
240+
// packages
241+
func (gosec *Analyzer) LoadAnalyzers(analyzerDefinitions map[string]analyzers.AnalyzerDefinition, analyzerSuppressed map[string]bool) {
242+
for id, def := range analyzerDefinitions {
243+
r := def.Create(def.ID, def.Description)
244+
gosec.analyzerSet.Register(r, analyzerSuppressed[id])
245+
}
246+
}
247+
239248
// Process kicks off the analysis process for a given package
240249
func (gosec *Analyzer) Process(buildTags []string, packagePaths ...string) error {
241250
config := &packages.Config{
@@ -415,7 +424,7 @@ func (gosec *Analyzer) CheckAnalyzers(pkg *packages.Package) {
415424

416425
generatedFiles := gosec.generatedFiles(pkg)
417426

418-
for _, analyzer := range gosec.analyzerList {
427+
for _, analyzer := range gosec.analyzerSet.Analyzers {
419428
pass := &analysis.Pass{
420429
Analyzer: analyzer,
421430
Fset: pkg.Fset,
@@ -666,7 +675,7 @@ func (gosec *Analyzer) getSuppressionsAtLineInFile(file string, line string, id
666675
suppressions := append(generalSuppressions, ruleSuppressions...)
667676

668677
// Track external suppressions of this rule.
669-
if gosec.ruleset.IsRuleSuppressed(id) {
678+
if gosec.ruleset.IsRuleSuppressed(id) || gosec.analyzerSet.IsSuppressed(id) {
670679
ignored = true
671680
suppressions = append(suppressions, issue.SuppressionInfo{
672681
Kind: "external",
@@ -705,4 +714,5 @@ func (gosec *Analyzer) Reset() {
705714
gosec.issues = make([]*issue.Issue, 0, 16)
706715
gosec.stats = &Metrics{}
707716
gosec.ruleset = NewRuleSet()
717+
gosec.analyzerSet = analyzers.NewAnalyzerSet()
708718
}

analyzer_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
. "github.com/onsi/ginkgo/v2"
2525
. "github.com/onsi/gomega"
2626
"github.com/securego/gosec/v2"
27+
"github.com/securego/gosec/v2/analyzers"
2728
"github.com/securego/gosec/v2/rules"
2829
"github.com/securego/gosec/v2/testutils"
2930
"golang.org/x/tools/go/packages"
@@ -1110,6 +1111,7 @@ var _ = Describe("Analyzer", func() {
11101111
It("should be able to scan generated files if NOT excluded when using the analyzes", func() {
11111112
customAnalyzer := gosec.NewAnalyzer(nil, true, false, false, 1, logger)
11121113
customAnalyzer.LoadRules(rules.Generate(false).RulesInfo())
1114+
customAnalyzer.LoadAnalyzers(analyzers.Generate(false).AnalyzersInfo())
11131115
pkg := testutils.NewTestPackage()
11141116
defer pkg.Close()
11151117
pkg.AddFile("foo.go", `
@@ -1132,6 +1134,7 @@ var _ = Describe("Analyzer", func() {
11321134
It("should be able to skip generated files if excluded when using the analyzes", func() {
11331135
customAnalyzer := gosec.NewAnalyzer(nil, true, true, false, 1, logger)
11341136
customAnalyzer.LoadRules(rules.Generate(false).RulesInfo())
1137+
customAnalyzer.LoadAnalyzers(analyzers.Generate(false).AnalyzersInfo())
11351138
pkg := testutils.NewTestPackage()
11361139
defer pkg.Close()
11371140
pkg.AddFile("foo.go", `
@@ -1499,6 +1502,44 @@ var _ = Describe("Analyzer", func() {
14991502
Expect(issues[0].Suppressions[0].Justification).To(Equal("Globally suppressed."))
15001503
})
15011504

1505+
It("should not report an error if the analyzer is not included", func() {
1506+
sample := testutils.SampleCodeG602[0]
1507+
source := sample.Code[0]
1508+
analyzer.LoadAnalyzers(analyzers.Generate(true, analyzers.NewAnalyzerFilter(false, "G115")).AnalyzersInfo())
1509+
1510+
controlPackage := testutils.NewTestPackage()
1511+
defer controlPackage.Close()
1512+
controlPackage.AddFile("cipher.go", source)
1513+
err := controlPackage.Build()
1514+
Expect(err).ShouldNot(HaveOccurred())
1515+
err = analyzer.Process(buildTags, controlPackage.Path)
1516+
Expect(err).ShouldNot(HaveOccurred())
1517+
controlIssues, _, _ := analyzer.Report()
1518+
Expect(controlIssues).Should(HaveLen(sample.Errors))
1519+
Expect(controlIssues[0].Suppressions).To(HaveLen(1))
1520+
Expect(controlIssues[0].Suppressions[0].Kind).To(Equal("external"))
1521+
Expect(controlIssues[0].Suppressions[0].Justification).To(Equal("Globally suppressed."))
1522+
})
1523+
1524+
It("should not report an error if the analyzer is excluded", func() {
1525+
sample := testutils.SampleCodeG602[0]
1526+
source := sample.Code[0]
1527+
analyzer.LoadAnalyzers(analyzers.Generate(true, analyzers.NewAnalyzerFilter(true, "G602")).AnalyzersInfo())
1528+
1529+
controlPackage := testutils.NewTestPackage()
1530+
defer controlPackage.Close()
1531+
controlPackage.AddFile("cipher.go", source)
1532+
err := controlPackage.Build()
1533+
Expect(err).ShouldNot(HaveOccurred())
1534+
err = analyzer.Process(buildTags, controlPackage.Path)
1535+
Expect(err).ShouldNot(HaveOccurred())
1536+
issues, _, _ := analyzer.Report()
1537+
Expect(issues).Should(HaveLen(sample.Errors))
1538+
Expect(issues[0].Suppressions).To(HaveLen(1))
1539+
Expect(issues[0].Suppressions[0].Kind).To(Equal("external"))
1540+
Expect(issues[0].Suppressions[0].Justification).To(Equal("Globally suppressed."))
1541+
})
1542+
15021543
It("should track multiple suppressions if the violation is multiply suppressed", func() {
15031544
sample := testutils.SampleCodeG101[0]
15041545
source := sample.Code[0]

analyzers/analyzers_set.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// (c) Copyright gosec's authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package analyzers
16+
17+
import "golang.org/x/tools/go/analysis"
18+
19+
type AnalyzerSet struct {
20+
Analyzers []*analysis.Analyzer
21+
AnalyzerSuppressedMap map[string]bool
22+
}
23+
24+
// NewAnalyzerSet constructs a new AnalyzerSet
25+
func NewAnalyzerSet() *AnalyzerSet {
26+
return &AnalyzerSet{nil, make(map[string]bool)}
27+
}
28+
29+
// Register adds a trigger for the supplied analyzer
30+
func (a *AnalyzerSet) Register(analyzer *analysis.Analyzer, isSuppressed bool) {
31+
a.Analyzers = append(a.Analyzers, analyzer)
32+
a.AnalyzerSuppressedMap[analyzer.Name] = isSuppressed
33+
}
34+
35+
// IsSuppressed will return whether the Analyzer is suppressed.
36+
func (a *AnalyzerSet) IsSuppressed(ruleID string) bool {
37+
return a.AnalyzerSuppressedMap[ruleID]
38+
}

analyzers/analyzers_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package analyzers_test
2+
3+
import (
4+
"fmt"
5+
"log"
6+
7+
. "github.com/onsi/ginkgo/v2"
8+
. "github.com/onsi/gomega"
9+
10+
"github.com/securego/gosec/v2"
11+
"github.com/securego/gosec/v2/analyzers"
12+
"github.com/securego/gosec/v2/testutils"
13+
)
14+
15+
var _ = Describe("gosec analyzers", func() {
16+
var (
17+
logger *log.Logger
18+
config gosec.Config
19+
analyzer *gosec.Analyzer
20+
runner func(string, []testutils.CodeSample)
21+
buildTags []string
22+
tests bool
23+
)
24+
25+
BeforeEach(func() {
26+
logger, _ = testutils.NewLogger()
27+
config = gosec.NewConfig()
28+
analyzer = gosec.NewAnalyzer(config, tests, false, false, 1, logger)
29+
runner = func(analyzerId string, samples []testutils.CodeSample) {
30+
for n, sample := range samples {
31+
analyzer.Reset()
32+
analyzer.SetConfig(sample.Config)
33+
analyzer.LoadAnalyzers(analyzers.Generate(false, analyzers.NewAnalyzerFilter(false, analyzerId)).AnalyzersInfo())
34+
pkg := testutils.NewTestPackage()
35+
defer pkg.Close()
36+
for i, code := range sample.Code {
37+
pkg.AddFile(fmt.Sprintf("sample_%d_%d.go", n, i), code)
38+
}
39+
err := pkg.Build()
40+
Expect(err).ShouldNot(HaveOccurred())
41+
Expect(pkg.PrintErrors()).Should(BeZero())
42+
err = analyzer.Process(buildTags, pkg.Path)
43+
Expect(err).ShouldNot(HaveOccurred())
44+
issues, _, _ := analyzer.Report()
45+
if len(issues) != sample.Errors {
46+
fmt.Println(sample.Code)
47+
}
48+
Expect(issues).Should(HaveLen(sample.Errors))
49+
}
50+
}
51+
})
52+
53+
Context("report correct errors for all samples", func() {
54+
It("should detect integer conversion overflow", func() {
55+
runner("G115", testutils.SampleCodeG115)
56+
})
57+
58+
It("should detect out of bounds slice access", func() {
59+
runner("G602", testutils.SampleCodeG602)
60+
})
61+
})
62+
})

analyzers/analyzerslist.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// (c) Copyright gosec's authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package analyzers
16+
17+
import (
18+
"golang.org/x/tools/go/analysis"
19+
)
20+
21+
// AnalyzerDefinition contains the description of an analyzer and a mechanism to
22+
// create it.
23+
type AnalyzerDefinition struct {
24+
ID string
25+
Description string
26+
Create AnalyzerBuilder
27+
}
28+
29+
// AnalyzerBuilder is used to register an analyzer definition with the analyzer
30+
type AnalyzerBuilder func(id string, description string) *analysis.Analyzer
31+
32+
// AnalyzerList contains a mapping of analyzer ID's to analyzer definitions and a mapping
33+
// of analyzer ID's to whether analyzers are suppressed.
34+
type AnalyzerList struct {
35+
Analyzers map[string]AnalyzerDefinition
36+
AnalyzerSuppressed map[string]bool
37+
}
38+
39+
// AnalyzersInfo returns all the create methods and the analyzer suppressed map for a
40+
// given list
41+
func (al *AnalyzerList) AnalyzersInfo() (map[string]AnalyzerDefinition, map[string]bool) {
42+
builders := make(map[string]AnalyzerDefinition)
43+
for _, def := range al.Analyzers {
44+
builders[def.ID] = def
45+
}
46+
return builders, al.AnalyzerSuppressed
47+
}
48+
49+
// AnalyzerFilter can be used to include or exclude an analyzer depending on the return
50+
// value of the function
51+
type AnalyzerFilter func(string) bool
52+
53+
// NewAnalyzerFilter is a closure that will include/exclude the analyzer ID's based on
54+
// the supplied boolean value.
55+
func NewAnalyzerFilter(action bool, analyzerIDs ...string) AnalyzerFilter {
56+
analyzerlist := make(map[string]bool)
57+
for _, analyzer := range analyzerIDs {
58+
analyzerlist[analyzer] = true
59+
}
60+
return func(analyzer string) bool {
61+
if _, found := analyzerlist[analyzer]; found {
62+
return action
63+
}
64+
return !action
65+
}
66+
}
67+
68+
var defaultAnalyzers = []AnalyzerDefinition{
69+
{"G115", "Type conversion which leads to integer overflow", newConversionOverflowAnalyzer},
70+
{"G602", "Possible slice bounds out of range", newSliceBoundsAnalyzer},
71+
}
72+
73+
// Generate the list of analyzers to use
74+
func Generate(trackSuppressions bool, filters ...AnalyzerFilter) *AnalyzerList {
75+
analyzerMap := make(map[string]AnalyzerDefinition)
76+
analyzerSuppressedMap := make(map[string]bool)
77+
78+
for _, analyzer := range defaultAnalyzers {
79+
analyzerSuppressedMap[analyzer.ID] = false
80+
addToAnalyzerList := true
81+
for _, filter := range filters {
82+
if filter(analyzer.ID) {
83+
analyzerSuppressedMap[analyzer.ID] = true
84+
if !trackSuppressions {
85+
addToAnalyzerList = false
86+
}
87+
}
88+
}
89+
if addToAnalyzerList {
90+
analyzerMap[analyzer.ID] = analyzer
91+
}
92+
}
93+
return &AnalyzerList{Analyzers: analyzerMap, AnalyzerSuppressed: analyzerSuppressedMap}
94+
}

analyzers/anaylzers_suite_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package analyzers_test
2+
3+
import (
4+
"testing"
5+
6+
. "github.com/onsi/ginkgo/v2"
7+
. "github.com/onsi/gomega"
8+
)
9+
10+
func TestAnalyzers(t *testing.T) {
11+
RegisterFailHandler(Fail)
12+
RunSpecs(t, "Analyzers Suite")
13+
}

analyzers/util.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,6 @@ type SSAAnalyzerResult struct {
3535
SSA *buildssa.SSA
3636
}
3737

38-
// BuildDefaultAnalyzers returns the default list of analyzers
39-
func BuildDefaultAnalyzers() []*analysis.Analyzer {
40-
return []*analysis.Analyzer{
41-
newConversionOverflowAnalyzer("G115", "Type conversion which leads to integer overflow"),
42-
newSliceBoundsAnalyzer("G602", "Possible slice bounds out of range"),
43-
}
44-
}
45-
4638
// getSSAResult retrieves the SSA result from analysis pass
4739
func getSSAResult(pass *analysis.Pass) (*SSAAnalyzerResult, error) {
4840
result, ok := pass.ResultOf[buildssa.Analyzer]

0 commit comments

Comments
 (0)