Skip to content

Commit

Permalink
ICU-22633 Fix more int overflow issues in calendar
Browse files Browse the repository at this point in the history
  • Loading branch information
FrankYFTang committed Feb 14, 2024
1 parent 939f08f commit b24b251
Show file tree
Hide file tree
Showing 9 changed files with 74 additions and 41 deletions.
19 changes: 16 additions & 3 deletions icu4c/source/i18n/calendar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1461,9 +1461,15 @@ void Calendar::computeFields(UErrorCode &ec)
// 11/6/00

int32_t millisInDay;
int32_t days = ClockMath::floorDivide(localMillis, U_MILLIS_PER_DAY, &millisInDay);
double days = ClockMath::floorDivide(
localMillis, U_MILLIS_PER_DAY, &millisInDay) +
kEpochStartAsJulianDay;
if (days > INT32_MAX || days < INT32_MIN) {
ec = U_ILLEGAL_ARGUMENT_ERROR;
return;
}

internalSet(UCAL_JULIAN_DAY,days + kEpochStartAsJulianDay);
internalSet(UCAL_JULIAN_DAY, static_cast<int32_t>(days));

#if defined (U_DEBUG_CAL)
//fprintf(stderr, "%s:%d- Hmm! Jules @ %d, as per %.0lf millis\n",
Expand Down Expand Up @@ -1579,7 +1585,14 @@ void Calendar::computeGregorianFields(int32_t julianDay, UErrorCode& ec) {
return;
}
int32_t gregorianDayOfWeekUnused;
Grego::dayToFields(julianDay - kEpochStartAsJulianDay, fGregorianYear, fGregorianMonth, fGregorianDayOfMonth, gregorianDayOfWeekUnused, fGregorianDayOfYear);
if (uprv_add32_overflow(
julianDay, -kEpochStartAsJulianDay, &julianDay)) {
ec = U_ILLEGAL_ARGUMENT_ERROR;
return;
}
Grego::dayToFields(julianDay, fGregorianYear, fGregorianMonth,
fGregorianDayOfMonth, gregorianDayOfWeekUnused,
fGregorianDayOfYear);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion icu4c/source/i18n/cecal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ CECalendar::handleComputeMonthStart(int32_t eyear,int32_t emonth, UBool /*useMon
return (
getJDEpochOffset() // difference from Julian epoch to 1,1,1
+ 365LL * year64 // number of days from years
+ ClockMath::floorDivide(year64, 4LL) // extra day of leap year
+ ClockMath::floorDivideInt64(year64, 4LL) // extra day of leap year
+ 30 * emonth // number of days from months (months are 0-based)
- 1 // number of days for present month (1 based)
);
Expand Down
3 changes: 2 additions & 1 deletion icu4c/source/i18n/gregocal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,8 @@ int64_t GregorianCalendar::handleComputeMonthStart(int32_t eyear, int32_t month,

UBool isLeap = eyear%4 == 0;
int64_t y = (int64_t)eyear-1;
int64_t julianDay = 365*y + ClockMath::floorDivide(y, (int64_t)4) + (kJan1_1JulianDay - 3);
int64_t julianDay = 365LL * y +
ClockMath::floorDivideInt64(y, 4LL) + kJan1_1JulianDay - 3LL;

nonConstThis->fIsGregorian = (eyear >= fGregorianCutoverYear);
#if defined (U_DEBUG_CAL)
Expand Down
8 changes: 5 additions & 3 deletions icu4c/source/i18n/gregoimp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ 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) {
int64_t ClockMath::floorDivideInt64(int64_t numerator, int64_t denominator) {
return (numerator >= 0) ?
numerator / denominator : ((numerator + 1) / denominator) - 1;
}
Expand Down Expand Up @@ -108,8 +108,10 @@ int64_t Grego::fieldsToDay(int32_t year, int32_t month, int32_t dom) {

int64_t y = year - 1;

int64_t julian = 365LL * y + ClockMath::floorDivide(y, 4LL) + (JULIAN_1_CE - 3) + // Julian cal
ClockMath::floorDivide(y, 400LL) - ClockMath::floorDivide(y, 100LL) + 2 + // => Gregorian cal
int64_t julian = 365LL * y +
ClockMath::floorDivideInt64(y, 4LL) + (JULIAN_1_CE - 3) + // Julian cal
ClockMath::floorDivideInt64(y, 400LL) -
ClockMath::floorDivideInt64(y, 100LL) + 2 + // => Gregorian cal
DAYS_BEFORE[month + (isLeapYear(year) ? 12 : 0)] + dom; // => month/dom

return julian - JULIAN_1970_CE; // JD => epoch day
Expand Down
8 changes: 4 additions & 4 deletions icu4c/source/i18n/gregoimp.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class ClockMath {
* @param denominator a divisor which must be != 0
* @return the floor of the quotient
*/
static int64_t floorDivide(int64_t numerator, int64_t denominator);
static int64_t floorDivideInt64(int64_t numerator, int64_t denominator);

/**
* Divide two numbers, returning the floor of the quotient.
Expand Down Expand Up @@ -309,7 +309,7 @@ inline void Grego::dayToFields(int32_t day, int32_t& year, int32_t& month,

inline double Grego::julianDayToMillis(int32_t julian)
{
return (julian - kEpochStartAsJulianDay) * kOneDay;
return (static_cast<double>(julian) - kEpochStartAsJulianDay) * kOneDay;
}

inline int32_t Grego::millisToJulianDay(double millis) {
Expand All @@ -318,8 +318,8 @@ inline int32_t Grego::millisToJulianDay(double millis) {

inline int32_t Grego::gregorianShift(int32_t eyear) {
int64_t y = (int64_t)eyear-1;
int32_t gregShift = static_cast<int32_t>(ClockMath::floorDivide(y, (int64_t)400) - ClockMath::floorDivide(y, (int64_t)100) + 2);
return gregShift;
int64_t gregShift = ClockMath::floorDivideInt64(y, 400LL) - ClockMath::floorDivideInt64(y, 100LL) + 2;
return static_cast<int32_t>(gregShift);
}

U_NAMESPACE_END
Expand Down
3 changes: 2 additions & 1 deletion icu4c/source/i18n/hebrwcal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,8 @@ int32_t HebrewCalendar::startOfYear(int32_t year, UErrorCode &status)

if (day == 0) {
// # of months before year
int64_t months = ClockMath::floorDivide((235 * (int64_t)year - 234), (int64_t)19);
int64_t months = ClockMath::floorDivideInt64(
(235LL * (int64_t)year - 234LL), 19LL);

int64_t frac = months * MONTH_FRACT + BAHARAD; // Fractional part of day #
day = months * 29LL + frac / DAY_PARTS; // Whole # part of calculation
Expand Down
34 changes: 15 additions & 19 deletions icu4c/source/i18n/islamcal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ int64_t IslamicCalendar::monthStart(int32_t year, int32_t month) const {
int32_t IslamicCalendar::trueMonthStart(int32_t month) const
{
UErrorCode status = U_ZERO_ERROR;
int32_t start = CalendarCache::get(&gMonthCache, month, status);
int64_t start = CalendarCache::get(&gMonthCache, month, status);

if (start==0) {
// Make a guess at when the month started, using the average length
Expand Down Expand Up @@ -379,8 +379,8 @@ int32_t IslamicCalendar::trueMonthStart(int32_t month) const
}
} while (age < 0);
}
start = (int32_t)(ClockMath::floorDivide(
(int64_t)((int64_t)origin - HIJRA_MILLIS), (int64_t)kOneDay) + 1);
start = ClockMath::floorDivideInt64(
(int64_t)((int64_t)origin - HIJRA_MILLIS), (int64_t)kOneDay) + 1;
CalendarCache::put(&gMonthCache, month, start, status);
}
trueMonthStartEnd :
Expand Down Expand Up @@ -694,9 +694,7 @@ IslamicCivilCalendar* IslamicCivilCalendar::clone() const {
* from the Hijri epoch, origin 0.
*/
int64_t IslamicCivilCalendar::yearStart(int32_t year) const{
return static_cast<int64_t>(
(year-1)*354LL + ClockMath::floorDivide((3+11*static_cast<int64_t>(year)),
static_cast<int64_t>(30)));
return 354LL * (year-1) + ClockMath::floorDivideInt64(3 + 11LL * year, 30LL);
}

/**
Expand All @@ -709,10 +707,9 @@ int64_t IslamicCivilCalendar::yearStart(int32_t year) const{
int64_t IslamicCivilCalendar::monthStart(int32_t year, int32_t month) const {
// This does not handle months out of the range 0..11
return static_cast<int64_t>(
uprv_ceil(29.5*month) + (year-1)*354LL +
static_cast<int32_t>(ClockMath::floorDivide(
3+11*static_cast<int64_t>(year),
static_cast<int64_t>(30))));
uprv_ceil(29.5*month) + 354LL*(year-1) +
ClockMath::floorDivideInt64(
11LL*static_cast<int64_t>(year) + 3LL, 30LL));
}

/**
Expand Down Expand Up @@ -759,9 +756,8 @@ void IslamicCivilCalendar::handleComputeFields(int32_t julianDay, UErrorCode &st
int32_t days = julianDay - getEpoc();

// Use the civil calendar approximation, which is just arithmetic
int32_t year = static_cast<int32_t>(
ClockMath::floorDivide(30 * static_cast<int64_t>(days) + 10646,
static_cast<int64_t>(10631)));
int64_t year =
ClockMath::floorDivideInt64(30LL * days + 10646LL, 10631LL);
int32_t month = static_cast<int32_t>(
uprv_ceil((days - 29 - yearStart(year)) / 29.5 ));
month = month<11?month:11;
Expand Down Expand Up @@ -837,9 +833,8 @@ IslamicUmalquraCalendar* IslamicUmalquraCalendar::clone() const {
*/
int64_t IslamicUmalquraCalendar::yearStart(int32_t year) const {
if (year < UMALQURA_YEAR_START || year > UMALQURA_YEAR_END) {
return static_cast<int64_t>(
(year-1)*354LL + ClockMath::floorDivide((3+11*static_cast<int64_t>(year)),
static_cast<int64_t>(30)));
return 354LL * (year-1) +
ClockMath::floorDivideInt64((11LL*year+3LL), 30LL);
}
year -= UMALQURA_YEAR_START;
// rounded least-squares fit of the dates previously calculated from UMALQURA_MONTHLENGTH iteration
Expand Down Expand Up @@ -915,15 +910,16 @@ int32_t IslamicUmalquraCalendar::handleGetYearLength(int32_t extendedYear) const
*/
void IslamicUmalquraCalendar::handleComputeFields(int32_t julianDay, UErrorCode &status) {
if (U_FAILURE(status)) return;
int32_t year, month, dayOfYear;
int64_t year;
int32_t month, dayOfYear;
int64_t dayOfMonth;
int32_t days = julianDay - getEpoc();

int64_t umalquraStartdays = yearStart(UMALQURA_YEAR_START) ;
if (days < umalquraStartdays) {
//Use Civil calculation
year = (int32_t)ClockMath::floorDivide(
(30 * (int64_t)days + 10646) , (int64_t)10631.0 );
year = ClockMath::floorDivideInt64(
(30LL * days + 10646LL) , 10631LL );
month = (int32_t)uprv_ceil((days - 29 - yearStart(year)) / 29.5 );
month = month < 11 ? month : 11;
} else {
Expand Down
32 changes: 23 additions & 9 deletions icu4c/source/i18n/persncal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

#if !UCONFIG_NO_FORMATTING

#include "uassert.h"
#include "umutex.h"
#include "gregoimp.h" // Math
#include <float.h>
Expand Down Expand Up @@ -173,7 +174,7 @@ int64_t PersianCalendar::handleComputeMonthStart(int32_t eyear, int32_t month, U
eyear += ClockMath::floorDivide(month, 12, &month);
}

int64_t julianDay = PERSIAN_EPOCH - 1 + 365LL * (eyear - 1) + ClockMath::floorDivide(8 * eyear + 21, 33);
int64_t julianDay = PERSIAN_EPOCH - 1 + 365LL * (eyear - 1) + ClockMath::floorDivide(8LL * eyear + 21, 33);

if (month != 0) {
julianDay += kPersianNumDays[month];
Expand Down Expand Up @@ -210,20 +211,33 @@ int32_t PersianCalendar::handleGetExtendedYear() {
* The DAY_OF_WEEK and DOW_LOCAL fields are already set when this
* method is called.
*/
void PersianCalendar::handleComputeFields(int32_t julianDay, UErrorCode &/*status*/) {
int32_t year, month, dayOfMonth, dayOfYear;

int32_t daysSinceEpoch = julianDay - PERSIAN_EPOCH;
year = 1 + (int32_t)ClockMath::floorDivide(33 * (int64_t)daysSinceEpoch + 3, (int64_t)12053);
void PersianCalendar::handleComputeFields(int32_t julianDay, UErrorCode& status) {
int64_t daysSinceEpoch = julianDay - PERSIAN_EPOCH;
int64_t year = ClockMath::floorDivideInt64(
33LL * daysSinceEpoch + 3LL, 12053LL) + 1LL;
if (year > INT32_MAX || year < INT32_MIN) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return;
}

int32_t farvardin1 = 365 * (year - 1) + ClockMath::floorDivide(8 * year + 21, 33);
dayOfYear = (daysSinceEpoch - farvardin1); // 0-based
int64_t farvardin1 = 365LL * (year - 1) + ClockMath::floorDivide(8LL * year + 21, 33);
int32_t dayOfYear = daysSinceEpoch - farvardin1; // 0-based
U_ASSERT(dayOfYear >= 0);
U_ASSERT(dayOfYear < 366);
//
int32_t month;
if (dayOfYear < 216) { // Compute 0-based month
month = dayOfYear / 31;
} else {
month = (dayOfYear - 6) / 30;
}
dayOfMonth = dayOfYear - kPersianNumDays[month] + 1;
U_ASSERT(month >= 0);
U_ASSERT(month < 12);

int32_t dayOfMonth = dayOfYear - kPersianNumDays[month] + 1;
U_ASSERT(dayOfMonth > 0);
U_ASSERT(dayOfMonth <= 31);

++dayOfYear; // Make it 1-based now

internalSet(UCAL_ERA, 0);
Expand Down
6 changes: 6 additions & 0 deletions icu4c/source/test/intltest/caltest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5670,6 +5670,12 @@ void CalendarTest::Test22633PersianOverflow() {
U_ASSERT(U_SUCCESS(status));
cal->add(UCAL_ORDINAL_MONTH, 1594095615, status);
assertTrue("Should return success", U_SUCCESS(status));

cal->clear();
cal->fieldDifference(
-874417153152678003697180890506448687181167523704194267774844295805672585701302166100950793070884718009504322601688549650298776623158701367393457997817732662883592665106020013730689242515513560464852918376875667091108609655859551000798163265126400.000000,
UCAL_YEAR, status);
assertFalse("Should not return success", U_SUCCESS(status));
}

void CalendarTest::TestChineseCalendarComputeMonthStart() { // ICU-22639
Expand Down

0 comments on commit b24b251

Please sign in to comment.