diff --git a/Changelog.md b/Changelog.md index df1ae06c65d..678815208c7 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,16 @@ * motoko (`moc`) + * BREAKING CHANGE + + Motoko now implements Candid 1.4 (dfinity/candid#311). + + In particular, when deserializing an actor or function reference, + Motoko will now first check that the type of the deserialized reference + is a subtype of the expected type and act accordingly. + + Very few users should be affected by this change in behaviour. + * BREAKING CHANGE On the IC, the act of making a call to a canister function can fail, so that the call cannot (and will not be) performed. diff --git a/nix/sources.json b/nix/sources.json index 5e19fa3c0ba..ea79ee1a716 100644 --- a/nix/sources.json +++ b/nix/sources.json @@ -6,10 +6,10 @@ "homepage": "", "owner": "dfinity", "repo": "candid", - "rev": "a555d77704d691bb8f34e21a049d44ba0acee3f8", - "sha256": "0vn171lcadpznrl5nq2mws2zjjqj9jxyvndb2is3dixbjqyvjssx", + "rev": "fa27eef96c96c2f774a479622996b42c7ae6c1bd", + "sha256": "18zhn0iyakq1212jizc406v9x275nivdnnwx5h2130ci10d7f1ah", "type": "tarball", - "url": "https://github.com/dfinity/candid/archive/a555d77704d691bb8f34e21a049d44ba0acee3f8.tar.gz", + "url": "https://github.com/dfinity/candid/archive/fa27eef96c96c2f774a479622996b42c7ae6c1bd.tar.gz", "url_template": "https://github.com///archive/.tar.gz" }, "esm": { diff --git a/rts/motoko-rts-tests/src/bitrel.rs b/rts/motoko-rts-tests/src/bitrel.rs new file mode 100644 index 00000000000..53689ff4ea5 --- /dev/null +++ b/rts/motoko-rts-tests/src/bitrel.rs @@ -0,0 +1,57 @@ +use motoko_rts::bitrel::BitRel; +use motoko_rts::types::{Value, Words}; + +pub unsafe fn test() { + println!("Testing bitrel ..."); + + const K: u32 = 128; + + const N: usize = (2 * K * K * 2 / usize::BITS) as usize; + + let mut cache: [u32; N] = [0xFFFFFFF; N]; + + assert_eq!(usize::BITS, 32); + for size1 in 0..K { + for size2 in 0..K { + let w = BitRel::words(size1, size2); + let bitrel = BitRel { + ptr: &mut cache[0], + end: &mut cache[w as usize], + size1: size1, + size2: size2, + }; + bitrel.init(); + for i in 0..size1 { + for j in 0..size2 { + // initially unvisited + assert!(!bitrel.visited(true, i, j)); // co + assert!(!bitrel.visited(false, j, i)); // contra + + // initially related + assert!(bitrel.related(true, i, j)); // co + assert!(bitrel.related(false, j, i)); // contra + + // test visiting + // co + bitrel.visit(true, i, j); + assert!(bitrel.visited(true, i, j)); + // contra + bitrel.visit(false, j, i); + assert!(bitrel.visited(false, j, i)); + + // test refutation + // co + bitrel.assume(true, i, j); + assert!(bitrel.related(true, i, j)); + bitrel.disprove(true, i, j); + assert!(!bitrel.related(true, i, j)); + // contra + bitrel.assume(false, j, i); + assert!(bitrel.related(false, j, i)); + bitrel.disprove(false, j, i); + assert!(!bitrel.related(false, j, i)); + } + } + } + } +} diff --git a/rts/motoko-rts-tests/src/main.rs b/rts/motoko-rts-tests/src/main.rs index 236be344bfb..04c8ea8b268 100644 --- a/rts/motoko-rts-tests/src/main.rs +++ b/rts/motoko-rts-tests/src/main.rs @@ -2,6 +2,7 @@ mod bigint; mod bitmap; +mod bitrel; mod continuation_table; mod crc32; mod gc; @@ -24,6 +25,7 @@ fn main() { unsafe { bigint::test(); bitmap::test(); + bitrel::test(); continuation_table::test(); crc32::test(); gc::test(); diff --git a/rts/motoko-rts/src/bitrel.rs b/rts/motoko-rts/src/bitrel.rs new file mode 100644 index 00000000000..ec71fcf78f6 --- /dev/null +++ b/rts/motoko-rts/src/bitrel.rs @@ -0,0 +1,92 @@ +//! This module implements a simple subtype cache used by the compiler (in generated code) + +use crate::constants::WORD_SIZE; +use crate::idl_trap_with; +use crate::mem_utils::memzero; +use crate::types::Words; + +const BITS: u32 = 2; + +#[repr(packed)] +pub struct BitRel { + /// Pointer into the bit set + pub ptr: *mut u32, + /// Pointer to the end of the bit set + /// must allow at least 2 * size1 * size2 bits + pub end: *mut u32, + pub size1: u32, + pub size2: u32, +} + +impl BitRel { + pub fn words(size1: u32, size2: u32) -> u32 { + return ((2 * size1 * size2 * BITS) + (usize::BITS - 1)) / usize::BITS; + } + + pub unsafe fn init(&self) { + if (self.end as usize) < (self.ptr as usize) { + idl_trap_with("BitRel invalid fields"); + }; + + let bytes = ((self.end as usize) - (self.ptr as usize)) as u32; + if bytes != BitRel::words(self.size1, self.size2) * WORD_SIZE { + idl_trap_with("BitRel missized"); + }; + memzero(self.ptr as usize, Words(bytes / WORD_SIZE)); + } + + unsafe fn locate_ptr_bit(&self, p: bool, i_j: u32, j_i: u32, bit: u32) -> (*mut u32, u32) { + let size1 = self.size1; + let size2 = self.size2; + let (base, i, j) = if p { (0, i_j, j_i) } else { (size1, j_i, i_j) }; + debug_assert!(i < size1); + debug_assert!(j < size2); + debug_assert!(bit < BITS); + let k = ((base + i) * size2 + j) * BITS + bit; + let word = (k / usize::BITS) as usize; + let bit = (k % usize::BITS) as u32; + let ptr = self.ptr.add(word); + if ptr > self.end { + idl_trap_with("BitRel indices out of bounds"); + }; + return (ptr, bit); + } + + unsafe fn set(&self, p: bool, i_j: u32, j_i: u32, bit: u32, v: bool) { + let (ptr, bit) = self.locate_ptr_bit(p, i_j, j_i, bit); + if v { + *ptr = *ptr | (1 << bit); + } else { + *ptr = *ptr & !(1 << bit); + } + } + + unsafe fn get(&self, p: bool, i_j: u32, j_i: u32, bit: u32) -> bool { + let (ptr, bit) = self.locate_ptr_bit(p, i_j, j_i, bit); + let mask = 1 << bit; + return *ptr & mask == mask; + } + + pub unsafe fn visited(&self, p: bool, i_j: u32, j_i: u32) -> bool { + self.get(p, i_j, j_i, 0) + } + + pub unsafe fn visit(&self, p: bool, i_j: u32, j_i: u32) { + self.set(p, i_j, j_i, 0, true) + } + + #[allow(dead_code)] + // NB: we store related bits in negated form to avoid setting on assumption + // This code is a nop in production code. + pub unsafe fn assume(&self, p: bool, i_j: u32, j_i: u32) { + debug_assert!(!self.get(p, i_j, j_i, 1)); + } + + pub unsafe fn related(&self, p: bool, i_j: u32, j_i: u32) -> bool { + !self.get(p, i_j, j_i, 1) + } + + pub unsafe fn disprove(&self, p: bool, i_j: u32, j_i: u32) { + self.set(p, i_j, j_i, 1, true) + } +} diff --git a/rts/motoko-rts/src/idl.rs b/rts/motoko-rts/src/idl.rs index 6d184ba8e3a..16581b5e428 100644 --- a/rts/motoko-rts/src/idl.rs +++ b/rts/motoko-rts/src/idl.rs @@ -1,5 +1,5 @@ #![allow(non_upper_case_globals)] - +use crate::bitrel::BitRel; use crate::buf::{read_byte, read_word, skip_leb128, Buf}; use crate::idl_trap_with; use crate::leb128::{leb128_decode, sleb128_decode}; @@ -46,10 +46,33 @@ const IDL_CON_alias: i32 = 1; const IDL_PRIM_lowest: i32 = -17; +pub unsafe fn leb128_decode_ptr(buf: *mut Buf) -> (u32, *mut u8) { + (leb128_decode(buf), (*buf).ptr) +} + unsafe fn is_primitive_type(ty: i32) -> bool { ty < 0 && (ty >= IDL_PRIM_lowest || ty == IDL_REF_principal) } +// TBR; based on Text.text_compare +unsafe fn utf8_cmp(len1: u32, p1: *mut u8, len2: u32, p2: *mut u8) -> i32 { + let len = min(len1, len2); + let cmp = libc::memcmp( + p1 as *mut libc::c_void, + p2 as *mut libc::c_void, + len as usize, + ); + if cmp != 0 { + return cmp; + } else if len1 > len { + return 1; + } else if len2 > len { + return -1; + } else { + return 0; + } +} + unsafe fn check_typearg(ty: i32, n_types: u32) { // Arguments to type constructors can be primitive types or type indices if !(is_primitive_type(ty) || (ty >= 0 && (ty as u32) < n_types)) { @@ -169,14 +192,18 @@ unsafe fn parse_idl_header( if !(1 <= a && a <= 2) { idl_trap_with("func annotation not within 1..2"); } + // TODO: shouldn't we also check + // * 1 (query) or 2 (oneway), but not both + // * 2 -> |Ret types| == 0 + // c.f. https://github.com/dfinity/candid/issues/318 + // NB: if this code changes, change sub type check below accordingly } } else if ty == IDL_CON_service { let mut last_len: u32 = 0 as u32; let mut last_p = core::ptr::null_mut(); for _ in 0..leb128_decode(buf) { // Name - let len = leb128_decode(buf); - let p = (*buf).ptr; + let (len, p) = leb128_decode_ptr(buf); buf.advance(len); // Method names must be valid unicode utf8_validate(p as *const _, len); @@ -264,8 +291,7 @@ unsafe fn skip_blob(buf: *mut Buf) { } unsafe fn skip_text(buf: *mut Buf) { - let len = leb128_decode(buf); - let p = (*buf).ptr; + let (len, p) = leb128_decode_ptr(buf); buf.advance(len); // advance first; does the bounds check utf8_validate(p as *const _, len); } @@ -293,7 +319,7 @@ unsafe fn skip_any_vec(buf: *mut Buf, typtbl: *mut *mut u8, t: i32, count: u32) // Assumes all type references in the typtbl are already checked // // This is currently implemented recursively, but we could -// do this in a loop (by maintaing a stack of the t arguments) +// do this in a loop (by maintaining a stack of the t arguments) #[no_mangle] unsafe extern "C" fn skip_any(buf: *mut Buf, typtbl: *mut *mut u8, t: i32, depth: i32) { if depth > 100 { @@ -488,3 +514,340 @@ unsafe extern "C" fn skip_fields(tb: *mut Buf, buf: *mut Buf, typtbl: *mut *mut *n -= 1; } } + +unsafe fn is_opt_reserved(typtbl: *mut *mut u8, end: *mut u8, t: i32) -> bool { + if is_primitive_type(t) { + return t == IDL_PRIM_reserved; + } + + // unfold t + let mut t = t; + + let mut tb = Buf { + ptr: *typtbl.add(t as usize), + end: end, + }; + + t = sleb128_decode(&mut tb); + + return t == IDL_CON_opt; +} + +// TODO: consider storing fixed args typtbl1...end2 in `rel` to use less stack. +unsafe fn sub( + rel: &BitRel, + p: bool, + typtbl1: *mut *mut u8, + typtbl2: *mut *mut u8, + end1: *mut u8, + end2: *mut u8, + t1: i32, + t2: i32, +) -> bool { + if t1 >= 0 && t2 >= 0 { + let t1 = t1 as u32; + let t2 = t2 as u32; + if rel.visited(p, t1, t2) { + // visited? (bit 0) + // return assumed or determined result + return rel.related(p, t1, t2); + }; + // cache and continue + rel.visit(p, t1, t2); // mark visited + rel.assume(p, t1, t2); // assume t1 <:/:> t2 true + }; + + /* primitives reflexive */ + if is_primitive_type(t1) && is_primitive_type(t2) && t1 == t2 { + return true; + } + + // unfold t1, if necessary + let mut tb1 = Buf { + ptr: if t1 < 0 { + end1 + } else { + *typtbl1.add(t1 as usize) + }, + end: end1, + }; + + let u1 = if t1 >= 0 { + sleb128_decode(&mut tb1) + } else { + t1 + }; + + // unfold t2, if necessary + let mut tb2 = Buf { + ptr: if t2 < 0 { + end2 + } else { + *typtbl2.add(t2 as usize) + }, + end: end2, + }; + + let u2 = if t2 >= 0 { + sleb128_decode(&mut tb2) + } else { + t2 + }; + + // NB we use a trivial labelled loop so we can factor out the common failure continuation. + // exit either via 'return true' or 'break 'return_false' to memoize the negative result + 'return_false: loop { + match (u1, u2) { + (_, IDL_CON_alias) | (IDL_CON_alias, _) => idl_trap_with("sub: unexpected alias"), + (_, IDL_PRIM_reserved) + | (IDL_PRIM_empty, _) + | (IDL_PRIM_nat, IDL_PRIM_int) + | (_, IDL_CON_opt) => return true, // apparently, this is admissable + (IDL_CON_vec, IDL_CON_vec) => { + let t11 = sleb128_decode(&mut tb1); + let t21 = sleb128_decode(&mut tb2); + if sub(rel, p, typtbl1, typtbl2, end1, end2, t11, t21) { + return true; + } else { + break 'return_false; + } + } + (IDL_CON_func, IDL_CON_func) => { + // contra in domain + let in1 = leb128_decode(&mut tb1); + let mut in2 = leb128_decode(&mut tb2); + for _ in 0..in1 { + let t11 = sleb128_decode(&mut tb1); + if in2 == 0 { + if !is_opt_reserved(typtbl1, end1, t11) { + break 'return_false; + } + } else { + let t21 = sleb128_decode(&mut tb2); + in2 -= 1; + // NB: invert p and args! + if !sub(rel, !p, typtbl2, typtbl1, end2, end1, t21, t11) { + break 'return_false; + } + } + } + while in2 > 0 { + let _ = sleb128_decode(&mut tb2); + in2 -= 1; + } + // co in range + let mut out1 = leb128_decode(&mut tb1); + let out2 = leb128_decode(&mut tb2); + for _ in 0..out2 { + let t21 = sleb128_decode(&mut tb2); + if out1 == 0 { + if !is_opt_reserved(typtbl2, end2, t21) { + break 'return_false; + } + } else { + let t11 = sleb128_decode(&mut tb1); + out1 -= 1; + if !sub(rel, p, typtbl1, typtbl2, end1, end2, t11, t21) { + break 'return_false; + } + } + } + while out1 > 0 { + let _ = sleb128_decode(&mut tb1); + out1 -= 1; + } + // check annotations (that we care about) + // TODO: more generally, we would check equality of 256-bit bit-vectors, + // but validity ensures each entry is 1 or 2 (for now) + // c.f. https://github.com/dfinity/candid/issues/318 + let mut a11 = false; + let mut a12 = false; + for _ in 0..leb128_decode(&mut tb1) { + match read_byte(&mut tb1) { + 1 => a11 = true, + 2 => a12 = true, + _ => {} + } + } + let mut a21 = false; + let mut a22 = false; + for _ in 0..leb128_decode(&mut tb2) { + match read_byte(&mut tb2) { + 1 => a21 = true, + 2 => a22 = true, + _ => {} + } + } + if (a11 == a21) && (a12 == a22) { + return true; + } else { + break 'return_false; + } + } + (IDL_CON_record, IDL_CON_record) => { + let mut n1 = leb128_decode(&mut tb1); + let n2 = leb128_decode(&mut tb2); + let mut tag1 = 0; + let mut t11 = 0; + let mut advance = true; + for _ in 0..n2 { + let tag2 = leb128_decode(&mut tb2); + let t21 = sleb128_decode(&mut tb2); + if n1 == 0 { + // check all remaining fields optional + if !is_opt_reserved(typtbl2, end2, t21) { + break 'return_false; + } + continue; + }; + if advance { + loop { + tag1 = leb128_decode(&mut tb1); + t11 = sleb128_decode(&mut tb1); + n1 -= 1; + if !(tag1 < tag2 && n1 > 0) { + break; + } + } + }; + if tag1 > tag2 { + if !is_opt_reserved(typtbl2, end2, t21) { + // missing, non_opt field + break 'return_false; + } + advance = false; // reconsider this field in next round + continue; + }; + if !sub(rel, p, typtbl1, typtbl2, end1, end2, t11, t21) { + break 'return_false; + } + advance = true; + } + return true; + } + (IDL_CON_variant, IDL_CON_variant) => { + let n1 = leb128_decode(&mut tb1); + let mut n2 = leb128_decode(&mut tb2); + for _ in 0..n1 { + if n2 == 0 { + break 'return_false; + }; + let tag1 = leb128_decode(&mut tb1); + let t11 = sleb128_decode(&mut tb1); + let mut tag2: u32; + let mut t21: i32; + loop { + tag2 = leb128_decode(&mut tb2); + t21 = sleb128_decode(&mut tb2); + n2 -= 1; + if !(tag2 < tag1 && n2 > 0) { + break; + } + } + if tag1 != tag2 { + break 'return_false; + }; + if !sub(rel, p, typtbl1, typtbl2, end1, end2, t11, t21) { + break 'return_false; + } + } + return true; + } + (IDL_CON_service, IDL_CON_service) => { + let mut n1 = leb128_decode(&mut tb1); + let n2 = leb128_decode(&mut tb2); + for _ in 0..n2 { + if n1 == 0 { + break 'return_false; + }; + let (len2, p2) = leb128_decode_ptr(&mut tb2); + Buf::advance(&mut tb2, len2); + let t21 = sleb128_decode(&mut tb2); + let mut len1: u32; + let mut p1: *mut u8; + let mut t11: i32; + let mut cmp: i32; + loop { + (len1, p1) = leb128_decode_ptr(&mut tb1); + Buf::advance(&mut tb1, len1); + t11 = sleb128_decode(&mut tb1); + n1 -= 1; + cmp = utf8_cmp(len1, p1, len2, p2); + if cmp < 0 && n1 > 0 { + continue; + }; + break; + } + if cmp != 0 { + break 'return_false; + }; + if !sub(rel, p, typtbl1, typtbl2, end1, end2, t11, t21) { + break 'return_false; + } + } + return true; + } + // default + (_, _) => { + break 'return_false; + } + } + } + // remember negative result ... + if t1 >= 0 && t2 >= 0 { + rel.disprove(p, t1 as u32, t2 as u32); + } + // .. only then return false + return false; +} + +#[no_mangle] +unsafe extern "C" fn idl_sub_buf_words(typtbl_size1: u32, typtbl_size2: u32) -> u32 { + return BitRel::words(typtbl_size1, typtbl_size2); +} + +#[no_mangle] +unsafe extern "C" fn idl_sub_buf_init(rel_buf: *mut u32, typtbl_size1: u32, typtbl_size2: u32) { + let rel = BitRel { + ptr: rel_buf, + end: rel_buf.add(idl_sub_buf_words(typtbl_size1, typtbl_size2) as usize), + size1: typtbl_size1, + size2: typtbl_size2, + }; + rel.init(); +} + +#[no_mangle] +unsafe extern "C" fn idl_sub( + rel_buf: *mut u32, // a buffer with at least 2 * typtbl_size1 * typtbl_size2 bits + typtbl1: *mut *mut u8, + typtbl2: *mut *mut u8, + typtbl_end1: *mut u8, + typtbl_end2: *mut u8, + typtbl_size1: u32, + typtbl_size2: u32, + t1: i32, + t2: i32, +) -> bool { + debug_assert!(rel_buf != (0 as *mut u32)); + + let rel = BitRel { + ptr: rel_buf, + end: rel_buf.add(idl_sub_buf_words(typtbl_size1, typtbl_size2) as usize), + size1: typtbl_size1, + size2: typtbl_size2, + }; + + debug_assert!(t1 < (typtbl_size1 as i32) && t2 < (typtbl_size2 as i32)); + + return sub( + &rel, + true, + typtbl1, + typtbl2, + typtbl_end1, + typtbl_end2, + t1, + t2, + ); +} diff --git a/rts/motoko-rts/src/lib.rs b/rts/motoko-rts/src/lib.rs index 9896d23b67f..c8ac1db0279 100644 --- a/rts/motoko-rts/src/lib.rs +++ b/rts/motoko-rts/src/lib.rs @@ -10,6 +10,7 @@ mod print; pub mod debug; pub mod bigint; +pub mod bitrel; #[cfg(feature = "ic")] mod blob_iter; pub mod buf; diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml index d07e63ea080..cc0af5f29ef 100644 --- a/src/codegen/compile.ml +++ b/src/codegen/compile.ml @@ -284,6 +284,11 @@ module E = struct static_roots : int32 list ref; (* GC roots in static memory. (Everything that may be mutable.) *) + (* Types accumulated in global typtbl (for candid subtype checks) + See Note [Candid subtype checks] + *) + typtbl_typs : Type.typ list ref; + (* Metadata *) args : (bool * string) option ref; service : (bool * string) option ref; @@ -291,7 +296,6 @@ module E = struct labs : LabSet.t ref; (* Used labels (fields and variants), collected for Motoko custom section 0 *) - (* Local fields (only valid/used inside a function) *) (* Static *) n_param : int32; (* Number of parameters (to calculate indices of locals) *) @@ -324,6 +328,7 @@ module E = struct static_memory = ref []; static_memory_frozen = ref false; static_roots = ref []; + typtbl_typs = ref []; (* Metadata *) args = ref None; service = ref None; @@ -360,11 +365,11 @@ module E = struct let mode (env : t) = env.mode let add_anon_local (env : t) ty = - let i = reg env.locals ty in - Wasm.I32.add env.n_param i + let i = reg env.locals ty in + Wasm.I32.add env.n_param i let add_local_name (env : t) li name = - let _ = reg env.local_names (li, name) in () + let _ = reg env.local_names (li, name) in () let get_locals (env : t) = !(env.locals) let get_local_names (env : t) : (int32 * string) list = !(env.local_names) @@ -548,6 +553,9 @@ module E = struct env.static_strings := StringEnv.add b ptr !(env.static_strings); ptr + let add_static_unskewed (env : t) (data : StaticBytes.t) : int32 = + Int32.add (add_static env data) ptr_unskew + let get_end_of_static_memory env : int32 = env.static_memory_frozen := true; !(env.end_of_static_memory) @@ -573,6 +581,16 @@ module E = struct in let gc_fn = if !Flags.force_gc then gc_fn else "schedule_" ^ gc_fn in call_import env "rts" (gc_fn ^ "_gc") + + (* See Note [Candid subtype checks] *) + (* NB: we don't bother detecting duplicate registrations here because the code sharing machinery + ensures that `add_typtbl_typ t` is called at most once for any `t` with a distinct type hash *) + let add_typtbl_typ (env : t) ty : Int32.t = + reg env.typtbl_typs ty + + let get_typtbl_typs (env : t) : Type.typ list = + !(env.typtbl_typs) + end @@ -805,7 +823,16 @@ module Func = struct (G.i (LocalGet (nr 2l))) (G.i (LocalGet (nr 3l))) ) - let share_code7 env name (p1, p2, p3, p4, p5, p6, p7) retty mk_body = + let share_code6 env name (p1, p2, p3, p4, p5, p6) retty mk_body = + share_code env name [p1; p2; p3; p4; p5; p6] retty (fun env -> mk_body env + (G.i (LocalGet (nr 0l))) + (G.i (LocalGet (nr 1l))) + (G.i (LocalGet (nr 2l))) + (G.i (LocalGet (nr 3l))) + (G.i (LocalGet (nr 4l))) + (G.i (LocalGet (nr 5l))) + ) + let _share_code7 env name (p1, p2, p3, p4, p5, p6, p7) retty mk_body = share_code env name [p1; p2; p3; p4; p5; p6; p7] retty (fun env -> mk_body env (G.i (LocalGet (nr 0l))) (G.i (LocalGet (nr 1l))) @@ -815,6 +842,18 @@ module Func = struct (G.i (LocalGet (nr 5l))) (G.i (LocalGet (nr 6l))) ) + let share_code9 env name (p1, p2, p3, p4, p5, p6, p7, p8, p9) retty mk_body = + share_code env name [p1; p2; p3; p4; p5; p6; p7; p8; p9] retty (fun env -> mk_body env + (G.i (LocalGet (nr 0l))) + (G.i (LocalGet (nr 1l))) + (G.i (LocalGet (nr 2l))) + (G.i (LocalGet (nr 3l))) + (G.i (LocalGet (nr 4l))) + (G.i (LocalGet (nr 5l))) + (G.i (LocalGet (nr 6l))) + (G.i (LocalGet (nr 7l))) + (G.i (LocalGet (nr 8l))) + ) end (* Func *) @@ -825,6 +864,10 @@ module RTS = struct E.add_func_import env "rts" "memcmp" [I32Type; I32Type; I32Type] [I32Type]; E.add_func_import env "rts" "version" [] [I32Type]; E.add_func_import env "rts" "parse_idl_header" [I32Type; I32Type; I32Type; I32Type; I32Type] []; + E.add_func_import env "rts" "idl_sub_buf_words" [I32Type; I32Type] [I32Type]; + E.add_func_import env "rts" "idl_sub_buf_init" [I32Type; I32Type; I32Type] []; + E.add_func_import env "rts" "idl_sub" + [I32Type; I32Type; I32Type; I32Type; I32Type; I32Type; I32Type; I32Type; I32Type] [I32Type]; E.add_func_import env "rts" "leb128_decode" [I32Type] [I32Type]; E.add_func_import env "rts" "sleb128_decode" [I32Type] [I32Type]; E.add_func_import env "rts" "bigint_of_word32" [I32Type] [I32Type]; @@ -1109,7 +1152,7 @@ module Stack = struct All pointers here are unskewed. *) - let end_ = page_size (* 64k of stack *) + let end_ = Int32.mul 2l page_size (* 128k of stack *) let register_globals env = (* stack pointer *) @@ -1121,6 +1164,7 @@ module Stack = struct let set_stack_ptr env = G.i (GlobalSet (nr (E.get_global env "__stack_pointer"))) + (* TODO: check for overflow *) let alloc_words env n = get_stack_ptr env ^^ compile_unboxed_const (Int32.mul n Heap.word_size) ^^ @@ -1134,12 +1178,42 @@ module Stack = struct G.i (Binary (Wasm.Values.I32 I32Op.Add)) ^^ set_stack_ptr env + (* TODO: why not just remember and reset the stack pointer, instead of calling free_words? Also below *) let with_words env name n f = let (set_x, get_x) = new_local env name in alloc_words env n ^^ set_x ^^ f get_x ^^ free_words env n + let dynamic_alloc_words env get_n = + get_stack_ptr env ^^ + compile_divU_const Heap.word_size ^^ + get_n ^^ + G.i (Compare (Wasm.Values.I32 I32Op.LtU)) ^^ + E.then_trap_with env "RTS Stack underflow" ^^ + get_stack_ptr env ^^ + get_n ^^ + compile_mul_const Heap.word_size ^^ + G.i (Binary (Wasm.Values.I32 I32Op.Sub)) ^^ + set_stack_ptr env ^^ + get_stack_ptr env + + let dynamic_free_words env get_n = + get_stack_ptr env ^^ + get_n ^^ + compile_mul_const Heap.word_size ^^ + G.i (Binary (Wasm.Values.I32 I32Op.Add)) ^^ + set_stack_ptr env + + (* TODO: why not just remember and reset the stack pointer, instead of calling free_words? Also above*) + let dynamic_with_words env name f = + let (set_n, get_n) = new_local env "n" in + let (set_x, get_x) = new_local env name in + set_n ^^ + dynamic_alloc_words env get_n ^^ set_x ^^ + f get_x ^^ + dynamic_free_words env get_n + end (* Stack *) @@ -4807,6 +4881,24 @@ module MakeSerialization (Strm : Stream) = struct module Strm = Strm + (* Globals recording known Candid types + See Note [Candid subtype checks] + *) + let register_delayed_globals env = + (E.add_global32_delayed env "__typtbl" Immutable, + E.add_global32_delayed env "__typtbl_end" Immutable, + E.add_global32_delayed env "__typtbl_size" Immutable, + E.add_global32_delayed env "__typtbl_idltyps" Immutable) + + let get_typtbl env = + G.i (GlobalGet (nr (E.get_global env "__typtbl"))) + let get_typtbl_size env = + G.i (GlobalGet (nr (E.get_global env "__typtbl_size"))) + let get_typtbl_end env = + G.i (GlobalGet (nr (E.get_global env "__typtbl_end"))) + let get_typtbl_idltyps env = + G.i (GlobalGet (nr (E.get_global env "__typtbl_idltyps"))) + open Typ_hash let sort_by_hash fs = @@ -4854,8 +4946,10 @@ module MakeSerialization (Strm : Stream) = struct let idl_service = -23l let idl_alias = 1l (* see Note [mutable stable values] *) - - let type_desc env ts : string = + (* TODO: use record *) + let type_desc env ts : + string * int list * int32 list (* type_desc, (relative offsets), indices of ts *) + = let open Type in (* Type traversal *) @@ -4928,6 +5022,12 @@ module MakeSerialization (Strm : Stream) = struct | Some i -> add_sleb128 (Int32.neg i) | None -> add_sleb128 (TM.find (normalize t) idx) in + let idx t = + let t = Type.normalize t in + match to_idl_prim t with + | Some i -> Int32.neg i + | None -> TM.find (normalize t) idx in + let rec add_typ t = match t with | Non -> assert false @@ -4991,10 +5091,35 @@ module MakeSerialization (Strm : Stream) = struct Buffer.add_string buf "DIDL"; add_leb128 (List.length typs); - List.iter add_typ typs; + let offsets = List.map (fun typ -> + let offset = Buffer.length buf in + add_typ typ; + offset) + typs + in add_leb128 (List.length ts); List.iter add_idx ts; - Buffer.contents buf + (Buffer.contents buf, + offsets, + List.map idx ts) + + (* See Note [Candid subtype checks] *) + let set_delayed_globals (env : E.t) (set_typtbl, set_typtbl_end, set_typtbl_size, set_typtbl_idltyps) = + let typdesc, offsets, idltyps = type_desc env (E.get_typtbl_typs env) in + let static_typedesc = E.add_static_unskewed env [StaticBytes.Bytes typdesc] in + let static_typtbl = + let bytes = StaticBytes.i32s + (List.map (fun offset -> + Int32.(add static_typedesc (of_int(offset)))) + offsets) + in + E.add_static_unskewed env [bytes] + in + let static_idltyps = E.add_static_unskewed env [StaticBytes.i32s idltyps] in + set_typtbl static_typtbl; + set_typtbl_end Int32.(add static_typedesc (of_int (String.length typdesc))); + set_typtbl_size (Int32.of_int (List.length offsets)); + set_typtbl_idltyps static_idltyps (* Returns data (in bytes) and reference buffer size (in entries) needed *) let rec buffer_size env t = @@ -5302,8 +5427,48 @@ module MakeSerialization (Strm : Stream) = struct I32 Tagged.(int_of_tag CoercionFailure); ] + (* See Note [Candid subtype checks] *) + let with_rel_buf_opt env extended get_typtbl_size1 f = + if extended then + f (compile_unboxed_const 0l) + else + get_typtbl_size1 ^^ get_typtbl_size env ^^ + E.call_import env "rts" "idl_sub_buf_words" ^^ + Stack.dynamic_with_words env "rel_buf" (fun get_ptr -> + get_ptr ^^ get_typtbl_size1 ^^ get_typtbl_size env ^^ + E.call_import env "rts" "idl_sub_buf_init" ^^ + f get_ptr) + + (* See Note [Candid subtype checks] *) + let idl_sub env t2 = + let idx = E.add_typtbl_typ env t2 in + get_typtbl_idltyps env ^^ + G.i (Load {ty = I32Type; align = 0; offset = Int32.mul idx 4l (*!*); sz = None}) ^^ + Func.share_code6 env ("idl_sub") + (("rel_buf", I32Type), + ("typtbl1", I32Type), + ("typtbl_end1", I32Type), + ("typtbl_size1", I32Type), + ("idltyp1", I32Type), + ("idltyp2", I32Type) + ) + [I32Type] + (fun env get_rel_buf get_typtbl1 get_typtbl_end1 get_typtbl_size1 get_idltyp1 get_idltyp2 -> + get_rel_buf ^^ + E.else_trap_with env "null rel_buf" ^^ + get_rel_buf ^^ + get_typtbl1 ^^ + get_typtbl env ^^ + get_typtbl_end1 ^^ + get_typtbl_end env ^^ + get_typtbl_size1 ^^ + get_typtbl_size env ^^ + get_idltyp1 ^^ + get_idltyp2 ^^ + E.call_import env "rts" "idl_sub") + (* The main deserialization function, generated once per type hash. - Is parameters are: + Its parameters are: * data_buffer: The current position of the input data buffer * ref_buffer: The current position of the input references buffer * typtbl: The type table, as returned by parse_idl_header @@ -5319,16 +5484,18 @@ module MakeSerialization (Strm : Stream) = struct let open Type in let t = Type.normalize t in let name = "@deserialize_go<" ^ typ_hash t ^ ">" in - Func.share_code7 env name - (("data_buffer", I32Type), + Func.share_code9 env name + (("rel_buf_opt", I32Type), + ("data_buffer", I32Type), ("ref_buffer", I32Type), ("typtbl", I32Type), - ("idltyp", I32Type), + ("typtbl_end", I32Type), ("typtbl_size", I32Type), + ("idltyp", I32Type), ("depth", I32Type), ("can_recover", I32Type) ) [I32Type] - (fun env get_data_buf get_ref_buf get_typtbl get_idltyp get_typtbl_size get_depth get_can_recover -> + (fun env get_rel_buf_opt get_data_buf get_ref_buf get_typtbl get_typtbl_end get_typtbl_size get_idltyp get_depth get_can_recover -> (* Check recursion depth (protects against empty record etc.) *) (* Factor 2 because at each step, the expected type could go through one @@ -5346,11 +5513,13 @@ module MakeSerialization (Strm : Stream) = struct let go' can_recover env t = let (set_idlty, get_idlty) = new_local env "idl_ty" in set_idlty ^^ + get_rel_buf_opt ^^ get_data_buf ^^ get_ref_buf ^^ get_typtbl ^^ - get_idlty ^^ + get_typtbl_end ^^ get_typtbl_size ^^ + get_idlty ^^ ( (* Reset depth counter if we made progress *) ReadBuf.get_ptr get_data_buf ^^ get_old_pos ^^ G.i (Compare (Wasm.Values.I32 I32Op.Eq)) ^^ @@ -5883,16 +6052,46 @@ module MakeSerialization (Strm : Stream) = struct ( coercion_failed "IDL error: unexpected variant tag" ) ) | Func _ -> - with_composite_typ idl_func (fun _get_typ_buf -> - read_byte_tagged - [ E.trap_with env "IDL error: unexpected function reference" - ; read_actor_data () ^^ - read_text () ^^ - Tuple.from_stack env 2 - ] - ); + (* See Note [Candid subtype checks] *) + get_rel_buf_opt ^^ + G.if1 I32Type + begin + get_rel_buf_opt ^^ + get_typtbl ^^ + get_typtbl_end ^^ + get_typtbl_size ^^ + get_idltyp ^^ + idl_sub env t + end + (Bool.lit true) ^^ (* if we don't have a subtype memo table, assume the types are ok *) + G.if1 I32Type + (with_composite_typ idl_func (fun _get_typ_buf -> + read_byte_tagged + [ E.trap_with env "IDL error: unexpected function reference" + ; read_actor_data () ^^ + read_text () ^^ + Tuple.from_stack env 2 + ])) + (skip get_idltyp ^^ + coercion_failed "IDL error: incompatible function type") | Obj (Actor, _) -> - with_composite_typ idl_service (fun _get_typ_buf -> read_actor_data ()) + (* See Note [Candid subtype checks] *) + get_rel_buf_opt ^^ + G.if1 I32Type + begin + get_rel_buf_opt ^^ + get_typtbl ^^ + get_typtbl_end ^^ + get_typtbl_size ^^ + get_idltyp ^^ + idl_sub env t + end + (Bool.lit true) ^^ + G.if1 I32Type + (with_composite_typ idl_service + (fun _get_typ_buf -> read_actor_data ())) + (skip get_idltyp ^^ + coercion_failed "IDL error: incompatible actor type") | Mut t -> read_alias env (Mut t) (fun get_arg_typ on_alloc -> let (set_result, get_result) = new_local env "result" in @@ -5903,7 +6102,8 @@ module MakeSerialization (Strm : Stream) = struct Heap.store_field MutBox.field ) | Non -> - E.trap_with env "IDL error: deserializing value of type None" + skip get_idltyp ^^ + coercion_failed "IDL error: deserializing value of type None" | _ -> todo_trap env "deserialize" (Arrange_ir.typ t) end ^^ (* Parsed value on the stack, return that, unless the failure flag is set *) @@ -5917,7 +6117,7 @@ module MakeSerialization (Strm : Stream) = struct let (set_data_size, get_data_size) = new_local env "data_size" in let (set_refs_size, get_refs_size) = new_local env "refs_size" in - let tydesc = type_desc env ts in + let (tydesc, _offsets, _idltyps) = type_desc env ts in let tydesc_len = Int32.of_int (String.length tydesc) in (* Get object sizes *) @@ -5960,9 +6160,13 @@ module MakeSerialization (Strm : Stream) = struct Strm.terminate env get_data_start get_data_size tydesc_len ) + let deserialize_from_blob extended env ts = let ts_name = typ_seq_hash ts in let name = + (* TODO(#3185): this specialization on `extended` seems redundant, + removing it might simplify things *and* share more code in binaries. + The only tricky bit might be the conditional Stack.dynamic_with_words bit... *) if extended then "@deserialize_extended<" ^ ts_name ^ ">" else "@deserialize<" ^ ts_name ^ ">" in @@ -5997,48 +6201,68 @@ module MakeSerialization (Strm : Stream) = struct Bool.lit extended ^^ get_data_buf ^^ get_typtbl_ptr ^^ get_typtbl_size_ptr ^^ get_maintyps_ptr ^^ E.call_import env "rts" "parse_idl_header" ^^ + (* Allocate memo table, if necessary *) + with_rel_buf_opt env extended (get_typtbl_size_ptr ^^ load_unskewed_ptr) (fun get_rel_buf_opt -> + (* set up a dedicated read buffer for the list of main types *) ReadBuf.alloc env (fun get_main_typs_buf -> ReadBuf.set_ptr get_main_typs_buf (get_maintyps_ptr ^^ load_unskewed_ptr) ^^ ReadBuf.set_end get_main_typs_buf (ReadBuf.get_end get_data_buf) ^^ ReadBuf.read_leb128 env get_main_typs_buf ^^ set_arg_count ^^ - get_arg_count ^^ - compile_rel_const I32Op.GeU (Int32.of_int (List.length ts)) ^^ - E.else_trap_with env ("IDL error: too few arguments " ^ ts_name) ^^ - G.concat_map (fun t -> - get_data_buf ^^ get_ref_buf ^^ - get_typtbl_ptr ^^ load_unskewed_ptr ^^ - ReadBuf.read_sleb128 env get_main_typs_buf ^^ - get_typtbl_size_ptr ^^ load_unskewed_ptr ^^ - compile_unboxed_const 0l ^^ (* initial depth *) - get_can_recover ^^ - deserialize_go env t ^^ set_val ^^ - get_can_recover ^^ - G.if0 - (G.nop) - (get_val ^^ compile_eq_const (coercion_error_value env) ^^ - E.then_trap_with env ("IDL error: coercion failure encountered")) ^^ - get_val + let can_recover, default_or_trap = Type.( + match normalize t with + | Opt _ | Any -> + (Bool.lit true, fun msg -> Opt.null_lit env) + | _ -> + (get_can_recover, fun msg -> + get_can_recover ^^ + G.if1 I32Type + (compile_unboxed_const (coercion_error_value env)) + (E.trap_with env msg))) + in + get_arg_count ^^ + compile_eq_const 0l ^^ + G.if1 I32Type + (default_or_trap ("IDL error: too few arguments " ^ ts_name)) + (begin + get_rel_buf_opt ^^ + get_data_buf ^^ get_ref_buf ^^ + get_typtbl_ptr ^^ load_unskewed_ptr ^^ + get_maintyps_ptr ^^ load_unskewed_ptr ^^ (* typtbl_end *) + get_typtbl_size_ptr ^^ load_unskewed_ptr ^^ + ReadBuf.read_sleb128 env get_main_typs_buf ^^ + compile_unboxed_const 0l ^^ (* initial depth *) + can_recover ^^ + deserialize_go env t ^^ set_val ^^ + get_arg_count ^^ compile_sub_const 1l ^^ set_arg_count ^^ + get_val ^^ compile_eq_const (coercion_error_value env) ^^ + (G.if1 I32Type + (default_or_trap "IDL error: coercion failure encountered") + get_val) + end) ) ts ^^ (* Skip any extra arguments *) - get_arg_count ^^ compile_sub_const (Int32.of_int (List.length ts)) ^^ - from_0_to_n env (fun _ -> - get_data_buf ^^ - get_typtbl_ptr ^^ load_unskewed_ptr ^^ - ReadBuf.read_sleb128 env get_main_typs_buf ^^ - compile_unboxed_const 0l ^^ - E.call_import env "rts" "skip_any" - ) ^^ + compile_while env + (get_arg_count ^^ compile_rel_const I32Op.GtU 0l) + begin + get_data_buf ^^ + get_typtbl_ptr ^^ load_unskewed_ptr ^^ + ReadBuf.read_sleb128 env get_main_typs_buf ^^ + compile_unboxed_const 0l ^^ + E.call_import env "rts" "skip_any" ^^ + get_arg_count ^^ compile_sub_const 1l ^^ set_arg_count + end ^^ ReadBuf.is_empty env get_data_buf ^^ E.else_trap_with env ("IDL error: left-over bytes " ^ ts_name) ^^ ReadBuf.is_empty env get_ref_buf ^^ E.else_trap_with env ("IDL error: left-over references " ^ ts_name) )))))) - ) + + )) let deserialize env ts = IC.arg_data env ^^ @@ -6141,6 +6365,53 @@ To detect and preserve aliasing, these steps are taken: after checking the type hash field to make sure we are aliasing at the same type. + *) + +(* +Note [Candid subtype checks] +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Deserializing Candid values requires a Candid subtype check when +deserializing values of reference types (actors and functions). + +The subtype test is performed directly on the expected and actual +candid type tables using RTS functions `idl_sub_buf_words`, +`idl_sub_buf_init` and `idl_sub`. One type table and vector of types +is generated statically from the list of statically known types +encountered during code generation, the other is determined +dynamically by, e.g. message payload. The latter will vary with +each payload to decode. + +The known Motoko types are accumulated in a global list as required +and then, in a final compilation step, encoded to global type table +and sequence of type indices. The encoding is stored as static +data referenced by dedicated wasm globals so that we can generate +code that references the globals before their final definitions are +known. + +Deserializing a proper (not extended) Candid value stack allocates a +mutable word buffer, of size determined by `idl_sub_buf_words`. +The word buffer is used to initialize and provide storage for a +Rust memo table (see bitrel.rs) memoizing the result of sub and +super type tests performed during deserialization of a given Candid +value sequence. The memo table is initialized once, using `idl_sub_buf_init`, +then shared between recursive calls to deserialize, by threading the (possibly +null) wasm address of the word buffer as an optional argument. The +word buffer is stack allocated in generated code, not Rust, because +it's size is dynamic and Rust doesn't seem to support dynamically-sized +stack allocation. + +Currently, we only perform Candid subtype checks when decoding proper +(not extended) Candid values. Extended values are required for +stable variables only: we can omit the check, because compatibility +should already be enforced by the static signature compatibility +check. We use the `null`-ness of the word buffer pointer to +dynamically determine whether to omit or perform Candid subtype checks. + +NB: Extending `idl_sub` to support extended, "stable" types (with mutable, +invariant type constructors) would require extending the polarity argument +from a Boolean to a three-valued argument to efficiently check equality for +invariant type constructors in a single pass. *) end (* MakeSerialization *) @@ -8696,7 +8967,7 @@ and compile_prim_invocation (env : E.t) ae p es at = | ICStableSize t, [e] -> SR.UnboxedWord64, - let tydesc = Serialization.type_desc env [t] in + let (tydesc, _, _) = Serialization.type_desc env [t] in let tydesc_len = Int32.of_int (String.length tydesc) in compile_exp_vanilla env ae e ^^ Serialization.buffer_size env t ^^ @@ -9448,7 +9719,7 @@ The compilation of declarations (and patterns!) needs to handle mutual recursion This requires conceptually three passes: 1. First we need to collect all names bound in a block, and find locations for then (which extends the environment). - The environment is extended monotonously: The type-checker ensures that + The environment is extended monotonically: The type-checker ensures that a Block does not bind the same name twice. We would not need to pass in the environment, just out ... but because it is bundled in the E.t type, threading it through is also easy. @@ -9981,11 +10252,15 @@ and metadata name value = List.mem name !Flags.public_metadata_names, value) -and conclude_module env start_fi_o = +and conclude_module env set_serialization_globals start_fi_o = FuncDec.export_async_method env; + (* See Note [Candid subtype checks] *) + Serialization.set_delayed_globals env set_serialization_globals; + let static_roots = GCRoots.store_static_roots env in + (* declare before building GC *) (* add beginning-of-heap pointer, may be changed by linker *) @@ -10086,6 +10361,9 @@ let compile mode rts (prog : Ir.prog) : Wasm_exts.CustomModule.extended_module = GC.register_globals env; StableMem.register_globals env; + (* See Note [Candid subtype checks] *) + let set_serialization_globals = Serialization.register_delayed_globals env in + IC.system_imports env; RTS.system_imports env; RTS_Exports.system_exports env; @@ -10102,4 +10380,4 @@ let compile mode rts (prog : Ir.prog) : Wasm_exts.CustomModule.extended_module = Some (nr (E.built_in env "init")) in - conclude_module env start_fi_o + conclude_module env set_serialization_globals start_fi_o diff --git a/src/exes/candid_tests.ml b/src/exes/candid_tests.ml index dfd53ac51b9..e3718135d69 100644 --- a/src/exes/candid_tests.ml +++ b/src/exes/candid_tests.ml @@ -70,7 +70,7 @@ let mo_of_test tenv test : (string * expected_behaviour, string) result = let not_equal e1 e2 = "assert (" ^ e1 ^ " != " ^ e2 ^ ")\n" in let ignore ts e = let open Type in - if sub (seq ts) unit then e (* avoid warnign about redundant ignore *) + if sub (seq ts) unit then e (* avoid warning about redundant ignore *) else "ignore (" ^ e ^ ")\n" in try @@ -147,7 +147,7 @@ type outcome = | UnwantedTrap of (string * string) (* stdout, stderr *) | Timeout | Ignored of string - | CantCompile of (string * string) (* stdout, stderr *) + | CantCompile of (string * string * string) (* stdout, stderr, src *) let red s = "\027[31m" ^ s ^ "\027[0m" let green s = "\027[32m" ^ s ^ "\027[0m" @@ -189,9 +189,9 @@ let report_outcome counts expected_fail outcome = | _, Ignored why -> bump counts.ignored; Printf.printf " %s\n" (grey (Printf.sprintf "ignored (%s)" why)) - | _, CantCompile (stdout, stderr) -> + | _, CantCompile (stdout, stderr, src) -> bump counts.fail; - Printf.printf " %s\n%s%s\n" (red "not ok (cannot compile)") stdout stderr + Printf.printf " %s\n%s%s\n%s" (red "not ok (cannot compile)") stdout stderr src (* Main *) let () = @@ -252,7 +252,7 @@ let () = Unix.putenv "MOC_UNLOCK_PRIM" "yesplease"; write_file "tmp.mo" src; match run_cmd "moc -Werror -wasi-system-api tmp.mo -o tmp.wasm" with - | ((Fail | Timeout), stdout, stderr) -> CantCompile (stdout, stderr) + | ((Fail | Timeout), stdout, stderr) -> CantCompile (stdout, stderr, src) | (Ok, _, _) -> match must_not_trap, run_cmd "timeout 10s wasmtime --disable-cache tmp.wasm" with | ShouldPass, (Ok, _, _) -> WantedPass diff --git a/src/mo_idl/idl_to_mo_value.ml b/src/mo_idl/idl_to_mo_value.ml index bfa71bcf341..2d603194724 100644 --- a/src/mo_idl/idl_to_mo_value.ml +++ b/src/mo_idl/idl_to_mo_value.ml @@ -3,6 +3,7 @@ open Idllib.Exception open Source module T = Mo_types.Type +module Pretty = T.MakePretty(T.ElideStamps) (* This module can translate Candid values (as parsed from the textual representation) into Motoko values. This is a pragmatic translation which is @@ -71,7 +72,7 @@ let rec value v t = Printf.sprintf "(actor %s : actor { %s : %s }).%s" (text_lit s) (Idllib.Escape.escape_method Source.no_region m) - (T.string_of_typ t) + (Pretty.string_of_typ t) (Idllib.Escape.escape_method Source.no_region m) | PrincipalV s, _ -> "Prim.principalOfActor" ^ parens ("actor " ^ text_lit s ^ " : actor {}") diff --git a/test/run-drun/idl-large-principal.mo b/test/run-drun/idl-large-principal.mo index ffaa09e14dd..9f85cd19d75 100644 --- a/test/run-drun/idl-large-principal.mo +++ b/test/run-drun/idl-large-principal.mo @@ -1,7 +1,7 @@ actor { public query func fun1(_: Principal) : async () {}; public query func fun2(_: actor {}) : async () {}; - public query func fun3(_: shared () -> ()) : async () {}; + public query func fun3(_: shared () -> async ()) : async () {}; } diff --git a/test/run-drun/idl-sub-ho-neg.mo b/test/run-drun/idl-sub-ho-neg.mo new file mode 100644 index 00000000000..f468701e391 --- /dev/null +++ b/test/run-drun/idl-sub-ho-neg.mo @@ -0,0 +1,422 @@ +import Prim "mo:⛔"; + +// test candid subtype test with higher-order arguments +actor this { + + public func send_f0( + f : shared (Nat) -> async Int + ) : async () { + Prim.debugPrint("wrong_0"); + }; + + public func send_f2( + f : shared (Nat, [Nat], {f:Nat;g:Nat}, {#l:Nat; #m:Nat}) -> + async(Int, [Int], {f:Int;g:Int}, {#l:Int; #m:Int}) + ) : async () { + Prim.debugPrint("wrong_2"); + }; + + public func send_f3( + f : shared () -> + async (Null, Any, ?None, Nat) + ) : async () { + Prim.debugPrint("wrong_3"); + }; + + public func send_f5( + f : shared {a : Nat; b : [Nat]; c : {f:Nat;g:Nat}; d : {#l:Nat; #m:Nat}} -> + async {a : Int; b : [Int]; c : {f:Int;g:Int}; d : {#l:Int; #m:Int}} + ) : async () { + Prim.debugPrint("ok"); + }; + + public func send_f6( + f : shared () -> + async {a : Null; b : Any; c : ?None} + ) : async () { + Prim.debugPrint("ok"); + }; + + + public func f0(n : Nat) : async Int { 0 }; + + public func f0_1_a(n : None) : async Int { 0 }; + public func f0_1_b(n : Nat) : async Any { () }; + + public func f0_2_a() : async (Any, Bool) { ((), true) }; + public func f0_2_b(n : Nat, a : Bool ) : async (Int, Bool) { (1, true) }; + + public func f0_3_a(on : ?Nat, i : Nat) : async (Int, ?Nat) { (0, null); }; + public func f0_3_b(i : Nat, on : ?Nat) : async (?Nat, Int) { (null, 0); }; + + public func f0_4(ob : ?Bool, n : Nat) : async Int { 0 }; + + public func f1_0(n : ?Nat) : async (Bool, Nat) { (true, 0) }; + + public func f2_0_a(n : Nat, a : [Nat], r : {f : Nat; g : Nat}, v : {#l : Nat; #m : None}) : + async (Int, [Int], {f : Int; g : Int}, {#l : Int; #m : Int}) { + (1, [1], {f=1;g=1}, (#l 0)) + }; + + public func f2_0_b(n : Nat, a : [Nat], r : {f : Nat; g : Nat}, v : {#l : Nat; #m : Nat}) : + async (Int, [Int], {f : Int; g : Int}, {#l : Int; #m : Any}) { + (1, [1], {f=1;g=1}, (#l 0)) + }; + + + public func f2_1_a(n : Int, a : [Int], r : {f : Int}, v : {#l : Int; #m : None; #o : Int}) : + async (Nat, [Nat], {f : Nat; g : Nat; h : Nat}, { #l : Nat}) { + (1, [1], {f = 1; g = 2; h = 3}, (#l 0)) + }; + + public func f2_1_b(n : Int, a : [Int], r : {f : Int}, v : {#l : Int; #m : Int; #o : Int}) : + async (Nat, [Nat], {f : Nat; g : Nat; h : Nat}, { #l : Any}) { + (1, [1], {f = 1; g = 2; h = 3}, (#l 0)) + }; + + + public func f3_0_a(x : Nat) : + async (n : Null, a : ?None, r : Any) { + (null, null, null) + }; + + public func f3_0_b() : + async (n : Null, a : ?None, r : Any) { + (null, null, null) + }; + + public func f5_0_a({a = n : Nat; b = a : [Nat]; c = r : {f : Nat; g : Nat}; d = v : {#l : Nat; #m : None}}) : + async { a : Int; b : [Int]; c : {f : Int; g : Int}; d : {#l : Int; #m : Int} } { + { a = 1; b = [1]; c = {f =1 ; g = 1}; d = (#l 0)} + }; + + public func f5_0_b({a = n : Nat; b = a : [Nat]; c = r : {f : Nat; g : Nat}; d = v : {#l : Nat; #m : Nat}}) : + async { a : Int; b : [Int]; c : {f : Int; g : Int}; d : {#l : Int; #m : Any} } { + { a = 1; b = [1]; c = {f =1 ; g = 1}; d = (#l 0)} + }; + + public func f5_1_a({a = n : Int; b = a : [Int]; c = r : {f : Int}; d = v : {#l : Int; #m : None; #o : Int} }) : + async { a : Nat; b : [Nat]; c : {f : Nat; g : Nat; h : Nat}; d : { #l : Nat}} { + { a = 1; b = [1]; c = {f = 1; g = 2; h = 3}; d = (#l 0)} + }; + + public func f5_1_b({a = n : Int; b = a : [Int]; c = r : {f : Int}; d = v : {#l : Int; #m : Int; #o : Int} }) : + async { a : Nat; b : [Nat]; c : {f : Nat; g : Nat; h : Nat}; d : { #l : Any}} { + { a = 1; b = [1]; c = {f = 1; g = 2; h = 3}; d = (#l 0)} + }; + + + public func f6_0_a({x = _:Nat}) : + async {a : Null; b : ?None; c : Any} { + { a= null; b = null; c = null} + }; + + public func f6_0_b({}) : + async {a : Null; b : ?None; c : Any} { + { a= null; b = null; c = null} + }; + + public func go() : async () { + let t = debug_show (Prim.principalOfActor(this)); + + // vanilla subtyping on in/out args + do { + let this = actor (t) : actor { + send_f0 : (shared (n:None) -> async Int) -> async (); + }; + try { + await this.send_f0(f0_1_a); + Prim.debugPrint "wrong_0_1_a"; + } + catch e { + Prim.debugPrint "ok_0_1_a"; + } + }; + + // vanilla subtyping on in/out args + do { + let this = actor (t) : actor { + send_f0 : (shared (n:Nat) -> async Any) -> async (); + }; + try { + await this.send_f0(f0_1_b); + Prim.debugPrint "wrong_0_1_b"; + } + catch e { + Prim.debugPrint "ok_0_1_b"; + } + }; + + // negative vanilla subtyping on in/out arg sequences + do { + let this = actor (t) : actor { + send_f0 : (shared () -> async (Any, Bool)) -> async (); + }; + try { + await this.send_f0(f0_2_a); + Prim.debugPrint "wrong_0_2_a"; + } + catch e { + Prim.debugPrint "ok_0_2_a"; + } + }; + + // negative vanilla subtyping on in/out arg sequences + do { + let this = actor (t) : actor { + send_f0 : (shared (n : Nat, a : Bool) -> async (Int, Bool)) -> async (); + }; + try { + await this.send_f0(f0_2_b); + Prim.debugPrint "wrong_0_2_b"; + } + catch e { + Prim.debugPrint "ok_0_2_a"; + } + }; + + // negative opt subtyping in arg and return + do { + let this = actor (t) : actor { + send_f0 : (shared (?Nat, Nat) -> async (Int, ?Nat)) -> async (); + }; + try { + await this.send_f0(f0_3_a); + Prim.debugPrint "wrong_0_3_a"; + } + catch e { + Prim.debugPrint "ok_0_3_a"; + } + }; + + // negative opt subtyping in arg and return + do { + let this = actor (t) : actor { + send_f0 : (shared (Nat, ?Nat) -> async (?Nat, Int)) -> async (); + }; + try { + await this.send_f0(f0_3_b); + Prim.debugPrint "wrong_0_3_b"; + } + catch e { + Prim.debugPrint "ok_0_3_b"; + } + }; + + // negative opt override in arg + do { + let this = actor (t) : actor { + send_f0 : (shared (?Bool, Nat) -> async Int) -> async (); + }; + try { + await this.send_f0(f0_4); + Prim.debugPrint "wrong_0_4"; + } + catch e { + Prim.debugPrint "ok 0_4"; } + }; + + + // negative several args + do { + let this = actor (t) : actor { + send_f2 : + (shared (Nat, [Nat], {f : Nat; g : Nat}, {#l : Nat; #m : None}) -> + async (Int, [Int], {f : Int; g : Int}, {#l : Int; #m : Int})) -> + async () + }; + try { + await this.send_f2(f2_0_a); + Prim.debugPrint "wrong_2_0_a"; + } + catch e { + Prim.debugPrint "ok 2_0_a"; } + }; + + // several args + do { + let this = actor (t) : actor { + send_f2 : + (shared (Nat, [Nat], {f : Nat; g : Nat}, {#l : Nat; #m : Nat}) -> + async (Int, [Int], {f : Int; g : Int}, {#l : Int; #m : Any})) -> + async () + }; + try { + await this.send_f2(f2_0_b); + Prim.debugPrint "wrong_2_0_b"; + } + catch e { + Prim.debugPrint "ok 2_0_b"; } + }; + + // negative several args, contra-co subtyping + do { + let this = actor (t) : actor { + send_f2 : + (shared (Int, [Int], {f : Int}, {#l : Int; #m : None; #o : Int}) -> + async (Nat, [Nat], {f : Nat; g : Nat; h : Nat}, {#l : Nat})) -> + async () + }; + try { + await this.send_f2(f2_1_a); + Prim.debugPrint "wrong 2_1_a"; + } + catch e { Prim.debugPrint "ok 2_1_a"; } + }; + + // negative several args, contra-co subtyping + do { + let this = actor (t) : actor { + send_f2 : + (shared (Int, [Int], {f : Int}, {#l : Int; #m : Int; #o : Int}) -> + async (Nat, [Nat], {f : Nat; g : Nat; h : Nat}, {#l : Any})) -> + async () + }; + try { + await this.send_f2(f2_1_b); + Prim.debugPrint "wrong 2_1_b"; + } + catch e { + Prim.debugPrint "ok 2_1_b";} + }; + + + // negative null, opt and any trailing args, defaulting + do { + let this = actor (t) : actor { + send_f3 : + (shared Nat -> + async (Null, ?None, Any)) -> + async () + }; + try { + await this.send_f3(f3_0_a); + Prim.debugPrint "wrong 3_0_a"; + } + catch e { + Prim.debugPrint "ok 3_0_a"; + } + }; + + // negative null, opt and any trailing args, defaulting + do { + let this = actor (t) : actor { + send_f3 : + (shared () -> + async (Null, ?None, Any)) -> + async () + }; + try { + await this.send_f3(f3_0_b); + Prim.debugPrint "wrong 3_0_b"; + } + catch e { + Prim.debugPrint "ok 3_0_b"; + } + }; + + // negative several fields + do { + let this = actor (t) : actor { + send_f5 : + (shared {a : Nat; b : [Nat]; c : {f : Nat; g : Nat}; d : {#l : Nat; #m : None}} -> + async {a : Int; b : [Int]; c : {f : Int; g : Int}; d : {#l : Int; #m : Int}}) -> + async () + }; + try { + await this.send_f5(f5_0_a); + Prim.debugPrint "wrong 5_0_a"; + } + catch e { + Prim.debugPrint "ok 5_0_a"; } + }; + + // negative several fields + do { + let this = actor (t) : actor { + send_f5 : + (shared { a : Nat; b : [Nat]; c : {f : Nat; g : Nat}; d : {#l : Nat; #m : Nat}} -> + async { a : Int; b : [Int]; c : {f : Int; g : Int}; d : {#l : Int; #m : Any}}) -> + async () + }; + try { + await this.send_f5(f5_0_b); + Prim.debugPrint "wrong 5_0_b"; + } + catch e { + Prim.debugPrint "ok 5_0_b"; } + }; + + // negative several fields, contra-co subtyping + do { + let this = actor (t) : actor { + send_f5 : + (shared { a : Int; b : [Int]; c : {f : Int}; d : {#l : Int; #m : None; #o : Int}} -> + async { a : Nat; b : [Nat]; c : {f : Nat; g : Nat; h : Nat}; d : {#l : Nat}}) -> + async () + }; + try { + await this.send_f5(f5_1_a); + Prim.debugPrint "wrong 5_1_a"; + } + catch e { Prim.debugPrint "ok 5_1_a"; } + }; + + // negative several args, contra-co subtyping + do { + let this = actor (t) : actor { + send_f5 : + (shared { a : Int; b : [Int]; c : {f : Int}; d : {#l : Int; #m : Int; #o : Int}} -> + async { a : Nat; b : [Nat]; c : {f : Nat; g : Nat; h : Nat}; d : {#l : Any}}) -> + async () + }; + try { + await this.send_f5(f5_1_b); + Prim.debugPrint "wrong 5_1_b"; + } + catch e { + Prim.debugPrint "ok 5_1_b";} + }; + + + // negative null, opt and any fields, defaulting + do { + let this = actor (t) : actor { + send_f6 : + (shared {x : Nat} -> + async { a : Null; b : ?None; c : Any}) -> + async () + }; + try { + await this.send_f6(f6_0_a); + Prim.debugPrint "wrong 6_0_a"; + } + catch e { + Prim.debugPrint "ok 6_0_a"; + } + }; + + // negative null, opt and any fields, defaulting + do { + let this = actor (t) : actor { + send_f6 : + (shared {} -> + async {a : Null; b : ?None; c : Any}) -> + async () + }; + try { + await this.send_f6(f6_0_b); + Prim.debugPrint "wrong 6_0_b"; + } + catch e { + Prim.debugPrint "ok 6_0_b"; + } + }; + + }; + +} +//SKIP run +//SKIP run-ir +//SKIP run-low +//CALL ingress go "DIDL\x00\x00" diff --git a/test/run-drun/idl-sub-ho.mo b/test/run-drun/idl-sub-ho.mo new file mode 100644 index 00000000000..85ed78331cd --- /dev/null +++ b/test/run-drun/idl-sub-ho.mo @@ -0,0 +1,241 @@ +import Prim "mo:⛔"; + +// test candid subtype test with higher-order arguments +actor this { + + public func send_f0( + f : shared (Nat) -> async Int + ) : async () { + Prim.debugPrint("ok"); + }; + + public func send_f1( + f : shared (?Nat) -> async ?Int + ) : async () { + Prim.debugPrint("ok"); + }; + + public func send_f2( + f : shared (Nat, [Nat], {f:Nat;g:Nat}, {#l:Nat; #m:Nat}) -> + async(Int, [Int], {f:Int;g:Int}, {#l:Int; #m:Int}) + ) : async () { + Prim.debugPrint("ok"); + }; + + public func send_f3( + f : shared () -> + async (Null, Any, ?None) + ) : async () { + Prim.debugPrint("ok"); + }; + + public func send_f5( + f : shared {a : Nat; b : [Nat]; c : {f:Nat;g:Nat}; d : {#l:Nat; #m:Nat}} -> + async {a : Int; b : [Int]; c : {f:Int;g:Int}; d : {#l:Int; #m:Int}} + ) : async () { + Prim.debugPrint("ok"); + }; + + public func send_f6( + f : shared () -> + async {a : Null; b : Any; c : ?None} + ) : async () { + Prim.debugPrint("ok"); + }; + + public func f0(n : Nat) : async Int { 0 }; + + public func f0_1(n : Int) : async Nat { 0 }; + + public func f0_2() : async (Nat, Bool) { (0,true) }; + + public func f0_3(i : Nat, on : ?Nat) : async (Int, ?Nat) { (0, null); }; + + public func f0_4(ob : ?Bool) : async Int { 0 }; + + public func f1_0(n : ?Nat) : async Bool { true }; + + public func f2_0(n : Nat, a : [Nat], r : {f : Nat; g : Nat}, v : {#l : Nat; #m : Nat}) : + async (Int, [Int], {f : Int; g : Int}, {#l : Int; #m : Int}) { + (1, [1], {f=1;g=1}, (#l 0)) + }; + + public func f2_1(n : Int, a : [Int], r : {f : Int}, v : {#l : Int; #m : Int; #o : Int}) : + async (Nat, [Nat], {f : Nat; g : Nat; h : Nat}, { #l : Nat}) { + (1, [1], {f = 1; g = 2; h = 3}, (#l 0)) + }; + + public func f3_0() : + async (n : Null, a : ?None, r : Any) { + (null, null, null) + }; + + public func f5_0({a = n : Nat; b = a : [Nat]; c = r : {f : Nat; g : Nat}; d = v : {#l : Nat; #m : Nat}}) : + async { a : Int; b : [Int]; c : {f : Int; g : Int}; d : {#l : Int; #m : Int} } { + { a = 1; b = [1]; c = {f =1 ; g = 1}; d = (#l 0)} + }; + + public func f5_1({a = n : Int; b = a : [Int]; c = r : {f : Int}; d = v : {#l : Int; #m : Int; #o : Int} }) : + async { a : Nat; b : [Nat]; c : {f : Nat; g : Nat; h : Nat}; d : { #l : Nat}} { + { a = 1; b = [1]; c = {f = 1; g = 2; h = 3}; d = (#l 0)} + }; + + public func f6_0() : + async {a : Null; b : ?None; c : Any} { + { a= null; b = null; c = null} + }; + + public func go() : async () { + + let t = debug_show (Prim.principalOfActor(this)); + + // vanilla subtyping on in/out args + do { + let this = actor (t) : actor { + send_f0 : (shared (n:Int) -> async Nat) -> async (); + }; + try { + await this.send_f0(f0_1); + } + catch e { Prim.debugPrint "wrong_0_1"; } + }; + + // vanilla subtyping on in/out arg sequences + do { + let this = actor (t) : actor { + send_f0 : (shared () -> async (Nat,Bool)) -> async (); + }; + try { + await this.send_f0(f0_2); + } + catch e { Prim.debugPrint "wrong_0_2"; } + }; + + // opt subtyping in arg and return + do { + let this = actor (t) : actor { + send_f0 : (shared (Nat, ?Nat) -> async (Int, ?Nat)) -> async (); + }; + try { + await this.send_f0(f0_3); + } + catch e { Prim.debugPrint "wrong_0_3"; } + }; + + // opt override in arg + do { + let this = actor (t) : actor { + send_f0 : (shared (?Bool) -> async Int) -> async (); + }; + try { + await this.send_f0(f0_4); + } + catch e { Prim.debugPrint "wrong_0_4"; } + }; + + + // opt override in return + do { + let this = actor (t) : actor { + send_f1 : (shared (?Nat) -> async Bool) -> async (); + }; + try { + await this.send_f1(f1_0); + } + catch e { Prim.debugPrint "wrong_1_0"; } + }; + + // several args + do { + let this = actor (t) : actor { + send_f2 : + (shared (Nat, [Nat], {f : Nat; g : Nat}, {#l : Nat; #m : Nat}) -> + async (Int, [Int], {f : Int; g : Int}, {#l : Int; #m : Int})) -> + async () + }; + try { + await this.send_f2(f2_0); + } + catch e { Prim.debugPrint "wrong_2_0"; } + }; + + // several args, contra-co subtyping + do { + let this = actor (t) : actor { + send_f2 : + (shared (Int, [Int], {f : Int}, {#l : Int; #m : Int; #o : Int}) -> + async (Nat, [Nat], {f : Nat; g : Nat; h : Nat}, {#l : Nat})) -> + async () + }; + try { + await this.send_f2(f2_1); + } + catch e { Prim.debugPrint "wrong_2_1"; } + }; + + + // null, opt and any trailing args, defaulting + do { + let this = actor (t) : actor { + send_f3 : + (shared () -> + async (Null, ?None, Any)) -> + async () + }; + try { + await this.send_f3(f3_0); + } + catch e { Prim.debugPrint "wrong_3_0"; } + }; + + + // record arg + do { + let this = actor (t) : actor { + send_f5 : + (shared {a : Nat; b : [Nat]; c : {f : Nat; g : Nat}; d : {#l : Nat; #m : Nat}} -> + async {a : Int; b : [Int]; c : {f : Int; g : Int}; d : {#l : Int; #m : Int}}) -> + async () + }; + try { + await this.send_f5(f5_0); + } + catch e { Prim.debugPrint "wrong_5_0"; } + }; + + // record arg, contra-co subtyping + do { + let this = actor (t) : actor { + send_f5 : + (shared {a : Int; b : [Int]; c : {f : Int}; d : {#l : Int; #m : Int; #o : Int}} -> + async {a : Nat; b : [Nat]; c : {f : Nat; g : Nat; h : Nat}; d : {#l : Nat}}) -> + async () + }; + try { + await this.send_f5(f5_1); + } + catch e { Prim.debugPrint "wrong_5_1"; } + }; + + + // null, opt and any record fields, defaulting + do { + let this = actor (t) : actor { + send_f6 : + (shared () -> + async {a : Null; b : ?None; c : Any}) -> + async () + }; + try { + await this.send_f6(f6_0); + } + catch e { Prim.debugPrint "wrong_6_0"; } + }; + + }; + +} +//SKIP run +//SKIP run-ir +//SKIP run-low +//CALL ingress go "DIDL\x00\x00" diff --git a/test/run-drun/idl-sub-opt-any-record.mo b/test/run-drun/idl-sub-opt-any-record.mo new file mode 100644 index 00000000000..47b32168d47 --- /dev/null +++ b/test/run-drun/idl-sub-opt-any-record.mo @@ -0,0 +1,71 @@ +import Prim "mo:⛔"; + +// test candid subtype test with optional record extension +actor this { + + public func send_f0( + f : shared {a : Int} -> async {b : Bool}) + : async () { + Prim.debugPrint("ok_0"); + }; + + public func f0() : async () { }; + + public func f0_1_a({a : Int; n : Null}) : async {b : Bool; x : Null} { {b = true; x = null} }; + public func f0_1_b({a : Int; n : ?Null}) : async {b : Bool; x : ?Null} { {b = true; x = null} }; + public func f0_1_c({a : Int; n : Any}) : async {b : Bool; x : Any} { {b = true; x = null } }; + + + public func go() : async () { + let t = debug_show (Prim.principalOfActor(this)); + + // appending Null not ok + do { + let this = actor (t) : actor { + send_f0 : (shared {a : Int; n : Null } -> async {b : Bool; x : Null}) -> async (); + }; + try { + await this.send_f0(f0_1_a); + Prim.debugPrint "wrong_0_1_a"; + } + catch e { + Prim.debugPrint "ok_0_1_a"; + } + }; + + // appending ?_ ok + do { + let this = actor (t) : actor { + send_f0 : (shared {a : Int; n : ?Null} -> async {b : Bool; x : ?Null}) -> async (); + }; + try { + await this.send_f0(f0_1_b); + Prim.debugPrint "ok_0_1_b"; + + } + catch e { + Prim.debugPrint "wrong_0_1_b"; + } + }; + + // appending Any ok + do { + let this = actor (t) : actor { + send_f0 : (shared {a : Int; n : Any} -> async {b : Bool; x : Any}) -> async (); + }; + try { + await this.send_f0(f0_1_c); + Prim.debugPrint "ok_0_1_c"; + } + catch e { + Prim.debugPrint "wrong_0_1_c"; + } + }; + + }; + +} +//SKIP run +//SKIP run-ir +//SKIP run-low +//CALL ingress go "DIDL\x00\x00" diff --git a/test/run-drun/idl-sub-opt-any.mo b/test/run-drun/idl-sub-opt-any.mo new file mode 100644 index 00000000000..b614db34734 --- /dev/null +++ b/test/run-drun/idl-sub-opt-any.mo @@ -0,0 +1,102 @@ +import Prim "mo:⛔"; + +// test candid subtype test with optional argument extension +actor this { + + public func send_f0( + f : shared (a : Int) -> async Bool) + : async () { + Prim.debugPrint("ok_0"); + }; + + public func f0() : async () { }; + + public func f0_1_a(a : Int, n : Null) : async (Bool, Null) { (true, null) }; + public func f0_1_b(a : Int, n : ?Null) : async (Bool, ?Null) { (true, null) }; + public func f0_1_c(a : Int, n : Any) : async (Bool, Any) { (true, null) }; + public func f0_1_d(n : ?Null, a : Int) : async (?Null, Bool) { (null, true) }; + public func f0_1_e(n : Any, a : Int) : async (Any, Bool) { (null, true) }; + + + public func go() : async () { + let t = debug_show (Prim.principalOfActor(this)); + + // appending Null not ok + do { + let this = actor (t) : actor { + send_f0 : (shared (a : Int, n : Null) -> async (Bool, Null)) -> async (); + }; + try { + await this.send_f0(f0_1_a); + Prim.debugPrint "wrong_0_1_a"; + } + catch e { + Prim.debugPrint "ok_0_1_a"; + } + }; + + // appending ?_ ok + do { + let this = actor (t) : actor { + send_f0 : (shared (a : Int, n : ?Null) -> async (Bool, ?Null)) -> async (); + }; + try { + await this.send_f0(f0_1_b); + Prim.debugPrint "ok_0_1_b"; + + } + catch e { + Prim.debugPrint "wrong_0_1_b"; + } + }; + + // appending Any ok + do { + let this = actor (t) : actor { + send_f0 : (shared (a : Int, n : Any) -> async (Bool, Any)) -> async (); + }; + try { + await this.send_f0(f0_1_c); + Prim.debugPrint "ok_0_1_c"; + } + catch e { + Prim.debugPrint "wrong_0_1_c"; + } + }; + + // prepending Any not ok + do { + let this = actor (t) : actor { + send_f0 : (shared (n : ?Null, a : Int) -> async (?Null, Bool)) -> async (); + }; + try { + await this.send_f0(f0_1_d); + Prim.debugPrint "wrong_0_1_d"; + } + catch e { + Prim.debugPrint "ok_0_1_d"; + } + }; + + // prepending ?_ not ok + do { + let this = actor (t) : actor { + send_f0 : (shared (n : Any, a : Int) -> async (Any, Bool)) -> async (); + }; + try { + await this.send_f0(f0_1_e); + Prim.debugPrint "wrong_0_1_e"; + + } + catch e { + Prim.debugPrint "ok_0_1_e"; + } + }; + + }; + +} +//SKIP run +//SKIP run-ir +//SKIP run-low +//CALL ingress go "DIDL\x00\x00" diff --git a/test/run-drun/idl-sub-rec.mo b/test/run-drun/idl-sub-rec.mo new file mode 100644 index 00000000000..f6dc181e52b --- /dev/null +++ b/test/run-drun/idl-sub-rec.mo @@ -0,0 +1,233 @@ +import Prim "mo:⛔"; +// test candid subtype check on recursive types +actor this { + + type List = { + #nil; + #left: (T, List) + }; + + type Seq = { + #nil; + #left: (T, Seq); + #right: (T, Seq) + }; + + type EvenList = { + #nil; + #left: + (T, { + //#nil; + #left: (T, EvenList)}) + }; + + type EvenSeq = { + #nil; + #left: (T, { + // #nil; + #left: (T, EvenSeq); + #right: (T, EvenSeq) + }); + #right: (T, { + // #nil; + #left: (T, EvenSeq); + #right: (T, EvenSeq)}); + }; + + // sanity subtype checks, verify: + // List <: Seq + // EvenList <: List + // EvenSeq <: Seq + func sub1(t : List) : Seq { t }; + func sub2(t : EvenList) : List { t }; + func sub3(t : EvenSeq) : Seq { t }; + + public func f0(l : Seq) : async EvenList { #nil }; + + public func send_f0( + f : shared EvenList -> async Seq + ) : async () { + Prim.debugPrint("ok f0"); + }; + + public func send_f1( + a : [shared EvenList -> async Seq] + ) : async () { + Prim.debugPrint("ok f1"); + }; + + public func send_f2( + f : shared List -> async EvenSeq + ) : async () { + Prim.debugPrint("ok f2"); + }; + + public func send_f3( + a : [shared List -> async EvenSeq] + ) : async () { + Prim.debugPrint("ok f3"); + }; + + public func send_f4( + a : [?(shared List -> async EvenSeq)] + ) : async () { + Prim.debugPrint("ok f4"); + }; + + + func tabulate(n : Nat, v : T) : [T] { + Prim.Array_tabulate(n, func (_ : Nat) : T { v }); + }; + + public func go() : async () { + + let t = debug_show (Prim.principalOfActor(this)); + + do { + try { + await this.send_f0(f0); + Prim.debugPrint "ok_0"; + } + catch e { + Prim.debugPrint "wrong_0"; + }; + }; + + do { + let this = actor (t) : actor { + send_f0 : (shared List -> async Seq) -> async (); + }; + try { + await this.send_f0(f0); + Prim.debugPrint "ok_1"; + } + catch e { + Prim.debugPrint "wrong_1"; + }; + }; + + + do { + let this = actor (t) : actor { + send_f0 : (shared Seq -> async List) -> async (); + }; + try { + await this.send_f0(f0); + Prim.debugPrint "ok_2"; + } + catch e { + Prim.debugPrint "wrong_2"; + }; + }; + + do { + let this = actor (t) : actor { + send_f0 : (shared Seq -> async List) -> async (); + }; + try { + await this.send_f0(f0); + Prim.debugPrint "ok_3"; + } + catch e { + Prim.debugPrint "wrong_3"; + }; + }; + + do { + let this = actor (t) : actor { + send_f0 : (shared EvenSeq -> async EvenList) -> async (); + }; + try { + await this.send_f0(f0); + Prim.debugPrint "ok_4"; + } + catch e { + Prim.debugPrint "wrong_4"; + }; + }; + + // negative test + do { + let this = actor (t) : actor { + send_f2 : (shared EvenList -> async Seq) -> async (); + }; + try { + await this.send_f2(f0); + Prim.debugPrint "wrong_5"; + } + catch e { + Prim.debugPrint ("ok 5:" # Prim.errorMessage(e)) + }; + }; + + // test vectors, should benefit from memoization + do { + try { + await this.send_f1(tabulate(1024, f0)); + Prim.debugPrint "ok_6"; + } + catch e { + Prim.debugPrint "wrong_6"; + }; + }; + + do { + let this = actor (t) : actor { + send_f1 : [shared List -> async Seq] -> async (); + }; + try { + await this.send_f1(tabulate(1024, f0)); + } + catch e { + Prim.debugPrint "wrong_7"; + }; + }; + + do { + let this = actor (t) : actor { + send_f1 : [shared Seq -> async List] -> async (); + }; + try { + await this.send_f1(tabulate(1024, f0)); + Prim.debugPrint "ok_8"; + } + catch e { + Prim.debugPrint "wrong_8"; + }; + }; + + // negative test + do { + let this = actor (t) : actor { + send_f3 : [shared EvenList -> async Seq] -> async (); + }; + try { + await this.send_f3(tabulate(1024, f0)); + Prim.debugPrint "wrong_9"; + } + catch e { + Prim.debugPrint ("ok_9" # Prim.errorMessage(e)) + }; + }; + + // test vector defaulting, should benefit from memoization + do { + let this = actor (t) : actor { + send_f4 : [?(shared EvenList -> async Seq)] -> async (); + }; + try { + await this.send_f4(tabulate(1024, ?f0)); + Prim.debugPrint ("ok_10") + } + catch e { + Prim.debugPrint ("wrong_10" # Prim.errorMessage(e)) + }; + }; + + + }; +} +//SKIP run +//SKIP run-ir +//SKIP run-low +//CALL ingress go "DIDL\x00\x00" diff --git a/test/run-drun/idl-sub-variant.mo b/test/run-drun/idl-sub-variant.mo new file mode 100644 index 00000000000..e0f22614c82 --- /dev/null +++ b/test/run-drun/idl-sub-variant.mo @@ -0,0 +1,58 @@ +import Prim "mo:⛔"; + +// test candid subtype test with higher-order arguments +actor this { + + type Enum1 = { + #A : Int; + #B + }; + + type Enum2 = { + #A : Int; + }; + + public func get_A() : async Enum1 { + #A(1) + }; + + public func get_B() : async Enum1 { + #B + }; + + public func go() : async () { + + let t = debug_show (Prim.principalOfActor(this)); + + // positive tests + do { + let (#A _) : Enum1 = await this.get_A(); + Prim.debugPrint("pass1"); + }; + + do { + let this = actor (t) : actor { + get_A : () -> async Enum2; + }; + let (#A _) : Enum2 = await this.get_A(); + Prim.debugPrint("pass1"); + }; + + do { + let this = actor (t) : actor { + get_B : () -> async Enum2; + }; + try { + let _ : Enum2 = await async { await this.get_B(); }; + assert false; + } catch e { + Prim.debugPrint("pass3: " # Prim.errorMessage(e)); + }; + }; + + }; +} +//SKIP run +//SKIP run-ir +//SKIP run-low +//CALL ingress go "DIDL\x00\x00" diff --git a/test/run-drun/idl-sub.mo b/test/run-drun/idl-sub.mo new file mode 100644 index 00000000000..3eb39531df9 --- /dev/null +++ b/test/run-drun/idl-sub.mo @@ -0,0 +1,182 @@ +import Prim "mo:⛔"; + +actor this { + + public func f0() : async () {}; + + public func send_f0( + f : shared () -> async () + ) : async () { + Prim.debugPrint("ok 0"); + }; + + public func f1(n:Nat) : async () {}; + + public func send_f1( + f1 : shared (n:Nat) -> async () + ) : async () { + Prim.debugPrint("ok 1"); + }; + + public func f2(n:Nat) : async Nat { 0 }; + + public func send_f2( + f2 : shared (n:Nat) -> async Nat + ) : async () { + Prim.debugPrint("ok 2"); + }; + + + // variants + public func f3(n:{#f:Nat}) : async {#f:Nat} { (#f 0) }; + + public func send_f3( + f3 : shared (n:{#f:Nat}) -> async {#f:Nat} + ) : async () { + Prim.debugPrint("ok 3"); + }; + + + // records + public func f4(n:{f:Nat}) : async {f:Nat} { { f = 0 } }; + + public func send_f4( + f4 : shared (n: {f : Nat}) -> async {f : Nat} + ) : async () { + Prim.debugPrint("ok 4"); + }; + + + // option + public func f5(n : ?Nat) : async ?Nat { null }; + + public func send_f5( + f5 : shared (n : ?Nat) -> async ?Nat + ) : async () { + Prim.debugPrint("ok 5"); + }; + + // actor + public func f6(n : actor {}) : async (actor {}) { n }; + + public func send_f6( + f6 : shared (n : actor {}) -> async (actor {}) + ) : async () { + Prim.debugPrint("ok 6"); + }; + + public func f7(n : Nat, b : Bool) : async (Nat,Bool) {(n,b)}; + + public func send_f7( + f7 : shared (Nat, Bool) -> async (Nat, Bool) + ) : async () { + Prim.debugPrint("ok 7"); + }; + + + // record with 2 fields + public func f8({n : Nat; b : Bool}) : async {n : Nat; b : Bool} {{ n; b}}; + + public func send_f8( + f8 : shared {n : Nat; b : Bool} -> async {n : Nat; b : Bool} + ) : async () { + Prim.debugPrint("ok 8"); + }; + + // arrays + public func f9(n:[Nat]) : async [Nat] { n }; + + public func send_f9( + f9 : shared (n:[Nat]) -> async [Nat] + ) : async () { + Prim.debugPrint("ok 9"); + }; + + // blob + public func f10(n:Blob) : async Blob { n }; + + public func send_f10( + f10 : shared (n:Blob) -> async Blob + ) : async () { + Prim.debugPrint("ok 10"); + }; + + + // record with opt field + public func f11({n : Nat; o : ?Bool}) : async {n : Nat; o : ?Bool} {{ n; o}}; + + public func send_f11( + f11 : shared {n : Nat; o : ?Bool} -> async {n : Nat; o : ?Bool} + ) : async () { + Prim.debugPrint("ok 11"); + }; + + // record with opt field record + public func f12({n : Nat; o : ?{b:Bool}}) : async {n : Nat; o : ?{b:Bool}} {{ n; o}}; + + public func send_f12( + f12 : shared {n : Nat; o : ?{b:Bool}} -> async {n : Nat; o : ?{b:Bool}} + ) : async () { + Prim.debugPrint("ok 12"); + }; + + // Principals + public func f13(n:Principal) : async Principal { n }; + + public func send_f13( + f13 : shared (n:Principal) -> async Principal + ) : async () { + Prim.debugPrint("ok 13"); + }; + + type Actor = actor { + f : () -> async (); + p : () -> (); + q : shared query () -> async (); + // r : shared () -> async (); + }; + + // non-triv actor + public func f14(n : Actor) : async Actor { n }; + + public func send_f14( + f14 : shared (n : Actor) -> async Actor + ) : async () { + Prim.debugPrint("ok 14"); + }; + + // non-triv actor + public query func f15() : async () { }; + + public func send_f15( + f15 : query () -> async () + ) : async () { + Prim.debugPrint("ok 15"); + }; + + + public func go() : async () { + await this.send_f0(f0); + await this.send_f1(f1); + await this.send_f2(f2); + await this.send_f3(f3); + await this.send_f4(f4); + await this.send_f5(f5); + await this.send_f6(f6); + await this.send_f7(f7); + await this.send_f8(f8); + await this.send_f9(f9); + await this.send_f10(f10); + await this.send_f11(f11); + await this.send_f12(f12); + await this.send_f13(f13); + await this.send_f14(f14); + await this.send_f15(f15); + }; + + +} +//SKIP run +//SKIP run-ir +//SKIP run-low +//CALL ingress go "DIDL\x00\x00" diff --git a/test/run-drun/ok/call-raw-candid.drun-run.ok b/test/run-drun/ok/call-raw-candid.drun-run.ok index 23ba2432f33..f01abb9e600 100644 --- a/test/run-drun/ok/call-raw-candid.drun-run.ok +++ b/test/run-drun/ok/call-raw-candid.drun-run.ok @@ -4,7 +4,7 @@ debug.print: unit! debug.print: ("int", +1) debug.print: ("text", "hello") debug.print: ("text", (1, true, 'a')) -debug.print: IC0503: Canister rwlgt-iiaaa-aaaaa-aaaaa-cai trapped explicitly: IDL error: too few arguments Nbc +debug.print: IC0503: Canister rwlgt-iiaaa-aaaaa-aaaaa-cai trapped explicitly: IDL error: unexpected IDL type when parsing Nat debug.print: IC0503: Canister rwlgt-iiaaa-aaaaa-aaaaa-cai trapped explicitly: ohoh debug.print: supercalifragilisticexpialidocious ingress Completed: Reply: 0x4449444c0000 diff --git a/test/run-drun/ok/call-raw-candid.ic-ref-run.ok b/test/run-drun/ok/call-raw-candid.ic-ref-run.ok index dc31784da65..1a4d6266d77 100644 --- a/test/run-drun/ok/call-raw-candid.ic-ref-run.ok +++ b/test/run-drun/ok/call-raw-candid.ic-ref-run.ok @@ -7,7 +7,7 @@ debug.print: unit! debug.print: ("int", +1) debug.print: ("text", "hello") debug.print: ("text", (1, true, 'a')) -debug.print: canister trapped: EvalTrapError region:0xXXX-0xXXX "canister trapped explicitly: IDL error: too few arguments Nbc" +debug.print: canister trapped: EvalTrapError region:0xXXX-0xXXX "canister trapped explicitly: IDL error: unexpected IDL type when parsing Nat" debug.print: canister trapped: EvalTrapError region:0xXXX-0xXXX "canister trapped explicitly: ohoh" debug.print: supercalifragilisticexpialidocious <= replied: () diff --git a/test/run-drun/ok/id-sub-ho.drun-run.ok b/test/run-drun/ok/id-sub-ho.drun-run.ok new file mode 100644 index 00000000000..a10eda26a67 --- /dev/null +++ b/test/run-drun/ok/id-sub-ho.drun-run.ok @@ -0,0 +1,12 @@ +ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101 +ingress Completed: Reply: 0x4449444c0000 +debug.print: ok +debug.print: ok +debug.print: ok +debug.print: ok +debug.print: ok +debug.print: ok +debug.print: ok +debug.print: ok +debug.print: ok +ingress Completed: Reply: 0x4449444c0000 diff --git a/test/run-drun/ok/id-sub-ho.ic-ref-run.ok b/test/run-drun/ok/id-sub-ho.ic-ref-run.ok new file mode 100644 index 00000000000..85e03181f69 --- /dev/null +++ b/test/run-drun/ok/id-sub-ho.ic-ref-run.ok @@ -0,0 +1,15 @@ +→ update create_canister(record {settings = null}) +← replied: (record {hymijyo = principal "cvccv-qqaaq-aaaaa-aaaaa-c"}) +→ update install_code(record {arg = blob ""; kca_xin = blob "\00asm\01\00\00\00\0… +← replied: () +→ update go() +debug.print: ok +debug.print: ok +debug.print: ok +debug.print: ok +debug.print: ok +debug.print: ok +debug.print: ok +debug.print: ok +debug.print: ok +← replied: () diff --git a/test/run-drun/ok/idl-sub-ho-neg.drun-run.ok b/test/run-drun/ok/idl-sub-ho-neg.drun-run.ok new file mode 100644 index 00000000000..5247ea3b4df --- /dev/null +++ b/test/run-drun/ok/idl-sub-ho-neg.drun-run.ok @@ -0,0 +1,22 @@ +ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101 +ingress Completed: Reply: 0x4449444c0000 +debug.print: ok_0_1_a +debug.print: ok_0_1_b +debug.print: ok_0_2_a +debug.print: ok_0_2_a +debug.print: ok_0_3_a +debug.print: ok_0_3_b +debug.print: ok 0_4 +debug.print: ok 2_0_a +debug.print: ok 2_0_b +debug.print: ok 2_1_a +debug.print: ok 2_1_b +debug.print: ok 3_0_a +debug.print: ok 3_0_b +debug.print: ok 5_0_a +debug.print: ok 5_0_b +debug.print: ok 5_1_a +debug.print: ok 5_1_b +debug.print: ok 6_0_a +debug.print: ok 6_0_b +ingress Completed: Reply: 0x4449444c0000 diff --git a/test/run-drun/ok/idl-sub-ho-neg.ic-ref-run.ok b/test/run-drun/ok/idl-sub-ho-neg.ic-ref-run.ok new file mode 100644 index 00000000000..992d55fa9a6 --- /dev/null +++ b/test/run-drun/ok/idl-sub-ho-neg.ic-ref-run.ok @@ -0,0 +1,25 @@ +=> update provisional_create_canister_with_cycles(record {settings = null; amount = null}) +<= replied: (record {hymijyo = principal "rwlgt-iiaaa-aaaaa-aaaaa-cai"}) +=> update install_code(record {arg = blob ""; kca_xin = blob "\00asm\01\00\00\00\0... +<= replied: () +=> update go() +debug.print: ok_0_1_a +debug.print: ok_0_1_b +debug.print: ok_0_2_a +debug.print: ok_0_2_a +debug.print: ok_0_3_a +debug.print: ok_0_3_b +debug.print: ok 0_4 +debug.print: ok 2_0_a +debug.print: ok 2_0_b +debug.print: ok 2_1_a +debug.print: ok 2_1_b +debug.print: ok 3_0_a +debug.print: ok 3_0_b +debug.print: ok 5_0_a +debug.print: ok 5_0_b +debug.print: ok 5_1_a +debug.print: ok 5_1_b +debug.print: ok 6_0_a +debug.print: ok 6_0_b +<= replied: () diff --git a/test/run-drun/ok/idl-sub-ho.drun-run.ok b/test/run-drun/ok/idl-sub-ho.drun-run.ok new file mode 100644 index 00000000000..0566758d621 --- /dev/null +++ b/test/run-drun/ok/idl-sub-ho.drun-run.ok @@ -0,0 +1,14 @@ +ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101 +ingress Completed: Reply: 0x4449444c0000 +debug.print: ok +debug.print: ok +debug.print: ok +debug.print: ok +debug.print: ok +debug.print: ok +debug.print: ok +debug.print: ok +debug.print: ok +debug.print: ok +debug.print: ok +ingress Completed: Reply: 0x4449444c0000 diff --git a/test/run-drun/ok/idl-sub-ho.ic-ref-run.ok b/test/run-drun/ok/idl-sub-ho.ic-ref-run.ok new file mode 100644 index 00000000000..56cf840e251 --- /dev/null +++ b/test/run-drun/ok/idl-sub-ho.ic-ref-run.ok @@ -0,0 +1,17 @@ +=> update provisional_create_canister_with_cycles(record {settings = null; amount = null}) +<= replied: (record {hymijyo = principal "rwlgt-iiaaa-aaaaa-aaaaa-cai"}) +=> update install_code(record {arg = blob ""; kca_xin = blob "\00asm\01\00\00\00\0... +<= replied: () +=> update go() +debug.print: ok +debug.print: ok +debug.print: ok +debug.print: ok +debug.print: ok +debug.print: ok +debug.print: ok +debug.print: ok +debug.print: ok +debug.print: ok +debug.print: ok +<= replied: () diff --git a/test/run-drun/ok/idl-sub-opt-any-record.drun-run.ok b/test/run-drun/ok/idl-sub-opt-any-record.drun-run.ok new file mode 100644 index 00000000000..4eed30d4928 --- /dev/null +++ b/test/run-drun/ok/idl-sub-opt-any-record.drun-run.ok @@ -0,0 +1,8 @@ +ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101 +ingress Completed: Reply: 0x4449444c0000 +debug.print: ok_0_1_a +debug.print: ok_0 +debug.print: ok_0_1_b +debug.print: ok_0 +debug.print: ok_0_1_c +ingress Completed: Reply: 0x4449444c0000 diff --git a/test/run-drun/ok/idl-sub-opt-any-record.ic-ref-run.ok b/test/run-drun/ok/idl-sub-opt-any-record.ic-ref-run.ok new file mode 100644 index 00000000000..0fc9c962cef --- /dev/null +++ b/test/run-drun/ok/idl-sub-opt-any-record.ic-ref-run.ok @@ -0,0 +1,11 @@ +=> update provisional_create_canister_with_cycles(record {settings = null; amount = null}) +<= replied: (record {hymijyo = principal "rwlgt-iiaaa-aaaaa-aaaaa-cai"}) +=> update install_code(record {arg = blob ""; kca_xin = blob "\00asm\01\00\00\00\0... +<= replied: () +=> update go() +debug.print: ok_0_1_a +debug.print: ok_0 +debug.print: ok_0_1_b +debug.print: ok_0 +debug.print: ok_0_1_c +<= replied: () diff --git a/test/run-drun/ok/idl-sub-opt-any.drun-run.ok b/test/run-drun/ok/idl-sub-opt-any.drun-run.ok new file mode 100644 index 00000000000..3eccfc5383f --- /dev/null +++ b/test/run-drun/ok/idl-sub-opt-any.drun-run.ok @@ -0,0 +1,10 @@ +ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101 +ingress Completed: Reply: 0x4449444c0000 +debug.print: ok_0_1_a +debug.print: ok_0 +debug.print: ok_0_1_b +debug.print: ok_0 +debug.print: ok_0_1_c +debug.print: ok_0_1_d +debug.print: ok_0_1_e +ingress Completed: Reply: 0x4449444c0000 diff --git a/test/run-drun/ok/idl-sub-opt-any.ic-ref-run.ok b/test/run-drun/ok/idl-sub-opt-any.ic-ref-run.ok new file mode 100644 index 00000000000..a0db110c387 --- /dev/null +++ b/test/run-drun/ok/idl-sub-opt-any.ic-ref-run.ok @@ -0,0 +1,13 @@ +=> update provisional_create_canister_with_cycles(record {settings = null; amount = null}) +<= replied: (record {hymijyo = principal "rwlgt-iiaaa-aaaaa-aaaaa-cai"}) +=> update install_code(record {arg = blob ""; kca_xin = blob "\00asm\01\00\00\00\0... +<= replied: () +=> update go() +debug.print: ok_0_1_a +debug.print: ok_0 +debug.print: ok_0_1_b +debug.print: ok_0 +debug.print: ok_0_1_c +debug.print: ok_0_1_d +debug.print: ok_0_1_e +<= replied: () diff --git a/test/run-drun/ok/idl-sub-rec.drun-run.ok b/test/run-drun/ok/idl-sub-rec.drun-run.ok new file mode 100644 index 00000000000..88b93db6860 --- /dev/null +++ b/test/run-drun/ok/idl-sub-rec.drun-run.ok @@ -0,0 +1,22 @@ +ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101 +ingress Completed: Reply: 0x4449444c0000 +debug.print: ok f0 +debug.print: ok_0 +debug.print: ok f0 +debug.print: ok_1 +debug.print: ok f0 +debug.print: ok_2 +debug.print: ok f0 +debug.print: ok_3 +debug.print: ok f0 +debug.print: ok_4 +debug.print: ok 5:IC0503: Canister rwlgt-iiaaa-aaaaa-aaaaa-cai trapped explicitly: IDL error: incompatible function type +debug.print: ok f1 +debug.print: ok_6 +debug.print: ok f1 +debug.print: ok f1 +debug.print: ok_8 +debug.print: ok_9IC0503: Canister rwlgt-iiaaa-aaaaa-aaaaa-cai trapped explicitly: IDL error: incompatible function type +debug.print: ok f4 +debug.print: ok_10 +ingress Completed: Reply: 0x4449444c0000 diff --git a/test/run-drun/ok/idl-sub-rec.ic-ref-run.ok b/test/run-drun/ok/idl-sub-rec.ic-ref-run.ok new file mode 100644 index 00000000000..07e06bc67c3 --- /dev/null +++ b/test/run-drun/ok/idl-sub-rec.ic-ref-run.ok @@ -0,0 +1,25 @@ +=> update provisional_create_canister_with_cycles(record {settings = null; amount = null}) +<= replied: (record {hymijyo = principal "rwlgt-iiaaa-aaaaa-aaaaa-cai"}) +=> update install_code(record {arg = blob ""; kca_xin = blob "\00asm\01\00\00\00\0... +<= replied: () +=> update go() +debug.print: ok f0 +debug.print: ok_0 +debug.print: ok f0 +debug.print: ok_1 +debug.print: ok f0 +debug.print: ok_2 +debug.print: ok f0 +debug.print: ok_3 +debug.print: ok f0 +debug.print: ok_4 +debug.print: ok 5:canister trapped: EvalTrapError region:0xXXX-0xXXX "canister trapped explicitly: IDL error: incompatible function type" +debug.print: ok f1 +debug.print: ok_6 +debug.print: ok f1 +debug.print: ok f1 +debug.print: ok_8 +debug.print: ok_9canister trapped: EvalTrapError region:0xXXX-0xXXX "canister trapped explicitly: IDL error: incompatible function type" +debug.print: ok f4 +debug.print: ok_10 +<= replied: () diff --git a/test/run-drun/ok/idl-sub-variant.drun-run.ok b/test/run-drun/ok/idl-sub-variant.drun-run.ok new file mode 100644 index 00000000000..0eac0de659e --- /dev/null +++ b/test/run-drun/ok/idl-sub-variant.drun-run.ok @@ -0,0 +1,6 @@ +ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101 +ingress Completed: Reply: 0x4449444c0000 +debug.print: pass1 +debug.print: pass1 +debug.print: pass3: IC0503: Canister rwlgt-iiaaa-aaaaa-aaaaa-cai trapped explicitly: IDL error: unexpected variant tag +ingress Completed: Reply: 0x4449444c0000 diff --git a/test/run-drun/ok/idl-sub-variant.ic-ref-run.ok b/test/run-drun/ok/idl-sub-variant.ic-ref-run.ok new file mode 100644 index 00000000000..676d9493fca --- /dev/null +++ b/test/run-drun/ok/idl-sub-variant.ic-ref-run.ok @@ -0,0 +1,9 @@ +=> update provisional_create_canister_with_cycles(record {settings = null; amount = null}) +<= replied: (record {hymijyo = principal "rwlgt-iiaaa-aaaaa-aaaaa-cai"}) +=> update install_code(record {arg = blob ""; kca_xin = blob "\00asm\01\00\00\00\0... +<= replied: () +=> update go() +debug.print: pass1 +debug.print: pass1 +debug.print: pass3: canister trapped: EvalTrapError region:0xXXX-0xXXX "canister trapped explicitly: IDL error: unexpected variant tag" +<= replied: () diff --git a/test/run-drun/ok/idl-sub-variant.tc.ok b/test/run-drun/ok/idl-sub-variant.tc.ok new file mode 100644 index 00000000000..6ca7af9cf1c --- /dev/null +++ b/test/run-drun/ok/idl-sub-variant.tc.ok @@ -0,0 +1,4 @@ +idl-sub-variant.mo:29.13-29.19: warning [M0145], this pattern of type + Enum1 = {#A : Int; #B} +does not cover value + #B diff --git a/test/run-drun/ok/idl-sub.drun-run.ok b/test/run-drun/ok/idl-sub.drun-run.ok new file mode 100644 index 00000000000..c5094c146e0 --- /dev/null +++ b/test/run-drun/ok/idl-sub.drun-run.ok @@ -0,0 +1,19 @@ +ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101 +ingress Completed: Reply: 0x4449444c0000 +debug.print: ok 0 +debug.print: ok 1 +debug.print: ok 2 +debug.print: ok 3 +debug.print: ok 4 +debug.print: ok 5 +debug.print: ok 6 +debug.print: ok 7 +debug.print: ok 8 +debug.print: ok 9 +debug.print: ok 10 +debug.print: ok 11 +debug.print: ok 12 +debug.print: ok 13 +debug.print: ok 14 +debug.print: ok 15 +ingress Completed: Reply: 0x4449444c0000 diff --git a/test/run-drun/ok/idl-sub.ic-ref-run.ok b/test/run-drun/ok/idl-sub.ic-ref-run.ok new file mode 100644 index 00000000000..8531556364f --- /dev/null +++ b/test/run-drun/ok/idl-sub.ic-ref-run.ok @@ -0,0 +1,22 @@ +=> update provisional_create_canister_with_cycles(record {settings = null; amount = null}) +<= replied: (record {hymijyo = principal "rwlgt-iiaaa-aaaaa-aaaaa-cai"}) +=> update install_code(record {arg = blob ""; kca_xin = blob "\00asm\01\00\00\00\0... +<= replied: () +=> update go() +debug.print: ok 0 +debug.print: ok 1 +debug.print: ok 2 +debug.print: ok 3 +debug.print: ok 4 +debug.print: ok 5 +debug.print: ok 6 +debug.print: ok 7 +debug.print: ok 8 +debug.print: ok 9 +debug.print: ok 10 +debug.print: ok 11 +debug.print: ok 12 +debug.print: ok 13 +debug.print: ok 14 +debug.print: ok 15 +<= replied: ()