Skip to content

Commit 07e72ed

Browse files
committed
removed >8 byte small decoding
1 parent 297694a commit 07e72ed

74 files changed

Lines changed: 1329 additions & 2708 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift‎

Lines changed: 19 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ public struct BridgeJSLink {
317317
///
318318
/// The wasm32 12-byte Swift `String` layout is a standard-library
319319
/// implementation detail, not a stable ABI. Small strings (`word2 & 0x2000`)
320-
/// store up to 10 UTF-8 bytes inline. Large strings store the UTF-8 count in
320+
/// store up to 8 UTF-8 bytes inline. Large strings store the UTF-8 count in
321321
/// `word0` and a variant/object pointer in `word1`; UTF-8 starts at
322322
/// `word1 + nativeBias` (20 on 32-bit). Immortal large strings
323323
/// (`word2 & 0x8000`) are cached by a 53-bit `ptr + len * 2**32` key when
@@ -326,16 +326,15 @@ public struct BridgeJSLink {
326326
printer.write(
327327
multilineString: """
328328
\(JSGlueVariableScope.reservedDecodeString) = (() => {
329-
const byteAt = (word0, word1, word2, byteIndex) =>
329+
const byteAt = (word0, word1, byteIndex) =>
330330
byteIndex < 4
331331
? (word0 >>> (byteIndex * 8)) & 255
332-
: byteIndex < 8
333-
? (word1 >>> ((byteIndex - 4) * 8)) & 255
334-
: byteIndex === 8
335-
? word2 & 255
336-
: (word2 >>> 16) & 255;
337-
// Valid small Swift strings need at most ten UTF-16 code units.
338-
const decodeSmallUTF8 = (word0, word1, word2, count) => {
332+
: (word1 >>> ((byteIndex - 4) * 8)) & 255;
333+
// Valid small Swift strings need at most eight UTF-16 code units.
334+
const decodeSmallUTF8 = (word0, word1, count) => {
335+
if (count > 8) {
336+
throw new Error(`Unsupported Swift inline String length: ${count} UTF-8 bytes (maximum 8). The Swift String layout may have changed; update JavaScriptKit and regenerate BridgeJS glue.`);
337+
}
339338
let byteIndex = 0,
340339
unitCount = 0,
341340
u0 = 0,
@@ -345,23 +344,21 @@ public struct BridgeJSLink {
345344
u4 = 0,
346345
u5 = 0,
347346
u6 = 0,
348-
u7 = 0,
349-
u8 = 0,
350-
u9 = 0;
347+
u7 = 0;
351348
while (byteIndex < count) {
352349
const start = byteIndex;
353-
const b0 = byteAt(word0, word1, word2, byteIndex++);
350+
const b0 = byteAt(word0, word1, byteIndex++);
354351
let codePoint;
355352
if (b0 < 128) codePoint = b0;
356-
else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63);
353+
else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, byteIndex++) & 63);
357354
else if (b0 < 240) {
358-
const b1 = byteAt(word0, word1, word2, byteIndex++),
359-
b2 = byteAt(word0, word1, word2, byteIndex++);
355+
const b1 = byteAt(word0, word1, byteIndex++),
356+
b2 = byteAt(word0, word1, byteIndex++);
360357
codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63);
361358
} else {
362-
const b1 = byteAt(word0, word1, word2, byteIndex++),
363-
b2 = byteAt(word0, word1, word2, byteIndex++),
364-
b3 = byteAt(word0, word1, word2, byteIndex++);
359+
const b1 = byteAt(word0, word1, byteIndex++),
360+
b2 = byteAt(word0, word1, byteIndex++),
361+
b3 = byteAt(word0, word1, byteIndex++);
365362
codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63);
366363
}
367364
// Match TextDecoder: consume exactly one leading UTF-8 BOM.
@@ -395,12 +392,6 @@ public struct BridgeJSLink {
395392
case 7:
396393
u7 = codeUnit;
397394
break;
398-
case 8:
399-
u8 = codeUnit;
400-
break;
401-
case 9:
402-
u9 = codeUnit;
403-
break;
404395
}
405396
if (supplementary) {
406397
const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023);
@@ -429,12 +420,6 @@ public struct BridgeJSLink {
429420
case 7:
430421
u7 = lowSurrogate;
431422
break;
432-
case 8:
433-
u8 = lowSurrogate;
434-
break;
435-
case 9:
436-
u9 = lowSurrogate;
437-
break;
438423
}
439424
}
440425
}
@@ -457,15 +442,11 @@ public struct BridgeJSLink {
457442
return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6);
458443
case 8:
459444
return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7);
460-
case 9:
461-
return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8);
462-
case 10:
463-
return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9);
464445
}
465446
};
466447
const decodeSmall = (word0, word1, word2) => {
467448
const count = (word2 >>> 8) & 0x0f;
468-
if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count);
449+
if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, count);
469450
const char = String.fromCharCode;
470451
switch (count) {
471452
case 0: return "";
@@ -477,8 +458,8 @@ public struct BridgeJSLink {
477458
case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff);
478459
case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff);
479460
case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24);
480-
case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff);
481-
case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff);
461+
default:
462+
throw new Error(`Unsupported Swift inline String length: ${count} UTF-8 bytes (maximum 8). The Swift String layout may have changed; update JavaScriptKit and regenerate BridgeJS glue.`);
482463
}
483464
};
484465
return (word0, word1, word2) => {

‎Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/StringDecoderTests.mjs‎

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,9 @@ bridge.setInstance(wasm);
2121
let exports = bridge.createExports(wasm);
2222
function small(value) {
2323
const bytes = encoder.encode(value);
24-
assert.ok(bytes.length <= 10);
24+
assert.ok(bytes.length <= 8);
2525
const storage = new Uint8Array(12);
2626
storage.set(bytes.subarray(0, 8));
27-
storage[8] = bytes[8] ?? 0;
28-
storage[10] = bytes[9] ?? 0;
2927
storage[9] = (bytes.every((b) => b < 128) ? 0xe0 : 0xa0) | bytes.length;
3028
const view = new DataView(storage.buffer);
3129
return [0, 4, 8].map((offset) => view.getInt32(offset, true));
@@ -41,26 +39,36 @@ const values = ['', 'div', 'abcdefgh', 'abcdefghi', 'abcdefghij', 'abcdefé', 'a
4139
'abc\ufeffdefghijk', '\ufeff\ufeffabcdefghijk'];
4240
for (const value of values) {
4341
const expected = decoder.decode(encoder.encode(value));
44-
if (encoder.encode(value).length <= 10) {
42+
if (encoder.encode(value).length <= 8) {
4543
words = small(value);
4644
assert.equal(exports.checkString(), expected);
4745
}
4846
words = large(value);
4947
assert.equal(exports.checkString(), expected);
5048
}
5149
// Alternate full and short inputs: bytes outside count must never leak into the result.
52-
for (const value of ['abcdefghé', 'é', 'abcdef😄', '\ufeff', 'abcdefghij', '', 'x\0', '€', '\ufeff\ufeff']) {
50+
for (const value of ['abcd😄', 'é', 'abcde€', '\ufeff', 'abcdefgh', '', 'x\0', '€', '\ufeff\ufeff']) {
5351
words = small(value);
5452
assert.equal(exports.checkString(), decoder.decode(encoder.encode(value)));
5553
}
5654
// Every ASCII byte, including NUL, at every small-string length.
5755
for (let byte = 0; byte < 128; byte++) {
58-
for (let count = 0; count <= 10; count++) {
56+
for (let count = 0; count <= 8; count++) {
5957
const value = String.fromCharCode(byte).repeat(count);
6058
words = small(value);
6159
assert.equal(exports.checkString(), value);
6260
}
6361
}
62+
// Unexpected inline lengths must fail for both ASCII and Unicode discriminators.
63+
for (const count of [9, 10]) {
64+
for (const discriminator of [0xe0, 0xa0]) {
65+
words = [0, 0, (discriminator | count) << 8];
66+
assert.throws(() => exports.checkString(), {
67+
name: 'Error',
68+
message: `Unsupported Swift inline String length: ${count} UTF-8 bytes (maximum 8). The Swift String layout may have changed; update JavaScriptKit and regenerate BridgeJS glue.`,
69+
});
70+
}
71+
}
6472
words = large('first literal', true);
6573
assert.equal(exports.checkString(), 'first literal');
6674
// A new instance using the same memory must preserve valid cache entries.

‎Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Alias.js‎

Lines changed: 18 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -686,16 +686,15 @@ export async function createInstantiator(options, swift) {
686686

687687
decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); }
688688
decodeString = (() => {
689-
const byteAt = (word0, word1, word2, byteIndex) =>
689+
const byteAt = (word0, word1, byteIndex) =>
690690
byteIndex < 4
691691
? (word0 >>> (byteIndex * 8)) & 255
692-
: byteIndex < 8
693-
? (word1 >>> ((byteIndex - 4) * 8)) & 255
694-
: byteIndex === 8
695-
? word2 & 255
696-
: (word2 >>> 16) & 255;
697-
// Valid small Swift strings need at most ten UTF-16 code units.
698-
const decodeSmallUTF8 = (word0, word1, word2, count) => {
692+
: (word1 >>> ((byteIndex - 4) * 8)) & 255;
693+
// Valid small Swift strings need at most eight UTF-16 code units.
694+
const decodeSmallUTF8 = (word0, word1, count) => {
695+
if (count > 8) {
696+
throw new Error(`Unsupported Swift inline String length: ${count} UTF-8 bytes (maximum 8). The Swift String layout may have changed; update JavaScriptKit and regenerate BridgeJS glue.`);
697+
}
699698
let byteIndex = 0,
700699
unitCount = 0,
701700
u0 = 0,
@@ -705,23 +704,21 @@ export async function createInstantiator(options, swift) {
705704
u4 = 0,
706705
u5 = 0,
707706
u6 = 0,
708-
u7 = 0,
709-
u8 = 0,
710-
u9 = 0;
707+
u7 = 0;
711708
while (byteIndex < count) {
712709
const start = byteIndex;
713-
const b0 = byteAt(word0, word1, word2, byteIndex++);
710+
const b0 = byteAt(word0, word1, byteIndex++);
714711
let codePoint;
715712
if (b0 < 128) codePoint = b0;
716-
else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63);
713+
else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, byteIndex++) & 63);
717714
else if (b0 < 240) {
718-
const b1 = byteAt(word0, word1, word2, byteIndex++),
719-
b2 = byteAt(word0, word1, word2, byteIndex++);
715+
const b1 = byteAt(word0, word1, byteIndex++),
716+
b2 = byteAt(word0, word1, byteIndex++);
720717
codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63);
721718
} else {
722-
const b1 = byteAt(word0, word1, word2, byteIndex++),
723-
b2 = byteAt(word0, word1, word2, byteIndex++),
724-
b3 = byteAt(word0, word1, word2, byteIndex++);
719+
const b1 = byteAt(word0, word1, byteIndex++),
720+
b2 = byteAt(word0, word1, byteIndex++),
721+
b3 = byteAt(word0, word1, byteIndex++);
725722
codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63);
726723
}
727724
// Match TextDecoder: consume exactly one leading UTF-8 BOM.
@@ -755,12 +752,6 @@ export async function createInstantiator(options, swift) {
755752
case 7:
756753
u7 = codeUnit;
757754
break;
758-
case 8:
759-
u8 = codeUnit;
760-
break;
761-
case 9:
762-
u9 = codeUnit;
763-
break;
764755
}
765756
if (supplementary) {
766757
const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023);
@@ -789,12 +780,6 @@ export async function createInstantiator(options, swift) {
789780
case 7:
790781
u7 = lowSurrogate;
791782
break;
792-
case 8:
793-
u8 = lowSurrogate;
794-
break;
795-
case 9:
796-
u9 = lowSurrogate;
797-
break;
798783
}
799784
}
800785
}
@@ -817,15 +802,11 @@ export async function createInstantiator(options, swift) {
817802
return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6);
818803
case 8:
819804
return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7);
820-
case 9:
821-
return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8);
822-
case 10:
823-
return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9);
824805
}
825806
};
826807
const decodeSmall = (word0, word1, word2) => {
827808
const count = (word2 >>> 8) & 0x0f;
828-
if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count);
809+
if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, count);
829810
const char = String.fromCharCode;
830811
switch (count) {
831812
case 0: return "";
@@ -837,8 +818,8 @@ export async function createInstantiator(options, swift) {
837818
case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff);
838819
case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff);
839820
case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24);
840-
case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff);
841-
case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff);
821+
default:
822+
throw new Error(`Unsupported Swift inline String length: ${count} UTF-8 bytes (maximum 8). The Swift String layout may have changed; update JavaScriptKit and regenerate BridgeJS glue.`);
842823
}
843824
};
844825
return (word0, word1, word2) => {

0 commit comments

Comments
 (0)