mustardscript 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/Cargo.lock +1579 -0
  2. package/Cargo.toml +40 -0
  3. package/LICENSE +201 -0
  4. package/README.md +828 -0
  5. package/SECURITY.md +34 -0
  6. package/crates/mustard/Cargo.toml +31 -0
  7. package/crates/mustard/src/cancellation.rs +28 -0
  8. package/crates/mustard/src/diagnostic.rs +145 -0
  9. package/crates/mustard/src/ir.rs +435 -0
  10. package/crates/mustard/src/lib.rs +21 -0
  11. package/crates/mustard/src/limits.rs +22 -0
  12. package/crates/mustard/src/parser/expressions.rs +723 -0
  13. package/crates/mustard/src/parser/mod.rs +115 -0
  14. package/crates/mustard/src/parser/operators.rs +105 -0
  15. package/crates/mustard/src/parser/patterns.rs +123 -0
  16. package/crates/mustard/src/parser/scope.rs +107 -0
  17. package/crates/mustard/src/parser/statements.rs +298 -0
  18. package/crates/mustard/src/parser/tests/acceptance.rs +339 -0
  19. package/crates/mustard/src/parser/tests/mod.rs +2 -0
  20. package/crates/mustard/src/parser/tests/rejections.rs +107 -0
  21. package/crates/mustard/src/runtime/accounting.rs +613 -0
  22. package/crates/mustard/src/runtime/api.rs +192 -0
  23. package/crates/mustard/src/runtime/async_runtime/mod.rs +5 -0
  24. package/crates/mustard/src/runtime/async_runtime/promises.rs +246 -0
  25. package/crates/mustard/src/runtime/async_runtime/reactions.rs +400 -0
  26. package/crates/mustard/src/runtime/async_runtime/scheduler.rs +224 -0
  27. package/crates/mustard/src/runtime/builtins/arrays.rs +1205 -0
  28. package/crates/mustard/src/runtime/builtins/collections.rs +573 -0
  29. package/crates/mustard/src/runtime/builtins/install.rs +501 -0
  30. package/crates/mustard/src/runtime/builtins/intl.rs +553 -0
  31. package/crates/mustard/src/runtime/builtins/mod.rs +25 -0
  32. package/crates/mustard/src/runtime/builtins/objects.rs +405 -0
  33. package/crates/mustard/src/runtime/builtins/primitives.rs +859 -0
  34. package/crates/mustard/src/runtime/builtins/promises.rs +335 -0
  35. package/crates/mustard/src/runtime/builtins/regexp.rs +356 -0
  36. package/crates/mustard/src/runtime/builtins/strings.rs +803 -0
  37. package/crates/mustard/src/runtime/builtins/support.rs +561 -0
  38. package/crates/mustard/src/runtime/bytecode.rs +123 -0
  39. package/crates/mustard/src/runtime/compiler/assignments.rs +690 -0
  40. package/crates/mustard/src/runtime/compiler/bindings.rs +92 -0
  41. package/crates/mustard/src/runtime/compiler/context.rs +46 -0
  42. package/crates/mustard/src/runtime/compiler/control.rs +342 -0
  43. package/crates/mustard/src/runtime/compiler/expressions.rs +372 -0
  44. package/crates/mustard/src/runtime/compiler/mod.rs +173 -0
  45. package/crates/mustard/src/runtime/compiler/statements.rs +459 -0
  46. package/crates/mustard/src/runtime/conversions/boundary.rs +293 -0
  47. package/crates/mustard/src/runtime/conversions/coercions.rs +217 -0
  48. package/crates/mustard/src/runtime/conversions/errors.rs +118 -0
  49. package/crates/mustard/src/runtime/conversions/mod.rs +14 -0
  50. package/crates/mustard/src/runtime/conversions/operators.rs +334 -0
  51. package/crates/mustard/src/runtime/env.rs +355 -0
  52. package/crates/mustard/src/runtime/exceptions.rs +377 -0
  53. package/crates/mustard/src/runtime/gc.rs +595 -0
  54. package/crates/mustard/src/runtime/mod.rs +318 -0
  55. package/crates/mustard/src/runtime/properties.rs +1762 -0
  56. package/crates/mustard/src/runtime/serialization.rs +127 -0
  57. package/crates/mustard/src/runtime/shared.rs +108 -0
  58. package/crates/mustard/src/runtime/snapshot_validation_tests.rs +93 -0
  59. package/crates/mustard/src/runtime/state.rs +652 -0
  60. package/crates/mustard/src/runtime/tests/async_host.rs +104 -0
  61. package/crates/mustard/src/runtime/tests/collections.rs +50 -0
  62. package/crates/mustard/src/runtime/tests/diagnostics.rs +36 -0
  63. package/crates/mustard/src/runtime/tests/exceptions.rs +122 -0
  64. package/crates/mustard/src/runtime/tests/execution.rs +553 -0
  65. package/crates/mustard/src/runtime/tests/gc.rs +533 -0
  66. package/crates/mustard/src/runtime/tests/mod.rs +56 -0
  67. package/crates/mustard/src/runtime/tests/serialization.rs +170 -0
  68. package/crates/mustard/src/runtime/validation/bytecode.rs +484 -0
  69. package/crates/mustard/src/runtime/validation/mod.rs +14 -0
  70. package/crates/mustard/src/runtime/validation/policy.rs +94 -0
  71. package/crates/mustard/src/runtime/validation/snapshot.rs +406 -0
  72. package/crates/mustard/src/runtime/validation/walk.rs +206 -0
  73. package/crates/mustard/src/runtime/vm.rs +1016 -0
  74. package/crates/mustard/src/span.rs +22 -0
  75. package/crates/mustard/src/structured.rs +107 -0
  76. package/crates/mustard-bridge/Cargo.toml +17 -0
  77. package/crates/mustard-bridge/src/codec.rs +46 -0
  78. package/crates/mustard-bridge/src/dto.rs +99 -0
  79. package/crates/mustard-bridge/src/lib.rs +12 -0
  80. package/crates/mustard-bridge/src/operations.rs +142 -0
  81. package/crates/mustard-node/Cargo.toml +24 -0
  82. package/crates/mustard-node/build.rs +3 -0
  83. package/crates/mustard-node/src/lib.rs +236 -0
  84. package/crates/mustard-sidecar/Cargo.toml +21 -0
  85. package/crates/mustard-sidecar/src/lib.rs +134 -0
  86. package/crates/mustard-sidecar/src/main.rs +36 -0
  87. package/dist/index.js +20 -0
  88. package/dist/install.js +117 -0
  89. package/dist/lib/cancellation.js +124 -0
  90. package/dist/lib/errors.js +46 -0
  91. package/dist/lib/executor.js +555 -0
  92. package/dist/lib/policy.js +292 -0
  93. package/dist/lib/progress.js +356 -0
  94. package/dist/lib/runtime.js +109 -0
  95. package/dist/lib/structured.js +286 -0
  96. package/dist/native-loader.js +227 -0
  97. package/index.d.ts +23 -0
  98. package/mustard.d.ts +220 -0
  99. package/package.json +97 -0
@@ -0,0 +1,170 @@
1
+ use super::*;
2
+
3
+ #[test]
4
+ fn round_trips_program_and_snapshot() {
5
+ let source = "const value = fetch_data(1); value + 2;";
6
+ let program = compile(source).expect("compile should succeed");
7
+ let bytecode = lower_to_bytecode(&program).expect("lowering should succeed");
8
+ let program_bytes = dump_program(&bytecode).expect("program dump should succeed");
9
+ let loaded_program = load_program(&program_bytes).expect("program load should succeed");
10
+ assert_eq!(loaded_program.root, bytecode.root);
11
+ assert_eq!(loaded_program.functions.len(), bytecode.functions.len());
12
+
13
+ let suspension = suspend(source, &["fetch_data"]);
14
+ let snapshot_bytes = dump_snapshot(&suspension.snapshot).expect("snapshot dump should succeed");
15
+ let loaded_snapshot = load_snapshot(&snapshot_bytes).expect("snapshot load should succeed");
16
+ let resumed = resume_with_options(
17
+ loaded_snapshot,
18
+ ResumePayload::Value(number(1.0)),
19
+ ResumeOptions {
20
+ cancellation_token: None,
21
+ snapshot_policy: Some(SnapshotPolicy {
22
+ capabilities: vec!["fetch_data".to_string()],
23
+ limits: RuntimeLimits::default(),
24
+ }),
25
+ },
26
+ )
27
+ .expect("resume should succeed");
28
+ match resumed {
29
+ ExecutionStep::Completed(value) => {
30
+ assert_eq!(value, number(3.0));
31
+ }
32
+ other => panic!("expected completion, got {other:?}"),
33
+ }
34
+ }
35
+
36
+ #[test]
37
+ fn rejects_invalid_jump_targets_before_execution() {
38
+ let program = invalid_program(vec![Instruction::Jump(99), Instruction::Return]);
39
+ let error = start_bytecode(&program, ExecutionOptions::default())
40
+ .expect_err("invalid jump target should fail validation");
41
+ assert!(error.to_string().contains("jumps to invalid target 99"));
42
+ }
43
+
44
+ #[test]
45
+ fn rejects_inconsistent_stack_depth_in_serialized_programs() {
46
+ let program = invalid_program(vec![
47
+ Instruction::PushNumber(1.0),
48
+ Instruction::JumpIfTrue(3),
49
+ Instruction::Pop,
50
+ Instruction::Return,
51
+ ]);
52
+ let bytes = dump_program(&program).expect("invalid program still serializes");
53
+ let error =
54
+ load_program(&bytes).expect_err("invalid serialized program should fail validation");
55
+ assert!(
56
+ error
57
+ .to_string()
58
+ .contains("has inconsistent validation state")
59
+ );
60
+ }
61
+
62
+ #[test]
63
+ fn rejects_cross_version_serialized_programs() {
64
+ let program = lower_to_bytecode(&compile("1;").expect("compile should succeed"))
65
+ .expect("lowering should succeed");
66
+ let mut bytes = dump_program(&program).expect("program should serialize");
67
+ bytes[0] = bytes[0].saturating_add(1);
68
+ let error = load_program(&bytes).expect_err("cross-version program should be rejected");
69
+ assert!(
70
+ error
71
+ .to_string()
72
+ .contains("serialized program version mismatch")
73
+ );
74
+ }
75
+
76
+ #[test]
77
+ fn rejects_invalid_snapshot_frame_state() {
78
+ let mut suspension = suspend("const value = fetch_data(1); value + 2;", &["fetch_data"]);
79
+ suspension.snapshot.runtime.frames[0].ip = 999;
80
+ let bytes = dump_snapshot(&suspension.snapshot).expect("snapshot should serialize");
81
+ let error = load_snapshot(&bytes).expect_err("invalid snapshot should fail validation");
82
+ assert!(
83
+ error
84
+ .to_string()
85
+ .contains("frame instruction pointer 999 is out of range")
86
+ );
87
+ }
88
+
89
+ #[test]
90
+ fn direct_execution_snapshot_deserialize_reruns_validation() {
91
+ let mut suspension = suspend("const value = fetch_data(1); value + 2;", &["fetch_data"]);
92
+ suspension.snapshot.runtime.frames[0].ip = 999;
93
+ let bytes =
94
+ bincode::serialize(&suspension.snapshot).expect("snapshot should serialize directly");
95
+ let error = bincode::deserialize::<ExecutionSnapshot>(&bytes)
96
+ .expect_err("invalid snapshots should fail validation during direct deserialize");
97
+ assert!(
98
+ error
99
+ .to_string()
100
+ .contains("frame instruction pointer 999 is out of range")
101
+ );
102
+ }
103
+
104
+ #[test]
105
+ fn rejects_cross_version_snapshots() {
106
+ let suspension = suspend("const value = fetch_data(1); value + 2;", &["fetch_data"]);
107
+
108
+ let mut bytes = dump_snapshot(&suspension.snapshot).expect("snapshot should serialize");
109
+ bytes[0] = bytes[0].saturating_add(1);
110
+ let error = load_snapshot(&bytes).expect_err("cross-version snapshot should be rejected");
111
+ assert!(
112
+ error
113
+ .to_string()
114
+ .contains("serialized snapshot version mismatch")
115
+ );
116
+ }
117
+
118
+ #[test]
119
+ fn rejects_out_of_range_promise_combinator_snapshot_state() {
120
+ let mut suspension = suspend(
121
+ r#"
122
+ async function main() {
123
+ return Promise.all([fetch_data(1), fetch_data(2)]);
124
+ }
125
+ main();
126
+ "#,
127
+ &["fetch_data"],
128
+ );
129
+
130
+ let target = suspension
131
+ .snapshot
132
+ .runtime
133
+ .promises
134
+ .iter()
135
+ .find_map(|(key, promise)| match promise.driver.as_ref() {
136
+ Some(PromiseDriver::All { .. }) => Some(key),
137
+ _ => None,
138
+ })
139
+ .expect("Promise.all target should exist");
140
+
141
+ let mutated = suspension
142
+ .snapshot
143
+ .runtime
144
+ .promises
145
+ .values_mut()
146
+ .find_map(|promise| {
147
+ promise.reactions.iter_mut().find_map(|reaction| match reaction {
148
+ PromiseReaction::Combinator {
149
+ target: reaction_target,
150
+ index,
151
+ ..
152
+ } if *reaction_target == target => {
153
+ *index = 99;
154
+ Some(())
155
+ }
156
+ _ => None,
157
+ })
158
+ });
159
+ assert!(mutated.is_some(), "Promise.all combinator reaction should exist");
160
+
161
+ let bytes = dump_snapshot(&suspension.snapshot).expect("snapshot should serialize");
162
+ let error = load_snapshot(&bytes).expect_err("forged snapshot should fail validation");
163
+ assert!(
164
+ error
165
+ .to_string()
166
+ .contains("promise")
167
+ && error.to_string().contains("combinator index"),
168
+ "unexpected error: {error}"
169
+ );
170
+ }
@@ -0,0 +1,484 @@
1
+ use super::*;
2
+
3
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
4
+ struct ValidationState {
5
+ stack_depth: usize,
6
+ scope_depth: usize,
7
+ handler_depth: usize,
8
+ pending_depth: usize,
9
+ }
10
+
11
+ pub(in crate::runtime) fn validate_bytecode_program(
12
+ program: &BytecodeProgram,
13
+ ) -> MustardResult<()> {
14
+ if program.functions.is_empty() {
15
+ return Err(MustardError::validation(
16
+ "bytecode validation failed: program defines no functions",
17
+ None,
18
+ ));
19
+ }
20
+ if program.root >= program.functions.len() {
21
+ return Err(MustardError::validation(
22
+ format!(
23
+ "bytecode validation failed: root function {} is out of range for {} functions",
24
+ program.root,
25
+ program.functions.len()
26
+ ),
27
+ None,
28
+ ));
29
+ }
30
+ for (function_id, function) in program.functions.iter().enumerate() {
31
+ validate_function(program, function_id, function)?;
32
+ }
33
+ Ok(())
34
+ }
35
+
36
+ fn validate_function(
37
+ program: &BytecodeProgram,
38
+ function_id: usize,
39
+ function: &FunctionPrototype,
40
+ ) -> MustardResult<()> {
41
+ if function.code.is_empty() {
42
+ return Err(MustardError::validation(
43
+ format!("bytecode validation failed: function {function_id} has no instructions"),
44
+ None,
45
+ ));
46
+ }
47
+ if !matches!(function.code.last(), Some(Instruction::Return)) {
48
+ return Err(MustardError::validation(
49
+ format!("bytecode validation failed: function {function_id} does not end in Return"),
50
+ None,
51
+ ));
52
+ }
53
+
54
+ let code_len = function.code.len();
55
+ for (ip, instruction) in function.code.iter().enumerate() {
56
+ match instruction {
57
+ Instruction::MakeClosure {
58
+ function_id: target,
59
+ } if *target >= program.functions.len() => {
60
+ return Err(MustardError::validation(
61
+ format!(
62
+ "bytecode validation failed: function {function_id} instruction {ip} references missing closure target {target}"
63
+ ),
64
+ None,
65
+ ));
66
+ }
67
+ Instruction::Jump(target)
68
+ | Instruction::JumpIfFalse(target)
69
+ | Instruction::JumpIfTrue(target)
70
+ | Instruction::JumpIfNullish(target)
71
+ | Instruction::EnterFinally { exit: target }
72
+ | Instruction::PushPendingJump { target, .. }
73
+ if *target >= code_len =>
74
+ {
75
+ return Err(MustardError::validation(
76
+ format!(
77
+ "bytecode validation failed: function {function_id} instruction {ip} jumps to invalid target {target}"
78
+ ),
79
+ None,
80
+ ));
81
+ }
82
+ Instruction::PushHandler { catch, finally }
83
+ if catch.is_some_and(|target| target >= code_len)
84
+ || finally.is_some_and(|target| target >= code_len) =>
85
+ {
86
+ return Err(MustardError::validation(
87
+ format!(
88
+ "bytecode validation failed: function {function_id} instruction {ip} references an invalid exception target"
89
+ ),
90
+ None,
91
+ ));
92
+ }
93
+ _ => {}
94
+ }
95
+ }
96
+
97
+ let mut states = vec![None; code_len];
98
+ let mut work = VecDeque::from([(
99
+ 0usize,
100
+ ValidationState {
101
+ stack_depth: 0,
102
+ scope_depth: 0,
103
+ handler_depth: 0,
104
+ pending_depth: 0,
105
+ },
106
+ )]);
107
+ while let Some((ip, state)) = work.pop_front() {
108
+ if let Some(existing) = states[ip] {
109
+ if existing != state {
110
+ return Err(MustardError::validation(
111
+ format!(
112
+ "bytecode validation failed: function {function_id} instruction {ip} has inconsistent validation state: existing={existing:?}, new={state:?}"
113
+ ),
114
+ None,
115
+ ));
116
+ }
117
+ continue;
118
+ }
119
+ states[ip] = Some(state);
120
+
121
+ let instruction = &function.code[ip];
122
+ let next_state = apply_validation_effect(function_id, ip, instruction, state)?;
123
+ for successor in validation_successors(ip, instruction, code_len) {
124
+ work.push_back((successor, next_state));
125
+ }
126
+ match instruction {
127
+ Instruction::PushHandler { catch, finally } => {
128
+ if let Some(target) = catch {
129
+ work.push_back((
130
+ *target,
131
+ ValidationState {
132
+ handler_depth: state.handler_depth,
133
+ ..state
134
+ },
135
+ ));
136
+ } else if let Some(target) = finally {
137
+ work.push_back((
138
+ *target,
139
+ ValidationState {
140
+ handler_depth: state.handler_depth,
141
+ pending_depth: state.pending_depth + 1,
142
+ ..state
143
+ },
144
+ ));
145
+ }
146
+ }
147
+ Instruction::PushPendingJump {
148
+ target,
149
+ target_handler_depth,
150
+ target_scope_depth,
151
+ } => {
152
+ work.push_back((
153
+ *target,
154
+ ValidationState {
155
+ scope_depth: *target_scope_depth,
156
+ handler_depth: *target_handler_depth,
157
+ ..state
158
+ },
159
+ ));
160
+ }
161
+ _ => {}
162
+ }
163
+ }
164
+
165
+ Ok(())
166
+ }
167
+
168
+ fn apply_validation_effect(
169
+ function_id: usize,
170
+ ip: usize,
171
+ instruction: &Instruction,
172
+ state: ValidationState,
173
+ ) -> MustardResult<ValidationState> {
174
+ let require_stack = |count: usize| -> MustardResult<()> {
175
+ if state.stack_depth < count {
176
+ return Err(MustardError::validation(
177
+ format!(
178
+ "bytecode validation failed: function {function_id} instruction {ip} requires stack depth {count}, found {}",
179
+ state.stack_depth
180
+ ),
181
+ None,
182
+ ));
183
+ }
184
+ Ok(())
185
+ };
186
+
187
+ let next = match instruction {
188
+ Instruction::PushUndefined
189
+ | Instruction::PushNull
190
+ | Instruction::PushBool(_)
191
+ | Instruction::PushNumber(_)
192
+ | Instruction::PushBigInt(_)
193
+ | Instruction::PushString(_)
194
+ | Instruction::PushRegExp { .. }
195
+ | Instruction::LoadName(_)
196
+ | Instruction::LoadGlobalObject
197
+ | Instruction::MakeClosure { .. }
198
+ | Instruction::BeginCatch => ValidationState {
199
+ stack_depth: state.stack_depth + 1,
200
+ ..state
201
+ },
202
+ Instruction::StoreName(_) => {
203
+ require_stack(1)?;
204
+ state
205
+ }
206
+ Instruction::InitializePattern(_) | Instruction::Pop => {
207
+ require_stack(1)?;
208
+ ValidationState {
209
+ stack_depth: state.stack_depth - 1,
210
+ ..state
211
+ }
212
+ }
213
+ Instruction::PushEnv => ValidationState {
214
+ scope_depth: state.scope_depth + 1,
215
+ ..state
216
+ },
217
+ Instruction::PopEnv => {
218
+ if state.scope_depth == 0 {
219
+ return Err(MustardError::validation(
220
+ format!(
221
+ "bytecode validation failed: function {function_id} instruction {ip} pops an empty scope stack"
222
+ ),
223
+ None,
224
+ ));
225
+ }
226
+ ValidationState {
227
+ scope_depth: state.scope_depth - 1,
228
+ ..state
229
+ }
230
+ }
231
+ Instruction::DeclareName { .. } => state,
232
+ Instruction::MakeArray { count } => {
233
+ require_stack(*count)?;
234
+ ValidationState {
235
+ stack_depth: state.stack_depth - count + 1,
236
+ ..state
237
+ }
238
+ }
239
+ Instruction::ArrayPush => {
240
+ require_stack(2)?;
241
+ state
242
+ }
243
+ Instruction::ArrayPushHole => {
244
+ require_stack(1)?;
245
+ state
246
+ }
247
+ Instruction::ArrayExtend => {
248
+ require_stack(2)?;
249
+ ValidationState {
250
+ stack_depth: state.stack_depth - 1,
251
+ ..state
252
+ }
253
+ }
254
+ Instruction::MakeObject { keys } => {
255
+ require_stack(keys.len())?;
256
+ ValidationState {
257
+ stack_depth: state.stack_depth - keys.len() + 1,
258
+ ..state
259
+ }
260
+ }
261
+ Instruction::CopyDataProperties => {
262
+ require_stack(2)?;
263
+ ValidationState {
264
+ stack_depth: state.stack_depth - 1,
265
+ ..state
266
+ }
267
+ }
268
+ Instruction::CreateIterator => {
269
+ require_stack(1)?;
270
+ state
271
+ }
272
+ Instruction::IteratorNext => {
273
+ require_stack(1)?;
274
+ ValidationState {
275
+ stack_depth: state.stack_depth + 1,
276
+ ..state
277
+ }
278
+ }
279
+ Instruction::GetPropStatic { .. } => {
280
+ require_stack(1)?;
281
+ state
282
+ }
283
+ Instruction::GetPropComputed { .. } => {
284
+ require_stack(2)?;
285
+ ValidationState {
286
+ stack_depth: state.stack_depth - 1,
287
+ ..state
288
+ }
289
+ }
290
+ Instruction::PatternArrayIndex(_)
291
+ | Instruction::PatternArrayRest(_)
292
+ | Instruction::PatternObjectRest(_)
293
+ | Instruction::Update(_) => {
294
+ require_stack(1)?;
295
+ state
296
+ }
297
+ Instruction::SetPropStatic { .. } => {
298
+ require_stack(2)?;
299
+ ValidationState {
300
+ stack_depth: state.stack_depth - 1,
301
+ ..state
302
+ }
303
+ }
304
+ Instruction::SetPropComputed => {
305
+ require_stack(3)?;
306
+ ValidationState {
307
+ stack_depth: state.stack_depth - 2,
308
+ ..state
309
+ }
310
+ }
311
+ Instruction::Unary(_) => {
312
+ require_stack(1)?;
313
+ state
314
+ }
315
+ Instruction::Binary(_) => {
316
+ require_stack(2)?;
317
+ ValidationState {
318
+ stack_depth: state.stack_depth - 1,
319
+ ..state
320
+ }
321
+ }
322
+ Instruction::Dup => {
323
+ require_stack(1)?;
324
+ ValidationState {
325
+ stack_depth: state.stack_depth + 1,
326
+ ..state
327
+ }
328
+ }
329
+ Instruction::Dup2 => {
330
+ require_stack(2)?;
331
+ ValidationState {
332
+ stack_depth: state.stack_depth + 2,
333
+ ..state
334
+ }
335
+ }
336
+ Instruction::PushHandler { .. } => ValidationState {
337
+ handler_depth: state.handler_depth + 1,
338
+ ..state
339
+ },
340
+ Instruction::PopHandler => {
341
+ if state.handler_depth == 0 {
342
+ return Err(MustardError::validation(
343
+ format!(
344
+ "bytecode validation failed: function {function_id} instruction {ip} pops an empty handler stack"
345
+ ),
346
+ None,
347
+ ));
348
+ }
349
+ ValidationState {
350
+ handler_depth: state.handler_depth - 1,
351
+ ..state
352
+ }
353
+ }
354
+ Instruction::EnterFinally { .. } => state,
355
+ Instruction::Throw { .. } => {
356
+ require_stack(1)?;
357
+ state
358
+ }
359
+ Instruction::PushPendingJump {
360
+ target_handler_depth,
361
+ target_scope_depth,
362
+ ..
363
+ } => {
364
+ if *target_handler_depth > state.handler_depth {
365
+ return Err(MustardError::validation(
366
+ format!(
367
+ "bytecode validation failed: function {function_id} instruction {ip} targets handler depth {target_handler_depth} from depth {}",
368
+ state.handler_depth
369
+ ),
370
+ None,
371
+ ));
372
+ }
373
+ if *target_scope_depth > state.scope_depth {
374
+ return Err(MustardError::validation(
375
+ format!(
376
+ "bytecode validation failed: function {function_id} instruction {ip} targets scope depth {target_scope_depth} from depth {}",
377
+ state.scope_depth
378
+ ),
379
+ None,
380
+ ));
381
+ }
382
+ ValidationState {
383
+ pending_depth: state.pending_depth + 1,
384
+ ..state
385
+ }
386
+ }
387
+ Instruction::PushPendingReturn => {
388
+ require_stack(1)?;
389
+ ValidationState {
390
+ stack_depth: state.stack_depth - 1,
391
+ pending_depth: state.pending_depth + 1,
392
+ ..state
393
+ }
394
+ }
395
+ Instruction::PushPendingThrow => {
396
+ require_stack(1)?;
397
+ ValidationState {
398
+ stack_depth: state.stack_depth - 1,
399
+ pending_depth: state.pending_depth + 1,
400
+ ..state
401
+ }
402
+ }
403
+ Instruction::ContinuePending => {
404
+ if state.pending_depth == 0 {
405
+ return Err(MustardError::validation(
406
+ format!(
407
+ "bytecode validation failed: function {function_id} instruction {ip} resumes without a pending completion"
408
+ ),
409
+ None,
410
+ ));
411
+ }
412
+ ValidationState {
413
+ pending_depth: state.pending_depth - 1,
414
+ ..state
415
+ }
416
+ }
417
+ Instruction::Jump(_) => state,
418
+ Instruction::JumpIfFalse(_)
419
+ | Instruction::JumpIfTrue(_)
420
+ | Instruction::JumpIfNullish(_) => {
421
+ require_stack(1)?;
422
+ state
423
+ }
424
+ Instruction::Call {
425
+ argc, with_this, ..
426
+ } => {
427
+ let required = argc + 1 + usize::from(*with_this);
428
+ require_stack(required)?;
429
+ ValidationState {
430
+ stack_depth: state.stack_depth - required + 1,
431
+ ..state
432
+ }
433
+ }
434
+ Instruction::CallWithArray { with_this, .. } => {
435
+ let required = 2 + usize::from(*with_this);
436
+ require_stack(required)?;
437
+ ValidationState {
438
+ stack_depth: state.stack_depth - required + 1,
439
+ ..state
440
+ }
441
+ }
442
+ Instruction::Await => {
443
+ require_stack(1)?;
444
+ state
445
+ }
446
+ Instruction::Construct { argc } => {
447
+ let required = argc + 1;
448
+ require_stack(required)?;
449
+ ValidationState {
450
+ stack_depth: state.stack_depth - required + 1,
451
+ ..state
452
+ }
453
+ }
454
+ Instruction::ConstructWithArray => {
455
+ require_stack(2)?;
456
+ ValidationState {
457
+ stack_depth: state.stack_depth - 1,
458
+ ..state
459
+ }
460
+ }
461
+ Instruction::Return => state,
462
+ };
463
+ Ok(next)
464
+ }
465
+
466
+ fn validation_successors(ip: usize, instruction: &Instruction, code_len: usize) -> Vec<usize> {
467
+ match instruction {
468
+ Instruction::Jump(target) => vec![*target],
469
+ Instruction::JumpIfFalse(target)
470
+ | Instruction::JumpIfTrue(target)
471
+ | Instruction::JumpIfNullish(target) => {
472
+ let mut successors = vec![*target];
473
+ if ip + 1 < code_len {
474
+ successors.push(ip + 1);
475
+ }
476
+ successors
477
+ }
478
+ Instruction::ContinuePending | Instruction::Throw { .. } | Instruction::Return => {
479
+ Vec::new()
480
+ }
481
+ _ if ip + 1 < code_len => vec![ip + 1],
482
+ _ => Vec::new(),
483
+ }
484
+ }
@@ -0,0 +1,14 @@
1
+ use std::collections::{HashSet, VecDeque};
2
+
3
+ use crate::diagnostic::{DiagnosticKind, MustardError, MustardResult};
4
+
5
+ use super::*;
6
+
7
+ mod bytecode;
8
+ mod policy;
9
+ mod snapshot;
10
+ mod walk;
11
+
12
+ pub(super) use bytecode::validate_bytecode_program;
13
+ pub(super) use policy::validate_snapshot_policy;
14
+ pub(super) use snapshot::validate_snapshot;