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 !analysisutil.Imports(pass.Pkg, "runtime/cgo") {
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 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
176 var cgoFiles []*ast.File
177 importMap := map[string]*types.Package{thispkg: pkg}
178 for _, raw := range files {
179
180
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
195 }
196
197
198 for _, spec := range raw.Imports {
199 path, _ := strconv.Unquote(spec.Path.Value)
200 importMap[path] = imported(info, spec)
201 }
202
203
204
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
220 for _, decl := range f.Decls {
221 switch decl := decl.(type) {
222 case *ast.GenDecl:
223 switch decl.Tok {
224 case token.TYPE:
225
226 continue
227 case token.IMPORT:
228
229 case token.VAR, token.CONST:
230
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
240 decl.Name.Name = "_"
241
242
243
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)
257 }
258 cgoFiles = append(cgoFiles, f)
259 }
260 if cgoFiles == nil {
261 return nil, nil, nil
262 }
263
264
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) {},
272 }
273
274
275
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
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
View as plain text