github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/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 "github.com/powerman/golang-tools/go/analysis" 21 "github.com/powerman/golang-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 // doesn't use cgo 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 // Is this a C.f() call? 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 // not a call we need to check 73 } 74 75 // A call to C.CBytes passes a pointer but is always safe. 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 // Check for passing the address of a bad type. 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 // typeCheckCgoSourceFiles returns type-checked syntax trees for the raw 107 // cgo files of a package (those that import "C"). Such files are not 108 // Go, so there may be gaps in type information around C.f references. 109 // 110 // This checker was initially written in vet to inspect raw cgo source 111 // files using partial type information. However, Analyzers in the new 112 // analysis API are presented with the type-checked, "cooked" Go ASTs 113 // resulting from cgo-processing files, so we must choose between 114 // working with the cooked file generated by cgo (which was tried but 115 // proved fragile) or locating the raw cgo file (e.g. from //line 116 // directives) and working with that, as we now do. 117 // 118 // Specifically, we must type-check the raw cgo source files (or at 119 // least the subtrees needed for this analyzer) in an environment that 120 // simulates the rest of the already type-checked package. 121 // 122 // For example, for each raw cgo source file in the original package, 123 // such as this one: 124 // 125 // package p 126 // import "C" 127 // import "fmt" 128 // type T int 129 // const k = 3 130 // var x, y = fmt.Println() 131 // func f() { ... } 132 // func g() { ... C.malloc(k) ... } 133 // func (T) f(int) string { ... } 134 // 135 // we synthesize a new ast.File, shown below, that dot-imports the 136 // original "cooked" package using a special name ("·this·"), so that all 137 // references to package members resolve correctly. (References to 138 // unexported names cause an "unexported" error, which we ignore.) 139 // 140 // To avoid shadowing names imported from the cooked package, 141 // package-level declarations in the new source file are modified so 142 // that they do not declare any names. 143 // (The cgocall analysis is concerned with uses, not declarations.) 144 // Specifically, type declarations are discarded; 145 // all names in each var and const declaration are blanked out; 146 // each method is turned into a regular function by turning 147 // the receiver into the first parameter; 148 // and all functions are renamed to "_". 149 // 150 // package p 151 // import . "·this·" // declares T, k, x, y, f, g, T.f 152 // import "C" 153 // import "fmt" 154 // const _ = 3 155 // var _, _ = fmt.Println() 156 // func _() { ... } 157 // func _() { ... C.malloc(k) ... } 158 // func _(T, int) string { ... } 159 // 160 // In this way, the raw function bodies and const/var initializer 161 // expressions are preserved but refer to the "cooked" objects imported 162 // from "·this·", and none of the transformed package-level declarations 163 // actually declares anything. In the example above, the reference to k 164 // in the argument of the call to C.malloc resolves to "·this·".k, which 165 // has an accurate type. 166 // 167 // This approach could in principle be generalized to more complex 168 // analyses on raw cgo files. One could synthesize a "C" package so that 169 // C.f would resolve to "·this·"._C_func_f, for example. But we have 170 // limited ourselves here to preserving function bodies and initializer 171 // expressions since that is all that the cgocall analyzer needs. 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 // Which files are cgo files? 177 var cgoFiles []*ast.File 178 importMap := map[string]*types.Package{thispkg: pkg} 179 for _, raw := range files { 180 // If f is a cgo-generated file, Position reports 181 // the original file, honoring //line directives. 182 filename := fset.Position(raw.Pos()).Filename 183 f, err := parser.ParseFile(fset, filename, nil, parser.Mode(0)) 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 // not a cgo file 196 } 197 198 // Record the original import map. 199 for _, spec := range raw.Imports { 200 path, _ := strconv.Unquote(spec.Path.Value) 201 importMap[path] = imported(info, spec) 202 } 203 204 // Add special dot-import declaration: 205 // import . "·this·" 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 // Transform declarations from the raw cgo file. 221 for _, decl := range f.Decls { 222 switch decl := decl.(type) { 223 case *ast.GenDecl: 224 switch decl.Tok { 225 case token.TYPE: 226 // Discard type declarations. 227 continue 228 case token.IMPORT: 229 // Keep imports. 230 case token.VAR, token.CONST: 231 // Blank the declared var/const names. 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 // Blank the declared func name. 241 decl.Name.Name = "_" 242 243 // Turn a method receiver: func (T) f(P) R {...} 244 // into regular parameter: func _(T, P) R {...} 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) // debugging 258 } 259 cgoFiles = append(cgoFiles, f) 260 } 261 if cgoFiles == nil { 262 return nil, nil, nil // nothing to do (can't happen?) 263 } 264 265 // Type-check the synthetic files. 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) {}, // ignore errors (e.g. unused import) 273 } 274 275 // It's tempting to record the new types in the 276 // existing pass.TypesInfo, but we don't own it. 277 altInfo := &types.Info{ 278 Types: make(map[ast.Expr]types.TypeAndValue), 279 } 280 tc.Check(pkg.Path(), fset, cgoFiles, altInfo) 281 282 return cgoFiles, altInfo, nil 283 } 284 285 // cgoBaseType tries to look through type conversions involving 286 // unsafe.Pointer to find the real type. It converts: 287 // unsafe.Pointer(x) => x 288 // *(*unsafe.Pointer)(unsafe.Pointer(&x)) => x 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 // Here arg is *f(v). 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 // Here arg is *(*p)(v) 310 elem, ok := ptr.Elem().Underlying().(*types.Basic) 311 if !ok || elem.Kind() != types.UnsafePointer { 312 break 313 } 314 // Here arg is *(*unsafe.Pointer)(v) 315 call, ok = call.Args[0].(*ast.CallExpr) 316 if !ok || len(call.Args) != 1 { 317 break 318 } 319 // Here arg is *(*unsafe.Pointer)(f(v)) 320 if !isUnsafePointer(info, call.Fun) { 321 break 322 } 323 // Here arg is *(*unsafe.Pointer)(unsafe.Pointer(v)) 324 u, ok := call.Args[0].(*ast.UnaryExpr) 325 if !ok || u.Op != token.AND { 326 break 327 } 328 // Here arg is *(*unsafe.Pointer)(unsafe.Pointer(&v)) 329 return cgoBaseType(info, u.X) 330 } 331 332 return info.Types[arg].Type 333 } 334 335 // typeOKForCgoCall reports whether the type of arg is OK to pass to a 336 // C function using cgo. This is not true for Go types with embedded 337 // pointers. m is used to avoid infinite recursion on recursive types. 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 // TODO(adonovan): make this a library function or method of Info. 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] // renaming import 374 } 375 return obj.(*types.PkgName).Imported() 376 }