encoding/japanese: make EUCJP replace with FFFD on error

Updates golang/go#18898

Change-Id: Ic5fb77af67656889a387fa75a3e6efc9b9975817
Reviewed-on: https://go-review.googlesource.com/37316
Run-TryBot: Marcel van Lohuizen <mpvl@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Nigel Tao <nigeltao@golang.org>
diff --git a/encoding/japanese/all_test.go b/encoding/japanese/all_test.go
index 39fb27d..cc7e8dd 100644
--- a/encoding/japanese/all_test.go
+++ b/encoding/japanese/all_test.go
@@ -5,6 +5,7 @@
 package japanese_test
 
 import (
+	"strings"
 	"testing"
 
 	"golang.org/x/text/encoding"
@@ -22,6 +23,8 @@
 }
 
 func TestNonRepertoire(t *testing.T) {
+	// Pick n to cause the destination buffer in transform.String to overflow.
+	const n = 10000
 	testCases := []struct {
 		init      func(e encoding.Encoding) (string, transform.Transformer, error)
 		e         encoding.Encoding
@@ -42,6 +45,26 @@
 		{enc, japanese.ShiftJIS, "갂", ""},
 		{enc, japanese.ShiftJIS, "a갂", "a"},
 		{enc, japanese.ShiftJIS, "\u2190갂", "\x81\xa9"},
+
+		// Continue correctly after errors
+		{dec, japanese.EUCJP, "\x8e\xa0", "\ufffd\ufffd"},
+		{dec, japanese.EUCJP, "\x8e\xe0", "\ufffd"},
+		{dec, japanese.EUCJP, "\x8e\xff", "\ufffd\ufffd"},
+		{dec, japanese.EUCJP, "\x8ea", "\ufffda"},
+		{dec, japanese.EUCJP, "\x8f\xa0", "\ufffd\ufffd"},
+		{dec, japanese.EUCJP, "\x8f\xa1a", "\ufffda"},
+		{dec, japanese.EUCJP, "\x8f\xa1a", "\ufffda"},
+		{dec, japanese.EUCJP, "\x8f\xa1\xa0", "\ufffd\ufffd"},
+		{dec, japanese.EUCJP, "\x8f\xa1a", "\ufffda"},
+		{dec, japanese.EUCJP, "\x8f\xa2\xa2", "\ufffd"},
+		{dec, japanese.EUCJP, "\xfe", "\ufffd"},
+		{dec, japanese.EUCJP, "\xfe\xff", "\ufffd\ufffd"},
+		// Correct handling of end of source
+		{dec, japanese.EUCJP, strings.Repeat("\x8e", n), strings.Repeat("\ufffd", n)},
+		{dec, japanese.EUCJP, strings.Repeat("\x8f", n), strings.Repeat("\ufffd", n)},
+		{dec, japanese.EUCJP, strings.Repeat("\x8f\xa0", n), strings.Repeat("\ufffd", 2*n)},
+		{dec, japanese.EUCJP, "a" + strings.Repeat("\x8f\xa1", n), "a" + strings.Repeat("\ufffd", n)},
+		{dec, japanese.EUCJP, "a" + strings.Repeat("\x8f\xa1\xff", n), "a" + strings.Repeat("\ufffd", 2*n)},
 	}
 	for _, tc := range testCases {
 		dir, tr, wantErr := tc.init(tc.e)
diff --git a/encoding/japanese/eucjp.go b/encoding/japanese/eucjp.go
index 40f9b05..79313fa 100644
--- a/encoding/japanese/eucjp.go
+++ b/encoding/japanese/eucjp.go
@@ -5,7 +5,6 @@
 package japanese
 
 import (
-	"errors"
 	"unicode/utf8"
 
 	"golang.org/x/text/encoding"
@@ -23,10 +22,9 @@
 	identifier.EUCPkdFmtJapanese,
 }
 
-var errInvalidEUCJP = errors.New("japanese: invalid EUC-JP encoding")
-
 type eucJPDecoder struct{ transform.NopResetter }
 
+// See https://encoding.spec.whatwg.org/#euc-jp-decoder.
 func (eucJPDecoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
 	r, size := rune(0), 0
 loop:
@@ -37,60 +35,79 @@
 
 		case c0 == 0x8e:
 			if nSrc+1 >= len(src) {
-				err = transform.ErrShortSrc
-				break loop
+				if !atEOF {
+					err = transform.ErrShortSrc
+					break loop
+				}
+				r, size = utf8.RuneError, 1
+				break
 			}
 			c1 := src[nSrc+1]
-			if c1 < 0xa1 || 0xdf < c1 {
-				err = errInvalidEUCJP
-				break loop
+			switch {
+			case c1 < 0xa1:
+				r, size = utf8.RuneError, 1
+			case c1 > 0xdf:
+				r, size = utf8.RuneError, 2
+				if c1 == 0xff {
+					size = 1
+				}
+			default:
+				r, size = rune(c1)+(0xff61-0xa1), 2
 			}
-			r, size = rune(c1)+(0xff61-0xa1), 2
-
 		case c0 == 0x8f:
 			if nSrc+2 >= len(src) {
-				err = transform.ErrShortSrc
-				break loop
+				if !atEOF {
+					err = transform.ErrShortSrc
+					break loop
+				}
+				r, size = utf8.RuneError, 1
+				if p := nSrc + 1; p < len(src) && 0xa1 <= src[p] && src[p] < 0xfe {
+					size = 2
+				}
+				break
 			}
 			c1 := src[nSrc+1]
 			if c1 < 0xa1 || 0xfe < c1 {
-				err = errInvalidEUCJP
-				break loop
+				r, size = utf8.RuneError, 1
+				break
 			}
 			c2 := src[nSrc+2]
 			if c2 < 0xa1 || 0xfe < c2 {
-				err = errInvalidEUCJP
-				break loop
+				r, size = utf8.RuneError, 2
+				break
 			}
-			r, size = '\ufffd', 3
+			r, size = utf8.RuneError, 3
 			if i := int(c1-0xa1)*94 + int(c2-0xa1); i < len(jis0212Decode) {
 				r = rune(jis0212Decode[i])
 				if r == 0 {
-					r = '\ufffd'
+					r = utf8.RuneError
 				}
 			}
 
 		case 0xa1 <= c0 && c0 <= 0xfe:
 			if nSrc+1 >= len(src) {
-				err = transform.ErrShortSrc
-				break loop
+				if !atEOF {
+					err = transform.ErrShortSrc
+					break loop
+				}
+				r, size = utf8.RuneError, 1
+				break
 			}
 			c1 := src[nSrc+1]
 			if c1 < 0xa1 || 0xfe < c1 {
-				err = errInvalidEUCJP
-				break loop
+				r, size = utf8.RuneError, 1
+				break
 			}
-			r, size = '\ufffd', 2
+			r, size = utf8.RuneError, 2
 			if i := int(c0-0xa1)*94 + int(c1-0xa1); i < len(jis0208Decode) {
 				r = rune(jis0208Decode[i])
 				if r == 0 {
-					r = '\ufffd'
+					r = utf8.RuneError
 				}
 			}
 
 		default:
-			err = errInvalidEUCJP
-			break loop
+			r, size = utf8.RuneError, 1
 		}
 
 		if nDst+utf8.RuneLen(r) > len(dst) {
@@ -99,9 +116,6 @@
 		}
 		nDst += utf8.EncodeRune(dst[nDst:], r)
 	}
-	if atEOF && err == transform.ErrShortSrc {
-		err = errInvalidEUCJP
-	}
 	return nDst, nSrc, err
 }