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