| // Copyright 2015 The Go Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style | 
 | // license that can be found in the LICENSE file. | 
 |  | 
 | // Package cgocall defines an Analyzer that detects some violations of | 
 | // the cgo pointer passing rules. | 
 | package cgocall | 
 |  | 
 | import ( | 
 | 	"fmt" | 
 | 	"go/ast" | 
 | 	"go/format" | 
 | 	"go/parser" | 
 | 	"go/token" | 
 | 	"go/types" | 
 | 	"log" | 
 | 	"os" | 
 | 	"strconv" | 
 |  | 
 | 	"golang.org/x/tools/go/analysis" | 
 | 	"golang.org/x/tools/go/analysis/passes/internal/analysisutil" | 
 | ) | 
 |  | 
 | const debug = false | 
 |  | 
 | const Doc = `detect some violations of the cgo pointer passing rules | 
 |  | 
 | Check for invalid cgo pointer passing. | 
 | This looks for code that uses cgo to call C code passing values | 
 | whose types are almost always invalid according to the cgo pointer | 
 | sharing rules. | 
 | Specifically, it warns about attempts to pass a Go chan, map, func, | 
 | or slice to C, either directly, or via a pointer, array, or struct.` | 
 |  | 
 | var Analyzer = &analysis.Analyzer{ | 
 | 	Name:             "cgocall", | 
 | 	Doc:              Doc, | 
 | 	RunDespiteErrors: true, | 
 | 	Run:              run, | 
 | } | 
 |  | 
 | func run(pass *analysis.Pass) (interface{}, error) { | 
 | 	if !analysisutil.Imports(pass.Pkg, "runtime/cgo") { | 
 | 		return nil, nil // doesn't use cgo | 
 | 	} | 
 |  | 
 | 	cgofiles, info, err := typeCheckCgoSourceFiles(pass.Fset, pass.Pkg, pass.Files, pass.TypesInfo, pass.TypesSizes) | 
 | 	if err != nil { | 
 | 		return nil, err | 
 | 	} | 
 | 	for _, f := range cgofiles { | 
 | 		checkCgo(pass.Fset, f, info, pass.Reportf) | 
 | 	} | 
 | 	return nil, nil | 
 | } | 
 |  | 
 | func checkCgo(fset *token.FileSet, f *ast.File, info *types.Info, reportf func(token.Pos, string, ...interface{})) { | 
 | 	ast.Inspect(f, func(n ast.Node) bool { | 
 | 		call, ok := n.(*ast.CallExpr) | 
 | 		if !ok { | 
 | 			return true | 
 | 		} | 
 |  | 
 | 		// Is this a C.f() call? | 
 | 		var name string | 
 | 		if sel, ok := analysisutil.Unparen(call.Fun).(*ast.SelectorExpr); ok { | 
 | 			if id, ok := sel.X.(*ast.Ident); ok && id.Name == "C" { | 
 | 				name = sel.Sel.Name | 
 | 			} | 
 | 		} | 
 | 		if name == "" { | 
 | 			return true // not a call we need to check | 
 | 		} | 
 |  | 
 | 		// A call to C.CBytes passes a pointer but is always safe. | 
 | 		if name == "CBytes" { | 
 | 			return true | 
 | 		} | 
 |  | 
 | 		if debug { | 
 | 			log.Printf("%s: call to C.%s", fset.Position(call.Lparen), name) | 
 | 		} | 
 |  | 
 | 		for _, arg := range call.Args { | 
 | 			if !typeOKForCgoCall(cgoBaseType(info, arg), make(map[types.Type]bool)) { | 
 | 				reportf(arg.Pos(), "possibly passing Go type with embedded pointer to C") | 
 | 				break | 
 | 			} | 
 |  | 
 | 			// Check for passing the address of a bad type. | 
 | 			if conv, ok := arg.(*ast.CallExpr); ok && len(conv.Args) == 1 && | 
 | 				isUnsafePointer(info, conv.Fun) { | 
 | 				arg = conv.Args[0] | 
 | 			} | 
 | 			if u, ok := arg.(*ast.UnaryExpr); ok && u.Op == token.AND { | 
 | 				if !typeOKForCgoCall(cgoBaseType(info, u.X), make(map[types.Type]bool)) { | 
 | 					reportf(arg.Pos(), "possibly passing Go type with embedded pointer to C") | 
 | 					break | 
 | 				} | 
 | 			} | 
 | 		} | 
 | 		return true | 
 | 	}) | 
 | } | 
 |  | 
 | // typeCheckCgoSourceFiles returns type-checked syntax trees for the raw | 
 | // cgo files of a package (those that import "C"). Such files are not | 
 | // Go, so there may be gaps in type information around C.f references. | 
 | // | 
 | // This checker was initially written in vet to inspect raw cgo source | 
 | // files using partial type information. However, Analyzers in the new | 
 | // analysis API are presented with the type-checked, "cooked" Go ASTs | 
 | // resulting from cgo-processing files, so we must choose between | 
 | // working with the cooked file generated by cgo (which was tried but | 
 | // proved fragile) or locating the raw cgo file (e.g. from //line | 
 | // directives) and working with that, as we now do. | 
 | // | 
 | // Specifically, we must type-check the raw cgo source files (or at | 
 | // least the subtrees needed for this analyzer) in an environment that | 
 | // simulates the rest of the already type-checked package. | 
 | // | 
 | // For example, for each raw cgo source file in the original package, | 
 | // such as this one: | 
 | // | 
 | // 	package p | 
 | // 	import "C" | 
 | //	import "fmt" | 
 | //	type T int | 
 | //	const k = 3 | 
 | //	var x, y = fmt.Println() | 
 | //	func f() { ... } | 
 | //	func g() { ... C.malloc(k) ... } | 
 | //	func (T) f(int) string { ... } | 
 | // | 
 | // we synthesize a new ast.File, shown below, that dot-imports the | 
 | // original "cooked" package using a special name ("·this·"), so that all | 
 | // references to package members resolve correctly. (References to | 
 | // unexported names cause an "unexported" error, which we ignore.) | 
 | // | 
 | // To avoid shadowing names imported from the cooked package, | 
 | // package-level declarations in the new source file are modified so | 
 | // that they do not declare any names. | 
 | // (The cgocall analysis is concerned with uses, not declarations.) | 
 | // Specifically, type declarations are discarded; | 
 | // all names in each var and const declaration are blanked out; | 
 | // each method is turned into a regular function by turning | 
 | // the receiver into the first parameter; | 
 | // and all functions are renamed to "_". | 
 | // | 
 | // 	package p | 
 | // 	import . "·this·" // declares T, k, x, y, f, g, T.f | 
 | // 	import "C" | 
 | //	import "fmt" | 
 | //	const _ = 3 | 
 | //	var _, _ = fmt.Println() | 
 | //	func _() { ... } | 
 | //	func _() { ... C.malloc(k) ... } | 
 | //	func _(T, int) string { ... } | 
 | // | 
 | // In this way, the raw function bodies and const/var initializer | 
 | // expressions are preserved but refer to the "cooked" objects imported | 
 | // from "·this·", and none of the transformed package-level declarations | 
 | // actually declares anything. In the example above, the reference to k | 
 | // in the argument of the call to C.malloc resolves to "·this·".k, which | 
 | // has an accurate type. | 
 | // | 
 | // This approach could in principle be generalized to more complex | 
 | // analyses on raw cgo files. One could synthesize a "C" package so that | 
 | // C.f would resolve to "·this·"._C_func_f, for example. But we have | 
 | // limited ourselves here to preserving function bodies and initializer | 
 | // expressions since that is all that the cgocall analyzer needs. | 
 | // | 
 | func typeCheckCgoSourceFiles(fset *token.FileSet, pkg *types.Package, files []*ast.File, info *types.Info, sizes types.Sizes) ([]*ast.File, *types.Info, error) { | 
 | 	const thispkg = "·this·" | 
 |  | 
 | 	// Which files are cgo files? | 
 | 	var cgoFiles []*ast.File | 
 | 	importMap := map[string]*types.Package{thispkg: pkg} | 
 | 	for _, raw := range files { | 
 | 		// If f is a cgo-generated file, Position reports | 
 | 		// the original file, honoring //line directives. | 
 | 		filename := fset.Position(raw.Pos()).Filename | 
 | 		f, err := parser.ParseFile(fset, filename, nil, parser.Mode(0)) | 
 | 		if err != nil { | 
 | 			return nil, nil, fmt.Errorf("can't parse raw cgo file: %v", err) | 
 | 		} | 
 | 		found := false | 
 | 		for _, spec := range f.Imports { | 
 | 			if spec.Path.Value == `"C"` { | 
 | 				found = true | 
 | 				break | 
 | 			} | 
 | 		} | 
 | 		if !found { | 
 | 			continue // not a cgo file | 
 | 		} | 
 |  | 
 | 		// Record the original import map. | 
 | 		for _, spec := range raw.Imports { | 
 | 			path, _ := strconv.Unquote(spec.Path.Value) | 
 | 			importMap[path] = imported(info, spec) | 
 | 		} | 
 |  | 
 | 		// Add special dot-import declaration: | 
 | 		//    import . "·this·" | 
 | 		var decls []ast.Decl | 
 | 		decls = append(decls, &ast.GenDecl{ | 
 | 			Tok: token.IMPORT, | 
 | 			Specs: []ast.Spec{ | 
 | 				&ast.ImportSpec{ | 
 | 					Name: &ast.Ident{Name: "."}, | 
 | 					Path: &ast.BasicLit{ | 
 | 						Kind:  token.STRING, | 
 | 						Value: strconv.Quote(thispkg), | 
 | 					}, | 
 | 				}, | 
 | 			}, | 
 | 		}) | 
 |  | 
 | 		// Transform declarations from the raw cgo file. | 
 | 		for _, decl := range f.Decls { | 
 | 			switch decl := decl.(type) { | 
 | 			case *ast.GenDecl: | 
 | 				switch decl.Tok { | 
 | 				case token.TYPE: | 
 | 					// Discard type declarations. | 
 | 					continue | 
 | 				case token.IMPORT: | 
 | 					// Keep imports. | 
 | 				case token.VAR, token.CONST: | 
 | 					// Blank the declared var/const names. | 
 | 					for _, spec := range decl.Specs { | 
 | 						spec := spec.(*ast.ValueSpec) | 
 | 						for i := range spec.Names { | 
 | 							spec.Names[i].Name = "_" | 
 | 						} | 
 | 					} | 
 | 				} | 
 | 			case *ast.FuncDecl: | 
 | 				// Blank the declared func name. | 
 | 				decl.Name.Name = "_" | 
 |  | 
 | 				// Turn a method receiver:  func (T) f(P) R {...} | 
 | 				// into regular parameter:  func _(T, P) R {...} | 
 | 				if decl.Recv != nil { | 
 | 					var params []*ast.Field | 
 | 					params = append(params, decl.Recv.List...) | 
 | 					params = append(params, decl.Type.Params.List...) | 
 | 					decl.Type.Params.List = params | 
 | 					decl.Recv = nil | 
 | 				} | 
 | 			} | 
 | 			decls = append(decls, decl) | 
 | 		} | 
 | 		f.Decls = decls | 
 | 		if debug { | 
 | 			format.Node(os.Stderr, fset, f) // debugging | 
 | 		} | 
 | 		cgoFiles = append(cgoFiles, f) | 
 | 	} | 
 | 	if cgoFiles == nil { | 
 | 		return nil, nil, nil // nothing to do (can't happen?) | 
 | 	} | 
 |  | 
 | 	// Type-check the synthetic files. | 
 | 	tc := &types.Config{ | 
 | 		FakeImportC: true, | 
 | 		Importer: importerFunc(func(path string) (*types.Package, error) { | 
 | 			return importMap[path], nil | 
 | 		}), | 
 | 		Sizes: sizes, | 
 | 		Error: func(error) {}, // ignore errors (e.g. unused import) | 
 | 	} | 
 |  | 
 | 	// It's tempting to record the new types in the | 
 | 	// existing pass.TypesInfo, but we don't own it. | 
 | 	altInfo := &types.Info{ | 
 | 		Types: make(map[ast.Expr]types.TypeAndValue), | 
 | 	} | 
 | 	tc.Check(pkg.Path(), fset, cgoFiles, altInfo) | 
 |  | 
 | 	return cgoFiles, altInfo, nil | 
 | } | 
 |  | 
 | // cgoBaseType tries to look through type conversions involving | 
 | // unsafe.Pointer to find the real type. It converts: | 
 | //   unsafe.Pointer(x) => x | 
 | //   *(*unsafe.Pointer)(unsafe.Pointer(&x)) => x | 
 | func cgoBaseType(info *types.Info, arg ast.Expr) types.Type { | 
 | 	switch arg := arg.(type) { | 
 | 	case *ast.CallExpr: | 
 | 		if len(arg.Args) == 1 && isUnsafePointer(info, arg.Fun) { | 
 | 			return cgoBaseType(info, arg.Args[0]) | 
 | 		} | 
 | 	case *ast.StarExpr: | 
 | 		call, ok := arg.X.(*ast.CallExpr) | 
 | 		if !ok || len(call.Args) != 1 { | 
 | 			break | 
 | 		} | 
 | 		// Here arg is *f(v). | 
 | 		t := info.Types[call.Fun].Type | 
 | 		if t == nil { | 
 | 			break | 
 | 		} | 
 | 		ptr, ok := t.Underlying().(*types.Pointer) | 
 | 		if !ok { | 
 | 			break | 
 | 		} | 
 | 		// Here arg is *(*p)(v) | 
 | 		elem, ok := ptr.Elem().Underlying().(*types.Basic) | 
 | 		if !ok || elem.Kind() != types.UnsafePointer { | 
 | 			break | 
 | 		} | 
 | 		// Here arg is *(*unsafe.Pointer)(v) | 
 | 		call, ok = call.Args[0].(*ast.CallExpr) | 
 | 		if !ok || len(call.Args) != 1 { | 
 | 			break | 
 | 		} | 
 | 		// Here arg is *(*unsafe.Pointer)(f(v)) | 
 | 		if !isUnsafePointer(info, call.Fun) { | 
 | 			break | 
 | 		} | 
 | 		// Here arg is *(*unsafe.Pointer)(unsafe.Pointer(v)) | 
 | 		u, ok := call.Args[0].(*ast.UnaryExpr) | 
 | 		if !ok || u.Op != token.AND { | 
 | 			break | 
 | 		} | 
 | 		// Here arg is *(*unsafe.Pointer)(unsafe.Pointer(&v)) | 
 | 		return cgoBaseType(info, u.X) | 
 | 	} | 
 |  | 
 | 	return info.Types[arg].Type | 
 | } | 
 |  | 
 | // typeOKForCgoCall reports whether the type of arg is OK to pass to a | 
 | // C function using cgo. This is not true for Go types with embedded | 
 | // pointers. m is used to avoid infinite recursion on recursive types. | 
 | func typeOKForCgoCall(t types.Type, m map[types.Type]bool) bool { | 
 | 	if t == nil || m[t] { | 
 | 		return true | 
 | 	} | 
 | 	m[t] = true | 
 | 	switch t := t.Underlying().(type) { | 
 | 	case *types.Chan, *types.Map, *types.Signature, *types.Slice: | 
 | 		return false | 
 | 	case *types.Pointer: | 
 | 		return typeOKForCgoCall(t.Elem(), m) | 
 | 	case *types.Array: | 
 | 		return typeOKForCgoCall(t.Elem(), m) | 
 | 	case *types.Struct: | 
 | 		for i := 0; i < t.NumFields(); i++ { | 
 | 			if !typeOKForCgoCall(t.Field(i).Type(), m) { | 
 | 				return false | 
 | 			} | 
 | 		} | 
 | 	} | 
 | 	return true | 
 | } | 
 |  | 
 | func isUnsafePointer(info *types.Info, e ast.Expr) bool { | 
 | 	t := info.Types[e].Type | 
 | 	return t != nil && t.Underlying() == types.Typ[types.UnsafePointer] | 
 | } | 
 |  | 
 | type importerFunc func(path string) (*types.Package, error) | 
 |  | 
 | func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) } | 
 |  | 
 | // TODO(adonovan): make this a library function or method of Info. | 
 | func imported(info *types.Info, spec *ast.ImportSpec) *types.Package { | 
 | 	obj, ok := info.Implicits[spec] | 
 | 	if !ok { | 
 | 		obj = info.Defs[spec.Name] // renaming import | 
 | 	} | 
 | 	return obj.(*types.PkgName).Imported() | 
 | } |