Skip to content

Commit b9f1aed

Browse files
authored
Merge pull request #4069 from shamb0/string_deduplication_for_localtime_r
localtime_r: deduplicate timezone name allocation
2 parents 11ab793 + 6cbd1eb commit b9f1aed

File tree

2 files changed

+233
-20
lines changed

2 files changed

+233
-20
lines changed

src/tools/miri/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
}

src/tools/miri/tests/pass-dep/libc/libc-time.rs

+219-16
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,21 @@ 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+
#[cfg(any(
12+
target_os = "linux",
13+
target_os = "macos",
14+
target_os = "freebsd",
15+
target_os = "android"
16+
))]
17+
test_localtime_r_multiple_calls_deduplication();
18+
// Architecture-specific tests.
19+
#[cfg(target_pointer_width = "32")]
20+
test_localtime_r_future_32b();
21+
#[cfg(target_pointer_width = "64")]
22+
test_localtime_r_future_64b();
923
}
1024

1125
/// Tests whether clock support exists at all
@@ -46,14 +60,9 @@ fn test_posix_gettimeofday() {
4660
assert_eq!(is_error, -1);
4761
}
4862

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 {
63+
/// Helper function to create an empty tm struct.
64+
fn create_empty_tm() -> libc::tm {
65+
libc::tm {
5766
tm_sec: 0,
5867
tm_min: 0,
5968
tm_hour: 0,
@@ -77,7 +86,17 @@ fn test_localtime_r() {
7786
target_os = "android"
7887
))]
7988
tm_zone: std::ptr::null_mut::<libc::c_char>(),
80-
};
89+
}
90+
}
91+
92+
/// Original GMT test
93+
fn test_localtime_r_gmt() {
94+
// Set timezone to GMT.
95+
let key = "TZ";
96+
env::set_var(key, "GMT");
97+
const TIME_SINCE_EPOCH: libc::time_t = 1712475836; // 2024-04-07 07:43:56 GMT
98+
let custom_time_ptr = &TIME_SINCE_EPOCH;
99+
let mut tm = create_empty_tm();
81100
let res = unsafe { libc::localtime_r(custom_time_ptr, &mut tm) };
82101

83102
assert_eq!(tm.tm_sec, 56);
@@ -95,20 +114,204 @@ fn test_localtime_r() {
95114
target_os = "freebsd",
96115
target_os = "android"
97116
))]
98-
assert_eq!(tm.tm_gmtoff, 0);
117+
{
118+
assert_eq!(tm.tm_gmtoff, 0);
119+
unsafe {
120+
assert_eq!(std::ffi::CStr::from_ptr(tm.tm_zone).to_str().unwrap(), "+00");
121+
}
122+
}
123+
124+
// The returned value is the pointer passed in.
125+
assert!(ptr::eq(res, &mut tm));
126+
127+
// Remove timezone setting.
128+
env::remove_var(key);
129+
}
130+
131+
/// PST timezone test (testing different timezone handling).
132+
fn test_localtime_r_pst() {
133+
let key = "TZ";
134+
env::set_var(key, "PST8PDT");
135+
const TIME_SINCE_EPOCH: libc::time_t = 1712475836; // 2024-04-07 07:43:56 GMT
136+
let custom_time_ptr = &TIME_SINCE_EPOCH;
137+
let mut tm = create_empty_tm();
138+
139+
let res = unsafe { libc::localtime_r(custom_time_ptr, &mut tm) };
140+
141+
assert_eq!(tm.tm_sec, 56);
142+
assert_eq!(tm.tm_min, 43);
143+
assert_eq!(tm.tm_hour, 0); // 7 - 7 = 0 (PDT offset)
144+
assert_eq!(tm.tm_mday, 7);
145+
assert_eq!(tm.tm_mon, 3);
146+
assert_eq!(tm.tm_year, 124);
147+
assert_eq!(tm.tm_wday, 0);
148+
assert_eq!(tm.tm_yday, 97);
149+
assert_eq!(tm.tm_isdst, -1); // DST information unavailable
150+
99151
#[cfg(any(
100152
target_os = "linux",
101153
target_os = "macos",
102154
target_os = "freebsd",
103155
target_os = "android"
104156
))]
105-
unsafe {
106-
assert_eq!(std::ffi::CStr::from_ptr(tm.tm_zone).to_str().unwrap(), "+00")
107-
};
157+
{
158+
assert_eq!(tm.tm_gmtoff, -7 * 3600); // -7 hours in seconds
159+
unsafe {
160+
assert_eq!(std::ffi::CStr::from_ptr(tm.tm_zone).to_str().unwrap(), "-07");
161+
}
162+
}
108163

109-
// The returned value is the pointer passed in.
110164
assert!(ptr::eq(res, &mut tm));
165+
env::remove_var(key);
166+
}
111167

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

0 commit comments

Comments
 (0)