From a5cab7a3d4963b2c6ad746b5ee0b012b8146e05b Mon Sep 17 00:00:00 2001 From: acheron Date: Sun, 29 Mar 2026 16:03:10 +0200 Subject: [PATCH 1/6] lang: Shorten invariant lifetimes during `Context` creation --- lang/syn/src/codegen/program/handlers.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/lang/syn/src/codegen/program/handlers.rs b/lang/syn/src/codegen/program/handlers.rs index 70bebed5c7..6aacbf07cd 100644 --- a/lang/syn/src/codegen/program/handlers.rs +++ b/lang/syn/src/codegen/program/handlers.rs @@ -127,7 +127,17 @@ 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_accounts<'a, 'info: 'a>( + value: &'a mut #accounts_struct_name<'info>, + ) -> &'a mut #accounts_struct_name<'a> { + unsafe { ::core::mem::transmute(value) } + } + + #[inline(always)] + unsafe fn __shorten_invariant_lifetime_remaining_accounts<'a, 'info: 'a>( + value: &'a [AccountInfo<'info>], + ) -> &'a [AccountInfo<'a>] { unsafe { ::core::mem::transmute(value) } } @@ -152,10 +162,8 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { // 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 { __shorten_invariant_lifetime_accounts(&mut __accounts) }, + unsafe { __shorten_invariant_lifetime_remaining_accounts(__remaining_accounts) }, __bumps, ), #(#ix_arg_names),* From 709d15dc5bea4c30dfa6803a1aae05c40f9af175 Mon Sep 17 00:00:00 2001 From: acheron Date: Sun, 29 Mar 2026 16:11:27 +0200 Subject: [PATCH 2/6] lang: Add `__shorten_invariant_lifetime` method to accounts structs --- .../accounts/__shorten_invariant_lifetime.rs | 40 +++++++++++++++++++ lang/syn/src/codegen/accounts/mod.rs | 3 ++ lang/syn/src/codegen/program/handlers.rs | 8 +--- 3 files changed, 44 insertions(+), 7 deletions(-) create mode 100644 lang/syn/src/codegen/accounts/__shorten_invariant_lifetime.rs 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..3f5a6756a8 --- /dev/null +++ b/lang/syn/src/codegen/accounts/__shorten_invariant_lifetime.rs @@ -0,0 +1,40 @@ +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() { + quote! { + unsafe fn __shorten_invariant_lifetime<'__a, '__info: '__a>( + value: &'__a mut #name<'__info>, + ) -> &'__a mut #name<'__a> { + unsafe { ::core::mem::transmute(value) } + } + } + } else { + quote! { + 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 f57290ffe7..efee5fb4a8 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 __client_accounts_mod = __client_accounts::generate(accs, quote!(crate::ID)); @@ -35,6 +37,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 6aacbf07cd..186709674d 100644 --- a/lang/syn/src/codegen/program/handlers.rs +++ b/lang/syn/src/codegen/program/handlers.rs @@ -127,12 +127,6 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { &mut __reallocs, )?; - #[inline(always)] - unsafe fn __shorten_invariant_lifetime_accounts<'a, 'info: 'a>( - value: &'a mut #accounts_struct_name<'info>, - ) -> &'a mut #accounts_struct_name<'a> { - unsafe { ::core::mem::transmute(value) } - } #[inline(always)] unsafe fn __shorten_invariant_lifetime_remaining_accounts<'a, 'info: 'a>( @@ -162,7 +156,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { // 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 { __shorten_invariant_lifetime_accounts(&mut __accounts) }, + unsafe { #accounts_struct_name::__shorten_invariant_lifetime(&mut __accounts) }, unsafe { __shorten_invariant_lifetime_remaining_accounts(__remaining_accounts) }, __bumps, ), From 855e2a35896a9e283a26ffb711762aa118c71cae Mon Sep 17 00:00:00 2001 From: acheron Date: Sun, 29 Mar 2026 16:18:39 +0200 Subject: [PATCH 3/6] lang: Make `__shorten_invariant_lifetime` public --- lang/syn/src/codegen/accounts/__shorten_invariant_lifetime.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lang/syn/src/codegen/accounts/__shorten_invariant_lifetime.rs b/lang/syn/src/codegen/accounts/__shorten_invariant_lifetime.rs index 3f5a6756a8..672499a94e 100644 --- a/lang/syn/src/codegen/accounts/__shorten_invariant_lifetime.rs +++ b/lang/syn/src/codegen/accounts/__shorten_invariant_lifetime.rs @@ -15,7 +15,7 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream { let shorten_invariant_lifetime = if accs.generics.lt_token.is_some() { quote! { - unsafe fn __shorten_invariant_lifetime<'__a, '__info: '__a>( + pub unsafe fn __shorten_invariant_lifetime<'__a, '__info: '__a>( value: &'__a mut #name<'__info>, ) -> &'__a mut #name<'__a> { unsafe { ::core::mem::transmute(value) } @@ -23,7 +23,7 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream { } } else { quote! { - fn __shorten_invariant_lifetime(value: &mut Self) -> &mut Self { + pub fn __shorten_invariant_lifetime(value: &mut Self) -> &mut Self { value } } From bfdf852e32a900d86dcd8c7d3f40eb3d066f867a Mon Sep 17 00:00:00 2001 From: acheron Date: Mon, 18 May 2026 05:28:19 +0200 Subject: [PATCH 4/6] lang: Fix generics accounts struct --- .../src/codegen/accounts/__shorten_invariant_lifetime.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lang/syn/src/codegen/accounts/__shorten_invariant_lifetime.rs b/lang/syn/src/codegen/accounts/__shorten_invariant_lifetime.rs index 672499a94e..a779eb9f10 100644 --- a/lang/syn/src/codegen/accounts/__shorten_invariant_lifetime.rs +++ b/lang/syn/src/codegen/accounts/__shorten_invariant_lifetime.rs @@ -14,10 +14,14 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream { } = 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>, - ) -> &'__a mut #name<'__a> { + value: &'__a mut #name<'__info #non_lifetime_generics>, + ) -> &'__a mut #name<'__a #non_lifetime_generics> { unsafe { ::core::mem::transmute(value) } } } From 671e8caaf3624cbd34198373aacb97e04045ab6c Mon Sep 17 00:00:00 2001 From: acheron Date: Mon, 18 May 2026 05:51:44 +0200 Subject: [PATCH 5/6] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bfd3a9ed6f..dff6431856 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ The minor version will be incremented upon a breaking change and the patch versi - lang: Support module constants in `max_len` attribute ([#3879](https://github.com/solana-foundation/anchor/pull/3879)). - spl: Deprecate broken `cpi_guard_enable/disable` functions ([#4465](https://github.com/solana-foundation/anchor/pull/4465)). - cli: Bump `cargo_toml` to allow parsing `resolver = "3"` ([#4515](https://github.com/solana-foundation/anchor/pull/4515)). +- lang: Shorten invariant lifetimes during `Context` creation ([#4363](https://github.com/solana-foundation/anchor/pull/4363)). ### Breaking From 99768302cb17bfc890ef5fba5d52446a204a2b11 Mon Sep 17 00:00:00 2001 From: acheron Date: Sat, 30 May 2026 16:57:08 +0200 Subject: [PATCH 6/6] lang: Update safety comment --- lang/syn/src/codegen/program/handlers.rs | 25 ++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/lang/syn/src/codegen/program/handlers.rs b/lang/syn/src/codegen/program/handlers.rs index bbbe260eda..facce85444 100644 --- a/lang/syn/src/codegen/program/handlers.rs +++ b/lang/syn/src/codegen/program/handlers.rs @@ -133,7 +133,6 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { &mut __reallocs, )?; - #[inline(always)] unsafe fn __shorten_invariant_lifetime_remaining_accounts<'a, 'info: 'a>( value: &'a [AccountInfo<'info>], @@ -145,19 +144,21 @@ 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