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",
"libsecp256k1",
"log",
"memoffset 0.8.0",
"rand 0.7.3",
"solana-measure",
"solana-program-runtime",

View File

@ -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();

View File

@ -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

View File

@ -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],
)?;
}

View File

@ -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"

View File

@ -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(),
&[],
)?;
}

View File

@ -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);

View File

@ -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]

View File

@ -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;

View File

@ -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;

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,
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;