quickjs_rusty/
compile.rs

1//! Utils to compile script to bytecode and run script from bytecode
2
3use std::os::raw::c_void;
4
5use libquickjs_ng_sys as q;
6
7use crate::utils::{ensure_no_excpetion, get_exception, make_cstring};
8use crate::value::{JsCompiledFunction, OwnedJsValue};
9use crate::ExecutionError;
10
11/// compile a script, will result in a JSValueRef with tag JS_TAG_FUNCTION_BYTECODE or JS_TAG_MODULE.
12///  It can be executed with run_compiled_function().
13pub fn compile(
14    context: *mut q::JSContext,
15    script: &str,
16    file_name: &str,
17) -> Result<OwnedJsValue, ExecutionError> {
18    let filename_c = make_cstring(file_name)?;
19    let code_c = make_cstring(script)?;
20
21    let value = unsafe {
22        let v = q::JS_Eval(
23            context,
24            code_c.as_ptr(),
25            script.len() as _,
26            filename_c.as_ptr(),
27            q::JS_EVAL_FLAG_COMPILE_ONLY as i32,
28        );
29        OwnedJsValue::new(context, v)
30    };
31
32    // check for error
33    ensure_no_excpetion(context)?;
34    Ok(value)
35}
36
37/// compile a script, will result in a JSValueRef with tag JS_TAG_FUNCTION_BYTECODE or JS_TAG_MODULE.
38///  It can be executed with run_compiled_function().
39pub fn compile_module(
40    context: *mut q::JSContext,
41    script: &str,
42    file_name: &str,
43) -> Result<OwnedJsValue, ExecutionError> {
44    let filename_c = make_cstring(file_name)?;
45    let code_c = make_cstring(script)?;
46
47    let value = unsafe {
48        let v = q::JS_Eval(
49            context,
50            code_c.as_ptr(),
51            script.len() as _,
52            filename_c.as_ptr(),
53            q::JS_EVAL_TYPE_MODULE as i32 | q::JS_EVAL_FLAG_COMPILE_ONLY as i32,
54        );
55        OwnedJsValue::new(context, v)
56    };
57
58    // check for error
59    ensure_no_excpetion(context)?;
60    Ok(value)
61}
62
63/// run a compiled function, see compile for an example
64pub fn run_compiled_function(func: &JsCompiledFunction) -> Result<OwnedJsValue, ExecutionError> {
65    let context = func.as_value().context();
66    let value = unsafe {
67        // NOTE: JS_EvalFunction takes ownership.
68        // We clone the func and extract the inner JsValue.
69        let f = func.clone().into_value().extract();
70        let v = q::JS_EvalFunction(context, f);
71        OwnedJsValue::new(context, v)
72    };
73
74    ensure_no_excpetion(context).map_err(|e| {
75        if let ExecutionError::Internal(msg) = e {
76            ExecutionError::Internal(format!("Could not evaluate compiled function: {}", msg))
77        } else {
78            e
79        }
80    })?;
81
82    Ok(value)
83}
84
85/// write a function to bytecode
86pub fn to_bytecode(context: *mut q::JSContext, compiled_func: &JsCompiledFunction) -> Vec<u8> {
87    unsafe {
88        let mut len = 0;
89        let raw = q::JS_WriteObject(
90            context,
91            &mut len,
92            *compiled_func.as_value().as_inner(),
93            q::JS_WRITE_OBJ_BYTECODE as i32,
94        );
95        let slice = std::slice::from_raw_parts(raw, len);
96        let data = slice.to_vec();
97        q::js_free(context, raw as *mut c_void);
98        data
99    }
100}
101
102/// read a compiled function from bytecode, see to_bytecode for an example
103pub fn from_bytecode(
104    context: *mut q::JSContext,
105    bytecode: &[u8],
106) -> Result<OwnedJsValue, ExecutionError> {
107    assert!(!bytecode.is_empty());
108    {
109        let len = bytecode.len();
110        let buf = bytecode.as_ptr();
111        let raw =
112            unsafe { q::JS_ReadObject(context, buf, len as _, q::JS_READ_OBJ_BYTECODE as i32) };
113
114        let func_ref = OwnedJsValue::new(context, raw);
115        if func_ref.is_exception() {
116            let ex_opt = get_exception(context);
117            if let Some(ex) = ex_opt {
118                Err(ex)
119            } else {
120                Err(ExecutionError::Internal(
121                    "from_bytecode failed and could not get exception".to_string(),
122                ))
123            }
124        } else {
125            Ok(func_ref)
126        }
127    }
128}
129
130#[cfg(test)]
131pub mod tests {
132    use super::*;
133    use crate::Context;
134
135    #[test]
136    fn test_compile_function() {
137        let ctx = Context::new(None).unwrap();
138
139        let func_res = compile(
140            ctx.context,
141            "{let a_tb3 = 7; let b_tb3 = 5; a_tb3 * b_tb3;}",
142            "test_func.es",
143        );
144        let func = func_res
145            .ok()
146            .expect("func compile failed")
147            .try_into_compiled_function()
148            .unwrap();
149        let bytecode: Vec<u8> = to_bytecode(ctx.context, &func);
150        drop(func);
151        assert!(!bytecode.is_empty());
152
153        let func2_res = from_bytecode(ctx.context, &bytecode);
154        let func2 = func2_res
155            .ok()
156            .expect("could not read bytecode")
157            .try_into_compiled_function()
158            .unwrap();
159        let run_res = run_compiled_function(&func2);
160        match run_res {
161            Ok(res) => {
162                assert_eq!(res.to_int().unwrap(), 7 * 5);
163            }
164            Err(e) => {
165                panic!("run failed1: {}", e);
166            }
167        }
168    }
169
170    #[test]
171    fn test_load_and_eval_compiled_function() {
172        let ctx = Context::new(None).unwrap();
173
174        let func_res = compile(
175            ctx.context,
176            "{let a_tb4 = 7; let b_tb4 = 5; a_tb4 * b_tb4;}",
177            "test_func.es",
178        );
179        let func = func_res
180            .ok()
181            .expect("func compile failed")
182            .try_into_compiled_function()
183            .unwrap();
184        let bytecode: Vec<u8> = to_bytecode(ctx.context, &func);
185        drop(func);
186        assert!(!bytecode.is_empty());
187        let func2_res = from_bytecode(ctx.context, &bytecode);
188        let func2 = func2_res
189            .ok()
190            .expect("could not read bytecode")
191            .try_into_compiled_function()
192            .unwrap();
193        let run_res = run_compiled_function(&func2);
194
195        match run_res {
196            Ok(res) => {
197                assert_eq!(res.to_int().unwrap(), 7 * 5);
198            }
199            Err(e) => {
200                panic!("run failed: {}", e);
201            }
202        }
203    }
204
205    #[test]
206    fn test_load_compiled_function_fail() {
207        let ctx = Context::new(None).unwrap();
208
209        let func_res = compile(
210            ctx.context,
211            "{the changes of me compil1ng a're slim to 0-0}",
212            "test_func_fail.es",
213        );
214        func_res.expect_err("func compiled unexpectedly");
215    }
216
217    #[test]
218    fn test_compiled_func_bad_eval() {
219        let ctx = Context::new(None).unwrap();
220
221        let func_res = compile(ctx.context, "let abcdef = 1;", "test_func_runfail.es");
222        let func = func_res
223            .ok()
224            .expect("func compile failed")
225            .try_into_compiled_function()
226            .unwrap();
227        assert_eq!(1, func.as_value().get_ref_count());
228
229        let bytecode: Vec<u8> = to_bytecode(ctx.context, &func);
230
231        assert_eq!(1, func.as_value().get_ref_count());
232
233        drop(func);
234
235        assert!(!bytecode.is_empty());
236
237        let func2_res = from_bytecode(ctx.context, &bytecode);
238        let func2 = func2_res
239            .ok()
240            .expect("could not read bytecode")
241            .try_into_compiled_function()
242            .unwrap();
243
244        //should fail the second time you run this because abcdef is already defined
245
246        assert_eq!(1, func2.as_value().get_ref_count());
247
248        let run_res1 = run_compiled_function(&func2)
249            .ok()
250            .expect("run 1 failed unexpectedly");
251        drop(run_res1);
252
253        assert_eq!(1, func2.as_value().get_ref_count());
254
255        let _run_res2 = run_compiled_function(&func2).expect_err("run 2 succeeded unexpectedly");
256
257        assert_eq!(1, func2.as_value().get_ref_count());
258    }
259}