golang.org/x/tools@v0.21.0/go/analysis/passes/cgocall/cgocall.go (about) 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. 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 // 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 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 // 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. 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 // 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 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 // 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. 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 // TODO(adonovan): make this a library function or method of Info. 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] // renaming import 377 } 378 return obj.(*types.PkgName).Imported() 379 }