github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/staticcheck/sa9007/sa9007.go (about)

     1  package sa9007
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  
     7  	"github.com/amarpal/go-tools/analysis/lint"
     8  	"github.com/amarpal/go-tools/analysis/report"
     9  	"github.com/amarpal/go-tools/go/ir"
    10  	"github.com/amarpal/go-tools/go/ir/irutil"
    11  	"github.com/amarpal/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.Documentation{
    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 mistakingly 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  }