mini_racer_rs 1.0.3 → 1.0.5

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.
@@ -9,6 +9,7 @@ use crate::process::code_cache::Key;
9
9
  const MAX_SOURCES: usize = 8;
10
10
  const MAX_BYTES: usize = 256 << 20;
11
11
  const MAX_REFUSED: usize = 8;
12
+ const MAX_SWEPT: usize = 8;
12
13
 
13
14
  /// A large script's source as V8 stores a string of it: a byte a unit when
14
15
  /// every unit fits Latin-1, two otherwise. Never changed once made.
@@ -63,8 +64,9 @@ impl Units {
63
64
 
64
65
  /// One copy of each large script's source, outside every V8 heap.
65
66
  ///
66
- /// A script is kept once the process has seen it before, and every context
67
- /// compiling it then reads the kept copy in place of one on its own heap.
67
+ /// A script is kept by the first context to compile it, and every context
68
+ /// compiling it, that one included, reads the kept copy in place of one on
69
+ /// its own heap.
68
70
  /// Each string V8 makes from one holds a reference to it, which V8 hands back
69
71
  /// when the string dies (`sys::external_string`). A source is dropped only
70
72
  /// when no string holds one, so V8 never releases the last reference itself.
@@ -72,6 +74,8 @@ pub struct Sources {
72
74
  list: Mutex<List>,
73
75
  // How many sources, and how many bytes of them, may be kept and read.
74
76
  bounds: (usize, usize),
77
+ // The number the next context's reads are told apart by.
78
+ next_reader: AtomicU64,
75
79
  made: AtomicU64,
76
80
  reads: AtomicU64,
77
81
  conversions: AtomicU64,
@@ -91,6 +95,9 @@ struct List {
91
95
  // left or, as invalid UTF-8, unbounded, so that while it is among the last
92
96
  // `MAX_REFUSED` refused it is not converted again without room for it.
93
97
  refused: Vec<(Key, usize)>,
98
+ // The last `MAX_SWEPT` sources swept, so one made again, which a second
99
+ // context has now read after all, is kept as such.
100
+ swept: Vec<Key>,
94
101
  }
95
102
 
96
103
  struct Kept {
@@ -99,23 +106,60 @@ struct Kept {
99
106
  // The references from this process's list and every ancestor's, which a
100
107
  // forked child never touches again; the strings reading it hold the rest.
101
108
  held: usize,
109
+ readers: Readers,
110
+ }
111
+
112
+ // The contexts that have read a kept source. One alone, as a script evaluated
113
+ // once has, gains nothing from a copy no string reads any more; a source more
114
+ // have read is likely to be read again, and an inherited one is only copied
115
+ // again if a child drops it, so both stay until a new source needs the room.
116
+ #[derive(Clone, Copy, PartialEq, Eq)]
117
+ enum Readers {
118
+ One(u64),
119
+ Many,
102
120
  }
103
121
 
104
122
  impl Kept {
105
123
  fn unread(&self) -> bool {
106
124
  self.units.references() == self.held
107
125
  }
126
+
127
+ fn spent(&self) -> bool {
128
+ matches!(self.readers, Readers::One(_)) && self.unread()
129
+ }
108
130
  }
109
131
 
110
132
  impl List {
111
- fn find(&mut self, key: &Key) -> Option<Units> {
133
+ fn find(&mut self, key: &Key, reader: u64) -> Option<Units> {
112
134
  let at = self.kept.iter().position(|kept| kept.key == *key)?;
113
- let found = self.kept.remove(at);
135
+ let mut found = self.kept.remove(at);
136
+ if found.readers != Readers::One(reader) {
137
+ found.readers = Readers::Many;
138
+ }
114
139
  let units = found.units.clone();
115
140
  self.kept.push(found);
116
141
  Some(units)
117
142
  }
118
143
 
144
+ // V8's release of a string touches only the count, so a copy whose last
145
+ // string has gone waits for the next `Sources::units` call.
146
+ fn sweep(&mut self) -> Vec<Kept> {
147
+ let spent: Vec<Kept> = self.kept.extract_if(.., |kept| kept.spent()).collect();
148
+ for kept in &spent {
149
+ if self.swept.len() >= MAX_SWEPT {
150
+ self.swept.remove(0);
151
+ }
152
+ self.swept.push(kept.key.clone());
153
+ }
154
+ spent
155
+ }
156
+
157
+ fn unswept(&mut self, key: &Key) -> bool {
158
+ let before = self.swept.len();
159
+ self.swept.retain(|swept| swept != key);
160
+ self.swept.len() < before
161
+ }
162
+
119
163
  // Whether `bytes` more fit beside what strings still read, which nothing
120
164
  // can drop.
121
165
  fn room(&self, bytes: usize, (sources, most): (usize, usize)) -> bool {
@@ -161,30 +205,43 @@ impl Sources {
161
205
  Self {
162
206
  list: Mutex::default(),
163
207
  bounds: (sources, bytes),
208
+ next_reader: AtomicU64::new(0),
164
209
  made: AtomicU64::new(0),
165
210
  reads: AtomicU64::new(0),
166
211
  conversions: AtomicU64::new(0),
167
212
  }
168
213
  }
169
214
 
215
+ /// A number for a new context to read shared sources as, told apart from
216
+ /// every other context of this process.
217
+ pub(crate) fn reader(&self) -> u64 {
218
+ self.next_reader.fetch_add(1, Ordering::Relaxed)
219
+ }
220
+
170
221
  /// The shared source of the script `text` under `key`, kept from the first
171
- /// context that asks for it. `None` leaves the context its own copy: the
172
- /// source is invalid UTF-8, or too large for what is kept and still read.
173
- /// Only a source that may fit is converted, outside the lock.
174
- pub(crate) fn units(&self, key: &Key, text: &JsText) -> Option<Units> {
222
+ /// context that asks for it, for the context `reader`. `None` leaves the
223
+ /// context its own copy: the source is invalid UTF-8, or too large for
224
+ /// what is kept and still read. Only a source that may fit is converted,
225
+ /// outside the lock. Every call looks up the requested source, then drops
226
+ /// each other copy that no string reads and that one context alone ever
227
+ /// read.
228
+ pub(crate) fn units(&self, key: &Key, text: &JsText, reader: u64) -> Option<Units> {
175
229
  let mut list = lock(&self.list);
176
- if let Some(units) = list.find(key) {
177
- return Some(units);
178
- }
179
- if !list.room(list.least_bytes(key, text), self.bounds) {
180
- return None;
181
- }
230
+ // Found before the sweep, so a copy the context before this one left
231
+ // unread is read again, and kept, rather than copied again.
232
+ let found = list.find(key, reader);
233
+ let spent = list.sweep();
234
+ let room = found.is_none() && list.room(list.least_bytes(key, text), self.bounds);
182
235
  drop(list);
236
+ drop(spent);
237
+ if !room {
238
+ return found;
239
+ }
183
240
  self.conversions.fetch_add(1, Ordering::Relaxed);
184
241
  let units = Units::of(text);
185
242
  let mut list = lock(&self.list);
186
243
  // Made meanwhile by a context loading the same script.
187
- if let Some(found) = list.find(key) {
244
+ if let Some(found) = list.find(key, reader) {
188
245
  return Some(found);
189
246
  }
190
247
  let bytes = units.as_ref().map_or(usize::MAX, Units::bytes);
@@ -211,13 +268,20 @@ impl Sources {
211
268
  };
212
269
  dropped.push(list.kept.remove(at));
213
270
  }
271
+ let readers = if list.unswept(key) {
272
+ Readers::Many
273
+ } else {
274
+ Readers::One(reader)
275
+ };
214
276
  list.kept.push(Kept {
215
277
  key: key.clone(),
216
278
  units: units.clone(),
217
279
  held: 1,
280
+ readers,
218
281
  });
219
282
  drop(list);
220
- // Freed once the lock is released, as a large source takes a while.
283
+ // Freed once the lock is released, as a large source takes a while,
284
+ // like the spent copies above.
221
285
  drop(dropped);
222
286
  self.made.fetch_add(1, Ordering::Relaxed);
223
287
  Some(units)
@@ -242,9 +306,11 @@ impl Sources {
242
306
  key: kept.key.clone(),
243
307
  units: kept.units.clone(),
244
308
  held: kept.held + 1,
309
+ readers: Readers::Many,
245
310
  })
246
311
  .collect(),
247
312
  refused: list.refused.clone(),
313
+ swept: list.swept.clone(),
248
314
  })
249
315
  .unwrap_or_default();
250
316
  Self {
@@ -291,16 +357,14 @@ mod tests {
291
357
  use crate::control::JsText;
292
358
  use crate::process::code_cache::{CodeCache, Key};
293
359
 
294
- // A script's key once the code cache has seen it twice.
360
+ // A script's key, as its first sighting in a context sharing it has it.
295
361
  fn key(text: &JsText) -> Key {
296
362
  let cache: &'static CodeCache = Box::leak(Box::default());
297
- let name = JsText::from("bundle.js");
298
- let _ = cache.plan(&name, text, None);
299
363
  cache
300
- .plan(&name, text, None)
364
+ .plan(&JsText::from("bundle.js"), text, None, true)
301
365
  .key()
302
366
  .cloned()
303
- .unwrap_or_else(|| panic!("a second sighting has a key"))
367
+ .unwrap_or_else(|| panic!("a shared first sighting has a key"))
304
368
  }
305
369
 
306
370
  fn script(seed: u8) -> JsText {
@@ -356,8 +420,8 @@ mod tests {
356
420
  let sources = Sources::default();
357
421
  let text = script(1);
358
422
  let key = key(&text);
359
- let first = one_byte(sources.units(&key, &text));
360
- let second = one_byte(sources.units(&key, &text));
423
+ let first = one_byte(sources.units(&key, &text, sources.reader()));
424
+ let second = one_byte(sources.units(&key, &text, sources.reader()));
361
425
  assert!(Arc::ptr_eq(&first, &second));
362
426
  assert_eq!(sources.made(), 1);
363
427
  assert_eq!(sources.reading(), 2);
@@ -377,6 +441,12 @@ mod tests {
377
441
  }
378
442
  }
379
443
 
444
+ // A copy two contexts have read, the second still reading it.
445
+ fn shared(sources: &Sources, key: &Key, text: &JsText) -> Option<Units> {
446
+ drop(sources.units(key, text, sources.reader()));
447
+ sources.units(key, text, sources.reader())
448
+ }
449
+
380
450
  #[test]
381
451
  fn a_source_still_read_is_kept_and_an_unread_one_makes_room() {
382
452
  let sources = Sources::default();
@@ -386,31 +456,108 @@ mod tests {
386
456
  let mut read: Vec<Option<Units>> = texts
387
457
  .iter()
388
458
  .zip(&keys)
389
- .map(|(text, key)| sources.units(key, text))
459
+ .map(|(text, key)| shared(&sources, key, text))
390
460
  .collect();
391
461
  let extra = script(100);
392
462
  let extra_key = key(&extra);
393
- assert!(sources.units(&extra_key, &extra).is_none());
463
+ assert!(shared(&sources, &extra_key, &extra).is_none());
394
464
  drop(read.remove(3));
395
465
  drop(read.remove(0));
396
- drop(sources.units(&keys[3], &texts[3]));
466
+ drop(sources.units(&keys[3], &texts[3], sources.reader()));
397
467
  // Of the two unread, the least recently used makes room.
398
- assert!(sources.units(&extra_key, &extra).is_some());
468
+ assert!(shared(&sources, &extra_key, &extra).is_some());
399
469
  let made = sources.made();
400
470
  assert_eq!(made, u64::from(count) + 1);
401
- drop(sources.units(&keys[3], &texts[3]));
471
+ drop(sources.units(&keys[3], &texts[3], sources.reader()));
402
472
  assert_eq!(sources.made(), made);
403
- drop(sources.units(&keys[0], &texts[0]));
473
+ drop(sources.units(&keys[0], &texts[0], sources.reader()));
404
474
  assert_eq!(sources.made(), made + 1);
405
475
  for (at, held) in [1, 2, 4, 5, 6, 7].into_iter().zip(&read) {
406
476
  assert!(same(
407
- sources.units(&keys[at], &texts[at]).as_ref(),
477
+ sources
478
+ .units(&keys[at], &texts[at], sources.reader())
479
+ .as_ref(),
408
480
  held.as_ref()
409
481
  ));
410
482
  }
411
483
  assert_eq!(sources.made(), made + 1);
412
484
  }
413
485
 
486
+ // One context's copy, however often it read it, waits for no new source
487
+ // once no string reads it: the next call drops and frees it.
488
+ #[test]
489
+ fn a_copy_one_context_read_goes_at_the_next_call_once_unread() {
490
+ let sources = Sources::default();
491
+ let (text, other) = (script(70), script(71));
492
+ let (key, other_key) = (key(&text), key(&other));
493
+ let reader = sources.reader();
494
+ let first = one_byte(sources.units(&key, &text, reader));
495
+ let again = one_byte(sources.units(&key, &text, reader));
496
+ assert!(Arc::ptr_eq(&first, &again));
497
+ let copy = Arc::downgrade(&first);
498
+ drop((first, again));
499
+ assert!(copy.upgrade().is_some());
500
+ drop(sources.units(&other_key, &other, sources.reader()));
501
+ assert!(copy.upgrade().is_none());
502
+ drop(sources.units(&key, &text, sources.reader()));
503
+ assert_eq!(sources.made(), 3);
504
+ }
505
+
506
+ #[test]
507
+ fn a_copy_two_contexts_read_in_turn_stays_once_unread() {
508
+ let sources = Sources::default();
509
+ let (text, other) = (script(72), script(73));
510
+ let (key, other_key) = (key(&text), key(&other));
511
+ let copy = Arc::downgrade(&one_byte(shared(&sources, &key, &text)));
512
+ drop(sources.units(&other_key, &other, sources.reader()));
513
+ let again = one_byte(sources.units(&key, &text, sources.reader()));
514
+ assert!(
515
+ copy.upgrade()
516
+ .is_some_and(|copy| Arc::ptr_eq(&copy, &again))
517
+ );
518
+ assert_eq!(sources.made(), 2);
519
+ }
520
+
521
+ #[test]
522
+ fn a_copy_a_second_context_reads_while_the_first_does_stays_once_unread() {
523
+ let sources = Sources::default();
524
+ let (text, other) = (script(74), script(75));
525
+ let (key, other_key) = (key(&text), key(&other));
526
+ let first = one_byte(sources.units(&key, &text, sources.reader()));
527
+ let second = one_byte(sources.units(&key, &text, sources.reader()));
528
+ let copy = Arc::downgrade(&first);
529
+ drop((first, second));
530
+ drop(sources.units(&other_key, &other, sources.reader()));
531
+ let again = one_byte(sources.units(&key, &text, sources.reader()));
532
+ assert!(
533
+ copy.upgrade()
534
+ .is_some_and(|copy| Arc::ptr_eq(&copy, &again))
535
+ );
536
+ assert_eq!(sources.made(), 2);
537
+ }
538
+
539
+ // Contexts loading two scripts in turn, each gone before the next, sweep
540
+ // each other's copy; a copy made again after its sweep is kept as one more
541
+ // than one context read, so each script is converted twice in all.
542
+ #[test]
543
+ fn scripts_loaded_in_turn_by_short_lived_contexts_stay_after_their_second_copy() {
544
+ let sources = Sources::default();
545
+ let texts = [script(78), script(79)];
546
+ let keys = texts.each_ref().map(key);
547
+ for _ in 0..2 {
548
+ for (key, text) in keys.iter().zip(&texts) {
549
+ drop(sources.units(key, text, sources.reader()));
550
+ }
551
+ }
552
+ assert_eq!(sources.made(), 4);
553
+ for _ in 0..3 {
554
+ for (key, text) in keys.iter().zip(&texts) {
555
+ drop(sources.units(key, text, sources.reader()));
556
+ }
557
+ }
558
+ assert_eq!(sources.made(), 4);
559
+ }
560
+
414
561
  #[test]
415
562
  fn a_full_list_converts_nothing() {
416
563
  let sources = Sources::default();
@@ -418,18 +565,26 @@ mod tests {
418
565
  let read: Vec<Option<Units>> = (40..40 + count)
419
566
  .map(|seed| {
420
567
  let text = script(seed);
421
- sources.units(&key(&text), &text)
568
+ sources.units(&key(&text), &text, sources.reader())
422
569
  })
423
570
  .collect();
424
571
  let converted = sources.conversions();
425
572
  let extra = script(100);
426
573
  let extra_key = key(&extra);
427
574
  for _ in 0..3 {
428
- assert!(sources.units(&extra_key, &extra).is_none());
575
+ assert!(
576
+ sources
577
+ .units(&extra_key, &extra, sources.reader())
578
+ .is_none()
579
+ );
429
580
  }
430
581
  assert_eq!(sources.conversions(), converted);
431
582
  drop(read);
432
- assert!(sources.units(&extra_key, &extra).is_some());
583
+ assert!(
584
+ sources
585
+ .units(&extra_key, &extra, sources.reader())
586
+ .is_some()
587
+ );
433
588
  assert_eq!(sources.conversions(), converted + 1);
434
589
  }
435
590
 
@@ -440,21 +595,29 @@ mod tests {
440
595
  fn a_source_is_converted_only_while_it_may_fit() {
441
596
  let sources = Sources::bounded(MAX_SOURCES, 5 << 20);
442
597
  let read = script(50);
443
- let still_read = sources.units(&key(&read), &read);
598
+ let still_read = sources.units(&key(&read), &read, sources.reader());
444
599
  assert!(still_read.is_some());
445
600
  let too_large = JsText::Latin1(vec![51; 4 << 20]);
446
- assert!(sources.units(&key(&too_large), &too_large).is_none());
601
+ assert!(
602
+ sources
603
+ .units(&key(&too_large), &too_large, sources.reader())
604
+ .is_none()
605
+ );
447
606
  assert_eq!(sources.conversions(), 1);
448
607
  let mut astral = vec![b'a'; 2 << 20];
449
608
  astral.extend_from_slice("\u{1F600}".as_bytes());
450
609
  let astral = JsText::Utf8(astral);
451
610
  let astral_key = key(&astral);
452
611
  for _ in 0..3 {
453
- assert!(sources.units(&astral_key, &astral).is_none());
612
+ assert!(
613
+ sources
614
+ .units(&astral_key, &astral, sources.reader())
615
+ .is_none()
616
+ );
454
617
  }
455
618
  assert_eq!(sources.conversions(), 2);
456
619
  drop(still_read);
457
- assert!(two_byte(sources.units(&astral_key, &astral)).len() > 2 << 20);
620
+ assert!(two_byte(sources.units(&astral_key, &astral, sources.reader())).len() > 2 << 20);
458
621
  assert_eq!((sources.conversions(), sources.made()), (3, 2));
459
622
  }
460
623
 
@@ -466,7 +629,11 @@ mod tests {
466
629
  let invalid = JsText::Utf8(invalid);
467
630
  let invalid_key = key(&invalid);
468
631
  for _ in 0..3 {
469
- assert!(sources.units(&invalid_key, &invalid).is_none());
632
+ assert!(
633
+ sources
634
+ .units(&invalid_key, &invalid, sources.reader())
635
+ .is_none()
636
+ );
470
637
  }
471
638
  assert_eq!((sources.conversions(), sources.made()), (1, 0));
472
639
  }
@@ -476,32 +643,56 @@ mod tests {
476
643
  let parent: &'static Sources = Box::leak(Box::default());
477
644
  let text = script(2);
478
645
  let key = key(&text);
479
- let shared = parent.units(&key, &text);
646
+ let shared = parent.units(&key, &text, parent.reader());
480
647
  let child = parent.inherited();
481
- let in_child = child.units(&key, &text);
648
+ let in_child = child.units(&key, &text, child.reader());
482
649
  assert!(same(shared.as_ref(), in_child.as_ref()));
483
650
  assert_eq!(child.reading(), 2);
484
651
  let copy = Arc::downgrade(&one_byte(shared));
485
652
  drop(in_child);
653
+ // Sources the child still reads fill its list, so the unread
654
+ // inherited copy makes room.
486
655
  let count = u8::try_from(MAX_SOURCES).unwrap_or(u8::MAX);
487
- for seed in 30..30 + count {
488
- let other = script(seed);
489
- drop(child.units(&self::key(&other), &other));
490
- }
656
+ let others: Vec<Option<Units>> = (30..30 + count)
657
+ .map(|seed| {
658
+ let other = script(seed);
659
+ child.units(&self::key(&other), &other, child.reader())
660
+ })
661
+ .collect();
491
662
  assert_eq!(copy.strong_count(), 1);
492
- let again = one_byte(child.units(&key, &text));
663
+ drop(others);
664
+ let again = one_byte(child.units(&key, &text, child.reader()));
493
665
  assert!(
494
666
  !copy
495
667
  .upgrade()
496
668
  .is_some_and(|copy| Arc::ptr_eq(&copy, &again))
497
669
  );
498
- let from_parent = one_byte(parent.units(&key, &text));
670
+ let from_parent = one_byte(parent.units(&key, &text, parent.reader()));
499
671
  assert!(
500
672
  copy.upgrade()
501
673
  .is_some_and(|copy| Arc::ptr_eq(&copy, &from_parent))
502
674
  );
503
675
  }
504
676
 
677
+ // Dropping an inherited copy frees nothing in a child, and its next
678
+ // context loading the script would copy it again, so the child keeps
679
+ // one only its parent's context read.
680
+ #[test]
681
+ fn a_child_keeps_an_inherited_copy_one_context_read() {
682
+ let parent: &'static Sources = Box::leak(Box::default());
683
+ let (text, other) = (script(76), script(77));
684
+ let (key, other_key) = (key(&text), key(&other));
685
+ let copy = Arc::downgrade(&one_byte(parent.units(&key, &text, parent.reader())));
686
+ let child = parent.inherited();
687
+ drop(child.units(&other_key, &other, child.reader()));
688
+ let in_child = one_byte(child.units(&key, &text, child.reader()));
689
+ assert!(
690
+ copy.upgrade()
691
+ .is_some_and(|copy| Arc::ptr_eq(&copy, &in_child))
692
+ );
693
+ assert_eq!(child.made(), 1);
694
+ }
695
+
505
696
  // A child's inherited copies stay read while its parent's strings hold
506
697
  // them, so a child forked with a full list must not convert either.
507
698
  #[test]
@@ -511,14 +702,14 @@ mod tests {
511
702
  let read: Vec<Option<Units>> = (60..60 + count)
512
703
  .map(|seed| {
513
704
  let text = script(seed);
514
- parent.units(&key(&text), &text)
705
+ parent.units(&key(&text), &text, parent.reader())
515
706
  })
516
707
  .collect();
517
708
  let child = parent.inherited();
518
709
  let extra = script(110);
519
710
  let extra_key = key(&extra);
520
711
  for _ in 0..3 {
521
- assert!(child.units(&extra_key, &extra).is_none());
712
+ assert!(child.units(&extra_key, &extra, child.reader()).is_none());
522
713
  }
523
714
  assert_eq!(child.conversions(), 0);
524
715
  drop(read);
@@ -529,11 +720,14 @@ mod tests {
529
720
  let parent = Sources::default();
530
721
  let text = script(3);
531
722
  let key = key(&text);
532
- let shared = one_byte(parent.units(&key, &text));
723
+ let shared = one_byte(parent.units(&key, &text, parent.reader()));
533
724
  let held = parent.list.lock();
534
725
  let child = parent.inherited();
535
726
  drop(held);
536
- assert!(!Arc::ptr_eq(&shared, &one_byte(child.units(&key, &text))));
727
+ assert!(!Arc::ptr_eq(
728
+ &shared,
729
+ &one_byte(child.units(&key, &text, child.reader()))
730
+ ));
537
731
  assert_eq!(child.made(), 1);
538
732
  }
539
733
  }
@@ -4,6 +4,8 @@ use std::sync::atomic::{AtomicBool, Ordering};
4
4
  use mini_racer_wire::{WRITE_VERSION, tag};
5
5
  use v8::ValueDeserializerHelper;
6
6
 
7
+ use crate::sys;
8
+
7
9
  // Deeper values are left to V8, which reads as deep as its stack allows.
8
10
  const MAX_DEPTH: u32 = 64;
9
11
  // Far below V8's own limit on an array's length, which it enforces itself.
@@ -48,16 +50,16 @@ pub const fn worth_building(objects: usize, properties: usize) -> bool {
48
50
  /// which its deserializer makes, element by element as a sparse array, so a
49
51
  /// function returning an argument costs far more than it need.
50
52
  ///
51
- /// A two-byte string is read by V8's own deserializer from its bytes alone.
52
- /// The whole request is `Unbuilt` for anything else this would not build as V8
53
- /// does or that V8 would refuse (a binary, a key that is not a string or an
54
- /// int32, a key given twice, an array inside itself, an object of more than
55
- /// `MAX_KEYS` keys, nesting past `MAX_DEPTH`), decided from the bytes before
56
- /// any value is made. A termination requested as it reads leaves it `Unbuilt`
57
- /// too: V8 acts on one before each value it reads, an array or object
58
- /// beginning as a value does, so this looks before the request and before
59
- /// each value. One requested before the dispatch refused it
60
- /// (`TermCell::refuses`).
53
+ /// A two-byte string whose units all fit Latin-1 is read by V8's own
54
+ /// deserializer from its bytes alone, which keeps it two-byte as V8's read of
55
+ /// the whole would. The whole request is `Unbuilt` for anything else this would
56
+ /// not build as V8 does or that V8 would refuse (a binary, a key that is not a
57
+ /// string or an int32, a key given twice, an array inside itself, an object of
58
+ /// more than `MAX_KEYS` keys, nesting past `MAX_DEPTH`), decided from the bytes
59
+ /// before any value is made. A termination requested as it reads leaves it
60
+ /// `Unbuilt` too: V8 acts on one before each value it reads, an array or object
61
+ /// beginning as a value does, so this looks before the request and before each
62
+ /// value. One requested before the dispatch refused it (`TermCell::refuses`).
61
63
  pub fn read<'s, 'b>(
62
64
  scope: &v8::PinScope<'s, '_>,
63
65
  bytes: &'b [u8],
@@ -74,6 +76,7 @@ pub fn read<'s, 'b>(
74
76
  objects: Vec::new(),
75
77
  elements: Vec::new(),
76
78
  shapes: Vec::new(),
79
+ units: Vec::new(),
77
80
  };
78
81
  match builder.call() {
79
82
  Some(call) => Read::Built(call),
@@ -119,6 +122,17 @@ impl<'b> Cursor<'b> {
119
122
  }
120
123
  }
121
124
 
125
+ // A value's tag, past the padding V8 skips, which aligns a two-byte
126
+ // string's units.
127
+ fn tag(&mut self) -> Option<u8> {
128
+ loop {
129
+ match self.byte()? {
130
+ tag::PADDING => {}
131
+ tag => return Some(tag),
132
+ }
133
+ }
134
+ }
135
+
122
136
  fn varint(&mut self) -> Option<u64> {
123
137
  let mut value = 0u64;
124
138
  for shift in (0..64).step_by(7) {
@@ -153,9 +167,9 @@ impl<'b> Cursor<'b> {
153
167
  .filter(|text| text.len() <= v8::String::MAX_LENGTH)
154
168
  }
155
169
 
156
- fn two_byte(&mut self) -> Option<()> {
157
- let len = self.text()?.len();
158
- (len.is_multiple_of(2) && len / 2 <= v8::String::MAX_LENGTH).then_some(())
170
+ fn two_byte(&mut self) -> Option<&'b [u8]> {
171
+ self.text()
172
+ .filter(|text| text.len().is_multiple_of(2) && text.len() / 2 <= v8::String::MAX_LENGTH)
159
173
  }
160
174
 
161
175
  // The length of `[path, ...arguments]`, past V8's header.
@@ -312,7 +326,7 @@ impl Check<'_> {
312
326
  }
313
327
 
314
328
  fn value(&mut self, depth: u32) -> Option<()> {
315
- match self.bytes.byte()? {
329
+ match self.bytes.tag()? {
316
330
  tag::INT32 => {
317
331
  self.bytes.varint_u32()?;
318
332
  }
@@ -323,7 +337,9 @@ impl Check<'_> {
323
337
  tag::UTF8_STRING | tag::ONE_BYTE_STRING => {
324
338
  self.bytes.string()?;
325
339
  }
326
- tag::TWO_BYTE_STRING => self.bytes.two_byte()?,
340
+ tag::TWO_BYTE_STRING => {
341
+ self.bytes.two_byte()?;
342
+ }
327
343
  tag::BIGINT => {
328
344
  self.bytes.bigint_bytes()?;
329
345
  }
@@ -393,6 +409,9 @@ struct Builder<'s, 'b, 'p, 'i> {
393
409
  // The names last made for each key position at each depth, so objects of
394
410
  // one shape share them instead of looking each up again.
395
411
  shapes: Vec<Vec<(Key<'b>, v8::Local<'s, v8::String>)>>,
412
+ // A two-byte string's units where the request's bytes hold them
413
+ // unaligned.
414
+ units: Vec<u16>,
396
415
  }
397
416
 
398
417
  // Reads again what `Check` accepted, so only a termination stops it, or V8's
@@ -419,7 +438,7 @@ impl<'s, 'b> Builder<'s, 'b, '_, '_> {
419
438
  self.unterminated()?;
420
439
  let scope = self.scope;
421
440
  let at = self.bytes;
422
- Some(match self.bytes.byte()? {
441
+ Some(match self.bytes.tag()? {
423
442
  tag::INT32 => v8::Integer::new(scope, zigzag(self.bytes.varint_u32()?)).into(),
424
443
  tag::DOUBLE => v8::Number::new(scope, self.bytes.double()?).into(),
425
444
  tag::NULL => v8::null(scope).into(),
@@ -459,17 +478,34 @@ impl<'s, 'b> Builder<'s, 'b, '_, '_> {
459
478
  tag::BEGIN_DENSE_ARRAY => self.array(depth.checked_sub(1)?)?,
460
479
  tag::BEGIN_OBJECT => self.object(depth.checked_sub(1)?)?,
461
480
  tag::TWO_BYTE_STRING => {
462
- self.bytes.two_byte()?;
463
- self.two_byte(at)?
481
+ let text = self.bytes.two_byte()?;
482
+ self.two_byte(text, at)?
464
483
  }
465
484
  _ => return None,
466
485
  })
467
486
  }
468
487
 
469
- // Reads the string from `at` up to the cursor with V8's own deserializer,
470
- // which keeps it two-byte, where V8's API makes a one-byte string of
471
- // whatever fits.
472
- fn two_byte(&mut self, at: Cursor<'b>) -> Option<v8::Local<'s, v8::Value>> {
488
+ // V8's API makes a one-byte string of units that all fit Latin-1, where
489
+ // its deserializer keeps them two-byte, so only a string with a wider
490
+ // unit is made through the API, from the request's own bytes where the
491
+ // padding before them aligned them.
492
+ fn two_byte(&mut self, text: &[u8], at: Cursor<'b>) -> Option<v8::Local<'s, v8::Value>> {
493
+ let pairs = text.as_chunks::<2>().0;
494
+ if !pairs.iter().any(|&[_, high]| high != 0) {
495
+ return self.deserialized(at);
496
+ }
497
+ let units = if let Some(units) = sys::units(text) {
498
+ units
499
+ } else {
500
+ self.units.clear();
501
+ self.units
502
+ .extend(pairs.iter().map(|&pair| u16::from_le_bytes(pair)));
503
+ &self.units
504
+ };
505
+ v8::String::new_from_two_byte(self.scope, units, v8::NewStringType::Normal).map(Into::into)
506
+ }
507
+
508
+ fn deserialized(&mut self, at: Cursor<'b>) -> Option<v8::Local<'s, v8::Value>> {
473
509
  let len = at.rest.len().checked_sub(self.bytes.rest.len())?;
474
510
  let mut bytes = vec![tag::HEADER, u8::try_from(WRITE_VERSION).ok()?];
475
511
  bytes.extend_from_slice(at.rest.get(..len)?);