char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somcore: add dependencies list to Extension81eaeb3

main
7.8 KiB171 linesraw
1use std::cell::RefCell;
2use std::io;
3use std::rc::Rc;
4use std::sync::{Arc, Weak};
5
6use avril_core::{Extension, Op, Runtime, RuntimeOptions, message, v8};
7
8struct Payload(u32);
9
10fn runtime() -> (Runtime, Rc<RefCell<Vec<Weak<Payload>>>>) {
11    let messages = Rc::new(RefCell::new(Vec::<Option<message::Message>>::new()));
12    let payloads = Rc::new(RefCell::new(Vec::new()));
13    let runtime = Runtime::new(RuntimeOptions {
14        extensions: vec![Extension {
15            name: "ext:test",
16            dependencies: &[],
17            ops: vec![
18                Op::with_scope("encode_payload", {
19                    let payloads = payloads.clone();
20                    move |scope, value: u32| {
21                        let payload = Arc::new(Payload(value));
22                        payloads.borrow_mut().push(Arc::downgrade(&payload));
23                        message::serialize_host(scope, payload)
24                    }
25                }),
26                Op::with_scope("decode_payload", |scope, id: u32| {
27                    Ok::<_, io::Error>(message::deserialize_host::<Payload>(scope, id)?.0)
28                }),
29                Op::raw("send", {
30                    let messages = messages.clone();
31                    move |scope, args| {
32                        let args = v8::Local::<v8::Array>::try_from(args.get(0)).ok()?;
33                        let value = args.get_index(scope, 0)?;
34                        let mut callbacks = Vec::new();
35                        for index in 1..6 {
36                            callbacks.push(v8::Local::<v8::Function>::try_from(args.get_index(scope, index)?).ok()?);
37                        }
38                        let raw_transfer = args.get_index(scope, 6)?;
39                        let transfer = v8::Local::<v8::Array>::try_from(raw_transfer).unwrap_or_else(|_| v8::Array::new(scope, 0));
40                        let packet = message::serialize(scope, value, transfer, callbacks.try_into().ok()?)?;
41                        let mut messages = messages.borrow_mut();
42                        let id = messages.len() as u32;
43                        messages.push(Some(packet));
44                        Some(v8::Integer::new_from_unsigned(scope, id).into())
45                    }
46                }),
47                Op::new("duplicate", {
48                    let messages = messages.clone();
49                    move |id: usize| {
50                        let mut messages = messages.borrow_mut();
51                        let copy = messages[id].as_ref().unwrap().try_clone();
52                        let id = copy.map(|message| {
53                            let id = messages.len();
54                            messages.push(Some(message));
55                            id
56                        });
57                        Ok::<_, std::convert::Infallible>(id)
58                    }
59                }),
60                Op::raw("receive", move |scope, args| {
61                    let args = v8::Local::<v8::Array>::try_from(args.get(0)).ok()?;
62                    let id = args.get_index(scope, 0)?.uint32_value(scope)?;
63                    let host = v8::Local::<v8::Function>::try_from(args.get_index(scope, 1)?).ok()?;
64                    let error = v8::Local::<v8::Function>::try_from(args.get_index(scope, 2)?).ok()?;
65                    let packet = messages.borrow_mut().get_mut(id as usize)?.take()?;
66                    message::deserialize(scope, packet, host, error)
67                }),
68            ],
69            modules: &[("ext:test/main.js", r#"
70                import {encode_payload,decode_payload,send,receive,duplicate} from 'ext:core/ops';
71                const hosts=new WeakMap();
72                globalThis.makeHost=value=>{const host={};hosts.set(host,value);return host;};
73                globalThis.hostValue=host=>hosts.get(host);
74                globalThis.encodePayload=encode_payload;
75                globalThis.decodePayload=decode_payload;
76                const error=message=>new Error(message);
77                globalThis.send=(value,encode=host=>encode_payload(hosts.get(host)))=>
78                    send([value,host=>hosts.has(host)?1:0,encode,error,()=>{},()=>{}]);
79                globalThis.receive=(id,decode=index=>makeHost(decode_payload(index)))=>receive([id,decode,error]);
80                globalThis.duplicate=duplicate;
81                globalThis.sendTransfer=(value,transfer)=>send([value,()=>0,()=>{},error,()=>{},()=>{},transfer]);
82            "#)],
83            entry_point: Some("ext:test/main.js"),
84        }],
85        ..Default::default()
86    }).unwrap();
87    (runtime, payloads)
88}
89
90#[test]
91fn opaque_payloads_live_until_messages_are_consumed_or_dropped() {
92    let (mut runtime, payloads) = runtime();
93    runtime
94        .execute_script::<()>("send", "globalThis.id=send(makeHost(42));")
95        .unwrap();
96    assert!(payloads.borrow()[0].upgrade().is_some());
97    assert_eq!(
98        runtime
99            .execute_script::<u32>("receive", "hostValue(receive(id))")
100            .unwrap(),
101        42
102    );
103    assert!(payloads.borrow()[0].upgrade().is_none());
104    runtime
105        .execute_script::<()>("queued", "send(makeHost(7));")
106        .unwrap();
107    assert!(payloads.borrow()[1].upgrade().is_some());
108    drop(runtime);
109    assert!(payloads.borrow()[1].upgrade().is_none());
110}
111
112#[test]
113fn failed_and_nested_messages_do_not_leak_or_replace_the_active_payloads() {
114    let (mut runtime, payloads) = runtime();
115    runtime
116        .execute_script::<()>(
117            "nested",
118            r#"
119        for(const call of [()=>encodePayload(1),()=>decodePayload(0)]) {
120            try{call();throw Error('Allowed outside callback');}
121            catch(e){if(!e.message.includes('No matching host serialization callback'))throw e;}
122        }
123        const id=send(makeHost(42),host=>{
124            try{send({host:makeHost(7),fail(){}});throw Error('Accepted function');}
125            catch(e){if(e.message==='Accepted function')throw e;}
126            const inner=send(makeHost(11));
127            if(hostValue(receive(inner))!==11)throw Error('Nested payload');
128            return encodePayload(hostValue(host));
129        });
130        const result=receive(id,index=>{
131            const inner=send(makeHost(13));
132            if(hostValue(receive(inner))!==13)throw Error('Nested decode');
133            try{decodePayload(999);throw Error('Accepted invalid index');}
134            catch(e){if(!e.message.includes('Invalid host payload'))throw e;}
135            return makeHost(decodePayload(index));
136        });
137        if(hostValue(result)!==42)throw Error('Outer payload');
138        try{send({host:makeHost(17),fail(){}});throw Error('Accepted function');}
139        catch(e){if(e.message==='Accepted function')throw e;}
140        try{encodePayload(1);throw Error('Callback context leaked');}
141        catch(e){if(!e.message.includes('No matching host serialization callback'))throw e;}
142    "#,
143        )
144        .unwrap();
145    assert!(!payloads.borrow().is_empty());
146    assert!(
147        payloads
148            .borrow()
149            .iter()
150            .all(|payload| payload.upgrade().is_none())
151    );
152}
153
154#[test]
155fn only_messages_without_transfers_can_be_duplicated() {
156    let (mut runtime, _) = runtime();
157    runtime.execute_script::<()>("copy", r#"
158        const original = send({bytes:new Uint8Array([1,2])});
159        const copy = duplicate(original);
160        const a = receive(original), b = receive(copy);
161        a.bytes[0] = 9;
162        if (b.bytes[0] !== 1) throw Error('Aliased nonshared buffer');
163        const buffer = new ArrayBuffer(4);
164        const transferred = sendTransfer(buffer,[buffer]);
165        if (buffer.byteLength !== 0 || duplicate(transferred) !== null) throw Error('Duplicated transfer');
166        if (receive(transferred).byteLength !== 4) throw Error('Lost transferred buffer');
167        const unused = new ArrayBuffer(8);
168        const withUnusedTransfer = sendTransfer('value',[unused]);
169        if (duplicate(withUnusedTransfer) !== null) throw Error('Ignored unused transfer');
170    "#).unwrap();
171}