use crate::iface::{Data, ErasedFn, Iface, MethodId, TypeDesc};
use crate::print::{self, Arg};
use crate::string::GoStr;
use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
#[derive(Clone)]
pub struct GoPanic {
text: Vec<u8>,
value: Iface,
}
impl GoPanic {
pub fn new(text: impl Into<Vec<u8>>) -> Self {
GoPanic {
text: text.into(),
value: Iface::nil(),
}
}
pub fn with_value(text: impl Into<Vec<u8>>, value: Iface) -> Self {
GoPanic {
text: text.into(),
value,
}
}
pub fn text(&self) -> &[u8] {
&self.text
}
pub fn value(&self) -> Iface {
self.value
}
}
rt_global! {
static RUNTIME_ERROR: core::cell::Cell<Option<&'static TypeDesc>> =
core::cell::Cell::new(None);
}
pub fn init_runtime_errors(error_id: MethodId, string_id: MethodId) {
fn message(d: Data) -> GoStr {
d.cast::<crate::place::Slot<GoStr>>().load()
}
let methods: &'static [(MethodId, ErasedFn)] =
alloc::boxed::Box::leak(alloc::boxed::Box::new(if error_id <= string_id {
[
(error_id, ErasedFn::new(message as *const ())),
(string_id, ErasedFn::new(message as *const ())),
]
} else {
[
(string_id, ErasedFn::new(message as *const ())),
(error_id, ErasedFn::new(message as *const ())),
]
}));
let desc: &'static TypeDesc = alloc::boxed::Box::leak(alloc::boxed::Box::new(TypeDesc {
name: "runtime.Error",
methods,
equal: Some(|a, b| message(a) == message(b)),
hash: Some(|d| crate::map::GoKey::go_hash(&message(d))),
print: |d, out| out.extend_from_slice(message(d).bytes()),
}));
RUNTIME_ERROR.with(|c| c.set(Some(desc)));
}
fn runtime_error_value(msg: &str) -> Iface {
match RUNTIME_ERROR.with(|c| c.get()) {
Some(desc) => {
let s = GoStr::from_bytes(msg.as_bytes());
let frame = crate::gc::Frame::<1>::new();
frame.scope(|| {
frame.set(0, &s);
Iface::new(
desc,
Data::of(crate::place::Ptr::<crate::place::Slot<GoStr>>::alloc(s)),
)
})
}
None => Iface::nil(),
}
}
#[cfg(feature = "std")]
rt_global! {
static CURRENT: core::cell::RefCell<Option<GoPanic>> =
core::cell::RefCell::new(None);
}
#[cfg(feature = "std")]
pub fn begin(payload: alloc::boxed::Box<dyn core::any::Any + Send>) {
match payload.downcast::<GoPanic>() {
Ok(p) => CURRENT.with(|c| *c.borrow_mut() = Some(*p)),
Err(other) => std::panic::resume_unwind(other),
}
}
#[cfg(feature = "std")]
pub fn recover() -> Iface {
CURRENT
.with(|c| c.borrow_mut().take())
.map_or(Iface::nil(), |p| p.value())
}
#[cfg(feature = "std")]
pub fn recovered() -> bool {
CURRENT.with(|c| c.borrow().is_none())
}
#[cfg(feature = "std")]
pub fn resume() -> ! {
match CURRENT.with(|c| c.borrow_mut().take()) {
Some(p) => go_panic(p),
None => unreachable!("resume without a panic in flight"),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum RuntimeError {
DivideByZero,
NegativeShift,
NilDeref,
Index {
index: i64,
len: usize,
},
IndexU {
index: u64,
len: usize,
},
SliceHigh {
high: i64,
len: usize,
},
SliceCap {
max: i64,
cap: usize,
},
MakeCap {
cap: i64,
},
MakeLen {
len: i64,
},
NilMapWrite,
UnsafeSliceLen,
UnsafeSliceNil,
UnsafeStringLen,
UnhashableKey {
type_name: &'static str,
},
UncomparableType {
type_name: &'static str,
},
SliceLow {
low: i64,
high: usize,
},
}
impl RuntimeError {
pub fn message(self) -> String {
match self {
RuntimeError::NilMapWrite => {
return String::from("assignment to entry in nil map");
}
RuntimeError::UnhashableKey { type_name } => {
return format!("runtime error: hash of unhashable type {type_name}");
}
RuntimeError::UncomparableType { type_name } => {
return format!("runtime error: comparing uncomparable type {type_name}");
}
_ => {}
}
let detail = match self {
RuntimeError::DivideByZero => String::from("integer divide by zero"),
RuntimeError::NegativeShift => String::from("negative shift amount"),
RuntimeError::NilDeref => {
String::from("invalid memory address or nil pointer dereference")
}
RuntimeError::Index { index, .. } if index < 0 => {
format!("index out of range [{index}]")
}
RuntimeError::Index { index, len } => {
format!("index out of range [{index}] with length {len}")
}
RuntimeError::IndexU { index, len } => {
format!("index out of range [{index}] with length {len}")
}
RuntimeError::SliceHigh { high, .. } if high < 0 => {
format!("slice bounds out of range [:{high}]")
}
RuntimeError::SliceHigh { high, len } => {
format!("slice bounds out of range [:{high}] with length {len}")
}
RuntimeError::SliceCap { max, .. } if max < 0 => {
format!("slice bounds out of range [::{max}]")
}
RuntimeError::SliceCap { max, cap } => {
format!("slice bounds out of range [::{max}] with capacity {cap}")
}
RuntimeError::MakeCap { .. } => String::from("makeslice: cap out of range"),
RuntimeError::MakeLen { .. } => String::from("makeslice: len out of range"),
RuntimeError::SliceLow { low, .. } if low < 0 => {
format!("slice bounds out of range [{low}:]")
}
RuntimeError::SliceLow { low, high } => {
format!("slice bounds out of range [{low}:{high}]")
}
RuntimeError::UnsafeSliceLen => String::from("unsafe.Slice: len out of range"),
RuntimeError::UnsafeSliceNil => {
String::from("unsafe.Slice: ptr is nil and len is not zero")
}
RuntimeError::UnsafeStringLen => String::from("unsafe.String: len out of range"),
RuntimeError::NilMapWrite
| RuntimeError::UnhashableKey { .. }
| RuntimeError::UncomparableType { .. } => unreachable!("handled above"),
};
format!("runtime error: {detail}")
}
}
#[cold]
pub fn go_panic(p: GoPanic) -> ! {
#[cfg(feature = "std")]
{
std::panic::resume_unwind(alloc::boxed::Box::new(p))
}
#[cfg(not(feature = "std"))]
{
panic!("{}", String::from_utf8_lossy(&p.text))
}
}
#[cold]
pub fn runtime_error(e: RuntimeError) -> ! {
let msg = e.message();
let value = runtime_error_value(&msg);
go_panic(GoPanic::with_value(msg, value))
}
#[cold]
pub fn panic_value(v: Arg<'_>) -> ! {
let mut text = Vec::new();
push_panicval(&mut text, v);
go_panic(GoPanic::new(text))
}
#[cold]
pub fn panic_custom(type_name: &str, v: Arg<'_>) -> ! {
let mut text = Vec::from(type_name.as_bytes());
if let Arg::Str(_) = v {
text.extend_from_slice(b"(\"");
push_panicval(&mut text, v);
text.extend_from_slice(b"\")");
} else {
text.push(b'(');
push_panicval(&mut text, v);
text.push(b')');
}
go_panic(GoPanic::new(text))
}
#[cold]
pub fn panic_iface(v: Iface) -> ! {
if v.is_nil() {
panic_nil();
}
let frame = crate::gc::Frame::<1>::new();
let mut text = Vec::new();
frame.scope(|| {
frame.set(0, &v);
v.print_to(&mut text);
});
let mut indented = Vec::with_capacity(text.len());
for b in text {
indented.push(b);
if b == b'\n' {
indented.push(b'\t');
}
}
go_panic(GoPanic::with_value(indented, v))
}
#[cold]
pub fn panic_nil() -> ! {
go_panic(GoPanic::new("panic called with nil argument"))
}
fn push_panicval(out: &mut Vec<u8>, v: Arg<'_>) {
match v {
Arg::Str(s) => {
for &b in s {
out.push(b);
if b == b'\n' {
out.push(b'\t');
}
}
}
_ => print::format(out, &[v], false),
}
}