Class: Kobako::Catalog::Handles
- Inherits:
-
Object
- Object
- Kobako::Catalog::Handles
- Defined in:
- lib/kobako/catalog/handles.rb,
sig/kobako/catalog/handles.rbs
Overview
Host-side mapping from opaque integer Handle IDs to Ruby objects.
The table is owned by Kobako::Sandbox and injected
into the per-Sandbox Kobako::Catalog::Namespaces so guest→host dispatch
resolves Handle targets and arguments against the same table that
host→guest wire encoding allocates into.
Lifecycle invariants:
- Handle IDs are allocated by a monotonically increasing counter
scoped to a single invocation. The first ID issued in an
invocation is 1; ID 0 is reserved as the invalid sentinel and is
never returned by +#alloc+.
- At every invocation boundary (via +#reset!+), every Handle issued
under the old state becomes invalid. Reset applies uniformly
regardless of allocation source (Service return or host-injected
argument).
- The cap is +0x7fff_ffff+ (2³¹ − 1). Allocation beyond the cap
raises immediately — no silent truncation, no wrap, no ID reuse.
Instance Method Summary collapse
-
#alloc(object) ⇒ Kobako::Handle
Bind
objectin the table and return aKobako::Handletoken for it. -
#ensure_capacity! ⇒ void
Guard #alloc against issuing an ID past the cap.
-
#fetch(id) ⇒ Object
Resolve a Handle ID to its bound object.
-
#initialize(next_id: 1) ⇒ Handles
constructor
Build a fresh, empty table.
-
#reject_unwrappable!(object) ⇒ void
Refuse to mint a Capability Handle for a reflective gadget: a
Binding/Method/UnboundMethodwould hand the guest a callable proxy onto host reflection (a returnedBindingreachesBinding#eval). -
#require_bound!(id) ⇒ void
Single source of truth for the "unknown Handle id" raise used by #fetch.
-
#reset! ⇒ self
Clear all entries AND reset the counter to 1.
-
#size ⇒ Integer
Number of currently-bound entries.
Constructor Details
#initialize(next_id: 1) ⇒ Handles
Build a fresh, empty table. next_id is an internal seam that
sets the starting value of the monotonic counter (defaults to 1);
tests pass a value near Kobako::Handle::MAX_ID to exercise
the cap-exhaustion path without 2³¹ allocations.
32 33 34 35 |
# File 'lib/kobako/catalog/handles.rb', line 32 def initialize(next_id: 1) @entries = {} # : Hash[Integer, untyped] @next_id = next_id end |
Instance Method Details
#alloc(object) ⇒ Kobako::Handle
Bind object in the table and return a Kobako::Handle token
for it. object is any host-side Ruby object to bind. Returns a
freshly-allocated Kobako::Handle whose #id falls in
[Kobako::Handle::MIN_ID, Kobako::Handle::MAX_ID]. Raises
Kobako::HandleExhaustedError if the next ID would exceed the
cap. The cap is anchored on Kobako::Handle — the wire codec
and the allocator share the same invariant.
Returning a Handle (rather than a bare Integer id) keeps the
allocator's output a domain entity. An id is the Handle's only
content, so the same internal Kobako::Handle.restore constructor
serves both this allocator and the codec's wire-decode path.
49 50 51 52 53 54 55 56 |
# File 'lib/kobako/catalog/handles.rb', line 49 def alloc(object) reject_unwrappable!(object) ensure_capacity! id = @next_id @entries[id] = object @next_id = id + 1 Kobako::Handle.restore(id) end |
#ensure_capacity! ⇒ void
This method returns an undefined value.
Guard #alloc against issuing an ID past the cap. Returns nil
on success; raises Kobako::HandleExhaustedError at exhaustion.
100 101 102 103 104 105 106 107 |
# File 'lib/kobako/catalog/handles.rb', line 100 def ensure_capacity! cap = Kobako::Handle::MAX_ID return unless @next_id > cap raise HandleExhaustedError, "Out of handle allocations: too many host objects were referenced " \ "in a single invocation (limit #{cap})" end |
#fetch(id) ⇒ Object
Resolve a Handle ID to its bound object. id is a Handle ID previously
returned by #alloc. Returns the bound object. Raises
Kobako::SandboxError if id is not currently bound.
61 62 63 64 |
# File 'lib/kobako/catalog/handles.rb', line 61 def fetch(id) require_bound!(id) @entries[id] end |
#reject_unwrappable!(object) ⇒ void
This method returns an undefined value.
Refuse to mint a Capability Handle for a reflective gadget:
a Binding / Method / UnboundMethod would hand the guest a
callable proxy onto host reflection (a returned Binding reaches
Binding#eval). Raising here keeps the rule at the single mint
point, so it holds on both the Service-return and the #run
host→guest auto-wrap paths.
91 92 93 94 95 96 |
# File 'lib/kobako/catalog/handles.rb', line 91 def reject_unwrappable!(object) case object when Binding, Method, UnboundMethod raise SandboxError, "a #{object.class} cannot cross as a Capability Handle" end end |
#require_bound!(id) ⇒ void
This method returns an undefined value.
Single source of truth for the "unknown Handle id" raise used by
#fetch. Returns nil on success; raises Kobako::SandboxError
when id is not currently bound.
112 113 114 115 116 |
# File 'lib/kobako/catalog/handles.rb', line 112 def require_bound!(id) return if @entries.key?(id) raise SandboxError, "unknown Handle id: #{id.inspect}" end |
#reset! ⇒ self
Clear all entries AND reset the counter to 1. Called at the
per-invocation boundary by Kobako::Sandbox. Returns self.
68 69 70 71 72 |
# File 'lib/kobako/catalog/handles.rb', line 68 def reset! @entries.clear @next_id = 1 self end |
#size ⇒ Integer
Number of currently-bound entries. Used by tests of the Dispatcher
and Codec::HandleWalk#deep_wrap to observe whether each path allocates
exactly the Handle entries it should — the Handles table itself never
consults its own size, but the surrounding code's allocation
contract is part of the observable boundary.
79 80 81 |
# File 'lib/kobako/catalog/handles.rb', line 79 def size @entries.size end |