github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/go/analysis/passes/atomicalign/atomicalign.go (about) 1 // Copyright 2019 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 atomicalign defines an Analyzer that checks for non-64-bit-aligned 6 // arguments to sync/atomic functions. On non-32-bit platforms, those functions 7 // panic if their argument variables are not 64-bit aligned. It is therefore 8 // the caller's responsibility to arrange for 64-bit alignment of such variables. 9 // See https://golang.org/pkg/sync/atomic/#pkg-note-BUG 10 package atomicalign 11 12 import ( 13 "go/ast" 14 "go/token" 15 "go/types" 16 17 "golang.org/x/tools/go/analysis" 18 "golang.org/x/tools/go/analysis/passes/inspect" 19 "golang.org/x/tools/go/analysis/passes/internal/analysisutil" 20 "golang.org/x/tools/go/ast/inspector" 21 ) 22 23 const Doc = "check for non-64-bits-aligned arguments to sync/atomic functions" 24 25 var Analyzer = &analysis.Analyzer{ 26 Name: "atomicalign", 27 Doc: Doc, 28 Requires: []*analysis.Analyzer{inspect.Analyzer}, 29 Run: run, 30 } 31 32 func run(pass *analysis.Pass) (interface{}, error) { 33 if 8*pass.TypesSizes.Sizeof(types.Typ[types.Uintptr]) == 64 { 34 return nil, nil // 64-bit platform 35 } 36 if !analysisutil.Imports(pass.Pkg, "sync/atomic") { 37 return nil, nil // doesn't directly import sync/atomic 38 } 39 40 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) 41 nodeFilter := []ast.Node{ 42 (*ast.CallExpr)(nil), 43 } 44 45 inspect.Preorder(nodeFilter, func(node ast.Node) { 46 call := node.(*ast.CallExpr) 47 sel, ok := call.Fun.(*ast.SelectorExpr) 48 if !ok { 49 return 50 } 51 pkgIdent, ok := sel.X.(*ast.Ident) 52 if !ok { 53 return 54 } 55 pkgName, ok := pass.TypesInfo.Uses[pkgIdent].(*types.PkgName) 56 if !ok || pkgName.Imported().Path() != "sync/atomic" { 57 return 58 } 59 60 switch sel.Sel.Name { 61 case "AddInt64", "AddUint64", 62 "LoadInt64", "LoadUint64", 63 "StoreInt64", "StoreUint64", 64 "SwapInt64", "SwapUint64", 65 "CompareAndSwapInt64", "CompareAndSwapUint64": 66 67 // For all the listed functions, the expression to check is always the first function argument. 68 check64BitAlignment(pass, sel.Sel.Name, call.Args[0]) 69 } 70 }) 71 72 return nil, nil 73 } 74 75 func check64BitAlignment(pass *analysis.Pass, funcName string, arg ast.Expr) { 76 // Checks the argument is made of the address operator (&) applied to 77 // to a struct field (as opposed to a variable as the first word of 78 // uint64 and int64 variables can be relied upon to be 64-bit aligned. 79 unary, ok := arg.(*ast.UnaryExpr) 80 if !ok || unary.Op != token.AND { 81 return 82 } 83 84 // Retrieve the types.Struct in order to get the offset of the 85 // atomically accessed field. 86 sel, ok := unary.X.(*ast.SelectorExpr) 87 if !ok { 88 return 89 } 90 tvar, ok := pass.TypesInfo.Selections[sel].Obj().(*types.Var) 91 if !ok || !tvar.IsField() { 92 return 93 } 94 95 stype, ok := pass.TypesInfo.Types[sel.X].Type.Underlying().(*types.Struct) 96 if !ok { 97 return 98 } 99 100 var offset int64 101 var fields []*types.Var 102 for i := 0; i < stype.NumFields(); i++ { 103 f := stype.Field(i) 104 fields = append(fields, f) 105 if f == tvar { 106 // We're done, this is the field we were looking for, 107 // no need to fill the fields slice further. 108 offset = pass.TypesSizes.Offsetsof(fields)[i] 109 break 110 } 111 } 112 if offset&7 == 0 { 113 return // 64-bit aligned 114 } 115 116 pass.ReportRangef(arg, "address of non 64-bit aligned field .%s passed to atomic.%s", tvar.Name(), funcName) 117 }