1 package sa1019
2 3 import (
4 "fmt"
5 "go/ast"
6 "go/types"
7 "go/version"
8 "strings"
9 10 "honnef.co/go/tools/analysis/code"
11 "honnef.co/go/tools/analysis/facts/deprecated"
12 "honnef.co/go/tools/analysis/facts/generated"
13 "honnef.co/go/tools/analysis/lint"
14 "honnef.co/go/tools/analysis/report"
15 "honnef.co/go/tools/knowledge"
16 17 "golang.org/x/tools/go/analysis"
18 "golang.org/x/tools/go/analysis/passes/inspect"
19 "golang.org/x/tools/go/ast/inspector"
20 )
21 22 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
23 Analyzer: &analysis.Analyzer{
24 Name: "SA1019",
25 Run: run,
26 Requires: []*analysis.Analyzer{inspect.Analyzer, deprecated.Analyzer, generated.Analyzer},
27 },
28 Doc: &lint.RawDocumentation{
29 Title: `Using a deprecated function, variable, constant or field`,
30 Since: "2017.1",
31 Severity: lint.SeverityDeprecated,
32 MergeIf: lint.MergeIfAny,
33 },
34 })
35 36 var Analyzer = SCAnalyzer.Analyzer
37 38 func formatGoVersion(s string) string {
39 return "Go " + strings.TrimPrefix(s, "go")
40 }
41 42 func run(pass *analysis.Pass) (interface{}, error) {
43 deprs := pass.ResultOf[deprecated.Analyzer].(deprecated.Result)
44 45 // Selectors can appear outside of function literals, e.g. when
46 // declaring package level variables.
47 48 isStdlibPath := func(path string) bool {
49 // Modules with no dot in the first path element are reserved for the standard library and tooling.
50 // This is the best we can currently do.
51 // Nobody tells us which import paths are part of the standard library.
52 //
53 // We check the entire path instead of just the first path element, because the standard library doesn't contain paths with any dots, anyway.
54 55 return !strings.Contains(path, ".")
56 }
57 58 handleDeprecation := func(depr *deprecated.IsDeprecated, node ast.Node, deprecatedObjName string, pkgPath string, tfn types.Object) {
59 std, ok := knowledge.StdlibDeprecations[deprecatedObjName]
60 if !ok && isStdlibPath(pkgPath) {
61 // Deprecated object in the standard library, but we don't know the details of the deprecation.
62 // Don't flag it at all, to avoid flagging an object that was deprecated in 1.N when targeting 1.N-1.
63 // See https://staticcheck.dev/issues/1108 for the background on this.
64 return
65 }
66 if ok {
67 // In the past, we made use of the AlternativeAvailableSince field. If a function was deprecated in Go
68 // 1.6 and an alternative had been available in Go 1.0, then we'd recommend using the alternative even
69 // if targeting Go 1.2. The idea was to suggest writing future-proof code by using already-existing
70 // alternatives. This had a major flaw, however: the user would need to use at least Go 1.6 for
71 // Staticcheck to know that the function had been deprecated. Thus, targeting Go 1.2 and using Go 1.2
72 // would behave differently from targeting Go 1.2 and using Go 1.6. This is especially a problem if the
73 // user tries to ignore the warning. Depending on the Go version in use, the ignore directive may or may
74 // not match, causing a warning of its own.
75 //
76 // To avoid this issue, we no longer try to be smart. We now only compare the targeted version against
77 // the version that deprecated an object.
78 //
79 // Unfortunately, this issue also applies to AlternativeAvailableSince == DeprecatedNeverUse. Even though it
80 // is only applied to seriously flawed API, such as broken cryptography, users may wish to ignore those
81 // warnings.
82 //
83 // See also https://staticcheck.dev/issues/1318.
84 if version.Compare(code.StdlibVersion(pass, node), std.DeprecatedSince) == -1 {
85 return
86 }
87 }
88 89 if tfn != nil {
90 if _, ok := deprs.Objects[tfn]; ok {
91 // functions that are deprecated may use deprecated
92 // symbols
93 return
94 }
95 }
96 97 if ok {
98 switch std.AlternativeAvailableSince {
99 case knowledge.DeprecatedNeverUse:
100 report.Report(pass, node,
101 fmt.Sprintf("%s has been deprecated since %s because it shouldn't be used: %s",
102 report.Render(pass, node), formatGoVersion(std.DeprecatedSince), depr.Msg))
103 case std.DeprecatedSince, knowledge.DeprecatedUseNoLonger:
104 report.Report(pass, node,
105 fmt.Sprintf("%s has been deprecated since %s: %s",
106 report.Render(pass, node), formatGoVersion(std.DeprecatedSince), depr.Msg))
107 default:
108 report.Report(pass, node,
109 fmt.Sprintf("%s has been deprecated since %s and an alternative has been available since %s: %s",
110 report.Render(pass, node), formatGoVersion(std.DeprecatedSince), formatGoVersion(std.AlternativeAvailableSince), depr.Msg))
111 }
112 } else {
113 report.Report(pass, node, fmt.Sprintf("%s is deprecated: %s", report.Render(pass, node), depr.Msg))
114 }
115 }
116 117 var tfn types.Object
118 stack := 0
119 fn := func(node ast.Node, push bool) bool {
120 if !push {
121 stack--
122 return false
123 }
124 stack++
125 if stack == 1 {
126 tfn = nil
127 }
128 if fn, ok := node.(*ast.FuncDecl); ok {
129 tfn = pass.TypesInfo.ObjectOf(fn.Name)
130 }
131 132 // FIXME(dh): this misses dot-imported objects
133 sel, ok := node.(*ast.SelectorExpr)
134 if !ok {
135 return true
136 }
137 138 obj := pass.TypesInfo.ObjectOf(sel.Sel)
139 if obj_, ok := obj.(*types.Func); ok {
140 obj = obj_.Origin()
141 }
142 if obj.Pkg() == nil {
143 return true
144 }
145 146 if obj.Pkg() == pass.Pkg {
147 // A package is allowed to use its own deprecated objects
148 return true
149 }
150 151 // A package "foo" has two related packages "foo_test" and "foo.test", for external tests and the package main
152 // generated by 'go test' respectively. "foo_test" can import and use "foo", "foo.test" imports and uses "foo"
153 // and "foo_test".
154 155 if strings.TrimSuffix(pass.Pkg.Path(), "_test") == obj.Pkg().Path() {
156 // foo_test (the external tests of foo) can use objects from foo.
157 return true
158 }
159 if strings.TrimSuffix(pass.Pkg.Path(), ".test") == obj.Pkg().Path() {
160 // foo.test (the main package of foo's tests) can use objects from foo.
161 return true
162 }
163 if strings.TrimSuffix(pass.Pkg.Path(), ".test") == strings.TrimSuffix(obj.Pkg().Path(), "_test") {
164 // foo.test (the main package of foo's tests) can use objects from foo's external tests.
165 return true
166 }
167 168 if depr, ok := deprs.Objects[obj]; ok {
169 handleDeprecation(depr, sel, code.SelectorName(pass, sel), obj.Pkg().Path(), tfn)
170 }
171 return true
172 }
173 174 fn2 := func(node ast.Node) {
175 spec := node.(*ast.ImportSpec)
176 var imp *types.Package
177 if spec.Name != nil {
178 imp = pass.TypesInfo.ObjectOf(spec.Name).(*types.PkgName).Imported()
179 } else {
180 imp = pass.TypesInfo.Implicits[spec].(*types.PkgName).Imported()
181 }
182 183 p := spec.Path.Value
184 path := p[1 : len(p)-1]
185 if depr, ok := deprs.Packages[imp]; ok {
186 if path == "github.com/golang/protobuf/proto" {
187 gen, ok := code.Generator(pass, spec.Path.Pos())
188 if ok && gen == generated.ProtocGenGo {
189 return
190 }
191 }
192 193 if strings.TrimSuffix(pass.Pkg.Path(), "_test") == path {
194 // foo_test can import foo
195 return
196 }
197 if strings.TrimSuffix(pass.Pkg.Path(), ".test") == path {
198 // foo.test can import foo
199 return
200 }
201 if strings.TrimSuffix(pass.Pkg.Path(), ".test") == strings.TrimSuffix(path, "_test") {
202 // foo.test can import foo_test
203 return
204 }
205 206 handleDeprecation(depr, spec.Path, path, path, nil)
207 }
208 }
209 pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Nodes(nil, fn)
210 code.Preorder(pass, fn2, (*ast.ImportSpec)(nil))
211 return nil, nil
212 }
213