// Copyright 2014 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package unsafeptr defines an Analyzer that checks for invalid // conversions of uintptr to unsafe.Pointer. package unsafeptr import ( "go/ast" "go/token" "go/types" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/inspect" "golang.org/x/tools/go/ast/inspector" ) const Doc = `check for invalid conversions of uintptr to unsafe.Pointer The unsafeptr analyzer reports likely incorrect uses of unsafe.Pointer to convert integers to pointers. A conversion from uintptr to unsafe.Pointer is invalid if it implies that there is a uintptr-typed word in memory that holds a pointer value, because that word will be invisible to stack copying and to the garbage collector.` var Analyzer = &analysis.Analyzer{ Name: "unsafeptr", Doc: Doc, Requires: []*analysis.Analyzer{inspect.Analyzer}, Run: run, } func run(pass *analysis.Pass) (interface{}, error) { inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) nodeFilter := []ast.Node{ (*ast.CallExpr)(nil), } inspect.Preorder(nodeFilter, func(n ast.Node) { x := n.(*ast.CallExpr) if len(x.Args) != 1 { return } if hasBasicType(pass.TypesInfo, x.Fun, types.UnsafePointer) && hasBasicType(pass.TypesInfo, x.Args[0], types.Uintptr) && !isSafeUintptr(pass.TypesInfo, x.Args[0]) { pass.ReportRangef(x, "possible misuse of unsafe.Pointer") } }) return nil, nil } // isSafeUintptr reports whether x - already known to be a uintptr - // is safe to convert to unsafe.Pointer. It is safe if x is itself derived // directly from an unsafe.Pointer via conversion and pointer arithmetic // or if x is the result of reflect.Value.Pointer or reflect.Value.UnsafeAddr // or obtained from the Data field of a *reflect.SliceHeader or *reflect.StringHeader. func isSafeUintptr(info *types.Info, x ast.Expr) bool { switch x := x.(type) { case *ast.ParenExpr: return isSafeUintptr(info, x.X) case *ast.SelectorExpr: if x.Sel.Name != "Data" { break } // reflect.SliceHeader and reflect.StringHeader are okay, // but only if they are pointing at a real slice or string. // It's not okay to do: // var x SliceHeader // x.Data = uintptr(unsafe.Pointer(...)) // ... use x ... // p := unsafe.Pointer(x.Data) // because in the middle the garbage collector doesn't // see x.Data as a pointer and so x.Data may be dangling // by the time we get to the conversion at the end. // For now approximate by saying that *Header is okay // but Header is not. pt, ok := info.Types[x.X].Type.(*types.Pointer) if ok { t, ok := pt.Elem().(*types.Named) if ok && t.Obj().Pkg().Path() == "reflect" { switch t.Obj().Name() { case "StringHeader", "SliceHeader": return true } } } case *ast.CallExpr: switch len(x.Args) { case 0: // maybe call to reflect.Value.Pointer or reflect.Value.UnsafeAddr. sel, ok := x.Fun.(*ast.SelectorExpr) if !ok { break } switch sel.Sel.Name { case "Pointer", "UnsafeAddr": t, ok := info.Types[sel.X].Type.(*types.Named) if ok && t.Obj().Pkg().Path() == "reflect" && t.Obj().Name() == "Value" { return true } } case 1: // maybe conversion of uintptr to unsafe.Pointer return hasBasicType(info, x.Fun, types.Uintptr) && hasBasicType(info, x.Args[0], types.UnsafePointer) } case *ast.BinaryExpr: switch x.Op { case token.ADD, token.SUB, token.AND_NOT: return isSafeUintptr(info, x.X) && !isSafeUintptr(info, x.Y) } } return false } // hasBasicType reports whether x's type is a types.Basic with the given kind. func hasBasicType(info *types.Info, x ast.Expr, kind types.BasicKind) bool { t := info.Types[x].Type if t != nil { t = t.Underlying() } b, ok := t.(*types.Basic) return ok && b.Kind() == kind }