From: Peter Edberg Date: Sun, 29 Oct 2017 03:38:05 +0000 (+0000) Subject: ICU-12504 in ICU4C Persian cal, use int64_t math for one operation to avoid overflow... X-Git-Tag: release-61-rc~193 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=71dd84d4ffd6600a70e5bca56a22b957e6642bd4;p=icu ICU-12504 in ICU4C Persian cal, use int64_t math for one operation to avoid overflow; add tests in C and J X-SVN-Rev: 40654 --- diff --git a/icu4c/source/i18n/gregoimp.cpp b/icu4c/source/i18n/gregoimp.cpp index e62044b361a..537aa19d8a4 100644 --- a/icu4c/source/i18n/gregoimp.cpp +++ b/icu4c/source/i18n/gregoimp.cpp @@ -27,6 +27,11 @@ int32_t ClockMath::floorDivide(int32_t numerator, int32_t denominator) { numerator / denominator : ((numerator + 1) / denominator) - 1; } +int64_t ClockMath::floorDivide(int64_t numerator, int64_t denominator) { + return (numerator >= 0) ? + numerator / denominator : ((numerator + 1) / denominator) - 1; +} + int32_t ClockMath::floorDivide(double numerator, int32_t denominator, int32_t& remainder) { double quotient; diff --git a/icu4c/source/i18n/gregoimp.h b/icu4c/source/i18n/gregoimp.h index b30741679df..afaacda0b41 100644 --- a/icu4c/source/i18n/gregoimp.h +++ b/icu4c/source/i18n/gregoimp.h @@ -40,6 +40,17 @@ class ClockMath { */ static int32_t floorDivide(int32_t numerator, int32_t denominator); + /** + * Divide two integers, returning the floor of the quotient. + * Unlike the built-in division, this is mathematically + * well-behaved. E.g., -1/4 => 0 but + * floorDivide(-1,4) => -1. + * @param numerator the numerator + * @param denominator a divisor which must be != 0 + * @return the floor of the quotient + */ + static int64_t floorDivide(int64_t numerator, int64_t denominator); + /** * Divide two numbers, returning the floor of the quotient. * Unlike the built-in division, this is mathematically diff --git a/icu4c/source/i18n/persncal.cpp b/icu4c/source/i18n/persncal.cpp index f66ac676a4a..f38f779fea9 100644 --- a/icu4c/source/i18n/persncal.cpp +++ b/icu4c/source/i18n/persncal.cpp @@ -213,7 +213,7 @@ void PersianCalendar::handleComputeFields(int32_t julianDay, UErrorCode &/*statu int32_t year, month, dayOfMonth, dayOfYear; int32_t daysSinceEpoch = julianDay - PERSIAN_EPOCH; - year = 1 + ClockMath::floorDivide(33 * daysSinceEpoch + 3, 12053); + year = 1 + (int32_t)ClockMath::floorDivide(33 * (int64_t)daysSinceEpoch + 3, (int64_t)12053); int32_t farvardin1 = 365 * (year - 1) + ClockMath::floorDivide(8 * year + 21, 33); dayOfYear = (daysSinceEpoch - farvardin1); // 0-based diff --git a/icu4c/source/test/intltest/calregts.cpp b/icu4c/source/test/intltest/calregts.cpp index 17fc81425c4..f1eb17bbed3 100644 --- a/icu4c/source/test/intltest/calregts.cpp +++ b/icu4c/source/test/intltest/calregts.cpp @@ -12,6 +12,7 @@ #include "calregts.h" +#include "unicode/calendar.h" #include "unicode/gregocal.h" #include "unicode/simpletz.h" #include "unicode/smpdtfmt.h" @@ -91,6 +92,7 @@ CalendarRegressionTest::runIndexedTest( int32_t index, UBool exec, const char* & CASE(49,Test9019); CASE(50,TestT9452); CASE(51,TestT11632); + CASE(52,TestPersianCalOverflow); default: name = ""; break; } } @@ -2985,6 +2987,36 @@ void CalendarRegressionTest::TestT11632(void) { assertEquals("correct datetime displayed for hour value", UnicodeString("1970-01-13T12:00:00"), dstr); } } -} +} + +/** + * @bug ticket 13454 + */ +void CalendarRegressionTest::TestPersianCalOverflow(void) { + const char* localeID = "bs_Cyrl@calendar=persian"; + UErrorCode status = U_ZERO_ERROR; + Calendar* cal = Calendar::createInstance(Locale(localeID), status); + if(U_FAILURE(status)) { + dataerrln("FAIL: Calendar::createInstance for localeID %s: %s", localeID, u_errorName(status)); + } else { + int32_t maxMonth = cal->getMaximum(UCAL_MONTH); + int32_t maxDayOfMonth = cal->getMaximum(UCAL_DATE); + int32_t jd, month, dayOfMonth; + for (jd = 67023580; jd <= 67023584; jd++) { // year 178171, int32_t overflow if jd >= 67023582 + status = U_ZERO_ERROR; + cal->clear(); + cal->set(UCAL_JULIAN_DAY, jd); + month = cal->get(UCAL_MONTH, status); + dayOfMonth = cal->get(UCAL_DATE, status); + if ( U_FAILURE(status) ) { + errln("FAIL: Calendar->get MONTH/DATE for localeID %s, julianDay %d, status %s\n", localeID, jd, u_errorName(status)); + } else if (month > maxMonth || dayOfMonth > maxDayOfMonth) { + errln("FAIL: localeID %s, julianDay %d; maxMonth %d, got month %d; maxDayOfMonth %d, got dayOfMonth %d\n", + localeID, jd, maxMonth, month, maxDayOfMonth, dayOfMonth); + } + } + delete cal; + } +} #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/test/intltest/calregts.h b/icu4c/source/test/intltest/calregts.h index e576b972631..15d55029093 100644 --- a/icu4c/source/test/intltest/calregts.h +++ b/icu4c/source/test/intltest/calregts.h @@ -78,6 +78,7 @@ public: void Test9019(void); void TestT9452(void); void TestT11632(void); + void TestPersianCalOverflow(void); void printdate(GregorianCalendar *cal, const char *string); void dowTest(UBool lenient) ; diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/calendar/CalendarRegressionTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/calendar/CalendarRegressionTest.java index e0ca7f7ffdd..062ac0bd7a4 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/calendar/CalendarRegressionTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/calendar/CalendarRegressionTest.java @@ -2503,5 +2503,23 @@ public class CalendarRegressionTest extends com.ibm.icu.dev.test.TestFmwk { assertEquals("Incorrect time for long range milliseconds","Sun Jan 25 21:00:00 PST 1970", cal.getTime().toString()); } -} + @Test + public void TestPersianCalOverflow() { + String localeID = "bs_Cyrl@calendar=persian"; + Calendar cal = Calendar.getInstance(new ULocale(localeID)); + int maxMonth = cal.getMaximum(Calendar.MONTH); + int maxDayOfMonth = cal.getMaximum(Calendar.DATE); + int jd, month, dayOfMonth; + for (jd = 67023580; jd <= 67023584; jd++) { // year 178171, int32_t overflow if jd >= 67023582 + cal.clear(); + cal.set(Calendar.JULIAN_DAY, jd); + month = cal.get(Calendar.MONTH); + dayOfMonth = cal.get(Calendar.DATE); + if (month > maxMonth || dayOfMonth > maxDayOfMonth) { + errln("Error: localeID " + localeID + ", julianDay " + jd + "; maxMonth " + maxMonth + ", got month " + month + + "; maxDayOfMonth " + maxDayOfMonth + ", got dayOfMonth " + dayOfMonth); + } + } + } + } //eof