Quellcode durchsuchen

codec: refactoring to eliminate more bounds-checking

- add conditionals to eliminate bounds checks

Also, some more refactoring done to enable these
- move appendstringasbytes for \uXXXXYYYY to its own dedicated function
- readUntil takes flag that determines whether to include last token or not
Ugorji Nwoke vor 6 Jahren
Ursprung
Commit
a2ee74cda4
5 geänderte Dateien mit 186 neuen und 138 gelöschten Zeilen
  1. 4 4
      codec/codec_test.go
  2. 3 0
      codec/helper.go
  3. 131 101
      codec/json.go
  4. 40 28
      codec/reader.go
  5. 8 5
      codec/writer.go

+ 4 - 4
codec/codec_test.go

@@ -3053,7 +3053,7 @@ func doTestBufioDecReader(t *testing.T, bufsize int) {
 		`56789      01234'`,
 		`56789      01234'`,
 	} {
-		out = br.readUntil('\'')
+		out = br.readUntil('\'', true)
 		testDeepEqualErr(string(out), v2, t, "-")
 		// fmt.Printf("readUntil: out: `%s`\n", out)
 	}
@@ -3063,13 +3063,13 @@ func doTestBufioDecReader(t *testing.T, bufsize int) {
 		out = br.readTo(&jsonNumSet)
 		testDeepEqualErr(string(out), `01234`, t, "-")
 		// fmt.Printf("readTo: out: `%s`\n", out)
-		out = br.readUntil('\'')
+		out = br.readUntil('\'', true)
 		testDeepEqualErr(string(out), "'", t, "-")
 		// fmt.Printf("readUntil: out: `%s`\n", out)
 		out = br.readTo(&jsonNumSet)
 		testDeepEqualErr(string(out), `56789`, t, "-")
 		// fmt.Printf("readTo: out: `%s`\n", out)
-		out = br.readUntil('0')
+		out = br.readUntil('0', true)
 		testDeepEqualErr(string(out), `      0`, t, "-")
 		// fmt.Printf("readUntil: out: `%s`\n", out)
 		br.unreadn1()
@@ -3077,7 +3077,7 @@ func doTestBufioDecReader(t *testing.T, bufsize int) {
 	br.reset(strings.NewReader(s), bufsizehalf, &blist)
 	// println()
 	for range [4]struct{}{} {
-		out = br.readUntil(' ')
+		out = br.readUntil(' ', true)
 		testDeepEqualErr(string(out), `01234'56789 `, t, "-")
 		// fmt.Printf("readUntil: out: |%s|\n", out)
 		token = br.skip(&jsonCharWhitespaceSet)

+ 3 - 0
codec/helper.go

@@ -126,6 +126,9 @@ import (
 )
 
 const (
+	// rvNLen is the length of the array for readn or writen calls
+	rwNLen = 7
+
 	scratchByteArrayLen = 64
 	// initCollectionCap   = 16 // 32 is defensive. 16 is preferred.
 

+ 131 - 101
codec/json.go

@@ -210,7 +210,7 @@ func (e *jsonEncDriver) EncodeNil() {
 	// ie if initial token is n.
 
 	// e.e.encWr.writeb(jsonLiteralNull)
-	e.e.encWr.writen([7]byte{'n', 'u', 'l', 'l'}, 4)
+	e.e.encWr.writen([rwNLen]byte{'n', 'u', 'l', 'l'}, 4)
 
 	// if e.h.MapKeyAsString && e.e.c == containerMapKey {
 	// 	e.e.encWr.writeb(jsonLiterals[jsonLitNullQ : jsonLitNullQ+6])
@@ -258,18 +258,18 @@ func (e *jsonEncDriver) EncodeBool(b bool) {
 	if e.ks && e.e.c == containerMapKey {
 		if b {
 			// e.e.encWr.writeb(jsonLiteralTrueQ)
-			e.e.encWr.writen([7]byte{'"', 't', 'r', 'u', 'e', '"'}, 6)
+			e.e.encWr.writen([rwNLen]byte{'"', 't', 'r', 'u', 'e', '"'}, 6)
 		} else {
 			// e.e.encWr.writeb(jsonLiteralFalseQ)
-			e.e.encWr.writen([7]byte{'"', 'f', 'a', 'l', 's', 'e', '"'}, 7)
+			e.e.encWr.writen([rwNLen]byte{'"', 'f', 'a', 'l', 's', 'e', '"'}, 7)
 		}
 	} else {
 		if b {
 			// e.e.encWr.writeb(jsonLiteralTrue)
-			e.e.encWr.writen([7]byte{'t', 'r', 'u', 'e'}, 4)
+			e.e.encWr.writen([rwNLen]byte{'t', 'r', 'u', 'e'}, 4)
 		} else {
 			// e.e.encWr.writeb(jsonLiteralFalse)
-			e.e.encWr.writen([7]byte{'f', 'a', 'l', 's', 'e'}, 5)
+			e.e.encWr.writen([rwNLen]byte{'f', 'a', 'l', 's', 'e'}, 5)
 		}
 	}
 }
@@ -357,7 +357,7 @@ func (e *jsonEncDriver) EncodeStringBytesRaw(v []byte) {
 	// }
 	bs[0] = '"'
 	base64.StdEncoding.Encode(bs[1:], v)
-	bs[slen-1] = '"'
+	bs[len(bs)-1] = '"'
 	e.e.encWr.writeb(bs)
 	if len(e.b) < slen {
 		e.e.blist.put(bs)
@@ -410,8 +410,8 @@ func (e *jsonEncDriver) quoteStr(s string) {
 	const hex = "0123456789abcdef"
 	w := e.e.w()
 	w.writen1('"')
-	var start int
-	for i := 0; i < len(s); {
+	var i, start uint
+	for i < uint(len(s)) {
 		// encode all bytes < 0x20 (except \r, \n).
 		// also encode < > & to prevent security holes when served to some browsers.
 
@@ -420,11 +420,12 @@ func (e *jsonEncDriver) quoteStr(s string) {
 
 		// if 0x20 <= b && b != '\\' && b != '"' && b != '<' && b != '>' && b != '&' {
 		// if (htmlasis && jsonCharSafeSet.isset(b)) || jsonCharHtmlSafeSet.isset(b) {
-		if e.s.isset(s[i]) {
+		b := s[i]
+		if e.s.isset(b) {
 			i++
 			continue
 		}
-		if b := s[i]; b < utf8.RuneSelf {
+		if b < utf8.RuneSelf {
 			if start < i {
 				w.writestr(s[start:i])
 			}
@@ -469,13 +470,13 @@ func (e *jsonEncDriver) quoteStr(s string) {
 			}
 			w.writestr(`\u202`)
 			w.writen1(hex[c&0xF])
-			i += size
+			i += uint(size)
 			start = i
 			continue
 		}
-		i += size
+		i += uint(size)
 	}
-	if start < len(s) {
+	if start < uint(len(s)) {
 		w.writestr(s[start:])
 	}
 	w.writen1('"')
@@ -536,10 +537,10 @@ func (e *jsonEncDriverTypical) WriteMapEnd() {
 func (e *jsonEncDriverTypical) EncodeBool(b bool) {
 	if b {
 		// e.e.encWr.writeb(jsonLiteralTrue)
-		e.e.encWr.writen([7]byte{'t', 'r', 'u', 'e'}, 4)
+		e.e.encWr.writen([rwNLen]byte{'t', 'r', 'u', 'e'}, 4)
 	} else {
 		// e.e.encWr.writeb(jsonLiteralFalse)
-		e.e.encWr.writen([7]byte{'f', 'a', 'l', 's', 'e'}, 5)
+		e.e.encWr.writen([rwNLen]byte{'f', 'a', 'l', 's', 'e'}, 5)
 	}
 }
 
@@ -727,7 +728,7 @@ func (d *jsonDecDriver) ReadMapEnd() {
 func (d *jsonDecDriver) readLit4True() {
 	bs := d.d.decRd.readn(3)
 	d.tok = 0
-	if jsonValidateSymbols && bs != [7]byte{'r', 'u', 'e'} { // !bytes.Equal(bs, jsonLiteral4True)
+	if jsonValidateSymbols && bs != [rwNLen]byte{'r', 'u', 'e'} { // !bytes.Equal(bs, jsonLiteral4True)
 		d.d.errorf("expecting %s: got %s", jsonLiteral4True, bs)
 	}
 }
@@ -735,7 +736,7 @@ func (d *jsonDecDriver) readLit4True() {
 func (d *jsonDecDriver) readLit4False() {
 	bs := d.d.decRd.readn(4)
 	d.tok = 0
-	if jsonValidateSymbols && bs != [7]byte{'a', 'l', 's', 'e'} { // !bytes.Equal(bs, jsonLiteral4False)
+	if jsonValidateSymbols && bs != [rwNLen]byte{'a', 'l', 's', 'e'} { // !bytes.Equal(bs, jsonLiteral4False)
 		d.d.errorf("expecting %s: got %s", jsonLiteral4False, bs)
 	}
 }
@@ -743,7 +744,7 @@ func (d *jsonDecDriver) readLit4False() {
 func (d *jsonDecDriver) readLit4Null() {
 	bs := d.d.decRd.readn(3) // readx(3)
 	d.tok = 0
-	if jsonValidateSymbols && bs != [7]byte{'u', 'l', 'l'} { // !bytes.Equal(bs, jsonLiteral4Null)
+	if jsonValidateSymbols && bs != [rwNLen]byte{'u', 'l', 'l'} { // !bytes.Equal(bs, jsonLiteral4Null)
 		d.d.errorf("expecting %s: got %s", jsonLiteral4Null, bs)
 	}
 	d.fnil = true
@@ -838,8 +839,7 @@ func (d *jsonDecDriver) ContainerType() (vt valueType) {
 func (d *jsonDecDriver) decNumBytes() (bs []byte) {
 	d.advance()
 	if d.tok == '"' {
-		bs = d.d.decRd.readUntil('"')
-		bs = bs[:len(bs)-1]
+		bs = d.d.decRd.readUntil('"', false)
 	} else if d.tok == 'n' {
 		d.readLit4Null()
 	} else {
@@ -1093,8 +1093,7 @@ func (d *jsonDecDriver) readString() (bs []byte) {
 		return
 	}
 
-	bs = d.d.decRd.readUntil('"')
-	bs = bs[:len(bs)-1]
+	bs = d.d.decRd.readUntil('"', false)
 	d.tok = 0
 	return
 }
@@ -1107,47 +1106,53 @@ func (d *jsonDecDriver) appendStringAsBytes() (bs []byte) {
 	}
 	d.tok = 0
 
-	// xdebug2f("start")
-	var cs = d.d.decRd.readUntil('"')
-	// xdebugf("appendStringAsBytes: len: %d, cs: %s", len(cs), cs)
-
 	// append on each byte seen can be expensive, so we just
 	// keep track of where we last read a contiguous set of
 	// non-special bytes (using cursor variable),
 	// and when we see a special byte
 	// e.g. end-of-slice, " or \,
 	// we will append the full range into the v slice before proceeding
-	var cslen = uint(len(cs))
+
+	// xdebug2f("start")
+	var cs = d.d.decRd.readUntil('"', true)
+	// xdebugf("appendStringAsBytes: len: %d, cs: %s", len(cs), cs)
+	// var cslen = uint(len(cs))
 	var c uint8
 	var i, cursor uint
 	for {
-		if i == cslen {
+		if i >= uint(len(cs)) {
 			// d.bp.appends(cs[cursor:])
 			// d.bp.ensureExtraCap(int(cslen - cursor))
 			d.buf = append(d.buf, cs[cursor:]...)
-			cs = d.d.decRd.readUntil('"')
+			cs = d.d.decRd.readUntil('"', true)
 			// xdebugf("appendStringAsBytes: len: %d, cs: %s", len(cs), cs)
-			cslen = uint(len(cs))
+			// cslen = uint(len(cs))
 			i, cursor = 0, 0
+			continue // this continue helps elide the cs[i] below
 		}
 		c = cs[i]
 		if c == '"' {
-			if len(d.buf) > 0 {
-				// d.bp.appends(cs[cursor:i])
-				// d.bp.ensureExtraCap(int(i - cursor))
-				d.buf = append(d.buf, cs[cursor:i]...)
-			}
+			// if len(d.buf) > 0 {
+			// 	// d.bp.appends(cs[cursor:i])
+			// 	// d.bp.ensureExtraCap(int(i - cursor))
+			// 	d.buf = append(d.buf, cs[cursor:i]...)
+			// }
 			break
 		}
 		if c != '\\' {
 			i++
 			continue
 		}
+
 		// d.bp.appends(cs[cursor:i])
 		// d.bp.ensureExtraCap(int(i - cursor))
 		d.buf = append(d.buf, cs[cursor:i]...)
 		// d.bp.ensureExtraCap(4) // NOTE: 1 is sufficient, but say 4 for now
 		i++
+		if i >= uint(len(cs)) {
+			d.d.errorf("need at least 1 more bytes for \\ escape sequence")
+			return // bounds-check elimination
+		}
 		c = cs[i]
 		switch c {
 		case '"', '\\', '/', '\'':
@@ -1163,81 +1168,106 @@ func (d *jsonDecDriver) appendStringAsBytes() (bs []byte) {
 		case 't':
 			d.buf = append(d.buf, '\t')
 		case 'u':
-			var r rune
-			var rr uint32
-			if cslen < i+4 {
-				d.d.errorf("need at least 4 more bytes for unicode sequence")
-			}
-			var j uint
-			for _, c = range cs[i+1 : i+5] { // bounds-check-elimination
-				// best to use explicit if-else
-				// - not a table, etc which involve memory loads, array lookup with bounds checks, etc
-				if c >= '0' && c <= '9' {
-					rr = rr*16 + uint32(c-jsonU4Chk2)
-				} else if c >= 'a' && c <= 'f' {
-					rr = rr*16 + uint32(c-jsonU4Chk1)
-				} else if c >= 'A' && c <= 'F' {
-					rr = rr*16 + uint32(c-jsonU4Chk0)
-				} else {
-					r = unicode.ReplacementChar
-					i += 4
-					goto encode_rune
-				}
-			}
-			r = rune(rr)
-			i += 4
-			if utf16.IsSurrogate(r) {
-				if len(cs) >= int(i+6) {
-					var cx = cs[i+1:][:6:6] // [:6] affords bounds-check-elimination
-					if cx[0] == '\\' && cx[1] == 'u' {
-						i += 2
-						var rr1 uint32
-						for j = 2; j < 6; j++ {
-							c = cx[j]
-							if c >= '0' && c <= '9' {
-								rr = rr*16 + uint32(c-jsonU4Chk2)
-							} else if c >= 'a' && c <= 'f' {
-								rr = rr*16 + uint32(c-jsonU4Chk1)
-							} else if c >= 'A' && c <= 'F' {
-								rr = rr*16 + uint32(c-jsonU4Chk0)
-							} else {
-								r = unicode.ReplacementChar
-								i += 4
-								goto encode_rune
-							}
-						}
-						r = utf16.DecodeRune(r, rune(rr1))
-						i += 4
-						goto encode_rune
-					}
-				}
-				r = unicode.ReplacementChar
-			}
-		encode_rune:
-			w2 := utf8.EncodeRune(d.bstr[:], r)
-			d.buf = append(d.buf, d.bstr[:w2]...)
+			i = d.appendStringAsBytesSlashU(cs, i)
 		default:
 			d.d.errorf("unsupported escaped value: %c", c)
 		}
 		i++
 		cursor = i
 	}
-	if len(d.buf) == 0 {
-		// return cs[:len(cs)-1]
-		// returning cs was failing for bufio, as it seems bufio needs the buffer for other things.
-		// only return cs if bytesDecReader
-		cs = cs[:len(cs)-1]
-		if d.d.bytes {
-			return cs
+	if len(cs) > 0 {
+		if len(d.buf) > 0 && cursor < uint(len(cs)) {
+			d.buf = append(d.buf, cs[cursor:i]...)
+		} else {
+			// if bytes, just return the cs got from readUntil.
+			// do not do it for io, especially bufio, as the buffer is needed for other things
+			cs = cs[:i]
+			if d.d.bytes {
+				return cs
+			}
+			// d.bp.ensureExtraCap(len(cs))
+			d.buf = d.d.blist.check(d.buf, len(cs))
+			copy(d.buf, cs)
 		}
-		// d.bp.ensureExtraCap(len(cs))
-		d.buf = d.d.blist.check(d.buf, len(cs))
-		copy(d.buf, cs)
-		// xdebugf("cs: '%s', d.buf: '%s'", cs, d.buf)
-		return d.buf
 	}
-	// xdebug2f("returning d.buf: %s", d.buf)
 	return d.buf
+	// if len(d.buf) == 0 && len(cs) > 0 {
+	// 	// return cs[:len(cs)-1]
+	// 	// returning cs was failing for bufio, as it seems bufio needs the buffer for other things.
+	// 	// only return cs if bytesDecReader
+	// 	cs = cs[:len(cs)-1]
+	// 	if d.d.bytes {
+	// 		return cs
+	// 	}
+	// 	// d.bp.ensureExtraCap(len(cs))
+	// 	d.buf = d.d.blist.check(d.buf, len(cs))
+	// 	copy(d.buf, cs)
+	// 	// xdebugf("cs: '%s', d.buf: '%s'", cs, d.buf)
+	// 	return d.buf
+	// }
+	// // xdebug2f("returning d.buf: %s", d.buf)
+	// return d.buf
+}
+
+func (d *jsonDecDriver) appendStringAsBytesSlashU(cs []byte, i uint) uint {
+	var r rune
+	var rr uint32
+	var j uint
+	var c byte
+	if uint(len(cs)) < i+4 {
+		d.d.errorf("need at least 4 more bytes for unicode sequence")
+		return 0 // bounds-check elimination
+	}
+	for _, c = range cs[i+1 : i+5] { // bounds-check-elimination
+		// best to use explicit if-else
+		// - not a table, etc which involve memory loads, array lookup with bounds checks, etc
+		if c >= '0' && c <= '9' {
+			rr = rr*16 + uint32(c-jsonU4Chk2)
+		} else if c >= 'a' && c <= 'f' {
+			rr = rr*16 + uint32(c-jsonU4Chk1)
+		} else if c >= 'A' && c <= 'F' {
+			rr = rr*16 + uint32(c-jsonU4Chk0)
+		} else {
+			r = unicode.ReplacementChar
+			i += 4
+			goto encode_rune
+		}
+	}
+	r = rune(rr)
+	i += 4
+	if utf16.IsSurrogate(r) {
+		if len(cs) >= int(i+6) {
+			var cx = cs[i+1:][:6:6] // [:6] affords bounds-check-elimination
+			//var cx [6]byte
+			//copy(cx[:], cs[i+1:])
+			if cx[0] == '\\' && cx[1] == 'u' {
+				i += 2
+				var rr1 uint32
+				for j = 2; j < 6; j++ {
+					c = cx[j]
+					if c >= '0' && c <= '9' {
+						rr = rr*16 + uint32(c-jsonU4Chk2)
+					} else if c >= 'a' && c <= 'f' {
+						rr = rr*16 + uint32(c-jsonU4Chk1)
+					} else if c >= 'A' && c <= 'F' {
+						rr = rr*16 + uint32(c-jsonU4Chk0)
+					} else {
+						r = unicode.ReplacementChar
+						i += 4
+						goto encode_rune
+					}
+				}
+				r = utf16.DecodeRune(r, rune(rr1))
+				i += 4
+				goto encode_rune
+			}
+		}
+		r = unicode.ReplacementChar
+	}
+encode_rune:
+	w2 := utf8.EncodeRune(d.bstr[:], r)
+	d.buf = append(d.buf, d.bstr[:w2]...)
+	return i
 }
 
 func (d *jsonDecDriver) nakedNum(z *decNaked, bs []byte) (err error) {

+ 40 - 28
codec/reader.go

@@ -16,7 +16,7 @@ type decReader interface {
 	readb([]byte)
 	readn1() uint8
 	// read up to 7 bytes at a time
-	readn(num uint8) (v [7]byte)
+	readn(num uint8) (v [rwNLen]byte)
 	numread() uint // number of bytes read
 	track()
 	stopTrack() []byte
@@ -26,7 +26,7 @@ type decReader interface {
 	// readTo will read any byte that matches, stopping once no-longer matching.
 	readTo(accept *bitset256) (out []byte)
 	// readUntil will read, only stopping once it matches the 'stop' byte.
-	readUntil(stop byte) (out []byte)
+	readUntil(stop byte, includeLast bool) (out []byte)
 }
 
 // ------------------------------------------------
@@ -192,7 +192,7 @@ func (z *ioDecReader) UnreadByte() (err error) {
 	return
 }
 
-func (z *ioDecReader) readn(num uint8) (bs [7]byte) {
+func (z *ioDecReader) readn(num uint8) (bs [rwNLen]byte) {
 	z.readb(bs[:num])
 	// copy(bs[:], z.readx(uint(num)))
 	return
@@ -310,7 +310,7 @@ LOOP:
 	return z.bufr
 }
 
-func (z *ioDecReader) readUntil(stop byte) []byte {
+func (z *ioDecReader) readUntil(stop byte, includeLast bool) []byte {
 	// for {
 	// 	token, eof := z.readn1eof()
 	// 	if eof {
@@ -329,7 +329,10 @@ LOOP:
 	}
 	z.bufr = append(z.bufr, token)
 	if token == stop {
-		return z.bufr
+		if includeLast {
+			return z.bufr
+		}
+		return z.bufr[:len(z.bufr)-1]
 	}
 	goto LOOP
 }
@@ -468,7 +471,7 @@ func (z *bufioDecReader) unreadn1() {
 	}
 }
 
-func (z *bufioDecReader) readn(num uint8) (bs [7]byte) {
+func (z *bufioDecReader) readn(num uint8) (bs [rwNLen]byte) {
 	z.readb(bs[:num])
 	// copy(bs[:], z.readx(uint(num)))
 	return
@@ -672,7 +675,7 @@ func (z *bufioDecReader) readToFill(accept *bitset256) []byte {
 // 	return z.readLoopFn(i+1, out0)
 // }
 
-func (z *bufioDecReader) readUntil(stop byte) (out []byte) {
+func (z *bufioDecReader) readUntil(stop byte, includeLast bool) (out []byte) {
 	// defer func() { xdebug2f("bufio: readUntil: %s", out) }()
 	// _, out = z.search(in, nil, stop, 4); return
 
@@ -695,12 +698,17 @@ LOOP:
 				z.tr = append(z.tr, z.buf[z.c:i]...) // z.doTrack(i)
 			}
 			z.c = i
-			return
+			goto FINISH
 		}
 		i++
 		goto LOOP
 	}
-	return z.readUntilFill(stop)
+	out = z.readUntilFill(stop)
+FINISH:
+	if includeLast {
+		return
+	}
+	return out[:len(out)-1]
 }
 
 func (z *bufioDecReader) readUntilFill(stop byte) []byte {
@@ -841,7 +849,7 @@ func (z *bytesDecReader) readn1() (v uint8) {
 	return
 }
 
-func (z *bytesDecReader) readn(num uint8) (bs [7]byte) {
+func (z *bytesDecReader) readn(num uint8) (bs [rwNLen]byte) {
 	// if z.c+2 >= uint(len(z.b)) {
 	// 	panic(io.EOF)
 	// }
@@ -967,7 +975,7 @@ LOOP:
 	// return z.b[i:z.c]
 }
 
-func (z *bytesDecReader) readUntil(stop byte) (out []byte) {
+func (z *bytesDecReader) readUntil(stop byte, includeLast bool) (out []byte) {
 	i := z.c
 	// if i == len(z.b) {
 	// 	panic(io.EOF)
@@ -984,20 +992,24 @@ func (z *bytesDecReader) readUntil(stop byte) (out []byte) {
 	// 	}
 	// }
 LOOP:
-	// if i < uint(len(z.b)) {
-	if z.b[i] == stop {
+	if i < uint(len(z.b)) {
+		if z.b[i] == stop {
+			i++
+			if includeLast {
+				out = z.b[z.c:i]
+			} else {
+				out = z.b[z.c : i-1]
+			}
+			// z.a -= (i - z.c)
+			z.c = i
+			return
+		}
 		i++
-		out = z.b[z.c:i]
-		// z.a -= (i - z.c)
-		z.c = i
-		return
+		goto LOOP
 	}
-	i++
-	goto LOOP
-	// }
 	// z.a = 0
 	// z.c = blen
-	// panic(io.EOF)
+	panic(io.EOF)
 }
 
 func (z *bytesDecReader) track() {
@@ -1090,7 +1102,7 @@ func (z *decRd) unreadn1() {
 	}
 }
 
-func (z *decRd) readn(num uint8) [7]byte {
+func (z *decRd) readn(num uint8) [rwNLen]byte {
 	if z.bytes {
 		return z.rb.readn(num)
 	}
@@ -1150,14 +1162,14 @@ func (z *decRd) readTo(accept *bitset256) (out []byte) {
 	return z.ri.readTo(accept)
 }
 
-func (z *decRd) readUntil(stop byte) (out []byte) {
+func (z *decRd) readUntil(stop byte, includeLast bool) (out []byte) {
 	if z.bytes {
-		return z.rb.readUntil(stop)
+		return z.rb.readUntil(stop, includeLast)
 	}
 	if z.bufio {
-		return z.bi.readUntil(stop)
+		return z.bi.readUntil(stop, includeLast)
 	}
-	return z.ri.readUntil(stop)
+	return z.ri.readUntil(stop, includeLast)
 }
 
 /*
@@ -1176,13 +1188,13 @@ func (z *decRd) unreadn1IO() {
 	}
 }
 
-func (z *decRd) readn(num uint8) [7]byte {
+func (z *decRd) readn(num uint8) [rwNLen]byte {
 	if z.bytes {
 		return z.rb.readn(num)
 	}
 	return z.readnIO(num)
 }
-func (z *decRd) readnIO(num uint8) [7]byte {
+func (z *decRd) readnIO(num uint8) [rwNLen]byte {
 	if z.bufio {
 		return z.bi.readn(num)
 	}

+ 8 - 5
codec/writer.go

@@ -13,7 +13,7 @@ type encWriter interface {
 	writen1(byte)
 	writen2(byte, byte)
 	// writen will write up to 7 bytes at a time.
-	writen(b [7]byte, num uint8)
+	writen(b [rwNLen]byte, num uint8)
 	end()
 }
 
@@ -275,7 +275,7 @@ func (z *bufioEncWriter) writen2(b1, b2 byte) {
 	z.n += 2
 }
 
-func (z *bufioEncWriter) writen(b [7]byte, num uint8) {
+func (z *bufioEncWriter) writen(b [rwNLen]byte, num uint8) {
 	if int(num) > len(z.buf)-z.n {
 		z.flush()
 	}
@@ -324,8 +324,11 @@ func (z *bytesEncAppender) writen2(b1, b2 byte) {
 	// z.b = append(z.b, []byte{b1, b2}...) // cost: 83
 	// z.b = append(append(z.b, b1), b2) // cost 82
 }
-func (z *bytesEncAppender) writen(s [7]byte, num uint8) {
-	z.b = append(z.b, s[:num]...)
+func (z *bytesEncAppender) writen(s [rwNLen]byte, num uint8) {
+	// if num <= rwNLen {
+	if int(num) <= len(s) {
+		z.b = append(z.b, s[:num]...)
+	}
 }
 func (z *bytesEncAppender) endErr() error {
 	*(z.out) = z.b
@@ -393,7 +396,7 @@ func (z *encWr) writen2(b1, b2 byte) {
 		z.wf.writen2(b1, b2)
 	}
 }
-func (z *encWr) writen(b [7]byte, num uint8) {
+func (z *encWr) writen(b [rwNLen]byte, num uint8) {
 	if z.bytes {
 		z.wb.writen(b, num)
 	} else {