rusty_racer 0.2.5 → 0.2.6
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/CHANGELOG.md +60 -0
- data/README.md +62 -0
- data/ext/rusty_racer/Cargo.toml +6 -1
- data/ext/rusty_racer/src/lib.rs +267 -77
- data/ext/rusty_racer/src/marshal.rs +356 -117
- data/ext/rusty_racer/src/ops.rs +244 -89
- data/lib/rusty_racer/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ebbfddd8204b62eed228d53d476247e3b76bf08a894b02fb3e8ffe78836b6b02
|
|
4
|
+
data.tar.gz: 77d0190f0387cbdf240d49ef4ac3b75c1550a18a74de8ceed78a8c97c1956d96
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e0a3df4af6404d9357a008e2adcb4d8303a39db35ad9d3d4ef0240217222bd0fd0be40c9b18b36b9778e30e9eac8a5ce9b20514ecc962975cc25b1466d504aac
|
|
7
|
+
data.tar.gz: 497c28687de4bc14c7b666e99a3205406e84384459b923dca24b8d2080db552d3266bbffbfbecfb0a4e61bde9c17aa4413f39e321d1c866339df6944782c3930
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.6
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `Context#function(path)` returns a `RustyRacer::Function`: the function a
|
|
8
|
+
dotted path names, resolved once, with the object it was read off kept as its
|
|
9
|
+
`this`. `#call` / `#call_void` invoke it as `Context#call` / `#call_void`
|
|
10
|
+
would — same marshalling, timeout, errors and re-entrancy — without walking
|
|
11
|
+
the path or making a property name per call. It raises `DisposedError` once
|
|
12
|
+
its realm is reset or disposed and never keeps that realm alive; `#dispose`
|
|
13
|
+
releases it early, and garbage collection releases it too.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- Ruby → JS marshalling no longer runs the page's JavaScript. Arrays and
|
|
18
|
+
objects built for JS have their elements and entries defined
|
|
19
|
+
(`CreateDataProperty`) rather than assigned, so an accessor on
|
|
20
|
+
`Array.prototype` or `Object.prototype` can no longer intercept or swallow
|
|
21
|
+
call arguments, host function results or the host namespace's members. A
|
|
22
|
+
`"__proto__"` key in a Hash now becomes an own property of the object, as
|
|
23
|
+
`JSON.parse` makes it, instead of setting its prototype.
|
|
24
|
+
- Ruby → JS marshalling dispatches on the value's built-in type first: a String,
|
|
25
|
+
Symbol, Array or Hash argument no longer pays for a raised-and-rescued
|
|
26
|
+
conversion probe or a constant lookup (a String argument cost ~1.2 µs more
|
|
27
|
+
than none; now ~0.07 µs). Subclasses, `to_f` (an `ActiveSupport::Duration`),
|
|
28
|
+
`to_str`, `to_ary` and `to_hash` conversions marshal as before — except that
|
|
29
|
+
an Array or Hash subclass is now always an array or an object, where one that
|
|
30
|
+
also defined `to_str` (or a Hash subclass `to_ary`) used to cross as that.
|
|
31
|
+
- A getter or `Proxy` trap that throws while `Context#call` resolves its path
|
|
32
|
+
now raises a `RuntimeError` carrying the JS stack, and the thrown value
|
|
33
|
+
propagates through a host function like any other JS throw.
|
|
34
|
+
- A `Module` or `Script` whose wrapper is garbage-collected without `#dispose`
|
|
35
|
+
is released too (at the end of the isolate's next operation), where 0.2.5 kept
|
|
36
|
+
it until its realm was reset or disposed. `#dispose` itself now only queues
|
|
37
|
+
the release, as `Function#dispose` does, so it works from any thread — in
|
|
38
|
+
0.2.5 a dispose from another thread marked the wrapper disposed but silently
|
|
39
|
+
kept the module or script for good.
|
|
40
|
+
- A module's url (its `import.meta.url`, its url as a referrer) is kept for as
|
|
41
|
+
long as V8 keeps the module, not as long as its wrapper or its realm: a
|
|
42
|
+
dependency the resolver compiled and Ruby dropped or disposed, or a function
|
|
43
|
+
of a disposed realm's module that another realm still holds, still reads it.
|
|
44
|
+
In 0.2.5 either read `undefined`.
|
|
45
|
+
|
|
46
|
+
### Documented
|
|
47
|
+
|
|
48
|
+
- What reading a JS value into Ruby runs (getters, `Proxy` traps, holes read
|
|
49
|
+
through the prototype chain, `toString`) and the binary-String ↔ `Uint8Array`
|
|
50
|
+
rule — README, "Marshalling and the page's JavaScript".
|
|
51
|
+
|
|
52
|
+
### Deliberately unchanged
|
|
53
|
+
|
|
54
|
+
- JS → Ruby still renders a value with no structural form — a function, a
|
|
55
|
+
RegExp, a depth-truncated object — through its ToString, which a page can
|
|
56
|
+
override (`Function.prototype.toString`) to run code or throw during the read.
|
|
57
|
+
V8's side-effect-free renderer (`Value::ToDetailString`, already used for
|
|
58
|
+
Symbols) would run nothing, but it truncates a function's source past 128
|
|
59
|
+
characters and renders objects as `#<Object>`, so switching is a behaviour
|
|
60
|
+
change left for a release that decides it on purpose.
|
data/README.md
CHANGED
|
@@ -67,6 +67,11 @@ ctx.call("add", 20, 22) # => 42
|
|
|
67
67
|
ctx.call_void("doSideEffect") # runs it; never marshals the return
|
|
68
68
|
ctx.eval_void("globalThis.app = boot()") # ditto for the completion value
|
|
69
69
|
|
|
70
|
+
# Resolve a function once and keep it: no path walk or name per call, and its
|
|
71
|
+
# holder stays its `this` (see "Function handles" below).
|
|
72
|
+
add = ctx.function("add")
|
|
73
|
+
add.call(20, 22) # => 42
|
|
74
|
+
|
|
70
75
|
# Ruby callbacks into JS; a raised Ruby exception becomes a JS exception.
|
|
71
76
|
ctx.attach("rubyUpcase", ->(s) { s.upcase })
|
|
72
77
|
ctx.eval("rubyUpcase('hi')") # => "HI"
|
|
@@ -96,6 +101,63 @@ make a plain `#eval` fail after every one of its writes has landed.
|
|
|
96
101
|
`#eval_primitive` sits between the two: it returns the completion value only
|
|
97
102
|
where it is a primitive, and nil for an object or a symbol, which it never reads.
|
|
98
103
|
|
|
104
|
+
### Function handles
|
|
105
|
+
|
|
106
|
+
`Context#function(path)` resolves a dotted path (`"app.router.go"`) the way
|
|
107
|
+
`#call` does — each segment an ordinary property read off the context's
|
|
108
|
+
`globalThis` — once, and returns a `RustyRacer::Function` bound to what it found:
|
|
109
|
+
the function, and the object it was read off as its `this`. `#call(*args)` and
|
|
110
|
+
`#call_void(*args)` then invoke it exactly as `Context#call` / `#call_void` would
|
|
111
|
+
— same argument and result marshalling, same timeout, same errors, and they work
|
|
112
|
+
from inside a host function like any other op — without walking the path or
|
|
113
|
+
making a property name per call. Reassigning or deleting the path afterwards
|
|
114
|
+
does not change what the handle calls.
|
|
115
|
+
|
|
116
|
+
A handle lives as long as its realm does. Once its context is `reset` or
|
|
117
|
+
`dispose`d (or the isolate is), `#call` raises `RustyRacer::DisposedError`, and
|
|
118
|
+
the handle no longer keeps that realm alive. (`#disposed?` answers for the handle
|
|
119
|
+
and its isolate only: a realm's reset is found by the next call.) `#dispose` lets
|
|
120
|
+
go of one early, and a handle that is simply garbage-collected is released too,
|
|
121
|
+
at the end of the isolate's next operation. A path that leads into *another*
|
|
122
|
+
realm (`frames[0].fn` in an embedder whose realms see each other) holds that
|
|
123
|
+
realm's function for as long as the handle's own realm lives, as any reference
|
|
124
|
+
kept there would.
|
|
125
|
+
|
|
126
|
+
### Releasing handles
|
|
127
|
+
|
|
128
|
+
A `Function`, `Script` or `Module` handle is released when it is `#dispose`d —
|
|
129
|
+
from any thread — or garbage-collected, at the end of the isolate's next
|
|
130
|
+
operation, and with its realm's `reset` or `dispose` at the latest. Releasing a
|
|
131
|
+
handle only drops the isolate's hold on what it names: functions a script
|
|
132
|
+
defined keep working, and a module V8 still keeps — linked into a live graph, or
|
|
133
|
+
reached from another realm — keeps its `import.meta.url` and its url as a
|
|
134
|
+
referrer, wrapper or realm or not.
|
|
135
|
+
|
|
136
|
+
### Marshalling and the page's JavaScript
|
|
137
|
+
|
|
138
|
+
Values cross by copy, not by reference.
|
|
139
|
+
|
|
140
|
+
- **Ruby → JS builds new values without running any JavaScript.** An array's
|
|
141
|
+
elements and an object's entries are *defined* on the new container
|
|
142
|
+
(`CreateDataProperty`), never assigned, so an accessor a script has put on
|
|
143
|
+
`Array.prototype[0]` or `Object.prototype.x` neither sees nor swallows them,
|
|
144
|
+
and a `"__proto__"` key is an own property, as `JSON.parse` makes it. Maps and
|
|
145
|
+
Sets are filled by V8's original builtins, whatever `Map.prototype.set` is now.
|
|
146
|
+
- **JS → Ruby reads values the way a script would.** An object's own enumerable
|
|
147
|
+
string keys are listed and each is read with an ordinary `[[Get]]`, so a getter
|
|
148
|
+
runs; a `Proxy` is read through its `ownKeys`, `getOwnPropertyDescriptor` and
|
|
149
|
+
`get` traps; an array's holes read through its prototype chain, as `arr[i]`
|
|
150
|
+
does; a function, a RegExp or any other value with no structural form becomes
|
|
151
|
+
its `toString()`, which may be the page's. A throw anywhere in that fails the op
|
|
152
|
+
(see above), and `#call_void` / `#eval_void` skip the read entirely. Map and Set
|
|
153
|
+
contents, typed arrays and buffers are read without running JavaScript.
|
|
154
|
+
- **A String's encoding tag is its type.** A binary (`ASCII-8BIT`) String crosses
|
|
155
|
+
as a `Uint8Array`, and a `Uint8Array`, `ArrayBuffer` or `SharedArrayBuffer`
|
|
156
|
+
comes back as a binary String. Any text encoding crosses as a JS string, and
|
|
157
|
+
must hold valid characters — invalid UTF-8 raises `EncodingError` rather than
|
|
158
|
+
turn into U+FFFD — so text read with `File.binread` or off a socket needs its
|
|
159
|
+
real encoding (`force_encoding`) before it crosses as text.
|
|
160
|
+
|
|
99
161
|
ES modules (the embedder owns the URL→module registry):
|
|
100
162
|
|
|
101
163
|
```ruby
|
data/ext/rusty_racer/Cargo.toml
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# links and loads with no custom archive.
|
|
6
6
|
[package]
|
|
7
7
|
name = "rusty_racer"
|
|
8
|
-
version = "0.2.
|
|
8
|
+
version = "0.2.6"
|
|
9
9
|
edition = "2024"
|
|
10
10
|
publish = false
|
|
11
11
|
|
|
@@ -31,3 +31,8 @@ v8 = { version = "150.4.0", features = ["simdutf"] }
|
|
|
31
31
|
# pthread stack-bounds query, to set V8's stack limit per op relative to the
|
|
32
32
|
# current native thread (in-thread V8 runs on the Ruby thread's stack).
|
|
33
33
|
libc = "0.2"
|
|
34
|
+
|
|
35
|
+
# `cargo test` links libruby: the crate calls into it, though the marshalling tests
|
|
36
|
+
# that run without a VM never do.
|
|
37
|
+
[dev-dependencies]
|
|
38
|
+
rb-sys = { version = "0.9", features = ["link-ruby"] }
|