diff --git a/c2rust-transpile/src/translator/mod.rs b/c2rust-transpile/src/translator/mod.rs index 7d2d5bdccd..89fdc79b18 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -2589,68 +2589,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_true: 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_true)) }; 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 @@ -4384,79 +4360,36 @@ 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); @@ -4478,32 +4411,11 @@ impl<'c> Translation<'c> { target_ty, )) } 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 { - // 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 { - Ok(WithStmts::new_val(mk().cast_expr(x, target_ty))) - } - }) + Ok(val.map(|val| mk().cast_expr(val, target_ty))) } } @@ -4896,30 +4808,13 @@ 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 + if ty.is_pointer() { + self.convert_pointer_is_null(ctx, ty_id, val, !target) } else if ty.is_bool() { if target { - val + Ok(val) } else { - mk().unary_expr(UnOp::Not(Default::default()), val) + Ok(mk().unary_expr(UnOp::Not(Default::default()), val)) } } else { // One simplification we can make at the cost of inspecting `val` more closely: if `val` @@ -4966,11 +4861,11 @@ impl<'c> Translation<'c> { }; if target { - mk().binary_expr(BinOp::Ne(Default::default()), val, zero) + Ok(mk().binary_expr(BinOp::Ne(Default::default()), val, zero)) } else { - mk().binary_expr(BinOp::Eq(Default::default()), val, zero) + Ok(mk().binary_expr(BinOp::Eq(Default::default()), val, zero)) } - }) + } } pub fn with_scope(&self, f: F) -> A diff --git a/c2rust-transpile/src/translator/operators.rs b/c2rust-transpile/src/translator/operators.rs index 04e1aa73f0..f880c35a65 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, @@ -199,6 +200,7 @@ impl<'c> Translation<'c> { fn convert_assignment_operator_aux( &self, + ctx: ExprContext, bin_op_kind: BinOp, bin_op: c_ast::BinOp, read: Box, @@ -234,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, @@ -460,6 +463,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, @@ -476,6 +480,7 @@ impl<'c> Translation<'c> { let lhs = mk().cast_expr(read.clone(), lhs_type.clone()); let ty = self.convert_type(result_type_id.ctype)?; let mut val = self.convert_binary_operator( + ctx, op, ty, result_type_id.ctype, @@ -543,6 +548,7 @@ impl<'c> Translation<'c> { _ => panic!("Cannot convert non-assignment operator"), }; self.convert_assignment_operator_aux( + ctx, bin_op_kind, bin_op, read.clone(), @@ -568,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, @@ -610,45 +617,27 @@ impl<'c> Translation<'c> { 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) + 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, true)? } - } else { - mk().binary_expr(BinOp::Eq(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, true)? + } + _ => mk().binary_expr(BinOp::Eq(Default::default()), lhs, rhs), }; 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, false)? } - } 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, false)? + } + _ => mk().binary_expr(BinOp::Ne(Default::default()), lhs, rhs), }; bool_to_int(expr) diff --git a/c2rust-transpile/src/translator/pointers.rs b/c2rust-transpile/src/translator/pointers.rs index 87f6fca067..983eefd51b 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> { @@ -453,4 +454,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 { + 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"))?; + + Ok(self.enum_cast(target_cty, enum_decl_id, expr, val, source_ty, target_ty)) + } 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_true: bool, + ) -> TranslationResult> { + if self.ast_context.is_function_pointer(ptr_type) { + if is_true { + val = mk().method_call_expr(val, "is_none", vec![]); + } else { + val = mk().method_call_expr(val, "is_some", 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_true { + val = mk().unary_expr(UnOp::Not(Default::default()), val); + } + } + + Ok(val) + } }