Skip to content

Commit 6943f9e

Browse files
committed
Major rework of codebase
- Get rid of 'core' and move CLI to cmd/gas directory - Migrate (most) tests to use Ginkgo and testutils framework - GAS now expects package to reside in $GOPATH - GAS now can resolve dependencies for better type checking (if package on GOPATH) - Simplified public API
1 parent f4b705a commit 6943f9e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1186
-2692
lines changed

analyzer.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@ package gas
1818
import (
1919
"go/ast"
2020
"go/build"
21+
"go/parser"
2122
"go/token"
2223
"go/types"
2324
"log"
25+
"os"
2426
"path"
2527
"reflect"
2628
"strings"
@@ -64,10 +66,11 @@ type Analyzer struct {
6466
// NewAnalyzer builds a new anaylzer.
6567
func NewAnalyzer(conf Config, logger *log.Logger) *Analyzer {
6668
ignoreNoSec := false
67-
if val, err := conf.Get("ignoreNoSec"); err == nil {
68-
if override, ok := val.(bool); ok {
69-
ignoreNoSec = override
70-
}
69+
if setting, err := conf.GetGlobal("nosec"); err == nil {
70+
ignoreNoSec = setting == "true" || setting == "enabled"
71+
}
72+
if logger == nil {
73+
logger = log.New(os.Stderr, "[gas]", log.LstdFlags)
7174
}
7275
return &Analyzer{
7376
ignoreNosec: ignoreNoSec,
@@ -94,7 +97,7 @@ func (gas *Analyzer) Process(packagePath string) error {
9497
return err
9598
}
9699

97-
packageConfig := loader.Config{Build: &build.Default}
100+
packageConfig := loader.Config{Build: &build.Default, ParserMode: parser.ParseComments}
98101
packageFiles := make([]string, 0)
99102
for _, filename := range basePackage.GoFiles {
100103
packageFiles = append(packageFiles, path.Join(packagePath, filename))
@@ -168,3 +171,10 @@ func (gas *Analyzer) Visit(n ast.Node) ast.Visitor {
168171
func (gas *Analyzer) Report() ([]*Issue, *Metrics) {
169172
return gas.issues, gas.stats
170173
}
174+
175+
// Reset clears state such as context, issues and metrics from the configured analyzer
176+
func (gas *Analyzer) Reset() {
177+
gas.context = &Context{}
178+
gas.issues = make([]*Issue, 0, 16)
179+
gas.stats = &Metrics{}
180+
}

analyzer_test.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package gas_test
2+
3+
import (
4+
"bytes"
5+
"io/ioutil"
6+
"log"
7+
"os"
8+
"strings"
9+
10+
"github.com/GoASTScanner/gas"
11+
"github.com/GoASTScanner/gas/rules"
12+
13+
"github.com/GoASTScanner/gas/testutils"
14+
. "github.com/onsi/ginkgo"
15+
. "github.com/onsi/gomega"
16+
)
17+
18+
var _ = Describe("Analyzer", func() {
19+
20+
var (
21+
analyzer *gas.Analyzer
22+
logger *log.Logger
23+
output *bytes.Buffer
24+
)
25+
BeforeEach(func() {
26+
logger, output = testutils.NewLogger()
27+
analyzer = gas.NewAnalyzer(nil, logger)
28+
})
29+
30+
Context("when processing a package", func() {
31+
32+
It("should return an error if the package contains no Go files", func() {
33+
analyzer.LoadRules(rules.Generate().Builders()...)
34+
dir, err := ioutil.TempDir("", "empty")
35+
defer os.RemoveAll(dir)
36+
Expect(err).ShouldNot(HaveOccurred())
37+
err = analyzer.Process(dir)
38+
Expect(err).Should(HaveOccurred())
39+
Expect(err.Error()).Should(MatchRegexp("no buildable Go source files"))
40+
})
41+
42+
It("should return an error if the package fails to build", func() {
43+
analyzer.LoadRules(rules.Generate().Builders()...)
44+
pkg := testutils.NewTestPackage()
45+
defer pkg.Close()
46+
pkg.AddFile("wonky.go", `func main(){ println("forgot the package")}`)
47+
pkg.Build()
48+
49+
err := analyzer.Process(pkg.Path)
50+
Expect(err).Should(HaveOccurred())
51+
Expect(err.Error()).Should(MatchRegexp(`expected 'package'`))
52+
53+
})
54+
55+
It("should find errors when nosec is not in use", func() {
56+
57+
// Rule for MD5 weak crypto usage
58+
sample := testutils.SampleCodeG401[0]
59+
source := sample.Code
60+
analyzer.LoadRules(rules.Generate(rules.NewRuleFilter(false, "G401")).Builders()...)
61+
62+
controlPackage := testutils.NewTestPackage()
63+
defer controlPackage.Close()
64+
controlPackage.AddFile("md5.go", source)
65+
controlPackage.Build()
66+
analyzer.Process(controlPackage.Path)
67+
controlIssues, _ := analyzer.Report()
68+
Expect(controlIssues).Should(HaveLen(sample.Errors))
69+
70+
})
71+
72+
It("should not report errors when a nosec comment is present", func() {
73+
// Rule for MD5 weak crypto usage
74+
sample := testutils.SampleCodeG401[0]
75+
source := sample.Code
76+
analyzer.LoadRules(rules.Generate(rules.NewRuleFilter(false, "G401")).Builders()...)
77+
78+
nosecPackage := testutils.NewTestPackage()
79+
defer nosecPackage.Close()
80+
nosecSource := strings.Replace(source, "h := md5.New()", "h := md5.New() // #nosec", 1)
81+
nosecPackage.AddFile("md5.go", nosecSource)
82+
nosecPackage.Build()
83+
84+
analyzer.Process(nosecPackage.Path)
85+
nosecIssues, _ := analyzer.Report()
86+
Expect(nosecIssues).Should(BeEmpty())
87+
})
88+
})
89+
90+
It("should be possible to overwrite nosec comments, and report issues", func() {
91+
92+
// Rule for MD5 weak crypto usage
93+
sample := testutils.SampleCodeG401[0]
94+
source := sample.Code
95+
96+
// overwrite nosec option
97+
nosecIgnoreConfig := gas.NewConfig()
98+
nosecIgnoreConfig.SetGlobal("nosec", "true")
99+
customAnalyzer := gas.NewAnalyzer(nosecIgnoreConfig, logger)
100+
customAnalyzer.LoadRules(rules.Generate(rules.NewRuleFilter(false, "G401")).Builders()...)
101+
102+
nosecPackage := testutils.NewTestPackage()
103+
defer nosecPackage.Close()
104+
nosecSource := strings.Replace(source, "h := md5.New()", "h := md5.New() // #nosec", 1)
105+
nosecPackage.AddFile("md5.go", nosecSource)
106+
nosecPackage.Build()
107+
108+
customAnalyzer.Process(nosecPackage.Path)
109+
nosecIssues, _ := customAnalyzer.Report()
110+
Expect(nosecIssues).Should(HaveLen(sample.Errors))
111+
112+
})
113+
})

call_list.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,19 +55,19 @@ func (c CallList) Contains(selector, ident string) bool {
5555

5656
/// ContainsCallExpr resolves the call expression name and type
5757
/// or package and determines if it exists within the CallList
58-
func (c CallList) ContainsCallExpr(n ast.Node, ctx *Context) bool {
58+
func (c CallList) ContainsCallExpr(n ast.Node, ctx *Context) *ast.CallExpr {
5959
selector, ident, err := GetCallInfo(n, ctx)
6060
if err != nil {
61-
return false
61+
return nil
6262
}
6363
// Try direct resolution
6464
if c.Contains(selector, ident) {
65-
return true
65+
return n.(*ast.CallExpr)
6666
}
6767

6868
// Also support explicit path
69-
if path, ok := GetImportPath(selector, ctx); ok {
70-
return c.Contains(path, ident)
69+
if path, ok := GetImportPath(selector, ctx); ok && c.Contains(path, ident) {
70+
return n.(*ast.CallExpr)
7171
}
72-
return false
72+
return nil
7373
}

call_list_test.go

Lines changed: 80 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,86 @@
1-
package gas
1+
package gas_test
22

33
import (
44
"go/ast"
5-
"testing"
5+
6+
"github.com/GoASTScanner/gas"
7+
"github.com/GoASTScanner/gas/testutils"
8+
. "github.com/onsi/ginkgo"
9+
. "github.com/onsi/gomega"
610
)
711

8-
type callListRule struct {
9-
MetaData
10-
callList CallList
11-
matched int
12-
}
13-
14-
func (r *callListRule) Match(n ast.Node, c *Context) (gi *Issue, err error) {
15-
if r.callList.ContainsCallExpr(n, c) {
16-
r.matched += 1
17-
}
18-
return nil, nil
19-
}
20-
21-
func TestCallListContainsCallExpr(t *testing.T) {
22-
config := map[string]interface{}{"ignoreNosec": false}
23-
analyzer := NewAnalyzer(config, nil)
24-
calls := NewCallList()
25-
calls.AddAll("bytes.Buffer", "Write", "WriteTo")
26-
rule := &callListRule{
27-
MetaData: MetaData{
28-
Severity: Low,
29-
Confidence: Low,
30-
What: "A dummy rule",
31-
},
32-
callList: calls,
33-
matched: 0,
34-
}
35-
analyzer.AddRule(rule, []ast.Node{(*ast.CallExpr)(nil)})
36-
source := `
37-
package main
38-
import (
39-
"bytes"
40-
"fmt"
12+
var _ = Describe("call list", func() {
13+
var (
14+
calls gas.CallList
4115
)
42-
func main() {
43-
var b bytes.Buffer
44-
b.Write([]byte("Hello "))
45-
fmt.Fprintf(&b, "world!")
46-
}`
47-
48-
analyzer.ProcessSource("dummy.go", source)
49-
if rule.matched != 1 {
50-
t.Errorf("Expected to match a bytes.Buffer.Write call")
51-
}
52-
}
53-
54-
func TestCallListContains(t *testing.T) {
55-
callList := NewCallList()
56-
callList.Add("fmt", "Printf")
57-
if !callList.Contains("fmt", "Printf") {
58-
t.Errorf("Expected call list to contain fmt.Printf")
59-
}
60-
}
16+
BeforeEach(func() {
17+
calls = gas.NewCallList()
18+
})
19+
20+
It("should not return any matches when empty", func() {
21+
Expect(calls.Contains("foo", "bar")).Should(BeFalse())
22+
})
23+
24+
It("should be possible to add a single call", func() {
25+
Expect(calls).Should(HaveLen(0))
26+
calls.Add("foo", "bar")
27+
Expect(calls).Should(HaveLen(1))
28+
29+
expected := make(map[string]bool)
30+
expected["bar"] = true
31+
actual := map[string]bool(calls["foo"])
32+
Expect(actual).Should(Equal(expected))
33+
})
34+
35+
It("should be possible to add multiple calls at once", func() {
36+
Expect(calls).Should(HaveLen(0))
37+
calls.AddAll("fmt", "Sprint", "Sprintf", "Printf", "Println")
38+
39+
expected := map[string]bool{
40+
"Sprint": true,
41+
"Sprintf": true,
42+
"Printf": true,
43+
"Println": true,
44+
}
45+
actual := map[string]bool(calls["fmt"])
46+
Expect(actual).Should(Equal(expected))
47+
})
48+
49+
It("should not return a match if none are present", func() {
50+
calls.Add("ioutil", "Copy")
51+
Expect(calls.Contains("fmt", "Println")).Should(BeFalse())
52+
})
53+
54+
It("should match a call based on selector and ident", func() {
55+
calls.Add("ioutil", "Copy")
56+
Expect(calls.Contains("ioutil", "Copy")).Should(BeTrue())
57+
})
58+
59+
It("should match a call expression", func() {
60+
61+
// Create file to be scanned
62+
pkg := testutils.NewTestPackage()
63+
defer pkg.Close()
64+
pkg.AddFile("md5.go", testutils.SampleCodeG401[0].Code)
65+
66+
ctx := pkg.CreateContext("md5.go")
67+
68+
// Search for md5.New()
69+
calls.Add("md5", "New")
70+
71+
// Stub out visitor and count number of matched call expr
72+
matched := 0
73+
v := testutils.NewMockVisitor()
74+
v.Context = ctx
75+
v.Callback = func(n ast.Node, ctx *gas.Context) bool {
76+
if _, ok := n.(*ast.CallExpr); ok && calls.ContainsCallExpr(n, ctx) != nil {
77+
matched++
78+
}
79+
return true
80+
}
81+
ast.Walk(v, ctx.Root)
82+
Expect(matched).Should(Equal(1))
83+
84+
})
85+
86+
})

0 commit comments

Comments
 (0)