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  	// isConversion reports whether e is a conversion T(x).
    60  	// If so, it returns T and x.
    61  	isConversion := func(curExpr inspector.Cursor) (t types.Type, x inspector.Cursor) {
    62  		e := curExpr.Node().(ast.Expr)
    63  		if conv, ok := ast.Unparen(e).(*ast.CallExpr); ok && len(conv.Args) == 1 {
    64  			if tv := pass.TypesInfo.Types[conv.Fun]; tv.IsType() {
    65  				return tv.Type, curExpr.ChildAt(edge.CallExpr_Args, 0)
    66  			}
    67  		}
    68  		return
    69  	}
    70  
    71  	// The general form is where ptr and the result are of type unsafe.Pointer:
    72  	//
    73  	// 	unsafe.Pointer(uintptr(ptr) + uintptr(n))
    74  	// =>
    75  	// 	unsafe.Add(ptr, n)
    76  
    77  	// Search for 'unsafe.Pointer(uintptr + uintptr)'
    78  	// where the left operand was converted from a pointer.
    79  	//
    80  	// (Start from sum, not conversion, as it is not
    81  	// uncommon to use a local type alias for unsafe.Pointer.)
    82  	for curSum := range inspect.Root().Preorder((*ast.BinaryExpr)(nil)) {
    83  		if sum, ok := curSum.Node().(*ast.BinaryExpr); ok &&
    84  			sum.Op == token.ADD &&
    85  			types.Identical(info.TypeOf(sum.X), types.Typ[types.Uintptr]) &&
    86  			curSum.ParentEdgeKind() == edge.CallExpr_Args {
    87  			// Have: sum ≡ T(x:...uintptr... + y:...uintptr...)
    88  			curX := curSum.ChildAt(edge.BinaryExpr_X, -1)
    89  			curY := curSum.ChildAt(edge.BinaryExpr_Y, -1)
    90  
    91  			// Is sum converted to unsafe.Pointer?
    92  			curResult := curSum.Parent()
    93  			if t, _ := isConversion(curResult); !(t != nil && types.Identical(t, tUnsafePointer)) {
    94  				continue
    95  			}
    96  			// Have: result ≡ unsafe.Pointer(x:...uintptr... + y:...uintptr...)
    97  
    98  			// Is sum.x converted from unsafe.Pointer?
    99  			_, curPtr := isConversion(curX)
   100  			if !curPtr.Valid() {
   101  				continue
   102  			}
   103  			ptr := curPtr.Node().(ast.Expr)
   104  			if !types.Identical(info.TypeOf(ptr), tUnsafePointer) {
   105  				continue
   106  			}
   107  			// Have: result ≡ unsafe.Pointer(x:uintptr(...unsafe.Pointer...) + y:...uintptr...)
   108  
   109  			file := astutil.EnclosingFile(curSum)
   110  			if !analyzerutil.FileUsesGoVersion(pass, file, versions.Go1_17) {
   111  				continue // unsafe.Add not available in this file
   112  			}
   113  
   114  			// import "unsafe"
   115  			unsafedot, edits := refactor.AddImport(info, file, "unsafe", "unsafe", "Add", sum.Pos())
   116  
   117  			// unsafe.Pointer(x + y)
   118  			// ---------------     -
   119  			//                x + y
   120  			edits = append(edits, deleteConv(curResult)...)
   121  
   122  			// uintptr   (ptr) + offset
   123  			// -----------   ----      -
   124  			// unsafe.Add(ptr,   offset)
   125  			edits = append(edits, []analysis.TextEdit{
   126  				{
   127  					Pos:     sum.Pos(),
   128  					End:     ptr.Pos(),
   129  					NewText: fmt.Appendf(nil, "%sAdd(", unsafedot),
   130  				},
   131  				{
   132  					Pos:     ptr.End(),
   133  					End:     sum.Y.Pos(),
   134  					NewText: []byte(", "),
   135  				},
   136  				{
   137  					Pos:     sum.Y.End(),
   138  					End:     sum.Y.End(),
   139  					NewText: []byte(")"),
   140  				},
   141  			}...)
   142  
   143  			// Variant: sum.y operand was converted from another integer type.
   144  			// Discard the conversion, as Add is generic over integers.
   145  			//
   146  			// e.g. unsafe.Pointer(uintptr(ptr) + uintptr(len(s)))
   147  			//                                    --------      -
   148  			//      unsafe.Add    (        ptr,           len(s))
   149  			if t, _ := isConversion(curY); t != nil && isInteger(t) {
   150  				edits = append(edits, deleteConv(curY)...)
   151  			}
   152  
   153  			pass.Report(analysis.Diagnostic{
   154  				Pos:     sum.Pos(),
   155  				End:     sum.End(),
   156  				Message: "pointer + integer can be simplified using unsafe.Add",
   157  				SuggestedFixes: []analysis.SuggestedFix{{
   158  					Message:   "Simplify pointer addition using unsafe.Add",
   159  					TextEdits: edits,
   160  				}},
   161  			})
   162  		}
   163  	}
   164  
   165  	return nil, nil
   166  }
   167  
   168  // deleteConv returns edits for changing T(x) to x, respecting precedence.
   169  func deleteConv(cur inspector.Cursor) []analysis.TextEdit {
   170  	conv := cur.Node().(*ast.CallExpr)
   171  
   172  	usesPrec := func(n ast.Node) bool {
   173  		switch n.(type) {
   174  		case *ast.BinaryExpr, *ast.UnaryExpr:
   175  			return true
   176  		}
   177  		return false
   178  	}
   179  
   180  	// Be careful not to change precedence of e.g. T(1+2) * 3.
   181  	// TODO(adonovan): refine this.
   182  	if usesPrec(cur.Parent().Node()) && usesPrec(conv.Args[0]) {
   183  		// T(x+y) * z
   184  		// -
   185  		//  (x+y) * z
   186  		return []analysis.TextEdit{{
   187  			Pos: conv.Fun.Pos(),
   188  			End: conv.Fun.End(),
   189  		}}
   190  	}
   191  
   192  	// T(x)
   193  	// -- -
   194  	//   x
   195  	return []analysis.TextEdit{
   196  		{
   197  			Pos: conv.Pos(),
   198  			End: conv.Args[0].Pos(),
   199  		},
   200  		{
   201  			Pos: conv.Args[0].End(),
   202  			End: conv.End(),
   203  		},
   204  	}
   205  }
   206  

View as plain text