From 2c36db9b0afe1ec4daa1dacd1efe28f4ff143bdd Mon Sep 17 00:00:00 2001 From: "Lasse R.H. Nielsen" Date: Tue, 19 Oct 2021 16:07:52 +0000 Subject: [PATCH] Deprecate `IntegerDivisionByZeroException`. Deprecates the class and makes it implement `Error` (more specifically `UnsupportedError`). Code throwing `IntegerDivisionByZeroException` should be migrated to throwing `unsupportedError` directly, code catching the exception class should start catching `UnsupportedError` immediately (or reconsider why they are catching at all). Integer division by zero also covers other ways that a double division can give a non-number result (any infinity or NaN result of the division prior to calling `truncate()` on the result, will cause `~/` to throw). Fixes #46776 Bug: https://github.com/dart-lang/sdk/issues/46776 Change-Id: Idf2657153dd16542e72c6ba921f587dd9fc9032a Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/208643 Reviewed-by: Nate Bosch Commit-Queue: Lasse R.H. Nielsen --- CHANGELOG.md | 7 ++++ sdk/lib/core/exceptions.dart | 7 +++- sdk/lib/core/num.dart | 16 ++++++-- tests/corelib/duration_test.dart | 2 +- tests/corelib_2/duration_test.dart | 2 +- .../operator/div_with_power_of_two2_test.dart | 3 +- .../integer_division_by_zero_test.dart | 39 ++++++++++++++++++- tests/language/operator/modulo_test.dart | 8 ++-- tests/language/operator/truncdiv_test.dart | 6 +-- .../language/operator/truncdiv_zero_test.dart | 4 +- tests/language/vm/div_mod_test.dart | 4 +- tests/language/vm/modtruncdiv_int_test.dart | 4 +- tests/language/vm/regression_37622_test.dart | 2 +- .../operator/div_with_power_of_two2_test.dart | 4 +- .../integer_division_by_zero_test.dart | 39 ++++++++++++++++++- tests/language_2/operator/modulo_test.dart | 8 ++-- tests/language_2/operator/truncdiv_test.dart | 6 +-- .../operator/truncdiv_zero_test.dart | 4 +- tests/language_2/vm/div_mod_test.dart | 4 +- tests/language_2/vm/modtruncdiv_int_test.dart | 4 +- .../language_2/vm/regression_37622_test.dart | 2 +- 21 files changed, 129 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8479f66f6c1..f165f47b16c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -412,6 +412,12 @@ - Add `Enum.compareByName` helper function for comparing enum values by name. - Add extension methods on `Iterable`, intended for `SomeEnumType.values` lists, to look up values by name. +- Deprecate `IntegerDivisionByZeroException`. + Makes the class also implement `Error`. Code throwing the exception will be + migrated to throwing an `Error` instead until the class is unused and + ready to be removed. + Code catching the class should move to catching `Error` instead + (or, for integers, check first for whether it's dividing by zero). #### `dart:io` @@ -670,6 +676,7 @@ This is a patch release that fixes: - The `Symbol` constructor now accepts any string as argument. Symbols are equal if they were created from the same string. + #### `dart:ffi` - Adds the `DynamicLibrary.providesSymbol` function to check whether a symbol is diff --git a/sdk/lib/core/exceptions.dart b/sdk/lib/core/exceptions.dart index 5904ec5549c..889c99163b6 100644 --- a/sdk/lib/core/exceptions.dart +++ b/sdk/lib/core/exceptions.dart @@ -166,8 +166,11 @@ class FormatException implements Exception { // Exception thrown when doing integer division with a zero divisor. // TODO(30743): Should be removed, and division by zero should just throw an -// [ArgumentError]. -class IntegerDivisionByZeroException implements Exception { +// [UnsupportedError]. +@Deprecated("Use UnsupportedError instead") +class IntegerDivisionByZeroException implements Exception, UnsupportedError { + String? get message => "Division resulted in non-finite value"; + StackTrace? get stackTrace => null; @pragma("vm:entry-point") const IntegerDivisionByZeroException(); String toString() => "IntegerDivisionByZeroException"; diff --git a/sdk/lib/core/num.dart b/sdk/lib/core/num.dart index 74dfd789e01..f7b72a5f693 100644 --- a/sdk/lib/core/num.dart +++ b/sdk/lib/core/num.dart @@ -142,11 +142,19 @@ abstract class num implements Comparable { /// Truncating division operator. /// - /// If either operand is a [double] then the result of the truncating division - /// `a ~/ b` is equivalent to `(a / b).truncate().toInt()`. + /// Performs truncating division of this number by [other]. + /// Truncating division is division where a fractional result + /// is converted to an integer by rounding towards zero. /// - /// If both operands are [int]s then `a ~/ b` performs the truncating - /// integer division. + /// If both operands are [int]s then [other] must not be zero. + /// Then `a ~/ b` corresponds to `a.remainder(b)` + /// such that `a == (a ~/ b) * b + a.remainder(b)`. + /// + /// If either operand is a [double] then the other operand is converted + /// to a double before performing the division and truncation of the result. + /// Then `a ~/ b` is equivalent to `(a / b).truncate()`. + /// This means that the intermediate result of the double division + /// must be a finite integer (not an infinity or [double.nan]). int operator ~/(num other); /// The negation of this value. diff --git a/tests/corelib/duration_test.dart b/tests/corelib/duration_test.dart index 1f8c543f3f0..7080723a45f 100644 --- a/tests/corelib/duration_test.dart +++ b/tests/corelib/duration_test.dart @@ -205,7 +205,7 @@ main() { Expect.equals(-3600000000, d.inMicroseconds); d = d1 * 0; Expect.equals(0, d.inMicroseconds); - Expect.throws(() => d1 ~/ 0, (e) => e is IntegerDivisionByZeroException); + Expect.throws(() => d1 ~/ 0, (e) => e is UnsupportedError); d = new Duration(microseconds: 0); Expect.isTrue(d < new Duration(microseconds: 1)); diff --git a/tests/corelib_2/duration_test.dart b/tests/corelib_2/duration_test.dart index 1e5c5a38639..fb804649d3d 100644 --- a/tests/corelib_2/duration_test.dart +++ b/tests/corelib_2/duration_test.dart @@ -207,7 +207,7 @@ main() { Expect.equals(-3600000000, d.inMicroseconds); d = d1 * 0; Expect.equals(0, d.inMicroseconds); - Expect.throws(() => d1 ~/ 0, (e) => e is IntegerDivisionByZeroException); + Expect.throws(() => d1 ~/ 0, (e) => e is UnsupportedError); d = new Duration(microseconds: 0); Expect.isTrue(d < new Duration(microseconds: 1)); diff --git a/tests/language/operator/div_with_power_of_two2_test.dart b/tests/language/operator/div_with_power_of_two2_test.dart index ef580260311..72cddc6aaf4 100644 --- a/tests/language/operator/div_with_power_of_two2_test.dart +++ b/tests/language/operator/div_with_power_of_two2_test.dart @@ -146,7 +146,6 @@ main() { Expect.equals(res, f(arg)); } } - Expect.throws(() => divBy0(4), - (e) => e is IntegerDivisionByZeroException || e is UnsupportedError); + Expect.throws(() => divBy0(4)); } } diff --git a/tests/language/operator/integer_division_by_zero_test.dart b/tests/language/operator/integer_division_by_zero_test.dart index 03cd66101cd..117bd18b4cc 100644 --- a/tests/language/operator/integer_division_by_zero_test.dart +++ b/tests/language/operator/integer_division_by_zero_test.dart @@ -7,8 +7,43 @@ import "package:expect/expect.dart"; -divBy0(a) => a ~/ 0; +num divBy(num a, num b) => a ~/ b; main() { - Expect.throws(() => divBy0(4), (e) => e is IntegerDivisionByZeroException); + // Dividing integers by zero is an error. + Expect.throws(() => divBy(1, 0)); + Expect.throws(() => divBy(0, 0)); + + // Dividing doubles by zero is an error (result is never finite). + Expect.throws(() => divBy(1.0, 0)); + Expect.throws(() => divBy(1, 0.0)); + Expect.throws(() => divBy(1, -0.0)); + Expect.throws(() => divBy(1.0, 0.0)); + // Double division yielding infinity is an error, even when not dividing + // by zero. + Expect.throws(() => divBy(double.maxFinite, 0.5)); + Expect.throws(() => divBy(1, double.minPositive)); + Expect.throws(() => divBy(double.infinity, 2.0)); + Expect.throws(() => divBy(-double.maxFinite, 0.5)); + Expect.throws(() => divBy(-1, double.minPositive)); + Expect.throws(() => divBy(-double.infinity, 2.0)); + // Double division yielding NaN is an error. + Expect.throws(() => divBy(0.0, 0.0)); + Expect.throws(() => divBy(double.infinity, double.infinity)); + Expect.throws(() => divBy(-0.0, 0.0)); + Expect.throws(() => divBy(-double.infinity, double.infinity)); + + // Truncating division containing a double truncates to max integer + // on non-web. + num one = 1; + if (one is! double) { + var minInt = -0x8000000000000000; + var maxInt = minInt - 1; + Expect.isTrue(maxInt > 0); + // Not on web. + Expect.equals(divBy(double.maxFinite, 2), maxInt); + Expect.equals(divBy(-double.maxFinite, 2), minInt); + Expect.equals(divBy(maxInt, 0.25), maxInt); + Expect.equals(divBy(minInt, 0.25), minInt); + } } diff --git a/tests/language/operator/modulo_test.dart b/tests/language/operator/modulo_test.dart index ae556267524..8f20730027c 100644 --- a/tests/language/operator/modulo_test.dart +++ b/tests/language/operator/modulo_test.dart @@ -13,11 +13,11 @@ main() { for (int i = -30; i < 30; i++) { Expect.equals(i % 256, foo(i)); Expect.equals(i % -256, boo(i)); - Expect.throws(() => hoo(i), (e) => e is IntegerDivisionByZeroException); + Expect.throws(() => hoo(i), (e) => e is UnsupportedError); Expect.equals(i ~/ 254 + i % 254, fooTwo(i)); Expect.equals(i ~/ -254 + i % -254, booTwo(i)); - Expect.throws(() => hooTwo(i), (e) => e is IntegerDivisionByZeroException); + Expect.throws(() => hooTwo(i), (e) => e is UnsupportedError); if (i > 0) { Expect.equals(i % 10, noDom(i)); } else { @@ -35,8 +35,8 @@ main() { Expect.equals(i ~/ i + i % i, fooTwo2(i)); } } - Expect.throws(() => foo2(0), (e) => e is IntegerDivisionByZeroException); - Expect.throws(() => fooTwo2(0), (e) => e is IntegerDivisionByZeroException); + Expect.throws(() => foo2(0), (e) => e is UnsupportedError); + Expect.throws(() => fooTwo2(0), (e) => e is UnsupportedError); } foo(i) => i % 256; // This will get optimized to AND instruction. diff --git a/tests/language/operator/truncdiv_test.dart b/tests/language/operator/truncdiv_test.dart index bd043d10966..12b53fe818d 100644 --- a/tests/language/operator/truncdiv_test.dart +++ b/tests/language/operator/truncdiv_test.dart @@ -17,10 +17,8 @@ main() { } } // We don't specify the exact exception type here, that is covered in - // truncdiv_zero_test. The correct answer is IntegerDivisionByZeroException, - // but the web platform has only one num type and can't distinguish between - // int and double, so it throws UnsupportedError (the behaviour for double). - Expect.throws(() => foo2(0)); + // truncdiv_zero_test. + Expect.throws(() => foo2(0)); } foo(i, x) => i % x; diff --git a/tests/language/operator/truncdiv_zero_test.dart b/tests/language/operator/truncdiv_zero_test.dart index 34cecc384ab..11f0bb4b2bb 100644 --- a/tests/language/operator/truncdiv_zero_test.dart +++ b/tests/language/operator/truncdiv_zero_test.dart @@ -9,6 +9,6 @@ import "package:expect/expect.dart"; import "truncdiv_test.dart" as truncdiv_test show foo, foo2; main() { - Expect.throws(() => truncdiv_test.foo(12, 0)); - Expect.throws(() => truncdiv_test.foo2(0)); + Expect.throws(() => truncdiv_test.foo(12, 0)); + Expect.throws(() => truncdiv_test.foo2(0)); } diff --git a/tests/language/vm/div_mod_test.dart b/tests/language/vm/div_mod_test.dart index ea222409ab6..84c8c717173 100755 --- a/tests/language/vm/div_mod_test.dart +++ b/tests/language/vm/div_mod_test.dart @@ -138,7 +138,7 @@ main() { bool threw = false; try { div0(i); - } on IntegerDivisionByZeroException catch (e) { + } on UnsupportedError catch (e) { threw = true; } Expect.isTrue(threw); @@ -149,7 +149,7 @@ main() { bool threw = false; try { mod0(i); - } on IntegerDivisionByZeroException catch (e) { + } on UnsupportedError catch (e) { threw = true; } Expect.isTrue(threw); diff --git a/tests/language/vm/modtruncdiv_int_test.dart b/tests/language/vm/modtruncdiv_int_test.dart index 9fc5b537415..59486476fbe 100644 --- a/tests/language/vm/modtruncdiv_int_test.dart +++ b/tests/language/vm/modtruncdiv_int_test.dart @@ -285,14 +285,14 @@ main() { try { doModVars(9, 9, -9, 0); acc = 0; // don't reach! - } on IntegerDivisionByZeroException catch (e, s) {} + } on UnsupportedError catch (e, s) {} Expect.equals(12, acc); acc = 0; try { doTruncDivVars(9, 9, -9, 0); acc = 0; // don't reach! - } on IntegerDivisionByZeroException catch (e, s) {} + } on UnsupportedError catch (e, s) {} Expect.equals(-23, acc); } } diff --git a/tests/language/vm/regression_37622_test.dart b/tests/language/vm/regression_37622_test.dart index d5999fc7972..b11e9a4b10a 100755 --- a/tests/language/vm/regression_37622_test.dart +++ b/tests/language/vm/regression_37622_test.dart @@ -30,7 +30,7 @@ main() { bool d = false; try { x = foo(); - } on IntegerDivisionByZeroException catch (e) { + } on UnsupportedError catch (e) { d = true; } Expect.equals(x, 0); diff --git a/tests/language_2/operator/div_with_power_of_two2_test.dart b/tests/language_2/operator/div_with_power_of_two2_test.dart index fdbf57135d2..b3db5ce5dfe 100644 --- a/tests/language_2/operator/div_with_power_of_two2_test.dart +++ b/tests/language_2/operator/div_with_power_of_two2_test.dart @@ -148,7 +148,7 @@ main() { Expect.equals(res, f(arg)); } } - Expect.throws(() => divBy0(4), - (e) => e is IntegerDivisionByZeroException || e is UnsupportedError); + Expect.throws(() => divBy0(4)); + ; } } diff --git a/tests/language_2/operator/integer_division_by_zero_test.dart b/tests/language_2/operator/integer_division_by_zero_test.dart index 3ba518acc66..e1fc3438f5a 100644 --- a/tests/language_2/operator/integer_division_by_zero_test.dart +++ b/tests/language_2/operator/integer_division_by_zero_test.dart @@ -9,8 +9,43 @@ import "package:expect/expect.dart"; -divBy0(a) => a ~/ 0; +num divBy(num a, num b) => a ~/ b; main() { - Expect.throws(() => divBy0(4), (e) => e is IntegerDivisionByZeroException); + // Dividing integers by zero is an error. + Expect.throws(() => divBy(1, 0)); + Expect.throws(() => divBy(0, 0)); + + // Dividing doubles by zero is an error (result is never finite). + Expect.throws(() => divBy(1.0, 0)); + Expect.throws(() => divBy(1, 0.0)); + Expect.throws(() => divBy(1, -0.0)); + Expect.throws(() => divBy(1.0, 0.0)); + // Double division yielding infinity is an error, even when not dividing + // by zero. + Expect.throws(() => divBy(double.maxFinite, 0.5)); + Expect.throws(() => divBy(1, double.minPositive)); + Expect.throws(() => divBy(double.infinity, 2.0)); + Expect.throws(() => divBy(-double.maxFinite, 0.5)); + Expect.throws(() => divBy(-1, double.minPositive)); + Expect.throws(() => divBy(-double.infinity, 2.0)); + // Double division yielding NaN is an error. + Expect.throws(() => divBy(0.0, 0.0)); + Expect.throws(() => divBy(double.infinity, double.infinity)); + Expect.throws(() => divBy(-0.0, 0.0)); + Expect.throws(() => divBy(-double.infinity, double.infinity)); + + // Truncating division containing a double truncates to max integer + // on non-web. + num one = 1; + if (one is! double) { + var minInt = -0x8000000000000000; + var maxInt = minInt - 1; + Expect.isTrue(maxInt > 0); + // Not on web. + Expect.equals(divBy(double.maxFinite, 2), maxInt); + Expect.equals(divBy(-double.maxFinite, 2), minInt); + Expect.equals(divBy(maxInt, 0.25), maxInt); + Expect.equals(divBy(minInt, 0.25), minInt); + } } diff --git a/tests/language_2/operator/modulo_test.dart b/tests/language_2/operator/modulo_test.dart index 6e38c09e99f..417c55673fc 100644 --- a/tests/language_2/operator/modulo_test.dart +++ b/tests/language_2/operator/modulo_test.dart @@ -15,11 +15,11 @@ main() { for (int i = -30; i < 30; i++) { Expect.equals(i % 256, foo(i)); Expect.equals(i % -256, boo(i)); - Expect.throws(() => hoo(i), (e) => e is IntegerDivisionByZeroException); + Expect.throws(() => hoo(i), (e) => e is UnsupportedError); Expect.equals(i ~/ 254 + i % 254, fooTwo(i)); Expect.equals(i ~/ -254 + i % -254, booTwo(i)); - Expect.throws(() => hooTwo(i), (e) => e is IntegerDivisionByZeroException); + Expect.throws(() => hooTwo(i), (e) => e is UnsupportedError); if (i > 0) { Expect.equals(i % 10, noDom(i)); } else { @@ -37,8 +37,8 @@ main() { Expect.equals(i ~/ i + i % i, fooTwo2(i)); } } - Expect.throws(() => foo2(0), (e) => e is IntegerDivisionByZeroException); - Expect.throws(() => fooTwo2(0), (e) => e is IntegerDivisionByZeroException); + Expect.throws(() => foo2(0), (e) => e is UnsupportedError); + Expect.throws(() => fooTwo2(0), (e) => e is UnsupportedError); } foo(i) => i % 256; // This will get optimized to AND instruction. diff --git a/tests/language_2/operator/truncdiv_test.dart b/tests/language_2/operator/truncdiv_test.dart index b3f3cbd9335..e8d7d924421 100644 --- a/tests/language_2/operator/truncdiv_test.dart +++ b/tests/language_2/operator/truncdiv_test.dart @@ -19,10 +19,8 @@ main() { } } // We don't specify the exact exception type here, that is covered in - // truncdiv_zero_test. The correct answer is IntegerDivisionByZeroException, - // but the web platform has only one num type and can't distinguish between - // int and double, so it throws UnsupportedError (the behaviour for double). - Expect.throws(() => foo2(0)); + // truncdiv_zero_test. + Expect.throws(() => foo2(0)); } foo(i, x) => i % x; diff --git a/tests/language_2/operator/truncdiv_zero_test.dart b/tests/language_2/operator/truncdiv_zero_test.dart index ed410907c11..c69b9288692 100644 --- a/tests/language_2/operator/truncdiv_zero_test.dart +++ b/tests/language_2/operator/truncdiv_zero_test.dart @@ -11,6 +11,6 @@ import "package:expect/expect.dart"; import "truncdiv_test.dart" as truncdiv_test show foo, foo2; main() { - Expect.throws(() => truncdiv_test.foo(12, 0)); - Expect.throws(() => truncdiv_test.foo2(0)); + Expect.throws(() => truncdiv_test.foo(12, 0)); + Expect.throws(() => truncdiv_test.foo2(0)); } diff --git a/tests/language_2/vm/div_mod_test.dart b/tests/language_2/vm/div_mod_test.dart index 0ce3c2845f4..47d3b0b9f07 100755 --- a/tests/language_2/vm/div_mod_test.dart +++ b/tests/language_2/vm/div_mod_test.dart @@ -138,7 +138,7 @@ main() { bool threw = false; try { div0(i); - } on IntegerDivisionByZeroException catch (e) { + } on UnsupportedError catch (e) { threw = true; } Expect.isTrue(threw); @@ -149,7 +149,7 @@ main() { bool threw = false; try { mod0(i); - } on IntegerDivisionByZeroException catch (e) { + } on UnsupportedError catch (e) { threw = true; } Expect.isTrue(threw); diff --git a/tests/language_2/vm/modtruncdiv_int_test.dart b/tests/language_2/vm/modtruncdiv_int_test.dart index 907f912904d..b34ac76e6eb 100644 --- a/tests/language_2/vm/modtruncdiv_int_test.dart +++ b/tests/language_2/vm/modtruncdiv_int_test.dart @@ -287,14 +287,14 @@ main() { try { doModVars(9, 9, -9, 0); acc = 0; // don't reach! - } on IntegerDivisionByZeroException catch (e, s) {} + } on UnsupportedError catch (e, s) {} Expect.equals(12, acc); acc = 0; try { doTruncDivVars(9, 9, -9, 0); acc = 0; // don't reach! - } on IntegerDivisionByZeroException catch (e, s) {} + } on UnsupportedError catch (e, s) {} Expect.equals(-23, acc); } } diff --git a/tests/language_2/vm/regression_37622_test.dart b/tests/language_2/vm/regression_37622_test.dart index c7ef6f0ec82..48562f53fcc 100755 --- a/tests/language_2/vm/regression_37622_test.dart +++ b/tests/language_2/vm/regression_37622_test.dart @@ -31,7 +31,7 @@ main() { bool d = false; try { x = foo(); - } on IntegerDivisionByZeroException catch (e) { + } on UnsupportedError catch (e) { d = true; } Expect.equals(x, 0);