GoPLS Viewer

Home|gopls/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.
7package cgocall
8
9import (
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
24const debug = false
25
26const Doc = `detect some violations of the cgo pointer passing rules
27
28Check for invalid cgo pointer passing.
29This looks for code that uses cgo to call C code passing values
30whose types are almost always invalid according to the cgo pointer
31sharing rules.
32Specifically, it warns about attempts to pass a Go chan, map, func,
33or slice to C, either directly, or via a pointer, array, or struct.`
34
35var Analyzer = &analysis.Analyzer{
36    Name:             "cgocall",
37    Doc:              Doc,
38    RunDespiteErrorstrue,
39    Run:              run,
40}
41
42func run(pass *analysis.Pass) (interface{}, error) {
43    if !analysisutil.Imports(pass.Pkg"runtime/cgo") {
44        return nilnil // doesn't use cgo
45    }
46
47    cgofilesinfoerr := typeCheckCgoSourceFiles(pass.Fsetpass.Pkgpass.Filespass.TypesInfopass.TypesSizes)
48    if err != nil {
49        return nilerr
50    }
51    for _f := range cgofiles {
52        checkCgo(pass.Fsetfinfopass.Reportf)
53    }
54    return nilnil
55}
56
57func checkCgo(fset *token.FileSetf *ast.Fileinfo *types.Inforeportf func(token.Posstring, ...interface{})) {
58    ast.Inspect(f, func(n ast.Nodebool {
59        callok := n.(*ast.CallExpr)
60        if !ok {
61            return true
62        }
63
64        // Is this a C.f() call?
65        var name string
66        if selok := analysisutil.Unparen(call.Fun).(*ast.SelectorExpr); ok {
67            if idok := 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(infoarg), 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 convok := arg.(*ast.CallExpr); ok && len(conv.Args) == 1 &&
92                isUnsafePointer(infoconv.Fun) {
93                arg = conv.Args[0]
94            }
95            if uok := arg.(*ast.UnaryExpr); ok && u.Op == token.AND {
96                if !typeOKForCgoCall(cgoBaseType(infou.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.
172func typeCheckCgoSourceFiles(fset *token.FileSetpkg *types.Packagefiles []*ast.Fileinfo *types.Infosizes types.Sizes) ([]*ast.File, *types.Infoerror) {
173    const thispkg = "·this·"
174
175    // Which files are cgo files?
176    var cgoFiles []*ast.File
177    importMap := map[string]*types.Package{thispkgpkg}
178    for _raw := range files {
179        // If f is a cgo-generated file, Position reports
180        // the original file, honoring //line directives.
181        filename := fset.Position(raw.Pos()).Filename
182        ferr := parser.ParseFile(fsetfilenamenilparser.Mode(0))
183        if err != nil {
184            return nilnilfmt.Errorf("can't parse raw cgo file: %v"err)
185        }
186        found := false
187        for _spec := range f.Imports {
188            if spec.Path.Value == `"C"` {
189                found = true
190                break
191            }
192        }
193        if !found {
194            continue // not a cgo file
195        }
196
197        // Record the original import map.
198        for _spec := range raw.Imports {
199            path_ := strconv.Unquote(spec.Path.Value)
200            importMap[path] = imported(infospec)
201        }
202
203        // Add special dot-import declaration:
204        //    import . "·this·"
205        var decls []ast.Decl
206        decls = append(decls, &ast.GenDecl{
207            Toktoken.IMPORT,
208            Specs: []ast.Spec{
209                &ast.ImportSpec{
210                    Name: &ast.Ident{Name"."},
211                    Path: &ast.BasicLit{
212                        Kind:  token.STRING,
213                        Valuestrconv.Quote(thispkg),
214                    },
215                },
216            },
217        })
218
219        // Transform declarations from the raw cgo file.
220        for _decl := range f.Decls {
221            switch decl := decl.(type) {
222            case *ast.GenDecl:
223                switch decl.Tok {
224                case token.TYPE:
225                    // Discard type declarations.
226                    continue
227                case token.IMPORT:
228                    // Keep imports.
229                case token.VARtoken.CONST:
230                    // Blank the declared var/const names.
231                    for _spec := range decl.Specs {
232                        spec := spec.(*ast.ValueSpec)
233                        for i := range spec.Names {
234                            spec.Names[i].Name = "_"
235                        }
236                    }
237                }
238            case *ast.FuncDecl:
239                // Blank the declared func name.
240                decl.Name.Name = "_"
241
242                // Turn a method receiver:  func (T) f(P) R {...}
243                // into regular parameter:  func _(T, P) R {...}
244                if decl.Recv != nil {
245                    var params []*ast.Field
246                    params = append(paramsdecl.Recv.List...)
247                    params = append(paramsdecl.Type.Params.List...)
248                    decl.Type.Params.List = params
249                    decl.Recv = nil
250                }
251            }
252            decls = append(declsdecl)
253        }
254        f.Decls = decls
255        if debug {
256            format.Node(os.Stderrfsetf// debugging
257        }
258        cgoFiles = append(cgoFilesf)
259    }
260    if cgoFiles == nil {
261        return nilnilnil // nothing to do (can't happen?)
262    }
263
264    // Type-check the synthetic files.
265    tc := &types.Config{
266        FakeImportCtrue,
267        ImporterimporterFunc(func(path string) (*types.Packageerror) {
268            return importMap[path], nil
269        }),
270        Sizessizes,
271        Error: func(error) {}, // ignore errors (e.g. unused import)
272    }
273
274    // It's tempting to record the new types in the
275    // existing pass.TypesInfo, but we don't own it.
276    altInfo := &types.Info{
277        Typesmake(map[ast.Expr]types.TypeAndValue),
278    }
279    tc.Check(pkg.Path(), fsetcgoFilesaltInfo)
280
281    return cgoFilesaltInfonil
282}
283
284// cgoBaseType tries to look through type conversions involving
285// unsafe.Pointer to find the real type. It converts:
286//
287//    unsafe.Pointer(x) => x
288//    *(*unsafe.Pointer)(unsafe.Pointer(&x)) => x
289func cgoBaseType(info *types.Infoarg ast.Exprtypes.Type {
290    switch arg := arg.(type) {
291    case *ast.CallExpr:
292        if len(arg.Args) == 1 && isUnsafePointer(infoarg.Fun) {
293            return cgoBaseType(infoarg.Args[0])
294        }
295    case *ast.StarExpr:
296        callok := 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        ptrok := t.Underlying().(*types.Pointer)
306        if !ok {
307            break
308        }
309        // Here arg is *(*p)(v)
310        elemok := ptr.Elem().Underlying().(*types.Basic)
311        if !ok || elem.Kind() != types.UnsafePointer {
312            break
313        }
314        // Here arg is *(*unsafe.Pointer)(v)
315        callok = 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(infocall.Fun) {
321            break
322        }
323        // Here arg is *(*unsafe.Pointer)(unsafe.Pointer(v))
324        uok := 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(infou.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.
338func typeOKForCgoCall(t types.Typem map[types.Type]boolbool {
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 := 0i < t.NumFields(); i++ {
352            if !typeOKForCgoCall(t.Field(i).Type(), m) {
353                return false
354            }
355        }
356    }
357    return true
358}
359
360func isUnsafePointer(info *types.Infoe ast.Exprbool {
361    t := info.Types[e].Type
362    return t != nil && t.Underlying() == types.Typ[types.UnsafePointer]
363}
364
365type importerFunc func(path string) (*types.Packageerror)
366
367func (f importerFuncImport(path string) (*types.Packageerror) { return f(path) }
368
369// TODO(adonovan): make this a library function or method of Info.
370func imported(info *types.Infospec *ast.ImportSpec) *types.Package {
371    objok := info.Implicits[spec]
372    if !ok {
373        obj = info.Defs[spec.Name// renaming import
374    }
375    return obj.(*types.PkgName).Imported()
376}
377
MembersX
checkCgo.info
typeCheckCgoSourceFiles.RangeStmt_6010.BlockStmt.RangeStmt_6555.BlockStmt.path
typeCheckCgoSourceFiles.RangeStmt_6010.BlockStmt.RangeStmt_6555.BlockStmt._
typeCheckCgoSourceFiles.RangeStmt_6010.BlockStmt.decls
typeOKForCgoCall.t
imported
fmt
analysisutil
typeCheckCgoSourceFiles.RangeStmt_6010.BlockStmt.filename
typeCheckCgoSourceFiles.RangeStmt_6010.BlockStmt.f
typeCheckCgoSourceFiles.RangeStmt_6010.BlockStmt.RangeStmt_6555.spec
typeCheckCgoSourceFiles.RangeStmt_6010.BlockStmt.RangeStmt_7083.decl
checkCgo.reportf
typeCheckCgoSourceFiles.RangeStmt_6010.BlockStmt.RangeStmt_7083.BlockStmt.BlockStmt.BlockStmt.RangeStmt_7387.spec
importerFunc.Import
run.err
checkCgo
typeCheckCgoSourceFiles.tc
ast
debug
typeCheckCgoSourceFiles.RangeStmt_6010.BlockStmt.RangeStmt_7083.BlockStmt.BlockStmt.BlockStmt.params
typeCheckCgoSourceFiles.info
cgoBaseType.arg
parser
analysis
typeCheckCgoSourceFiles.sizes
isUnsafePointer.e
importerFunc.Import.f
log
cgoBaseType.BlockStmt.t
typeCheckCgoSourceFiles.cgoFiles
strconv
run.pass
run.RangeStmt_1339.f
checkCgo.fset
checkCgo.f
checkCgo.BlockStmt.name
typeCheckCgoSourceFiles.fset
typeCheckCgoSourceFiles.RangeStmt_6010.BlockStmt.err
cgoBaseType
token
os
typeOKForCgoCall
importerFunc.Import.path
format
typeCheckCgoSourceFiles.importMap
typeCheckCgoSourceFiles.RangeStmt_6010.BlockStmt.found
imported.spec
run
run.cgofiles
run.info
typeCheckCgoSourceFiles
typeCheckCgoSourceFiles.RangeStmt_6010.BlockStmt.RangeStmt_7083.BlockStmt.BlockStmt.BlockStmt.RangeStmt_7387.BlockStmt.RangeStmt_7463.i
isUnsafePointer.info
isUnsafePointer.t
typeCheckCgoSourceFiles.pkg
typeCheckCgoSourceFiles.altInfo
Doc
typeCheckCgoSourceFiles.thispkg
typeCheckCgoSourceFiles.RangeStmt_6010.raw
typeCheckCgoSourceFiles.RangeStmt_6010.BlockStmt.RangeStmt_6365.spec
typeOKForCgoCall.m
importerFunc
imported.info
typeCheckCgoSourceFiles.files
cgoBaseType.info
typeOKForCgoCall.BlockStmt.i
isUnsafePointer
types
checkCgo.BlockStmt.RangeStmt_2128.arg
Members
X