@wasm-oj/server 0.2.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 (94) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +6 -0
  3. package/THIRD_PARTY_NOTICES.md +302 -0
  4. package/crates/runtime-core/Cargo.lock +5099 -0
  5. package/crates/runtime-core/Cargo.toml +66 -0
  6. package/crates/runtime-core/README.md +47 -0
  7. package/crates/runtime-core/src/bin/wasm-oj-compiler.rs +418 -0
  8. package/crates/runtime-core/src/bin/wasm-oj-runner.rs +294 -0
  9. package/crates/runtime-core/src/capabilities.rs +118 -0
  10. package/crates/runtime-core/src/compiler.rs +658 -0
  11. package/crates/runtime-core/src/contract.rs +5 -0
  12. package/crates/runtime-core/src/deterministic.rs +1051 -0
  13. package/crates/runtime-core/src/error.rs +58 -0
  14. package/crates/runtime-core/src/filesystem.rs +547 -0
  15. package/crates/runtime-core/src/filesystem_quota.rs +167 -0
  16. package/crates/runtime-core/src/go_compiler_session.rs +297 -0
  17. package/crates/runtime-core/src/interactive.rs +1019 -0
  18. package/crates/runtime-core/src/judge_package.rs +1539 -0
  19. package/crates/runtime-core/src/lib.rs +98 -0
  20. package/crates/runtime-core/src/memory.rs +84 -0
  21. package/crates/runtime-core/src/meter.rs +549 -0
  22. package/crates/runtime-core/src/module_imports.rs +149 -0
  23. package/crates/runtime-core/src/module_policy.rs +714 -0
  24. package/crates/runtime-core/src/output.rs +204 -0
  25. package/crates/runtime-core/src/run/mod.rs +208 -0
  26. package/crates/runtime-core/src/run/native.rs +260 -0
  27. package/crates/runtime-core/src/run/web.rs +229 -0
  28. package/crates/runtime-core/src/run/web_runtime.rs +109 -0
  29. package/crates/runtime-core/src/types.rs +268 -0
  30. package/crates/runtime-core/src/web.rs +83 -0
  31. package/dist/chunks/go-toolchain-Dbt-lp2L.js +426 -0
  32. package/dist/chunks/java-toolchain-DajoRCHu.js +44 -0
  33. package/dist/chunks/python-toolchain-Dx834o2A.js +4 -0
  34. package/dist/chunks/rust-toolchain-CJ3sMxPE.js +252 -0
  35. package/dist/chunks/toolchains-C6KuA1yM.js +224 -0
  36. package/dist/go-stage.mjs +193 -0
  37. package/dist/index.d.ts +221 -0
  38. package/dist/index.js +4393 -0
  39. package/dist/java-stage.mjs +111 -0
  40. package/dist/python-stage.mjs +90 -0
  41. package/dist/rustc-stage.mjs +301 -0
  42. package/dist/server-build-stage.mjs +2564 -0
  43. package/dist/server-runner-stage.mjs +155 -0
  44. package/licenses/fflate-MIT.txt +21 -0
  45. package/licenses/runtime-core-dependencies.html +6253 -0
  46. package/licenses/runtime-core-dependencies.json +3041 -0
  47. package/licenses/wasmer-sdk-MIT.txt +21 -0
  48. package/licenses/wasmer-sdk-dependencies.html +6901 -0
  49. package/licenses/wasmer-sdk-dependencies.json +3013 -0
  50. package/package.json +70 -0
  51. package/rust-toolchain.toml +5 -0
  52. package/testdata/wojjdg02-v2-text.hex +1 -0
  53. package/vendor/shared-buffer/Cargo.toml +22 -0
  54. package/vendor/shared-buffer/LICENSE_APACHE.md +176 -0
  55. package/vendor/shared-buffer/LICENSE_MIT.md +25 -0
  56. package/vendor/shared-buffer/README.md +34 -0
  57. package/vendor/shared-buffer/src/lib.rs +58 -0
  58. package/vendor/shared-buffer/src/mmap.rs +250 -0
  59. package/vendor/shared-buffer/src/owned.rs +389 -0
  60. package/vendor/virtual-fs/Cargo.toml +181 -0
  61. package/vendor/virtual-fs/LICENSE +25 -0
  62. package/vendor/virtual-fs/src/arc_box_file.rs +142 -0
  63. package/vendor/virtual-fs/src/arc_file.rs +182 -0
  64. package/vendor/virtual-fs/src/arc_fs.rs +68 -0
  65. package/vendor/virtual-fs/src/buffer_file.rs +103 -0
  66. package/vendor/virtual-fs/src/builder.rs +232 -0
  67. package/vendor/virtual-fs/src/combine_file.rs +101 -0
  68. package/vendor/virtual-fs/src/cow_file.rs +345 -0
  69. package/vendor/virtual-fs/src/dual_write_file.rs +113 -0
  70. package/vendor/virtual-fs/src/empty_fs.rs +81 -0
  71. package/vendor/virtual-fs/src/filesystems.rs +108 -0
  72. package/vendor/virtual-fs/src/host_fs.rs +1390 -0
  73. package/vendor/virtual-fs/src/lib.rs +782 -0
  74. package/vendor/virtual-fs/src/limiter.rs +252 -0
  75. package/vendor/virtual-fs/src/mem_fs/file.rs +1799 -0
  76. package/vendor/virtual-fs/src/mem_fs/file_opener.rs +941 -0
  77. package/vendor/virtual-fs/src/mem_fs/filesystem.rs +2134 -0
  78. package/vendor/virtual-fs/src/mem_fs/mod.rs +245 -0
  79. package/vendor/virtual-fs/src/mem_fs/offloaded_file.rs +474 -0
  80. package/vendor/virtual-fs/src/mem_fs/stdio.rs +318 -0
  81. package/vendor/virtual-fs/src/mount_fs.rs +2225 -0
  82. package/vendor/virtual-fs/src/null_file.rs +87 -0
  83. package/vendor/virtual-fs/src/ops.rs +364 -0
  84. package/vendor/virtual-fs/src/overlay_fs.rs +2216 -0
  85. package/vendor/virtual-fs/src/passthru_fs.rs +119 -0
  86. package/vendor/virtual-fs/src/pipe.rs +603 -0
  87. package/vendor/virtual-fs/src/random_file.rs +88 -0
  88. package/vendor/virtual-fs/src/special_file.rs +108 -0
  89. package/vendor/virtual-fs/src/static_file.rs +133 -0
  90. package/vendor/virtual-fs/src/static_fs.rs +460 -0
  91. package/vendor/virtual-fs/src/tmp_fs.rs +95 -0
  92. package/vendor/virtual-fs/src/trace_fs.rs +258 -0
  93. package/vendor/virtual-fs/src/webc_volume_fs.rs +829 -0
  94. package/vendor/virtual-fs/src/zero_file.rs +90 -0
@@ -0,0 +1,714 @@
1
+ use std::fmt;
2
+ use wasm_encoder::reencode::{Error, Reencode, RoundtripReencoder};
3
+
4
+ pub(crate) const INTERACTIVE_WASIP1_DETERMINISTIC_NAMESPACE: &str =
5
+ "wasm_oj_interactive_wasi_snapshot_preview1";
6
+ pub(crate) const INTERACTIVE_WASIX32_DETERMINISTIC_NAMESPACE: &str =
7
+ "wasm_oj_interactive_wasix_32v1";
8
+ pub(crate) const INTERACTIVE_WASIX64_DETERMINISTIC_NAMESPACE: &str =
9
+ "wasm_oj_interactive_wasix_64v1";
10
+
11
+ const WASM_PAGE_BYTES: u64 = 65_536;
12
+ pub(crate) const DEFERRED_START_EXPORT: &str = "__wasm_oj_deferred_start";
13
+ const WASIP1_FUNCTIONS: &[&str] = &[
14
+ "args_get",
15
+ "args_sizes_get",
16
+ "clock_res_get",
17
+ "clock_time_get",
18
+ "environ_get",
19
+ "environ_sizes_get",
20
+ "fd_advise",
21
+ "fd_allocate",
22
+ "fd_close",
23
+ "fd_datasync",
24
+ "fd_fdstat_get",
25
+ "fd_fdstat_set_flags",
26
+ "fd_fdstat_set_rights",
27
+ "fd_filestat_get",
28
+ "fd_filestat_set_size",
29
+ "fd_filestat_set_times",
30
+ "fd_pread",
31
+ "fd_prestat_dir_name",
32
+ "fd_prestat_get",
33
+ "fd_pwrite",
34
+ "fd_read",
35
+ "fd_readdir",
36
+ "fd_renumber",
37
+ "fd_seek",
38
+ "fd_sync",
39
+ "fd_tell",
40
+ "fd_write",
41
+ "path_create_directory",
42
+ "path_filestat_get",
43
+ "path_filestat_set_times",
44
+ "path_link",
45
+ "path_open",
46
+ "path_readlink",
47
+ "path_remove_directory",
48
+ "path_rename",
49
+ "path_symlink",
50
+ "path_unlink_file",
51
+ "poll_oneoff",
52
+ "proc_exit",
53
+ "proc_raise",
54
+ "random_get",
55
+ "sched_yield",
56
+ "sock_accept",
57
+ "sock_recv",
58
+ "sock_send",
59
+ "sock_shutdown",
60
+ "thread-spawn",
61
+ ];
62
+
63
+ // Exact function surface exported by pinned wasmer-wasix 0.702.1 for both
64
+ // wasix_32v1 and wasix_64v1. Updating Wasmer requires an explicit ABI and
65
+ // capability review before new host functions become reachable.
66
+ const WASIX_V1_FUNCTIONS: &[&str] = &[
67
+ "args_get",
68
+ "args_sizes_get",
69
+ "call_dynamic",
70
+ "callback_signal",
71
+ "chdir",
72
+ "clock_res_get",
73
+ "clock_time_get",
74
+ "clock_time_set",
75
+ "closure_allocate",
76
+ "closure_free",
77
+ "closure_prepare",
78
+ "context_create",
79
+ "context_destroy",
80
+ "context_switch",
81
+ "dl_invalid_handle",
82
+ "dlopen",
83
+ "dlsym",
84
+ "environ_get",
85
+ "environ_sizes_get",
86
+ "epoll_create",
87
+ "epoll_ctl",
88
+ "epoll_wait",
89
+ "fd_advise",
90
+ "fd_allocate",
91
+ "fd_close",
92
+ "fd_datasync",
93
+ "fd_dup",
94
+ "fd_dup2",
95
+ "fd_event",
96
+ "fd_fdflags_get",
97
+ "fd_fdflags_set",
98
+ "fd_fdstat_get",
99
+ "fd_fdstat_set_flags",
100
+ "fd_fdstat_set_rights",
101
+ "fd_filestat_get",
102
+ "fd_filestat_set_size",
103
+ "fd_filestat_set_times",
104
+ "fd_pipe",
105
+ "fd_pread",
106
+ "fd_prestat_dir_name",
107
+ "fd_prestat_get",
108
+ "fd_pwrite",
109
+ "fd_read",
110
+ "fd_readdir",
111
+ "fd_renumber",
112
+ "fd_seek",
113
+ "fd_sync",
114
+ "fd_tell",
115
+ "fd_write",
116
+ "futex_wait",
117
+ "futex_wake",
118
+ "futex_wake_all",
119
+ "getcwd",
120
+ "path_create_directory",
121
+ "path_filestat_get",
122
+ "path_filestat_set_times",
123
+ "path_link",
124
+ "path_open",
125
+ "path_open2",
126
+ "path_readlink",
127
+ "path_remove_directory",
128
+ "path_rename",
129
+ "path_symlink",
130
+ "path_unlink_file",
131
+ "poll_oneoff",
132
+ "port_addr_add",
133
+ "port_addr_clear",
134
+ "port_addr_list",
135
+ "port_addr_remove",
136
+ "port_bridge",
137
+ "port_dhcp_acquire",
138
+ "port_gateway_set",
139
+ "port_mac",
140
+ "port_route_add",
141
+ "port_route_clear",
142
+ "port_route_list",
143
+ "port_route_remove",
144
+ "port_unbridge",
145
+ "proc_exec",
146
+ "proc_exec2",
147
+ "proc_exec3",
148
+ "proc_exec4",
149
+ "proc_exit",
150
+ "proc_exit2",
151
+ "proc_fork",
152
+ "proc_fork_env",
153
+ "proc_id",
154
+ "proc_join",
155
+ "proc_parent",
156
+ "proc_raise",
157
+ "proc_raise_interval",
158
+ "proc_signal",
159
+ "proc_signals_get",
160
+ "proc_signals_sizes_get",
161
+ "proc_snapshot",
162
+ "proc_spawn",
163
+ "proc_spawn2",
164
+ "proc_spawn3",
165
+ "random_get",
166
+ "reflect_signature",
167
+ "resolve",
168
+ "sched_yield",
169
+ "sock_accept",
170
+ "sock_accept_v2",
171
+ "sock_addr_local",
172
+ "sock_addr_peer",
173
+ "sock_bind",
174
+ "sock_connect",
175
+ "sock_get_opt_flag",
176
+ "sock_get_opt_size",
177
+ "sock_get_opt_time",
178
+ "sock_join_multicast_v4",
179
+ "sock_join_multicast_v6",
180
+ "sock_leave_multicast_v4",
181
+ "sock_leave_multicast_v6",
182
+ "sock_listen",
183
+ "sock_open",
184
+ "sock_pair",
185
+ "sock_recv",
186
+ "sock_recv_from",
187
+ "sock_send",
188
+ "sock_send_file",
189
+ "sock_send_to",
190
+ "sock_set_opt_flag",
191
+ "sock_set_opt_size",
192
+ "sock_set_opt_time",
193
+ "sock_shutdown",
194
+ "sock_status",
195
+ "stack_checkpoint",
196
+ "stack_restore",
197
+ "thread_exit",
198
+ "thread_id",
199
+ "thread_join",
200
+ "thread_parallelism",
201
+ "thread_signal",
202
+ "thread_sleep",
203
+ "thread_spawn",
204
+ "thread_spawn_v2",
205
+ "tty_get",
206
+ "tty_set",
207
+ ];
208
+
209
+ #[derive(Debug)]
210
+ pub(crate) struct DeferredStartModule {
211
+ pub(crate) wasm: Vec<u8>,
212
+ pub(crate) has_deferred_start: bool,
213
+ }
214
+
215
+ #[derive(Debug)]
216
+ struct MemoryPolicyError(String);
217
+
218
+ impl fmt::Display for MemoryPolicyError {
219
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
220
+ formatter.write_str(&self.0)
221
+ }
222
+ }
223
+
224
+ impl std::error::Error for MemoryPolicyError {}
225
+
226
+ struct MemoryLimiter {
227
+ limit_pages: u64,
228
+ }
229
+
230
+ struct InteractiveDeterministicImports;
231
+
232
+ impl Reencode for InteractiveDeterministicImports {
233
+ type Error = std::convert::Infallible;
234
+
235
+ fn parse_import_section(
236
+ &mut self,
237
+ imports: &mut wasm_encoder::ImportSection,
238
+ section: wasmparser::ImportSectionReader<'_>,
239
+ ) -> Result<(), Error<Self::Error>> {
240
+ for import in section.into_imports() {
241
+ let import = import?;
242
+ let namespace = interactive_deterministic_namespace(import.module, import.name)
243
+ .unwrap_or(import.module);
244
+ imports.import(namespace, import.name, self.entity_type(import.ty)?);
245
+ }
246
+ Ok(())
247
+ }
248
+ }
249
+
250
+ fn interactive_deterministic_namespace(module: &str, name: &str) -> Option<&'static str> {
251
+ let common = matches!(
252
+ name,
253
+ "clock_res_get"
254
+ | "clock_time_get"
255
+ | "fd_filestat_set_times"
256
+ | "path_filestat_set_times"
257
+ | "poll_oneoff"
258
+ | "random_get"
259
+ );
260
+ match module {
261
+ "wasi_snapshot_preview1" if common => Some(INTERACTIVE_WASIP1_DETERMINISTIC_NAMESPACE),
262
+ "wasix_32v1"
263
+ if common || matches!(name, "thread_id" | "thread_parallelism" | "thread_sleep") =>
264
+ {
265
+ Some(INTERACTIVE_WASIX32_DETERMINISTIC_NAMESPACE)
266
+ }
267
+ "wasix_64v1"
268
+ if common || matches!(name, "thread_id" | "thread_parallelism" | "thread_sleep") =>
269
+ {
270
+ Some(INTERACTIVE_WASIX64_DETERMINISTIC_NAMESPACE)
271
+ }
272
+ _ => None,
273
+ }
274
+ }
275
+
276
+ pub(crate) fn rewrite_interactive_deterministic_imports(wasm: &[u8]) -> Result<Vec<u8>, String> {
277
+ let mut module = wasm_encoder::Module::new();
278
+ InteractiveDeterministicImports
279
+ .parse_core_module(&mut module, wasmparser::Parser::new(0), wasm)
280
+ .map_err(|error| format!("failed to isolate interactive deterministic imports: {error}"))?;
281
+ Ok(module.finish())
282
+ }
283
+
284
+ impl Reencode for MemoryLimiter {
285
+ type Error = MemoryPolicyError;
286
+
287
+ fn memory_type(
288
+ &mut self,
289
+ memory: wasmparser::MemoryType,
290
+ ) -> Result<wasm_encoder::MemoryType, Error<Self::Error>> {
291
+ if memory.memory64 {
292
+ return Err(Error::UserError(MemoryPolicyError(
293
+ "memory64 modules are unsupported by the pinned WASM-OJ runtime".to_string(),
294
+ )));
295
+ }
296
+ if memory.initial > self.limit_pages {
297
+ return Err(Error::UserError(MemoryPolicyError(format!(
298
+ "module requires {} memory pages, limit is {}",
299
+ memory.initial, self.limit_pages
300
+ ))));
301
+ }
302
+ let mut encoded = wasm_encoder::reencode::utils::memory_type(self, memory);
303
+ encoded.maximum = Some(
304
+ encoded
305
+ .maximum
306
+ .map_or(self.limit_pages, |maximum| maximum.min(self.limit_pages)),
307
+ );
308
+ Ok(encoded)
309
+ }
310
+ }
311
+
312
+ /// Re-encodes every defined or imported memory with a strict maximum.
313
+ /// The WebAssembly engine therefore rejects growth beyond the policy on both
314
+ /// native and browser hosts.
315
+ pub fn enforce_memory_limit(wasm: &[u8], memory_limit_bytes: u64) -> Result<Vec<u8>, String> {
316
+ if memory_limit_bytes == 0 || !memory_limit_bytes.is_multiple_of(WASM_PAGE_BYTES) {
317
+ return Err("memory limit must be a positive multiple of 64 KiB".to_string());
318
+ }
319
+ validate_runtime_import_namespaces(wasm)?;
320
+ let mut module = wasm_encoder::Module::new();
321
+ MemoryLimiter {
322
+ limit_pages: memory_limit_bytes / WASM_PAGE_BYTES,
323
+ }
324
+ .parse_core_module(&mut module, wasmparser::Parser::new(0), wasm)
325
+ .map_err(|error| format!("failed to apply memory policy: {error}"))?;
326
+ Ok(module.finish())
327
+ }
328
+
329
+ fn validate_runtime_import_namespaces(wasm: &[u8]) -> Result<(), String> {
330
+ for payload in wasmparser::Parser::new(0).parse_all(wasm) {
331
+ let payload =
332
+ payload.map_err(|error| format!("failed to inspect module imports: {error}"))?;
333
+ let wasmparser::Payload::ImportSection(section) = payload else {
334
+ continue;
335
+ };
336
+ for import in section.into_imports() {
337
+ let import =
338
+ import.map_err(|error| format!("failed to inspect module import: {error}"))?;
339
+ validate_runtime_import(import.module, import.name, import.ty)?;
340
+ }
341
+ }
342
+ Ok(())
343
+ }
344
+
345
+ fn validate_runtime_import(
346
+ namespace: &str,
347
+ name: &str,
348
+ import_type: wasmparser::TypeRef,
349
+ ) -> Result<(), String> {
350
+ let is_function = matches!(import_type, wasmparser::TypeRef::Func(_));
351
+ match namespace {
352
+ "env" if name == "memory" && matches!(import_type, wasmparser::TypeRef::Memory(_)) => {
353
+ Ok(())
354
+ }
355
+ "env" => Err(format!(
356
+ "unsupported runtime import env.{name}; WASM-OJ admits only env.memory"
357
+ )),
358
+ "wasi" if name == "thread-spawn" && is_function => Ok(()),
359
+ "wasi" => Err(format!(
360
+ "unsupported generic WASI import wasi.{name}; only the pinned WASIX thread-spawn declaration is recognized"
361
+ )),
362
+ "wasi_snapshot_preview1" if is_function && WASIP1_FUNCTIONS.contains(&name) => Ok(()),
363
+ "wasi_snapshot_preview1" => Err(format!(
364
+ "unsupported wasip1 import wasi_snapshot_preview1.{name}"
365
+ )),
366
+ "wasix_32v1" | "wasix_64v1" if is_function && WASIX_V1_FUNCTIONS.contains(&name) => Ok(()),
367
+ "wasix_32v1" | "wasix_64v1" if is_function => Err(format!(
368
+ "unsupported WASIX import {namespace}.{name}; the function is outside the pinned WASIX v1 ABI"
369
+ )),
370
+ "wasix_32v1" | "wasix_64v1" => Err(format!(
371
+ "unsupported non-function WASIX import {namespace}.{name}"
372
+ )),
373
+ _ => Err(format!(
374
+ "unsupported runtime import namespace '{namespace}'; WASM-OJ accepts only wasip1 and WASIX modules"
375
+ )),
376
+ }
377
+ }
378
+
379
+ /// Converts the WebAssembly start section into a private host-invoked export.
380
+ ///
381
+ /// A native start section runs inside `Instance::new`, before WASM-OJ can attach
382
+ /// guest memory to deterministic clock/random functions or initialize WASI
383
+ /// instance handles. Deferring it keeps the same instrumented function body
384
+ /// and function index while allowing the runner to invoke it immediately after
385
+ /// those prerequisites are ready.
386
+ pub(crate) fn defer_start_section(wasm: &[u8]) -> Result<DeferredStartModule, String> {
387
+ let mut start_function = None;
388
+ for payload in wasmparser::Parser::new(0).parse_all(wasm) {
389
+ match payload.map_err(|error| format!("failed to inspect start section: {error}"))? {
390
+ wasmparser::Payload::StartSection { func, .. } => start_function = Some(func),
391
+ wasmparser::Payload::ExportSection(section) => {
392
+ for export in section {
393
+ let export = export
394
+ .map_err(|error| format!("failed to inspect module export: {error}"))?;
395
+ if export.name == DEFERRED_START_EXPORT {
396
+ return Err(format!(
397
+ "module export name {DEFERRED_START_EXPORT} is reserved by WASM-OJ"
398
+ ));
399
+ }
400
+ }
401
+ }
402
+ _ => {}
403
+ }
404
+ }
405
+
406
+ let Some(start_function) = start_function else {
407
+ return Ok(DeferredStartModule {
408
+ wasm: wasm.to_vec(),
409
+ has_deferred_start: false,
410
+ });
411
+ };
412
+
413
+ let mut module = wasm_encoder::Module::new();
414
+ let mut added_export = false;
415
+ for payload in wasmparser::Parser::new(0).parse_all(wasm) {
416
+ let payload = payload.map_err(|error| format!("failed to defer start section: {error}"))?;
417
+ match payload {
418
+ wasmparser::Payload::Version { .. }
419
+ | wasmparser::Payload::CodeSectionEntry(_)
420
+ | wasmparser::Payload::End(_) => {}
421
+ wasmparser::Payload::ExportSection(section) => {
422
+ let mut exports = wasm_encoder::ExportSection::new();
423
+ RoundtripReencoder
424
+ .parse_export_section(&mut exports, section)
425
+ .map_err(|error| format!("failed to re-encode module exports: {error}"))?;
426
+ exports.export(
427
+ DEFERRED_START_EXPORT,
428
+ wasm_encoder::ExportKind::Func,
429
+ start_function,
430
+ );
431
+ module.section(&exports);
432
+ added_export = true;
433
+ }
434
+ wasmparser::Payload::StartSection { .. } => {}
435
+ other => {
436
+ let Some((section_id, range)) = other.as_section() else {
437
+ continue;
438
+ };
439
+ if !added_export
440
+ && section_id != 0
441
+ && section_id > wasm_encoder::SectionId::Export as u8
442
+ {
443
+ append_deferred_start_export(&mut module, start_function);
444
+ added_export = true;
445
+ }
446
+ module.section(&wasm_encoder::RawSection {
447
+ id: section_id,
448
+ data: &wasm[range],
449
+ });
450
+ }
451
+ }
452
+ }
453
+ if !added_export {
454
+ append_deferred_start_export(&mut module, start_function);
455
+ }
456
+
457
+ Ok(DeferredStartModule {
458
+ wasm: module.finish(),
459
+ has_deferred_start: true,
460
+ })
461
+ }
462
+
463
+ fn append_deferred_start_export(module: &mut wasm_encoder::Module, start_function: u32) {
464
+ let mut exports = wasm_encoder::ExportSection::new();
465
+ exports.export(
466
+ DEFERRED_START_EXPORT,
467
+ wasm_encoder::ExportKind::Func,
468
+ start_function,
469
+ );
470
+ module.section(&exports);
471
+ }
472
+
473
+ /// Rejects modules whose declared memory range exceeds the hard policy.
474
+ /// Package commands are content-addressed and cannot be rewritten without
475
+ /// invalidating their identity, so compiler packages use validation rather
476
+ /// than re-encoding.
477
+ pub fn validate_memory_limit(wasm: &[u8], memory_limit_bytes: u64) -> Result<(), String> {
478
+ if memory_limit_bytes == 0 || !memory_limit_bytes.is_multiple_of(WASM_PAGE_BYTES) {
479
+ return Err("memory limit must be a positive multiple of 64 KiB".to_string());
480
+ }
481
+ let limit_pages = memory_limit_bytes / WASM_PAGE_BYTES;
482
+ for payload in wasmparser::Parser::new(0).parse_all(wasm) {
483
+ match payload.map_err(|error| format!("failed to inspect module memory: {error}"))? {
484
+ wasmparser::Payload::ImportSection(section) => {
485
+ for import in section.into_imports() {
486
+ let import = import.map_err(|error| {
487
+ format!("failed to inspect imported module memory: {error}")
488
+ })?;
489
+ if let wasmparser::TypeRef::Memory(memory) = import.ty {
490
+ validate_memory_type(memory, limit_pages)?;
491
+ }
492
+ }
493
+ }
494
+ wasmparser::Payload::MemorySection(section) => {
495
+ for memory in section {
496
+ validate_memory_type(
497
+ memory.map_err(|error| {
498
+ format!("failed to inspect defined module memory: {error}")
499
+ })?,
500
+ limit_pages,
501
+ )?;
502
+ }
503
+ }
504
+ _ => {}
505
+ }
506
+ }
507
+ Ok(())
508
+ }
509
+
510
+ fn validate_memory_type(memory: wasmparser::MemoryType, limit_pages: u64) -> Result<(), String> {
511
+ if memory.memory64 {
512
+ return Err("memory64 modules are unsupported by the pinned WASM-OJ runtime".to_string());
513
+ }
514
+ if memory.initial > limit_pages || memory.maximum.is_none_or(|maximum| maximum > limit_pages) {
515
+ return Err(format!(
516
+ "module memory range {}..={} exceeds the configured limit of {} pages",
517
+ memory.initial,
518
+ memory
519
+ .maximum
520
+ .map_or_else(|| "unbounded".to_string(), |value| value.to_string()),
521
+ limit_pages,
522
+ ));
523
+ }
524
+ Ok(())
525
+ }
526
+
527
+ #[cfg(test)]
528
+ mod tests {
529
+ use super::{
530
+ DEFERRED_START_EXPORT, defer_start_section, enforce_memory_limit, validate_memory_limit,
531
+ };
532
+ use wasmparser::{Parser, Payload};
533
+
534
+ #[test]
535
+ fn clamps_an_unbounded_memory() {
536
+ let wasm = wat::parse_str("(module (memory (export \"memory\") 1))").unwrap();
537
+ let limited = enforce_memory_limit(&wasm, 2 * 65_536).unwrap();
538
+ let maximum = Parser::new(0).parse_all(&limited).find_map(|payload| {
539
+ let Payload::MemorySection(section) = payload.ok()? else {
540
+ return None;
541
+ };
542
+ section.into_iter().next()?.ok()?.maximum
543
+ });
544
+ assert_eq!(maximum, Some(2));
545
+ }
546
+
547
+ #[test]
548
+ fn rejects_a_minimum_above_the_limit() {
549
+ let wasm = wat::parse_str("(module (memory 3))").unwrap();
550
+ assert!(
551
+ enforce_memory_limit(&wasm, 2 * 65_536)
552
+ .unwrap_err()
553
+ .contains("requires 3")
554
+ );
555
+ }
556
+
557
+ #[test]
558
+ fn rejects_memory64_before_engine_compilation() {
559
+ for source in [
560
+ "(module (memory i64 1))",
561
+ r#"(module (import "env" "memory" (memory i64 1 2)))"#,
562
+ ] {
563
+ let wasm = wat::parse_str(source).unwrap();
564
+ let error = enforce_memory_limit(&wasm, 2 * 65_536).unwrap_err();
565
+ assert!(
566
+ error.contains("memory64 modules are unsupported"),
567
+ "{error}"
568
+ );
569
+ }
570
+ }
571
+
572
+ #[test]
573
+ fn rejects_preview0_imports() {
574
+ let wasm = wat::parse_str(
575
+ r#"(module
576
+ (import "wasi_unstable" "clock_time_get" (func (param i32 i64 i32) (result i32)))
577
+ (memory (export "memory") 1)
578
+ (func (export "_start")))"#,
579
+ )
580
+ .unwrap();
581
+ let error = enforce_memory_limit(&wasm, 2 * 65_536).unwrap_err();
582
+ assert!(error.contains("unsupported runtime import namespace 'wasi_unstable'"));
583
+ }
584
+
585
+ #[test]
586
+ fn rejects_unknown_wasip1_symbols() {
587
+ let wasm = wat::parse_str(
588
+ r#"(module
589
+ (import "wasi_snapshot_preview1" "future_extension" (func))
590
+ (memory (export "memory") 1)
591
+ (func (export "_start")))"#,
592
+ )
593
+ .unwrap();
594
+ let error = enforce_memory_limit(&wasm, 2 * 65_536).unwrap_err();
595
+ assert!(error.contains("unsupported wasip1 import"));
596
+ }
597
+
598
+ #[test]
599
+ fn rejects_non_memory_env_imports() {
600
+ let wasm = wat::parse_str(
601
+ r#"(module
602
+ (import "env" "host_callback" (func))
603
+ (memory (export "memory") 1)
604
+ (func (export "_start")))"#,
605
+ )
606
+ .unwrap();
607
+ let error = enforce_memory_limit(&wasm, 2 * 65_536).unwrap_err();
608
+ assert!(error.contains("WASM-OJ admits only env.memory"));
609
+ }
610
+
611
+ #[test]
612
+ fn rejects_unknown_wasix_functions_for_both_memory_models() {
613
+ for namespace in ["wasix_32v1", "wasix_64v1"] {
614
+ let wasm = wat::parse_str(format!(
615
+ r#"(module
616
+ (import "{namespace}" "future_network_api" (func))
617
+ (memory (export "memory") 1)
618
+ (func (export "_start")))"#,
619
+ ))
620
+ .unwrap();
621
+ let error = enforce_memory_limit(&wasm, 2 * 65_536).unwrap_err();
622
+ assert!(
623
+ error.contains("outside the pinned WASIX v1 ABI"),
624
+ "unexpected {namespace} error: {error}"
625
+ );
626
+ }
627
+ }
628
+
629
+ #[test]
630
+ fn clamps_shared_runtime_memory_without_enabling_parallel_execution() {
631
+ let wasm = wat::parse_str("(module (memory 1 10 shared))").unwrap();
632
+ let limited = enforce_memory_limit(&wasm, 2 * 65_536).unwrap();
633
+ let memory = Parser::new(0)
634
+ .parse_all(&limited)
635
+ .find_map(|payload| {
636
+ let Payload::MemorySection(section) = payload.ok()? else {
637
+ return None;
638
+ };
639
+ section.into_iter().next()?.ok()
640
+ })
641
+ .unwrap();
642
+ assert!(memory.shared);
643
+ assert_eq!(memory.maximum, Some(2));
644
+ }
645
+
646
+ #[test]
647
+ fn validation_accepts_a_memory_bounded_by_the_limit() {
648
+ let wasm = wat::parse_str("(module (memory 1 2))").unwrap();
649
+ validate_memory_limit(&wasm, 2 * 65_536).unwrap();
650
+ }
651
+
652
+ #[test]
653
+ fn validation_rejects_unbounded_memory() {
654
+ let wasm = wat::parse_str("(module (memory 1))").unwrap();
655
+ assert!(
656
+ validate_memory_limit(&wasm, 2 * 65_536)
657
+ .unwrap_err()
658
+ .contains("unbounded")
659
+ );
660
+ }
661
+
662
+ #[test]
663
+ fn validation_rejects_memory64() {
664
+ let wasm = wat::parse_str("(module (memory i64 1 2))").unwrap();
665
+ let error = validate_memory_limit(&wasm, 2 * 65_536).unwrap_err();
666
+ assert!(
667
+ error.contains("memory64 modules are unsupported"),
668
+ "{error}"
669
+ );
670
+ }
671
+
672
+ #[test]
673
+ fn converts_a_start_section_to_a_reserved_export_without_reindexing_it() {
674
+ let wasm = wat::parse_str(
675
+ r#"(module
676
+ (func $initialize)
677
+ (start $initialize)
678
+ (memory (export "memory") 1)
679
+ (func (export "_start")))"#,
680
+ )
681
+ .unwrap();
682
+ let deferred = defer_start_section(&wasm).unwrap();
683
+ assert!(deferred.has_deferred_start);
684
+
685
+ let mut saw_start = false;
686
+ let mut deferred_export = None;
687
+ for payload in Parser::new(0).parse_all(&deferred.wasm) {
688
+ match payload.unwrap() {
689
+ Payload::StartSection { .. } => saw_start = true,
690
+ Payload::ExportSection(section) => {
691
+ for export in section {
692
+ let export = export.unwrap();
693
+ if export.name == DEFERRED_START_EXPORT {
694
+ deferred_export = Some((export.kind, export.index));
695
+ }
696
+ }
697
+ }
698
+ _ => {}
699
+ }
700
+ }
701
+ assert!(!saw_start);
702
+ assert_eq!(deferred_export, Some((wasmparser::ExternalKind::Func, 0)));
703
+ }
704
+
705
+ #[test]
706
+ fn rejects_the_private_deferred_start_export() {
707
+ let wasm = wat::parse_str(format!(
708
+ "(module (func (export \"{DEFERRED_START_EXPORT}\")))"
709
+ ))
710
+ .unwrap();
711
+ let error = defer_start_section(&wasm).unwrap_err();
712
+ assert!(error.contains("reserved by WASM-OJ"));
713
+ }
714
+ }