Skip to content

Commit

Permalink
feat(gstd): get panic message without panic_info_message (#3527)
Browse files Browse the repository at this point in the history
  • Loading branch information
StackOverflowExcept1on authored Jan 16, 2024
1 parent 5852ee9 commit d9de34e
Show file tree
Hide file tree
Showing 10 changed files with 264 additions and 51 deletions.
22 changes: 22 additions & 0 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ gcli = { path = "gcli" }
gclient = { path = "gclient" }
gsdk = { path = "gsdk" }
gsdk-codegen = { path = "gsdk/codegen" }
gstd = { path = "gstd", features = [ "nightly" ] }
gstd = { path = "gstd", features = ["nightly"] }
gstd-codegen = { path = "gstd/codegen" }
gsys = { path = "gsys" }
gtest = { path = "gtest" }
Expand Down
2 changes: 1 addition & 1 deletion core-backend/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ pub enum TrapExplanation {
ProgramAllocOutOfBounds,
#[display(fmt = "Syscall unrecoverable error: {_0}")]
UnrecoverableExt(UnrecoverableExtError),
#[display(fmt = "{_0}")]
#[display(fmt = "Panic occurred: {_0}")]
Panic(LimitedStr<'static>),
#[display(fmt = "Stack limit exceeded")]
StackLimitExceeded,
Expand Down
8 changes: 2 additions & 6 deletions examples/waiter/tests/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,6 @@ pub fn assert_panicked(result: &RunResult, panic_msg: &str) {
)))
));
let payload = String::from_utf8(result.log()[0].payload().into())
.expect("Unable to decode panic message")
.split(',')
.map(String::from)
.next()
.expect("Unable to split panic message");
assert_eq!(payload, format!("'{}'", panic_msg));
.expect("Unable to decode panic message");
assert!(payload.contains(&format!("panicked with '{panic_msg}'")));
}
45 changes: 38 additions & 7 deletions gstd/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ name = "gstd"
description = "Gear programs standard library"
keywords = ["gear", "std", "no-std", "wasm", "smart-contracts"]
categories = ["api-bindings"]
# Stable version of Rust >=1.73 is required due to new format of panic message.
rust-version = "1.73"
version.workspace = true
edition.workspace = true
authors.workspace = true
Expand All @@ -11,6 +13,8 @@ homepage.workspace = true
repository.workspace = true

[dependencies]
arrayvec = { version = "0.7.4", default-features = false, optional = true }
const_format = { version = "0.2.32", optional = true }
document-features = { version = "0.2.8", optional = true }
galloc.workspace = true
gcore = { workspace = true, features = ["codec"] }
Expand All @@ -27,18 +31,45 @@ futures = { workspace = true, features = ["alloc"] }
[features]
#! ## Default features

default = ["panic-handler"]
## When enabled, a panic handler is provided by this crate.
panic-handler = []
default = ["panic-message"]

#! ## Panic handler profiles
#! We currently use the following format for panic messages from Rust code:
#! `panicked with '{message}'[ at '{location}']`. Also `Panic occurred: `
#! will be added to the beginning of the panic message by our core-backend.
#!
#! So the final panic message looks like this:
#! `Panic occurred: panicked with '{message}'[ at '{location}']`.
#!
#! You can configure which panic handler profile you need
#! by specifying one of the following functions:

## When enabled, a minimal panic handler is provided by this crate.
## Instead of a panic message, `<unknown>` is displayed.
panic-handler = ["const_format"]
## When enabled, a panic handler will also display a panic message.
panic-message = ["panic-handler", "arrayvec"]
## When enabled, a panic handler will also display a panic message and location.
## This function is not recommended for use in production environment
## because it displays the code path.
panic-location = ["panic-message"]

#! ## Nightly features
#!
#! The `panic-message` and `panic-location` features gets additional
#! optimizations when using the nightly compiler.
#!
#! For example, if you don't use the `panic-location` feature, the compiler
#! will remove all locations such as `/home/username/dapp/src/lib.rs:1:2`
#! from the binary. The size of smart contract will be reduced and
#! `/home/username/...` information will not be included in the binary.

## Enables all features below.
## These features depend on unstable Rust API and require nightly toolchain.
nightly = ["panic-messages", "oom-handler"]
## When enabled, additional context information is available from
## panic messages in debug mode. Relies on [`panic_info_message`][rust-66745].
panic-messages = []
nightly = ["panic-info-message", "oom-handler"]
## When enabled, a panic handler will use a more efficient implementation.
## Relies on [`panic_info_message`][rust-66745].
panic-info-message = []
## When enabled, an OOM error handler is provided.
## Relies on [`alloc_error_handler`][rust-51540],
oom-handler = []
Expand Down
207 changes: 181 additions & 26 deletions gstd/src/common/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,50 +25,205 @@
//! debug and non-debug mode, for programs built in `wasm32` architecture.
//! For `debug` mode it provides more extensive logging.

#[cfg(target_arch = "wasm32")]
#[cfg(feature = "oom-handler")]
#[alloc_error_handler]
pub fn oom(_: core::alloc::Layout) -> ! {
crate::ext::oom_panic()
}

/// We currently support 3 panic handler profiles:
/// - `panic-handler`: it displays `panicked with '<unknown>'`
/// - `panic-message`: it displays `panicked with '{message}'`
/// - `panic-location`: it displays `panicked with '{message}' at '{location}'`
///
/// How we get the panic message in different versions of Rust:
/// - In nightly Rust, we use `#![feature(panic_info_message)]` and the
/// [`write!`] macro.
/// - In stable Rust, we need to modify the default panic handler message
/// format.
///
/// Default panic handler message format (according to <https://github.com/rust-lang/rust/pull/112849>):
/// `panicked at {location}:\n{message}`
///
/// We parse the output of `impl Display for PanicInfo<'_>` and
/// then convert it to custom format:
/// `panicked with '{message}'[ at '{location}']`.
#[cfg(target_arch = "wasm32")]
#[cfg(feature = "panic-handler")]
mod panic_handler {
use crate::ext;
use core::panic::PanicInfo;

#[cfg(not(feature = "debug"))]
mod constants {
/// This prefix is used before the panic message.
pub const PANIC_PREFIX: &str = "panicked with ";
/// This panic message is used in the minimal panic handler and when
/// internal errors occur.
#[cfg(any(feature = "panic-info-message", not(feature = "panic-message")))]
pub const UNKNOWN_REASON: &str = "<unknown>";

/// This prefix is used by `impl Display for PanicInfo<'_>`.
#[cfg(all(not(feature = "panic-info-message"), feature = "panic-message"))]
pub const PANICKED_AT: &str = "panicked at ";

/// Max amount of bytes allowed to be thrown as string explanation
/// of the error.
#[cfg(feature = "panic-message")]
pub const TRIMMED_MAX_LEN: usize = 1024; //TODO: do not duplicate
// `gear_core::str::TRIMMED_MAX_LEN`
}

use constants::*;

/// Minimal panic handler.
#[cfg(not(feature = "panic-message"))]
#[panic_handler]
pub fn panic(_: &PanicInfo) -> ! {
ext::panic("no info")
const MESSAGE: &str = const_format::formatcp!("{PANIC_PREFIX}'{UNKNOWN_REASON}'");

#[cfg(feature = "debug")]
let _ = ext::debug(MESSAGE);

ext::panic(MESSAGE)
}

#[cfg(feature = "debug")]
/// Panic handler for nightly Rust.
#[cfg(all(feature = "panic-info-message", feature = "panic-message"))]
#[panic_handler]
pub fn panic(panic_info: &PanicInfo) -> ! {
use crate::prelude::format;
#[cfg(not(feature = "panic-messages"))]
let message = None::<&core::fmt::Arguments<'_>>;
#[cfg(feature = "panic-messages")]
let message = panic_info.message();

let msg = match (message, panic_info.location()) {
(Some(msg), Some(loc)) => format!(
"'{:?}', {}:{}:{}",
msg,
loc.file(),
loc.line(),
loc.column()
),
(Some(msg), None) => format!("'{msg:?}'"),
(None, Some(loc)) => {
format!("{}:{}:{}", loc.file(), loc.line(), loc.column())
use crate::prelude::fmt::Write;
use arrayvec::ArrayString;

let mut debug_msg = ArrayString::<TRIMMED_MAX_LEN>::new();
let _ = debug_msg.try_push_str(PANIC_PREFIX);

match (panic_info.message(), panic_info.location()) {
#[cfg(feature = "panic-location")]
(Some(msg), Some(loc)) => {
let _ = write!(&mut debug_msg, "'{msg}' at '{loc}'");
}
#[cfg(not(feature = "panic-location"))]
(Some(msg), _) => {
let _ = write!(&mut debug_msg, "'{msg}'");
}
_ => {
let _ = debug_msg.try_push_str(const_format::formatcp!("'{UNKNOWN_REASON}'"));
}
_ => ext::panic("no info"),
};

crate::debug!("panic occurred: {msg}");
ext::panic(&msg)
#[cfg(feature = "debug")]
let _ = ext::debug(&debug_msg);

ext::panic(&debug_msg)
}

/// Panic handler for stable Rust.
#[cfg(all(not(feature = "panic-info-message"), feature = "panic-message"))]
#[panic_handler]
pub fn panic(panic_info: &PanicInfo) -> ! {
use crate::prelude::fmt::{self, Write};
use arrayvec::ArrayString;

#[derive(Default)]
struct TempBuffer<const CAP: usize> {
overflowed: bool,
buffer: ArrayString<CAP>,
}

impl<const CAP: usize> TempBuffer<CAP> {
#[inline]
fn write_str(&mut self, s: &str) {
if !self.overflowed && self.buffer.write_str(s).is_err() {
self.overflowed = true;
}
}
}

#[derive(Default)]
struct TempOutput {
found_prefix: bool,
found_delimiter: bool,
#[cfg(feature = "panic-location")]
location: TempBuffer<TRIMMED_MAX_LEN>,
message: TempBuffer<TRIMMED_MAX_LEN>,
}

impl fmt::Write for TempOutput {
fn write_str(&mut self, s: &str) -> fmt::Result {
if !self.found_prefix && s.len() == PANICKED_AT.len() {
self.found_prefix = true;
return Ok(());
}

if !self.found_delimiter {
if s == ":\n" {
self.found_delimiter = true;
return Ok(());
}
#[cfg(feature = "panic-location")]
self.location.write_str(s);
} else {
self.message.write_str(s);
}

Ok(())
}
}

let mut output = TempOutput::default();
let _ = write!(&mut output, "{panic_info}");

#[cfg(feature = "panic-location")]
let location = &*output.location.buffer;
let message = &*output.message.buffer;

let mut debug_msg = ArrayString::<TRIMMED_MAX_LEN>::new();
let _ = debug_msg.try_push_str(PANIC_PREFIX);

#[cfg(feature = "panic-location")]
for s in ["'", message, "' at '", location, "'"] {
if debug_msg.try_push_str(s).is_err() {
break;
}
}

#[cfg(not(feature = "panic-location"))]
for s in ["'", message, "'"] {
if debug_msg.try_push_str(s).is_err() {
break;
}
}

#[cfg(feature = "debug")]
let _ = ext::debug(&debug_msg);

ext::panic(&debug_msg)
}
}

#[cfg(test)]
mod tests {
extern crate std;

use std::{format, panic, prelude::v1::*};

/// Here is a test to verify that the default panic handler message
/// format has not changed.
#[test]
fn panic_msg_format_not_changed() {
const MESSAGE: &str = "message";

panic::set_hook(Box::new(|panic_info| {
let location = panic_info.location().unwrap();
assert_eq!(
panic_info.to_string(),
format!("panicked at {location}:\n{MESSAGE}")
);
}));

let result = panic::catch_unwind(|| {
panic!("{MESSAGE}");
});
assert!(result.is_err());
}
}
#[cfg(feature = "panic-handler")]
pub use panic_handler::*;
1 change: 0 additions & 1 deletion gstd/src/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,5 @@
//! Common modules for each Gear smart contract.

pub mod errors;
#[cfg(target_arch = "wasm32")]
mod handlers;
pub mod primitives;
Loading

0 comments on commit d9de34e

Please sign in to comment.