diff options
Diffstat (limited to 'js/src/fuzz-tests')
-rw-r--r-- | js/src/fuzz-tests/README | 31 | ||||
-rw-r--r-- | js/src/fuzz-tests/differential-parsing.js | 148 | ||||
-rw-r--r-- | js/src/fuzz-tests/gluesmith/Cargo.toml | 10 | ||||
-rw-r--r-- | js/src/fuzz-tests/gluesmith/moz.build | 15 | ||||
-rw-r--r-- | js/src/fuzz-tests/gluesmith/src/lib.rs | 75 | ||||
-rw-r--r-- | js/src/fuzz-tests/moz.build | 45 | ||||
-rw-r--r-- | js/src/fuzz-tests/parsing-evaluate.js | 83 | ||||
-rw-r--r-- | js/src/fuzz-tests/testExample.cpp | 61 | ||||
-rw-r--r-- | js/src/fuzz-tests/testRegExp.cpp | 112 | ||||
-rw-r--r-- | js/src/fuzz-tests/testStructuredCloneReader.cpp | 84 | ||||
-rw-r--r-- | js/src/fuzz-tests/testWasm.cpp | 557 | ||||
-rw-r--r-- | js/src/fuzz-tests/tests.cpp | 121 | ||||
-rw-r--r-- | js/src/fuzz-tests/tests.h | 19 | ||||
-rw-r--r-- | js/src/fuzz-tests/util/sanitize.js | 100 |
14 files changed, 1461 insertions, 0 deletions
diff --git a/js/src/fuzz-tests/README b/js/src/fuzz-tests/README new file mode 100644 index 0000000000..2c98c1ffa2 --- /dev/null +++ b/js/src/fuzz-tests/README @@ -0,0 +1,31 @@ +# JS Fuzzing Interface + +This directory contains fuzzing targets that implement the unified fuzzing +interface to be used with libFuzzer or AFL. + +## Building the fuzzing targets + +To include this directory in your JS build, you need to build with Clang +and the --enable-fuzzing flag enabled. The build system will automatically +detect if you are building with afl-clang-fast for AFL or regular Clang +for libFuzzer. + +## Running a fuzzing target + +To run a particular target with libFuzzer, use: + + cd $OBJDIR/dist/bin + FUZZER=YourTargetName ./fuzz-tests + +To run with AFL, use something like + + cd $OBJDIR/dist/bin + FUZZER=YourTargetName MOZ_FUZZ_TESTFILE=input \ + afl-fuzz <regular AFL options> -f input ./fuzz-tests + + +## Writing a fuzzing target + +1. Check testExample.cpp for a target skeleton with comments. + +2. Add your own .cpp file to UNIFIED_SOURCES in moz.build diff --git a/js/src/fuzz-tests/differential-parsing.js b/js/src/fuzz-tests/differential-parsing.js new file mode 100644 index 0000000000..1a61175721 --- /dev/null +++ b/js/src/fuzz-tests/differential-parsing.js @@ -0,0 +1,148 @@ +/* -*- Mode: javascript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This file is used to detect and find cases where the Visage's parser is +// accepting more inputs than SpiderMonkey's parser. +// +// 1. Find new cases: +// +// To find new cases, we have to build with libFuzzer. The JS Shell can easily +// be built with libFuzzer by adding --enable-fuzzing to the configure script +// command line. +// +// Create a directory, and copy all test cases from the JS shell to this +// directory: +// +// $ mkdir fuzzer-input +// $ cd fuzzer-input +// $ find ../ -name \*.js -print0 | xargs -I '{}' -0 -n1 cp '{}' $(pwd) +// +// Once the JS Shell is built, set the FUZZER environment variable to this +// script location. +// +// $ FUZZER="./fuzz-tests/differential-parsing.js" build.dir/dist/bin/js -- \ +// -use_value_profile=1 -print_pcs=1 -timeout=5 -max_len=32 -only_ascii=1 \ +// ./fuzzer-input +// +// 2. Test a crashing test case: +// +// Once a new crashing test case is found, this script can be used to +// reproduce the crashing conditions. +// +// To do so, you need a JS Shell and to load this script and use the testFile +// function with the location of the crashing file. +// +// $ build.dir/dist/bin/js +// js> load("./fuzz-tests/differential-parsing.js"); +// js> testFile("./crash-42"); +// Parse Script C++: fail +// Parse Module C++: fail +// Parse Script Rust: succeed +// Parse Module Rust: fail +// Hit MOZ_CRASH(Rust accept more than C++) +// + +/* global crash, os, parse, timeout */ + +// This global will hold the current fuzzing buffer for each iteration. +var fuzzBuf; + +function timed(sec, f) { + // If the function `f` takes more than 3 seconds, then the evaluation ends + // prematurely and returns in libFuzzer handler without considering this + // test case as interesting. + timeout(sec, function() { + return false; + }); + f(); + + // Remove the timeout handler, to not kill future executions. + timeout(-1); +} + +var parseScriptCpp = { module: false, smoosh: false }; +var parseScriptRust = { module: false, smoosh: true }; +var parseModuleRust = { module: true, smoosh: true }; +var parseModuleCpp = { module: true, smoosh: false }; +function test(code, verbose = false) { + var isScriptCpp = false, + isModuleCpp = false, + isScriptRust = false, + isModuleRust = false; + try { + parse(code, parseScriptCpp); + isScriptCpp = true; + if (verbose) { + console.log("Parse Script C++: succeed"); + } + } catch (exc) { + if (verbose) { + console.log("Parse Script C++: fail"); + } + } + try { + parse(code, parseModuleCpp); + isModuleCpp = true; + if (verbose) { + console.log("Parse Module C++: succeed"); + } + } catch (exc) { + if (verbose) { + console.log("Parse Module C++: fail"); + } + } + try { + parse(code, parseScriptRust); + isScriptRust = true; + if (verbose) { + console.log("Parse Script Rust: succeed"); + } + } catch (exc) { + if (verbose) { + console.log("Parse Script Rust: fail"); + } + } + try { + parse(code, parseModuleRust); + isModuleRust = true; + if (verbose) { + console.log("Parse Module Rust: succeed"); + } + } catch (exc) { + if (verbose) { + console.log("Parse Module Rust: fail"); + } + } + if ((isScriptRust && !isScriptCpp) || (isModuleRust && !isModuleCpp)) { + crash("Rust accept more than C++"); + } +} + +function JSFuzzIterate() { + // This function is called per iteration. You must ensure that: + // + // 1) Each of your actions/decisions is only based on fuzzBuf, + // in particular not on Math.random(), Date/Time or other + // external inputs. + // + // 2) Your actions should be deterministic. The same fuzzBuf + // should always lead to the same set of actions/decisions. + // + // 3) You can modify the global where needed, but ensure that + // each iteration is isolated from one another by cleaning + // any modifications to the global after each iteration. + // In particular, iterations must not depend on or influence + // each other in any way (see also 1)). + // + // 4) You must catch all exceptions. + let code = String.fromCharCode(...fuzzBuf); + timed(3, _ => test(code)); + return 0; +} + +function testFile(file) { + let content = os.file.readFile(file); + test(content, true); +} diff --git a/js/src/fuzz-tests/gluesmith/Cargo.toml b/js/src/fuzz-tests/gluesmith/Cargo.toml new file mode 100644 index 0000000000..42c8cec4f2 --- /dev/null +++ b/js/src/fuzz-tests/gluesmith/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "gluesmith" +version = "0.1.0" +authors = ["Christian Holler"] +license = "MPL-2.0" + +[dependencies] +wasm-smith = "0.12.5" +arbitrary = { version = "1.0.0", features = ["derive"] } +libc = "0.2" diff --git a/js/src/fuzz-tests/gluesmith/moz.build b/js/src/fuzz-tests/gluesmith/moz.build new file mode 100644 index 0000000000..d75c4c18ba --- /dev/null +++ b/js/src/fuzz-tests/gluesmith/moz.build @@ -0,0 +1,15 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +FINAL_LIBRARY = "js" + +# Includes should be relative to parent path +LOCAL_INCLUDES += ["!../..", "../.."] + +include("../../js-config.mozbuild") +include("../../js-cxxflags.mozbuild") + +DIRS += ["../../rust"] diff --git a/js/src/fuzz-tests/gluesmith/src/lib.rs b/js/src/fuzz-tests/gluesmith/src/lib.rs new file mode 100644 index 0000000000..2efb678a03 --- /dev/null +++ b/js/src/fuzz-tests/gluesmith/src/lib.rs @@ -0,0 +1,75 @@ +/* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +extern crate arbitrary; +extern crate wasm_smith; + +use arbitrary::{Arbitrary, Unstructured}; +use wasm_smith::{Config, Module}; + +use std::ptr; + +// A custom configuration to enable all experimental features that we have +// some support for. +#[derive(Arbitrary, Debug)] +struct SpiderMonkeyConfig; + +impl Config for SpiderMonkeyConfig { + fn bulk_memory_enabled(&self) -> bool { + true + } + fn reference_types_enabled(&self) -> bool { + true + } + fn exceptions_enabled(&self) -> bool { + true + } + fn memory64_enabled(&self) -> bool { + true + } + fn simd_enabled(&self) -> bool { + true + } +} + +#[no_mangle] +pub unsafe extern "C" fn gluesmith( + data: *mut u8, + len: usize, + out: *mut u8, + maxlen: usize, +) -> usize { + let buf: &[u8] = std::slice::from_raw_parts(data, len); + + let mut u = Unstructured::new(buf); + + let module = match Module::new(SpiderMonkeyConfig {}, &mut u) { + Ok(m) => m, + Err(_e) => return 0, + }; + + let wasm_bytes = module.to_bytes(); + + let src_len = wasm_bytes.len(); + + if src_len > maxlen { + return 0; + } + + let src_ptr = wasm_bytes.as_ptr(); + ptr::copy_nonoverlapping(src_ptr, out, src_len); + + return src_len; +} diff --git a/js/src/fuzz-tests/moz.build b/js/src/fuzz-tests/moz.build new file mode 100644 index 0000000000..ccec998f0b --- /dev/null +++ b/js/src/fuzz-tests/moz.build @@ -0,0 +1,45 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +GeckoProgram("fuzz-tests", linkage=None) + +include("../js-cxxflags.mozbuild") +include("../js-standalone.mozbuild") + +UNIFIED_SOURCES += [ + "testExample.cpp", + "testRegExp.cpp", + "tests.cpp", + "testStructuredCloneReader.cpp", + "testWasm.cpp", +] + +DEFINES["EXPORT_JS_API"] = True + +LOCAL_INCLUDES += [ + "!..", + "..", +] + +if CONFIG["FUZZING"]: + USE_LIBS += [ + "static:fuzzer-registry", + ] + + # Add libFuzzer configuration directives + include("/tools/fuzzing/libfuzzer-config.mozbuild") + +if CONFIG["FUZZING_INTERFACES"]: + USE_LIBS += [ + "static:fuzzer", + ] + +USE_LIBS += [ + "static:js", + "static:jsrust", +] + +DEFINES["topsrcdir"] = "%s/js/src" % TOPSRCDIR diff --git a/js/src/fuzz-tests/parsing-evaluate.js b/js/src/fuzz-tests/parsing-evaluate.js new file mode 100644 index 0000000000..1714e051a9 --- /dev/null +++ b/js/src/fuzz-tests/parsing-evaluate.js @@ -0,0 +1,83 @@ +/* -*- Mode: javascript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This fuzzing target aims to stress the SpiderMonkey parser. However, for +// this purpose, it does *not* use `parse()` because some past bugs in the +// parser could only be triggered in the runtime later. Instead, we use +// the `evaluate` function which parses and runs the code. This brings in +// other problems like timeouts and permanent side-effects. We try to minimize +// the amount of permanent side-effects from running the code by running it +// in a fresh global for each iteration. We also use a special function +// called `sanitizeGlobal` to remove any harmful shell functions from the +// global prior to running. Many of these shell functions would otherwise +// have permanent side-effects of some sort or be disruptive to testing like +// increasing the amount of timeouts or leak memory. Finally, the target also +// tries to catch timeouts locally and signal back any timeouts by returning 1 +// from the iteration function. + +// This global will hold the current fuzzing buffer for each iteration. +var fuzzBuf; + +loadRelativeToScript("util/sanitize.js"); + +deterministicgc(true); + +// Set a default value for timeouts to 1 second, but allow this to +// be set on the command line as well using -e fuzzTimeout=VAL. +if (typeof fuzzTimeout === "undefined") { + fuzzTimeout = 1; +} + +function JSFuzzIterate() { + try { + let code = String.fromCharCode(...fuzzBuf); + let result = null; + + // Create a new global and sanitize it such that its potentially permanent + // side-effects are reduced to a minimum. + let global = newGlobal(); + sanitizeGlobal(global); + + // Work around memory leaks when the hook is not set + evaluate(` + setModuleResolveHook(function(module, specifier) { + throw "Module '" + specifier + "' not found"; + }); + setModuleResolveHook = function() {}; + `, { global: global, catchTermination: true }); + + // Start a timer and set a timeout in addition + let lfStart = monotonicNow(); + timeout(fuzzTimeout, function() { return false; }); + + try { + result = evaluate(code, { global: global, catchTermination: true }); + } catch(exc) { + print(exc); + } + + timeout(-1); + let lfStop = monotonicNow(); + + // Reset some things that could have been altered by the code we ran + gczeal(0); + schedulegc(0); + setGCCallback({ action: "majorGC" }); + clearSavedFrames(); + + // If we either ended terminating the script, or we took longer than + // the timeout set (but timeout didn't kick in), then we return 1 to + // signal libFuzzer that the sample just be abandoned. + if (result === "terminated" || (lfStop - lfStart > (fuzzTimeout * 1000 + 200))) { + return 1; + } + + return 0; + } catch(exc) { + print("Caught toplevel exception: " + exc); + } + + return 1; +} diff --git a/js/src/fuzz-tests/testExample.cpp b/js/src/fuzz-tests/testExample.cpp new file mode 100644 index 0000000000..91ee827d3d --- /dev/null +++ b/js/src/fuzz-tests/testExample.cpp @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ScopeExit.h" + +#include "jsapi.h" + +#include "fuzz-tests/tests.h" +#include "vm/Interpreter.h" + +#include "vm/JSContext-inl.h" + +using namespace JS; +using namespace js; + +extern JS::PersistentRootedObject gGlobal; +extern JSContext* gCx; + +static int testExampleInit(int* argc, char*** argv) { + /* This function is called once at startup. You can use it to e.g. read + environment variables to initialize additional options you might need. + Note that `gCx` and `gGlobal` are pre-initialized by the harness. + */ + return 0; +} + +static int testExampleFuzz(const uint8_t* buf, size_t size) { + /* If your code directly or indirectly allocates GC memory, then it makes + sense to attempt and collect that after every iteration. This should detect + GC issues as soon as possible (right after your iteration), rather than + later when your code happens to trigger GC coincidentially. You can of + course disable this code + if it is not required in your use case, which will speed up fuzzing. */ + auto gcGuard = mozilla::MakeScopeExit([&] { + JS::PrepareForFullGC(gCx); + JS::NonIncrementalGC(gCx, JS::GCOptions::Normal, JS::GCReason::API); + }); + + /* Add code here that processes the given buffer. + While doing so, you need to follow these rules: + + 1. Do not modify or free the buffer. Make a copy if necessary. + 2. This function must always return 0. + 3. Do not crash or abort unless the condition constitutes a bug. + 4. You may use the `gGlobal` and `gCx` variables, they are pre-initialized. + 5. Try to keep the effects of this function contained, such that future + calls to this function are not affected. Otherwise you end up with + non-reproducible testcases and coverage measurements will be incorrect. + */ + + return 0; +} + +MOZ_FUZZING_INTERFACE_RAW(testExampleInit, /* init function */ + testExampleFuzz, /* fuzzing function */ + Example /* module name */ +); diff --git a/js/src/fuzz-tests/testRegExp.cpp b/js/src/fuzz-tests/testRegExp.cpp new file mode 100644 index 0000000000..5c71eda873 --- /dev/null +++ b/js/src/fuzz-tests/testRegExp.cpp @@ -0,0 +1,112 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ScopeExit.h" + +#include "jsapi.h" + +#include "fuzz-tests/tests.h" +#include "irregexp/RegExpAPI.h" +#include "vm/Interpreter.h" +#include "vm/JSAtom.h" +#include "vm/MatchPairs.h" + +#include "vm/JSContext-inl.h" + +using namespace JS; +using namespace js; + +extern JS::PersistentRootedObject gGlobal; +extern JSContext* gCx; + +static int testRegExpInit(int* argc, char*** argv) { return 0; } + +static int testRegExpFuzz(const uint8_t* buf, size_t size) { + auto gcGuard = mozilla::MakeScopeExit([&] { + JS::PrepareForFullGC(gCx); + JS::NonIncrementalGC(gCx, JS::GCOptions::Normal, JS::GCReason::API); + }); + + const uint32_t HEADER_LEN = 2; + if (size <= HEADER_LEN) { + return 0; + } + + uint8_t rawFlags = buf[0]; + int32_t patternLength = buf[1]; + + const uint32_t startIndex = 0; + + RegExpFlags flags(rawFlags & RegExpFlag::AllFlags); + + int32_t inputLength = size - HEADER_LEN - patternLength; + + const char* patternChars = reinterpret_cast<const char*>(buf + HEADER_LEN); + + const char* inputChars; + if (inputLength < 0) { + patternLength = size - HEADER_LEN; + + bool useUnicodeInput = (buf[1] & 1) == 0; + inputChars = useUnicodeInput ? "Привет мир" : "Hello\nworld!"; + inputLength = strlen(inputChars); + } else { + inputChars = patternChars + patternLength; + } + + Rooted<JSAtom*> pattern(gCx, + AtomizeUTF8Chars(gCx, patternChars, patternLength)); + if (!pattern) { + ReportOutOfMemory(gCx); + return 0; + } + Rooted<JSAtom*> input(gCx, AtomizeUTF8Chars(gCx, inputChars, inputLength)); + if (!input) { + ReportOutOfMemory(gCx); + return 0; + } + + VectorMatchPairs interpretedMatches; + VectorMatchPairs compiledMatches; + + RegExpRunStatus iStatus = irregexp::ExecuteForFuzzing( + gCx, pattern, input, flags, startIndex, &interpretedMatches, + RegExpShared::CodeKind::Bytecode); + if (iStatus == RegExpRunStatus_Error) { + if (gCx->isThrowingOverRecursed()) { + return 0; + } + gCx->clearPendingException(); + } + RegExpRunStatus cStatus = irregexp::ExecuteForFuzzing( + gCx, pattern, input, flags, startIndex, &compiledMatches, + RegExpShared::CodeKind::Jitcode); + if (cStatus == RegExpRunStatus_Error) { + if (gCx->isThrowingOverRecursed()) { + return 0; + } + gCx->clearPendingException(); + } + + // Use release asserts to enable fuzzing on non-debug builds. + MOZ_RELEASE_ASSERT(iStatus == cStatus); + if (iStatus == RegExpRunStatus_Success) { + MOZ_RELEASE_ASSERT(interpretedMatches.pairCount() == + compiledMatches.pairCount()); + for (uint32_t i = 0; i < interpretedMatches.pairCount(); i++) { + MOZ_RELEASE_ASSERT( + interpretedMatches[i].start == compiledMatches[i].start && + interpretedMatches[i].limit == compiledMatches[i].limit); + } + } + return 0; +} + +MOZ_FUZZING_INTERFACE_RAW(testRegExpInit, /* init function */ + testRegExpFuzz, /* fuzzing function */ + RegExp /* module name */ +); diff --git a/js/src/fuzz-tests/testStructuredCloneReader.cpp b/js/src/fuzz-tests/testStructuredCloneReader.cpp new file mode 100644 index 0000000000..6b70d8bc52 --- /dev/null +++ b/js/src/fuzz-tests/testStructuredCloneReader.cpp @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ScopeExit.h" + +#include "jsapi.h" + +#include "fuzz-tests/tests.h" +#include "js/StructuredClone.h" +#include "vm/Interpreter.h" + +#include "vm/JSContext-inl.h" + +using namespace js; + +// These are defined and pre-initialized by the harness (in tests.cpp). +extern JS::PersistentRootedObject gGlobal; +extern JSContext* gCx; + +static int testStructuredCloneReaderInit(int* argc, char*** argv) { return 0; } + +static int testStructuredCloneReaderFuzz(const uint8_t* buf, size_t size) { + auto gcGuard = mozilla::MakeScopeExit([&] { + JS::PrepareForFullGC(gCx); + JS::NonIncrementalGC(gCx, JS::GCOptions::Normal, JS::GCReason::API); + }); + + if (!size) return 0; + + // Make sure to pad the buffer to a multiple of kSegmentAlignment + const size_t kSegmentAlignment = 8; + size_t buf_size = RoundUp(size, kSegmentAlignment); + + JS::StructuredCloneScope scope = JS::StructuredCloneScope::DifferentProcess; + + auto clonebuf = MakeUnique<JSStructuredCloneData>(scope); + if (!clonebuf || !clonebuf->Init(buf_size)) { + ReportOutOfMemory(gCx); + return 0; + } + + // Copy buffer then pad with zeroes. + if (!clonebuf->AppendBytes((const char*)buf, size)) { + ReportOutOfMemory(gCx); + return 0; + } + char padding[kSegmentAlignment] = {0}; + if (!clonebuf->AppendBytes(padding, buf_size - size)) { + ReportOutOfMemory(gCx); + return 0; + } + + JS::CloneDataPolicy policy; + RootedValue deserialized(gCx); + if (!JS_ReadStructuredClone(gCx, *clonebuf, JS_STRUCTURED_CLONE_VERSION, + scope, &deserialized, policy, nullptr, nullptr)) { + return 0; + } + + /* If we succeeded in deserializing, we should try to reserialize the data. + This has two main advantages: + + 1) It tests parts of the serializer as well. + 2) The deserialized data is actually used, making it more likely to detect + further memory-related problems. + + Tests show that this also doesn't cause a serious performance penalty. + */ + mozilla::Maybe<JSAutoStructuredCloneBuffer> clonebufOut; + + clonebufOut.emplace(scope, nullptr, nullptr); + if (!clonebufOut->write(gCx, deserialized, UndefinedHandleValue, policy)) { + return 0; + } + + return 0; +} + +MOZ_FUZZING_INTERFACE_RAW(testStructuredCloneReaderInit, + testStructuredCloneReaderFuzz, StructuredCloneReader); diff --git a/js/src/fuzz-tests/testWasm.cpp b/js/src/fuzz-tests/testWasm.cpp new file mode 100644 index 0000000000..25c3ea09e3 --- /dev/null +++ b/js/src/fuzz-tests/testWasm.cpp @@ -0,0 +1,557 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ScopeExit.h" + +#include "jsapi.h" +#include "jspubtd.h" + +#include "fuzz-tests/tests.h" +#include "js/CallAndConstruct.h" +#include "js/PropertyAndElement.h" // JS_Enumerate, JS_GetProperty, JS_GetPropertyById, JS_HasProperty, JS_SetProperty +#include "vm/GlobalObject.h" +#include "vm/Interpreter.h" +#include "vm/TypedArrayObject.h" + +#include "wasm/WasmCompile.h" +#include "wasm/WasmIonCompile.h" +#include "wasm/WasmJS.h" +#include "wasm/WasmTable.h" + +#include "vm/ArrayBufferObject-inl.h" +#include "vm/JSContext-inl.h" + +using namespace js; +using namespace js::wasm; + +// These are defined and pre-initialized by the harness (in tests.cpp). +extern JS::PersistentRootedObject gGlobal; +extern JSContext* gCx; + +static bool gIsWasmSmith = false; +extern "C" { +size_t gluesmith(uint8_t* data, size_t size, uint8_t* out, size_t maxsize); +} + +static int testWasmInit(int* argc, char*** argv) { + if (!wasm::HasSupport(gCx) || + !GlobalObject::getOrCreateConstructor(gCx, JSProto_WebAssembly)) { + MOZ_CRASH("Failed to initialize wasm support"); + } + + return 0; +} + +static int testWasmSmithInit(int* argc, char*** argv) { + gIsWasmSmith = true; + return testWasmInit(argc, argv); +} + +static bool emptyNativeFunction(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().setUndefined(); + return true; +} + +static bool callExportedFunc(HandleFunction func, + MutableHandleValue lastReturnVal) { + // TODO: We can specify a thisVal here. + RootedValue thisVal(gCx, UndefinedValue()); + JS::RootedValueVector args(gCx); + + if (!lastReturnVal.isNull() && !lastReturnVal.isUndefined() && + !args.append(lastReturnVal)) { + return false; + } + + RootedValue returnVal(gCx); + if (!Call(gCx, thisVal, func, args, &returnVal)) { + gCx->clearPendingException(); + } else { + lastReturnVal.set(returnVal); + } + + return true; +} + +template <typename T> +static bool assignImportKind(const Import& import, HandleObject obj, + HandleObject lastExportsObj, + JS::Handle<JS::IdVector> lastExportIds, + size_t* currentExportId, size_t exportsLength, + HandleValue defaultValue) { + RootedId fieldName(gCx); + if (!import.field.toPropertyKey(gCx, &fieldName)) { + return false; + } + bool assigned = false; + while (*currentExportId < exportsLength) { + RootedValue propVal(gCx); + if (!JS_GetPropertyById(gCx, lastExportsObj, + lastExportIds[*currentExportId], &propVal)) { + return false; + } + + (*currentExportId)++; + + if (propVal.isObject() && propVal.toObject().is<T>()) { + if (!JS_SetPropertyById(gCx, obj, fieldName, propVal)) { + return false; + } + + assigned = true; + break; + } + } + if (!assigned) { + if (!JS_SetPropertyById(gCx, obj, fieldName, defaultValue)) { + return false; + } + } + return true; +} + +static bool FuzzerBuildId(JS::BuildIdCharVector* buildId) { + const char buildid[] = "testWasmFuzz"; + return buildId->append(buildid, sizeof(buildid)); +} + +static int testWasmFuzz(const uint8_t* buf, size_t size) { + auto gcGuard = mozilla::MakeScopeExit([&] { + JS::PrepareForFullGC(gCx); + JS::NonIncrementalGC(gCx, JS::GCOptions::Normal, JS::GCReason::API); + }); + + JS::SetProcessBuildIdOp(FuzzerBuildId); + + const size_t MINIMUM_MODULE_SIZE = 8; + + // The smallest valid wasm module is 8 bytes and we need 1 byte for size + if (size < MINIMUM_MODULE_SIZE + 1) return 0; + + size_t currentIndex = 0; + + // Store the last non-empty exports object and its enumerated Ids here + RootedObject lastExportsObj(gCx); + JS::Rooted<JS::IdVector> lastExportIds(gCx, JS::IdVector(gCx)); + + // Store the last return value so we can pass it in as an argument during + // the next call (which can be on another module as well). + RootedValue lastReturnVal(gCx); + + while (size - currentIndex >= MINIMUM_MODULE_SIZE + 1) { + // Ensure we have no lingering exceptions from previous modules + gCx->clearPendingException(); + + uint16_t moduleLen; + if (gIsWasmSmith) { + // Jump over the optByte. Unlike with the regular format, for + // wasm-smith we are fixing this and use byte 0 as opt-byte. + // Eventually this will also be changed for the regular format. + if (!currentIndex) { + currentIndex++; + } + + // Caller ensures the structural soundness of the input here + moduleLen = *((uint16_t*)&buf[currentIndex]); + currentIndex += 2; + } else { + moduleLen = buf[currentIndex]; + currentIndex++; + } + + if (size - currentIndex < moduleLen) { + moduleLen = size - currentIndex; + } + + if (moduleLen < MINIMUM_MODULE_SIZE) { + continue; + } + + if (currentIndex == 1 || (gIsWasmSmith && currentIndex == 3)) { + // If this is the first module we are reading, we use the first + // few bytes to tweak some settings. These are fixed anyway and + // overwritten later on. + uint8_t optByte; + if (gIsWasmSmith) { + optByte = (uint8_t)buf[0]; + } else { + optByte = (uint8_t)buf[currentIndex]; + } + + // Note that IonPlatformSupport() does not take into account whether + // the compiler supports particular features that may have been enabled. + bool enableWasmBaseline = ((optByte & 0xF0) == (1 << 7)); + bool enableWasmOptimizing = + IonPlatformSupport() && ((optByte & 0xF0) == (1 << 6)); + bool enableWasmAwaitTier2 = + (IonPlatformSupport()) && ((optByte & 0xF) == (1 << 3)); + + if (!enableWasmBaseline && !enableWasmOptimizing) { + // If nothing is selected explicitly, enable an optimizing compiler to + // test more platform specific JIT code. However, on some platforms, + // e.g. ARM64 on Windows, we do not have Ion available, so we need to + // switch to baseline instead. + if (IonPlatformSupport()) { + enableWasmOptimizing = true; + } else { + enableWasmBaseline = true; + } + } + + if (enableWasmAwaitTier2) { + // Tier 2 needs Baseline + Optimizing + enableWasmBaseline = true; + + if (!enableWasmOptimizing) { + enableWasmOptimizing = true; + } + } + + JS::ContextOptionsRef(gCx) + .setWasmBaseline(enableWasmBaseline) + .setWasmIon(enableWasmOptimizing) + .setTestWasmAwaitTier2(enableWasmAwaitTier2); + } + + // Expected header for a valid WebAssembly module + uint32_t magic_header = 0x6d736100; + uint32_t magic_version = 0x1; + + if (gIsWasmSmith) { + // When using wasm-smith, magic values should already be there. + // Checking this to make sure the data passed is sane. + MOZ_RELEASE_ASSERT(*(uint32_t*)(&buf[currentIndex]) == magic_header, + "Magic header mismatch!"); + MOZ_RELEASE_ASSERT(*(uint32_t*)(&buf[currentIndex + 4]) == magic_version, + "Magic version mismatch!"); + } + + // We just skip over the first 8 bytes now because we fill them + // with `magic_header` and `magic_version` anyway. + currentIndex += 8; + moduleLen -= 8; + + Rooted<WasmInstanceObject*> instanceObj(gCx); + + MutableBytes bytecode = gCx->new_<ShareableBytes>(); + if (!bytecode || !bytecode->append((uint8_t*)&magic_header, 4) || + !bytecode->append((uint8_t*)&magic_version, 4) || + !bytecode->append(&buf[currentIndex], moduleLen)) { + return 0; + } + + currentIndex += moduleLen; + + ScriptedCaller scriptedCaller; + FeatureOptions options; + SharedCompileArgs compileArgs = + CompileArgs::buildAndReport(gCx, std::move(scriptedCaller), options); + if (!compileArgs) { + return 0; + } + + UniqueChars error; + UniqueCharsVector warnings; + SharedModule module = + CompileBuffer(*compileArgs, *bytecode, &error, &warnings); + if (!module) { + // We should always have a valid module if we are using wasm-smith. Check + // that no error is reported, signalling an OOM. + MOZ_RELEASE_ASSERT(!gIsWasmSmith || !error); + continue; + } + + // At this point we have a valid module and we should try to ensure + // that its import requirements are met for instantiation. + const ImportVector& importVec = module->imports(); + + // Empty native function used to fill in function import slots if we + // run out of functions exported by other modules. + JS::RootedFunction emptyFunction(gCx); + emptyFunction = + JS_NewFunction(gCx, emptyNativeFunction, 0, 0, "emptyFunction"); + + if (!emptyFunction) { + return 0; + } + + RootedValue emptyFunctionValue(gCx, ObjectValue(*emptyFunction)); + RootedValue nullValue(gCx, NullValue()); + + RootedObject importObj(gCx, JS_NewPlainObject(gCx)); + + if (!importObj) { + return 0; + } + + size_t exportsLength = lastExportIds.length(); + size_t currentFunctionExportId = 0; + size_t currentTableExportId = 0; + size_t currentMemoryExportId = 0; + size_t currentGlobalExportId = 0; + size_t currentTagExportId = 0; + + for (const Import& import : importVec) { + RootedId moduleName(gCx); + if (!import.module.toPropertyKey(gCx, &moduleName)) { + return false; + } + RootedId fieldName(gCx); + if (!import.field.toPropertyKey(gCx, &fieldName)) { + return false; + } + + // First try to get the namespace object, create one if this is the + // first time. + RootedValue v(gCx); + if (!JS_GetPropertyById(gCx, importObj, moduleName, &v) || + !v.isObject()) { + // Insert empty object at importObj[moduleName] + RootedObject plainObj(gCx, JS_NewPlainObject(gCx)); + + if (!plainObj) { + return 0; + } + + RootedValue plainVal(gCx, ObjectValue(*plainObj)); + if (!JS_SetPropertyById(gCx, importObj, moduleName, plainVal)) { + return 0; + } + + // Get the object we just inserted, store in v, ensure it is an + // object (no proxies or other magic at work). + if (!JS_GetPropertyById(gCx, importObj, moduleName, &v) || + !v.isObject()) { + return 0; + } + } + + RootedObject obj(gCx, &v.toObject()); + bool found = false; + if (JS_HasPropertyById(gCx, obj, fieldName, &found) && !found) { + // Insert i-th export object that fits the type requirement + // at `v[fieldName]`. + + switch (import.kind) { + case DefinitionKind::Function: + if (!assignImportKind<JSFunction>( + import, obj, lastExportsObj, lastExportIds, + ¤tFunctionExportId, exportsLength, + emptyFunctionValue)) { + return 0; + } + break; + + case DefinitionKind::Table: + // TODO: Pass a dummy defaultValue + if (!assignImportKind<WasmTableObject>( + import, obj, lastExportsObj, lastExportIds, + ¤tTableExportId, exportsLength, nullValue)) { + return 0; + } + break; + + case DefinitionKind::Memory: + // TODO: Pass a dummy defaultValue + if (!assignImportKind<WasmMemoryObject>( + import, obj, lastExportsObj, lastExportIds, + ¤tMemoryExportId, exportsLength, nullValue)) { + return 0; + } + break; + + case DefinitionKind::Global: + // TODO: Pass a dummy defaultValue + if (!assignImportKind<WasmGlobalObject>( + import, obj, lastExportsObj, lastExportIds, + ¤tGlobalExportId, exportsLength, nullValue)) { + return 0; + } + break; + + case DefinitionKind::Tag: + // TODO: Pass a dummy defaultValue + if (!assignImportKind<WasmTagObject>( + import, obj, lastExportsObj, lastExportIds, + ¤tTagExportId, exportsLength, nullValue)) { + return 0; + } + break; + } + } + } + + Rooted<ImportValues> imports(gCx); + if (!GetImports(gCx, *module, importObj, imports.address())) { + continue; + } + + if (!module->instantiate(gCx, imports.get(), nullptr, &instanceObj)) { + continue; + } + + // At this module we have a valid WebAssembly module instance. + + RootedObject exportsObj(gCx, &instanceObj->exportsObj()); + JS::Rooted<JS::IdVector> exportIds(gCx, JS::IdVector(gCx)); + if (!JS_Enumerate(gCx, exportsObj, &exportIds)) { + continue; + } + + if (!exportIds.length()) { + continue; + } + + // Store the last exports for re-use later + lastExportsObj = exportsObj; + lastExportIds.get() = std::move(exportIds.get()); + + for (size_t i = 0; i < lastExportIds.length(); i++) { + RootedValue propVal(gCx); + if (!JS_GetPropertyById(gCx, exportsObj, lastExportIds[i], &propVal)) { + return 0; + } + + if (propVal.isObject()) { + RootedObject propObj(gCx, &propVal.toObject()); + + if (propObj->is<JSFunction>()) { + RootedFunction func(gCx, &propObj->as<JSFunction>()); + + if (!callExportedFunc(func, &lastReturnVal)) { + return 0; + } + } + + if (propObj->is<WasmTableObject>()) { + Rooted<WasmTableObject*> tableObj(gCx, + &propObj->as<WasmTableObject>()); + size_t tableLen = tableObj->table().length(); + + RootedValue tableGetVal(gCx); + if (!JS_GetProperty(gCx, tableObj, "get", &tableGetVal)) { + return 0; + } + RootedFunction tableGet(gCx, + &tableGetVal.toObject().as<JSFunction>()); + + for (size_t i = 0; i < tableLen; i++) { + JS::RootedValueVector tableGetArgs(gCx); + if (!tableGetArgs.append(NumberValue(uint32_t(i)))) { + return 0; + } + + RootedValue readFuncValue(gCx); + if (!Call(gCx, tableObj, tableGet, tableGetArgs, &readFuncValue)) { + return 0; + } + + if (readFuncValue.isNull()) { + continue; + } + + RootedFunction callee(gCx, + &readFuncValue.toObject().as<JSFunction>()); + + if (!callExportedFunc(callee, &lastReturnVal)) { + return 0; + } + } + } + + if (propObj->is<WasmMemoryObject>()) { + Rooted<WasmMemoryObject*> memory(gCx, + &propObj->as<WasmMemoryObject>()); + size_t byteLen = memory->volatileMemoryLength(); + if (byteLen) { + // Read the bounds of the buffer to ensure it is valid. + // AddressSanitizer would detect any out-of-bounds here. + uint8_t* rawMemory = memory->buffer().dataPointerEither().unwrap(); + volatile uint8_t rawMemByte = 0; + rawMemByte += rawMemory[0]; + rawMemByte += rawMemory[byteLen - 1]; + (void)rawMemByte; + } + } + + if (propObj->is<WasmGlobalObject>()) { + Rooted<WasmGlobalObject*> global(gCx, + &propObj->as<WasmGlobalObject>()); + if (global->type() != ValType::I64) { + global->val().get().toJSValue(gCx, &lastReturnVal); + } + } + } + } + } + + return 0; +} + +static int testWasmSmithFuzz(const uint8_t* buf, size_t size) { + // Define maximum sizes for the input to wasm-smith as well + // as the resulting modules. The input to output size factor + // of wasm-smith is somewhat variable but a factor of 4 seems + // to roughly work out. The logic below also assumes that these + // are powers of 2. + const size_t maxInputSize = 1024; + const size_t maxModuleSize = 4096; + + size_t maxModules = size / maxInputSize + 1; + + // We need 1 leading byte for options and 2 bytes for size per module + uint8_t* out = + new uint8_t[1 + maxModules * (maxModuleSize + sizeof(uint16_t))]; + + auto deleteGuard = mozilla::MakeScopeExit([&] { delete[] out; }); + + // Copy the opt-byte. + out[0] = buf[0]; + + size_t outIndex = 1; + size_t currentIndex = 1; + + while (currentIndex < size) { + size_t remaining = size - currentIndex; + + // We need to have at least a size and some byte to read. + if (remaining <= sizeof(uint16_t)) { + break; + } + + // Determine size of the next input, limited to `maxInputSize`. + uint16_t inSize = + (*((uint16_t*)&buf[currentIndex]) & (maxInputSize - 1)) + 1; + remaining -= sizeof(uint16_t); + currentIndex += sizeof(uint16_t); + + // Cap to remaining bytes. + inSize = remaining >= inSize ? inSize : remaining; + + size_t outSize = + gluesmith((uint8_t*)&buf[currentIndex], inSize, + out + outIndex + sizeof(uint16_t), maxModuleSize); + + if (!outSize) { + break; + } + + currentIndex += inSize; + + // Write the size of the resulting module to our output buffer. + *(uint16_t*)(&out[outIndex]) = (uint16_t)outSize; + outIndex += sizeof(uint16_t) + outSize; + } + + // If we lack at least one module, don't do anything. + if (outIndex == 1) { + return 0; + } + + return testWasmFuzz(out, outIndex); +} + +MOZ_FUZZING_INTERFACE_RAW(testWasmInit, testWasmFuzz, Wasm); +MOZ_FUZZING_INTERFACE_RAW(testWasmSmithInit, testWasmSmithFuzz, WasmSmith); diff --git a/js/src/fuzz-tests/tests.cpp b/js/src/fuzz-tests/tests.cpp new file mode 100644 index 0000000000..f6002bd739 --- /dev/null +++ b/js/src/fuzz-tests/tests.cpp @@ -0,0 +1,121 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "fuzz-tests/tests.h" + +#include <stdio.h> + +#include "js/AllocPolicy.h" +#include "js/GlobalObject.h" +#include "js/Initialization.h" +#include "js/RootingAPI.h" +#include "js/Stack.h" +#include "vm/JSContext.h" + +#ifdef LIBFUZZER +# include "FuzzerDefs.h" +#endif + +using namespace mozilla; + +JS::PersistentRootedObject gGlobal; +JSContext* gCx = nullptr; + +static const JSClass* getGlobalClass() { + static const JSClass c = {"global", JSCLASS_GLOBAL_FLAGS, + &JS::DefaultGlobalClassOps}; + return &c; +} + +static JSObject* jsfuzz_createGlobal(JSContext* cx, JSPrincipals* principals) { + /* Create the global object. */ + JS::RealmOptions options; + options.creationOptions().setWeakRefsEnabled( + JS::WeakRefSpecifier::EnabledWithCleanupSome); + return JS_NewGlobalObject(cx, getGlobalClass(), principals, + JS::FireOnNewGlobalHook, options); +} + +static bool jsfuzz_init(JSContext** cx, JS::PersistentRootedObject* global) { + *cx = JS_NewContext(8L * 1024 * 1024); + if (!*cx) { + return false; + } + + const size_t MAX_STACK_SIZE = 500000; + + JS_SetNativeStackQuota(*cx, MAX_STACK_SIZE); + + js::UseInternalJobQueues(*cx); + if (!JS::InitSelfHostedCode(*cx)) { + return false; + } + global->init(*cx); + *global = jsfuzz_createGlobal(*cx, nullptr); + if (!*global) { + return false; + } + JS::EnterRealm(*cx, *global); + return true; +} + +static void jsfuzz_uninit(JSContext* cx) { + if (cx) { + JS::LeaveRealm(cx, nullptr); + JS_DestroyContext(cx); + cx = nullptr; + } +} + +int main(int argc, char* argv[]) { + if (!JS_Init()) { + fprintf(stderr, "Error: Call to jsfuzz_init() failed\n"); + return 1; + } + + if (!jsfuzz_init(&gCx, &gGlobal)) { + fprintf(stderr, "Error: Call to jsfuzz_init() failed\n"); + return 1; + } + + const char* fuzzerEnv = getenv("FUZZER"); + if (!fuzzerEnv) { + fprintf(stderr, + "Must specify fuzzing target in FUZZER environment variable\n"); + return 1; + } + + std::string moduleNameStr(getenv("FUZZER")); + + FuzzerFunctions funcs = + FuzzerRegistry::getInstance().getModuleFunctions(moduleNameStr); + FuzzerInitFunc initFunc = funcs.first; + FuzzerTestingFunc testingFunc = funcs.second; + if (initFunc) { + int ret = initFunc(&argc, &argv); + if (ret) { + fprintf(stderr, "Fuzzing Interface: Error: Initialize callback failed\n"); + return ret; + } + } + + if (!testingFunc) { + fprintf(stderr, "Fuzzing Interface: Error: No testing callback found\n"); + return 1; + } + +#ifdef LIBFUZZER + fuzzer::FuzzerDriver(&argc, &argv, testingFunc); +#elif AFLFUZZ + testingFunc(nullptr, 0); +#endif + + jsfuzz_uninit(gCx); + + JS_ShutDown(); + + return 0; +} diff --git a/js/src/fuzz-tests/tests.h b/js/src/fuzz-tests/tests.h new file mode 100644 index 0000000000..3935ade748 --- /dev/null +++ b/js/src/fuzz-tests/tests.h @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef fuzz_tests_tests_h +#define fuzz_tests_tests_h + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "FuzzingInterface.h" + +#include "vm/JSContext.h" + +#endif /* fuzz_tests_tests_h */ diff --git a/js/src/fuzz-tests/util/sanitize.js b/js/src/fuzz-tests/util/sanitize.js new file mode 100644 index 0000000000..77c5badc00 --- /dev/null +++ b/js/src/fuzz-tests/util/sanitize.js @@ -0,0 +1,100 @@ +/* -*- Mode: javascript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This function can be used to "sanitize" a new global for fuzzing in such +// a way that permanent side-effects, hangs and behavior that could be harmful +// to libFuzzer targets is reduced to a minimum. +function sanitizeGlobal(g) { + let lfFuncs = { + // Noisy functions (output) + backtrace: function () { }, + getBacktrace: function () { }, + help: function () { }, + print: function (s) { return s.toString(); }, + printErr: function (s) { return s.toString(); }, + putstr: function (s) { return s.toString(); }, + stackDump: function () { }, + dumpHeap: function () { }, + dumpScopeChain: function () { }, + dumpObjectWrappers: function () { }, + dumpGCArenaInfo: function () { }, + printProfilerEvents: function () { }, + + // Harmful functions (hangs, timeouts, leaks) + getLcovInfo: function () { }, + readline: function () { }, + readlineBuf: function () { }, + timeout: function () { }, + quit: function () { }, + interruptIf: function () { }, + terminate: function () { }, + invokeInterruptCallback: function () { }, + setInterruptCallback: function () { }, + intern: function () { }, + evalInWorker: function () { }, + sleep: function () { }, + cacheEntry: function () { }, + streamCacheEntry: function () { }, + createMappedArrayBuffer: function () { }, + wasmCompileInSeparateProcess: function () { }, + gcparam: function () { }, + newGlobal: function () { return g; }, + + // Harmful functions (throw) + assertEq: function (a, b) { return a.toString() == b.toString(); }, + throwError: function () { }, + reportOutOfMemory: function () { }, + throwOutOfMemory: function () { }, + reportLargeAllocationFailure: function () { }, + + // Functions that need limiting + gczeal: function (m, f) { return gczeal(m, 100); }, + startgc: function (n, o) { startgc(n > 20 ? 20 : n, o); }, + gcslice: function (n) { gcslice(n > 20 ? 20 : n); }, + + // Global side-effects + deterministicgc: function () { }, + fullcompartmentchecks: function () { }, + setIonCheckGraphCoherency: function () { }, + enableShellAllocationMetadataBuilder: function () { }, + setTimeResolution: function () { }, + options: function () { return "tracejit,methodjit,typeinfer"; }, + setJitCompilerOption: function () { }, + clearLastWarning: function () { }, + enableSingleStepProfiling: function () { }, + disableSingleStepProfiling: function () { }, + enableGeckoProfiling: function () { }, + enableGeckoProfilingWithSlowAssertions: function () { }, + disableGeckoProfiling: function () { }, + enqueueJob: function () { }, + globalOfFirstJobInQueue: function () { }, + drainJobQueue: function () { }, + setPromiseRejectionTrackerCallback: function () { }, + startTimingMutator: function () { }, + stopTimingMutator: function () { }, + setModuleLoadHook: function () { }, + // Left enabled, as it is required for now to avoid leaks + //setModuleResolveHook: function() {}, + setModuleMetadataHook: function () { }, + setModuleDynamicImportHook: function () { }, + finishDynamicModuleImport: function () { }, + abortDynamicModuleImport: function () { }, + offThreadCompileToStencil: function () { }, + offThreadCompileModuleToStencil: function () { }, + offThreadDecodeStencil: function () { }, + finishOffThreadStencil: function () { }, + addPromiseReactions: function () { }, + ignoreUnhandledRejections: function () { }, + enableTrackAllocations: function () { }, + disableTrackAllocations: function () { }, + setTestFilenameValidationCallback: function () { }, + }; + + for (let lfFunc in lfFuncs) { + g[lfFunc] = lfFuncs[lfFunc]; + } + + return g; +} |