--- /dev/null
+// 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()
+}