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