mirror of
https://github.com/nodejs/node.git
synced 2025-05-15 16:01:52 +00:00

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>
1143 lines
38 KiB
JavaScript
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");
|
|
})();
|