Source file src/pkg/cmd/vendor/golang.org/x/tools/go/analysis/passes/cgocall/cgocall.go
1
2
3
4
5
6
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 imports(pass.Pkg, "runtime/cgo") == nil {
44 return nil, nil
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
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
73 }
74
75
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
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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173 func typeCheckCgoSourceFiles(fset *token.FileSet, pkg *types.Package, files []*ast.File, info *types.Info, sizes types.Sizes) ([]*ast.File, *types.Info, error) {
174 const thispkg = "·this·"
175
176
177 var cgoFiles []*ast.File
178 importMap := map[string]*types.Package{thispkg: pkg}
179 for _, raw := range files {
180
181
182 filename := fset.Position(raw.Pos()).Filename
183 f, err := parser.ParseFile(fset, filename, nil, parser.Mode(0))
184 if err != nil {
185 return nil, nil, fmt.Errorf("can't parse raw cgo file: %v", err)
186 }
187 found := false
188 for _, spec := range f.Imports {
189 if spec.Path.Value == `"C"` {
190 found = true
191 break
192 }
193 }
194 if !found {
195 continue
196 }
197
198
199 for _, spec := range raw.Imports {
200 path, _ := strconv.Unquote(spec.Path.Value)
201 importMap[path] = imported(info, spec)
202 }
203
204
205
206 var decls []ast.Decl
207 decls = append(decls, &ast.GenDecl{
208 Tok: token.IMPORT,
209 Specs: []ast.Spec{
210 &ast.ImportSpec{
211 Name: &ast.Ident{Name: "."},
212 Path: &ast.BasicLit{
213 Kind: token.STRING,
214 Value: strconv.Quote(thispkg),
215 },
216 },
217 },
218 })
219
220
221 for _, decl := range f.Decls {
222 switch decl := decl.(type) {
223 case *ast.GenDecl:
224 switch decl.Tok {
225 case token.TYPE:
226
227 continue
228 case token.IMPORT:
229
230 case token.VAR, token.CONST:
231
232 for _, spec := range decl.Specs {
233 spec := spec.(*ast.ValueSpec)
234 for i := range spec.Names {
235 spec.Names[i].Name = "_"
236 }
237 }
238 }
239 case *ast.FuncDecl:
240
241 decl.Name.Name = "_"
242
243
244
245 if decl.Recv != nil {
246 var params []*ast.Field
247 params = append(params, decl.Recv.List...)
248 params = append(params, decl.Type.Params.List...)
249 decl.Type.Params.List = params
250 decl.Recv = nil
251 }
252 }
253 decls = append(decls, decl)
254 }
255 f.Decls = decls
256 if debug {
257 format.Node(os.Stderr, fset, f)
258 }
259 cgoFiles = append(cgoFiles, f)
260 }
261 if cgoFiles == nil {
262 return nil, nil, nil
263 }
264
265
266 tc := &types.Config{
267 FakeImportC: true,
268 Importer: importerFunc(func(path string) (*types.Package, error) {
269 return importMap[path], nil
270 }),
271 Sizes: sizes,
272 Error: func(error) {},
273 }
274
275
276
277 altInfo := &types.Info{
278 Types: make(map[ast.Expr]types.TypeAndValue),
279 }
280 tc.Check(pkg.Path(), fset, cgoFiles, altInfo)
281
282 return cgoFiles, altInfo, nil
283 }
284
285
286
287
288
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
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
310 elem, ok := ptr.Elem().Underlying().(*types.Basic)
311 if !ok || elem.Kind() != types.UnsafePointer {
312 break
313 }
314
315 call, ok = call.Args[0].(*ast.CallExpr)
316 if !ok || len(call.Args) != 1 {
317 break
318 }
319
320 if !isUnsafePointer(info, call.Fun) {
321 break
322 }
323
324 u, ok := call.Args[0].(*ast.UnaryExpr)
325 if !ok || u.Op != token.AND {
326 break
327 }
328
329 return cgoBaseType(info, u.X)
330 }
331
332 return info.Types[arg].Type
333 }
334
335
336
337
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
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]
374 }
375 return obj.(*types.PkgName).Imported()
376 }
377
378
379
380
381 func imports(pkg *types.Package, path string) *types.Package {
382 for _, imp := range pkg.Imports() {
383 if imp.Path() == path {
384 return imp
385 }
386 }
387 return nil
388 }
389
View as plain text