Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 71 additions & 51 deletions sdk/pinocchio/src/account_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -456,94 +456,114 @@ impl AccountInfo {
Ok(())
}

/// Realloc the account's data and optionally zero-initialize the new
/// memory.
/// Realloc (either truncating or zero extending) the account's data.
///
/// The account data can be increased by up to [`MAX_PERMITTED_DATA_INCREASE`] bytes
/// within an instruction.
///
/// # Important
///
/// Memory used to grow is already zero-initialized upon program
/// entrypoint and re-zeroing it wastes compute units. If within the same
/// call a program reallocs from larger to smaller and back to larger again
/// the new space could contain stale data. Pass `true` for `zero_init` in
/// this case, otherwise compute units will be wasted re-zero-initializing.
/// This method makes assumptions about the layout and location of memory
/// referenced by `AccountInfo` fields. It should only be called for
/// instances of `AccountInfo` that were created by the runtime and received
/// in the `process_instruction` entrypoint of a program.
///
/// Note that `zero_init` being `false` will not be supported by the
/// program runtime in the future. Programs should not rely on being able to
/// access stale data and use [`Self::resize`] instead.
/// The use of the `zero_init` parameter, which indicated whether the newly
/// allocated memory should be zero-initialized or not, is now deprecated and
/// ignored. The method will always zero-initialize the newly allocated memory
/// if the new length is larger than the current data length. This is the same
/// behavior as [`Self::resize`].
///
/// # Safety
/// To avoid zero-initializing the memory, use [`Self::resize_unchecked`] instead.
#[deprecated(since = "0.9.0", note = "Use AccountInfo::resize() instead")]
#[inline(always)]
pub fn realloc(&self, new_len: usize, _zero_init: bool) -> Result<(), ProgramError> {
self.resize(new_len)
}

/// Resize (either truncating or zero extending) the account's data.
///
/// The account data can be increased by up to [`MAX_PERMITTED_DATA_INCREASE`] bytes
/// within an instruction.
///
/// # Important
///
/// This method makes assumptions about the layout and location of memory
/// referenced by `AccountInfo` fields. It should only be called for
/// instances of `AccountInfo` that were created by the runtime and received
/// in the `process_instruction` entrypoint of a program.
#[deprecated(since = "0.9.0", note = "Use AccountInfo::resize() instead")]
pub fn realloc(&self, new_len: usize, zero_init: bool) -> Result<(), ProgramError> {
#[inline]
pub fn resize(&self, new_len: usize) -> Result<(), ProgramError> {
// Check wheather the account data is already borrowed.
self.can_borrow_mut_data()?;

// Account length is always `< i32::MAX`...
let current_len = self.data_len() as i32;
// ...so the new length must fit in an `i32`.
let new_len = i32::try_from(new_len).map_err(|_| ProgramError::InvalidRealloc)?;

// Return early if length hasn't changed.
if new_len == current_len {
return Ok(());
}

let difference = new_len - current_len;
let accumulated_resize_delta = self.resize_delta() + difference;

// Return an error when the length increase from the original serialized data
// length is too large and would result in an out of bounds allocation
if accumulated_resize_delta > MAX_PERMITTED_DATA_INCREASE as i32 {
return Err(ProgramError::InvalidRealloc);
}
let current_len = self.data_len();

unsafe {
(*self.raw).data_len = new_len as u64;
(*self.raw).resize_delta = accumulated_resize_delta;
// SAFETY: account data is not borrowed at this point and the new
// memory will be zero-initialized if the new length is larger than
// the current length.
self.resize_unchecked(new_len)?;
}

if zero_init && difference > 0 {
let difference = new_len.saturating_sub(current_len);

if difference > 0 {
unsafe {
#[cfg(target_os = "solana")]
sol_memset_(
self.data_ptr().add(current_len as usize),
0,
difference as u64,
);
sol_memset_(self.data_ptr().add(current_len), 0, difference as u64);

#[cfg(not(target_os = "solana"))]
core::ptr::write_bytes(
self.data_ptr().add(current_len as usize),
0,
difference as usize,
);
core::ptr::write_bytes(self.data_ptr().add(current_len), 0, difference);
}
}

Ok(())
}

/// Resize (either truncating or zero extending) the account's data.
/// Resize (either truncating or extending) the account's data.
///
/// The account data can be increased by up to [`MAX_PERMITTED_DATA_INCREASE`] bytes
/// within an instruction.
/// The account data can be increased by up to [`MAX_PERMITTED_DATA_INCREASE`]
/// bytes within an instruction.
///
/// # Important
///
/// This method makes assumptions about the layout and location of memory
/// referenced by `AccountInfo` fields. It should only be called for
/// instances of `AccountInfo` that were created by the runtime and received
/// in the `process_instruction` entrypoint of a program.
#[inline]
pub fn resize(&self, new_len: usize) -> Result<(), ProgramError> {
#[allow(deprecated)]
self.realloc(new_len, true)
///
/// # Safety
///
/// This method does not check if the account data is already borrowed. It should
/// only be called when the account is not being used. Additionaly, it does not
/// zero-initialize the new memory. Program should ensure to initialize the new
/// memory before using it; otherwise, it will result in undefined behavior.
#[inline(always)]
pub unsafe fn resize_unchecked(&self, new_len: usize) -> Result<(), ProgramError> {
// Account length is always `< i32::MAX`...
let current_len = self.data_len() as i32;
// ...so the new length must fit in an `i32`.
let new_len = i32::try_from(new_len).map_err(|_| ProgramError::InvalidRealloc)?;

// Only resize if the length has changed.
if new_len != current_len {
let difference = new_len - current_len;
let accumulated_resize_delta = self.resize_delta() + difference;

// Return an error when the length increase from the original serialized data
// length is too large and would result in an out of bounds allocation
if accumulated_resize_delta > MAX_PERMITTED_DATA_INCREASE as i32 {
return Err(ProgramError::InvalidRealloc);
}

unsafe {
(*self.raw).data_len = new_len as u64;
(*self.raw).resize_delta = accumulated_resize_delta;
}
}

Ok(())
}

/// Zero out the the account's data length, lamports and owner fields, effectively
Expand Down
Loading