1/*
2 * Copyright (C) 2016-2019 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 "JSWebAssembly.h"
28
29#if ENABLE(WEBASSEMBLY)
30
31#include "CatchScope.h"
32#include "Exception.h"
33#include "FunctionPrototype.h"
34#include "JSCBuiltins.h"
35#include "JSCInlines.h"
36#include "JSModuleNamespaceObject.h"
37#include "JSPromise.h"
38#include "JSToWasm.h"
39#include "JSWebAssemblyHelpers.h"
40#include "JSWebAssemblyInstance.h"
41#include "JSWebAssemblyModule.h"
42#include "ObjectConstructor.h"
43#include "Options.h"
44#include "PromiseTimer.h"
45#include "StrongInlines.h"
46#include "ThrowScope.h"
47#include "WasmBBQPlan.h"
48#include "WasmOperations.h"
49#include "WasmToJS.h"
50#include "WasmWorklist.h"
51#include "WebAssemblyInstanceConstructor.h"
52#include "WebAssemblyModuleConstructor.h"
53
54using JSC::Wasm::Plan;
55using JSC::Wasm::BBQPlan;
56
57namespace JSC {
58
59STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(JSWebAssembly);
60
61#define DEFINE_CALLBACK_FOR_CONSTRUCTOR(capitalName, lowerName, properName, instanceType, jsName, prototypeBase, featureFlag) \
62static JSValue create##capitalName(VM& vm, JSObject* object) \
63{ \
64 JSWebAssembly* webAssembly = jsCast<JSWebAssembly*>(object); \
65 JSGlobalObject* globalObject = webAssembly->globalObject(vm); \
66 return globalObject->properName##Constructor(); \
67}
68
69FOR_EACH_WEBASSEMBLY_CONSTRUCTOR_TYPE(DEFINE_CALLBACK_FOR_CONSTRUCTOR)
70
71#undef DEFINE_CALLBACK_FOR_CONSTRUCTOR
72
73static EncodedJSValue JSC_HOST_CALL webAssemblyCompileFunc(JSGlobalObject*, CallFrame*);
74static EncodedJSValue JSC_HOST_CALL webAssemblyInstantiateFunc(JSGlobalObject*, CallFrame*);
75static EncodedJSValue JSC_HOST_CALL webAssemblyValidateFunc(JSGlobalObject*, CallFrame*);
76
77}
78
79#include "JSWebAssembly.lut.h"
80
81namespace JSC {
82
83const ClassInfo JSWebAssembly::s_info = { "WebAssembly", &Base::s_info, &webAssemblyTable, nullptr, CREATE_METHOD_TABLE(JSWebAssembly) };
84
85/* Source for JSWebAssembly.lut.h
86@begin webAssemblyTable
87 CompileError createWebAssemblyCompileError DontEnum|PropertyCallback
88 Instance createWebAssemblyInstance DontEnum|PropertyCallback
89 LinkError createWebAssemblyLinkError DontEnum|PropertyCallback
90 Memory createWebAssemblyMemory DontEnum|PropertyCallback
91 Module createWebAssemblyModule DontEnum|PropertyCallback
92 RuntimeError createWebAssemblyRuntimeError DontEnum|PropertyCallback
93 Table createWebAssemblyTable DontEnum|PropertyCallback
94 compile webAssemblyCompileFunc Function 1
95 instantiate webAssemblyInstantiateFunc Function 1
96 validate webAssemblyValidateFunc Function 1
97@end
98*/
99
100JSWebAssembly* JSWebAssembly::create(VM& vm, JSGlobalObject* globalObject, Structure* structure)
101{
102 auto* object = new (NotNull, allocateCell<JSWebAssembly>(vm.heap)) JSWebAssembly(vm, structure);
103 object->finishCreation(vm, globalObject);
104 return object;
105}
106
107Structure* JSWebAssembly::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
108{
109 return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info());
110}
111
112void JSWebAssembly::finishCreation(VM& vm, JSGlobalObject* globalObject)
113{
114 Base::finishCreation(vm);
115 ASSERT(inherits(vm, info()));
116 if (Options::useWebAssemblyStreamingApi()) {
117 JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION("compileStreaming", webAssemblyCompileStreamingCodeGenerator, static_cast<unsigned>(PropertyAttribute::DontEnum));
118 JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION("instantiateStreaming", webAssemblyInstantiateStreamingCodeGenerator, static_cast<unsigned>(PropertyAttribute::DontEnum));
119 }
120}
121
122JSWebAssembly::JSWebAssembly(VM& vm, Structure* structure)
123 : JSNonFinalObject(vm, structure)
124{
125}
126
127static void reject(JSGlobalObject* globalObject, CatchScope& catchScope, JSPromise* promise)
128{
129 Exception* exception = catchScope.exception();
130 ASSERT(exception);
131 catchScope.clearException();
132 promise->reject(globalObject, exception->value());
133 CLEAR_AND_RETURN_IF_EXCEPTION(catchScope, void());
134}
135
136static void webAssemblyModuleValidateAsyncInternal(JSGlobalObject* globalObject, JSPromise* promise, Vector<uint8_t>&& source)
137{
138 VM& vm = globalObject->vm();
139
140 Vector<Strong<JSCell>> dependencies;
141 dependencies.append(Strong<JSCell>(vm, globalObject));
142
143 vm.promiseTimer->addPendingPromise(vm, promise, WTFMove(dependencies));
144
145 Wasm::Module::validateAsync(&vm.wasmContext, WTFMove(source), createSharedTask<Wasm::Module::CallbackType>([promise, globalObject, &vm] (Wasm::Module::ValidationResult&& result) mutable {
146 vm.promiseTimer->scheduleWorkSoon(promise, [promise, globalObject, result = WTFMove(result), &vm] () mutable {
147 auto scope = DECLARE_CATCH_SCOPE(vm);
148 JSValue module = JSWebAssemblyModule::createStub(vm, globalObject, globalObject->webAssemblyModuleStructure(), WTFMove(result));
149 if (UNLIKELY(scope.exception())) {
150 reject(globalObject, scope, promise);
151 return;
152 }
153
154 promise->resolve(globalObject, module);
155 CLEAR_AND_RETURN_IF_EXCEPTION(scope, void());
156 });
157 }));
158}
159
160static EncodedJSValue JSC_HOST_CALL webAssemblyCompileFunc(JSGlobalObject* globalObject, CallFrame* callFrame)
161{
162 VM& vm = globalObject->vm();
163 auto throwScope = DECLARE_THROW_SCOPE(vm);
164
165 auto* promise = JSPromise::create(vm, globalObject->promiseStructure());
166 RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
167
168 {
169 auto catchScope = DECLARE_CATCH_SCOPE(vm);
170 Vector<uint8_t> source = createSourceBufferFromValue(vm, globalObject, callFrame->argument(0));
171
172 if (UNLIKELY(catchScope.exception()))
173 reject(globalObject, catchScope, promise);
174 else
175 webAssemblyModuleValidateAsyncInternal(globalObject, promise, WTFMove(source));
176
177 return JSValue::encode(promise);
178 }
179}
180
181enum class Resolve { WithInstance, WithModuleRecord, WithModuleAndInstance };
182static void resolve(VM& vm, JSGlobalObject* globalObject, JSPromise* promise, JSWebAssemblyInstance* instance, JSWebAssemblyModule* module, JSObject* importObject, Ref<Wasm::CodeBlock>&& codeBlock, Resolve resolveKind, Wasm::CreationMode creationMode)
183{
184 auto scope = DECLARE_CATCH_SCOPE(vm);
185 instance->finalizeCreation(vm, globalObject, WTFMove(codeBlock), importObject, creationMode);
186 RETURN_IF_EXCEPTION(scope, reject(globalObject, scope, promise));
187
188 if (resolveKind == Resolve::WithInstance)
189 promise->resolve(globalObject, instance);
190 else if (resolveKind == Resolve::WithModuleRecord) {
191 auto* moduleRecord = instance->moduleNamespaceObject()->moduleRecord();
192 if (Options::dumpModuleRecord())
193 moduleRecord->dump();
194 promise->resolve(globalObject, moduleRecord);
195 } else {
196 JSObject* result = constructEmptyObject(globalObject);
197 result->putDirect(vm, Identifier::fromString(vm, "module"_s), module);
198 result->putDirect(vm, Identifier::fromString(vm, "instance"_s), instance);
199 promise->resolve(globalObject, result);
200 }
201 CLEAR_AND_RETURN_IF_EXCEPTION(scope, void());
202}
203
204void JSWebAssembly::webAssemblyModuleValidateAsync(JSGlobalObject* globalObject, JSPromise* promise, Vector<uint8_t>&& source)
205{
206 VM& vm = globalObject->vm();
207 auto catchScope = DECLARE_CATCH_SCOPE(vm);
208 webAssemblyModuleValidateAsyncInternal(globalObject, promise, WTFMove(source));
209 CLEAR_AND_RETURN_IF_EXCEPTION(catchScope, void());
210}
211
212static void instantiate(VM& vm, JSGlobalObject* globalObject, JSPromise* promise, JSWebAssemblyModule* module, JSObject* importObject, const Identifier& moduleKey, Resolve resolveKind, Wasm::CreationMode creationMode)
213{
214 auto scope = DECLARE_CATCH_SCOPE(vm);
215 // In order to avoid potentially recompiling a module. We first gather all the import/memory information prior to compiling code.
216 JSWebAssemblyInstance* instance = JSWebAssemblyInstance::create(vm, globalObject, moduleKey, module, importObject, globalObject->webAssemblyInstanceStructure(), Ref<Wasm::Module>(module->module()), creationMode);
217 RETURN_IF_EXCEPTION(scope, reject(globalObject, scope, promise));
218
219 Vector<Strong<JSCell>> dependencies;
220 // The instance keeps the module alive.
221 dependencies.append(Strong<JSCell>(vm, instance));
222 dependencies.append(Strong<JSCell>(vm, importObject));
223 vm.promiseTimer->addPendingPromise(vm, promise, WTFMove(dependencies));
224 // Note: This completion task may or may not get called immediately.
225 module->module().compileAsync(&vm.wasmContext, instance->memoryMode(), createSharedTask<Wasm::CodeBlock::CallbackType>([promise, instance, module, importObject, resolveKind, creationMode, &vm] (Ref<Wasm::CodeBlock>&& refCodeBlock) mutable {
226 RefPtr<Wasm::CodeBlock> codeBlock = WTFMove(refCodeBlock);
227 vm.promiseTimer->scheduleWorkSoon(promise, [promise, instance, module, importObject, resolveKind, creationMode, &vm, codeBlock = WTFMove(codeBlock)] () mutable {
228 JSGlobalObject* globalObject = instance->globalObject();
229 resolve(vm, globalObject, promise, instance, module, importObject, codeBlock.releaseNonNull(), resolveKind, creationMode);
230 });
231 }), &Wasm::createJSToWasmWrapper, &Wasm::operationWasmToJSException);
232}
233
234static void compileAndInstantiate(VM& vm, JSGlobalObject* globalObject, JSPromise* promise, const Identifier& moduleKey, JSValue buffer, JSObject* importObject, Resolve resolveKind, Wasm::CreationMode creationMode)
235{
236 auto scope = DECLARE_CATCH_SCOPE(vm);
237
238 JSCell* moduleKeyCell = identifierToJSValue(vm, moduleKey).asCell();
239 Vector<Strong<JSCell>> dependencies;
240 dependencies.append(Strong<JSCell>(vm, importObject));
241 dependencies.append(Strong<JSCell>(vm, moduleKeyCell));
242 vm.promiseTimer->addPendingPromise(vm, promise, WTFMove(dependencies));
243
244 Vector<uint8_t> source = createSourceBufferFromValue(vm, globalObject, buffer);
245 RETURN_IF_EXCEPTION(scope, reject(globalObject, scope, promise));
246
247 Wasm::Module::validateAsync(&vm.wasmContext, WTFMove(source), createSharedTask<Wasm::Module::CallbackType>([promise, importObject, moduleKeyCell, globalObject, resolveKind, creationMode, &vm] (Wasm::Module::ValidationResult&& result) mutable {
248 vm.promiseTimer->scheduleWorkSoon(promise, [promise, importObject, moduleKeyCell, globalObject, result = WTFMove(result), resolveKind, creationMode, &vm] () mutable {
249 auto scope = DECLARE_CATCH_SCOPE(vm);
250 JSWebAssemblyModule* module = JSWebAssemblyModule::createStub(vm, globalObject, globalObject->webAssemblyModuleStructure(), WTFMove(result));
251 if (UNLIKELY(scope.exception()))
252 return reject(globalObject, scope, promise);
253
254 const Identifier moduleKey = JSValue(moduleKeyCell).toPropertyKey(globalObject);
255 if (UNLIKELY(scope.exception()))
256 return reject(globalObject, scope, promise);
257
258 instantiate(vm, globalObject, promise, module, importObject, moduleKey, resolveKind, creationMode);
259 });
260 }));
261}
262
263JSValue JSWebAssembly::instantiate(JSGlobalObject* globalObject, JSPromise* promise, const Identifier& moduleKey, JSValue argument)
264{
265 VM& vm = globalObject->vm();
266 compileAndInstantiate(vm, globalObject, promise, moduleKey, argument, nullptr, Resolve::WithModuleRecord, Wasm::CreationMode::FromModuleLoader);
267 return promise;
268}
269
270static void webAssemblyModuleInstantinateAsyncInternal(JSGlobalObject* globalObject, JSPromise* promise, Vector<uint8_t>&& source, JSObject* importObject)
271{
272 VM& vm = globalObject->vm();
273
274 Vector<Strong<JSCell>> dependencies;
275 dependencies.append(Strong<JSCell>(vm, importObject));
276 dependencies.append(Strong<JSCell>(vm, globalObject));
277 vm.promiseTimer->addPendingPromise(vm, promise, WTFMove(dependencies));
278
279 Wasm::Module::validateAsync(&vm.wasmContext, WTFMove(source), createSharedTask<Wasm::Module::CallbackType>([promise, importObject, globalObject, &vm] (Wasm::Module::ValidationResult&& result) mutable {
280 vm.promiseTimer->scheduleWorkSoon(promise, [promise, importObject, globalObject, result = WTFMove(result), &vm] () mutable {
281 auto scope = DECLARE_CATCH_SCOPE(vm);
282 JSWebAssemblyModule* module = JSWebAssemblyModule::createStub(vm, globalObject, globalObject->webAssemblyModuleStructure(), WTFMove(result));
283 if (UNLIKELY(scope.exception()))
284 return reject(globalObject, scope, promise);
285
286 instantiate(vm, globalObject, promise, module, importObject, JSWebAssemblyInstance::createPrivateModuleKey(), Resolve::WithModuleAndInstance, Wasm::CreationMode::FromJS);
287 CLEAR_AND_RETURN_IF_EXCEPTION(scope, reject(globalObject, scope, promise));
288 });
289 }));
290}
291
292void JSWebAssembly::webAssemblyModuleInstantinateAsync(JSGlobalObject* globalObject, JSPromise* promise, Vector<uint8_t>&& source, JSObject* importedObject)
293{
294 VM& vm = globalObject->vm();
295 auto catchScope = DECLARE_CATCH_SCOPE(vm);
296 webAssemblyModuleInstantinateAsyncInternal(globalObject, promise, WTFMove(source), importedObject);
297 CLEAR_AND_RETURN_IF_EXCEPTION(catchScope, void());
298}
299
300static EncodedJSValue JSC_HOST_CALL webAssemblyInstantiateFunc(JSGlobalObject* globalObject, CallFrame* callFrame)
301{
302 VM& vm = globalObject->vm();
303
304 auto* promise = JSPromise::create(vm, globalObject->promiseStructure());
305 {
306 auto catchScope = DECLARE_CATCH_SCOPE(vm);
307
308 JSValue importArgument = callFrame->argument(1);
309 JSObject* importObject = importArgument.getObject();
310 if (UNLIKELY(!importArgument.isUndefined() && !importObject)) {
311 promise->reject(globalObject, createTypeError(globalObject,
312 "second argument to WebAssembly.instantiate must be undefined or an Object"_s, defaultSourceAppender, runtimeTypeForValue(vm, importArgument)));
313 CLEAR_AND_RETURN_IF_EXCEPTION(catchScope, JSValue::encode(promise));
314 } else {
315 JSValue firstArgument = callFrame->argument(0);
316 if (auto* module = jsDynamicCast<JSWebAssemblyModule*>(vm, firstArgument))
317 instantiate(vm, globalObject, promise, module, importObject, JSWebAssemblyInstance::createPrivateModuleKey(), Resolve::WithInstance, Wasm::CreationMode::FromJS);
318 else
319 compileAndInstantiate(vm, globalObject, promise, JSWebAssemblyInstance::createPrivateModuleKey(), firstArgument, importObject, Resolve::WithModuleAndInstance, Wasm::CreationMode::FromJS);
320 }
321
322 return JSValue::encode(promise);
323 }
324}
325
326static EncodedJSValue JSC_HOST_CALL webAssemblyValidateFunc(JSGlobalObject* globalObject, CallFrame* callFrame)
327{
328 VM& vm = globalObject->vm();
329 auto scope = DECLARE_THROW_SCOPE(vm);
330
331 auto [base, byteSize] = getWasmBufferFromValue(globalObject, callFrame->argument(0));
332 RETURN_IF_EXCEPTION(scope, encodedJSValue());
333 BBQPlan plan(&vm.wasmContext, BBQPlan::Validation, Plan::dontFinalize());
334 // FIXME: We might want to throw an OOM exception here if we detect that something will OOM.
335 // https://bugs.webkit.org/show_bug.cgi?id=166015
336 return JSValue::encode(jsBoolean(plan.parseAndValidateModule(base, byteSize)));
337}
338
339EncodedJSValue JSC_HOST_CALL webAssemblyCompileStreamingInternal(JSGlobalObject* globalObject, CallFrame* callFrame)
340{
341 VM& vm = globalObject->vm();
342 auto catchScope = DECLARE_CATCH_SCOPE(vm);
343
344 auto* promise = JSPromise::create(vm, globalObject->promiseStructure());
345
346 Vector<Strong<JSCell>> dependencies;
347 dependencies.append(Strong<JSCell>(vm, globalObject));
348 vm.promiseTimer->addPendingPromise(vm, promise, WTFMove(dependencies));
349
350 if (globalObject->globalObjectMethodTable()->compileStreaming)
351 globalObject->globalObjectMethodTable()->compileStreaming(globalObject, promise, callFrame->argument(0));
352 else {
353 // CompileStreaming is not supported in jsc, only in browser environment
354 ASSERT_NOT_REACHED();
355 }
356
357 CLEAR_AND_RETURN_IF_EXCEPTION(catchScope, JSValue::encode(promise));
358
359 return JSValue::encode(promise);
360}
361
362EncodedJSValue JSC_HOST_CALL webAssemblyInstantiateStreamingInternal(JSGlobalObject* globalObject, CallFrame* callFrame)
363{
364 VM& vm = globalObject->vm();
365
366 auto* promise = JSPromise::create(vm, globalObject->promiseStructure());
367 {
368 auto catchScope = DECLARE_CATCH_SCOPE(vm);
369
370 JSValue importArgument = callFrame->argument(1);
371 JSObject* importObject = importArgument.getObject();
372 if (UNLIKELY(!importArgument.isUndefined() && !importObject)) {
373 promise->reject(globalObject, createTypeError(globalObject,
374 "second argument to WebAssembly.instantiateStreaming must be undefined or an Object"_s, defaultSourceAppender, runtimeTypeForValue(vm, importArgument)));
375 CLEAR_AND_RETURN_IF_EXCEPTION(catchScope, JSValue::encode(promise));
376 } else {
377 if (globalObject->globalObjectMethodTable()->instantiateStreaming) {
378 Vector<Strong<JSCell>> dependencies;
379 dependencies.append(Strong<JSCell>(vm, globalObject));
380 dependencies.append(Strong<JSCell>(vm, importObject));
381 vm.promiseTimer->addPendingPromise(vm, promise, WTFMove(dependencies));
382
383 // FIXME: <http://webkit.org/b/184888> if there's an importObject and it contains a Memory, then we can compile the module with the right memory type (fast or not) by looking at the memory's type.
384 globalObject->globalObjectMethodTable()->instantiateStreaming(globalObject, promise, callFrame->argument(0), importObject);
385 } else {
386 // InstantiateStreaming is not supported in jsc, only in browser environment.
387 ASSERT_NOT_REACHED();
388 }
389 }
390 CLEAR_AND_RETURN_IF_EXCEPTION(catchScope, JSValue::encode(promise));
391
392 return JSValue::encode(promise);
393 }
394}
395
396} // namespace JSC
397
398#endif // ENABLE(WEBASSEMBLY)
399