1use crate::{ConsoleFunc, DukContext, JsError, Return};
2use log::log;
3use std::any::TypeId;
4
5pub trait JsInterop: std::any::Any + std::fmt::Debug + 'static {
6 fn call(&mut self, engine: &mut DukContext, func_name: &str) -> Result<Return, JsError>;
7
8 unsafe fn alloc(&mut self, size: usize) -> *mut u8 {
9 super::alloc::alloc(size)
10 }
11
12 unsafe fn realloc(&mut self, ptr: *mut u8, size: usize) -> *mut u8 {
13 super::alloc::realloc(ptr, size)
14 }
15
16 unsafe fn free(&mut self, ptr: *mut u8) {
17 super::alloc::free(ptr)
18 }
19
20 fn fatal(&mut self, msg: &str) -> ! {
21 panic!("Duktape fatal error: {}", msg);
22 }
23
24 fn console(&mut self, func: ConsoleFunc, msg: &str) {
25 log!(func.level(), "JS: {}", msg);
26 }
27}
28
29impl dyn JsInterop {
30 pub fn downcast_ref<T: JsInterop>(&self) -> Option<&T> {
31 if self.type_id() == TypeId::of::<T>() {
32 unsafe { Some(&*(self as *const dyn JsInterop as *const T)) }
33 } else {
34 None
35 }
36 }
37
38 pub fn downcast_mut<T: JsInterop>(&mut self) -> Option<&mut T> {
39 let t = (self as &dyn JsInterop).type_id();
40 if t == TypeId::of::<T>() {
41 unsafe { Some(&mut *(self as *mut dyn JsInterop as *mut T)) }
42 } else {
43 None
44 }
45 }
46}
47
48#[derive(Debug)]
49pub struct NoopInterop;
50
51impl JsInterop for NoopInterop {
52 fn call(&mut self, _ctx: &mut DukContext, _func_name: &str) -> Result<Return, JsError> {
53 Ok(Return::Undefined)
54 }
55}
56
57#[cfg(test)]
58mod tests {
59 use std::sync::Arc;
60 use crate::JsEngine;
61 use super::*;
62
63 #[derive(Debug)]
64 struct Interop {
65 stdout: String,
66 number: f64,
67 pub tracker: Arc<std::sync::Mutex<alloc_tracker::AllocTracker>>,
68 }
69
70 impl Interop {
71 pub fn new() -> Self {
72 Self {
73 stdout: String::new(),
74 number: 0.,
75 tracker: Arc::new(std::sync::Mutex::new(alloc_tracker::AllocTracker::new())),
76 }
77 }
78 }
79
80 impl JsInterop for Interop {
81 fn call(&mut self, ctx: &mut DukContext, func_name: &str) -> Result<Return, JsError> {
82 match func_name {
83 "add" => {
84 let a = ctx.get_number(0);
85 let b = ctx.get_number(1);
86 let res = a + b;
87 ctx.push_number(res);
88 Ok(Return::Top)
89 }
90 "sub" => {
91 let a = ctx.get_number(0);
92 let b = ctx.get_number(1);
93 let res = a - b;
94 ctx.push_number(res);
95 Ok(Return::Top)
96 }
97 "put_number" => {
98 let n = ctx.get_number(0);
99 self.number = n;
100 Ok(Return::Undefined)
101 }
102 "get_number" => {
103 ctx.push_number(self.number);
104 Ok(Return::Top)
105 }
106 _ => unreachable!(),
107 }
108 }
109
110
111 #[cfg(test)]
112 unsafe fn alloc(&mut self, size: usize) -> *mut u8 {
113 let ptr = crate::alloc::alloc(size);
114 self.tracker.lock().unwrap().alloc(ptr, size);
115 ptr
116 }
117
118
119 #[cfg(test)]
120 unsafe fn realloc(&mut self, ptr: *mut u8, size: usize) -> *mut u8 {
121 let new_ptr = crate::alloc::realloc(ptr, size);
122 self.tracker.lock().unwrap().realloc(ptr, new_ptr, size);
123 new_ptr
124 }
125
126 #[cfg(test)]
127 unsafe fn free(&mut self, ptr: *mut u8) {
128 self.tracker.lock().unwrap().free(ptr);
129 crate::alloc::free(ptr)
130 }
131
132
133 fn console(&mut self, _func: ConsoleFunc, msg: &str) {
134 self.stdout.push_str(msg);
135 self.stdout.push('\n');
136 }
137 }
138
139 fn init() -> JsEngine {
140 let e = JsEngine::with_interop(Interop::new()).unwrap();
141 e.init_console();
142 e.put_global_function("add", 2);
143 e.put_global_function("sub", 2);
144 e.put_global_function("put_number", 1);
145 e.put_global_function("get_number", 0);
146 e
147 }
148
149 #[test]
150 fn call_rust_function() {
151 let e = init();
152
153 e.eval("var a = add(10, 11); put_number(a);").unwrap();
154 assert_eq!(21f64, e.interop_as::<Interop>().number);
155
156 e.eval("var b = sub(12, 10); put_number(b);").unwrap();
157 assert_eq!(2f64, e.interop_as::<Interop>().number);
158
159 e.eval("put_number(123.5); console.log(get_number());")
160 .unwrap();
161 assert_eq!("123.5\n", e.interop_as::<Interop>().stdout);
162 }
163
164 #[test]
165 fn test_changed_duk_context() {
166 let e = init();
167
168 e.eval("typeof add === 'function'").unwrap();
170 assert!(e.get_boolean(-1));
171 e.pop();
172
173 let new_idx = e.push_thread_new_globalenv();
174 let ctx = e.get_context(new_idx).unwrap();
175
176 ctx.eval("typeof add === 'undefined'").unwrap();
179
180 ctx.put_global_function("add", 2);
182
183 ctx.eval("add(2, 3)").unwrap();
186 assert_eq!(5.0, ctx.get_number(-1));
187
188 ctx.eval("var f = function (a, b) { return a * b; }; f").unwrap();
191 ctx.put_global_string("multiply");
192
193 ctx.eval("multiply(2, 5)").unwrap();
196 assert_eq!(10.0, ctx.get_number(-1));
197
198 drop(ctx);
200 e.pop();
201
202 e.eval("add(2, 3)").unwrap();
205 assert_eq!(5.0, e.get_number(-1));
206 e.pop();
207
208 e.eval("typeof multiply === 'undefined'").unwrap();
211 assert!(e.get_boolean(-1));
212 e.pop();
213
214 assert!(e.get_stack_dump().contains("ctx: top=0"));
215 }
216
217
218 #[test]
219 fn test_eval_allocations() {
220 let engine = init();
221 let tracker = engine.interop_as::<Interop>().tracker.clone();
222 assert_eq!(tracker.lock().unwrap().total_bytes(), 102591);
223
224 engine.eval(r#"100 + 2"#).unwrap();
226 assert_eq!(engine.get_number(-1), 102.);
227 assert_eq!(tracker.lock().unwrap().total_bytes(), 102591);
228
229 engine.gc();
230 assert_eq!(tracker.lock().unwrap().total_bytes(), 101686);
231
232 drop(engine);
233
234 assert_eq!(tracker.lock().unwrap().total_bytes(), 0);
235 }
236
237
238 pub mod alloc_tracker {
239 use std::collections::HashMap;
240
241 #[derive(Debug)]
242 pub struct AllocTracker {
243 allocs: HashMap<usize, usize>,
244 alloc_count: u64,
245 }
246
247 impl AllocTracker {
248 pub fn new() -> Self {
249 Self {
250 allocs: HashMap::new(),
251 alloc_count: 0,
252 }
253 }
254
255 pub fn alloc(&mut self, ptr: *mut u8, size: usize) {
256 self.alloc_count += 1;
257 self.allocs.insert(ptr as usize, size);
258 }
259
260 pub fn realloc(&mut self, old_ptr: *mut u8, new_ptr: *mut u8, size: usize) {
261 self.alloc_count += 1;
262 self.allocs.remove(&(old_ptr as usize));
263 self.allocs.insert(new_ptr as usize, size);
264 }
265
266 pub fn free(&mut self, ptr: *mut u8) {
267 self.allocs.remove(&(ptr as usize));
268 }
269
270 pub fn total_bytes(&self) -> usize {
271 self.allocs.values().sum()
272 }
273
274 pub fn alloc_count(&self) -> u64 {
275 self.alloc_count
276 }
277 }
278 }
279}