summaryrefslogtreecommitdiffstats
path: root/js/src/fuzz-tests
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /js/src/fuzz-tests
parentInitial commit. (diff)
downloadfirefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz
firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/fuzz-tests')
-rw-r--r--js/src/fuzz-tests/README31
-rw-r--r--js/src/fuzz-tests/differential-parsing.js148
-rw-r--r--js/src/fuzz-tests/moz.build44
-rw-r--r--js/src/fuzz-tests/parsing-evaluate.js83
-rw-r--r--js/src/fuzz-tests/testExample.cpp61
-rw-r--r--js/src/fuzz-tests/testRegExp.cpp111
-rw-r--r--js/src/fuzz-tests/testStructuredCloneReader.cpp84
-rw-r--r--js/src/fuzz-tests/testWasm.cpp448
-rw-r--r--js/src/fuzz-tests/tests.cpp127
-rw-r--r--js/src/fuzz-tests/tests.h19
-rw-r--r--js/src/fuzz-tests/util/sanitize.js104
11 files changed, 1260 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/moz.build b/js/src/fuzz-tests/moz.build
new file mode 100644
index 0000000000..dc6e5cb9a5
--- /dev/null
+++ b/js/src/fuzz-tests/moz.build
@@ -0,0 +1,44 @@
+# -*- 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",
+ ]
+
+if CONFIG["LIBFUZZER"]:
+ USE_LIBS += [
+ "static:fuzzer",
+ ]
+
+ # Add libFuzzer configuration directives
+ include("/tools/fuzzing/libfuzzer-config.mozbuild")
+
+USE_LIBS += [
+ "static:js",
+]
+
+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..311f230b3a
--- /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, GC_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..edd9157fbe
--- /dev/null
+++ b/js/src/fuzz-tests/testRegExp.cpp
@@ -0,0 +1,111 @@
+/* -*- 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, GC_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;
+ }
+
+ RootedAtom pattern(gCx, AtomizeUTF8Chars(gCx, patternChars, patternLength));
+ if (!pattern) {
+ ReportOutOfMemory(gCx);
+ return 0;
+ }
+ RootedAtom 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..0025779b45
--- /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, GC_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..a5611419d5
--- /dev/null
+++ b/js/src/fuzz-tests/testWasm.cpp
@@ -0,0 +1,448 @@
+/* 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 "vm/GlobalObject.h"
+#include "vm/Interpreter.h"
+#include "vm/TypedArrayObject.h"
+
+#include "wasm/WasmCompile.h"
+#include "wasm/WasmCraneliftCompile.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 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 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) {
+ 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_SetProperty(gCx, obj, import.field.get(), propVal)) {
+ return false;
+ }
+
+ assigned = true;
+ break;
+ }
+ }
+ if (!assigned) {
+ if (!JS_SetProperty(gCx, obj, import.field.get(), defaultValue)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static int testWasmFuzz(const uint8_t* buf, size_t size) {
+ auto gcGuard = mozilla::MakeScopeExit([&] {
+ JS::PrepareForFullGC(gCx);
+ JS::NonIncrementalGC(gCx, GC_NORMAL, JS::GCReason::API);
+ });
+
+ 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();
+
+ unsigned char moduleLen = buf[currentIndex];
+ currentIndex++;
+
+ if (size - currentIndex < moduleLen) {
+ moduleLen = size - currentIndex;
+ }
+
+ if (moduleLen < MINIMUM_MODULE_SIZE) {
+ continue;
+ }
+
+ if (currentIndex == 1) {
+ // 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 = (uint8_t)buf[currentIndex];
+
+ // Note that IonPlatformSupport() and CraneliftPlatformSupport() do not
+ // take into account whether those compilers support particular features
+ // that may have been enabled.
+ bool enableWasmBaseline = ((optByte & 0xF0) == (1 << 7));
+ bool enableWasmOptimizing = false;
+#ifdef ENABLE_WASM_CRANELIFT
+ enableWasmOptimizing =
+ CraneliftPlatformSupport() && ((optByte & 0xF0) == (1 << 5));
+#else
+ enableWasmOptimizing =
+ IonPlatformSupport() && ((optByte & 0xF0) == (1 << 6));
+#endif
+ bool enableWasmAwaitTier2 = (IonPlatformSupport()
+#ifdef ENABLE_WASM_CRANELIFT
+ || CraneliftPlatformSupport()
+#endif
+ ) &&
+ ((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() || CraneliftPlatformSupport()) {
+ enableWasmOptimizing = true;
+ } else {
+ enableWasmBaseline = true;
+ }
+ }
+
+ if (enableWasmAwaitTier2) {
+ // Tier 2 needs Baseline + Optimizing
+ enableWasmBaseline = true;
+
+ if (!enableWasmOptimizing) {
+ enableWasmOptimizing = true;
+ }
+ }
+
+ JS::ContextOptionsRef(gCx)
+ .setWasmBaseline(enableWasmBaseline)
+#ifdef ENABLE_WASM_CRANELIFT
+ .setWasmCranelift(enableWasmOptimizing)
+#else
+ .setWasmIon(enableWasmOptimizing)
+#endif
+ .setTestWasmAwaitTier2(enableWasmAwaitTier2);
+ }
+
+ // Expected header for a valid WebAssembly module
+ uint32_t magic_header = 0x6d736100;
+ uint32_t magic_version = 0x1;
+
+ // We just skip over the first 8 bytes now because we fill them
+ // with `magic_header` and `magic_version` anyway.
+ currentIndex += 8;
+ moduleLen -= 8;
+
+ RootedWasmInstanceObject 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;
+ SharedCompileArgs compileArgs =
+ CompileArgs::build(gCx, std::move(scriptedCaller));
+ if (!compileArgs) {
+ return 0;
+ }
+
+ UniqueChars error;
+ UniqueCharsVector warnings;
+ SharedModule module =
+ CompileBuffer(*compileArgs, *bytecode, &error, &warnings);
+ if (!module) {
+ 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;
+#ifdef ENABLE_WASM_EXCEPTIONS
+ size_t currentEventExportId = 0;
+#endif
+
+ for (const Import& import : importVec) {
+ // First try to get the namespace object, create one if this is the
+ // first time.
+ RootedValue v(gCx);
+ if (!JS_GetProperty(gCx, importObj, import.module.get(), &v) ||
+ !v.isObject()) {
+ // Insert empty object at importObj[import.module.get()]
+ RootedObject plainObj(gCx, JS_NewPlainObject(gCx));
+
+ if (!plainObj) {
+ return 0;
+ }
+
+ RootedValue plainVal(gCx, ObjectValue(*plainObj));
+ if (!JS_SetProperty(gCx, importObj, import.module.get(), 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_GetProperty(gCx, importObj, import.module.get(), &v) ||
+ !v.isObject()) {
+ return 0;
+ }
+ }
+
+ RootedObject obj(gCx, &v.toObject());
+ bool found = false;
+ if (JS_HasProperty(gCx, obj, import.field.get(), &found) && !found) {
+ // Insert i-th export object that fits the type requirement
+ // at `v[import.field.get()]`.
+
+ switch (import.kind) {
+ case DefinitionKind::Function:
+ if (!assignImportKind<JSFunction>(
+ import, obj, lastExportsObj, lastExportIds,
+ &currentFunctionExportId, exportsLength,
+ emptyFunctionValue)) {
+ return 0;
+ }
+ break;
+
+ case DefinitionKind::Table:
+ // TODO: Pass a dummy defaultValue
+ if (!assignImportKind<WasmTableObject>(
+ import, obj, lastExportsObj, lastExportIds,
+ &currentTableExportId, exportsLength, nullValue)) {
+ return 0;
+ }
+ break;
+
+ case DefinitionKind::Memory:
+ // TODO: Pass a dummy defaultValue
+ if (!assignImportKind<WasmMemoryObject>(
+ import, obj, lastExportsObj, lastExportIds,
+ &currentMemoryExportId, exportsLength, nullValue)) {
+ return 0;
+ }
+ break;
+
+ case DefinitionKind::Global:
+ // TODO: Pass a dummy defaultValue
+ if (!assignImportKind<WasmGlobalObject>(
+ import, obj, lastExportsObj, lastExportIds,
+ &currentGlobalExportId, exportsLength, nullValue)) {
+ return 0;
+ }
+ break;
+
+#ifdef ENABLE_WASM_EXCEPTIONS
+ case DefinitionKind::Event:
+ // TODO: Pass a dummy defaultValue
+ if (!assignImportKind<WasmExceptionObject>(
+ import, obj, lastExportsObj, lastExportIds,
+ &currentEventExportId, exportsLength, nullValue)) {
+ return 0;
+ }
+ break;
+#endif
+ }
+ }
+ }
+
+ 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->volatileMemoryLength32();
+ 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];
+ }
+ }
+
+ if (propObj->is<WasmGlobalObject>()) {
+ Rooted<WasmGlobalObject*> global(gCx,
+ &propObj->as<WasmGlobalObject>());
+ if (global->type() != ValType::I64) {
+ global->val().get().toJSValue(gCx, &lastReturnVal);
+ }
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+MOZ_FUZZING_INTERFACE_RAW(testWasmInit, testWasmFuzz, Wasm);
diff --git a/js/src/fuzz-tests/tests.cpp b/js/src/fuzz-tests/tests.cpp
new file mode 100644
index 0000000000..d85bad72d3
--- /dev/null
+++ b/js/src/fuzz-tests/tests.cpp
@@ -0,0 +1,127 @@
+/* -*- 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/Initialization.h"
+#include "js/RootingAPI.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().setStreamsEnabled(true).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_DestroyContext(cx);
+ cx = nullptr;
+ }
+}
+
+#ifdef LIBFUZZER
+static void jsfuzz_atexit() { JS_ShutDown(); }
+#endif
+
+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;
+ }
+
+#ifdef LIBFUZZER
+ // This is required because libFuzzer can exit() in various cases
+ std::atexit(jsfuzz_atexit);
+#endif
+
+ 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 __AFL_COMPILER
+ 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..59d43a42bd
--- /dev/null
+++ b/js/src/fuzz-tests/util/sanitize.js
@@ -0,0 +1,104 @@
+/* -*- 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() {},
+ offThreadCompileScript: function() {},
+ runOffThreadScript: function() {},
+ offThreadCompileModule: function() {},
+ finishOffThreadModule: function() {},
+ offThreadDecodeScript: function() {},
+ runOffThreadDecodedScript: function() {},
+ addPromiseReactions: function() {},
+ ignoreUnhandledRejections: function() {},
+ enableTrackAllocations: function() {},
+ disableTrackAllocations: function() {},
+ startTraceLogger: function() {},
+ stopTraceLogger: function() {},
+ setTestFilenameValidationCallback: function() {},
+ };
+
+ for (let lfFunc in lfFuncs) {
+ g[lfFunc] = lfFuncs[lfFunc];
+ }
+
+ return g;
+}