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 | "strconv" |
19 | |
20 | "golang.org/x/tools/go/analysis" |
21 | "golang.org/x/tools/go/analysis/passes/internal/analysisutil" |
22 | ) |
23 | |
24 | const debug = false |
25 | |
26 | const Doc = `detect some violations of the cgo pointer passing rules |
27 | |
28 | Check for invalid cgo pointer passing. |
29 | This looks for code that uses cgo to call C code passing values |
30 | whose types are almost always invalid according to the cgo pointer |
31 | sharing rules. |
32 | Specifically, it warns about attempts to pass a Go chan, map, func, |
33 | or slice to C, either directly, or via a pointer, array, or struct.` |
34 | |
35 | var Analyzer = &analysis.Analyzer{ |
36 | Name: "cgocall", |
37 | Doc: Doc, |
38 | RunDespiteErrors: true, |
39 | Run: run, |
40 | } |
41 | |
42 | func run(pass *analysis.Pass) (interface{}, error) { |
43 | if !analysisutil.Imports(pass.Pkg, "runtime/cgo") { |
44 | return nil, nil // doesn't use cgo |
45 | } |
46 | |
47 | cgofiles, info, err := typeCheckCgoSourceFiles(pass.Fset, pass.Pkg, pass.Files, pass.TypesInfo, pass.TypesSizes) |
48 | if err != nil { |
49 | return nil, err |
50 | } |
51 | for _, f := range cgofiles { |
52 | checkCgo(pass.Fset, f, info, pass.Reportf) |
53 | } |
54 | return nil, nil |
55 | } |
56 | |
57 | func checkCgo(fset *token.FileSet, f *ast.File, info *types.Info, reportf func(token.Pos, string, ...interface{})) { |
58 | ast.Inspect(f, func(n ast.Node) bool { |
59 | call, ok := n.(*ast.CallExpr) |
60 | if !ok { |
61 | return true |
62 | } |
63 | |
64 | // Is this a C.f() call? |
65 | var name string |
66 | if sel, ok := analysisutil.Unparen(call.Fun).(*ast.SelectorExpr); ok { |
67 | if id, ok := 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(info, arg), 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 conv, ok := arg.(*ast.CallExpr); ok && len(conv.Args) == 1 && |
92 | isUnsafePointer(info, conv.Fun) { |
93 | arg = conv.Args[0] |
94 | } |
95 | if u, ok := arg.(*ast.UnaryExpr); ok && u.Op == token.AND { |
96 | if !typeOKForCgoCall(cgoBaseType(info, u.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. |
172 | func typeCheckCgoSourceFiles(fset *token.FileSet, pkg *types.Package, files []*ast.File, info *types.Info, sizes types.Sizes) ([]*ast.File, *types.Info, error) { |
173 | const thispkg = "·this·" |
174 | |
175 | // Which files are cgo files? |
176 | var cgoFiles []*ast.File |
177 | importMap := map[string]*types.Package{thispkg: pkg} |
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 | f, err := parser.ParseFile(fset, filename, nil, parser.Mode(0)) |
183 | if err != nil { |
184 | return nil, nil, fmt.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(info, spec) |
201 | } |
202 | |
203 | // Add special dot-import declaration: |
204 | // import . "·this·" |
205 | var decls []ast.Decl |
206 | decls = append(decls, &ast.GenDecl{ |
207 | Tok: token.IMPORT, |
208 | Specs: []ast.Spec{ |
209 | &ast.ImportSpec{ |
210 | Name: &ast.Ident{Name: "."}, |
211 | Path: &ast.BasicLit{ |
212 | Kind: token.STRING, |
213 | Value: strconv.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.VAR, token.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(params, decl.Recv.List...) |
247 | params = append(params, decl.Type.Params.List...) |
248 | decl.Type.Params.List = params |
249 | decl.Recv = nil |
250 | } |
251 | } |
252 | decls = append(decls, decl) |
253 | } |
254 | f.Decls = decls |
255 | if debug { |
256 | format.Node(os.Stderr, fset, f) // debugging |
257 | } |
258 | cgoFiles = append(cgoFiles, f) |
259 | } |
260 | if cgoFiles == nil { |
261 | return nil, nil, nil // nothing to do (can't happen?) |
262 | } |
263 | |
264 | // Type-check the synthetic files. |
265 | tc := &types.Config{ |
266 | FakeImportC: true, |
267 | Importer: importerFunc(func(path string) (*types.Package, error) { |
268 | return importMap[path], nil |
269 | }), |
270 | Sizes: sizes, |
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 | Types: make(map[ast.Expr]types.TypeAndValue), |
278 | } |
279 | tc.Check(pkg.Path(), fset, cgoFiles, altInfo) |
280 | |
281 | return cgoFiles, altInfo, nil |
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 |
289 | func cgoBaseType(info *types.Info, arg ast.Expr) types.Type { |
290 | switch arg := arg.(type) { |
291 | case *ast.CallExpr: |
292 | if len(arg.Args) == 1 && isUnsafePointer(info, arg.Fun) { |
293 | return cgoBaseType(info, arg.Args[0]) |
294 | } |
295 | case *ast.StarExpr: |
296 | call, ok := 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 | ptr, ok := t.Underlying().(*types.Pointer) |
306 | if !ok { |
307 | break |
308 | } |
309 | // Here arg is *(*p)(v) |
310 | elem, ok := ptr.Elem().Underlying().(*types.Basic) |
311 | if !ok || elem.Kind() != types.UnsafePointer { |
312 | break |
313 | } |
314 | // Here arg is *(*unsafe.Pointer)(v) |
315 | call, ok = 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(info, call.Fun) { |
321 | break |
322 | } |
323 | // Here arg is *(*unsafe.Pointer)(unsafe.Pointer(v)) |
324 | u, ok := 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(info, u.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. |
338 | func typeOKForCgoCall(t types.Type, m map[types.Type]bool) bool { |
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 := 0; i < t.NumFields(); i++ { |
352 | if !typeOKForCgoCall(t.Field(i).Type(), m) { |
353 | return false |
354 | } |
355 | } |
356 | } |
357 | return true |
358 | } |
359 | |
360 | func isUnsafePointer(info *types.Info, e ast.Expr) bool { |
361 | t := info.Types[e].Type |
362 | return t != nil && t.Underlying() == types.Typ[types.UnsafePointer] |
363 | } |
364 | |
365 | type importerFunc func(path string) (*types.Package, error) |
366 | |
367 | func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) } |
368 | |
369 | // TODO(adonovan): make this a library function or method of Info. |
370 | func imported(info *types.Info, spec *ast.ImportSpec) *types.Package { |
371 | obj, ok := info.Implicits[spec] |
372 | if !ok { |
373 | obj = info.Defs[spec.Name] // renaming import |
374 | } |
375 | return obj.(*types.PkgName).Imported() |
376 | } |
377 |
Members