]> git.ipfire.org Git - thirdparty/gcc.git/blob - libgo/go/golang.org/x/tools/go/analysis/passes/cgocall/cgocall.go
libgo: update to Go1.14beta1
[thirdparty/gcc.git] / libgo / go / 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 "runtime"
19 "strconv"
20
21 "golang.org/x/tools/go/analysis"
22 "golang.org/x/tools/go/analysis/passes/internal/analysisutil"
23 )
24
25 const debug = false
26
27 const Doc = `detect some violations of the cgo pointer passing rules
28
29 Check for invalid cgo pointer passing.
30 This looks for code that uses cgo to call C code passing values
31 whose types are almost always invalid according to the cgo pointer
32 sharing rules.
33 Specifically, it warns about attempts to pass a Go chan, map, func,
34 or slice to C, either directly, or via a pointer, array, or struct.`
35
36 var Analyzer = &analysis.Analyzer{
37 Name: "cgocall",
38 Doc: Doc,
39 RunDespiteErrors: true,
40 Run: run,
41 }
42
43 func run(pass *analysis.Pass) (interface{}, error) {
44 if runtime.Compiler != "gccgo" && imports(pass.Pkg, "runtime/cgo") == nil {
45 return nil, nil // doesn't use cgo
46 }
47
48 cgofiles, info, err := typeCheckCgoSourceFiles(pass.Fset, pass.Pkg, pass.Files, pass.TypesInfo, pass.TypesSizes)
49 if err != nil {
50 return nil, err
51 }
52 for _, f := range cgofiles {
53 checkCgo(pass.Fset, f, info, pass.Reportf)
54 }
55 return nil, nil
56 }
57
58 func checkCgo(fset *token.FileSet, f *ast.File, info *types.Info, reportf func(token.Pos, string, ...interface{})) {
59 ast.Inspect(f, func(n ast.Node) bool {
60 call, ok := n.(*ast.CallExpr)
61 if !ok {
62 return true
63 }
64
65 // Is this a C.f() call?
66 var name string
67 if sel, ok := analysisutil.Unparen(call.Fun).(*ast.SelectorExpr); ok {
68 if id, ok := sel.X.(*ast.Ident); ok && id.Name == "C" {
69 name = sel.Sel.Name
70 }
71 }
72 if name == "" {
73 return true // not a call we need to check
74 }
75
76 // A call to C.CBytes passes a pointer but is always safe.
77 if name == "CBytes" {
78 return true
79 }
80
81 if debug {
82 log.Printf("%s: call to C.%s", fset.Position(call.Lparen), name)
83 }
84
85 for _, arg := range call.Args {
86 if !typeOKForCgoCall(cgoBaseType(info, arg), make(map[types.Type]bool)) {
87 reportf(arg.Pos(), "possibly passing Go type with embedded pointer to C")
88 break
89 }
90
91 // Check for passing the address of a bad type.
92 if conv, ok := arg.(*ast.CallExpr); ok && len(conv.Args) == 1 &&
93 isUnsafePointer(info, conv.Fun) {
94 arg = conv.Args[0]
95 }
96 if u, ok := arg.(*ast.UnaryExpr); ok && u.Op == token.AND {
97 if !typeOKForCgoCall(cgoBaseType(info, u.X), make(map[types.Type]bool)) {
98 reportf(arg.Pos(), "possibly passing Go type with embedded pointer to C")
99 break
100 }
101 }
102 }
103 return true
104 })
105 }
106
107 // typeCheckCgoSourceFiles returns type-checked syntax trees for the raw
108 // cgo files of a package (those that import "C"). Such files are not
109 // Go, so there may be gaps in type information around C.f references.
110 //
111 // This checker was initially written in vet to inspect raw cgo source
112 // files using partial type information. However, Analyzers in the new
113 // analysis API are presented with the type-checked, "cooked" Go ASTs
114 // resulting from cgo-processing files, so we must choose between
115 // working with the cooked file generated by cgo (which was tried but
116 // proved fragile) or locating the raw cgo file (e.g. from //line
117 // directives) and working with that, as we now do.
118 //
119 // Specifically, we must type-check the raw cgo source files (or at
120 // least the subtrees needed for this analyzer) in an environment that
121 // simulates the rest of the already type-checked package.
122 //
123 // For example, for each raw cgo source file in the original package,
124 // such as this one:
125 //
126 // package p
127 // import "C"
128 // import "fmt"
129 // type T int
130 // const k = 3
131 // var x, y = fmt.Println()
132 // func f() { ... }
133 // func g() { ... C.malloc(k) ... }
134 // func (T) f(int) string { ... }
135 //
136 // we synthesize a new ast.File, shown below, that dot-imports the
137 // original "cooked" package using a special name ("·this·"), so that all
138 // references to package members resolve correctly. (References to
139 // unexported names cause an "unexported" error, which we ignore.)
140 //
141 // To avoid shadowing names imported from the cooked package,
142 // package-level declarations in the new source file are modified so
143 // that they do not declare any names.
144 // (The cgocall analysis is concerned with uses, not declarations.)
145 // Specifically, type declarations are discarded;
146 // all names in each var and const declaration are blanked out;
147 // each method is turned into a regular function by turning
148 // the receiver into the first parameter;
149 // and all functions are renamed to "_".
150 //
151 // package p
152 // import . "·this·" // declares T, k, x, y, f, g, T.f
153 // import "C"
154 // import "fmt"
155 // const _ = 3
156 // var _, _ = fmt.Println()
157 // func _() { ... }
158 // func _() { ... C.malloc(k) ... }
159 // func _(T, int) string { ... }
160 //
161 // In this way, the raw function bodies and const/var initializer
162 // expressions are preserved but refer to the "cooked" objects imported
163 // from "·this·", and none of the transformed package-level declarations
164 // actually declares anything. In the example above, the reference to k
165 // in the argument of the call to C.malloc resolves to "·this·".k, which
166 // has an accurate type.
167 //
168 // This approach could in principle be generalized to more complex
169 // analyses on raw cgo files. One could synthesize a "C" package so that
170 // C.f would resolve to "·this·"._C_func_f, for example. But we have
171 // limited ourselves here to preserving function bodies and initializer
172 // expressions since that is all that the cgocall analyzer needs.
173 //
174 func typeCheckCgoSourceFiles(fset *token.FileSet, pkg *types.Package, files []*ast.File, info *types.Info, sizes types.Sizes) ([]*ast.File, *types.Info, error) {
175 const thispkg = "·this·"
176
177 // Which files are cgo files?
178 var cgoFiles []*ast.File
179 importMap := map[string]*types.Package{thispkg: pkg}
180 for _, raw := range files {
181 // If f is a cgo-generated file, Position reports
182 // the original file, honoring //line directives.
183 filename := fset.Position(raw.Pos()).Filename
184 f, err := parser.ParseFile(fset, filename, nil, parser.Mode(0))
185 if err != nil {
186 return nil, nil, fmt.Errorf("can't parse raw cgo file: %v", err)
187 }
188 found := false
189 for _, spec := range f.Imports {
190 if spec.Path.Value == `"C"` {
191 found = true
192 break
193 }
194 }
195 if !found {
196 continue // not a cgo file
197 }
198
199 // Record the original import map.
200 for _, spec := range raw.Imports {
201 path, _ := strconv.Unquote(spec.Path.Value)
202 importMap[path] = imported(info, spec)
203 }
204
205 // Add special dot-import declaration:
206 // import . "·this·"
207 var decls []ast.Decl
208 decls = append(decls, &ast.GenDecl{
209 Tok: token.IMPORT,
210 Specs: []ast.Spec{
211 &ast.ImportSpec{
212 Name: &ast.Ident{Name: "."},
213 Path: &ast.BasicLit{
214 Kind: token.STRING,
215 Value: strconv.Quote(thispkg),
216 },
217 },
218 },
219 })
220
221 // Transform declarations from the raw cgo file.
222 for _, decl := range f.Decls {
223 switch decl := decl.(type) {
224 case *ast.GenDecl:
225 switch decl.Tok {
226 case token.TYPE:
227 // Discard type declarations.
228 continue
229 case token.IMPORT:
230 // Keep imports.
231 case token.VAR, token.CONST:
232 // Blank the declared var/const names.
233 for _, spec := range decl.Specs {
234 spec := spec.(*ast.ValueSpec)
235 for i := range spec.Names {
236 spec.Names[i].Name = "_"
237 }
238 }
239 }
240 case *ast.FuncDecl:
241 // Blank the declared func name.
242 decl.Name.Name = "_"
243
244 // Turn a method receiver: func (T) f(P) R {...}
245 // into regular parameter: func _(T, P) R {...}
246 if decl.Recv != nil {
247 var params []*ast.Field
248 params = append(params, decl.Recv.List...)
249 params = append(params, decl.Type.Params.List...)
250 decl.Type.Params.List = params
251 decl.Recv = nil
252 }
253 }
254 decls = append(decls, decl)
255 }
256 f.Decls = decls
257 if debug {
258 format.Node(os.Stderr, fset, f) // debugging
259 }
260 cgoFiles = append(cgoFiles, f)
261 }
262 if cgoFiles == nil {
263 return nil, nil, nil // nothing to do (can't happen?)
264 }
265
266 // Type-check the synthetic files.
267 tc := &types.Config{
268 FakeImportC: true,
269 Importer: importerFunc(func(path string) (*types.Package, error) {
270 return importMap[path], nil
271 }),
272 Sizes: sizes,
273 Error: func(error) {}, // ignore errors (e.g. unused import)
274 }
275
276 // It's tempting to record the new types in the
277 // existing pass.TypesInfo, but we don't own it.
278 altInfo := &types.Info{
279 Types: make(map[ast.Expr]types.TypeAndValue),
280 }
281 tc.Check(pkg.Path(), fset, cgoFiles, altInfo)
282
283 return cgoFiles, altInfo, nil
284 }
285
286 // cgoBaseType tries to look through type conversions involving
287 // unsafe.Pointer to find the real type. It converts:
288 // unsafe.Pointer(x) => x
289 // *(*unsafe.Pointer)(unsafe.Pointer(&x)) => x
290 func cgoBaseType(info *types.Info, arg ast.Expr) types.Type {
291 switch arg := arg.(type) {
292 case *ast.CallExpr:
293 if len(arg.Args) == 1 && isUnsafePointer(info, arg.Fun) {
294 return cgoBaseType(info, arg.Args[0])
295 }
296 case *ast.StarExpr:
297 call, ok := arg.X.(*ast.CallExpr)
298 if !ok || len(call.Args) != 1 {
299 break
300 }
301 // Here arg is *f(v).
302 t := info.Types[call.Fun].Type
303 if t == nil {
304 break
305 }
306 ptr, ok := t.Underlying().(*types.Pointer)
307 if !ok {
308 break
309 }
310 // Here arg is *(*p)(v)
311 elem, ok := ptr.Elem().Underlying().(*types.Basic)
312 if !ok || elem.Kind() != types.UnsafePointer {
313 break
314 }
315 // Here arg is *(*unsafe.Pointer)(v)
316 call, ok = call.Args[0].(*ast.CallExpr)
317 if !ok || len(call.Args) != 1 {
318 break
319 }
320 // Here arg is *(*unsafe.Pointer)(f(v))
321 if !isUnsafePointer(info, call.Fun) {
322 break
323 }
324 // Here arg is *(*unsafe.Pointer)(unsafe.Pointer(v))
325 u, ok := call.Args[0].(*ast.UnaryExpr)
326 if !ok || u.Op != token.AND {
327 break
328 }
329 // Here arg is *(*unsafe.Pointer)(unsafe.Pointer(&v))
330 return cgoBaseType(info, u.X)
331 }
332
333 return info.Types[arg].Type
334 }
335
336 // typeOKForCgoCall reports whether the type of arg is OK to pass to a
337 // C function using cgo. This is not true for Go types with embedded
338 // pointers. m is used to avoid infinite recursion on recursive types.
339 func typeOKForCgoCall(t types.Type, m map[types.Type]bool) bool {
340 if t == nil || m[t] {
341 return true
342 }
343 m[t] = true
344 switch t := t.Underlying().(type) {
345 case *types.Chan, *types.Map, *types.Signature, *types.Slice:
346 return false
347 case *types.Pointer:
348 return typeOKForCgoCall(t.Elem(), m)
349 case *types.Array:
350 return typeOKForCgoCall(t.Elem(), m)
351 case *types.Struct:
352 for i := 0; i < t.NumFields(); i++ {
353 if !typeOKForCgoCall(t.Field(i).Type(), m) {
354 return false
355 }
356 }
357 }
358 return true
359 }
360
361 func isUnsafePointer(info *types.Info, e ast.Expr) bool {
362 t := info.Types[e].Type
363 return t != nil && t.Underlying() == types.Typ[types.UnsafePointer]
364 }
365
366 type importerFunc func(path string) (*types.Package, error)
367
368 func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) }
369
370 // TODO(adonovan): make this a library function or method of Info.
371 func imported(info *types.Info, spec *ast.ImportSpec) *types.Package {
372 obj, ok := info.Implicits[spec]
373 if !ok {
374 obj = info.Defs[spec.Name] // renaming import
375 }
376 return obj.(*types.PkgName).Imported()
377 }
378
379 // imports reports whether pkg has path among its direct imports.
380 // It returns the imported package if so, or nil if not.
381 // TODO(adonovan): move to analysisutil.
382 func imports(pkg *types.Package, path string) *types.Package {
383 for _, imp := range pkg.Imports() {
384 if imp.Path() == path {
385 return imp
386 }
387 }
388 return nil
389 }