2022-05-25 11:27:09 +00:00
|
|
|
// Copyright 2022 the V8 project authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
|
|
// found in the LICENSE file.
|
|
|
|
|
|
|
|
// Flags: --experimental-wasm-stringref
|
|
|
|
|
|
|
|
d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
|
|
|
|
|
|
|
|
let kSig_w_ii = makeSig([kWasmI32, kWasmI32], [kWasmStringRef]);
|
2022-06-08 15:39:39 +00:00
|
|
|
let kSig_w_v = makeSig([], [kWasmStringRef]);
|
2022-06-09 12:05:07 +00:00
|
|
|
let kSig_i_w = makeSig([kWasmStringRef], [kWasmI32]);
|
2022-06-10 14:10:21 +00:00
|
|
|
let kSig_i_wi = makeSig([kWasmStringRef, kWasmI32], [kWasmI32]);
|
2022-07-08 09:59:20 +00:00
|
|
|
let kSig_i_wii = makeSig([kWasmStringRef, kWasmI32, kWasmI32], [kWasmI32]);
|
2022-06-14 07:37:12 +00:00
|
|
|
let kSig_i_ww = makeSig([kWasmStringRef, kWasmStringRef], [kWasmI32]);
|
2022-07-07 07:49:49 +00:00
|
|
|
let kSig_i_wiii = makeSig([kWasmStringRef, kWasmI32, kWasmI32, kWasmI32],
|
|
|
|
[kWasmI32]);
|
2022-07-12 14:12:35 +00:00
|
|
|
let kSig_ii_wiii = makeSig([kWasmStringRef, kWasmI32, kWasmI32, kWasmI32],
|
|
|
|
[kWasmI32, kWasmI32]);
|
2022-06-10 18:22:29 +00:00
|
|
|
let kSig_w_wii = makeSig([kWasmStringRef, kWasmI32, kWasmI32],
|
|
|
|
[kWasmStringRef]);
|
2022-06-14 06:37:22 +00:00
|
|
|
let kSig_w_ww = makeSig([kWasmStringRef, kWasmStringRef], [kWasmStringRef]);
|
|
|
|
let kSig_w_w = makeSig([kWasmStringRef], [kWasmStringRef]);
|
2022-05-25 11:27:09 +00:00
|
|
|
|
|
|
|
function encodeWtf8(str) {
|
|
|
|
// String iterator coalesces surrogate pairs.
|
|
|
|
let out = [];
|
|
|
|
for (let codepoint of str) {
|
|
|
|
codepoint = codepoint.codePointAt(0);
|
|
|
|
if (codepoint <= 0x7f) {
|
|
|
|
out.push(codepoint);
|
|
|
|
} else if (codepoint <= 0x7ff) {
|
|
|
|
out.push(0xc0 | (codepoint >> 6));
|
|
|
|
out.push(0x80 | (codepoint & 0x3f));
|
|
|
|
} else if (codepoint <= 0xffff) {
|
|
|
|
out.push(0xe0 | (codepoint >> 12));
|
|
|
|
out.push(0x80 | ((codepoint >> 6) & 0x3f));
|
|
|
|
out.push(0x80 | (codepoint & 0x3f));
|
|
|
|
} else if (codepoint <= 0x10ffff) {
|
|
|
|
out.push(0xf0 | (codepoint >> 18));
|
|
|
|
out.push(0x80 | ((codepoint >> 12) & 0x3f));
|
|
|
|
out.push(0x80 | ((codepoint >> 6) & 0x3f));
|
|
|
|
out.push(0x80 | (codepoint & 0x3f));
|
|
|
|
} else {
|
|
|
|
throw new Error("bad codepoint " + codepoint);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
|
2022-07-12 14:12:35 +00:00
|
|
|
// Compute the string that corresponds to the valid WTF-8 bytes from
|
|
|
|
// start (inclusive) to end (exclusive).
|
|
|
|
function decodeWtf8(wtf8, start, end) {
|
|
|
|
let result = ''
|
|
|
|
while (start < end) {
|
|
|
|
let cp;
|
|
|
|
let b0 = wtf8[start];
|
|
|
|
if ((b0 & 0xC0) == 0x80) {
|
|
|
|
// The precondition is that we have valid WTF-8 bytes and that
|
|
|
|
// start and end are codepoint boundaries. Here we make a weak
|
|
|
|
// assertion about that invariant, that we don't start decoding
|
|
|
|
// with a continuation byte.
|
|
|
|
throw new Error('invalid wtf8');
|
|
|
|
}
|
|
|
|
if (b0 <= 0x7F) {
|
|
|
|
cp = b0;
|
|
|
|
start += 1;
|
|
|
|
} else if (b0 <= 0xDF) {
|
|
|
|
cp = (b0 & 0x1f) << 6;
|
|
|
|
cp |= (wtf8[start + 1] & 0x3f);
|
|
|
|
start += 2;
|
|
|
|
} else if (b0 <= 0xEF) {
|
|
|
|
cp = (b0 & 0x0f) << 12;
|
|
|
|
cp |= (wtf8[start + 1] & 0x3f) << 6;
|
|
|
|
cp |= (wtf8[start + 2] & 0x3f);
|
|
|
|
start += 3;
|
|
|
|
} else {
|
|
|
|
cp = (b0 & 0x07) << 18;
|
|
|
|
cp |= (wtf8[start + 1] & 0x3f) << 12;
|
|
|
|
cp |= (wtf8[start + 2] & 0x3f) << 6;
|
|
|
|
cp |= (wtf8[start + 3] & 0x3f);
|
|
|
|
start += 4;
|
|
|
|
}
|
|
|
|
result += String.fromCodePoint(cp);
|
|
|
|
}
|
|
|
|
assertEquals(start, end);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2022-06-08 15:39:39 +00:00
|
|
|
let interestingStrings = ['',
|
|
|
|
'ascii',
|
|
|
|
'latin \xa9 1',
|
|
|
|
'two \ucccc byte',
|
2022-07-12 14:12:35 +00:00
|
|
|
'surrogate \ud800\udc00 pair',
|
2022-06-08 15:39:39 +00:00
|
|
|
'isolated \ud800 leading',
|
2022-06-27 06:20:00 +00:00
|
|
|
'isolated \udc00 trailing',
|
|
|
|
'\ud800 isolated leading at beginning',
|
|
|
|
'\udc00 isolated trailing at beginning',
|
|
|
|
'isolated leading at end \ud800',
|
|
|
|
'isolated trailing at end \udc00',
|
|
|
|
'swapped surrogate \udc00\ud800 pair'];
|
2022-06-08 15:39:39 +00:00
|
|
|
|
2022-06-23 13:17:34 +00:00
|
|
|
function IsSurrogate(codepoint) {
|
|
|
|
return 0xD800 <= codepoint && codepoint <= 0xDFFF
|
|
|
|
}
|
|
|
|
function HasIsolatedSurrogate(str) {
|
|
|
|
for (let codepoint of str) {
|
|
|
|
let value = codepoint.codePointAt(0);
|
|
|
|
if (IsSurrogate(value)) return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
function ReplaceIsolatedSurrogates(str, replacement='\ufffd') {
|
|
|
|
let replaced = '';
|
|
|
|
for (let codepoint of str) {
|
|
|
|
replaced +=
|
|
|
|
IsSurrogate(codepoint.codePointAt(0)) ? replacement : codepoint;
|
|
|
|
}
|
|
|
|
return replaced;
|
|
|
|
}
|
|
|
|
|
2022-05-25 11:27:09 +00:00
|
|
|
function makeWtf8TestDataSegment() {
|
|
|
|
let data = []
|
|
|
|
let valid = {};
|
|
|
|
let invalid = {};
|
|
|
|
|
2022-06-08 15:39:39 +00:00
|
|
|
for (let str of interestingStrings) {
|
2022-05-25 11:27:09 +00:00
|
|
|
let bytes = encodeWtf8(str);
|
|
|
|
valid[str] = { offset: data.length, length: bytes.length };
|
|
|
|
for (let byte of bytes) {
|
|
|
|
data.push(byte);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (let bytes of ['trailing high byte \xa9',
|
|
|
|
'interstitial high \xa9 byte',
|
|
|
|
'invalid \xc0 byte',
|
2022-06-27 06:20:00 +00:00
|
|
|
'invalid three-byte \xed\xd0\x80',
|
|
|
|
'surrogate \xed\xa0\x80\xed\xb0\x80 pair']) {
|
2022-05-25 11:27:09 +00:00
|
|
|
invalid[bytes] = { offset: data.length, length: bytes.length };
|
|
|
|
for (let i = 0; i < bytes.length; i++) {
|
|
|
|
data.push(bytes.charCodeAt(i));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return { valid, invalid, data: Uint8Array.from(data) };
|
|
|
|
};
|
|
|
|
|
|
|
|
(function TestStringNewWtf8() {
|
|
|
|
let builder = new WasmModuleBuilder();
|
|
|
|
|
|
|
|
builder.addMemory(1, undefined, false, false);
|
|
|
|
let data = makeWtf8TestDataSegment();
|
|
|
|
builder.addDataSegment(0, data.data);
|
|
|
|
|
2022-06-23 13:17:34 +00:00
|
|
|
builder.addFunction("string_new_utf8", kSig_w_ii)
|
|
|
|
.exportFunc()
|
|
|
|
.addBody([
|
|
|
|
kExprLocalGet, 0, kExprLocalGet, 1,
|
|
|
|
kGCPrefix, kExprStringNewWtf8, 0, kWtf8PolicyReject
|
|
|
|
]);
|
|
|
|
|
2022-05-25 11:27:09 +00:00
|
|
|
builder.addFunction("string_new_wtf8", kSig_w_ii)
|
2022-06-08 15:39:39 +00:00
|
|
|
.exportFunc()
|
2022-05-25 11:27:09 +00:00
|
|
|
.addBody([
|
|
|
|
kExprLocalGet, 0, kExprLocalGet, 1,
|
2022-06-23 13:17:34 +00:00
|
|
|
kGCPrefix, kExprStringNewWtf8, 0, kWtf8PolicyAccept
|
|
|
|
]);
|
|
|
|
|
|
|
|
builder.addFunction("string_new_utf8_sloppy", kSig_w_ii)
|
|
|
|
.exportFunc()
|
|
|
|
.addBody([
|
|
|
|
kExprLocalGet, 0, kExprLocalGet, 1,
|
|
|
|
kGCPrefix, kExprStringNewWtf8, 0, kWtf8PolicyReplace
|
2022-05-25 11:27:09 +00:00
|
|
|
]);
|
|
|
|
|
|
|
|
let instance = builder.instantiate();
|
|
|
|
for (let [str, {offset, length}] of Object.entries(data.valid)) {
|
|
|
|
assertEquals(str, instance.exports.string_new_wtf8(offset, length));
|
2022-06-23 13:17:34 +00:00
|
|
|
if (HasIsolatedSurrogate(str)) {
|
|
|
|
assertThrows(() => instance.exports.string_new_utf8(offset, length),
|
|
|
|
WebAssembly.RuntimeError, "invalid UTF-8 string");
|
|
|
|
|
|
|
|
// Isolated surrogates have the three-byte pattern ED [A0,BF]
|
|
|
|
// [80,BF]. When the sloppy decoder gets to the second byte, it
|
|
|
|
// will reject the sequence, and then retry parsing at the second
|
|
|
|
// byte. Seeing the second byte can't start a sequence, it
|
|
|
|
// replaces the second byte and continues with the next, which
|
|
|
|
// also can't start a sequence. The result is that one isolated
|
|
|
|
// surrogate is replaced by three U+FFFD codepoints.
|
|
|
|
assertEquals(ReplaceIsolatedSurrogates(str, '\ufffd\ufffd\ufffd'),
|
|
|
|
instance.exports.string_new_utf8_sloppy(offset, length));
|
|
|
|
} else {
|
|
|
|
assertEquals(str, instance.exports.string_new_utf8(offset, length));
|
|
|
|
assertEquals(str,
|
|
|
|
instance.exports.string_new_utf8_sloppy(offset, length));
|
|
|
|
}
|
2022-05-25 11:27:09 +00:00
|
|
|
}
|
|
|
|
for (let [str, {offset, length}] of Object.entries(data.invalid)) {
|
|
|
|
assertThrows(() => instance.exports.string_new_wtf8(offset, length),
|
|
|
|
WebAssembly.RuntimeError, "invalid WTF-8 string");
|
2022-06-23 13:17:34 +00:00
|
|
|
assertThrows(() => instance.exports.string_new_utf8(offset, length),
|
|
|
|
WebAssembly.RuntimeError, "invalid UTF-8 string");
|
2022-05-25 11:27:09 +00:00
|
|
|
}
|
|
|
|
})();
|
2022-05-30 13:49:54 +00:00
|
|
|
|
|
|
|
function encodeWtf16LE(str) {
|
|
|
|
// String iterator coalesces surrogate pairs.
|
|
|
|
let out = [];
|
|
|
|
for (let i = 0; i < str.length; i++) {
|
|
|
|
codeunit = str.charCodeAt(i);
|
|
|
|
out.push(codeunit & 0xff)
|
|
|
|
out.push(codeunit >> 8);
|
|
|
|
}
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
|
|
|
|
function makeWtf16TestDataSegment() {
|
|
|
|
let data = []
|
|
|
|
let valid = {};
|
|
|
|
|
2022-06-08 15:39:39 +00:00
|
|
|
for (let str of interestingStrings) {
|
2022-05-30 13:49:54 +00:00
|
|
|
valid[str] = { offset: data.length, length: str.length };
|
|
|
|
for (let byte of encodeWtf16LE(str)) {
|
|
|
|
data.push(byte);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return { valid, data: Uint8Array.from(data) };
|
|
|
|
};
|
|
|
|
|
|
|
|
(function TestStringNewWtf16() {
|
|
|
|
let builder = new WasmModuleBuilder();
|
|
|
|
|
|
|
|
builder.addMemory(1, undefined, false, false);
|
|
|
|
let data = makeWtf16TestDataSegment();
|
|
|
|
builder.addDataSegment(0, data.data);
|
|
|
|
|
|
|
|
builder.addFunction("string_new_wtf16", kSig_w_ii)
|
2022-06-08 15:39:39 +00:00
|
|
|
.exportFunc()
|
2022-05-30 13:49:54 +00:00
|
|
|
.addBody([
|
|
|
|
kExprLocalGet, 0, kExprLocalGet, 1,
|
|
|
|
kGCPrefix, kExprStringNewWtf16, 0
|
|
|
|
]);
|
|
|
|
|
|
|
|
let instance = builder.instantiate();
|
|
|
|
for (let [str, {offset, length}] of Object.entries(data.valid)) {
|
|
|
|
assertEquals(str, instance.exports.string_new_wtf16(offset, length));
|
|
|
|
}
|
|
|
|
})();
|
2022-06-08 15:39:39 +00:00
|
|
|
|
|
|
|
(function TestStringConst() {
|
|
|
|
let builder = new WasmModuleBuilder();
|
|
|
|
for (let [index, str] of interestingStrings.entries()) {
|
|
|
|
builder.addLiteralStringRef(encodeWtf8(str));
|
|
|
|
|
|
|
|
builder.addFunction("string_const" + index, kSig_w_v)
|
|
|
|
.exportFunc()
|
2022-06-20 14:37:49 +00:00
|
|
|
.addBody([kGCPrefix, kExprStringConst, index]);
|
2022-06-08 15:39:39 +00:00
|
|
|
|
2022-06-20 14:37:49 +00:00
|
|
|
builder.addGlobal(kWasmStringRef, false,
|
|
|
|
[kGCPrefix, kExprStringConst, index])
|
2022-06-08 15:39:39 +00:00
|
|
|
.exportAs("global" + index);
|
|
|
|
}
|
|
|
|
|
|
|
|
let instance = builder.instantiate();
|
|
|
|
for (let [index, str] of interestingStrings.entries()) {
|
|
|
|
assertEquals(str, instance.exports["string_const" + index]());
|
|
|
|
assertEquals(str, instance.exports["global" + index].value);
|
|
|
|
}
|
|
|
|
})();
|
2022-06-09 12:05:07 +00:00
|
|
|
|
|
|
|
(function TestStringMeasureUtf8AndWtf8() {
|
|
|
|
let builder = new WasmModuleBuilder();
|
|
|
|
|
|
|
|
builder.addFunction("string_measure_utf8", kSig_i_w)
|
|
|
|
.exportFunc()
|
|
|
|
.addBody([
|
|
|
|
kExprLocalGet, 0,
|
2022-06-22 15:38:14 +00:00
|
|
|
kGCPrefix, kExprStringMeasureWtf8, 0
|
2022-06-09 12:05:07 +00:00
|
|
|
]);
|
|
|
|
|
|
|
|
builder.addFunction("string_measure_wtf8", kSig_i_w)
|
|
|
|
.exportFunc()
|
|
|
|
.addBody([
|
|
|
|
kExprLocalGet, 0,
|
2022-06-22 15:38:14 +00:00
|
|
|
kGCPrefix, kExprStringMeasureWtf8, 1
|
|
|
|
]);
|
|
|
|
|
|
|
|
builder.addFunction("string_measure_wtf8_replace", kSig_i_w)
|
|
|
|
.exportFunc()
|
|
|
|
.addBody([
|
|
|
|
kExprLocalGet, 0,
|
|
|
|
kGCPrefix, kExprStringMeasureWtf8, 2
|
2022-06-09 12:05:07 +00:00
|
|
|
]);
|
|
|
|
|
|
|
|
builder.addFunction("string_measure_utf8_null", kSig_i_v)
|
|
|
|
.exportFunc()
|
|
|
|
.addBody([
|
|
|
|
kExprRefNull, kStringRefCode,
|
2022-06-22 15:38:14 +00:00
|
|
|
kGCPrefix, kExprStringMeasureWtf8, 0
|
2022-06-09 12:05:07 +00:00
|
|
|
]);
|
|
|
|
|
|
|
|
builder.addFunction("string_measure_wtf8_null", kSig_i_v)
|
|
|
|
.exportFunc()
|
|
|
|
.addBody([
|
|
|
|
kExprRefNull, kStringRefCode,
|
2022-06-22 15:38:14 +00:00
|
|
|
kGCPrefix, kExprStringMeasureWtf8, 1
|
|
|
|
]);
|
|
|
|
|
|
|
|
builder.addFunction("string_measure_wtf8_replace_null", kSig_i_v)
|
|
|
|
.exportFunc()
|
|
|
|
.addBody([
|
|
|
|
kExprRefNull, kStringRefCode,
|
|
|
|
kGCPrefix, kExprStringMeasureWtf8, 2
|
2022-06-09 12:05:07 +00:00
|
|
|
]);
|
|
|
|
|
|
|
|
let instance = builder.instantiate();
|
|
|
|
for (let str of interestingStrings) {
|
|
|
|
let wtf8 = encodeWtf8(str);
|
|
|
|
assertEquals(wtf8.length, instance.exports.string_measure_wtf8(str));
|
2022-06-22 15:38:14 +00:00
|
|
|
assertEquals(wtf8.length,
|
|
|
|
instance.exports.string_measure_wtf8_replace(str));
|
2022-06-09 12:05:07 +00:00
|
|
|
if (HasIsolatedSurrogate(str)) {
|
|
|
|
assertEquals(-1, instance.exports.string_measure_utf8(str));
|
|
|
|
} else {
|
|
|
|
assertEquals(wtf8.length, instance.exports.string_measure_utf8(str));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
assertThrows(() => instance.exports.string_measure_utf8_null(),
|
|
|
|
WebAssembly.RuntimeError, "dereferencing a null pointer");
|
|
|
|
assertThrows(() => instance.exports.string_measure_wtf8_null(),
|
|
|
|
WebAssembly.RuntimeError, "dereferencing a null pointer");
|
2022-06-22 15:38:14 +00:00
|
|
|
assertThrows(() => instance.exports.string_measure_wtf8_replace_null(),
|
|
|
|
WebAssembly.RuntimeError, "dereferencing a null pointer");
|
2022-06-09 12:05:07 +00:00
|
|
|
})();
|
2022-06-09 12:15:46 +00:00
|
|
|
|
|
|
|
(function TestStringMeasureWtf16() {
|
|
|
|
let builder = new WasmModuleBuilder();
|
|
|
|
|
|
|
|
builder.addFunction("string_measure_wtf16", kSig_i_w)
|
|
|
|
.exportFunc()
|
|
|
|
.addBody([
|
|
|
|
kExprLocalGet, 0,
|
|
|
|
kGCPrefix, kExprStringMeasureWtf16
|
|
|
|
]);
|
|
|
|
|
|
|
|
builder.addFunction("string_measure_wtf16_null", kSig_i_v)
|
|
|
|
.exportFunc()
|
|
|
|
.addBody([
|
|
|
|
kExprRefNull, kStringRefCode,
|
|
|
|
kGCPrefix, kExprStringMeasureWtf16
|
|
|
|
]);
|
|
|
|
|
|
|
|
let instance = builder.instantiate();
|
|
|
|
for (let str of interestingStrings) {
|
|
|
|
assertEquals(str.length, instance.exports.string_measure_wtf16(str));
|
|
|
|
}
|
|
|
|
|
|
|
|
assertThrows(() => instance.exports.string_measure_wtf16_null(),
|
|
|
|
WebAssembly.RuntimeError, "dereferencing a null pointer");
|
|
|
|
})();
|
2022-06-09 12:22:55 +00:00
|
|
|
|
2022-06-13 15:39:33 +00:00
|
|
|
(function TestStringEncodeWtf8() {
|
|
|
|
let builder = new WasmModuleBuilder();
|
|
|
|
|
|
|
|
builder.addMemory(1, undefined, true /* exported */, false);
|
|
|
|
|
|
|
|
for (let [policy, name] of ["utf8", "wtf8", "replace"].entries()) {
|
2022-07-07 07:49:49 +00:00
|
|
|
builder.addFunction("encode_" + name, kSig_i_wi)
|
2022-06-13 15:39:33 +00:00
|
|
|
.exportFunc()
|
|
|
|
.addBody([
|
|
|
|
kExprLocalGet, 0,
|
|
|
|
kExprLocalGet, 1,
|
|
|
|
kGCPrefix, kExprStringEncodeWtf8, 0, policy,
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2022-07-07 07:49:49 +00:00
|
|
|
builder.addFunction("encode_null", kSig_i_v)
|
2022-06-13 15:39:33 +00:00
|
|
|
.exportFunc()
|
|
|
|
.addBody([
|
|
|
|
kExprRefNull, kStringRefCode,
|
|
|
|
kExprI32Const, 42,
|
|
|
|
kGCPrefix, kExprStringEncodeWtf8, 0, 0,
|
|
|
|
]);
|
|
|
|
|
|
|
|
let instance = builder.instantiate();
|
|
|
|
let memory = new Uint8Array(instance.exports.memory.buffer);
|
|
|
|
function clearMemory(low, high) {
|
|
|
|
for (let i = low; i < high; i++) {
|
|
|
|
memory[i] = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function assertMemoryBytesZero(low, high) {
|
|
|
|
for (let i = low; i < high; i++) {
|
|
|
|
assertEquals(0, memory[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function checkMemory(offset, bytes) {
|
|
|
|
let slop = 64;
|
|
|
|
assertMemoryBytesZero(Math.max(0, offset - slop), offset);
|
|
|
|
for (let i = 0; i < bytes.length; i++) {
|
|
|
|
assertEquals(bytes[i], memory[offset + i]);
|
|
|
|
}
|
|
|
|
assertMemoryBytesZero(offset + bytes.length,
|
|
|
|
Math.min(memory.length,
|
|
|
|
offset + bytes.length + slop));
|
|
|
|
}
|
|
|
|
|
|
|
|
for (let str of interestingStrings) {
|
|
|
|
let wtf8 = encodeWtf8(str);
|
|
|
|
let offset = memory.length - wtf8.length;
|
2022-07-07 07:49:49 +00:00
|
|
|
assertEquals(wtf8.length, instance.exports.encode_wtf8(str, offset));
|
2022-06-13 15:39:33 +00:00
|
|
|
checkMemory(offset, wtf8);
|
|
|
|
clearMemory(offset, offset + wtf8.length);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (let str of interestingStrings) {
|
|
|
|
let offset = 0;
|
|
|
|
if (HasIsolatedSurrogate(str)) {
|
|
|
|
assertThrows(() => instance.exports.encode_utf8(str, offset),
|
|
|
|
WebAssembly.RuntimeError,
|
|
|
|
"Failed to encode string as UTF-8: contains unpaired surrogate");
|
|
|
|
} else {
|
|
|
|
let wtf8 = encodeWtf8(str);
|
2022-07-07 07:49:49 +00:00
|
|
|
assertEquals(wtf8.length, instance.exports.encode_utf8(str, offset));
|
2022-06-13 15:39:33 +00:00
|
|
|
checkMemory(offset, wtf8);
|
|
|
|
clearMemory(offset, offset + wtf8.length);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (let str of interestingStrings) {
|
|
|
|
let offset = 42;
|
2022-06-23 13:17:34 +00:00
|
|
|
let replaced = ReplaceIsolatedSurrogates(str);
|
2022-06-13 15:39:33 +00:00
|
|
|
if (!HasIsolatedSurrogate(str)) assertEquals(str, replaced);
|
|
|
|
let wtf8 = encodeWtf8(replaced);
|
2022-07-07 07:49:49 +00:00
|
|
|
assertEquals(wtf8.length, instance.exports.encode_replace(str, offset));
|
2022-06-13 15:39:33 +00:00
|
|
|
checkMemory(offset, wtf8);
|
|
|
|
clearMemory(offset, offset + wtf8.length);
|
|
|
|
}
|
|
|
|
|
|
|
|
assertThrows(() => instance.exports.encode_null(),
|
|
|
|
WebAssembly.RuntimeError, "dereferencing a null pointer");
|
|
|
|
|
|
|
|
checkMemory(memory.length - 10, []);
|
|
|
|
|
|
|
|
for (let str of interestingStrings) {
|
|
|
|
let wtf8 = encodeWtf8(str);
|
|
|
|
let offset = memory.length - wtf8.length + 1;
|
|
|
|
assertThrows(() => instance.exports.encode_wtf8(str, offset),
|
|
|
|
WebAssembly.RuntimeError, "memory access out of bounds");
|
|
|
|
assertThrows(() => instance.exports.encode_utf8(str, offset),
|
|
|
|
WebAssembly.RuntimeError, "memory access out of bounds");
|
|
|
|
assertThrows(() => instance.exports.encode_replace(str, offset),
|
|
|
|
WebAssembly.RuntimeError, "memory access out of bounds");
|
|
|
|
checkMemory(offset - 1, []);
|
|
|
|
}
|
|
|
|
})();
|
|
|
|
|
2022-06-13 16:10:43 +00:00
|
|
|
(function TestStringEncodeWtf16() {
|
|
|
|
let builder = new WasmModuleBuilder();
|
|
|
|
|
|
|
|
builder.addMemory(1, undefined, true /* exported */, false);
|
|
|
|
|
2022-07-07 07:49:49 +00:00
|
|
|
builder.addFunction("encode_wtf16", kSig_i_wi)
|
2022-06-13 16:10:43 +00:00
|
|
|
.exportFunc()
|
|
|
|
.addBody([
|
|
|
|
kExprLocalGet, 0,
|
|
|
|
kExprLocalGet, 1,
|
|
|
|
kGCPrefix, kExprStringEncodeWtf16, 0,
|
|
|
|
]);
|
|
|
|
|
2022-07-07 07:49:49 +00:00
|
|
|
builder.addFunction("encode_null", kSig_i_v)
|
2022-06-13 16:10:43 +00:00
|
|
|
.exportFunc()
|
|
|
|
.addBody([
|
|
|
|
kExprRefNull, kStringRefCode,
|
|
|
|
kExprI32Const, 42,
|
|
|
|
kGCPrefix, kExprStringEncodeWtf16, 0,
|
|
|
|
]);
|
|
|
|
|
|
|
|
let instance = builder.instantiate();
|
|
|
|
let memory = new Uint8Array(instance.exports.memory.buffer);
|
|
|
|
function clearMemory(low, high) {
|
|
|
|
for (let i = low; i < high; i++) {
|
|
|
|
memory[i] = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function assertMemoryBytesZero(low, high) {
|
|
|
|
for (let i = low; i < high; i++) {
|
|
|
|
assertEquals(0, memory[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function checkMemory(offset, bytes) {
|
|
|
|
let slop = 64;
|
|
|
|
assertMemoryBytesZero(Math.max(0, offset - slop), offset);
|
|
|
|
for (let i = 0; i < bytes.length; i++) {
|
|
|
|
assertEquals(bytes[i], memory[offset + i]);
|
|
|
|
}
|
|
|
|
assertMemoryBytesZero(offset + bytes.length,
|
|
|
|
Math.min(memory.length,
|
|
|
|
offset + bytes.length + slop));
|
|
|
|
}
|
|
|
|
|
|
|
|
for (let str of interestingStrings) {
|
|
|
|
let wtf16 = encodeWtf16LE(str);
|
|
|
|
let offset = memory.length - wtf16.length;
|
2022-07-07 07:49:49 +00:00
|
|
|
assertEquals(str.length, instance.exports.encode_wtf16(str, offset));
|
2022-06-13 16:10:43 +00:00
|
|
|
checkMemory(offset, wtf16);
|
|
|
|
clearMemory(offset, offset + wtf16.length);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (let str of interestingStrings) {
|
|
|
|
let wtf16 = encodeWtf16LE(str);
|
|
|
|
let offset = 0;
|
2022-07-07 07:49:49 +00:00
|
|
|
assertEquals(str.length, instance.exports.encode_wtf16(str, offset));
|
2022-06-13 16:10:43 +00:00
|
|
|
checkMemory(offset, wtf16);
|
|
|
|
clearMemory(offset, offset + wtf16.length);
|
|
|
|
}
|
|
|
|
|
|
|
|
assertThrows(() => instance.exports.encode_null(),
|
|
|
|
WebAssembly.RuntimeError, "dereferencing a null pointer");
|
|
|
|
|
|
|
|
checkMemory(memory.length - 10, []);
|
|
|
|
|
|
|
|
for (let str of interestingStrings) {
|
|
|
|
let offset = 1;
|
|
|
|
assertThrows(() => instance.exports.encode_wtf16(str, offset),
|
|
|
|
WebAssembly.RuntimeError,
|
|
|
|
"operation does not support unaligned accesses");
|
|
|
|
}
|
|
|
|
|
|
|
|
for (let str of interestingStrings) {
|
|
|
|
let wtf16 = encodeWtf16LE(str);
|
|
|
|
let offset = memory.length - wtf16.length + 2;
|
|
|
|
assertThrows(() => instance.exports.encode_wtf16(str, offset),
|
|
|
|
WebAssembly.RuntimeError, "memory access out of bounds");
|
|
|
|
checkMemory(offset - 2, []);
|
|
|
|
}
|
|
|
|
})();
|
|
|
|
|
2022-06-14 06:37:22 +00:00
|
|
|
(function TestStringConcat() {
|
|
|
|
let builder = new WasmModuleBuilder();
|
|
|
|
|
|
|
|
builder.addFunction("concat", kSig_w_ww)
|
|
|
|
.exportFunc()
|
|
|
|
.addBody([
|
|
|
|
kExprLocalGet, 0,
|
|
|
|
kExprLocalGet, 1,
|
|
|
|
kGCPrefix, kExprStringConcat
|
|
|
|
]);
|
|
|
|
|
|
|
|
builder.addFunction("concat_null_head", kSig_w_w)
|
|
|
|
.exportFunc()
|
|
|
|
.addBody([
|
|
|
|
kExprRefNull, kStringRefCode,
|
|
|
|
kExprLocalGet, 0,
|
|
|
|
kGCPrefix, kExprStringConcat
|
|
|
|
]);
|
|
|
|
builder.addFunction("concat_null_tail", kSig_w_w)
|
|
|
|
.exportFunc()
|
|
|
|
.addBody([
|
|
|
|
kExprLocalGet, 0,
|
|
|
|
kExprRefNull, kStringRefCode,
|
|
|
|
kGCPrefix, kExprStringConcat
|
|
|
|
]);
|
|
|
|
|
|
|
|
let instance = builder.instantiate();
|
|
|
|
|
|
|
|
for (let head of interestingStrings) {
|
|
|
|
for (let tail of interestingStrings) {
|
|
|
|
assertEquals(head + tail, instance.exports.concat(head, tail));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
assertThrows(() => instance.exports.concat_null_head("hey"),
|
|
|
|
WebAssembly.RuntimeError, "dereferencing a null pointer");
|
|
|
|
assertThrows(() => instance.exports.concat_null_tail("hey"),
|
|
|
|
WebAssembly.RuntimeError, "dereferencing a null pointer");
|
|
|
|
})();
|
|
|
|
|
2022-06-14 07:37:12 +00:00
|
|
|
(function TestStringEq() {
|
|
|
|
let builder = new WasmModuleBuilder();
|
|
|
|
|
|
|
|
builder.addFunction("eq", kSig_i_ww)
|
|
|
|
.exportFunc()
|
|
|
|
.addBody([
|
|
|
|
kExprLocalGet, 0,
|
|
|
|
kExprLocalGet, 1,
|
|
|
|
kGCPrefix, kExprStringEq
|
|
|
|
]);
|
|
|
|
|
|
|
|
builder.addFunction("eq_null_a", kSig_i_w)
|
|
|
|
.exportFunc()
|
|
|
|
.addBody([
|
|
|
|
kExprRefNull, kStringRefCode,
|
|
|
|
kExprLocalGet, 0,
|
|
|
|
kGCPrefix, kExprStringEq
|
|
|
|
]);
|
|
|
|
builder.addFunction("eq_null_b", kSig_i_w)
|
|
|
|
.exportFunc()
|
|
|
|
.addBody([
|
|
|
|
kExprLocalGet, 0,
|
|
|
|
kExprRefNull, kStringRefCode,
|
|
|
|
kGCPrefix, kExprStringEq
|
|
|
|
]);
|
2022-07-07 06:45:50 +00:00
|
|
|
builder.addFunction("eq_both_null", kSig_i_v)
|
|
|
|
.exportFunc()
|
|
|
|
.addBody([
|
|
|
|
kExprRefNull, kStringRefCode,
|
|
|
|
kExprRefNull, kStringRefCode,
|
|
|
|
kGCPrefix, kExprStringEq
|
|
|
|
]);
|
2022-06-14 07:37:12 +00:00
|
|
|
|
|
|
|
let instance = builder.instantiate();
|
|
|
|
|
|
|
|
for (let head of interestingStrings) {
|
|
|
|
for (let tail of interestingStrings) {
|
|
|
|
let result = (head == tail)|0;
|
|
|
|
assertEquals(result, instance.exports.eq(head, tail));
|
|
|
|
assertEquals(result, instance.exports.eq(head + head, tail + tail));
|
|
|
|
}
|
2022-07-07 06:45:50 +00:00
|
|
|
assertEquals(0, instance.exports.eq_null_a(head))
|
|
|
|
assertEquals(0, instance.exports.eq_null_b(head))
|
2022-06-14 07:37:12 +00:00
|
|
|
}
|
|
|
|
|
2022-07-07 06:45:50 +00:00
|
|
|
assertEquals(1, instance.exports.eq_both_null());
|
2022-06-14 07:37:12 +00:00
|
|
|
})();
|
|
|
|
|
2022-06-14 08:33:01 +00:00
|
|
|
(function TestStringIsUSVSequence() {
|
|
|
|
let builder = new WasmModuleBuilder();
|
|
|
|
|
|
|
|
builder.addFunction("is_usv_sequence", kSig_i_w)
|
|
|
|
.exportFunc()
|
|
|
|
.addBody([
|
|
|
|
kExprLocalGet, 0,
|
|
|
|
kGCPrefix, kExprStringIsUsvSequence
|
|
|
|
]);
|
|
|
|
|
|
|
|
builder.addFunction("is_usv_sequence_null", kSig_i_v)
|
|
|
|
.exportFunc()
|
|
|
|
.addBody([
|
|
|
|
kExprRefNull, kStringRefCode,
|
|
|
|
kGCPrefix, kExprStringIsUsvSequence
|
|
|
|
]);
|
|
|
|
|
|
|
|
let instance = builder.instantiate();
|
|
|
|
|
|
|
|
for (let str of interestingStrings) {
|
|
|
|
assertEquals(HasIsolatedSurrogate(str) ? 0 : 1,
|
|
|
|
instance.exports.is_usv_sequence(str));
|
|
|
|
}
|
|
|
|
|
|
|
|
assertThrows(() => instance.exports.is_usv_sequence_null(),
|
|
|
|
WebAssembly.RuntimeError, "dereferencing a null pointer");
|
|
|
|
})();
|
|
|
|
|
2022-06-09 12:22:55 +00:00
|
|
|
(function TestStringViewWtf16() {
|
|
|
|
let builder = new WasmModuleBuilder();
|
|
|
|
|
2022-06-14 11:38:14 +00:00
|
|
|
builder.addMemory(1, undefined, true /* exported */, false);
|
|
|
|
|
|
|
|
builder.addFunction("length", kSig_i_w)
|
2022-06-09 12:22:55 +00:00
|
|
|
.exportFunc()
|
|
|
|
.addBody([
|
|
|
|
kExprLocalGet, 0,
|
|
|
|
kGCPrefix, kExprStringAsWtf16,
|
|
|
|
kGCPrefix, kExprStringViewWtf16Length
|
|
|
|
]);
|
|
|
|
|
2022-06-14 11:38:14 +00:00
|
|
|
builder.addFunction("length_null", kSig_i_v)
|
2022-06-09 12:22:55 +00:00
|
|
|
.exportFunc()
|
|
|
|
.addBody([
|
|
|
|
kExprRefNull, kStringViewWtf16Code,
|
|
|
|
kGCPrefix, kExprStringViewWtf16Length
|
|
|
|
]);
|
|
|
|
|
2022-06-14 11:38:14 +00:00
|
|
|
builder.addFunction("get_codeunit", kSig_i_wi)
|
2022-06-10 14:10:21 +00:00
|
|
|
.exportFunc()
|
|
|
|
.addBody([
|
|
|
|
kExprLocalGet, 0,
|
|
|
|
kGCPrefix, kExprStringAsWtf16,
|
|
|
|
kExprLocalGet, 1,
|
|
|
|
kGCPrefix, kExprStringViewWtf16GetCodeunit
|
|
|
|
]);
|
|
|
|
|
2022-06-14 11:38:14 +00:00
|
|
|
builder.addFunction("get_codeunit_null", kSig_i_v)
|
2022-06-10 14:10:21 +00:00
|
|
|
.exportFunc()
|
|
|
|
.addBody([
|
|
|
|
kExprRefNull, kStringViewWtf16Code,
|
|
|
|
kExprI32Const, 0,
|
|
|
|
kGCPrefix, kExprStringViewWtf16GetCodeunit
|
|
|
|
]);
|
|
|
|
|
2022-07-07 07:49:49 +00:00
|
|
|
builder.addFunction("encode", kSig_i_wiii)
|
2022-06-14 11:38:14 +00:00
|
|
|
.exportFunc()
|
|
|
|
.addBody([
|
|
|
|
kExprLocalGet, 0,
|
|
|
|
kGCPrefix, kExprStringAsWtf16,
|
|
|
|
kExprLocalGet, 1,
|
|
|
|
kExprLocalGet, 2,
|
|
|
|
kExprLocalGet, 3,
|
|
|
|
kGCPrefix, kExprStringViewWtf16Encode, 0
|
|
|
|
]);
|
|
|
|
|
2022-07-07 07:49:49 +00:00
|
|
|
builder.addFunction("encode_null", kSig_i_v)
|
2022-06-14 11:38:14 +00:00
|
|
|
.exportFunc()
|
|
|
|
.addBody([
|
|
|
|
kExprRefNull, kStringViewWtf16Code,
|
|
|
|
kExprI32Const, 0,
|
|
|
|
kExprI32Const, 0,
|
|
|
|
kExprI32Const, 0,
|
|
|
|
kGCPrefix, kExprStringViewWtf16Encode, 0
|
|
|
|
]);
|
|
|
|
|
|
|
|
builder.addFunction("slice", kSig_w_wii)
|
2022-06-10 18:22:29 +00:00
|
|
|
.exportFunc()
|
|
|
|
.addBody([
|
|
|
|
kExprLocalGet, 0,
|
|
|
|
kGCPrefix, kExprStringAsWtf16,
|
|
|
|
kExprLocalGet, 1,
|
|
|
|
kExprLocalGet, 2,
|
|
|
|
kGCPrefix, kExprStringViewWtf16Slice
|
|
|
|
]);
|
|
|
|
|
2022-06-14 11:38:14 +00:00
|
|
|
builder.addFunction("slice_null", kSig_w_v)
|
2022-06-10 18:22:29 +00:00
|
|
|
.exportFunc()
|
|
|
|
.addBody([
|
|
|
|
kExprRefNull, kStringViewWtf16Code,
|
|
|
|
kExprI32Const, 0,
|
|
|
|
kExprI32Const, 0,
|
|
|
|
kGCPrefix, kExprStringViewWtf16Slice
|
|
|
|
]);
|
|
|
|
|
2022-06-09 12:22:55 +00:00
|
|
|
let instance = builder.instantiate();
|
2022-06-14 11:38:14 +00:00
|
|
|
let memory = new Uint8Array(instance.exports.memory.buffer);
|
2022-06-09 12:22:55 +00:00
|
|
|
for (let str of interestingStrings) {
|
2022-06-14 11:38:14 +00:00
|
|
|
assertEquals(str.length, instance.exports.length(str));
|
2022-06-10 14:10:21 +00:00
|
|
|
for (let i = 0; i < str.length; i++) {
|
|
|
|
assertEquals(str.charCodeAt(i),
|
2022-06-14 11:38:14 +00:00
|
|
|
instance.exports.get_codeunit(str, i));
|
2022-06-10 14:10:21 +00:00
|
|
|
}
|
2022-06-14 11:38:14 +00:00
|
|
|
assertEquals(str, instance.exports.slice(str, 0, -1));
|
2022-06-09 12:22:55 +00:00
|
|
|
}
|
|
|
|
|
2022-06-14 11:38:14 +00:00
|
|
|
function checkEncoding(str, slice, start, length) {
|
|
|
|
let bytes = encodeWtf16LE(slice);
|
|
|
|
function clearMemory(low, high) {
|
|
|
|
for (let i = low; i < high; i++) {
|
|
|
|
memory[i] = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function assertMemoryBytesZero(low, high) {
|
|
|
|
for (let i = low; i < high; i++) {
|
|
|
|
assertEquals(0, memory[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function checkMemory(offset, bytes) {
|
|
|
|
let slop = 64;
|
|
|
|
assertMemoryBytesZero(Math.max(0, offset - slop), offset);
|
|
|
|
for (let i = 0; i < bytes.length; i++) {
|
|
|
|
assertEquals(bytes[i], memory[offset + i]);
|
|
|
|
}
|
|
|
|
assertMemoryBytesZero(offset + bytes.length,
|
|
|
|
Math.min(memory.length,
|
|
|
|
offset + bytes.length + slop));
|
|
|
|
}
|
2022-06-10 18:22:29 +00:00
|
|
|
|
2022-06-14 11:38:14 +00:00
|
|
|
for (let offset of [0, 42, memory.length - bytes.length]) {
|
2022-07-07 07:49:49 +00:00
|
|
|
assertEquals(slice.length,
|
|
|
|
instance.exports.encode(str, offset, start, length));
|
2022-06-14 11:38:14 +00:00
|
|
|
checkMemory(offset, bytes);
|
|
|
|
clearMemory(offset, offset + bytes.length);
|
|
|
|
}
|
|
|
|
|
|
|
|
assertThrows(() => instance.exports.encode(str, 1, start, length),
|
|
|
|
WebAssembly.RuntimeError,
|
|
|
|
"operation does not support unaligned accesses");
|
|
|
|
assertThrows(
|
|
|
|
() => instance.exports.encode(str, memory.length - bytes.length + 2,
|
|
|
|
start, length),
|
|
|
|
WebAssembly.RuntimeError, "memory access out of bounds");
|
|
|
|
checkMemory(memory.length - bytes.length - 2, []);
|
|
|
|
}
|
|
|
|
checkEncoding("fox", "f", 0, 1);
|
|
|
|
checkEncoding("fox", "fo", 0, 2);
|
|
|
|
checkEncoding("fox", "fox", 0, 3);
|
|
|
|
checkEncoding("fox", "fox", 0, 300);
|
|
|
|
checkEncoding("fox", "", 1, 0);
|
|
|
|
checkEncoding("fox", "o", 1, 1);
|
|
|
|
checkEncoding("fox", "ox", 1, 2);
|
|
|
|
checkEncoding("fox", "ox", 1, 200);
|
|
|
|
checkEncoding("fox", "", 2, 0);
|
|
|
|
checkEncoding("fox", "x", 2, 1);
|
|
|
|
checkEncoding("fox", "x", 2, 2);
|
|
|
|
checkEncoding("fox", "", 3, 0);
|
|
|
|
checkEncoding("fox", "", 3, 1_000_000_000);
|
|
|
|
checkEncoding("fox", "", 1_000_000_000, 1_000_000_000);
|
|
|
|
checkEncoding("fox", "", 100, 100);
|
|
|
|
// Bounds checks before alignment checks.
|
|
|
|
assertThrows(() => instance.exports.encode("foo", memory.length - 1, 0, 3),
|
|
|
|
WebAssembly.RuntimeError, "memory access out of bounds");
|
|
|
|
|
2022-07-07 06:26:29 +00:00
|
|
|
assertEquals("", instance.exports.slice("foo", 0, 0));
|
|
|
|
assertEquals("f", instance.exports.slice("foo", 0, 1));
|
|
|
|
assertEquals("fo", instance.exports.slice("foo", 0, 2));
|
|
|
|
assertEquals("foo", instance.exports.slice("foo", 0, 3));
|
|
|
|
assertEquals("foo", instance.exports.slice("foo", 0, 4));
|
|
|
|
assertEquals("o", instance.exports.slice("foo", 1, 2));
|
|
|
|
assertEquals("oo", instance.exports.slice("foo", 1, 3));
|
2022-06-14 11:38:14 +00:00
|
|
|
assertEquals("oo", instance.exports.slice("foo", 1, 100));
|
|
|
|
assertEquals("", instance.exports.slice("foo", 1, 0));
|
|
|
|
|
|
|
|
assertThrows(() => instance.exports.length_null(),
|
2022-06-10 14:10:21 +00:00
|
|
|
WebAssembly.RuntimeError, "dereferencing a null pointer");
|
2022-06-14 11:38:14 +00:00
|
|
|
assertThrows(() => instance.exports.get_codeunit_null(),
|
2022-06-09 12:22:55 +00:00
|
|
|
WebAssembly.RuntimeError, "dereferencing a null pointer");
|
2022-06-14 11:38:14 +00:00
|
|
|
assertThrows(() => instance.exports.get_codeunit("", 0),
|
2022-06-10 14:10:21 +00:00
|
|
|
WebAssembly.RuntimeError, "string offset out of bounds");
|
2022-06-14 11:38:14 +00:00
|
|
|
assertThrows(() => instance.exports.encode_null(),
|
|
|
|
WebAssembly.RuntimeError, "dereferencing a null pointer");
|
|
|
|
assertThrows(() => instance.exports.slice_null(),
|
2022-06-10 18:22:29 +00:00
|
|
|
WebAssembly.RuntimeError, "dereferencing a null pointer");
|
2022-06-09 12:22:55 +00:00
|
|
|
})();
|
2022-07-08 09:59:20 +00:00
|
|
|
|
|
|
|
(function TestStringViewWtf8() {
|
|
|
|
let builder = new WasmModuleBuilder();
|
|
|
|
|
2022-07-12 14:12:35 +00:00
|
|
|
builder.addMemory(1, undefined, true /* exported */, false);
|
|
|
|
|
2022-07-08 09:59:20 +00:00
|
|
|
builder.addFunction("advance", kSig_i_wii)
|
|
|
|
.exportFunc()
|
|
|
|
.addBody([
|
|
|
|
kExprLocalGet, 0,
|
|
|
|
kGCPrefix, kExprStringAsWtf8,
|
|
|
|
kExprLocalGet, 1,
|
|
|
|
kExprLocalGet, 2,
|
|
|
|
kGCPrefix, kExprStringViewWtf8Advance
|
|
|
|
]);
|
|
|
|
|
|
|
|
builder.addFunction("advance_null", kSig_i_v)
|
|
|
|
.exportFunc()
|
|
|
|
.addBody([
|
|
|
|
kExprRefNull, kStringViewWtf8Code,
|
|
|
|
kExprI32Const, 0,
|
|
|
|
kExprI32Const, 0,
|
|
|
|
kGCPrefix, kExprStringViewWtf8Advance
|
|
|
|
]);
|
|
|
|
|
2022-07-12 14:12:35 +00:00
|
|
|
for (let [name, policy] of Object.entries({utf8: kWtf8PolicyReject,
|
|
|
|
wtf8: kWtf8PolicyAccept,
|
|
|
|
replace: kWtf8PolicyReplace})) {
|
|
|
|
builder.addFunction(`encode_${name}`, kSig_ii_wiii)
|
|
|
|
.exportFunc()
|
|
|
|
.addBody([
|
|
|
|
kExprLocalGet, 0,
|
|
|
|
kGCPrefix, kExprStringAsWtf8,
|
|
|
|
kExprLocalGet, 1,
|
|
|
|
kExprLocalGet, 2,
|
|
|
|
kExprLocalGet, 3,
|
|
|
|
kGCPrefix, kExprStringViewWtf8Encode, 0, policy
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
builder.addFunction("encode_null", kSig_v_v)
|
|
|
|
.exportFunc()
|
|
|
|
.addBody([
|
|
|
|
kExprRefNull, kStringViewWtf8Code,
|
|
|
|
kExprI32Const, 0,
|
|
|
|
kExprI32Const, 0,
|
|
|
|
kExprI32Const, 0,
|
|
|
|
kGCPrefix, kExprStringViewWtf8Encode, 0, kWtf8PolicyAccept,
|
|
|
|
kExprDrop,
|
|
|
|
kExprDrop
|
|
|
|
]);
|
|
|
|
|
2022-07-08 09:59:20 +00:00
|
|
|
function Wtf8StartsCodepoint(wtf8, offset) {
|
|
|
|
return (wtf8[offset] & 0xc0) != 0x80;
|
|
|
|
}
|
|
|
|
function Wtf8PositionTreatment(wtf8, offset) {
|
|
|
|
while (offset < wtf8.length) {
|
|
|
|
if (Wtf8StartsCodepoint(wtf8, offset)) return offset;
|
|
|
|
offset++;
|
|
|
|
}
|
|
|
|
return wtf8.length;
|
|
|
|
}
|
|
|
|
function CodepointStart(wtf8, offset) {
|
|
|
|
if (offset >= wtf8.length) return wtf8.length;
|
|
|
|
while (!Wtf8StartsCodepoint(wtf8, offset)) {
|
|
|
|
offset--;
|
|
|
|
}
|
|
|
|
return offset;
|
|
|
|
}
|
|
|
|
|
|
|
|
let instance = builder.instantiate();
|
2022-07-12 14:12:35 +00:00
|
|
|
let memory = new Uint8Array(instance.exports.memory.buffer);
|
2022-07-08 09:59:20 +00:00
|
|
|
|
|
|
|
for (let pos = 0; pos < "ascii".length; pos++) {
|
|
|
|
assertEquals(pos + 1, instance.exports.advance("ascii", pos, 1));
|
|
|
|
}
|
|
|
|
|
|
|
|
for (let str of interestingStrings) {
|
|
|
|
let wtf8 = encodeWtf8(str);
|
|
|
|
assertEquals(wtf8.length, instance.exports.advance(str, 0, -1));
|
|
|
|
assertEquals(wtf8.length, instance.exports.advance(str, -1, 0));
|
|
|
|
assertEquals(wtf8.length, instance.exports.advance(str, 0, wtf8.length));
|
|
|
|
assertEquals(wtf8.length, instance.exports.advance(str, wtf8.length, 0));
|
|
|
|
assertEquals(wtf8.length,
|
|
|
|
instance.exports.advance(str, 0, wtf8.length + 1));
|
|
|
|
assertEquals(wtf8.length,
|
|
|
|
instance.exports.advance(str, wtf8.length + 1, 0));
|
2022-07-12 14:12:35 +00:00
|
|
|
for (let pos = 0; pos <= wtf8.length; pos++) {
|
|
|
|
for (let bytes = 0; bytes <= wtf8.length - pos; bytes++) {
|
2022-07-08 09:59:20 +00:00
|
|
|
assertEquals(
|
|
|
|
CodepointStart(wtf8, Wtf8PositionTreatment(wtf8, pos) + bytes),
|
|
|
|
instance.exports.advance(str, pos, bytes));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-12 14:12:35 +00:00
|
|
|
function checkEncoding(variant, str, slice, start, length) {
|
|
|
|
let all_bytes = encodeWtf8(str);
|
|
|
|
let bytes = encodeWtf8(slice);
|
|
|
|
function clearMemory(low, high) {
|
|
|
|
for (let i = low; i < high; i++) {
|
|
|
|
memory[i] = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function assertMemoryBytesZero(low, high) {
|
|
|
|
for (let i = low; i < high; i++) {
|
|
|
|
assertEquals(0, memory[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function checkMemory(offset, bytes) {
|
|
|
|
let slop = 64;
|
|
|
|
assertMemoryBytesZero(Math.max(0, offset - slop), offset);
|
|
|
|
for (let i = 0; i < bytes.length; i++) {
|
|
|
|
assertEquals(bytes[i], memory[offset + i]);
|
|
|
|
}
|
|
|
|
assertMemoryBytesZero(offset + bytes.length,
|
|
|
|
Math.min(memory.length,
|
|
|
|
offset + bytes.length + slop));
|
|
|
|
}
|
|
|
|
|
|
|
|
let encode = instance.exports[`encode_${variant}`];
|
|
|
|
let expected_start = Wtf8PositionTreatment(all_bytes, start);
|
|
|
|
let expected_end = CodepointStart(all_bytes, expected_start + bytes.length);
|
|
|
|
for (let offset of [0, 42, memory.length - bytes.length]) {
|
|
|
|
assertArrayEquals([expected_end, expected_end - expected_start],
|
|
|
|
encode(str, offset, start, length));
|
|
|
|
checkMemory(offset, bytes);
|
|
|
|
clearMemory(offset, offset + bytes.length);
|
|
|
|
}
|
|
|
|
|
|
|
|
assertThrows(() => encode(str, memory.length - bytes.length + 2,
|
|
|
|
start, length),
|
|
|
|
WebAssembly.RuntimeError, "memory access out of bounds");
|
|
|
|
checkMemory(memory.length - bytes.length - 2, []);
|
|
|
|
}
|
|
|
|
|
|
|
|
checkEncoding('utf8', "fox", "f", 0, 1);
|
|
|
|
checkEncoding('utf8', "fox", "fo", 0, 2);
|
|
|
|
checkEncoding('utf8', "fox", "fox", 0, 3);
|
|
|
|
checkEncoding('utf8', "fox", "fox", 0, 300);
|
|
|
|
checkEncoding('utf8', "fox", "", 1, 0);
|
|
|
|
checkEncoding('utf8', "fox", "o", 1, 1);
|
|
|
|
checkEncoding('utf8', "fox", "ox", 1, 2);
|
|
|
|
checkEncoding('utf8', "fox", "ox", 1, 200);
|
|
|
|
checkEncoding('utf8', "fox", "", 2, 0);
|
|
|
|
checkEncoding('utf8', "fox", "x", 2, 1);
|
|
|
|
checkEncoding('utf8', "fox", "x", 2, 2);
|
|
|
|
checkEncoding('utf8', "fox", "", 3, 0);
|
|
|
|
checkEncoding('utf8', "fox", "", 3, 1_000_000_000);
|
|
|
|
checkEncoding('utf8', "fox", "", 1_000_000_000, 1_000_000_000);
|
|
|
|
checkEncoding('utf8', "fox", "", 100, 100);
|
|
|
|
|
|
|
|
for (let str of interestingStrings) {
|
|
|
|
let wtf8 = encodeWtf8(str);
|
|
|
|
for (let pos = 0; pos <= wtf8.length; pos++) {
|
|
|
|
for (let bytes = 0; bytes <= wtf8.length - pos; bytes++) {
|
|
|
|
let start = Wtf8PositionTreatment(wtf8, pos);
|
|
|
|
let end = CodepointStart(wtf8, start + bytes);
|
|
|
|
let expected = decodeWtf8(wtf8, start, end);
|
|
|
|
checkEncoding('wtf8', str, expected, pos, bytes);
|
|
|
|
if (HasIsolatedSurrogate(expected)) {
|
|
|
|
assertThrows(() => instance.exports.encode_utf8(str, 0, pos, bytes),
|
|
|
|
WebAssembly.RuntimeError,
|
|
|
|
"Failed to encode string as UTF-8: " +
|
|
|
|
"contains unpaired surrogate");
|
|
|
|
checkEncoding('replace', str,
|
|
|
|
ReplaceIsolatedSurrogates(expected), pos, bytes);
|
|
|
|
} else {
|
|
|
|
checkEncoding('utf8', str, expected, pos, bytes);
|
|
|
|
checkEncoding('replace', str, expected, pos, bytes);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-08 09:59:20 +00:00
|
|
|
assertThrows(() => instance.exports.advance_null(),
|
|
|
|
WebAssembly.RuntimeError, "dereferencing a null pointer");
|
2022-07-12 14:12:35 +00:00
|
|
|
assertThrows(() => instance.exports.encode_null(),
|
|
|
|
WebAssembly.RuntimeError, "dereferencing a null pointer");
|
2022-07-08 09:59:20 +00:00
|
|
|
})();
|