gosec/core/analyzer.go
Tim Kelsey d2d49f1c8c Try to resolve all elements in an expression to a known const
This is used in the subprocess launching test but will be added to
others as applicable.

This also closes #28
2016-08-03 17:21:48 +01:00

175 lines
4.3 KiB
Go

// (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
}