github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/go/analysis/passes/cgocall/cgocall.go (about)

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