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 }