e34646c6db81d86e67f82077af893a6ddf2cf36b
[WebKit-https.git] / Source / JavaScriptCore / wasm / js / WebAssemblyModuleRecord.cpp
1 /*
2  * Copyright (C) 2016-2018 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "WebAssemblyModuleRecord.h"
28
29 #if ENABLE(WEBASSEMBLY)
30
31 #include "Error.h"
32 #include "JSCInlines.h"
33 #include "JSLexicalEnvironment.h"
34 #include "JSModuleEnvironment.h"
35 #include "JSWebAssemblyHelpers.h"
36 #include "JSWebAssemblyInstance.h"
37 #include "JSWebAssemblyLinkError.h"
38 #include "JSWebAssemblyModule.h"
39 #include "ProtoCallFrame.h"
40 #include "WasmSignature.h"
41 #include "WebAssemblyFunction.h"
42 #include <limits>
43
44 namespace JSC {
45
46 const ClassInfo WebAssemblyModuleRecord::s_info = { "WebAssemblyModuleRecord", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(WebAssemblyModuleRecord) };
47
48 Structure* WebAssemblyModuleRecord::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
49 {
50     return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info());
51 }
52
53 WebAssemblyModuleRecord* WebAssemblyModuleRecord::create(ExecState* exec, VM& vm, Structure* structure, const Identifier& moduleKey, const Wasm::ModuleInformation& moduleInformation)
54 {
55     WebAssemblyModuleRecord* instance = new (NotNull, allocateCell<WebAssemblyModuleRecord>(vm.heap)) WebAssemblyModuleRecord(vm, structure, moduleKey);
56     instance->finishCreation(exec, vm, moduleInformation);
57     return instance;
58 }
59
60 WebAssemblyModuleRecord::WebAssemblyModuleRecord(VM& vm, Structure* structure, const Identifier& moduleKey)
61     : Base(vm, structure, moduleKey)
62 {
63 }
64
65 void WebAssemblyModuleRecord::destroy(JSCell* cell)
66 {
67     WebAssemblyModuleRecord* thisObject = static_cast<WebAssemblyModuleRecord*>(cell);
68     thisObject->WebAssemblyModuleRecord::~WebAssemblyModuleRecord();
69 }
70
71 void WebAssemblyModuleRecord::finishCreation(ExecState* exec, VM& vm, const Wasm::ModuleInformation& moduleInformation)
72 {
73     Base::finishCreation(exec, vm);
74     ASSERT(inherits(vm, info()));
75     for (const auto& exp : moduleInformation.exports) {
76         Identifier field = Identifier::fromString(&vm, String::fromUTF8(exp.field));
77         addExportEntry(ExportEntry::createLocal(field, field));
78     }
79 }
80
81 void WebAssemblyModuleRecord::visitChildren(JSCell* cell, SlotVisitor& visitor)
82 {
83     WebAssemblyModuleRecord* thisObject = jsCast<WebAssemblyModuleRecord*>(cell);
84     Base::visitChildren(thisObject, visitor);
85     visitor.append(thisObject->m_instance);
86     visitor.append(thisObject->m_startFunction);
87 }
88
89 void WebAssemblyModuleRecord::link(ExecState* exec, JSWebAssemblyModule* module, JSWebAssemblyInstance* instance)
90 {
91     VM& vm = exec->vm();
92     auto scope = DECLARE_THROW_SCOPE(vm);
93     UNUSED_PARAM(scope);
94     auto* globalObject = exec->lexicalGlobalObject();
95
96     Wasm::CodeBlock* codeBlock = instance->instance().codeBlock();
97     const Wasm::ModuleInformation& moduleInformation = module->moduleInformation();
98
99     SymbolTable* exportSymbolTable = module->exportSymbolTable();
100     unsigned functionImportCount = codeBlock->functionImportCount();
101
102     // Let exports be a list of (string, JS value) pairs that is mapped from each external value e in instance.exports as follows:
103     JSModuleEnvironment* moduleEnvironment = JSModuleEnvironment::create(vm, globalObject, nullptr, exportSymbolTable, JSValue(), this);
104     for (const auto& exp : moduleInformation.exports) {
105         JSValue exportedValue;
106         switch (exp.kind) {
107         case Wasm::ExternalKind::Function: {
108             // 1. If e is a closure c:
109             //   i. If there is an Exported Function Exotic Object func in funcs whose func.[[Closure]] equals c, then return func.
110             //   ii. (Note: At most one wrapper is created for any closure, so func is unique, even if there are multiple occurrances in the list. Moreover, if the item was an import that is already an Exported Function Exotic Object, then the original function object will be found. For imports that are regular JS functions, a new wrapper will be created.)
111             if (exp.kindIndex < functionImportCount) {
112                 unsigned functionIndex = exp.kindIndex;
113                 JSObject* functionImport = instance->instance().importFunction<JSWebAssemblyInstance::PoisonedBarrier<JSObject>>(functionIndex)->get();
114                 if (isWebAssemblyHostFunction(vm, functionImport))
115                     exportedValue = functionImport;
116                 else {
117                     Wasm::SignatureIndex signatureIndex = module->signatureIndexFromFunctionIndexSpace(functionIndex);
118                     exportedValue = WebAssemblyWrapperFunction::create(vm, globalObject, functionImport, functionIndex, instance, signatureIndex);
119                 }
120             } else {
121                 //   iii. Otherwise:
122                 //     a. Let func be an Exported Function Exotic Object created from c.
123                 //     b. Append func to funcs.
124                 //     c. Return func.
125                 Wasm::Callee& embedderEntrypointCallee = codeBlock->embedderEntrypointCalleeFromFunctionIndexSpace(exp.kindIndex);
126                 Wasm::WasmToWasmImportableFunction::LoadLocation entrypointLoadLocation = codeBlock->entrypointLoadLocationFromFunctionIndexSpace(exp.kindIndex);
127                 Wasm::SignatureIndex signatureIndex = module->signatureIndexFromFunctionIndexSpace(exp.kindIndex);
128                 const Wasm::Signature& signature = Wasm::SignatureInformation::get(signatureIndex);
129                 WebAssemblyFunction* function = WebAssemblyFunction::create(vm, globalObject, signature.argumentCount(), String::fromUTF8(exp.field), instance, embedderEntrypointCallee, entrypointLoadLocation, signatureIndex);
130                 exportedValue = function;
131             }
132             break;
133         }
134         case Wasm::ExternalKind::Table: {
135             // This should be guaranteed by module verification.
136             RELEASE_ASSERT(instance->table()); 
137             ASSERT(exp.kindIndex == 0);
138
139             exportedValue = instance->table();
140             break;
141         }
142         case Wasm::ExternalKind::Memory: {
143             ASSERT(exp.kindIndex == 0);
144
145             exportedValue = instance->memory();
146             break;
147         }
148         case Wasm::ExternalKind::Global: {
149             // Assert: the global is immutable by MVP validation constraint.
150             const Wasm::Global& global = moduleInformation.globals[exp.kindIndex];
151             ASSERT(global.mutability == Wasm::Global::Immutable);
152             // Return ToJSValue(v).
153             switch (global.type) {
154             case Wasm::I32:
155                 exportedValue = JSValue(instance->instance().loadI32Global(exp.kindIndex));
156                 break;
157
158             case Wasm::I64:
159                 throwException(exec, scope, createJSWebAssemblyLinkError(exec, vm, ASCIILiteral("exported global cannot be an i64")));
160                 return;
161
162             case Wasm::F32:
163                 exportedValue = JSValue(instance->instance().loadF32Global(exp.kindIndex));
164                 break;
165
166             case Wasm::F64:
167                 exportedValue = JSValue(instance->instance().loadF64Global(exp.kindIndex));
168                 break;
169
170             default:
171                 RELEASE_ASSERT_NOT_REACHED();
172             }
173             break;
174         }
175         }
176
177         bool shouldThrowReadOnlyError = false;
178         bool ignoreReadOnlyErrors = true;
179         bool putResult = false;
180         symbolTablePutTouchWatchpointSet(moduleEnvironment, exec, Identifier::fromString(&vm, String::fromUTF8(exp.field)), exportedValue, shouldThrowReadOnlyError, ignoreReadOnlyErrors, putResult);
181         scope.assertNoException();
182         RELEASE_ASSERT(putResult);
183     }
184
185     bool hasStart = !!moduleInformation.startFunctionIndexSpace;
186     if (hasStart) {
187         auto startFunctionIndexSpace = moduleInformation.startFunctionIndexSpace.value_or(0);
188         Wasm::SignatureIndex signatureIndex = module->signatureIndexFromFunctionIndexSpace(startFunctionIndexSpace);
189         const Wasm::Signature& signature = Wasm::SignatureInformation::get(signatureIndex);
190         // The start function must not take any arguments or return anything. This is enforced by the parser.
191         ASSERT(!signature.argumentCount());
192         ASSERT(signature.returnType() == Wasm::Void);
193         if (startFunctionIndexSpace < codeBlock->functionImportCount()) {
194             JSObject* startFunction = instance->instance().importFunction<JSWebAssemblyInstance::PoisonedBarrier<JSObject>>(startFunctionIndexSpace)->get();
195             m_startFunction.set(vm, this, startFunction);
196         } else {
197             Wasm::Callee& embedderEntrypointCallee = codeBlock->embedderEntrypointCalleeFromFunctionIndexSpace(startFunctionIndexSpace);
198             Wasm::WasmToWasmImportableFunction::LoadLocation entrypointLoadLocation = codeBlock->entrypointLoadLocationFromFunctionIndexSpace(startFunctionIndexSpace);
199             WebAssemblyFunction* function = WebAssemblyFunction::create(vm, globalObject, signature.argumentCount(), "start", instance, embedderEntrypointCallee, entrypointLoadLocation, signatureIndex);
200             m_startFunction.set(vm, this, function);
201         }
202     }
203
204     RELEASE_ASSERT(!m_instance);
205     m_instance.set(vm, this, instance);
206     m_moduleEnvironment.set(vm, this, moduleEnvironment);
207 }
208
209 template <typename Scope, typename M, typename N, typename ...Args>
210 NEVER_INLINE static JSValue dataSegmentFail(ExecState* exec, VM& vm, Scope& scope, M memorySize, N segmentSize, N offset, Args... args)
211 {
212     return throwException(exec, scope, createJSWebAssemblyLinkError(exec, vm, makeString(ASCIILiteral("Invalid data segment initialization: segment of "), String::number(segmentSize), ASCIILiteral(" bytes memory of "), String::number(memorySize), ASCIILiteral(" bytes, at offset "), String::number(offset), args...)));
213 }
214
215 JSValue WebAssemblyModuleRecord::evaluate(ExecState* exec)
216 {
217     VM& vm = exec->vm();
218     auto scope = DECLARE_THROW_SCOPE(vm);
219
220     Wasm::Module& module = m_instance->instance().module();
221     Wasm::CodeBlock* codeBlock = m_instance->instance().codeBlock();
222     const Wasm::ModuleInformation& moduleInformation = module.moduleInformation();
223     JSWebAssemblyTable* table = m_instance->table();
224
225     const Vector<Wasm::Segment::Ptr>& data = moduleInformation.data;
226     
227     std::optional<JSValue> exception;
228
229     auto forEachElement = [&] (auto fn) {
230         for (const Wasm::Element& element : moduleInformation.elements) {
231             // It should be a validation error to have any elements without a table.
232             // Also, it could be that a table wasn't imported, or that the table
233             // imported wasn't compatible. However, those should error out before
234             // getting here.
235             ASSERT(!!table);
236
237             if (!element.functionIndices.size())
238                 continue;
239
240             uint32_t tableIndex = element.offset.isGlobalImport()
241                 ? static_cast<uint32_t>(m_instance->instance().loadI32Global(element.offset.globalImportIndex()))
242                 : element.offset.constValue();
243
244             fn(element, tableIndex);
245
246             if (exception)
247                 break;
248         }
249     };
250
251     auto forEachSegment = [&] (auto fn) {
252         uint8_t* memory = reinterpret_cast<uint8_t*>(m_instance->instance().cachedMemory());
253         uint64_t sizeInBytes = m_instance->instance().cachedMemorySize();
254
255         for (const Wasm::Segment::Ptr& segment : data) {
256             uint32_t offset = segment->offset.isGlobalImport()
257                 ? static_cast<uint32_t>(m_instance->instance().loadI32Global(segment->offset.globalImportIndex()))
258                 : segment->offset.constValue();
259
260             fn(memory, sizeInBytes, segment, offset);
261
262             if (exception)
263                 break;
264         }
265     };
266
267     // Validation of all element ranges comes before all Table and Memory initialization.
268     forEachElement([&] (const Wasm::Element& element, uint32_t tableIndex) {
269         uint64_t lastWrittenIndex = static_cast<uint64_t>(tableIndex) + static_cast<uint64_t>(element.functionIndices.size()) - 1;
270         if (UNLIKELY(lastWrittenIndex >= table->length()))
271             exception = JSValue(throwException(exec, scope, createJSWebAssemblyLinkError(exec, vm, ASCIILiteral("Element is trying to set an out of bounds table index"))));
272     });
273
274     if (UNLIKELY(exception))
275         return exception.value();
276
277     // Validation of all segment ranges comes before all Table and Memory initialization.
278     forEachSegment([&] (uint8_t*, uint64_t sizeInBytes, const Wasm::Segment::Ptr& segment, uint32_t offset) {
279         if (UNLIKELY(sizeInBytes < segment->sizeInBytes))
280             exception = dataSegmentFail(exec, vm, scope, sizeInBytes, segment->sizeInBytes, offset, ASCIILiteral(", segment is too big"));
281         else if (UNLIKELY(offset > sizeInBytes - segment->sizeInBytes))
282             exception = dataSegmentFail(exec, vm, scope, sizeInBytes, segment->sizeInBytes, offset, ASCIILiteral(", segment writes outside of memory"));
283     });
284
285     if (UNLIKELY(exception))
286         return exception.value();
287
288     forEachElement([&] (const Wasm::Element& element, uint32_t tableIndex) {
289         for (uint32_t i = 0; i < element.functionIndices.size(); ++i) {
290             // FIXME: This essentially means we're exporting an import.
291             // We need a story here. We need to create a WebAssemblyFunction
292             // for the import.
293             // https://bugs.webkit.org/show_bug.cgi?id=165510
294             uint32_t functionIndex = element.functionIndices[i];
295             Wasm::SignatureIndex signatureIndex = module.signatureIndexFromFunctionIndexSpace(functionIndex);
296             if (functionIndex < codeBlock->functionImportCount()) {
297                 JSObject* functionImport = m_instance->instance().importFunction<JSWebAssemblyInstance::PoisonedBarrier<JSObject>>(functionIndex)->get();
298                 if (isWebAssemblyHostFunction(vm, functionImport)) {
299                     WebAssemblyFunction* wasmFunction = jsDynamicCast<WebAssemblyFunction*>(vm, functionImport);
300                     // If we ever import a WebAssemblyWrapperFunction, we set the import as the unwrapped value.
301                     // Because a WebAssemblyWrapperFunction can never wrap another WebAssemblyWrapperFunction,
302                     // the only type this could be is WebAssemblyFunction.
303                     RELEASE_ASSERT(wasmFunction);
304                     table->setFunction(vm, tableIndex, wasmFunction);
305                     ++tableIndex;
306                     continue;
307                 }
308
309                 table->setFunction(vm, tableIndex,
310                     WebAssemblyWrapperFunction::create(vm, m_instance->globalObject(), functionImport, functionIndex, m_instance.get(), signatureIndex));
311                 ++tableIndex;
312                 continue;
313             }
314
315             Wasm::Callee& embedderEntrypointCallee = codeBlock->embedderEntrypointCalleeFromFunctionIndexSpace(functionIndex);
316             Wasm::WasmToWasmImportableFunction::LoadLocation entrypointLoadLocation = codeBlock->entrypointLoadLocationFromFunctionIndexSpace(functionIndex);
317             const Wasm::Signature& signature = Wasm::SignatureInformation::get(signatureIndex);
318             // FIXME: Say we export local function "foo" at function index 0.
319             // What if we also set it to the table an Element w/ index 0.
320             // Does (new Instance(...)).exports.foo === table.get(0)?
321             // https://bugs.webkit.org/show_bug.cgi?id=165825
322             WebAssemblyFunction* function = WebAssemblyFunction::create(
323                 vm, m_instance->globalObject(), signature.argumentCount(), String(), m_instance.get(), embedderEntrypointCallee, entrypointLoadLocation, signatureIndex);
324
325             table->setFunction(vm, tableIndex, function);
326             ++tableIndex;
327         }
328     });
329
330     ASSERT(!exception);
331
332     forEachSegment([&] (uint8_t* memory, uint64_t, const Wasm::Segment::Ptr& segment, uint32_t offset) {
333         // Empty segments are valid, but only if memory isn't present, which would be undefined behavior in memcpy.
334         if (segment->sizeInBytes) {
335             RELEASE_ASSERT(memory);
336             memcpy(memory + offset, &segment->byte(0), segment->sizeInBytes);
337         }
338     });
339
340     ASSERT(!exception);
341
342     if (JSObject* startFunction = m_startFunction.get()) {
343         CallData callData;
344         CallType callType = JSC::getCallData(startFunction, callData);
345         call(exec, startFunction, callType, callData, jsUndefined(), *vm.emptyList);
346         RETURN_IF_EXCEPTION(scope, { });
347     }
348
349     return jsUndefined();
350 }
351
352 } // namespace JSC
353
354 #endif // ENABLE(WEBASSEMBLY)