github.com/shogo82148/std@v1.22.1-0.20240327122250-4e474527810c/internal/bisect/bisect.go (about) 1 // Copyright 2023 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package bisect can be used by compilers and other programs 6 // to serve as a target for the bisect debugging tool. 7 // See [golang.org/x/tools/cmd/bisect] for details about using the tool. 8 // 9 // To be a bisect target, allowing bisect to help determine which of a set of independent 10 // changes provokes a failure, a program needs to: 11 // 12 // 1. Define a way to accept a change pattern on its command line or in its environment. 13 // The most common mechanism is a command-line flag. 14 // The pattern can be passed to [New] to create a [Matcher], the compiled form of a pattern. 15 // 16 // 2. Assign each change a unique ID. One possibility is to use a sequence number, 17 // but the most common mechanism is to hash some kind of identifying information 18 // like the file and line number where the change might be applied. 19 // [Hash] hashes its arguments to compute an ID. 20 // 21 // 3. Enable each change that the pattern says should be enabled. 22 // The [Matcher.ShouldEnable] method answers this question for a given change ID. 23 // 24 // 4. Print a report identifying each change that the pattern says should be printed. 25 // The [Matcher.ShouldPrint] method answers this question for a given change ID. 26 // The report consists of one more lines on standard error or standard output 27 // that contain a “match marker”. [Marker] returns the match marker for a given ID. 28 // When bisect reports a change as causing the failure, it identifies the change 29 // by printing the report lines with the match marker removed. 30 // 31 // # Example Usage 32 // 33 // A program starts by defining how it receives the pattern. In this example, we will assume a flag. 34 // The next step is to compile the pattern: 35 // 36 // m, err := bisect.New(patternFlag) 37 // if err != nil { 38 // log.Fatal(err) 39 // } 40 // 41 // Then, each time a potential change is considered, the program computes 42 // a change ID by hashing identifying information (source file and line, in this case) 43 // and then calls m.ShouldPrint and m.ShouldEnable to decide whether to 44 // print and enable the change, respectively. The two can return different values 45 // depending on whether bisect is trying to find a minimal set of changes to 46 // disable or to enable to provoke the failure. 47 // 48 // It is usually helpful to write a helper function that accepts the identifying information 49 // and then takes care of hashing, printing, and reporting whether the identified change 50 // should be enabled. For example, a helper for changes identified by a file and line number 51 // would be: 52 // 53 // func ShouldEnable(file string, line int) { 54 // h := bisect.Hash(file, line) 55 // if m.ShouldPrint(h) { 56 // fmt.Fprintf(os.Stderr, "%v %s:%d\n", bisect.Marker(h), file, line) 57 // } 58 // return m.ShouldEnable(h) 59 // } 60 // 61 // Finally, note that New returns a nil Matcher when there is no pattern, 62 // meaning that the target is not running under bisect at all, 63 // so all changes should be enabled and none should be printed. 64 // In that common case, the computation of the hash can be avoided entirely 65 // by checking for m == nil first: 66 // 67 // func ShouldEnable(file string, line int) bool { 68 // if m == nil { 69 // return true 70 // } 71 // h := bisect.Hash(file, line) 72 // if m.ShouldPrint(h) { 73 // fmt.Fprintf(os.Stderr, "%v %s:%d\n", bisect.Marker(h), file, line) 74 // } 75 // return m.ShouldEnable(h) 76 // } 77 // 78 // When the identifying information is expensive to format, this code can call 79 // [Matcher.MarkerOnly] to find out whether short report lines containing only the 80 // marker are permitted for a given run. (Bisect permits such lines when it is 81 // still exploring the space of possible changes and will not be showing the 82 // output to the user.) If so, the client can choose to print only the marker: 83 // 84 // func ShouldEnable(file string, line int) bool { 85 // if m == nil { 86 // return true 87 // } 88 // h := bisect.Hash(file, line) 89 // if m.ShouldPrint(h) { 90 // if m.MarkerOnly() { 91 // bisect.PrintMarker(os.Stderr, h) 92 // } else { 93 // fmt.Fprintf(os.Stderr, "%v %s:%d\n", bisect.Marker(h), file, line) 94 // } 95 // } 96 // return m.ShouldEnable(h) 97 // } 98 // 99 // This specific helper – deciding whether to enable a change identified by 100 // file and line number and printing about the change when necessary – is 101 // provided by the [Matcher.FileLine] method. 102 // 103 // Another common usage is deciding whether to make a change in a function 104 // based on the caller's stack, to identify the specific calling contexts that the 105 // change breaks. The [Matcher.Stack] method takes care of obtaining the stack, 106 // printing it when necessary, and reporting whether to enable the change 107 // based on that stack. 108 // 109 // # Pattern Syntax 110 // 111 // Patterns are generated by the bisect tool and interpreted by [New]. 112 // Users should not have to understand the patterns except when 113 // debugging a target's bisect support or debugging the bisect tool itself. 114 // 115 // The pattern syntax selecting a change is a sequence of bit strings 116 // separated by + and - operators. Each bit string denotes the set of 117 // changes with IDs ending in those bits, + is set addition, - is set subtraction, 118 // and the expression is evaluated in the usual left-to-right order. 119 // The special binary number “y” denotes the set of all changes, 120 // standing in for the empty bit string. 121 // In the expression, all the + operators must appear before all the - operators. 122 // A leading + adds to an empty set. A leading - subtracts from the set of all 123 // possible suffixes. 124 // 125 // For example: 126 // 127 // - “01+10” and “+01+10” both denote the set of changes 128 // with IDs ending with the bits 01 or 10. 129 // 130 // - “01+10-1001” denotes the set of changes with IDs 131 // ending with the bits 01 or 10, but excluding those ending in 1001. 132 // 133 // - “-01-1000” and “y-01-1000 both denote the set of all changes 134 // with IDs not ending in 01 nor 1000. 135 // 136 // - “0+1-01+001” is not a valid pattern, because all the + operators do not 137 // appear before all the - operators. 138 // 139 // In the syntaxes described so far, the pattern specifies the changes to 140 // enable and report. If a pattern is prefixed by a “!”, the meaning 141 // changes: the pattern specifies the changes to DISABLE and report. This 142 // mode of operation is needed when a program passes with all changes 143 // enabled but fails with no changes enabled. In this case, bisect 144 // searches for minimal sets of changes to disable. 145 // Put another way, the leading “!” inverts the result from [Matcher.ShouldEnable] 146 // but does not invert the result from [Matcher.ShouldPrint]. 147 // 148 // As a convenience for manual debugging, “n” is an alias for “!y”, 149 // meaning to disable and report all changes. 150 // 151 // Finally, a leading “v” in the pattern indicates that the reports will be shown 152 // to the user of bisect to describe the changes involved in a failure. 153 // At the API level, the leading “v” causes [Matcher.Visible] to return true. 154 // See the next section for details. 155 // 156 // # Match Reports 157 // 158 // The target program must enable only those changed matched 159 // by the pattern, and it must print a match report for each such change. 160 // A match report consists of one or more lines of text that will be 161 // printed by the bisect tool to describe a change implicated in causing 162 // a failure. Each line in the report for a given change must contain a 163 // match marker with that change ID, as returned by [Marker]. 164 // The markers are elided when displaying the lines to the user. 165 // 166 // A match marker has the form “[bisect-match 0x1234]” where 167 // 0x1234 is the change ID in hexadecimal. 168 // An alternate form is “[bisect-match 010101]”, giving the change ID in binary. 169 // 170 // When [Matcher.Visible] returns false, the match reports are only 171 // being processed by bisect to learn the set of enabled changes, 172 // not shown to the user, meaning that each report can be a match 173 // marker on a line by itself, eliding the usual textual description. 174 // When the textual description is expensive to compute, 175 // checking [Matcher.Visible] can help the avoid that expense 176 // in most runs. 177 package bisect 178 179 import ( 180 "github.com/shogo82148/std/sync/atomic" 181 ) 182 183 // New creates and returns a new Matcher implementing the given pattern. 184 // The pattern syntax is defined in the package doc comment. 185 // 186 // In addition to the pattern syntax syntax, New("") returns nil, nil. 187 // The nil *Matcher is valid for use: it returns true from ShouldEnable 188 // and false from ShouldPrint for all changes. Callers can avoid calling 189 // [Hash], [Matcher.ShouldEnable], and [Matcher.ShouldPrint] entirely 190 // when they recognize the nil Matcher. 191 func New(pattern string) (*Matcher, error) 192 193 // A Matcher is the parsed, compiled form of a PATTERN string. 194 // The nil *Matcher is valid: it has all changes enabled but none reported. 195 type Matcher struct { 196 verbose bool 197 quiet bool 198 enable bool 199 list []cond 200 dedup atomic.Pointer[dedup] 201 } 202 203 // MarkerOnly reports whether it is okay to print only the marker for 204 // a given change, omitting the identifying information. 205 // MarkerOnly returns true when bisect is using the printed reports 206 // only for an intermediate search step, not for showing to users. 207 func (m *Matcher) MarkerOnly() bool 208 209 // ShouldEnable reports whether the change with the given id should be enabled. 210 func (m *Matcher) ShouldEnable(id uint64) bool 211 212 // ShouldPrint reports whether to print identifying information about the change with the given id. 213 func (m *Matcher) ShouldPrint(id uint64) bool 214 215 // FileLine reports whether the change identified by file and line should be enabled. 216 // If the change should be printed, FileLine prints a one-line report to w. 217 func (m *Matcher) FileLine(w Writer, file string, line int) bool 218 219 // MatchStack assigns the current call stack a change ID. 220 // If the stack should be printed, MatchStack prints it. 221 // Then MatchStack reports whether a change at the current call stack should be enabled. 222 func (m *Matcher) Stack(w Writer) bool 223 224 // Writer is the same interface as io.Writer. 225 // It is duplicated here to avoid importing io. 226 type Writer interface { 227 Write([]byte) (int, error) 228 } 229 230 // PrintMarker prints to w a one-line report containing only the marker for h. 231 // It is appropriate to use when [Matcher.ShouldPrint] and [Matcher.MarkerOnly] both return true. 232 func PrintMarker(w Writer, h uint64) error 233 234 // Marker returns the match marker text to use on any line reporting details 235 // about a match of the given ID. 236 // It always returns the hexadecimal format. 237 func Marker(id uint64) string 238 239 // AppendMarker is like [Marker] but appends the marker to dst. 240 func AppendMarker(dst []byte, id uint64) []byte 241 242 // CutMarker finds the first match marker in line and removes it, 243 // returning the shortened line (with the marker removed), 244 // the ID from the match marker, 245 // and whether a marker was found at all. 246 // If there is no marker, CutMarker returns line, 0, false. 247 func CutMarker(line string) (short string, id uint64, ok bool) 248 249 // Hash computes a hash of the data arguments, 250 // each of which must be of type string, byte, int, uint, int32, uint32, int64, uint64, uintptr, or a slice of one of those types. 251 func Hash(data ...any) uint64