[corelib] Fix truncation errors in int.modPow

Use BigInt arithmetic when intermediate products might overflow.

Web versions range-check inputs to avoid working on truncated inputs.

Bug: 37469
Change-Id: I27d2da2fff7901ce6dbfa5929c6998e87e8808e2
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/109360
Reviewed-by: Lasse R.H. Nielsen <lrn@google.com>
Commit-Queue: Stephen Adams <sra@google.com>
This commit is contained in:
Stephen Adams 2019-07-17 13:52:25 +00:00 committed by commit-bot@chromium.org
parent f77093145a
commit 41d3971e83
3 changed files with 60 additions and 4 deletions

View file

@ -360,6 +360,14 @@ abstract class _IntegerImplementation implements int {
if (e < 0) throw new RangeError.range(e, 0, null, "exponent");
if (m <= 0) throw new RangeError.range(m, 1, null, "modulus");
if (e == 0) return 1;
// This is floor(sqrt(2^63)).
const int maxValueThatCanBeSquaredWithoutTruncation = 3037000499;
if (m > maxValueThatCanBeSquaredWithoutTruncation) {
// Use BigInt version to avoid truncation in multiplications below.
return BigInt.from(this).modPow(BigInt.from(e), BigInt.from(m)).toInt();
}
int b = this;
if (b < 0 || b > m) {
b %= m;

View file

@ -437,6 +437,30 @@ class JSNumber extends Interceptor implements int, double {
if (e < 0) throw RangeError.range(e, 0, null, "exponent");
if (m <= 0) throw RangeError.range(m, 1, null, "modulus");
if (e == 0) return 1;
const int maxPreciseInteger = 9007199254740991;
// Reject inputs that are outside the range of integer values that can be
// represented precisely as a Number (double).
if (this < -maxPreciseInteger || this > maxPreciseInteger) {
throw RangeError.range(
this, -maxPreciseInteger, maxPreciseInteger, 'receiver');
}
if (e > maxPreciseInteger) {
throw RangeError.range(e, 0, maxPreciseInteger, 'exponent');
}
if (m > maxPreciseInteger) {
throw RangeError.range(e, 1, maxPreciseInteger, 'modulus');
}
// This is floor(sqrt(maxPreciseInteger)).
const int maxValueThatCanBeSquaredWithoutTruncation = 94906265;
if (m > maxValueThatCanBeSquaredWithoutTruncation) {
// Use BigInt version to avoid truncation in multiplications below. The
// 'maxPreciseInteger' check on [m] ensures that toInt() does not round.
return BigInt.from(this).modPow(BigInt.from(e), BigInt.from(m)).toInt();
}
int b = this;
if (b < 0 || b > m) {
b %= m;

View file

@ -503,14 +503,38 @@ class JSInt extends JSNumber implements int {
// Returns pow(this, e) % m.
int modPow(int e, int m) {
if (e is! int) {
throw new ArgumentError.value(e, 'exponent', 'not an integer');
throw ArgumentError.value(e, 'exponent', 'not an integer');
}
if (m is! int) {
throw new ArgumentError.value(m, 'modulus', 'not an integer');
throw ArgumentError.value(m, 'modulus', 'not an integer');
}
if (e < 0) throw new RangeError.range(e, 0, null, 'exponent');
if (m <= 0) throw new RangeError.range(m, 1, null, 'modulus');
if (e < 0) throw RangeError.range(e, 0, null, 'exponent');
if (m <= 0) throw RangeError.range(m, 1, null, 'modulus');
if (e == 0) return 1;
const int maxPreciseInteger = 9007199254740991;
// Reject inputs that are outside the range of integer values that can be
// represented precisely as a Number (double).
if (this < -maxPreciseInteger || this > maxPreciseInteger) {
throw RangeError.range(
this, -maxPreciseInteger, maxPreciseInteger, 'receiver');
}
if (e > maxPreciseInteger) {
throw RangeError.range(e, 0, maxPreciseInteger, 'exponent');
}
if (m > maxPreciseInteger) {
throw RangeError.range(e, 1, maxPreciseInteger, 'modulus');
}
// This is floor(sqrt(maxPreciseInteger)).
const int maxValueThatCanBeSquaredWithoutTruncation = 94906265;
if (m > maxValueThatCanBeSquaredWithoutTruncation) {
// Use BigInt version to avoid truncation in multiplications below. The
// 'maxPreciseInteger' check on [m] ensures that toInt() does not round.
return BigInt.from(this).modPow(BigInt.from(e), BigInt.from(m)).toInt();
}
int b = this;
if (b < 0 || b > m) {
b %= m;