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. 7package cgocall 8 9import ( 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 25const debug = false 26 27const Doc = `detect some violations of the cgo pointer passing rules 28 29Check for invalid cgo pointer passing. 30This looks for code that uses cgo to call C code passing values 31whose types are almost always invalid according to the cgo pointer 32sharing rules. 33Specifically, it warns about attempts to pass a Go chan, map, func, 34or slice to C, either directly, or via a pointer, array, or struct.` 35 36var 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 44func run(pass *analysis.Pass) (interface{}, error) { 45 if !analysisutil.Imports(pass.Pkg, "runtime/cgo") { 46 return nil, nil // doesn't use cgo 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 59func 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 // Is this a C.f() call? 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 // not a call we need to check 75 } 76 77 // A call to C.CBytes passes a pointer but is always safe. 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 // Check for passing the address of a bad type. 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// typeCheckCgoSourceFiles returns type-checked syntax trees for the raw 109// cgo files of a package (those that import "C"). Such files are not 110// Go, so there may be gaps in type information around C.f references. 111// 112// This checker was initially written in vet to inspect raw cgo source 113// files using partial type information. However, Analyzers in the new 114// analysis API are presented with the type-checked, "cooked" Go ASTs 115// resulting from cgo-processing files, so we must choose between 116// working with the cooked file generated by cgo (which was tried but 117// proved fragile) or locating the raw cgo file (e.g. from //line 118// directives) and working with that, as we now do. 119// 120// Specifically, we must type-check the raw cgo source files (or at 121// least the subtrees needed for this analyzer) in an environment that 122// simulates the rest of the already type-checked package. 123// 124// For example, for each raw cgo source file in the original package, 125// such as this one: 126// 127// package p 128// import "C" 129// import "fmt" 130// type T int 131// const k = 3 132// var x, y = fmt.Println() 133// func f() { ... } 134// func g() { ... C.malloc(k) ... } 135// func (T) f(int) string { ... } 136// 137// we synthesize a new ast.File, shown below, that dot-imports the 138// original "cooked" package using a special name ("·this·"), so that all 139// references to package members resolve correctly. (References to 140// unexported names cause an "unexported" error, which we ignore.) 141// 142// To avoid shadowing names imported from the cooked package, 143// package-level declarations in the new source file are modified so 144// that they do not declare any names. 145// (The cgocall analysis is concerned with uses, not declarations.) 146// Specifically, type declarations are discarded; 147// all names in each var and const declaration are blanked out; 148// each method is turned into a regular function by turning 149// the receiver into the first parameter; 150// and all functions are renamed to "_". 151// 152// package p 153// import . "·this·" // declares T, k, x, y, f, g, T.f 154// import "C" 155// import "fmt" 156// const _ = 3 157// var _, _ = fmt.Println() 158// func _() { ... } 159// func _() { ... C.malloc(k) ... } 160// func _(T, int) string { ... } 161// 162// In this way, the raw function bodies and const/var initializer 163// expressions are preserved but refer to the "cooked" objects imported 164// from "·this·", and none of the transformed package-level declarations 165// actually declares anything. In the example above, the reference to k 166// in the argument of the call to C.malloc resolves to "·this·".k, which 167// has an accurate type. 168// 169// This approach could in principle be generalized to more complex 170// analyses on raw cgo files. One could synthesize a "C" package so that 171// C.f would resolve to "·this·"._C_func_f, for example. But we have 172// limited ourselves here to preserving function bodies and initializer 173// expressions since that is all that the cgocall analyzer needs. 174func 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 // Which files are cgo files? 178 var cgoFiles []*ast.File 179 importMap := map[string]*types.Package{thispkg: pkg} 180 for _, raw := range files { 181 // If f is a cgo-generated file, Position reports 182 // the original file, honoring //line directives. 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 // not a cgo file 197 } 198 199 // Record the original import map. 200 for _, spec := range raw.Imports { 201 path, _ := strconv.Unquote(spec.Path.Value) 202 importMap[path] = imported(info, spec) 203 } 204 205 // Add special dot-import declaration: 206 // import . "·this·" 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 // Transform declarations from the raw cgo file. 222 for _, decl := range f.Decls { 223 switch decl := decl.(type) { 224 case *ast.GenDecl: 225 switch decl.Tok { 226 case token.TYPE: 227 // Discard type declarations. 228 continue 229 case token.IMPORT: 230 // Keep imports. 231 case token.VAR, token.CONST: 232 // Blank the declared var/const names. 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 // Blank the declared func name. 242 decl.Name.Name = "_" 243 244 // Turn a method receiver: func (T) f(P) R {...} 245 // into regular parameter: func _(T, P) R {...} 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) // debugging 259 } 260 cgoFiles = append(cgoFiles, f) 261 } 262 if cgoFiles == nil { 263 return nil, nil, nil // nothing to do (can't happen?) 264 } 265 266 // Type-check the synthetic files. 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) {}, // ignore errors (e.g. unused import) 274 } 275 setGoVersion(tc, pkg) 276 277 // It's tempting to record the new types in the 278 // existing pass.TypesInfo, but we don't own it. 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// cgoBaseType tries to look through type conversions involving 288// unsafe.Pointer to find the real type. It converts: 289// 290// unsafe.Pointer(x) => x 291// *(*unsafe.Pointer)(unsafe.Pointer(&x)) => x 292func 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 // Here arg is *f(v). 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 // Here arg is *(*p)(v) 313 elem, ok := ptr.Elem().Underlying().(*types.Basic) 314 if !ok || elem.Kind() != types.UnsafePointer { 315 break 316 } 317 // Here arg is *(*unsafe.Pointer)(v) 318 call, ok = call.Args[0].(*ast.CallExpr) 319 if !ok || len(call.Args) != 1 { 320 break 321 } 322 // Here arg is *(*unsafe.Pointer)(f(v)) 323 if !isUnsafePointer(info, call.Fun) { 324 break 325 } 326 // Here arg is *(*unsafe.Pointer)(unsafe.Pointer(v)) 327 u, ok := call.Args[0].(*ast.UnaryExpr) 328 if !ok || u.Op != token.AND { 329 break 330 } 331 // Here arg is *(*unsafe.Pointer)(unsafe.Pointer(&v)) 332 return cgoBaseType(info, u.X) 333 } 334 335 return info.Types[arg].Type 336} 337 338// typeOKForCgoCall reports whether the type of arg is OK to pass to a 339// C function using cgo. This is not true for Go types with embedded 340// pointers. m is used to avoid infinite recursion on recursive types. 341func 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 363func 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 368type importerFunc func(path string) (*types.Package, error) 369 370func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) } 371 372// TODO(adonovan): make this a library function or method of Info. 373func imported(info *types.Info, spec *ast.ImportSpec) *types.Package { 374 obj, ok := info.Implicits[spec] 375 if !ok { 376 obj = info.Defs[spec.Name] // renaming import 377 } 378 return obj.(*types.PkgName).Imported() 379} 380