diff --git a/c2rust-transpile/src/translator/mod.rs b/c2rust-transpile/src/translator/mod.rs index c014935ccf..d99e94f4ef 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -2723,68 +2723,44 @@ impl<'c> Translation<'c> { .ok_or_else(|| format_err!("bad condition type"))?; let null_pointer_case = - |negated: bool, ptr: CExprId| -> TranslationResult>> { + |ptr: CExprId, is_null: bool| -> TranslationResult>> { let val = self.convert_expr(ctx.used().decay_ref(), ptr, None)?; let ptr_type = self.ast_context[ptr] .kind .get_type() .ok_or_else(|| format_err!("bad pointer type for condition"))?; - val.and_then(|e| { - Ok(WithStmts::new_val( - if self.ast_context.is_function_pointer(ptr_type) { - if negated { - mk().method_call_expr(e, "is_some", vec![]) - } else { - mk().method_call_expr(e, "is_none", vec![]) - } - } else { - // TODO: `pointer::is_null` becomes stably const in Rust 1.84. - if ctx.is_const { - return Err(format_translation_err!( - None, - "cannot check nullity of pointer in `const` context", - )); - } - let is_null = mk().method_call_expr(e, "is_null", vec![]); - if negated { - mk().unary_expr(UnOp::Not(Default::default()), is_null) - } else { - is_null - } - }, - )) - }) + + val.result_map(|val| self.convert_pointer_is_null(ctx, ptr_type, val, is_null)) }; match self.ast_context[cond_id].kind { CExprKind::Binary(_, c_ast::BinOp::EqualEqual, null_expr, ptr, _, _) if self.ast_context.is_null_expr(null_expr) => { - null_pointer_case(!target, ptr) + null_pointer_case(ptr, target) } CExprKind::Binary(_, c_ast::BinOp::EqualEqual, ptr, null_expr, _, _) if self.ast_context.is_null_expr(null_expr) => { - null_pointer_case(!target, ptr) + null_pointer_case(ptr, target) } CExprKind::Binary(_, c_ast::BinOp::NotEqual, null_expr, ptr, _, _) if self.ast_context.is_null_expr(null_expr) => { - null_pointer_case(target, ptr) + null_pointer_case(ptr, !target) } CExprKind::Binary(_, c_ast::BinOp::NotEqual, ptr, null_expr, _, _) if self.ast_context.is_null_expr(null_expr) => { - null_pointer_case(target, ptr) + null_pointer_case(ptr, !target) } CExprKind::Unary(_, c_ast::UnOp::Not, subexpr_id, _) => { self.convert_condition(ctx, !target, subexpr_id) } - _ => { // DecayRef could (and probably should) be Default instead of Yes here; however, as noted // in https://github.com/rust-lang/rust/issues/53772, you cant compare a reference (lhs) to @@ -4526,79 +4502,35 @@ impl<'c> Translation<'c> { match kind { CastKind::BitCast | CastKind::NoOp => { - if self.ast_context.is_function_pointer(target_cty.ctype) - || self.ast_context.is_function_pointer(source_cty.ctype) - { - let source_ty = self - .type_converter - .borrow_mut() - .convert(&self.ast_context, source_cty.ctype)?; - let target_ty = self - .type_converter - .borrow_mut() - .convert(&self.ast_context, target_cty.ctype)?; - - if source_ty == target_ty { - return Ok(val); - } - - self.import_type(source_cty.ctype); - self.import_type(target_cty.ctype); - - val.and_then(|val| { - Ok(WithStmts::new_unsafe_val(transmute_expr( - source_ty, target_ty, val, - ))) - }) - } else { - // Normal case - let target_ty = self.convert_type(target_cty.ctype)?; - Ok(val.map(|val| mk().cast_expr(val, target_ty))) - } + self.convert_pointer_to_pointer_cast(source_cty.ctype, target_cty.ctype, val) } - CastKind::IntegralToPointer - if self.ast_context.is_function_pointer(target_cty.ctype) => - { - let target_ty = self.convert_type(target_cty.ctype)?; - val.and_then(|x| { - self.use_crate(ExternCrate::Libc); - let intptr_t = mk().abs_path_ty(vec!["libc", "intptr_t"]); - let intptr = mk().cast_expr(x, intptr_t.clone()); - if ctx.is_const { - return Err(format_translation_err!( - None, - "cannot transmute integers to Option in `const` context", - )); - } - Ok(WithStmts::new_unsafe_val(transmute_expr( - intptr_t, target_ty, intptr, - ))) - }) + CastKind::IntegralToPointer => { + self.convert_integral_to_pointer_cast(ctx, source_cty.ctype, target_cty.ctype, val) } - CastKind::IntegralToPointer - | CastKind::PointerToIntegral - | CastKind::IntegralCast + CastKind::PointerToIntegral => self.convert_pointer_to_integral_cast( + ctx, + source_cty.ctype, + target_cty.ctype, + val, + expr, + ), + + CastKind::IntegralCast | CastKind::FloatingCast | CastKind::FloatingToIntegral | CastKind::IntegralToFloating | CastKind::BooleanToSignedIntegral => { - if kind == CastKind::PointerToIntegral && ctx.is_const { - return Err(format_translation_err!( - None, - "cannot observe pointer values in `const` context", - )); - } let target_ty = self.convert_type(target_cty.ctype)?; - let source_ty = self.convert_type(source_cty.ctype)?; if let CTypeKind::LongDouble = target_ty_kind { if ctx.is_const { return Err(format_translation_err!( - None, - "f128 cannot be used in constants because `f128::f128::new` is not `const`", - )); + None, + "f128 cannot be used in constants because \ + `f128::f128::new` is not `const`", + )); } self.use_crate(ExternCrate::F128); @@ -4621,35 +4553,13 @@ impl<'c> Translation<'c> { ) }) } else if target_ty_kind.is_floating_type() && source_ty_kind.is_bool() { - val.and_then(|x| { - Ok(WithStmts::new_val(mk().cast_expr( - mk().cast_expr(x, mk().path_ty(vec!["u8"])), - target_ty, - ))) - }) - } else if target_ty_kind.is_pointer() && source_ty_kind.is_bool() { - val.and_then(|x| { - self.use_crate(ExternCrate::Libc); - Ok(WithStmts::new_val(mk().cast_expr( - mk().cast_expr(x, mk().abs_path_ty(vec!["libc", "size_t"])), - target_ty, - ))) - }) + Ok(val.map(|val| { + mk().cast_expr(mk().cast_expr(val, mk().path_ty(vec!["u8"])), target_ty) + })) + } else if let &CTypeKind::Enum(..) = source_ty_kind { + val.result_map(|val| self.convert_cast_from_enum(target_cty.ctype, val)) } else { - // Other numeric casts translate to Rust `as` casts, - // unless the cast is to a function pointer then use `transmute`. - val.and_then(|x| { - if self.ast_context.is_function_pointer(source_cty.ctype) { - Ok(WithStmts::new_unsafe_val(transmute_expr( - source_ty, target_ty, x, - ))) - } else if let &CTypeKind::Enum(..) = source_ty_kind { - self.convert_cast_from_enum(target_cty.ctype, x) - .map(WithStmts::new_val) - } else { - Ok(WithStmts::new_val(mk().cast_expr(x, target_ty))) - } - }) + Ok(val.map(|val| mk().cast_expr(val, target_ty))) } } @@ -4981,25 +4891,8 @@ impl<'c> Translation<'c> { ) -> TranslationResult> { let ty = &self.ast_context.resolve_type(ty_id).kind; - Ok(if self.ast_context.is_function_pointer(ty_id) { - if target { - mk().method_call_expr(val, "is_some", vec![]) - } else { - mk().method_call_expr(val, "is_none", vec![]) - } - } else if ty.is_pointer() { - // TODO: `pointer::is_null` becomes stably const in Rust 1.84. - if ctx.is_const { - return Err(format_translation_err!( - None, - "cannot check nullity of pointer in `const` context", - )); - } - let mut res = mk().method_call_expr(val, "is_null", vec![]); - if target { - res = mk().unary_expr(UnOp::Not(Default::default()), res) - } - res + Ok(if ty.is_pointer() { + self.convert_pointer_is_null(ctx, ty_id, val, !target)? } else if ty.is_bool() { if target { val diff --git a/c2rust-transpile/src/translator/operators.rs b/c2rust-transpile/src/translator/operators.rs index a8f0a612e4..c63dfdc900 100644 --- a/c2rust-transpile/src/translator/operators.rs +++ b/c2rust-transpile/src/translator/operators.rs @@ -181,6 +181,7 @@ impl<'c> Translation<'c> { .and_then(|rhs_val| { let expr_ids = Some((lhs, rhs)); self.convert_binary_operator( + ctx, op, ty, expr_type_id.ctype, @@ -235,6 +236,7 @@ impl<'c> Translation<'c> { }; let ty = self.convert_type(compute_res_type_id.ctype)?; let mut val = self.convert_binary_operator( + ctx, bin_op, ty, compute_res_type_id.ctype, @@ -456,6 +458,7 @@ impl<'c> Translation<'c> { let val = if expr_or_comp_type_id.ctype == initial_lhs_type_id.ctype { self.convert_binary_operator( + ctx, op, ty, expr_type_id.ctype, @@ -472,6 +475,7 @@ impl<'c> Translation<'c> { let lhs = mk().cast_expr(read.clone(), lhs_type); let ty = self.convert_type(result_type_id.ctype)?; let mut val = self.convert_binary_operator( + ctx, op, ty, result_type_id.ctype, @@ -570,6 +574,7 @@ impl<'c> Translation<'c> { /// arguments be usable as rvalues. fn convert_binary_operator( &self, + ctx: ExprContext, op: c_ast::BinOp, ty: Box, ctype: CTypeId, @@ -611,46 +616,23 @@ impl<'c> Translation<'c> { c_ast::BinOp::ShiftRight => mk().binary_expr(BinOp::Shr(Default::default()), lhs, rhs), c_ast::BinOp::ShiftLeft => mk().binary_expr(BinOp::Shl(Default::default()), lhs, rhs), - c_ast::BinOp::EqualEqual => { - // Using is_none method for null comparison means we don't have to - // rely on the PartialEq trait as much and is also more idiomatic - let expr = if let Some((lhs_expr_id, rhs_expr_id)) = lhs_rhs_ids { - let fn_eq_null = self.ast_context.is_function_pointer(lhs_type.ctype) - && self.ast_context.is_null_expr(rhs_expr_id); - let null_eq_fn = self.ast_context.is_function_pointer(rhs_type.ctype) - && self.ast_context.is_null_expr(lhs_expr_id); - - if fn_eq_null { - mk().method_call_expr(lhs, "is_none", vec![]) - } else if null_eq_fn { - mk().method_call_expr(rhs, "is_none", vec![]) - } else { - mk().binary_expr(BinOp::Eq(Default::default()), lhs, rhs) - } + // Using is_none method for null comparison means we don't have to + // rely on the PartialEq trait as much and is also more idiomatic + c_ast::BinOp::EqualEqual | c_ast::BinOp::NotEqual => { + let (negated, bin_op) = if let c_ast::BinOp::EqualEqual = op { + (true, BinOp::Eq(Default::default())) } else { - mk().binary_expr(BinOp::Eq(Default::default()), lhs, rhs) + (false, BinOp::Ne(Default::default())) }; - bool_to_int(expr) - } - c_ast::BinOp::NotEqual => { - // Using is_some method for null comparison means we don't have to - // rely on the PartialEq trait as much and is also more idiomatic - let expr = if let Some((lhs_expr_id, rhs_expr_id)) = lhs_rhs_ids { - let fn_eq_null = self.ast_context.is_function_pointer(lhs_type.ctype) - && self.ast_context.is_null_expr(rhs_expr_id); - let null_eq_fn = self.ast_context.is_function_pointer(rhs_type.ctype) - && self.ast_context.is_null_expr(lhs_expr_id); - - if fn_eq_null { - mk().method_call_expr(lhs, "is_some", vec![]) - } else if null_eq_fn { - mk().method_call_expr(rhs, "is_some", vec![]) - } else { - mk().binary_expr(BinOp::Ne(Default::default()), lhs, rhs) + let expr = match lhs_rhs_ids { + Some((lhs_expr_id, _)) if self.ast_context.is_null_expr(lhs_expr_id) => { + self.convert_pointer_is_null(ctx, rhs_type.ctype, rhs, negated)? } - } else { - mk().binary_expr(BinOp::Ne(Default::default()), lhs, rhs) + Some((_, rhs_expr_id)) if self.ast_context.is_null_expr(rhs_expr_id) => { + self.convert_pointer_is_null(ctx, lhs_type.ctype, lhs, negated)? + } + _ => mk().binary_expr(bin_op, lhs, rhs), }; bool_to_int(expr) diff --git a/c2rust-transpile/src/translator/pointers.rs b/c2rust-transpile/src/translator/pointers.rs index 763aefe6e2..191d6da92d 100644 --- a/c2rust-transpile/src/translator/pointers.rs +++ b/c2rust-transpile/src/translator/pointers.rs @@ -8,9 +8,10 @@ use syn::{BinOp, Expr, Type, UnOp}; use crate::{ c_ast, diagnostics::{TranslationError, TranslationErrorKind, TranslationResult}, - translator::{cast_int, unwrap_function_pointer, ExprContext, Translation}, + format_translation_err, + translator::{cast_int, transmute_expr, unwrap_function_pointer, ExprContext, Translation}, with_stmts::WithStmts, - CExprId, CExprKind, CLiteral, CQualTypeId, CTypeId, CTypeKind, CastKind, + CExprId, CExprKind, CLiteral, CQualTypeId, CTypeId, CTypeKind, CastKind, ExternCrate, }; impl<'c> Translation<'c> { @@ -449,4 +450,147 @@ impl<'c> Translation<'c> { .borrow_mut() .convert_pointee(&self.ast_context, type_id) } + + pub fn convert_pointer_to_pointer_cast( + &self, + source_cty: CTypeId, + target_cty: CTypeId, + val: WithStmts>, + ) -> TranslationResult>> { + if self.ast_context.is_function_pointer(target_cty) + || self.ast_context.is_function_pointer(source_cty) + { + let source_ty = self + .type_converter + .borrow_mut() + .convert(&self.ast_context, source_cty)?; + let target_ty = self + .type_converter + .borrow_mut() + .convert(&self.ast_context, target_cty)?; + + if source_ty == target_ty { + return Ok(val); + } + + self.import_type(source_cty); + self.import_type(target_cty); + + val.and_then(|val| { + Ok(WithStmts::new_unsafe_val(transmute_expr( + source_ty, target_ty, val, + ))) + }) + } else { + // Normal case + let target_ty = self.convert_type(target_cty)?; + Ok(val.map(|val| mk().cast_expr(val, target_ty))) + } + } + + pub fn convert_integral_to_pointer_cast( + &self, + ctx: ExprContext, + source_cty: CTypeId, + target_cty: CTypeId, + val: WithStmts>, + ) -> TranslationResult>> { + let source_ty_kind = &self.ast_context.resolve_type(source_cty).kind; + let target_ty = self.convert_type(target_cty)?; + + if self.ast_context.is_function_pointer(target_cty) { + if ctx.is_const { + return Err(format_translation_err!( + None, + "cannot transmute integers to Option in `const` context", + )); + } + + self.use_crate(ExternCrate::Libc); + val.and_then(|mut val| { + // First cast the integer to pointer size + let intptr_t = mk().abs_path_ty(vec!["libc", "intptr_t"]); + val = mk().cast_expr(val, intptr_t.clone()); + + Ok(WithStmts::new_unsafe_val(transmute_expr( + intptr_t, target_ty, val, + ))) + }) + } else if source_ty_kind.is_bool() { + self.use_crate(ExternCrate::Libc); + Ok(val.map(|mut val| { + // First cast the boolean to pointer size + val = mk().cast_expr(val, mk().abs_path_ty(vec!["libc", "size_t"])); + mk().cast_expr(val, target_ty) + })) + } else if let &CTypeKind::Enum(..) = source_ty_kind { + val.result_map(|val| self.convert_cast_from_enum(target_cty, val)) + } else { + Ok(val.map(|val| mk().cast_expr(val, target_ty))) + } + } + + pub fn convert_pointer_to_integral_cast( + &self, + ctx: ExprContext, + source_cty: CTypeId, + target_cty: CTypeId, + val: WithStmts>, + expr: Option, + ) -> TranslationResult>> { + if ctx.is_const { + return Err(format_translation_err!( + None, + "cannot observe pointer values in `const` context", + )); + } + + let target_ty = self.convert_type(target_cty)?; + let source_ty = self.convert_type(source_cty)?; + let target_ty_kind = &self.ast_context.resolve_type(target_cty).kind; + + if self.ast_context.is_function_pointer(source_cty) { + val.and_then(|val| { + Ok(WithStmts::new_unsafe_val(transmute_expr( + source_ty, target_ty, val, + ))) + }) + } else if let &CTypeKind::Enum(enum_decl_id) = target_ty_kind { + let expr = expr.ok_or_else(|| format_err!("Casts to enums require a C ExprId"))?; + val.result_map(|val| { + self.convert_cast_to_enum(ctx, target_cty, enum_decl_id, Some(expr), val) + }) + } else { + Ok(val.map(|val| mk().cast_expr(val, target_ty))) + } + } + + pub fn convert_pointer_is_null( + &self, + ctx: ExprContext, + ptr_type: CTypeId, + mut val: Box, + is_null: bool, + ) -> TranslationResult> { + if self.ast_context.is_function_pointer(ptr_type) { + let method = if is_null { "is_none" } else { "is_some" }; + val = mk().method_call_expr(val, method, vec![]); + } else { + // TODO: `pointer::is_null` becomes stably const in Rust 1.84. + if ctx.is_const { + return Err(format_translation_err!( + None, + "cannot check nullity of pointer in `const` context", + )); + } + + val = mk().method_call_expr(val, "is_null", vec![]); + + if !is_null { + val = mk().unary_expr(UnOp::Not(Default::default()), val); + } + } + + Ok(val) + } }