Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 86 additions & 16 deletions c2rust-transpile/src/translator/builtins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -680,32 +680,102 @@ impl<'c> Translation<'c> {
method_name: &str,
args: &[CExprId],
) -> TranslationResult<WithStmts<Box<Expr>>> {
let &[a_arg, b_arg, out_arg] = args else {
return Err(TranslationError::generic(
"`convert_overflow_arith` must have exactly 3 arguments",
));
};

let arg_type = |arg: CExprId| {
self.ast_context
.index_unwrap_parens(arg)
.kind
.get_type()
.ok_or_else(|| TranslationError::generic("overflow builtin argument has no type"))
};
let result_ty_id = self
.ast_context
.get_pointee_qual_type(arg_type(out_arg)?)
.ok_or_else(|| TranslationError::generic("overflow builtin output is not a pointer"))?
.ctype;
let type_kind = |ty| &self.ast_context.resolve_type(ty).kind;
let a_kind = type_kind(arg_type(a_arg)?);
let b_kind = type_kind(arg_type(b_arg)?);
let result_kind = type_kind(result_ty_id);

// The `s`/`u`-prefixed builtins use one common type; the type-generic
// ones accept operand and result types that all differ. With a common
// type, Rust's native `overflowing_*` matches the builtin exactly.
// Otherwise, overflow is defined against the infinite-precision result,
// so compute in `i128` and check that narrowing to the result type
// preserves the value.
let same_types = a_kind == b_kind && b_kind == result_kind;
let is_128_bit = |kind: &CTypeKind| matches!(kind, CTypeKind::Int128 | CTypeKind::UInt128);
if !same_types && [a_kind, b_kind, result_kind].into_iter().any(is_128_bit) {
// The `i128` computation below cannot represent all values of
// `unsigned __int128` operands or results. Reject rather than
// translate wrongly: https://github.com/immunant/c2rust/issues/1878
return Err(TranslationError::generic(
"mixed-type overflow builtins involving 128-bit integers are not supported",
));
}
let result_ty = if same_types {
None
} else {
Some(self.convert_type(result_ty_id)?)
};

let args = self.convert_exprs(ctx.used(), args, None)?;
args.and_then_try(|args| {
let [a, b, c]: [_; 3] = args
.try_into()
.map_err(|_| "`convert_overflow_arith` must have exactly 3 arguments")?;
let [a, b, out]: [_; 3] = args.try_into().map_err(|_| "expected 3 arguments")?;
let (a, b) = if same_types {
(a, b)
} else {
(cast_int(a, "i128", true), cast_int(b, "i128", true))
};
let overflowing = mk().method_call_expr(a, method_name, vec![b]);
let sum_name = self.renamer.borrow_mut().pick_name("c2rust_result");
let result_name = self.renamer.borrow_mut().pick_name("c2rust_result");
let over_name = self.renamer.borrow_mut().pick_name("c2rust_overflowed");
let overflow_let = mk().local_stmt(Box::new(mk().local(
let mut stmts = vec![mk().local_stmt(Box::new(mk().local(
mk().tuple_pat(vec![
mk().ident_pat(&sum_name),
mk().ident_pat(over_name.clone()),
mk().ident_pat(&result_name),
mk().ident_pat(&over_name),
]),
None,
Some(overflowing),
)))];
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
Comment thread
thedataking marked this conversation as resolved.
// 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.
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),
);
overflowed = mk().binary_expr(BinOp::Or(Default::default()), overflowed, truncated);
out_name = narrow_name;
}

stmts.push(mk().expr_stmt(mk().assign_expr(
mk().unary_expr(UnOp::Deref(Default::default()), out),
mk().ident_expr(&out_name),
)));

let out_assign = mk().assign_expr(
mk().unary_expr(UnOp::Deref(Default::default()), c),
mk().ident_expr(&sum_name),
);

Ok(WithStmts::new(
vec![overflow_let, mk().expr_stmt(out_assign)],
mk().ident_expr(over_name),
))
Ok(WithStmts::new(stmts, overflowed))
})
}

Expand Down
27 changes: 26 additions & 1 deletion c2rust-transpile/tests/snapshots.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ fn transpile_snapshot(
c_path: &Path,
edition: RustEdition,
expect_compile_error: bool,
expect_translation_error: bool,
imported_crates: &[&str],
) {
let c_file_name = c_path.file_name().unwrap().to_str().unwrap();
Expand All @@ -126,7 +127,10 @@ fn transpile_snapshot(
(0.., _) => "clang15",
};

let cfg = config(edition);
let mut cfg = config(edition);
if expect_translation_error {
cfg.fail_on_error = false;
}
compile_and_transpile_file(c_path, cfg);
let cwd = current_dir().unwrap();
// The crate name can't have `.`s in it, so use the file stem.
Expand Down Expand Up @@ -171,6 +175,7 @@ struct TranspileTest<'a> {
os_specific: bool,
expect_compile_error_edition_2021: bool,
expect_compile_error_edition_2024: bool,
expect_translation_error: bool,
imported_crates: Vec<&'a str>,
}

Expand All @@ -181,6 +186,7 @@ fn transpile(c_file_name: &str) -> TranspileTest {
os_specific: false,
expect_compile_error_edition_2021: false,
expect_compile_error_edition_2024: false,
expect_translation_error: false,
imported_crates: Default::default(),
}
}
Expand Down Expand Up @@ -226,6 +232,15 @@ impl<'a> TranspileTest<'a> {
.expect_compile_error_edition_2024(expect_error)
}

/// Expect some decls to fail translation. Failed decls are omitted from
/// the output instead of aborting, so the snapshot pins what is rejected.
pub fn expect_translation_error(self, expect_translation_error: bool) -> Self {
Self {
expect_translation_error,
..self
}
}

pub fn expect_unresolved_import(mut self, imported_crate: &'a str) -> Self {
self.imported_crates.push(imported_crate);
self
Expand All @@ -238,6 +253,7 @@ impl<'a> TranspileTest<'a> {
os_specific,
expect_compile_error_edition_2021,
expect_compile_error_edition_2024,
expect_translation_error,
imported_crates,
} = self;

Expand Down Expand Up @@ -289,13 +305,15 @@ impl<'a> TranspileTest<'a> {
&c_path,
Edition2021,
expect_compile_error_edition_2021,
expect_translation_error,
&imported_crates,
);
transpile_snapshot(
&platform,
&c_path,
Edition2024,
expect_compile_error_edition_2024,
expect_translation_error,
&imported_crates,
);
}
Expand Down Expand Up @@ -442,6 +460,13 @@ fn test_out_of_range_lit() {
transpile("out_of_range_lit.c").run();
}

#[test]
fn test_overflow_128() {
transpile("overflow_128.c")
.expect_translation_error(true)
.run();
}

#[test]
fn test_predefined() {
transpile("predefined.c").run();
Expand Down
30 changes: 30 additions & 0 deletions c2rust-transpile/tests/snapshots/overflow_128.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Overflow builtins mixing 128-bit and other integer types are rejected, so
// the `reject`-prefixed functions must be missing from the snapshot.
// See https://github.com/immunant/c2rust/issues/1878.

int reject_uint128_operands(unsigned __int128 a, unsigned __int128 b,
unsigned long long *out)
{
return __builtin_add_overflow(a, b, out);
}

int reject_uint128_result(int a, int b, unsigned __int128 *out)
{
return __builtin_mul_overflow(a, b, out);
}

int reject_int128_operand(__int128 a, long long b, long long *out)
{
return __builtin_sub_overflow(a, b, out);
}

int supported_uint128(unsigned __int128 a, unsigned __int128 b,
unsigned __int128 *out)
{
return __builtin_add_overflow(a, b, out);
}

int supported_int128(__int128 a, __int128 b, __int128 *out)
{
return __builtin_mul_overflow(a, b, out);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
source: c2rust-transpile/tests/snapshots.rs
expression: cat tests/snapshots/overflow_128.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 supported_uint128(
mut a: u128,
mut b: u128,
mut out: *mut u128,
) -> ::core::ffi::c_int {
let (c2rust_result, c2rust_overflowed) = a.overflowing_add(b);
*out = c2rust_result;
return c2rust_overflowed as ::core::ffi::c_int;
}
#[no_mangle]
pub unsafe extern "C" fn supported_int128(
mut a: i128,
mut b: i128,
mut out: *mut i128,
) -> ::core::ffi::c_int {
let (c2rust_result, c2rust_overflowed) = a.overflowing_mul(b);
*out = c2rust_result;
return c2rust_overflowed as ::core::ffi::c_int;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
source: c2rust-transpile/tests/snapshots.rs
expression: cat tests/snapshots/overflow_128.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 supported_uint128(
mut a: u128,
mut b: u128,
mut out: *mut u128,
) -> ::core::ffi::c_int {
let (c2rust_result, c2rust_overflowed) = a.overflowing_add(b);
*out = c2rust_result;
return c2rust_overflowed as ::core::ffi::c_int;
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn supported_int128(
mut a: i128,
mut b: i128,
mut out: *mut i128,
) -> ::core::ffi::c_int {
let (c2rust_result, c2rust_overflowed) = a.overflowing_mul(b);
*out = c2rust_result;
return c2rust_overflowed as ::core::ffi::c_int;
}
Loading
Loading