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  }