diff --git a/c2rust-transpile/src/translator/builtins.rs b/c2rust-transpile/src/translator/builtins.rs index 1ee2ec74cd..ccdfbcdd5f 100644 --- a/c2rust-transpile/src/translator/builtins.rs +++ b/c2rust-transpile/src/translator/builtins.rs @@ -722,7 +722,21 @@ impl<'c> Translation<'c> { let result_ty = if same_types { None } else { - Some(self.convert_type(result_ty_id)?) + let ty = self.convert_type(result_ty_id)?; + // Also derive `result_ty::try_from` for the truncation check below. + let try_from = match &*ty { + Type::Path(ty_path) if ty_path.qself.is_none() => { + let mut path = ty_path.path.clone(); + path.segments.push(mk().path_segment("try_from")); + path + } + _ => { + return Err(TranslationError::generic( + "overflow builtin result type is not a plain path type", + )) + } + }; + Some((ty, try_from)) }; let args = self.convert_exprs(ctx.used(), args, None)?; @@ -747,24 +761,25 @@ impl<'c> Translation<'c> { let mut overflowed = mk().ident_expr(&over_name); let mut out_name = result_name.clone(); - if let Some(result_ty) = result_ty { - // `c2rust_result as result_ty` truncates silently. Casting back - // up and comparing against `c2rust_result` detects when that - // truncation lost information -- overflow distinct from - // `overflowing`'s own flag, which only fires when even `i128` - // cannot hold the result. + if let Some((result_ty, try_from)) = result_ty { + // `c2rust_result as result_ty` truncates silently, matching the + // value C stores on overflow. `result_ty::try_from` detects + // whether that truncation lost information -- overflow distinct + // from `overflowing`'s own flag, which only fires when even + // `i128` cannot hold the result. let narrow_name = self.renamer.borrow_mut().pick_name("c2rust_result_narrow"); stmts.push(mk().local_stmt(Box::new(mk().local( mk().ident_pat(&narrow_name), None, Some(mk().cast_expr(mk().ident_expr(&result_name), result_ty)), )))); - let roundtrip = - mk().cast_expr(mk().ident_expr(&narrow_name), mk().path_ty(vec!["i128"])); - let truncated = mk().binary_expr( - BinOp::Ne(Default::default()), - roundtrip, - mk().ident_expr(&result_name), + let truncated = mk().method_call_expr( + mk().call_expr( + mk().path_expr(try_from), + vec![mk().ident_expr(&result_name)], + ), + "is_err", + vec![], ); overflowed = mk().binary_expr(BinOp::Or(Default::default()), overflowed, truncated); out_name = narrow_name; diff --git a/c2rust-transpile/tests/snapshots.rs b/c2rust-transpile/tests/snapshots.rs index 5b72ce1293..d8fa51fac8 100644 --- a/c2rust-transpile/tests/snapshots.rs +++ b/c2rust-transpile/tests/snapshots.rs @@ -467,6 +467,11 @@ fn test_overflow_128() { .run(); } +#[test] +fn test_overflow_mixed() { + transpile("overflow_mixed.c").run(); +} + #[test] fn test_predefined() { transpile("predefined.c").run(); diff --git a/c2rust-transpile/tests/snapshots/overflow_mixed.c b/c2rust-transpile/tests/snapshots/overflow_mixed.c new file mode 100644 index 0000000000..a96deb5871 --- /dev/null +++ b/c2rust-transpile/tests/snapshots/overflow_mixed.c @@ -0,0 +1,12 @@ +// Overflow builtins with operand/result types that do not share one Rust +// type compute in i128 and check that the result fits via `try_from`. + +int mixed_types(long long a, int b, unsigned long long *out) +{ + return __builtin_add_overflow(a, b, out); +} + +int narrow_result(long long a, long long b, int *out) +{ + return __builtin_mul_overflow(a, b, out); +} diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@overflow_mixed.c.2021.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@overflow_mixed.c.2021.clang15.snap new file mode 100644 index 0000000000..f6683822ef --- /dev/null +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@overflow_mixed.c.2021.clang15.snap @@ -0,0 +1,37 @@ +--- +source: c2rust-transpile/tests/snapshots.rs +expression: cat tests/snapshots/overflow_mixed.2021.clang15.rs +--- +#![allow( + clippy::missing_safety_doc, + dead_code, + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused_assignments, + unused_mut +)] +#[no_mangle] +pub unsafe extern "C" fn mixed_types( + mut a: ::core::ffi::c_longlong, + mut b: ::core::ffi::c_int, + mut out: *mut ::core::ffi::c_ulonglong, +) -> ::core::ffi::c_int { + let (c2rust_result, c2rust_overflowed) = (a as i128).overflowing_add(b as i128); + let c2rust_result_narrow = c2rust_result as ::core::ffi::c_ulonglong; + *out = c2rust_result_narrow; + return (c2rust_overflowed || ::core::ffi::c_ulonglong::try_from(c2rust_result).is_err()) + as ::core::ffi::c_int; +} +#[no_mangle] +pub unsafe extern "C" fn narrow_result( + mut a: ::core::ffi::c_longlong, + mut b: ::core::ffi::c_longlong, + mut out: *mut ::core::ffi::c_int, +) -> ::core::ffi::c_int { + let (c2rust_result, c2rust_overflowed) = (a as i128).overflowing_mul(b as i128); + let c2rust_result_narrow = c2rust_result as ::core::ffi::c_int; + *out = c2rust_result_narrow; + return (c2rust_overflowed || ::core::ffi::c_int::try_from(c2rust_result).is_err()) + as ::core::ffi::c_int; +} diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@overflow_mixed.c.2024.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@overflow_mixed.c.2024.clang15.snap new file mode 100644 index 0000000000..efc9ee3c47 --- /dev/null +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@overflow_mixed.c.2024.clang15.snap @@ -0,0 +1,38 @@ +--- +source: c2rust-transpile/tests/snapshots.rs +expression: cat tests/snapshots/overflow_mixed.2024.clang15.rs +--- +#![allow( + clippy::missing_safety_doc, + dead_code, + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unsafe_op_in_unsafe_fn, + unused_assignments, + unused_mut +)] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn mixed_types( + mut a: ::core::ffi::c_longlong, + mut b: ::core::ffi::c_int, + mut out: *mut ::core::ffi::c_ulonglong, +) -> ::core::ffi::c_int { + let (c2rust_result, c2rust_overflowed) = (a as i128).overflowing_add(b as i128); + let c2rust_result_narrow = c2rust_result as ::core::ffi::c_ulonglong; + *out = c2rust_result_narrow; + return (c2rust_overflowed || ::core::ffi::c_ulonglong::try_from(c2rust_result).is_err()) + as ::core::ffi::c_int; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn narrow_result( + mut a: ::core::ffi::c_longlong, + mut b: ::core::ffi::c_longlong, + mut out: *mut ::core::ffi::c_int, +) -> ::core::ffi::c_int { + let (c2rust_result, c2rust_overflowed) = (a as i128).overflowing_mul(b as i128); + let c2rust_result_narrow = c2rust_result as ::core::ffi::c_int; + *out = c2rust_result_narrow; + return (c2rust_overflowed || ::core::ffi::c_int::try_from(c2rust_result).is_err()) + as ::core::ffi::c_int; +}