Skip to content

Commit 026fe4c

Browse files
committed
Simplify analyzer and command line interface
The analyzer now only handles packages rather than one off files. This simplifies the CLI functionality significantly.
1 parent 65b18da commit 026fe4c

File tree

2 files changed

+160
-331
lines changed

2 files changed

+160
-331
lines changed

analyzer.go

Lines changed: 60 additions & 152 deletions
Original file line numberDiff line numberDiff line change
@@ -12,39 +12,22 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
// Package core holds the central scanning logic used by GAS
15+
// Package gas holds the central scanning logic used by GAS
1616
package gas
1717

1818
import (
1919
"go/ast"
20-
"go/importer"
21-
"go/parser"
20+
"go/build"
2221
"go/token"
2322
"go/types"
2423
"log"
25-
"os"
2624
"path"
2725
"reflect"
2826
"strings"
2927

3028
"golang.org/x/tools/go/loader"
3129
)
3230

33-
// ImportInfo is used to track aliased and initialization only imports.
34-
type ImportInfo struct {
35-
Imported map[string]string
36-
Aliased map[string]string
37-
InitOnly map[string]bool
38-
}
39-
40-
func NewImportInfo() *ImportInfo {
41-
return &ImportInfo{
42-
make(map[string]string),
43-
make(map[string]string),
44-
make(map[string]bool),
45-
}
46-
}
47-
4831
// The Context is populated with data parsed from the source code as it is scanned.
4932
// It is passed through to all rule functions as they are called. Rules may use
5033
// this data in conjunction withe the encoutered AST node.
@@ -55,19 +38,9 @@ type Context struct {
5538
Pkg *types.Package
5639
Root *ast.File
5740
Config map[string]interface{}
58-
Imports *ImportInfo
59-
}
60-
61-
// The Rule interface used by all rules supported by GAS.
62-
type Rule interface {
63-
Match(ast.Node, *Context) (*Issue, error)
41+
Imports *ImportTracker
6442
}
6543

66-
// A RuleSet maps lists of rules to the type of AST node they should be run on.
67-
// The anaylzer will only invoke rules contained in the list associated with the
68-
// type of AST node it is currently visiting.
69-
type RuleSet map[reflect.Type][]Rule
70-
7144
// Metrics used when reporting information about a scanning run.
7245
type Metrics struct {
7346
NumFiles int `json:"files"`
@@ -84,134 +57,81 @@ type Analyzer struct {
8457
context *Context
8558
config Config
8659
logger *log.Logger
87-
Issues []*Issue `json:"issues"`
88-
Stats *Metrics `json:"metrics"`
60+
issues []*Issue
61+
stats *Metrics
8962
}
9063

9164
// NewAnalyzer builds a new anaylzer.
92-
func NewAnalyzer(conf Config, logger *log.Logger) Analyzer {
93-
if logger == nil {
94-
logger = log.New(os.Stdout, "[gas]", 0)
95-
}
65+
func NewAnalyzer(conf Config, logger *log.Logger) *Analyzer {
9666
ignoreNoSec := false
9767
if val, err := conf.Get("ignoreNoSec"); err == nil {
9868
if override, ok := val.(bool); ok {
9969
ignoreNoSec = override
10070
}
10171
}
102-
a := Analyzer{
72+
return &Analyzer{
10373
ignoreNosec: ignoreNoSec,
10474
ruleset: make(RuleSet),
10575
context: &Context{},
10676
config: conf,
10777
logger: logger,
108-
Issues: make([]*Issue, 0, 16),
109-
Stats: &Metrics{0, 0, 0, 0},
78+
issues: make([]*Issue, 0, 16),
79+
stats: &Metrics{},
11080
}
111-
112-
return a
11381
}
11482

115-
func (gas *Analyzer) process(filename string, source interface{}) error {
116-
mode := parser.ParseComments
117-
gas.context.FileSet = token.NewFileSet()
118-
root, err := parser.ParseFile(gas.context.FileSet, filename, source, mode)
119-
if err == nil {
120-
gas.context.Config = gas.config
121-
gas.context.Comments = ast.NewCommentMap(gas.context.FileSet, root, root.Comments)
122-
gas.context.Root = root
123-
124-
// here we get type info
125-
gas.context.Info = &types.Info{
126-
Types: make(map[ast.Expr]types.TypeAndValue),
127-
Defs: make(map[*ast.Ident]types.Object),
128-
Uses: make(map[*ast.Ident]types.Object),
129-
Selections: make(map[*ast.SelectorExpr]*types.Selection),
130-
Scopes: make(map[ast.Node]*types.Scope),
131-
Implicits: make(map[ast.Node]types.Object),
132-
}
133-
134-
conf := types.Config{Importer: importer.Default()}
135-
gas.context.Pkg, err = conf.Check("pkg", gas.context.FileSet, []*ast.File{root}, gas.context.Info)
136-
if err != nil {
137-
// TODO(gm) Type checker not currently considering all files within a package
138-
// see: issue #113
139-
gas.logger.Printf(`Error during type checking: "%s"`, err)
140-
err = nil
141-
}
142-
143-
gas.context.Imports = NewImportInfo()
144-
for _, pkg := range gas.context.Pkg.Imports() {
145-
gas.context.Imports.Imported[pkg.Path()] = pkg.Name()
146-
}
147-
ast.Walk(gas, root)
148-
gas.Stats.NumFiles++
83+
func (gas *Analyzer) LoadRules(ruleDefinitions ...RuleBuilder) {
84+
for _, builder := range ruleDefinitions {
85+
r, nodes := builder(gas.config)
86+
gas.ruleset.Register(r, nodes...)
14987
}
150-
return err
15188
}
15289

153-
// AddRule adds a rule into a rule set list mapped to the given AST node's type.
154-
// The node is only needed for its type and is not otherwise used.
155-
func (gas *Analyzer) AddRule(r Rule, nodes []ast.Node) {
156-
for _, n := range nodes {
157-
t := reflect.TypeOf(n)
158-
if val, ok := gas.ruleset[t]; ok {
159-
gas.ruleset[t] = append(val, r)
160-
} else {
161-
gas.ruleset[t] = []Rule{r}
162-
}
163-
}
164-
}
90+
func (gas *Analyzer) Process(packagePath string) error {
16591

166-
// Process reads in a source file, convert it to an AST and traverse it.
167-
// Rule methods added with AddRule will be invoked as necessary.
168-
func (gas *Analyzer) Process(filename string) error {
169-
err := gas.process(filename, nil)
170-
fun := func(f *token.File) bool {
171-
gas.Stats.NumLines += f.LineCount()
172-
return true
92+
basePackage, err := build.Default.ImportDir(packagePath, build.ImportComment)
93+
if err != nil {
94+
return err
17395
}
174-
gas.context.FileSet.Iterate(fun)
175-
return err
176-
}
17796

178-
func (gas *Analyzer) ProcessPackage(prog *loader.Program, pkg *loader.PackageInfo, file *ast.File) error {
97+
packageConfig := loader.Config{Build: &build.Default}
98+
packageFiles := make([]string, 0)
99+
for _, filename := range basePackage.GoFiles {
100+
packageFiles = append(packageFiles, path.Join(packagePath, filename))
101+
}
179102

180-
gas.context.FileSet = prog.Fset
181-
gas.context.Config = gas.config
182-
gas.context.Comments = ast.NewCommentMap(gas.context.FileSet, file, file.Comments)
183-
gas.context.Root = file
184-
gas.context.Info = &pkg.Info
185-
gas.context.Pkg = pkg.Pkg
186-
gas.context.Imports = NewImportInfo()
187-
for _, imported := range gas.context.Pkg.Imports() {
188-
gas.context.Imports.Imported[imported.Path()] = imported.Name()
103+
packageConfig.CreateFromFilenames(basePackage.Name, packageFiles...)
104+
builtPackage, err := packageConfig.Load()
105+
if err != nil {
106+
return err
189107
}
190-
ast.Walk(gas, file)
191-
gas.Stats.NumFiles++
192-
gas.Stats.NumLines += prog.Fset.File(file.Pos()).LineCount()
193-
return nil
194-
}
195108

196-
// ProcessSource will convert a source code string into an AST and traverse it.
197-
// Rule methods added with AddRule will be invoked as necessary. The string is
198-
// identified by the filename given but no file IO will be done.
199-
func (gas *Analyzer) ProcessSource(filename string, source string) error {
200-
err := gas.process(filename, source)
201-
fun := func(f *token.File) bool {
202-
gas.Stats.NumLines += f.LineCount()
203-
return true
109+
for _, pkg := range builtPackage.Created {
110+
gas.logger.Println("Checking package:", pkg.String())
111+
for _, file := range pkg.Files {
112+
gas.logger.Println("Checking file:", builtPackage.Fset.File(file.Pos()).Name())
113+
gas.context.FileSet = builtPackage.Fset
114+
gas.context.Config = gas.config
115+
gas.context.Comments = ast.NewCommentMap(gas.context.FileSet, file, file.Comments)
116+
gas.context.Root = file
117+
gas.context.Info = &pkg.Info
118+
gas.context.Pkg = pkg.Pkg
119+
gas.context.Imports = NewImportTracker()
120+
gas.context.Imports.TrackPackages(gas.context.Pkg.Imports()...)
121+
ast.Walk(gas, file)
122+
gas.stats.NumFiles++
123+
gas.stats.NumLines += builtPackage.Fset.File(file.Pos()).LineCount()
124+
}
204125
}
205-
gas.context.FileSet.Iterate(fun)
206-
return err
126+
return nil
207127
}
208128

209129
// ignore a node (and sub-tree) if it is tagged with a "#nosec" comment
210130
func (gas *Analyzer) ignore(n ast.Node) bool {
211131
if groups, ok := gas.context.Comments[n]; ok && !gas.ignoreNosec {
212132
for _, group := range groups {
213133
if strings.Contains(group.Text(), "#nosec") {
214-
gas.Stats.NumNosec++
134+
gas.stats.NumNosec++
215135
return true
216136
}
217137
}
@@ -225,38 +145,26 @@ func (gas *Analyzer) Visit(n ast.Node) ast.Visitor {
225145
if !gas.ignore(n) {
226146

227147
// Track aliased and initialization imports
228-
if imported, ok := n.(*ast.ImportSpec); ok {
229-
path := strings.Trim(imported.Path.Value, `"`)
230-
if imported.Name != nil {
231-
if imported.Name.Name == "_" {
232-
// Initialization import
233-
gas.context.Imports.InitOnly[path] = true
234-
} else {
235-
// Aliased import
236-
gas.context.Imports.Aliased[path] = imported.Name.Name
237-
}
148+
gas.context.Imports.TrackImport(n)
149+
150+
for _, rule := range gas.ruleset.RegisteredFor(n) {
151+
issue, err := rule.Match(n, gas.context)
152+
if err != nil {
153+
file, line := GetLocation(n, gas.context)
154+
file = path.Base(file)
155+
gas.logger.Printf("Rule error: %v => %s (%s:%d)\n", reflect.TypeOf(rule), err, file, line)
238156
}
239-
// unsafe is not included in Package.Imports()
240-
if path == "unsafe" {
241-
gas.context.Imports.Imported[path] = path
242-
}
243-
}
244-
245-
if val, ok := gas.ruleset[reflect.TypeOf(n)]; ok {
246-
for _, rule := range val {
247-
ret, err := rule.Match(n, gas.context)
248-
if err != nil {
249-
file, line := GetLocation(n, gas.context)
250-
file = path.Base(file)
251-
gas.logger.Printf("Rule error: %v => %s (%s:%d)\n", reflect.TypeOf(rule), err, file, line)
252-
}
253-
if ret != nil {
254-
gas.Issues = append(gas.Issues, ret)
255-
gas.Stats.NumFound++
256-
}
157+
if issue != nil {
158+
gas.issues = append(gas.issues, issue)
159+
gas.stats.NumFound++
257160
}
258161
}
259162
return gas
260163
}
261164
return nil
262165
}
166+
167+
// Report returns the current issues discovered and the metrics about the scan
168+
func (gas *Analyzer) Report() ([]*Issue, *Metrics) {
169+
return gas.issues, gas.stats
170+
}

0 commit comments

Comments
 (0)