12
12
// See the License for the specific language governing permissions and
13
13
// limitations under the License.
14
14
15
- // Package core holds the central scanning logic used by GAS
15
+ // Package gas holds the central scanning logic used by GAS
16
16
package gas
17
17
18
18
import (
19
19
"go/ast"
20
- "go/importer"
21
- "go/parser"
20
+ "go/build"
22
21
"go/token"
23
22
"go/types"
24
23
"log"
25
- "os"
26
24
"path"
27
25
"reflect"
28
26
"strings"
29
27
30
28
"golang.org/x/tools/go/loader"
31
29
)
32
30
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
-
48
31
// The Context is populated with data parsed from the source code as it is scanned.
49
32
// It is passed through to all rule functions as they are called. Rules may use
50
33
// this data in conjunction withe the encoutered AST node.
@@ -55,19 +38,9 @@ type Context struct {
55
38
Pkg * types.Package
56
39
Root * ast.File
57
40
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
64
42
}
65
43
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
-
71
44
// Metrics used when reporting information about a scanning run.
72
45
type Metrics struct {
73
46
NumFiles int `json:"files"`
@@ -84,134 +57,81 @@ type Analyzer struct {
84
57
context * Context
85
58
config Config
86
59
logger * log.Logger
87
- Issues []* Issue `json:"issues"`
88
- Stats * Metrics `json:"metrics"`
60
+ issues []* Issue
61
+ stats * Metrics
89
62
}
90
63
91
64
// 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 {
96
66
ignoreNoSec := false
97
67
if val , err := conf .Get ("ignoreNoSec" ); err == nil {
98
68
if override , ok := val .(bool ); ok {
99
69
ignoreNoSec = override
100
70
}
101
71
}
102
- a := Analyzer {
72
+ return & Analyzer {
103
73
ignoreNosec : ignoreNoSec ,
104
74
ruleset : make (RuleSet ),
105
75
context : & Context {},
106
76
config : conf ,
107
77
logger : logger ,
108
- Issues : make ([]* Issue , 0 , 16 ),
109
- Stats : & Metrics {0 , 0 , 0 , 0 },
78
+ issues : make ([]* Issue , 0 , 16 ),
79
+ stats : & Metrics {},
110
80
}
111
-
112
- return a
113
81
}
114
82
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 ... )
149
87
}
150
- return err
151
88
}
152
89
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 {
165
91
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
173
95
}
174
- gas .context .FileSet .Iterate (fun )
175
- return err
176
- }
177
96
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
+ }
179
102
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
189
107
}
190
- ast .Walk (gas , file )
191
- gas .Stats .NumFiles ++
192
- gas .Stats .NumLines += prog .Fset .File (file .Pos ()).LineCount ()
193
- return nil
194
- }
195
108
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
+ }
204
125
}
205
- gas .context .FileSet .Iterate (fun )
206
- return err
126
+ return nil
207
127
}
208
128
209
129
// ignore a node (and sub-tree) if it is tagged with a "#nosec" comment
210
130
func (gas * Analyzer ) ignore (n ast.Node ) bool {
211
131
if groups , ok := gas .context .Comments [n ]; ok && ! gas .ignoreNosec {
212
132
for _ , group := range groups {
213
133
if strings .Contains (group .Text (), "#nosec" ) {
214
- gas .Stats .NumNosec ++
134
+ gas .stats .NumNosec ++
215
135
return true
216
136
}
217
137
}
@@ -225,38 +145,26 @@ func (gas *Analyzer) Visit(n ast.Node) ast.Visitor {
225
145
if ! gas .ignore (n ) {
226
146
227
147
// 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 )
238
156
}
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 ++
257
160
}
258
161
}
259
162
return gas
260
163
}
261
164
return nil
262
165
}
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