sa1019.go (8094B)
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) (any, 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 deprecatedObjName, 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 deprecatedObjName, 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 deprecatedObjName, formatGoVersion(std.DeprecatedSince), formatGoVersion(std.AlternativeAvailableSince), depr.Msg)) 111 } 112 } else { 113 report.Report(pass, node, fmt.Sprintf("%s is deprecated: %s", deprecatedObjName, depr.Msg)) 114 } 115 } 116 117 var tfn types.Object 118 stack := 0 119 120 checkIdentObj := func(sel *ast.SelectorExpr) bool { 121 obj := pass.TypesInfo.ObjectOf(sel.Sel) 122 123 if obj_, ok := obj.(*types.Func); ok { 124 obj = obj_.Origin() 125 } 126 if obj.Pkg() == nil { 127 return true 128 } 129 if obj.Pkg() == pass.Pkg { 130 // A package is allowed to use its own deprecated objects 131 return true 132 } 133 134 // A package "foo" has two related packages "foo_test" and "foo.test", for external tests and the package main 135 // generated by 'go test' respectively. "foo_test" can import and use "foo", "foo.test" imports and uses "foo" 136 // and "foo_test". 137 138 if strings.TrimSuffix(pass.Pkg.Path(), "_test") == obj.Pkg().Path() { 139 // foo_test (the external tests of foo) can use objects from foo. 140 return true 141 } 142 if strings.TrimSuffix(pass.Pkg.Path(), ".test") == obj.Pkg().Path() { 143 // foo.test (the main package of foo's tests) can use objects from foo. 144 return true 145 } 146 if strings.TrimSuffix(pass.Pkg.Path(), ".test") == strings.TrimSuffix(obj.Pkg().Path(), "_test") { 147 // foo.test (the main package of foo's tests) can use objects from foo's external tests. 148 return true 149 } 150 151 node := ast.Node(sel) 152 if pass.TypesInfo.Types[sel.X].IsType() { 153 node = sel.Sel 154 } 155 if depr, ok := deprs.Objects[obj]; ok { 156 handleDeprecation(depr, node, code.SelectorName(pass, sel), obj.Pkg().Path(), tfn) 157 } 158 return true 159 } 160 161 fn := func(node ast.Node, push bool) bool { 162 if !push { 163 stack-- 164 return false 165 } 166 stack++ 167 if stack == 1 { 168 tfn = nil 169 } 170 if fn, ok := node.(*ast.FuncDecl); ok { 171 tfn = pass.TypesInfo.ObjectOf(fn.Name) 172 } 173 174 switch v := node.(type) { 175 // FIXME(dh): this misses dot-imported objects 176 case *ast.SelectorExpr: 177 return checkIdentObj(v) 178 179 case *ast.CompositeLit: 180 litType := pass.TypesInfo.Types[v.Type] 181 if !litType.IsType() { 182 // This is probably unreachable. 183 return true 184 } 185 if _, ok := litType.Type.Underlying().(*types.Struct); !ok { 186 // We don't want to look at expressions in map initializers, for 187 // example. 188 return true 189 } 190 for _, elt := range v.Elts { 191 kv, ok := elt.(*ast.KeyValueExpr) 192 if !ok { 193 return true 194 } 195 key, ok := kv.Key.(*ast.Ident) 196 if !ok { 197 // This is probably unreachable, since we're looking at keys 198 // in a struct initializer. 199 return true 200 } 201 sel := &ast.SelectorExpr{X: v.Type, Sel: key} 202 checkIdentObj(sel) 203 } 204 } 205 return true 206 } 207 208 fn2 := func(node ast.Node) { 209 spec := node.(*ast.ImportSpec) 210 var imp *types.Package 211 if spec.Name != nil { 212 imp = pass.TypesInfo.ObjectOf(spec.Name).(*types.PkgName).Imported() 213 } else { 214 imp = pass.TypesInfo.Implicits[spec].(*types.PkgName).Imported() 215 } 216 217 p := spec.Path.Value 218 path := p[1 : len(p)-1] 219 if depr, ok := deprs.Packages[imp]; ok { 220 if path == "github.com/golang/protobuf/proto" { 221 gen, ok := code.Generator(pass, spec.Path.Pos()) 222 if ok && gen == generated.ProtocGenGo { 223 return 224 } 225 } 226 227 if strings.TrimSuffix(pass.Pkg.Path(), "_test") == path { 228 // foo_test can import foo 229 return 230 } 231 if strings.TrimSuffix(pass.Pkg.Path(), ".test") == path { 232 // foo.test can import foo 233 return 234 } 235 if strings.TrimSuffix(pass.Pkg.Path(), ".test") == strings.TrimSuffix(path, "_test") { 236 // foo.test can import foo_test 237 return 238 } 239 240 handleDeprecation(depr, spec.Path, path, path, nil) 241 } 242 } 243 pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Nodes(nil, fn) 244 code.Preorder(pass, fn2, (*ast.ImportSpec)(nil)) 245 return nil, nil 246 }