...

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

Documentation: cmd/vendor/golang.org/x/tools/go/analysis/passes/cgocall

     1  // Copyright 2015 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 cgocall defines an Analyzer that detects some violations of
     6  // the cgo pointer passing rules.
     7  package cgocall
     8  
     9  import (
    10  	"fmt"
    11  	"go/ast"
    12  	"go/format"
    13  	"go/parser"
    14  	"go/token"
    15  	"go/types"
    16  	"log"
    17  	"os"
    18  	"strconv"
    19  
    20  	"golang.org/x/tools/go/analysis"
    21  	"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
    22  	"golang.org/x/tools/go/ast/astutil"
    23  )
    24  
    25  const debug = false
    26  
    27  const Doc = `detect some violations of the cgo pointer passing rules
    28  
    29  Check for invalid cgo pointer passing.
    30  This looks for code that uses cgo to call C code passing values
    31  whose types are almost always invalid according to the cgo pointer
    32  sharing rules.
    33  Specifically, it warns about attempts to pass a Go chan, map, func,
    34  or slice to C, either directly, or via a pointer, array, or struct.`
    35  
    36  var Analyzer = &analysis.Analyzer{
    37  	Name:             "cgocall",
    38  	Doc:              Doc,
    39  	URL:              "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/cgocall",
    40  	RunDespiteErrors: true,
    41  	Run:              run,
    42  }
    43  
    44  func run(pass *analysis.Pass) (interface{}, error) {
    45  	if !analysisutil.Imports(pass.Pkg, "runtime/cgo") {
    46  		return nil, nil // doesn't use cgo
    47  	}
    48  
    49  	cgofiles, info, err := typeCheckCgoSourceFiles(pass.Fset, pass.Pkg, pass.Files, pass.TypesInfo, pass.TypesSizes)
    50  	if err != nil {
    51  		return nil, err
    52  	}
    53  	for _, f := range cgofiles {
    54  		checkCgo(pass.Fset, f, info, pass.Reportf)
    55  	}
    56  	return nil, nil
    57  }
    58  
    59  func checkCgo(fset *token.FileSet, f *ast.File, info *types.Info, reportf func(token.Pos, string, ...interface{})) {
    60  	ast.Inspect(f, func(n ast.Node) bool {
    61  		call, ok := n.(*ast.CallExpr)
    62  		if !ok {
    63  			return true
    64  		}
    65  
    66  		// Is this a C.f() call?
    67  		var name string
    68  		if sel, ok := astutil.Unparen(call.Fun).(*ast.SelectorExpr); ok {
    69  			if id, ok := sel.X.(*ast.Ident); ok && id.Name == "C" {
    70  				name = sel.Sel.Name
    71  			}
    72  		}
    73  		if name == "" {
    74  			return true // not a call we need to check
    75  		}
    76  
    77  		// A call to C.CBytes passes a pointer but is always safe.
    78  		if name == "CBytes" {
    79  			return true
    80  		}
    81  
    82  		if debug {
    83  			log.Printf("%s: call to C.%s", fset.Position(call.Lparen), name)
    84  		}
    85  
    86  		for _, arg := range call.Args {
    87  			if !typeOKForCgoCall(cgoBaseType(info, arg), make(map[types.Type]bool)) {
    88  				reportf(arg.Pos(), "possibly passing Go type with embedded pointer to C")
    89  				break
    90  			}
    91  
    92  			// Check for passing the address of a bad type.
    93  			if conv, ok := arg.(*ast.CallExpr); ok && len(conv.Args) == 1 &&
    94  				isUnsafePointer(info, conv.Fun) {
    95  				arg = conv.Args[0]
    96  			}
    97  			if u, ok := arg.(*ast.UnaryExpr); ok && u.Op == token.AND {
    98  				if !typeOKForCgoCall(cgoBaseType(info, u.X), make(map[types.Type]bool)) {
    99  					reportf(arg.Pos(), "possibly passing Go type with embedded pointer to C")
   100  					break
   101  				}
   102  			}
   103  		}
   104  		return true
   105  	})
   106  }
   107  
   108  // typeCheckCgoSourceFiles returns type-checked syntax trees for the raw
   109  // cgo files of a package (those that import "C"). Such files are not
   110  // Go, so there may be gaps in type information around C.f references.
   111  //
   112  // This checker was initially written in vet to inspect raw cgo source
   113  // files using partial type information. However, Analyzers in the new
   114  // analysis API are presented with the type-checked, "cooked" Go ASTs
   115  // resulting from cgo-processing files, so we must choose between
   116  // working with the cooked file generated by cgo (which was tried but
   117  // proved fragile) or locating the raw cgo file (e.g. from //line
   118  // directives) and working with that, as we now do.
   119  //
   120  // Specifically, we must type-check the raw cgo source files (or at
   121  // least the subtrees needed for this analyzer) in an environment that
   122  // simulates the rest of the already type-checked package.
   123  //
   124  // For example, for each raw cgo source file in the original package,
   125  // such as this one:
   126  //
   127  //	package p
   128  //	import "C"
   129  //	import "fmt"
   130  //	type T int
   131  //	const k = 3
   132  //	var x, y = fmt.Println()
   133  //	func f() { ... }
   134  //	func g() { ... C.malloc(k) ... }
   135  //	func (T) f(int) string { ... }
   136  //
   137  // we synthesize a new ast.File, shown below, that dot-imports the
   138  // original "cooked" package using a special name ("·this·"), so that all
   139  // references to package members resolve correctly. (References to
   140  // unexported names cause an "unexported" error, which we ignore.)
   141  //
   142  // To avoid shadowing names imported from the cooked package,
   143  // package-level declarations in the new source file are modified so
   144  // that they do not declare any names.
   145  // (The cgocall analysis is concerned with uses, not declarations.)
   146  // Specifically, type declarations are discarded;
   147  // all names in each var and const declaration are blanked out;
   148  // each method is turned into a regular function by turning
   149  // the receiver into the first parameter;
   150  // and all functions are renamed to "_".
   151  //
   152  //	package p
   153  //	import . "·this·" // declares T, k, x, y, f, g, T.f
   154  //	import "C"
   155  //	import "fmt"
   156  //	const _ = 3
   157  //	var _, _ = fmt.Println()
   158  //	func _() { ... }
   159  //	func _() { ... C.malloc(k) ... }
   160  //	func _(T, int) string { ... }
   161  //
   162  // In this way, the raw function bodies and const/var initializer
   163  // expressions are preserved but refer to the "cooked" objects imported
   164  // from "·this·", and none of the transformed package-level declarations
   165  // actually declares anything. In the example above, the reference to k
   166  // in the argument of the call to C.malloc resolves to "·this·".k, which
   167  // has an accurate type.
   168  //
   169  // This approach could in principle be generalized to more complex
   170  // analyses on raw cgo files. One could synthesize a "C" package so that
   171  // C.f would resolve to "·this·"._C_func_f, for example. But we have
   172  // limited ourselves here to preserving function bodies and initializer
   173  // expressions since that is all that the cgocall analyzer needs.
   174  func typeCheckCgoSourceFiles(fset *token.FileSet, pkg *types.Package, files []*ast.File, info *types.Info, sizes types.Sizes) ([]*ast.File, *types.Info, error) {
   175  	const thispkg = "·this·"
   176  
   177  	// Which files are cgo files?
   178  	var cgoFiles []*ast.File
   179  	importMap := map[string]*types.Package{thispkg: pkg}
   180  	for _, raw := range files {
   181  		// If f is a cgo-generated file, Position reports
   182  		// the original file, honoring //line directives.
   183  		filename := fset.Position(raw.Pos()).Filename
   184  		f, err := parser.ParseFile(fset, filename, nil, parser.SkipObjectResolution)
   185  		if err != nil {
   186  			return nil, nil, fmt.Errorf("can't parse raw cgo file: %v", err)
   187  		}
   188  		found := false
   189  		for _, spec := range f.Imports {
   190  			if spec.Path.Value == `"C"` {
   191  				found = true
   192  				break
   193  			}
   194  		}
   195  		if !found {
   196  			continue // not a cgo file
   197  		}
   198  
   199  		// Record the original import map.
   200  		for _, spec := range raw.Imports {
   201  			path, _ := strconv.Unquote(spec.Path.Value)
   202  			importMap[path] = imported(info, spec)
   203  		}
   204  
   205  		// Add special dot-import declaration:
   206  		//    import . "·this·"
   207  		var decls []ast.Decl
   208  		decls = append(decls, &ast.GenDecl{
   209  			Tok: token.IMPORT,
   210  			Specs: []ast.Spec{
   211  				&ast.ImportSpec{
   212  					Name: &ast.Ident{Name: "."},
   213  					Path: &ast.BasicLit{
   214  						Kind:  token.STRING,
   215  						Value: strconv.Quote(thispkg),
   216  					},
   217  				},
   218  			},
   219  		})
   220  
   221  		// Transform declarations from the raw cgo file.
   222  		for _, decl := range f.Decls {
   223  			switch decl := decl.(type) {
   224  			case *ast.GenDecl:
   225  				switch decl.Tok {
   226  				case token.TYPE:
   227  					// Discard type declarations.
   228  					continue
   229  				case token.IMPORT:
   230  					// Keep imports.
   231  				case token.VAR, token.CONST:
   232  					// Blank the declared var/const names.
   233  					for _, spec := range decl.Specs {
   234  						spec := spec.(*ast.ValueSpec)
   235  						for i := range spec.Names {
   236  							spec.Names[i].Name = "_"
   237  						}
   238  					}
   239  				}
   240  			case *ast.FuncDecl:
   241  				// Blank the declared func name.
   242  				decl.Name.Name = "_"
   243  
   244  				// Turn a method receiver:  func (T) f(P) R {...}
   245  				// into regular parameter:  func _(T, P) R {...}
   246  				if decl.Recv != nil {
   247  					var params []*ast.Field
   248  					params = append(params, decl.Recv.List...)
   249  					params = append(params, decl.Type.Params.List...)
   250  					decl.Type.Params.List = params
   251  					decl.Recv = nil
   252  				}
   253  			}
   254  			decls = append(decls, decl)
   255  		}
   256  		f.Decls = decls
   257  		if debug {
   258  			format.Node(os.Stderr, fset, f) // debugging
   259  		}
   260  		cgoFiles = append(cgoFiles, f)
   261  	}
   262  	if cgoFiles == nil {
   263  		return nil, nil, nil // nothing to do (can't happen?)
   264  	}
   265  
   266  	// Type-check the synthetic files.
   267  	tc := &types.Config{
   268  		FakeImportC: true,
   269  		Importer: importerFunc(func(path string) (*types.Package, error) {
   270  			return importMap[path], nil
   271  		}),
   272  		Sizes: sizes,
   273  		Error: func(error) {}, // ignore errors (e.g. unused import)
   274  	}
   275  	setGoVersion(tc, pkg)
   276  
   277  	// It's tempting to record the new types in the
   278  	// existing pass.TypesInfo, but we don't own it.
   279  	altInfo := &types.Info{
   280  		Types: make(map[ast.Expr]types.TypeAndValue),
   281  	}
   282  	tc.Check(pkg.Path(), fset, cgoFiles, altInfo)
   283  
   284  	return cgoFiles, altInfo, nil
   285  }
   286  
   287  // cgoBaseType tries to look through type conversions involving
   288  // unsafe.Pointer to find the real type. It converts:
   289  //
   290  //	unsafe.Pointer(x) => x
   291  //	*(*unsafe.Pointer)(unsafe.Pointer(&x)) => x
   292  func cgoBaseType(info *types.Info, arg ast.Expr) types.Type {
   293  	switch arg := arg.(type) {
   294  	case *ast.CallExpr:
   295  		if len(arg.Args) == 1 && isUnsafePointer(info, arg.Fun) {
   296  			return cgoBaseType(info, arg.Args[0])
   297  		}
   298  	case *ast.StarExpr:
   299  		call, ok := arg.X.(*ast.CallExpr)
   300  		if !ok || len(call.Args) != 1 {
   301  			break
   302  		}
   303  		// Here arg is *f(v).
   304  		t := info.Types[call.Fun].Type
   305  		if t == nil {
   306  			break
   307  		}
   308  		ptr, ok := t.Underlying().(*types.Pointer)
   309  		if !ok {
   310  			break
   311  		}
   312  		// Here arg is *(*p)(v)
   313  		elem, ok := ptr.Elem().Underlying().(*types.Basic)
   314  		if !ok || elem.Kind() != types.UnsafePointer {
   315  			break
   316  		}
   317  		// Here arg is *(*unsafe.Pointer)(v)
   318  		call, ok = call.Args[0].(*ast.CallExpr)
   319  		if !ok || len(call.Args) != 1 {
   320  			break
   321  		}
   322  		// Here arg is *(*unsafe.Pointer)(f(v))
   323  		if !isUnsafePointer(info, call.Fun) {
   324  			break
   325  		}
   326  		// Here arg is *(*unsafe.Pointer)(unsafe.Pointer(v))
   327  		u, ok := call.Args[0].(*ast.UnaryExpr)
   328  		if !ok || u.Op != token.AND {
   329  			break
   330  		}
   331  		// Here arg is *(*unsafe.Pointer)(unsafe.Pointer(&v))
   332  		return cgoBaseType(info, u.X)
   333  	}
   334  
   335  	return info.Types[arg].Type
   336  }
   337  
   338  // typeOKForCgoCall reports whether the type of arg is OK to pass to a
   339  // C function using cgo. This is not true for Go types with embedded
   340  // pointers. m is used to avoid infinite recursion on recursive types.
   341  func typeOKForCgoCall(t types.Type, m map[types.Type]bool) bool {
   342  	if t == nil || m[t] {
   343  		return true
   344  	}
   345  	m[t] = true
   346  	switch t := t.Underlying().(type) {
   347  	case *types.Chan, *types.Map, *types.Signature, *types.Slice:
   348  		return false
   349  	case *types.Pointer:
   350  		return typeOKForCgoCall(t.Elem(), m)
   351  	case *types.Array:
   352  		return typeOKForCgoCall(t.Elem(), m)
   353  	case *types.Struct:
   354  		for i := 0; i < t.NumFields(); i++ {
   355  			if !typeOKForCgoCall(t.Field(i).Type(), m) {
   356  				return false
   357  			}
   358  		}
   359  	}
   360  	return true
   361  }
   362  
   363  func isUnsafePointer(info *types.Info, e ast.Expr) bool {
   364  	t := info.Types[e].Type
   365  	return t != nil && t.Underlying() == types.Typ[types.UnsafePointer]
   366  }
   367  
   368  type importerFunc func(path string) (*types.Package, error)
   369  
   370  func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) }
   371  
   372  // TODO(adonovan): make this a library function or method of Info.
   373  func imported(info *types.Info, spec *ast.ImportSpec) *types.Package {
   374  	obj, ok := info.Implicits[spec]
   375  	if !ok {
   376  		obj = info.Defs[spec.Name] // renaming import
   377  	}
   378  	return obj.(*types.PkgName).Imported()
   379  }
   380  

View as plain text