mini_racer_rs 1.0.0 → 1.0.2
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 +14 -0
- data/COMPATIBILITY.md +7 -1
- data/Cargo.lock +4 -4
- data/Cargo.toml +1 -1
- data/README.md +3 -5
- data/crates/mini_racer_engine/src/context.rs +11 -6
- data/crates/mini_racer_engine/src/control.rs +24 -3
- data/crates/mini_racer_engine/src/lib.rs +2 -1
- data/crates/mini_racer_engine/src/process/code_cache.rs +26 -15
- data/crates/mini_racer_engine/src/process/fork.rs +12 -2
- data/crates/mini_racer_engine/src/process/sources.rs +539 -0
- data/crates/mini_racer_engine/src/process.rs +1 -0
- data/crates/mini_racer_engine/src/request.rs +552 -0
- data/crates/mini_racer_engine/src/runner.rs +84 -28
- data/crates/mini_racer_engine/src/serialize.rs +65 -4
- data/crates/mini_racer_engine/src/sys.rs +91 -1
- data/crates/mini_racer_wire/src/lib.rs +1 -1
- data/crates/mini_racer_wire/src/write.rs +33 -1
- data/ext/mini_racer_rs/src/context.rs +7 -5
- data/ext/mini_racer_rs/src/decode.rs +11 -4
- data/ext/mini_racer_rs/src/encode.rs +221 -69
- data/lib/mini_racer_rs/version.rb +1 -1
- metadata +3 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
use std::borrow::Cow;
|
|
1
2
|
use std::collections::hash_map::RandomState;
|
|
2
3
|
use std::hash::BuildHasher;
|
|
3
4
|
use std::panic::{AssertUnwindSafe, catch_unwind};
|
|
@@ -11,12 +12,15 @@ use mini_racer_sync::{
|
|
|
11
12
|
use mini_racer_wire::Writer;
|
|
12
13
|
use v8::ValueDeserializerHelper;
|
|
13
14
|
|
|
14
|
-
use crate::control::{
|
|
15
|
+
use crate::control::{
|
|
16
|
+
CallRequest, CallbackReply, ControlBlock, Depth, IsolateTerminator, JsText, Request,
|
|
17
|
+
};
|
|
15
18
|
use crate::fast_calls::{self, Hoisted, Shape};
|
|
16
|
-
use crate::process::code_cache::{Building, Plan};
|
|
19
|
+
use crate::process::code_cache::{Building, Key, Plan};
|
|
17
20
|
use crate::process::fork::Generation;
|
|
18
21
|
use crate::process::platform::{self, Mode};
|
|
19
22
|
use crate::process::watchdog;
|
|
23
|
+
use crate::request::{self, Read};
|
|
20
24
|
use crate::serialize::{self, Filter, Tc};
|
|
21
25
|
use crate::spin::spin_until;
|
|
22
26
|
use crate::{EngineError, JsError, JsErrorKind};
|
|
@@ -258,10 +262,9 @@ fn serve(block: &Arc<ControlBlock>, generation: &'static Generation) -> bool {
|
|
|
258
262
|
));
|
|
259
263
|
isolate.set_slot(Arc::clone(&runtime));
|
|
260
264
|
// Installed only once the context exists, so nothing terminates its setup.
|
|
261
|
-
block
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
));
|
|
265
|
+
block
|
|
266
|
+
.term
|
|
267
|
+
.install(IsolateTerminator::new(block, isolate.thread_safe_handle()));
|
|
265
268
|
let shared = match sys::share(isolate) {
|
|
266
269
|
Ok(shared) => shared,
|
|
267
270
|
Err(refused) => {
|
|
@@ -301,10 +304,9 @@ fn adopt(
|
|
|
301
304
|
locker.set_slot(Arc::clone(&runtime));
|
|
302
305
|
runtime
|
|
303
306
|
};
|
|
304
|
-
block
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
));
|
|
307
|
+
block
|
|
308
|
+
.term
|
|
309
|
+
.install(IsolateTerminator::new(block, shared.thread_safe_handle()));
|
|
308
310
|
let _ = block.isolate.set(shared.thread_safe_handle());
|
|
309
311
|
serve_isolate(block, shared, runtime)
|
|
310
312
|
}
|
|
@@ -773,10 +775,12 @@ fn evaluate(
|
|
|
773
775
|
.code_cache()
|
|
774
776
|
.plan(filename, source, runtime.snapshot)
|
|
775
777
|
} else {
|
|
776
|
-
Plan::Compile
|
|
778
|
+
Plan::Compile(None)
|
|
777
779
|
};
|
|
778
780
|
let too_large = || EngineError::Internal("source too large".to_owned());
|
|
779
|
-
let source =
|
|
781
|
+
let source = shared_source(tc, runtime, plan.key(), source)
|
|
782
|
+
.or_else(|| string(tc, source))
|
|
783
|
+
.ok_or_else(too_large)?;
|
|
780
784
|
let name = string(tc, filename).ok_or_else(too_large)?;
|
|
781
785
|
let origin = serialize::origin(tc, name.into());
|
|
782
786
|
let (script, make) = compile(tc, runtime, source, &origin, plan);
|
|
@@ -792,6 +796,25 @@ fn evaluate(
|
|
|
792
796
|
Ok(reply)
|
|
793
797
|
}
|
|
794
798
|
|
|
799
|
+
// A large script the process has seen before reads its source from the copy
|
|
800
|
+
// every context compiling it shares, outside the V8 heap. `max_memory:`
|
|
801
|
+
// measures the heap, where mini_racer keeps the source, so a context with it
|
|
802
|
+
// keeps its own copy.
|
|
803
|
+
fn shared_source<'s>(
|
|
804
|
+
scope: &v8::PinScope<'s, '_>,
|
|
805
|
+
runtime: &Runtime,
|
|
806
|
+
key: Option<&Key>,
|
|
807
|
+
source: &JsText,
|
|
808
|
+
) -> Option<v8::Local<'s, v8::String>> {
|
|
809
|
+
if runtime.block.config.max_memory.is_some() {
|
|
810
|
+
return None;
|
|
811
|
+
}
|
|
812
|
+
let sources = runtime.generation.sources();
|
|
813
|
+
let string = sys::external_string(scope, &sources.units(key?, source)?)?;
|
|
814
|
+
sources.read();
|
|
815
|
+
Some(string)
|
|
816
|
+
}
|
|
817
|
+
|
|
795
818
|
fn settle<'s>(
|
|
796
819
|
tc: &mut Tc<'s, '_, '_, '_>,
|
|
797
820
|
runtime: &Runtime,
|
|
@@ -1043,7 +1066,7 @@ fn compile<'s>(
|
|
|
1043
1066
|
plan: Plan,
|
|
1044
1067
|
) -> (Option<v8::Local<'s, v8::Script>>, Option<Building>) {
|
|
1045
1068
|
let (entry, key) = match plan {
|
|
1046
|
-
Plan::Compile => return (v8::Script::compile(scope, source, Some(origin)), None),
|
|
1069
|
+
Plan::Compile(_) => return (v8::Script::compile(scope, source, Some(origin)), None),
|
|
1047
1070
|
Plan::Make(building) => {
|
|
1048
1071
|
return (
|
|
1049
1072
|
v8::Script::compile(scope, source, Some(origin)),
|
|
@@ -1265,19 +1288,36 @@ fn read_request<'s>(
|
|
|
1265
1288
|
Some((request, path))
|
|
1266
1289
|
}
|
|
1267
1290
|
|
|
1291
|
+
enum Arguments<'s> {
|
|
1292
|
+
Built(Vec<v8::Local<'s, v8::Value>>),
|
|
1293
|
+
Read(v8::Local<'s, v8::Object>),
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1268
1296
|
// mini_racer's order: everything from the path walk on fails as a runtime
|
|
1269
1297
|
// error. Each dotted segment but the last becomes an object, and the function
|
|
1270
1298
|
// is called on its parent.
|
|
1271
1299
|
fn invoke(
|
|
1272
1300
|
tc: &mut Tc<'_, '_, '_, '_>,
|
|
1273
1301
|
runtime: &Runtime,
|
|
1274
|
-
request: &
|
|
1302
|
+
request: &CallRequest,
|
|
1275
1303
|
(depth, awaited): (Depth, bool),
|
|
1276
1304
|
) -> Result<Vec<u8>, EngineError> {
|
|
1277
1305
|
let context = tc.get_current_context();
|
|
1278
1306
|
let failed = |tc: &Tc<'_, '_, '_, '_>| failure(tc, JsErrorKind::Runtime);
|
|
1279
|
-
let (request,
|
|
1280
|
-
|
|
1307
|
+
let built = if request::worth_building(request.objects, request.properties) {
|
|
1308
|
+
request::read(tc, &request.bytes, &runtime.block.termination_requested)
|
|
1309
|
+
} else {
|
|
1310
|
+
Read::Unbuilt
|
|
1311
|
+
};
|
|
1312
|
+
let (path, arguments) = match built {
|
|
1313
|
+
Read::Built(call) => (call.path, Arguments::Built(call.args)),
|
|
1314
|
+
Read::Failed => return Err(failure(tc, JsErrorKind::Internal)),
|
|
1315
|
+
Read::Unbuilt => {
|
|
1316
|
+
let (request, path) = read_request(tc, &request.bytes)
|
|
1317
|
+
.ok_or_else(|| failure(tc, JsErrorKind::Internal))?;
|
|
1318
|
+
(Cow::Owned(path), Arguments::Read(request))
|
|
1319
|
+
}
|
|
1320
|
+
};
|
|
1281
1321
|
let mut receiver = context.global(tc);
|
|
1282
1322
|
let mut keys = path.split('.').peekable();
|
|
1283
1323
|
let mut function = None;
|
|
@@ -1296,11 +1336,17 @@ fn invoke(
|
|
|
1296
1336
|
tc.throw_exception(exception);
|
|
1297
1337
|
return Err(failed(tc));
|
|
1298
1338
|
};
|
|
1299
|
-
let
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1339
|
+
let args = match arguments {
|
|
1340
|
+
Arguments::Built(args) => args,
|
|
1341
|
+
Arguments::Read(request) => {
|
|
1342
|
+
let count = v8::Local::<v8::Array>::try_from(request).map_or(0, |array| array.length());
|
|
1343
|
+
let mut args = Vec::new();
|
|
1344
|
+
for index in 1..count {
|
|
1345
|
+
args.push(request.get_index(tc, index).ok_or_else(|| failed(tc))?);
|
|
1346
|
+
}
|
|
1347
|
+
args
|
|
1348
|
+
}
|
|
1349
|
+
};
|
|
1304
1350
|
let value = function
|
|
1305
1351
|
.call(tc, receiver.into(), &args)
|
|
1306
1352
|
.ok_or_else(|| failed(tc))?;
|
|
@@ -1372,7 +1418,7 @@ fn trampoline<'s>(
|
|
|
1372
1418
|
};
|
|
1373
1419
|
let outcome = catch_unwind(AssertUnwindSafe(|| callback(scope, &runtime, &args, rv)));
|
|
1374
1420
|
if outcome.is_err() {
|
|
1375
|
-
scope
|
|
1421
|
+
terminate(scope, &runtime);
|
|
1376
1422
|
runtime.block.channel.close();
|
|
1377
1423
|
}
|
|
1378
1424
|
}
|
|
@@ -1406,7 +1452,7 @@ fn callback<'s>(
|
|
|
1406
1452
|
return;
|
|
1407
1453
|
};
|
|
1408
1454
|
if runtime.block.channel.callback(bytes).is_err() {
|
|
1409
|
-
scope
|
|
1455
|
+
terminate(scope, runtime);
|
|
1410
1456
|
return;
|
|
1411
1457
|
}
|
|
1412
1458
|
loop {
|
|
@@ -1424,13 +1470,13 @@ fn callback<'s>(
|
|
|
1424
1470
|
handle(scope, runtime, request)
|
|
1425
1471
|
};
|
|
1426
1472
|
if runtime.block.channel.respond(response).is_err() {
|
|
1427
|
-
scope
|
|
1473
|
+
terminate(scope, runtime);
|
|
1428
1474
|
return;
|
|
1429
1475
|
}
|
|
1430
1476
|
continue;
|
|
1431
1477
|
}
|
|
1432
1478
|
None => {
|
|
1433
|
-
scope
|
|
1479
|
+
terminate(scope, runtime);
|
|
1434
1480
|
return;
|
|
1435
1481
|
}
|
|
1436
1482
|
};
|
|
@@ -1448,7 +1494,7 @@ fn callback<'s>(
|
|
|
1448
1494
|
scope.throw_exception(exception);
|
|
1449
1495
|
}
|
|
1450
1496
|
if !runtime.block.term.is_settled() {
|
|
1451
|
-
scope
|
|
1497
|
+
terminate(scope, runtime);
|
|
1452
1498
|
}
|
|
1453
1499
|
}
|
|
1454
1500
|
// mini_racer throws a raising callback's exception even with a
|
|
@@ -1463,11 +1509,11 @@ fn callback<'s>(
|
|
|
1463
1509
|
*lock(&runtime.raised) = Some(v8::Global::new(scope, exception));
|
|
1464
1510
|
scope.throw_exception(exception);
|
|
1465
1511
|
if terminating {
|
|
1466
|
-
scope
|
|
1512
|
+
terminate(scope, runtime);
|
|
1467
1513
|
}
|
|
1468
1514
|
}
|
|
1469
1515
|
_ if terminating => {
|
|
1470
|
-
scope
|
|
1516
|
+
terminate(scope, runtime);
|
|
1471
1517
|
}
|
|
1472
1518
|
// As mini_racer reads the reply: a termination pending for this
|
|
1473
1519
|
// script makes the read throw it, and the script ends here.
|
|
@@ -1485,6 +1531,16 @@ fn callback<'s>(
|
|
|
1485
1531
|
}
|
|
1486
1532
|
}
|
|
1487
1533
|
|
|
1534
|
+
// Sets the flag a runner's own reads consult before V8 is asked to terminate,
|
|
1535
|
+
// as the termination cell's requests do.
|
|
1536
|
+
fn terminate(isolate: &v8::Isolate, runtime: &Runtime) {
|
|
1537
|
+
runtime
|
|
1538
|
+
.block
|
|
1539
|
+
.termination_requested
|
|
1540
|
+
.store(true, Ordering::Release);
|
|
1541
|
+
isolate.terminate_execution();
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1488
1544
|
fn error<'s>(scope: &v8::PinScope<'s, '_>, message: &JsText) -> v8::Local<'s, v8::Value> {
|
|
1489
1545
|
let message = string(scope, message)
|
|
1490
1546
|
.or_else(|| v8::String::new(scope, "Ruby exception"))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
use mini_racer_wire::marker;
|
|
1
|
+
use mini_racer_wire::{Writer, marker};
|
|
2
2
|
|
|
3
|
-
use crate::snapshot;
|
|
3
|
+
use crate::{snapshot, sys};
|
|
4
4
|
use v8::ValueSerializerHelper;
|
|
5
5
|
|
|
6
6
|
// mini_racer's filter (mini_racer_v8.cc, MIT, Copyright (c) 2016-2019 the
|
|
@@ -59,6 +59,9 @@ pub type Tc<'a, 'b, 'c, 'i> = v8::PinnedRef<'a, v8::TryCatch<'b, 'c, v8::HandleS
|
|
|
59
59
|
pub struct Filter {
|
|
60
60
|
pub context: v8::Global<v8::Context>,
|
|
61
61
|
function: v8::Global<v8::Function>,
|
|
62
|
+
// The format version V8's serializer writes, which a reply written
|
|
63
|
+
// without it carries too.
|
|
64
|
+
version: u32,
|
|
62
65
|
}
|
|
63
66
|
|
|
64
67
|
pub fn origin<'s>(
|
|
@@ -85,7 +88,7 @@ pub fn contexts(
|
|
|
85
88
|
.flatten()
|
|
86
89
|
.unwrap_or_else(|| v8::Context::new(scope, v8::ContextOptions::default()));
|
|
87
90
|
let user = v8::Context::new(scope, v8::ContextOptions::default());
|
|
88
|
-
let function = {
|
|
91
|
+
let (function, version) = {
|
|
89
92
|
let scope = &mut v8::ContextScope::new(scope, safe);
|
|
90
93
|
let source = v8::String::new(scope, FILTER_SOURCE)?;
|
|
91
94
|
let name = v8::String::new(scope, "safe_context_script.js")?;
|
|
@@ -97,17 +100,32 @@ pub fn contexts(
|
|
|
97
100
|
let global = user.global(scope).into();
|
|
98
101
|
let filter = setup.call(scope, receiver, &[global]);
|
|
99
102
|
safe.use_default_security_token();
|
|
100
|
-
v8::Local::<v8::Function>::try_from(filter?).ok()
|
|
103
|
+
let filter = v8::Local::<v8::Function>::try_from(filter?).ok()?;
|
|
104
|
+
(filter, wire_version(scope)?)
|
|
101
105
|
};
|
|
102
106
|
Some((
|
|
103
107
|
v8::Global::new(scope, user),
|
|
104
108
|
Filter {
|
|
105
109
|
context: v8::Global::new(scope, safe),
|
|
106
110
|
function: v8::Global::new(scope, function),
|
|
111
|
+
version,
|
|
107
112
|
},
|
|
108
113
|
))
|
|
109
114
|
}
|
|
110
115
|
|
|
116
|
+
fn wire_version(scope: &v8::PinScope<'_, '_>) -> Option<u32> {
|
|
117
|
+
let bytes = write(scope, v8::undefined(scope).into())?;
|
|
118
|
+
let (_, header) = bytes.split_first()?;
|
|
119
|
+
let mut version = 0u32;
|
|
120
|
+
for (shift, byte) in (0..32).step_by(7).zip(header) {
|
|
121
|
+
version |= u32::from(byte & 0x7f).checked_shl(shift)?;
|
|
122
|
+
if byte & 0x80 == 0 {
|
|
123
|
+
return Some(version);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
None
|
|
127
|
+
}
|
|
128
|
+
|
|
111
129
|
/// What `mini_racer` does to a result before serializing it: proxies give way
|
|
112
130
|
/// to their targets, symbols to their descriptions, functions to the marker
|
|
113
131
|
/// its Ruby side reads as `JavaScriptFunction`, and weak collections and
|
|
@@ -189,6 +207,46 @@ impl v8::ValueSerializerImpl for Delegate {
|
|
|
189
207
|
}
|
|
190
208
|
}
|
|
191
209
|
|
|
210
|
+
// The bytes V8's serializer writes for a primitive, written without it: for
|
|
211
|
+
// these values it checks for no termination and runs no script, and the
|
|
212
|
+
// microtasks it would run as it returns ran as the script returned. `None` for
|
|
213
|
+
// anything else, and for a NaN, whose bits V8 copies unchanged.
|
|
214
|
+
fn primitive(
|
|
215
|
+
isolate: &mut v8::Isolate,
|
|
216
|
+
version: u32,
|
|
217
|
+
value: v8::Local<'_, v8::Value>,
|
|
218
|
+
) -> Option<Vec<u8>> {
|
|
219
|
+
let mut out = Vec::new();
|
|
220
|
+
let mut writer = Writer::versioned(&mut out, version);
|
|
221
|
+
if sys::is_smi(&value) {
|
|
222
|
+
let small = v8::Local::<v8::Int32>::try_from(value).ok()?;
|
|
223
|
+
writer.integer(small.value().into());
|
|
224
|
+
} else if let Ok(number) = v8::Local::<v8::Number>::try_from(value) {
|
|
225
|
+
let number = number.value();
|
|
226
|
+
if number.is_nan() {
|
|
227
|
+
return None;
|
|
228
|
+
}
|
|
229
|
+
writer.number(number);
|
|
230
|
+
} else if let Ok(string) = v8::Local::<v8::String>::try_from(value) {
|
|
231
|
+
let view = v8::ValueView::new(isolate, string);
|
|
232
|
+
match view.data() {
|
|
233
|
+
v8::ValueViewData::OneByte(bytes) => writer.latin1(bytes),
|
|
234
|
+
v8::ValueViewData::TwoByte(units) => writer.utf16_aligned(units),
|
|
235
|
+
}
|
|
236
|
+
} else if value.is_undefined() {
|
|
237
|
+
writer.undefined();
|
|
238
|
+
} else if value.is_null() {
|
|
239
|
+
writer.null();
|
|
240
|
+
} else if value.is_true() {
|
|
241
|
+
writer.bool(true);
|
|
242
|
+
} else if value.is_false() {
|
|
243
|
+
writer.bool(false);
|
|
244
|
+
} else {
|
|
245
|
+
return None;
|
|
246
|
+
}
|
|
247
|
+
Some(out)
|
|
248
|
+
}
|
|
249
|
+
|
|
192
250
|
/// Serializes `value` with no filter retry; `None` leaves an exception pending.
|
|
193
251
|
pub fn write(scope: &v8::PinScope<'_, '_>, value: v8::Local<'_, v8::Value>) -> Option<Vec<u8>> {
|
|
194
252
|
let context = scope.get_current_context();
|
|
@@ -213,6 +271,9 @@ pub fn reply(
|
|
|
213
271
|
// mini_racer serializes in a TryCatch of its own, which is not verbose, and
|
|
214
272
|
// rethrows from it; V8 never reports a rethrown exception.
|
|
215
273
|
tc.set_verbose(false);
|
|
274
|
+
if let Some(bytes) = primitive(tc, filter.version, value) {
|
|
275
|
+
return Some(bytes);
|
|
276
|
+
}
|
|
216
277
|
if let Some(bytes) = write(tc, value) {
|
|
217
278
|
return Some(bytes);
|
|
218
279
|
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
use std::ffi::c_void;
|
|
1
|
+
use std::ffi::{c_char, c_void};
|
|
2
2
|
use std::ptr::NonNull;
|
|
3
|
+
use std::sync::Arc;
|
|
3
4
|
use std::time::Duration;
|
|
4
5
|
|
|
6
|
+
use crate::process::sources::Units;
|
|
7
|
+
|
|
5
8
|
// The prebuilt V8 archive marks every heap mapping MADV_DONTFORK, so a forked
|
|
6
9
|
// child would lose the isolate's memory. `-Wl,--wrap=madvise` (build.rs) routes
|
|
7
10
|
// V8's calls here; this drops that one advice and forwards the rest unchanged.
|
|
@@ -47,6 +50,77 @@ pub fn startup_data(blob: &crate::SnapshotBlob) -> v8::StartupData {
|
|
|
47
50
|
v8::StartupData::from(bytes)
|
|
48
51
|
}
|
|
49
52
|
|
|
53
|
+
/// A string V8 reads from `units` in place. It holds a reference to them,
|
|
54
|
+
/// which V8 hands back through `release_one_byte` or `release_two_byte` once,
|
|
55
|
+
/// on whatever thread it runs that on: when the string dies in a collection
|
|
56
|
+
/// or its isolate's teardown, or when V8 internalizes it while it lives and
|
|
57
|
+
/// gives the units to the internalized string, to release when that one dies,
|
|
58
|
+
/// or releases them at once if that string has its own (`MigrateExternalString`
|
|
59
|
+
/// in V8's `string.cc`). `None` past V8's length limit.
|
|
60
|
+
pub fn external_string<'s>(
|
|
61
|
+
scope: &v8::PinScope<'s, '_>,
|
|
62
|
+
units: &Units,
|
|
63
|
+
) -> Option<v8::Local<'s, v8::String>> {
|
|
64
|
+
// V8 refuses a resource past its limit without disposing of it, which
|
|
65
|
+
// would keep the reference for good, so it never gets one.
|
|
66
|
+
match units {
|
|
67
|
+
Units::OneByte(bytes) if bytes.len() <= v8::String::MAX_LENGTH => {
|
|
68
|
+
let data = Arc::into_raw(Arc::clone(bytes));
|
|
69
|
+
// SAFETY: the bytes stay where they are, unchanged, while the
|
|
70
|
+
// reference made here lives; V8 only reads them, and hands the
|
|
71
|
+
// reference back through `release_one_byte`, with this pointer and
|
|
72
|
+
// length, once the string dies.
|
|
73
|
+
unsafe {
|
|
74
|
+
v8::String::new_external_onebyte_raw(
|
|
75
|
+
scope,
|
|
76
|
+
data.cast::<c_char>().cast_mut(),
|
|
77
|
+
bytes.len(),
|
|
78
|
+
release_one_byte,
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
Units::TwoByte(units) if units.len() <= v8::String::MAX_LENGTH => {
|
|
83
|
+
let data = Arc::into_raw(Arc::clone(units));
|
|
84
|
+
// SAFETY: as for one byte a unit, through `release_two_byte`.
|
|
85
|
+
unsafe {
|
|
86
|
+
v8::String::new_external_twobyte_raw(
|
|
87
|
+
scope,
|
|
88
|
+
data.cast::<u16>().cast_mut(),
|
|
89
|
+
units.len(),
|
|
90
|
+
release_two_byte,
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
Units::OneByte(_) | Units::TwoByte(_) => None,
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Each runs once per string `external_string` made, and only releases its
|
|
99
|
+
// reference: the list in `process::sources` holds another for as long as any
|
|
100
|
+
// string reads them, so the units are never freed here.
|
|
101
|
+
unsafe extern "C" fn release_one_byte(data: *mut c_char, length: usize) {
|
|
102
|
+
let released = std::panic::catch_unwind(|| {
|
|
103
|
+
let bytes = std::ptr::slice_from_raw_parts(data.cast::<u8>().cast_const(), length);
|
|
104
|
+
// SAFETY: V8 passes back the pointer and length `external_string`
|
|
105
|
+
// gave it, once, so this releases the one reference it made.
|
|
106
|
+
unsafe { Arc::decrement_strong_count(bytes) };
|
|
107
|
+
});
|
|
108
|
+
if released.is_err() {
|
|
109
|
+
std::process::abort();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
unsafe extern "C" fn release_two_byte(data: *mut u16, length: usize) {
|
|
114
|
+
let released = std::panic::catch_unwind(|| {
|
|
115
|
+
let units = std::ptr::slice_from_raw_parts(data.cast_const(), length);
|
|
116
|
+
// SAFETY: as in `release_one_byte`.
|
|
117
|
+
unsafe { Arc::decrement_strong_count(units) };
|
|
118
|
+
});
|
|
119
|
+
if released.is_err() {
|
|
120
|
+
std::process::abort();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
50
124
|
/// The address V8 names `isolate` by when it posts the isolate a task.
|
|
51
125
|
#[must_use]
|
|
52
126
|
pub fn isolate_key(isolate: &v8::Isolate) -> usize {
|
|
@@ -58,6 +132,22 @@ pub fn isolate_key(isolate: &v8::Isolate) -> usize {
|
|
|
58
132
|
pointer.addr()
|
|
59
133
|
}
|
|
60
134
|
|
|
135
|
+
// V8 tags a small integer's word with a clear low bit and a heap object's with
|
|
136
|
+
// a set one.
|
|
137
|
+
const SMI_TAG_MASK: usize = 1;
|
|
138
|
+
|
|
139
|
+
/// Whether `value` is a small integer, which V8's serializer writes as an
|
|
140
|
+
/// int32; a heap number it writes as a double, even one holding an int32.
|
|
141
|
+
#[must_use]
|
|
142
|
+
pub const fn is_smi(value: &v8::Value) -> bool {
|
|
143
|
+
let slot: *const v8::Value = value;
|
|
144
|
+
// SAFETY: rusty_v8 builds V8 without direct handles, so a reference to a
|
|
145
|
+
// value is the address of its handle slot, one aligned tagged word that
|
|
146
|
+
// lives as long as the reference.
|
|
147
|
+
let tagged = unsafe { slot.cast::<usize>().read() };
|
|
148
|
+
tagged & SMI_TAG_MASK == 0
|
|
149
|
+
}
|
|
150
|
+
|
|
61
151
|
/// Has V8 report each garbage collection on `isolate`, so its runner can
|
|
62
152
|
/// check the heap against `max_memory`.
|
|
63
153
|
pub fn watch_heap(isolate: &mut v8::Isolate) {
|
|
@@ -20,7 +20,7 @@ pub use write::{WriteError, Writer};
|
|
|
20
20
|
pub const READ_VERSIONS: RangeInclusive<u32> = 13..=16;
|
|
21
21
|
pub const WRITE_VERSION: u32 = 15;
|
|
22
22
|
|
|
23
|
-
mod tag {
|
|
23
|
+
pub mod tag {
|
|
24
24
|
pub const HEADER: u8 = 0xFF;
|
|
25
25
|
pub const PADDING: u8 = 0x00;
|
|
26
26
|
pub const VERIFY_OBJECT_COUNT: u8 = b'?';
|
|
@@ -22,10 +22,16 @@ pub struct Writer<'a> {
|
|
|
22
22
|
impl<'a> Writer<'a> {
|
|
23
23
|
/// Starts a frame: clears `out` and writes the header.
|
|
24
24
|
pub fn new(out: &'a mut Vec<u8>) -> Self {
|
|
25
|
+
Self::versioned(out, WRITE_VERSION)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/// Starts a frame under another format version, such as the one V8's own
|
|
29
|
+
/// serializer writes, for bytes that must match its output.
|
|
30
|
+
pub fn versioned(out: &'a mut Vec<u8>, version: u32) -> Self {
|
|
25
31
|
out.clear();
|
|
26
32
|
out.push(tag::HEADER);
|
|
27
33
|
let mut writer = Self { out, next_id: 0 };
|
|
28
|
-
writer.varint(
|
|
34
|
+
writer.varint(version.into());
|
|
29
35
|
writer
|
|
30
36
|
}
|
|
31
37
|
|
|
@@ -131,6 +137,32 @@ impl<'a> Writer<'a> {
|
|
|
131
137
|
self.out.extend_from_slice(bytes);
|
|
132
138
|
}
|
|
133
139
|
|
|
140
|
+
/// A two-byte string as V8's serializer writes one: after a padding byte
|
|
141
|
+
/// wherever its characters would otherwise start at an odd offset.
|
|
142
|
+
pub fn utf16_aligned(&mut self, units: &[u16]) {
|
|
143
|
+
let bytes = units.len().saturating_mul(2);
|
|
144
|
+
let mut length_bytes = 1usize;
|
|
145
|
+
let mut rest = bytes >> 7;
|
|
146
|
+
while rest > 0 {
|
|
147
|
+
length_bytes = length_bytes.saturating_add(1);
|
|
148
|
+
rest >>= 7;
|
|
149
|
+
}
|
|
150
|
+
let start = self
|
|
151
|
+
.out
|
|
152
|
+
.len()
|
|
153
|
+
.saturating_add(1)
|
|
154
|
+
.saturating_add(length_bytes);
|
|
155
|
+
if !start.is_multiple_of(2) {
|
|
156
|
+
self.out.push(tag::PADDING);
|
|
157
|
+
}
|
|
158
|
+
self.out.push(tag::TWO_BYTE_STRING);
|
|
159
|
+
self.len(bytes);
|
|
160
|
+
self.out.reserve(bytes);
|
|
161
|
+
for unit in units {
|
|
162
|
+
self.out.extend_from_slice(&unit.to_le_bytes());
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
134
166
|
/// Milliseconds since the Unix epoch; non-finite times become NaN.
|
|
135
167
|
pub fn date(&mut self, millis: f64) -> u32 {
|
|
136
168
|
self.out.push(tag::DATE);
|
|
@@ -298,11 +298,13 @@ impl Context {
|
|
|
298
298
|
|
|
299
299
|
fn invoke(ruby: &Ruby, rb_self: &Self, args: &[Value], awaited: bool) -> Result<Value, Error> {
|
|
300
300
|
let handle = rb_self.handle(ruby)?;
|
|
301
|
-
let
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
301
|
+
let Some((&name, args)) = args.split_first() else {
|
|
302
|
+
// The arity error mini_racer's rb_scan_args raises.
|
|
303
|
+
scan_args::<(Value,), (), RArray, (), (), ()>(args)?;
|
|
304
|
+
return Err(errors::internal(ruby, "no function name"));
|
|
305
|
+
};
|
|
306
|
+
let name = rb_raw::expect_string(ruby, name)?;
|
|
307
|
+
let request = encode::call(ruby, name, args, |message| {
|
|
306
308
|
errors::runtime(ruby, [b"Context.call: ".as_slice(), &message].concat())
|
|
307
309
|
})?;
|
|
308
310
|
let bytes = rb_self.dispatch(ruby, |host| {
|
|
@@ -87,10 +87,11 @@ struct Pending {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
// The visitor and its frames live on the native stack for the whole decode,
|
|
90
|
-
// where Ruby's GC finds them (I3); `refs` holds every value V8 gave an id
|
|
90
|
+
// where Ruby's GC finds them (I3); `refs` holds every value V8 gave an id,
|
|
91
|
+
// made at the first, so a result without one allocates none.
|
|
91
92
|
struct RubyVisitor<'r> {
|
|
92
93
|
ruby: &'r Ruby,
|
|
93
|
-
refs: RArray
|
|
94
|
+
refs: Option<RArray>,
|
|
94
95
|
pending: Pending,
|
|
95
96
|
}
|
|
96
97
|
|
|
@@ -359,12 +360,18 @@ impl Visitor for RubyVisitor<'_> {
|
|
|
359
360
|
}
|
|
360
361
|
|
|
361
362
|
fn register(&mut self, id: u32, value: Value) -> Result<(), Error> {
|
|
363
|
+
let ruby = self.ruby;
|
|
362
364
|
self.refs
|
|
365
|
+
.get_or_insert_with(|| ruby.ary_new())
|
|
363
366
|
.store(isize::try_from(id).unwrap_or(isize::MAX), value)
|
|
364
367
|
}
|
|
365
368
|
|
|
369
|
+
// The decoder refers back only to an id it saw registered.
|
|
366
370
|
fn back_ref(&mut self, id: u32) -> Result<Value, Error> {
|
|
367
|
-
self.refs.
|
|
371
|
+
self.refs.map_or_else(
|
|
372
|
+
|| self.bad_state(),
|
|
373
|
+
|refs| refs.entry(isize::try_from(id).unwrap_or(isize::MAX)),
|
|
374
|
+
)
|
|
368
375
|
}
|
|
369
376
|
|
|
370
377
|
fn push_int32s(&mut self, frame: &mut Frame, values: &[i32]) -> Result<(), Error> {
|
|
@@ -416,7 +423,7 @@ const RESULT_LIMITS: Limits = Limits {
|
|
|
416
423
|
pub fn value(ruby: &Ruby, bytes: &[u8]) -> Result<Value, Error> {
|
|
417
424
|
let mut visitor = RubyVisitor {
|
|
418
425
|
ruby,
|
|
419
|
-
refs:
|
|
426
|
+
refs: None,
|
|
420
427
|
pending: Pending {
|
|
421
428
|
target: None,
|
|
422
429
|
values: [ruby.qnil().as_value(); BATCH],
|