golang cgocall 代码
文件路径:/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/cgocall/cgocall.go
/ 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()
}