diff --git a/CHANGELOG.md b/CHANGELOG.md index f1e71603cf..9aded7defe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ The minor version will be incremented upon a breaking change and the patch versi ### Fixes +- lang: Shorten invariant lifetimes during `Context` creation ([#4363](https://github.com/solana-foundation/anchor/pull/4363)). - ts: Guard recursive IDL layouts against stack overflows while preserving supported recursive types ([#4604](https://github.com/solana-foundation/anchor/pull/4604)). - idl: Bump version to 0.1.3 ([#4453](https://github.com/solana-foundation/anchor/pull/4453)). - lang: Migrate `anchor-syn` from syn 1.x to syn 2.0, allowing use of modern Rust syntax ([#4523](https://github.com/solana-foundation/anchor/issues/4523)). diff --git a/lang/syn/src/codegen/accounts/__shorten_invariant_lifetime.rs b/lang/syn/src/codegen/accounts/__shorten_invariant_lifetime.rs new file mode 100644 index 0000000000..a779eb9f10 --- /dev/null +++ b/lang/syn/src/codegen/accounts/__shorten_invariant_lifetime.rs @@ -0,0 +1,44 @@ +use { + super::{generics, ParsedGenerics}, + crate::AccountsStruct, + quote::quote, +}; + +pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream { + let name = &accs.ident; + let ParsedGenerics { + combined_generics, + trait_generics: _, + struct_generics, + where_clause, + } = generics(accs); + + let shorten_invariant_lifetime = if accs.generics.lt_token.is_some() { + let non_lifetime_generics = struct_generics + .iter() + .skip_while(|g| matches!(g, syn::GenericParam::Lifetime(_))) + .fold(quote! {}, |acc, g| quote! { #acc, #g }); + quote! { + pub unsafe fn __shorten_invariant_lifetime<'__a, '__info: '__a>( + value: &'__a mut #name<'__info #non_lifetime_generics>, + ) -> &'__a mut #name<'__a #non_lifetime_generics> { + unsafe { ::core::mem::transmute(value) } + } + } + } else { + quote! { + pub fn __shorten_invariant_lifetime(value: &mut Self) -> &mut Self { + value + } + } + }; + + quote! { + #[automatically_derived] + impl<#combined_generics> #name<#struct_generics> #where_clause { + #[doc(hidden)] + #[inline(always)] + #shorten_invariant_lifetime + } + } +} diff --git a/lang/syn/src/codegen/accounts/mod.rs b/lang/syn/src/codegen/accounts/mod.rs index 5bf2ea3a0a..834e38f016 100644 --- a/lang/syn/src/codegen/accounts/mod.rs +++ b/lang/syn/src/codegen/accounts/mod.rs @@ -10,6 +10,7 @@ use { pub mod __client_accounts; pub mod __cpi_client_accounts; +mod __shorten_invariant_lifetime; mod bumps; mod constraints; mod duplicate_mutable_account_keys; @@ -24,6 +25,7 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream { let impl_to_account_metas = to_account_metas::generate(accs); let impl_exit = exit::generate(accs); let impl_dup_mutable_keys = duplicate_mutable_account_keys::generate(accs); + let impl_shorten_invariant_lifetime = __shorten_invariant_lifetime::generate(accs); let bumps_struct = bumps::generate(accs); let program_id = quote! { @@ -45,6 +47,7 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream { #impl_to_account_metas #impl_exit #impl_dup_mutable_keys + #impl_shorten_invariant_lifetime #bumps_struct #__client_accounts_mod diff --git a/lang/syn/src/codegen/program/handlers.rs b/lang/syn/src/codegen/program/handlers.rs index c81213c1b6..facce85444 100644 --- a/lang/syn/src/codegen/program/handlers.rs +++ b/lang/syn/src/codegen/program/handlers.rs @@ -133,7 +133,10 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { &mut __reallocs, )?; - unsafe fn __shrink_lifetime<'from, 'to, T>(value: &'from mut T) -> &'to mut T { + #[inline(always)] + unsafe fn __shorten_invariant_lifetime_remaining_accounts<'a, 'info: 'a>( + value: &'a [AccountInfo<'info>], + ) -> &'a [AccountInfo<'a>] { unsafe { ::core::mem::transmute(value) } } @@ -141,27 +144,27 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { let result = #program_name::#ix_method_name( anchor_lang::context::Context::new( __program_id, - // SAFETY: `__shrink_lifetime` is used to *shrink* the lifetime of - // the inner `AccountInfo` from `'info` to the local function lifetime. - // No lifetime is extended by this operation. - // The lifetime is not shrunk automatically as `RefCell` causes `AccountInfo` + // SAFETY: `__shorten_invariant_lifetime` functions are used to *shrink* + // the lifetime of the inner `AccountInfo`s from `'info` to the local + // function's lifetime. No lifetime is extended by this operation. The + // lifetime is not shrunk automatically as `RefCell` causes `AccountInfo` // to be invariant. + // // This is sound provided the following invariants hold: - // (1) The `'info` lifetime strictly outlives the local function - // lifetime; therefore, the transmuted references cannot outlive - // their backing data. - // (2) `AccountInfo` does not implement custom `Drop` logic and does not - // rely on its lifetime parameter during destruction. - // (3) The `Context` value is dropped before the `__accounts` reference - // is dropped or otherwise accessed, preventing any use-after-scope. + // + // * The `'info` lifetime strictly outlives the local function lifetime; + // therefore, the transmuted references cannot outlive their backing + // data. + // * `AccountInfo` does not implement custom `Drop` logic and does not + // rely on its lifetime parameter during destruction. + // * The `Context` value is dropped before the `__accounts` reference + // is dropped or otherwise accessed, preventing any use-after-scope. // // This lifetime narrowing is required to conform to the `Context` // struct's single-lifetime parameterization, which uses a single // lifetime to keep the API simple and ergonomic. - unsafe { - __shrink_lifetime(&mut __accounts) - }, - __remaining_accounts, + unsafe { #accounts_struct_name::__shorten_invariant_lifetime(&mut __accounts) }, + unsafe { __shorten_invariant_lifetime_remaining_accounts(__remaining_accounts) }, __bumps, ), #(#ix_arg_names),*