github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/tools/checklocks/annotations.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
    16  
    17  import (
    18  	"fmt"
    19  
    20  	"go/token"
    21  	"strconv"
    22  	"strings"
    23  )
    24  
    25  const (
    26  	checkLocksAnnotation     = "// +checklocks:"
    27  	checkLocksAnnotationRead = "// +checklocksread:"
    28  	checkLocksAcquires       = "// +checklocksacquire:"
    29  	checkLocksAcquiresRead   = "// +checklocksacquireread:"
    30  	checkLocksReleases       = "// +checklocksrelease:"
    31  	checkLocksReleasesRead   = "// +checklocksreleaseread:"
    32  	checkLocksIgnore         = "// +checklocksignore"
    33  	checkLocksForce          = "// +checklocksforce"
    34  	checkLocksFail           = "// +checklocksfail"
    35  	checkLocksAlias          = "// +checklocksalias:"
    36  	checkAtomicAnnotation    = "// +checkatomic"
    37  )
    38  
    39  // failData indicates an expected failure.
    40  type failData struct {
    41  	pos   token.Pos
    42  	count int
    43  	seen  int
    44  }
    45  
    46  // positionKey is a simple position string.
    47  type positionKey string
    48  
    49  // positionKey converts from a token.Pos to a key we can use to track failures
    50  // as the position of the failure annotation is not the same as the position of
    51  // the actual failure (different column/offsets). Hence we ignore these fields
    52  // and only use the file/line numbers to track failures.
    53  func (pc *passContext) positionKey(pos token.Pos) positionKey {
    54  	position := pc.pass.Fset.Position(pos)
    55  	return positionKey(fmt.Sprintf("%s:%d", position.Filename, position.Line))
    56  }
    57  
    58  // addFailures adds an expected failure.
    59  func (pc *passContext) addFailures(pos token.Pos, s string) {
    60  	count := 1
    61  	if len(s) > 0 && s[0] == ':' {
    62  		parsedCount, err := strconv.Atoi(s[1:])
    63  		if err != nil {
    64  			pc.pass.Reportf(pos, "unable to parse failure annotation %q: %v", s[1:], err)
    65  			return
    66  		}
    67  		count = parsedCount
    68  	}
    69  	pc.failures[pc.positionKey(pos)] = &failData{
    70  		pos:   pos,
    71  		count: count,
    72  	}
    73  }
    74  
    75  // addExemption adds an exemption.
    76  func (pc *passContext) addExemption(pos token.Pos) {
    77  	pc.exemptions[pc.positionKey(pos)] = struct{}{}
    78  }
    79  
    80  // addForce adds a force annotation.
    81  func (pc *passContext) addForce(pos token.Pos) {
    82  	pc.forced[pc.positionKey(pos)] = struct{}{}
    83  }
    84  
    85  // maybeFail checks a potential failure against a specific failure map.
    86  func (pc *passContext) maybeFail(pos token.Pos, fmtStr string, args ...any) {
    87  	if fd, ok := pc.failures[pc.positionKey(pos)]; ok {
    88  		fd.seen++
    89  		return
    90  	}
    91  	if _, ok := pc.exemptions[pc.positionKey(pos)]; ok {
    92  		return // Ignored, not counted.
    93  	}
    94  	if !enableWrappers && !pos.IsValid() {
    95  		return // Ignored, implicit.
    96  	}
    97  	pc.pass.Reportf(pos, fmtStr, args...)
    98  }
    99  
   100  // checkFailure checks for the expected failure counts.
   101  func (pc *passContext) checkFailures() {
   102  	for _, fd := range pc.failures {
   103  		if fd.count != fd.seen {
   104  			// We are missing expect failures, report as much as possible.
   105  			pc.pass.Reportf(fd.pos, "got %d failures, want %d failures", fd.seen, fd.count)
   106  		}
   107  	}
   108  }
   109  
   110  // extractAnnotations extracts annotations from text.
   111  func (pc *passContext) extractAnnotations(s string, fns map[string]func(p string)) {
   112  	for prefix, fn := range fns {
   113  		if strings.HasPrefix(s, prefix) {
   114  			fn(s[len(prefix):])
   115  		}
   116  	}
   117  }
   118  
   119  // extractLineFailures extracts all line-based exceptions.
   120  //
   121  // Note that this applies only to individual line exemptions, and does not
   122  // consider function-wide exemptions, or specific field exemptions, which are
   123  // extracted separately as part of the saved facts for those objects.
   124  func (pc *passContext) extractLineFailures() {
   125  	for _, f := range pc.pass.Files {
   126  		for _, cg := range f.Comments {
   127  			for _, c := range cg.List {
   128  				pc.extractAnnotations(c.Text, map[string]func(string){
   129  					checkLocksFail:   func(p string) { pc.addFailures(c.Pos(), p) },
   130  					checkLocksIgnore: func(string) { pc.addExemption(c.Pos()) },
   131  					checkLocksForce:  func(string) { pc.addForce(c.Pos()) },
   132  				})
   133  			}
   134  		}
   135  	}
   136  }