diff --git a/src/tools/miri/Cargo.lock b/src/tools/miri/Cargo.lock index 4fb479e1c543b..1e6b5502b0457 100644 --- a/src/tools/miri/Cargo.lock +++ b/src/tools/miri/Cargo.lock @@ -37,6 +37,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "annotate-snippets" version = "0.9.2" @@ -106,6 +121,12 @@ dependencies = [ "serde", ] +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + [[package]] name = "camino" version = "1.1.6" @@ -150,6 +171,18 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "windows-targets 0.52.3", +] + [[package]] name = "cipher" version = "0.4.4" @@ -216,6 +249,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + [[package]] name = "cpufeatures" version = "0.2.12" @@ -319,6 +358,29 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "indenter" version = "0.3.3" @@ -372,6 +434,15 @@ dependencies = [ "libc", ] +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -484,6 +555,7 @@ name = "miri" version = "0.1.0" dependencies = [ "aes", + "chrono", "colored", "ctrlc", "getrandom", @@ -512,6 +584,15 @@ dependencies = [ "libc", ] +[[package]] +name = "num-traits" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", +] + [[package]] name = "number_prefix" version = "0.4.0" @@ -964,6 +1045,60 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + [[package]] name = "winapi" version = "0.3.9" @@ -986,6 +1121,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.3", +] + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/src/tools/miri/Cargo.toml b/src/tools/miri/Cargo.toml index 9d24d3c6f4779..7748d630b12be 100644 --- a/src/tools/miri/Cargo.toml +++ b/src/tools/miri/Cargo.toml @@ -24,6 +24,7 @@ smallvec = "1.7" aes = { version = "0.8.3", features = ["hazmat"] } measureme = "11" ctrlc = "3.2.5" +chrono = { version = "0.4.38", default-features = false, features = ["clock"] } # Copied from `compiler/rustc/Cargo.toml`. # But only for some targets, it fails for others. Rustc configures this in its CI, but we can't diff --git a/src/tools/miri/src/shims/time.rs b/src/tools/miri/src/shims/time.rs index 1126c900226df..dfdf58470d63a 100644 --- a/src/tools/miri/src/shims/time.rs +++ b/src/tools/miri/src/shims/time.rs @@ -1,5 +1,9 @@ +use std::ffi::OsString; +use std::fmt::Write; use std::time::{Duration, SystemTime}; +use chrono::{DateTime, Datelike, Local, Timelike, Utc}; + use crate::concurrency::thread::MachineCallback; use crate::*; @@ -107,6 +111,80 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { Ok(0) } + // The localtime() function shall convert the time in seconds since the Epoch pointed to by + // timer into a broken-down time, expressed as a local time. + // https://linux.die.net/man/3/localtime_r + fn localtime_r( + &mut self, + timep: &OpTy<'tcx, Provenance>, + result_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, Pointer>> { + let this = self.eval_context_mut(); + + this.assert_target_os_is_unix("localtime_r"); + this.check_no_isolation("`localtime_r`")?; + + let timep = this.deref_pointer(timep)?; + let result = this.deref_pointer_as(result_op, this.libc_ty_layout("tm"))?; + + // The input "represents the number of seconds elapsed since the Epoch, + // 1970-01-01 00:00:00 +0000 (UTC)". + let sec_since_epoch: i64 = this + .read_scalar(&timep)? + .to_int(this.libc_ty_layout("time_t").size)? + .try_into() + .unwrap(); + let dt_utc: DateTime = + DateTime::from_timestamp(sec_since_epoch, 0).expect("Invalid timestamp"); + // Convert that to local time, then return the broken-down time value. + let dt: DateTime = DateTime::from(dt_utc); + + // This value is always set to -1, because there is no way to know if dst is in effect with + // chrono crate yet. + // This may not be consistent with libc::localtime_r's result. + let tm_isdst = -1; + + // tm_zone represents the timezone value in the form of: +0730, +08, -0730 or -08. + // This may not be consistent with libc::localtime_r's result. + let offset_in_second = Local::now().offset().local_minus_utc(); + let tm_gmtoff = offset_in_second; + let mut tm_zone = String::new(); + if offset_in_second < 0 { + tm_zone.push('-'); + } else { + tm_zone.push('+'); + } + let offset_hour = offset_in_second.abs() / 3600; + write!(tm_zone, "{:02}", offset_hour).unwrap(); + let offset_min = (offset_in_second.abs() % 3600) / 60; + if offset_min != 0 { + write!(tm_zone, "{:02}", offset_min).unwrap(); + } + + // FIXME: String de-duplication is needed so that we only allocate this string only once + // even when there are multiple calls to this function. + let tm_zone_ptr = + this.alloc_os_str_as_c_str(&OsString::from(tm_zone), MiriMemoryKind::Machine.into())?; + + this.write_pointer(tm_zone_ptr, &this.project_field_named(&result, "tm_zone")?)?; + this.write_int_fields_named( + &[ + ("tm_sec", dt.second().into()), + ("tm_min", dt.minute().into()), + ("tm_hour", dt.hour().into()), + ("tm_mday", dt.day().into()), + ("tm_mon", dt.month0().into()), + ("tm_year", dt.year().checked_sub(1900).unwrap().into()), + ("tm_wday", dt.weekday().num_days_from_sunday().into()), + ("tm_yday", dt.ordinal0().into()), + ("tm_isdst", tm_isdst), + ("tm_gmtoff", tm_gmtoff.into()), + ], + &result, + )?; + + Ok(result.ptr()) + } #[allow(non_snake_case, clippy::arithmetic_side_effects)] fn GetSystemTimeAsFileTime( &mut self, diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs index c72d3bb3df4a8..bd299aaa1253a 100644 --- a/src/tools/miri/src/shims/unix/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/foreign_items.rs @@ -234,6 +234,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let result = this.gettimeofday(tv, tz)?; this.write_scalar(Scalar::from_i32(result), dest)?; } + "localtime_r" => { + let [timep, result_op] = this.check_shim(abi, Abi::C {unwind: false}, link_name, args)?; + let result = this.localtime_r(timep, result_op)?; + this.write_pointer(result, dest)?; + } "clock_gettime" => { let [clk_id, tp] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; diff --git a/src/tools/miri/tests/pass-dep/shims/libc-misc.rs b/src/tools/miri/tests/pass-dep/shims/libc-misc.rs index abb384b0a857c..f710daf527745 100644 --- a/src/tools/miri/tests/pass-dep/shims/libc-misc.rs +++ b/src/tools/miri/tests/pass-dep/shims/libc-misc.rs @@ -213,6 +213,50 @@ fn test_posix_gettimeofday() { assert_eq!(is_error, -1); } +fn test_localtime_r() { + use std::ffi::CStr; + use std::{env, ptr}; + + // Set timezone to GMT. + let key = "TZ"; + env::set_var(key, "GMT"); + + const TIME_SINCE_EPOCH: libc::time_t = 1712475836; + let custom_time_ptr = &TIME_SINCE_EPOCH; + let mut tm = libc::tm { + tm_sec: 0, + tm_min: 0, + tm_hour: 0, + tm_mday: 0, + tm_mon: 0, + tm_year: 0, + tm_wday: 0, + tm_yday: 0, + tm_isdst: 0, + tm_gmtoff: 0, + tm_zone: std::ptr::null_mut::(), + }; + let res = unsafe { libc::localtime_r(custom_time_ptr, &mut tm) }; + + assert_eq!(tm.tm_sec, 56); + assert_eq!(tm.tm_min, 43); + assert_eq!(tm.tm_hour, 7); + assert_eq!(tm.tm_mday, 7); + assert_eq!(tm.tm_mon, 3); + assert_eq!(tm.tm_year, 124); + assert_eq!(tm.tm_wday, 0); + assert_eq!(tm.tm_yday, 97); + assert_eq!(tm.tm_isdst, -1); + assert_eq!(tm.tm_gmtoff, 0); + unsafe { assert_eq!(CStr::from_ptr(tm.tm_zone).to_str().unwrap(), "+00") }; + + // The returned value is the pointer passed in. + assert!(ptr::eq(res, &mut tm)); + + //Remove timezone setting. + env::remove_var(key); +} + fn test_isatty() { // Testing whether our isatty shim returns the right value would require controlling whether // these streams are actually TTYs, which is hard. @@ -365,6 +409,7 @@ fn main() { test_posix_realpath_errors(); test_thread_local_errno(); + test_localtime_r(); test_isatty();