Skip to content

Commit cfd6df9

Browse files
committed
Improve timezone handling in 'localtime_r()' using 'allocate_bytes()'
Signed-off-by: shamb0 <[email protected]>
1 parent d7822b3 commit cfd6df9

File tree

2 files changed

+244
-20
lines changed

2 files changed

+244
-20
lines changed

src/shims/time.rs

+14-4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ use std::time::{Duration, SystemTime};
55

66
use chrono::{DateTime, Datelike, Offset, Timelike, Utc};
77
use chrono_tz::Tz;
8+
use rustc_abi::Align;
9+
use rustc_ast::ast::Mutability;
810

911
use crate::*;
1012

@@ -180,6 +182,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
180182
if !matches!(&*this.tcx.sess.target.os, "solaris" | "illumos") {
181183
// tm_zone represents the timezone value in the form of: +0730, +08, -0730 or -08.
182184
// This may not be consistent with libc::localtime_r's result.
185+
183186
let offset_in_seconds = dt.offset().fix().local_minus_utc();
184187
let tm_gmtoff = offset_in_seconds;
185188
let mut tm_zone = String::new();
@@ -195,11 +198,18 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
195198
write!(tm_zone, "{:02}", offset_min).unwrap();
196199
}
197200

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())?;
201+
// Add null terminator for C string compatibility.
202+
tm_zone.push('\0');
203+
204+
// Deduplicate and allocate the string.
205+
let tm_zone_ptr = this.allocate_bytes(
206+
tm_zone.as_bytes(),
207+
Align::ONE,
208+
MiriMemoryKind::Machine.into(),
209+
Mutability::Not,
210+
)?;
202211

212+
// Write the timezone pointer and offset into the result structure.
203213
this.write_pointer(tm_zone_ptr, &this.project_field_named(&result, "tm_zone")?)?;
204214
this.write_int_fields_named(&[("tm_gmtoff", tm_gmtoff.into())], &result)?;
205215
}

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

+230-16
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,15 @@ 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_multiple_calls_deduplication();
12+
// Architecture-specific tests.
13+
#[cfg(target_pointer_width = "32")]
14+
test_localtime_r_future_32b();
15+
#[cfg(target_pointer_width = "64")]
16+
test_localtime_r_future_64b();
917
}
1018

1119
/// Tests whether clock support exists at all
@@ -46,14 +54,9 @@ fn test_posix_gettimeofday() {
4654
assert_eq!(is_error, -1);
4755
}
4856

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 {
57+
// Helper function to create an empty tm struct.
58+
fn create_empty_tm() -> libc::tm {
59+
libc::tm {
5760
tm_sec: 0,
5861
tm_min: 0,
5962
tm_hour: 0,
@@ -77,7 +80,17 @@ fn test_localtime_r() {
7780
target_os = "android"
7881
))]
7982
tm_zone: std::ptr::null_mut::<libc::c_char>(),
80-
};
83+
}
84+
}
85+
86+
// Original GMT test
87+
fn test_localtime_r_gmt() {
88+
// Set timezone to GMT.
89+
let key = "TZ";
90+
env::set_var(key, "GMT");
91+
const TIME_SINCE_EPOCH: libc::time_t = 1712475836; // 2024-04-07 07:43:56 GMT
92+
let custom_time_ptr = &TIME_SINCE_EPOCH;
93+
let mut tm = create_empty_tm();
8194
let res = unsafe { libc::localtime_r(custom_time_ptr, &mut tm) };
8295

8396
assert_eq!(tm.tm_sec, 56);
@@ -95,20 +108,221 @@ fn test_localtime_r() {
95108
target_os = "freebsd",
96109
target_os = "android"
97110
))]
98-
assert_eq!(tm.tm_gmtoff, 0);
111+
{
112+
assert_eq!(tm.tm_gmtoff, 0);
113+
unsafe {
114+
assert_eq!(std::ffi::CStr::from_ptr(tm.tm_zone).to_str().unwrap(), "+00");
115+
}
116+
}
117+
118+
// The returned value is the pointer passed in.
119+
assert!(ptr::eq(res, &mut tm));
120+
121+
// Remove timezone setting.
122+
env::remove_var(key);
123+
}
124+
125+
// PST timezone test (testing different timezone handling).
126+
fn test_localtime_r_pst() {
127+
let key = "TZ";
128+
env::set_var(key, "PST8PDT");
129+
const TIME_SINCE_EPOCH: libc::time_t = 1712475836; // 2024-04-07 07:43:56 GMT
130+
let custom_time_ptr = &TIME_SINCE_EPOCH;
131+
let mut tm = create_empty_tm();
132+
133+
let res = unsafe { libc::localtime_r(custom_time_ptr, &mut tm) };
134+
135+
assert_eq!(tm.tm_sec, 56);
136+
assert_eq!(tm.tm_min, 43);
137+
assert_eq!(tm.tm_hour, 0); // 7 - 7 = 0 (PDT offset)
138+
assert_eq!(tm.tm_mday, 7);
139+
assert_eq!(tm.tm_mon, 3);
140+
assert_eq!(tm.tm_year, 124);
141+
assert_eq!(tm.tm_wday, 0);
142+
assert_eq!(tm.tm_yday, 97);
143+
assert_eq!(tm.tm_isdst, -1); // DST information unavailable
144+
99145
#[cfg(any(
100146
target_os = "linux",
101147
target_os = "macos",
102148
target_os = "freebsd",
103149
target_os = "android"
104150
))]
105-
unsafe {
106-
assert_eq!(std::ffi::CStr::from_ptr(tm.tm_zone).to_str().unwrap(), "+00")
107-
};
151+
{
152+
assert_eq!(tm.tm_gmtoff, -7 * 3600); // -7 hours in seconds
153+
unsafe {
154+
assert_eq!(std::ffi::CStr::from_ptr(tm.tm_zone).to_str().unwrap(), "-07");
155+
}
156+
}
108157

109-
// The returned value is the pointer passed in.
110158
assert!(ptr::eq(res, &mut tm));
159+
env::remove_var(key);
160+
}
111161

112-
// Remove timezone setting.
162+
// Unix epoch test (edge case testing).
163+
fn test_localtime_r_epoch() {
164+
let key = "TZ";
165+
env::set_var(key, "GMT");
166+
const TIME_SINCE_EPOCH: libc::time_t = 0; // 1970-01-01 00:00:00
167+
let custom_time_ptr = &TIME_SINCE_EPOCH;
168+
let mut tm = create_empty_tm();
169+
170+
let res = unsafe { libc::localtime_r(custom_time_ptr, &mut tm) };
171+
172+
assert_eq!(tm.tm_sec, 0);
173+
assert_eq!(tm.tm_min, 0);
174+
assert_eq!(tm.tm_hour, 0);
175+
assert_eq!(tm.tm_mday, 1);
176+
assert_eq!(tm.tm_mon, 0);
177+
assert_eq!(tm.tm_year, 70);
178+
assert_eq!(tm.tm_wday, 4); // Thursday
179+
assert_eq!(tm.tm_yday, 0);
180+
assert_eq!(tm.tm_isdst, -1);
181+
182+
#[cfg(any(
183+
target_os = "linux",
184+
target_os = "macos",
185+
target_os = "freebsd",
186+
target_os = "android"
187+
))]
188+
{
189+
assert_eq!(tm.tm_gmtoff, 0);
190+
unsafe {
191+
assert_eq!(std::ffi::CStr::from_ptr(tm.tm_zone).to_str().unwrap(), "+00");
192+
}
193+
}
194+
195+
assert!(ptr::eq(res, &mut tm));
196+
env::remove_var(key);
197+
}
198+
199+
// Future date test (testing large values).
200+
#[cfg(target_pointer_width = "64")]
201+
fn test_localtime_r_future_64b() {
202+
let key = "TZ";
203+
env::set_var(key, "GMT");
204+
205+
// Using 2050-01-01 00:00:00 for 64-bit systems
206+
// value that's safe for 64-bit time_t
207+
const TIME_SINCE_EPOCH: libc::time_t = 2524608000;
208+
let custom_time_ptr = &TIME_SINCE_EPOCH;
209+
let mut tm = create_empty_tm();
210+
211+
let res = unsafe { libc::localtime_r(custom_time_ptr, &mut tm) };
212+
213+
assert_eq!(tm.tm_sec, 0);
214+
assert_eq!(tm.tm_min, 0);
215+
assert_eq!(tm.tm_hour, 0);
216+
assert_eq!(tm.tm_mday, 1);
217+
assert_eq!(tm.tm_mon, 0);
218+
assert_eq!(tm.tm_year, 150); // 2050 - 1900
219+
assert_eq!(tm.tm_wday, 6); // Saturday
220+
assert_eq!(tm.tm_yday, 0);
221+
assert_eq!(tm.tm_isdst, -1);
222+
223+
#[cfg(any(
224+
target_os = "linux",
225+
target_os = "macos",
226+
target_os = "freebsd",
227+
target_os = "android"
228+
))]
229+
{
230+
assert_eq!(tm.tm_gmtoff, 0);
231+
unsafe {
232+
assert_eq!(std::ffi::CStr::from_ptr(tm.tm_zone).to_str().unwrap(), "+00");
233+
}
234+
}
235+
236+
assert!(ptr::eq(res, &mut tm));
113237
env::remove_var(key);
114238
}
239+
240+
#[cfg(target_pointer_width = "32")]
241+
fn test_localtime_r_future_32b() {
242+
let key = "TZ";
243+
env::set_var(key, "GMT");
244+
245+
// Using 2030-01-01 00:00:00 for 32-bit systems
246+
// Safe value within i32 range
247+
const TIME_SINCE_EPOCH: libc::time_t = 1893456000;
248+
let custom_time_ptr = &TIME_SINCE_EPOCH;
249+
let mut tm = create_empty_tm();
250+
251+
let res = unsafe { libc::localtime_r(custom_time_ptr, &mut tm) };
252+
253+
// Verify 2030-01-01 00:00:00
254+
assert_eq!(tm.tm_sec, 0);
255+
assert_eq!(tm.tm_min, 0);
256+
assert_eq!(tm.tm_hour, 0);
257+
assert_eq!(tm.tm_mday, 1);
258+
assert_eq!(tm.tm_mon, 0);
259+
assert_eq!(tm.tm_year, 130); // 2030 - 1900
260+
assert_eq!(tm.tm_wday, 2); // Tuesday
261+
assert_eq!(tm.tm_yday, 0);
262+
assert_eq!(tm.tm_isdst, -1);
263+
264+
#[cfg(any(
265+
target_os = "linux",
266+
target_os = "macos",
267+
target_os = "freebsd",
268+
target_os = "android"
269+
))]
270+
{
271+
assert_eq!(tm.tm_gmtoff, 0);
272+
unsafe {
273+
assert_eq!(std::ffi::CStr::from_ptr(tm.tm_zone).to_str().unwrap(), "+00");
274+
}
275+
}
276+
277+
assert!(ptr::eq(res, &mut tm));
278+
env::remove_var(key);
279+
}
280+
281+
fn test_localtime_r_multiple_calls_deduplication() {
282+
let key = "TZ";
283+
env::set_var(key, "PST8PDT");
284+
285+
const TIME_SINCE_EPOCH_BASE: libc::time_t = 1712475836; // Base timestamp: 2024-04-07 07:43:56 GMT
286+
const NUM_CALLS: usize = 50;
287+
288+
let mut unique_pointers = std::collections::HashSet::new();
289+
290+
unsafe {
291+
for i in 0..NUM_CALLS {
292+
let timestamp = TIME_SINCE_EPOCH_BASE + (i as libc::time_t * 3600); // Increment by 1 hour for each call
293+
let mut tm: libc::tm = create_empty_tm();
294+
let tm_ptr = libc::localtime_r(&timestamp, &mut tm);
295+
296+
assert!(!tm_ptr.is_null(), "localtime_r failed for timestamp {timestamp}");
297+
298+
#[cfg(any(
299+
target_os = "linux",
300+
target_os = "macos",
301+
target_os = "freebsd",
302+
target_os = "android"
303+
))]
304+
{
305+
let tm_zone_ptr = tm.tm_zone;
306+
unique_pointers.insert(tm_zone_ptr);
307+
}
308+
}
309+
310+
#[cfg(any(
311+
target_os = "linux",
312+
target_os = "macos",
313+
target_os = "freebsd",
314+
target_os = "android"
315+
))]
316+
{
317+
let unique_count = unique_pointers.len();
318+
println!("Number of unique tm_zone pointers: {}", unique_count);
319+
320+
assert!(
321+
unique_count >= 2 && unique_count <= (NUM_CALLS - 1),
322+
"Unexpected number of unique tm_zone pointers: {} (expected between 2 and {})",
323+
unique_count,
324+
NUM_CALLS - 1
325+
);
326+
}
327+
}
328+
}

0 commit comments

Comments
 (0)