src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

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 }