// (c) Copyright 2016 Hewlett Packard Enterprise Development LP // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package core import ( "encoding/json" "go/ast" "go/importer" "go/parser" "go/token" "go/types" "io/ioutil" "log" "os" "reflect" "strings" ) type Context struct { FileSet *token.FileSet Comments ast.CommentMap Info *types.Info Pkg *types.Package } type Rule interface { Match(ast.Node, *Context) (*Issue, error) } type RuleSet map[reflect.Type][]Rule type Metrics struct { NumFiles int `json:"files"` NumLines int `json:"lines"` NumNosec int `json:"nosec"` NumFound int `json:"found"` } type Analyzer struct { ignoreNosec bool ruleset RuleSet context Context logger *log.Logger Issues []Issue `json:"issues"` Stats Metrics `json:"metrics"` Config map[string]interface{} } func NewAnalyzer(ignoreNosec bool, conf *string, logger *log.Logger) Analyzer { if logger == nil { logger = log.New(os.Stdout, "[gas]", 0) } a := Analyzer{ ignoreNosec: ignoreNosec, ruleset: make(RuleSet), Issues: make([]Issue, 0), context: Context{token.NewFileSet(), nil, nil, nil}, logger: logger, Config: nil, } if conf != nil && *conf != "" { // if we have a config if data, err := ioutil.ReadFile(*conf); err == nil { if err := json.Unmarshal(data, &(a.Config)); err != nil { logger.Fatal("Could not parse JSON config: ", *conf, ": ", err) } } else { logger.Fatal("Could not read config file: ", *conf) } } return a } func (gas *Analyzer) process(filename string, source interface{}) error { mode := parser.ParseComments root, err := parser.ParseFile(gas.context.FileSet, filename, source, mode) if err == nil { gas.context.Comments = ast.NewCommentMap(gas.context.FileSet, root, root.Comments) // here we get type info gas.context.Info = &types.Info{ Types: make(map[ast.Expr]types.TypeAndValue), Defs: make(map[*ast.Ident]types.Object), Uses: make(map[*ast.Ident]types.Object), Selections: make(map[*ast.SelectorExpr]*types.Selection), Scopes: make(map[ast.Node]*types.Scope), Implicits: make(map[ast.Node]types.Object), } conf := types.Config{Importer: importer.Default()} gas.context.Pkg, _ = conf.Check("pkg", gas.context.FileSet, []*ast.File{root}, gas.context.Info) if err != nil { gas.logger.Println("failed to check imports") return err } ast.Walk(gas, root) gas.Stats.NumFiles++ } return err } func (gas *Analyzer) AddRule(r Rule, n ast.Node) { t := reflect.TypeOf(n) if val, ok := gas.ruleset[t]; ok { gas.ruleset[t] = append(val, r) } else { gas.ruleset[t] = []Rule{r} } } func (gas *Analyzer) Process(filename string) error { err := gas.process(filename, nil) fun := func(f *token.File) bool { gas.Stats.NumLines += f.LineCount() return true } gas.context.FileSet.Iterate(fun) return err } func (gas *Analyzer) ProcessSource(filename string, source string) error { err := gas.process(filename, source) fun := func(f *token.File) bool { gas.Stats.NumLines += f.LineCount() return true } gas.context.FileSet.Iterate(fun) return err } func (gas *Analyzer) Ignore(n ast.Node) bool { if groups, ok := gas.context.Comments[n]; ok && !gas.ignoreNosec { for _, group := range groups { if strings.Contains(group.Text(), "nosec") { gas.Stats.NumNosec++ return true } } } return false } func (gas *Analyzer) Visit(n ast.Node) ast.Visitor { if !gas.Ignore(n) { if val, ok := gas.ruleset[reflect.TypeOf(n)]; ok { for _, rule := range val { ret, err := rule.Match(n, &gas.context) if err != nil { // will want to give more info than this ... gas.logger.Println("internal error running rule:", err) } if ret != nil { gas.Issues = append(gas.Issues, *ret) gas.Stats.NumFound++ } } } return gas } return nil }