github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/tools/checklocks/checklocks.go (about)

     1  // Copyright 2020 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 checklocks performs lock analysis to identify and flag unprotected
    16  // access to annotated fields.
    17  //
    18  // For detailed usage refer to README.md in the same directory.
    19  //
    20  // Note that this package uses the built-in atomics, in order to avoid the use
    21  // of our own atomic package. This is because our own atomic package depends on
    22  // our own sync package, which includes lock dependency analysis. This in turn
    23  // requires goid, which introduces a dependency cycle. To avoid this, we simply
    24  // use the simpler, built-in sync package.
    25  //
    26  // +checkalignedignore
    27  package checklocks
    28  
    29  import (
    30  	"go/ast"
    31  	"go/token"
    32  	"go/types"
    33  
    34  	"golang.org/x/tools/go/analysis"
    35  	"golang.org/x/tools/go/analysis/passes/buildssa"
    36  	"golang.org/x/tools/go/ssa"
    37  )
    38  
    39  // Analyzer is the main entrypoint.
    40  var Analyzer = &analysis.Analyzer{
    41  	Name:     "checklocks",
    42  	Doc:      "checks lock preconditions on functions and fields",
    43  	Run:      run,
    44  	Requires: []*analysis.Analyzer{buildssa.Analyzer},
    45  	FactTypes: []analysis.Fact{
    46  		(*atomicAlignment)(nil),
    47  		(*lockGuardFacts)(nil),
    48  		(*lockFunctionFacts)(nil),
    49  	},
    50  }
    51  
    52  var (
    53  	enableInferred = true
    54  	enableAtomic   = true
    55  	enableWrappers = true
    56  )
    57  
    58  func init() {
    59  	Analyzer.Flags.BoolVar(&enableInferred, "inferred", true, "enable inferred locks")
    60  	Analyzer.Flags.BoolVar(&enableAtomic, "atomic", true, "enable atomic checks")
    61  	Analyzer.Flags.BoolVar(&enableWrappers, "wrappers", true, "enable analysis of wrappers")
    62  }
    63  
    64  // objectObservations tracks lock correlations.
    65  type objectObservations struct {
    66  	counts map[types.Object]int
    67  	total  int
    68  }
    69  
    70  // passContext is a pass with additional expected failures.
    71  type passContext struct {
    72  	pass         *analysis.Pass
    73  	failures     map[positionKey]*failData
    74  	exemptions   map[positionKey]struct{}
    75  	forced       map[positionKey]struct{}
    76  	functions    map[*ssa.Function]struct{}
    77  	observations map[types.Object]*objectObservations
    78  }
    79  
    80  // observationsFor retrieves observations for the given object.
    81  func (pc *passContext) observationsFor(obj types.Object) *objectObservations {
    82  	if pc.observations == nil {
    83  		pc.observations = make(map[types.Object]*objectObservations)
    84  	}
    85  	oo, ok := pc.observations[obj]
    86  	if !ok {
    87  		oo = &objectObservations{
    88  			counts: make(map[types.Object]int),
    89  		}
    90  		pc.observations[obj] = oo
    91  	}
    92  	return oo
    93  }
    94  
    95  // forAllGlobals applies the given function to all globals.
    96  func (pc *passContext) forAllGlobals(fn func(ts *ast.ValueSpec)) {
    97  	for _, f := range pc.pass.Files {
    98  		for _, decl := range f.Decls {
    99  			d, ok := decl.(*ast.GenDecl)
   100  			if !ok || d.Tok != token.VAR {
   101  				continue
   102  			}
   103  			for _, gs := range d.Specs {
   104  				fn(gs.(*ast.ValueSpec))
   105  			}
   106  		}
   107  	}
   108  }
   109  
   110  // forAllTypes applies the given function over all types.
   111  func (pc *passContext) forAllTypes(fn func(ts *ast.TypeSpec)) {
   112  	for _, f := range pc.pass.Files {
   113  		for _, decl := range f.Decls {
   114  			d, ok := decl.(*ast.GenDecl)
   115  			if !ok || d.Tok != token.TYPE {
   116  				continue
   117  			}
   118  			for _, gs := range d.Specs {
   119  				fn(gs.(*ast.TypeSpec))
   120  			}
   121  		}
   122  	}
   123  }
   124  
   125  // forAllFunctions applies the given function over all functions.
   126  func (pc *passContext) forAllFunctions(fn func(fn *ast.FuncDecl)) {
   127  	for _, f := range pc.pass.Files {
   128  		for _, decl := range f.Decls {
   129  			d, ok := decl.(*ast.FuncDecl)
   130  			if !ok {
   131  				continue
   132  			}
   133  			fn(d)
   134  		}
   135  	}
   136  }
   137  
   138  // run is the main entrypoint.
   139  func run(pass *analysis.Pass) (any, error) {
   140  	pc := &passContext{
   141  		pass:       pass,
   142  		failures:   make(map[positionKey]*failData),
   143  		exemptions: make(map[positionKey]struct{}),
   144  		forced:     make(map[positionKey]struct{}),
   145  		functions:  make(map[*ssa.Function]struct{}),
   146  	}
   147  
   148  	// Find all line failure annotations.
   149  	pc.extractLineFailures()
   150  
   151  	// Find all struct declarations and export relevant facts.
   152  	pc.forAllGlobals(func(vs *ast.ValueSpec) {
   153  		if ss, ok := vs.Type.(*ast.StructType); ok {
   154  			structType := pc.pass.TypesInfo.TypeOf(vs.Type).Underlying().(*types.Struct)
   155  			pc.structLockGuardFacts(structType, ss)
   156  		}
   157  		pc.globalLockGuardFacts(vs)
   158  	})
   159  	pc.forAllTypes(func(ts *ast.TypeSpec) {
   160  		if ss, ok := ts.Type.(*ast.StructType); ok {
   161  			structType := pc.pass.TypesInfo.TypeOf(ts.Name).Underlying().(*types.Struct)
   162  			pc.structLockGuardFacts(structType, ss)
   163  		}
   164  	})
   165  
   166  	// Check all alignments.
   167  	pc.forAllTypes(func(ts *ast.TypeSpec) {
   168  		typ, ok := pass.TypesInfo.TypeOf(ts.Name).(*types.Named)
   169  		if !ok {
   170  			return
   171  		}
   172  		pc.checkTypeAlignment(pass.Pkg, typ)
   173  	})
   174  
   175  	// Find all function declarations and export relevant facts.
   176  	pc.forAllFunctions(func(fn *ast.FuncDecl) {
   177  		pc.functionFacts(fn)
   178  	})
   179  
   180  	// Scan all code looking for invalid accesses.
   181  	state := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA)
   182  	for _, fn := range state.SrcFuncs {
   183  		// Import function facts generated above.
   184  		//
   185  		// Note that anonymous(closures) functions do not have an
   186  		// object but do show up in the SSA. They can only be invoked
   187  		// by named functions in the package, and they are analyzing
   188  		// inline on every call. Thus we skip the analysis here. They
   189  		// will be hit on calls, or picked up in the pass below.
   190  		if obj := fn.Object(); obj == nil {
   191  			continue
   192  		}
   193  		var lff lockFunctionFacts
   194  		pc.pass.ImportObjectFact(fn.Object(), &lff)
   195  
   196  		// Check the basic blocks in the function.
   197  		pc.checkFunction(nil, fn, &lff, nil, false /* force */)
   198  	}
   199  	for _, fn := range state.SrcFuncs {
   200  		// Ensure all anonymous functions are hit. They are not
   201  		// permitted to have any lock preconditions.
   202  		if obj := fn.Object(); obj != nil {
   203  			continue
   204  		}
   205  		var nolff lockFunctionFacts
   206  		pc.checkFunction(nil, fn, &nolff, nil, false /* force */)
   207  	}
   208  
   209  	// Check for inferred checklocks annotations.
   210  	if enableInferred {
   211  		pc.checkInferred()
   212  	}
   213  
   214  	// Check for expected failures.
   215  	pc.checkFailures()
   216  
   217  	return nil, nil
   218  }