Bug 1874684 - Part 21: Rename SecondsAndNanoseconds::toTotalNanoseconds. r=dminor
[gecko.git] / js / src / fuzz-tests / testWasm.cpp
blobd7ba0511b48addb1c87eec95d3e4f9ef039ea609
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "mozilla/ScopeExit.h"
7 #include "jsapi.h"
8 #include "jspubtd.h"
10 #include "fuzz-tests/tests.h"
11 #include "js/CallAndConstruct.h"
12 #include "js/Prefs.h"
13 #include "js/PropertyAndElement.h" // JS_Enumerate, JS_GetProperty, JS_GetPropertyById, JS_HasProperty, JS_SetProperty
14 #include "vm/GlobalObject.h"
15 #include "vm/Interpreter.h"
16 #include "vm/TypedArrayObject.h"
18 #include "wasm/WasmCompile.h"
19 #include "wasm/WasmFeatures.h"
20 #include "wasm/WasmIonCompile.h"
21 #include "wasm/WasmJS.h"
22 #include "wasm/WasmTable.h"
24 #include "vm/ArrayBufferObject-inl.h"
25 #include "vm/JSContext-inl.h"
27 using namespace js;
28 using namespace js::wasm;
30 // These are defined and pre-initialized by the harness (in tests.cpp).
31 extern JS::PersistentRootedObject gGlobal;
32 extern JSContext* gCx;
34 static bool gIsWasmSmith = false;
35 extern "C" {
36 size_t gluesmith(uint8_t* data, size_t size, uint8_t* out, size_t maxsize);
39 static int testWasmInit(int* argc, char*** argv) {
40 if (!wasm::HasSupport(gCx)) {
41 MOZ_CRASH("Wasm is not supported");
44 #define WASM_FEATURE(NAME, LOWER_NAME, COMPILE_PRED, COMPILER_PRED, FLAG_PRED, \
45 FLAG_FORCE_ON, FLAG_FUZZ_ON, PREF) \
46 JS::Prefs::setAtStartup_wasm_##PREF(FLAG_FUZZ_ON);
47 JS_FOR_WASM_FEATURES(WASM_FEATURE)
48 #undef WASM_FEATURE
50 if (!GlobalObject::getOrCreateConstructor(gCx, JSProto_WebAssembly)) {
51 MOZ_CRASH("Failed to initialize wasm engine");
54 return 0;
57 static int testWasmSmithInit(int* argc, char*** argv) {
58 gIsWasmSmith = true;
59 return testWasmInit(argc, argv);
62 static bool emptyNativeFunction(JSContext* cx, unsigned argc, Value* vp) {
63 CallArgs args = CallArgsFromVp(argc, vp);
64 args.rval().setUndefined();
65 return true;
68 static bool callExportedFunc(HandleFunction func,
69 MutableHandleValue lastReturnVal) {
70 // TODO: We can specify a thisVal here.
71 RootedValue thisVal(gCx, UndefinedValue());
72 JS::RootedValueVector args(gCx);
74 if (!lastReturnVal.isNull() && !lastReturnVal.isUndefined() &&
75 !args.append(lastReturnVal)) {
76 return false;
79 RootedValue returnVal(gCx);
80 if (!Call(gCx, thisVal, func, args, &returnVal)) {
81 gCx->clearPendingException();
82 } else {
83 lastReturnVal.set(returnVal);
86 return true;
89 template <typename T>
90 static bool assignImportKind(const Import& import, HandleObject obj,
91 HandleObject lastExportsObj,
92 JS::Handle<JS::IdVector> lastExportIds,
93 size_t* currentExportId, size_t exportsLength,
94 HandleValue defaultValue) {
95 RootedId fieldName(gCx);
96 if (!import.field.toPropertyKey(gCx, &fieldName)) {
97 return false;
99 bool assigned = false;
100 while (*currentExportId < exportsLength) {
101 RootedValue propVal(gCx);
102 if (!JS_GetPropertyById(gCx, lastExportsObj,
103 lastExportIds[*currentExportId], &propVal)) {
104 return false;
107 (*currentExportId)++;
109 if (propVal.isObject() && propVal.toObject().is<T>()) {
110 if (!JS_SetPropertyById(gCx, obj, fieldName, propVal)) {
111 return false;
114 assigned = true;
115 break;
118 if (!assigned) {
119 if (!JS_SetPropertyById(gCx, obj, fieldName, defaultValue)) {
120 return false;
123 return true;
126 static bool FuzzerBuildId(JS::BuildIdCharVector* buildId) {
127 const char buildid[] = "testWasmFuzz";
128 return buildId->append(buildid, sizeof(buildid));
131 static int testWasmFuzz(const uint8_t* buf, size_t size) {
132 auto gcGuard = mozilla::MakeScopeExit([&] {
133 JS::PrepareForFullGC(gCx);
134 JS::NonIncrementalGC(gCx, JS::GCOptions::Normal, JS::GCReason::API);
137 JS::SetProcessBuildIdOp(FuzzerBuildId);
139 const size_t MINIMUM_MODULE_SIZE = 8;
141 // The smallest valid wasm module is 8 bytes and we need 1 byte for size
142 if (size < MINIMUM_MODULE_SIZE + 1) return 0;
144 size_t currentIndex = 0;
146 // Store the last non-empty exports object and its enumerated Ids here
147 RootedObject lastExportsObj(gCx);
148 JS::Rooted<JS::IdVector> lastExportIds(gCx, JS::IdVector(gCx));
150 // Store the last return value so we can pass it in as an argument during
151 // the next call (which can be on another module as well).
152 RootedValue lastReturnVal(gCx);
154 while (size - currentIndex >= MINIMUM_MODULE_SIZE + 1) {
155 // Ensure we have no lingering exceptions from previous modules
156 gCx->clearPendingException();
158 uint16_t moduleLen;
159 if (gIsWasmSmith) {
160 // Jump over the optByte. Unlike with the regular format, for
161 // wasm-smith we are fixing this and use byte 0 as opt-byte.
162 // Eventually this will also be changed for the regular format.
163 if (!currentIndex) {
164 currentIndex++;
167 // Caller ensures the structural soundness of the input here
168 moduleLen = *((uint16_t*)&buf[currentIndex]);
169 currentIndex += 2;
170 } else {
171 moduleLen = buf[currentIndex];
172 currentIndex++;
175 if (size - currentIndex < moduleLen) {
176 moduleLen = size - currentIndex;
179 if (moduleLen < MINIMUM_MODULE_SIZE) {
180 continue;
183 if (currentIndex == 1 || (gIsWasmSmith && currentIndex == 3)) {
184 // If this is the first module we are reading, we use the first
185 // few bytes to tweak some settings. These are fixed anyway and
186 // overwritten later on.
187 uint8_t optByte;
188 if (gIsWasmSmith) {
189 optByte = (uint8_t)buf[0];
190 } else {
191 optByte = (uint8_t)buf[currentIndex];
194 // Note that IonPlatformSupport() does not take into account whether
195 // the compiler supports particular features that may have been enabled.
196 bool enableWasmBaseline = ((optByte & 0xF0) == (1 << 7));
197 bool enableWasmOptimizing =
198 IonPlatformSupport() && ((optByte & 0xF0) == (1 << 6));
199 bool enableWasmAwaitTier2 =
200 (IonPlatformSupport()) && ((optByte & 0xF) == (1 << 3));
202 if (!enableWasmBaseline && !enableWasmOptimizing) {
203 // If nothing is selected explicitly, enable an optimizing compiler to
204 // test more platform specific JIT code. However, on some platforms,
205 // e.g. ARM64 on Windows, we do not have Ion available, so we need to
206 // switch to baseline instead.
207 if (IonPlatformSupport()) {
208 enableWasmOptimizing = true;
209 } else {
210 enableWasmBaseline = true;
214 if (enableWasmAwaitTier2) {
215 // Tier 2 needs Baseline + Optimizing
216 enableWasmBaseline = true;
218 if (!enableWasmOptimizing) {
219 enableWasmOptimizing = true;
223 JS::ContextOptionsRef(gCx)
224 .setWasmBaseline(enableWasmBaseline)
225 .setWasmIon(enableWasmOptimizing)
226 .setTestWasmAwaitTier2(enableWasmAwaitTier2);
229 // Expected header for a valid WebAssembly module
230 uint32_t magic_header = 0x6d736100;
231 uint32_t magic_version = 0x1;
233 if (gIsWasmSmith) {
234 // When using wasm-smith, magic values should already be there.
235 // Checking this to make sure the data passed is sane.
236 MOZ_RELEASE_ASSERT(*(uint32_t*)(&buf[currentIndex]) == magic_header,
237 "Magic header mismatch!");
238 MOZ_RELEASE_ASSERT(*(uint32_t*)(&buf[currentIndex + 4]) == magic_version,
239 "Magic version mismatch!");
242 // We just skip over the first 8 bytes now because we fill them
243 // with `magic_header` and `magic_version` anyway.
244 currentIndex += 8;
245 moduleLen -= 8;
247 Rooted<WasmInstanceObject*> instanceObj(gCx);
249 MutableBytes bytecode = gCx->new_<ShareableBytes>();
250 if (!bytecode || !bytecode->append((uint8_t*)&magic_header, 4) ||
251 !bytecode->append((uint8_t*)&magic_version, 4) ||
252 !bytecode->append(&buf[currentIndex], moduleLen)) {
253 return 0;
256 currentIndex += moduleLen;
258 ScriptedCaller scriptedCaller;
259 FeatureOptions options;
260 SharedCompileArgs compileArgs =
261 CompileArgs::buildAndReport(gCx, std::move(scriptedCaller), options);
262 if (!compileArgs) {
263 return 0;
266 UniqueChars error;
267 UniqueCharsVector warnings;
268 SharedModule module =
269 CompileBuffer(*compileArgs, *bytecode, &error, &warnings);
270 if (!module) {
271 // We should always have a valid module if we are using wasm-smith. Check
272 // that no error is reported, signalling an OOM.
273 MOZ_RELEASE_ASSERT(!gIsWasmSmith || !error);
274 continue;
277 // At this point we have a valid module and we should try to ensure
278 // that its import requirements are met for instantiation.
279 const ImportVector& importVec = module->imports();
281 // Empty native function used to fill in function import slots if we
282 // run out of functions exported by other modules.
283 JS::RootedFunction emptyFunction(gCx);
284 emptyFunction =
285 JS_NewFunction(gCx, emptyNativeFunction, 0, 0, "emptyFunction");
287 if (!emptyFunction) {
288 return 0;
291 RootedValue emptyFunctionValue(gCx, ObjectValue(*emptyFunction));
292 RootedValue nullValue(gCx, NullValue());
294 RootedObject importObj(gCx, JS_NewPlainObject(gCx));
296 if (!importObj) {
297 return 0;
300 size_t exportsLength = lastExportIds.length();
301 size_t currentFunctionExportId = 0;
302 size_t currentTableExportId = 0;
303 size_t currentMemoryExportId = 0;
304 size_t currentGlobalExportId = 0;
305 size_t currentTagExportId = 0;
307 for (const Import& import : importVec) {
308 RootedId moduleName(gCx);
309 if (!import.module.toPropertyKey(gCx, &moduleName)) {
310 return false;
312 RootedId fieldName(gCx);
313 if (!import.field.toPropertyKey(gCx, &fieldName)) {
314 return false;
317 // First try to get the namespace object, create one if this is the
318 // first time.
319 RootedValue v(gCx);
320 if (!JS_GetPropertyById(gCx, importObj, moduleName, &v) ||
321 !v.isObject()) {
322 // Insert empty object at importObj[moduleName]
323 RootedObject plainObj(gCx, JS_NewPlainObject(gCx));
325 if (!plainObj) {
326 return 0;
329 RootedValue plainVal(gCx, ObjectValue(*plainObj));
330 if (!JS_SetPropertyById(gCx, importObj, moduleName, plainVal)) {
331 return 0;
334 // Get the object we just inserted, store in v, ensure it is an
335 // object (no proxies or other magic at work).
336 if (!JS_GetPropertyById(gCx, importObj, moduleName, &v) ||
337 !v.isObject()) {
338 return 0;
342 RootedObject obj(gCx, &v.toObject());
343 bool found = false;
344 if (JS_HasPropertyById(gCx, obj, fieldName, &found) && !found) {
345 // Insert i-th export object that fits the type requirement
346 // at `v[fieldName]`.
348 switch (import.kind) {
349 case DefinitionKind::Function:
350 if (!assignImportKind<JSFunction>(
351 import, obj, lastExportsObj, lastExportIds,
352 &currentFunctionExportId, exportsLength,
353 emptyFunctionValue)) {
354 return 0;
356 break;
358 case DefinitionKind::Table:
359 // TODO: Pass a dummy defaultValue
360 if (!assignImportKind<WasmTableObject>(
361 import, obj, lastExportsObj, lastExportIds,
362 &currentTableExportId, exportsLength, nullValue)) {
363 return 0;
365 break;
367 case DefinitionKind::Memory:
368 // TODO: Pass a dummy defaultValue
369 if (!assignImportKind<WasmMemoryObject>(
370 import, obj, lastExportsObj, lastExportIds,
371 &currentMemoryExportId, exportsLength, nullValue)) {
372 return 0;
374 break;
376 case DefinitionKind::Global:
377 // TODO: Pass a dummy defaultValue
378 if (!assignImportKind<WasmGlobalObject>(
379 import, obj, lastExportsObj, lastExportIds,
380 &currentGlobalExportId, exportsLength, nullValue)) {
381 return 0;
383 break;
385 case DefinitionKind::Tag:
386 // TODO: Pass a dummy defaultValue
387 if (!assignImportKind<WasmTagObject>(
388 import, obj, lastExportsObj, lastExportIds,
389 &currentTagExportId, exportsLength, nullValue)) {
390 return 0;
392 break;
397 Rooted<ImportValues> imports(gCx);
398 if (!GetImports(gCx, *module, importObj, imports.address())) {
399 continue;
402 if (!module->instantiate(gCx, imports.get(), nullptr, &instanceObj)) {
403 continue;
406 // At this module we have a valid WebAssembly module instance.
408 RootedObject exportsObj(gCx, &instanceObj->exportsObj());
409 JS::Rooted<JS::IdVector> exportIds(gCx, JS::IdVector(gCx));
410 if (!JS_Enumerate(gCx, exportsObj, &exportIds)) {
411 continue;
414 if (!exportIds.length()) {
415 continue;
418 // Store the last exports for re-use later
419 lastExportsObj = exportsObj;
420 lastExportIds.get() = std::move(exportIds.get());
422 for (size_t i = 0; i < lastExportIds.length(); i++) {
423 RootedValue propVal(gCx);
424 if (!JS_GetPropertyById(gCx, exportsObj, lastExportIds[i], &propVal)) {
425 return 0;
428 if (propVal.isObject()) {
429 RootedObject propObj(gCx, &propVal.toObject());
431 if (propObj->is<JSFunction>()) {
432 RootedFunction func(gCx, &propObj->as<JSFunction>());
434 if (!callExportedFunc(func, &lastReturnVal)) {
435 return 0;
439 if (propObj->is<WasmTableObject>()) {
440 Rooted<WasmTableObject*> tableObj(gCx,
441 &propObj->as<WasmTableObject>());
442 size_t tableLen = tableObj->table().length();
444 RootedValue tableGetVal(gCx);
445 if (!JS_GetProperty(gCx, tableObj, "get", &tableGetVal)) {
446 return 0;
448 RootedFunction tableGet(gCx,
449 &tableGetVal.toObject().as<JSFunction>());
451 for (size_t i = 0; i < tableLen; i++) {
452 JS::RootedValueVector tableGetArgs(gCx);
453 if (!tableGetArgs.append(NumberValue(uint32_t(i)))) {
454 return 0;
457 RootedValue readFuncValue(gCx);
458 if (!Call(gCx, tableObj, tableGet, tableGetArgs, &readFuncValue)) {
459 return 0;
462 if (readFuncValue.isNull()) {
463 continue;
466 RootedFunction callee(gCx,
467 &readFuncValue.toObject().as<JSFunction>());
469 if (!callExportedFunc(callee, &lastReturnVal)) {
470 return 0;
475 if (propObj->is<WasmMemoryObject>()) {
476 Rooted<WasmMemoryObject*> memory(gCx,
477 &propObj->as<WasmMemoryObject>());
478 size_t byteLen = memory->volatileMemoryLength();
479 if (byteLen) {
480 // Read the bounds of the buffer to ensure it is valid.
481 // AddressSanitizer would detect any out-of-bounds here.
482 uint8_t* rawMemory = memory->buffer().dataPointerEither().unwrap();
483 volatile uint8_t rawMemByte = 0;
484 rawMemByte += rawMemory[0];
485 rawMemByte += rawMemory[byteLen - 1];
486 (void)rawMemByte;
490 if (propObj->is<WasmGlobalObject>()) {
491 Rooted<WasmGlobalObject*> global(gCx,
492 &propObj->as<WasmGlobalObject>());
493 if (global->type() != ValType::I64) {
494 global->val().get().toJSValue(gCx, &lastReturnVal);
501 return 0;
504 static int testWasmSmithFuzz(const uint8_t* buf, size_t size) {
505 // Define maximum sizes for the input to wasm-smith as well
506 // as the resulting modules. The input to output size factor
507 // of wasm-smith is somewhat variable but a factor of 4 seems
508 // to roughly work out. The logic below also assumes that these
509 // are powers of 2.
510 const size_t maxInputSize = 1024;
511 const size_t maxModuleSize = 4096;
513 size_t maxModules = size / maxInputSize + 1;
515 // We need 1 leading byte for options and 2 bytes for size per module
516 uint8_t* out =
517 new uint8_t[1 + maxModules * (maxModuleSize + sizeof(uint16_t))];
519 auto deleteGuard = mozilla::MakeScopeExit([&] { delete[] out; });
521 // Copy the opt-byte.
522 out[0] = buf[0];
524 size_t outIndex = 1;
525 size_t currentIndex = 1;
527 while (currentIndex < size) {
528 size_t remaining = size - currentIndex;
530 // We need to have at least a size and some byte to read.
531 if (remaining <= sizeof(uint16_t)) {
532 break;
535 // Determine size of the next input, limited to `maxInputSize`.
536 uint16_t inSize =
537 (*((uint16_t*)&buf[currentIndex]) & (maxInputSize - 1)) + 1;
538 remaining -= sizeof(uint16_t);
539 currentIndex += sizeof(uint16_t);
541 // Cap to remaining bytes.
542 inSize = remaining >= inSize ? inSize : remaining;
544 size_t outSize =
545 gluesmith((uint8_t*)&buf[currentIndex], inSize,
546 out + outIndex + sizeof(uint16_t), maxModuleSize);
548 if (!outSize) {
549 break;
552 currentIndex += inSize;
554 // Write the size of the resulting module to our output buffer.
555 *(uint16_t*)(&out[outIndex]) = (uint16_t)outSize;
556 outIndex += sizeof(uint16_t) + outSize;
559 // If we lack at least one module, don't do anything.
560 if (outIndex == 1) {
561 return 0;
564 return testWasmFuzz(out, outIndex);
567 MOZ_FUZZING_INTERFACE_RAW(testWasmInit, testWasmFuzz, Wasm);
568 MOZ_FUZZING_INTERFACE_RAW(testWasmSmithInit, testWasmSmithFuzz, WasmSmith);