Skip to content
This repository was archived by the owner on Aug 12, 2026. It is now read-only.

Commit bef8de5

Browse files
committed
WASI arguments and environment, and the shapes resource imports will need
:args and :env are the two parts of the WASI config that need an array of C strings rather than one call each, so they are where the wiring can be wrong without the boolean capabilities noticing. dev/resources/args/ is a guest that asks WASI how many arguments it has -- the smallest observable consequence -- and the test walks 0, 1, 3 and 5 of them. Deny-by-default holds here too: no :args means no arguments, not the host's. I picked this over resource imports deliberately and say so: resource imports are a multi-turn feature and this is a complete one. What that unit needs is now read from the headers and written into 0017 F, so it starts on the shapes rather than looking for them -- resource_type_new_host(uint32_t ty) linker_instance_add_resource(li, name, len, type, void (*dtor)(void*, wasmtime_context_t*, uint32_t), data, finalizer) resource_host_new(bool owned, uint32_t rep, uint32_t ty) -- a u32 type tag, a destructor upcall of a second shape, and handles minted per call, with resource_host_to_any the conversion that panics outside a call scope. One slip: Arena.allocate(MemoryLayout, long) does not exist on this JVM, so the pointer array is allocated as bytes. Caught by running it. 211 assertions. Claude-Session: https://claude.ai/code/session_01XF5Hfq4Ca2N2XYEzWQQuHt
1 parent 4179e8b commit bef8de5

6 files changed

Lines changed: 83 additions & 4 deletions

File tree

‎dev/resources/args.wat‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
;; The guest half of dev/resources/args/: it asks WASI how many arguments it
2+
;; was given, which is the smallest observable consequence of `:args`.
3+
(module
4+
(import "wasi:cli/environment@0.2.0" "get-arguments" (func $ga (param i32)))
5+
(memory (export "memory") 1)
6+
(global $next (mut i32) (i32.const 1024))
7+
(func (export "cabi_realloc") (param i32 i32 i32 i32) (result i32)
8+
(local $p i32)
9+
(local.set $p (global.get $next))
10+
(global.set $next (i32.add (local.get $p) (local.get 3)))
11+
(local.get $p))
12+
(func (export "argc") (result i32)
13+
(call $ga (i32.const 128))
14+
(i32.load offset=4 (i32.const 128))))
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// A hand-declared subset of the real wasi:cli@0.2.0, so this repo can check
2+
// `:args` and `:env` without vendoring the WASI WIT tree. The signatures are
3+
// the real ones; wasmtime satisfies them through add_wasip2.
4+
package wasi:cli@0.2.0;
5+
interface environment {
6+
get-arguments: func() -> list<string>;
7+
get-environment: func() -> list<tuple<string, string>>;
8+
}

‎dev/resources/args/world.wit‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package local:args@0.1.0;
2+
world w {
3+
import wasi:cli/environment@0.2.0;
4+
export argc: func() -> u32;
5+
}

‎doc/design/0017-host-imports.md‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,22 @@ handles.
153153

154154
The first draft listed this as a falsifier. It is not one waiting to fire.
155155

156+
The shapes, read from the pinned headers so the next unit starts on them:
157+
158+
```c
159+
wasmtime_component_resource_type_new_host(uint32_t ty);
160+
wasmtime_component_linker_instance_add_resource(
161+
linker_instance, name, name_len, resource_type,
162+
void (*destructor)(void *, wasmtime_context_t *, uint32_t rep),
163+
void *data, void (*finalizer)(void *));
164+
wasmtime_component_resource_host_new(bool owned, uint32_t rep, uint32_t ty);
165+
```
166+
167+
So a host resource is a `u32` type tag, a destructor upcall of a *second*
168+
shape, and handles minted per call. `resource_host_to_any` is how one becomes
169+
a val — and it panics outside a call scope, which is the landmine already
170+
recorded above.
171+
156172
## Alternatives rejected
157173
158174
- **Imports on the artifact, sharing one set of host functions.** Wrong the

‎src/cljwit/host.clj‎

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,8 @@
165165
:wasi-stderr (b "wasi_config_inherit_stderr" nil ADDR)
166166
:wasi-stdin (b "wasi_config_inherit_stdin" nil ADDR)
167167
:wasi-env (b "wasi_config_inherit_env" nil ADDR)
168+
:wasi-argv (b "wasi_config_set_argv" ValueLayout/JAVA_BOOLEAN ADDR I64 ADDR)
169+
:wasi-setenv (b "wasi_config_set_env" ValueLayout/JAVA_BOOLEAN ADDR I64 ADDR ADDR)
168170
;; 0017 C: wasmtime frees what an import callback writes, so a string
169171
;; result cannot come from an Arena. libc's malloc is the allocator it
170172
;; expects, and this is the only place in the library that needs one.
@@ -916,14 +918,28 @@
916918
"Adds every WASI 0.2 interface to the linker and gives the store a context.
917919
Deny-by-default: a capability is inherited only when named, because the
918920
alternative leaks the host's environment into the guest."
919-
[api ^MemorySegment ctx clink {:keys [inherit-stdout inherit-stderr
920-
inherit-stdin inherit-env]}]
921+
[api ^Arena arena ^MemorySegment ctx clink
922+
{:keys [inherit-stdout inherit-stderr inherit-stdin inherit-env args env]}]
921923
(ok! api "add_wasip2" (invoke (:add-wasip2 api) clink))
922-
(let [cfg (invoke (:wasi-new api))]
924+
(let [cfg (invoke (:wasi-new api))
925+
strs (fn [xs]
926+
(let [a ^MemorySegment (.allocate arena (long (* 8 (max 1 (count xs)))))]
927+
(dotimes [i (count xs)]
928+
(.set a ADDR (long (* 8 i)) ^MemorySegment (cstr arena (str (nth xs i)))))
929+
a))]
923930
(when inherit-stdout (invoke (:wasi-stdout api) cfg))
924931
(when inherit-stderr (invoke (:wasi-stderr api) cfg))
925932
(when inherit-stdin (invoke (:wasi-stdin api) cfg))
926933
(when inherit-env (invoke (:wasi-env api) cfg))
934+
(when args
935+
(when-not (invoke (:wasi-argv api) cfg (long (count args)) (strs args))
936+
(throw (ex-info "wasi_config_set_argv refused the arguments"
937+
{:cljwit/error :wasi-config}))))
938+
(when env
939+
(when-not (invoke (:wasi-setenv api) cfg (long (count env))
940+
(strs (map key env)) (strs (map val env)))
941+
(throw (ex-info "wasi_config_set_env refused the environment"
942+
{:cljwit/error :wasi-config}))))
927943
;; The config is consumed even on error, so it is per-instantiate.
928944
(ok! api "set_wasi" (invoke (:set-wasi api) ctx cfg))))
929945

@@ -946,7 +962,7 @@
946962
;; report a trap with the cause gone.
947963
dead (atom nil)
948964
_ (define-imports! api arena e ctx clink ct imports dead)
949-
_ (when wasi (enable-wasi! api ctx clink wasi))
965+
_ (when wasi (enable-wasi! api arena ctx clink wasi))
950966
inst ^MemorySegment (.allocate arena (long INSTANCE))
951967
_ (ok! api "linker_instantiate"
952968
(invoke (:instantiate api) clink ctx (.-ptr art) inst))

‎test/cljwit/host_test.clj‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,3 +492,23 @@
492492
(is (integer? ((i "roll")))
493493
"naming one capability does not break the rest")))))
494494
(finally (.delete ^File f))))))
495+
496+
(deftest wasi-arguments
497+
;; `0017` E's remaining surface. `:args` and `:env` are the two that need an
498+
;; array of C strings rather than a single call, so they are the ones where
499+
;; the wiring can be wrong without the boolean capabilities noticing.
500+
(if-not lib
501+
(println "CLJWIT_WASMTIME_LIB unset — skipping wasi-args test")
502+
(let [f (build-component! "args")]
503+
(try
504+
(with-open [e (host/engine)]
505+
(with-open [a (host/compile e (io/file f))]
506+
(testing "the guest sees exactly the arguments it was given"
507+
(doseq [as [[] ["one"] ["a" "b" "c"] ["x" "y" "z" "w" "v"]]]
508+
(with-open [i (host/instantiate a {:wasi {:args as}})]
509+
(is (= (count as) ((i "argc"))) (pr-str as)))))
510+
(testing "and none when none are named — deny-by-default"
511+
(with-open [i (host/instantiate a {:wasi {}})]
512+
(is (= 0 ((i "argc")))
513+
"no :args means no arguments, not the host's")))))
514+
(finally (.delete ^File f))))))

0 commit comments

Comments
 (0)