Adds stable layout types to pass to the runtime (#30192)

This commit is contained in:
Brooks 2023-02-16 08:16:25 -05:00 committed by GitHub
parent fa22389b22
commit 0c36e4c82d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 423 additions and 35 deletions

1
Cargo.lock generated
View File

@ -4954,6 +4954,7 @@ dependencies = [
"byteorder", "byteorder",
"libsecp256k1", "libsecp256k1",
"log", "log",
"memoffset 0.8.0",
"rand 0.7.3", "rand 0.7.3",
"solana-measure", "solana-measure",
"solana-program-runtime", "solana-program-runtime",

View File

@ -17,11 +17,12 @@ use {
bpf_loader_upgradeable::{self, UpgradeableLoaderState}, bpf_loader_upgradeable::{self, UpgradeableLoaderState},
feature_set::{enable_early_verification_of_account_modifications, FeatureSet}, feature_set::{enable_early_verification_of_account_modifications, FeatureSet},
hash::Hash, hash::Hash,
instruction::{AccountMeta, Instruction, InstructionError}, instruction::{AccountMeta, InstructionError},
native_loader, native_loader,
pubkey::Pubkey, pubkey::Pubkey,
rent::Rent, rent::Rent,
saturating_add_assign, saturating_add_assign,
stable_layout::stable_instruction::StableInstruction,
transaction_context::{ transaction_context::{
IndexOfAccount, InstructionAccount, TransactionAccount, TransactionContext, IndexOfAccount, InstructionAccount, TransactionAccount, TransactionContext,
}, },
@ -489,7 +490,7 @@ impl<'a> InvokeContext<'a> {
/// Entrypoint for a cross-program invocation from a builtin program /// Entrypoint for a cross-program invocation from a builtin program
pub fn native_invoke( pub fn native_invoke(
&mut self, &mut self,
instruction: Instruction, instruction: StableInstruction,
signers: &[Pubkey], signers: &[Pubkey],
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
let (instruction_accounts, program_indices) = let (instruction_accounts, program_indices) =
@ -509,7 +510,7 @@ impl<'a> InvokeContext<'a> {
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
pub fn prepare_instruction( pub fn prepare_instruction(
&mut self, &mut self,
instruction: &Instruction, instruction: &StableInstruction,
signers: &[Pubkey], signers: &[Pubkey],
) -> Result<(Vec<InstructionAccount>, Vec<IndexOfAccount>), InstructionError> { ) -> Result<(Vec<InstructionAccount>, Vec<IndexOfAccount>), InstructionError> {
// Finds the index of each account in the instruction by its pubkey. // Finds the index of each account in the instruction by its pubkey.
@ -1036,7 +1037,7 @@ mod tests {
super::*, super::*,
crate::compute_budget, crate::compute_budget,
serde::{Deserialize, Serialize}, serde::{Deserialize, Serialize},
solana_sdk::account::WritableAccount, solana_sdk::{account::WritableAccount, instruction::Instruction},
}; };
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
@ -1161,7 +1162,7 @@ mod tests {
assert_eq!(result, Err(InstructionError::UnbalancedInstruction)); assert_eq!(result, Err(InstructionError::UnbalancedInstruction));
result?; result?;
invoke_context invoke_context
.native_invoke(inner_instruction, &[]) .native_invoke(inner_instruction.into(), &[])
.and(invoke_context.pop())?; .and(invoke_context.pop())?;
} }
MockInstruction::UnbalancedPop => instruction_context MockInstruction::UnbalancedPop => instruction_context
@ -1336,7 +1337,7 @@ mod tests {
let inner_instruction = let inner_instruction =
Instruction::new_with_bincode(callee_program_id, &case.0, metas.clone()); Instruction::new_with_bincode(callee_program_id, &case.0, metas.clone());
let result = invoke_context let result = invoke_context
.native_invoke(inner_instruction, &[]) .native_invoke(inner_instruction.into(), &[])
.and(invoke_context.pop()); .and(invoke_context.pop());
assert_eq!(result, case.1); assert_eq!(result, case.1);
} }
@ -1359,6 +1360,7 @@ mod tests {
}, },
metas.clone(), metas.clone(),
); );
let inner_instruction = StableInstruction::from(inner_instruction);
let (inner_instruction_accounts, program_indices) = invoke_context let (inner_instruction_accounts, program_indices) = invoke_context
.prepare_instruction(&inner_instruction, &[]) .prepare_instruction(&inner_instruction, &[])
.unwrap(); .unwrap();

View File

@ -40,6 +40,7 @@ use {
pubkey::Pubkey, pubkey::Pubkey,
rent::Rent, rent::Rent,
signature::{Keypair, Signer}, signature::{Keypair, Signer},
stable_layout::stable_instruction::StableInstruction,
sysvar::{Sysvar, SysvarId}, sysvar::{Sysvar, SysvarId},
}, },
solana_vote_program::vote_state::{self, VoteState, VoteStateVersions}, solana_vote_program::vote_state::{self, VoteState, VoteStateVersions},
@ -227,6 +228,7 @@ impl solana_sdk::program_stubs::SyscallStubs for SyscallStubs {
account_infos: &[AccountInfo], account_infos: &[AccountInfo],
signers_seeds: &[&[&[u8]]], signers_seeds: &[&[&[u8]]],
) -> ProgramResult { ) -> ProgramResult {
let instruction = StableInstruction::from(instruction.clone());
let invoke_context = get_invoke_context(); let invoke_context = get_invoke_context();
let log_collector = invoke_context.get_log_collector(); let log_collector = invoke_context.get_log_collector();
let transaction_context = &invoke_context.transaction_context; let transaction_context = &invoke_context.transaction_context;
@ -249,7 +251,7 @@ impl solana_sdk::program_stubs::SyscallStubs for SyscallStubs {
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let (instruction_accounts, program_indices) = invoke_context let (instruction_accounts, program_indices) = invoke_context
.prepare_instruction(instruction, &signers) .prepare_instruction(&instruction, &signers)
.unwrap(); .unwrap();
// Copy caller's account_info modifications into invoke_context accounts // Copy caller's account_info modifications into invoke_context accounts

View File

@ -144,18 +144,18 @@ impl Processor {
if required_lamports > 0 { if required_lamports > 0 {
invoke_context.native_invoke( invoke_context.native_invoke(
system_instruction::transfer(&payer_key, &table_key, required_lamports), system_instruction::transfer(&payer_key, &table_key, required_lamports).into(),
&[payer_key], &[payer_key],
)?; )?;
} }
invoke_context.native_invoke( invoke_context.native_invoke(
system_instruction::allocate(&table_key, table_account_data_len as u64), system_instruction::allocate(&table_key, table_account_data_len as u64).into(),
&[table_key], &[table_key],
)?; )?;
invoke_context.native_invoke( invoke_context.native_invoke(
system_instruction::assign(&table_key, &crate::id()), system_instruction::assign(&table_key, &crate::id()).into(),
&[table_key], &[table_key],
)?; )?;
@ -332,7 +332,7 @@ impl Processor {
drop(payer_account); drop(payer_account);
invoke_context.native_invoke( invoke_context.native_invoke(
system_instruction::transfer(&payer_key, &table_key, required_lamports), system_instruction::transfer(&payer_key, &table_key, required_lamports).into(),
&[payer_key], &[payer_key],
)?; )?;
} }

View File

@ -22,6 +22,9 @@ solana-zk-token-sdk = { path = "../../zk-token-sdk", version = "=1.16.0" }
solana_rbpf = "=0.2.38" solana_rbpf = "=0.2.38"
thiserror = "1.0" thiserror = "1.0"
[dev-dependencies]
memoffset = "0.8"
[lib] [lib]
crate-type = ["lib"] crate-type = ["lib"]
name = "solana_bpf_loader_program" name = "solana_bpf_loader_program"

View File

@ -736,7 +736,7 @@ fn process_loader_upgradeable_instruction(
.iter() .iter()
.map(|seeds| Pubkey::create_program_address(seeds, caller_program_id)) .map(|seeds| Pubkey::create_program_address(seeds, caller_program_id))
.collect::<Result<Vec<Pubkey>, solana_sdk::pubkey::PubkeyError>>()?; .collect::<Result<Vec<Pubkey>, solana_sdk::pubkey::PubkeyError>>()?;
invoke_context.native_invoke(instruction, signers.as_slice())?; invoke_context.native_invoke(instruction.into(), signers.as_slice())?;
// Load and verify the program bits // Load and verify the program bits
let transaction_context = &invoke_context.transaction_context; let transaction_context = &invoke_context.transaction_context;
@ -1360,7 +1360,8 @@ fn process_loader_upgradeable_instruction(
)?; )?;
invoke_context.native_invoke( invoke_context.native_invoke(
system_instruction::transfer(&payer_key, &programdata_key, required_payment), system_instruction::transfer(&payer_key, &programdata_key, required_payment)
.into(),
&[], &[],
)?; )?;
} }

View File

@ -3,6 +3,7 @@ use {
crate::declare_syscall, crate::declare_syscall,
solana_sdk::{ solana_sdk::{
feature_set::enable_bpf_loader_set_authority_checked_ix, feature_set::enable_bpf_loader_set_authority_checked_ix,
stable_layout::stable_instruction::StableInstruction,
syscalls::{ syscalls::{
MAX_CPI_ACCOUNT_INFOS, MAX_CPI_INSTRUCTION_ACCOUNTS, MAX_CPI_INSTRUCTION_DATA_LEN, MAX_CPI_ACCOUNT_INFOS, MAX_CPI_INSTRUCTION_ACCOUNTS, MAX_CPI_INSTRUCTION_DATA_LEN,
}, },
@ -211,7 +212,7 @@ trait SyscallInvokeSigned {
addr: u64, addr: u64,
memory_mapping: &mut MemoryMapping, memory_mapping: &mut MemoryMapping,
invoke_context: &mut InvokeContext, invoke_context: &mut InvokeContext,
) -> Result<Instruction, EbpfError>; ) -> Result<StableInstruction, EbpfError>;
fn translate_accounts<'a>( fn translate_accounts<'a>(
instruction_accounts: &[InstructionAccount], instruction_accounts: &[InstructionAccount],
program_indices: &[IndexOfAccount], program_indices: &[IndexOfAccount],
@ -258,8 +259,8 @@ impl SyscallInvokeSigned for SyscallInvokeSignedRust {
addr: u64, addr: u64,
memory_mapping: &mut MemoryMapping, memory_mapping: &mut MemoryMapping,
invoke_context: &mut InvokeContext, invoke_context: &mut InvokeContext,
) -> Result<Instruction, EbpfError> { ) -> Result<StableInstruction, EbpfError> {
let ix = translate_type::<Instruction>( let ix = translate_type::<StableInstruction>(
memory_mapping, memory_mapping,
addr, addr,
invoke_context.get_check_aligned(), invoke_context.get_check_aligned(),
@ -296,10 +297,11 @@ impl SyscallInvokeSigned for SyscallInvokeSignedRust {
invoke_context.get_check_size(), invoke_context.get_check_size(),
)? )?
.to_vec(); .to_vec();
Ok(Instruction {
Ok(StableInstruction {
accounts: accounts.into(),
data: data.into(),
program_id: ix.program_id, program_id: ix.program_id,
accounts,
data,
}) })
} }
@ -469,7 +471,7 @@ impl SyscallInvokeSigned for SyscallInvokeSignedC {
addr: u64, addr: u64,
memory_mapping: &mut MemoryMapping, memory_mapping: &mut MemoryMapping,
invoke_context: &mut InvokeContext, invoke_context: &mut InvokeContext,
) -> Result<Instruction, EbpfError> { ) -> Result<StableInstruction, EbpfError> {
let ix_c = translate_type::<SolInstruction>( let ix_c = translate_type::<SolInstruction>(
memory_mapping, memory_mapping,
addr, addr,
@ -530,10 +532,10 @@ impl SyscallInvokeSigned for SyscallInvokeSignedC {
}) })
.collect::<Result<Vec<AccountMeta>, EbpfError>>()?; .collect::<Result<Vec<AccountMeta>, EbpfError>>()?;
Ok(Instruction { Ok(StableInstruction {
accounts: accounts.into(),
data: data.into(),
program_id: *program_id, program_id: *program_id,
accounts,
data,
}) })
} }
@ -1128,6 +1130,7 @@ mod tests {
solana_sdk::{ solana_sdk::{
account::{Account, AccountSharedData}, account::{Account, AccountSharedData},
clock::Epoch, clock::Epoch,
instruction::Instruction,
rent::Rent, rent::Rent,
transaction_context::{TransactionAccount, TransactionContext}, transaction_context::{TransactionAccount, TransactionContext},
}, },
@ -1693,12 +1696,12 @@ mod tests {
fn into_region(self, vm_addr: u64) -> (Vec<u8>, MemoryRegion) { fn into_region(self, vm_addr: u64) -> (Vec<u8>, MemoryRegion) {
let accounts_len = mem::size_of::<AccountMeta>() * self.accounts.len(); let accounts_len = mem::size_of::<AccountMeta>() * self.accounts.len();
let size = mem::size_of::<Instruction>() + accounts_len + self.data.len(); let size = mem::size_of::<StableInstruction>() + accounts_len + self.data.len();
let mut data = vec![0; size]; let mut data = vec![0; size];
let vm_addr = vm_addr as usize; let vm_addr = vm_addr as usize;
let accounts_addr = vm_addr + mem::size_of::<Instruction>(); let accounts_addr = vm_addr + mem::size_of::<StableInstruction>();
let data_addr = accounts_addr + accounts_len; let data_addr = accounts_addr + accounts_len;
let ins = Instruction { let ins = Instruction {
@ -1714,6 +1717,7 @@ mod tests {
Vec::from_raw_parts(data_addr as *mut _, self.data.len(), self.data.len()) Vec::from_raw_parts(data_addr as *mut _, self.data.len(), self.data.len())
}, },
}; };
let ins = StableInstruction::from(ins);
unsafe { unsafe {
ptr::write_unaligned(data.as_mut_ptr().cast(), ins); ptr::write_unaligned(data.as_mut_ptr().cast(), ins);

View File

@ -44,7 +44,7 @@ use {
}, },
hash::{Hasher, HASH_BYTES}, hash::{Hasher, HASH_BYTES},
instruction::{ instruction::{
AccountMeta, Instruction, InstructionError, ProcessedSiblingInstruction, AccountMeta, InstructionError, ProcessedSiblingInstruction,
TRANSACTION_LEVEL_STACK_HEIGHT, TRANSACTION_LEVEL_STACK_HEIGHT,
}, },
keccak, native_loader, keccak, native_loader,
@ -1821,7 +1821,9 @@ mod tests {
bpf_loader, bpf_loader,
fee_calculator::FeeCalculator, fee_calculator::FeeCalculator,
hash::hashv, hash::hashv,
instruction::Instruction,
program::check_type_assumptions, program::check_type_assumptions,
stable_layout::stable_instruction::StableInstruction,
sysvar::{clock::Clock, epoch_schedule::EpochSchedule, rent::Rent}, sysvar::{clock::Clock, epoch_schedule::EpochSchedule, rent::Rent},
transaction_context::TransactionContext, transaction_context::TransactionContext,
}, },
@ -1936,17 +1938,18 @@ mod tests {
&"foobar", &"foobar",
vec![AccountMeta::new(solana_sdk::pubkey::new_rand(), false)], vec![AccountMeta::new(solana_sdk::pubkey::new_rand(), false)],
); );
let instruction = StableInstruction::from(instruction);
let addr = &instruction as *const _ as u64; let addr = &instruction as *const _ as u64;
let mut memory_region = MemoryRegion { let mut memory_region = MemoryRegion {
host_addr: addr, host_addr: addr,
vm_addr: 0x100000000, vm_addr: 0x100000000,
len: std::mem::size_of::<Instruction>() as u64, len: std::mem::size_of::<StableInstruction>() as u64,
vm_gap_shift: 63, vm_gap_shift: 63,
is_writable: false, is_writable: false,
}; };
let mut memory_mapping = MemoryMapping::new(vec![memory_region.clone()], &config).unwrap(); let mut memory_mapping = MemoryMapping::new(vec![memory_region.clone()], &config).unwrap();
let translated_instruction = let translated_instruction =
translate_type::<Instruction>(&memory_mapping, 0x100000000, true).unwrap(); translate_type::<StableInstruction>(&memory_mapping, 0x100000000, true).unwrap();
assert_eq!(instruction, *translated_instruction); assert_eq!(instruction, *translated_instruction);
memory_region.len = 1; memory_region.len = 1;
let memory_region_index = memory_mapping let memory_region_index = memory_mapping
@ -1957,7 +1960,7 @@ mod tests {
memory_mapping memory_mapping
.replace_region(memory_region_index, memory_region) .replace_region(memory_region_index, memory_region)
.unwrap(); .unwrap();
assert!(translate_type::<Instruction>(&memory_mapping, 0x100000000, true).is_err()); assert!(translate_type::<StableInstruction>(&memory_mapping, 0x100000000, true).is_err());
} }
#[test] #[test]

View File

@ -521,6 +521,7 @@ pub mod serialize_utils;
pub mod short_vec; pub mod short_vec;
pub mod slot_hashes; pub mod slot_hashes;
pub mod slot_history; pub mod slot_history;
pub mod stable_layout;
pub mod stake; pub mod stake;
pub mod stake_history; pub mod stake_history;
pub mod syscalls; pub mod syscalls;

View File

@ -10,6 +10,7 @@
use crate::{ use crate::{
account_info::AccountInfo, entrypoint::ProgramResult, instruction::Instruction, pubkey::Pubkey, account_info::AccountInfo, entrypoint::ProgramResult, instruction::Instruction, pubkey::Pubkey,
stable_layout::stable_instruction::StableInstruction,
}; };
/// Invoke a cross-program instruction. /// Invoke a cross-program instruction.
@ -292,9 +293,10 @@ pub fn invoke_signed_unchecked(
) -> ProgramResult { ) -> ProgramResult {
#[cfg(target_os = "solana")] #[cfg(target_os = "solana")]
{ {
let instruction = StableInstruction::from(instruction.clone());
let result = unsafe { let result = unsafe {
crate::syscalls::sol_invoke_signed_rust( crate::syscalls::sol_invoke_signed_rust(
instruction as *const _ as *const u8, &instruction as *const _ as *const u8,
account_infos as *const _ as *const u8, account_infos as *const _ as *const u8,
account_infos.len() as u64, account_infos.len() as u64,
signers_seeds as *const _ as *const u8, signers_seeds as *const _ as *const u8,
@ -432,17 +434,18 @@ pub fn check_type_assumptions() {
accounts: vec![account_meta1.clone(), account_meta2.clone()], accounts: vec![account_meta1.clone(), account_meta2.clone()],
data: data.clone(), data: data.clone(),
}; };
let instruction = StableInstruction::from(instruction);
let instruction_addr = &instruction as *const _ as u64; let instruction_addr = &instruction as *const _ as u64;
// program id // program id
assert_eq!(offset_of!(Instruction, program_id), 48); assert_eq!(offset_of!(StableInstruction, program_id), 48);
let pubkey_ptr = (instruction_addr + 48) as *const Pubkey; let pubkey_ptr = (instruction_addr + 48) as *const Pubkey;
unsafe { unsafe {
assert_eq!(*pubkey_ptr, pubkey1); assert_eq!(*pubkey_ptr, pubkey1);
} }
// accounts // accounts
assert_eq!(offset_of!(Instruction, accounts), 0); assert_eq!(offset_of!(StableInstruction, accounts), 0);
let accounts_ptr = (instruction_addr) as *const *const AccountMeta; let accounts_ptr = (instruction_addr) as *const *const AccountMeta;
let accounts_cap = (instruction_addr + 8) as *const usize; let accounts_cap = (instruction_addr + 8) as *const usize;
let accounts_len = (instruction_addr + 16) as *const usize; let accounts_len = (instruction_addr + 16) as *const usize;
@ -455,7 +458,7 @@ pub fn check_type_assumptions() {
} }
// data // data
assert_eq!(offset_of!(Instruction, data), 24); assert_eq!(offset_of!(StableInstruction, data), 24);
let data_ptr = (instruction_addr + 24) as *const *const [u8; 5]; let data_ptr = (instruction_addr + 24) as *const *const [u8; 5];
let data_cap = (instruction_addr + 24 + 8) as *const usize; let data_cap = (instruction_addr + 24 + 8) as *const usize;
let data_len = (instruction_addr + 24 + 16) as *const usize; let data_len = (instruction_addr + 24 + 16) as *const usize;

View File

@ -0,0 +1,10 @@
#![doc(hidden)]
//! Types with stable memory layouts
//!
//! Internal use only; here be dragons!
pub mod stable_instruction;
pub mod stable_rc;
pub mod stable_ref_cell;
pub mod stable_slice;
pub mod stable_vec;

View File

@ -0,0 +1,96 @@
//! `Instruction`, with a stable memory layout
use {
crate::{
instruction::{AccountMeta, Instruction},
pubkey::Pubkey,
stable_layout::stable_vec::StableVec,
},
std::fmt::Debug,
};
/// `Instruction`, with a stable memory layout
///
/// This is used within the runtime to ensure memory mapping and memory accesses are valid. We
/// rely on known addresses and offsets within the runtime, and since `Instruction`'s layout is
/// allowed to change, we must provide a way to lock down the memory layout. `StableInstruction`
/// reimplements the bare minimum of `Instruction`'s API sufficient only for the runtime's needs.
///
/// # Examples
///
/// Creating a `StableInstruction` from an `Instruction`
///
/// ```
/// # use solana_program::{instruction::Instruction, pubkey::Pubkey, stable_layout::stable_instruction::StableInstruction};
/// # let program_id = Pubkey::default();
/// # let accounts = Vec::default();
/// # let data = Vec::default();
/// let instruction = Instruction { program_id, accounts, data };
/// let instruction = StableInstruction::from(instruction);
/// ```
#[derive(Debug, PartialEq)]
#[repr(C)]
pub struct StableInstruction {
pub accounts: StableVec<AccountMeta>,
pub data: StableVec<u8>,
pub program_id: Pubkey,
}
impl From<Instruction> for StableInstruction {
fn from(other: Instruction) -> Self {
Self {
accounts: other.accounts.into(),
data: other.data.into(),
program_id: other.program_id,
}
}
}
#[cfg(test)]
mod tests {
use {
super::*,
memoffset::offset_of,
std::mem::{align_of, size_of},
};
#[test]
fn test_memory_layout() {
assert_eq!(offset_of!(StableInstruction, accounts), 0);
assert_eq!(offset_of!(StableInstruction, data), 24);
assert_eq!(offset_of!(StableInstruction, program_id), 48);
assert_eq!(align_of::<StableInstruction>(), 8);
assert_eq!(size_of::<StableInstruction>(), 24 + 24 + 32);
let program_id = Pubkey::new_unique();
let account_meta1 = AccountMeta {
pubkey: Pubkey::new_unique(),
is_signer: true,
is_writable: false,
};
let account_meta2 = AccountMeta {
pubkey: Pubkey::new_unique(),
is_signer: false,
is_writable: true,
};
let accounts = vec![account_meta1, account_meta2];
let data = vec![1, 2, 3, 4, 5];
let instruction = Instruction {
program_id,
accounts: accounts.clone(),
data: data.clone(),
};
let instruction = StableInstruction::from(instruction);
let instruction_addr = &instruction as *const _ as u64;
let accounts_ptr = instruction_addr as *const StableVec<AccountMeta>;
assert_eq!(unsafe { &*accounts_ptr }, &accounts);
let data_ptr = (instruction_addr + 24) as *const StableVec<u8>;
assert_eq!(unsafe { &*data_ptr }, &data);
let pubkey_ptr = (instruction_addr + 48) as *const Pubkey;
assert_eq!(unsafe { *pubkey_ptr }, program_id);
}
}

View File

@ -0,0 +1,29 @@
//! Ensure Rc has a stable memory layout
#[cfg(test)]
mod tests {
use std::{
mem::{align_of, size_of},
rc::Rc,
};
#[test]
fn test_memory_layout() {
assert_eq!(align_of::<Rc<i32>>(), 8);
assert_eq!(size_of::<Rc<i32>>(), 8);
let value = 42;
let rc = Rc::new(value);
let _rc2 = Rc::clone(&rc); // used to increment strong count
let addr_rc = &rc as *const _ as usize;
let addr_ptr = addr_rc;
let addr_rcbox = unsafe { *(addr_ptr as *const *const i32) } as usize;
let addr_strong = addr_rcbox;
let addr_weak = addr_rcbox + 8;
let addr_value = addr_rcbox + 16;
assert_eq!(unsafe { *(addr_strong as *const usize) }, 2);
assert_eq!(unsafe { *(addr_weak as *const usize) }, 1);
assert_eq!(unsafe { *(addr_value as *const i32) }, 42);
}
}

View File

@ -0,0 +1,25 @@
//! Ensure RefCell has a stable layout
#[cfg(test)]
mod tests {
use std::{
cell::RefCell,
mem::{align_of, size_of},
};
#[test]
fn test_memory_layout() {
assert_eq!(align_of::<RefCell<i32>>(), 8);
assert_eq!(size_of::<RefCell<i32>>(), 8 + 4 + /* padding */4);
let value = 42;
let refcell = RefCell::new(value);
let _borrow = refcell.borrow(); // used to increment borrow count
let addr_refcell = &refcell as *const _ as usize;
let addr_borrow = addr_refcell;
let addr_value = addr_refcell + 8;
assert_eq!(unsafe { *(addr_borrow as *const isize) }, 1);
assert_eq!(unsafe { *(addr_value as *const i32) }, 42);
}
}

View File

@ -0,0 +1,27 @@
//! Ensure slice has a stable memory layout
#[cfg(test)]
mod tests {
use std::mem::{align_of, size_of};
#[test]
fn test_memory_layout() {
assert_eq!(align_of::<&[i32]>(), 8);
assert_eq!(size_of::<&[i32]>(), /*ptr*/ 8 + /*len*/8);
let array = [11, 22, 33, 44, 55];
let slice = array.as_slice();
let addr_slice = &slice as *const _ as usize;
let addr_ptr = addr_slice;
let addr_len = addr_slice + 8;
assert_eq!(unsafe { *(addr_len as *const usize) }, 5);
let ptr_data = addr_ptr as *const *const i32;
assert_eq!(unsafe { *((*ptr_data).offset(0)) }, 11);
assert_eq!(unsafe { *((*ptr_data).offset(1)) }, 22);
assert_eq!(unsafe { *((*ptr_data).offset(2)) }, 33);
assert_eq!(unsafe { *((*ptr_data).offset(3)) }, 44);
assert_eq!(unsafe { *((*ptr_data).offset(4)) }, 55);
}
}

View File

@ -0,0 +1,181 @@
//! `Vec`, with a stable memory layout
use std::{marker::PhantomData, mem::ManuallyDrop, ptr::NonNull};
/// `Vec`, with a stable memory layout
///
/// This container is used within the runtime to ensure memory mapping and memory accesses are
/// valid. We rely on known addresses and offsets within the runtime, and since `Vec`'s layout
/// is allowed to change, we must provide a way to lock down the memory layout. `StableVec`
/// reimplements the bare minimum of `Vec`'s API sufficient only for the runtime's needs.
///
/// To ensure memory allocation and deallocation is handled correctly, it is only possible to
/// create a new `StableVec` from an existing `Vec`. This way we ensure all Rust invariants are
/// upheld.
///
/// # Examples
///
/// Creating a `StableVec` from a `Vec`
///
/// ```
/// # use solana_program::stable_layout::stable_vec::StableVec;
/// let vec = vec!["meow", "woof", "moo"];
/// let vec = StableVec::from(vec);
/// ```
#[repr(C)]
pub struct StableVec<T> {
pub ptr: NonNull<T>,
pub cap: usize,
pub len: usize,
_marker: PhantomData<T>,
}
impl<T> StableVec<T> {
#[inline]
pub fn as_ptr(&self) -> *const T {
// We shadow the slice method of the same name to avoid going through
// `deref`, which creates an intermediate reference.
self.ptr.as_ptr()
}
#[inline]
pub fn as_mut_ptr(&mut self) -> *mut T {
// We shadow the slice method of the same name to avoid going through
// `deref_mut`, which creates an intermediate reference.
self.ptr.as_ptr()
}
}
impl<T> AsRef<[T]> for StableVec<T> {
fn as_ref(&self) -> &[T] {
self
}
}
impl<T> AsMut<[T]> for StableVec<T> {
fn as_mut(&mut self) -> &mut [T] {
self
}
}
impl<T> std::ops::Deref for StableVec<T> {
type Target = [T];
#[inline]
fn deref(&self) -> &[T] {
unsafe { core::slice::from_raw_parts(self.as_ptr(), self.len) }
}
}
impl<T> std::ops::DerefMut for StableVec<T> {
#[inline]
fn deref_mut(&mut self) -> &mut [T] {
unsafe { core::slice::from_raw_parts_mut(self.as_mut_ptr(), self.len) }
}
}
impl<T: std::fmt::Debug> std::fmt::Debug for StableVec<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Debug::fmt(&**self, f)
}
}
macro_rules! impl_partial_eq {
([$($vars:tt)*] $lhs:ty, $rhs:ty) => {
impl<T, U, $($vars)*> PartialEq<$rhs> for $lhs
where
T: PartialEq<U>,
{
#[inline]
fn eq(&self, other: &$rhs) -> bool { self[..] == other[..] }
}
}
}
impl_partial_eq! { [] StableVec<T>, StableVec<U> }
impl_partial_eq! { [] StableVec<T>, Vec<U> }
impl_partial_eq! { [] Vec<T>, StableVec<U> }
impl_partial_eq! { [] StableVec<T>, &[U] }
impl_partial_eq! { [] StableVec<T>, &mut [U] }
impl_partial_eq! { [] &[T], StableVec<U> }
impl_partial_eq! { [] &mut [T], StableVec<U> }
impl_partial_eq! { [] StableVec<T>, [U] }
impl_partial_eq! { [] [T], StableVec<U> }
impl_partial_eq! { [const N: usize] StableVec<T>, [U; N] }
impl_partial_eq! { [const N: usize] StableVec<T>, &[U; N] }
impl<T> From<Vec<T>> for StableVec<T> {
fn from(other: Vec<T>) -> Self {
// NOTE: This impl is basically copied from `Vec::into_raw_parts()`. Once that fn is
// stabilized, use it here.
//
// We are going to pilfer `other`'s guts, and we don't want it to be dropped when it goes
// out of scope.
let mut other = ManuallyDrop::new(other);
Self {
// SAFETY: We have a valid Vec, so its ptr is non-null.
ptr: unsafe { NonNull::new_unchecked(other.as_mut_ptr()) },
cap: other.capacity(),
len: other.len(),
_marker: PhantomData,
}
}
}
impl<T> From<StableVec<T>> for Vec<T> {
fn from(other: StableVec<T>) -> Self {
// We are going to pilfer `other`'s guts, and we don't want it to be dropped when it goes
// out of scope.
let other = ManuallyDrop::new(other);
// SAFETY: We have a valid StableVec, which we can only get from a Vec. Therefore it is
// safe to convert back to Vec.
unsafe { Vec::from_raw_parts(other.ptr.as_ptr(), other.len, other.cap) }
}
}
impl<T> Drop for StableVec<T> {
fn drop(&mut self) {
// We only allow creating a StableVec through creating a Vec. To ensure we are dropped
// correctly, convert ourselves back to a Vec and let Vec's drop handling take over.
//
// SAFETY: We have a valid StableVec, which we can only get from a Vec. Therefore it is
// safe to convert back to Vec.
let _vec = unsafe { Vec::from_raw_parts(self.ptr.as_ptr(), self.len, self.cap) };
}
}
#[cfg(test)]
mod tests {
use {
super::*,
memoffset::offset_of,
std::mem::{align_of, size_of},
};
#[test]
fn test_memory_layout() {
assert_eq!(offset_of!(StableVec<i32>, ptr), 0);
assert_eq!(offset_of!(StableVec<i32>, cap), 8);
assert_eq!(offset_of!(StableVec<i32>, len), 16);
assert_eq!(align_of::<StableVec<i32>>(), 8);
assert_eq!(size_of::<StableVec<i32>>(), 8 + 8 + 8);
// create a vec with different values for cap and len
let vec = {
let mut vec = Vec::with_capacity(3);
vec.push(11);
vec.push(22);
vec
};
let vec = StableVec::from(vec);
let addr_vec = &vec as *const _ as usize;
let addr_ptr = addr_vec;
let addr_cap = addr_vec + 8;
let addr_len = addr_vec + 16;
assert_eq!(unsafe { *(addr_cap as *const usize) }, 3);
assert_eq!(unsafe { *(addr_len as *const usize) }, 2);
let ptr_data = addr_ptr as *const &[i32; 2];
assert_eq!(unsafe { *ptr_data }, &[11, 22]);
}
}

View File

@ -50,8 +50,8 @@ pub use solana_program::{
instruction, keccak, lamports, loader_instruction, loader_upgradeable_instruction, message, instruction, keccak, lamports, loader_instruction, loader_upgradeable_instruction, message,
msg, native_token, nonce, program, program_error, program_memory, program_option, program_pack, msg, native_token, nonce, program, program_error, program_memory, program_option, program_pack,
rent, sanitize, sdk_ids, secp256k1_program, secp256k1_recover, serde_varint, serialize_utils, rent, sanitize, sdk_ids, secp256k1_program, secp256k1_recover, serde_varint, serialize_utils,
short_vec, slot_hashes, slot_history, stake, stake_history, syscalls, system_instruction, short_vec, slot_hashes, slot_history, stable_layout, stake, stake_history, syscalls,
system_program, sysvar, unchecked_div_by_const, vote, wasm_bindgen, system_instruction, system_program, sysvar, unchecked_div_by_const, vote, wasm_bindgen,
}; };
pub mod account; pub mod account;