2020-04-21 03:57:02 +00:00
|
|
|
// Copyright 2019 The gVisor Authors.
|
|
|
|
//
|
|
|
|
// 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 nogo
|
|
|
|
|
|
|
|
import (
|
2020-10-26 18:09:34 +00:00
|
|
|
"fmt"
|
|
|
|
"regexp"
|
2020-04-21 03:57:02 +00:00
|
|
|
)
|
|
|
|
|
2020-10-26 18:09:34 +00:00
|
|
|
// GroupName is a named group.
|
|
|
|
type GroupName string
|
|
|
|
|
|
|
|
// AnalyzerName is a named analyzer.
|
|
|
|
type AnalyzerName string
|
|
|
|
|
|
|
|
// Group represents a named collection of files.
|
|
|
|
type Group struct {
|
|
|
|
// Name is the short name for the group.
|
|
|
|
Name GroupName `yaml:"name"`
|
|
|
|
|
|
|
|
// Regex matches all full paths in the group.
|
|
|
|
Regex string `yaml:"regex"`
|
|
|
|
regex *regexp.Regexp `yaml:"-"`
|
|
|
|
|
|
|
|
// Default determines the default group behavior.
|
|
|
|
//
|
|
|
|
// If Default is true, all Analyzers are enabled for this
|
|
|
|
// group. Otherwise, Analyzers must be individually enabled
|
|
|
|
// by specifying a (possible empty) ItemConfig for the group
|
|
|
|
// in the AnalyzerConfig.
|
|
|
|
Default bool `yaml:"default"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *Group) compile() error {
|
|
|
|
r, err := regexp.Compile(g.Regex)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
g.regex = r
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ItemConfig is an (Analyzer,Group) configuration.
|
|
|
|
type ItemConfig struct {
|
|
|
|
// Exclude are analyzer exclusions.
|
|
|
|
//
|
|
|
|
// Exclude is a list of regular expressions. If the corresponding
|
|
|
|
// Analyzer emits a Finding for which Finding.Position.String()
|
|
|
|
// matches a regular expression in Exclude, the finding will not
|
|
|
|
// be reported.
|
|
|
|
Exclude []string `yaml:"exclude,omitempty"`
|
|
|
|
exclude []*regexp.Regexp `yaml:"-"`
|
|
|
|
|
|
|
|
// Suppress are analyzer suppressions.
|
|
|
|
//
|
|
|
|
// Suppress is a list of regular expressions. If the corresponding
|
|
|
|
// Analyzer emits a Finding for which Finding.Message matches a regular
|
|
|
|
// expression in Suppress, the finding will not be reported.
|
|
|
|
Suppress []string `yaml:"suppress,omitempty"`
|
|
|
|
suppress []*regexp.Regexp `yaml:"-"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func compileRegexps(ss []string, rs *[]*regexp.Regexp) error {
|
|
|
|
*rs = make([]*regexp.Regexp, 0, len(ss))
|
|
|
|
for _, s := range ss {
|
|
|
|
r, err := regexp.Compile(s)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
*rs = append(*rs, r)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *ItemConfig) compile() error {
|
|
|
|
if i == nil {
|
|
|
|
// This may be nil if nothing is included in the
|
|
|
|
// item configuration. That's fine, there's nothing
|
|
|
|
// to compile and nothing to exclude & suppress.
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if err := compileRegexps(i.Exclude, &i.exclude); err != nil {
|
|
|
|
return fmt.Errorf("in exclude: %w", err)
|
|
|
|
}
|
|
|
|
if err := compileRegexps(i.Suppress, &i.suppress); err != nil {
|
|
|
|
return fmt.Errorf("in suppress: %w", err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *ItemConfig) merge(other *ItemConfig) {
|
|
|
|
i.Exclude = append(i.Exclude, other.Exclude...)
|
|
|
|
i.Suppress = append(i.Suppress, other.Suppress...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *ItemConfig) shouldReport(fullPos, msg string) bool {
|
|
|
|
if i == nil {
|
|
|
|
// See above.
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
for _, r := range i.exclude {
|
|
|
|
if r.MatchString(fullPos) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, r := range i.suppress {
|
|
|
|
if r.MatchString(msg) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// AnalyzerConfig is the configuration for a single analyzers.
|
|
|
|
//
|
|
|
|
// This map is keyed by individual Group names, to allow for different
|
|
|
|
// configurations depending on what Group the file belongs to.
|
|
|
|
type AnalyzerConfig map[GroupName]*ItemConfig
|
|
|
|
|
|
|
|
func (a AnalyzerConfig) compile() error {
|
|
|
|
for name, gc := range a {
|
|
|
|
if err := gc.compile(); err != nil {
|
|
|
|
return fmt.Errorf("invalid group %q: %v", name, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a AnalyzerConfig) merge(other AnalyzerConfig) {
|
|
|
|
// Merge all the groups.
|
|
|
|
for name, gc := range other {
|
|
|
|
old, ok := a[name]
|
|
|
|
if !ok || old == nil {
|
|
|
|
a[name] = gc // Not configured in a.
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
old.merge(gc)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a AnalyzerConfig) shouldReport(groupConfig *Group, fullPos, msg string) bool {
|
|
|
|
gc, ok := a[groupConfig.Name]
|
|
|
|
if !ok {
|
|
|
|
return groupConfig.Default
|
|
|
|
}
|
|
|
|
|
|
|
|
// Note that if a section appears for a particular group
|
|
|
|
// for a particular analyzer, then it will now be enabled,
|
|
|
|
// and the group default no longer applies.
|
|
|
|
return gc.shouldReport(fullPos, msg)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Config is a nogo configuration.
|
|
|
|
type Config struct {
|
|
|
|
// Prefixes defines a set of regular expressions that
|
|
|
|
// are standard "prefixes", so that files can be grouped
|
|
|
|
// and specific rules applied to individual groups.
|
|
|
|
Groups []Group `yaml:"groups"`
|
2020-08-26 21:40:30 +00:00
|
|
|
|
2020-10-26 18:09:34 +00:00
|
|
|
// Global is the global analyzer config.
|
|
|
|
Global AnalyzerConfig `yaml:"global"`
|
2020-04-21 03:57:02 +00:00
|
|
|
|
2020-10-26 18:09:34 +00:00
|
|
|
// Analyzers are individual analyzer configurations. The
|
|
|
|
// key for each analyzer is the name of the analyzer. The
|
|
|
|
// value is either a boolean (enable/disable), or a map to
|
|
|
|
// the groups above.
|
|
|
|
Analyzers map[AnalyzerName]AnalyzerConfig `yaml:"analyzers"`
|
2020-04-21 03:57:02 +00:00
|
|
|
}
|
2020-09-01 00:07:35 +00:00
|
|
|
|
2020-10-26 18:09:34 +00:00
|
|
|
// Merge merges two configurations.
|
|
|
|
func (c *Config) Merge(other *Config) {
|
|
|
|
// Merge all groups.
|
|
|
|
for _, g := range other.Groups {
|
|
|
|
// Is there a matching group? If yes, we just delete
|
|
|
|
// it. This will preserve the order provided in the
|
|
|
|
// overriding file, even if it differs.
|
|
|
|
for i := 0; i < len(c.Groups); i++ {
|
|
|
|
if g.Name == c.Groups[i].Name {
|
|
|
|
copy(c.Groups[i:], c.Groups[i+1:])
|
|
|
|
c.Groups = c.Groups[:len(c.Groups)-1]
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
c.Groups = append(c.Groups, g)
|
|
|
|
}
|
2020-10-08 01:27:00 +00:00
|
|
|
|
2020-10-26 18:09:34 +00:00
|
|
|
// Merge global configurations.
|
|
|
|
c.Global.merge(other.Global)
|
2020-10-08 01:27:00 +00:00
|
|
|
|
2020-10-26 18:09:34 +00:00
|
|
|
// Merge all analyzer configurations.
|
|
|
|
for name, ac := range other.Analyzers {
|
|
|
|
old, ok := c.Analyzers[name]
|
|
|
|
if !ok {
|
|
|
|
c.Analyzers[name] = ac // No analyzer in original config.
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
old.merge(ac)
|
|
|
|
}
|
|
|
|
}
|
2020-10-08 01:27:00 +00:00
|
|
|
|
2020-10-26 18:09:34 +00:00
|
|
|
// Compile compiles a configuration to make it useable.
|
|
|
|
func (c *Config) Compile() error {
|
|
|
|
for i := 0; i < len(c.Groups); i++ {
|
|
|
|
if err := c.Groups[i].compile(); err != nil {
|
|
|
|
return fmt.Errorf("invalid group %q: %w", c.Groups[i].Name, err)
|
|
|
|
}
|
2020-10-08 01:27:00 +00:00
|
|
|
}
|
2020-10-26 18:09:34 +00:00
|
|
|
if err := c.Global.compile(); err != nil {
|
|
|
|
return fmt.Errorf("invalid global: %w", err)
|
2020-10-08 01:27:00 +00:00
|
|
|
}
|
2020-10-26 18:09:34 +00:00
|
|
|
for name, ac := range c.Analyzers {
|
|
|
|
if err := ac.compile(); err != nil {
|
|
|
|
return fmt.Errorf("invalid analyzer %q: %w", name, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
2020-10-08 01:27:00 +00:00
|
|
|
}
|
|
|
|
|
2020-10-26 18:09:34 +00:00
|
|
|
// ShouldReport returns true iff the finding should match the Config.
|
|
|
|
func (c *Config) ShouldReport(finding Finding) bool {
|
|
|
|
fullPos := finding.Position.String()
|
|
|
|
|
|
|
|
// Find the matching group.
|
|
|
|
var groupConfig *Group
|
|
|
|
for i := 0; i < len(c.Groups); i++ {
|
|
|
|
if c.Groups[i].regex.MatchString(fullPos) {
|
|
|
|
groupConfig = &c.Groups[i]
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If there is no group matching this path, then
|
|
|
|
// we default to accept the finding.
|
|
|
|
if groupConfig == nil {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// Suppress via global rule?
|
|
|
|
if !c.Global.shouldReport(groupConfig, fullPos, finding.Message) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// Try the analyzer config.
|
|
|
|
ac, ok := c.Analyzers[finding.Category]
|
|
|
|
if !ok {
|
|
|
|
return groupConfig.Default
|
|
|
|
}
|
|
|
|
return ac.shouldReport(groupConfig, fullPos, finding.Message)
|
2020-09-01 00:07:35 +00:00
|
|
|
}
|