Adds stable layout types to pass to the runtime (#30192)
This commit is contained in:
parent
fa22389b22
commit
0c36e4c82d
|
@ -4954,6 +4954,7 @@ dependencies = [
|
|||
"byteorder",
|
||||
"libsecp256k1",
|
||||
"log",
|
||||
"memoffset 0.8.0",
|
||||
"rand 0.7.3",
|
||||
"solana-measure",
|
||||
"solana-program-runtime",
|
||||
|
|
|
@ -17,11 +17,12 @@ use {
|
|||
bpf_loader_upgradeable::{self, UpgradeableLoaderState},
|
||||
feature_set::{enable_early_verification_of_account_modifications, FeatureSet},
|
||||
hash::Hash,
|
||||
instruction::{AccountMeta, Instruction, InstructionError},
|
||||
instruction::{AccountMeta, InstructionError},
|
||||
native_loader,
|
||||
pubkey::Pubkey,
|
||||
rent::Rent,
|
||||
saturating_add_assign,
|
||||
stable_layout::stable_instruction::StableInstruction,
|
||||
transaction_context::{
|
||||
IndexOfAccount, InstructionAccount, TransactionAccount, TransactionContext,
|
||||
},
|
||||
|
@ -489,7 +490,7 @@ impl<'a> InvokeContext<'a> {
|
|||
/// Entrypoint for a cross-program invocation from a builtin program
|
||||
pub fn native_invoke(
|
||||
&mut self,
|
||||
instruction: Instruction,
|
||||
instruction: StableInstruction,
|
||||
signers: &[Pubkey],
|
||||
) -> Result<(), InstructionError> {
|
||||
let (instruction_accounts, program_indices) =
|
||||
|
@ -509,7 +510,7 @@ impl<'a> InvokeContext<'a> {
|
|||
#[allow(clippy::type_complexity)]
|
||||
pub fn prepare_instruction(
|
||||
&mut self,
|
||||
instruction: &Instruction,
|
||||
instruction: &StableInstruction,
|
||||
signers: &[Pubkey],
|
||||
) -> Result<(Vec<InstructionAccount>, Vec<IndexOfAccount>), InstructionError> {
|
||||
// Finds the index of each account in the instruction by its pubkey.
|
||||
|
@ -1036,7 +1037,7 @@ mod tests {
|
|||
super::*,
|
||||
crate::compute_budget,
|
||||
serde::{Deserialize, Serialize},
|
||||
solana_sdk::account::WritableAccount,
|
||||
solana_sdk::{account::WritableAccount, instruction::Instruction},
|
||||
};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
|
@ -1161,7 +1162,7 @@ mod tests {
|
|||
assert_eq!(result, Err(InstructionError::UnbalancedInstruction));
|
||||
result?;
|
||||
invoke_context
|
||||
.native_invoke(inner_instruction, &[])
|
||||
.native_invoke(inner_instruction.into(), &[])
|
||||
.and(invoke_context.pop())?;
|
||||
}
|
||||
MockInstruction::UnbalancedPop => instruction_context
|
||||
|
@ -1336,7 +1337,7 @@ mod tests {
|
|||
let inner_instruction =
|
||||
Instruction::new_with_bincode(callee_program_id, &case.0, metas.clone());
|
||||
let result = invoke_context
|
||||
.native_invoke(inner_instruction, &[])
|
||||
.native_invoke(inner_instruction.into(), &[])
|
||||
.and(invoke_context.pop());
|
||||
assert_eq!(result, case.1);
|
||||
}
|
||||
|
@ -1359,6 +1360,7 @@ mod tests {
|
|||
},
|
||||
metas.clone(),
|
||||
);
|
||||
let inner_instruction = StableInstruction::from(inner_instruction);
|
||||
let (inner_instruction_accounts, program_indices) = invoke_context
|
||||
.prepare_instruction(&inner_instruction, &[])
|
||||
.unwrap();
|
||||
|
|
|
@ -40,6 +40,7 @@ use {
|
|||
pubkey::Pubkey,
|
||||
rent::Rent,
|
||||
signature::{Keypair, Signer},
|
||||
stable_layout::stable_instruction::StableInstruction,
|
||||
sysvar::{Sysvar, SysvarId},
|
||||
},
|
||||
solana_vote_program::vote_state::{self, VoteState, VoteStateVersions},
|
||||
|
@ -227,6 +228,7 @@ impl solana_sdk::program_stubs::SyscallStubs for SyscallStubs {
|
|||
account_infos: &[AccountInfo],
|
||||
signers_seeds: &[&[&[u8]]],
|
||||
) -> ProgramResult {
|
||||
let instruction = StableInstruction::from(instruction.clone());
|
||||
let invoke_context = get_invoke_context();
|
||||
let log_collector = invoke_context.get_log_collector();
|
||||
let transaction_context = &invoke_context.transaction_context;
|
||||
|
@ -249,7 +251,7 @@ impl solana_sdk::program_stubs::SyscallStubs for SyscallStubs {
|
|||
.collect::<Vec<_>>();
|
||||
|
||||
let (instruction_accounts, program_indices) = invoke_context
|
||||
.prepare_instruction(instruction, &signers)
|
||||
.prepare_instruction(&instruction, &signers)
|
||||
.unwrap();
|
||||
|
||||
// Copy caller's account_info modifications into invoke_context accounts
|
||||
|
|
|
@ -144,18 +144,18 @@ impl Processor {
|
|||
|
||||
if required_lamports > 0 {
|
||||
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],
|
||||
)?;
|
||||
}
|
||||
|
||||
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],
|
||||
)?;
|
||||
|
||||
invoke_context.native_invoke(
|
||||
system_instruction::assign(&table_key, &crate::id()),
|
||||
system_instruction::assign(&table_key, &crate::id()).into(),
|
||||
&[table_key],
|
||||
)?;
|
||||
|
||||
|
@ -332,7 +332,7 @@ impl Processor {
|
|||
drop(payer_account);
|
||||
|
||||
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],
|
||||
)?;
|
||||
}
|
||||
|
|
|
@ -22,6 +22,9 @@ solana-zk-token-sdk = { path = "../../zk-token-sdk", version = "=1.16.0" }
|
|||
solana_rbpf = "=0.2.38"
|
||||
thiserror = "1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
memoffset = "0.8"
|
||||
|
||||
[lib]
|
||||
crate-type = ["lib"]
|
||||
name = "solana_bpf_loader_program"
|
||||
|
|
|
@ -736,7 +736,7 @@ fn process_loader_upgradeable_instruction(
|
|||
.iter()
|
||||
.map(|seeds| Pubkey::create_program_address(seeds, caller_program_id))
|
||||
.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
|
||||
let transaction_context = &invoke_context.transaction_context;
|
||||
|
@ -1360,7 +1360,8 @@ fn process_loader_upgradeable_instruction(
|
|||
)?;
|
||||
|
||||
invoke_context.native_invoke(
|
||||
system_instruction::transfer(&payer_key, &programdata_key, required_payment),
|
||||
system_instruction::transfer(&payer_key, &programdata_key, required_payment)
|
||||
.into(),
|
||||
&[],
|
||||
)?;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ use {
|
|||
crate::declare_syscall,
|
||||
solana_sdk::{
|
||||
feature_set::enable_bpf_loader_set_authority_checked_ix,
|
||||
stable_layout::stable_instruction::StableInstruction,
|
||||
syscalls::{
|
||||
MAX_CPI_ACCOUNT_INFOS, MAX_CPI_INSTRUCTION_ACCOUNTS, MAX_CPI_INSTRUCTION_DATA_LEN,
|
||||
},
|
||||
|
@ -211,7 +212,7 @@ trait SyscallInvokeSigned {
|
|||
addr: u64,
|
||||
memory_mapping: &mut MemoryMapping,
|
||||
invoke_context: &mut InvokeContext,
|
||||
) -> Result<Instruction, EbpfError>;
|
||||
) -> Result<StableInstruction, EbpfError>;
|
||||
fn translate_accounts<'a>(
|
||||
instruction_accounts: &[InstructionAccount],
|
||||
program_indices: &[IndexOfAccount],
|
||||
|
@ -258,8 +259,8 @@ impl SyscallInvokeSigned for SyscallInvokeSignedRust {
|
|||
addr: u64,
|
||||
memory_mapping: &mut MemoryMapping,
|
||||
invoke_context: &mut InvokeContext,
|
||||
) -> Result<Instruction, EbpfError> {
|
||||
let ix = translate_type::<Instruction>(
|
||||
) -> Result<StableInstruction, EbpfError> {
|
||||
let ix = translate_type::<StableInstruction>(
|
||||
memory_mapping,
|
||||
addr,
|
||||
invoke_context.get_check_aligned(),
|
||||
|
@ -296,10 +297,11 @@ impl SyscallInvokeSigned for SyscallInvokeSignedRust {
|
|||
invoke_context.get_check_size(),
|
||||
)?
|
||||
.to_vec();
|
||||
Ok(Instruction {
|
||||
|
||||
Ok(StableInstruction {
|
||||
accounts: accounts.into(),
|
||||
data: data.into(),
|
||||
program_id: ix.program_id,
|
||||
accounts,
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -469,7 +471,7 @@ impl SyscallInvokeSigned for SyscallInvokeSignedC {
|
|||
addr: u64,
|
||||
memory_mapping: &mut MemoryMapping,
|
||||
invoke_context: &mut InvokeContext,
|
||||
) -> Result<Instruction, EbpfError> {
|
||||
) -> Result<StableInstruction, EbpfError> {
|
||||
let ix_c = translate_type::<SolInstruction>(
|
||||
memory_mapping,
|
||||
addr,
|
||||
|
@ -530,10 +532,10 @@ impl SyscallInvokeSigned for SyscallInvokeSignedC {
|
|||
})
|
||||
.collect::<Result<Vec<AccountMeta>, EbpfError>>()?;
|
||||
|
||||
Ok(Instruction {
|
||||
Ok(StableInstruction {
|
||||
accounts: accounts.into(),
|
||||
data: data.into(),
|
||||
program_id: *program_id,
|
||||
accounts,
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1128,6 +1130,7 @@ mod tests {
|
|||
solana_sdk::{
|
||||
account::{Account, AccountSharedData},
|
||||
clock::Epoch,
|
||||
instruction::Instruction,
|
||||
rent::Rent,
|
||||
transaction_context::{TransactionAccount, TransactionContext},
|
||||
},
|
||||
|
@ -1693,12 +1696,12 @@ mod tests {
|
|||
fn into_region(self, vm_addr: u64) -> (Vec<u8>, MemoryRegion) {
|
||||
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 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 ins = Instruction {
|
||||
|
@ -1714,6 +1717,7 @@ mod tests {
|
|||
Vec::from_raw_parts(data_addr as *mut _, self.data.len(), self.data.len())
|
||||
},
|
||||
};
|
||||
let ins = StableInstruction::from(ins);
|
||||
|
||||
unsafe {
|
||||
ptr::write_unaligned(data.as_mut_ptr().cast(), ins);
|
||||
|
|
|
@ -44,7 +44,7 @@ use {
|
|||
},
|
||||
hash::{Hasher, HASH_BYTES},
|
||||
instruction::{
|
||||
AccountMeta, Instruction, InstructionError, ProcessedSiblingInstruction,
|
||||
AccountMeta, InstructionError, ProcessedSiblingInstruction,
|
||||
TRANSACTION_LEVEL_STACK_HEIGHT,
|
||||
},
|
||||
keccak, native_loader,
|
||||
|
@ -1821,7 +1821,9 @@ mod tests {
|
|||
bpf_loader,
|
||||
fee_calculator::FeeCalculator,
|
||||
hash::hashv,
|
||||
instruction::Instruction,
|
||||
program::check_type_assumptions,
|
||||
stable_layout::stable_instruction::StableInstruction,
|
||||
sysvar::{clock::Clock, epoch_schedule::EpochSchedule, rent::Rent},
|
||||
transaction_context::TransactionContext,
|
||||
},
|
||||
|
@ -1936,17 +1938,18 @@ mod tests {
|
|||
&"foobar",
|
||||
vec![AccountMeta::new(solana_sdk::pubkey::new_rand(), false)],
|
||||
);
|
||||
let instruction = StableInstruction::from(instruction);
|
||||
let addr = &instruction as *const _ as u64;
|
||||
let mut memory_region = MemoryRegion {
|
||||
host_addr: addr,
|
||||
vm_addr: 0x100000000,
|
||||
len: std::mem::size_of::<Instruction>() as u64,
|
||||
len: std::mem::size_of::<StableInstruction>() as u64,
|
||||
vm_gap_shift: 63,
|
||||
is_writable: false,
|
||||
};
|
||||
let mut memory_mapping = MemoryMapping::new(vec![memory_region.clone()], &config).unwrap();
|
||||
let translated_instruction =
|
||||
translate_type::<Instruction>(&memory_mapping, 0x100000000, true).unwrap();
|
||||
translate_type::<StableInstruction>(&memory_mapping, 0x100000000, true).unwrap();
|
||||
assert_eq!(instruction, *translated_instruction);
|
||||
memory_region.len = 1;
|
||||
let memory_region_index = memory_mapping
|
||||
|
@ -1957,7 +1960,7 @@ mod tests {
|
|||
memory_mapping
|
||||
.replace_region(memory_region_index, memory_region)
|
||||
.unwrap();
|
||||
assert!(translate_type::<Instruction>(&memory_mapping, 0x100000000, true).is_err());
|
||||
assert!(translate_type::<StableInstruction>(&memory_mapping, 0x100000000, true).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -521,6 +521,7 @@ pub mod serialize_utils;
|
|||
pub mod short_vec;
|
||||
pub mod slot_hashes;
|
||||
pub mod slot_history;
|
||||
pub mod stable_layout;
|
||||
pub mod stake;
|
||||
pub mod stake_history;
|
||||
pub mod syscalls;
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
use crate::{
|
||||
account_info::AccountInfo, entrypoint::ProgramResult, instruction::Instruction, pubkey::Pubkey,
|
||||
stable_layout::stable_instruction::StableInstruction,
|
||||
};
|
||||
|
||||
/// Invoke a cross-program instruction.
|
||||
|
@ -292,9 +293,10 @@ pub fn invoke_signed_unchecked(
|
|||
) -> ProgramResult {
|
||||
#[cfg(target_os = "solana")]
|
||||
{
|
||||
let instruction = StableInstruction::from(instruction.clone());
|
||||
let result = unsafe {
|
||||
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.len() as u64,
|
||||
signers_seeds as *const _ as *const u8,
|
||||
|
@ -432,17 +434,18 @@ pub fn check_type_assumptions() {
|
|||
accounts: vec![account_meta1.clone(), account_meta2.clone()],
|
||||
data: data.clone(),
|
||||
};
|
||||
let instruction = StableInstruction::from(instruction);
|
||||
let instruction_addr = &instruction as *const _ as u64;
|
||||
|
||||
// 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;
|
||||
unsafe {
|
||||
assert_eq!(*pubkey_ptr, pubkey1);
|
||||
}
|
||||
|
||||
// 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_cap = (instruction_addr + 8) as *const usize;
|
||||
let accounts_len = (instruction_addr + 16) as *const usize;
|
||||
|
@ -455,7 +458,7 @@ pub fn check_type_assumptions() {
|
|||
}
|
||||
|
||||
// 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_cap = (instruction_addr + 24 + 8) as *const usize;
|
||||
let data_len = (instruction_addr + 24 + 16) as *const usize;
|
||||
|
|
|
@ -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;
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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]);
|
||||
}
|
||||
}
|
|
@ -50,8 +50,8 @@ pub use solana_program::{
|
|||
instruction, keccak, lamports, loader_instruction, loader_upgradeable_instruction, message,
|
||||
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,
|
||||
short_vec, slot_hashes, slot_history, stake, stake_history, syscalls, system_instruction,
|
||||
system_program, sysvar, unchecked_div_by_const, vote, wasm_bindgen,
|
||||
short_vec, slot_hashes, slot_history, stable_layout, stake, stake_history, syscalls,
|
||||
system_instruction, system_program, sysvar, unchecked_div_by_const, vote, wasm_bindgen,
|
||||
};
|
||||
|
||||
pub mod account;
|
||||
|
|
Loading…
Reference in New Issue