internal/export/idna: fix int32 overflows

Prefer multiplication (int64(b)*int64(c) > MaxInt32) over division (b >
MaxInt32/c) for overflow checking as it is a little faster on 386, and a
LOT faster on amd64.

For golang/go#28233.

Change-Id: Ibf42529b93b699417781adc7eca6e66474f00bbf
Reviewed-on: https://go-review.googlesource.com/c/text/+/317731
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Trust: Damien Neil <dneil@google.com>
diff --git a/internal/export/idna/punycode.go b/internal/export/idna/punycode.go
index f0cbd48..7e96feb 100644
--- a/internal/export/idna/punycode.go
+++ b/internal/export/idna/punycode.go
@@ -47,6 +47,7 @@
 		}
 	}
 	i, n, bias := int32(0), initialN, initialBias
+	overflow := false
 	for pos < len(encoded) {
 		oldI, w := i, int32(1)
 		for k := base; ; k += base {
@@ -58,29 +59,32 @@
 				return "", punyError(encoded)
 			}
 			pos++
-			i += digit * w
-			if i < 0 {
+			i, overflow = madd(i, digit, w)
+			if overflow {
 				return "", punyError(encoded)
 			}
 			t := k - bias
-			if t < tmin {
+			if k <= bias {
 				t = tmin
-			} else if t > tmax {
+			} else if k >= bias+tmax {
 				t = tmax
 			}
 			if digit < t {
 				break
 			}
-			w *= base - t
-			if w >= math.MaxInt32/base {
+			w, overflow = madd(0, w, base-t)
+			if overflow {
 				return "", punyError(encoded)
 			}
 		}
+		if len(output) >= 1024 {
+			return "", punyError(encoded)
+		}
 		x := int32(len(output) + 1)
 		bias = adapt(i-oldI, x, oldI == 0)
 		n += i / x
 		i %= x
-		if n > utf8.MaxRune || len(output) >= 1024 {
+		if n < 0 || n > utf8.MaxRune {
 			return "", punyError(encoded)
 		}
 		output = append(output, 0)
@@ -113,6 +117,7 @@
 	if b > 0 {
 		output = append(output, '-')
 	}
+	overflow := false
 	for remaining != 0 {
 		m := int32(0x7fffffff)
 		for _, r := range s {
@@ -120,8 +125,8 @@
 				m = r
 			}
 		}
-		delta += (m - n) * (h + 1)
-		if delta < 0 {
+		delta, overflow = madd(delta, m-n, h+1)
+		if overflow {
 			return "", punyError(s)
 		}
 		n = m
@@ -139,9 +144,9 @@
 			q := delta
 			for k := base; ; k += base {
 				t := k - bias
-				if t < tmin {
+				if k <= bias {
 					t = tmin
-				} else if t > tmax {
+				} else if k >= bias+tmax {
 					t = tmax
 				}
 				if q < t {
@@ -162,6 +167,15 @@
 	return string(output), nil
 }
 
+// madd computes a + (b * c), detecting overflow.
+func madd(a, b, c int32) (next int32, overflow bool) {
+	p := int64(b) * int64(c)
+	if p > math.MaxInt32-int64(a) {
+		return 0, true
+	}
+	return a + int32(p), false
+}
+
 func decodeDigit(x byte) (digit int32, ok bool) {
 	switch {
 	case '0' <= x && x <= '9':
diff --git a/internal/export/idna/punycode_test.go b/internal/export/idna/punycode_test.go
index 2d99239..5cf0c96 100644
--- a/internal/export/idna/punycode_test.go
+++ b/internal/export/idna/punycode_test.go
@@ -177,6 +177,7 @@
 	"decode 9999999999a",  // "9999999999a" overflows the int32 calculation.
 
 	"encode " + strings.Repeat("x", 65536) + "\uff00", // int32 overflow.
+	"encode " + strings.Repeat("x", 65666) + "\uffff", // int32 overflow. issue #28233
 }
 
 func TestPunycodeErrors(t *testing.T) {