114 lines
2.9 KiB
Go
114 lines
2.9 KiB
Go
// Copyright 2022 The gVisor Authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// Package checkaligned ensures that atomic (u)int operations happen
|
|
// exclusively via the atomicbitops package.
|
|
//
|
|
// We support a "// +checkalignedignore" escape hatch in the package comment
|
|
// that disables checking throughout the package.
|
|
package checkaligned
|
|
|
|
import (
|
|
"fmt"
|
|
"go/ast"
|
|
"strings"
|
|
|
|
"golang.org/x/tools/go/analysis"
|
|
)
|
|
|
|
// Analyzer defines the entrypoint.
|
|
var Analyzer = &analysis.Analyzer{
|
|
Name: "checkaligned",
|
|
Doc: "prohibits direct use of atomic int operations",
|
|
Run: run,
|
|
}
|
|
|
|
// blocklist lists prohibited identifiers in the atomic package.
|
|
var blocklist = []string{
|
|
"AddInt64",
|
|
"AddUint64",
|
|
"CompareAndSwapInt64",
|
|
"CompareAndSwapUint64",
|
|
"LoadInt64",
|
|
"LoadUint64",
|
|
"StoreInt64",
|
|
"StoreUint64",
|
|
"SwapInt64",
|
|
"SwapUint64",
|
|
|
|
"AddInt32",
|
|
"AddUint32",
|
|
"CompareAndSwapInt32",
|
|
"CompareAndSwapUint32",
|
|
"LoadInt32",
|
|
"LoadUint32",
|
|
"StoreInt32",
|
|
"StoreUint32",
|
|
"SwapInt32",
|
|
"SwapUint32",
|
|
}
|
|
|
|
func run(pass *analysis.Pass) (interface{}, error) {
|
|
// Check for the "// +checkalignedignore" escape hatch.
|
|
for _, file := range pass.Files {
|
|
if file.Doc == nil {
|
|
continue
|
|
}
|
|
for _, comment := range file.Doc.List {
|
|
if len(comment.Text) > 2 && strings.HasPrefix(comment.Text[2:], " +checkalignedignore") {
|
|
return nil, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, file := range pass.Files {
|
|
ast.Inspect(file, func(node ast.Node) bool {
|
|
// Only look at selector expressions (e.g. "foo.Bar").
|
|
selExpr, ok := node.(*ast.SelectorExpr)
|
|
if !ok {
|
|
return true
|
|
}
|
|
|
|
// Package names are always identifiers and do not refer to objects.
|
|
pkgIdent, ok := selExpr.X.(*ast.Ident)
|
|
if !ok || pkgIdent.Obj != nil {
|
|
return true
|
|
}
|
|
|
|
// Please don't trick this checker by renaming the atomic import.
|
|
if pkgIdent.Name != "atomic" {
|
|
return false
|
|
}
|
|
|
|
for _, blocked := range blocklist {
|
|
if selExpr.Sel.Name == blocked {
|
|
// All blocked functions end in Int32 or Uint32, which we can use to
|
|
// suggest the correct type.
|
|
typeNameLen := len("Int")
|
|
if unsigned := "Uint"; strings.Contains(blocked, unsigned) {
|
|
typeNameLen = len(unsigned)
|
|
}
|
|
typeNameLen += 2 // Account for the "32" or "64" suffix.
|
|
typeName := blocked[len(blocked)-typeNameLen:]
|
|
pass.Reportf(selExpr.Pos(), fmt.Sprintf("don't call atomic.%s; use atomicbitops.%s instead", blocked, typeName))
|
|
}
|
|
}
|
|
|
|
return false
|
|
})
|
|
}
|
|
|
|
return nil, nil
|
|
}
|