Adding some inline documentation for godoc

This commit is contained in:
Tim Kelsey 2016-08-12 14:17:28 +01:00
parent 37205e9afa
commit 223cded656
5 changed files with 53 additions and 12 deletions

View file

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// Package core holds the central scanning logic used by GAS
package core
import (
@ -26,6 +27,9 @@ import (
"strings"
)
// The Context is populated with data parsed from the source code as it is scanned.
// It is passed through to all rule functions as they are called. Rules may use
// this data in conjunction withe the encoutered AST node.
type Context struct {
FileSet *token.FileSet
Comments ast.CommentMap
@ -35,12 +39,17 @@ type Context struct {
Config map[string]interface{}
}
// The Rule interface used by all rules supported by GAS.
type Rule interface {
Match(ast.Node, *Context) (*Issue, error)
}
// A RuleSet maps lists of rules to the type of AST node they should be run on.
// The anaylzer will only invoke rules contained in the list associated with the
// type of AST node it is currently visiting.
type RuleSet map[reflect.Type][]Rule
// Metrics used when reporting information about a scanning run.
type Metrics struct {
NumFiles int `json:"files"`
NumLines int `json:"lines"`
@ -48,6 +57,8 @@ type Metrics struct {
NumFound int `json:"found"`
}
// The Analyzer object is the main object of GAS. It has methods traverse an AST
// and invoke the correct checking rules as on each node as required.
type Analyzer struct {
ignoreNosec bool
ruleset RuleSet
@ -57,6 +68,7 @@ type Analyzer struct {
Stats Metrics `json:"metrics"`
}
// NewAnalyzer buildas a new anaylzer.
func NewAnalyzer(conf map[string]interface{}, logger *log.Logger) Analyzer {
if logger == nil {
logger = log.New(os.Stdout, "[gas]", 0)
@ -104,6 +116,8 @@ func (gas *Analyzer) process(filename string, source interface{}) error {
return err
}
// AddRule adds a rule into a rule set list mapped to the given AST node's type.
// The node is only needed for its type and is not otherwise used.
func (gas *Analyzer) AddRule(r Rule, n ast.Node) {
t := reflect.TypeOf(n)
if val, ok := gas.ruleset[t]; ok {
@ -113,6 +127,8 @@ func (gas *Analyzer) AddRule(r Rule, n ast.Node) {
}
}
// Process reads in a source file, convert it to an AST and traverse it.
// Rule methods added with AddRule will be invoked as necessary.
func (gas *Analyzer) Process(filename string) error {
err := gas.process(filename, nil)
fun := func(f *token.File) bool {
@ -123,6 +139,9 @@ func (gas *Analyzer) Process(filename string) error {
return err
}
// ProcessSource will convert a source code string into an AST and traverse it.
// Rule methods added with AddRule will be invoked as necessary. The string is
// identified by the filename given but no file IO will be done.
func (gas *Analyzer) ProcessSource(filename string, source string) error {
err := gas.process(filename, source)
fun := func(f *token.File) bool {
@ -133,7 +152,8 @@ func (gas *Analyzer) ProcessSource(filename string, source string) error {
return err
}
func (gas *Analyzer) Ignore(n ast.Node) bool {
// ignore a node (and sub-tree) if it is tagged with a "nosec" comment
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") {
@ -145,8 +165,10 @@ func (gas *Analyzer) Ignore(n ast.Node) bool {
return false
}
// Visit runs the GAS visitor logic over an AST created by parsing go code.
// Rule methods added with AddRule will be invoked as necessary.
func (gas *Analyzer) Visit(n ast.Node) ast.Visitor {
if !gas.Ignore(n) {
if !gas.ignore(n) {
if val, ok := gas.ruleset[reflect.TypeOf(n)]; ok {
for _, rule := range val {
ret, err := rule.Match(n, &gas.context)

View file

@ -37,6 +37,7 @@ func selectName(n ast.Node, s reflect.Type) (string, bool) {
return "", false
}
// MatchCall will match an ast.CallNode if its method name obays the given regex.
func MatchCall(n ast.Node, r *regexp.Regexp) *ast.CallExpr {
t := reflect.TypeOf(&ast.CallExpr{})
if name, ok := selectName(n, t); ok && r.MatchString(name) {
@ -45,6 +46,7 @@ func MatchCall(n ast.Node, r *regexp.Regexp) *ast.CallExpr {
return nil
}
// MatcMatchCompLit hCall will match an ast.CompositeLit if its string value obays the given regex.
func MatchCompLit(n ast.Node, r *regexp.Regexp) *ast.CompositeLit {
t := reflect.TypeOf(&ast.CompositeLit{})
if name, ok := selectName(n, t); ok && r.MatchString(name) {
@ -53,6 +55,7 @@ func MatchCompLit(n ast.Node, r *regexp.Regexp) *ast.CompositeLit {
return nil
}
// GetInt will read and return an integer value from an ast.BasicLit
func GetInt(n ast.Node) (int64, error) {
if node, ok := n.(*ast.BasicLit); ok && node.Kind == token.INT {
return strconv.ParseInt(node.Value, 0, 64)
@ -60,6 +63,7 @@ func GetInt(n ast.Node) (int64, error) {
return 0, fmt.Errorf("Unexpected AST node type: %T", n)
}
// GetInt will read and return a float value from an ast.BasicLit
func GetFloat(n ast.Node) (float64, error) {
if node, ok := n.(*ast.BasicLit); ok && node.Kind == token.FLOAT {
return strconv.ParseFloat(node.Value, 64)
@ -67,6 +71,7 @@ func GetFloat(n ast.Node) (float64, error) {
return 0.0, fmt.Errorf("Unexpected AST node type: %T", n)
}
// GetInt will read and return a char value from an ast.BasicLit
func GetChar(n ast.Node) (byte, error) {
if node, ok := n.(*ast.BasicLit); ok && node.Kind == token.CHAR {
return node.Value[0], nil
@ -74,6 +79,7 @@ func GetChar(n ast.Node) (byte, error) {
return 0, fmt.Errorf("Unexpected AST node type: %T", n)
}
// GetInt will read and return a string value from an ast.BasicLit
func GetString(n ast.Node) (string, error) {
if node, ok := n.(*ast.BasicLit); ok && node.Kind == token.STRING {
return strconv.Unquote(node.Value)

View file

@ -20,33 +20,39 @@ import (
"os"
)
// Score type used by severity and confidence values
type Score int
const (
Low Score = iota
Medium
High
Low Score = iota // Low value
Medium // Medium value
High // High value
)
// An Issue is returnd by a GAS rule if it discovers an issue with the scanned code.
type Issue struct {
Severity Score `json:"severity"`
Confidence Score `json:"confidence"`
What string `json:"details"`
File string `json:"file"`
Code string `json:"code"`
Line int `json:"line"`
Severity Score `json:"severity"` // issue severity (how problematic it is)
Confidence Score `json:"confidence"` // issue confidence (how sure we are we found it)
What string `json:"details"` // Human readable explanation
File string `json:"file"` // File name we found it in
Code string `json:"code"` // Impacted code line
Line int `json:"line"` // Line number in file
}
// MetaData is embedded in all GAS rules. The Severity, Confidence and What message
// will be passed tbhrough to reported issues.
type MetaData struct {
Severity Score
Confidence Score
What string
}
// MarshalJSON is used convert a Score object into a JSON representation
func (c Score) MarshalJSON() ([]byte, error) {
return json.Marshal(c.String())
}
// String converts a Score into a string
func (c Score) String() string {
switch c {
case High:
@ -74,6 +80,7 @@ func codeSnippet(file *os.File, start int64, end int64, n ast.Node) (string, err
return string(buf), nil
}
// NewIssue creates a new Issue
func NewIssue(ctx *Context, node ast.Node, desc string, severity Score, confidence Score) *Issue {
var code string
fobj := ctx.FileSet.File(node.Pos())

View file

@ -53,6 +53,9 @@ func resolveCallExpr(n *ast.CallExpr, c *Context) bool {
return false
}
// TryResolve will attempt, given a subtree starting at some ATS node, to resolve
// all values contained within to a known constant. It is used to check for any
// unkown values in compound expressions.
func TryResolve(n ast.Node, c *Context) bool {
switch node := n.(type) {
case *ast.BasicLit:

View file

@ -20,7 +20,7 @@ import (
"reflect"
)
// A selector function. This is like a visitor, but has a richer interface. It
// SelectFunc is like an AST visitor, but has a richer interface. It
// is called with the current ast.Node being visitied and that nodes depth in
// the tree. The function can return true to continue traversing the tree, or
// false to end traversal here.
@ -379,6 +379,9 @@ func Select(s Selector, n ast.Node, bits ...reflect.Type) {
depthWalk(n, 0, fun)
}
// SimpleSelect will try to match a path through a sub-tree starting at a given AST node.
// The type of each node in the path at a given depth must match its entry in list of
// node types given.
func SimpleSelect(n ast.Node, bits ...reflect.Type) ast.Node {
var found ast.Node
fun := func(n ast.Node, d int) bool {