* localtime.c (leaps_thru_end_of_nonneg, leaps_thru_end_of): Args and result are now of type time_t not int, so as to not mishandle arguments that do not fit in int. All callers changed to not pass negative arguments unless time_t is signed. (timesub): Prefer int_fast32_t to int_fast64_t when either will do. Use int_fast32_t instead of int when values might not fit into 16-bit range (the C standard still allows 16-bit ints). Use time_t instead of int when values might not fit into even 64-bit int (at least in theory, time_t could unsigned and wider than 64 bits), taking care to avoid negative values when time_t is unsigned. Fix glitches in range testing by simply testing whether the year is in range before assigning it to tm_year. Avoid potential overflow in subtraction, by computing with TM_WDAY_BASE and tm_year rather than EPOCH_WDAY and (y - EPOCH_YEAR). * private.h (TM_WDAY_BASE): New macro. --- localtime.c | 111 ++++++++++++++++++++++++---------------------------- private.h | 1 + 2 files changed, 52 insertions(+), 60 deletions(-) diff --git a/localtime.c b/localtime.c index f37280e..b0a03bb 100644 --- a/localtime.c +++ b/localtime.c @@ -1655,14 +1655,14 @@ offtime(const time_t *timep, long offset) ** where, to make the math easy, the answer for year zero is defined as zero. */ -static int -leaps_thru_end_of_nonneg(int y) +static time_t +leaps_thru_end_of_nonneg(time_t y) { return y / 4 - y / 100 + y / 400; } -static int -leaps_thru_end_of(register const int y) +static time_t +leaps_thru_end_of(time_t y) { return (y < 0 ? -1 - leaps_thru_end_of_nonneg(-1 - y) @@ -1675,13 +1675,12 @@ timesub(const time_t *timep, int_fast32_t offset, { register const struct lsinfo * lp; register time_t tdays; - register int idays; /* unsigned would be so 2003 */ - register int_fast64_t rem; - int y; register const int * ip; register int_fast32_t corr; register bool hit; register int i; + int_fast32_t idays, rem, dayoff, dayrem; + time_t y; corr = 0; hit = false; @@ -1695,67 +1694,63 @@ timesub(const time_t *timep, int_fast32_t offset, break; } } - y = EPOCH_YEAR; + + /* Calculate the year, avoiding integer overflow even if + time_t is unsigned. */ tdays = *timep / SECSPERDAY; rem = *timep % SECSPERDAY; - while (tdays < 0 || tdays >= year_lengths[isleap(y)]) { - int newy; - register time_t tdelta; - register int idelta; + rem += offset % SECSPERDAY - corr % SECSPERDAY + 3 * SECSPERDAY; + dayoff = offset / SECSPERDAY - corr / SECSPERDAY + rem / SECSPERDAY - 3; + rem %= SECSPERDAY; + /* y = (EPOCH_YEAR + + floor((tdays + dayoff) / DAYSPERREPEAT) * YEARSPERREPEAT), + sans overflow. But calculate against 1570 (EPOCH_YEAR - + YEARSPERREPEAT) instead of against 1970 so that things work + for localtime values before 1970 when time_t is unsigned. */ + dayrem = tdays % DAYSPERREPEAT; + dayrem += dayoff % DAYSPERREPEAT; + y = (EPOCH_YEAR - YEARSPERREPEAT + + ((1 + dayoff / DAYSPERREPEAT + dayrem / DAYSPERREPEAT + - ((dayrem % DAYSPERREPEAT) < 0) + + tdays / DAYSPERREPEAT) + * YEARSPERREPEAT)); + /* idays = (tdays + dayoff) mod DAYSPERREPEAT, sans overflow. */ + idays = tdays % DAYSPERREPEAT; + idays += dayoff % DAYSPERREPEAT + 2 * DAYSPERREPEAT; + idays %= DAYSPERREPEAT; + /* Increase Y and decrease IDAYS until IDAYS is in range for Y. */ + while (year_lengths[isleap(y)] <= idays) { + int tdelta = idays / DAYSPERLYEAR; + int_fast32_t ydelta = tdelta + !tdelta; + time_t newy = y + ydelta; register int leapdays; - - tdelta = tdays / DAYSPERLYEAR; - if (! ((! TYPE_SIGNED(time_t) || INT_MIN <= tdelta) - && tdelta <= INT_MAX)) - goto out_of_range; - idelta = tdelta; - if (idelta == 0) - idelta = (tdays < 0) ? -1 : 1; - newy = y; - if (increment_overflow(&newy, idelta)) - goto out_of_range; leapdays = leaps_thru_end_of(newy - 1) - leaps_thru_end_of(y - 1); - tdays -= ((time_t) newy - y) * DAYSPERNYEAR; - tdays -= leapdays; + idays -= ydelta * DAYSPERNYEAR; + idays -= leapdays; y = newy; } - /* - ** Given the range, we can now fearlessly cast... - */ - idays = tdays; - rem += offset - corr; - while (rem < 0) { - rem += SECSPERDAY; - --idays; - } - while (rem >= SECSPERDAY) { - rem -= SECSPERDAY; - ++idays; - } - while (idays < 0) { - if (increment_overflow(&y, -1)) - goto out_of_range; - idays += year_lengths[isleap(y)]; - } - while (idays >= year_lengths[isleap(y)]) { - idays -= year_lengths[isleap(y)]; - if (increment_overflow(&y, 1)) - goto out_of_range; + + if (!TYPE_SIGNED(time_t) && y < TM_YEAR_BASE) { + int signed_y = y; + tmp->tm_year = signed_y - TM_YEAR_BASE; + } else if ((!TYPE_SIGNED(time_t) || INT_MIN + TM_YEAR_BASE <= y) + && y - TM_YEAR_BASE <= INT_MAX) + tmp->tm_year = y - TM_YEAR_BASE; + else { + errno = EOVERFLOW; + return NULL; } - tmp->tm_year = y; - if (increment_overflow(&tmp->tm_year, -TM_YEAR_BASE)) - goto out_of_range; tmp->tm_yday = idays; /* ** The "extra" mods below avoid overflow problems. */ - tmp->tm_wday = EPOCH_WDAY + - ((y - EPOCH_YEAR) % DAYSPERWEEK) * - (DAYSPERNYEAR % DAYSPERWEEK) + - leaps_thru_end_of(y - 1) - - leaps_thru_end_of(EPOCH_YEAR - 1) + - idays; + tmp->tm_wday = (TM_WDAY_BASE + + ((tmp->tm_year % DAYSPERWEEK) + * (DAYSPERNYEAR % DAYSPERWEEK)) + + leaps_thru_end_of(y - 1) + - leaps_thru_end_of(TM_YEAR_BASE - 1) + + idays); tmp->tm_wday %= DAYSPERWEEK; if (tmp->tm_wday < 0) tmp->tm_wday += DAYSPERWEEK; @@ -1776,10 +1771,6 @@ timesub(const time_t *timep, int_fast32_t offset, tmp->TM_GMTOFF = offset; #endif /* defined TM_GMTOFF */ return tmp; - - out_of_range: - errno = EOVERFLOW; - return NULL; } char * diff --git a/private.h b/private.h index e38a4f3..7e587e4 100644 --- a/private.h +++ b/private.h @@ -748,6 +748,7 @@ char *ctime_r(time_t const *, char *); #define TM_DECEMBER 11 #define TM_YEAR_BASE 1900 +#define TM_WDAY_BASE TM_MONDAY #define EPOCH_YEAR 1970 #define EPOCH_WDAY TM_THURSDAY -- 2.27.0