diff --git a/src/hyperlight_component_util/src/host.rs b/src/hyperlight_component_util/src/host.rs index 79b682332..ce00b659c 100644 --- a/src/hyperlight_component_util/src/host.rs +++ b/src/hyperlight_component_util/src/host.rs @@ -46,6 +46,7 @@ fn emit_export_extern_decl<'a, 'b, 'c>( .map(|p| rtypes::emit_func_param(s, p)) .collect::>(); let result_decl = rtypes::emit_func_result(s, &ft.result); + let result_decl = quote! { ::std::result::Result<#result_decl, ::hyperlight_host::error::HyperlightError> }; let hln = emit_fn_hl_name(s, ed.kebab_name); let ret = format_ident!("ret"); let marshal = ft @@ -66,11 +67,11 @@ fn emit_export_extern_decl<'a, 'b, 'c>( #hln, marshalled, ); - let ::std::result::Result::Ok(#ret) = #ret else { panic!("bad return from guest {:?}", #ret) }; + let #ret = #ret?; #[allow(clippy::unused_unit)] let mut rts = self.rt.lock().unwrap(); #[allow(clippy::unused_unit)] - #unmarshal + Ok(#unmarshal) } } } @@ -215,17 +216,28 @@ fn emit_import_extern_decl<'a, 'b, 'c>( }) .unzip::<_, _, Vec<_>, Vec<_>>(); let tp = s.cur_trait_path(); - let callname = match kebab_to_fn(ed.kebab_name) { - FnName::Plain(n) => quote! { #tp::#n }, + let (callname, is_constructor) = match kebab_to_fn(ed.kebab_name) { + FnName::Plain(n) => (quote! { #tp::#n }, false), FnName::Associated(r, m) => { let hp = s.helper_path(); match m { - ResourceItemName::Constructor => quote! { #hp #r::new }, - ResourceItemName::Method(mn) => quote! { #hp #r::#mn }, - ResourceItemName::Static(mn) => quote! { #hp #r::#mn }, + ResourceItemName::Constructor => (quote! { #hp #r::new }, true), + ResourceItemName::Method(mn) => (quote! { #hp #r::#mn }, false), + ResourceItemName::Static(mn) => (quote! { #hp #r::#mn }, false), } } }; + // WIT constructors always return `own` with no + // spec-level mechanism for fallibility, so their trait + // signature stays as `fn new(...) -> Self::T` (no Result + // wrapper). Non-constructor trait methods return + // `Result` on the host side, so we append `?` to + // propagate errors from the host implementation. + let try_op = if is_constructor { + quote! {} + } else { + quote! { ? } + }; let SelfInfo { orig_id, type_id, @@ -246,7 +258,7 @@ fn emit_import_extern_decl<'a, 'b, 'c>( &mut #inner_id ), #(#pus),* - ); + )#try_op; Ok(#marshal_result) }) .unwrap(); diff --git a/src/hyperlight_component_util/src/rtypes.rs b/src/hyperlight_component_util/src/rtypes.rs index 427344dae..bbd5d2b40 100644 --- a/src/hyperlight_component_util/src/rtypes.rs +++ b/src/hyperlight_component_util/src/rtypes.rs @@ -632,6 +632,11 @@ fn emit_extern_decl<'a, 'b, 'c>( .map(|p| emit_func_param(&mut s, p)) .collect::>(); let result = emit_func_result(&mut s, &ft.result); + let result = if !s.is_guest { + quote! { ::std::result::Result<#result, ::hyperlight_host::error::HyperlightError> } + } else { + result + }; quote! { fn #n(&mut self, #(#params),*) -> #result; } @@ -654,12 +659,22 @@ fn emit_extern_decl<'a, 'b, 'c>( } ResourceItemName::Method(n) => { let result = emit_func_result(&mut sv, &ft.result); + let result = if !sv.is_guest { + quote! { ::std::result::Result<#result, ::hyperlight_host::error::HyperlightError> } + } else { + result + }; sv.cur_trait().items.extend(quote! { fn #n(&mut self, #(#params),*) -> #result; }); } ResourceItemName::Static(n) => { let result = emit_func_result(&mut sv, &ft.result); + let result = if !sv.is_guest { + quote! { ::std::result::Result<#result, ::hyperlight_host::error::HyperlightError> } + } else { + result + }; sv.cur_trait().items.extend(quote! { fn #n(&mut self, #(#params),*) -> #result; }); diff --git a/src/hyperlight_host/tests/wit_test.rs b/src/hyperlight_host/tests/wit_test.rs index 7d8f646f9..6e3d3c65a 100644 --- a/src/hyperlight_host/tests/wit_test.rs +++ b/src/hyperlight_host/tests/wit_test.rs @@ -18,6 +18,7 @@ limitations under the License. use std::sync::{Arc, Mutex}; use hyperlight_common::resource::BorrowedResourceGuard; +use hyperlight_host::error::HyperlightError; use hyperlight_host::{GuestBinary, MultiUseSandbox, UninitializedSandbox}; use hyperlight_testing::wit_guest_as_string; @@ -68,137 +69,157 @@ impl Clone for Testvariant { struct Host {} impl test::wit::Roundtrip for Host { - fn roundtrip_bool(&mut self, x: bool) -> bool { - x + fn roundtrip_bool(&mut self, x: bool) -> Result { + Ok(x) } - fn roundtrip_s8(&mut self, x: i8) -> i8 { - x + fn roundtrip_s8(&mut self, x: i8) -> Result { + Ok(x) } - fn roundtrip_s16(&mut self, x: i16) -> i16 { - x + fn roundtrip_s16(&mut self, x: i16) -> Result { + Ok(x) } - fn roundtrip_s32(&mut self, x: i32) -> i32 { - x + fn roundtrip_s32(&mut self, x: i32) -> Result { + Ok(x) } - fn roundtrip_s64(&mut self, x: i64) -> i64 { - x + fn roundtrip_s64(&mut self, x: i64) -> Result { + Ok(x) } - fn roundtrip_u8(&mut self, x: u8) -> u8 { - x + fn roundtrip_u8(&mut self, x: u8) -> Result { + Ok(x) } - fn roundtrip_u16(&mut self, x: u16) -> u16 { - x + fn roundtrip_u16(&mut self, x: u16) -> Result { + Ok(x) } - fn roundtrip_u32(&mut self, x: u32) -> u32 { - x + fn roundtrip_u32(&mut self, x: u32) -> Result { + Ok(x) } - fn roundtrip_u64(&mut self, x: u64) -> u64 { - x + fn roundtrip_u64(&mut self, x: u64) -> Result { + Ok(x) } - fn roundtrip_f32(&mut self, x: f32) -> f32 { - x + fn roundtrip_f32(&mut self, x: f32) -> Result { + Ok(x) } - fn roundtrip_f64(&mut self, x: f64) -> f64 { - x + fn roundtrip_f64(&mut self, x: f64) -> Result { + Ok(x) } - fn roundtrip_char(&mut self, x: char) -> char { - x + fn roundtrip_char(&mut self, x: char) -> Result { + Ok(x) } - fn roundtrip_string(&mut self, x: alloc::string::String) -> alloc::string::String { - x + fn roundtrip_string( + &mut self, + x: alloc::string::String, + ) -> Result { + Ok(x) } - fn roundtrip_list(&mut self, x: alloc::vec::Vec) -> alloc::vec::Vec { - x + fn roundtrip_list( + &mut self, + x: alloc::vec::Vec, + ) -> Result, HyperlightError> { + Ok(x) } - fn roundtrip_tuple(&mut self, x: (alloc::string::String, u8)) -> (alloc::string::String, u8) { - x + fn roundtrip_tuple( + &mut self, + x: (alloc::string::String, u8), + ) -> Result<(alloc::string::String, u8), HyperlightError> { + Ok(x) } fn roundtrip_option( &mut self, x: ::core::option::Option, - ) -> ::core::option::Option { - x + ) -> Result<::core::option::Option, HyperlightError> { + Ok(x) } fn roundtrip_result( &mut self, x: ::core::result::Result, - ) -> ::core::result::Result { - x + ) -> Result<::core::result::Result, HyperlightError> { + Ok(x) } fn roundtrip_record( &mut self, x: test::wit::roundtrip::Testrecord, - ) -> test::wit::roundtrip::Testrecord { - x + ) -> Result { + Ok(x) } fn roundtrip_flags_small( &mut self, x: test::wit::roundtrip::Smallflags, - ) -> test::wit::roundtrip::Smallflags { - x + ) -> Result { + Ok(x) } fn roundtrip_flags_large( &mut self, x: test::wit::roundtrip::Largeflags, - ) -> test::wit::roundtrip::Largeflags { - x + ) -> Result { + Ok(x) } fn roundtrip_variant( &mut self, x: test::wit::roundtrip::Testvariant, - ) -> test::wit::roundtrip::Testvariant { - x + ) -> Result { + Ok(x) } fn roundtrip_enum( &mut self, x: test::wit::roundtrip::Testenum, - ) -> test::wit::roundtrip::Testenum { - x + ) -> Result { + Ok(x) } - fn roundtrip_fix_list(&mut self, x: [u8; 4]) -> [u8; 4] { - x + fn roundtrip_fix_list(&mut self, x: [u8; 4]) -> Result<[u8; 4], HyperlightError> { + Ok(x) } - fn roundtrip_fix_list_u32(&mut self, x: [u32; 4]) -> [u32; 4] { - x + fn roundtrip_fix_list_u32(&mut self, x: [u32; 4]) -> Result<[u32; 4], HyperlightError> { + Ok(x) } - fn roundtrip_fix_list_u64(&mut self, x: [u64; 4]) -> [u64; 4] { - x + fn roundtrip_fix_list_u64(&mut self, x: [u64; 4]) -> Result<[u64; 4], HyperlightError> { + Ok(x) } - fn roundtrip_fix_list_i8(&mut self, x: [i8; 4]) -> [i8; 4] { - x + fn roundtrip_fix_list_i8(&mut self, x: [i8; 4]) -> Result<[i8; 4], HyperlightError> { + Ok(x) } - fn roundtrip_fix_list_i16(&mut self, x: [i16; 4]) -> [i16; 4] { - x + fn roundtrip_fix_list_i16(&mut self, x: [i16; 4]) -> Result<[i16; 4], HyperlightError> { + Ok(x) } - fn roundtrip_fix_list_i32(&mut self, x: [i32; 4]) -> [i32; 4] { - x + fn roundtrip_fix_list_i32(&mut self, x: [i32; 4]) -> Result<[i32; 4], HyperlightError> { + Ok(x) } - fn roundtrip_fix_list_i64(&mut self, x: [i64; 4]) -> [i64; 4] { - x + fn roundtrip_fix_list_i64(&mut self, x: [i64; 4]) -> Result<[i64; 4], HyperlightError> { + Ok(x) } - fn roundtrip_fix_list_f32(&mut self, x: [f32; 4]) -> [f32; 4] { - x + fn roundtrip_fix_list_f32(&mut self, x: [f32; 4]) -> Result<[f32; 4], HyperlightError> { + Ok(x) } - fn roundtrip_fix_list_f64(&mut self, x: [f64; 4]) -> [f64; 4] { - x + fn roundtrip_fix_list_f64(&mut self, x: [f64; 4]) -> Result<[f64; 4], HyperlightError> { + Ok(x) } - fn roundtrip_fix_list_u8_size8(&mut self, x: [u8; 8]) -> [u8; 8] { - x + fn roundtrip_fix_list_u8_size8(&mut self, x: [u8; 8]) -> Result<[u8; 8], HyperlightError> { + Ok(x) } - fn roundtrip_fix_list_u64_size2(&mut self, x: [u64; 2]) -> [u64; 2] { - x + fn roundtrip_fix_list_u64_size2(&mut self, x: [u64; 2]) -> Result<[u64; 2], HyperlightError> { + Ok(x) } - fn roundtrip_fix_list_string(&mut self, x: [String; 4]) -> [String; 4] { - x + fn roundtrip_fix_list_string( + &mut self, + x: [String; 4], + ) -> Result<[String; 4], HyperlightError> { + Ok(x) } - fn roundtrip_fix_array_of_lists(&mut self, x: [Vec; 3]) -> [Vec; 3] { - x + fn roundtrip_fix_array_of_lists( + &mut self, + x: [Vec; 3], + ) -> Result<[Vec; 3], HyperlightError> { + Ok(x) } - fn roundtrip_fix_array_of_string_lists(&mut self, x: [Vec; 2]) -> [Vec; 2] { - x + fn roundtrip_fix_array_of_string_lists( + &mut self, + x: [Vec; 2], + ) -> Result<[Vec; 2], HyperlightError> { + Ok(x) } - fn roundtrip_no_result(&mut self, _x: u32) {} + fn roundtrip_no_result(&mut self, _x: u32) -> Result<(), HyperlightError> { + Ok(()) + } } struct TestResource { @@ -236,7 +257,11 @@ impl test::wit::host_resource::Testresource for Host { fn new(&mut self, x: String, last: char) -> Self::T { TestResource::new(x, last) } - fn append_char(&mut self, self_: BorrowedResourceGuard<'_, Self::T>, c: char) { + fn append_char( + &mut self, + self_: BorrowedResourceGuard<'_, Self::T>, + c: char, + ) -> Result<(), HyperlightError> { let mut self_ = self_.lock().unwrap(); match self_.n_calls { // These line up to the initial values and calls made by @@ -260,16 +285,27 @@ impl test::wit::host_resource::Testresource for Host { self_.n_calls += 1; self_.x.push(c); self_.last = c; + Ok(()) } } impl test::wit::HostResource for Host { - fn roundtrip_own(&mut self, owned: Arc>) -> Arc> { - owned + fn roundtrip_own( + &mut self, + owned: Arc>, + ) -> Result>, HyperlightError> { + Ok(owned) } - fn return_own(&mut self, _: Arc>) { + fn return_own(&mut self, _: Arc>) -> Result<(), HyperlightError> { // Not much to do here other than let it be dropped + Ok(()) + } +} + +impl test::wit::FailableHost for Host { + fn will_fail(&mut self) -> Result { + Err(HyperlightError::Error("deliberate host error".into())) } } @@ -283,6 +319,10 @@ impl test::wit::TestImports for Host { fn host_resource(&mut self) -> &mut Self { self } + type FailableHost = Self; + fn failable_host(&mut self) -> &mut Self { + self + } } fn sb() -> TestSandbox { @@ -296,7 +336,9 @@ mod wit_test { use proptest::prelude::*; - use crate::bindings::test::wit::{Roundtrip, TestExports, TestHostResource, roundtrip}; + use crate::bindings::test::wit::{ + Failable, Roundtrip, TestExports, TestHostResource, roundtrip, + }; use crate::sb; prop_compose! { @@ -348,7 +390,7 @@ mod wit_test { proptest! { #[test] fn $fn(x $($ty)*) { - assert_eq!(x, sb().roundtrip().$fn(x.clone())) + assert_eq!(x, sb().roundtrip().$fn(x.clone()).unwrap()) } } } @@ -395,7 +437,7 @@ mod wit_test { #[test] fn test_roundtrip_no_result() { - sb().roundtrip().roundtrip_no_result(42); + sb().roundtrip().roundtrip_no_result(42).unwrap(); } use std::sync::atomic::Ordering::Relaxed; @@ -405,7 +447,7 @@ mod wit_test { let guard = crate::SERIALIZE_TEST_RESOURCE_TESTS.lock(); crate::HAS_BEEN_DROPPED.store(false, Relaxed); { - sb().test_host_resource().test_uses_locally(); + sb().test_host_resource().test_uses_locally().unwrap(); } assert!(crate::HAS_BEEN_DROPPED.load(Relaxed)); drop(guard); @@ -417,14 +459,38 @@ mod wit_test { { let mut sb = sb(); let inst = sb.test_host_resource(); - let r = inst.test_makes(); - inst.test_accepts_borrow(&r); - inst.test_accepts_own(r); - inst.test_returns(); + let r = inst.test_makes().unwrap(); + inst.test_accepts_borrow(&r).unwrap(); + inst.test_accepts_own(r).unwrap(); + inst.test_returns().unwrap(); } assert!(crate::HAS_BEEN_DROPPED.load(Relaxed)); drop(guard); } + + #[test] + fn test_guest_trap_returns_error() { + let mut sb = sb(); + let failable = sb.failable(); + let err = failable.will_trap().unwrap_err(); + let msg = format!("{err:?}"); + assert!( + msg.contains("deliberate guest crash"), + "Expected error to mention 'deliberate guest crash', got: {msg}" + ); + } + + #[test] + fn test_host_error_returns_error() { + let mut sb = sb(); + let failable = sb.failable(); + let err = failable.call_failing_host_func().unwrap_err(); + let msg = format!("{err:?}"); + assert!( + msg.contains("deliberate host error"), + "Expected error to mention 'deliberate host error', got: {msg}" + ); + } } mod pick_world_bindings { diff --git a/src/tests/rust_guests/dummyguest/Cargo.toml b/src/tests/rust_guests/dummyguest/Cargo.toml index ccf3a7010..dc3da9f05 100644 --- a/src/tests/rust_guests/dummyguest/Cargo.toml +++ b/src/tests/rust_guests/dummyguest/Cargo.toml @@ -12,3 +12,5 @@ hyperlight-common = { path = "../../../hyperlight_common", default-features = fa default = [] trace_guest = ["hyperlight-guest-bin/trace_guest"] mem_profile = ["hyperlight-common/mem_profile", "hyperlight-guest-bin/mem_profile"] + +[workspace] diff --git a/src/tests/rust_guests/simpleguest/Cargo.toml b/src/tests/rust_guests/simpleguest/Cargo.toml index 5edff7ff5..cd1fdf80f 100644 --- a/src/tests/rust_guests/simpleguest/Cargo.toml +++ b/src/tests/rust_guests/simpleguest/Cargo.toml @@ -16,3 +16,5 @@ default = [] trace_guest = ["hyperlight-guest-bin/trace_guest", "hyperlight-guest/trace_guest", "hyperlight-guest-tracing/trace"] mem_profile = ["hyperlight-common/mem_profile", "hyperlight-guest-bin/mem_profile"] +[workspace] + diff --git a/src/tests/rust_guests/witguest/Cargo.toml b/src/tests/rust_guests/witguest/Cargo.toml index 4db4984ab..d8331af5f 100644 --- a/src/tests/rust_guests/witguest/Cargo.toml +++ b/src/tests/rust_guests/witguest/Cargo.toml @@ -14,3 +14,5 @@ spin = "0.10.0" default = [] trace_guest = ["hyperlight-guest-bin/trace_guest", "hyperlight-guest/trace_guest"] mem_profile = ["hyperlight-common/mem_profile", "hyperlight-guest-bin/mem_profile"] + +[workspace] diff --git a/src/tests/rust_guests/witguest/guest.wit b/src/tests/rust_guests/witguest/guest.wit index 9ed164b80..97f558d6f 100644 --- a/src/tests/rust_guests/witguest/guest.wit +++ b/src/tests/rust_guests/witguest/guest.wit @@ -3,8 +3,10 @@ package test:wit; world test { import roundtrip; import host-resource; + import failable-host; export roundtrip; export test-host-resource; + export failable; } interface roundtrip { @@ -89,4 +91,16 @@ interface test-host-resource { test-accepts-borrow: func(x: borrow); test-accepts-own: func(x: own); test-returns: func() -> own; -} \ No newline at end of file +} + +interface failable { + /// This function will deliberately trap in the guest. + will-trap: func() -> string; + /// This function calls a host import that returns an error. + call-failing-host-func: func() -> string; +} + +interface failable-host { + /// This host function will deliberately return an error. + will-fail: func() -> string; +} diff --git a/src/tests/rust_guests/witguest/src/main.rs b/src/tests/rust_guests/witguest/src/main.rs index 307d9cca6..7c989f651 100644 --- a/src/tests/rust_guests/witguest/src/main.rs +++ b/src/tests/rust_guests/witguest/src/main.rs @@ -204,6 +204,16 @@ impl test::wit::TestHostResource<::T> for Guest { } } +impl test::wit::Failable for Guest { + fn will_trap(&mut self) -> alloc::string::String { + panic!("deliberate guest crash") + } + fn call_failing_host_func(&mut self) -> alloc::string::String { + use test::wit::FailableHost as _; + (Host {}).will_fail() + } +} + #[allow(refining_impl_trait)] impl test::wit::TestExports for Guest { type Roundtrip = Self; @@ -214,6 +224,10 @@ impl test::wit::TestExports for Guest { fn test_host_resource(&mut self) -> &mut Self { self } + type Failable = Self; + fn failable(&mut self) -> &mut Self { + self + } } static GUEST_STATE: Mutex = Mutex::new(Guest {