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 "golang.org/x/tools/go/ast/astutil"
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 URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/cgocall",
40 RunDespiteErrors: true,
41 Run: run,
42 }
43
44 func run(pass *analysis.Pass) (interface{}, error) {
45 if !analysisutil.Imports(pass.Pkg, "runtime/cgo") {
46 return nil, nil
47 }
48
49 cgofiles, info, err := typeCheckCgoSourceFiles(pass.Fset, pass.Pkg, pass.Files, pass.TypesInfo, pass.TypesSizes)
50 if err != nil {
51 return nil, err
52 }
53 for _, f := range cgofiles {
54 checkCgo(pass.Fset, f, info, pass.Reportf)
55 }
56 return nil, nil
57 }
58
59 func checkCgo(fset *token.FileSet, f *ast.File, info *types.Info, reportf func(token.Pos, string, ...interface{})) {
60 ast.Inspect(f, func(n ast.Node) bool {
61 call, ok := n.(*ast.CallExpr)
62 if !ok {
63 return true
64 }
65
66
67 var name string
68 if sel, ok := astutil.Unparen(call.Fun).(*ast.SelectorExpr); ok {
69 if id, ok := sel.X.(*ast.Ident); ok && id.Name == "C" {
70 name = sel.Sel.Name
71 }
72 }
73 if name == "" {
74 return true
75 }
76
77
78 if name == "CBytes" {
79 return true
80 }
81
82 if debug {
83 log.Printf("%s: call to C.%s", fset.Position(call.Lparen), name)
84 }
85
86 for _, arg := range call.Args {
87 if !typeOKForCgoCall(cgoBaseType(info, arg), make(map[types.Type]bool)) {
88 reportf(arg.Pos(), "possibly passing Go type with embedded pointer to C")
89 break
90 }
91
92
93 if conv, ok := arg.(*ast.CallExpr); ok && len(conv.Args) == 1 &&
94 isUnsafePointer(info, conv.Fun) {
95 arg = conv.Args[0]
96 }
97 if u, ok := arg.(*ast.UnaryExpr); ok && u.Op == token.AND {
98 if !typeOKForCgoCall(cgoBaseType(info, u.X), make(map[types.Type]bool)) {
99 reportf(arg.Pos(), "possibly passing Go type with embedded pointer to C")
100 break
101 }
102 }
103 }
104 return true
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
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
178 var cgoFiles []*ast.File
179 importMap := map[string]*types.Package{thispkg: pkg}
180 for _, raw := range files {
181
182
183 filename := fset.Position(raw.Pos()).Filename
184 f, err := parser.ParseFile(fset, filename, nil, parser.SkipObjectResolution)
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
197 }
198
199
200 for _, spec := range raw.Imports {
201 path, _ := strconv.Unquote(spec.Path.Value)
202 importMap[path] = imported(info, spec)
203 }
204
205
206
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
222 for _, decl := range f.Decls {
223 switch decl := decl.(type) {
224 case *ast.GenDecl:
225 switch decl.Tok {
226 case token.TYPE:
227
228 continue
229 case token.IMPORT:
230
231 case token.VAR, token.CONST:
232
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
242 decl.Name.Name = "_"
243
244
245
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)
259 }
260 cgoFiles = append(cgoFiles, f)
261 }
262 if cgoFiles == nil {
263 return nil, nil, nil
264 }
265
266
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) {},
274 }
275 setGoVersion(tc, pkg)
276
277
278
279 altInfo := &types.Info{
280 Types: make(map[ast.Expr]types.TypeAndValue),
281 }
282 tc.Check(pkg.Path(), fset, cgoFiles, altInfo)
283
284 return cgoFiles, altInfo, nil
285 }
286
287
288
289
290
291
292 func cgoBaseType(info *types.Info, arg ast.Expr) types.Type {
293 switch arg := arg.(type) {
294 case *ast.CallExpr:
295 if len(arg.Args) == 1 && isUnsafePointer(info, arg.Fun) {
296 return cgoBaseType(info, arg.Args[0])
297 }
298 case *ast.StarExpr:
299 call, ok := arg.X.(*ast.CallExpr)
300 if !ok || len(call.Args) != 1 {
301 break
302 }
303
304 t := info.Types[call.Fun].Type
305 if t == nil {
306 break
307 }
308 ptr, ok := t.Underlying().(*types.Pointer)
309 if !ok {
310 break
311 }
312
313 elem, ok := ptr.Elem().Underlying().(*types.Basic)
314 if !ok || elem.Kind() != types.UnsafePointer {
315 break
316 }
317
318 call, ok = call.Args[0].(*ast.CallExpr)
319 if !ok || len(call.Args) != 1 {
320 break
321 }
322
323 if !isUnsafePointer(info, call.Fun) {
324 break
325 }
326
327 u, ok := call.Args[0].(*ast.UnaryExpr)
328 if !ok || u.Op != token.AND {
329 break
330 }
331
332 return cgoBaseType(info, u.X)
333 }
334
335 return info.Types[arg].Type
336 }
337
338
339
340
341 func typeOKForCgoCall(t types.Type, m map[types.Type]bool) bool {
342 if t == nil || m[t] {
343 return true
344 }
345 m[t] = true
346 switch t := t.Underlying().(type) {
347 case *types.Chan, *types.Map, *types.Signature, *types.Slice:
348 return false
349 case *types.Pointer:
350 return typeOKForCgoCall(t.Elem(), m)
351 case *types.Array:
352 return typeOKForCgoCall(t.Elem(), m)
353 case *types.Struct:
354 for i := 0; i < t.NumFields(); i++ {
355 if !typeOKForCgoCall(t.Field(i).Type(), m) {
356 return false
357 }
358 }
359 }
360 return true
361 }
362
363 func isUnsafePointer(info *types.Info, e ast.Expr) bool {
364 t := info.Types[e].Type
365 return t != nil && t.Underlying() == types.Typ[types.UnsafePointer]
366 }
367
368 type importerFunc func(path string) (*types.Package, error)
369
370 func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) }
371
372
373 func imported(info *types.Info, spec *ast.ImportSpec) *types.Package {
374 obj, ok := info.Implicits[spec]
375 if !ok {
376 obj = info.Defs[spec.Name]
377 }
378 return obj.(*types.PkgName).Imported()
379 }
380
View as plain text