Skip to content

Commit ed82c0e

Browse files
committed
- localtime_r: Ensure string deduplication with a single buffer allocation
- tests: Add additional scenarios for libc::localtime_r validation Signed-off-by: shamb0 <[email protected]>
1 parent b27f1d6 commit ed82c0e

File tree

2 files changed

+154
-22
lines changed

2 files changed

+154
-22
lines changed

src/shims/time.rs

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
use std::ffi::{OsStr, OsString};
22
use std::fmt::Write;
33
use std::str::FromStr;
4+
use std::sync::OnceLock;
45
use std::time::{Duration, SystemTime};
56

67
use chrono::{DateTime, Datelike, Offset, Timelike, Utc};
78
use chrono_tz::Tz;
9+
use rustc_middle::ty::Ty;
10+
use rustc_middle::ty::layout::LayoutOf;
811

912
use crate::*;
1013

@@ -180,6 +183,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
180183
if !matches!(&*this.tcx.sess.target.os, "solaris" | "illumos") {
181184
// tm_zone represents the timezone value in the form of: +0730, +08, -0730 or -08.
182185
// This may not be consistent with libc::localtime_r's result.
186+
const TZ_MAX_LEN: u64 = 6; // 5 chars max + null terminator
187+
static TIMEZONE_PTR: OnceLock<Pointer> = OnceLock::new();
188+
183189
let offset_in_seconds = dt.offset().fix().local_minus_utc();
184190
let tm_gmtoff = offset_in_seconds;
185191
let mut tm_zone = String::new();
@@ -195,12 +201,23 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
195201
write!(tm_zone, "{:02}", offset_min).unwrap();
196202
}
197203

198-
// FIXME: String de-duplication is needed so that we only allocate this string only once
199-
// even when there are multiple calls to this function.
200-
let tm_zone_ptr = this
201-
.alloc_os_str_as_c_str(&OsString::from(tm_zone), MiriMemoryKind::Machine.into())?;
202-
203-
this.write_pointer(tm_zone_ptr, &this.project_field_named(&result, "tm_zone")?)?;
204+
// Ensure string deduplication by allocating the buffer only once,
205+
// even if the function is called multiple times.
206+
let tm_zone_ptr = TIMEZONE_PTR.get_or_init(|| {
207+
let arg_type = Ty::new_array(this.tcx.tcx, this.tcx.types.u8, TZ_MAX_LEN);
208+
let arg_place = this
209+
.allocate(this.layout_of(arg_type).unwrap(), MiriMemoryKind::Machine.into())
210+
.expect("timezone buffer allocation failed");
211+
arg_place.ptr()
212+
});
213+
214+
// Write the `tm_zone` string into the allocated buffer.
215+
let (written, _) =
216+
this.write_os_str_to_c_str(&OsString::from(tm_zone), *tm_zone_ptr, TZ_MAX_LEN)?;
217+
assert!(written);
218+
219+
// Write the timezone pointer and offset into the result structure.
220+
this.write_pointer(*tm_zone_ptr, &this.project_field_named(&result, "tm_zone")?)?;
204221
this.write_int_fields_named(&[("tm_gmtoff", tm_gmtoff.into())], &result)?;
205222
}
206223
interp_ok(result.ptr())

tests/pass-dep/libc/libc-time.rs

Lines changed: 131 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ use std::{env, mem, ptr};
55
fn main() {
66
test_clocks();
77
test_posix_gettimeofday();
8-
test_localtime_r();
8+
test_localtime_r_gmt();
9+
test_localtime_r_pst();
10+
test_localtime_r_epoch();
11+
test_localtime_r_future();
912
}
1013

1114
/// Tests whether clock support exists at all
@@ -46,14 +49,9 @@ fn test_posix_gettimeofday() {
4649
assert_eq!(is_error, -1);
4750
}
4851

49-
fn test_localtime_r() {
50-
// Set timezone to GMT.
51-
let key = "TZ";
52-
env::set_var(key, "GMT");
53-
54-
const TIME_SINCE_EPOCH: libc::time_t = 1712475836;
55-
let custom_time_ptr = &TIME_SINCE_EPOCH;
56-
let mut tm = libc::tm {
52+
// Helper function to create an empty tm struct
53+
fn create_empty_tm() -> libc::tm {
54+
libc::tm {
5755
tm_sec: 0,
5856
tm_min: 0,
5957
tm_hour: 0,
@@ -77,7 +75,17 @@ fn test_localtime_r() {
7775
target_os = "android"
7876
))]
7977
tm_zone: std::ptr::null_mut::<libc::c_char>(),
80-
};
78+
}
79+
}
80+
81+
// Original GMT test
82+
fn test_localtime_r_gmt() {
83+
// Set timezone to GMT.
84+
let key = "TZ";
85+
env::set_var(key, "GMT");
86+
const TIME_SINCE_EPOCH: libc::time_t = 1712475836; // 2024-04-07 07:43:56 GMT
87+
let custom_time_ptr = &TIME_SINCE_EPOCH;
88+
let mut tm = create_empty_tm();
8189
let res = unsafe { libc::localtime_r(custom_time_ptr, &mut tm) };
8290

8391
assert_eq!(tm.tm_sec, 56);
@@ -95,20 +103,127 @@ fn test_localtime_r() {
95103
target_os = "freebsd",
96104
target_os = "android"
97105
))]
98-
assert_eq!(tm.tm_gmtoff, 0);
106+
{
107+
assert_eq!(tm.tm_gmtoff, 0);
108+
unsafe {
109+
assert_eq!(std::ffi::CStr::from_ptr(tm.tm_zone).to_str().unwrap(), "+00");
110+
}
111+
}
112+
113+
// The returned value is the pointer passed in.
114+
assert!(ptr::eq(res, &mut tm));
115+
116+
// Remove timezone setting.
117+
env::remove_var(key);
118+
}
119+
120+
// PST timezone test (testing different timezone handling)
121+
fn test_localtime_r_pst() {
122+
let key = "TZ";
123+
env::set_var(key, "PST8PDT");
124+
const TIME_SINCE_EPOCH: libc::time_t = 1712475836;
125+
let custom_time_ptr = &TIME_SINCE_EPOCH;
126+
let mut tm = create_empty_tm();
127+
128+
let res = unsafe { libc::localtime_r(custom_time_ptr, &mut tm) };
129+
130+
assert_eq!(tm.tm_sec, 56);
131+
assert_eq!(tm.tm_min, 43);
132+
assert_eq!(tm.tm_hour, 0); // 7 - 7 = 0 (PDT offset)
133+
assert_eq!(tm.tm_mday, 7);
134+
assert_eq!(tm.tm_mon, 3);
135+
assert_eq!(tm.tm_year, 124);
136+
assert_eq!(tm.tm_wday, 0);
137+
assert_eq!(tm.tm_yday, 97);
138+
assert_eq!(tm.tm_isdst, -1); // DST information unavailable
139+
99140
#[cfg(any(
100141
target_os = "linux",
101142
target_os = "macos",
102143
target_os = "freebsd",
103144
target_os = "android"
104145
))]
105-
unsafe {
106-
assert_eq!(std::ffi::CStr::from_ptr(tm.tm_zone).to_str().unwrap(), "+00")
107-
};
146+
{
147+
assert_eq!(tm.tm_gmtoff, -25200); // -7 hours in seconds
148+
unsafe {
149+
assert_eq!(std::ffi::CStr::from_ptr(tm.tm_zone).to_str().unwrap(), "-07");
150+
}
151+
}
152+
153+
assert!(ptr::eq(res, &mut tm));
154+
env::remove_var(key);
155+
}
156+
157+
// Unix epoch test (edge case testing)
158+
fn test_localtime_r_epoch() {
159+
let key = "TZ";
160+
env::set_var(key, "GMT");
161+
const TIME_SINCE_EPOCH: libc::time_t = 0; // 1970-01-01 00:00:00
162+
let custom_time_ptr = &TIME_SINCE_EPOCH;
163+
let mut tm = create_empty_tm();
164+
165+
let res = unsafe { libc::localtime_r(custom_time_ptr, &mut tm) };
166+
167+
assert_eq!(tm.tm_sec, 0);
168+
assert_eq!(tm.tm_min, 0);
169+
assert_eq!(tm.tm_hour, 0);
170+
assert_eq!(tm.tm_mday, 1);
171+
assert_eq!(tm.tm_mon, 0);
172+
assert_eq!(tm.tm_year, 70);
173+
assert_eq!(tm.tm_wday, 4); // Thursday
174+
assert_eq!(tm.tm_yday, 0);
175+
assert_eq!(tm.tm_isdst, -1);
176+
177+
#[cfg(any(
178+
target_os = "linux",
179+
target_os = "macos",
180+
target_os = "freebsd",
181+
target_os = "android"
182+
))]
183+
{
184+
assert_eq!(tm.tm_gmtoff, 0);
185+
unsafe {
186+
assert_eq!(std::ffi::CStr::from_ptr(tm.tm_zone).to_str().unwrap(), "+00");
187+
}
188+
}
108189

109-
// The returned value is the pointer passed in.
110190
assert!(ptr::eq(res, &mut tm));
191+
env::remove_var(key);
192+
}
111193

112-
// Remove timezone setting.
194+
// Future date test (testing large values)
195+
fn test_localtime_r_future() {
196+
let key = "TZ";
197+
env::set_var(key, "GMT");
198+
const TIME_SINCE_EPOCH: libc::time_t = 2524608000; // 2050-01-01 00:00:00
199+
let custom_time_ptr = &TIME_SINCE_EPOCH;
200+
let mut tm = create_empty_tm();
201+
202+
let res = unsafe { libc::localtime_r(custom_time_ptr, &mut tm) };
203+
204+
assert_eq!(tm.tm_sec, 0);
205+
assert_eq!(tm.tm_min, 0);
206+
assert_eq!(tm.tm_hour, 0);
207+
assert_eq!(tm.tm_mday, 1);
208+
assert_eq!(tm.tm_mon, 0);
209+
assert_eq!(tm.tm_year, 150);
210+
assert_eq!(tm.tm_wday, 6); // Saturday
211+
assert_eq!(tm.tm_yday, 0);
212+
assert_eq!(tm.tm_isdst, -1);
213+
214+
#[cfg(any(
215+
target_os = "linux",
216+
target_os = "macos",
217+
target_os = "freebsd",
218+
target_os = "android"
219+
))]
220+
{
221+
assert_eq!(tm.tm_gmtoff, 0);
222+
unsafe {
223+
assert_eq!(std::ffi::CStr::from_ptr(tm.tm_zone).to_str().unwrap(), "+00");
224+
}
225+
}
226+
227+
assert!(ptr::eq(res, &mut tm));
113228
env::remove_var(key);
114229
}

0 commit comments

Comments
 (0)