Source file src/encoding/json/jsontext/decode_test.go

     1  // Copyright 2020 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  //go:build goexperiment.jsonv2
     6  
     7  package jsontext
     8  
     9  import (
    10  	"bytes"
    11  	"errors"
    12  	"fmt"
    13  	"io"
    14  	"net"
    15  	"path"
    16  	"reflect"
    17  	"slices"
    18  	"strings"
    19  	"testing"
    20  	"testing/iotest"
    21  
    22  	"encoding/json/internal/jsonflags"
    23  	"encoding/json/internal/jsontest"
    24  	"encoding/json/internal/jsonwire"
    25  )
    26  
    27  // equalTokens reports whether to sequences of tokens formats the same way.
    28  func equalTokens(xs, ys []Token) bool {
    29  	if len(xs) != len(ys) {
    30  		return false
    31  	}
    32  	for i := range xs {
    33  		if !(reflect.DeepEqual(xs[i], ys[i]) || xs[i].String() == ys[i].String()) {
    34  			return false
    35  		}
    36  	}
    37  	return true
    38  }
    39  
    40  // TestDecoder tests whether we can parse JSON with either tokens or raw values.
    41  func TestDecoder(t *testing.T) {
    42  	for _, td := range coderTestdata {
    43  		for _, typeName := range []string{"Token", "Value", "TokenDelims"} {
    44  			t.Run(path.Join(td.name.Name, typeName), func(t *testing.T) {
    45  				testDecoder(t, td.name.Where, typeName, td)
    46  			})
    47  		}
    48  	}
    49  }
    50  func testDecoder(t *testing.T, where jsontest.CasePos, typeName string, td coderTestdataEntry) {
    51  	dec := NewDecoder(bytes.NewBufferString(td.in))
    52  	switch typeName {
    53  	case "Token":
    54  		var tokens []Token
    55  		var pointers []Pointer
    56  		for {
    57  			tok, err := dec.ReadToken()
    58  			if err != nil {
    59  				if err == io.EOF {
    60  					break
    61  				}
    62  				t.Fatalf("%s: Decoder.ReadToken error: %v", where, err)
    63  			}
    64  			tokens = append(tokens, tok.Clone())
    65  			if td.pointers != nil {
    66  				pointers = append(pointers, dec.StackPointer())
    67  			}
    68  		}
    69  		if !equalTokens(tokens, td.tokens) {
    70  			t.Fatalf("%s: tokens mismatch:\ngot  %v\nwant %v", where, tokens, td.tokens)
    71  		}
    72  		if !slices.Equal(pointers, td.pointers) {
    73  			t.Fatalf("%s: pointers mismatch:\ngot  %q\nwant %q", where, pointers, td.pointers)
    74  		}
    75  	case "Value":
    76  		val, err := dec.ReadValue()
    77  		if err != nil {
    78  			t.Fatalf("%s: Decoder.ReadValue error: %v", where, err)
    79  		}
    80  		got := string(val)
    81  		want := strings.TrimSpace(td.in)
    82  		if got != want {
    83  			t.Fatalf("%s: Decoder.ReadValue = %s, want %s", where, got, want)
    84  		}
    85  	case "TokenDelims":
    86  		// Use ReadToken for object/array delimiters, ReadValue otherwise.
    87  		var tokens []Token
    88  	loop:
    89  		for {
    90  			switch dec.PeekKind() {
    91  			case '{', '}', '[', ']':
    92  				tok, err := dec.ReadToken()
    93  				if err != nil {
    94  					if err == io.EOF {
    95  						break loop
    96  					}
    97  					t.Fatalf("%s: Decoder.ReadToken error: %v", where, err)
    98  				}
    99  				tokens = append(tokens, tok.Clone())
   100  			default:
   101  				val, err := dec.ReadValue()
   102  				if err != nil {
   103  					if err == io.EOF {
   104  						break loop
   105  					}
   106  					t.Fatalf("%s: Decoder.ReadValue error: %v", where, err)
   107  				}
   108  				tokens = append(tokens, rawToken(string(val)))
   109  			}
   110  		}
   111  		if !equalTokens(tokens, td.tokens) {
   112  			t.Fatalf("%s: tokens mismatch:\ngot  %v\nwant %v", where, tokens, td.tokens)
   113  		}
   114  	}
   115  }
   116  
   117  // TestFaultyDecoder tests that temporary I/O errors are not fatal.
   118  func TestFaultyDecoder(t *testing.T) {
   119  	for _, td := range coderTestdata {
   120  		for _, typeName := range []string{"Token", "Value"} {
   121  			t.Run(path.Join(td.name.Name, typeName), func(t *testing.T) {
   122  				testFaultyDecoder(t, td.name.Where, typeName, td)
   123  			})
   124  		}
   125  	}
   126  }
   127  func testFaultyDecoder(t *testing.T, where jsontest.CasePos, typeName string, td coderTestdataEntry) {
   128  	b := &FaultyBuffer{
   129  		B:        []byte(td.in),
   130  		MaxBytes: 1,
   131  		MayError: io.ErrNoProgress,
   132  	}
   133  
   134  	// Read all the tokens.
   135  	// If the underlying io.Reader is faulty, then Read may return
   136  	// an error without changing the internal state machine.
   137  	// In other words, I/O errors occur before syntactic errors.
   138  	dec := NewDecoder(b)
   139  	switch typeName {
   140  	case "Token":
   141  		var tokens []Token
   142  		for {
   143  			tok, err := dec.ReadToken()
   144  			if err != nil {
   145  				if err == io.EOF {
   146  					break
   147  				}
   148  				if !errors.Is(err, io.ErrNoProgress) {
   149  					t.Fatalf("%s: %d: Decoder.ReadToken error: %v", where, len(tokens), err)
   150  				}
   151  				continue
   152  			}
   153  			tokens = append(tokens, tok.Clone())
   154  		}
   155  		if !equalTokens(tokens, td.tokens) {
   156  			t.Fatalf("%s: tokens mismatch:\ngot  %s\nwant %s", where, tokens, td.tokens)
   157  		}
   158  	case "Value":
   159  		for {
   160  			val, err := dec.ReadValue()
   161  			if err != nil {
   162  				if err == io.EOF {
   163  					break
   164  				}
   165  				if !errors.Is(err, io.ErrNoProgress) {
   166  					t.Fatalf("%s: Decoder.ReadValue error: %v", where, err)
   167  				}
   168  				continue
   169  			}
   170  			got := string(val)
   171  			want := strings.TrimSpace(td.in)
   172  			if got != want {
   173  				t.Fatalf("%s: Decoder.ReadValue = %s, want %s", where, got, want)
   174  			}
   175  		}
   176  	}
   177  }
   178  
   179  type decoderMethodCall struct {
   180  	wantKind    Kind
   181  	wantOut     tokOrVal
   182  	wantErr     error
   183  	wantPointer Pointer
   184  }
   185  
   186  var decoderErrorTestdata = []struct {
   187  	name       jsontest.CaseName
   188  	opts       []Options
   189  	in         string
   190  	calls      []decoderMethodCall
   191  	wantOffset int
   192  }{{
   193  	name: jsontest.Name("InvalidStart"),
   194  	in:   ` #`,
   195  	calls: []decoderMethodCall{
   196  		{'#', zeroToken, newInvalidCharacterError("#", "at start of value").withPos(" ", ""), ""},
   197  		{'#', zeroValue, newInvalidCharacterError("#", "at start of value").withPos(" ", ""), ""},
   198  	},
   199  }, {
   200  	name: jsontest.Name("StreamN0"),
   201  	in:   ` `,
   202  	calls: []decoderMethodCall{
   203  		{0, zeroToken, io.EOF, ""},
   204  		{0, zeroValue, io.EOF, ""},
   205  	},
   206  }, {
   207  	name: jsontest.Name("StreamN1"),
   208  	in:   ` null `,
   209  	calls: []decoderMethodCall{
   210  		{'n', Null, nil, ""},
   211  		{0, zeroToken, io.EOF, ""},
   212  		{0, zeroValue, io.EOF, ""},
   213  	},
   214  	wantOffset: len(` null`),
   215  }, {
   216  	name: jsontest.Name("StreamN2"),
   217  	in:   ` nullnull `,
   218  	calls: []decoderMethodCall{
   219  		{'n', Null, nil, ""},
   220  		{'n', Null, nil, ""},
   221  		{0, zeroToken, io.EOF, ""},
   222  		{0, zeroValue, io.EOF, ""},
   223  	},
   224  	wantOffset: len(` nullnull`),
   225  }, {
   226  	name: jsontest.Name("StreamN2/ExtraComma"), // stream is whitespace delimited, not comma delimited
   227  	in:   ` null , null `,
   228  	calls: []decoderMethodCall{
   229  		{'n', Null, nil, ""},
   230  		{0, zeroToken, newInvalidCharacterError(",", `at start of value`).withPos(` null `, ""), ""},
   231  		{0, zeroValue, newInvalidCharacterError(",", `at start of value`).withPos(` null `, ""), ""},
   232  	},
   233  	wantOffset: len(` null`),
   234  }, {
   235  	name: jsontest.Name("TruncatedNull"),
   236  	in:   `nul`,
   237  	calls: []decoderMethodCall{
   238  		{'n', zeroToken, E(io.ErrUnexpectedEOF).withPos(`nul`, ""), ""},
   239  		{'n', zeroValue, E(io.ErrUnexpectedEOF).withPos(`nul`, ""), ""},
   240  	},
   241  }, {
   242  	name: jsontest.Name("InvalidNull"),
   243  	in:   `nulL`,
   244  	calls: []decoderMethodCall{
   245  		{'n', zeroToken, newInvalidCharacterError("L", `in literal null (expecting 'l')`).withPos(`nul`, ""), ""},
   246  		{'n', zeroValue, newInvalidCharacterError("L", `in literal null (expecting 'l')`).withPos(`nul`, ""), ""},
   247  	},
   248  }, {
   249  	name: jsontest.Name("TruncatedFalse"),
   250  	in:   `fals`,
   251  	calls: []decoderMethodCall{
   252  		{'f', zeroToken, E(io.ErrUnexpectedEOF).withPos(`fals`, ""), ""},
   253  		{'f', zeroValue, E(io.ErrUnexpectedEOF).withPos(`fals`, ""), ""},
   254  	},
   255  }, {
   256  	name: jsontest.Name("InvalidFalse"),
   257  	in:   `falsE`,
   258  	calls: []decoderMethodCall{
   259  		{'f', zeroToken, newInvalidCharacterError("E", `in literal false (expecting 'e')`).withPos(`fals`, ""), ""},
   260  		{'f', zeroValue, newInvalidCharacterError("E", `in literal false (expecting 'e')`).withPos(`fals`, ""), ""},
   261  	},
   262  }, {
   263  	name: jsontest.Name("TruncatedTrue"),
   264  	in:   `tru`,
   265  	calls: []decoderMethodCall{
   266  		{'t', zeroToken, E(io.ErrUnexpectedEOF).withPos(`tru`, ""), ""},
   267  		{'t', zeroValue, E(io.ErrUnexpectedEOF).withPos(`tru`, ""), ""},
   268  	},
   269  }, {
   270  	name: jsontest.Name("InvalidTrue"),
   271  	in:   `truE`,
   272  	calls: []decoderMethodCall{
   273  		{'t', zeroToken, newInvalidCharacterError("E", `in literal true (expecting 'e')`).withPos(`tru`, ""), ""},
   274  		{'t', zeroValue, newInvalidCharacterError("E", `in literal true (expecting 'e')`).withPos(`tru`, ""), ""},
   275  	},
   276  }, {
   277  	name: jsontest.Name("TruncatedString"),
   278  	in:   `"start`,
   279  	calls: []decoderMethodCall{
   280  		{'"', zeroToken, E(io.ErrUnexpectedEOF).withPos(`"start`, ""), ""},
   281  		{'"', zeroValue, E(io.ErrUnexpectedEOF).withPos(`"start`, ""), ""},
   282  	},
   283  }, {
   284  	name: jsontest.Name("InvalidString"),
   285  	in:   `"ok` + "\x00",
   286  	calls: []decoderMethodCall{
   287  		{'"', zeroToken, newInvalidCharacterError("\x00", `in string (expecting non-control character)`).withPos(`"ok`, ""), ""},
   288  		{'"', zeroValue, newInvalidCharacterError("\x00", `in string (expecting non-control character)`).withPos(`"ok`, ""), ""},
   289  	},
   290  }, {
   291  	name: jsontest.Name("ValidString/AllowInvalidUTF8/Token"),
   292  	opts: []Options{AllowInvalidUTF8(true)},
   293  	in:   "\"living\xde\xad\xbe\xef\"",
   294  	calls: []decoderMethodCall{
   295  		{'"', rawToken("\"living\xde\xad\xbe\xef\""), nil, ""},
   296  	},
   297  	wantOffset: len("\"living\xde\xad\xbe\xef\""),
   298  }, {
   299  	name: jsontest.Name("ValidString/AllowInvalidUTF8/Value"),
   300  	opts: []Options{AllowInvalidUTF8(true)},
   301  	in:   "\"living\xde\xad\xbe\xef\"",
   302  	calls: []decoderMethodCall{
   303  		{'"', Value("\"living\xde\xad\xbe\xef\""), nil, ""},
   304  	},
   305  	wantOffset: len("\"living\xde\xad\xbe\xef\""),
   306  }, {
   307  	name: jsontest.Name("InvalidString/RejectInvalidUTF8"),
   308  	opts: []Options{AllowInvalidUTF8(false)},
   309  	in:   "\"living\xde\xad\xbe\xef\"",
   310  	calls: []decoderMethodCall{
   311  		{'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos("\"living\xde\xad", ""), ""},
   312  		{'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos("\"living\xde\xad", ""), ""},
   313  	},
   314  }, {
   315  	name: jsontest.Name("TruncatedNumber"),
   316  	in:   `0.`,
   317  	calls: []decoderMethodCall{
   318  		{'0', zeroToken, E(io.ErrUnexpectedEOF), ""},
   319  		{'0', zeroValue, E(io.ErrUnexpectedEOF), ""},
   320  	},
   321  }, {
   322  	name: jsontest.Name("InvalidNumber"),
   323  	in:   `0.e`,
   324  	calls: []decoderMethodCall{
   325  		{'0', zeroToken, newInvalidCharacterError("e", "in number (expecting digit)").withPos(`0.`, ""), ""},
   326  		{'0', zeroValue, newInvalidCharacterError("e", "in number (expecting digit)").withPos(`0.`, ""), ""},
   327  	},
   328  }, {
   329  	name: jsontest.Name("TruncatedObject/AfterStart"),
   330  	in:   `{`,
   331  	calls: []decoderMethodCall{
   332  		{'{', zeroValue, E(io.ErrUnexpectedEOF).withPos("{", ""), ""},
   333  		{'{', BeginObject, nil, ""},
   334  		{0, zeroToken, E(io.ErrUnexpectedEOF).withPos("{", ""), ""},
   335  		{0, zeroValue, E(io.ErrUnexpectedEOF).withPos("{", ""), ""},
   336  	},
   337  	wantOffset: len(`{`),
   338  }, {
   339  	name: jsontest.Name("TruncatedObject/AfterName"),
   340  	in:   `{"0"`,
   341  	calls: []decoderMethodCall{
   342  		{'{', zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0"`, "/0"), ""},
   343  		{'{', BeginObject, nil, ""},
   344  		{'"', String("0"), nil, ""},
   345  		{0, zeroToken, E(io.ErrUnexpectedEOF).withPos(`{"0"`, "/0"), ""},
   346  		{0, zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0"`, "/0"), ""},
   347  	},
   348  	wantOffset: len(`{"0"`),
   349  }, {
   350  	name: jsontest.Name("TruncatedObject/AfterColon"),
   351  	in:   `{"0":`,
   352  	calls: []decoderMethodCall{
   353  		{'{', zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0":`, "/0"), ""},
   354  		{'{', BeginObject, nil, ""},
   355  		{'"', String("0"), nil, ""},
   356  		{0, zeroToken, E(io.ErrUnexpectedEOF).withPos(`{"0":`, "/0"), ""},
   357  		{0, zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0":`, "/0"), ""},
   358  	},
   359  	wantOffset: len(`{"0"`),
   360  }, {
   361  	name: jsontest.Name("TruncatedObject/AfterValue"),
   362  	in:   `{"0":0`,
   363  	calls: []decoderMethodCall{
   364  		{'{', zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0":0`, ""), ""},
   365  		{'{', BeginObject, nil, ""},
   366  		{'"', String("0"), nil, ""},
   367  		{'0', Uint(0), nil, ""},
   368  		{0, zeroToken, E(io.ErrUnexpectedEOF).withPos(`{"0":0`, ""), ""},
   369  		{0, zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0":0`, ""), ""},
   370  	},
   371  	wantOffset: len(`{"0":0`),
   372  }, {
   373  	name: jsontest.Name("TruncatedObject/AfterComma"),
   374  	in:   `{"0":0,`,
   375  	calls: []decoderMethodCall{
   376  		{'{', zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0":0,`, ""), ""},
   377  		{'{', BeginObject, nil, ""},
   378  		{'"', String("0"), nil, ""},
   379  		{'0', Uint(0), nil, ""},
   380  		{0, zeroToken, E(io.ErrUnexpectedEOF).withPos(`{"0":0,`, ""), ""},
   381  		{0, zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0":0,`, ""), ""},
   382  	},
   383  	wantOffset: len(`{"0":0`),
   384  }, {
   385  	name: jsontest.Name("InvalidObject/MissingColon"),
   386  	in:   ` { "fizz" "buzz" } `,
   387  	calls: []decoderMethodCall{
   388  		{'{', zeroValue, newInvalidCharacterError("\"", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""},
   389  		{'{', BeginObject, nil, ""},
   390  		{'"', String("fizz"), nil, ""},
   391  		{0, zeroToken, newInvalidCharacterError("\"", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""},
   392  		{0, zeroValue, newInvalidCharacterError("\"", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""},
   393  	},
   394  	wantOffset: len(` { "fizz"`),
   395  }, {
   396  	name: jsontest.Name("InvalidObject/MissingColon/GotComma"),
   397  	in:   ` { "fizz" , "buzz" } `,
   398  	calls: []decoderMethodCall{
   399  		{'{', zeroValue, newInvalidCharacterError(",", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""},
   400  		{'{', BeginObject, nil, ""},
   401  		{'"', String("fizz"), nil, ""},
   402  		{0, zeroToken, newInvalidCharacterError(",", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""},
   403  		{0, zeroValue, newInvalidCharacterError(",", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""},
   404  	},
   405  	wantOffset: len(` { "fizz"`),
   406  }, {
   407  	name: jsontest.Name("InvalidObject/MissingColon/GotHash"),
   408  	in:   ` { "fizz" # "buzz" } `,
   409  	calls: []decoderMethodCall{
   410  		{'{', zeroValue, newInvalidCharacterError("#", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""},
   411  		{'{', BeginObject, nil, ""},
   412  		{'"', String("fizz"), nil, ""},
   413  		{0, zeroToken, newInvalidCharacterError("#", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""},
   414  		{0, zeroValue, newInvalidCharacterError("#", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""},
   415  	},
   416  	wantOffset: len(` { "fizz"`),
   417  }, {
   418  	name: jsontest.Name("InvalidObject/MissingComma"),
   419  	in:   ` { "fizz" : "buzz" "gazz" } `,
   420  	calls: []decoderMethodCall{
   421  		{'{', zeroValue, newInvalidCharacterError("\"", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""},
   422  		{'{', BeginObject, nil, ""},
   423  		{'"', String("fizz"), nil, ""},
   424  		{'"', String("buzz"), nil, ""},
   425  		{0, zeroToken, newInvalidCharacterError("\"", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""},
   426  		{0, zeroValue, newInvalidCharacterError("\"", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""},
   427  	},
   428  	wantOffset: len(` { "fizz" : "buzz"`),
   429  }, {
   430  	name: jsontest.Name("InvalidObject/MissingComma/GotColon"),
   431  	in:   ` { "fizz" : "buzz" : "gazz" } `,
   432  	calls: []decoderMethodCall{
   433  		{'{', zeroValue, newInvalidCharacterError(":", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""},
   434  		{'{', BeginObject, nil, ""},
   435  		{'"', String("fizz"), nil, ""},
   436  		{'"', String("buzz"), nil, ""},
   437  		{0, zeroToken, newInvalidCharacterError(":", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""},
   438  		{0, zeroValue, newInvalidCharacterError(":", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""},
   439  	},
   440  	wantOffset: len(` { "fizz" : "buzz"`),
   441  }, {
   442  	name: jsontest.Name("InvalidObject/MissingComma/GotHash"),
   443  	in:   ` { "fizz" : "buzz" # "gazz" } `,
   444  	calls: []decoderMethodCall{
   445  		{'{', zeroValue, newInvalidCharacterError("#", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""},
   446  		{'{', BeginObject, nil, ""},
   447  		{'"', String("fizz"), nil, ""},
   448  		{'"', String("buzz"), nil, ""},
   449  		{0, zeroToken, newInvalidCharacterError("#", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""},
   450  		{0, zeroValue, newInvalidCharacterError("#", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""},
   451  	},
   452  	wantOffset: len(` { "fizz" : "buzz"`),
   453  }, {
   454  	name: jsontest.Name("InvalidObject/ExtraComma/AfterStart"),
   455  	in:   ` { , } `,
   456  	calls: []decoderMethodCall{
   457  		{'{', zeroValue, newInvalidCharacterError(",", `at start of string (expecting '"')`).withPos(` { `, ""), ""},
   458  		{'{', BeginObject, nil, ""},
   459  		{0, zeroToken, newInvalidCharacterError(",", `at start of value`).withPos(` { `, ""), ""},
   460  		{0, zeroValue, newInvalidCharacterError(",", `at start of value`).withPos(` { `, ""), ""},
   461  	},
   462  	wantOffset: len(` {`),
   463  }, {
   464  	name: jsontest.Name("InvalidObject/ExtraComma/AfterValue"),
   465  	in:   ` { "fizz" : "buzz" , } `,
   466  	calls: []decoderMethodCall{
   467  		{'{', zeroValue, newInvalidCharacterError("}", `at start of string (expecting '"')`).withPos(` { "fizz" : "buzz" , `, ""), ""},
   468  		{'{', BeginObject, nil, ""},
   469  		{'"', String("fizz"), nil, ""},
   470  		{'"', String("buzz"), nil, ""},
   471  		{0, zeroToken, newInvalidCharacterError(",", `at start of value`).withPos(` { "fizz" : "buzz" `, ""), ""},
   472  		{0, zeroValue, newInvalidCharacterError(",", `at start of value`).withPos(` { "fizz" : "buzz" `, ""), ""},
   473  	},
   474  	wantOffset: len(` { "fizz" : "buzz"`),
   475  }, {
   476  	name: jsontest.Name("InvalidObject/InvalidName/GotNull"),
   477  	in:   ` { null : null } `,
   478  	calls: []decoderMethodCall{
   479  		{'{', zeroValue, newInvalidCharacterError("n", "at start of string (expecting '\"')").withPos(` { `, ""), ""},
   480  		{'{', BeginObject, nil, ""},
   481  		{'n', zeroToken, E(ErrNonStringName).withPos(` { `, ""), ""},
   482  		{'n', zeroValue, E(ErrNonStringName).withPos(` { `, ""), ""},
   483  	},
   484  	wantOffset: len(` {`),
   485  }, {
   486  	name: jsontest.Name("InvalidObject/InvalidName/GotFalse"),
   487  	in:   ` { false : false } `,
   488  	calls: []decoderMethodCall{
   489  		{'{', zeroValue, newInvalidCharacterError("f", "at start of string (expecting '\"')").withPos(` { `, ""), ""},
   490  		{'{', BeginObject, nil, ""},
   491  		{'f', zeroToken, E(ErrNonStringName).withPos(` { `, ""), ""},
   492  		{'f', zeroValue, E(ErrNonStringName).withPos(` { `, ""), ""},
   493  	},
   494  	wantOffset: len(` {`),
   495  }, {
   496  	name: jsontest.Name("InvalidObject/InvalidName/GotTrue"),
   497  	in:   ` { true : true } `,
   498  	calls: []decoderMethodCall{
   499  		{'{', zeroValue, newInvalidCharacterError("t", "at start of string (expecting '\"')").withPos(` { `, ""), ""},
   500  		{'{', BeginObject, nil, ""},
   501  		{'t', zeroToken, E(ErrNonStringName).withPos(` { `, ""), ""},
   502  		{'t', zeroValue, E(ErrNonStringName).withPos(` { `, ""), ""},
   503  	},
   504  	wantOffset: len(` {`),
   505  }, {
   506  	name: jsontest.Name("InvalidObject/InvalidName/GotNumber"),
   507  	in:   ` { 0 : 0 } `,
   508  	calls: []decoderMethodCall{
   509  		{'{', zeroValue, newInvalidCharacterError("0", "at start of string (expecting '\"')").withPos(` { `, ""), ""},
   510  		{'{', BeginObject, nil, ""},
   511  		{'0', zeroToken, E(ErrNonStringName).withPos(` { `, ""), ""},
   512  		{'0', zeroValue, E(ErrNonStringName).withPos(` { `, ""), ""},
   513  	},
   514  	wantOffset: len(` {`),
   515  }, {
   516  	name: jsontest.Name("InvalidObject/InvalidName/GotObject"),
   517  	in:   ` { {} : {} } `,
   518  	calls: []decoderMethodCall{
   519  		{'{', zeroValue, newInvalidCharacterError("{", "at start of string (expecting '\"')").withPos(` { `, ""), ""},
   520  		{'{', BeginObject, nil, ""},
   521  		{'{', zeroToken, E(ErrNonStringName).withPos(` { `, ""), ""},
   522  		{'{', zeroValue, E(ErrNonStringName).withPos(` { `, ""), ""},
   523  	},
   524  	wantOffset: len(` {`),
   525  }, {
   526  	name: jsontest.Name("InvalidObject/InvalidName/GotArray"),
   527  	in:   ` { [] : [] } `,
   528  	calls: []decoderMethodCall{
   529  		{'{', zeroValue, newInvalidCharacterError("[", "at start of string (expecting '\"')").withPos(` { `, ""), ""},
   530  		{'{', BeginObject, nil, ""},
   531  		{'[', zeroToken, E(ErrNonStringName).withPos(` { `, ""), ""},
   532  		{'[', zeroValue, E(ErrNonStringName).withPos(` { `, ""), ""},
   533  	},
   534  	wantOffset: len(` {`),
   535  }, {
   536  	name: jsontest.Name("InvalidObject/MismatchingDelim"),
   537  	in:   ` { ] `,
   538  	calls: []decoderMethodCall{
   539  		{'{', zeroValue, newInvalidCharacterError("]", "at start of string (expecting '\"')").withPos(` { `, ""), ""},
   540  		{'{', BeginObject, nil, ""},
   541  		{']', zeroToken, newInvalidCharacterError("]", "at start of value").withPos(` { `, ""), ""},
   542  		{']', zeroValue, newInvalidCharacterError("]", "at start of value").withPos(` { `, ""), ""},
   543  	},
   544  	wantOffset: len(` {`),
   545  }, {
   546  	name: jsontest.Name("ValidObject/InvalidValue"),
   547  	in:   ` { } `,
   548  	calls: []decoderMethodCall{
   549  		{'{', BeginObject, nil, ""},
   550  		{'}', zeroValue, newInvalidCharacterError("}", "at start of value").withPos(" { ", ""), ""},
   551  	},
   552  	wantOffset: len(` {`),
   553  }, {
   554  	name: jsontest.Name("ValidObject/UniqueNames"),
   555  	in:   `{"0":0,"1":1} `,
   556  	calls: []decoderMethodCall{
   557  		{'{', BeginObject, nil, ""},
   558  		{'"', String("0"), nil, ""},
   559  		{'0', Uint(0), nil, ""},
   560  		{'"', String("1"), nil, ""},
   561  		{'0', Uint(1), nil, ""},
   562  		{'}', EndObject, nil, ""},
   563  	},
   564  	wantOffset: len(`{"0":0,"1":1}`),
   565  }, {
   566  	name: jsontest.Name("ValidObject/DuplicateNames"),
   567  	opts: []Options{AllowDuplicateNames(true)},
   568  	in:   `{"0":0,"0":0} `,
   569  	calls: []decoderMethodCall{
   570  		{'{', BeginObject, nil, ""},
   571  		{'"', String("0"), nil, ""},
   572  		{'0', Uint(0), nil, ""},
   573  		{'"', String("0"), nil, ""},
   574  		{'0', Uint(0), nil, ""},
   575  		{'}', EndObject, nil, ""},
   576  	},
   577  	wantOffset: len(`{"0":0,"0":0}`),
   578  }, {
   579  	name: jsontest.Name("InvalidObject/DuplicateNames"),
   580  	in:   `{"X":{},"Y":{},"X":{}} `,
   581  	calls: []decoderMethodCall{
   582  		{'{', zeroValue, E(ErrDuplicateName).withPos(`{"X":{},"Y":{},`, "/X"), ""},
   583  		{'{', BeginObject, nil, ""},
   584  		{'"', String("X"), nil, ""},
   585  		{'{', BeginObject, nil, ""},
   586  		{'}', EndObject, nil, ""},
   587  		{'"', String("Y"), nil, ""},
   588  		{'{', BeginObject, nil, ""},
   589  		{'}', EndObject, nil, ""},
   590  		{'"', zeroToken, E(ErrDuplicateName).withPos(`{"X":{},"Y":{},`, "/X"), "/Y"},
   591  		{'"', zeroValue, E(ErrDuplicateName).withPos(`{"0":{},"Y":{},`, "/X"), "/Y"},
   592  	},
   593  	wantOffset: len(`{"0":{},"1":{}`),
   594  }, {
   595  	name: jsontest.Name("TruncatedArray/AfterStart"),
   596  	in:   `[`,
   597  	calls: []decoderMethodCall{
   598  		{'[', zeroValue, E(io.ErrUnexpectedEOF).withPos("[", ""), ""},
   599  		{'[', BeginArray, nil, ""},
   600  		{0, zeroToken, E(io.ErrUnexpectedEOF).withPos("[", ""), ""},
   601  		{0, zeroValue, E(io.ErrUnexpectedEOF).withPos("[", ""), ""},
   602  	},
   603  	wantOffset: len(`[`),
   604  }, {
   605  	name: jsontest.Name("TruncatedArray/AfterValue"),
   606  	in:   `[0`,
   607  	calls: []decoderMethodCall{
   608  		{'[', zeroValue, E(io.ErrUnexpectedEOF).withPos("[0", ""), ""},
   609  		{'[', BeginArray, nil, ""},
   610  		{'0', Uint(0), nil, ""},
   611  		{0, zeroToken, E(io.ErrUnexpectedEOF).withPos("[0", ""), ""},
   612  		{0, zeroValue, E(io.ErrUnexpectedEOF).withPos("[0", ""), ""},
   613  	},
   614  	wantOffset: len(`[0`),
   615  }, {
   616  	name: jsontest.Name("TruncatedArray/AfterComma"),
   617  	in:   `[0,`,
   618  	calls: []decoderMethodCall{
   619  		{'[', zeroValue, E(io.ErrUnexpectedEOF).withPos("[0,", ""), ""},
   620  		{'[', BeginArray, nil, ""},
   621  		{'0', Uint(0), nil, ""},
   622  		{0, zeroToken, E(io.ErrUnexpectedEOF).withPos("[0,", ""), ""},
   623  		{0, zeroValue, E(io.ErrUnexpectedEOF).withPos("[0,", ""), ""},
   624  	},
   625  	wantOffset: len(`[0`),
   626  }, {
   627  	name: jsontest.Name("InvalidArray/MissingComma"),
   628  	in:   ` [ "fizz" "buzz" ] `,
   629  	calls: []decoderMethodCall{
   630  		{'[', zeroValue, newInvalidCharacterError("\"", "after array element (expecting ',' or ']')").withPos(` [ "fizz" `, ""), ""},
   631  		{'[', BeginArray, nil, ""},
   632  		{'"', String("fizz"), nil, ""},
   633  		{0, zeroToken, newInvalidCharacterError("\"", "after array element (expecting ',' or ']')").withPos(` [ "fizz" `, ""), ""},
   634  		{0, zeroValue, newInvalidCharacterError("\"", "after array element (expecting ',' or ']')").withPos(` [ "fizz" `, ""), ""},
   635  	},
   636  	wantOffset: len(` [ "fizz"`),
   637  }, {
   638  	name: jsontest.Name("InvalidArray/MismatchingDelim"),
   639  	in:   ` [ } `,
   640  	calls: []decoderMethodCall{
   641  		{'[', zeroValue, newInvalidCharacterError("}", "at start of value").withPos(` [ `, "/0"), ""},
   642  		{'[', BeginArray, nil, ""},
   643  		{'}', zeroToken, newInvalidCharacterError("}", "at start of value").withPos(` [ `, "/0"), ""},
   644  		{'}', zeroValue, newInvalidCharacterError("}", "at start of value").withPos(` [ `, "/0"), ""},
   645  	},
   646  	wantOffset: len(` [`),
   647  }, {
   648  	name: jsontest.Name("ValidArray/InvalidValue"),
   649  	in:   ` [ ] `,
   650  	calls: []decoderMethodCall{
   651  		{'[', BeginArray, nil, ""},
   652  		{']', zeroValue, newInvalidCharacterError("]", "at start of value").withPos(" [ ", "/0"), ""},
   653  	},
   654  	wantOffset: len(` [`),
   655  }, {
   656  	name: jsontest.Name("InvalidDelim/AfterTopLevel"),
   657  	in:   `"",`,
   658  	calls: []decoderMethodCall{
   659  		{'"', String(""), nil, ""},
   660  		{0, zeroToken, newInvalidCharacterError(",", "at start of value").withPos(`""`, ""), ""},
   661  		{0, zeroValue, newInvalidCharacterError(",", "at start of value").withPos(`""`, ""), ""},
   662  	},
   663  	wantOffset: len(`""`),
   664  }, {
   665  	name: jsontest.Name("InvalidDelim/AfterBeginObject"),
   666  	in:   `{:`,
   667  	calls: []decoderMethodCall{
   668  		{'{', zeroValue, newInvalidCharacterError(":", `at start of string (expecting '"')`).withPos(`{`, ""), ""},
   669  		{'{', BeginObject, nil, ""},
   670  		{0, zeroToken, newInvalidCharacterError(":", "at start of value").withPos(`{`, ""), ""},
   671  		{0, zeroValue, newInvalidCharacterError(":", "at start of value").withPos(`{`, ""), ""},
   672  	},
   673  	wantOffset: len(`{`),
   674  }, {
   675  	name: jsontest.Name("InvalidDelim/AfterObjectName"),
   676  	in:   `{"",`,
   677  	calls: []decoderMethodCall{
   678  		{'{', zeroValue, newInvalidCharacterError(",", "after object name (expecting ':')").withPos(`{""`, "/"), ""},
   679  		{'{', BeginObject, nil, ""},
   680  		{'"', String(""), nil, ""},
   681  		{0, zeroToken, newInvalidCharacterError(",", "after object name (expecting ':')").withPos(`{""`, "/"), ""},
   682  		{0, zeroValue, newInvalidCharacterError(",", "after object name (expecting ':')").withPos(`{""`, "/"), ""},
   683  	},
   684  	wantOffset: len(`{""`),
   685  }, {
   686  	name: jsontest.Name("ValidDelim/AfterObjectName"),
   687  	in:   `{"":`,
   688  	calls: []decoderMethodCall{
   689  		{'{', zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"":`, "/"), ""},
   690  		{'{', BeginObject, nil, ""},
   691  		{'"', String(""), nil, ""},
   692  		{0, zeroToken, E(io.ErrUnexpectedEOF).withPos(`{"":`, "/"), ""},
   693  		{0, zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"":`, "/"), ""},
   694  	},
   695  	wantOffset: len(`{""`),
   696  }, {
   697  	name: jsontest.Name("InvalidDelim/AfterObjectValue"),
   698  	in:   `{"":"":`,
   699  	calls: []decoderMethodCall{
   700  		{'{', zeroValue, newInvalidCharacterError(":", "after object value (expecting ',' or '}')").withPos(`{"":""`, ""), ""},
   701  		{'{', BeginObject, nil, ""},
   702  		{'"', String(""), nil, ""},
   703  		{'"', String(""), nil, ""},
   704  		{0, zeroToken, newInvalidCharacterError(":", "after object value (expecting ',' or '}')").withPos(`{"":""`, ""), ""},
   705  		{0, zeroValue, newInvalidCharacterError(":", "after object value (expecting ',' or '}')").withPos(`{"":""`, ""), ""},
   706  	},
   707  	wantOffset: len(`{"":""`),
   708  }, {
   709  	name: jsontest.Name("ValidDelim/AfterObjectValue"),
   710  	in:   `{"":"",`,
   711  	calls: []decoderMethodCall{
   712  		{'{', zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"":"",`, ""), ""},
   713  		{'{', BeginObject, nil, ""},
   714  		{'"', String(""), nil, ""},
   715  		{'"', String(""), nil, ""},
   716  		{0, zeroToken, E(io.ErrUnexpectedEOF).withPos(`{"":"",`, ""), ""},
   717  		{0, zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"":"",`, ""), ""},
   718  	},
   719  	wantOffset: len(`{"":""`),
   720  }, {
   721  	name: jsontest.Name("InvalidDelim/AfterBeginArray"),
   722  	in:   `[,`,
   723  	calls: []decoderMethodCall{
   724  		{'[', zeroValue, newInvalidCharacterError(",", "at start of value").withPos(`[`, "/0"), ""},
   725  		{'[', BeginArray, nil, ""},
   726  		{0, zeroToken, newInvalidCharacterError(",", "at start of value").withPos(`[`, ""), ""},
   727  		{0, zeroValue, newInvalidCharacterError(",", "at start of value").withPos(`[`, ""), ""},
   728  	},
   729  	wantOffset: len(`[`),
   730  }, {
   731  	name: jsontest.Name("InvalidDelim/AfterArrayValue"),
   732  	in:   `["":`,
   733  	calls: []decoderMethodCall{
   734  		{'[', zeroValue, newInvalidCharacterError(":", "after array element (expecting ',' or ']')").withPos(`[""`, ""), ""},
   735  		{'[', BeginArray, nil, ""},
   736  		{'"', String(""), nil, ""},
   737  		{0, zeroToken, newInvalidCharacterError(":", "after array element (expecting ',' or ']')").withPos(`[""`, ""), ""},
   738  		{0, zeroValue, newInvalidCharacterError(":", "after array element (expecting ',' or ']')").withPos(`[""`, ""), ""},
   739  	},
   740  	wantOffset: len(`[""`),
   741  }, {
   742  	name: jsontest.Name("ValidDelim/AfterArrayValue"),
   743  	in:   `["",`,
   744  	calls: []decoderMethodCall{
   745  		{'[', zeroValue, E(io.ErrUnexpectedEOF).withPos(`["",`, ""), ""},
   746  		{'[', BeginArray, nil, ""},
   747  		{'"', String(""), nil, ""},
   748  		{0, zeroToken, E(io.ErrUnexpectedEOF).withPos(`["",`, ""), ""},
   749  		{0, zeroValue, E(io.ErrUnexpectedEOF).withPos(`["",`, ""), ""},
   750  	},
   751  	wantOffset: len(`[""`),
   752  }, {
   753  	name: jsontest.Name("ErrorPosition"),
   754  	in:   ` "a` + "\xff" + `0" `,
   755  	calls: []decoderMethodCall{
   756  		{'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` "a`, ""), ""},
   757  		{'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` "a`, ""), ""},
   758  	},
   759  }, {
   760  	name: jsontest.Name("ErrorPosition/0"),
   761  	in:   ` [ "a` + "\xff" + `1" ] `,
   762  	calls: []decoderMethodCall{
   763  		{'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a`, "/0"), ""},
   764  		{'[', BeginArray, nil, ""},
   765  		{'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a`, "/0"), ""},
   766  		{'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a`, "/0"), ""},
   767  	},
   768  	wantOffset: len(` [`),
   769  }, {
   770  	name: jsontest.Name("ErrorPosition/1"),
   771  	in:   ` [ "a1" , "b` + "\xff" + `1" ] `,
   772  	calls: []decoderMethodCall{
   773  		{'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , "b`, "/1"), ""},
   774  		{'[', BeginArray, nil, ""},
   775  		{'"', String("a1"), nil, ""},
   776  		{'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , "b`, "/1"), ""},
   777  		{'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , "b`, "/1"), ""},
   778  	},
   779  	wantOffset: len(` [ "a1"`),
   780  }, {
   781  	name: jsontest.Name("ErrorPosition/0/0"),
   782  	in:   ` [ [ "a` + "\xff" + `2" ] ] `,
   783  	calls: []decoderMethodCall{
   784  		{'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ [ "a`, "/0/0"), ""},
   785  		{'[', BeginArray, nil, ""},
   786  		{'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ [ "a`, "/0/0"), ""},
   787  		{'[', BeginArray, nil, "/0"},
   788  		{'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ [ "a`, "/0/0"), ""},
   789  		{'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` [ [ "a`, "/0/0"), ""},
   790  	},
   791  	wantOffset: len(` [ [`),
   792  }, {
   793  	name: jsontest.Name("ErrorPosition/1/0"),
   794  	in:   ` [ "a1" , [ "a` + "\xff" + `2" ] ] `,
   795  	calls: []decoderMethodCall{
   796  		{'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , [ "a`, "/1/0"), ""},
   797  		{'[', BeginArray, nil, ""},
   798  		{'"', String("a1"), nil, "/0"},
   799  		{'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , [ "a`, "/1/0"), "/0"},
   800  		{'[', BeginArray, nil, "/1"},
   801  		{'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , [ "a`, "/1/0"), "/1"},
   802  		{'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , [ "a`, "/1/0"), "/1"},
   803  	},
   804  	wantOffset: len(` [ "a1" , [`),
   805  }, {
   806  	name: jsontest.Name("ErrorPosition/0/1"),
   807  	in:   ` [ [ "a2" , "b` + "\xff" + `2" ] ] `,
   808  	calls: []decoderMethodCall{
   809  		{'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ [ "a2" , "b`, "/0/1"), ""},
   810  		{'[', BeginArray, nil, ""},
   811  		{'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ [ "a2" , "b`, "/0/1"), ""},
   812  		{'[', BeginArray, nil, "/0"},
   813  		{'"', String("a2"), nil, "/0/0"},
   814  		{'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ [ "a2" , "b`, "/0/1"), "/0/0"},
   815  		{'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` [ [ "a2" , "b`, "/0/1"), "/0/0"},
   816  	},
   817  	wantOffset: len(` [ [ "a2"`),
   818  }, {
   819  	name: jsontest.Name("ErrorPosition/1/1"),
   820  	in:   ` [ "a1" , [ "a2" , "b` + "\xff" + `2" ] ] `,
   821  	calls: []decoderMethodCall{
   822  		{'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , [ "a2" , "b`, "/1/1"), ""},
   823  		{'[', BeginArray, nil, ""},
   824  		{'"', String("a1"), nil, "/0"},
   825  		{'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , [ "a2" , "b`, "/1/1"), ""},
   826  		{'[', BeginArray, nil, "/1"},
   827  		{'"', String("a2"), nil, "/1/0"},
   828  		{'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , [ "a2" , "b`, "/1/1"), "/1/0"},
   829  		{'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , [ "a2" , "b`, "/1/1"), "/1/0"},
   830  	},
   831  	wantOffset: len(` [ "a1" , [ "a2"`),
   832  }, {
   833  	name: jsontest.Name("ErrorPosition/a1-"),
   834  	in:   ` { "a` + "\xff" + `1" : "b1" } `,
   835  	calls: []decoderMethodCall{
   836  		{'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a`, ""), ""},
   837  		{'{', BeginObject, nil, ""},
   838  		{'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a`, ""), ""},
   839  		{'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` { "a`, ""), ""},
   840  	},
   841  	wantOffset: len(` {`),
   842  }, {
   843  	name: jsontest.Name("ErrorPosition/a1"),
   844  	in:   ` { "a1" : "b` + "\xff" + `1" } `,
   845  	calls: []decoderMethodCall{
   846  		{'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b`, "/a1"), ""},
   847  		{'{', BeginObject, nil, ""},
   848  		{'"', String("a1"), nil, "/a1"},
   849  		{'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b`, "/a1"), ""},
   850  		{'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b`, "/a1"), ""},
   851  	},
   852  	wantOffset: len(` { "a1"`),
   853  }, {
   854  	name: jsontest.Name("ErrorPosition/c1-"),
   855  	in:   ` { "a1" : "b1" , "c` + "\xff" + `1" : "d1" } `,
   856  	calls: []decoderMethodCall{
   857  		{'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" , "c`, ""), ""},
   858  		{'{', BeginObject, nil, ""},
   859  		{'"', String("a1"), nil, "/a1"},
   860  		{'"', String("b1"), nil, "/a1"},
   861  		{'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" : "c`, ""), "/a1"},
   862  		{'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" : "c`, ""), "/a1"},
   863  	},
   864  	wantOffset: len(` { "a1" : "b1"`),
   865  }, {
   866  	name: jsontest.Name("ErrorPosition/c1"),
   867  	in:   ` { "a1" : "b1" , "c1" : "d` + "\xff" + `1" } `,
   868  	calls: []decoderMethodCall{
   869  		{'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" , "c1" : "d`, "/c1"), ""},
   870  		{'{', BeginObject, nil, ""},
   871  		{'"', String("a1"), nil, "/a1"},
   872  		{'"', String("b1"), nil, "/a1"},
   873  		{'"', String("c1"), nil, "/c1"},
   874  		{'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" : "c1" : "d`, "/c1"), "/c1"},
   875  		{'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" : "c1" : "d`, "/c1"), "/c1"},
   876  	},
   877  	wantOffset: len(` { "a1" : "b1" , "c1"`),
   878  }, {
   879  	name: jsontest.Name("ErrorPosition/a1/a2-"),
   880  	in:   ` { "a1" : { "a` + "\xff" + `2" : "b2" } } `,
   881  	calls: []decoderMethodCall{
   882  		{'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a`, "/a1"), ""},
   883  		{'{', BeginObject, nil, ""},
   884  		{'"', String("a1"), nil, "/a1"},
   885  		{'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a`, "/a1"), ""},
   886  		{'{', BeginObject, nil, "/a1"},
   887  		{'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a`, "/a1"), "/a1"},
   888  		{'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a`, "/a1"), "/a1"},
   889  	},
   890  	wantOffset: len(` { "a1" : {`),
   891  }, {
   892  	name: jsontest.Name("ErrorPosition/a1/a2"),
   893  	in:   ` { "a1" : { "a2" : "b` + "\xff" + `2" } } `,
   894  	calls: []decoderMethodCall{
   895  		{'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b`, "/a1/a2"), ""},
   896  		{'{', BeginObject, nil, ""},
   897  		{'"', String("a1"), nil, "/a1"},
   898  		{'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b`, "/a1/a2"), ""},
   899  		{'{', BeginObject, nil, "/a1"},
   900  		{'"', String("a2"), nil, "/a1/a2"},
   901  		{'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b`, "/a1/a2"), "/a1/a2"},
   902  		{'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b`, "/a1/a2"), "/a1/a2"},
   903  	},
   904  	wantOffset: len(` { "a1" : { "a2"`),
   905  }, {
   906  	name: jsontest.Name("ErrorPosition/a1/c2-"),
   907  	in:   ` { "a1" : { "a2" : "b2" , "c` + "\xff" + `2" : "d2" } } `,
   908  	calls: []decoderMethodCall{
   909  		{'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b2" , "c`, "/a1"), ""},
   910  		{'{', BeginObject, nil, ""},
   911  		{'"', String("a1"), nil, "/a1"},
   912  		{'{', BeginObject, nil, "/a1"},
   913  		{'"', String("a2"), nil, "/a1/a2"},
   914  		{'"', String("b2"), nil, "/a1/a2"},
   915  		{'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b2" , "c`, "/a1"), "/a1/a2"},
   916  		{'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b2" , "c`, "/a1"), "/a1/a2"},
   917  	},
   918  	wantOffset: len(` { "a1" : { "a2" : "b2"`),
   919  }, {
   920  	name: jsontest.Name("ErrorPosition/a1/c2"),
   921  	in:   ` { "a1" : { "a2" : "b2" , "c2" : "d` + "\xff" + `2" } } `,
   922  	calls: []decoderMethodCall{
   923  		{'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b2" , "c2" : "d`, "/a1/c2"), ""},
   924  		{'{', BeginObject, nil, ""},
   925  		{'"', String("a1"), nil, "/a1"},
   926  		{'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b2" , "c2" : "d`, "/a1/c2"), ""},
   927  		{'{', BeginObject, nil, ""},
   928  		{'"', String("a2"), nil, "/a1/a2"},
   929  		{'"', String("b2"), nil, "/a1/a2"},
   930  		{'"', String("c2"), nil, "/a1/c2"},
   931  		{'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b2" , "c2" : "d`, "/a1/c2"), "/a1/c2"},
   932  		{'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b2" , "c2" : "d`, "/a1/c2"), "/a1/c2"},
   933  	},
   934  	wantOffset: len(` { "a1" : { "a2" : "b2" , "c2"`),
   935  }, {
   936  	name: jsontest.Name("ErrorPosition/1/a2"),
   937  	in:   ` [ "a1" , { "a2" : "b` + "\xff" + `2" } ] `,
   938  	calls: []decoderMethodCall{
   939  		{'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , { "a2" : "b`, "/1/a2"), ""},
   940  		{'[', BeginArray, nil, ""},
   941  		{'"', String("a1"), nil, "/0"},
   942  		{'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , { "a2" : "b`, "/1/a2"), ""},
   943  		{'{', BeginObject, nil, "/1"},
   944  		{'"', String("a2"), nil, "/1/a2"},
   945  		{'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , { "a2" : "b`, "/1/a2"), "/1/a2"},
   946  		{'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , { "a2" : "b`, "/1/a2"), "/1/a2"},
   947  	},
   948  	wantOffset: len(` [ "a1" , { "a2"`),
   949  }, {
   950  	name: jsontest.Name("ErrorPosition/c1/1"),
   951  	in:   ` { "a1" : "b1" , "c1" : [ "a2" , "b` + "\xff" + `2" ] } `,
   952  	calls: []decoderMethodCall{
   953  		{'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" , "c1" : [ "a2" , "b`, "/c1/1"), ""},
   954  		{'{', BeginObject, nil, ""},
   955  		{'"', String("a1"), nil, "/a1"},
   956  		{'"', String("b1"), nil, "/a1"},
   957  		{'"', String("c1"), nil, "/c1"},
   958  		{'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" , "c1" : [ "a2" , "b`, "/c1/1"), ""},
   959  		{'[', BeginArray, nil, "/c1"},
   960  		{'"', String("a2"), nil, "/c1/0"},
   961  		{'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" , "c1" : [ "a2" , "b`, "/c1/1"), "/c1/0"},
   962  		{'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" , "c1" : [ "a2" , "b`, "/c1/1"), "/c1/0"},
   963  	},
   964  	wantOffset: len(` { "a1" : "b1" , "c1" : [ "a2"`),
   965  }, {
   966  	name: jsontest.Name("ErrorPosition/0/a1/1/c3/1"),
   967  	in:   ` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b` + "\xff" + `4" ] } ] } ] `,
   968  	calls: []decoderMethodCall{
   969  		{'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), ""},
   970  		{'[', BeginArray, nil, ""},
   971  		{'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), ""},
   972  		{'{', BeginObject, nil, "/0"},
   973  		{'"', String("a1"), nil, "/0/a1"},
   974  		{'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), ""},
   975  		{'[', BeginArray, nil, ""},
   976  		{'"', String("a2"), nil, "/0/a1/0"},
   977  		{'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), ""},
   978  		{'{', BeginObject, nil, "/0/a1/1"},
   979  		{'"', String("a3"), nil, "/0/a1/1/a3"},
   980  		{'"', String("b3"), nil, "/0/a1/1/a3"},
   981  		{'"', String("c3"), nil, "/0/a1/1/c3"},
   982  		{'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), ""},
   983  		{'[', BeginArray, nil, "/0/a1/1/c3"},
   984  		{'"', String("a4"), nil, "/0/a1/1/c3/0"},
   985  		{'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), "/0/a1/1/c3/0"},
   986  		{'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), "/0/a1/1/c3/0"},
   987  	},
   988  	wantOffset: len(` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4"`),
   989  }}
   990  
   991  // TestDecoderErrors test that Decoder errors occur when we expect and
   992  // leaves the Decoder in a consistent state.
   993  func TestDecoderErrors(t *testing.T) {
   994  	for _, td := range decoderErrorTestdata {
   995  		t.Run(path.Join(td.name.Name), func(t *testing.T) {
   996  			testDecoderErrors(t, td.name.Where, td.opts, td.in, td.calls, td.wantOffset)
   997  		})
   998  	}
   999  }
  1000  func testDecoderErrors(t *testing.T, where jsontest.CasePos, opts []Options, in string, calls []decoderMethodCall, wantOffset int) {
  1001  	src := bytes.NewBufferString(in)
  1002  	dec := NewDecoder(src, opts...)
  1003  	for i, call := range calls {
  1004  		gotKind := dec.PeekKind()
  1005  		if gotKind != call.wantKind {
  1006  			t.Fatalf("%s: %d: Decoder.PeekKind = %v, want %v", where, i, gotKind, call.wantKind)
  1007  		}
  1008  
  1009  		var gotErr error
  1010  		switch wantOut := call.wantOut.(type) {
  1011  		case Token:
  1012  			var gotOut Token
  1013  			gotOut, gotErr = dec.ReadToken()
  1014  			if gotOut.String() != wantOut.String() {
  1015  				t.Fatalf("%s: %d: Decoder.ReadToken = %v, want %v", where, i, gotOut, wantOut)
  1016  			}
  1017  		case Value:
  1018  			var gotOut Value
  1019  			gotOut, gotErr = dec.ReadValue()
  1020  			if string(gotOut) != string(wantOut) {
  1021  				t.Fatalf("%s: %d: Decoder.ReadValue = %s, want %s", where, i, gotOut, wantOut)
  1022  			}
  1023  		}
  1024  		if !equalError(gotErr, call.wantErr) {
  1025  			t.Fatalf("%s: %d: error mismatch:\ngot  %v\nwant %v", where, i, gotErr, call.wantErr)
  1026  		}
  1027  		if call.wantPointer != "" {
  1028  			gotPointer := dec.StackPointer()
  1029  			if gotPointer != call.wantPointer {
  1030  				t.Fatalf("%s: %d: Decoder.StackPointer = %s, want %s", where, i, gotPointer, call.wantPointer)
  1031  			}
  1032  		}
  1033  	}
  1034  	gotOffset := int(dec.InputOffset())
  1035  	if gotOffset != wantOffset {
  1036  		t.Fatalf("%s: Decoder.InputOffset = %v, want %v", where, gotOffset, wantOffset)
  1037  	}
  1038  	gotUnread := string(dec.s.unreadBuffer()) // should be a prefix of wantUnread
  1039  	wantUnread := in[wantOffset:]
  1040  	if !strings.HasPrefix(wantUnread, gotUnread) {
  1041  		t.Fatalf("%s: Decoder.UnreadBuffer = %v, want %v", where, gotUnread, wantUnread)
  1042  	}
  1043  }
  1044  
  1045  // TestBufferDecoder tests that we detect misuses of bytes.Buffer with Decoder.
  1046  func TestBufferDecoder(t *testing.T) {
  1047  	bb := bytes.NewBufferString("[null, false, true]")
  1048  	dec := NewDecoder(bb)
  1049  	var err error
  1050  	for {
  1051  		if _, err = dec.ReadToken(); err != nil {
  1052  			break
  1053  		}
  1054  		bb.WriteByte(' ') // not allowed to write to the buffer while reading
  1055  	}
  1056  	want := &ioError{action: "read", err: errBufferWriteAfterNext}
  1057  	if !equalError(err, want) {
  1058  		t.Fatalf("error mismatch: got %v, want %v", err, want)
  1059  	}
  1060  }
  1061  
  1062  var resumableDecoderTestdata = []string{
  1063  	`0`,
  1064  	`123456789`,
  1065  	`0.0`,
  1066  	`0.123456789`,
  1067  	`0e0`,
  1068  	`0e+0`,
  1069  	`0e123456789`,
  1070  	`0e+123456789`,
  1071  	`123456789.123456789e+123456789`,
  1072  	`-0`,
  1073  	`-123456789`,
  1074  	`-0.0`,
  1075  	`-0.123456789`,
  1076  	`-0e0`,
  1077  	`-0e-0`,
  1078  	`-0e123456789`,
  1079  	`-0e-123456789`,
  1080  	`-123456789.123456789e-123456789`,
  1081  
  1082  	`""`,
  1083  	`"a"`,
  1084  	`"ab"`,
  1085  	`"abc"`,
  1086  	`"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"`,
  1087  	`"\"\\\/\b\f\n\r\t"`,
  1088  	`"\u0022\u005c\u002f\u0008\u000c\u000a\u000d\u0009"`,
  1089  	`"\ud800\udead"`,
  1090  	"\"\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\U0001f602\"",
  1091  	`"\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\ud83d\ude02"`,
  1092  }
  1093  
  1094  // TestResumableDecoder tests that resume logic for parsing a
  1095  // JSON string and number properly works across every possible split point.
  1096  func TestResumableDecoder(t *testing.T) {
  1097  	for _, want := range resumableDecoderTestdata {
  1098  		t.Run("", func(t *testing.T) {
  1099  			dec := NewDecoder(iotest.OneByteReader(strings.NewReader(want)))
  1100  			got, err := dec.ReadValue()
  1101  			if err != nil {
  1102  				t.Fatalf("Decoder.ReadValue error: %v", err)
  1103  			}
  1104  			if string(got) != want {
  1105  				t.Fatalf("Decoder.ReadValue = %s, want %s", got, want)
  1106  			}
  1107  		})
  1108  	}
  1109  }
  1110  
  1111  // TestBlockingDecoder verifies that JSON values except numbers can be
  1112  // synchronously sent and received on a blocking pipe without a deadlock.
  1113  // Numbers are the exception since termination cannot be determined until
  1114  // either the pipe ends or a non-numeric character is encountered.
  1115  func TestBlockingDecoder(t *testing.T) {
  1116  	values := []string{"null", "false", "true", `""`, `{}`, `[]`}
  1117  
  1118  	r, w := net.Pipe()
  1119  	defer r.Close()
  1120  	defer w.Close()
  1121  
  1122  	enc := NewEncoder(w, jsonflags.OmitTopLevelNewline|1)
  1123  	dec := NewDecoder(r)
  1124  
  1125  	errCh := make(chan error)
  1126  
  1127  	// Test synchronous ReadToken calls.
  1128  	for _, want := range values {
  1129  		go func() {
  1130  			errCh <- enc.WriteValue(Value(want))
  1131  		}()
  1132  
  1133  		tok, err := dec.ReadToken()
  1134  		if err != nil {
  1135  			t.Fatalf("Decoder.ReadToken error: %v", err)
  1136  		}
  1137  		got := tok.String()
  1138  		switch tok.Kind() {
  1139  		case '"':
  1140  			got = `"` + got + `"`
  1141  		case '{', '[':
  1142  			tok, err := dec.ReadToken()
  1143  			if err != nil {
  1144  				t.Fatalf("Decoder.ReadToken error: %v", err)
  1145  			}
  1146  			got += tok.String()
  1147  		}
  1148  		if got != want {
  1149  			t.Fatalf("ReadTokens = %s, want %s", got, want)
  1150  		}
  1151  
  1152  		if err := <-errCh; err != nil {
  1153  			t.Fatalf("Encoder.WriteValue error: %v", err)
  1154  		}
  1155  	}
  1156  
  1157  	// Test synchronous ReadValue calls.
  1158  	for _, want := range values {
  1159  		go func() {
  1160  			errCh <- enc.WriteValue(Value(want))
  1161  		}()
  1162  
  1163  		got, err := dec.ReadValue()
  1164  		if err != nil {
  1165  			t.Fatalf("Decoder.ReadValue error: %v", err)
  1166  		}
  1167  		if string(got) != want {
  1168  			t.Fatalf("ReadValue = %s, want %s", got, want)
  1169  		}
  1170  
  1171  		if err := <-errCh; err != nil {
  1172  			t.Fatalf("Encoder.WriteValue error: %v", err)
  1173  		}
  1174  	}
  1175  }
  1176  
  1177  func TestPeekableDecoder(t *testing.T) {
  1178  	type operation any // PeekKind | ReadToken | ReadValue | BufferWrite
  1179  	type PeekKind struct {
  1180  		want Kind
  1181  	}
  1182  	type ReadToken struct {
  1183  		wantKind Kind
  1184  		wantErr  error
  1185  	}
  1186  	type ReadValue struct {
  1187  		wantKind Kind
  1188  		wantErr  error
  1189  	}
  1190  	type WriteString struct {
  1191  		in string
  1192  	}
  1193  	ops := []operation{
  1194  		PeekKind{0},
  1195  		WriteString{"[ "},
  1196  		ReadToken{0, io.EOF}, // previous error from PeekKind is cached once
  1197  		ReadToken{'[', nil},
  1198  
  1199  		PeekKind{0},
  1200  		WriteString{"] "},
  1201  		ReadValue{0, E(io.ErrUnexpectedEOF).withPos("[ ", "")}, // previous error from PeekKind is cached once
  1202  		ReadValue{0, newInvalidCharacterError("]", "at start of value").withPos("[ ", "/0")},
  1203  		ReadToken{']', nil},
  1204  
  1205  		WriteString{"[ "},
  1206  		ReadToken{'[', nil},
  1207  
  1208  		WriteString{" null "},
  1209  		PeekKind{'n'},
  1210  		PeekKind{'n'},
  1211  		ReadToken{'n', nil},
  1212  
  1213  		WriteString{", "},
  1214  		PeekKind{0},
  1215  		WriteString{"fal"},
  1216  		PeekKind{'f'},
  1217  		ReadValue{0, E(io.ErrUnexpectedEOF).withPos("[ ] [  null , fal", "/1")},
  1218  		WriteString{"se "},
  1219  		ReadValue{'f', nil},
  1220  
  1221  		PeekKind{0},
  1222  		WriteString{" , "},
  1223  		PeekKind{0},
  1224  		WriteString{` "" `},
  1225  		ReadValue{0, E(io.ErrUnexpectedEOF).withPos("[ ] [  null , false  , ", "")}, // previous error from PeekKind is cached once
  1226  		ReadValue{'"', nil},
  1227  
  1228  		WriteString{" , 0"},
  1229  		PeekKind{'0'},
  1230  		ReadToken{'0', nil},
  1231  
  1232  		WriteString{" , {} , []"},
  1233  		PeekKind{'{'},
  1234  		ReadValue{'{', nil},
  1235  		ReadValue{'[', nil},
  1236  
  1237  		WriteString{"]"},
  1238  		ReadToken{']', nil},
  1239  	}
  1240  
  1241  	bb := struct{ *bytes.Buffer }{new(bytes.Buffer)}
  1242  	d := NewDecoder(bb)
  1243  	for i, op := range ops {
  1244  		switch op := op.(type) {
  1245  		case PeekKind:
  1246  			if got := d.PeekKind(); got != op.want {
  1247  				t.Fatalf("%d: Decoder.PeekKind() = %v, want %v", i, got, op.want)
  1248  			}
  1249  		case ReadToken:
  1250  			gotTok, gotErr := d.ReadToken()
  1251  			gotKind := gotTok.Kind()
  1252  			if gotKind != op.wantKind || !equalError(gotErr, op.wantErr) {
  1253  				t.Fatalf("%d: Decoder.ReadToken() = (%v, %v), want (%v, %v)", i, gotKind, gotErr, op.wantKind, op.wantErr)
  1254  			}
  1255  		case ReadValue:
  1256  			gotVal, gotErr := d.ReadValue()
  1257  			gotKind := gotVal.Kind()
  1258  			if gotKind != op.wantKind || !equalError(gotErr, op.wantErr) {
  1259  				t.Fatalf("%d: Decoder.ReadValue() = (%v, %v), want (%v, %v)", i, gotKind, gotErr, op.wantKind, op.wantErr)
  1260  			}
  1261  		case WriteString:
  1262  			bb.WriteString(op.in)
  1263  		default:
  1264  			panic(fmt.Sprintf("unknown operation: %T", op))
  1265  		}
  1266  	}
  1267  }
  1268  

View as plain text