| 1 | // Copyright 2014 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 unsafeptr defines an Analyzer that checks for invalid |
| 6 | // conversions of uintptr to unsafe.Pointer. |
| 7 | package unsafeptr |
| 8 | |
| 9 | import ( |
| 10 | "go/ast" |
| 11 | "go/token" |
| 12 | "go/types" |
| 13 | |
| 14 | "golang.org/x/tools/go/analysis" |
| 15 | "golang.org/x/tools/go/analysis/passes/inspect" |
| 16 | "golang.org/x/tools/go/analysis/passes/internal/analysisutil" |
| 17 | "golang.org/x/tools/go/ast/inspector" |
| 18 | ) |
| 19 | |
| 20 | const Doc = `check for invalid conversions of uintptr to unsafe.Pointer |
| 21 | |
| 22 | The unsafeptr analyzer reports likely incorrect uses of unsafe.Pointer |
| 23 | to convert integers to pointers. A conversion from uintptr to |
| 24 | unsafe.Pointer is invalid if it implies that there is a uintptr-typed |
| 25 | word in memory that holds a pointer value, because that word will be |
| 26 | invisible to stack copying and to the garbage collector.` |
| 27 | |
| 28 | var Analyzer = &analysis.Analyzer{ |
| 29 | Name: "unsafeptr", |
| 30 | Doc: Doc, |
| 31 | Requires: []*analysis.Analyzer{inspect.Analyzer}, |
| 32 | Run: run, |
| 33 | } |
| 34 | |
| 35 | func run(pass *analysis.Pass) (interface{}, error) { |
| 36 | inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) |
| 37 | |
| 38 | nodeFilter := []ast.Node{ |
| 39 | (*ast.CallExpr)(nil), |
| 40 | (*ast.StarExpr)(nil), |
| 41 | (*ast.UnaryExpr)(nil), |
| 42 | } |
| 43 | inspect.Preorder(nodeFilter, func(n ast.Node) { |
| 44 | switch x := n.(type) { |
| 45 | case *ast.CallExpr: |
| 46 | if len(x.Args) == 1 && |
| 47 | hasBasicType(pass.TypesInfo, x.Fun, types.UnsafePointer) && |
| 48 | hasBasicType(pass.TypesInfo, x.Args[0], types.Uintptr) && |
| 49 | !isSafeUintptr(pass.TypesInfo, x.Args[0]) { |
| 50 | pass.ReportRangef(x, "possible misuse of unsafe.Pointer") |
| 51 | } |
| 52 | case *ast.StarExpr: |
| 53 | if t := pass.TypesInfo.Types[x].Type; isReflectHeader(t) { |
| 54 | pass.ReportRangef(x, "possible misuse of %s", t) |
| 55 | } |
| 56 | case *ast.UnaryExpr: |
| 57 | if x.Op != token.AND { |
| 58 | return |
| 59 | } |
| 60 | if t := pass.TypesInfo.Types[x.X].Type; isReflectHeader(t) { |
| 61 | pass.ReportRangef(x, "possible misuse of %s", t) |
| 62 | } |
| 63 | } |
| 64 | }) |
| 65 | return nil, nil |
| 66 | } |
| 67 | |
| 68 | // isSafeUintptr reports whether x - already known to be a uintptr - |
| 69 | // is safe to convert to unsafe.Pointer. |
| 70 | func isSafeUintptr(info *types.Info, x ast.Expr) bool { |
| 71 | // Check unsafe.Pointer safety rules according to |
| 72 | // https://golang.org/pkg/unsafe/#Pointer. |
| 73 | |
| 74 | switch x := analysisutil.Unparen(x).(type) { |
| 75 | case *ast.SelectorExpr: |
| 76 | // "(6) Conversion of a reflect.SliceHeader or |
| 77 | // reflect.StringHeader Data field to or from Pointer." |
| 78 | if x.Sel.Name != "Data" { |
| 79 | break |
| 80 | } |
| 81 | // reflect.SliceHeader and reflect.StringHeader are okay, |
| 82 | // but only if they are pointing at a real slice or string. |
| 83 | // It's not okay to do: |
| 84 | // var x SliceHeader |
| 85 | // x.Data = uintptr(unsafe.Pointer(...)) |
| 86 | // ... use x ... |
| 87 | // p := unsafe.Pointer(x.Data) |
| 88 | // because in the middle the garbage collector doesn't |
| 89 | // see x.Data as a pointer and so x.Data may be dangling |
| 90 | // by the time we get to the conversion at the end. |
| 91 | // For now approximate by saying that *Header is okay |
| 92 | // but Header is not. |
| 93 | pt, ok := info.Types[x.X].Type.(*types.Pointer) |
| 94 | if ok && isReflectHeader(pt.Elem()) { |
| 95 | return true |
| 96 | } |
| 97 | |
| 98 | case *ast.CallExpr: |
| 99 | // "(5) Conversion of the result of reflect.Value.Pointer or |
| 100 | // reflect.Value.UnsafeAddr from uintptr to Pointer." |
| 101 | if len(x.Args) != 0 { |
| 102 | break |
| 103 | } |
| 104 | sel, ok := x.Fun.(*ast.SelectorExpr) |
| 105 | if !ok { |
| 106 | break |
| 107 | } |
| 108 | switch sel.Sel.Name { |
| 109 | case "Pointer", "UnsafeAddr": |
| 110 | t, ok := info.Types[sel.X].Type.(*types.Named) |
| 111 | if ok && t.Obj().Pkg().Path() == "reflect" && t.Obj().Name() == "Value" { |
| 112 | return true |
| 113 | } |
| 114 | } |
| 115 | } |
| 116 | |
| 117 | // "(3) Conversion of a Pointer to a uintptr and back, with arithmetic." |
| 118 | return isSafeArith(info, x) |
| 119 | } |
| 120 | |
| 121 | // isSafeArith reports whether x is a pointer arithmetic expression that is safe |
| 122 | // to convert to unsafe.Pointer. |
| 123 | func isSafeArith(info *types.Info, x ast.Expr) bool { |
| 124 | switch x := analysisutil.Unparen(x).(type) { |
| 125 | case *ast.CallExpr: |
| 126 | // Base case: initial conversion from unsafe.Pointer to uintptr. |
| 127 | return len(x.Args) == 1 && |
| 128 | hasBasicType(info, x.Fun, types.Uintptr) && |
| 129 | hasBasicType(info, x.Args[0], types.UnsafePointer) |
| 130 | |
| 131 | case *ast.BinaryExpr: |
| 132 | // "It is valid both to add and to subtract offsets from a |
| 133 | // pointer in this way. It is also valid to use &^ to round |
| 134 | // pointers, usually for alignment." |
| 135 | switch x.Op { |
| 136 | case token.ADD, token.SUB, token.AND_NOT: |
| 137 | // TODO(mdempsky): Match compiler |
| 138 | // semantics. ADD allows a pointer on either |
| 139 | // side; SUB and AND_NOT don't care about RHS. |
| 140 | return isSafeArith(info, x.X) && !isSafeArith(info, x.Y) |
| 141 | } |
| 142 | } |
| 143 | |
| 144 | return false |
| 145 | } |
| 146 | |
| 147 | // hasBasicType reports whether x's type is a types.Basic with the given kind. |
| 148 | func hasBasicType(info *types.Info, x ast.Expr, kind types.BasicKind) bool { |
| 149 | t := info.Types[x].Type |
| 150 | if t != nil { |
| 151 | t = t.Underlying() |
| 152 | } |
| 153 | b, ok := t.(*types.Basic) |
| 154 | return ok && b.Kind() == kind |
| 155 | } |
| 156 | |
| 157 | // isReflectHeader reports whether t is reflect.SliceHeader or reflect.StringHeader. |
| 158 | func isReflectHeader(t types.Type) bool { |
| 159 | if named, ok := t.(*types.Named); ok { |
| 160 | if obj := named.Obj(); obj.Pkg() != nil && obj.Pkg().Path() == "reflect" { |
| 161 | switch obj.Name() { |
| 162 | case "SliceHeader", "StringHeader": |
| 163 | return true |
| 164 | } |
| 165 | } |
| 166 | } |
| 167 | return false |
| 168 | } |
| 169 |
Members