Source file src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/unsafefuncs.go

     1  // Copyright 2025 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package modernize
     6  
     7  import (
     8  	"fmt"
     9  	"go/ast"
    10  	"go/token"
    11  	"go/types"
    12  
    13  	"golang.org/x/tools/go/analysis"
    14  	"golang.org/x/tools/go/analysis/passes/inspect"
    15  	"golang.org/x/tools/go/ast/edge"
    16  	"golang.org/x/tools/go/ast/inspector"
    17  	"golang.org/x/tools/internal/analysis/analyzerutil"
    18  	"golang.org/x/tools/internal/astutil"
    19  	"golang.org/x/tools/internal/goplsexport"
    20  	"golang.org/x/tools/internal/refactor"
    21  	"golang.org/x/tools/internal/typesinternal"
    22  	"golang.org/x/tools/internal/versions"
    23  )
    24  
    25  // TODO(adonovan): also support:
    26  //
    27  // func String(ptr *byte, len IntegerType) string
    28  // func StringData(str string) *byte
    29  // func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType
    30  // func SliceData(slice []ArbitraryType) *ArbitraryType
    31  
    32  var unsafeFuncsAnalyzer = &analysis.Analyzer{
    33  	Name:     "unsafefuncs",
    34  	Doc:      analyzerutil.MustExtractDoc(doc, "unsafefuncs"),
    35  	Requires: []*analysis.Analyzer{inspect.Analyzer},
    36  	Run:      unsafefuncs,
    37  	URL:      "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#unsafefuncs",
    38  }
    39  
    40  func init() {
    41  	// Export to gopls until this is a published modernizer.
    42  	goplsexport.UnsafeFuncsModernizer = unsafeFuncsAnalyzer
    43  }
    44  
    45  func unsafefuncs(pass *analysis.Pass) (any, error) {
    46  	// Short circuit if the package doesn't use unsafe.
    47  	// (In theory one could use some imported alias of unsafe.Pointer,
    48  	// but let's ignore that.)
    49  	if !typesinternal.Imports(pass.Pkg, "unsafe") {
    50  		return nil, nil
    51  	}
    52  
    53  	var (
    54  		inspect        = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
    55  		info           = pass.TypesInfo
    56  		tUnsafePointer = types.Typ[types.UnsafePointer]
    57  	)
    58  
    59  	isInteger := func(t types.Type) bool {
    60  		basic, ok := t.Underlying().(*types.Basic)
    61  		return ok && basic.Info()&types.IsInteger != 0
    62  	}
    63  
    64  	// isConversion reports whether e is a conversion T(x).
    65  	// If so, it returns T and x.
    66  	isConversion := func(curExpr inspector.Cursor) (t types.Type, x inspector.Cursor) {
    67  		e := curExpr.Node().(ast.Expr)
    68  		if conv, ok := ast.Unparen(e).(*ast.CallExpr); ok && len(conv.Args) == 1 {
    69  			if tv := pass.TypesInfo.Types[conv.Fun]; tv.IsType() {
    70  				return tv.Type, curExpr.ChildAt(edge.CallExpr_Args, 0)
    71  			}
    72  		}
    73  		return
    74  	}
    75  
    76  	// The general form is where ptr and the result are of type unsafe.Pointer:
    77  	//
    78  	// 	unsafe.Pointer(uintptr(ptr) + uintptr(n))
    79  	// =>
    80  	// 	unsafe.Add(ptr, n)
    81  
    82  	// Search for 'unsafe.Pointer(uintptr + uintptr)'
    83  	// where the left operand was converted from a pointer.
    84  	//
    85  	// (Start from sum, not conversion, as it is not
    86  	// uncommon to use a local type alias for unsafe.Pointer.)
    87  	for curSum := range inspect.Root().Preorder((*ast.BinaryExpr)(nil)) {
    88  		if sum, ok := curSum.Node().(*ast.BinaryExpr); ok &&
    89  			sum.Op == token.ADD &&
    90  			types.Identical(info.TypeOf(sum.X), types.Typ[types.Uintptr]) &&
    91  			astutil.IsChildOf(curSum, edge.CallExpr_Args) {
    92  			// Have: sum ≡ T(x:...uintptr... + y:...uintptr...)
    93  			curX := curSum.ChildAt(edge.BinaryExpr_X, -1)
    94  			curY := curSum.ChildAt(edge.BinaryExpr_Y, -1)
    95  
    96  			// Is sum converted to unsafe.Pointer?
    97  			curResult := curSum.Parent()
    98  			if t, _ := isConversion(curResult); !(t != nil && types.Identical(t, tUnsafePointer)) {
    99  				continue
   100  			}
   101  			// Have: result ≡ unsafe.Pointer(x:...uintptr... + y:...uintptr...)
   102  
   103  			// Is sum.x converted from unsafe.Pointer?
   104  			_, curPtr := isConversion(curX)
   105  			if !astutil.CursorValid(curPtr) {
   106  				continue
   107  			}
   108  			ptr := curPtr.Node().(ast.Expr)
   109  			if !types.Identical(info.TypeOf(ptr), tUnsafePointer) {
   110  				continue
   111  			}
   112  			// Have: result ≡ unsafe.Pointer(x:uintptr(...unsafe.Pointer...) + y:...uintptr...)
   113  
   114  			file := astutil.EnclosingFile(curSum)
   115  			if !analyzerutil.FileUsesGoVersion(pass, file, versions.Go1_17) {
   116  				continue // unsafe.Add not available in this file
   117  			}
   118  
   119  			// import "unsafe"
   120  			unsafedot, edits := refactor.AddImport(info, file, "unsafe", "unsafe", "Add", sum.Pos())
   121  
   122  			// unsafe.Pointer(x + y)
   123  			// ---------------     -
   124  			//                x + y
   125  			edits = append(edits, deleteConv(curResult)...)
   126  
   127  			// uintptr   (ptr) + offset
   128  			// -----------   ----      -
   129  			// unsafe.Add(ptr,   offset)
   130  			edits = append(edits, []analysis.TextEdit{
   131  				{
   132  					Pos:     sum.Pos(),
   133  					End:     ptr.Pos(),
   134  					NewText: fmt.Appendf(nil, "%sAdd(", unsafedot),
   135  				},
   136  				{
   137  					Pos:     ptr.End(),
   138  					End:     sum.Y.Pos(),
   139  					NewText: []byte(", "),
   140  				},
   141  				{
   142  					Pos:     sum.Y.End(),
   143  					End:     sum.Y.End(),
   144  					NewText: []byte(")"),
   145  				},
   146  			}...)
   147  
   148  			// Variant: sum.y operand was converted from another integer type.
   149  			// Discard the conversion, as Add is generic over integers.
   150  			//
   151  			// e.g. unsafe.Pointer(uintptr(ptr) + uintptr(len(s)))
   152  			//                                    --------      -
   153  			//      unsafe.Add    (        ptr,           len(s))
   154  			if t, _ := isConversion(curY); t != nil && isInteger(t) {
   155  				edits = append(edits, deleteConv(curY)...)
   156  			}
   157  
   158  			pass.Report(analysis.Diagnostic{
   159  				Pos:     sum.Pos(),
   160  				End:     sum.End(),
   161  				Message: "pointer + integer can be simplified using unsafe.Add",
   162  				SuggestedFixes: []analysis.SuggestedFix{{
   163  					Message:   "Simplify pointer addition using unsafe.Add",
   164  					TextEdits: edits,
   165  				}},
   166  			})
   167  		}
   168  	}
   169  
   170  	return nil, nil
   171  }
   172  
   173  // deleteConv returns edits for changing T(x) to x, respecting precedence.
   174  func deleteConv(cur inspector.Cursor) []analysis.TextEdit {
   175  	conv := cur.Node().(*ast.CallExpr)
   176  
   177  	usesPrec := func(n ast.Node) bool {
   178  		switch n.(type) {
   179  		case *ast.BinaryExpr, *ast.UnaryExpr:
   180  			return true
   181  		}
   182  		return false
   183  	}
   184  
   185  	// Be careful not to change precedence of e.g. T(1+2) * 3.
   186  	// TODO(adonovan): refine this.
   187  	if usesPrec(cur.Parent().Node()) && usesPrec(conv.Args[0]) {
   188  		// T(x+y) * z
   189  		// -
   190  		//  (x+y) * z
   191  		return []analysis.TextEdit{{
   192  			Pos: conv.Fun.Pos(),
   193  			End: conv.Fun.End(),
   194  		}}
   195  	}
   196  
   197  	// T(x)
   198  	// -- -
   199  	//   x
   200  	return []analysis.TextEdit{
   201  		{
   202  			Pos: conv.Pos(),
   203  			End: conv.Args[0].Pos(),
   204  		},
   205  		{
   206  			Pos: conv.Args[0].End(),
   207  			End: conv.End(),
   208  		},
   209  	}
   210  }
   211  

View as plain text