WebAssembly: misc memory testing
authorjfbastien@apple.com <jfbastien@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 27 Mar 2017 23:24:31 +0000 (23:24 +0000)
committerjfbastien@apple.com <jfbastien@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 27 Mar 2017 23:24:31 +0000 (23:24 +0000)
https://bugs.webkit.org/show_bug.cgi?id=170137

Reviewed by Keith Miller.

JSTests:

* wasm/assert.js: handle newlines in code we print out, avoid regex
* wasm/function-tests/memory-import-and-grow.js: Added.
(const.instantiate):
(const.test):
* wasm/function-tests/memory-section-and-import.js: Added.
(const.instantiate):

Source/JavaScriptCore:

* wasm/js/WebAssemblyInstanceConstructor.cpp:
(JSC::WebAssemblyInstanceConstructor::createInstance): improve error messages

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@214438 268f45cc-cd09-0410-ab3c-d52691b4dbfc

JSTests/ChangeLog
JSTests/wasm/assert.js
JSTests/wasm/function-tests/memory-import-and-grow.js [new file with mode: 0644]
JSTests/wasm/function-tests/memory-section-and-import.js [new file with mode: 0644]
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/wasm/js/WebAssemblyInstanceConstructor.cpp

index 45f9d9a..0d76b9b 100644 (file)
@@ -1,3 +1,17 @@
+2017-03-27  JF Bastien  <jfbastien@apple.com>
+
+        WebAssembly: misc memory testing
+        https://bugs.webkit.org/show_bug.cgi?id=170137
+
+        Reviewed by Keith Miller.
+
+        * wasm/assert.js: handle newlines in code we print out, avoid regex
+        * wasm/function-tests/memory-import-and-grow.js: Added.
+        (const.instantiate):
+        (const.test):
+        * wasm/function-tests/memory-section-and-import.js: Added.
+        (const.instantiate):
+
 2017-03-23  Yusuke Suzuki  <utatane.tea@gmail.com>
 
         [JSC] Use jsNontrivialString agressively for ToString(Int52)
index d1aa087..e9db0a9 100644 (file)
@@ -111,9 +111,6 @@ export const le = (lhs, rhs, msg) => {
         _fail(`Expected: "${lhs}" > "${rhs}"`, msg);
 };
 
-// Ignore source information at the end of the error message if the expected message didn't specify that information. Sometimes it changes, or it's tricky to get just right.
-const _sourceRe = new RegExp(/( \(evaluating '.*'\))|( \(In .*\))/);
-
 const _throws = (func, type, message, ...args) => {
     try {
         func(...args);
@@ -121,9 +118,16 @@ const _throws = (func, type, message, ...args) => {
         if (e instanceof type) {
             if (e.message === message)
                 return e;
-            const cleanMessage = e.message.replace(_sourceRe, '');
-            if (cleanMessage === message)
-                return e;
+            // Ignore source information at the end of the error message if the
+            // expected message didn't specify that information. Sometimes it
+            // changes, or it's tricky to get just right.
+            const evaluatingIndex = e.message.indexOf(" (evaluating '");
+            if (evaluatingIndex !== -1) {
+                const cleanMessage = e.message.substring(0, evaluatingIndex);
+                if (cleanMessage === message)
+                    return e;
+            }
+            return e;
         }
         _fail(`Expected to throw a ${type.name} with message "${message}", got ${e.name} with message "${e.message}"`);
     }
diff --git a/JSTests/wasm/function-tests/memory-import-and-grow.js b/JSTests/wasm/function-tests/memory-import-and-grow.js
new file mode 100644 (file)
index 0000000..34ce59d
--- /dev/null
@@ -0,0 +1,131 @@
+import Builder from '../Builder.js';
+import * as assert from '../assert.js';
+
+const pageSize = 64 * 1024;
+
+const verbose = false;
+
+// https://github.com/WebAssembly/design/blob/master/Modules.md#imports
+// Says:
+//
+// A linear memory import includes the same set of fields defined in the Linear
+// Memory section: initial length and optional maximum length. The host
+// environment must only allow imports of WebAssembly linear memories that have
+// initial length greater-or-equal than the initial length declared in the
+// import and that have maximum length less-or-equal than the maximum length
+// declared in the import. This ensures that separate compilation can assume:
+// memory accesses below the declared initial length are always in-bounds,
+// accesses above the declared maximum length are always out-of-bounds and if
+// initial equals maximum, the length is fixed.
+
+const instantiate = (builder, importObject = undefined) => {
+    return new WebAssembly.Instance(
+        new WebAssembly.Module(
+            builder.WebAssembly().get()),
+        importObject);
+};
+
+const test = (memoryToImport, importedMemoryDeclaration, growMemoryToImportBy = undefined, growImportedMemoryDeclarationBy = undefined, expectedFinal) => {
+    const builder0 = (new Builder())
+          .Type().End()
+          .Function().End()
+          .Memory().InitialMaxPages(memoryToImport.initial, memoryToImport.maximum).End()
+          .Export()
+              .Function("current").Function("grow").Function("get")
+              .Memory("memory", 0)
+          .End()
+          .Code()
+              .Function("current", { params: [], ret: "i32" }).CurrentMemory(0).Return().End()
+              .Function("grow", { params: ["i32"], ret: "i32" }).GetLocal(0).GrowMemory(0).Return().End()
+              .Function("get", { params: ["i32"], ret: "i32" }).GetLocal(0).I32Load(2, 0).Return().End()
+          .End();
+
+    const builder1 = (new Builder())
+          .Type().End()
+          .Import().Memory("imp", "memory", importedMemoryDeclaration).End()
+          .Function().End()
+          .Export()
+              .Function("current").Function("grow").Function("get")
+              .Memory("memory", 0)
+          .End()
+          .Code()
+              .Function("current", { params: [], ret: "i32" }).CurrentMemory(0).Return().End()
+              .Function("grow", { params: ["i32"], ret: "i32" }).GetLocal(0).GrowMemory(0).Return().End()
+              .Function("get", { params: ["i32"], ret: "i32" }).GetLocal(0).I32Load(2, 0).Return().End()
+          .End();
+
+    const i0 = instantiate(builder0);
+    const i1 = instantiate(builder1, { imp: { memory: i0.exports.memory } });
+    assert.eq(i0.exports.current(), i1.exports.current());
+    assert.eq(i0.exports.current(), memoryToImport.initial);
+
+    if (growMemoryToImportBy !== undefined) {
+        const grow = i0.exports.grow(growMemoryToImportBy);
+        if (verbose)
+            print(`currents: ${i0.exports.current()} and ${i1.exports.current()} -- grow result: ${grow}`);
+    }
+
+    if (growImportedMemoryDeclarationBy !== undefined) {
+        const grow = i1.exports.grow(growImportedMemoryDeclarationBy);
+        if (verbose)
+            print(`currents: ${i0.exports.current()} and ${i1.exports.current()} -- grow result: ${grow}`);
+    }
+
+    assert.eq(i0.exports.current(), i1.exports.current());
+    assert.eq(i0.exports.current(), expectedFinal);
+};
+
+const u = undefined;
+
+// Identical Just Works.
+test({ initial: 2, maximum: 4 }, { initial: 2, maximum: 4 }, 0, u, 2);
+test({ initial: 2, maximum: 4 }, { initial: 2, maximum: 4 }, 1, u, 3);
+test({ initial: 2, maximum: 4 }, { initial: 2, maximum: 4 }, 2, u, 4);
+test({ initial: 2, maximum: 4 }, { initial: 2, maximum: 4 }, 3, u, 2);
+test({ initial: 2, maximum: 4 }, { initial: 2, maximum: 4 }, u, 0, 2);
+test({ initial: 2, maximum: 4 }, { initial: 2, maximum: 4 }, u, 1, 3);
+test({ initial: 2, maximum: 4 }, { initial: 2, maximum: 4 }, u, 2, 4);
+test({ initial: 2, maximum: 4 }, { initial: 2, maximum: 4 }, u, 3, 2);
+
+// Allowed: imported initial is greater than declared.
+test({ initial: 2, maximum: 4 }, { initial: 1, maximum: 4 }, 0, u, 2);
+test({ initial: 2, maximum: 4 }, { initial: 1, maximum: 4 }, 1, u, 3);
+test({ initial: 2, maximum: 4 }, { initial: 1, maximum: 4 }, 2, u, 4);
+test({ initial: 2, maximum: 4 }, { initial: 1, maximum: 4 }, 3, u, 2);
+test({ initial: 2, maximum: 4 }, { initial: 0, maximum: 4 }, 0, u, 2);
+test({ initial: 2, maximum: 4 }, { initial: 0, maximum: 4 }, 1, u, 3);
+test({ initial: 2, maximum: 4 }, { initial: 0, maximum: 4 }, 2, u, 4);
+test({ initial: 2, maximum: 4 }, { initial: 0, maximum: 4 }, 3, u, 2);
+test({ initial: 2, maximum: 4 }, { initial: 1, maximum: 4 }, u, 0, 2);
+test({ initial: 2, maximum: 4 }, { initial: 1, maximum: 4 }, u, 1, 3);
+test({ initial: 2, maximum: 4 }, { initial: 1, maximum: 4 }, u, 2, 4);
+test({ initial: 2, maximum: 4 }, { initial: 1, maximum: 4 }, u, 3, 2);
+test({ initial: 2, maximum: 4 }, { initial: 0, maximum: 4 }, u, 0, 2);
+test({ initial: 2, maximum: 4 }, { initial: 0, maximum: 4 }, u, 1, 3);
+test({ initial: 2, maximum: 4 }, { initial: 0, maximum: 4 }, u, 2, 4);
+test({ initial: 2, maximum: 4 }, { initial: 0, maximum: 4 }, u, 3, 2);
+
+// Allowed: imported maximum is lesser than declared.
+test({ initial: 2, maximum: 3 }, { initial: 2, maximum: 4 }, 0, u, 2);
+test({ initial: 2, maximum: 3 }, { initial: 2, maximum: 4 }, 1, u, 3);
+test({ initial: 2, maximum: 3 }, { initial: 2, maximum: 4 }, 2, u, 2);
+test({ initial: 2, maximum: 2 }, { initial: 2, maximum: 4 }, 0, u, 2);
+test({ initial: 2, maximum: 2 }, { initial: 2, maximum: 4 }, 1, u, 2);
+test({ initial: 2, maximum: 3 }, { initial: 2, maximum: 4 }, u, 0, 2);
+test({ initial: 2, maximum: 3 }, { initial: 2, maximum: 4 }, u, 1, 3);
+test({ initial: 2, maximum: 3 }, { initial: 2, maximum: 4 }, u, 2, 2);
+test({ initial: 2, maximum: 2 }, { initial: 2, maximum: 4 }, u, 0, 2);
+test({ initial: 2, maximum: 2 }, { initial: 2, maximum: 4 }, u, 1, 2);
+
+// Allowed: no declared maximum, same as above.
+test({ initial: 2, maximum: 4 }, { initial: 2 }, 0, u, 2);
+
+// Disallowed: imported initial is lesser than declared.
+assert.throws(() => test({ initial: 1, maximum: 4 }, { initial: 2, maximum: 4 }, u, u, 2), WebAssembly.LinkError, `Memory import provided an 'initial' that is smaller than the module's declared 'initial' import memory size`);
+assert.throws(() => test({ initial: 0, maximum: 4 }, { initial: 2, maximum: 4 }, u, u, 2), WebAssembly.LinkError, `Memory import provided an 'initial' that is smaller than the module's declared 'initial' import memory size`);
+
+// Disallowed: imported maximum is greater than declared.
+assert.throws(() => test({ initial: 2, maximum: 5 }, { initial: 2, maximum: 4 }, u, u, 2), WebAssembly.LinkError, `Memory import provided a 'maximum' that is larger than the module's declared 'maximum' import memory size`);
+
+// Disallowed: no imported maximum, same as above.
+assert.throws(() => test({ initial: 2 }, { initial: 2, maximum: 4 }, 0, u, 2), WebAssembly.LinkError, `Memory import did not have a 'maximum' but the module requires that it does`);
diff --git a/JSTests/wasm/function-tests/memory-section-and-import.js b/JSTests/wasm/function-tests/memory-section-and-import.js
new file mode 100644 (file)
index 0000000..d09a878
--- /dev/null
@@ -0,0 +1,31 @@
+import Builder from '../Builder.js';
+import * as assert from '../assert.js';
+
+const instantiate = (builder, importObject = undefined) => {
+    return new WebAssembly.Instance(
+        new WebAssembly.Module(
+            builder.WebAssembly().get()),
+        importObject);
+};
+
+const initial = 0;
+const maximum = 2;
+
+const builder0 = (new Builder())
+      .Type().End()
+      .Function().End()
+      .Memory().InitialMaxPages(initial, maximum).End()
+      .Export()
+          .Memory("memory", 0)
+      .End()
+      .Code().End();
+
+const builder1 = (new Builder())
+      .Type().End()
+      .Import().Memory("imp", "memory", { initial: initial, maximum: maximum }).End()
+      .Function().End()
+      .Memory().InitialMaxPages(initial, maximum).End()
+      .Code().End();
+
+const i0 = instantiate(builder0);
+assert.throws(() => instantiate(builder1, { imp: { memory: i0.exports.memory } }), WebAssembly.CompileError, `WebAssembly.Module doesn't parse at byte 34 / 40: Memory section cannot exist if an Import has a memory`);
index 5d15358..0709ac7 100644 (file)
@@ -1,3 +1,13 @@
+2017-03-27  JF Bastien  <jfbastien@apple.com>
+
+        WebAssembly: misc memory testing
+        https://bugs.webkit.org/show_bug.cgi?id=170137
+
+        Reviewed by Keith Miller.
+
+        * wasm/js/WebAssemblyInstanceConstructor.cpp:
+        (JSC::WebAssemblyInstanceConstructor::createInstance): improve error messages
+
 2017-03-27  Michael Saboff  <msaboff@apple.com>
 
         Add ARM64 system instructions to disassembler
index ac3181b..16fc8d2 100644 (file)
@@ -168,16 +168,16 @@ JSWebAssemblyInstance* WebAssemblyInstanceConstructor::createInstance(ExecState*
             if (!table)
                 return exception(createJSWebAssemblyLinkError(exec, vm, ASCIILiteral("Table import is not an instance of WebAssembly.Table")));
 
-            uint32_t expectedInitial = moduleInformation.tableInformation.initial();
-            uint32_t actualInitial = table->size();
-            if (actualInitial < expectedInitial)
+            uint32_t declaredInitial = moduleInformation.tableInformation.initial();
+            uint32_t importedInitial = table->size();
+            if (importedInitial < declaredInitial)
                 return exception(createJSWebAssemblyLinkError(exec, vm, ASCIILiteral("Table import provided an 'initial' that is too small")));
 
-            if (std::optional<uint32_t> expectedMaximum = moduleInformation.tableInformation.maximum()) {
-                std::optional<uint32_t> actualMaximum = table->maximum();
-                if (!actualMaximum)
+            if (std::optional<uint32_t> declaredMaximum = moduleInformation.tableInformation.maximum()) {
+                std::optional<uint32_t> importedMaximum = table->maximum();
+                if (!importedMaximum)
                     return exception(createJSWebAssemblyLinkError(exec, vm, ASCIILiteral("Table import does not have a 'maximum' but the module requires that it does")));
-                if (*actualMaximum > *expectedMaximum)
+                if (*importedMaximum > *declaredMaximum)
                     return exception(createJSWebAssemblyLinkError(exec, vm, ASCIILiteral("Imported Table's 'maximum' is larger than the module's expected 'maximum'")));
             }
 
@@ -196,18 +196,18 @@ JSWebAssemblyInstance* WebAssemblyInstanceConstructor::createInstance(ExecState*
             if (!memory)
                 return exception(createJSWebAssemblyLinkError(exec, vm, ASCIILiteral("Memory import is not an instance of WebAssembly.Memory")));
 
-            Wasm::PageCount expectedInitial = moduleInformation.memory.initial();
-            Wasm::PageCount actualInitial = memory->memory().initial();
-            if (actualInitial < expectedInitial)
-                return exception(createJSWebAssemblyLinkError(exec, vm, ASCIILiteral("Memory import provided an 'initial' that is too small")));
+            Wasm::PageCount declaredInitial = moduleInformation.memory.initial();
+            Wasm::PageCount importedInitial = memory->memory().initial();
+            if (importedInitial < declaredInitial)
+                return exception(createJSWebAssemblyLinkError(exec, vm, ASCIILiteral("Memory import provided an 'initial' that is smaller than the module's declared 'initial' import memory size")));
 
-            if (Wasm::PageCount expectedMaximum = moduleInformation.memory.maximum()) {
-                Wasm::PageCount actualMaximum = memory->memory().maximum();
-                if (!actualMaximum)
+            if (Wasm::PageCount declaredMaximum = moduleInformation.memory.maximum()) {
+                Wasm::PageCount importedMaximum = memory->memory().maximum();
+                if (!importedMaximum)
                     return exception(createJSWebAssemblyLinkError(exec, vm, ASCIILiteral("Memory import did not have a 'maximum' but the module requires that it does")));
 
-                if (actualMaximum > expectedMaximum)
-                    return exception(createJSWebAssemblyLinkError(exec, vm, ASCIILiteral("Memory imports 'maximum' is larger than the module's expected 'maximum'")));
+                if (importedMaximum > declaredMaximum)
+                    return exception(createJSWebAssemblyLinkError(exec, vm, ASCIILiteral("Memory import provided a 'maximum' that is larger than the module's declared 'maximum' import memory size")));
             }
 
             // ii. Append v to memories.