github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/tools/nogo/filter/main.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 // Binary check is the nogo entrypoint. 16 package main 17 18 import ( 19 "bytes" 20 "flag" 21 "fmt" 22 "io/ioutil" 23 "log" 24 "os" 25 "strings" 26 27 yaml "gopkg.in/yaml.v2" 28 "github.com/SagerNet/gvisor/tools/nogo" 29 "github.com/SagerNet/gvisor/tools/worker" 30 ) 31 32 type stringList []string 33 34 func (s *stringList) String() string { 35 return strings.Join(*s, ",") 36 } 37 38 func (s *stringList) Set(value string) error { 39 *s = append(*s, value) 40 return nil 41 } 42 43 var ( 44 inputFiles stringList 45 configFiles stringList 46 outputFile string 47 showConfig bool 48 check bool 49 ) 50 51 func init() { 52 flag.Var(&inputFiles, "input", "findings input files (gob format)") 53 flag.StringVar(&outputFile, "output", "", "findings output file (json format)") 54 flag.Var(&configFiles, "config", "findings configuration files") 55 flag.BoolVar(&showConfig, "show-config", false, "dump configuration only") 56 flag.BoolVar(&check, "check", false, "assume input is in json format") 57 } 58 59 func main() { 60 worker.Work(run) 61 } 62 63 var ( 64 cachedFindings = worker.NewCache("findings") // With nogo.FindingSet. 65 cachedFiltered = worker.NewCache("filtered") // With nogo.FindingSet. 66 cachedConfigs = worker.NewCache("configs") // With nogo.Config. 67 cachedFullConfigs = worker.NewCache("compiled") // With nogo.Config. 68 ) 69 70 func loadFindings(filename string) nogo.FindingSet { 71 return cachedFindings.Lookup([]string{filename}, func() worker.Sizer { 72 r, err := os.Open(filename) 73 if err != nil { 74 log.Fatalf("unable to open input %q: %v", filename, err) 75 } 76 inputFindings, err := nogo.ExtractFindingsFrom(r, check /* json */) 77 if err != nil { 78 log.Fatalf("unable to extract findings from %s: %v", filename, err) 79 } 80 return inputFindings 81 }).(nogo.FindingSet) 82 } 83 84 func loadConfig(filename string) *nogo.Config { 85 return cachedConfigs.Lookup([]string{filename}, func() worker.Sizer { 86 content, err := ioutil.ReadFile(filename) 87 if err != nil { 88 log.Fatalf("unable to read %s: %v", filename, err) 89 } 90 var newConfig nogo.Config // For current file. 91 dec := yaml.NewDecoder(bytes.NewBuffer(content)) 92 dec.SetStrict(true) 93 if err := dec.Decode(&newConfig); err != nil { 94 log.Fatalf("unable to decode %s: %v", filename, err) 95 } 96 if showConfig { 97 content, err := yaml.Marshal(&newConfig) 98 if err != nil { 99 log.Fatalf("error marshalling config: %v", err) 100 } 101 fmt.Fprintf(os.Stdout, "Loaded configuration from %s:\n%s\n", filename, string(content)) 102 } 103 return &newConfig 104 }).(*nogo.Config) 105 } 106 107 func loadConfigs(filenames []string) *nogo.Config { 108 return cachedFullConfigs.Lookup(filenames, func() worker.Sizer { 109 config := &nogo.Config{ 110 Global: make(nogo.AnalyzerConfig), 111 Analyzers: make(map[nogo.AnalyzerName]nogo.AnalyzerConfig), 112 } 113 for _, filename := range configFiles { 114 config.Merge(loadConfig(filename)) 115 if showConfig { 116 mergedBytes, err := yaml.Marshal(config) 117 if err != nil { 118 log.Fatalf("error marshalling config: %v", err) 119 } 120 fmt.Fprintf(os.Stdout, "Merged configuration:\n%s\n", string(mergedBytes)) 121 } 122 } 123 if err := config.Compile(); err != nil { 124 log.Fatalf("error compiling config: %v", err) 125 } 126 return config 127 }).(*nogo.Config) 128 } 129 130 func run([]string) int { 131 // Open and merge all configuations. 132 config := loadConfigs(configFiles) 133 if showConfig { 134 return 0 135 } 136 137 // Load and filer available findings. 138 var filteredFindings []nogo.Finding 139 for _, filename := range inputFiles { 140 // Note that this applies a caching strategy to the filtered 141 // findings, because *this is by far the most expensive part of 142 // evaluation*. The set of findings is large and applying the 143 // configuration is complex. Therefore, we segment this cache 144 // on each individual raw findings input file and the 145 // configuration files. Note that this cache is keyed on all 146 // the configuration files and each individual raw findings, so 147 // is guaranteed to be safe. This allows us to reuse the same 148 // filter result many times over, because e.g. all standard 149 // library findings will be available to all packages. 150 filteredFindings = append(filteredFindings, 151 cachedFiltered.Lookup(append(configFiles, filename), func() worker.Sizer { 152 inputFindings := loadFindings(filename) 153 filteredFindings := make(nogo.FindingSet, 0, len(inputFindings)) 154 for _, finding := range inputFindings { 155 if ok := config.ShouldReport(finding); ok { 156 filteredFindings = append(filteredFindings, finding) 157 } 158 } 159 return filteredFindings 160 }).(nogo.FindingSet)...) 161 } 162 163 // Write the output (if required). 164 // 165 // If the outputFile is specified, then we exit here. Otherwise, 166 // we continue to write to stdout and treat like a test. 167 // 168 // Note that the output of the filter is always json, which is 169 // human readable and the format that is consumed by tricorder. 170 if outputFile != "" { 171 w, err := os.OpenFile(outputFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) 172 if err != nil { 173 log.Fatalf("unable to open output file %q: %v", outputFile, err) 174 } 175 if err := nogo.WriteFindingsTo(w, filteredFindings, true /* json */); err != nil { 176 log.Fatalf("unable to write findings: %v", err) 177 } 178 return 0 179 } 180 181 // Treat the run as a test. 182 if len(filteredFindings) == 0 { 183 fmt.Fprintf(os.Stdout, "PASS\n") 184 return 0 185 } 186 for _, finding := range filteredFindings { 187 fmt.Fprintf(os.Stdout, "%s\n", finding.String()) 188 } 189 return 1 190 }