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