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
58 changes: 58 additions & 0 deletions c2rust-transpile/src/translator/literals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,64 @@ impl<'c> Translation<'c> {
bytes_padded
}

/// Convert a C compound literal expression to a Rust expression.
pub fn convert_compound_literal(
&self,
ctx: ExprContext,
qty: CQualTypeId,
val: CExprId,
override_ty: Option<CQualTypeId>,
) -> TranslationResult<WithStmts<Box<Expr>>> {
// C compound literals are lvalues, but equivalent Rust expressions generally are not.
// So if an address is needed, store it in an intermediate variable first.
if !ctx.needs_address() || ctx.expanding_macro.is_some() {
return self.convert_expr(ctx, val, override_ty);
}

let fresh_name = self.renamer.borrow_mut().fresh();
let fresh_ty = self.convert_type(override_ty.unwrap_or(qty).ctype)?;

// Translate the expression to be assigned to the fresh variable.
// It will be assigned by value, so we don't need its address anymore.
let val = self.convert_expr(ctx.set_needs_address(false), val, override_ty)?;

val.and_then(|val| {
// If we are translating a static variable,
// then the fresh variable should also be static.
if ctx.is_static {
let item = mk().mutbl().static_item(&fresh_name, fresh_ty, val);
let fresh_stmt = mk().item_stmt(item);
let mut val = WithStmts::new(vec![fresh_stmt], mk().ident_expr(fresh_name));

// Accessing a static variable is unsafe.
// In the current nightly, this applies also to taking a raw pointer,
// but this requirement was removed in later versions of the
// `raw_ref_op` feature.
if self.tcfg.edition < Edition2024 {
val.set_unsafe();
}

Ok(val)
} else {
let mutbl = if qty.qualifiers.is_const {
Mutability::Immutable
} else {
Mutability::Mutable
};
let local = mk().local(
mk().set_mutbl(mutbl).ident_pat(&fresh_name),
Some(fresh_ty),
Some(val),
);
let fresh_stmt = mk().local_stmt(Box::new(local));
Ok(WithStmts::new(
vec![fresh_stmt],
mk().ident_expr(fresh_name),
))
}
})
}

/// 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
275 changes: 102 additions & 173 deletions c2rust-transpile/src/translator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1667,54 +1667,6 @@ impl<'c> Translation<'c> {
}
}

fn static_initializer_is_unsafe(&self, expr_id: Option<CExprId>, qty: CQualTypeId) -> bool {
// SIMD types are always unsafe in statics
match self.ast_context.resolve_type(qty.ctype).kind {
CTypeKind::Vector(..) => return true,
CTypeKind::ConstantArray(ctype, ..) => {
let kind = &self.ast_context.resolve_type(ctype).kind;

if let CTypeKind::Vector(..) = kind {
return true;
}
}
_ => {}
}

// Get the initializer if there is one
let expr_id = match expr_id {
Some(expr_id) => expr_id,
None => return false,
};

// Look for code which can only be translated unsafely
let iter = DFExpr::new(&self.ast_context, expr_id.into());

for i in iter {
let expr_id = match i {
SomeId::Expr(expr_id) => expr_id,
_ => unreachable!("Found static initializer type other than expr"),
};

use CExprKind::*;
match self.ast_context[expr_id].kind {
DeclRef(_, _, LRValue::LValue) => return true,
ImplicitCast(_, _, cast_kind, _, _) | ExplicitCast(_, _, cast_kind, _, _) => {
use CastKind::*;
match cast_kind {
IntegralToPointer | FunctionToPointerDecay | PointerToIntegral => {
return true;
}
_ => {}
}
}
_ => {}
}
}

false
}

/// The purpose of this function is to decide on whether or not a static initializer's
/// translation is able to be compiled as a valid rust static initializer
fn static_initializer_is_uncompilable(
Expand Down Expand Up @@ -2156,21 +2108,47 @@ impl<'c> Translation<'c> {
ref attrs,
..
} if has_static_duration || has_thread_duration => {
if has_thread_duration {
self.use_feature("thread_local");
}

let new_name = &self
.renamer
.borrow()
.get(&decl_id)
.expect("Variables should already be renamed");

let mut static_def = if is_externally_visible {
mk_linkage(false, new_name, ident, self.tcfg.edition)
.pub_()
.extern_("C")
} else if self.cur_file.get().is_some() {
mk().pub_()
} else {
mk()
};

// Force mutability due to the potential for raw pointers occurring in the type
// and because we may be assigning to these variables in the external initializer
static_def = static_def.mutbl();

if has_thread_duration {
self.use_feature("thread_local");
static_def = static_def.single_attr("thread_local");
}

// Add static attributes
for attr in attrs {
static_def = match attr {
c_ast::Attribute::Used => static_def.single_attr("used"),
c_ast::Attribute::Section(name) => {
static_def.str_attr("link_section", name)
}
_ => continue,
};
}

let ctx = ctx.static_();

// Collect problematic static initializers and offload them to sections for the linker
// to initialize for us
let (ty, init) = if self.static_initializer_is_uncompilable(initializer, typ) {
if self.static_initializer_is_uncompilable(initializer, typ) {
// Note: We don't pass `is_const` through here. Extracted initializers are run
// outside of the static initializer, in a non-const context.
let ctx = ctx.not_const();
Expand Down Expand Up @@ -2199,55 +2177,22 @@ impl<'c> Translation<'c> {

self.add_static_initializer_to_section(ctx, new_name, typ, &mut init)?;

(ty, init)
Ok(ConvertedDecl::Item(
static_def.span(span).static_item(new_name, ty, init),
))
} else {
let ConvertedVariable { ty, mutbl: _, init } =
self.convert_variable(ctx.const_(), initializer, typ)?;
let mut init = init?;
// TODO: Replace this by relying entirely on
// WithStmts.is_unsafe() of the translated variable
if self.static_initializer_is_unsafe(initializer, typ) {
init.set_unsafe()
}
let init = init.to_unsafe_pure_expr().ok_or_else(|| {
format_err!("Expected no side-effects in static initializer")
let mut items = init.stmts_to_items().ok_or_else(|| {
format_err!("Expected only item statements in static initializer")
})?;
let init = init.to_unsafe_pure_expr().unwrap();
let item = static_def.span(span).static_item(new_name, ty, init);
items.push(item);

(ty, init)
};

let static_def = if is_externally_visible {
mk_linkage(false, new_name, ident, self.tcfg.edition)
.pub_()
.extern_("C")
} else if self.cur_file.get().is_some() {
mk().pub_()
} else {
mk()
};

// Force mutability due to the potential for raw pointers occurring in the type
// and because we may be assigning to these variables in the external initializer
let mut static_def = static_def.span(span).mutbl();
if has_thread_duration {
static_def = static_def.single_attr("thread_local");
}

// Add static attributes
for attr in attrs {
static_def = match attr {
c_ast::Attribute::Used => static_def.single_attr("used"),
c_ast::Attribute::Section(name) => static_def
.unsafety(attr_unsafety(self.tcfg.edition))
.str_attr("link_section", name)
.unsafety(Unsafety::Normal),
_ => continue,
}
Ok(ConvertedDecl::Items(items))
}

Ok(ConvertedDecl::Item(
static_def.static_item(new_name, ty, init),
))
}

Variable { .. } => Err(TranslationError::generic(
Expand Down Expand Up @@ -3478,53 +3423,73 @@ impl<'c> Translation<'c> {
}

let mut val = mk().path_expr(vec![rustname]);
let mut set_unsafe = false;

// If the variable is actually an `EnumConstant`, we need to add a cast to the
// expected integral type.
if let &CDeclKind::EnumConstant { .. } = decl {
val = self.convert_cast_from_enum(qual_ty.ctype, val)?;
}
match decl {
CDeclKind::EnumConstant { .. } => {
// If the variable is actually an `EnumConstant`, we need to add a cast to
// the expected integral type.
val = self.convert_cast_from_enum(qual_ty.ctype, val)?;
}

// If we are referring to a function and need its address, we
// need to cast it to fn() to ensure that it has a real address.
let mut set_unsafe = false;
if ctx.needs_address() {
if let CDeclKind::Function { parameters, .. } = decl {
let ty = self.convert_type(qual_ty.ctype)?;
let actual_ty = self
.type_converter
.borrow_mut()
.knr_function_type_with_parameters(
&self.ast_context,
qual_ty.ctype,
parameters,
)?;
if let Some(actual_ty) = actual_ty {
if actual_ty != ty {
// If we're casting a concrete function to
// a K&R function pointer type, use transmute
self.import_type(qual_ty.ctype);

val = transmute_expr(actual_ty, ty, val);
set_unsafe = true;
}
} else {
let decl_kind = &self.ast_context[decl_id].kind;
let kind_with_declared_args =
self.ast_context.fn_decl_ty_with_declared_args(decl_kind);

if let Some(ty) = self
.ast_context
.type_for_kind(&kind_with_declared_args)
.map(CQualTypeId::new)
{
let ty = self.convert_type(ty.ctype)?;
val = mk().cast_expr(val, ty);
CDeclKind::Function { parameters, .. } => {
// If we are referring to a function and need its address, we
// need to cast it to fn() to ensure that it has a real address.
if ctx.needs_address() {
let ty = self.convert_type(qual_ty.ctype)?;
let actual_ty = self
.type_converter
.borrow_mut()
.knr_function_type_with_parameters(
&self.ast_context,
qual_ty.ctype,
parameters,
)?;
if let Some(actual_ty) = actual_ty {
if actual_ty != ty {
// If we're casting a concrete function to
// a K&R function pointer type, use transmute
self.import_type(qual_ty.ctype);

val = transmute_expr(actual_ty, ty, val);
set_unsafe = true;
}
} else {
val = mk().cast_expr(val, ty);
let decl_kind = &self.ast_context[decl_id].kind;
let kind_with_declared_args =
self.ast_context.fn_decl_ty_with_declared_args(decl_kind);

if let Some(ty) = self
.ast_context
.type_for_kind(&kind_with_declared_args)
.map(CQualTypeId::new)
{
let ty = self.convert_type(ty.ctype)?;
val = mk().cast_expr(val, ty);
} else {
val = mk().cast_expr(val, ty);
}
}
}
}

CDeclKind::Variable {
has_static_duration,
has_thread_duration,
..
} => {
// Accessing a static variable is unsafe.
// In the current nightly, this applies also to taking a raw pointer,
// but this requirement was removed in later versions of the
// `raw_ref_op` feature.
if (*has_static_duration || *has_thread_duration)
&& (self.tcfg.edition < Edition2024 || !ctx.needs_address())
{
set_unsafe = true;
}
}

_ => {}
}

if let CTypeKind::VariableArray(..) =
Expand Down Expand Up @@ -3908,43 +3873,7 @@ impl<'c> Translation<'c> {

Paren(_, val) => self.convert_expr(ctx, val, override_ty),

CompoundLiteral(qty, val) => {
if !ctx.needs_address() || ctx.is_const {
// consts have their intermediates' lifetimes extended.
return self.convert_expr(ctx, val, override_ty);
}

// C compound literals are lvalues, but equivalent Rust expressions generally are not.
// So if an address is needed, store it in an intermediate variable first.
let fresh_name = self.renamer.borrow_mut().fresh();
let fresh_ty = self.convert_type(override_ty.unwrap_or(qty).ctype)?;

// Translate the expression to be assigned to the fresh variable.
// It will be assigned by value, so we don't need its address anymore.
let val = self.convert_expr(ctx.set_needs_address(false), val, override_ty)?;

val.and_then(|val| {
let fresh_stmt = {
let mutbl = if qty.qualifiers.is_const {
Mutability::Immutable
} else {
Mutability::Mutable
};

let local = mk().local(
mk().set_mutbl(mutbl).ident_pat(&fresh_name),
Some(fresh_ty),
Some(val),
);
mk().local_stmt(Box::new(local))
};

Ok(WithStmts::new(
vec![fresh_stmt],
mk().ident_expr(fresh_name),
))
})
}
CompoundLiteral(qty, val) => self.convert_compound_literal(ctx, qty, val, override_ty),

InitList(ty, ref ids, opt_union_field_id, _) => {
self.convert_init_list(ctx, ty, ids, opt_union_field_id)
Expand Down
Loading
Loading