Skip to content

Commit

Permalink
Implement space-efficient panic
Browse files Browse the repository at this point in the history
  • Loading branch information
LucasSte committed Jan 7, 2025
1 parent 1877601 commit 06c5ca8
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 2 deletions.
7 changes: 7 additions & 0 deletions programs/sbf/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions programs/sbf/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ members = [
"rust/deprecated_loader",
"rust/divide_by_zero",
"rust/dup_accounts",
"rust/efficient_panic",
"rust/error_handling",
"rust/external_spend",
"rust/finalize",
Expand Down
22 changes: 22 additions & 0 deletions programs/sbf/rust/efficient_panic/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[package]
name = "solana-sbf-rust-efficient-panic"
version = { workspace = true }
description = { workspace = true }
authors = { workspace = true }
repository = { workspace = true }
homepage = { workspace = true }
license = { workspace = true }
edition = { workspace = true }

[dependencies]
solana-program = { workspace = true }

[features]
default = ["efficient-panic"]
efficient-panic = []

[lib]
crate-type = ["cdylib"]

[lints]
workspace = true
34 changes: 34 additions & 0 deletions programs/sbf/rust/efficient_panic/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Cargo clippy runs with 1.81, but cargo-build-sbf is on 1.79.
#![allow(stable_features)]
#![feature(panic_info_message)]

use solana_program::{
account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError,
pubkey::Pubkey,
};

solana_program::entrypoint!(process_instruction);

fn process_instruction(
_program_id: &Pubkey,
_accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
match instruction_data[0] {
0 => {
// Accessing an invalid index creates a dynamic generated panic message.
let a = instruction_data[92341];
return Err(ProgramError::Custom(a as u32));
}
1 => {
// Unwrap on a None emit a compiler constant panic message.
let a: Option<u64> = None;
#[allow(clippy::unnecessary_literal_unwrap)]
let b = a.unwrap();
return Err(ProgramError::Custom(b as u32));
}

_ => (),
}
Ok(())
}
56 changes: 56 additions & 0 deletions programs/sbf/tests/programs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5662,3 +5662,59 @@ fn test_mem_syscalls_overlap_account_begin_or_end() {
}
}
}

#[test]
#[cfg(feature = "sbf_rust")]
fn test_efficient_panic() {
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(50);

let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
let mut bank_client = BankClient::new_shared(bank.clone());
let authority_keypair = Keypair::new();

let (bank, program_id) = load_upgradeable_program_and_advance_slot(
&mut bank_client,
bank_forks.as_ref(),
&mint_keypair,
&authority_keypair,
"solana_sbf_rust_efficient_panic",
);

bank.freeze();

let account_metas = vec![AccountMeta::new(mint_keypair.pubkey(), true)];
let instruction = Instruction::new_with_bytes(program_id, &[0, 2], account_metas.clone());

let blockhash = bank.last_blockhash();
let message = Message::new(&[instruction], Some(&mint_keypair.pubkey()));
let transaction = Transaction::new(&[&mint_keypair], message, blockhash);
let sanitized_tx = RuntimeTransaction::from_transaction_for_tests(transaction);

let result = bank.simulate_transaction(&sanitized_tx, false);
assert!(result.logs.contains(&format!(
"Program {} \
failed: SBF program Panicked in rust/efficient_panic/src/lib.rs at 22:21",
program_id
)));

let instruction = Instruction::new_with_bytes(program_id, &[1, 2], account_metas);

let blockhash = bank.last_blockhash();
let message = Message::new(&[instruction], Some(&mint_keypair.pubkey()));
let transaction = Transaction::new(&[&mint_keypair], message, blockhash);
let sanitized_tx = RuntimeTransaction::from_transaction_for_tests(transaction);

let result = bank.simulate_transaction(&sanitized_tx, false);
assert!(result
.logs
.contains(&"Program log: called `Option::unwrap()` on a `None` value".to_string()));
assert!(result.logs.contains(&format!(
"Program {} \
failed: SBF program Panicked in rust/efficient_panic/src/lib.rs at 28:23",
program_id
)));
}
1 change: 1 addition & 0 deletions sdk/define-syscall/src/definitions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ define_syscall!(fn sol_remaining_compute_units() -> u64, SOL_REMAINING_COMPUTE_U
define_syscall!(fn sol_alt_bn128_compression(op: u64, input: *const u8, input_size: u64, result: *mut u8) -> u64, SOL_ALT_BN128_COMPRESSION);
define_syscall!(fn sol_get_sysvar(sysvar_id_addr: *const u8, result: *mut u8, offset: u64, length: u64) -> u64, SOL_GET_SYSVAR);
define_syscall!(fn sol_get_epoch_stake(vote_address: *const u8) -> u64, SOL_GET_EPOCH_STAKE);
define_syscall!(fn sol_panic_(filename: *const u8, filename_len: u64, line: u64, column: u64), SOL_PANIC_);

// these are to be deprecated once they are superceded by sol_get_sysvar
define_syscall!(fn sol_get_clock_sysvar(addr: *mut u8) -> u64, SOL_GET_CLOCK_SYSVAR);
Expand Down
49 changes: 48 additions & 1 deletion sdk/program-entrypoint/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ macro_rules! entrypoint {
}
$crate::custom_heap_default!();
$crate::custom_panic_default!();
$crate::efficient_panic!();
};
}

Expand Down Expand Up @@ -189,6 +190,7 @@ macro_rules! entrypoint_no_alloc {
}
$crate::custom_heap_default!();
$crate::custom_panic_default!();
$crate::efficient_panic!();
};
}

Expand Down Expand Up @@ -270,7 +272,10 @@ macro_rules! custom_heap_default {
#[macro_export]
macro_rules! custom_panic_default {
() => {
#[cfg(all(not(feature = "custom-panic"), target_os = "solana"))]
#[cfg(all(
not(any(feature = "custom-panic", feature = "efficient-panic")),
target_os = "solana"
))]
#[no_mangle]
fn custom_panic(info: &core::panic::PanicInfo<'_>) {
// Full panic reporting
Expand All @@ -279,6 +284,48 @@ macro_rules! custom_panic_default {
};
}

/// This is an efficient implementation fo custom panic. It contains two syscalls and has a size
/// of about 264 bytes. It depends on the unstable feature `panic_info_message`, which is going to
/// be stabilized in Rust 1.84.
///
/// It works just like the default custom panic, except that dynamic generated error messages are
/// not shown. For instance, trying to access an index out of bounds of a vector would give us
/// `index Y is out of bounds for length X`. as the length is only known at runtime, this message
/// is elided.
///
/// All messages known at compile time are correctly displayed, e.g. `called unwrap in a None`,
/// file names, line numbers, and column numbers.
#[macro_export]
macro_rules! efficient_panic {
() => {
#[cfg(all(
not(feature = "custom-panic"),
feature = "efficient-panic",
target_os = "solana"
))]
#[no_mangle]
fn custom_panic(info: &core::panic::PanicInfo<'_>) {
if let Some(Some(mm)) = info.message().map(|mes| mes.as_str()) {
let mes = mm.as_bytes();
unsafe {
solana_program::syscalls::sol_log_(mes.as_ptr(), mes.len() as u64);
}
}

if let Some(loc) = info.location() {
unsafe {
solana_program::syscalls::sol_panic_(
loc.file().as_ptr(),
loc.file().len() as u64,
loc.line() as u64,
loc.column() as u64,
);
}
}
}
};
}

/// The bump allocator used as the default rust heap when running programs.
pub struct BumpAllocator {
pub start: usize,
Expand Down
2 changes: 1 addition & 1 deletion sdk/program/src/syscalls/definitions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub use solana_define_syscall::definitions::{
sol_curve_group_op, sol_curve_multiscalar_mul, sol_curve_pairing_map, sol_curve_validate_point,
sol_get_clock_sysvar, sol_get_epoch_rewards_sysvar, sol_get_epoch_schedule_sysvar,
sol_get_epoch_stake, sol_get_fees_sysvar, sol_get_last_restart_slot, sol_get_rent_sysvar,
sol_get_sysvar, sol_keccak256, sol_remaining_compute_units,
sol_get_sysvar, sol_keccak256, sol_panic_, sol_remaining_compute_units,
};
#[deprecated(since = "2.1.0", note = "Use `solana_instruction::syscalls` instead")]
pub use solana_instruction::syscalls::{
Expand Down

0 comments on commit 06c5ca8

Please sign in to comment.