-
-
Notifications
You must be signed in to change notification settings - Fork 18.8k
Remove undefined behavior from npy_datetimestruct_to_datetime #55151
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 38 commits
Commits
Show all changes
46 commits
Select commit
Hold shift + click to select a range
e27a0da
refactor npy_datetimestruct_to_datetime
WillAyd 0dea606
Used builtin overflow directives
WillAyd d6a24f3
macro fixups
WillAyd 21e919c
more macro cleanups
WillAyd 6302f2f
more macro refactor
WillAyd e2646a6
musllinux c support
WillAyd 5852d20
Merge remote-tracking branch 'upstream/main' into refactor-np-datetime
WillAyd f55a58a
macro cleanup
WillAyd 057e74f
more refactor
WillAyd 2d6d5fc
fix cython warning
WillAyd e65e229
windows fix
WillAyd af29e7c
Raise Outofboundsdatetime
WillAyd b69b489
Merge remote-tracking branch 'upstream/main' into refactor-np-datetime
WillAyd f9e5e35
cleanup GIL warnings
WillAyd 1b89dfe
more error handling cleanup
WillAyd 73a1507
updates
WillAyd ef24509
error message update
WillAyd 2240b09
error fixups
WillAyd 1640002
test fixup
WillAyd 9cdb9c9
clang-format
WillAyd 9e0cec0
Merge branch 'main' into refactor-np-datetime
WillAyd 25f3edf
Merge branch 'main' into refactor-np-datetime
WillAyd 5c09a13
Merge branch 'main' into refactor-np-datetime
WillAyd 4d8696c
updates
WillAyd b3d5b7c
fixed error message
WillAyd 2f60947
try nogil
WillAyd f26c924
Merge remote-tracking branch 'upstream/main' into refactor-np-datetime
WillAyd 03315fc
revert offsets changes
WillAyd b3a80b4
simplified error handling
WillAyd 038db5f
period fixup
WillAyd df2a4df
fixed test failure
WillAyd 1afb12d
try speedup
WillAyd e293642
updated benchmark
WillAyd 2203421
Merge remote-tracking branch 'upstream/main' into refactor-np-datetime
WillAyd 7ee90dd
revert noexcepts
WillAyd 81f32a9
Merge remote-tracking branch 'upstream/main' into refactor-np-datetime
WillAyd 8d918dc
Merge branch 'main' into refactor-np-datetime
WillAyd 7e8571f
Merge remote-tracking branch 'upstream/main' into refactor-np-datetime
WillAyd 07e8106
Merge branch 'main' into refactor-np-datetime
WillAyd eab64ac
shared function for dts formatting
WillAyd 7fbd191
import -> cimport
WillAyd 4eba919
pass address
WillAyd 9e1914d
typo
WillAyd 188eb10
Merge branch 'main' into refactor-np-datetime
WillAyd 162c858
remove comment
WillAyd de90a2f
cdef object -> str
WillAyd File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,6 +29,58 @@ This file is derived from NumPy 1.7. See NUMPY_LICENSE.txt | |
#include <numpy/arrayscalars.h> | ||
#include <numpy/ndarraytypes.h> | ||
|
||
#if defined(_WIN32) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FYI C23 has the |
||
#ifndef ENABLE_INTSAFE_SIGNED_FUNCTIONS | ||
#define ENABLE_INTSAFE_SIGNED_FUNCTIONS | ||
#endif | ||
#include <intsafe.h> | ||
#define checked_int64_add(a, b, res) LongLongAdd(a, b, res) | ||
#define checked_int64_sub(a, b, res) LongLongSub(a, b, res) | ||
#define checked_int64_mul(a, b, res) LongLongMult(a, b, res) | ||
#else | ||
#if defined __has_builtin | ||
#if __has_builtin(__builtin_add_overflow) | ||
#if _LP64 || __LP64__ || _ILP64 || __ILP64__ | ||
#define checked_int64_add(a, b, res) __builtin_saddl_overflow(a, b, res) | ||
#define checked_int64_sub(a, b, res) __builtin_ssubl_overflow(a, b, res) | ||
#define checked_int64_mul(a, b, res) __builtin_smull_overflow(a, b, res) | ||
#else | ||
#define checked_int64_add(a, b, res) __builtin_saddll_overflow(a, b, res) | ||
#define checked_int64_sub(a, b, res) __builtin_ssubll_overflow(a, b, res) | ||
#define checked_int64_mul(a, b, res) __builtin_smulll_overflow(a, b, res) | ||
#endif | ||
#else | ||
_Static_assert(0, | ||
"Overflow checking not detected; please try a newer compiler"); | ||
#endif | ||
// __has_builtin was added in gcc 10, but our muslinux_1_1 build environment | ||
// only has gcc-9.3, so fall back to __GNUC__ macro as long as we have that | ||
#elif __GNUC__ > 7 | ||
#if _LP64 || __LP64__ || _ILP64 || __ILP64__ | ||
#define checked_int64_add(a, b, res) __builtin_saddl_overflow(a, b, res) | ||
#define checked_int64_sub(a, b, res) __builtin_ssubl_overflow(a, b, res) | ||
#define checked_int64_mul(a, b, res) __builtin_smull_overflow(a, b, res) | ||
#else | ||
#define checked_int64_add(a, b, res) __builtin_saddll_overflow(a, b, res) | ||
#define checked_int64_sub(a, b, res) __builtin_ssubll_overflow(a, b, res) | ||
#define checked_int64_mul(a, b, res) __builtin_smulll_overflow(a, b, res) | ||
#endif | ||
#else | ||
_Static_assert(0, "__has_builtin not detected; please try a newer compiler"); | ||
#endif | ||
#endif | ||
|
||
#define PD_CHECK_OVERFLOW(FUNC) \ | ||
do { \ | ||
if ((FUNC) != 0) { \ | ||
PyGILState_STATE gstate = PyGILState_Ensure(); \ | ||
PyErr_SetString(PyExc_OverflowError, \ | ||
"Overflow occurred in npy_datetimestruct_to_datetime"); \ | ||
PyGILState_Release(gstate); \ | ||
return -1; \ | ||
} \ | ||
} while (0) | ||
|
||
const int days_per_month_table[2][12] = { | ||
{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, | ||
{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}}; | ||
|
@@ -299,96 +351,189 @@ PyObject *extract_utc_offset(PyObject *obj) { | |
return tmp; | ||
} | ||
|
||
static inline int scaleYearToEpoch(int64_t year, int64_t *result) { | ||
return checked_int64_sub(year, 1970, result); | ||
} | ||
|
||
static inline int scaleYearsToMonths(int64_t years, int64_t *result) { | ||
return checked_int64_mul(years, 12, result); | ||
} | ||
|
||
static inline int scaleDaysToWeeks(int64_t days, int64_t *result) { | ||
if (days >= 0) { | ||
*result = days / 7; | ||
return 0; | ||
} else { | ||
int res; | ||
int64_t checked_days; | ||
if ((res = checked_int64_sub(days, 6, &checked_days))) { | ||
return res; | ||
} | ||
|
||
*result = checked_days / 7; | ||
return 0; | ||
} | ||
} | ||
|
||
static inline int scaleDaysToHours(int64_t days, int64_t *result) { | ||
return checked_int64_mul(days, 24, result); | ||
} | ||
|
||
static inline int scaleHoursToMinutes(int64_t hours, int64_t *result) { | ||
return checked_int64_mul(hours, 60, result); | ||
} | ||
|
||
static inline int scaleMinutesToSeconds(int64_t minutes, int64_t *result) { | ||
return checked_int64_mul(minutes, 60, result); | ||
} | ||
|
||
static inline int scaleSecondsToMilliseconds(int64_t seconds, int64_t *result) { | ||
return checked_int64_mul(seconds, 1000, result); | ||
} | ||
|
||
static inline int scaleSecondsToMicroseconds(int64_t seconds, int64_t *result) { | ||
return checked_int64_mul(seconds, 1000000, result); | ||
} | ||
|
||
static inline int scaleMicrosecondsToNanoseconds(int64_t microseconds, | ||
int64_t *result) { | ||
return checked_int64_mul(microseconds, 1000, result); | ||
} | ||
|
||
static inline int scaleMicrosecondsToPicoseconds(int64_t microseconds, | ||
int64_t *result) { | ||
return checked_int64_mul(microseconds, 1000000, result); | ||
} | ||
|
||
static inline int64_t scalePicosecondsToFemtoseconds(int64_t picoseconds, | ||
int64_t *result) { | ||
return checked_int64_mul(picoseconds, 1000, result); | ||
} | ||
|
||
static inline int64_t scalePicosecondsToAttoseconds(int64_t picoseconds, | ||
int64_t *result) { | ||
return checked_int64_mul(picoseconds, 1000000, result); | ||
} | ||
|
||
/* | ||
* Converts a datetime from a datetimestruct to a datetime based | ||
* on a metadata unit. The date is assumed to be valid. | ||
* on a metadata unit. Returns -1 on and sets PyErr on error. | ||
*/ | ||
npy_datetime npy_datetimestruct_to_datetime(NPY_DATETIMEUNIT base, | ||
const npy_datetimestruct *dts) { | ||
npy_datetime ret; | ||
|
||
if (base == NPY_FR_Y) { | ||
/* Truncate to the year */ | ||
ret = dts->year - 1970; | ||
} else if (base == NPY_FR_M) { | ||
/* Truncate to the month */ | ||
ret = 12 * (dts->year - 1970) + (dts->month - 1); | ||
} else { | ||
/* Otherwise calculate the number of days to start */ | ||
npy_int64 days = get_datetimestruct_days(dts); | ||
|
||
switch (base) { | ||
case NPY_FR_W: | ||
/* Truncate to weeks */ | ||
if (days >= 0) { | ||
ret = days / 7; | ||
} else { | ||
ret = (days - 6) / 7; | ||
} | ||
break; | ||
case NPY_FR_D: | ||
ret = days; | ||
break; | ||
case NPY_FR_h: | ||
ret = days * 24 + dts->hour; | ||
break; | ||
case NPY_FR_m: | ||
ret = (days * 24 + dts->hour) * 60 + dts->min; | ||
break; | ||
case NPY_FR_s: | ||
ret = ((days * 24 + dts->hour) * 60 + dts->min) * 60 + dts->sec; | ||
break; | ||
case NPY_FR_ms: | ||
ret = (((days * 24 + dts->hour) * 60 + dts->min) * 60 + dts->sec) * 1000 + | ||
dts->us / 1000; | ||
break; | ||
case NPY_FR_us: | ||
ret = (((days * 24 + dts->hour) * 60 + dts->min) * 60 + dts->sec) * | ||
1000000 + | ||
dts->us; | ||
break; | ||
case NPY_FR_ns: | ||
ret = ((((days * 24 + dts->hour) * 60 + dts->min) * 60 + dts->sec) * | ||
1000000 + | ||
dts->us) * | ||
1000 + | ||
dts->ps / 1000; | ||
break; | ||
case NPY_FR_ps: | ||
ret = ((((days * 24 + dts->hour) * 60 + dts->min) * 60 + dts->sec) * | ||
1000000 + | ||
dts->us) * | ||
1000000 + | ||
dts->ps; | ||
break; | ||
case NPY_FR_fs: | ||
/* only 2.6 hours */ | ||
ret = (((((days * 24 + dts->hour) * 60 + dts->min) * 60 + dts->sec) * | ||
1000000 + | ||
dts->us) * | ||
1000000 + | ||
dts->ps) * | ||
1000 + | ||
dts->as / 1000; | ||
break; | ||
case NPY_FR_as: | ||
/* only 9.2 secs */ | ||
ret = (((((days * 24 + dts->hour) * 60 + dts->min) * 60 + dts->sec) * | ||
1000000 + | ||
dts->us) * | ||
1000000 + | ||
dts->ps) * | ||
1000000 + | ||
dts->as; | ||
break; | ||
default: | ||
/* Something got corrupted */ | ||
PyErr_SetString(PyExc_ValueError, | ||
"NumPy datetime metadata with corrupt unit value"); | ||
return -1; | ||
} | ||
} | ||
return ret; | ||
if ((base == NPY_FR_Y) || (base == NPY_FR_M)) { | ||
int64_t years; | ||
PD_CHECK_OVERFLOW(scaleYearToEpoch(dts->year, &years)); | ||
|
||
if (base == NPY_FR_Y) { | ||
return years; | ||
} | ||
|
||
int64_t months; | ||
PD_CHECK_OVERFLOW(scaleYearsToMonths(years, &months)); | ||
|
||
int64_t months_adder; | ||
PD_CHECK_OVERFLOW(checked_int64_sub(dts->month, 1, &months_adder)); | ||
PD_CHECK_OVERFLOW(checked_int64_add(months, months_adder, &months)); | ||
|
||
if (base == NPY_FR_M) { | ||
return months; | ||
} | ||
} | ||
|
||
const int64_t days = get_datetimestruct_days(dts); | ||
if (base == NPY_FR_D) { | ||
return days; | ||
} | ||
|
||
if (base == NPY_FR_W) { | ||
int64_t weeks; | ||
PD_CHECK_OVERFLOW(scaleDaysToWeeks(days, &weeks)); | ||
return weeks; | ||
} | ||
|
||
int64_t hours; | ||
PD_CHECK_OVERFLOW(scaleDaysToHours(days, &hours)); | ||
PD_CHECK_OVERFLOW(checked_int64_add(hours, dts->hour, &hours)); | ||
|
||
if (base == NPY_FR_h) { | ||
return hours; | ||
} | ||
|
||
int64_t minutes; | ||
PD_CHECK_OVERFLOW(scaleHoursToMinutes(hours, &minutes)); | ||
PD_CHECK_OVERFLOW(checked_int64_add(minutes, dts->min, &minutes)); | ||
|
||
if (base == NPY_FR_m) { | ||
return minutes; | ||
} | ||
|
||
int64_t seconds; | ||
PD_CHECK_OVERFLOW(scaleMinutesToSeconds(minutes, &seconds)); | ||
PD_CHECK_OVERFLOW(checked_int64_add(seconds, dts->sec, &seconds)); | ||
|
||
if (base == NPY_FR_s) { | ||
return seconds; | ||
} | ||
|
||
if (base == NPY_FR_ms) { | ||
int64_t milliseconds; | ||
PD_CHECK_OVERFLOW(scaleSecondsToMilliseconds(seconds, &milliseconds)); | ||
PD_CHECK_OVERFLOW( | ||
checked_int64_add(milliseconds, dts->us / 1000, &milliseconds)); | ||
|
||
return milliseconds; | ||
} | ||
|
||
int64_t microseconds; | ||
PD_CHECK_OVERFLOW(scaleSecondsToMicroseconds(seconds, µseconds)); | ||
PD_CHECK_OVERFLOW(checked_int64_add(microseconds, dts->us, µseconds)); | ||
|
||
if (base == NPY_FR_us) { | ||
return microseconds; | ||
} | ||
|
||
if (base == NPY_FR_ns) { | ||
int64_t nanoseconds; | ||
PD_CHECK_OVERFLOW( | ||
scaleMicrosecondsToNanoseconds(microseconds, &nanoseconds)); | ||
PD_CHECK_OVERFLOW( | ||
checked_int64_add(nanoseconds, dts->ps / 1000, &nanoseconds)); | ||
|
||
return nanoseconds; | ||
} | ||
|
||
int64_t picoseconds; | ||
PD_CHECK_OVERFLOW(scaleMicrosecondsToPicoseconds(microseconds, &picoseconds)); | ||
PD_CHECK_OVERFLOW(checked_int64_add(picoseconds, dts->ps, &picoseconds)); | ||
|
||
if (base == NPY_FR_ps) { | ||
return picoseconds; | ||
} | ||
|
||
if (base == NPY_FR_fs) { | ||
int64_t femtoseconds; | ||
PD_CHECK_OVERFLOW( | ||
scalePicosecondsToFemtoseconds(picoseconds, &femtoseconds)); | ||
PD_CHECK_OVERFLOW( | ||
checked_int64_add(femtoseconds, dts->as / 1000, &femtoseconds)); | ||
return femtoseconds; | ||
} | ||
|
||
if (base == NPY_FR_as) { | ||
int64_t attoseconds; | ||
PD_CHECK_OVERFLOW(scalePicosecondsToAttoseconds(picoseconds, &attoseconds)); | ||
PD_CHECK_OVERFLOW(checked_int64_add(attoseconds, dts->as, &attoseconds)); | ||
return attoseconds; | ||
} | ||
|
||
/* Something got corrupted */ | ||
PyGILState_STATE gstate = PyGILState_Ensure(); | ||
PyErr_SetString(PyExc_ValueError, | ||
"NumPy datetime metadata with corrupt unit value"); | ||
PyGILState_Release(gstate); | ||
|
||
return -1; | ||
} | ||
|
||
/* | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.