golang.org/x/tools@v0.21.0/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 "golang.org/x/tools/go/types/typeutil" 22 ) 23 24 const Doc = "check for non-64-bits-aligned arguments to sync/atomic functions" 25 26 var Analyzer = &analysis.Analyzer{ 27 Name: "atomicalign", 28 Doc: Doc, 29 URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/atomicalign", 30 Requires: []*analysis.Analyzer{inspect.Analyzer}, 31 Run: run, 32 } 33 34 func run(pass *analysis.Pass) (interface{}, error) { 35 if 8*pass.TypesSizes.Sizeof(types.Typ[types.Uintptr]) == 64 { 36 return nil, nil // 64-bit platform 37 } 38 if !analysisutil.Imports(pass.Pkg, "sync/atomic") { 39 return nil, nil // doesn't directly import sync/atomic 40 } 41 42 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) 43 nodeFilter := []ast.Node{ 44 (*ast.CallExpr)(nil), 45 } 46 funcNames := []string{ 47 "AddInt64", "AddUint64", 48 "LoadInt64", "LoadUint64", 49 "StoreInt64", "StoreUint64", 50 "SwapInt64", "SwapUint64", 51 "CompareAndSwapInt64", "CompareAndSwapUint64", 52 } 53 54 inspect.Preorder(nodeFilter, func(node ast.Node) { 55 call := node.(*ast.CallExpr) 56 fn := typeutil.StaticCallee(pass.TypesInfo, call) 57 if analysisutil.IsFunctionNamed(fn, "sync/atomic", funcNames...) { 58 // For all the listed functions, the expression to check is always the first function argument. 59 check64BitAlignment(pass, fn.Name(), call.Args[0]) 60 } 61 }) 62 63 return nil, nil 64 } 65 66 func check64BitAlignment(pass *analysis.Pass, funcName string, arg ast.Expr) { 67 // Checks the argument is made of the address operator (&) applied to 68 // a struct field (as opposed to a variable as the first word of 69 // uint64 and int64 variables can be relied upon to be 64-bit aligned). 70 unary, ok := arg.(*ast.UnaryExpr) 71 if !ok || unary.Op != token.AND { 72 return 73 } 74 75 // Retrieve the types.Struct in order to get the offset of the 76 // atomically accessed field. 77 sel, ok := unary.X.(*ast.SelectorExpr) 78 if !ok { 79 return 80 } 81 tvar, ok := pass.TypesInfo.Selections[sel].Obj().(*types.Var) 82 if !ok || !tvar.IsField() { 83 return 84 } 85 86 stype, ok := pass.TypesInfo.Types[sel.X].Type.Underlying().(*types.Struct) 87 if !ok { 88 return 89 } 90 91 var offset int64 92 var fields []*types.Var 93 for i := 0; i < stype.NumFields(); i++ { 94 f := stype.Field(i) 95 fields = append(fields, f) 96 if f == tvar { 97 // We're done, this is the field we were looking for, 98 // no need to fill the fields slice further. 99 offset = pass.TypesSizes.Offsetsof(fields)[i] 100 break 101 } 102 } 103 if offset&7 == 0 { 104 return // 64-bit aligned 105 } 106 107 pass.ReportRangef(arg, "address of non 64-bit aligned field .%s passed to atomic.%s", tvar.Name(), funcName) 108 }