Skip to content
Open
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
49 changes: 27 additions & 22 deletions c2rust-transpile/src/convert_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,44 +268,49 @@ impl TypeConverter {
Ok(mk().unsafe_().extern_("C").barefn_ty(fn_ty))
}

/// Converts the qualified type of a pointer.
pub fn convert_pointer(
&mut self,
ctxt: &TypedAstContext,
qtype: CQualTypeId,
) -> TranslationResult<Box<Type>> {
let mutbl = if qtype.qualifiers.is_const {
Mutability::Immutable
let pointee_ty = self.convert_pointee(ctxt, qtype.ctype)?;

if let CTypeKind::Function(..) = ctxt.resolve_type(qtype.ctype).kind {
// Function pointers are translated to Option applied to the function type
// in order to support NULL function pointers natively
let param = mk().angle_bracketed_args(vec![pointee_ty]);
Ok(mk().path_ty(vec![mk().path_segment_with_args("Option", param)]))
} else {
Mutability::Mutable
};
let mutbl = if qtype.qualifiers.is_const {
Mutability::Immutable
} else {
Mutability::Mutable
};

match ctxt.resolve_type(qtype.ctype).kind {
Ok(mk().set_mutbl(mutbl).ptr_ty(pointee_ty))
}
}

/// Converts the pointee type of a pointer.
pub fn convert_pointee(
&mut self,
ctxt: &TypedAstContext,
ctype: CTypeId,
) -> TranslationResult<Box<Type>> {
match ctxt.resolve_type(ctype).kind {
// While void converts to () in function returns, it converts to c_void
// in the case of pointers.
CTypeKind::Void => Ok(mk()
.set_mutbl(mutbl)
.ptr_ty(mk().abs_path_ty(vec!["core", "ffi", "c_void"]))),
CTypeKind::Void => Ok(mk().abs_path_ty(vec!["core", "ffi", "c_void"])),

CTypeKind::VariableArray(mut elt, _len) => {
while let CTypeKind::VariableArray(elt_, _) = ctxt.resolve_type(elt).kind {
elt = elt_
}
let child_ty = self.convert(ctxt, elt)?;
Ok(mk().set_mutbl(mutbl).ptr_ty(child_ty))
}

// Function pointers are translated to Option applied to the function type
// in order to support NULL function pointers natively
CTypeKind::Function(..) => {
let fn_ty = self.convert(ctxt, qtype.ctype)?;
let param = mk().angle_bracketed_args(vec![fn_ty]);
Ok(mk().path_ty(vec![mk().path_segment_with_args("Option", param)]))
self.convert(ctxt, elt)
}

_ => {
let child_ty = self.convert(ctxt, qtype.ctype)?;
Ok(mk().set_mutbl(mutbl).ptr_ty(child_ty))
}
_ => self.convert(ctxt, ctype),
}
}

Expand Down
47 changes: 29 additions & 18 deletions c2rust-transpile/src/translator/literals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,31 +151,42 @@ impl<'c> Translation<'c> {
Ok(WithStmts::new_val(val))
}

CLiteral::String(ref val, width) => {
let mut val = val.to_owned();
let num_elems = match self.ast_context.resolve_type(ty.ctype).kind {
CTypeKind::ConstantArray(_elem_ty, num_elems) => num_elems,
ref kind => {
panic!("String literal with unknown size: {val:?}, kind = {kind:?}")
}
};

// Match the literal size to the expected size padding with zeros as needed
let size = num_elems * (width as usize);
val.resize(size, 0);
CLiteral::String(ref bytes, element_size) => {
let bytes_padded = self.string_literal_bytes(ty.ctype, bytes, element_size);

// std::mem::transmute::<[u8; size], ctype>(*b"xxxx")
let u8_ty = mk().path_ty(vec!["u8"]);
let width_lit = mk().lit_expr(mk().int_unsuffixed_lit(val.len() as u128));
Ok(WithStmts::new_unsafe_val(transmute_expr(
mk().array_ty(u8_ty, width_lit),
let array_ty = mk().array_ty(
mk().ident_ty("u8"),
mk().lit_expr(bytes_padded.len() as u128),
);
let val = transmute_expr(
array_ty,
self.convert_type(ty.ctype)?,
mk().unary_expr(UnOp::Deref(Default::default()), mk().lit_expr(val)),
)))
mk().unary_expr(UnOp::Deref(Default::default()), mk().lit_expr(bytes_padded)),
);
Ok(WithStmts::new_unsafe_val(val))
}
}
}

/// Returns the bytes of a string literal, including any additional zero bytes to pad the
/// literal to the expected size.
pub fn string_literal_bytes(&self, ctype: CTypeId, bytes: &[u8], element_size: u8) -> Vec<u8> {
let num_elems = match self.ast_context.resolve_type(ctype).kind {
CTypeKind::ConstantArray(_, num_elems) => num_elems,
ref kind => {
panic!("String literal with unknown size: {bytes:?}, kind = {kind:?}")
}
};

let size = num_elems * (element_size as usize);
let mut bytes_padded = Vec::with_capacity(size);
bytes_padded.extend(bytes);
bytes_padded.resize(size, 0);

bytes_padded
}

/// Convert an initialization list into an expression. These initialization lists can be
/// used as array literals, struct literals, and union literals in code.
pub fn convert_init_list(
Expand Down
196 changes: 116 additions & 80 deletions c2rust-transpile/src/translator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3071,6 +3071,14 @@ impl<'c> Translation<'c> {
.convert(&self.ast_context, type_id)
}

fn convert_pointee_type(&self, type_id: CTypeId) -> TranslationResult<Box<Type>> {
self.import_type(type_id);

self.type_converter
.borrow_mut()
.convert_pointee(&self.ast_context, type_id)
}

/// Construct an expression for a NULL at any type, including forward declarations,
/// function pointers, and normal pointers.
fn null_ptr(&self, type_id: CTypeId, is_static: bool) -> TranslationResult<Box<Expr>> {
Expand All @@ -3085,13 +3093,8 @@ impl<'c> Translation<'c> {
let ty = self.convert_type(type_id)?;
let mut zero = mk().lit_expr(mk().int_unsuffixed_lit(0));
if is_static && !pointee.qualifiers.is_const {
let mut qtype = pointee;
qtype.qualifiers.is_const = true;
let ty_ = self
.type_converter
.borrow_mut()
.convert_pointer(&self.ast_context, qtype)?;
zero = mk().cast_expr(zero, ty_);
let ty_ = self.convert_pointee_type(pointee.ctype)?;
zero = mk().cast_expr(zero, mk().ptr_ty(ty_));
}
Ok(mk().cast_expr(zero, ty))
}
Expand Down Expand Up @@ -4705,80 +4708,12 @@ impl<'c> Translation<'c> {
return Ok(val);
}

let pointee = self
.ast_context
.get_pointee_qual_type(target_cty.ctype)
.unwrap_or_else(|| panic!("dereferencing a non-pointer"));

let is_const = pointee.qualifiers.is_const;

// Handle literals by looking at the next level of expr nesting. Avoid doing this
// for expressions that will be translated as const macros, because emitting the
// name of the const macro only occurs if we process the expr_id with a direct call
// to `convert_expr`.
let expr_kind = expr.map(|e| &self.ast_context.index(e).kind);
let translate_as_macro = expr
.map(|e| {
self.convert_const_macro_expansion(ctx, e, None)
.ok()
.flatten()
.is_some()
})
.unwrap_or(false);
match expr_kind {
Some(&CExprKind::Literal(_, CLiteral::String(ref bytes, 1)))
if is_const && !translate_as_macro =>
{
let target_ty = self.convert_type(target_cty.ctype)?;

let mut bytes = bytes.to_owned();
bytes.push(0);
let byte_literal = mk().lit_expr(bytes);
let val =
mk().cast_expr(byte_literal, mk().ptr_ty(mk().path_ty(vec!["u8"])));
let val = mk().cast_expr(val, target_ty);
Ok(WithStmts::new_val(val))
}
_ => {
// Variable length arrays are already represented as pointers.
if let CTypeKind::VariableArray(..) = source_ty_kind {
Ok(val)
} else {
let method = if is_const || ctx.is_static {
"as_ptr"
} else {
"as_mut_ptr"
};

let call = val.map(|x| mk().method_call_expr(x, method, vec![]));

// If the target pointee type is different from the source element type,
// then we need to cast the ptr type as well.
let call = match source_ty_kind.element_ty() {
None => call,
Some(source_element_ty) if source_element_ty == pointee.ctype => {
call
}
Some(_) => {
let target_ty = self.convert_type(target_cty.ctype)?;
call.map(|ptr| mk().cast_expr(ptr, target_ty))
}
};

// Static arrays can now use as_ptr. Can also cast that const ptr to a
// mutable pointer as we do here:
if ctx.is_static && !is_const {
return Ok(call.map(|val| {
let inferred_type = mk().infer_ty();
let ptr_type = mk().mutbl().ptr_ty(inferred_type);
mk().cast_expr(val, ptr_type)
}));
}

Ok(call)
}
}
// Variable length arrays are already represented as pointers.
if let CTypeKind::VariableArray(..) = source_ty_kind {
return Ok(val);
}

self.convert_address_of(ctx, expr, source_cty, target_cty, val, true)
}

CastKind::NullToPointer => {
Expand Down Expand Up @@ -4938,6 +4873,107 @@ impl<'c> Translation<'c> {
val.map(|x| mk().cast_expr(x, target_ty))
}

pub fn convert_address_of(
&self,
ctx: ExprContext,
arg: Option<CExprId>,
arg_cty: CQualTypeId,
pointer_cty: CQualTypeId,
mut val: WithStmts<Box<Expr>>,
is_array_decay: bool,
) -> TranslationResult<WithStmts<Box<Expr>>> {
let arg_expr_kind = arg.map(|arg| &self.ast_context.index(arg).kind);
let pointee_cty = self
.ast_context
.get_pointee_qual_type(pointer_cty.ctype)
.ok_or_else(|| TranslationError::generic("Address-of should return a pointer"))?;
let arg_is_macro = arg.map_or(false, |arg| {
matches!(
self.convert_const_macro_expansion(ctx, arg, None),
Ok(Some(_))
)
});

let mut needs_cast = false;
let mut ref_cast_pointee_ty = None;
let mutbl = if pointee_cty.qualifiers.is_const {
Mutability::Immutable
} else if ctx.is_static {
// static variable initializers aren't able to use &mut, so we work around that
// by using & and an extra cast through & to *const to *mut
// TODO: Rust 1.83: Allowed, so this can be removed.
needs_cast = true;
Mutability::Immutable
} else {
Mutability::Mutable
};

// String literals are translated with a transmute, which produces a temporary.
// Taking the address of a temporary leaves a dangling pointer. So instead,
// cast the string literal directly so that its 'static lifetime is preserved.
if let (
Some(&CExprKind::Literal(literal_cty, CLiteral::String(ref bytes, element_size @ 1))),
false,
) = (arg_expr_kind, arg_is_macro)
{
let bytes_padded = self.string_literal_bytes(literal_cty.ctype, bytes, element_size);
let len = bytes_padded.len();
val = WithStmts::new_val(mk().lit_expr(bytes_padded));

if is_array_decay {
ref_cast_pointee_ty = Some(mk().ident_ty("u8"));
} else {
ref_cast_pointee_ty =
Some(mk().array_ty(mk().ident_ty("u8"), mk().lit_expr(len as u128)));
}
needs_cast = true;
} else {
let arg_cty_kind = &self.ast_context.resolve_type(arg_cty.ctype).kind;

if is_array_decay {
let method = match mutbl {
Mutability::Mutable => "as_mut_ptr",
Mutability::Immutable => "as_ptr",
};
val = val.map(|val| mk().method_call_expr(val, method, vec![]));

// If the target pointee type is different from the source element type,
// then we need to cast the ptr type as well.
if arg_cty_kind.element_ty().map_or(false, |arg_element_cty| {
arg_element_cty != pointee_cty.ctype
}) {
needs_cast = true;
}
} else {
val = val.map(|val| mk().set_mutbl(mutbl).addr_of_expr(val));

// Add an intermediate reference-to-pointer cast if the context needs
// reference-to-pointer decay, or if another cast follows.
if ctx.decay_ref.is_yes() || needs_cast {
ref_cast_pointee_ty = Some(
self.type_converter
.borrow_mut()
.convert_pointee(&self.ast_context, arg_cty.ctype)?,
);
}
}
}

// Perform an intermediate reference-to-pointer cast if needed.
// TODO: Rust 1.76: Use `ptr::from_ref`.
if let Some(pointee_ty) = ref_cast_pointee_ty {
val = val.map(|val| mk().cast_expr(val, mk().set_mutbl(mutbl).ptr_ty(pointee_ty)));
}

// Perform a final cast to the target type if needed.
if needs_cast {
let pointer_ty = self.convert_type(pointer_cty.ctype)?;
val = val.map(|val| mk().cast_expr(val, pointer_ty));
}

Ok(val)
}

pub fn implicit_default_expr(
&self,
ty_id: CTypeId,
Expand Down
Loading