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 URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/cgocall",
39 RunDespiteErrors: true,
40 Run: run,
41 }
42
43 func run(pass *analysis.Pass) (interface{}, error) {
44 if !analysisutil.Imports(pass.Pkg, "runtime/cgo") {
45 return nil, nil
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
66 var name string
67 if sel, ok := ast.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
74 }
75
76
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
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
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.SkipObjectResolution)
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 setGoVersion(tc, pkg)
275
276
277
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
287
288
289
290
291 func cgoBaseType(info *types.Info, arg ast.Expr) types.Type {
292 switch arg := arg.(type) {
293 case *ast.CallExpr:
294 if len(arg.Args) == 1 && isUnsafePointer(info, arg.Fun) {
295 return cgoBaseType(info, arg.Args[0])
296 }
297 case *ast.StarExpr:
298 call, ok := arg.X.(*ast.CallExpr)
299 if !ok || len(call.Args) != 1 {
300 break
301 }
302
303 t := info.Types[call.Fun].Type
304 if t == nil {
305 break
306 }
307 ptr, ok := t.Underlying().(*types.Pointer)
308 if !ok {
309 break
310 }
311
312 elem, ok := ptr.Elem().Underlying().(*types.Basic)
313 if !ok || elem.Kind() != types.UnsafePointer {
314 break
315 }
316
317 call, ok = call.Args[0].(*ast.CallExpr)
318 if !ok || len(call.Args) != 1 {
319 break
320 }
321
322 if !isUnsafePointer(info, call.Fun) {
323 break
324 }
325
326 u, ok := call.Args[0].(*ast.UnaryExpr)
327 if !ok || u.Op != token.AND {
328 break
329 }
330
331 return cgoBaseType(info, u.X)
332 }
333
334 return info.Types[arg].Type
335 }
336
337
338
339
340 func typeOKForCgoCall(t types.Type, m map[types.Type]bool) bool {
341 if t == nil || m[t] {
342 return true
343 }
344 m[t] = true
345 switch t := t.Underlying().(type) {
346 case *types.Chan, *types.Map, *types.Signature, *types.Slice:
347 return false
348 case *types.Pointer:
349 return typeOKForCgoCall(t.Elem(), m)
350 case *types.Array:
351 return typeOKForCgoCall(t.Elem(), m)
352 case *types.Struct:
353 for i := 0; i < t.NumFields(); i++ {
354 if !typeOKForCgoCall(t.Field(i).Type(), m) {
355 return false
356 }
357 }
358 }
359 return true
360 }
361
362 func isUnsafePointer(info *types.Info, e ast.Expr) bool {
363 t := info.Types[e].Type
364 return t != nil && t.Underlying() == types.Typ[types.UnsafePointer]
365 }
366
367 type importerFunc func(path string) (*types.Package, error)
368
369 func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) }
370
371
372 func imported(info *types.Info, spec *ast.ImportSpec) *types.Package {
373 obj, ok := info.Implicits[spec]
374 if !ok {
375 obj = info.Defs[spec.Name]
376 }
377 return obj.(*types.PkgName).Imported()
378 }
379
View as plain text