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 }