@@ -1851,70 +1851,184 @@ term nif_erlang_make_ref_0(Context *ctx, int argc, term argv[])
18511851 return term_from_ref_ticks (ref_ticks , & ctx -> heap );
18521852}
18531853
1854- term nif_erlang_monotonic_time_1 ( Context * ctx , int argc , term argv [] )
1854+ static bool time_unit_to_parts_per_second ( term unit , avm_int64_t * parts_per_second )
18551855{
1856- UNUSED (ctx );
1857-
1858- term unit ;
1859- if (argc == 0 ) {
1860- unit = NATIVE_ATOM ;
1856+ if (unit == SECOND_ATOM ) {
1857+ * parts_per_second = 1 ;
1858+ } else if (unit == MILLISECOND_ATOM ) {
1859+ * parts_per_second = 1000 ;
1860+ } else if (unit == MICROSECOND_ATOM ) {
1861+ * parts_per_second = INT64_C (1000000 );
1862+ } else if (unit == NANOSECOND_ATOM || unit == NATIVE_ATOM ) {
1863+ // AtomVM exposes the Erlang `native` time unit as nanoseconds on all platforms.
1864+ * parts_per_second = INT64_C (1000000000 );
1865+ } else if (term_is_int64 (unit )) {
1866+ * parts_per_second = term_maybe_unbox_int64 (unit );
1867+ if (UNLIKELY (* parts_per_second <= 0 )) {
1868+ return false;
1869+ }
18611870 } else {
1862- unit = argv [ 0 ] ;
1871+ return false ;
18631872 }
18641873
1865- struct timespec ts ;
1866- sys_monotonic_time ( & ts );
1874+ return true ;
1875+ }
18671876
1868- if (unit == SECOND_ATOM ) {
1869- return make_maybe_boxed_int64 (ctx , ts .tv_sec );
1877+ // Convert nanoseconds to parts using: parts = nanoseconds * pps / 1e9
1878+ // Splits into high/low to avoid intermediate overflow.
1879+ static bool nanoseconds_to_parts_per_second (
1880+ avm_int64_t nanoseconds , avm_int64_t parts_per_second , bool round_up , avm_int64_t * parts )
1881+ {
1882+ if (UNLIKELY (
1883+ nanoseconds < 0 || nanoseconds >= INT64_C (1000000000 ) || parts_per_second <= 0 )) {
1884+ return false;
1885+ }
18701886
1871- } else if (unit == MILLISECOND_ATOM ) {
1872- return make_maybe_boxed_int64 (ctx , ((int64_t ) ts .tv_sec ) * 1000UL + ts .tv_nsec / 1000000UL );
1887+ avm_int64_t quotient = parts_per_second / INT64_C (1000000000 );
1888+ avm_int64_t remainder = parts_per_second % INT64_C (1000000000 );
1889+ avm_int64_t fractional_high = nanoseconds * quotient ;
1890+ avm_int64_t remainder_product = nanoseconds * remainder ;
1891+ avm_int64_t fractional_low = remainder_product / INT64_C (1000000000 );
18731892
1874- } else if (unit == MICROSECOND_ATOM ) {
1875- return make_maybe_boxed_int64 (ctx , ((int64_t ) ts .tv_sec ) * 1000000UL + ts .tv_nsec / 1000UL );
1893+ if (round_up && (remainder_product % INT64_C (1000000000 )) != 0 ) {
1894+ fractional_low += 1 ;
1895+ }
18761896
1877- } else if (unit == NANOSECOND_ATOM || unit == NATIVE_ATOM ) {
1878- return make_maybe_boxed_int64 (ctx , ((int64_t ) ts .tv_sec ) * INT64_C (1000000000 ) + ts .tv_nsec );
1897+ if (UNLIKELY (fractional_high > INT64_MAX - fractional_low )) {
1898+ return false;
1899+ }
18791900
1880- } else {
1881- RAISE_ERROR (BADARG_ATOM );
1901+ * parts = fractional_high + fractional_low ;
1902+ return true;
1903+ }
1904+
1905+ // Convert a normalized timespec (0 <= tv_nsec < 1e9) to integer parts.
1906+ // Uses floor semantics for negative timestamps with non-zero tv_nsec.
1907+ static bool timespec_to_parts_per_second (
1908+ const struct timespec * ts , avm_int64_t parts_per_second , avm_int64_t * parts )
1909+ {
1910+ if (UNLIKELY (
1911+ parts_per_second <= 0 || ts -> tv_nsec < 0 || ts -> tv_nsec >= INT64_C (1000000000 ))) {
1912+ return false;
18821913 }
1914+
1915+ avm_int64_t seconds = (avm_int64_t ) ts -> tv_sec ;
1916+ avm_int64_t fractional_part ;
1917+
1918+ if (ts -> tv_nsec == 0 || seconds >= 0 ) {
1919+ if (UNLIKELY (
1920+ ((seconds > 0 ) && (seconds > INT64_MAX / parts_per_second ))
1921+ || ((seconds < 0 ) && (seconds < INT64_MIN / parts_per_second )))) {
1922+ return false;
1923+ }
1924+
1925+ if (UNLIKELY (!nanoseconds_to_parts_per_second (
1926+ (avm_int64_t ) ts -> tv_nsec , parts_per_second , false, & fractional_part ))) {
1927+ return false;
1928+ }
1929+
1930+ avm_int64_t second_part = seconds * parts_per_second ;
1931+ if (UNLIKELY (second_part > INT64_MAX - fractional_part )) {
1932+ return false;
1933+ }
1934+
1935+ * parts = second_part + fractional_part ;
1936+ return true;
1937+ }
1938+
1939+ // Preserve floor semantics for normalized negative timespecs such as {-2, 999999999}.
1940+ avm_int64_t adjusted_seconds = seconds + 1 ;
1941+ if (UNLIKELY (adjusted_seconds < INT64_MIN / parts_per_second )) {
1942+ return false;
1943+ }
1944+
1945+ if (UNLIKELY (!nanoseconds_to_parts_per_second (
1946+ INT64_C (1000000000 ) - (avm_int64_t ) ts -> tv_nsec , parts_per_second , true,
1947+ & fractional_part ))) {
1948+ return false;
1949+ }
1950+
1951+ avm_int64_t second_part = adjusted_seconds * parts_per_second ;
1952+ if (UNLIKELY (second_part < INT64_MIN + fractional_part )) {
1953+ return false;
1954+ }
1955+
1956+ * parts = second_part - fractional_part ;
1957+ return true;
18831958}
18841959
1885- term nif_erlang_system_time_1 (Context * ctx , int argc , term argv [] )
1960+ static term make_time_in_unit (Context * ctx , term unit , void ( * time_fun )( struct timespec * ) )
18861961{
1887- UNUSED (ctx );
1962+ avm_int64_t parts_per_second ;
1963+ if (UNLIKELY (!time_unit_to_parts_per_second (unit , & parts_per_second ))) {
1964+ RAISE_ERROR (BADARG_ATOM );
1965+ }
18881966
1967+ struct timespec ts ;
1968+ time_fun (& ts );
1969+
1970+ avm_int64_t value ;
1971+ if (UNLIKELY (!timespec_to_parts_per_second (& ts , parts_per_second , & value ))) {
1972+ RAISE_ERROR (BADARG_ATOM );
1973+ }
1974+
1975+ return make_maybe_boxed_int64 (ctx , value );
1976+ }
1977+
1978+ term nif_erlang_monotonic_time_1 (Context * ctx , int argc , term argv [])
1979+ {
18891980 term unit ;
18901981 if (argc == 0 ) {
18911982 unit = NATIVE_ATOM ;
18921983 } else {
18931984 unit = argv [0 ];
18941985 }
18951986
1896- struct timespec ts ;
1897- sys_time ( & ts );
1987+ return make_time_in_unit ( ctx , unit , sys_monotonic_time ) ;
1988+ }
18981989
1899- if (unit == SECOND_ATOM ) {
1900- return make_maybe_boxed_int64 (ctx , ts .tv_sec );
1990+ term nif_erlang_system_time_1 (Context * ctx , int argc , term argv [])
1991+ {
1992+ term unit ;
1993+ if (argc == 0 ) {
1994+ unit = NATIVE_ATOM ;
1995+ } else {
1996+ unit = argv [0 ];
1997+ }
19011998
1902- } else if ( unit == MILLISECOND_ATOM ) {
1903- return make_maybe_boxed_int64 ( ctx , (( int64_t ) ts . tv_sec ) * 1000UL + ts . tv_nsec / 1000000UL );
1999+ return make_time_in_unit ( ctx , unit , sys_time );
2000+ }
19042001
1905- } else if (unit == MICROSECOND_ATOM ) {
1906- return make_maybe_boxed_int64 (ctx , ((int64_t ) ts .tv_sec ) * 1000000UL + ts .tv_nsec / 1000UL );
2002+ static bool int64_to_time_t_checked (avm_int64_t seconds , time_t * out )
2003+ {
2004+ if (((time_t ) - 1 ) > (time_t ) 0 ) {
2005+ if (seconds < 0 ) {
2006+ return false;
2007+ }
19072008
1908- } else if (unit == NANOSECOND_ATOM || unit == NATIVE_ATOM ) {
1909- return make_maybe_boxed_int64 (ctx , ((int64_t ) ts .tv_sec ) * INT64_C (1000000000 ) + ts .tv_nsec );
2009+ time_t time_seconds = (time_t ) (uint64_t ) seconds ;
2010+ if ((uint64_t ) time_seconds != (uint64_t ) seconds ) {
2011+ return false;
2012+ }
2013+ * out = time_seconds ;
2014+ return true;
2015+ }
19102016
1911- } else {
1912- RAISE_ERROR (BADARG_ATOM );
2017+ time_t time_seconds = (time_t ) seconds ;
2018+ if ((avm_int64_t ) time_seconds != seconds ) {
2019+ return false;
19132020 }
2021+ * out = time_seconds ;
2022+ return true;
19142023}
19152024
19162025static term build_datetime_from_tm (Context * ctx , struct tm * broken_down_time )
19172026{
2027+ avm_int64_t year = (avm_int64_t ) broken_down_time -> tm_year + 1900 ;
2028+ if (UNLIKELY (year < AVM_INT_MIN || year > AVM_INT_MAX )) {
2029+ RAISE_ERROR (BADARG_ATOM );
2030+ }
2031+
19182032 // 4 = size of date/time tuple, 3 size of date time tuple
19192033 if (UNLIKELY (memory_ensure_free_opt (ctx , 3 + 4 + 4 , MEMORY_CAN_SHRINK ) != MEMORY_GC_OK )) {
19202034 RAISE_ERROR (OUT_OF_MEMORY_ATOM );
@@ -1923,7 +2037,7 @@ static term build_datetime_from_tm(Context *ctx, struct tm *broken_down_time)
19232037 term time_tuple = term_alloc_tuple (3 , & ctx -> heap );
19242038 term date_time_tuple = term_alloc_tuple (2 , & ctx -> heap );
19252039
1926- term_put_tuple_element (date_tuple , 0 , term_from_int11 ( 1900 + broken_down_time -> tm_year ));
2040+ term_put_tuple_element (date_tuple , 0 , term_from_int (( avm_int_t ) year ));
19272041 term_put_tuple_element (date_tuple , 1 , term_from_int11 (broken_down_time -> tm_mon + 1 ));
19282042 term_put_tuple_element (date_tuple , 2 , term_from_int11 (broken_down_time -> tm_mday ));
19292043
@@ -1946,7 +2060,12 @@ term nif_erlang_universaltime_0(Context *ctx, int argc, term argv[])
19462060 sys_time (& ts );
19472061
19482062 struct tm broken_down_time ;
1949- return build_datetime_from_tm (ctx , gmtime_r (& ts .tv_sec , & broken_down_time ));
2063+ struct tm * universal_time = gmtime_r (& ts .tv_sec , & broken_down_time );
2064+ if (UNLIKELY (universal_time == NULL )) {
2065+ RAISE_ERROR (BADARG_ATOM );
2066+ }
2067+
2068+ return build_datetime_from_tm (ctx , universal_time );
19502069}
19512070
19522071// setenv leaks the prior "TZ=value" string on overwrite (unbounded on
@@ -2095,35 +2214,35 @@ term nif_erlang_timestamp_0(Context *ctx, int argc, term argv[])
20952214
20962215term nif_calendar_system_time_to_universal_time_2 (Context * ctx , int argc , term argv [])
20972216{
2098- UNUSED (ctx );
20992217 UNUSED (argc );
21002218
2101- struct timespec ts ;
2102-
2219+ if (UNLIKELY (!term_is_int64 (argv [0 ]))) {
2220+ RAISE_ERROR (BADARG_ATOM );
2221+ }
21032222 avm_int64_t value = term_maybe_unbox_int64 (argv [0 ]);
21042223
2105- if (argv [1 ] == SECOND_ATOM ) {
2106- ts .tv_sec = (time_t ) value ;
2107- ts .tv_nsec = 0 ;
2108-
2109- } else if (argv [1 ] == MILLISECOND_ATOM ) {
2110- ts .tv_sec = (time_t ) (value / 1000 );
2111- ts .tv_nsec = (value % 1000 ) * 1000000 ;
2112-
2113- } else if (argv [1 ] == MICROSECOND_ATOM ) {
2114- ts .tv_sec = (time_t ) (value / 1000000 );
2115- ts .tv_nsec = (value % 1000000 ) * 1000 ;
2224+ avm_int64_t parts_per_second ;
2225+ if (UNLIKELY (!time_unit_to_parts_per_second (argv [1 ], & parts_per_second ))) {
2226+ RAISE_ERROR (BADARG_ATOM );
2227+ }
21162228
2117- } else if (argv [1 ] == NANOSECOND_ATOM || argv [1 ] == NATIVE_ATOM ) {
2118- ts .tv_sec = (time_t ) (value / INT64_C (1000000000 ));
2119- ts .tv_nsec = value % INT64_C (1000000000 );
2229+ // Floor division: round negative fractional seconds toward negative infinity
2230+ avm_int64_t quotient = value / parts_per_second ;
2231+ avm_int64_t remainder = value % parts_per_second ;
2232+ avm_int64_t seconds = quotient - (remainder < 0 );
21202233
2121- } else {
2234+ time_t time_seconds ;
2235+ if (UNLIKELY (!int64_to_time_t_checked (seconds , & time_seconds ))) {
21222236 RAISE_ERROR (BADARG_ATOM );
21232237 }
21242238
21252239 struct tm broken_down_time ;
2126- return build_datetime_from_tm (ctx , gmtime_r (& ts .tv_sec , & broken_down_time ));
2240+ struct tm * universal_time = gmtime_r (& time_seconds , & broken_down_time );
2241+ if (UNLIKELY (universal_time == NULL )) {
2242+ RAISE_ERROR (BADARG_ATOM );
2243+ }
2244+
2245+ return build_datetime_from_tm (ctx , universal_time );
21272246}
21282247
21292248static term nif_os_getenv_1 (Context * ctx , int argc , term argv [])
0 commit comments