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
8.8 KiB252 linesraw
1use std::cell::Cell;
2use std::io;
3use std::rc::Rc;
4
5use avril_core::{Extension, Op, Runtime, RuntimeOptions, v8};
6
7#[test]
8fn raw_ops_receive_positional_values_and_preserve_javascript_exceptions() {
9    let mut runtime = Runtime::new(RuntimeOptions {
10        extensions: vec![Extension {
11            name: "ext:test",
12            dependencies: &[],
13            ops: vec![
14                Op::raw("invoke", |scope, args| {
15                    let callback = v8::Local::<v8::Function>::try_from(args.get(0)).ok()?;
16                    let values: Vec<_> = (2..args.length()).map(|index| args.get(index)).collect();
17                    callback.call(scope, args.get(1), &values)
18                }),
19                Op::raw("count", |scope, args| {
20                    Some(v8::Integer::new(scope, args.length()).into())
21                }),
22                Op::raw("empty", |_, _| None),
23            ],
24            modules: &[(
25                "ext:test/main.js",
26                "import {invoke,count,empty} from 'ext:core/ops'; Object.assign(globalThis,{invoke,count,empty});",
27            )],
28            entry_point: Some("ext:test/main.js"),
29        }],
30        ..Default::default()
31    })
32    .unwrap();
33
34    runtime
35        .execute_script::<()>(
36            "arguments.js",
37            r#"
38            const marker = {toJSON() {throw Error('Serialized arguments');}};
39            const symbol = Symbol('opaque');
40            const array = [1, 2];
41            if (count() !== 0 || count(undefined) !== 1 || count(marker, array, symbol) !== 3) {
42                throw Error('Argument count');
43            }
44            const result = invoke(function(first, second, third, fourth) {
45                if (this !== marker || first !== marker || second !== array || third !== symbol
46                    || fourth !== undefined || arguments.length !== 4) throw Error('Argument values');
47                return marker;
48            }, marker, marker, array, symbol, undefined);
49            if (result !== marker || invoke(() => 42) !== 42 || empty() !== undefined) throw Error('Return value');
50            if (invoke((first, second) => first === undefined && second === undefined, null) !== true) {
51                throw Error('Missing arguments');
52            }
53            try {invoke(() => {throw marker;}, null); throw Error('Lost exception');}
54            catch (error) {if (error !== marker) throw error;}
55            if (invoke(() => 7, null) !== 7) throw Error('Poisoned bridge');
56            "#,
57        )
58        .unwrap();
59}
60
61#[test]
62fn captured_op_state_survives_gc_reentrancy_and_unrelated_isolate_slots() {
63    struct Probe(Rc<Cell<u32>>);
64    impl Drop for Probe {
65        fn drop(&mut self) {
66            self.0.set(self.0.get() + 1);
67        }
68    }
69
70    let drops = Rc::new(Cell::new(0));
71    let mut ops: Vec<_> = [("one", 10), ("two", 20)]
72        .into_iter()
73        .map(|(name, offset)| {
74            let probe = Probe(drops.clone());
75            Op::raw(name, move |scope, args| {
76                let value = args.get(0).uint32_value(scope)?;
77                assert_eq!(probe.0.get(), 0);
78                Some(v8::Integer::new_from_unsigned(scope, value + offset).into())
79            })
80        })
81        .collect();
82    ops.push(Op::raw("collect", |scope, _| {
83        scope.set_slot(Rc::<[Op]>::from([]));
84        scope.low_memory_notification();
85        None
86    }));
87    let mut runtime = Runtime::new(RuntimeOptions {
88        extensions: vec![Extension {
89            name: "ext:test",
90            dependencies: &[],
91            ops,
92            modules: &[(
93                "ext:test/main.js",
94                "import {one,two,collect} from 'ext:core/ops'; Object.assign(globalThis,{one,two,collect});",
95            )],
96            entry_point: Some("ext:test/main.js"),
97        }],
98        ..Default::default()
99    })
100    .unwrap();
101    runtime
102        .execute_script::<()>(
103            "captures.js",
104            r#"
105            if (one({valueOf() {collect(); return two(1);}}) !== 31) throw Error('Reentrant state');
106            for (let i = 0; i < 1000; i++) {
107                if (one(i) !== i + 10 || two(i) !== i + 20) throw Error('Shared callback state');
108            }
109            const marker = {};
110            try {one({valueOf() {collect(); throw marker;}}); throw Error('Lost exception');}
111            catch (error) {if (error !== marker) throw error;}
112            if (two(2) !== 22) throw Error('Poisoned state');
113            "#,
114        )
115        .unwrap();
116    assert_eq!(drops.get(), 0);
117    drop(runtime);
118    assert_eq!(drops.get(), 2);
119}
120
121#[test]
122fn zero_sized_captures_are_dropped_only_with_the_runtime() {
123    thread_local! {
124        static DROPS: Cell<u32> = const { Cell::new(0) };
125    }
126    #[repr(align(64))]
127    struct Probe;
128    impl Drop for Probe {
129        fn drop(&mut self) {
130            DROPS.with(|drops| drops.set(drops.get() + 1));
131        }
132    }
133
134    DROPS.with(|drops| drops.set(0));
135    let probe = Probe;
136    let op = Op::raw("identity", move |_, args| {
137        let address = std::hint::black_box(&probe) as *const Probe as usize;
138        assert_eq!(address % std::mem::align_of::<Probe>(), 0);
139        Some(args.get(0))
140    });
141    let mut runtime = Runtime::new(RuntimeOptions {
142        extensions: vec![Extension {
143            name: "ext:test",
144            dependencies: &[],
145            ops: vec![op],
146            modules: &[(
147                "ext:test/main.js",
148                "import {identity} from 'ext:core/ops'; globalThis.identity = identity;",
149            )],
150            entry_point: Some("ext:test/main.js"),
151        }],
152        ..Default::default()
153    })
154    .unwrap();
155    assert_eq!(
156        runtime
157            .execute_script::<u32>("zero.js", "identity(42)")
158            .unwrap(),
159        42
160    );
161    DROPS.with(|drops| assert_eq!(drops.get(), 0));
162    drop(runtime);
163    DROPS.with(|drops| assert_eq!(drops.get(), 1));
164}
165
166#[test]
167fn fallible_raw_ops_return_opaque_values_and_throw_host_errors() {
168    let mut runtime = Runtime::new(RuntimeOptions {
169        extensions: vec![Extension {
170            name: "ext:test",
171            dependencies: &[],
172            ops: vec![Op::try_raw("select", |_, args| {
173                if args.get(0).is_true() {
174                    Ok(args.get(1))
175                } else {
176                    Err(io::Error::other("Selection failed"))
177                }
178            })],
179            modules: &[(
180                "ext:test/main.js",
181                "import {select} from 'ext:core/ops'; globalThis.select = select;",
182            )],
183            entry_point: Some("ext:test/main.js"),
184        }],
185        ..Default::default()
186    })
187    .unwrap();
188    runtime
189        .execute_script::<()>(
190            "fallible.js",
191            r#"
192            const value = {toJSON() {throw Error('Serialized result');}};
193            if (select(true, value) !== value) throw Error('Lost identity');
194            try {select(false, value); throw Error('Expected failure');}
195            catch (error) {
196                if (!(error instanceof Error) || error.message !== 'Selection failed') throw error;
197            }
198            if (select(true, value) !== value) throw Error('Poisoned op');
199            "#,
200        )
201        .unwrap();
202}
203
204#[test]
205fn typed_op_adapters_still_decode_only_the_first_argument() {
206    let mut runtime = Runtime::new(RuntimeOptions {
207        extensions: vec![Extension {
208            name: "ext:test",
209            dependencies: &[],
210            ops: vec![
211                Op::new("sum", |(left, right): (u32, u32)| {
212                    Ok::<_, io::Error>(left + right)
213                }),
214                Op::with_scope("scoped", |_, (left, right): (u32, u32)| {
215                    Ok::<_, io::Error>(left + right)
216                }),
217                Op::asynchronous("asyncSum", |(left, right): (u32, u32)| async move {
218                    Ok::<_, io::Error>(left + right)
219                }),
220                Op::asynchronous_unreferenced("unreferenced", |(left, right): (u32, u32)| async move {
221                    Ok::<_, io::Error>(left + right)
222                }),
223            ],
224            modules: &[(
225                "ext:test/main.js",
226                "import {sum,scoped,asyncSum,unreferenced} from 'ext:core/ops'; Object.assign(globalThis,{sum,scoped,asyncSum,unreferenced});",
227            )],
228            entry_point: Some("ext:test/main.js"),
229        }],
230        ..Default::default()
231    })
232    .unwrap();
233
234    runtime
235        .execute_script::<()>(
236            "typed.js",
237            r#"
238            const extra = Symbol('ignored');
239            globalThis.results = [sum([20, 22], extra), scoped([20, 22], extra)];
240            Promise.all([asyncSum([20, 22], extra), unreferenced([20, 22], extra)])
241                .then(values => results.push(...values));
242            "#,
243        )
244        .unwrap();
245    runtime.run_event_loop().unwrap();
246    assert_eq!(
247        runtime
248            .execute_script::<Vec<u32>>("results.js", "results")
249            .unwrap(),
250        [42, 42, 42, 42]
251    );
252}