node/deps/v8/test/mjsunit/wasm/imported-strings.js
Michaël Zasso 1d29d81c69 deps: update V8 to 12.3.219.16
PR-URL: https://github.com/nodejs/node/pull/52293
Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
Reviewed-By: Richard Lau <rlau@redhat.com>
2024-04-19 08:39:47 +00:00

1143 lines
38 KiB
JavaScript

// Copyright 2023 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-imported-strings
// For {isOneByteString}:
// Flags: --expose-externalize-string
d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
let kRefExtern = wasmRefType(kWasmExternRef);
// We use "r" for nullable "externref", and "e" for non-nullable "ref extern".
let kSig_e_ii = makeSig([kWasmI32, kWasmI32], [kRefExtern]);
let kSig_e_v = makeSig([], [kRefExtern]);
let kSig_i_ri = makeSig([kWasmExternRef, kWasmI32], [kWasmI32]);
let kSig_i_rii = makeSig([kWasmExternRef, kWasmI32, kWasmI32], [kWasmI32]);
let kSig_i_rr = makeSig([kWasmExternRef, kWasmExternRef], [kWasmI32]);
let kSig_i_rri = makeSig([kWasmExternRef, kWasmExternRef, kWasmI32], [kWasmI32]);
let kSig_i_riii = makeSig([kWasmExternRef, kWasmI32, kWasmI32, kWasmI32],
[kWasmI32]);
let kSig_ii_riii = makeSig([kWasmExternRef, kWasmI32, kWasmI32, kWasmI32],
[kWasmI32, kWasmI32]);
let kSig_e_i = makeSig([kWasmI32], [kRefExtern]);
let kSig_e_rii = makeSig([kWasmExternRef, kWasmI32, kWasmI32],
[kRefExtern]);
let kSig_e_rr = makeSig([kWasmExternRef, kWasmExternRef], [kRefExtern]);
let kSig_e_r = makeSig([kWasmExternRef], [kRefExtern]);
let interestingStrings = [
'',
'ascii',
'latin\xa91', // Latin-1.
'2 \ucccc b', // Two-byte.
'a \ud800\udc00 b', // Proper surrogate pair.
'a \ud800 b', // Lone lead surrogate.
'a \udc00 b', // Lone trail surrogate.
'\ud800 bc', // Lone lead surrogate at the start.
'\udc00 bc', // Lone trail surrogate at the start.
'ab \ud800', // Lone lead surrogate at the end.
'ab \udc00', // Lone trail surrogate at the end.
'a \udc00\ud800 b', // Swapped surrogate pair.
];
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;
}
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;
}
let kArrayI16;
let kArrayI8;
let kStringCast;
let kStringTest;
let kStringFromWtf16Array;
let kStringFromUtf8Array;
let kStringIntoUtf8Array;
let kStringToUtf8Array;
let kStringToWtf16Array;
let kStringMeasureUtf8;
let kStringFromCharCode;
let kStringFromCodePoint;
let kStringCharCodeAt;
let kStringCodePointAt;
let kStringLength;
let kStringConcat;
let kStringSubstring;
let kStringEquals;
let kStringCompare;
let kStringIndexOfImported;
let kStringToLowerCaseImported;
function MakeBuilder() {
let builder = new WasmModuleBuilder();
builder.startRecGroup();
kArrayI16 = builder.addArray(kWasmI16, true, kNoSuperType, true);
builder.endRecGroup();
builder.startRecGroup();
kArrayI8 = builder.addArray(kWasmI8, true, kNoSuperType, true);
builder.endRecGroup();
let arrayref = wasmRefNullType(kArrayI16);
let array8ref = wasmRefNullType(kArrayI8);
kStringCast = builder.addImport('wasm:js-string', 'cast', kSig_e_r);
kStringTest = builder.addImport('wasm:js-string', 'test', kSig_i_r);
kStringFromWtf16Array = builder.addImport(
'wasm:js-string', 'fromCharCodeArray',
makeSig([arrayref, kWasmI32, kWasmI32], [kRefExtern]));
kStringFromUtf8Array = builder.addImport(
'wasm:text-decoder', 'decodeStringFromUTF8Array',
makeSig([array8ref, kWasmI32, kWasmI32], [kRefExtern]));
kStringToWtf16Array = builder.addImport(
'wasm:js-string', 'intoCharCodeArray',
makeSig([kWasmExternRef, arrayref, kWasmI32], [kWasmI32]));
kStringMeasureUtf8 =
builder.addImport('wasm:text-encoder', 'measureStringAsUTF8', kSig_i_r);
kStringIntoUtf8Array = builder.addImport(
'wasm:text-encoder', 'encodeStringIntoUTF8Array',
makeSig([kWasmExternRef, array8ref, kWasmI32], [kWasmI32]));
kStringToUtf8Array = builder.addImport(
'wasm:text-encoder', 'encodeStringToUTF8Array',
makeSig([kWasmExternRef], [wasmRefType(kArrayI8)]));
kStringFromCharCode =
builder.addImport('wasm:js-string', 'fromCharCode', kSig_e_i);
kStringFromCodePoint =
builder.addImport('wasm:js-string', 'fromCodePoint', kSig_e_i);
kStringCharCodeAt =
builder.addImport('wasm:js-string', 'charCodeAt', kSig_i_ri);
kStringCodePointAt =
builder.addImport('wasm:js-string', 'codePointAt', kSig_i_ri);
kStringLength = builder.addImport('wasm:js-string', 'length', kSig_i_r);
kStringConcat = builder.addImport('wasm:js-string', 'concat', kSig_e_rr);
kStringSubstring =
builder.addImport('wasm:js-string', 'substring', kSig_e_rii);
kStringEquals = builder.addImport('wasm:js-string', 'equals', kSig_i_rr);
kStringCompare = builder.addImport('wasm:js-string', 'compare', kSig_i_rr);
kStringIndexOfImported = builder.addImport('m', 'indexOf', kSig_i_rri);
kStringToLowerCaseImported = builder.addImport('m', 'toLowerCase', kSig_r_r);
return builder;
}
let kImports = {
strings: interestingStrings,
m: {
indexOf: Function.prototype.call.bind(String.prototype.indexOf),
toLowerCase: Function.prototype.call.bind(String.prototype.toLowerCase),
},
};
let kBuiltins = { builtins: ["js-string", "text-decoder", "text-encoder"] };
(function TestStringCast() {
print(arguments.callee.name);
let builder = MakeBuilder();
builder.addFunction("cast", kSig_e_r)
.exportFunc()
.addBody([
kExprLocalGet, 0,
kExprCallFunction, kStringCast,
]);
builder.addFunction("cast_null", kSig_e_v)
.exportFunc()
.addBody([
kExprRefNull, kExternRefCode,
kExprCallFunction, kStringCast,
]);
let instance = builder.instantiate(kImports, kBuiltins);
assertEquals('foo', instance.exports.cast('foo'));
assertThrows(
() => instance.exports.cast(123), WebAssembly.RuntimeError,
'illegal cast');
assertThrows(
() => instance.exports.cast(undefined), WebAssembly.RuntimeError,
'illegal cast');
assertThrows(
() => instance.exports.cast(true), WebAssembly.RuntimeError,
'illegal cast');
assertThrows(
() => instance.exports.cast(null), WebAssembly.RuntimeError,
'illegal cast');
assertThrows(
() => instance.exports.cast_null(), WebAssembly.RuntimeError,
'illegal cast');
})();
(function TestStringTest() {
print(arguments.callee.name);
let builder = MakeBuilder();
builder.addFunction("test", kSig_i_r)
.exportFunc()
.addBody([
kExprLocalGet, 0,
kExprCallFunction, kStringTest,
]);
builder.addFunction("test_null", kSig_i_v)
.exportFunc()
.addBody([
kExprRefNull, kExternRefCode,
kExprCallFunction, kStringTest,
]);
let instance = builder.instantiate(kImports, kBuiltins);
assertEquals(1, instance.exports.test("foo"));
assertEquals(0, instance.exports.test(123));
assertEquals(0, instance.exports.test(undefined));
assertEquals(0, instance.exports.test(true));
assertEquals(0, instance.exports.test(null));
assertEquals(0, instance.exports.test_null());
})();
(function TestIndexOfImportedStrings() {
print(arguments.callee.name);
let builder = new MakeBuilder();
builder.addFunction('indexOf', kSig_i_rri).exportFunc().addBody([
kExprLocalGet, 0,
kExprCallFunction, kStringCast,
kExprLocalGet, 1,
kExprCallFunction, kStringCast,
kExprLocalGet, 2,
kExprCallFunction, kStringIndexOfImported,
]);
let instance = builder.instantiate(kImports, kBuiltins);
assertEquals(2, instance.exports.indexOf('xxfooxx', 'foo', 0));
assertEquals(2, instance.exports.indexOf('xxfooxx', 'foo', -2));
assertEquals(-1, instance.exports.indexOf('xxfooxx', 'foo', 100));
// Make sure we don't lose bits when Smi-tagging of the start position.
assertEquals(-1, instance.exports.indexOf('xxfooxx', 'foo', 0x4000_0000));
assertEquals(-1, instance.exports.indexOf('xxfooxx', 'foo', 0x2000_0000));
assertEquals(
2,
instance.exports.indexOf(
'xxfooxx', 'foo', 0x8000_0000)); // Negative i32.
// Both first and second args should be non-null strings.
assertThrows(
() => instance.exports.indexOf('xxnullxx', null, 0),
WebAssembly.RuntimeError, 'illegal cast');
assertThrows(
() => instance.exports.indexOf(12345, 234, 0), WebAssembly.RuntimeError,
'illegal cast');
assertThrows(
() => instance.exports.indexOf(null, 'foo', 0), WebAssembly.RuntimeError,
'illegal cast');
assertThrows(
() => instance.exports.indexOf(null, 'null', 0), WebAssembly.RuntimeError,
'illegal cast');
})();
(function TestStringToLowerCaseImported() {
print(arguments.callee.name);
let builder = new MakeBuilder();
builder.addFunction('toLowerCase', kSig_r_r).exportFunc().addBody([
kExprLocalGet, 0,
kExprCallFunction, kStringCast,
kExprCallFunction, kStringToLowerCaseImported,
]);
let instance = builder.instantiate(kImports, kBuiltins);
assertEquals(
'make this lowercase!',
instance.exports.toLowerCase('MAKE THIS LOWERCASE!'));
// The argument should be a non-null string.
assertThrows(
() => instance.exports.toLowerCase(null), WebAssembly.RuntimeError,
'illegal cast');
assertThrows(
() => instance.exports.toLowerCase(123), WebAssembly.RuntimeError,
'illegal cast');
})();
(function TestStringConst() {
print(arguments.callee.name);
let builder = MakeBuilder();
for (let i = 0; i < interestingStrings.length; i++) {
builder.addImportedGlobal('strings', i.toString(), kRefExtern, false);
builder.addFunction("string_const" + i, kSig_e_v).exportFunc()
.addBody([kExprGlobalGet, i]);
}
for (let i = 0; i < interestingStrings.length; i++) {
builder.addGlobal(kRefExtern, false, false, [kExprGlobalGet, i])
.exportAs("global" + i);
}
let instance = builder.instantiate(kImports, kBuiltins);
for (let [i, str] of interestingStrings.entries()) {
assertEquals(str, instance.exports["string_const" + i]());
assertEquals(str, instance.exports["global" + i].value);
}
})();
(function TestStringLength() {
print(arguments.callee.name);
let builder = MakeBuilder();
builder.addFunction("string_length", kSig_i_r)
.exportFunc()
.addBody([
kExprLocalGet, 0,
kExprCallFunction, kStringLength,
]);
builder.addFunction("string_length_null", kSig_i_v)
.exportFunc()
.addBody([
kExprRefNull, kExternRefCode,
kExprCallFunction, kStringLength,
]);
let instance = builder.instantiate(kImports, kBuiltins);
for (let str of interestingStrings) {
assertEquals(str.length, instance.exports.string_length(str));
}
assertThrows(() => instance.exports.string_length(null),
WebAssembly.RuntimeError, "illegal cast");
assertThrows(() => instance.exports.string_length_null(),
WebAssembly.RuntimeError, "illegal cast");
})();
(function TestStringConcat() {
print(arguments.callee.name);
let builder = MakeBuilder();
builder.addFunction("concat", kSig_e_rr)
.exportFunc()
.addBody([
kExprLocalGet, 0,
kExprLocalGet, 1,
kExprCallFunction, kStringConcat,
]);
builder.addFunction("concat_null_head", kSig_e_r)
.exportFunc()
.addBody([
kExprRefNull, kExternRefCode,
kExprLocalGet, 0,
kExprCallFunction, kStringConcat,
]);
builder.addFunction("concat_null_tail", kSig_e_r)
.exportFunc()
.addBody([
kExprLocalGet, 0,
kExprRefNull, kExternRefCode,
kExprCallFunction, kStringConcat,
]);
let instance = builder.instantiate(kImports, kBuiltins);
for (let head of interestingStrings) {
for (let tail of interestingStrings) {
assertEquals(head + tail, instance.exports.concat(head, tail));
}
}
assertThrows(() => instance.exports.concat(null, "hey"),
WebAssembly.RuntimeError, "illegal cast");
assertThrows(() => instance.exports.concat('hey', null),
WebAssembly.RuntimeError, "illegal cast");
assertThrows(() => instance.exports.concat_null_head("hey"),
WebAssembly.RuntimeError, "illegal cast");
assertThrows(() => instance.exports.concat_null_tail("hey"),
WebAssembly.RuntimeError, "illegal cast");
})();
(function TestStringEq() {
print(arguments.callee.name);
let builder = MakeBuilder();
builder.addFunction("eq", kSig_i_rr)
.exportFunc()
.addBody([
kExprLocalGet, 0,
kExprLocalGet, 1,
kExprCallFunction, kStringEquals,
]);
builder.addFunction("eq_null_a", kSig_i_r)
.exportFunc()
.addBody([
kExprRefNull, kExternRefCode,
kExprLocalGet, 0,
kExprCallFunction, kStringEquals,
]);
builder.addFunction("eq_null_b", kSig_i_r)
.exportFunc()
.addBody([
kExprLocalGet, 0,
kExprRefNull, kExternRefCode,
kExprCallFunction, kStringEquals,
]);
builder.addFunction("eq_both_null", kSig_i_v)
.exportFunc()
.addBody([
kExprRefNull, kExternRefCode,
kExprRefNull, kExternRefCode,
kExprCallFunction, kStringEquals,
]);
let instance = builder.instantiate(kImports, kBuiltins);
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));
}
assertEquals(0, instance.exports.eq_null_a(head))
assertEquals(0, instance.exports.eq(null, head))
assertEquals(0, instance.exports.eq(head, null))
}
assertEquals(1, instance.exports.eq_both_null());
assertEquals(1, instance.exports.eq(null, null));
})();
(function TestStringViewWtf16() {
print(arguments.callee.name);
let builder = MakeBuilder();
builder.addFunction("get_codeunit", kSig_i_ri)
.exportFunc()
.addBody([
kExprLocalGet, 0,
kExprLocalGet, 1,
kExprCallFunction, kStringCharCodeAt,
]);
builder.addFunction("get_codeunit_null", kSig_i_v)
.exportFunc()
.addBody([
kExprRefNull, kExternRefCode,
kExprI32Const, 0,
kExprCallFunction, kStringCharCodeAt,
]);
builder.addFunction("get_codepoint", kSig_i_ri)
.exportFunc()
.addBody([
kExprLocalGet, 0,
kExprLocalGet, 1,
kExprCallFunction, kStringCodePointAt,
]);
builder.addFunction("get_codepoint_null", kSig_i_v)
.exportFunc()
.addBody([
kExprRefNull, kExternRefCode,
kExprI32Const, 0,
kExprCallFunction, kStringCodePointAt,
]);
builder.addFunction("slice", kSig_e_rii)
.exportFunc()
.addBody([
kExprLocalGet, 0,
kExprLocalGet, 1,
kExprLocalGet, 2,
kExprCallFunction, kStringSubstring,
]);
builder.addFunction("slice_null", kSig_e_v)
.exportFunc()
.addBody([
kExprRefNull, kExternRefCode,
kExprI32Const, 0,
kExprI32Const, 0,
kExprCallFunction, kStringSubstring,
]);
let instance = builder.instantiate(kImports, kBuiltins);
for (let str of interestingStrings) {
for (let i = 0; i < str.length; i++) {
assertEquals(str.charCodeAt(i),
instance.exports.get_codeunit(str, i));
assertEquals(str.codePointAt(i),
instance.exports.get_codepoint(str, i));
}
assertEquals(str, instance.exports.slice(str, 0, -1));
}
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));
assertEquals("oo", instance.exports.slice("foo", 1, 100));
assertEquals("", instance.exports.slice("foo", 1, 0));
assertEquals("", instance.exports.slice("foo", 3, 4));
assertEquals("foo", instance.exports.slice("foo", 0, -1));
assertEquals("", instance.exports.slice("foo", -1, 1));
assertThrows(() => instance.exports.get_codeunit(null, 0),
WebAssembly.RuntimeError, "illegal cast");
assertThrows(() => instance.exports.get_codeunit_null(),
WebAssembly.RuntimeError, "illegal cast");
assertThrows(() => instance.exports.get_codeunit("", 0),
WebAssembly.RuntimeError, "string offset out of bounds");
assertThrows(() => instance.exports.get_codepoint(null, 0),
WebAssembly.RuntimeError, "illegal cast");
assertThrows(() => instance.exports.get_codepoint_null(),
WebAssembly.RuntimeError, "illegal cast");
assertThrows(() => instance.exports.get_codepoint("", 0),
WebAssembly.RuntimeError, "string offset out of bounds");
assertThrows(() => instance.exports.slice(null, 0, 0),
WebAssembly.RuntimeError, "illegal cast");
assertThrows(() => instance.exports.slice_null(),
WebAssembly.RuntimeError, "illegal cast");
// Cover runtime code path for long slices.
const prefix = "a".repeat(10);
const slice = "x".repeat(40);
const suffix = "b".repeat(40);
const input = prefix + slice + suffix;
const start = prefix.length;
const end = start + slice.length;
assertEquals(slice, instance.exports.slice(input, start, end));
// Check that we create one-byte substrings when possible.
let onebyte = instance.exports.slice("\u1234abcABCDE", 1, 4);
assertEquals("abc", onebyte);
assertTrue(isOneByteString(onebyte));
// Check that the CodeStubAssembler implementation also creates one-byte
// substrings.
onebyte = instance.exports.slice("\u1234abcA", 1, 4);
assertEquals("abc", onebyte);
assertTrue(isOneByteString(onebyte));
// Cover the code path that checks 8 characters at a time.
onebyte = instance.exports.slice("\u1234abcdefgh\u1234", 1, 9);
assertEquals("abcdefgh", onebyte); // Exactly 8 characters.
assertTrue(isOneByteString(onebyte));
onebyte = instance.exports.slice("\u1234abcdefghij\u1234", 1, 11);
assertEquals("abcdefghij", onebyte); // Longer than 8.
assertTrue(isOneByteString(onebyte));
// Check that the runtime code path also creates one-byte substrings.
assertTrue(isOneByteString(
instance.exports.slice(input + "\u1234", start, end)));
})();
(function TestStringCompare() {
print(arguments.callee.name);
let builder = MakeBuilder();
builder.addFunction("compare", kSig_i_rr)
.exportFunc()
.addBody([
kExprLocalGet, 0,
kExprLocalGet, 1,
kExprCallFunction, kStringCompare,
]);
builder.addFunction("compare_null_a", kSig_i_r)
.exportFunc()
.addBody([
kExprRefNull, kExternRefCode,
kExprLocalGet, 0,
kExprCallFunction, kStringCompare,
]);
builder.addFunction("compare_null_b", kSig_i_r)
.exportFunc()
.addBody([
kExprLocalGet, 0,
kExprRefNull, kExternRefCode,
kExprCallFunction, kStringCompare,
]);
let instance = builder.instantiate(kImports, kBuiltins);
for (let lhs of interestingStrings) {
for (let rhs of interestingStrings) {
const expected = lhs < rhs ? -1 : lhs > rhs ? 1 : 0;
assertEquals(expected, instance.exports.compare(lhs, rhs));
}
}
assertThrows(() => instance.exports.compare(null, "abc"),
WebAssembly.RuntimeError, "illegal cast");
assertThrows(() => instance.exports.compare("abc", null),
WebAssembly.RuntimeError, "illegal cast");
assertThrows(() => instance.exports.compare_null_a("abc"),
WebAssembly.RuntimeError, "illegal cast");
assertThrows(() => instance.exports.compare_null_b("abc"),
WebAssembly.RuntimeError, "illegal cast");
})();
(function TestStringCompareNullCheckStaticType() {
print(arguments.callee.name);
let builder = MakeBuilder();
// Use a mix of nullable and non-nullable input types to the compare.
builder.addFunction("compareLhsNullable",
makeSig([kWasmExternRef, kWasmExternRef], [kWasmI32]))
.exportFunc()
.addBody([
kExprLocalGet, 0,
kExprRefAsNonNull,
kExprLocalGet, 1,
kExprCallFunction, kStringCompare,
]);
builder.addFunction("compareRhsNullable",
makeSig([kWasmExternRef, kWasmExternRef], [kWasmI32]))
.exportFunc()
.addBody([
kExprLocalGet, 0,
kExprLocalGet, 1,
kExprRefAsNonNull,
kExprCallFunction, kStringCompare,
]);
let instance = builder.instantiate(kImports, kBuiltins);
assertThrows(() => instance.exports.compareLhsNullable(null, "abc"),
WebAssembly.RuntimeError, "dereferencing a null pointer");
assertThrows(() => instance.exports.compareLhsNullable("abc", null),
WebAssembly.RuntimeError, "illegal cast");
assertThrows(() => instance.exports.compareRhsNullable(null, "abc"),
WebAssembly.RuntimeError, "illegal cast");
assertThrows(() => instance.exports.compareRhsNullable("abc", null),
WebAssembly.RuntimeError, "dereferencing a null pointer");
})();
(function TestStringFromCodePoint() {
print(arguments.callee.name);
let builder = MakeBuilder();
builder.addFunction("asString", kSig_e_i)
.exportFunc()
.addBody([
kExprLocalGet, 0,
kExprCallFunction, kStringFromCodePoint,
]);
let instance = builder.instantiate(kImports, kBuiltins);
for (let char of "Az1#\n\ucccc\ud800\udc00") {
assertEquals(char, instance.exports.asString(char.codePointAt(0)));
}
for (let codePoint of [0x110000, 0xFFFFFFFF, -1]) {
assertThrows(() => instance.exports.asString(codePoint),
WebAssembly.RuntimeError, /Invalid code point -?[0-9]+/);
}
})();
(function TestStringFromCharCode() {
print(arguments.callee.name);
let builder = MakeBuilder();
builder.addFunction("asString", kSig_e_i)
.exportFunc()
.addBody([
kExprLocalGet, 0,
kExprCallFunction, kStringFromCharCode,
]);
let instance = builder.instantiate(kImports, kBuiltins);
let inputs = "Az1#\n\ucccc\ud800\udc00";
for (let i = 0; i < inputs.length; i++) {
assertEquals(inputs.charAt(i),
instance.exports.asString(inputs.charCodeAt(i)));
}
})();
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;
}
function makeWtf8TestDataSegment() {
let data = []
let valid = {};
let invalid = {};
for (let str of interestingStrings) {
let bytes = encodeWtf8(str);
valid[str] = { offset: data.length, length: bytes.length };
for (let byte of bytes) {
data.push(byte);
}
}
let invalid_inputs = [
'trailing high byte \xa9',
'interstitial high \xa9 byte',
'invalid \xc0 byte',
'invalid three-byte \xed\xd0\x80',
'surrogate \xed\xa0\x80\xed\xb0\x80 pair'
];
let invalid_replaced = [
'trailing high byte \uFFFD',
'interstitial high \uFFFD byte',
'invalid \uFFFD byte',
'invalid three-byte \uFFFD\u0400',
'surrogate \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD pair'
];
for (let i = 0; i < invalid_inputs.length; i++) {
let bytes = invalid_inputs[i];
invalid[bytes] = {
offset: data.length,
length: bytes.length,
replaced: invalid_replaced[i]
};
for (let i = 0; i < bytes.length; i++) {
data.push(bytes.charCodeAt(i));
}
}
return { valid, invalid, data: Uint8Array.from(data) };
};
(function TestStringNewUtf8Array() {
print(arguments.callee.name);
let builder = MakeBuilder();
let data = makeWtf8TestDataSegment();
let data_index = builder.addPassiveDataSegment(data.data);
let ascii_data_index =
builder.addPassiveDataSegment(Uint8Array.from(encodeWtf8("ascii")));
let make_i8_array = builder.addFunction(
"make_i8_array", makeSig([], [wasmRefType(kArrayI8)]))
.addBody([
...wasmI32Const(0),
...wasmI32Const(data.data.length),
kGCPrefix, kExprArrayNewData, kArrayI8, data_index
]).index;
builder.addFunction("new_utf8", kSig_e_ii)
.exportFunc()
.addBody([
kExprCallFunction, make_i8_array,
kExprLocalGet, 0, kExprLocalGet, 1,
kExprCallFunction, kStringFromUtf8Array,
]);
builder.addFunction("bounds_check", kSig_e_ii)
.exportFunc()
.addBody([
...wasmI32Const(0),
...wasmI32Const("ascii".length),
kGCPrefix, kExprArrayNewData, kArrayI8, ascii_data_index,
kExprLocalGet, 0, kExprLocalGet, 1,
kExprCallFunction, kStringFromUtf8Array,
]);
builder.addFunction("null_array", kSig_e_v).exportFunc()
.addBody([
kExprRefNull, kArrayI8,
kExprI32Const, 0, kExprI32Const, 0,
kExprCallFunction, kStringFromUtf8Array,
]);
let instance = builder.instantiate(kImports, kBuiltins);
for (let [str, {offset, length}] of Object.entries(data.valid)) {
let start = offset;
let end = offset + length;
if (HasIsolatedSurrogate(str)) {
// 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.new_utf8(start, end));
} else {
assertEquals(str, instance.exports.new_utf8(start, end));
}
}
for (let [str, {offset, length, replaced}] of Object.entries(data.invalid)) {
let start = offset;
let end = offset + length;
assertEquals(replaced, instance.exports.new_utf8(start, end));
}
assertEquals("ascii", instance.exports.bounds_check(0, "ascii".length));
assertEquals("", instance.exports.bounds_check("ascii".length,
"ascii".length));
assertEquals("i", instance.exports.bounds_check("ascii".length - 1,
"ascii".length));
assertThrows(() => instance.exports.bounds_check(0, 100),
WebAssembly.RuntimeError, "array element access out of bounds");
assertThrows(() => instance.exports.bounds_check(0, -1),
WebAssembly.RuntimeError, "array element access out of bounds");
assertThrows(() => instance.exports.bounds_check(-1, 0),
WebAssembly.RuntimeError, "array element access out of bounds");
assertThrows(() => instance.exports.bounds_check("ascii".length,
"ascii".length + 1),
WebAssembly.RuntimeError, "array element access out of bounds");
assertThrows(() => instance.exports.null_array(),
WebAssembly.RuntimeError, "dereferencing a null pointer");
})();
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(strings) {
let data = []
let valid = {};
for (let str of strings) {
valid[str] = { offset: data.length, length: str.length };
for (let byte of encodeWtf16LE(str)) {
data.push(byte);
}
}
return { valid, data: Uint8Array.from(data) };
};
(function TestStringNewWtf16Array() {
print(arguments.callee.name);
let builder = MakeBuilder();
// string.new_wtf16_array switches to a different implementation (runtime
// instead of Torque) for more than 32 characters, so provide some coverage
// for that case.
let strings = interestingStrings.concat([
"String with more than 32 characters, all of which are ASCII",
"Two-byte string with more than 32 characters \ucccc \ud800\udc00 \xa9?"
]);
let data = makeWtf16TestDataSegment(strings);
let data_index = builder.addPassiveDataSegment(data.data);
let ascii_data_index =
builder.addPassiveDataSegment(Uint8Array.from(encodeWtf16LE("ascii")));
let make_i16_array = builder.addFunction(
"make_i16_array", makeSig([], [wasmRefType(kArrayI16)]))
.addBody([
...wasmI32Const(0),
...wasmI32Const(data.data.length / 2),
kGCPrefix, kExprArrayNewData, kArrayI16, data_index
]).index;
builder.addFunction("new_wtf16", kSig_e_ii)
.exportFunc()
.addBody([
kExprCallFunction, make_i16_array,
kExprLocalGet, 0, kExprLocalGet, 1,
kExprCallFunction, kStringFromWtf16Array,
]);
builder.addFunction("bounds_check", kSig_e_ii)
.exportFunc()
.addBody([
...wasmI32Const(0),
...wasmI32Const("ascii".length),
kGCPrefix, kExprArrayNewData, kArrayI16, ascii_data_index,
kExprLocalGet, 0, kExprLocalGet, 1,
kExprCallFunction, kStringFromWtf16Array,
]);
builder.addFunction("null_array", kSig_e_v).exportFunc()
.addBody([
kExprRefNull, kArrayI16,
kExprI32Const, 0, kExprI32Const, 0,
kExprCallFunction, kStringFromWtf16Array,
]);
let instance = builder.instantiate(kImports, kBuiltins);
for (let [str, {offset, length}] of Object.entries(data.valid)) {
let start = offset / 2;
let end = start + length;
assertEquals(str, instance.exports.new_wtf16(start, end));
}
assertEquals("ascii", instance.exports.bounds_check(0, "ascii".length));
assertEquals("", instance.exports.bounds_check("ascii".length,
"ascii".length));
assertEquals("i", instance.exports.bounds_check("ascii".length - 1,
"ascii".length));
assertThrows(() => instance.exports.bounds_check(0, 100),
WebAssembly.RuntimeError, "array element access out of bounds");
assertThrows(() => instance.exports.bounds_check("ascii".length,
"ascii".length + 1),
WebAssembly.RuntimeError, "array element access out of bounds");
assertThrows(() => instance.exports.null_array(),
WebAssembly.RuntimeError, "dereferencing a null pointer");
})();
(function TestStringEncodeWtf16Array() {
print(arguments.callee.name);
let builder = MakeBuilder();
// Allocate an array and encode into it. Then decode it.
// (str, length, offset) -> str
builder.addFunction("encode", kSig_e_rii)
.exportFunc()
.addLocals(wasmRefNullType(kArrayI16), 1)
.addLocals(kWasmI32, 1)
.addBody([
// Allocate buffer.
kExprLocalGet, 1,
kGCPrefix, kExprArrayNewDefault, kArrayI16,
kExprLocalSet, 3,
// Write buffer, store number of code units written.
kExprLocalGet, 0,
kExprLocalGet, 3,
kExprLocalGet, 2,
kExprCallFunction, kStringToWtf16Array,
kExprLocalSet, 4,
// Read buffer.
kExprLocalGet, 3,
kExprLocalGet, 2,
kExprLocalGet, 2, kExprLocalGet, 4, kExprI32Add,
kExprCallFunction, kStringFromWtf16Array,
]);
builder.addFunction("encode_null_string", kSig_i_v)
.exportFunc()
.addBody([
kExprRefNull, kExternRefCode,
kExprI32Const, 0, kGCPrefix, kExprArrayNewDefault, kArrayI16,
kExprI32Const, 0,
kExprCallFunction, kStringToWtf16Array,
]);
builder.addFunction("encode_null_array", kSig_i_v)
.exportFunc()
.addBody([
kExprI32Const, 0, kGCPrefix, kExprArrayNewDefault, kArrayI16,
kExprI32Const, 0, kExprI32Const, 0,
kExprCallFunction, kStringFromWtf16Array,
kExprRefNull, kArrayI16,
kExprI32Const, 0,
kExprCallFunction, kStringToWtf16Array,
]);
let instance = builder.instantiate(kImports, kBuiltins);
for (let str of interestingStrings) {
assertEquals(str, instance.exports.encode(str, str.length, 0));
assertEquals(str, instance.exports.encode(str, str.length + 20, 10));
}
assertThrows(() => instance.exports.encode(null, 0, 0),
WebAssembly.RuntimeError, "illegal cast");
assertThrows(() => instance.exports.encode_null_array(),
WebAssembly.RuntimeError, "dereferencing a null pointer");
assertThrows(() => instance.exports.encode_null_string(),
WebAssembly.RuntimeError, "illegal cast");
for (let str of interestingStrings) {
let message = "array element access out of bounds";
assertThrows(() => instance.exports.encode(str, str.length, 1),
WebAssembly.RuntimeError, message);
}
})();
(function TestStringMeasureUtf8() {
print(arguments.callee.name);
let builder = MakeBuilder();
builder.addFunction("string_measure_utf8", kSig_i_r)
.exportFunc()
.addBody([
kExprLocalGet, 0,
kExprCallFunction, kStringMeasureUtf8,
]);
builder.addFunction("string_measure_utf8_null", kSig_i_v)
.exportFunc()
.addBody([
kExprRefNull, kExternRefCode,
kExprCallFunction, kStringMeasureUtf8,
]);
let instance = builder.instantiate(kImports, kBuiltins);
for (let str of interestingStrings) {
let wtf8 = encodeWtf8(str);
assertEquals(wtf8.length, instance.exports.string_measure_utf8(str));
}
assertThrows(() => instance.exports.string_measure_utf8_null(),
WebAssembly.RuntimeError, "illegal cast");
})();
(function TestStringEncodeUtf8Array() {
print(arguments.callee.name);
let builder = MakeBuilder();
// Allocate an array that's exactly the expected size, and encode
// into it. Then decode it.
// (str, length, offset=0) -> str
builder.addFunction("encode_utf8", kSig_e_rii)
.exportFunc()
.addLocals(wasmRefNullType(kArrayI8), 1)
.addLocals(kWasmI32, 1)
.addBody([
// Allocate buffer.
kExprLocalGet, 1,
kGCPrefix, kExprArrayNewDefault, kArrayI8,
kExprLocalSet, 3,
// Write buffer, store number of bytes written.
kExprLocalGet, 0,
kExprLocalGet, 3,
kExprLocalGet, 2,
kExprCallFunction, kStringIntoUtf8Array,
kExprLocalSet, 4,
// Read buffer.
kExprLocalGet, 3,
kExprLocalGet, 2,
kExprLocalGet, 2, kExprLocalGet, 4, kExprI32Add,
kExprCallFunction, kStringFromUtf8Array,
]);
builder.addFunction("encode_null_string", kSig_i_v)
.exportFunc()
.addBody([
kExprRefNull, kExternRefCode,
kExprI32Const, 0, kGCPrefix, kExprArrayNewDefault, kArrayI8,
kExprI32Const, 0,
kExprCallFunction, kStringIntoUtf8Array,
]);
builder.addFunction("encode_null_array", kSig_i_v)
.exportFunc()
.addBody([
kExprI32Const, 0, kGCPrefix, kExprArrayNewDefault, kArrayI8,
kExprI32Const, 0, kExprI32Const, 0,
kExprCallFunction, kStringFromUtf8Array,
kExprRefNull, kArrayI8,
kExprI32Const, 0,
kExprCallFunction, kStringIntoUtf8Array,
]);
let instance = builder.instantiate(kImports, kBuiltins);
for (let str of interestingStrings) {
let replaced = ReplaceIsolatedSurrogates(str);
if (!HasIsolatedSurrogate(str)) assertEquals(str, replaced);
let wtf8 = encodeWtf8(replaced);
assertEquals(replaced,
instance.exports.encode_utf8(str, wtf8.length, 0));
assertEquals(replaced,
instance.exports.encode_utf8(str, wtf8.length + 20, 10));
}
assertThrows(() => instance.exports.encode_null_array(),
WebAssembly.RuntimeError, "dereferencing a null pointer");
assertThrows(() => instance.exports.encode_null_string(),
WebAssembly.RuntimeError, "illegal cast");
for (let str of interestingStrings) {
let wtf8 = encodeWtf8(str);
let message = "array element access out of bounds";
assertThrows(() => instance.exports.encode_utf8(str, wtf8.length, 1),
WebAssembly.RuntimeError, message);
}
})();
(function TestStringToUtf8Array() {
print(arguments.callee.name);
let builder = MakeBuilder();
// Convert the string to an array, then decode it back.
builder.addFunction("encode_utf8", kSig_e_r)
.exportFunc()
.addLocals(wasmRefNullType(kArrayI8), 1)
.addBody([
kExprLocalGet, 0,
kExprCallFunction, kStringToUtf8Array,
kExprLocalTee, 1,
kExprI32Const, 0, // start
kExprLocalGet, 1, kGCPrefix, kExprArrayLen, // end
kExprCallFunction, kStringFromUtf8Array,
]);
let sig_a8_v = makeSig([], [wasmRefType(kArrayI8)]);
builder.addFunction("encode_null_string", sig_a8_v)
.exportFunc()
.addBody([
kExprRefNull, kExternRefCode,
kExprCallFunction, kStringToUtf8Array,
]);
let instance = builder.instantiate(kImports, kBuiltins);
for (let str of interestingStrings) {
let replaced = ReplaceIsolatedSurrogates(str);
if (!HasIsolatedSurrogate(str)) assertEquals(str, replaced);
let wtf8 = encodeWtf8(replaced);
assertEquals(replaced,
instance.exports.encode_utf8(str, wtf8.length, 0));
assertEquals(replaced,
instance.exports.encode_utf8(str, wtf8.length + 20, 10));
}
assertThrows(() => instance.exports.encode_null_string(),
WebAssembly.RuntimeError, "illegal cast");
})();