Skip to content

Commit 715cf4c

Browse files
test(conformance): gate scanf scansets, printf positional/*, ecvt/fcvt rounding
Probed three complex, hand-rolled, previously-ungated code paths vs host glibc and verified each matches exactly — now permanent gates (run in the suite, no #[ignore]): - conformance_diff_scanf_scanset: %[...] edge cases — negation, ranges, ] as the first set member, '-' at set ends, reversed ranges (c-a), width limits, the A-z punctuation span. Distinct hand-rolled state machine, not the numeric parsers. - conformance_diff_printf_positional: %N$ positional args + dynamic width/ precision (*) incl. %21$.*3-style combinations — the argument-reordering path, across flags and several int triples. - conformance_diff_ecvt_rounding: ecvt/fcvt over NORMAL values x ndigit in {0, negative, huge} — digit string + decpt + sign — complementing the existing specials-only cvt gate. All pass with 0 divergences vs host glibc. No fixes needed (these paths are already conformant) — the gates protect them from future regression. (Also verified clean this session, not promoted: strfmon C-locale formatting.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent ee84462 commit 715cf4c

3 files changed

Lines changed: 137 additions & 0 deletions

File tree

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#![cfg(target_os = "linux")]
2+
#![allow(unsafe_code)]
3+
use frankenlibc_abi::stdlib_abi as fl;
4+
use std::ffi::{CStr, c_char, c_int};
5+
unsafe extern "C" {
6+
fn ecvt(v: f64, n: c_int, dp: *mut c_int, sg: *mut c_int) -> *mut c_char;
7+
fn fcvt(v: f64, n: c_int, dp: *mut c_int, sg: *mut c_int) -> *mut c_char;
8+
}
9+
fn tup(eng: u8, v: f64, n: c_int, fc: bool) -> (String, c_int, c_int) {
10+
let mut dp: c_int = -999; let mut sg: c_int = -999;
11+
let r = match (eng, fc) {
12+
(0, false) => unsafe { fl::ecvt(v, n, &mut dp, &mut sg) },
13+
(0, true) => unsafe { fl::fcvt(v, n, &mut dp, &mut sg) },
14+
(_, false) => unsafe { ecvt(v, n, &mut dp, &mut sg) },
15+
(_, true) => unsafe { fcvt(v, n, &mut dp, &mut sg) },
16+
};
17+
let s = if r.is_null() { "<null>".into() } else { unsafe { CStr::from_ptr(r) }.to_string_lossy().into_owned() };
18+
(s, dp, sg)
19+
}
20+
#[test]
21+
fn ecvt_fcvt_rounding_parity() {
22+
let vals: &[f64] = &[
23+
1.0, 3.14159265358979, 9.999999999, 0.0001234567, 2.5, 0.5, 1.5, 0.15, 0.25, 0.35,
24+
99.5, 100.5, 0.000099995, 123456.789, 9.9999999e10, 1e-10, 2.0/3.0, 1.0/3.0,
25+
0.1, 0.2, 0.3, 99999.9999995, 0.99999999999999, 1000000.0, -3.14159, -0.5, -2.5,
26+
12345.6785, 12345.6795, 0.00012345005, 5.0e-5, 9.95, 0.045, 2.675,
27+
];
28+
let ndigits: &[c_int] = &[0, 1, 2, 3, 5, 6, 10, 15, 17, 20, -1, -5, 50];
29+
let mut div = Vec::new();
30+
for &fc in &[false, true] {
31+
let name = if fc { "fcvt" } else { "ecvt" };
32+
for &v in vals {
33+
for &n in ndigits {
34+
let f = tup(0, v, n, fc);
35+
let g = tup(1, v, n, fc);
36+
if f != g {
37+
div.push(format!("{name}({v:e}, n={n}): fl=({:?},dp {},sg {}) glibc=({:?},dp {},sg {})",
38+
f.0, f.1, f.2, g.0, g.1, g.2));
39+
}
40+
}
41+
}
42+
}
43+
if !div.is_empty() {
44+
eprintln!("ECVT/FCVT ROUNDING DIVERGENCES ({}):", div.len());
45+
for d in div.iter().take(80) { eprintln!(" {d}"); }
46+
}
47+
assert!(div.is_empty(), "{} ecvt/fcvt rounding divergences", div.len());
48+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#![cfg(target_os = "linux")]
2+
#![allow(unsafe_code)]
3+
use frankenlibc_abi::stdio_abi as fl;
4+
use std::ffi::{CString, c_char, c_int};
5+
unsafe extern "C" {
6+
fn snprintf(b: *mut c_char, s: usize, f: *const c_char, ...) -> i32;
7+
}
8+
// render with 3 int args + 1 string arg available (callers pick via positional)
9+
fn r_iii(eng: u8, fmt: &CString, a: c_int, b: c_int, c: c_int) -> (String, i32) {
10+
let mut buf = [0u8; 256];
11+
let n = if eng == 0 { unsafe { fl::snprintf(buf.as_mut_ptr() as *mut c_char, 256, fmt.as_ptr(), a, b, c) } }
12+
else { unsafe { snprintf(buf.as_mut_ptr() as *mut c_char, 256, fmt.as_ptr(), a, b, c) } };
13+
(String::from_utf8_lossy(&buf[..n.max(0) as usize]).into_owned(), n)
14+
}
15+
#[test]
16+
fn printf_positional_star_parity() {
17+
// positional + dynamic width/precision via '*'
18+
let fmts = [
19+
"%1$d", "%2$d %1$d", "%3$d-%1$d-%2$d", "%1$d %1$d %1$d",
20+
"%*d", "%-*d|", "%.*d", "%*.*d", "%2$*1$d", "%3$*2$.*1$d",
21+
"%1$*2$d", "%0*d", "%+*d", "%*.*x", "%2$5d|%1$-5d|",
22+
"%1$d%%%2$d", "%*1$d", // %*1$d is invalid-ish; check both agree
23+
"[%1$3d][%2$03d][%3$+d]",
24+
];
25+
let mut div = Vec::new();
26+
let triples = [(5, 42, 7), (3, -1, 100), (0, 8, -250), (10, 2, 0)];
27+
for fmt in fmts {
28+
let cf = match CString::new(fmt) { Ok(c) => c, Err(_) => continue };
29+
for (a, b, c) in triples {
30+
let f = r_iii(0, &cf, a, b, c);
31+
let g = r_iii(1, &cf, a, b, c);
32+
if f != g {
33+
div.push(format!("snprintf({fmt:?}, {a},{b},{c}): fl=({:?},ret {}) glibc=({:?},ret {})", f.0, f.1, g.0, g.1));
34+
}
35+
}
36+
}
37+
if !div.is_empty() {
38+
eprintln!("PRINTF POSITIONAL/STAR DIVERGENCES ({}):", div.len());
39+
for d in div.iter().take(80) { eprintln!(" {d}"); }
40+
}
41+
assert!(div.is_empty(), "{} printf positional/star divergences", div.len());
42+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#![cfg(target_os = "linux")]
2+
#![allow(unsafe_code)]
3+
use frankenlibc_abi::stdio_abi as fl;
4+
use std::ffi::{CString, c_char, c_int};
5+
unsafe extern "C" {
6+
fn sscanf(s: *const c_char, f: *const c_char, ...) -> c_int;
7+
}
8+
// scan one string field into a 64-byte buf; return (ret, captured-string)
9+
fn scan1(eng: u8, input: &CString, fmt: &CString) -> (c_int, String) {
10+
let mut buf = [0u8; 64];
11+
let r = if eng == 0 { unsafe { fl::sscanf(input.as_ptr(), fmt.as_ptr(), buf.as_mut_ptr() as *mut c_char) } }
12+
else { unsafe { sscanf(input.as_ptr(), fmt.as_ptr(), buf.as_mut_ptr() as *mut c_char) } };
13+
let s = buf.iter().position(|&b| b == 0).map(|n| String::from_utf8_lossy(&buf[..n]).into_owned()).unwrap_or_default();
14+
(r, s)
15+
}
16+
#[test]
17+
fn scanf_scanset_parity() {
18+
// (input, format) pairs exercising scanset edge cases
19+
let cases: &[(&str, &str)] = &[
20+
("abc123", "%[a-c]"), ("abc123", "%[^0-9]"), ("abc123", "%[abc]"),
21+
("]abc", "%[]a]"), ("a]bc", "%[]a]"), ("^abc", "%[^]"), // ] handling
22+
("abc", "%[^]abc"), // unterminated set
23+
("a-c", "%[a-c]"), ("-abc", "%[-a]"), ("abc-", "%[a-]"), // - at ends
24+
("HELLOworld", "%[A-Z]"), ("12.5e3", "%[0-9.eE+-]"),
25+
("aaabbb", "%3[a]"), (" abc", "%[^ ]"), (" abc", "%[ a-c]"),
26+
("xyz", "%[^xyz]"), ("", "%[a-z]"), ("ZZ", "%[A-Za-z]"),
27+
("a1b2c3", "%[a-c0-9]"), ("Hello, World!", "%[^,]"),
28+
("\t\nabc", "%[^a]"), ("ABCabc", "%[A-z]"), // A-z spans punctuation
29+
("123abc456", "%[0-9]"), ("...", "%[.]"), ("a]]b", "%[]a]"),
30+
("ccba", "%[c-a]"), // reversed range (glibc: only 'c'? or empty?)
31+
];
32+
let mut div = Vec::new();
33+
for (inp, fmt) in cases {
34+
let ci = CString::new(*inp).unwrap();
35+
let cf = CString::new(*fmt).unwrap();
36+
let f = scan1(0, &ci, &cf);
37+
let g = scan1(1, &ci, &cf);
38+
if f != g {
39+
div.push(format!("sscanf({inp:?}, {fmt:?}): fl=(ret {},{:?}) glibc=(ret {},{:?})", f.0, f.1, g.0, g.1));
40+
}
41+
}
42+
if !div.is_empty() {
43+
eprintln!("SCANF SCANSET DIVERGENCES ({}):", div.len());
44+
for d in &div { eprintln!(" {d}"); }
45+
}
46+
assert!(div.is_empty(), "{} scanf scanset divergences", div.len());
47+
}

0 commit comments

Comments
 (0)