sa1019.go raw

   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