sa9007.go raw

   1  package sa9007
   2  
   3  import (
   4  	"fmt"
   5  	"os"
   6  
   7  	"honnef.co/go/tools/analysis/lint"
   8  	"honnef.co/go/tools/analysis/report"
   9  	"honnef.co/go/tools/go/ir"
  10  	"honnef.co/go/tools/go/ir/irutil"
  11  	"honnef.co/go/tools/internal/passes/buildir"
  12  
  13  	"golang.org/x/tools/go/analysis"
  14  )
  15  
  16  var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
  17  	Analyzer: &analysis.Analyzer{
  18  		Name:     "SA9007",
  19  		Run:      run,
  20  		Requires: []*analysis.Analyzer{buildir.Analyzer},
  21  	},
  22  	Doc: &lint.RawDocumentation{
  23  		Title: "Deleting a directory that shouldn't be deleted",
  24  		Text: `
  25  It is virtually never correct to delete system directories such as
  26  /tmp or the user's home directory. However, it can be fairly easy to
  27  do by mistake, for example by mistakenly using \'os.TempDir\' instead
  28  of \'ioutil.TempDir\', or by forgetting to add a suffix to the result
  29  of \'os.UserHomeDir\'.
  30  
  31  Writing
  32  
  33      d := os.TempDir()
  34      defer os.RemoveAll(d)
  35  
  36  in your unit tests will have a devastating effect on the stability of your system.
  37  
  38  This check flags attempts at deleting the following directories:
  39  
  40  - os.TempDir
  41  - os.UserCacheDir
  42  - os.UserConfigDir
  43  - os.UserHomeDir
  44  `,
  45  		Since:    "2022.1",
  46  		Severity: lint.SeverityWarning,
  47  		MergeIf:  lint.MergeIfAny,
  48  	},
  49  })
  50  
  51  var Analyzer = SCAnalyzer.Analyzer
  52  
  53  func run(pass *analysis.Pass) (interface{}, error) {
  54  	for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
  55  		for _, b := range fn.Blocks {
  56  			for _, instr := range b.Instrs {
  57  				call, ok := instr.(ir.CallInstruction)
  58  				if !ok {
  59  					continue
  60  				}
  61  				if !irutil.IsCallTo(call.Common(), "os.RemoveAll") {
  62  					continue
  63  				}
  64  
  65  				kind := ""
  66  				ex := ""
  67  				callName := ""
  68  				arg := irutil.Flatten(call.Common().Args[0])
  69  				switch arg := arg.(type) {
  70  				case *ir.Call:
  71  					callName = irutil.CallName(&arg.Call)
  72  					if callName != "os.TempDir" {
  73  						continue
  74  					}
  75  					kind = "temporary"
  76  					ex = os.TempDir()
  77  				case *ir.Extract:
  78  					if arg.Index != 0 {
  79  						continue
  80  					}
  81  					first, ok := arg.Tuple.(*ir.Call)
  82  					if !ok {
  83  						continue
  84  					}
  85  					callName = irutil.CallName(&first.Call)
  86  					switch callName {
  87  					case "os.UserCacheDir":
  88  						kind = "cache"
  89  						ex, _ = os.UserCacheDir()
  90  					case "os.UserConfigDir":
  91  						kind = "config"
  92  						ex, _ = os.UserConfigDir()
  93  					case "os.UserHomeDir":
  94  						kind = "home"
  95  						ex, _ = os.UserHomeDir()
  96  					default:
  97  						continue
  98  					}
  99  				default:
 100  					continue
 101  				}
 102  
 103  				if ex == "" {
 104  					report.Report(pass, call, fmt.Sprintf("this call to os.RemoveAll deletes the user's entire %s directory, not a subdirectory therein", kind),
 105  						report.Related(arg, fmt.Sprintf("this call to %s returns the user's %s directory", callName, kind)))
 106  				} else {
 107  					report.Report(pass, call, fmt.Sprintf("this call to os.RemoveAll deletes the user's entire %s directory, not a subdirectory therein", kind),
 108  						report.Related(arg, fmt.Sprintf("this call to %s returns the user's %s directory, for example %s", callName, kind, ex)))
 109  				}
 110  			}
 111  		}
 112  	}
 113  	return nil, nil
 114  }
 115