gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/tools/checkaligned/checkaligned.go (about) 1 // Copyright 2022 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package checkaligned ensures that atomic (u)int operations happen 16 // exclusively via the atomicbitops package. 17 // 18 // We support a "// +checkalignedignore" escape hatch in the package comment 19 // that disables checking throughout the package. 20 package checkaligned 21 22 import ( 23 "fmt" 24 "go/ast" 25 "strings" 26 27 "golang.org/x/tools/go/analysis" 28 ) 29 30 // Analyzer defines the entrypoint. 31 var Analyzer = &analysis.Analyzer{ 32 Name: "checkaligned", 33 Doc: "prohibits direct use of atomic int operations", 34 Run: run, 35 } 36 37 // blocklist lists prohibited identifiers in the atomic package. 38 var blocklist = []string{ 39 "AddInt64", 40 "AddUint64", 41 "CompareAndSwapInt64", 42 "CompareAndSwapUint64", 43 "LoadInt64", 44 "LoadUint64", 45 "StoreInt64", 46 "StoreUint64", 47 "SwapInt64", 48 "SwapUint64", 49 50 "AddInt32", 51 "AddUint32", 52 "CompareAndSwapInt32", 53 "CompareAndSwapUint32", 54 "LoadInt32", 55 "LoadUint32", 56 "StoreInt32", 57 "StoreUint32", 58 "SwapInt32", 59 "SwapUint32", 60 } 61 62 func run(pass *analysis.Pass) (any, error) { 63 // Check for the "// +checkalignedignore" escape hatch. 64 for _, file := range pass.Files { 65 if file.Doc == nil { 66 continue 67 } 68 for _, comment := range file.Doc.List { 69 if len(comment.Text) > 2 && strings.HasPrefix(comment.Text[2:], " +checkalignedignore") { 70 return nil, nil 71 } 72 } 73 } 74 75 for _, file := range pass.Files { 76 ast.Inspect(file, func(node ast.Node) bool { 77 // Only look at selector expressions (e.g. "foo.Bar"). 78 selExpr, ok := node.(*ast.SelectorExpr) 79 if !ok { 80 return true 81 } 82 83 // Package names are always identifiers and do not refer to objects. 84 pkgIdent, ok := selExpr.X.(*ast.Ident) 85 if !ok || pkgIdent.Obj != nil { 86 return true 87 } 88 89 // Please don't trick this checker by renaming the atomic import. 90 if pkgIdent.Name != "atomic" { 91 return false 92 } 93 94 for _, blocked := range blocklist { 95 if selExpr.Sel.Name == blocked { 96 // All blocked functions end in Int32 or Uint32, which we can use to 97 // suggest the correct type. 98 typeNameLen := len("Int") 99 if unsigned := "Uint"; strings.Contains(blocked, unsigned) { 100 typeNameLen = len(unsigned) 101 } 102 typeNameLen += 2 // Account for the "32" or "64" suffix. 103 typeName := blocked[len(blocked)-typeNameLen:] 104 pass.Reportf(selExpr.Pos(), fmt.Sprintf("don't call atomic.%s; use atomicbitops.%s instead", blocked, typeName)) 105 } 106 } 107 108 return false 109 }) 110 } 111 112 return nil, nil 113 }