...

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

     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	)
    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 imports(pass.Pkg, "runtime/cgo") == nil {
    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 inpect 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	// orginal "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	}
   377	
   378	// imports reports whether pkg has path among its direct imports.
   379	// It returns the imported package if so, or nil if not.
   380	// TODO(adonovan): move to analysisutil.
   381	func imports(pkg *types.Package, path string) *types.Package {
   382		for _, imp := range pkg.Imports() {
   383			if imp.Path() == path {
   384				return imp
   385			}
   386		}
   387		return nil
   388	}
   389	

View as plain text