diff --git a/cpp2rust/converter/converter.cpp b/cpp2rust/converter/converter.cpp index 7a40fb153..68802115f 100644 --- a/cpp2rust/converter/converter.cpp +++ b/cpp2rust/converter/converter.cpp @@ -555,6 +555,25 @@ void Converter::ConvertVarDecl(clang::VarDecl *decl) { } HoistMaterializedTempBindings hoist_temps(*this); + if (decl->isLocalVarDecl() && !IsGlobalVar(decl) && decl->hasInit() && + ReferencesVar(decl->getInit(), decl)) { + if (decl->getType()->isReferenceType()) { + llvm::report_fatal_error( + "self-referential initializer of a reference is not supported"); + } + if (DefaultInitHasSideEffects(ctx_, decl->getType())) { + llvm::report_fatal_error("self-referential initializer of a variable " + "whose default initialization has side effects " + "is not supported"); + } + hoisted_decls_.insert(decl); + if (ConvertVarDeclSkipInit(decl)) { + StrCat(token::kAssign, ConvertVarDefaultInit(decl), token::kSemiColon); + } + EmitHoistedInArmAssignment(decl); + return; + } + if (!ConvertVarDeclSkipInit(decl)) { // Skip global variables declared extern return; @@ -591,6 +610,10 @@ bool Converter::VisitVarDecl(clang::VarDecl *decl) { } if (IsGlobalVar(decl)) { + if (decl->hasInit() && ReferencesVar(decl->getInit(), decl)) { + llvm::report_fatal_error( + "self-referential initializer of a static variable is not supported"); + } ConvertGlobalVarDecl(decl); } else { ConvertVarDecl(decl); diff --git a/cpp2rust/converter/converter_lib.cpp b/cpp2rust/converter/converter_lib.cpp index 97afa0a39..a4731e8b0 100644 --- a/cpp2rust/converter/converter_lib.cpp +++ b/cpp2rust/converter/converter_lib.cpp @@ -1579,6 +1579,48 @@ GetAllVars(const clang::Stmt *stmt) { return vars; } +bool ReferencesVar(const clang::Stmt *stmt, const clang::VarDecl *var) { + if (!stmt) { + return false; + } + if (auto *decl_ref = clang::dyn_cast(stmt)) { + return decl_ref->getDecl() == var; + } + if (clang::isa(stmt)) { + return false; + } + return std::ranges::any_of(stmt->children(), [var](const clang::Stmt *child) { + return ReferencesVar(child, var); + }); +} + +bool DefaultInitHasSideEffects(const clang::ASTContext &ctx, + clang::QualType type) { + auto *record = ctx.getBaseElementType(type)->getAsCXXRecordDecl(); + if (!record || record->hasTrivialDefaultConstructor()) { + return false; + } + if (!record->hasDefaultConstructor() || + record->hasUserProvidedDefaultConstructor()) { + return true; + } + for (const auto &base : record->bases()) { + if (DefaultInitHasSideEffects(ctx, base.getType())) { + return true; + } + } + for (auto *field : record->fields()) { + if (auto *init = field->getInClassInitializer()) { + if (init->HasSideEffects(ctx)) { + return true; + } + } else if (DefaultInitHasSideEffects(ctx, field->getType())) { + return true; + } + } + return false; +} + bool ReadsMemory(const clang::Stmt *stmt) { if (auto *cast = clang::dyn_cast(stmt); cast && cast->getCastKind() == clang::CK_LValueToRValue) { diff --git a/cpp2rust/converter/converter_lib.h b/cpp2rust/converter/converter_lib.h index afce5742b..d0b91b2f9 100644 --- a/cpp2rust/converter/converter_lib.h +++ b/cpp2rust/converter/converter_lib.h @@ -279,6 +279,11 @@ clang::Expr *GetCallee(clang::CallExpr *expr); std::unordered_set GetAllVars(const clang::Stmt *stmt); +bool ReferencesVar(const clang::Stmt *stmt, const clang::VarDecl *var); + +bool DefaultInitHasSideEffects(const clang::ASTContext &ctx, + clang::QualType type); + bool ReferencesThis(const clang::Stmt *stmt); // Whether evaluating `stmt` reads a value from memory, e.g., a pointer. diff --git a/tests/ub/out/refcount/self_referencing_initializer.rs b/tests/ub/out/refcount/self_referencing_initializer.rs new file mode 100644 index 000000000..b374e835e --- /dev/null +++ b/tests/ub/out/refcount/self_referencing_initializer.rs @@ -0,0 +1,28 @@ +extern crate libcc2rs; +use libcc2rs::*; +use std::cell::RefCell; +use std::collections::BTreeMap; +use std::io::prelude::*; +use std::io::{Read, Seek, Write}; +use std::os::fd::AsFd; +use std::rc::{Rc, Weak}; +#[derive(Clone, Record, ByteRepr, VaArg, FnPtrArg, Default)] +#[byte_size(8)] +pub struct pair { + #[offset(0)] + pub a: i32, + #[offset(4)] + pub b: i32, +} +pub fn main() { + __cpp2rust_init_globals(); + std::process::exit(main_0()); +} +fn main_0() -> i32 { + let mut x: i32 = 0_i32; + x = (x + 1); + let mut p: pair = ::default(); + p = pair { a: 1, b: (p.a + 1) }; + return (x + p.b); +} +pub fn __cpp2rust_init_globals() {} diff --git a/tests/ub/out/unsafe/self_referencing_initializer.rs b/tests/ub/out/unsafe/self_referencing_initializer.rs new file mode 100644 index 000000000..f33614c65 --- /dev/null +++ b/tests/ub/out/unsafe/self_referencing_initializer.rs @@ -0,0 +1,31 @@ +extern crate libc; +use libc::*; +extern crate libcc2rs; +use libcc2rs::*; +use std::collections::BTreeMap; +use std::io::{Read, Seek, Write}; +use std::os::fd::{AsFd, FromRawFd, IntoRawFd}; +use std::rc::Rc; +#[repr(C)] +#[derive(Copy, Clone, VaArg, FnPtrArg, Default)] +pub struct pair { + pub a: i32, + pub b: i32, +} +pub fn main() { + unsafe { + __cpp2rust_init_globals(); + std::process::exit(main_0() as i32); + } +} +unsafe fn main_0() -> i32 { + let mut x: i32 = 0_i32; + x = ((x) + (1)); + let mut p: pair = ::default(); + p = pair { + a: 1, + b: ((p.a) + (1)), + }; + return ((x) + (p.b)); +} +pub unsafe fn __cpp2rust_init_globals() {} diff --git a/tests/ub/self_referencing_initializer.cpp b/tests/ub/self_referencing_initializer.cpp new file mode 100644 index 000000000..901f4f6c5 --- /dev/null +++ b/tests/ub/self_referencing_initializer.cpp @@ -0,0 +1,12 @@ +// nondet-result + +struct pair { + int a; + int b; +}; + +int main() { + int x = x + 1; + pair p = {1, p.a + 1}; + return x + p.b; +} diff --git a/tests/unit/out/refcount/self_referencing_initializer.rs b/tests/unit/out/refcount/self_referencing_initializer.rs new file mode 100644 index 000000000..80797122f --- /dev/null +++ b/tests/unit/out/refcount/self_referencing_initializer.rs @@ -0,0 +1,103 @@ +extern crate libcc2rs; +use libcc2rs::*; +use std::cell::RefCell; +use std::collections::BTreeMap; +use std::io::prelude::*; +use std::io::{Read, Seek, Write}; +use std::os::fd::AsFd; +use std::rc::{Rc, Weak}; +#[derive(Clone, Record, ByteRepr, VaArg, FnPtrArg, Default)] +#[byte_size(8)] +pub struct list_head { + #[offset(0)] + #[byte_size(8)] + pub next: Ptr, +} +#[derive(Clone, Record, ByteRepr, VaArg, FnPtrArg, Default)] +#[byte_size(16)] +pub struct node { + #[offset(0)] + pub value: i32, + #[offset(8)] + #[byte_size(8)] + pub value_ptr: Ptr, +} +#[derive(Clone, Record, ByteRepr, VaArg, FnPtrArg)] +#[byte_size(16)] +pub struct node_with_defaults { + #[offset(0)] + #[byte_size(8)] + pub next: Ptr, + #[offset(8)] + pub value: i32, +} +impl Default for node_with_defaults { + fn default() -> Self { + node_with_defaults { + next: Ptr::::null(), + value: 3, + } + } +} +pub fn init_0(mut l: Ptr) -> Ptr { + field!(l, next).write(Ptr::::null()); + return l; +} +pub fn main() { + __cpp2rust_init_globals(); + std::process::exit(main_0()); +} +fn main_0() -> i32 { + let list: Value = >::default(); + *list.borrow_mut() = list_head { + next: (list.as_pointer()), + }; + assert!(({ { (*list.borrow()).next.clone() } } == { (list.as_pointer()) })); + let n: Value = >::default(); + *n.borrow_mut() = node { + value: 42, + value_ptr: (field_ptr!(n.as_pointer(), value)), + }; + assert!(({ { (*n.borrow()).value_ptr.clone() } } == { (field_ptr!(n.as_pointer(), value)) })); + { (*n.borrow()).value_ptr.clone() }.write(7); + assert!(({ (*n.borrow()).value } == 7)); + let arr: Value> = Rc::new(RefCell::new( + (0..2) + .map(|_| ::default()) + .collect::>(), + )); + *arr.borrow_mut() = Box::new([ + list_head { + next: ((arr.as_pointer() as Ptr).offset(1)), + }, + list_head { + next: ((arr.as_pointer() as Ptr).offset(0)), + }, + ]); + assert!( + ({ { (*arr.borrow())[(0) as usize].next.clone() } } == { + ((arr.as_pointer() as Ptr).offset(1)) + }) + ); + assert!( + ({ { (*arr.borrow())[(1) as usize].next.clone() } } == { + ((arr.as_pointer() as Ptr).offset(0)) + }) + ); + let d: Value = >::default(); + *d.borrow_mut() = node_with_defaults { + next: (d.as_pointer()), + value: 3, + }; + assert!(({ { (*d.borrow()).next.clone() } } == { (d.as_pointer()) })); + assert!(({ (*d.borrow()).value } == 3)); + let called: Value = >::default(); + *called.borrow_mut() = list_head { + next: ({ init_0((called.as_pointer())) }), + }; + assert!(({ { (*called.borrow()).next.clone() } } == { (called.as_pointer()) })); + let mut size: usize = ::std::mem::size_of::(); + assert!((size == ::std::mem::size_of::())); + return 0; +} +pub fn __cpp2rust_init_globals() {} diff --git a/tests/unit/out/unsafe/self_referencing_initializer.rs b/tests/unit/out/unsafe/self_referencing_initializer.rs new file mode 100644 index 000000000..c851c493b --- /dev/null +++ b/tests/unit/out/unsafe/self_referencing_initializer.rs @@ -0,0 +1,85 @@ +extern crate libc; +use libc::*; +extern crate libcc2rs; +use libcc2rs::*; +use std::collections::BTreeMap; +use std::io::{Read, Seek, Write}; +use std::os::fd::{AsFd, FromRawFd, IntoRawFd}; +use std::rc::Rc; +#[repr(C)] +#[derive(Copy, Clone, VaArg, FnPtrArg, Default)] +pub struct list_head { + pub next: *mut list_head, +} +#[repr(C)] +#[derive(Copy, Clone, VaArg, FnPtrArg, Default)] +pub struct node { + pub value: i32, + pub value_ptr: *mut i32, +} +#[repr(C)] +#[derive(Copy, Clone, VaArg, FnPtrArg)] +pub struct node_with_defaults { + pub next: *mut node_with_defaults, + pub value: i32, +} +impl Default for node_with_defaults { + fn default() -> Self { + node_with_defaults { + next: std::ptr::null_mut(), + value: 3, + } + } +} +pub unsafe fn init_0(mut l: *mut list_head) -> *mut list_head { + (*l).next = std::ptr::null_mut(); + return l; +} +pub fn main() { + unsafe { + __cpp2rust_init_globals(); + std::process::exit(main_0() as i32); + } +} +unsafe fn main_0() -> i32 { + let mut list: list_head = ::default(); + list = list_head { + next: (&mut list as *mut list_head), + }; + assert!(((list.next) == (&mut list as *mut list_head))); + let mut n: node = ::default(); + n = node { + value: 42, + value_ptr: (&mut n.value as *mut i32), + }; + assert!(((n.value_ptr) == (&mut n.value as *mut i32))); + (*n.value_ptr) = 7; + assert!(((n.value) == (7))); + let mut arr: [list_head; 2] = [::default(); 2]; + arr = [ + list_head { + next: (&mut arr[(1) as usize] as *mut list_head), + }, + list_head { + next: (&mut arr[(0) as usize] as *mut list_head), + }, + ]; + assert!(((arr[(0) as usize].next) == (&mut arr[(1) as usize] as *mut list_head))); + assert!(((arr[(1) as usize].next) == (&mut arr[(0) as usize] as *mut list_head))); + let mut d: node_with_defaults = ::default(); + d = node_with_defaults { + next: (&mut d as *mut node_with_defaults), + value: 3, + }; + assert!(((d.next) == (&mut d as *mut node_with_defaults))); + assert!(((d.value) == (3))); + let mut called: list_head = ::default(); + called = list_head { + next: (unsafe { init_0((&mut called as *mut list_head)) }), + }; + assert!(((called.next) == (&mut called as *mut list_head))); + let mut size: usize = ::std::mem::size_of::(); + assert!(((size) == (::std::mem::size_of::()))); + return 0; +} +pub unsafe fn __cpp2rust_init_globals() {} diff --git a/tests/unit/self_referencing_initializer.cpp b/tests/unit/self_referencing_initializer.cpp new file mode 100644 index 000000000..27e318363 --- /dev/null +++ b/tests/unit/self_referencing_initializer.cpp @@ -0,0 +1,47 @@ +#include +#include + +struct list_head { + list_head *next; +}; + +struct node { + int value; + int *value_ptr; +}; + +struct node_with_defaults { + node_with_defaults *next = nullptr; + int value = 3; +}; + +static list_head *init(list_head *l) { + l->next = nullptr; + return l; +} + +int main() { + list_head list = {&list}; + assert(list.next == &list); + + node n = {42, &n.value}; + assert(n.value_ptr == &n.value); + *n.value_ptr = 7; + assert(n.value == 7); + + list_head arr[2] = {{&arr[1]}, {&arr[0]}}; + assert(arr[0].next == &arr[1]); + assert(arr[1].next == &arr[0]); + + node_with_defaults d{&d}; + assert(d.next == &d); + assert(d.value == 3); + + list_head called = {init(&called)}; + assert(called.next == &called); + + size_t size = sizeof(size); + assert(size == sizeof(size_t)); + + return 0; +}