Skip to content

Commit

Permalink
Merge branch 'main' into fix-typos
Browse files Browse the repository at this point in the history
  • Loading branch information
someguynamedjosh authored Jun 14, 2023
2 parents f8d9691 + 8f5919a commit 7f9901c
Show file tree
Hide file tree
Showing 19 changed files with 265 additions and 88 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ Dual licensed under MIT / Apache 2.0.
While this crate is `no_std` compatible, it still requires the `alloc` crate.

Version notes:
- Version `0.17.0` fixes a potential soundness issue, but removes support for
template parameters in the process. Lifetime parameters are still supported.
As of 2023-06-13, the compiler is practically sound with 0.16 but this may
stop being the case at any time and without warning.
- Version `0.13.0` and later contain checks for additional situations which
cause undefined behavior if not caught.
- Version `0.11.0` and later place restrictions on derive macros, earlier
Expand Down
4 changes: 2 additions & 2 deletions examples/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ouroboros_examples"
version = "0.15.7"
version = "0.17.0"
authors = ["Joshua Maros <[email protected]>"]
edition = "2018"
license = "MIT OR Apache-2.0"
Expand All @@ -21,7 +21,7 @@ std = []
__tokio = ["tokio", "std"]

[dependencies]
ouroboros = { version = "0.15.7", path = "../ouroboros" }
ouroboros = { version = "0.17.0", path = "../ouroboros" }
tokio = { version = "1.27.0", features = [ "macros", "rt" ], optional = true }

[dev-dependencies]
Expand Down
7 changes: 6 additions & 1 deletion examples/src/fail_tests/auto_covariant.stderr
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
error: Ouroboros cannot automatically determine if this type is covariant.

If it is covariant, it should be legal to convert any instance of that type to an instance of that type where all usages of 'this are replaced with a smaller lifetime. For example, Box<&'this i32> is covariant because it is legal to use it as a Box<&'a i32> where 'this: 'a. In contrast, Fn(&'this i32) cannot be used as Fn(&'a i32).
As an example, a Box<&'this ()> is covariant because it can be used as a
Box<&'smaller ()> for any lifetime smaller than 'this. In contrast,
a Fn(&'this ()) is not covariant because it cannot be used as a
Fn(&'smaller ()). In general, values that are safe to use with smaller
lifetimes than they were defined with are covariant, breaking this
guarantee means the value is not covariant.

To resolve this error, add #[covariant] or #[not_covariant] to the field.
--> src/fail_tests/auto_covariant.rs:11:12
Expand Down
109 changes: 62 additions & 47 deletions examples/src/ok_tests.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use alloc::borrow::ToOwned;
use alloc::boxed::Box;
use alloc::{boxed::Box, borrow::ToOwned};
use core::fmt::Debug;

use ouroboros::self_referencing;
Expand Down Expand Up @@ -32,7 +31,7 @@ struct ChainedAndUndocumented {
data: i32,
#[borrows(data)]
ref1: &'this i32,
#[borrows(data)]
#[borrows(ref1)]
ref2: &'this &'this i32,
}

Expand All @@ -51,32 +50,32 @@ struct AutoDetectCovarianceOnFieldsWithoutThis {
self_reference: &'this (),
}

/// This test just makes sure that the macro copes with a ton of template parameters being thrown at
/// it, specifically checking that the templates work fine even when a generated struct doesn't need
/// all of them. (E.G. heads will only contain 'd, A, and B.)
#[self_referencing]
struct TemplateMess<'d, A, B: 'static, C: 'static>
where
A: ?Sized,
B: 'static,
C: 'static,
{
external: &'d A,
data1: B,
#[borrows(data1)]
data2: &'this C,
data3: B,
#[borrows(mut data3)]
data4: &'this mut C,
}
// /// This test just makes sure that the macro copes with a ton of template parameters being thrown at
// /// it, specifically checking that the templates work fine even when a generated struct doesn't need
// /// all of them. (E.G. heads will only contain 'd, A, and B.)
// #[self_referencing]
// struct TemplateMess<'d, A, B: 'static, C: 'static>
// where
// A: ?Sized,
// B: 'static,
// C: 'static,
// {
// external: &'d A,
// data1: B,
// #[borrows(data1)]
// data2: &'this C,
// data3: B,
// #[borrows(mut data3)]
// data4: &'this mut C,
// }

/// Regression test for #46
#[self_referencing]
struct PreviouslyBrokeAutoGeneratedChecker<T: 'static> {
x: T,
#[borrows(mut x)]
y: &'this (),
}
// /// Regression test for #46
// #[self_referencing]
// struct PreviouslyBrokeAutoGeneratedChecker<T: 'static> {
// x: T,
// #[borrows(mut x)]
// y: &'this (),
// }

#[test]
fn box_and_ref() {
Expand Down Expand Up @@ -179,6 +178,16 @@ fn try_new_recover_heads() {
}
}

#[test]
fn into_heads() {
let bar = BoxAndRefBuilder {
data: 12,
dref_builder: |data| data,
}
.build();
assert!(bar.into_heads().data == 12);
}

#[test]
fn box_and_mut_ref() {
let mut bar = BoxAndMutRefBuilder {
Expand All @@ -191,25 +200,25 @@ fn box_and_mut_ref() {
assert!(bar.with_dref(|dref| **dref) == 34);
}

#[test]
fn template_mess() {
let ext_str = "Hello World!".to_owned();
let mut instance = TemplateMessBuilder {
external: &ext_str[..],
data1: "asdf".to_owned(),
data2_builder: |data1_contents| data1_contents,
data3: "asdf".to_owned(),
data4_builder: |data3_contents| data3_contents,
}
.build();
instance.with_external(|ext| assert_eq!(*ext, "Hello World!"));
instance.with_data1(|data| assert_eq!(data, "asdf"));
instance.with_data4_mut(|con| **con = "Modified".to_owned());
instance.with(|fields| {
assert!(**fields.data1 == **fields.data2);
assert!(*fields.data4 == "Modified");
});
}
// #[test]
// fn template_mess() {
// let ext_str = "Hello World!".to_owned();
// let mut instance = TemplateMessBuilder {
// external: &ext_str[..],
// data1: "asdf".to_owned(),
// data2_builder: |data1_contents| data1_contents,
// data3: "asdf".to_owned(),
// data4_builder: |data3_contents| data3_contents,
// }
// .build();
// instance.with_external(|ext| assert_eq!(*ext, "Hello World!"));
// instance.with_data1(|data| assert_eq!(data, "asdf"));
// instance.with_data4_mut(|con| **con = "Modified".to_owned());
// instance.with(|fields| {
// assert!(**fields.data1 == **fields.data2);
// assert!(*fields.data4 == "Modified");
// });
// }

const STATIC_INT: i32 = 456;
#[test]
Expand All @@ -236,6 +245,12 @@ fn single_lifetime() {
#[borrows(external)]
internal: &'this &'a str,
}

let external = "Hello world!".to_owned();
let instance = Struct::new(&external, |field_ref| field_ref);
drop(instance.borrow_external());
drop(instance.borrow_internal());
drop(instance);
}

#[test]
Expand Down
5 changes: 3 additions & 2 deletions ouroboros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ouroboros"
version = "0.15.7"
version = "0.17.0"
authors = ["Joshua Maros <[email protected]>"]
edition = "2018"
license = "MIT OR Apache-2.0"
Expand All @@ -11,7 +11,8 @@ repository = "https://github.com/joshua-maros/ouroboros"

[dependencies]
aliasable = "0.1.3"
ouroboros_macro = { version = "0.15.7", path = "../ouroboros_macro" }
ouroboros_macro = { version = "0.17.0", path = "../ouroboros_macro" }
static_assertions = "1.1.0"

[features]
default = ["std"]
Expand Down
3 changes: 2 additions & 1 deletion ouroboros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@
/// immutable: i32,
/// mutable: i32,
/// #[borrows(immutable, mut mutable)]
/// #[covariant]
/// #[not_covariant]
/// complex_data: ComplexData<'this, 'this>,
/// }
///
Expand Down Expand Up @@ -351,6 +351,7 @@ pub mod macro_help {
pub extern crate alloc;

pub use aliasable::boxed::AliasableBox;
pub use static_assertions::const_assert_eq;
use aliasable::boxed::UniqueBox;

pub struct CheckIfTypeIsStd<T>(core::marker::PhantomData<T>);
Expand Down
2 changes: 1 addition & 1 deletion ouroboros_macro/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ouroboros_macro"
version = "0.15.7"
version = "0.17.0"
authors = ["Joshua Maros <[email protected]>"]
edition = "2018"
license = "MIT OR Apache-2.0"
Expand Down
16 changes: 14 additions & 2 deletions ouroboros_macro/src/generate/constructor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub fn create_builder_and_constructor(
) -> Result<(Ident, TokenStream, TokenStream), Error> {
let struct_name = info.ident.clone();
let generic_args = info.generic_arguments();
let generic_args_with_static_lifetimes = info.generic_arguments_with_static_lifetimes();

let vis = if options.do_pub_extras {
info.vis.clone()
Expand Down Expand Up @@ -154,12 +155,23 @@ pub fn create_builder_and_constructor(
BuilderType::Sync => quote! { fn new },
};
let field_names: Vec<_> = info.fields.iter().map(|field| field.name.clone()).collect();
let internal_ident = &info.internal_ident;
let constructor_def = quote! {
#documentation
#vis #constructor_fn(#(#params),*) -> #struct_name <#(#generic_args),*> {
::ouroboros::macro_help::const_assert_eq!(
::core::mem::size_of::<#struct_name<#(#generic_args_with_static_lifetimes),*>>(),
::core::mem::size_of::<#internal_ident<#(#generic_args_with_static_lifetimes),*>>()
);
::ouroboros::macro_help::const_assert_eq!(
::core::mem::align_of::<#struct_name<#(#generic_args_with_static_lifetimes),*>>(),
::core::mem::align_of::<#internal_ident<#(#generic_args_with_static_lifetimes),*>>()
);
#(#code)*
Self {
#(#field_names),*
unsafe {
::core::mem::transmute(#internal_ident::<#(#generic_args),*> {
#(#field_names),*
})
}
}
};
Expand Down
25 changes: 25 additions & 0 deletions ouroboros_macro/src/generate/drop.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use crate::info_structures::StructInfo;
use proc_macro2::TokenStream;
use quote::quote;
use syn::Error;

pub fn create_drop_impl(info: &StructInfo) -> Result<TokenStream, Error> {
let ident = &info.ident;
let internal_ident = &info.internal_ident;
let generics = &info.generics;
let generic_args = info.generic_arguments();

let mut where_clause = quote! {};
if let Some(clause) = &generics.where_clause {
where_clause = quote! { #clause };
}
Ok(quote! {
impl #generics ::core::ops::Drop for #ident<#(#generic_args,)*> #where_clause {
fn drop(&mut self) {
unsafe {
::core::ptr::drop_in_place(::core::mem::transmute::<_, *mut #internal_ident <#(#generic_args,)*>>(self));
}
}
}
})
}
43 changes: 35 additions & 8 deletions ouroboros_macro/src/generate/into_heads.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ use quote::quote;

use crate::info_structures::{Options, StructInfo};

/// Returns the Heads struct.
pub fn make_heads(info: &StructInfo, options: Options) -> TokenStream {
/// Returns the Heads struct and a function to convert the original struct into a Heads instance.
pub fn make_into_heads(info: &StructInfo, options: Options) -> (TokenStream, TokenStream) {
let visibility = if options.do_pub_extras {
info.vis.clone()
} else {
Expand All @@ -13,12 +13,16 @@ pub fn make_heads(info: &StructInfo, options: Options) -> TokenStream {
let mut code = Vec::new();
let mut field_initializers = Vec::new();
let mut head_fields = Vec::new();
let internal_struct = &info.internal_ident;
// Drop everything in the reverse order of what it was declared in. Fields that come later
// are only dependent on fields that came before them.
for field in info.fields.iter().rev() {
let field_name = &field.name;
if !field.self_referencing {
code.push(quote! { let #field_name = self.#field_name; });
if field.self_referencing {
// Heads are fields that do not borrow anything.
code.push(quote! { ::core::mem::drop(this.#field_name); });
} else {
code.push(quote! { let #field_name = this.#field_name; });
if field.is_borrowed() {
field_initializers
.push(quote! { #field_name: ::ouroboros::macro_help::unbox(#field_name) });
Expand All @@ -27,9 +31,6 @@ pub fn make_heads(info: &StructInfo, options: Options) -> TokenStream {
}
let field_type = &field.typ;
head_fields.push(quote! { #visibility #field_name: #field_type });
} else {
// Heads are fields that do not borrow anything.
code.push(quote! { ::core::mem::drop(self.#field_name); });
}
}
for (ty, ident) in info.generic_consumers() {
Expand All @@ -51,6 +52,32 @@ pub fn make_heads(info: &StructInfo, options: Options) -> TokenStream {
#(#head_fields),*
}
};
let documentation = concat!(
"This function drops all internally referencing fields and returns only the ",
"[head fields](https://docs.rs/ouroboros/latest/ouroboros/attr.self_referencing.html#definitions) of this struct."
).to_owned();

heads_struct_def
let documentation = if !options.do_no_doc {
quote! {
#[doc=#documentation]
}
} else {
quote! { #[doc(hidden)] }
};

let generic_args = info.generic_arguments();
let into_heads_fn = quote! {
#documentation
#[allow(clippy::drop_ref)]
#[allow(clippy::drop_copy)]
#[allow(clippy::drop_non_drop)]
#visibility fn into_heads(self) -> Heads<#(#generic_args),*> {
let this: #internal_struct<#(#generic_args),*> = unsafe { ::core::mem::transmute(self) };
#(#code)*
Heads {
#(#field_initializers),*
}
}
};
(heads_struct_def, into_heads_fn)
}
1 change: 1 addition & 0 deletions ouroboros_macro/src/generate/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod constructor;
pub mod derives;
pub mod drop;
pub mod into_heads;
pub mod struc;
pub mod summon_checker;
Expand Down
Loading

0 comments on commit 7f9901c

Please sign in to comment.