diff options
Diffstat (limited to '')
-rw-r--r-- | js/src/jit-test/tests/wasm/binary.js | 408 |
1 files changed, 408 insertions, 0 deletions
diff --git a/js/src/jit-test/tests/wasm/binary.js b/js/src/jit-test/tests/wasm/binary.js new file mode 100644 index 0000000000..5a59330768 --- /dev/null +++ b/js/src/jit-test/tests/wasm/binary.js @@ -0,0 +1,408 @@ +load(libdir + "wasm-binary.js"); + +const { extractStackFrameFunction } = WasmHelpers; + +const { Module, RuntimeError, CompileError } = WebAssembly; + +const magicError = /failed to match magic number/; +const unknownSection = /expected custom section/; + +function sectionError(section) { + return RegExp(`failed to start ${section} section`); +} + +function versionError(actual) { + var expect = encodingVersion; + var str = `binary version 0x${actual.toString(16)} does not match expected version 0x${expect.toString(16)}`; + return RegExp(str); +} + +const U32MAX_LEB = [255, 255, 255, 255, 15]; + +const wasmEval = (code, imports) => new WebAssembly.Instance(new Module(code), imports).exports; + +assertErrorMessage(() => wasmEval(toU8([])), CompileError, magicError); +assertErrorMessage(() => wasmEval(toU8([42])), CompileError, magicError); +assertErrorMessage(() => wasmEval(toU8([magic0, magic1, magic2])), CompileError, magicError); +assertErrorMessage(() => wasmEval(toU8([1,2,3,4])), CompileError, magicError); +assertErrorMessage(() => wasmEval(toU8([magic0, magic1, magic2, magic3])), CompileError, versionError(0x6d736100)); +assertErrorMessage(() => wasmEval(toU8([magic0, magic1, magic2, magic3, 1])), CompileError, versionError(0x6d736100)); +assertErrorMessage(() => wasmEval(toU8([magic0, magic1, magic2, magic3, ver0])), CompileError, versionError(0x6d736100)); +assertErrorMessage(() => wasmEval(toU8([magic0, magic1, magic2, magic3, ver0, ver1, ver2])), CompileError, versionError(0x6d736100)); + +var o = wasmEval(toU8(moduleHeaderThen())); +assertEq(Object.getOwnPropertyNames(o).length, 0); + +// unfinished known sections +assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(typeId))), CompileError, sectionError("type")); +assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(importId))), CompileError, sectionError("import")); +assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(functionId))), CompileError, sectionError("function")); +assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(tableId))), CompileError, sectionError("table")); +assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(memoryId))), CompileError, sectionError("memory")); +assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(globalId))), CompileError, sectionError("global")); +assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(exportId))), CompileError, sectionError("export")); +assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(startId))), CompileError, sectionError("start")); +assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(elemId))), CompileError, sectionError("elem")); +assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(codeId))), CompileError, sectionError("code")); +assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(dataId))), CompileError, sectionError("data")); + +// unknown sections are unconditionally rejected +assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(37))), CompileError, unknownSection); +assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(37, 0))), CompileError, unknownSection); +assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(37, 1, 0))), CompileError, unknownSection); + +// user sections have special rules +assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(0))), CompileError, sectionError("custom")); // no length +assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(0, 0))), CompileError, sectionError("custom")); // no id +assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(0, 0, 0))), CompileError, sectionError("custom")); // payload too small to have id length +assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(0, 1, 1))), CompileError, sectionError("custom")); // id not present +assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(0, 1, 1, 65))), CompileError, sectionError("custom")); // id length doesn't fit in section +assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(0, 1, 0, 0))), CompileError, sectionError("custom")); // second, unfinished custom section +wasmEval(toU8(moduleHeaderThen(0, 1, 0))); // empty id +wasmEval(toU8(moduleHeaderThen(0, 1, 0, 0, 1, 0))); // 2x empty id +wasmEval(toU8(moduleHeaderThen(0, 2, 1, 65))); // id = "A" + +const v2vSig = {args:[], ret:VoidCode}; +const v2vSigSection = sigSection([v2vSig]); +const i2vSig = {args:[I32Code], ret:VoidCode}; +const v2vBody = funcBody({locals:[], body:[]}); + +assertErrorMessage(() => wasmEval(moduleWithSections([ {name: typeId, body: U32MAX_LEB } ])), CompileError, /too many types/); +assertErrorMessage(() => wasmEval(moduleWithSections([ {name: typeId, body: [1, 0], } ])), CompileError, /expected type form/); +assertErrorMessage(() => wasmEval(moduleWithSections([ {name: typeId, body: [1, FuncCode, ...U32MAX_LEB], } ])), CompileError, /too many arguments in signature/); + +assertThrowsInstanceOf(() => wasmEval(moduleWithSections([{name: typeId, body: [1]}])), CompileError); +assertThrowsInstanceOf(() => wasmEval(moduleWithSections([{name: typeId, body: [1, 1, 0]}])), CompileError); + +wasmEval(moduleWithSections([sigSection([])])); +wasmEval(moduleWithSections([v2vSigSection])); +wasmEval(moduleWithSections([sigSection([i2vSig])])); +wasmEval(moduleWithSections([sigSection([v2vSig, i2vSig])])); + +assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([{args:[], ret:33}])])), CompileError, /bad type/); +assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([{args:[33], ret:VoidCode}])])), CompileError, /bad type/); + +assertThrowsInstanceOf(() => wasmEval(moduleWithSections([sigSection([]), declSection([0])])), CompileError, /signature index out of range/); +assertThrowsInstanceOf(() => wasmEval(moduleWithSections([v2vSigSection, declSection([1])])), CompileError, /signature index out of range/); +assertErrorMessage(() => wasmEval(moduleWithSections([v2vSigSection, declSection([0])])), CompileError, /expected code section/); +wasmEval(moduleWithSections([v2vSigSection, declSection([0]), bodySection([v2vBody])])); + +assertErrorMessage(() => wasmEval(moduleWithSections([v2vSigSection, declSection([0]), bodySection([v2vBody.concat(v2vBody)])])), CompileError, /byte size mismatch in code section/); + +assertThrowsInstanceOf(() => wasmEval(moduleWithSections([v2vSigSection, {name: importId, body:[]}])), CompileError); +assertErrorMessage(() => wasmEval(moduleWithSections([importSection([{sigIndex:0, module:"a", func:"b"}])])), CompileError, /signature index out of range/); +assertErrorMessage(() => wasmEval(moduleWithSections([v2vSigSection, importSection([{sigIndex:1, module:"a", func:"b"}])])), CompileError, /signature index out of range/); +wasmEval(moduleWithSections([v2vSigSection, importSection([])])); +wasmEval(moduleWithSections([v2vSigSection, importSection([{sigIndex:0, module:"a", func:""}])]), {a:{"":()=>{}}}); + +wasmEval(moduleWithSections([ + v2vSigSection, + importSection([{sigIndex:0, module:"a", func:""}]), + declSection([0]), + bodySection([v2vBody]) +]), {a:{"":()=>{}}}); + +assertErrorMessage(() => wasmEval(moduleWithSections([ dataSection([{offset:1, elems:[]}]) ])), CompileError, /data segment requires a memory section/); + +wasmEval(moduleWithSections([tableSection(0)])); +wasmEval(moduleWithSections([elemSection([])])); +wasmEval(moduleWithSections([tableSection(0), elemSection([])])); +wasmEval(moduleWithSections([tableSection(1), elemSection([{offset:1, elems:[]}])])); +assertErrorMessage(() => wasmEval(moduleWithSections([tableSection(1), elemSection([{offset:0, elems:[0]}])])), CompileError, /element index out of range/); +wasmEval(moduleWithSections([v2vSigSection, declSection([0]), tableSection(1), elemSection([{offset:0, elems:[0]}]), bodySection([v2vBody])])); +wasmEval(moduleWithSections([v2vSigSection, declSection([0]), tableSection(2), elemSection([{offset:0, elems:[0,0]}]), bodySection([v2vBody])])); +assertErrorMessage(() => wasmEval(moduleWithSections([v2vSigSection, declSection([0]), tableSection(2), elemSection([{offset:0, elems:[0,1]}]), bodySection([v2vBody])])), CompileError, /element index out of range/); +wasmEval(moduleWithSections([v2vSigSection, declSection([0,0,0]), tableSection(4), elemSection([{offset:0, elems:[0,1,0,2]}]), bodySection([v2vBody, v2vBody, v2vBody])])); +wasmEval(moduleWithSections([sigSection([v2vSig,i2vSig]), declSection([0,0,1]), tableSection(3), elemSection([{offset:0,elems:[0,1,2]}]), bodySection([v2vBody, v2vBody, v2vBody])])); + +wasmEval(moduleWithSections([tableSection0()])); + +wasmEval(moduleWithSections([memorySection(0)])); + +function invalidMemorySection2() { + var body = []; + body.push(...varU32(2)); // number of memories + body.push(...varU32(0x0)); + body.push(...varU32(0)); + body.push(...varU32(0x0)); + body.push(...varU32(0)); + return { name: memoryId, body }; +} + +wasmEval(moduleWithSections([memorySection0()])); +assertErrorMessage(() => wasmEval(moduleWithSections([invalidMemorySection2()])), CompileError, /number of memories must be at most one/); + +// Test early 'end' +const bodyMismatch = /(function body length mismatch)|(operators remaining after end of function)/; +assertErrorMessage(() => wasmEval(moduleWithSections([v2vSigSection, declSection([0]), bodySection([funcBody({locals:[], body:[EndCode]})])])), CompileError, bodyMismatch); +assertErrorMessage(() => wasmEval(moduleWithSections([v2vSigSection, declSection([0]), bodySection([funcBody({locals:[], body:[UnreachableCode,EndCode]})])])), CompileError, bodyMismatch); +assertErrorMessage(() => wasmEval(moduleWithSections([v2vSigSection, declSection([0]), bodySection([funcBody({locals:[], body:[EndCode,UnreachableCode]})])])), CompileError, bodyMismatch); + +// Ignore errors in name section. +var tooBigNameSection = { + name: userDefinedId, + body: [...string(nameName), ...varU32(Math.pow(2, 31))] // declare 2**31 functions. +}; +wasmEval(moduleWithSections([tooBigNameSection])); + +// Skip custom sections before any expected section +var customDefSec = customSection("wee", 42, 13); +var declSec = declSection([0]); +var bodySec = bodySection([v2vBody]); +var nameSec = nameSection([funcNameSubsection([{name:'hi'}])]); +wasmEval(moduleWithSections([customDefSec, v2vSigSection, declSec, bodySec])); +wasmEval(moduleWithSections([v2vSigSection, customDefSec, declSec, bodySec])); +wasmEval(moduleWithSections([v2vSigSection, declSec, customDefSec, bodySec])); +wasmEval(moduleWithSections([v2vSigSection, declSec, bodySec, customDefSec])); +wasmEval(moduleWithSections([customDefSec, customDefSec, v2vSigSection, declSec, bodySec])); +wasmEval(moduleWithSections([customDefSec, customDefSec, v2vSigSection, customDefSec, declSec, customDefSec, bodySec])); + +// custom sections reflection: +function checkCustomSection(buf, val) { + assertEq(buf instanceof ArrayBuffer, true); + assertEq(buf.byteLength, 1); + assertEq(new Uint8Array(buf)[0], val); +} +var custom1 = customSection("one", 1); +var custom2 = customSection("one", 2); +var custom3 = customSection("two", 3); +var custom4 = customSection("three", 4); +var custom5 = customSection("three", 5); +var custom6 = customSection("three", 6); +var m = new Module(moduleWithSections([custom1, v2vSigSection, custom2, declSec, custom3, bodySec, custom4, nameSec, custom5, custom6])); +var arr = Module.customSections(m, "one"); +assertEq(arr.length, 2); +checkCustomSection(arr[0], 1); +checkCustomSection(arr[1], 2); +var arr = Module.customSections(m, "two"); +assertEq(arr.length, 1); +checkCustomSection(arr[0], 3); +var arr = Module.customSections(m, "three"); +assertEq(arr.length, 3); +checkCustomSection(arr[0], 4); +checkCustomSection(arr[1], 5); +checkCustomSection(arr[2], 6); +var arr = Module.customSections(m, "name"); +assertEq(arr.length, 1); +assertEq(arr[0].byteLength, nameSec.body.length - 5 /* 4name */); + +// Test name/custom section warnings: +const nameWarning = /validated with warning.*'name' custom section/; +const okNameSec = nameSection([]); +assertNoWarning(() => wasmEval(moduleWithSections([v2vSigSection, declSec, bodySec, okNameSec]))); +const badNameSec1 = nameSection([]); +badNameSec1.body.push(1); +assertWarning(() => wasmEval(moduleWithSections([v2vSigSection, declSec, bodySec, badNameSec1])), nameWarning); +const badNameSec2 = nameSection([funcNameSubsection([{name:'blah'}])]); +badNameSec2.body.push(100, 20, 42, 83); +assertWarning(() => wasmEval(moduleWithSections([v2vSigSection, declSec, bodySec, badNameSec2])), nameWarning); +const badNameSec3 = nameSection([funcNameSubsection([{name:'blah'}])]); +badNameSec3.body.pop(); +assertWarning(() => wasmEval(moduleWithSections([v2vSigSection, declSec, bodySec, badNameSec3])), nameWarning); +assertNoWarning(() => wasmEval(moduleWithSections([nameSection([moduleNameSubsection('hi')])]))); +assertWarning(() => wasmEval(moduleWithSections([nameSection([moduleNameSubsection('hi'), moduleNameSubsection('boo')])])), nameWarning); +// Unknown name subsection +assertNoWarning(() => wasmEval(moduleWithSections([nameSection([moduleNameSubsection('hi'), [4, 0]])]))); +assertWarning(() => wasmEval(moduleWithSections([nameSection([moduleNameSubsection('hi'), [4, 1]])])), nameWarning); +assertNoWarning(() => wasmEval(moduleWithSections([nameSection([moduleNameSubsection('hi'), [4, 1, 42]])]))); + +// Provide a module name but no function names. +assertErrorMessage(() => wasmEval(moduleWithSections([ + v2vSigSection, + declSection([0]), + exportSection([{funcIndex: 0, name: "f"}]), + bodySection([funcBody({locals:[], body:[UnreachableCode]})]), + nameSection([moduleNameSubsection('hi')])]) +).f(), RuntimeError, /unreachable/); + +// Diagnose invalid block signature types. +for (var bad of [0xff, 1, 0x3f]) + assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([0]), bodySection([funcBody({locals:[], body:[BlockCode, bad, EndCode]})])])), CompileError, /(invalid .*block type)|(unknown type)/); + +const multiValueModule = moduleWithSections([sigSection([v2vSig]), declSection([0]), bodySection([funcBody({locals:[], body:[BlockCode, 0, EndCode]})])]); +// In this test module, 0 denotes a void-to-void block type. +assertEq(WebAssembly.validate(multiValueModule), true); + +// Ensure all invalid opcodes are rejected. Note that the game here (and for +// the prefixed cases below) is to present only opcodes which will be rejected by +// *both* Baseline and Ion. +for (let op of undefinedOpcodes) { + let binary = moduleWithSections([v2vSigSection, declSection([0]), bodySection([funcBody({locals:[], body:[op]})])]); + assertErrorMessage(() => wasmEval(binary), CompileError, /((unrecognized|Unknown) opcode)|(tail calls support is not enabled)|(Exceptions support is not enabled)|(Unexpected EOF)/); + assertEq(WebAssembly.validate(binary), false); +} + +// Prefixed opcodes + +function checkIllegalPrefixed(prefix, opcode) { + let binary = moduleWithSections([v2vSigSection, + declSection([0]), + bodySection([funcBody({locals:[], + body:[prefix, ...varU32(opcode)]})])]); + assertErrorMessage(() => wasmEval(binary), CompileError, /((unrecognized|Unknown) opcode)|(Unknown.*subopcode)|(Unexpected EOF)|(SIMD support is not enabled)|(invalid lane index)/); + assertEq(WebAssembly.validate(binary), false); +} + +// Illegal GcPrefix opcodes + +let reservedGc = {}; +if (wasmGcEnabled()) { + reservedGc = { + // Structure operations + 0x00: true, 0x01: true, 0x02: true, 0x03: true, 0x04: true, 0x05: true, + // Array operations + 0x06: true, 0x07: true, 0x08: true, 0x09: true, 0x0a: true, 0x0b: true, + 0x0c: true, 0x0d: true, 0x0e: true, 0x0f: true, 0x10: true, 0x11: true, + 0x12: true, 0x13: true, + // Ref operations + 0x14: true, 0x15: true, 0x16: true, 0x17: true, 0x18: true, 0x19: true, + 0x1a: true, 0x1b: true, + // i31 operations + 0x1c: true, 0x1d: true, 0x1e: true, + }; +} +for (let i = 0; i < 256; i++) { + if (reservedGc.hasOwnProperty(i)) { + continue; + } + checkIllegalPrefixed(GcPrefix, i); +} + +// Illegal ThreadPrefix opcodes +// +// June 2017 threads draft: +// +// 0x00 .. 0x03 are wait/wake/fence ops +// 0x10 .. 0x4f are primitive atomic ops + +for (let i = 0x4; i < 0x10; i++) + checkIllegalPrefixed(ThreadPrefix, i); + +for (let i = 0x4f; i < 0x100; i++) + checkIllegalPrefixed(ThreadPrefix, i); + +// Illegal Misc opcodes + +var reservedMisc = + { // Saturating conversions (standardized) + 0x00: true, 0x01: true, 0x02: true, 0x03: true, 0x04: true, 0x05: true, 0x06: true, 0x07: true, + // Bulk memory (proposed) + 0x08: true, 0x09: true, 0x0a: true, 0x0b: true, 0x0c: true, 0x0d: true, 0x0e: true, + // Table (proposed) + 0x0f: true, 0x10: true, 0x11: true, 0x12: true, + // Structure operations (experimental, internal) + 0x50: true, 0x51: true, 0x52: true, 0x53: true }; + +for (let i = 0; i < 256; i++) { + if (reservedMisc.hasOwnProperty(i)) + continue; + checkIllegalPrefixed(MiscPrefix, i); +} + +// Illegal SIMD opcodes - the upper bound is actually very large, not much to be +// done about that. + +if (!wasmSimdEnabled()) { + for (let i = 0; i < 0x130; i++) { + checkIllegalPrefixed(SimdPrefix, i); + } +} else { + let reservedSimd = [ + 0x9a, 0xa2, 0xa5, 0xa6, 0xaf, 0xb0, 0xb2, 0xb3, 0xb4, 0xbb, + 0xc2, 0xc5, 0xc6, 0xcf, 0xd0, 0xd2, 0xd3, 0xd4, 0xe2, 0xee, + 0x115, 0x116, 0x117, + 0x118, 0x119, 0x11a, 0x11b, 0x11c, 0x11d, 0x11e, 0x11f, + 0x120, 0x121, 0x122, 0x123, 0x124, 0x125, 0x126, 0x127, + 0x128, 0x129, 0x12a, 0x12b, 0x12c, 0x12d, 0x12e, 0x12f, + ]; + for (let i of reservedSimd) { + checkIllegalPrefixed(SimdPrefix, i); + } +} + +// Illegal MozPrefix opcodes (all of them) +for (let i = 0; i < 256; i++) + checkIllegalPrefixed(MozPrefix, i); + +for (let prefix of [ThreadPrefix, MiscPrefix, SimdPrefix, MozPrefix]) { + // Prefix without a subsequent opcode. We must ask funcBody not to add an + // End code after the prefix, so the body really is just the prefix byte. + let binary = moduleWithSections([v2vSigSection, declSection([0]), bodySection([funcBody({locals:[], body:[prefix]}, /*withEndCode=*/false)])]); + assertErrorMessage(() => wasmEval(binary), CompileError, /(unable to read opcode)|(Unexpected EOF)|(Unknown opcode)/); + assertEq(WebAssembly.validate(binary), false); +} + +// Checking stack trace. +function runStackTraceTest(moduleName, funcNames, expectedName) { + var sections = [ + sigSection([v2vSig]), + importSection([{sigIndex:0, module:"env", func:"callback"}]), + declSection([0]), + exportSection([{funcIndex:1, name: "run"}]), + bodySection([funcBody({locals: [], body: [CallCode, varU32(0)]})]), + customSection("whoa"), + customSection("wee", 42), + ]; + if (moduleName || funcNames) { + var subsections = []; + if (moduleName) + subsections.push(moduleNameSubsection(moduleName)); + if (funcNames) + subsections.push(funcNameSubsection(funcNames)); + sections.push(nameSection(subsections)); + } + sections.push(customSection("yay", 13)); + + var result = ""; + var callback = () => { + result = extractStackFrameFunction(new Error().stack.split('\n')[1]); + }; + wasmEval(moduleWithSections(sections), {"env": { callback }}).run(); + assertEq(result, expectedName); +}; + +runStackTraceTest(null, null, 'wasm-function[1]'); +runStackTraceTest(null, [{name:'blah'}, {name:'test'}], 'test'); +runStackTraceTest(null, [{name:'test', index:1}], 'test'); +runStackTraceTest(null, [{name:'blah'}, {name:'test', locals: [{name: 'var1'}, {name: 'var2'}]}], 'test'); +runStackTraceTest(null, [{name:'blah'}, {name:'test', locals: [{name: 'var1'}, {name: 'var2'}]}], 'test'); +runStackTraceTest(null, [{name:'blah'}, {name:'test1'}], 'test1'); +runStackTraceTest(null, [{name:'blah'}, {name:'test☃'}], 'test☃'); +runStackTraceTest(null, [{name:'blah'}, {name:'te\xE0\xFF'}], 'te\xE0\xFF'); +runStackTraceTest(null, [{name:'blah'}], 'wasm-function[1]'); +runStackTraceTest(null, [], 'wasm-function[1]'); +runStackTraceTest("", [{name:'blah'}, {name:'test'}], 'test'); +runStackTraceTest("a", [{name:'blah'}, {name:'test'}], 'a.test'); +// Notice that invalid names section content shall not fail the parsing +runStackTraceTest(null, [{name:'blah'}, {name:'test', index: 2}], 'wasm-function[1]'); // invalid index +runStackTraceTest(null, [{name:'blah'}, {name:'test', index: 100000}], 'wasm-function[1]'); // invalid index +runStackTraceTest(null, [{name:'blah'}, {name:'test', nameLen: 100}], 'wasm-function[1]'); // invalid name size +runStackTraceTest(null, [{name:'blah'}, {name:''}], 'wasm-function[1]'); // empty name + +// Enable and disable Gecko profiling mode, to ensure all live instances +// names won't make us crash. +enableGeckoProfiling(); +disableGeckoProfiling(); + +function testValidNameSectionWithProfiling() { + enableGeckoProfiling(); + wasmEval(moduleWithSections([v2vSigSection, declSec, bodySec, nameSec])); + disableGeckoProfiling(); +} +testValidNameSectionWithProfiling(); + +// Memory alignment can use non-minimal LEB128 +wasmEval(moduleWithSections([ + v2vSigSection, + declSection([0]), + memorySection(0), + bodySection([ + funcBody({locals: [], body: [ + I32ConstCode, 0x00, // i32.const 0 + I32Load, + 0x81, 0x00, // alignment 1, non-minimal + 0x00, // offset 0 + DropCode, + ]}), + ]), +])); |