1#![deny(unsafe_code)]
4
5mod timers;
6
7use std::collections::BTreeMap;
8use std::time::{Instant, SystemTime, UNIX_EPOCH};
9
10#[cfg(feature = "script-compiler")]
11use std::sync::Arc;
12
13#[cfg(feature = "script-compiler")]
14use bamts_bytecode::{Program, Verified};
15use bamts_runtime::Host;
16
17#[cfg(feature = "script-compiler")]
19#[derive(Default)]
20pub struct ScriptCompiler;
21
22#[cfg(feature = "script-compiler")]
23impl bamts_runtime::CompileProvider for ScriptCompiler {
24 fn compile_script(
25 &mut self,
26 source: bamts_runtime::ScriptSource<'_>,
27 ) -> std::result::Result<Arc<Program<Verified>>, bamts_runtime::ScriptCompileError> {
28 bamts_compiler::compile_classic_script(
29 source.source,
30 &String::from_utf16_lossy(source.name),
31 )
32 .map(Arc::new)
33 .map_err(map_script_compile_error)
34 }
35}
36
37#[cfg(feature = "script-compiler")]
38fn map_script_compile_error(
39 error: bamts_compiler::ScriptCompileError,
40) -> bamts_runtime::ScriptCompileError {
41 match error {
42 bamts_compiler::ScriptCompileError::IllFormedSource { unit_offset } => {
43 bamts_runtime::ScriptCompileError::IllFormedSource { unit_offset }
44 }
45 bamts_compiler::ScriptCompileError::Syntax {
46 message,
47 line,
48 column,
49 } => bamts_runtime::ScriptCompileError::Syntax {
50 message,
51 line,
52 column,
53 },
54 bamts_compiler::ScriptCompileError::Unsupported {
55 message,
56 line,
57 column,
58 } => bamts_runtime::ScriptCompileError::Unsupported {
59 message,
60 line,
61 column,
62 },
63 bamts_compiler::ScriptCompileError::Capacity { message } => {
64 bamts_runtime::ScriptCompileError::Capacity { message }
65 }
66 }
67}
68
69pub struct NodeHost {
74 stdout: Vec<u8>,
75 stderr: Vec<u8>,
76 exit_code: i32,
77 argv: Vec<String>,
78 env: BTreeMap<String, String>,
79 started: Instant,
80 random_state: u64,
81 compiler: Option<Box<dyn bamts_runtime::CompileProvider>>,
82 timers: timers::NodeTimers,
83}
84
85impl Default for NodeHost {
86 fn default() -> Self {
87 Self::new()
88 }
89}
90
91impl NodeHost {
92 #[must_use]
93 pub fn new() -> Self {
94 Self {
95 stdout: Vec::new(),
96 stderr: Vec::new(),
97 exit_code: 0,
98 argv: Vec::new(),
99 env: BTreeMap::new(),
100 started: Instant::now(),
101 random_state: 0x6a09_e667_f3bc_c909,
102 compiler: None,
103 timers: timers::NodeTimers::new(),
104 }
105 }
106
107 #[must_use]
108 pub fn stdout(&self) -> &[u8] {
109 &self.stdout
110 }
111
112 #[must_use]
113 pub fn stderr(&self) -> &[u8] {
114 &self.stderr
115 }
116
117 #[must_use]
118 pub const fn exit_code(&self) -> i32 {
119 self.exit_code
120 }
121
122 pub fn set_argv(&mut self, argv: impl IntoIterator<Item = String>) {
123 self.argv = argv.into_iter().collect();
124 }
125
126 #[must_use]
127 pub fn argv(&self) -> &[String] {
128 &self.argv
129 }
130
131 #[must_use]
132 pub fn env(&self, name: &str) -> Option<&str> {
133 self.env.get(name).map(String::as_str)
134 }
135
136 pub fn set_env(&mut self, name: impl Into<String>, value: impl Into<String>) {
137 self.env.insert(name.into(), value.into());
138 }
139
140 pub fn delete_env(&mut self, name: &str) -> bool {
141 self.env.remove(name).is_some()
142 }
143
144 pub fn set_script_compiler(&mut self, compiler: Box<dyn bamts_runtime::CompileProvider>) {
145 self.compiler = Some(compiler);
146 }
147}
148
149impl Host for NodeHost {
150 fn write_stdout(&mut self, bytes: &[u8]) {
151 self.stdout.extend_from_slice(bytes);
152 }
153
154 fn write_stderr(&mut self, bytes: &[u8]) {
155 self.stderr.extend_from_slice(bytes);
156 }
157
158 fn exit_code(&self) -> i32 {
159 self.exit_code
160 }
161
162 fn set_exit_code(&mut self, exit_code: i32) {
163 self.exit_code = exit_code;
164 }
165
166 fn argv(&self) -> &[String] {
167 &self.argv
168 }
169
170 fn env(&self, name: &str) -> Option<&str> {
171 self.env.get(name).map(String::as_str)
172 }
173
174 fn set_env(&mut self, name: &str, value: &str) {
175 self.env.insert(name.to_owned(), value.to_owned());
176 }
177
178 fn delete_env(&mut self, name: &str) -> bool {
179 self.env.remove(name).is_some()
180 }
181
182 fn now_ms(&mut self) -> u64 {
183 let elapsed = SystemTime::now()
184 .duration_since(UNIX_EPOCH)
185 .unwrap_or_default();
186 u64::try_from(elapsed.as_millis()).unwrap_or(u64::MAX)
187 }
188
189 fn monotonic_ns(&mut self) -> u64 {
190 u64::try_from(self.started.elapsed().as_nanos()).unwrap_or(u64::MAX)
191 }
192
193 fn random(&mut self) -> f64 {
194 let mut state = self.random_state;
196 state ^= state >> 12;
197 state ^= state << 25;
198 state ^= state >> 27;
199 self.random_state = state;
200 let bits = state.wrapping_mul(0x2545_f491_4f6c_dd1d) >> 11;
201 (bits as f64) * (1.0 / ((1_u64 << 53) as f64))
202 }
203
204 fn script_compiler(&mut self) -> Option<&mut (dyn bamts_runtime::CompileProvider + 'static)> {
205 self.compiler.as_deref_mut()
206 }
207
208 fn timers(&mut self) -> Option<&mut (dyn bamts_runtime::TimerProvider + 'static)> {
209 Some(&mut self.timers)
210 }
211
212 fn hash(&mut self, algorithm: &str, data: &[u8]) -> Option<Vec<u8>> {
213 match algorithm.to_ascii_lowercase().replace('-', "").as_str() {
214 "sha256" => Some(sha256(data).to_vec()),
215 "sha512" => Some(sha512(data).to_vec()),
216 _ => None,
217 }
218 }
219}
220
221fn sha256(data: &[u8]) -> [u8; 32] {
222 const INITIAL: [u32; 8] = [
223 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab,
224 0x5be0cd19,
225 ];
226 const K: [u32; 64] = [
227 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4,
228 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe,
229 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f,
230 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
231 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc,
232 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
233 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116,
234 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
235 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7,
236 0xc67178f2,
237 ];
238 let bit_len = (data.len() as u64).wrapping_mul(8);
239 let mut padded = data.to_vec();
240 padded.push(0x80);
241 while padded.len() % 64 != 56 {
242 padded.push(0);
243 }
244 padded.extend_from_slice(&bit_len.to_be_bytes());
245 let mut state = INITIAL;
246 for block in padded.chunks_exact(64) {
247 let mut words = [0_u32; 64];
248 for (word, bytes) in words[..16].iter_mut().zip(block.chunks_exact(4)) {
249 *word = u32::from_be_bytes(bytes.try_into().expect("four bytes"));
250 }
251 for index in 16..64 {
252 let s0 = words[index - 15].rotate_right(7)
253 ^ words[index - 15].rotate_right(18)
254 ^ (words[index - 15] >> 3);
255 let s1 = words[index - 2].rotate_right(17)
256 ^ words[index - 2].rotate_right(19)
257 ^ (words[index - 2] >> 10);
258 words[index] = words[index - 16]
259 .wrapping_add(s0)
260 .wrapping_add(words[index - 7])
261 .wrapping_add(s1);
262 }
263 let [mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h] = state;
264 for index in 0..64 {
265 let s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25);
266 let choice = (e & f) ^ (!e & g);
267 let t1 = h
268 .wrapping_add(s1)
269 .wrapping_add(choice)
270 .wrapping_add(K[index])
271 .wrapping_add(words[index]);
272 let s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22);
273 let majority = (a & b) ^ (a & c) ^ (b & c);
274 let t2 = s0.wrapping_add(majority);
275 h = g;
276 g = f;
277 f = e;
278 e = d.wrapping_add(t1);
279 d = c;
280 c = b;
281 b = a;
282 a = t1.wrapping_add(t2);
283 }
284 for (slot, value) in state.iter_mut().zip([a, b, c, d, e, f, g, h]) {
285 *slot = slot.wrapping_add(value);
286 }
287 }
288 let mut digest = [0_u8; 32];
289 for (chunk, value) in digest.chunks_exact_mut(4).zip(state) {
290 chunk.copy_from_slice(&value.to_be_bytes());
291 }
292 digest
293}
294
295fn sha512(data: &[u8]) -> [u8; 64] {
296 const INITIAL: [u64; 8] = [
297 0x6a09e667f3bcc908,
298 0xbb67ae8584caa73b,
299 0x3c6ef372fe94f82b,
300 0xa54ff53a5f1d36f1,
301 0x510e527fade682d1,
302 0x9b05688c2b3e6c1f,
303 0x1f83d9abfb41bd6b,
304 0x5be0cd19137e2179,
305 ];
306 const K: [u64; 80] = [
307 0x428a2f98d728ae22,
308 0x7137449123ef65cd,
309 0xb5c0fbcfec4d3b2f,
310 0xe9b5dba58189dbbc,
311 0x3956c25bf348b538,
312 0x59f111f1b605d019,
313 0x923f82a4af194f9b,
314 0xab1c5ed5da6d8118,
315 0xd807aa98a3030242,
316 0x12835b0145706fbe,
317 0x243185be4ee4b28c,
318 0x550c7dc3d5ffb4e2,
319 0x72be5d74f27b896f,
320 0x80deb1fe3b1696b1,
321 0x9bdc06a725c71235,
322 0xc19bf174cf692694,
323 0xe49b69c19ef14ad2,
324 0xefbe4786384f25e3,
325 0x0fc19dc68b8cd5b5,
326 0x240ca1cc77ac9c65,
327 0x2de92c6f592b0275,
328 0x4a7484aa6ea6e483,
329 0x5cb0a9dcbd41fbd4,
330 0x76f988da831153b5,
331 0x983e5152ee66dfab,
332 0xa831c66d2db43210,
333 0xb00327c898fb213f,
334 0xbf597fc7beef0ee4,
335 0xc6e00bf33da88fc2,
336 0xd5a79147930aa725,
337 0x06ca6351e003826f,
338 0x142929670a0e6e70,
339 0x27b70a8546d22ffc,
340 0x2e1b21385c26c926,
341 0x4d2c6dfc5ac42aed,
342 0x53380d139d95b3df,
343 0x650a73548baf63de,
344 0x766a0abb3c77b2a8,
345 0x81c2c92e47edaee6,
346 0x92722c851482353b,
347 0xa2bfe8a14cf10364,
348 0xa81a664bbc423001,
349 0xc24b8b70d0f89791,
350 0xc76c51a30654be30,
351 0xd192e819d6ef5218,
352 0xd69906245565a910,
353 0xf40e35855771202a,
354 0x106aa07032bbd1b8,
355 0x19a4c116b8d2d0c8,
356 0x1e376c085141ab53,
357 0x2748774cdf8eeb99,
358 0x34b0bcb5e19b48a8,
359 0x391c0cb3c5c95a63,
360 0x4ed8aa4ae3418acb,
361 0x5b9cca4f7763e373,
362 0x682e6ff3d6b2b8a3,
363 0x748f82ee5defb2fc,
364 0x78a5636f43172f60,
365 0x84c87814a1f0ab72,
366 0x8cc702081a6439ec,
367 0x90befffa23631e28,
368 0xa4506cebde82bde9,
369 0xbef9a3f7b2c67915,
370 0xc67178f2e372532b,
371 0xca273eceea26619c,
372 0xd186b8c721c0c207,
373 0xeada7dd6cde0eb1e,
374 0xf57d4f7fee6ed178,
375 0x06f067aa72176fba,
376 0x0a637dc5a2c898a6,
377 0x113f9804bef90dae,
378 0x1b710b35131c471b,
379 0x28db77f523047d84,
380 0x32caab7b40c72493,
381 0x3c9ebe0a15c9bebc,
382 0x431d67c49c100d4c,
383 0x4cc5d4becb3e42b6,
384 0x597f299cfc657e2a,
385 0x5fcb6fab3ad6faec,
386 0x6c44198c4a475817,
387 ];
388 let bit_len = (data.len() as u128).wrapping_mul(8);
389 let mut padded = data.to_vec();
390 padded.push(0x80);
391 while padded.len() % 128 != 112 {
392 padded.push(0);
393 }
394 padded.extend_from_slice(&bit_len.to_be_bytes());
395 let mut state = INITIAL;
396 for block in padded.chunks_exact(128) {
397 let mut words = [0_u64; 80];
398 for (word, bytes) in words[..16].iter_mut().zip(block.chunks_exact(8)) {
399 *word = u64::from_be_bytes(bytes.try_into().expect("eight bytes"));
400 }
401 for index in 16..80 {
402 let s0 = words[index - 15].rotate_right(1)
403 ^ words[index - 15].rotate_right(8)
404 ^ (words[index - 15] >> 7);
405 let s1 = words[index - 2].rotate_right(19)
406 ^ words[index - 2].rotate_right(61)
407 ^ (words[index - 2] >> 6);
408 words[index] = words[index - 16]
409 .wrapping_add(s0)
410 .wrapping_add(words[index - 7])
411 .wrapping_add(s1);
412 }
413 let [mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h] = state;
414 for index in 0..80 {
415 let s1 = e.rotate_right(14) ^ e.rotate_right(18) ^ e.rotate_right(41);
416 let choice = (e & f) ^ (!e & g);
417 let t1 = h
418 .wrapping_add(s1)
419 .wrapping_add(choice)
420 .wrapping_add(K[index])
421 .wrapping_add(words[index]);
422 let s0 = a.rotate_right(28) ^ a.rotate_right(34) ^ a.rotate_right(39);
423 let majority = (a & b) ^ (a & c) ^ (b & c);
424 let t2 = s0.wrapping_add(majority);
425 h = g;
426 g = f;
427 f = e;
428 e = d.wrapping_add(t1);
429 d = c;
430 c = b;
431 b = a;
432 a = t1.wrapping_add(t2);
433 }
434 for (slot, value) in state.iter_mut().zip([a, b, c, d, e, f, g, h]) {
435 *slot = slot.wrapping_add(value);
436 }
437 }
438 let mut digest = [0_u8; 64];
439 for (chunk, value) in digest.chunks_exact_mut(8).zip(state) {
440 chunk.copy_from_slice(&value.to_be_bytes());
441 }
442 digest
443}
444
445#[cfg(feature = "aot-main")]
446fn decode_aot_program(
447 bytes: &[u8],
448) -> Result<bamts_bytecode::Program<bamts_bytecode::Verified>, bamts_bytecode::ProgramLoadError> {
449 bamts_bytecode::decode_verified_program(bytes, &bamts_bytecode::ProgramDecodeLimits::default())
450}
451
452#[cfg(all(feature = "aot-main", not(test)))]
453fn run_aot_main() -> i32 {
454 use bamts_native::linked_program;
455 use bamts_runtime::{Limits, run_linked_program};
456
457 let mut host = NodeHost::new();
458 #[cfg(feature = "script-compiler")]
459 host.set_script_compiler(Box::new(ScriptCompiler));
460 let linked = match linked_program() {
461 Ok(linked) => linked,
462 Err(_) => return finish_aot_process(&host, AotCompletion::Failure(AotMainFailure::Link)),
463 };
464 let program = match decode_aot_program(linked.bytecode()) {
465 Ok(program) => program,
466 Err(_) => return finish_aot_process(&host, AotCompletion::Failure(AotMainFailure::Decode)),
467 };
468 if let Err(error) =
469 initialize_aot_process_context(&mut host, std::env::args_os(), std::env::vars_os())
470 {
471 return finish_aot_process(
472 &host,
473 AotCompletion::Failure(AotMainFailure::Context(error)),
474 );
475 }
476 let outcome = match run_linked_program(&program, &linked, &mut host, &Limits::default()) {
477 Ok(outcome) => outcome,
478 Err(_) => {
479 return finish_aot_process(&host, AotCompletion::Failure(AotMainFailure::Runtime));
480 }
481 };
482 finish_aot_process(&host, AotCompletion::Success(&outcome))
483}
484
485#[cfg(any(feature = "aot-main", test))]
486#[derive(Debug, Clone, Copy, PartialEq, Eq)]
487enum AotMainFailure {
488 Link,
489 Decode,
490 Context(AotProcessContextError),
491 Runtime,
492}
493
494#[cfg(any(feature = "aot-main", test))]
495impl std::fmt::Display for AotMainFailure {
496 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
497 match self {
498 Self::Link => formatter.write_str("aot link"),
499 Self::Decode => formatter.write_str("aot decode"),
500 Self::Context(AotProcessContextError::Argument) => {
501 formatter.write_str("aot context argument")
502 }
503 Self::Context(AotProcessContextError::EnvironmentName) => {
504 formatter.write_str("aot context environment name")
505 }
506 Self::Context(AotProcessContextError::EnvironmentValue) => {
507 formatter.write_str("aot context environment value")
508 }
509 Self::Runtime => formatter.write_str("aot runtime"),
510 }
511 }
512}
513
514#[cfg(any(feature = "aot-main", test))]
515enum AotCompletion<'a> {
516 Success(&'a bamts_runtime::ExecutionOutcome),
517 Failure(AotMainFailure),
518}
519
520#[cfg(any(feature = "aot-main", test))]
522fn write_aot_completion(
523 host: &NodeHost,
524 completion: AotCompletion<'_>,
525 stdout: &mut impl std::io::Write,
526 stderr: &mut impl std::io::Write,
527) -> std::io::Result<i32> {
528 let (exit_code, failure) = match completion {
529 AotCompletion::Success(outcome) => {
530 stdout.write_all(host.stdout())?;
531 stdout.write_all(&outcome.stdout)?;
532 (
533 if host.exit_code() == 0 {
534 outcome.exit_code
535 } else {
536 host.exit_code()
537 },
538 None,
539 )
540 }
541 AotCompletion::Failure(error) => (1, Some(error)),
542 };
543 stderr.write_all(host.stderr())?;
544 if let Some(error) = failure {
545 writeln!(stderr, "bamts: {error}")?;
546 }
547 stderr.flush()?;
548 Ok(exit_code)
549}
550
551#[cfg(all(feature = "aot-main", not(test)))]
552fn finish_aot_process(host: &NodeHost, completion: AotCompletion<'_>) -> i32 {
553 let mut stdout = std::io::stdout().lock();
554 let mut stderr = std::io::stderr().lock();
555 write_aot_completion(host, completion, &mut stdout, &mut stderr).unwrap_or(1)
556}
557
558#[cfg(all(feature = "aot-main", not(test)))]
560#[allow(unsafe_code)]
561#[unsafe(no_mangle)]
562pub extern "C" fn main() -> i32 {
563 run_aot_main()
564}
565
566#[cfg(any(feature = "aot-main", test))]
567#[derive(Debug, Clone, Copy, PartialEq, Eq)]
568enum AotProcessContextError {
569 Argument,
570 EnvironmentName,
571 EnvironmentValue,
572}
573
574#[cfg(any(feature = "aot-main", test))]
580fn initialize_aot_process_context(
581 host: &mut NodeHost,
582 args: impl IntoIterator<Item = std::ffi::OsString>,
583 environment: impl IntoIterator<Item = (std::ffi::OsString, std::ffi::OsString)>,
584) -> Result<(), AotProcessContextError> {
585 let mut argv = vec!["bamts".to_owned()];
586 for argument in args {
587 argv.push(
588 argument
589 .into_string()
590 .map_err(|_| AotProcessContextError::Argument)?,
591 );
592 }
593
594 let mut env = BTreeMap::new();
595 for (name, value) in environment {
596 let name = name
597 .into_string()
598 .map_err(|_| AotProcessContextError::EnvironmentName)?;
599 let value = value
600 .into_string()
601 .map_err(|_| AotProcessContextError::EnvironmentValue)?;
602 env.insert(name, value);
603 }
604
605 host.argv = argv;
606 host.env = env;
607 Ok(())
608}
609
610#[cfg(test)]
611mod tests {
612 use super::*;
613 #[cfg(feature = "aot-main")]
614 #[test]
615 fn linked_descriptor_decodes_whole_program_and_tuple_entry() {
616 use bamts_bytecode::{
617 Constant, ConstantId, EcmaString, Function, FunctionFlags, FunctionId, Instruction,
618 Module, ModuleId, Program, ProgramModule,
619 };
620
621 let module = |name: &str| ProgramModule {
622 name: ConstantId::new(0),
623 code: Module::new(
624 vec![Constant::String(EcmaString::from_utf8(name))],
625 vec![Function::new(
626 None,
627 0,
628 0,
629 0,
630 FunctionFlags::default(),
631 vec![Instruction::Halt],
632 Vec::new(),
633 )],
634 FunctionId::new(0),
635 )
636 .verify()
637 .expect("descriptor test module verifies"),
638 edges: Vec::new(),
639 bindings: Vec::new(),
640 exports: Vec::new(),
641 };
642 let program = Program::link(
643 vec![module("dependency"), module("entry")],
644 ModuleId::new(1),
645 )
646 .expect("descriptor test program links");
647
648 let decoded = decode_aot_program(&program.encode()).expect("descriptor program decodes");
649
650 assert_eq!(decoded.entry(), ModuleId::new(1));
651 assert_eq!(decoded.modules().len(), 2);
652 assert!(
653 decoded
654 .modules()
655 .iter()
656 .all(|module| module.code().entry() == FunctionId::new(0))
657 );
658 }
659
660 fn hex(bytes: &[u8]) -> String {
661 const DIGITS: &[u8; 16] = b"0123456789abcdef";
662 let mut text = String::with_capacity(bytes.len() * 2);
663 for byte in bytes {
664 text.push(char::from(DIGITS[usize::from(byte >> 4)]));
665 text.push(char::from(DIGITS[usize::from(byte & 0x0f)]));
666 }
667 text
668 }
669
670 #[test]
671 fn capabilities_capture_bytes_and_mutate_process_state() {
672 let mut host = NodeHost::new();
673 Host::write_stdout(&mut host, b"out");
674 Host::write_stderr(&mut host, b"err");
675 Host::set_exit_code(&mut host, 23);
676 host.set_argv(["bamts".to_owned(), "file.ts".to_owned()]);
677 Host::set_env(&mut host, "NODE_ENV", "test");
678 assert_eq!(host.stdout(), b"out");
679 assert_eq!(host.stderr(), b"err");
680 assert_eq!(host.exit_code(), 23);
681 assert_eq!(host.argv(), ["bamts", "file.ts"]);
682 assert_eq!(host.env("NODE_ENV"), Some("test"));
683 assert!(Host::delete_env(&mut host, "NODE_ENV"));
684 assert_eq!(host.env("NODE_ENV"), None);
685 }
686
687 #[test]
688 fn sha2_matches_standard_vectors() {
689 let mut host = NodeHost::new();
690 assert_eq!(
691 hex(&Host::hash(&mut host, "sha-256", b"abc").unwrap()),
692 "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
693 );
694 assert_eq!(
695 hex(&Host::hash(&mut host, "SHA512", b"abc").unwrap()),
696 "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"
697 );
698 assert_eq!(Host::hash(&mut host, "md5", b"abc"), None);
699 }
700
701 #[test]
702 fn aot_process_context_uses_jit_argv_normalization() {
703 let mut host = NodeHost::new();
704 initialize_aot_process_context(
705 &mut host,
706 [
707 std::ffi::OsString::from("/tmp/program"),
708 std::ffi::OsString::from("--flag"),
709 ],
710 [
711 (
712 std::ffi::OsString::from("ZED"),
713 std::ffi::OsString::from("last"),
714 ),
715 (
716 std::ffi::OsString::from("ALPHA"),
717 std::ffi::OsString::from("first"),
718 ),
719 ],
720 )
721 .unwrap();
722
723 assert_eq!(host.argv(), ["bamts", "/tmp/program", "--flag"]);
724 assert_eq!(host.env("ALPHA"), Some("first"));
725 assert_eq!(host.env("ZED"), Some("last"));
726 }
727
728 #[test]
729 fn aot_runtime_failure_emits_host_stderr_and_stable_error() {
730 let mut host = NodeHost::new();
731 Host::write_stdout(&mut host, b"before failure");
732 Host::write_stderr(&mut host, b"host diagnostic\n");
733 let mut stdout = Vec::new();
734 let mut stderr = Vec::new();
735
736 let exit_code = write_aot_completion(
737 &host,
738 AotCompletion::Failure(AotMainFailure::Runtime),
739 &mut stdout,
740 &mut stderr,
741 )
742 .expect("completion writes");
743
744 assert_eq!(exit_code, 1);
745 assert!(stdout.is_empty());
746 assert_eq!(stderr, b"host diagnostic\nbamts: aot runtime\n");
747 }
748
749 #[test]
750 fn aot_failure_labels_are_stable() {
751 assert_eq!(AotMainFailure::Link.to_string(), "aot link");
752 assert_eq!(AotMainFailure::Decode.to_string(), "aot decode");
753 assert_eq!(
754 AotMainFailure::Context(AotProcessContextError::Argument).to_string(),
755 "aot context argument"
756 );
757 }
758
759 #[test]
760 fn aot_success_preserves_host_and_runtime_output_and_exit_precedence() {
761 let mut host = NodeHost::new();
762 Host::write_stdout(&mut host, b"host stdout");
763 Host::write_stderr(&mut host, b"host stderr");
764 let outcome = bamts_runtime::ExecutionOutcome {
765 stdout: b"runtime stdout".to_vec(),
766 exit_code: 7,
767 };
768 let mut stdout = Vec::new();
769 let mut stderr = Vec::new();
770
771 let exit_code = write_aot_completion(
772 &host,
773 AotCompletion::Success(&outcome),
774 &mut stdout,
775 &mut stderr,
776 )
777 .expect("completion writes");
778
779 assert_eq!(exit_code, 7);
780 assert_eq!(stdout, b"host stdoutruntime stdout");
781 assert_eq!(stderr, b"host stderr");
782
783 Host::set_exit_code(&mut host, 11);
784 let mut stdout = Vec::new();
785 let mut stderr = Vec::new();
786 let exit_code = write_aot_completion(
787 &host,
788 AotCompletion::Success(&outcome),
789 &mut stdout,
790 &mut stderr,
791 )
792 .expect("completion writes");
793
794 assert_eq!(exit_code, 11);
795 assert_eq!(stdout, b"host stdoutruntime stdout");
796 assert_eq!(stderr, b"host stderr");
797 }
798
799 #[cfg(unix)]
800 #[test]
801 fn aot_process_context_rejects_non_unicode_without_mutating_host() {
802 use std::os::unix::ffi::OsStringExt;
803
804 let mut host = NodeHost::new();
805 let error = initialize_aot_process_context(
806 &mut host,
807 [std::ffi::OsString::from_vec(vec![0xff])],
808 [(
809 std::ffi::OsString::from("SAFE"),
810 std::ffi::OsString::from("value"),
811 )],
812 )
813 .unwrap_err();
814
815 assert_eq!(error, AotProcessContextError::Argument);
816 assert!(host.argv().is_empty());
817 assert_eq!(host.env("SAFE"), None);
818 }
819
820 #[test]
821 fn clocks_and_random_are_capabilities() {
822 let mut host = NodeHost::new();
823 assert!(Host::now_ms(&mut host) > 0);
824 let first = Host::monotonic_ns(&mut host);
825 let second = Host::monotonic_ns(&mut host);
826 assert!(second >= first);
827 let random = Host::random(&mut host);
828 assert!((0.0..1.0).contains(&random));
829 }
830
831 #[cfg(unix)]
832 #[test]
833 fn aot_process_context_rejects_non_unicode_environment_without_mutating_host() {
834 use std::os::unix::ffi::OsStringExt;
835
836 let mut host = NodeHost::new();
837 let error = initialize_aot_process_context(
838 &mut host,
839 [std::ffi::OsString::from("/tmp/program")],
840 [(
841 std::ffi::OsString::from("SAFE"),
842 std::ffi::OsString::from_vec(vec![0xff]),
843 )],
844 )
845 .unwrap_err();
846
847 assert_eq!(error, AotProcessContextError::EnvironmentValue);
848 assert!(host.argv().is_empty());
849 assert_eq!(host.env("SAFE"), None);
850 }
851}
852
853#[cfg(test)]
854mod timer_tests {
855 use super::*;
856 use std::time::Duration;
857
858 #[test]
859 fn real_timers_expire_in_deadline_order_through_one_delay_queue() {
860 let mut host = NodeHost::new();
861 let timers = Host::timers(&mut host).expect("timer capability is always present");
862
863 let late = timers.schedule(1, 12).expect("schedule id 1");
866 let early = timers.schedule(2, 4).expect("schedule id 2");
867 assert!(early <= late, "smaller delay yields an earlier deadline");
868 assert!(timers.has_pending());
869
870 let first = timers.wait_expired().expect("wait").expect("a wakeup");
871 let second = timers.wait_expired().expect("wait").expect("a wakeup");
872 assert_eq!(first.id, 2, "the earlier deadline fires first");
873 assert_eq!(second.id, 1);
874 assert_eq!(first.deadline_ms, early);
875 assert_eq!(second.deadline_ms, late);
876
877 assert!(!timers.has_pending());
878 assert!(
879 timers.wait_expired().expect("wait").is_none(),
880 "an empty pending set never blocks"
881 );
882 }
883
884 #[test]
885 fn cancellation_removes_exactly_the_target_id() {
886 let mut host = NodeHost::new();
887 let timers = Host::timers(&mut host).unwrap();
888
889 timers.schedule(10, 20).expect("schedule id 10");
890 timers.schedule(11, 4).expect("schedule id 11");
891
892 assert!(timers.cancel(10).expect("cancel"), "an armed timer cancels");
893 assert!(
894 !timers.cancel(10).expect("cancel"),
895 "a second cancel of the same id is false"
896 );
897 assert!(
898 !timers.cancel(999).expect("cancel"),
899 "an unknown id cancels to false"
900 );
901
902 let wakeup = timers.wait_expired().expect("wait").expect("a wakeup");
903 assert_eq!(wakeup.id, 11, "only the surviving timer fires");
904 assert!(!timers.has_pending());
905 assert!(timers.wait_expired().expect("wait").is_none());
906 }
907
908 #[test]
909 fn expiry_that_races_a_cancel_is_dropped_as_stale() {
910 let mut host = NodeHost::new();
911 let timers = Host::timers(&mut host).unwrap();
912
913 timers.schedule(7, 1).expect("schedule id 7");
914 std::thread::sleep(Duration::from_millis(40));
917 assert!(
918 timers.cancel(7).expect("cancel"),
919 "still pending from the caller's view until polled"
920 );
921
922 let mut output = Vec::new();
923 timers.poll_expired(&mut output).expect("poll");
924 assert!(output.is_empty(), "a cancelled id is never delivered");
925 assert!(!timers.has_pending());
926 assert!(timers.wait_expired().expect("wait").is_none());
927 }
928
929 #[test]
930 fn worker_is_lazy_and_shuts_down_cleanly_on_drop() {
931 let mut host = NodeHost::new();
932 assert!(
933 !host.timers.worker_active(),
934 "no worker thread before the first schedule"
935 );
936
937 let _ = Host::timers(&mut host);
939 assert!(
940 !host.timers.worker_active(),
941 "returning the capability is not a schedule"
942 );
943
944 Host::timers(&mut host)
945 .unwrap()
946 .schedule(1, 1)
947 .expect("schedule id 1");
948 assert!(
949 host.timers.worker_active(),
950 "the worker starts lazily on first schedule"
951 );
952
953 drop(host);
957 }
958
959 #[test]
960 fn constructs_and_runs_inside_an_ambient_tokio_runtime_without_panic() {
961 let runtime = tokio::runtime::Builder::new_current_thread()
962 .enable_time()
963 .build()
964 .expect("ambient runtime builds");
965 let _guard = runtime.enter();
966
967 let mut host = NodeHost::new();
971 let timers = Host::timers(&mut host).unwrap();
972 let deadline = timers
973 .schedule(1, 2)
974 .expect("schedule under ambient runtime");
975 assert!(deadline >= 2);
976 let wakeup = timers.wait_expired().expect("wait").expect("a wakeup");
977 assert_eq!(wakeup.id, 1);
978 assert!(!timers.has_pending());
979 }
980}