kobako 0.27.0 → 0.29.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.
- checksums.yaml +4 -4
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +241 -0
- data/Cargo.lock +132 -126
- data/README.md +240 -147
- data/ROADMAP.md +28 -22
- data/SECURITY.md +2 -2
- data/crates/kobako-runtime/CHANGELOG.md +117 -0
- data/crates/kobako-runtime/Cargo.toml +27 -6
- data/crates/kobako-runtime/README.md +16 -10
- data/crates/kobako-runtime/src/dispatch.rs +8 -0
- data/crates/kobako-runtime/src/error.rs +8 -9
- data/crates/kobako-runtime/src/filesystem/directory/spelling.rs +194 -0
- data/crates/kobako-runtime/src/filesystem/directory.rs +1082 -0
- data/crates/kobako-runtime/src/filesystem/memory.rs +1100 -0
- data/crates/kobako-runtime/src/filesystem/overlay/exclusive.rs +88 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack.rs +808 -0
- data/crates/kobako-runtime/src/filesystem/overlay.rs +1955 -0
- data/crates/kobako-runtime/src/filesystem/path.rs +60 -0
- data/crates/kobako-runtime/src/filesystem/temporary.rs +167 -0
- data/crates/kobako-runtime/src/filesystem.rs +392 -0
- data/crates/kobako-runtime/src/grant.rs +20 -0
- data/crates/kobako-runtime/src/lib.rs +12 -2
- data/crates/kobako-runtime/src/mount.rs +178 -0
- data/crates/kobako-runtime/src/profile.rs +4 -4
- data/crates/kobako-runtime/src/runtime.rs +43 -17
- data/crates/kobako-runtime/src/snapshot.rs +33 -15
- data/crates/kobako-runtime/src/yielder.rs +14 -6
- data/crates/kobako-transport/CHANGELOG.md +45 -0
- data/crates/kobako-transport/Cargo.toml +1 -1
- data/crates/kobako-transport/README.md +21 -12
- data/crates/kobako-transport/src/abi.rs +11 -14
- data/crates/kobako-transport/src/envelope/bytes.rs +18 -6
- data/crates/kobako-transport/src/envelope/compile_reply.rs +139 -0
- data/crates/kobako-transport/src/envelope/fault.rs +2 -1
- data/crates/kobako-transport/src/envelope/outcome.rs +17 -16
- data/crates/kobako-transport/src/envelope/preamble.rs +513 -0
- data/crates/kobako-transport/src/envelope/reply.rs +103 -61
- data/crates/kobako-transport/src/envelope/request.rs +305 -0
- data/crates/kobako-transport/src/envelope/run.rs +1 -2
- data/crates/kobako-transport/src/envelope/tag.rs +49 -0
- data/crates/kobako-transport/src/envelope/unsupported.rs +105 -0
- data/crates/kobako-transport/src/envelope.rs +19 -12
- data/crates/kobako-transport/src/lib.rs +0 -2
- data/crates/kobako-wasmtime/CHANGELOG.md +163 -0
- data/crates/kobako-wasmtime/Cargo.toml +23 -16
- data/crates/kobako-wasmtime/README.md +6 -4
- data/crates/kobako-wasmtime/src/abi.rs +67 -36
- data/crates/kobako-wasmtime/src/ambient.rs +64 -5
- data/crates/kobako-wasmtime/src/cache.rs +204 -127
- data/crates/kobako-wasmtime/src/capture.rs +4 -11
- data/crates/kobako-wasmtime/src/config.rs +69 -15
- data/crates/kobako-wasmtime/src/descriptors.rs +167 -0
- data/crates/kobako-wasmtime/src/dispatch.rs +95 -87
- data/crates/kobako-wasmtime/src/driver.rs +174 -125
- data/crates/kobako-wasmtime/src/exports.rs +88 -34
- data/crates/kobako-wasmtime/src/guest_mem.rs +151 -138
- data/crates/kobako-wasmtime/src/imports.rs +46 -0
- data/crates/kobako-wasmtime/src/instance_pre.rs +19 -58
- data/crates/kobako-wasmtime/src/invocation.rs +84 -412
- data/crates/kobako-wasmtime/src/io.rs +89 -0
- data/crates/kobako-wasmtime/src/lib.rs +7 -24
- data/crates/kobako-wasmtime/src/limiter.rs +253 -0
- data/crates/kobako-wasmtime/src/preview1/args.rs +60 -0
- data/crates/kobako-wasmtime/src/preview1/beneath.rs +188 -0
- data/crates/kobako-wasmtime/src/preview1/differential/mount.rs +609 -0
- data/crates/kobako-wasmtime/src/preview1/differential.rs +372 -0
- data/crates/kobako-wasmtime/src/preview1/errno.rs +267 -0
- data/crates/kobako-wasmtime/src/preview1/fd.rs +672 -0
- data/crates/kobako-wasmtime/src/preview1/memory.rs +205 -0
- data/crates/kobako-wasmtime/src/preview1/mount_view.rs +2499 -0
- data/crates/kobako-wasmtime/src/preview1/path.rs +421 -0
- data/crates/kobako-wasmtime/src/preview1/poll.rs +333 -0
- data/crates/kobako-wasmtime/src/preview1/sock.rs +30 -0
- data/crates/kobako-wasmtime/src/preview1.rs +461 -0
- data/crates/kobako-wasmtime/src/trap.rs +66 -59
- data/crates/kobako-wasmtime/src/wasi.rs +317 -0
- data/crates/kobako-wasmtime/tests/compile_posture.rs +43 -0
- data/crates/kobako-wasmtime/tests/guest_file.rs +1054 -0
- data/crates/kobako-wasmtime/tests/preamble_flags.rs +70 -0
- data/crates/kobako-wasmtime/tests/snippet_compile.rs +95 -0
- data/crates/kobako-wasmtime/tests/support/mod.rs +30 -0
- data/crates/kobako-wasmtime/tests/unanswered_call.rs +86 -0
- data/crates/kobako-wasmtime/tests/unsupported_call.rs +51 -0
- data/data/kobako.wasm +0 -0
- data/ext/kobako/Cargo.toml +4 -2
- data/ext/kobako/src/runtime/bridge.rs +177 -113
- data/ext/kobako/src/runtime/errors.rs +26 -32
- data/ext/kobako/src/runtime/escape.rs +53 -0
- data/ext/kobako/src/runtime/filesystem/grants.rs +91 -0
- data/ext/kobako/src/runtime/filesystem/ruby.rs +400 -0
- data/ext/kobako/src/runtime/filesystem.rs +236 -0
- data/ext/kobako/src/runtime/gvl.rs +11 -13
- data/ext/kobako/src/runtime/pinned_value.rs +31 -0
- data/ext/kobako/src/runtime/snapshot.rs +127 -0
- data/ext/kobako/src/runtime.rs +190 -312
- data/lib/kobako/capture.rb +1 -4
- data/lib/kobako/catalog/extensions.rb +21 -59
- data/lib/kobako/catalog/flags.rb +29 -0
- data/lib/kobako/catalog/grants.rb +30 -0
- data/lib/kobako/catalog/handles.rb +7 -54
- data/lib/kobako/catalog/mount.rb +27 -0
- data/lib/kobako/catalog/services.rb +17 -60
- data/lib/kobako/catalog/snippets.rb +41 -69
- data/lib/kobako/catalog.rb +13 -10
- data/lib/kobako/codec/decoder.rb +10 -28
- data/lib/kobako/codec/encoder.rb +3 -22
- data/lib/kobako/codec/error.rb +6 -27
- data/lib/kobako/codec/ext_types.rb +72 -94
- data/lib/kobako/codec/handle_walk.rb +65 -130
- data/lib/kobako/codec/nesting.rb +3 -8
- data/lib/kobako/codec/state.rb +21 -11
- data/lib/kobako/codec/utils.rb +6 -34
- data/lib/kobako/codec/value_budget.rb +86 -0
- data/lib/kobako/codec.rb +10 -22
- data/lib/kobako/constant_name.rb +8 -0
- data/lib/kobako/context.rb +83 -117
- data/lib/kobako/errors.rb +57 -132
- data/lib/kobako/execution.rb +16 -21
- data/lib/kobako/extension.rb +23 -40
- data/lib/kobako/filesystem.rb +38 -0
- data/lib/kobako/handle.rb +7 -12
- data/lib/kobako/outcome.rb +51 -80
- data/lib/kobako/payload/arguments.rb +1 -16
- data/lib/kobako/payload.rb +4 -12
- data/lib/kobako/pool.rb +25 -55
- data/lib/kobako/runtime.rb +7 -21
- data/lib/kobako/sandbox.rb +128 -137
- data/lib/kobako/sandbox_options.rb +71 -90
- data/lib/kobako/snippet/bytecode.rb +11 -0
- data/lib/kobako/snippet/source.rb +1 -16
- data/lib/kobako/snippet.rb +2 -10
- data/lib/kobako/transport/block_failure.rb +35 -0
- data/lib/kobako/transport/call.rb +1 -14
- data/lib/kobako/transport/dispatcher.rb +127 -206
- data/lib/kobako/transport/error.rb +6 -20
- data/lib/kobako/transport/exposure.rb +10 -17
- data/lib/kobako/transport/reflection.rb +108 -63
- data/lib/kobako/transport/run.rb +17 -71
- data/lib/kobako/transport/yielder.rb +20 -94
- data/lib/kobako/transport.rb +4 -15
- data/lib/kobako/unresolved.rb +4 -11
- data/lib/kobako/usage.rb +2 -6
- data/lib/kobako/version.rb +1 -1
- data/lib/kobako.rb +1 -0
- data/sig/kobako/catalog/extensions.rbs +2 -2
- data/sig/kobako/catalog/flags.rbs +13 -0
- data/sig/kobako/catalog/grants.rbs +13 -0
- data/sig/kobako/catalog/mount.rbs +28 -0
- data/sig/kobako/catalog/services.rbs +3 -5
- data/sig/kobako/catalog/snippets.rbs +5 -5
- data/sig/kobako/catalog.rbs +1 -0
- data/sig/kobako/codec/ext_types.rbs +10 -8
- data/sig/kobako/codec/handle_walk.rbs +9 -7
- data/sig/kobako/codec/state.rbs +7 -1
- data/sig/kobako/codec/utils.rbs +2 -2
- data/sig/kobako/codec/value_budget.rbs +23 -0
- data/sig/kobako/codec.rbs +4 -2
- data/sig/kobako/constant_name.rbs +3 -0
- data/sig/kobako/context.rbs +15 -8
- data/sig/kobako/errors.rbs +10 -4
- data/sig/kobako/filesystem.rbs +88 -0
- data/sig/kobako/guest_yielder.rbs +9 -0
- data/sig/kobako/outcome.rbs +6 -8
- data/sig/kobako/pool.rbs +1 -1
- data/sig/kobako/runtime.rbs +21 -45
- data/sig/kobako/sandbox.rbs +11 -1
- data/sig/kobako/sandbox_options.rbs +25 -7
- data/sig/kobako/snapshot.rbs +20 -0
- data/sig/kobako/snippet/bytecode.rbs +12 -0
- data/sig/kobako/transport/block_failure.rbs +13 -0
- data/sig/kobako/transport/dispatcher.rbs +15 -19
- data/sig/kobako/transport/exposure.rbs +1 -1
- data/sig/kobako/transport/reflection.rbs +19 -4
- data/sig/kobako/transport/run.rbs +1 -3
- data/sig/kobako/transport/yielder.rbs +5 -9
- data/sig/kobako/transport.rbs +9 -8
- metadata +75 -10
- data/crates/kobako-transport/src/envelope/invocation_frames.rs +0 -241
- data/crates/kobako-wasmtime/src/frames.rs +0 -273
- data/lib/kobako/snippet/binary.rb +0 -26
- data/rust-toolchain.toml +0 -15
- data/sig/kobako/snippet/binary.rbs +0 -12
data/lib/kobako/codec/utils.rb
CHANGED
|
@@ -4,45 +4,17 @@ require_relative "error"
|
|
|
4
4
|
|
|
5
5
|
module Kobako
|
|
6
6
|
module Codec
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
#
|
|
10
|
-
# - UTF-8 assertion at the codec boundary
|
|
11
|
-
# ({docs/wire-codec.md}[link:../../../docs/wire-codec.md]
|
|
12
|
-
# § str/bin Encoding Rules and § Ext Types → ext 0x00). Used by
|
|
13
|
-
# Decoder when walking +str+ family payloads and by ExtTypes
|
|
14
|
-
# when validating the +ext 0x00+ Symbol payload.
|
|
15
|
-
# - +ArgumentError+ translation at the codec boundary
|
|
16
|
-
# (#with_boundary) so the public taxonomy stays
|
|
17
|
-
# Kobako::Codec::Error.
|
|
18
|
-
#
|
|
19
|
-
# Both helpers are pure — they only inspect inputs, never mutate them.
|
|
20
|
-
# The host↔guest Handle substitution walk lives in HandleWalk.
|
|
21
|
-
module Utils
|
|
22
|
-
module_function
|
|
23
|
-
|
|
24
|
-
# Raise InvalidEncodingError unless +string+'s bytes are valid under
|
|
25
|
-
# its current encoding tag. +label+ is the caller-supplied prefix
|
|
26
|
-
# for the error message (e.g. +"str payload"+, +"Symbol payload"+).
|
|
27
|
-
def assert_utf8!(string, label)
|
|
7
|
+
module Utils # :nodoc:
|
|
8
|
+
def self.assert_utf8!(string, label)
|
|
28
9
|
return if string.valid_encoding?
|
|
29
10
|
|
|
30
11
|
raise InvalidEncodingError, "#{label} is not valid UTF-8"
|
|
31
12
|
end
|
|
32
13
|
|
|
33
|
-
#
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
|
|
37
|
-
# the Ruby standard library.
|
|
38
|
-
#
|
|
39
|
-
# Reach for this only where a value object is constructed outside a
|
|
40
|
-
# Decoder.decode block, whose rescue already performs the same
|
|
41
|
-
# mapping (worked example: ExtTypes#unpack_handle building
|
|
42
|
-
# +Handle.restore+ from a raw fixext payload). Do not use it for
|
|
43
|
-
# general-purpose validation outside the codec boundary —
|
|
44
|
-
# host-layer +ArgumentError+ values should propagate unchanged.
|
|
45
|
-
def with_boundary
|
|
14
|
+
# Only for a value object built outside a Decoder.decode block, whose
|
|
15
|
+
# rescue already maps the same way; a host-layer +ArgumentError+
|
|
16
|
+
# elsewhere should propagate unchanged.
|
|
17
|
+
def self.with_boundary
|
|
46
18
|
yield
|
|
47
19
|
rescue ::ArgumentError => e
|
|
48
20
|
raise InvalidTypeError, e.message
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "error"
|
|
4
|
+
|
|
5
|
+
module Kobako
|
|
6
|
+
module Codec
|
|
7
|
+
# Counts the values a payload holds before the decoder builds any, so a
|
|
8
|
+
# guest cannot make the host build more objects than its budget allows
|
|
9
|
+
# (docs/wire/payload-msgpack.md § Value Budget). Every value takes at
|
|
10
|
+
# least one byte, so a payload no longer than the budget needs no count.
|
|
11
|
+
#
|
|
12
|
+
# The count stops at the first byte it cannot frame and leaves that
|
|
13
|
+
# payload for the decoder to refuse in its own words.
|
|
14
|
+
module ValueBudget # :nodoc:
|
|
15
|
+
# Marker to the body width of a scalar with no length field.
|
|
16
|
+
SCALARS = {
|
|
17
|
+
0xc0 => 0, 0xc2 => 0, 0xc3 => 0, 0xca => 4, 0xcb => 8,
|
|
18
|
+
0xcc => 1, 0xcd => 2, 0xce => 4, 0xcf => 8,
|
|
19
|
+
0xd0 => 1, 0xd1 => 2, 0xd2 => 4, 0xd3 => 8,
|
|
20
|
+
0xd4 => 2, 0xd5 => 3, 0xd6 => 5, 0xd7 => 9, 0xd8 => 17
|
|
21
|
+
}.freeze
|
|
22
|
+
# Marker to a container's count width and the values each entry holds.
|
|
23
|
+
COUNTS = { 0xdc => [2, 1], 0xdd => [4, 1], 0xde => [2, 2], 0xdf => [4, 2] }.freeze
|
|
24
|
+
# Marker to a str, bin, or ext's length width and type tag width.
|
|
25
|
+
LENGTHS = {
|
|
26
|
+
0xc4 => [1, 0], 0xc5 => [2, 0], 0xc6 => [4, 0],
|
|
27
|
+
0xd9 => [1, 0], 0xda => [2, 0], 0xdb => [4, 0],
|
|
28
|
+
0xc7 => [1, 1], 0xc8 => [2, 1], 0xc9 => [4, 1]
|
|
29
|
+
}.freeze
|
|
30
|
+
UINT = { 1 => "C", 2 => "n", 4 => "N" }.freeze
|
|
31
|
+
private_constant :SCALARS, :COUNTS, :LENGTHS, :UINT
|
|
32
|
+
|
|
33
|
+
def self.assert_within!(bytes, budget)
|
|
34
|
+
return if budget.nil? || bytes.bytesize <= budget
|
|
35
|
+
|
|
36
|
+
remaining = budget
|
|
37
|
+
pending = 1
|
|
38
|
+
pos = 0
|
|
39
|
+
while pending.positive?
|
|
40
|
+
pos, children = step(bytes, pos)
|
|
41
|
+
return if pos.nil?
|
|
42
|
+
raise InvalidTypeError, "payload holds more than #{budget} values" if (remaining -= 1).negative?
|
|
43
|
+
|
|
44
|
+
pending += children - 1
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# The position after the value at +pos+, nil for one the payload
|
|
49
|
+
# cannot frame, and the values it holds directly.
|
|
50
|
+
def self.step(bytes, pos)
|
|
51
|
+
marker = bytes.getbyte(pos) or return [nil, 0]
|
|
52
|
+
pos += 1
|
|
53
|
+
case marker
|
|
54
|
+
when 0x00..0x7f, 0xe0..0xff then [pos, 0]
|
|
55
|
+
when 0x80..0x8f then [pos, (marker & 0x0f) * 2]
|
|
56
|
+
when 0x90..0x9f then [pos, marker & 0x0f]
|
|
57
|
+
when 0xa0..0xbf then [pos + (marker & 0x1f), 0]
|
|
58
|
+
else step_framed(bytes, pos, marker)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def self.step_framed(bytes, pos, marker)
|
|
63
|
+
return [pos + SCALARS.fetch(marker), 0] if SCALARS.key?(marker)
|
|
64
|
+
|
|
65
|
+
width, per = COUNTS[marker]
|
|
66
|
+
return [pos + width, uint(bytes, pos, width) * per] if width && per
|
|
67
|
+
|
|
68
|
+
width, tag = LENGTHS[marker]
|
|
69
|
+
return [nil, 0] unless width && tag
|
|
70
|
+
|
|
71
|
+
[pos + width + tag + uint(bytes, pos, width), 0]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# A length cut short reads as zero, so the count runs off the end and
|
|
75
|
+
# stops there.
|
|
76
|
+
def self.uint(bytes, pos, width)
|
|
77
|
+
slice = bytes.byteslice(pos, width).to_s
|
|
78
|
+
return 0 if slice.bytesize < width
|
|
79
|
+
|
|
80
|
+
slice.unpack1(UINT.fetch(width)) # : Integer
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private_class_method :step, :step_framed, :uint
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
data/lib/kobako/codec.rb
CHANGED
|
@@ -5,29 +5,13 @@ require_relative "codec/utils"
|
|
|
5
5
|
require_relative "codec/handle_walk"
|
|
6
6
|
require_relative "codec/nesting"
|
|
7
7
|
require_relative "codec/state"
|
|
8
|
+
require_relative "codec/value_budget"
|
|
8
9
|
require_relative "codec/ext_types"
|
|
9
10
|
require_relative "codec/encoder"
|
|
10
11
|
require_relative "codec/decoder"
|
|
11
12
|
|
|
12
13
|
module Kobako
|
|
13
|
-
|
|
14
|
-
# byte-level layer ({docs/wire-codec.md}[link:../../docs/wire-codec.md]).
|
|
15
|
-
# This is the payload codec: the core envelope is framed natively, so
|
|
16
|
-
# what reaches here is one codec document — an invocation's arguments
|
|
17
|
-
# (+Kobako::Payload::Arguments+), a dispatch or yield value, or an
|
|
18
|
-
# invocation's value and a Panic's diagnostics
|
|
19
|
-
# (+Kobako::Outcome+). The ext-type leaves this layer
|
|
20
|
-
# carries — +Kobako::Handle+ (0x01) — lives at the kobako root so the
|
|
21
|
-
# codec can register it without depending upward on Transport.
|
|
22
|
-
#
|
|
23
|
-
# Backed by the official +msgpack+ gem: ExtTypes registers the two
|
|
24
|
-
# kobako-specific ext types (0x00 Symbol, 0x01 Capability Handle) on
|
|
25
|
-
# one process-wide +MessagePack::Factory+,
|
|
26
|
-
# and Encoder / Decoder are thin wrappers over it. The Rust side
|
|
27
|
-
# mirrors this layer as the +codec+ module in the +kobako-codec+ crate;
|
|
28
|
-
# the ext-code constants live as module-private values on ExtTypes
|
|
29
|
-
# alongside +codec::EXT_SYMBOL+ / +codec::EXT_HANDLE+ on that side.
|
|
30
|
-
module Codec
|
|
14
|
+
module Codec # :nodoc:
|
|
31
15
|
# The maximum structural nesting depth the wire represents (the
|
|
32
16
|
# MessagePack ecosystem's bound), shared with the guest +kobako_codec+
|
|
33
17
|
# so both sides cap identically. The host refuses a value nesting past
|
|
@@ -35,12 +19,16 @@ module Kobako
|
|
|
35
19
|
# packer.
|
|
36
20
|
MAX_NESTING_DEPTH = 128
|
|
37
21
|
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
# dispatch path uses to skip an all-identity Handle-resolution walk.
|
|
41
|
-
# The tracking state is codec-internal; this is its only readout.
|
|
22
|
+
# Answers the block's result with whether the decode carried a
|
|
23
|
+
# Capability Handle, so a caller can skip an all-identity restore walk.
|
|
42
24
|
def self.track_handles(&block)
|
|
43
25
|
State.current.track_handles(&block)
|
|
44
26
|
end
|
|
27
|
+
|
|
28
|
+
# Holds every decode the block runs on this thread to +budget+ values,
|
|
29
|
+
# or to none when +budget+ is nil.
|
|
30
|
+
def self.within_value_budget(budget, &block)
|
|
31
|
+
State.current.within_value_budget(budget, &block)
|
|
32
|
+
end
|
|
45
33
|
end
|
|
46
34
|
end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kobako
|
|
4
|
+
# A bind-path segment, a snippet name and a #run entrypoint all take the
|
|
5
|
+
# constant-name form, so they are refused alike. The guest interns each
|
|
6
|
+
# one, and mruby holds a symbol of at most 65534 bytes.
|
|
7
|
+
CONSTANT_NAME = /\A[A-Z]\w{0,65533}\z/ # :nodoc:
|
|
8
|
+
end
|
data/lib/kobako/context.rb
CHANGED
|
@@ -11,41 +11,30 @@ require_relative "transport"
|
|
|
11
11
|
require_relative "catalog"
|
|
12
12
|
|
|
13
13
|
module Kobako
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
# none and overlapping evals never touch each other's state.
|
|
14
|
+
# One invocation, as the block given to Sandbox#eval or Sandbox#run sees it.
|
|
15
|
+
# The block runs before the guest does, so it can choose the objects this
|
|
16
|
+
# invocation alone reaches:
|
|
17
|
+
#
|
|
18
|
+
# sandbox.eval("Req::Current.user_id") { |ctx| ctx.bind("Req::Current", request) }
|
|
20
19
|
class Context
|
|
21
|
-
|
|
22
|
-
# sealed +Catalog::Services+ / +Catalog::Snippets+ registries, and the
|
|
23
|
-
# +Catalog::Extensions+ whose callable backends this Context resolves for
|
|
24
|
-
# its own run. The +Catalog::Handles+ table is this invocation's own, so
|
|
25
|
-
# guest→host dispatch and host→guest auto-wrap share one allocator scoped
|
|
26
|
-
# to the run; the resolved provider map is likewise per-invocation, so
|
|
27
|
-
# concurrent invocations never share mutable state.
|
|
28
|
-
def initialize(runtime:, services:, snippets:, extensions:)
|
|
20
|
+
def initialize(runtime:, services:, extensions:, grants:, preamble:) # :nodoc:
|
|
29
21
|
@runtime = runtime
|
|
30
22
|
@services = services
|
|
31
|
-
@snippets = snippets
|
|
32
23
|
@extensions = extensions
|
|
24
|
+
@grants = grants
|
|
25
|
+
@preamble = preamble
|
|
33
26
|
@resolved = {} # : Hash[String, Transport::Exposure]
|
|
34
27
|
@overrides = {} # : Hash[String, Transport::Exposure]
|
|
35
|
-
@
|
|
36
|
-
@handler = Catalog::Handles.new
|
|
28
|
+
@handles = Catalog::Handles.new
|
|
37
29
|
@stdout_capture = @stderr_capture = Capture::EMPTY
|
|
38
30
|
@usage = Usage::EMPTY
|
|
39
31
|
end
|
|
40
32
|
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
# while the block runs — the Context is spent once the block returns, so a
|
|
47
|
-
# captured +ctx+ used afterward raises +ArgumentError+ too, putting both
|
|
48
|
-
# misuses on one rescuable channel. Returns +self+.
|
|
33
|
+
# Use +object+ for the Service at +path+ during this invocation only,
|
|
34
|
+
# filling a fillable Service or shadowing a bound one. Returns +self+.
|
|
35
|
+
#
|
|
36
|
+
# Raises ArgumentError when +path+ was never bound on the Sandbox, or when
|
|
37
|
+
# called after the block has returned.
|
|
49
38
|
def bind(path, object)
|
|
50
39
|
raise ArgumentError, "Kobako::Context is spent; ctx.bind is only valid inside the #eval / #run block" if @spent
|
|
51
40
|
|
|
@@ -56,16 +45,29 @@ module Kobako
|
|
|
56
45
|
self
|
|
57
46
|
end
|
|
58
47
|
|
|
59
|
-
#
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
|
|
68
|
-
|
|
48
|
+
# Use +filesystem+ for the mount at +at+ during this invocation only,
|
|
49
|
+
# with its own +access+ and block, as Sandbox#mount takes them. Returns
|
|
50
|
+
# +self+.
|
|
51
|
+
#
|
|
52
|
+
# Raises TypeError for a path that is no String or something other than
|
|
53
|
+
# a Filesystem, and ArgumentError when nothing was mounted at +at+ on the
|
|
54
|
+
# Sandbox, for a malformed path or an unknown access, or when called
|
|
55
|
+
# after the block has returned.
|
|
56
|
+
def mount(at, filesystem, access: :read, &policy)
|
|
57
|
+
raise ArgumentError, "Kobako::Context is spent; ctx.mount is only valid inside the #eval / #run block" if @spent
|
|
58
|
+
|
|
59
|
+
Runtime.validate_mount!(at, access, filesystem)
|
|
60
|
+
mount = Catalog::Mount.new(at: at, access: access, filesystem: filesystem, policy: policy)
|
|
61
|
+
raise ArgumentError, "cannot override unmounted path #{at.inspect}" unless @grants.include?(mount)
|
|
62
|
+
|
|
63
|
+
mounts = @mounts ||= {} # : Hash[String, Catalog::Mount]
|
|
64
|
+
mounts[mount.key] = mount
|
|
65
|
+
self
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# A fillable left unfilled raises like an unbound path, so the guest's
|
|
69
|
+
# call fails closed instead of reaching the sentinel.
|
|
70
|
+
def lookup(path) # :nodoc:
|
|
69
71
|
key = path.to_s
|
|
70
72
|
exposure = @overrides.fetch(key) { @resolved.fetch(key) { @services.lookup(path) } }
|
|
71
73
|
raise KeyError, "service #{path} is declared but unresolved this invocation" if Unresolved.equal?(exposure.object)
|
|
@@ -73,136 +75,100 @@ module Kobako
|
|
|
73
75
|
exposure
|
|
74
76
|
end
|
|
75
77
|
|
|
76
|
-
|
|
77
|
-
# decoded last expression. A given +block+ runs first, receiving this
|
|
78
|
-
# Context to collect +ctx.bind+ overrides before the guest drives.
|
|
79
|
-
def eval(code, &block)
|
|
78
|
+
def eval(code, &block) # :nodoc:
|
|
80
79
|
collect_overrides(&block) if block
|
|
81
|
-
invoke!(:eval)
|
|
82
|
-
@runtime.eval(dispatch_handler, @services.paths, code.b, @snippets.entries)
|
|
83
|
-
end
|
|
80
|
+
invoke!(:eval) { @runtime.eval(dispatch_handler, setup, code.b) }
|
|
84
81
|
end
|
|
85
82
|
|
|
86
|
-
|
|
87
|
-
# decoded result. A given +block+ runs first, receiving this Context to
|
|
88
|
-
# collect +ctx.bind+ overrides before the guest drives.
|
|
89
|
-
def run(request, &block)
|
|
83
|
+
def run(request, &block) # :nodoc:
|
|
90
84
|
collect_overrides(&block) if block
|
|
91
85
|
invoke!(:run, entrypoint: request.entrypoint) do
|
|
92
|
-
@runtime.run(dispatch_handler,
|
|
93
|
-
request.entrypoint.to_s, request.payload(@handler))
|
|
86
|
+
@runtime.run(dispatch_handler, setup, request.entrypoint.to_s, request.payload(@handles))
|
|
94
87
|
end
|
|
95
88
|
end
|
|
96
89
|
|
|
97
90
|
private
|
|
98
91
|
|
|
99
|
-
#
|
|
100
|
-
#
|
|
101
|
-
|
|
102
|
-
|
|
92
|
+
# What the Runtime builds the invocation from: the Preamble's parts,
|
|
93
|
+
# then this invocation's mounts.
|
|
94
|
+
def setup
|
|
95
|
+
paths, snippets, flags = @preamble
|
|
96
|
+
[paths, snippets, flags, @grants.entries(@mounts || {})]
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# The Context is spent once the block returns, so a captured +ctx+ used
|
|
100
|
+
# later raises. A block that raises propagates before the guest drives,
|
|
101
|
+
# so the guest never runs and no Execution is produced.
|
|
103
102
|
def collect_overrides
|
|
104
103
|
yield self
|
|
105
104
|
ensure
|
|
106
105
|
@spent = true
|
|
107
106
|
end
|
|
108
107
|
|
|
109
|
-
#
|
|
110
|
-
#
|
|
111
|
-
#
|
|
112
|
-
#
|
|
113
|
-
# +Runtime#eval+ / +#run+ as a call argument, so the Runtime holds no
|
|
114
|
-
# dispatch state and the +Proc+ stays GC-rooted as a live argument for the
|
|
115
|
-
# synchronous call. The ext hands the +Proc+ a per-dispatch +guest_yielder+
|
|
116
|
-
# — a +String → String+ callable that re-enters the in-flight guest to run
|
|
117
|
-
# a yielded block — which the +Dispatcher+ forwards to the
|
|
118
|
-
# +Transport::Yielder+ it builds for the call.
|
|
108
|
+
# Handed to the Runtime as a call argument, so the Runtime holds no
|
|
109
|
+
# dispatch state and the Proc stays GC-rooted for the synchronous call.
|
|
110
|
+
# Built once per invocation, so every Call it answers shares one
|
|
111
|
+
# BlockFailure.
|
|
119
112
|
def dispatch_handler
|
|
113
|
+
failure = Transport::BlockFailure.new
|
|
120
114
|
lambda do |target, method_name, block_given, payload, guest_yielder|
|
|
121
115
|
call = Transport::Call.new(target: target, method_name: method_name,
|
|
122
116
|
block_given: block_given, payload: payload)
|
|
123
|
-
Transport::Dispatcher.dispatch(call, self, @
|
|
117
|
+
Transport::Dispatcher.dispatch(call, self, @handles, failure, guest_yielder)
|
|
124
118
|
end
|
|
125
119
|
end
|
|
126
120
|
|
|
127
|
-
#
|
|
128
|
-
#
|
|
129
|
-
|
|
130
|
-
def populate_observability!(snapshot)
|
|
121
|
+
# Every Snapshot carries usage and captures, trap or not, so they stay
|
|
122
|
+
# readable after a rescued trap.
|
|
123
|
+
def populate_observability(snapshot)
|
|
131
124
|
@usage = Usage.new(wall_time: snapshot.wall_time, memory_peak: snapshot.memory_peak)
|
|
132
125
|
@stdout_capture = Capture.new(bytes: snapshot.stdout, truncated: snapshot.stdout_truncated?)
|
|
133
126
|
@stderr_capture = Capture.new(bytes: snapshot.stderr, truncated: snapshot.stderr_truncated?)
|
|
134
127
|
end
|
|
135
128
|
|
|
136
|
-
#
|
|
137
|
-
#
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
def trap_class_for(err)
|
|
141
|
-
case err
|
|
142
|
-
when TimeoutError then TimeoutError
|
|
143
|
-
when MemoryLimitError then MemoryLimitError
|
|
144
|
-
else TrapError
|
|
145
|
-
end
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
# Build the +TrapError+-family exception for a trapped +Snapshot+ from its
|
|
149
|
-
# neutral trap kind, tagged with the verb — the cap subclasses
|
|
150
|
-
# (+TimeoutError+ / +MemoryLimitError+) keep their identity, every other
|
|
151
|
-
# engine fault is the base +TrapError+.
|
|
152
|
-
def trap_error_for(snapshot, verb)
|
|
153
|
-
klass = case snapshot.trap_kind
|
|
154
|
-
when :timeout then TimeoutError
|
|
155
|
-
when :memory_limit then MemoryLimitError
|
|
156
|
-
else TrapError
|
|
157
|
-
end
|
|
158
|
-
klass.new("Sandbox##{verb} failed: #{snapshot.trap_message}")
|
|
129
|
+
# Every TrapError leaving an invocation names the verb that ran it; the
|
|
130
|
+
# cap subclasses keep their identity through the rebuild.
|
|
131
|
+
def with_verb(err, verb)
|
|
132
|
+
err.class.new("Sandbox##{verb} failed: #{err.message}")
|
|
159
133
|
end
|
|
160
134
|
|
|
161
|
-
#
|
|
162
|
-
#
|
|
163
|
-
# +failed+ records the two apart so a +nil+ +value+ from a successful run
|
|
164
|
-
# stays distinct from a failed one.
|
|
135
|
+
# +failed+ keeps a +nil+ value from a successful run apart from a failed
|
|
136
|
+
# run.
|
|
165
137
|
def build_execution(value, failed:)
|
|
166
138
|
Execution.new(value: value, usage: @usage, stdout: @stdout_capture, stderr: @stderr_capture, failed: failed)
|
|
167
139
|
end
|
|
168
140
|
|
|
169
|
-
#
|
|
170
|
-
#
|
|
171
|
-
#
|
|
172
|
-
#
|
|
173
|
-
# name +#run+ asked for, which the host knows and the wire never carries,
|
|
174
|
-
# so an unresolved one names itself on the error it raises.
|
|
141
|
+
# The settle sits in the rescue so a wire-violation trap or a Panic both
|
|
142
|
+
# attach this run's Execution, just like a guest-call trap does.
|
|
143
|
+
# +entrypoint+ is known to the host and never carried by the wire, so an
|
|
144
|
+
# unresolved one names itself on the error it raises.
|
|
175
145
|
def settle_outcome(snapshot, verb, entrypoint)
|
|
176
|
-
|
|
177
|
-
value, carried = Codec.track_handles { Outcome.reify(
|
|
178
|
-
value = Codec::HandleWalk.deep_restore(value, @
|
|
146
|
+
payload, panic = snapshot.outcome
|
|
147
|
+
value, carried = Codec.track_handles { Outcome.reify(payload, panic, entrypoint: entrypoint) }
|
|
148
|
+
value = Codec::HandleWalk.deep_restore(value, @handles) if carried
|
|
179
149
|
build_execution(value, failed: false)
|
|
180
150
|
rescue Kobako::TrapError => e
|
|
181
|
-
raise
|
|
182
|
-
failed: true))
|
|
151
|
+
raise with_verb(e, verb).with_execution(build_execution(nil, failed: true))
|
|
183
152
|
rescue Kobako::SandboxError, Kobako::ServiceError => e
|
|
184
153
|
raise e.with_execution(build_execution(nil, failed: true))
|
|
185
154
|
end
|
|
186
155
|
|
|
187
|
-
#
|
|
188
|
-
#
|
|
189
|
-
#
|
|
190
|
-
#
|
|
191
|
-
# the guest unrun. Usage and captures are recorded before the trap check,
|
|
192
|
-
# so a trapped Snapshot's error carries them just like a completed run's
|
|
193
|
-
# return value. A could-not-start fault ran no invocation at all, so it
|
|
194
|
-
# carries no Execution and gains only the verb prefix.
|
|
156
|
+
# Extension backends are resolved before the guest runs, so a provider
|
|
157
|
+
# that raises propagates unwrapped and leaves the guest unrun. A
|
|
158
|
+
# could-not-start fault ran no invocation at all, so it carries no
|
|
159
|
+
# Execution and gains only the verb prefix.
|
|
195
160
|
def invoke!(verb, entrypoint: nil)
|
|
196
161
|
@resolved = @extensions.resolve.transform_values { |object| Transport::Exposure.of(object) }
|
|
197
162
|
begin
|
|
198
163
|
snapshot = yield
|
|
199
164
|
rescue Kobako::TrapError => e
|
|
200
|
-
raise
|
|
165
|
+
raise with_verb(e, verb)
|
|
201
166
|
end
|
|
202
|
-
populate_observability
|
|
203
|
-
|
|
167
|
+
populate_observability(snapshot)
|
|
168
|
+
trap = snapshot.trap_error
|
|
169
|
+
return settle_outcome(snapshot, verb, entrypoint) unless trap
|
|
204
170
|
|
|
205
|
-
raise
|
|
171
|
+
raise with_verb(trap, verb).with_execution(build_execution(nil, failed: true))
|
|
206
172
|
end
|
|
207
173
|
end
|
|
208
174
|
end
|