Skip to content

Commit 3558805

Browse files
authored
Make [deny(unsafe_op_in_unsafe_fn)] optional (#1720)
We should not emit an extra `unsafe` block around most function bodies by default. The c2rust output is less verbose and easier to consume by TRACTOR tools if we silence the lint instead. We also do not want to inadvertently change the TRACTOR T&E verbosity baseline. Add a new flag `--deny-unsafe_op_in_unsafe_fn` so users opt into the feature implemented in PR #1662. Emission of unsafe blocks can be default-on for Rust 2027 editions if necessary.
2 parents 7c5f899 + dd855f4 commit 3558805

102 files changed

Lines changed: 1945 additions & 2380 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

c2rust-ast-builder/src/builder.rs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,7 @@ pub struct Builder {
455455
ext: Extern,
456456
attrs: Vec<Attribute>,
457457
span: Span,
458+
deny_unsafe_op_in_unsafe_fn: bool,
458459
}
459460

460461
impl Default for Builder {
@@ -468,6 +469,7 @@ impl Default for Builder {
468469
ext: Extern::None,
469470
attrs: Vec::new(),
470471
span: Span::call_site(),
472+
deny_unsafe_op_in_unsafe_fn: false,
471473
}
472474
}
473475
}
@@ -507,6 +509,13 @@ impl Builder {
507509
self.unsafety(Unsafety::Unsafe)
508510
}
509511

512+
pub fn deny_unsafe_op_in_unsafe_fn(self) -> Self {
513+
Builder {
514+
deny_unsafe_op_in_unsafe_fn: true,
515+
..self
516+
}
517+
}
518+
510519
pub fn constness<C: Make<Constness>>(self, constness: C) -> Self {
511520
let constness = constness.make(&self);
512521
Builder { constness, ..self }
@@ -1413,14 +1422,11 @@ impl Builder {
14131422
{
14141423
let sig = sig.make(&self);
14151424

1416-
if sig.unsafety.is_some() && !block.stmts.is_empty() {
1417-
// In edition 2024, `#[warn(unsafe_op_in_unsafe_fn)]` is on,
1418-
// so we need to wrap any `unsafe` operation in an `unsafe` block.
1419-
// For now, just wrap the whole function body in an `unsafe` block,
1420-
// which we can later narrow down to individual `unsafe` operations.
1421-
// In previous editions, this does not warn by default,
1422-
// but it can be turned on, and it's generally good practice anyways,
1423-
// so we do this unconditionally (i.e., no edition check needed).
1425+
if sig.unsafety.is_some() && !block.stmts.is_empty() && self.deny_unsafe_op_in_unsafe_fn {
1426+
// When `#[deny(unsafe_op_in_unsafe_fn)]` is in effect, unsafe operations
1427+
// inside an `unsafe fn` must be wrapped in an `unsafe` block.
1428+
// Wrap the whole function body for now; this can later be narrowed to
1429+
// individual operations.
14241430
let unsafe_expr = mk().unsafe_block_expr(block);
14251431
block = mk().block(vec![mk().expr_stmt(unsafe_expr)]);
14261432
}

c2rust-transpile/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ pub struct TranspilerConfig {
107107
pub preserve_unused_functions: bool,
108108
pub log_level: log::LevelFilter,
109109
pub edition: RustEdition,
110+
pub deny_unsafe_op_in_unsafe_fn: bool,
110111

111112
/// Run `c2rust-postprocess` after transpiling and potentially refactoring.
112113
pub postprocess: bool,

c2rust-transpile/src/translator/mod.rs

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1592,20 +1592,28 @@ impl<'c> Translation<'c> {
15921592
let mut features = vec![];
15931593
features.extend(self.features.borrow().iter());
15941594
features.extend(self.type_converter.borrow().features_used());
1595-
let mut pragmas: PragmaVec = vec![
1596-
(
1597-
"allow",
1598-
vec![
1599-
"non_upper_case_globals",
1600-
"non_camel_case_types",
1601-
"non_snake_case",
1602-
"dead_code",
1603-
"unused_mut",
1604-
"unused_assignments",
1605-
],
1606-
),
1607-
("deny", vec!["unsafe_op_in_unsafe_fn"]),
1595+
1596+
let mut allow = vec![
1597+
"non_upper_case_globals",
1598+
"non_camel_case_types",
1599+
"non_snake_case",
1600+
"dead_code",
1601+
"unused_mut",
1602+
"unused_assignments",
16081603
];
1604+
let mut pragmas: PragmaVec = vec![];
1605+
if self.tcfg.deny_unsafe_op_in_unsafe_fn {
1606+
// Edition 2024 defaults to deny `unsafe_op_in_unsafe_fn` so the
1607+
// deny pragma only has an effect on older versions. Go for brevity.
1608+
if self.tcfg.edition < Edition2024 {
1609+
pragmas.push(("deny", vec!["unsafe_op_in_unsafe_fn"]));
1610+
}
1611+
} else if self.tcfg.edition >= Edition2024 {
1612+
// Allow generation of less verbose code (fn bodies not wrapped in unsafe).
1613+
allow.push("unsafe_op_in_unsafe_fn");
1614+
}
1615+
pragmas.push(("allow", allow));
1616+
16091617
if self.tcfg.cross_checks {
16101618
features.append(&mut vec!["plugin"]);
16111619
pragmas.push(("cross_check", vec!["yes"]));
@@ -1643,6 +1651,14 @@ impl<'c> Translation<'c> {
16431651
))
16441652
}
16451653

1654+
fn mk(&self) -> Builder {
1655+
let mut b = mk();
1656+
if self.tcfg.deny_unsafe_op_in_unsafe_fn {
1657+
b = b.deny_unsafe_op_in_unsafe_fn();
1658+
}
1659+
b
1660+
}
1661+
16461662
fn mk_cross_check(&self, mk: Builder, args: Vec<&str>) -> Builder {
16471663
if self.tcfg.cross_checks {
16481664
mk.call_attr("cross_check", args)
@@ -1847,7 +1863,7 @@ impl<'c> Translation<'c> {
18471863
let fn_decl = mk().fn_decl(fn_name.clone(), vec![], None, fn_ty.clone());
18481864
let fn_bare_decl = (vec![], None, fn_ty);
18491865
let fn_block = mk().block(sectioned_static_initializers);
1850-
let fn_attributes = self.mk_cross_check(mk(), vec!["none"]);
1866+
let fn_attributes = self.mk_cross_check(self.mk(), vec!["none"]);
18511867
let fn_item = fn_attributes
18521868
.unsafe_()
18531869
.extern_("C")
@@ -2434,6 +2450,12 @@ impl<'c> Translation<'c> {
24342450
mk().extern_("C")
24352451
};
24362452

2453+
// In Edition2024, `unsafe_op_in_unsafe_fn` is deny-by-default so we emit an allow pragma
2454+
// to silence warnings. Was this overridden by the `--deny_unsafe_op_in_unsafe_fn` flag?
2455+
if self.tcfg.deny_unsafe_op_in_unsafe_fn {
2456+
mk_ = mk_.deny_unsafe_op_in_unsafe_fn();
2457+
}
2458+
24372459
for attr in attrs {
24382460
mk_ = match attr {
24392461
c_ast::Attribute::AlwaysInline => mk_.call_attr("inline", vec!["always"]),

c2rust-transpile/tests/snapshots.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ fn config(edition: RustEdition) -> TranspilerConfig {
5656
preserve_unused_functions: false,
5757
log_level: log::LevelFilter::Warn,
5858
edition,
59+
deny_unsafe_op_in_unsafe_fn: false,
5960
postprocess: false,
6061
emit_build_files: false,
6162
binaries: Vec::new(),

c2rust-transpile/tests/snapshots/snapshots__transpile@alloca.c.2021.snap

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,36 +10,33 @@ expression: cat tests/snapshots/alloca.2021.rs
1010
unused_assignments,
1111
unused_mut
1212
)]
13-
#![deny(unsafe_op_in_unsafe_fn)]
1413
#[no_mangle]
1514
pub static mut TRUE: ::core::ffi::c_int = 1 as ::core::ffi::c_int;
1615
#[no_mangle]
1716
pub unsafe extern "C" fn alloca_sum(
1817
mut val1: ::core::ffi::c_int,
1918
mut val2: ::core::ffi::c_int,
2019
) -> ::core::ffi::c_int {
21-
unsafe {
22-
let mut c2rust_alloca_allocations: Vec<Vec<u8>> = Vec::new();
23-
let mut alloca1: *mut ::core::ffi::c_int = ::core::ptr::null_mut::<::core::ffi::c_int>();
24-
let mut alloca2: *mut ::core::ffi::c_int = ::core::ptr::null_mut::<::core::ffi::c_int>();
25-
if TRUE != 0 {
26-
c2rust_alloca_allocations.push(::std::vec::from_elem(
27-
0,
28-
::core::mem::size_of::<::core::ffi::c_int>() as usize,
29-
));
30-
alloca1 = c2rust_alloca_allocations.last_mut().unwrap().as_mut_ptr()
31-
as *mut ::core::ffi::c_void as *mut ::core::ffi::c_int;
32-
*alloca1 = val1;
33-
}
34-
if TRUE != 0 {
35-
c2rust_alloca_allocations.push(::std::vec::from_elem(
36-
0,
37-
::core::mem::size_of::<::core::ffi::c_int>() as usize,
38-
));
39-
alloca2 = c2rust_alloca_allocations.last_mut().unwrap().as_mut_ptr()
40-
as *mut ::core::ffi::c_void as *mut ::core::ffi::c_int;
41-
*alloca2 = val2;
42-
}
43-
return *alloca1 + *alloca2;
20+
let mut c2rust_alloca_allocations: Vec<Vec<u8>> = Vec::new();
21+
let mut alloca1: *mut ::core::ffi::c_int = ::core::ptr::null_mut::<::core::ffi::c_int>();
22+
let mut alloca2: *mut ::core::ffi::c_int = ::core::ptr::null_mut::<::core::ffi::c_int>();
23+
if TRUE != 0 {
24+
c2rust_alloca_allocations.push(::std::vec::from_elem(
25+
0,
26+
::core::mem::size_of::<::core::ffi::c_int>() as usize,
27+
));
28+
alloca1 = c2rust_alloca_allocations.last_mut().unwrap().as_mut_ptr()
29+
as *mut ::core::ffi::c_void as *mut ::core::ffi::c_int;
30+
*alloca1 = val1;
4431
}
32+
if TRUE != 0 {
33+
c2rust_alloca_allocations.push(::std::vec::from_elem(
34+
0,
35+
::core::mem::size_of::<::core::ffi::c_int>() as usize,
36+
));
37+
alloca2 = c2rust_alloca_allocations.last_mut().unwrap().as_mut_ptr()
38+
as *mut ::core::ffi::c_void as *mut ::core::ffi::c_int;
39+
*alloca2 = val2;
40+
}
41+
return *alloca1 + *alloca2;
4542
}

c2rust-transpile/tests/snapshots/snapshots__transpile@alloca.c.2024.snap

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,39 +7,37 @@ expression: cat tests/snapshots/alloca.2024.rs
77
non_camel_case_types,
88
non_snake_case,
99
non_upper_case_globals,
10+
unsafe_op_in_unsafe_fn,
1011
unused_assignments,
1112
unused_mut
1213
)]
13-
#![deny(unsafe_op_in_unsafe_fn)]
1414
#[unsafe(no_mangle)]
1515
pub static mut TRUE: ::core::ffi::c_int = 1 as ::core::ffi::c_int;
1616
#[unsafe(no_mangle)]
1717
pub unsafe extern "C" fn alloca_sum(
1818
mut val1: ::core::ffi::c_int,
1919
mut val2: ::core::ffi::c_int,
2020
) -> ::core::ffi::c_int {
21-
unsafe {
22-
let mut c2rust_alloca_allocations: Vec<Vec<u8>> = Vec::new();
23-
let mut alloca1: *mut ::core::ffi::c_int = ::core::ptr::null_mut::<::core::ffi::c_int>();
24-
let mut alloca2: *mut ::core::ffi::c_int = ::core::ptr::null_mut::<::core::ffi::c_int>();
25-
if TRUE != 0 {
26-
c2rust_alloca_allocations.push(::std::vec::from_elem(
27-
0,
28-
::core::mem::size_of::<::core::ffi::c_int>() as usize,
29-
));
30-
alloca1 = c2rust_alloca_allocations.last_mut().unwrap().as_mut_ptr()
31-
as *mut ::core::ffi::c_void as *mut ::core::ffi::c_int;
32-
*alloca1 = val1;
33-
}
34-
if TRUE != 0 {
35-
c2rust_alloca_allocations.push(::std::vec::from_elem(
36-
0,
37-
::core::mem::size_of::<::core::ffi::c_int>() as usize,
38-
));
39-
alloca2 = c2rust_alloca_allocations.last_mut().unwrap().as_mut_ptr()
40-
as *mut ::core::ffi::c_void as *mut ::core::ffi::c_int;
41-
*alloca2 = val2;
42-
}
43-
return *alloca1 + *alloca2;
21+
let mut c2rust_alloca_allocations: Vec<Vec<u8>> = Vec::new();
22+
let mut alloca1: *mut ::core::ffi::c_int = ::core::ptr::null_mut::<::core::ffi::c_int>();
23+
let mut alloca2: *mut ::core::ffi::c_int = ::core::ptr::null_mut::<::core::ffi::c_int>();
24+
if TRUE != 0 {
25+
c2rust_alloca_allocations.push(::std::vec::from_elem(
26+
0,
27+
::core::mem::size_of::<::core::ffi::c_int>() as usize,
28+
));
29+
alloca1 = c2rust_alloca_allocations.last_mut().unwrap().as_mut_ptr()
30+
as *mut ::core::ffi::c_void as *mut ::core::ffi::c_int;
31+
*alloca1 = val1;
4432
}
33+
if TRUE != 0 {
34+
c2rust_alloca_allocations.push(::std::vec::from_elem(
35+
0,
36+
::core::mem::size_of::<::core::ffi::c_int>() as usize,
37+
));
38+
alloca2 = c2rust_alloca_allocations.last_mut().unwrap().as_mut_ptr()
39+
as *mut ::core::ffi::c_void as *mut ::core::ffi::c_int;
40+
*alloca2 = val2;
41+
}
42+
return *alloca1 + *alloca2;
4543
}

0 commit comments

Comments
 (0)