github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/tools/nogo/config.go (about)

     1  // Copyright 2019 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package nogo
    16  
    17  import (
    18  	"fmt"
    19  	"regexp"
    20  )
    21  
    22  // GroupName is a named group.
    23  type GroupName string
    24  
    25  // AnalyzerName is a named analyzer.
    26  type AnalyzerName string
    27  
    28  // Group represents a named collection of files.
    29  type Group struct {
    30  	// Name is the short name for the group.
    31  	Name GroupName `yaml:"name"`
    32  
    33  	// Regex matches all full paths in the group.
    34  	Regex string         `yaml:"regex"`
    35  	regex *regexp.Regexp `yaml:"-"`
    36  
    37  	// Default determines the default group behavior.
    38  	//
    39  	// If Default is true, all Analyzers are enabled for this
    40  	// group. Otherwise, Analyzers must be individually enabled
    41  	// by specifying a (possible empty) ItemConfig for the group
    42  	// in the AnalyzerConfig.
    43  	Default bool `yaml:"default"`
    44  }
    45  
    46  func (g *Group) compile() error {
    47  	r, err := regexp.Compile(g.Regex)
    48  	if err != nil {
    49  		return err
    50  	}
    51  	g.regex = r
    52  	return nil
    53  }
    54  
    55  // ItemConfig is an (Analyzer,Group) configuration.
    56  type ItemConfig struct {
    57  	// Exclude are analyzer exclusions.
    58  	//
    59  	// Exclude is a list of regular expressions. If the corresponding
    60  	// Analyzer emits a Finding for which Finding.Position.String()
    61  	// matches a regular expression in Exclude, the finding will not
    62  	// be reported.
    63  	Exclude []string         `yaml:"exclude,omitempty"`
    64  	exclude []*regexp.Regexp `yaml:"-"`
    65  
    66  	// Suppress are analyzer suppressions.
    67  	//
    68  	// Suppress is a list of regular expressions. If the corresponding
    69  	// Analyzer emits a Finding for which Finding.Message matches a regular
    70  	// expression in Suppress, the finding will not be reported.
    71  	Suppress []string         `yaml:"suppress,omitempty"`
    72  	suppress []*regexp.Regexp `yaml:"-"`
    73  }
    74  
    75  func compileRegexps(ss []string, rs *[]*regexp.Regexp) error {
    76  	*rs = make([]*regexp.Regexp, len(ss))
    77  	for i, s := range ss {
    78  		r, err := regexp.Compile(s)
    79  		if err != nil {
    80  			return err
    81  		}
    82  		(*rs)[i] = r
    83  	}
    84  	return nil
    85  }
    86  
    87  // RegexpCount is used by AnalyzerConfig.RegexpCount.
    88  func (i *ItemConfig) RegexpCount() int64 {
    89  	if i == nil {
    90  		// See compile.
    91  		return 0
    92  	}
    93  	// Return the number of regular expressions compiled for these items.
    94  	// This is how the cache size of the configuration is measured.
    95  	return int64(len(i.exclude) + len(i.suppress))
    96  }
    97  
    98  func (i *ItemConfig) compile() error {
    99  	if i == nil {
   100  		// This may be nil if nothing is included in the
   101  		// item configuration. That's fine, there's nothing
   102  		// to compile and nothing to exclude & suppress.
   103  		return nil
   104  	}
   105  	if err := compileRegexps(i.Exclude, &i.exclude); err != nil {
   106  		return fmt.Errorf("in exclude: %w", err)
   107  	}
   108  	if err := compileRegexps(i.Suppress, &i.suppress); err != nil {
   109  		return fmt.Errorf("in suppress: %w", err)
   110  	}
   111  	return nil
   112  }
   113  
   114  func merge(a, b []string) []string {
   115  	found := make(map[string]struct{})
   116  	result := make([]string, 0, len(a)+len(b))
   117  	for _, elem := range a {
   118  		found[elem] = struct{}{}
   119  		result = append(result, elem)
   120  	}
   121  	for _, elem := range b {
   122  		if _, ok := found[elem]; ok {
   123  			continue
   124  		}
   125  		result = append(result, elem)
   126  	}
   127  	return result
   128  }
   129  
   130  func (i *ItemConfig) merge(other *ItemConfig) {
   131  	i.Exclude = merge(i.Exclude, other.Exclude)
   132  	i.Suppress = merge(i.Suppress, other.Suppress)
   133  }
   134  
   135  func (i *ItemConfig) shouldReport(fullPos, msg string) bool {
   136  	if i == nil {
   137  		// See above.
   138  		return true
   139  	}
   140  	for _, r := range i.exclude {
   141  		if r.MatchString(fullPos) {
   142  			return false
   143  		}
   144  	}
   145  	for _, r := range i.suppress {
   146  		if r.MatchString(msg) {
   147  			return false
   148  		}
   149  	}
   150  	return true
   151  }
   152  
   153  // AnalyzerConfig is the configuration for a single analyzers.
   154  //
   155  // This map is keyed by individual Group names, to allow for different
   156  // configurations depending on what Group the file belongs to.
   157  type AnalyzerConfig map[GroupName]*ItemConfig
   158  
   159  // RegexpCount is used by Config.Size.
   160  func (a AnalyzerConfig) RegexpCount() int64 {
   161  	count := int64(0)
   162  	for _, gc := range a {
   163  		count += gc.RegexpCount()
   164  	}
   165  	return count
   166  }
   167  
   168  func (a AnalyzerConfig) compile() error {
   169  	for name, gc := range a {
   170  		if err := gc.compile(); err != nil {
   171  			return fmt.Errorf("invalid group %q: %v", name, err)
   172  		}
   173  	}
   174  	return nil
   175  }
   176  
   177  func (a AnalyzerConfig) merge(other AnalyzerConfig) {
   178  	// Merge all the groups.
   179  	for name, gc := range other {
   180  		old, ok := a[name]
   181  		if !ok || old == nil {
   182  			a[name] = gc // Not configured in a.
   183  			continue
   184  		}
   185  		old.merge(gc)
   186  	}
   187  }
   188  
   189  func (a AnalyzerConfig) shouldReport(groupConfig *Group, fullPos, msg string) bool {
   190  	gc, ok := a[groupConfig.Name]
   191  	if !ok {
   192  		return groupConfig.Default
   193  	}
   194  
   195  	// Note that if a section appears for a particular group
   196  	// for a particular analyzer, then it will now be enabled,
   197  	// and the group default no longer applies.
   198  	return gc.shouldReport(fullPos, msg)
   199  }
   200  
   201  // Config is a nogo configuration.
   202  type Config struct {
   203  	// Prefixes defines a set of regular expressions that
   204  	// are standard "prefixes", so that files can be grouped
   205  	// and specific rules applied to individual groups.
   206  	Groups []Group `yaml:"groups"`
   207  
   208  	// Global is the global analyzer config.
   209  	Global AnalyzerConfig `yaml:"global"`
   210  
   211  	// Analyzers are individual analyzer configurations. The
   212  	// key for each analyzer is the name of the analyzer. The
   213  	// value is either a boolean (enable/disable), or a map to
   214  	// the groups above.
   215  	Analyzers map[AnalyzerName]AnalyzerConfig `yaml:"analyzers"`
   216  }
   217  
   218  // Size implements worker.Sizer.Size.
   219  func (c *Config) Size() int64 {
   220  	count := c.Global.RegexpCount()
   221  	for _, config := range c.Analyzers {
   222  		count += config.RegexpCount()
   223  	}
   224  	// The size is measured as the number of regexps that are compiled
   225  	// here. We multiply by 1k to produce an estimate.
   226  	return 1024 * count
   227  }
   228  
   229  // Merge merges two configurations.
   230  func (c *Config) Merge(other *Config) {
   231  	// Merge all groups.
   232  	//
   233  	// Select the other first, as the order provided in the second will
   234  	// provide precendence over the same group defined in the first one.
   235  	seenGroups := make(map[GroupName]struct{})
   236  	newGroups := make([]Group, 0, len(c.Groups)+len(other.Groups))
   237  	for _, g := range other.Groups {
   238  		newGroups = append(newGroups, g)
   239  		seenGroups[g.Name] = struct{}{}
   240  	}
   241  	for _, g := range c.Groups {
   242  		if _, ok := seenGroups[g.Name]; ok {
   243  			continue
   244  		}
   245  		newGroups = append(newGroups, g)
   246  	}
   247  	c.Groups = newGroups
   248  
   249  	// Merge global configurations.
   250  	c.Global.merge(other.Global)
   251  
   252  	// Merge all analyzer configurations.
   253  	for name, ac := range other.Analyzers {
   254  		old, ok := c.Analyzers[name]
   255  		if !ok {
   256  			c.Analyzers[name] = ac // No analyzer in original config.
   257  			continue
   258  		}
   259  		old.merge(ac)
   260  	}
   261  }
   262  
   263  // Compile compiles a configuration to make it useable.
   264  func (c *Config) Compile() error {
   265  	for i := 0; i < len(c.Groups); i++ {
   266  		if err := c.Groups[i].compile(); err != nil {
   267  			return fmt.Errorf("invalid group %q: %w", c.Groups[i].Name, err)
   268  		}
   269  	}
   270  	if err := c.Global.compile(); err != nil {
   271  		return fmt.Errorf("invalid global: %w", err)
   272  	}
   273  	for name, ac := range c.Analyzers {
   274  		if err := ac.compile(); err != nil {
   275  			return fmt.Errorf("invalid analyzer %q: %w", name, err)
   276  		}
   277  	}
   278  	return nil
   279  }
   280  
   281  // ShouldReport returns true iff the finding should match the Config.
   282  func (c *Config) ShouldReport(finding Finding) bool {
   283  	fullPos := finding.Position.String()
   284  
   285  	// Find the matching group.
   286  	var groupConfig *Group
   287  	for i := 0; i < len(c.Groups); i++ {
   288  		if c.Groups[i].regex.MatchString(fullPos) {
   289  			groupConfig = &c.Groups[i]
   290  			break
   291  		}
   292  	}
   293  
   294  	// If there is no group matching this path, then
   295  	// we default to accept the finding.
   296  	if groupConfig == nil {
   297  		return true
   298  	}
   299  
   300  	// Suppress via global rule?
   301  	if !c.Global.shouldReport(groupConfig, fullPos, finding.Message) {
   302  		return false
   303  	}
   304  
   305  	// Try the analyzer config.
   306  	ac, ok := c.Analyzers[finding.Category]
   307  	if !ok {
   308  		return groupConfig.Default
   309  	}
   310  	return ac.shouldReport(groupConfig, fullPos, finding.Message)
   311  }