summaryrefslogtreecommitdiffstats
path: root/js/src/jsapi-tests
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/jsapi-tests')
-rw-r--r--js/src/jsapi-tests/Makefile.in9
-rw-r--r--js/src/jsapi-tests/README173
-rw-r--r--js/src/jsapi-tests/hidePointer.cpp25
-rw-r--r--js/src/jsapi-tests/jsapi-tests-gdb.py.in8
-rw-r--r--js/src/jsapi-tests/moz.build200
-rw-r--r--js/src/jsapi-tests/selfTest.cpp19
-rw-r--r--js/src/jsapi-tests/testAddPropertyPropcache.cpp67
-rw-r--r--js/src/jsapi-tests/testArgumentsObject.cpp91
-rw-r--r--js/src/jsapi-tests/testArrayBuffer.cpp449
-rw-r--r--js/src/jsapi-tests/testArrayBufferOrViewAPI.cpp111
-rw-r--r--js/src/jsapi-tests/testArrayBufferView.cpp177
-rw-r--r--js/src/jsapi-tests/testArrayBufferWithUserOwnedContents.cpp54
-rw-r--r--js/src/jsapi-tests/testAssemblerBuffer.cpp588
-rw-r--r--js/src/jsapi-tests/testAtomicOperations.cpp304
-rw-r--r--js/src/jsapi-tests/testAtomizeUtf8NonAsciiLatin1CodePoint.cpp212
-rw-r--r--js/src/jsapi-tests/testAtomizeWithoutActiveZone.cpp52
-rw-r--r--js/src/jsapi-tests/testAvlTree.cpp383
-rw-r--r--js/src/jsapi-tests/testBigInt.cpp768
-rw-r--r--js/src/jsapi-tests/testBoundFunction.cpp32
-rw-r--r--js/src/jsapi-tests/testBug604087.cpp91
-rw-r--r--js/src/jsapi-tests/testCallArgs.cpp85
-rw-r--r--js/src/jsapi-tests/testCallNonGenericMethodOnProxy.cpp87
-rw-r--r--js/src/jsapi-tests/testChromeBuffer.cpp257
-rw-r--r--js/src/jsapi-tests/testCompileNonSyntactic.cpp114
-rw-r--r--js/src/jsapi-tests/testCompileScript.cpp242
-rw-r--r--js/src/jsapi-tests/testCompileUtf8.cpp300
-rw-r--r--js/src/jsapi-tests/testDateToLocaleString.cpp63
-rw-r--r--js/src/jsapi-tests/testDebugger.cpp62
-rw-r--r--js/src/jsapi-tests/testDecimalNumber.cpp289
-rw-r--r--js/src/jsapi-tests/testDeduplication.cpp113
-rw-r--r--js/src/jsapi-tests/testDeepFreeze.cpp58
-rw-r--r--js/src/jsapi-tests/testDefineGetterSetterNonEnumerable.cpp51
-rw-r--r--js/src/jsapi-tests/testDefineProperty.cpp26
-rw-r--r--js/src/jsapi-tests/testDeflateStringToUTF8Buffer.cpp162
-rw-r--r--js/src/jsapi-tests/testDifferentNewTargetInvokeConstructor.cpp38
-rw-r--r--js/src/jsapi-tests/testEmptyWindowIsOmitted.cpp155
-rw-r--r--js/src/jsapi-tests/testErrorCopying.cpp33
-rw-r--r--js/src/jsapi-tests/testErrorInterceptor.cpp145
-rw-r--r--js/src/jsapi-tests/testErrorInterceptorGC.cpp32
-rw-r--r--js/src/jsapi-tests/testErrorLineOfContext.cpp74
-rw-r--r--js/src/jsapi-tests/testException.cpp92
-rw-r--r--js/src/jsapi-tests/testExecuteInJSMEnvironment.cpp107
-rw-r--r--js/src/jsapi-tests/testExternalStrings.cpp52
-rw-r--r--js/src/jsapi-tests/testFindSCCs.cpp241
-rw-r--r--js/src/jsapi-tests/testForOfIterator.cpp50
-rw-r--r--js/src/jsapi-tests/testForceLexicalInitialization.cpp39
-rw-r--r--js/src/jsapi-tests/testForwardSetProperty.cpp98
-rw-r--r--js/src/jsapi-tests/testFreshGlobalEvalRedefinition.cpp63
-rw-r--r--js/src/jsapi-tests/testFrontendCompileStencil.cpp68
-rw-r--r--js/src/jsapi-tests/testFrontendJSON.cpp200
-rw-r--r--js/src/jsapi-tests/testFunctionBinding.cpp83
-rw-r--r--js/src/jsapi-tests/testFunctionNonSyntactic.cpp95
-rw-r--r--js/src/jsapi-tests/testFunctionProperties.cpp26
-rw-r--r--js/src/jsapi-tests/testGCAllocator.cpp354
-rw-r--r--js/src/jsapi-tests/testGCCellPtr.cpp58
-rw-r--r--js/src/jsapi-tests/testGCChunkPool.cpp74
-rw-r--r--js/src/jsapi-tests/testGCExactRooting.cpp915
-rw-r--r--js/src/jsapi-tests/testGCFinalizeCallback.cpp202
-rw-r--r--js/src/jsapi-tests/testGCGrayMarking.cpp803
-rw-r--r--js/src/jsapi-tests/testGCHeapBarriers.cpp828
-rw-r--r--js/src/jsapi-tests/testGCHooks.cpp279
-rw-r--r--js/src/jsapi-tests/testGCMarking.cpp448
-rw-r--r--js/src/jsapi-tests/testGCOutOfMemory.cpp79
-rw-r--r--js/src/jsapi-tests/testGCStoreBufferRemoval.cpp110
-rw-r--r--js/src/jsapi-tests/testGCUniqueId.cpp125
-rw-r--r--js/src/jsapi-tests/testGCWeakCache.cpp698
-rw-r--r--js/src/jsapi-tests/testGetPropertyDescriptor.cpp54
-rw-r--r--js/src/jsapi-tests/testHashTable.cpp564
-rw-r--r--js/src/jsapi-tests/testIndexToString.cpp120
-rw-r--r--js/src/jsapi-tests/testInformalValueTypeName.cpp43
-rw-r--r--js/src/jsapi-tests/testIntString.cpp49
-rw-r--r--js/src/jsapi-tests/testIntern.cpp51
-rw-r--r--js/src/jsapi-tests/testIntlAvailableLocales.cpp68
-rw-r--r--js/src/jsapi-tests/testIsInsideNursery.cpp31
-rw-r--r--js/src/jsapi-tests/testIteratorObject.cpp34
-rw-r--r--js/src/jsapi-tests/testJSEvaluateScript.cpp38
-rw-r--r--js/src/jsapi-tests/testJSON.cpp112
-rw-r--r--js/src/jsapi-tests/testJitABIcalls.cpp718
-rw-r--r--js/src/jsapi-tests/testJitDCEinGVN.cpp147
-rw-r--r--js/src/jsapi-tests/testJitFoldsTo.cpp262
-rw-r--r--js/src/jsapi-tests/testJitGVN.cpp299
-rw-r--r--js/src/jsapi-tests/testJitMacroAssembler.cpp816
-rw-r--r--js/src/jsapi-tests/testJitMinimalFunc.h123
-rw-r--r--js/src/jsapi-tests/testJitMoveEmitterCycles-mips32.cpp480
-rw-r--r--js/src/jsapi-tests/testJitMoveEmitterCycles.cpp616
-rw-r--r--js/src/jsapi-tests/testJitRValueAlloc.cpp256
-rw-r--r--js/src/jsapi-tests/testJitRangeAnalysis.cpp368
-rw-r--r--js/src/jsapi-tests/testJitRegisterSet.cpp211
-rw-r--r--js/src/jsapi-tests/testLargeArrayBuffers.cpp166
-rw-r--r--js/src/jsapi-tests/testLookup.cpp102
-rw-r--r--js/src/jsapi-tests/testLooselyEqual.cpp186
-rw-r--r--js/src/jsapi-tests/testMappedArrayBuffer.cpp195
-rw-r--r--js/src/jsapi-tests/testMemoryAssociation.cpp44
-rw-r--r--js/src/jsapi-tests/testMutedErrors.cpp99
-rw-r--r--js/src/jsapi-tests/testNewObject.cpp255
-rw-r--r--js/src/jsapi-tests/testNewTargetInvokeConstructor.cpp25
-rw-r--r--js/src/jsapi-tests/testNullRoot.cpp24
-rw-r--r--js/src/jsapi-tests/testNumberToString.cpp133
-rw-r--r--js/src/jsapi-tests/testOOM.cpp114
-rw-r--r--js/src/jsapi-tests/testObjectEmulatingUndefined.cpp109
-rw-r--r--js/src/jsapi-tests/testObjectSwap.cpp445
-rw-r--r--js/src/jsapi-tests/testParseJSON.cpp357
-rw-r--r--js/src/jsapi-tests/testParserAtom.cpp445
-rw-r--r--js/src/jsapi-tests/testPersistentRooted.cpp212
-rw-r--r--js/src/jsapi-tests/testPreserveJitCode.cpp100
-rw-r--r--js/src/jsapi-tests/testPrintError.cpp125
-rw-r--r--js/src/jsapi-tests/testPrintf.cpp69
-rw-r--r--js/src/jsapi-tests/testPrivateGCThingValue.cpp64
-rw-r--r--js/src/jsapi-tests/testProfileStrings.cpp225
-rw-r--r--js/src/jsapi-tests/testPromise.cpp170
-rw-r--r--js/src/jsapi-tests/testPropCache.cpp47
-rw-r--r--js/src/jsapi-tests/testPropertyKey.cpp69
-rw-r--r--js/src/jsapi-tests/testRecordTupleToSource.cpp45
-rw-r--r--js/src/jsapi-tests/testRegExp.cpp65
-rw-r--r--js/src/jsapi-tests/testResolveRecursion.cpp184
-rw-r--r--js/src/jsapi-tests/testResult.cpp98
-rw-r--r--js/src/jsapi-tests/testSABAccounting.cpp31
-rw-r--r--js/src/jsapi-tests/testSameValue.cpp26
-rw-r--r--js/src/jsapi-tests/testSavedStacks.cpp396
-rw-r--r--js/src/jsapi-tests/testScriptInfo.cpp56
-rw-r--r--js/src/jsapi-tests/testScriptObject.cpp214
-rw-r--r--js/src/jsapi-tests/testScriptSourceCompression.cpp551
-rw-r--r--js/src/jsapi-tests/testSetProperty.cpp54
-rw-r--r--js/src/jsapi-tests/testSetPropertyIgnoringNamedGetter.cpp73
-rw-r--r--js/src/jsapi-tests/testSharedImmutableStringsCache.cpp72
-rw-r--r--js/src/jsapi-tests/testSliceBudget.cpp117
-rw-r--r--js/src/jsapi-tests/testSlowScript.cpp72
-rw-r--r--js/src/jsapi-tests/testSourcePolicy.cpp57
-rw-r--r--js/src/jsapi-tests/testSparseBitmap.cpp115
-rw-r--r--js/src/jsapi-tests/testStencil.cpp881
-rw-r--r--js/src/jsapi-tests/testStringBuffer.cpp27
-rw-r--r--js/src/jsapi-tests/testStringIsArrayIndex.cpp81
-rw-r--r--js/src/jsapi-tests/testStructuredClone.cpp365
-rw-r--r--js/src/jsapi-tests/testSymbol.cpp79
-rw-r--r--js/src/jsapi-tests/testThreadingConditionVariable.cpp220
-rw-r--r--js/src/jsapi-tests/testThreadingExclusiveData.cpp84
-rw-r--r--js/src/jsapi-tests/testThreadingMutex.cpp33
-rw-r--r--js/src/jsapi-tests/testThreadingThread.cpp107
-rw-r--r--js/src/jsapi-tests/testToSignedOrUnsignedInteger.cpp68
-rw-r--r--js/src/jsapi-tests/testTypedArrays.cpp278
-rw-r--r--js/src/jsapi-tests/testUTF8.cpp47
-rw-r--r--js/src/jsapi-tests/testUbiNode.cpp971
-rw-r--r--js/src/jsapi-tests/testUncaughtSymbol.cpp57
-rw-r--r--js/src/jsapi-tests/testValueABI.cpp53
-rw-r--r--js/src/jsapi-tests/testWasmLEB128.cpp173
-rw-r--r--js/src/jsapi-tests/testWeakMap.cpp255
-rw-r--r--js/src/jsapi-tests/testWindowNonConfigurable.cpp69
-rw-r--r--js/src/jsapi-tests/tests.cpp292
-rw-r--r--js/src/jsapi-tests/tests.h621
-rw-r--r--js/src/jsapi-tests/testsJit.cpp80
-rw-r--r--js/src/jsapi-tests/testsJit.h21
-rw-r--r--js/src/jsapi-tests/valueABI.c25
152 files changed, 28739 insertions, 0 deletions
diff --git a/js/src/jsapi-tests/Makefile.in b/js/src/jsapi-tests/Makefile.in
new file mode 100644
index 0000000000..2aac798393
--- /dev/null
+++ b/js/src/jsapi-tests/Makefile.in
@@ -0,0 +1,9 @@
+# -*- Mode: makefile -*-
+#
+# 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/.
+
+ifdef QEMU_EXE
+MOZ_POST_PROGRAM_COMMAND = $(topsrcdir)/build/qemu-wrap --qemu $(QEMU_EXE) --libdir $(CROSS_LIB)
+endif
diff --git a/js/src/jsapi-tests/README b/js/src/jsapi-tests/README
new file mode 100644
index 0000000000..5e03748f0e
--- /dev/null
+++ b/js/src/jsapi-tests/README
@@ -0,0 +1,173 @@
+# JSAPI Test Suite
+
+The tests in this directory exercise the JSAPI.
+
+
+## Building and running the tests
+
+If you built JS, you already built the tests.
+
+The tests are built by default when you build JS. All the tests are compiled
+into a single binary named jsapi-tests. They all run in a single process.
+They must be run from the js/src directory.
+
+To run the tests:
+
+ $OBJDIR/dist/bin/jsapi-tests
+
+To run the tests in a debugger:
+
+ gdb $OBJDIR/dist/bin/jsapi-tests
+
+
+## Creating new tests
+
+1. You can either add to an existing test*.cpp file or make a new one.
+ Copy an existing test and replace the body with your test code.
+ The test harness provides `cx`, `rt`, and `global` for your use.
+
+2. If you made a new .cpp file, add it to the UNIFIED_SOURCES list
+ in moz.build.
+
+
+## Writing test code
+
+Here is a sample test:
+
+ #include "jsapi-tests/tests.h"
+
+ BEGIN_TEST(testIntString_bug515273)
+ {
+ RootedValue v(cx);
+
+ EVAL("'42';", &v);
+ JSString *str = v.toString();
+ CHECK(JS_StringHasBeenInterned(cx, str));
+ CHECK(JS_FlatStringEqualsLiteral(JS_ASSERT_STRING_IS_FLAT(str), "42"));
+ return true;
+ }
+ END_TEST(testIntString_bug515273)
+
+The BEGIN_TEST and END_TEST macros bracket each test. By convention, the test
+name is <testFilename>_<detail>. (The above test is in testIntString.cpp.)
+
+The curly braces are required. This block is the body of a C++ member function
+that returns bool. The test harness calls this member function
+automatically. If the function returns true, the test passes. False, it fails.
+
+JSAPI tests often need extra global C/C++ code: a JSClass, a getter or setter
+function, a resolve hook. Put these before the BEGIN_TEST macro.
+
+The body of the test can use these member variables and macros, defined in
+tests.h:
+
+ JSRuntime *rt;
+ JSContext *cx;
+ JSObject *global;
+
+ The test framework creates these fresh for each test. The default
+ environment has reasonable default settings, including
+ JSOPTION_VAROBJFIX, JSOPTION_JIT, a global object of a class with
+ JSCLASS_GLOBAL_FLAGS, and an error reporter that prints to stderr.
+ See also "Custom test setup" below.
+
+ EXEC(const char *code);
+
+ Execute some JS code in global scope, using JS::Evaluate. Return
+ false if that fails. (This means that if the code throws an uncaught JS
+ exception, the test fails.)
+
+ EVAL(const char *code, jsval *vp);
+
+ Same as EXEC, but store the result value in *vp.
+
+ CHECK(bool cond);
+
+ If the condition is not true, print an error message and return false,
+ failing the test.
+
+ CHECK_SAME(jsval a, jsval b);
+
+ If a and b are different values, print an error message and return
+ false, failing the test.
+
+ This is like CHECK(sameValue(a, b)) but with a more detailed error
+ message on failure. See sameValue below.
+
+ CHECK_EQUAL(const T &a, const U &b);
+
+ CHECK(a == b), but with a more detailed error message.
+
+ CHECK_NULL(const T *ptr);
+
+ CHECK(ptr == nullptr), but with a more detailed error message.
+
+ (This is here because CHECK_EQUAL(ptr, nullptr) fails to compile on GCC
+ 2.5 and before.)
+
+
+ bool knownFail;
+
+ Set this to true if your test is known to fail. The test runner will
+ print a TEST-KNOWN-FAIL line rather than a TEST-UNEXPECTED-FAIL
+ line. This way you can check in a test illustrating a bug ahead of the
+ fix.
+
+ If your test actually crashes the process or triggers an assertion,
+ this of course will not help, so you should add something like
+
+ knownFail = true; // see bug 123456
+ return false; // the code below crashes!
+
+ as the first two lines of the test.
+
+ bool isNegativeZero(jsval v);
+ bool isNaN(jsval v);
+
+ Self-explanatory.
+
+ bool sameValue(jsval v1, jsval v2);
+
+ True if v1 and v2 are the same value according to the ES5 SameValue()
+ function, to wit:
+
+ SameValue(NaN, NaN) is true.
+ SameValue(-0, 0) is false.
+ Otherwise SameValue(a, b) iff a === b.
+
+
+## Custom test setup
+
+Before executing each test, the test framework calls the tests' init() member
+function, which populates the rt, cx, and global member variables.
+
+A test can customize the test setup process by overloading virtual member
+functions from the JSAPIRuntimeTest class, like this:
+
+ const JSClass globalClassWithResolve = { ... };
+
+ BEGIN_TEST(testGlobalResolveHook)
+ {
+ RootedValue v;
+ EVAL("v", v.address());
+ CHECK_SAME(v, JSVAL_VOID);
+ return true;
+ }
+
+ // Other class members can go here.
+
+ // This one overloads a base-class method.
+ virtual JSClass *getGlobalJSClass() {
+ return &globalClassWithResolve;
+ }
+ END_TEST(testGlobalResolveHook)
+
+The overloadable member functions are:
+
+ virtual bool init();
+ virtual void uninit();
+ virtual JSRuntime * createRuntime();
+ virtual JSContext * createContext();
+ virtual JSClass * getGlobalClass();
+ virtual JSObject * createGlobal();
+
diff --git a/js/src/jsapi-tests/hidePointer.cpp b/js/src/jsapi-tests/hidePointer.cpp
new file mode 100644
index 0000000000..144c890359
--- /dev/null
+++ b/js/src/jsapi-tests/hidePointer.cpp
@@ -0,0 +1,25 @@
+/* -*- 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/Atomics.h"
+
+// This is an attempt to hide pointer values from the C++ compiler. This might
+// not stand up in the presence of PGO but that's probably not important.
+
+// g_hidden_pointer is public so that it's limited what the compiler can assume
+// about it, and atomic so that we don't run afoul of the compiler's UB
+// analysis.
+
+mozilla::Atomic<void*> g_hidden_pointer;
+
+// Call this to install a pointer into the global.
+
+MOZ_NEVER_INLINE void setHiddenPointer(void* p) { g_hidden_pointer = p; }
+
+// Call this to retrieve the pointer.
+
+MOZ_NEVER_INLINE void* getHiddenPointer() { return g_hidden_pointer; }
diff --git a/js/src/jsapi-tests/jsapi-tests-gdb.py.in b/js/src/jsapi-tests/jsapi-tests-gdb.py.in
new file mode 100644
index 0000000000..c5a12f38e0
--- /dev/null
+++ b/js/src/jsapi-tests/jsapi-tests-gdb.py.in
@@ -0,0 +1,8 @@
+"""GDB Python customization auto-loader for jsapi-tests executable"""
+#filter substitution
+
+import os.path
+sys.path[0:0] = [os.path.join('@topsrcdir@', 'gdb')]
+
+import mozilla.autoload
+mozilla.autoload.register(gdb.current_objfile())
diff --git a/js/src/jsapi-tests/moz.build b/js/src/jsapi-tests/moz.build
new file mode 100644
index 0000000000..41ee139698
--- /dev/null
+++ b/js/src/jsapi-tests/moz.build
@@ -0,0 +1,200 @@
+# -*- 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("jsapi-tests", linkage=None)
+
+include("../js-cxxflags.mozbuild")
+include("../js-standalone.mozbuild")
+
+UNIFIED_SOURCES += [
+ "selfTest.cpp",
+ "testAddPropertyPropcache.cpp",
+ "testArgumentsObject.cpp",
+ "testArrayBuffer.cpp",
+ "testArrayBufferOrViewAPI.cpp",
+ "testArrayBufferView.cpp",
+ "testArrayBufferWithUserOwnedContents.cpp",
+ "testAtomicOperations.cpp",
+ "testAtomizeUtf8NonAsciiLatin1CodePoint.cpp",
+ "testAtomizeWithoutActiveZone.cpp",
+ "testAvlTree.cpp",
+ "testBigInt.cpp",
+ "testBoundFunction.cpp",
+ "testBug604087.cpp",
+ "testCallArgs.cpp",
+ "testCallNonGenericMethodOnProxy.cpp",
+ "testChromeBuffer.cpp",
+ "testCompileNonSyntactic.cpp",
+ "testCompileScript.cpp",
+ "testCompileUtf8.cpp",
+ "testDateToLocaleString.cpp",
+ "testDebugger.cpp",
+ "testDeduplication.cpp",
+ "testDeepFreeze.cpp",
+ "testDefineGetterSetterNonEnumerable.cpp",
+ "testDefineProperty.cpp",
+ "testDeflateStringToUTF8Buffer.cpp",
+ "testDifferentNewTargetInvokeConstructor.cpp",
+ "testEmptyWindowIsOmitted.cpp",
+ "testErrorCopying.cpp",
+ "testErrorLineOfContext.cpp",
+ "testException.cpp",
+ "testExecuteInJSMEnvironment.cpp",
+ "testExternalStrings.cpp",
+ "testFindSCCs.cpp",
+ "testForceLexicalInitialization.cpp",
+ "testForOfIterator.cpp",
+ "testForwardSetProperty.cpp",
+ "testFreshGlobalEvalRedefinition.cpp",
+ "testFrontendCompileStencil.cpp",
+ "testFrontendJSON.cpp",
+ "testFunctionBinding.cpp",
+ "testFunctionNonSyntactic.cpp",
+ "testFunctionProperties.cpp",
+ "testGCAllocator.cpp",
+ "testGCCellPtr.cpp",
+ "testGCChunkPool.cpp",
+ "testGCExactRooting.cpp",
+ "testGCFinalizeCallback.cpp",
+ "testGCGrayMarking.cpp",
+ "testGCHeapBarriers.cpp",
+ "testGCHooks.cpp",
+ "testGCMarking.cpp",
+ "testGCOutOfMemory.cpp",
+ "testGCStoreBufferRemoval.cpp",
+ "testGCUniqueId.cpp",
+ "testGCWeakCache.cpp",
+ "testGetPropertyDescriptor.cpp",
+ "testHashTable.cpp",
+ "testIndexToString.cpp",
+ "testInformalValueTypeName.cpp",
+ "testIntern.cpp",
+ "testIntlAvailableLocales.cpp",
+ "testIntString.cpp",
+ "testIsInsideNursery.cpp",
+ "testIteratorObject.cpp",
+ "testJSEvaluateScript.cpp",
+ "testJSON.cpp",
+ "testLargeArrayBuffers.cpp",
+ "testLookup.cpp",
+ "testLooselyEqual.cpp",
+ "testMappedArrayBuffer.cpp",
+ "testMemoryAssociation.cpp",
+ "testMutedErrors.cpp",
+ "testNewObject.cpp",
+ "testNewTargetInvokeConstructor.cpp",
+ "testNullRoot.cpp",
+ "testNumberToString.cpp",
+ "testObjectEmulatingUndefined.cpp",
+ "testObjectSwap.cpp",
+ "testOOM.cpp",
+ "testParseJSON.cpp",
+ "testParserAtom.cpp",
+ "testPersistentRooted.cpp",
+ "testPreserveJitCode.cpp",
+ "testPrintf.cpp",
+ "testPrivateGCThingValue.cpp",
+ "testProfileStrings.cpp",
+ "testPromise.cpp",
+ "testPropCache.cpp",
+ "testPropertyKey.cpp",
+ "testRecordTupleToSource.cpp",
+ "testRegExp.cpp",
+ "testResolveRecursion.cpp",
+ "testResult.cpp",
+ "tests.cpp",
+ "testSABAccounting.cpp",
+ "testSameValue.cpp",
+ "testSavedStacks.cpp",
+ "testScriptInfo.cpp",
+ "testScriptObject.cpp",
+ "testScriptSourceCompression.cpp",
+ "testSetProperty.cpp",
+ "testSetPropertyIgnoringNamedGetter.cpp",
+ "testSharedImmutableStringsCache.cpp",
+ "testSliceBudget.cpp",
+ "testSlowScript.cpp",
+ "testSourcePolicy.cpp",
+ "testSparseBitmap.cpp",
+ "testStencil.cpp",
+ "testStringBuffer.cpp",
+ "testStringIsArrayIndex.cpp",
+ "testStructuredClone.cpp",
+ "testSymbol.cpp",
+ "testThreadingConditionVariable.cpp",
+ "testThreadingExclusiveData.cpp",
+ "testThreadingMutex.cpp",
+ "testThreadingThread.cpp",
+ "testToSignedOrUnsignedInteger.cpp",
+ "testTypedArrays.cpp",
+ "testUbiNode.cpp",
+ "testUncaughtSymbol.cpp",
+ "testUTF8.cpp",
+ "testWasmLEB128.cpp",
+ "testWeakMap.cpp",
+ "testWindowNonConfigurable.cpp",
+]
+
+SOURCES += [
+ # There are clashing definitions of js::jit::AssemblerBuffer.
+ "testAssemblerBuffer.cpp",
+]
+
+SOURCES += [
+ # We don't want this in the C++ files with the test cases.
+ "hidePointer.cpp",
+]
+
+if CONFIG["JS_HAS_INTL_API"]:
+ UNIFIED_SOURCES += [
+ "testDecimalNumber.cpp",
+ ]
+
+if not CONFIG["JS_CODEGEN_NONE"]:
+ UNIFIED_SOURCES += [
+ "testJitABIcalls.cpp",
+ "testJitDCEinGVN.cpp",
+ "testJitFoldsTo.cpp",
+ "testJitGVN.cpp",
+ "testJitMacroAssembler.cpp",
+ "testJitMoveEmitterCycles-mips32.cpp",
+ "testJitMoveEmitterCycles.cpp",
+ "testJitRangeAnalysis.cpp",
+ "testJitRegisterSet.cpp",
+ "testJitRValueAlloc.cpp",
+ "testsJit.cpp",
+ ]
+
+if CONFIG["NIGHTLY_BUILD"]:
+ # The Error interceptor only exists on Nightly.
+ UNIFIED_SOURCES += [
+ "testErrorInterceptor.cpp",
+ "testErrorInterceptorGC.cpp",
+ ]
+
+if CONFIG["OS_ARCH"] not in ("WINNT", "Darwin") and CONFIG["OS_TARGET"] != "Android":
+ # open_memstream() not available on Windows, macOS, or Android
+ UNIFIED_SOURCES += [
+ "testPrintError.cpp",
+ ]
+
+if CONFIG["MOZ_DEBUG"] or CONFIG["NIGHTLY_BUILD"]:
+ DEFINES["JS_CACHEIR_SPEW"] = True
+ DEFINES["JS_STRUCTURED_SPEW"] = True
+
+DEFINES["EXPORT_JS_API"] = True
+
+LOCAL_INCLUDES += [
+ "!..",
+ "..",
+]
+
+USE_LIBS += [
+ "static:js",
+]
+
+DEFINES["topsrcdir"] = "%s/js/src" % TOPSRCDIR
+OBJDIR_PP_FILES.js.src["jsapi-tests"] += ["jsapi-tests-gdb.py.in"]
diff --git a/js/src/jsapi-tests/selfTest.cpp b/js/src/jsapi-tests/selfTest.cpp
new file mode 100644
index 0000000000..aefd6982d9
--- /dev/null
+++ b/js/src/jsapi-tests/selfTest.cpp
@@ -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/. */
+
+#include "jsapi-tests/tests.h"
+
+BEGIN_TEST(selfTest_NaNsAreSame) {
+ JS::RootedValue v1(cx), v2(cx);
+ EVAL("0/0", &v1); // NaN
+ CHECK_SAME(v1, v1);
+
+ EVAL("Math.sin('no')", &v2); // also NaN
+ CHECK_SAME(v1, v2);
+ return true;
+}
+END_TEST(selfTest_NaNsAreSame)
diff --git a/js/src/jsapi-tests/testAddPropertyPropcache.cpp b/js/src/jsapi-tests/testAddPropertyPropcache.cpp
new file mode 100644
index 0000000000..7fa1e86df9
--- /dev/null
+++ b/js/src/jsapi-tests/testAddPropertyPropcache.cpp
@@ -0,0 +1,67 @@
+/* -*- 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 "js/Array.h" // JS::NewArrayObject
+#include "js/PropertyAndElement.h" // JS_DefineElement, JS_DefineProperty
+#include "jsapi-tests/tests.h"
+
+static int callCount = 0;
+
+static bool AddProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
+ JS::HandleValue v) {
+ callCount++;
+ return true;
+}
+
+static const JSClassOps AddPropertyClassOps = {
+ AddProperty, // addProperty
+ nullptr, // delProperty
+ nullptr, // enumerate
+ nullptr, // newEnumerate
+ nullptr, // resolve
+ nullptr, // mayResolve
+ nullptr, // finalize
+ nullptr, // call
+ nullptr, // construct
+ nullptr, // trace
+};
+
+static const JSClass AddPropertyClass = {"AddPropertyTester", 0,
+ &AddPropertyClassOps};
+
+BEGIN_TEST(testAddPropertyHook) {
+ /*
+ * Do the test a bunch of times, because sometimes we seem to randomly
+ * miss the propcache.
+ */
+ static const int ExpectedCount = 100;
+
+ JS::RootedObject obj(cx, JS::NewArrayObject(cx, 0));
+ CHECK(obj);
+ JS::RootedValue arr(cx, JS::ObjectValue(*obj));
+
+ CHECK(JS_DefineProperty(cx, global, "arr", arr, JSPROP_ENUMERATE));
+
+ JS::RootedObject arrObj(cx, &arr.toObject());
+ for (int i = 0; i < ExpectedCount; ++i) {
+ obj = JS_NewObject(cx, &AddPropertyClass);
+ CHECK(obj);
+ CHECK(JS_DefineElement(cx, arrObj, i, obj, JSPROP_ENUMERATE));
+ }
+
+ // Now add a prop to each of the objects, but make sure to do
+ // so at the same bytecode location so we can hit the propcache.
+ EXEC(
+ "'use strict'; \n"
+ "for (var i = 0; i < arr.length; ++i) \n"
+ " arr[i].prop = 42; \n");
+
+ CHECK(callCount == ExpectedCount);
+
+ return true;
+}
+END_TEST(testAddPropertyHook)
diff --git a/js/src/jsapi-tests/testArgumentsObject.cpp b/js/src/jsapi-tests/testArgumentsObject.cpp
new file mode 100644
index 0000000000..8378de7e08
--- /dev/null
+++ b/js/src/jsapi-tests/testArgumentsObject.cpp
@@ -0,0 +1,91 @@
+/* -*- 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 "jsapi-tests/tests.h"
+
+#include "vm/ArgumentsObject-inl.h"
+#include "vm/JSObject-inl.h"
+
+using namespace js;
+
+static const char NORMAL_ZERO[] = "function f() { return arguments; }";
+static const char NORMAL_ONE[] = "function f(a) { return arguments; }";
+static const char NORMAL_TWO[] = "function f(a, b) { return arguments; }";
+static const char NORMAL_THREE[] = "function f(a, b, c) { return arguments; }";
+
+static const char STRICT_ZERO[] =
+ "function f() { 'use strict'; return arguments; }";
+static const char STRICT_ONE[] =
+ "function f() { 'use strict'; return arguments; }";
+static const char STRICT_TWO[] =
+ "function f() { 'use strict'; return arguments; }";
+static const char STRICT_THREE[] =
+ "function f() { 'use strict'; return arguments; }";
+
+static const char* const CALL_CODES[] = {"f()", "f(0)",
+ "f(0, 1)", "f(0, 1, 2)",
+ "f(0, 1, 2, 3)", "f(0, 1, 2, 3, 4)"};
+
+BEGIN_TEST(testArgumentsObject) {
+ return ExhaustiveTest<0>(NORMAL_ZERO) && ExhaustiveTest<1>(NORMAL_ZERO) &&
+ ExhaustiveTest<2>(NORMAL_ZERO) && ExhaustiveTest<0>(NORMAL_ONE) &&
+ ExhaustiveTest<1>(NORMAL_ONE) && ExhaustiveTest<2>(NORMAL_ONE) &&
+ ExhaustiveTest<3>(NORMAL_ONE) && ExhaustiveTest<0>(NORMAL_TWO) &&
+ ExhaustiveTest<1>(NORMAL_TWO) && ExhaustiveTest<2>(NORMAL_TWO) &&
+ ExhaustiveTest<3>(NORMAL_TWO) && ExhaustiveTest<4>(NORMAL_TWO) &&
+ ExhaustiveTest<0>(NORMAL_THREE) && ExhaustiveTest<1>(NORMAL_THREE) &&
+ ExhaustiveTest<2>(NORMAL_THREE) && ExhaustiveTest<3>(NORMAL_THREE) &&
+ ExhaustiveTest<4>(NORMAL_THREE) && ExhaustiveTest<5>(NORMAL_THREE) &&
+ ExhaustiveTest<0>(STRICT_ZERO) && ExhaustiveTest<1>(STRICT_ZERO) &&
+ ExhaustiveTest<2>(STRICT_ZERO) && ExhaustiveTest<0>(STRICT_ONE) &&
+ ExhaustiveTest<1>(STRICT_ONE) && ExhaustiveTest<2>(STRICT_ONE) &&
+ ExhaustiveTest<3>(STRICT_ONE) && ExhaustiveTest<0>(STRICT_TWO) &&
+ ExhaustiveTest<1>(STRICT_TWO) && ExhaustiveTest<2>(STRICT_TWO) &&
+ ExhaustiveTest<3>(STRICT_TWO) && ExhaustiveTest<4>(STRICT_TWO) &&
+ ExhaustiveTest<0>(STRICT_THREE) && ExhaustiveTest<1>(STRICT_THREE) &&
+ ExhaustiveTest<2>(STRICT_THREE) && ExhaustiveTest<3>(STRICT_THREE) &&
+ ExhaustiveTest<4>(STRICT_THREE) && ExhaustiveTest<5>(STRICT_THREE);
+}
+
+static const size_t MAX_ELEMS = 6;
+
+template <size_t ArgCount>
+bool ExhaustiveTest(const char funcode[]) {
+ RootedValue v(cx);
+ EVAL(funcode, &v);
+
+ EVAL(CALL_CODES[ArgCount], &v);
+ Rooted<ArgumentsObject*> argsobj(cx,
+ &v.toObjectOrNull()->as<ArgumentsObject>());
+
+ JS::RootedValueArray<MAX_ELEMS> elems(cx);
+
+ for (size_t i = 0; i <= ArgCount; i++) {
+ for (size_t j = 0; j <= ArgCount - i; j++) {
+ ClearElements(elems);
+ CHECK(argsobj->maybeGetElements(i, j, elems.begin()));
+ for (size_t k = 0; k < j; k++) {
+ CHECK(elems[k].isInt32(i + k));
+ }
+ for (size_t k = j; k < MAX_ELEMS - 1; k++) {
+ CHECK(elems[k].isNull());
+ }
+ CHECK(elems[MAX_ELEMS - 1].isInt32(42));
+ }
+ }
+
+ return true;
+}
+
+template <size_t N>
+static void ClearElements(JS::RootedValueArray<N>& elems) {
+ for (size_t i = 0; i < elems.length() - 1; i++) {
+ elems[i].setNull();
+ }
+ elems[elems.length() - 1].setInt32(42);
+}
+END_TEST(testArgumentsObject)
diff --git a/js/src/jsapi-tests/testArrayBuffer.cpp b/js/src/jsapi-tests/testArrayBuffer.cpp
new file mode 100644
index 0000000000..fd74993893
--- /dev/null
+++ b/js/src/jsapi-tests/testArrayBuffer.cpp
@@ -0,0 +1,449 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ */
+
+#include "builtin/TestingFunctions.h"
+#include "js/Array.h" // JS::NewArrayObject
+#include "js/ArrayBuffer.h" // JS::{GetArrayBuffer{ByteLength,Data},IsArrayBufferObject,NewArrayBuffer{,WithContents},StealArrayBufferContents}
+#include "js/ArrayBufferMaybeShared.h"
+#include "js/CallAndConstruct.h"
+#include "js/Exception.h"
+#include "js/experimental/TypedData.h" // JS_New{Int32,Uint8}ArrayWithBuffer
+#include "js/friend/ErrorMessages.h" // JSMSG_*
+#include "js/MemoryFunctions.h"
+#include "js/PropertyAndElement.h" // JS_GetElement, JS_GetProperty, JS_SetElement
+#include "js/Realm.h"
+#include "jsapi-tests/tests.h"
+
+#include "vm/Realm-inl.h"
+
+BEGIN_TEST(testArrayBuffer_bug720949_steal) {
+ static const unsigned NUM_TEST_BUFFERS = 2;
+ static const unsigned MAGIC_VALUE_1 = 3;
+ static const unsigned MAGIC_VALUE_2 = 17;
+
+ JS::RootedObject buf_len1(cx), buf_len200(cx);
+ JS::RootedObject tarray_len1(cx), tarray_len200(cx);
+
+ uint32_t sizes[NUM_TEST_BUFFERS] = {sizeof(uint32_t), 200 * sizeof(uint32_t)};
+ JS::HandleObject testBuf[NUM_TEST_BUFFERS] = {buf_len1, buf_len200};
+ JS::HandleObject testArray[NUM_TEST_BUFFERS] = {tarray_len1, tarray_len200};
+
+ // Single-element ArrayBuffer (uses fixed slots for storage)
+ CHECK(buf_len1 = JS::NewArrayBuffer(cx, sizes[0]));
+ CHECK(tarray_len1 = JS_NewInt32ArrayWithBuffer(cx, testBuf[0], 0, -1));
+
+ CHECK(JS_SetElement(cx, testArray[0], 0, MAGIC_VALUE_1));
+
+ // Many-element ArrayBuffer (uses dynamic storage)
+ CHECK(buf_len200 = JS::NewArrayBuffer(cx, 200 * sizeof(uint32_t)));
+ CHECK(tarray_len200 = JS_NewInt32ArrayWithBuffer(cx, testBuf[1], 0, -1));
+
+ for (unsigned i = 0; i < NUM_TEST_BUFFERS; i++) {
+ JS::HandleObject obj = testBuf[i];
+ JS::HandleObject view = testArray[i];
+ uint32_t size = sizes[i];
+ JS::RootedValue v(cx);
+
+ // Byte lengths should all agree
+ CHECK(JS::IsArrayBufferObject(obj));
+ CHECK_EQUAL(JS::GetArrayBufferByteLength(obj), size);
+ CHECK(JS_GetProperty(cx, obj, "byteLength", &v));
+ CHECK(v.isInt32(size));
+ CHECK(JS_GetProperty(cx, view, "byteLength", &v));
+ CHECK(v.isInt32(size));
+
+ // Modifying the underlying data should update the value returned through
+ // the view
+ {
+ JS::AutoCheckCannotGC nogc;
+ bool sharedDummy;
+ uint8_t* data = JS::GetArrayBufferData(obj, &sharedDummy, nogc);
+ CHECK(data != nullptr);
+ *reinterpret_cast<uint32_t*>(data) = MAGIC_VALUE_2;
+ }
+ CHECK(JS_GetElement(cx, view, 0, &v));
+ CHECK(v.isInt32(MAGIC_VALUE_2));
+
+ // Steal the contents
+ void* contents = JS::StealArrayBufferContents(cx, obj);
+ CHECK(contents != nullptr);
+
+ CHECK(JS::IsDetachedArrayBufferObject(obj));
+
+ // Transfer to a new ArrayBuffer
+ JS::RootedObject dst(cx,
+ JS::NewArrayBufferWithContents(cx, size, contents));
+ CHECK(JS::IsArrayBufferObject(dst));
+ {
+ JS::AutoCheckCannotGC nogc;
+ bool sharedDummy;
+ (void)JS::GetArrayBufferData(obj, &sharedDummy, nogc);
+ }
+
+ JS::RootedObject dstview(cx, JS_NewInt32ArrayWithBuffer(cx, dst, 0, -1));
+ CHECK(dstview != nullptr);
+
+ CHECK_EQUAL(JS::GetArrayBufferByteLength(dst), size);
+ {
+ JS::AutoCheckCannotGC nogc;
+ bool sharedDummy;
+ uint8_t* data = JS::GetArrayBufferData(dst, &sharedDummy, nogc);
+ CHECK(data != nullptr);
+ CHECK_EQUAL(*reinterpret_cast<uint32_t*>(data), MAGIC_VALUE_2);
+ }
+ CHECK(JS_GetElement(cx, dstview, 0, &v));
+ CHECK(v.isInt32(MAGIC_VALUE_2));
+ }
+
+ return true;
+}
+END_TEST(testArrayBuffer_bug720949_steal)
+
+// Varying number of views of a buffer, to test the detachment weak pointers
+BEGIN_TEST(testArrayBuffer_bug720949_viewList) {
+ JS::RootedObject buffer(cx);
+
+ // No views
+ buffer = JS::NewArrayBuffer(cx, 2000);
+ buffer = nullptr;
+ GC(cx);
+
+ // One view.
+ {
+ buffer = JS::NewArrayBuffer(cx, 2000);
+ JS::RootedObject view(cx, JS_NewUint8ArrayWithBuffer(cx, buffer, 0, -1));
+ void* contents = JS::StealArrayBufferContents(cx, buffer);
+ CHECK(contents != nullptr);
+ JS_free(nullptr, contents);
+ GC(cx);
+ CHECK(hasDetachedBuffer(view));
+ CHECK(JS::IsDetachedArrayBufferObject(buffer));
+ view = nullptr;
+ GC(cx);
+ buffer = nullptr;
+ GC(cx);
+ }
+
+ // Two views
+ {
+ buffer = JS::NewArrayBuffer(cx, 2000);
+
+ JS::RootedObject view1(cx, JS_NewUint8ArrayWithBuffer(cx, buffer, 0, -1));
+ JS::RootedObject view2(cx, JS_NewUint8ArrayWithBuffer(cx, buffer, 1, 200));
+
+ // Remove, re-add a view
+ view2 = nullptr;
+ GC(cx);
+ view2 = JS_NewUint8ArrayWithBuffer(cx, buffer, 1, 200);
+
+ // Detach
+ void* contents = JS::StealArrayBufferContents(cx, buffer);
+ CHECK(contents != nullptr);
+ JS_free(nullptr, contents);
+
+ CHECK(hasDetachedBuffer(view1));
+ CHECK(hasDetachedBuffer(view2));
+ CHECK(JS::IsDetachedArrayBufferObject(buffer));
+
+ view1 = nullptr;
+ GC(cx);
+ view2 = nullptr;
+ GC(cx);
+ buffer = nullptr;
+ GC(cx);
+ }
+
+ return true;
+}
+
+static void GC(JSContext* cx) {
+ JS_GC(cx);
+ JS_GC(cx); // Trigger another to wait for background finalization to end
+}
+
+bool hasDetachedBuffer(JS::HandleObject obj) {
+ JS::RootedValue v(cx);
+ return JS_GetProperty(cx, obj, "byteLength", &v) && v.toInt32() == 0;
+}
+
+END_TEST(testArrayBuffer_bug720949_viewList)
+
+BEGIN_TEST(testArrayBuffer_customFreeFunc) {
+ ExternalData data("One two three four");
+
+ // The buffer takes ownership of the data.
+ JS::RootedObject buffer(
+ cx, JS::NewExternalArrayBuffer(cx, data.len(), data.contents(),
+ &ExternalData::freeCallback, &data));
+ CHECK(buffer);
+ CHECK(!data.wasFreed());
+
+ size_t len;
+ bool isShared;
+ uint8_t* bufferData;
+ JS::GetArrayBufferLengthAndData(buffer, &len, &isShared, &bufferData);
+ CHECK_EQUAL(len, data.len());
+ CHECK(bufferData == data.contents());
+ CHECK(strcmp(reinterpret_cast<char*>(bufferData), data.asString()) == 0);
+
+ buffer = nullptr;
+ JS_GC(cx);
+ JS_GC(cx);
+ CHECK(data.wasFreed());
+
+ return true;
+}
+END_TEST(testArrayBuffer_customFreeFunc)
+
+BEGIN_TEST(testArrayBuffer_staticContents) {
+ ExternalData data("One two three four");
+
+ // When not passing a free function, the buffer doesn't own the data.
+ JS::RootedObject buffer(
+ cx, JS::NewExternalArrayBuffer(cx, data.len(), data.contents(), nullptr));
+ CHECK(buffer);
+ CHECK(!data.wasFreed());
+
+ size_t len;
+ bool isShared;
+ uint8_t* bufferData;
+ JS::GetArrayBufferLengthAndData(buffer, &len, &isShared, &bufferData);
+ CHECK_EQUAL(len, data.len());
+ CHECK(bufferData == data.contents());
+ CHECK(strcmp(reinterpret_cast<char*>(bufferData), data.asString()) == 0);
+
+ buffer = nullptr;
+ JS_GC(cx);
+ JS_GC(cx);
+ CHECK(!data.wasFreed());
+
+ data.free();
+ return true;
+}
+END_TEST(testArrayBuffer_staticContents)
+
+BEGIN_TEST(testArrayBuffer_stealDetachExternal) {
+ static const char dataBytes[] = "One two three four";
+ ExternalData data(dataBytes);
+ JS::RootedObject buffer(
+ cx, JS::NewExternalArrayBuffer(cx, data.len(), data.contents(),
+ &ExternalData::freeCallback, &data));
+ CHECK(buffer);
+ CHECK(!data.wasFreed());
+
+ void* stolenContents = JS::StealArrayBufferContents(cx, buffer);
+
+ // External buffers are stealable: the data is copied into freshly allocated
+ // memory, and the buffer's data pointer is cleared (immediately freeing the
+ // data) and the buffer is marked as detached.
+ CHECK(stolenContents != data.contents());
+ CHECK(strcmp(reinterpret_cast<char*>(stolenContents), dataBytes) == 0);
+ CHECK(data.wasFreed());
+ CHECK(JS::IsDetachedArrayBufferObject(buffer));
+
+ JS_free(cx, stolenContents);
+ return true;
+}
+END_TEST(testArrayBuffer_stealDetachExternal)
+
+BEGIN_TEST(testArrayBuffer_serializeExternal) {
+ JS::RootedValue serializeValue(cx);
+
+ {
+ JS::RootedFunction serialize(cx);
+ serialize =
+ JS_NewFunction(cx, js::testingFunc_serialize, 1, 0, "serialize");
+ CHECK(serialize);
+
+ serializeValue.setObject(*JS_GetFunctionObject(serialize));
+ }
+
+ ExternalData data("One two three four");
+ JS::RootedObject externalBuffer(
+ cx, JS::NewExternalArrayBuffer(cx, data.len(), data.contents(),
+ &ExternalData::freeCallback, &data));
+ CHECK(externalBuffer);
+ CHECK(!data.wasFreed());
+
+ JS::RootedValue v(cx, JS::ObjectValue(*externalBuffer));
+ JS::RootedObject transferMap(cx,
+ JS::NewArrayObject(cx, JS::HandleValueArray(v)));
+ CHECK(transferMap);
+
+ JS::RootedValueArray<2> args(cx);
+ args[0].setObject(*externalBuffer);
+ args[1].setObject(*transferMap);
+
+ // serialize(externalBuffer, [externalBuffer]) should throw for an unhandled
+ // BufferContents kind.
+ CHECK(!JS::Call(cx, JS::UndefinedHandleValue, serializeValue,
+ JS::HandleValueArray(args), &v));
+
+ JS::ExceptionStack exnStack(cx);
+ CHECK(JS::StealPendingExceptionStack(cx, &exnStack));
+
+ JS::ErrorReportBuilder report(cx);
+ CHECK(report.init(cx, exnStack, JS::ErrorReportBuilder::NoSideEffects));
+
+ CHECK_EQUAL(report.report()->errorNumber,
+ static_cast<unsigned int>(JSMSG_SC_NOT_TRANSFERABLE));
+
+ // Data should have been left alone.
+ CHECK(!data.wasFreed());
+
+ v.setNull();
+ transferMap = nullptr;
+ args[0].setNull();
+ args[1].setNull();
+ externalBuffer = nullptr;
+
+ JS_GC(cx);
+ JS_GC(cx);
+ CHECK(data.wasFreed());
+
+ return true;
+}
+END_TEST(testArrayBuffer_serializeExternal)
+
+BEGIN_TEST(testArrayBuffer_copyData) {
+ ExternalData data1("One two three four");
+ JS::RootedObject buffer1(cx, JS::NewExternalArrayBuffer(
+ cx, data1.len(), data1.contents(), nullptr));
+
+ CHECK(buffer1);
+
+ ExternalData data2("Six");
+ JS::RootedObject buffer2(cx, JS::NewExternalArrayBuffer(
+ cx, data2.len(), data2.contents(), nullptr));
+
+ CHECK(buffer2);
+
+ // Check we can't copy from a larger to a smaller buffer.
+ CHECK(!JS::ArrayBufferCopyData(cx, buffer2, 0, buffer1, 0, data1.len()));
+
+ // Verify expected exception is thrown.
+ {
+ JS::ExceptionStack exnStack(cx);
+ CHECK(JS::StealPendingExceptionStack(cx, &exnStack));
+
+ JS::ErrorReportBuilder report(cx);
+ CHECK(report.init(cx, exnStack, JS::ErrorReportBuilder::NoSideEffects));
+
+ CHECK_EQUAL(report.report()->errorNumber,
+ static_cast<unsigned int>(JSMSG_ARRAYBUFFER_COPY_RANGE));
+ }
+
+ CHECK(JS::ArrayBufferCopyData(
+ cx, buffer1, 0, buffer2, 0,
+ data2.len() - 1 /* don't copy null terminator */));
+
+ {
+ size_t len;
+ bool isShared;
+ uint8_t* bufferData;
+ JS::GetArrayBufferLengthAndData(buffer1, &len, &isShared, &bufferData);
+
+ ExternalData expected1("Six two three four");
+
+ fprintf(stderr, "expected %s actual %s\n", expected1.asString(),
+ bufferData);
+
+ CHECK_EQUAL(len, expected1.len());
+ CHECK_EQUAL(memcmp(expected1.contents(), bufferData, expected1.len()), 0);
+ }
+
+ return true;
+}
+END_TEST(testArrayBuffer_copyData)
+
+BEGIN_TEST(testArrayBuffer_copyDataAcrossGlobals) {
+ JS::RootedObject otherGlobal(cx, createGlobal(nullptr));
+ if (!otherGlobal) {
+ return false;
+ }
+
+ ExternalData data1("One two three four");
+ JS::RootedObject buffer1(cx);
+ {
+ js::AutoRealm realm(cx, otherGlobal);
+ buffer1 =
+ JS::NewExternalArrayBuffer(cx, data1.len(), data1.contents(), nullptr);
+ }
+ CHECK(buffer1);
+ CHECK(JS_WrapObject(cx, &buffer1));
+
+ ExternalData data2("Six");
+ JS::RootedObject buffer2(cx, JS::NewExternalArrayBuffer(
+ cx, data2.len(), data2.contents(), nullptr));
+
+ CHECK(buffer2);
+
+ // Check we can't copy from a larger to a smaller buffer.
+ CHECK(!JS::ArrayBufferCopyData(cx, buffer2, 0, buffer1, 0, data1.len()));
+
+ // Verify expected exception is thrown.
+ {
+ JS::ExceptionStack exnStack(cx);
+ CHECK(JS::StealPendingExceptionStack(cx, &exnStack));
+
+ JS::ErrorReportBuilder report(cx);
+ CHECK(report.init(cx, exnStack, JS::ErrorReportBuilder::NoSideEffects));
+
+ CHECK_EQUAL(report.report()->errorNumber,
+ static_cast<unsigned int>(JSMSG_ARRAYBUFFER_COPY_RANGE));
+ }
+
+ CHECK(JS::ArrayBufferCopyData(
+ cx, buffer1, 0, buffer2, 0,
+ data2.len() - 1 /* don't copy null terminator */));
+
+ {
+ JS::RootedObject unwrappedBuffer1(
+ cx, JS::UnwrapArrayBufferMaybeShared(buffer1));
+ CHECK(unwrappedBuffer1);
+
+ size_t len;
+ bool isShared;
+ uint8_t* bufferData;
+ JS::GetArrayBufferLengthAndData(unwrappedBuffer1, &len, &isShared,
+ &bufferData);
+
+ ExternalData expected1("Six two three four");
+
+ fprintf(stderr, "expected %s actual %s\n", expected1.asString(),
+ bufferData);
+
+ CHECK_EQUAL(len, expected1.len());
+ CHECK_EQUAL(memcmp(expected1.contents(), bufferData, expected1.len()), 0);
+ }
+
+ return true;
+}
+END_TEST(testArrayBuffer_copyDataAcrossGlobals)
+
+BEGIN_TEST(testArrayBuffer_ArrayBufferClone) {
+ ExternalData data("One two three four");
+ JS::RootedObject externalBuffer(
+ cx, JS::NewExternalArrayBuffer(cx, data.len(), data.contents(), nullptr));
+
+ CHECK(externalBuffer);
+
+ size_t lengthToCopy = 3;
+ JS::RootedObject clonedBuffer(
+ cx, JS::ArrayBufferClone(cx, externalBuffer, 4, lengthToCopy));
+ CHECK(clonedBuffer);
+
+ size_t len;
+ bool isShared;
+ uint8_t* bufferData;
+ JS::GetArrayBufferLengthAndData(clonedBuffer, &len, &isShared, &bufferData);
+
+ CHECK_EQUAL(len, lengthToCopy);
+
+ ExternalData expectedData("two");
+ CHECK_EQUAL(memcmp(expectedData.contents(), bufferData, len), 0);
+
+ return true;
+}
+END_TEST(testArrayBuffer_ArrayBufferClone)
diff --git a/js/src/jsapi-tests/testArrayBufferOrViewAPI.cpp b/js/src/jsapi-tests/testArrayBufferOrViewAPI.cpp
new file mode 100644
index 0000000000..6b9189af04
--- /dev/null
+++ b/js/src/jsapi-tests/testArrayBufferOrViewAPI.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 <type_traits>
+
+#include "jsfriendapi.h"
+
+#include "js/ArrayBuffer.h" // JS::NewArrayBuffer
+#include "js/experimental/TypedData.h"
+#include "js/ScalarType.h" // JS::Scalar::Type
+#include "jsapi-tests/tests.h"
+
+#include "vm/Realm-inl.h"
+
+using namespace js;
+
+template <class ViewType>
+static JSObject* CreateObj(JSContext* cx, size_t len) {
+ return ViewType::create(cx, len).asObject();
+}
+
+template <>
+JSObject* CreateObj<JS::DataView>(JSContext* cx, size_t len) {
+ JS::Rooted<JSObject*> buffer(cx, JS::NewArrayBuffer(cx, len));
+ if (!buffer) {
+ return nullptr;
+ }
+ return JS_NewDataView(cx, buffer, 0, len);
+}
+
+BEGIN_TEST(testArrayBufferOrView_type) {
+ JS::RealmOptions options;
+ JS::RootedObject otherGlobal(
+ cx, JS_NewGlobalObject(cx, basicGlobalClass(), nullptr,
+ JS::DontFireOnNewGlobalHook, options));
+ CHECK(otherGlobal);
+
+ CHECK((TestType<JS::TypedArray<Scalar::Uint8>>(cx, otherGlobal)));
+ CHECK((TestType<JS::TypedArray<Scalar::Int8>>(cx, otherGlobal)));
+ CHECK((TestType<JS::TypedArray<Scalar::Uint8Clamped>>(cx, otherGlobal)));
+ CHECK((TestType<JS::TypedArray<Scalar::Uint16>>(cx, otherGlobal)));
+ CHECK((TestType<JS::TypedArray<Scalar::Int16>>(cx, otherGlobal)));
+ CHECK((TestType<JS::TypedArray<Scalar::Uint32>>(cx, otherGlobal)));
+ CHECK((TestType<JS::TypedArray<Scalar::Int32>>(cx, otherGlobal)));
+ CHECK((TestType<JS::TypedArray<Scalar::Float32>>(cx, otherGlobal)));
+ CHECK((TestType<JS::TypedArray<Scalar::Float64>>(cx, otherGlobal)));
+ CHECK((TestType<JS::DataView>(cx, otherGlobal)));
+ CHECK((TestType<JS::ArrayBuffer>(cx, otherGlobal)));
+
+ return true;
+}
+
+template <class APIType>
+bool TestType(JSContext* cx, Handle<JSObject*> otherGlobal) {
+ JS::Rooted<JSObject*> obj(cx, CreateObj<APIType>(cx, 8));
+ CHECK(obj);
+
+ // Any of these should be creatable as an ArrayBufferOrView.
+ JS::Rooted<JS::ArrayBufferOrView> abov(
+ cx, JS::ArrayBufferOrView::fromObject(obj));
+ CHECK(abov);
+
+ // And that should allow unwrapping as well.
+ abov = JS::ArrayBufferOrView::unwrap(obj);
+ CHECK(abov);
+
+ if constexpr (!std::is_same_v<APIType, JS::Uint16Array>) {
+ // Check that we can't make an API object of a different type.
+ JS::Rooted<JS::Uint16Array> nope(cx, JS::Uint16Array::unwrap(obj));
+ CHECK(!nope);
+
+ // And that we can't make an API object from an object of a different type.
+ JS::Rooted<JSObject*> u16array(cx, CreateObj<JS::Uint16Array>(cx, 10));
+ CHECK(u16array);
+ auto deny = APIType::fromObject(u16array);
+ CHECK(!deny);
+ deny = APIType::unwrap(u16array);
+ CHECK(!deny);
+ }
+
+ CHECK_EQUAL(abov.asObject(), obj);
+
+ JS::Rooted<JSObject*> wrapped(cx);
+ {
+ AutoRealm ar(cx, otherGlobal);
+ wrapped = CreateObj<APIType>(cx, 8); // Not wrapped yet!
+ CHECK(wrapped);
+ }
+ CHECK(wrapped->compartment() == otherGlobal->compartment());
+ CHECK(JS_WrapObject(cx, &wrapped)); // Now it's wrapped.
+ CHECK(wrapped->compartment() == global->compartment());
+
+ abov = JS::ArrayBufferOrView::fromObject(wrapped);
+ CHECK(!abov);
+ abov = JS::ArrayBufferOrView::unwrap(wrapped);
+ CHECK(abov);
+
+ JS::Rooted<APIType> dummy(cx, APIType::fromObject(obj));
+ CHECK(obj);
+ CHECK(dummy);
+ CHECK(dummy.asObject());
+
+ return true;
+}
+
+END_TEST(testArrayBufferOrView_type)
diff --git a/js/src/jsapi-tests/testArrayBufferView.cpp b/js/src/jsapi-tests/testArrayBufferView.cpp
new file mode 100644
index 0000000000..60cae01e1b
--- /dev/null
+++ b/js/src/jsapi-tests/testArrayBufferView.cpp
@@ -0,0 +1,177 @@
+/* -*- 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 "jsfriendapi.h"
+
+#include "js/ArrayBuffer.h" // JS::NewArrayBuffer
+#include "js/experimental/TypedData.h" // JS_GetArrayBufferView{Type,ByteLength,Data}, JS_GetObjectAsArrayBufferView, JS_GetObjectAs{{Ui,I}nt{8,16,32},Float{32,64}}Array, JS_IsArrayBufferViewObject, JS_NewDataView, JS_New{{Ui,I}nt{8,16,32},Float{32,64},Uint8Clamped}Array
+#include "js/GlobalObject.h" // JS_NewGlobalObject
+#include "js/PropertyAndElement.h" // JS_SetProperty
+#include "js/ScalarType.h" // js::Scalar::Type
+#include "jsapi-tests/tests.h"
+#include "vm/ProxyObject.h"
+#include "vm/Realm.h"
+#include "vm/Uint8Clamped.h" // js::uint8_clamped_t
+
+#include "vm/JSObject-inl.h"
+#include "vm/Realm-inl.h"
+#include "vm/TypedArrayObject-inl.h" // TypeIDOfType
+
+using namespace js;
+
+template <class ViewType>
+static JSObject* Create(JSContext* cx, size_t len) {
+ return ViewType::create(cx, len).asObject();
+}
+
+template <>
+JSObject* Create<JS::DataView>(JSContext* cx, size_t len) {
+ JS::Rooted<JSObject*> buffer(cx, JS::NewArrayBuffer(cx, len));
+ if (!buffer) {
+ return nullptr;
+ }
+ return JS_NewDataView(cx, buffer, 0, len);
+}
+
+template <class T>
+struct InternalType {
+ using Type = uint8_t;
+};
+
+#define INT_TYPE(ExternalType, NativeType, Name) \
+ template <> \
+ struct InternalType<JS::TypedArray<js::Scalar::Name>> { \
+ using Type = NativeType; \
+ };
+JS_FOR_EACH_TYPED_ARRAY(INT_TYPE)
+#undef INT_TYPE
+
+BEGIN_TEST(testArrayBufferView_type) {
+ CHECK((TestViewType<JS::TypedArray<Scalar::Uint8>, 7, 7>(cx)));
+ CHECK((TestViewType<JS::TypedArray<Scalar::Int8>, 33, 33>(cx)));
+ CHECK((TestViewType<JS::TypedArray<Scalar::Uint8Clamped>, 7, 7>(cx)));
+ CHECK((TestViewType<JS::TypedArray<Scalar::Uint16>, 3, 6>(cx)));
+ CHECK((TestViewType<JS::TypedArray<Scalar::Int16>, 17, 34>(cx)));
+ CHECK((TestViewType<JS::TypedArray<Scalar::Uint32>, 15, 60>(cx)));
+ CHECK((TestViewType<JS::TypedArray<Scalar::Int32>, 8, 32>(cx)));
+ CHECK((TestViewType<JS::TypedArray<Scalar::Float32>, 7, 28>(cx)));
+ CHECK((TestViewType<JS::TypedArray<Scalar::Float64>, 9, 72>(cx)));
+ CHECK((TestViewType<JS::DataView, 8, 8>(cx)));
+
+ JS::Rooted<JS::Value> hasTypedObject(cx);
+ EVAL("typeof TypedObject !== 'undefined'", &hasTypedObject);
+ if (hasTypedObject.isTrue()) {
+ JS::Rooted<JS::Value> tval(cx);
+ EVAL(
+ "var T = new TypedObject.StructType({ x: TypedObject.uint32 });\n"
+ "new T(new ArrayBuffer(4));",
+ &tval);
+
+ JS::Rooted<JSObject*> tobj(cx, &tval.toObject());
+ CHECK(!JS_IsArrayBufferViewObject(tobj));
+ }
+
+ return true;
+}
+
+template <class T>
+struct ScalarTypeOf {
+ static constexpr js::Scalar::Type value = js::Scalar::MaxTypedArrayViewType;
+};
+
+template <js::Scalar::Type EType>
+struct ScalarTypeOf<JS::TypedArray<EType>> {
+ static constexpr js::Scalar::Type value = EType;
+};
+template <class ViewType, uint32_t ExpectedLength, uint32_t ExpectedByteLength>
+bool TestViewType(JSContext* cx) {
+ JS::Rooted<JSObject*> obj(cx, Create<ViewType>(cx, ExpectedLength));
+ CHECK(obj);
+
+ CHECK(JS_IsArrayBufferViewObject(obj));
+
+ CHECK(JS_GetArrayBufferViewByteLength(obj) == ExpectedByteLength);
+
+ {
+ JS::AutoCheckCannotGC nogc;
+ bool shared1;
+ JSObject* unwrapped = js::UnwrapArrayBufferView(obj);
+ uint8_t* data1 =
+ (uint8_t*)JS_GetArrayBufferViewData(unwrapped, &shared1, nogc);
+
+ auto view = ViewType::unwrap(obj);
+ CHECK(JS_GetArrayBufferViewType(obj) == ScalarTypeOf<ViewType>::value);
+
+ if (JS_IsTypedArrayObject(unwrapped)) {
+ CHECK(unwrapped->as<TypedArrayObject>().type() ==
+ TypeIDOfType<typename InternalType<ViewType>::Type>::id);
+ }
+
+ bool shared2;
+ size_t len;
+ uint8_t* data2 =
+ reinterpret_cast<uint8_t*>(view.getLengthAndData(&len, &shared2, nogc));
+ CHECK(obj == view.asObject());
+ CHECK(data1 == data2);
+ CHECK(shared1 == shared2);
+ CHECK(len == ExpectedLength);
+
+ JS::Heap<ViewType> hv(view);
+
+ bool shared3;
+ size_t len3;
+ uint8_t* data3 =
+ reinterpret_cast<uint8_t*>(hv.getLengthAndData(&len3, &shared3, nogc));
+ CHECK(obj == hv.asObject());
+ CHECK(data1 == data3);
+ CHECK(shared1 == shared3);
+ CHECK(len3 == ExpectedLength);
+ }
+
+ JS::RealmOptions options;
+ JS::RootedObject otherGlobal(
+ cx, JS_NewGlobalObject(cx, basicGlobalClass(), nullptr,
+ JS::DontFireOnNewGlobalHook, options));
+ CHECK(otherGlobal);
+
+ JS::Rooted<JSObject*> buffer(cx);
+ {
+ AutoRealm ar(cx, otherGlobal);
+ buffer = JS::NewArrayBuffer(cx, 8);
+ CHECK(buffer);
+ CHECK(buffer->as<ArrayBufferObject>().byteLength() == 8);
+ }
+ CHECK(buffer->compartment() == otherGlobal->compartment());
+ CHECK(JS_WrapObject(cx, &buffer));
+ CHECK(buffer->compartment() == global->compartment());
+
+ JS::Rooted<JSObject*> dataview(cx, JS_NewDataView(cx, buffer, 4, 4));
+ CHECK(dataview);
+ CHECK(dataview->is<ProxyObject>());
+
+ JS::Rooted<JS::Value> val(cx);
+
+ val = ObjectValue(*dataview);
+ CHECK(JS_SetProperty(cx, global, "view", val));
+
+ EVAL("view.buffer", &val);
+ CHECK(val.toObject().is<ProxyObject>());
+
+ CHECK(dataview->compartment() == global->compartment());
+ JS::Rooted<JSObject*> otherView(cx, js::UncheckedUnwrap(dataview));
+ CHECK(otherView->compartment() == otherGlobal->compartment());
+ JS::Rooted<JSObject*> otherBuffer(cx, js::UncheckedUnwrap(&val.toObject()));
+ CHECK(otherBuffer->compartment() == otherGlobal->compartment());
+
+ EVAL("Object.getPrototypeOf(view) === DataView.prototype", &val);
+ CHECK(val.toBoolean() == true);
+
+ return true;
+}
+
+END_TEST(testArrayBufferView_type)
diff --git a/js/src/jsapi-tests/testArrayBufferWithUserOwnedContents.cpp b/js/src/jsapi-tests/testArrayBufferWithUserOwnedContents.cpp
new file mode 100644
index 0000000000..9a360e38be
--- /dev/null
+++ b/js/src/jsapi-tests/testArrayBufferWithUserOwnedContents.cpp
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ */
+
+#include <stdint.h> // uint32_t
+
+#include "js/ArrayBuffer.h" // JS::{DetachArrayBuffer,GetArrayBuffer{ByteLength,Data},IsArrayBufferObject,NewArrayBufferWithUserOwnedContents}
+#include "js/GCAPI.h" // JS::AutoCheckCannotGC, JS_GC
+#include "js/RootingAPI.h" // JS::Rooted
+#include "jsapi-tests/tests.h"
+#include "vm/ArrayBufferObject.h"
+
+char testData[] =
+ "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+constexpr size_t testDataLength = sizeof(testData);
+
+static void GC(JSContext* cx) {
+ JS_GC(cx);
+ // Trigger another to wait for background finalization to end.
+ JS_GC(cx);
+}
+
+BEGIN_TEST(testArrayBufferWithUserOwnedContents) {
+ JS::Rooted<JSObject*> obj(cx, JS::NewArrayBufferWithUserOwnedContents(
+ cx, testDataLength, testData));
+ GC(cx);
+ CHECK(VerifyObject(obj, testDataLength));
+ GC(cx);
+ JS::DetachArrayBuffer(cx, obj);
+ GC(cx);
+ CHECK(VerifyObject(obj, 0));
+
+ return true;
+}
+
+bool VerifyObject(JS::HandleObject obj, uint32_t length) {
+ JS::AutoCheckCannotGC nogc;
+
+ CHECK(obj);
+ CHECK(JS::IsArrayBufferObject(obj));
+ CHECK_EQUAL(JS::GetArrayBufferByteLength(obj), length);
+ bool sharedDummy;
+ const char* data = reinterpret_cast<const char*>(
+ JS::GetArrayBufferData(obj, &sharedDummy, nogc));
+ if (length == testDataLength) {
+ CHECK(data);
+ CHECK(testData == data);
+ }
+
+ return true;
+}
+
+END_TEST(testArrayBufferWithUserOwnedContents)
diff --git a/js/src/jsapi-tests/testAssemblerBuffer.cpp b/js/src/jsapi-tests/testAssemblerBuffer.cpp
new file mode 100644
index 0000000000..7979e6822a
--- /dev/null
+++ b/js/src/jsapi-tests/testAssemblerBuffer.cpp
@@ -0,0 +1,588 @@
+/* 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 <stdlib.h>
+
+#include "jit/shared/IonAssemblerBufferWithConstantPools.h"
+#include "jsapi-tests/tests.h"
+#include "vm/JSAtom.h"
+
+// Tests for classes in:
+//
+// jit/shared/IonAssemblerBuffer.h
+// jit/shared/IonAssemblerBufferWithConstantPools.h
+//
+// Classes in js::jit tested:
+//
+// BufferOffset
+// BufferSlice (implicitly)
+// AssemblerBuffer
+//
+// BranchDeadlineSet
+// Pool (implicitly)
+// AssemblerBufferWithConstantPools
+//
+
+BEGIN_TEST(testAssemblerBuffer_BufferOffset) {
+ using js::jit::BufferOffset;
+
+ BufferOffset off1;
+ BufferOffset off2(10);
+
+ CHECK(!off1.assigned());
+ CHECK(off2.assigned());
+ CHECK_EQUAL(off2.getOffset(), 10);
+ off1 = off2;
+ CHECK(off1.assigned());
+ CHECK_EQUAL(off1.getOffset(), 10);
+
+ return true;
+}
+END_TEST(testAssemblerBuffer_BufferOffset)
+
+BEGIN_TEST(testAssemblerBuffer_AssemblerBuffer) {
+ using js::jit::BufferOffset;
+ typedef js::jit::AssemblerBuffer<5 * sizeof(uint32_t), uint32_t> AsmBuf;
+
+ AsmBuf ab;
+ CHECK(ab.isAligned(16));
+ CHECK_EQUAL(ab.size(), 0u);
+ CHECK_EQUAL(ab.nextOffset().getOffset(), 0);
+ CHECK(!ab.oom());
+
+ BufferOffset off1 = ab.putInt(1000017);
+ CHECK_EQUAL(off1.getOffset(), 0);
+ CHECK_EQUAL(ab.size(), 4u);
+ CHECK_EQUAL(ab.nextOffset().getOffset(), 4);
+ CHECK(!ab.isAligned(16));
+ CHECK(ab.isAligned(4));
+ CHECK(ab.isAligned(1));
+ CHECK_EQUAL(*ab.getInst(off1), 1000017u);
+
+ BufferOffset off2 = ab.putInt(1000018);
+ CHECK_EQUAL(off2.getOffset(), 4);
+
+ BufferOffset off3 = ab.putInt(1000019);
+ CHECK_EQUAL(off3.getOffset(), 8);
+
+ BufferOffset off4 = ab.putInt(1000020);
+ CHECK_EQUAL(off4.getOffset(), 12);
+ CHECK_EQUAL(ab.size(), 16u);
+ CHECK_EQUAL(ab.nextOffset().getOffset(), 16);
+
+ // Last one in the slice.
+ BufferOffset off5 = ab.putInt(1000021);
+ CHECK_EQUAL(off5.getOffset(), 16);
+ CHECK_EQUAL(ab.size(), 20u);
+ CHECK_EQUAL(ab.nextOffset().getOffset(), 20);
+
+ BufferOffset off6 = ab.putInt(1000022);
+ CHECK_EQUAL(off6.getOffset(), 20);
+ CHECK_EQUAL(ab.size(), 24u);
+ CHECK_EQUAL(ab.nextOffset().getOffset(), 24);
+
+ // Reference previous slice. Excercise the finger.
+ CHECK_EQUAL(*ab.getInst(off1), 1000017u);
+ CHECK_EQUAL(*ab.getInst(off6), 1000022u);
+ CHECK_EQUAL(*ab.getInst(off1), 1000017u);
+ CHECK_EQUAL(*ab.getInst(off5), 1000021u);
+
+ // Too much data for one slice.
+ const uint32_t fixdata[] = {2000036, 2000037, 2000038,
+ 2000039, 2000040, 2000041};
+
+ // Split payload across multiple slices.
+ CHECK_EQUAL(ab.nextOffset().getOffset(), 24);
+ BufferOffset good1 = ab.putBytesLarge(sizeof(fixdata), fixdata);
+ CHECK_EQUAL(good1.getOffset(), 24);
+ CHECK_EQUAL(ab.nextOffset().getOffset(), 48);
+ CHECK_EQUAL(*ab.getInst(good1), 2000036u);
+ CHECK_EQUAL(*ab.getInst(BufferOffset(32)), 2000038u);
+ CHECK_EQUAL(*ab.getInst(BufferOffset(36)), 2000039u);
+ CHECK_EQUAL(*ab.getInst(BufferOffset(40)), 2000040u);
+ CHECK_EQUAL(*ab.getInst(BufferOffset(44)), 2000041u);
+
+ return true;
+}
+END_TEST(testAssemblerBuffer_AssemblerBuffer)
+
+BEGIN_TEST(testAssemblerBuffer_BranchDeadlineSet) {
+ typedef js::jit::BranchDeadlineSet<3> DLSet;
+ using js::jit::BufferOffset;
+
+ js::LifoAlloc alloc(1024);
+ DLSet dls(alloc);
+
+ CHECK(dls.empty());
+ CHECK(alloc.isEmpty()); // Constructor must be infallible.
+ CHECK_EQUAL(dls.size(), 0u);
+ CHECK_EQUAL(dls.maxRangeSize(), 0u);
+
+ // Removing non-existant deadline is OK.
+ dls.removeDeadline(1, BufferOffset(7));
+
+ // Add deadlines in increasing order as intended. This is optimal.
+ dls.addDeadline(1, BufferOffset(10));
+ CHECK(!dls.empty());
+ CHECK_EQUAL(dls.size(), 1u);
+ CHECK_EQUAL(dls.maxRangeSize(), 1u);
+ CHECK_EQUAL(dls.earliestDeadline().getOffset(), 10);
+ CHECK_EQUAL(dls.earliestDeadlineRange(), 1u);
+
+ // Removing non-existant deadline is OK.
+ dls.removeDeadline(1, BufferOffset(7));
+ dls.removeDeadline(1, BufferOffset(17));
+ dls.removeDeadline(0, BufferOffset(10));
+ CHECK_EQUAL(dls.size(), 1u);
+ CHECK_EQUAL(dls.maxRangeSize(), 1u);
+
+ // Two identical deadlines for different ranges.
+ dls.addDeadline(2, BufferOffset(10));
+ CHECK(!dls.empty());
+ CHECK_EQUAL(dls.size(), 2u);
+ CHECK_EQUAL(dls.maxRangeSize(), 1u);
+ CHECK_EQUAL(dls.earliestDeadline().getOffset(), 10);
+
+ // It doesn't matter which range earliestDeadlineRange() reports first,
+ // but it must report both.
+ if (dls.earliestDeadlineRange() == 1) {
+ dls.removeDeadline(1, BufferOffset(10));
+ CHECK_EQUAL(dls.earliestDeadline().getOffset(), 10);
+ CHECK_EQUAL(dls.earliestDeadlineRange(), 2u);
+ } else {
+ CHECK_EQUAL(dls.earliestDeadlineRange(), 2u);
+ dls.removeDeadline(2, BufferOffset(10));
+ CHECK_EQUAL(dls.earliestDeadline().getOffset(), 10);
+ CHECK_EQUAL(dls.earliestDeadlineRange(), 1u);
+ }
+
+ // Add deadline which is the front of range 0, but not the global earliest.
+ dls.addDeadline(0, BufferOffset(20));
+ CHECK_EQUAL(dls.earliestDeadline().getOffset(), 10);
+ CHECK(dls.earliestDeadlineRange() > 0);
+
+ // Non-optimal add to front of single-entry range 0.
+ dls.addDeadline(0, BufferOffset(15));
+ CHECK_EQUAL(dls.earliestDeadline().getOffset(), 10);
+ CHECK(dls.earliestDeadlineRange() > 0);
+
+ // Append to 2-entry range 0.
+ dls.addDeadline(0, BufferOffset(30));
+ CHECK_EQUAL(dls.earliestDeadline().getOffset(), 10);
+ CHECK(dls.earliestDeadlineRange() > 0);
+
+ // Add penultimate entry.
+ dls.addDeadline(0, BufferOffset(25));
+ CHECK_EQUAL(dls.earliestDeadline().getOffset(), 10);
+ CHECK(dls.earliestDeadlineRange() > 0);
+
+ // Prepend, stealing earliest from other range.
+ dls.addDeadline(0, BufferOffset(5));
+ CHECK_EQUAL(dls.earliestDeadline().getOffset(), 5);
+ CHECK_EQUAL(dls.earliestDeadlineRange(), 0u);
+
+ // Remove central element.
+ dls.removeDeadline(0, BufferOffset(20));
+ CHECK_EQUAL(dls.earliestDeadline().getOffset(), 5);
+ CHECK_EQUAL(dls.earliestDeadlineRange(), 0u);
+
+ // Remove front, giving back the lead.
+ dls.removeDeadline(0, BufferOffset(5));
+ CHECK_EQUAL(dls.earliestDeadline().getOffset(), 10);
+ CHECK(dls.earliestDeadlineRange() > 0);
+
+ // Remove front, giving back earliest to range 0.
+ dls.removeDeadline(dls.earliestDeadlineRange(), BufferOffset(10));
+ CHECK_EQUAL(dls.earliestDeadline().getOffset(), 15);
+ CHECK_EQUAL(dls.earliestDeadlineRange(), 0u);
+
+ // Remove tail.
+ dls.removeDeadline(0, BufferOffset(30));
+ CHECK_EQUAL(dls.earliestDeadline().getOffset(), 15);
+ CHECK_EQUAL(dls.earliestDeadlineRange(), 0u);
+
+ // Now range 0 = [15, 25].
+ CHECK_EQUAL(dls.size(), 2u);
+ dls.removeDeadline(0, BufferOffset(25));
+ dls.removeDeadline(0, BufferOffset(15));
+ CHECK(dls.empty());
+
+ return true;
+}
+END_TEST(testAssemblerBuffer_BranchDeadlineSet)
+
+// Mock Assembler class for testing the AssemblerBufferWithConstantPools
+// callbacks.
+namespace {
+
+struct TestAssembler;
+
+typedef js::jit::AssemblerBufferWithConstantPools<
+ /* SliceSize */ 5 * sizeof(uint32_t),
+ /* InstSize */ 4,
+ /* Inst */ uint32_t,
+ /* Asm */ TestAssembler,
+ /* NumShortBranchRanges */ 3>
+ AsmBufWithPool;
+
+struct TestAssembler {
+ // Mock instruction set:
+ //
+ // 0x1111xxxx - align filler instructions.
+ // 0x2222xxxx - manually inserted 'arith' instructions.
+ // 0xaaaaxxxx - noop filler instruction.
+ // 0xb0bbxxxx - branch xxxx bytes forward. (Pool guard).
+ // 0xb1bbxxxx - branch xxxx bytes forward. (Short-range branch).
+ // 0xb2bbxxxx - branch xxxx bytes forward. (Veneer branch).
+ // 0xb3bbxxxx - branch xxxx bytes forward. (Patched short-range branch).
+ // 0xc0ccxxxx - constant pool load (uninitialized).
+ // 0xc1ccxxxx - constant pool load to index xxxx.
+ // 0xc2ccxxxx - constant pool load xxxx bytes ahead.
+ // 0xffffxxxx - pool header with xxxx bytes.
+
+ static const unsigned BranchRange = 36;
+
+ static void InsertIndexIntoTag(uint8_t* load_, uint32_t index) {
+ uint32_t* load = reinterpret_cast<uint32_t*>(load_);
+ MOZ_ASSERT(*load == 0xc0cc0000,
+ "Expected uninitialized constant pool load");
+ MOZ_ASSERT(index < 0x10000);
+ *load = 0xc1cc0000 + index;
+ }
+
+ static void PatchConstantPoolLoad(void* loadAddr, void* constPoolAddr) {
+ uint32_t* load = reinterpret_cast<uint32_t*>(loadAddr);
+ uint32_t index = *load & 0xffff;
+ MOZ_ASSERT(*load == (0xc1cc0000 | index),
+ "Expected constant pool load(index)");
+ ptrdiff_t offset = reinterpret_cast<uint8_t*>(constPoolAddr) -
+ reinterpret_cast<uint8_t*>(loadAddr);
+ offset += index * 4;
+ MOZ_ASSERT(offset % 4 == 0, "Unaligned constant pool");
+ MOZ_ASSERT(offset > 0 && offset < 0x10000, "Pool out of range");
+ *load = 0xc2cc0000 + offset;
+ }
+
+ static void WritePoolGuard(js::jit::BufferOffset branch, uint32_t* dest,
+ js::jit::BufferOffset afterPool) {
+ MOZ_ASSERT(branch.assigned());
+ MOZ_ASSERT(afterPool.assigned());
+ size_t branchOff = branch.getOffset();
+ size_t afterPoolOff = afterPool.getOffset();
+ MOZ_ASSERT(afterPoolOff > branchOff);
+ uint32_t delta = afterPoolOff - branchOff;
+ *dest = 0xb0bb0000 + delta;
+ }
+
+ static void WritePoolHeader(void* start, js::jit::Pool* p, bool isNatural) {
+ MOZ_ASSERT(!isNatural, "Natural pool guards not implemented.");
+ uint32_t* hdr = reinterpret_cast<uint32_t*>(start);
+ *hdr = 0xffff0000 + p->getPoolSize();
+ }
+
+ static void PatchShortRangeBranchToVeneer(AsmBufWithPool* buffer,
+ unsigned rangeIdx,
+ js::jit::BufferOffset deadline,
+ js::jit::BufferOffset veneer) {
+ size_t branchOff = deadline.getOffset() - BranchRange;
+ size_t veneerOff = veneer.getOffset();
+ uint32_t* branch = buffer->getInst(js::jit::BufferOffset(branchOff));
+
+ MOZ_ASSERT((*branch & 0xffff0000) == 0xb1bb0000,
+ "Expected short-range branch instruction");
+ // Copy branch offset to veneer. A real instruction set would require
+ // some adjustment of the label linked-list.
+ *buffer->getInst(veneer) = 0xb2bb0000 | (*branch & 0xffff);
+ MOZ_ASSERT(veneerOff > branchOff, "Veneer should follow branch");
+ *branch = 0xb3bb0000 + (veneerOff - branchOff);
+ }
+};
+} // namespace
+
+BEGIN_TEST(testAssemblerBuffer_AssemblerBufferWithConstantPools) {
+ using js::jit::BufferOffset;
+
+ AsmBufWithPool ab(/* guardSize= */ 1,
+ /* headerSize= */ 1,
+ /* instBufferAlign(unused)= */ 0,
+ /* poolMaxOffset= */ 17,
+ /* pcBias= */ 0,
+ /* alignFillInst= */ 0x11110000,
+ /* nopFillInst= */ 0xaaaa0000,
+ /* nopFill= */ 0);
+
+ CHECK(ab.isAligned(16));
+ CHECK_EQUAL(ab.size(), 0u);
+ CHECK_EQUAL(ab.nextOffset().getOffset(), 0);
+ CHECK(!ab.oom());
+
+ // Each slice holds 5 instructions. Trigger a constant pool inside the slice.
+ uint32_t poolLoad[] = {0xc0cc0000};
+ uint32_t poolData[] = {0xdddd0000, 0xdddd0001, 0xdddd0002, 0xdddd0003};
+ AsmBufWithPool::PoolEntry pe;
+ BufferOffset load =
+ ab.allocEntry(1, 1, (uint8_t*)poolLoad, (uint8_t*)poolData, &pe);
+ CHECK_EQUAL(pe.index(), 0u);
+ CHECK_EQUAL(load.getOffset(), 0);
+
+ // Pool hasn't been emitted yet. Load has been patched by
+ // InsertIndexIntoTag.
+ CHECK_EQUAL(*ab.getInst(load), 0xc1cc0000);
+
+ // Expected layout:
+ //
+ // 0: load [pc+16]
+ // 4: 0x22220001
+ // 8: guard branch pc+12
+ // 12: pool header
+ // 16: poolData
+ // 20: 0x22220002
+ //
+ ab.putInt(0x22220001);
+ // One could argue that the pool should be flushed here since there is no
+ // more room. However, the current implementation doesn't dump pool until
+ // asked to add data:
+ ab.putInt(0x22220002);
+
+ CHECK_EQUAL(*ab.getInst(BufferOffset(0)), 0xc2cc0010u);
+ CHECK_EQUAL(*ab.getInst(BufferOffset(4)), 0x22220001u);
+ CHECK_EQUAL(*ab.getInst(BufferOffset(8)), 0xb0bb000cu);
+ CHECK_EQUAL(*ab.getInst(BufferOffset(12)), 0xffff0004u);
+ CHECK_EQUAL(*ab.getInst(BufferOffset(16)), 0xdddd0000u);
+ CHECK_EQUAL(*ab.getInst(BufferOffset(20)), 0x22220002u);
+
+ // allocEntry() overwrites the load instruction! Restore the original.
+ poolLoad[0] = 0xc0cc0000;
+
+ // Now try with load and pool data on separate slices.
+ load = ab.allocEntry(1, 1, (uint8_t*)poolLoad, (uint8_t*)poolData, &pe);
+ CHECK_EQUAL(pe.index(), 1u); // Global pool entry index.
+ CHECK_EQUAL(load.getOffset(), 24);
+ CHECK_EQUAL(*ab.getInst(load), 0xc1cc0000); // Index into current pool.
+ ab.putInt(0x22220001);
+ ab.putInt(0x22220002);
+ CHECK_EQUAL(*ab.getInst(BufferOffset(24)), 0xc2cc0010u);
+ CHECK_EQUAL(*ab.getInst(BufferOffset(28)), 0x22220001u);
+ CHECK_EQUAL(*ab.getInst(BufferOffset(32)), 0xb0bb000cu);
+ CHECK_EQUAL(*ab.getInst(BufferOffset(36)), 0xffff0004u);
+ CHECK_EQUAL(*ab.getInst(BufferOffset(40)), 0xdddd0000u);
+ CHECK_EQUAL(*ab.getInst(BufferOffset(44)), 0x22220002u);
+
+ // Two adjacent loads to the same pool.
+ poolLoad[0] = 0xc0cc0000;
+ load = ab.allocEntry(1, 1, (uint8_t*)poolLoad, (uint8_t*)poolData, &pe);
+ CHECK_EQUAL(pe.index(), 2u); // Global pool entry index.
+ CHECK_EQUAL(load.getOffset(), 48);
+ CHECK_EQUAL(*ab.getInst(load), 0xc1cc0000); // Index into current pool.
+
+ poolLoad[0] = 0xc0cc0000;
+ load = ab.allocEntry(1, 1, (uint8_t*)poolLoad, (uint8_t*)(poolData + 1), &pe);
+ CHECK_EQUAL(pe.index(), 3u); // Global pool entry index.
+ CHECK_EQUAL(load.getOffset(), 52);
+ CHECK_EQUAL(*ab.getInst(load), 0xc1cc0001); // Index into current pool.
+
+ ab.putInt(0x22220005);
+
+ CHECK_EQUAL(*ab.getInst(BufferOffset(48)), 0xc2cc0010u); // load pc+16.
+ CHECK_EQUAL(*ab.getInst(BufferOffset(52)), 0xc2cc0010u); // load pc+16.
+ CHECK_EQUAL(*ab.getInst(BufferOffset(56)),
+ 0xb0bb0010u); // guard branch pc+16.
+ CHECK_EQUAL(*ab.getInst(BufferOffset(60)), 0xffff0008u); // header 8 bytes.
+ CHECK_EQUAL(*ab.getInst(BufferOffset(64)), 0xdddd0000u); // datum 1.
+ CHECK_EQUAL(*ab.getInst(BufferOffset(68)), 0xdddd0001u); // datum 2.
+ CHECK_EQUAL(*ab.getInst(BufferOffset(72)),
+ 0x22220005u); // putInt(0x22220005)
+
+ // Two loads as above, but the first load has an 8-byte pool entry, and the
+ // second load wouldn't be able to reach its data. This must produce two
+ // pools.
+ poolLoad[0] = 0xc0cc0000;
+ load = ab.allocEntry(1, 2, (uint8_t*)poolLoad, (uint8_t*)(poolData + 2), &pe);
+ CHECK_EQUAL(pe.index(), 4u); // Global pool entry index.
+ CHECK_EQUAL(load.getOffset(), 76);
+ CHECK_EQUAL(*ab.getInst(load), 0xc1cc0000); // Index into current pool.
+
+ poolLoad[0] = 0xc0cc0000;
+ load = ab.allocEntry(1, 1, (uint8_t*)poolLoad, (uint8_t*)poolData, &pe);
+ CHECK_EQUAL(pe.index(),
+ 6u); // Global pool entry index. (Prev one is two indexes).
+ CHECK_EQUAL(load.getOffset(), 96);
+ CHECK_EQUAL(*ab.getInst(load), 0xc1cc0000); // Index into current pool.
+
+ CHECK_EQUAL(*ab.getInst(BufferOffset(76)), 0xc2cc000cu); // load pc+12.
+ CHECK_EQUAL(*ab.getInst(BufferOffset(80)),
+ 0xb0bb0010u); // guard branch pc+16.
+ CHECK_EQUAL(*ab.getInst(BufferOffset(84)), 0xffff0008u); // header 8 bytes.
+ CHECK_EQUAL(*ab.getInst(BufferOffset(88)), 0xdddd0002u); // datum 1.
+ CHECK_EQUAL(*ab.getInst(BufferOffset(92)), 0xdddd0003u); // datum 2.
+
+ // Second pool is not flushed yet, and there is room for one instruction
+ // after the load. Test the keep-together feature.
+ ab.enterNoPool(2);
+ ab.putInt(0x22220006);
+ ab.putInt(0x22220007);
+ ab.leaveNoPool();
+
+ CHECK_EQUAL(*ab.getInst(BufferOffset(96)), 0xc2cc000cu); // load pc+16.
+ CHECK_EQUAL(*ab.getInst(BufferOffset(100)),
+ 0xb0bb000cu); // guard branch pc+12.
+ CHECK_EQUAL(*ab.getInst(BufferOffset(104)), 0xffff0004u); // header 4 bytes.
+ CHECK_EQUAL(*ab.getInst(BufferOffset(108)), 0xdddd0000u); // datum 1.
+ CHECK_EQUAL(*ab.getInst(BufferOffset(112)), 0x22220006u);
+ CHECK_EQUAL(*ab.getInst(BufferOffset(116)), 0x22220007u);
+
+ return true;
+}
+END_TEST(testAssemblerBuffer_AssemblerBufferWithConstantPools)
+
+BEGIN_TEST(testAssemblerBuffer_AssemblerBufferWithConstantPools_ShortBranch) {
+ using js::jit::BufferOffset;
+
+ AsmBufWithPool ab(/* guardSize= */ 1,
+ /* headerSize= */ 1,
+ /* instBufferAlign(unused)= */ 0,
+ /* poolMaxOffset= */ 17,
+ /* pcBias= */ 0,
+ /* alignFillInst= */ 0x11110000,
+ /* nopFillInst= */ 0xaaaa0000,
+ /* nopFill= */ 0);
+
+ // Insert short-range branch.
+ BufferOffset br1 = ab.putInt(0xb1bb00cc);
+ ab.registerBranchDeadline(
+ 1, BufferOffset(br1.getOffset() + TestAssembler::BranchRange));
+ ab.putInt(0x22220001);
+ BufferOffset off = ab.putInt(0x22220002);
+ ab.registerBranchDeadline(
+ 1, BufferOffset(off.getOffset() + TestAssembler::BranchRange));
+ ab.putInt(0x22220003);
+ ab.putInt(0x22220004);
+
+ // Second short-range branch that will be swiped up by hysteresis.
+ BufferOffset br2 = ab.putInt(0xb1bb0d2d);
+ ab.registerBranchDeadline(
+ 1, BufferOffset(br2.getOffset() + TestAssembler::BranchRange));
+
+ // Branch should not have been patched yet here.
+ CHECK_EQUAL(*ab.getInst(br1), 0xb1bb00cc);
+ CHECK_EQUAL(*ab.getInst(br2), 0xb1bb0d2d);
+
+ // Cancel one of the pending branches.
+ // This is what will happen to most branches as they are bound before
+ // expiring by Assembler::bind().
+ ab.unregisterBranchDeadline(
+ 1, BufferOffset(off.getOffset() + TestAssembler::BranchRange));
+
+ off = ab.putInt(0x22220006);
+ // Here we may or may not have patched the branch yet, but it is inevitable
+ // now:
+ //
+ // 0: br1 pc+36
+ // 4: 0x22220001
+ // 8: 0x22220002 (unpatched)
+ // 12: 0x22220003
+ // 16: 0x22220004
+ // 20: br2 pc+20
+ // 24: 0x22220006
+ CHECK_EQUAL(off.getOffset(), 24);
+ // 28: guard branch pc+16
+ // 32: pool header
+ // 36: veneer1
+ // 40: veneer2
+ // 44: 0x22220007
+
+ off = ab.putInt(0x22220007);
+ CHECK_EQUAL(off.getOffset(), 44);
+
+ // Now the branch must have been patched.
+ CHECK_EQUAL(*ab.getInst(br1), 0xb3bb0000 + 36); // br1 pc+36 (patched)
+ CHECK_EQUAL(*ab.getInst(BufferOffset(8)),
+ 0x22220002u); // 0x22220002 (unpatched)
+ CHECK_EQUAL(*ab.getInst(br2), 0xb3bb0000 + 20); // br2 pc+20 (patched)
+ CHECK_EQUAL(*ab.getInst(BufferOffset(28)), 0xb0bb0010u); // br pc+16 (guard)
+ CHECK_EQUAL(*ab.getInst(BufferOffset(32)),
+ 0xffff0000u); // pool header 0 bytes.
+ CHECK_EQUAL(*ab.getInst(BufferOffset(36)),
+ 0xb2bb00ccu); // veneer1 w/ original 'cc' offset.
+ CHECK_EQUAL(*ab.getInst(BufferOffset(40)),
+ 0xb2bb0d2du); // veneer2 w/ original 'd2d' offset.
+ CHECK_EQUAL(*ab.getInst(BufferOffset(44)), 0x22220007u);
+
+ return true;
+}
+END_TEST(testAssemblerBuffer_AssemblerBufferWithConstantPools_ShortBranch)
+
+// Test that everything is put together correctly in the ARM64 assembler.
+#if defined(JS_CODEGEN_ARM64)
+
+# include "jit/MacroAssembler-inl.h"
+
+BEGIN_TEST(testAssemblerBuffer_ARM64) {
+ using namespace js::jit;
+
+ js::LifoAlloc lifo(4096);
+ TempAllocator alloc(&lifo);
+ JitContext jc(cx);
+ StackMacroAssembler masm(cx, alloc);
+ AutoCreatedBy acb(masm, __func__);
+
+ // Branches to an unbound label.
+ Label lab1;
+ masm.branch(Assembler::Equal, &lab1);
+ masm.branch(Assembler::LessThan, &lab1);
+ masm.bind(&lab1);
+ masm.branch(Assembler::Equal, &lab1);
+
+ CHECK_EQUAL(masm.getInstructionAt(BufferOffset(0))->InstructionBits(),
+ vixl::B_cond | vixl::Assembler::ImmCondBranch(2) | vixl::eq);
+ CHECK_EQUAL(masm.getInstructionAt(BufferOffset(4))->InstructionBits(),
+ vixl::B_cond | vixl::Assembler::ImmCondBranch(1) | vixl::lt);
+ CHECK_EQUAL(masm.getInstructionAt(BufferOffset(8))->InstructionBits(),
+ vixl::B_cond | vixl::Assembler::ImmCondBranch(0) | vixl::eq);
+
+ // Branches can reach the label, but the linked list of uses needs to be
+ // rearranged. The final conditional branch cannot reach the first branch.
+ Label lab2a;
+ Label lab2b;
+ masm.bind(&lab2a);
+ masm.B(&lab2b);
+ // Generate 1,100,000 bytes of NOPs.
+ for (unsigned n = 0; n < 1100000; n += 4) {
+ masm.Nop();
+ }
+ masm.branch(Assembler::LessThan, &lab2b);
+ masm.bind(&lab2b);
+ CHECK_EQUAL(
+ masm.getInstructionAt(BufferOffset(lab2a.offset()))->InstructionBits(),
+ vixl::B | vixl::Assembler::ImmUncondBranch(1100000 / 4 + 2));
+ CHECK_EQUAL(masm.getInstructionAt(BufferOffset(lab2b.offset() - 4))
+ ->InstructionBits(),
+ vixl::B_cond | vixl::Assembler::ImmCondBranch(1) | vixl::lt);
+
+ // Generate a conditional branch that can't reach its label.
+ Label lab3a;
+ Label lab3b;
+ masm.bind(&lab3a);
+ masm.branch(Assembler::LessThan, &lab3b);
+ for (unsigned n = 0; n < 1100000; n += 4) {
+ masm.Nop();
+ }
+ masm.bind(&lab3b);
+ masm.B(&lab3a);
+ Instruction* bcond3 = masm.getInstructionAt(BufferOffset(lab3a.offset()));
+ CHECK_EQUAL(bcond3->BranchType(), vixl::CondBranchType);
+ ptrdiff_t delta = bcond3->ImmPCRawOffset() * 4;
+ Instruction* veneer =
+ masm.getInstructionAt(BufferOffset(lab3a.offset() + delta));
+ CHECK_EQUAL(veneer->BranchType(), vixl::UncondBranchType);
+ delta += veneer->ImmPCRawOffset() * 4;
+ CHECK_EQUAL(delta, lab3b.offset() - lab3a.offset());
+ Instruction* b3 = masm.getInstructionAt(BufferOffset(lab3b.offset()));
+ CHECK_EQUAL(b3->BranchType(), vixl::UncondBranchType);
+ CHECK_EQUAL(4 * b3->ImmPCRawOffset(), -delta);
+
+ return true;
+}
+END_TEST(testAssemblerBuffer_ARM64)
+#endif /* JS_CODEGEN_ARM64 */
diff --git a/js/src/jsapi-tests/testAtomicOperations.cpp b/js/src/jsapi-tests/testAtomicOperations.cpp
new file mode 100644
index 0000000000..06000f7676
--- /dev/null
+++ b/js/src/jsapi-tests/testAtomicOperations.cpp
@@ -0,0 +1,304 @@
+/* -*- 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/Alignment.h"
+#include "mozilla/Assertions.h"
+
+#include "jit/AtomicOperations.h"
+#include "jsapi-tests/tests.h"
+#include "vm/ArrayBufferObject.h"
+#include "vm/SharedMem.h"
+#include "vm/Uint8Clamped.h"
+#include "wasm/WasmJS.h"
+
+using namespace js;
+
+// Machinery to disguise pointer addresses to the C++ compiler -- quite possibly
+// not thread-safe.
+
+extern void setHiddenPointer(void* p);
+extern void* getHiddenPointer();
+
+void* hidePointerValue(void* p) {
+ setHiddenPointer(p);
+ return getHiddenPointer();
+}
+
+//////////////////////////////////////////////////////////////////////
+//
+// Lock-freedom predicates
+
+BEGIN_REUSABLE_TEST(testAtomicLockFree8) {
+ // isLockfree8() must not return true if there are no 8-byte atomics
+
+ CHECK(!jit::AtomicOperations::isLockfree8() ||
+ jit::AtomicOperations::hasAtomic8());
+
+ // We must have lock-free 8-byte atomics on every platform where we support
+ // wasm, but we don't care otherwise.
+
+ CHECK(!wasm::HasSupport(cx) || jit::AtomicOperations::isLockfree8());
+ return true;
+}
+END_TEST(testAtomicLockFree8)
+
+// The JS spec requires specific behavior for all but 1 and 2.
+
+BEGIN_REUSABLE_TEST(testAtomicLockFreeJS) {
+ static_assert(jit::AtomicOperations::isLockfreeJS(1) ==
+ true); // false is allowed by spec but not in SpiderMonkey
+ static_assert(jit::AtomicOperations::isLockfreeJS(2) == true); // ditto
+ static_assert(jit::AtomicOperations::isLockfreeJS(8) == true); // ditto
+ static_assert(jit::AtomicOperations::isLockfreeJS(3) == false); // required
+ static_assert(jit::AtomicOperations::isLockfreeJS(4) == true); // required
+ static_assert(jit::AtomicOperations::isLockfreeJS(5) == false); // required
+ static_assert(jit::AtomicOperations::isLockfreeJS(6) == false); // required
+ static_assert(jit::AtomicOperations::isLockfreeJS(7) == false); // required
+ return true;
+}
+END_TEST(testAtomicLockFreeJS)
+
+//////////////////////////////////////////////////////////////////////
+//
+// Fence
+
+// This only tests that fenceSeqCst is defined and that it doesn't crash if we
+// call it, but it has no return value and its effect is not observable here.
+
+BEGIN_REUSABLE_TEST(testAtomicFence) {
+ jit::AtomicOperations::fenceSeqCst();
+ return true;
+}
+END_TEST(testAtomicFence)
+
+//////////////////////////////////////////////////////////////////////
+//
+// Memory access primitives
+
+// These tests for the atomic load and store primitives ascertain that the
+// primitives are defined and that they load and store the values they should,
+// but not that the primitives are actually atomic wrt to the memory subsystem.
+
+// Memory for testing atomics. This must be aligned to the natural alignment of
+// the type we're testing; for now, use 8-byte alignment for all.
+
+MOZ_ALIGNED_DECL(8, static uint8_t atomicMem[8]);
+MOZ_ALIGNED_DECL(8, static uint8_t atomicMem2[8]);
+
+// T is the primitive type we're testing, and A and B are references to constant
+// bindings holding values of that type.
+//
+// No bytes of A and B should be 0 or FF. A+B and A-B must not overflow.
+
+#define ATOMIC_TESTS(T, A, B) \
+ T* q = (T*)hidePointerValue((void*)atomicMem); \
+ *q = A; \
+ SharedMem<T*> p = \
+ SharedMem<T*>::shared((T*)hidePointerValue((T*)atomicMem)); \
+ CHECK(*q == A); \
+ CHECK(jit::AtomicOperations::loadSeqCst(p) == A); \
+ CHECK(*q == A); \
+ jit::AtomicOperations::storeSeqCst(p, B); \
+ CHECK(*q == B); \
+ CHECK(jit::AtomicOperations::exchangeSeqCst(p, A) == B); \
+ CHECK(*q == A); \
+ CHECK(jit::AtomicOperations::compareExchangeSeqCst(p, (T)0, (T)1) == \
+ A); /*failure*/ \
+ CHECK(*q == A); \
+ CHECK(jit::AtomicOperations::compareExchangeSeqCst(p, A, B) == \
+ A); /*success*/ \
+ CHECK(*q == B); \
+ *q = A; \
+ CHECK(jit::AtomicOperations::fetchAddSeqCst(p, B) == A); \
+ CHECK(*q == A + B); \
+ *q = A; \
+ CHECK(jit::AtomicOperations::fetchSubSeqCst(p, B) == A); \
+ CHECK(*q == A - B); \
+ *q = A; \
+ CHECK(jit::AtomicOperations::fetchAndSeqCst(p, B) == A); \
+ CHECK(*q == (A & B)); \
+ *q = A; \
+ CHECK(jit::AtomicOperations::fetchOrSeqCst(p, B) == A); \
+ CHECK(*q == (A | B)); \
+ *q = A; \
+ CHECK(jit::AtomicOperations::fetchXorSeqCst(p, B) == A); \
+ CHECK(*q == (A ^ B)); \
+ *q = A; \
+ CHECK(jit::AtomicOperations::loadSafeWhenRacy(p) == A); \
+ jit::AtomicOperations::storeSafeWhenRacy(p, B); \
+ CHECK(*q == B); \
+ T* q2 = (T*)hidePointerValue((void*)atomicMem2); \
+ SharedMem<T*> p2 = \
+ SharedMem<T*>::shared((T*)hidePointerValue((void*)atomicMem2)); \
+ *q = A; \
+ *q2 = B; \
+ jit::AtomicOperations::memcpySafeWhenRacy(p2, p, sizeof(T)); \
+ CHECK(*q2 == A); \
+ *q = A; \
+ *q2 = B; \
+ jit::AtomicOperations::memcpySafeWhenRacy(p2, p.unwrap(), sizeof(T)); \
+ CHECK(*q2 == A); \
+ *q = A; \
+ *q2 = B; \
+ jit::AtomicOperations::memcpySafeWhenRacy(p2.unwrap(), p, sizeof(T)); \
+ CHECK(*q2 == A); \
+ *q = A; \
+ *q2 = B; \
+ jit::AtomicOperations::memmoveSafeWhenRacy(p2, p, sizeof(T)); \
+ CHECK(*q2 == A); \
+ *q = A; \
+ *q2 = B; \
+ jit::AtomicOperations::podCopySafeWhenRacy(p2, p, 1); \
+ CHECK(*q2 == A); \
+ *q = A; \
+ *q2 = B; \
+ jit::AtomicOperations::podMoveSafeWhenRacy(p2, p, 1); \
+ CHECK(*q2 == A); \
+ return true
+
+BEGIN_REUSABLE_TEST(testAtomicOperationsU8) {
+ const uint8_t A = 0xab;
+ const uint8_t B = 0x37;
+ ATOMIC_TESTS(uint8_t, A, B);
+}
+END_TEST(testAtomicOperationsU8)
+
+BEGIN_REUSABLE_TEST(testAtomicOperationsI8) {
+ const int8_t A = 0x3b;
+ const int8_t B = 0x27;
+ ATOMIC_TESTS(int8_t, A, B);
+}
+END_TEST(testAtomicOperationsI8)
+
+BEGIN_REUSABLE_TEST(testAtomicOperationsU16) {
+ const uint16_t A = 0xabdc;
+ const uint16_t B = 0x3789;
+ ATOMIC_TESTS(uint16_t, A, B);
+}
+END_TEST(testAtomicOperationsU16)
+
+BEGIN_REUSABLE_TEST(testAtomicOperationsI16) {
+ const int16_t A = 0x3bdc;
+ const int16_t B = 0x2737;
+ ATOMIC_TESTS(int16_t, A, B);
+}
+END_TEST(testAtomicOperationsI16)
+
+BEGIN_REUSABLE_TEST(testAtomicOperationsU32) {
+ const uint32_t A = 0xabdc0588;
+ const uint32_t B = 0x37891942;
+ ATOMIC_TESTS(uint32_t, A, B);
+}
+END_TEST(testAtomicOperationsU32)
+
+BEGIN_REUSABLE_TEST(testAtomicOperationsI32) {
+ const int32_t A = 0x3bdc0588;
+ const int32_t B = 0x27371843;
+ ATOMIC_TESTS(int32_t, A, B);
+}
+END_TEST(testAtomicOperationsI32)
+
+BEGIN_REUSABLE_TEST(testAtomicOperationsU64) {
+ if (!jit::AtomicOperations::hasAtomic8()) {
+ return true;
+ }
+
+ const uint64_t A(0x9aadf00ddeadbeef);
+ const uint64_t B(0x4eedbead1337f001);
+ ATOMIC_TESTS(uint64_t, A, B);
+}
+END_TEST(testAtomicOperationsU64)
+
+BEGIN_REUSABLE_TEST(testAtomicOperationsI64) {
+ if (!jit::AtomicOperations::hasAtomic8()) {
+ return true;
+ }
+
+ const int64_t A(0x2aadf00ddeadbeef);
+ const int64_t B(0x4eedbead1337f001);
+ ATOMIC_TESTS(int64_t, A, B);
+}
+END_TEST(testAtomicOperationsI64)
+
+// T is the primitive float type we're testing, and A and B are references to
+// constant bindings holding values of that type.
+//
+// Stay away from 0, NaN, infinities, and denormals.
+
+#define ATOMIC_FLOAT_TESTS(T, A, B) \
+ T* q = (T*)hidePointerValue((void*)atomicMem); \
+ *q = A; \
+ SharedMem<T*> p = \
+ SharedMem<T*>::shared((T*)hidePointerValue((T*)atomicMem)); \
+ CHECK(*q == A); \
+ CHECK(jit::AtomicOperations::loadSafeWhenRacy(p) == A); \
+ jit::AtomicOperations::storeSafeWhenRacy(p, B); \
+ CHECK(*q == B); \
+ T* q2 = (T*)hidePointerValue((void*)atomicMem2); \
+ SharedMem<T*> p2 = \
+ SharedMem<T*>::shared((T*)hidePointerValue((void*)atomicMem2)); \
+ *q = A; \
+ *q2 = B; \
+ jit::AtomicOperations::memcpySafeWhenRacy(p2, p, sizeof(T)); \
+ CHECK(*q2 == A); \
+ *q = A; \
+ *q2 = B; \
+ jit::AtomicOperations::memcpySafeWhenRacy(p2, p.unwrap(), sizeof(T)); \
+ CHECK(*q2 == A); \
+ *q = A; \
+ *q2 = B; \
+ jit::AtomicOperations::memcpySafeWhenRacy(p2.unwrap(), p, sizeof(T)); \
+ CHECK(*q2 == A); \
+ *q = A; \
+ *q2 = B; \
+ jit::AtomicOperations::memmoveSafeWhenRacy(p2, p, sizeof(T)); \
+ CHECK(*q2 == A); \
+ *q = A; \
+ *q2 = B; \
+ jit::AtomicOperations::podCopySafeWhenRacy(p2, p, 1); \
+ CHECK(*q2 == A); \
+ *q = A; \
+ *q2 = B; \
+ jit::AtomicOperations::podMoveSafeWhenRacy(p2, p, 1); \
+ CHECK(*q2 == A); \
+ return true
+
+BEGIN_REUSABLE_TEST(testAtomicOperationsF32) {
+ const float A(123.25);
+ const float B(-987.75);
+ ATOMIC_FLOAT_TESTS(float, A, B);
+}
+END_TEST(testAtomicOperationsF32)
+
+BEGIN_REUSABLE_TEST(testAtomicOperationsF64) {
+ const double A(123.25);
+ const double B(-987.75);
+ ATOMIC_FLOAT_TESTS(double, A, B);
+}
+END_TEST(testAtomicOperationsF64)
+
+#define ATOMIC_CLAMPED_TESTS(T, A, B) \
+ T* q = (T*)hidePointerValue((void*)atomicMem); \
+ *q = A; \
+ SharedMem<T*> p = \
+ SharedMem<T*>::shared((T*)hidePointerValue((T*)atomicMem)); \
+ CHECK(*q == A); \
+ CHECK(jit::AtomicOperations::loadSafeWhenRacy(p) == A); \
+ jit::AtomicOperations::storeSafeWhenRacy(p, B); \
+ CHECK(*q == B); \
+ return true
+
+BEGIN_REUSABLE_TEST(testAtomicOperationsU8Clamped) {
+ const uint8_clamped A(0xab);
+ const uint8_clamped B(0x37);
+ ATOMIC_CLAMPED_TESTS(uint8_clamped, A, B);
+}
+END_TEST(testAtomicOperationsU8Clamped)
+
+#undef ATOMIC_TESTS
+#undef ATOMIC_FLOAT_TESTS
+#undef ATOMIC_CLAMPED_TESTS
diff --git a/js/src/jsapi-tests/testAtomizeUtf8NonAsciiLatin1CodePoint.cpp b/js/src/jsapi-tests/testAtomizeUtf8NonAsciiLatin1CodePoint.cpp
new file mode 100644
index 0000000000..4b16d303a5
--- /dev/null
+++ b/js/src/jsapi-tests/testAtomizeUtf8NonAsciiLatin1CodePoint.cpp
@@ -0,0 +1,212 @@
+/* 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/Maybe.h" // mozilla::Maybe
+#include "mozilla/Utf8.h" // mozilla::IsTrailingUnit, mozilla::Utf8Unit, mozilla::DecodeOneUtf8CodePoint
+
+#include <inttypes.h> // UINT8_MAX
+#include <stdint.h> // uint16_t
+
+#include "js/Exception.h" // JS_IsExceptionPending, JS_ClearPendingException
+#include "js/RootingAPI.h" // JS::Rooted, JS::MutableHandle
+#include "jsapi-tests/tests.h" // BEGIN_TEST, END_TEST, CHECK
+#include "vm/JSAtom.h" // js::AtomizeChars, js::AtomizeUTF8Chars
+#include "vm/StringType.h" // JSAtom
+
+using mozilla::DecodeOneUtf8CodePoint;
+using mozilla::IsAscii;
+using mozilla::IsTrailingUnit;
+using mozilla::Maybe;
+using mozilla::Utf8Unit;
+
+using JS::Latin1Char;
+using JS::MutableHandle;
+using JS::Rooted;
+
+BEGIN_TEST(testAtomizeTwoByteUTF8) {
+ Rooted<JSAtom*> atom16(cx);
+ Rooted<JSAtom*> atom8(cx);
+
+ for (uint16_t i = 0; i <= UINT8_MAX; i++) {
+ // Test cases where the first unit is ASCII.
+ if (IsAscii(char16_t(i))) {
+ for (uint16_t j = 0; j <= UINT8_MAX; j++) {
+ if (IsAscii(char16_t(j))) {
+ // If both units are ASCII, the sequence encodes a two-code point
+ // string.
+ if (!shouldBeTwoCodePoints(i, j, &atom16, &atom8)) {
+ return false;
+ }
+ } else {
+ // ASCII followed by non-ASCII should be invalid.
+ if (!shouldBeInvalid(i, j)) {
+ return false;
+ }
+ }
+ }
+
+ continue;
+ }
+
+ // Test remaining cases where the first unit isn't a two-byte lead.
+ if ((i & 0b1110'0000) != 0b1100'0000) {
+ for (uint16_t j = 0; j <= UINT8_MAX; j++) {
+ // If the first unit isn't a two-byte lead, the sequence is invalid no
+ // matter what the second unit is.
+ if (!shouldBeInvalid(i, j)) {
+ return false;
+ }
+ }
+
+ continue;
+ }
+
+ // Test remaining cases where the first unit is the two-byte lead of a
+ // non-Latin-1 code point.
+ if (i >= 0b1100'0100) {
+ for (uint16_t j = 0; j <= UINT8_MAX; j++) {
+ if (IsTrailingUnit(Utf8Unit(static_cast<uint8_t>(j)))) {
+ if (!shouldBeSingleNonLatin1(i, j, &atom16, &atom8)) {
+ return false;
+ }
+ } else {
+ if (!shouldBeInvalid(i, j)) {
+ return false;
+ }
+ }
+ }
+
+ continue;
+ }
+
+ // Test remaining cases where the first unit is the two-byte lead of an
+ // overlong ASCII code point.
+ if (i < 0b1100'0010) {
+ for (uint16_t j = 0; j <= UINT8_MAX; j++) {
+ if (!shouldBeInvalid(i, j)) {
+ return false;
+ }
+ }
+
+ continue;
+ }
+
+ // Finally, test remaining cases where the first unit is the two-byte lead
+ // of a Latin-1 code point.
+ for (uint16_t j = 0; j <= UINT8_MAX; j++) {
+ if (IsTrailingUnit(Utf8Unit(static_cast<uint8_t>(j)))) {
+ if (!shouldBeSingleLatin1(i, j, &atom16, &atom8)) {
+ return false;
+ }
+ } else {
+ if (!shouldBeInvalid(i, j)) {
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+bool shouldBeTwoCodePoints(uint16_t first, uint16_t second,
+ MutableHandle<JSAtom*> atom16,
+ MutableHandle<JSAtom*> atom8) {
+ CHECK(first <= UINT8_MAX);
+ CHECK(second <= UINT8_MAX);
+ CHECK(IsAscii(char16_t(first)));
+ CHECK(IsAscii(char16_t(second)));
+
+ const char16_t utf16[] = {static_cast<char16_t>(first),
+ static_cast<char16_t>(second)};
+ atom16.set(js::AtomizeChars(cx, utf16, 2));
+ CHECK(atom16);
+ CHECK(atom16->length() == 2);
+ CHECK(atom16->latin1OrTwoByteChar(0) == first);
+ CHECK(atom16->latin1OrTwoByteChar(1) == second);
+
+ const char utf8[] = {static_cast<char>(first), static_cast<char>(second)};
+ atom8.set(js::AtomizeUTF8Chars(cx, utf8, 2));
+ CHECK(atom8);
+ CHECK(atom8->length() == 2);
+ CHECK(atom8->latin1OrTwoByteChar(0) == first);
+ CHECK(atom8->latin1OrTwoByteChar(1) == second);
+
+ CHECK(atom16 == atom8);
+
+ return true;
+}
+
+bool shouldBeOneCodePoint(uint16_t first, uint16_t second, char32_t v,
+ MutableHandle<JSAtom*> atom16,
+ MutableHandle<JSAtom*> atom8) {
+ CHECK(first <= UINT8_MAX);
+ CHECK(second <= UINT8_MAX);
+ CHECK(v <= UINT16_MAX);
+
+ const char16_t utf16[] = {static_cast<char16_t>(v)};
+ atom16.set(js::AtomizeChars(cx, utf16, 1));
+ CHECK(atom16);
+ CHECK(atom16->length() == 1);
+ CHECK(atom16->latin1OrTwoByteChar(0) == v);
+
+ const char utf8[] = {static_cast<char>(first), static_cast<char>(second)};
+ atom8.set(js::AtomizeUTF8Chars(cx, utf8, 2));
+ CHECK(atom8);
+ CHECK(atom8->length() == 1);
+ CHECK(atom8->latin1OrTwoByteChar(0) == v);
+
+ CHECK(atom16 == atom8);
+
+ return true;
+}
+
+bool shouldBeSingleNonLatin1(uint16_t first, uint16_t second,
+ MutableHandle<JSAtom*> atom16,
+ MutableHandle<JSAtom*> atom8) {
+ CHECK(first <= UINT8_MAX);
+ CHECK(second <= UINT8_MAX);
+
+ const char bytes[] = {static_cast<char>(first), static_cast<char>(second)};
+ const char* iter = &bytes[1];
+ Maybe<char32_t> cp =
+ DecodeOneUtf8CodePoint(Utf8Unit(bytes[0]), &iter, bytes + 2);
+ CHECK(cp.isSome());
+
+ char32_t v = cp.value();
+ CHECK(v > UINT8_MAX);
+
+ return shouldBeOneCodePoint(first, second, v, atom16, atom8);
+}
+
+bool shouldBeSingleLatin1(uint16_t first, uint16_t second,
+ MutableHandle<JSAtom*> atom16,
+ MutableHandle<JSAtom*> atom8) {
+ CHECK(first <= UINT8_MAX);
+ CHECK(second <= UINT8_MAX);
+
+ const char bytes[] = {static_cast<char>(first), static_cast<char>(second)};
+ const char* iter = &bytes[1];
+ Maybe<char32_t> cp =
+ DecodeOneUtf8CodePoint(Utf8Unit(bytes[0]), &iter, bytes + 2);
+ CHECK(cp.isSome());
+
+ char32_t v = cp.value();
+ CHECK(v <= UINT8_MAX);
+
+ return shouldBeOneCodePoint(first, second, v, atom16, atom8);
+}
+
+bool shouldBeInvalid(uint16_t first, uint16_t second) {
+ CHECK(first <= UINT8_MAX);
+ CHECK(second <= UINT8_MAX);
+
+ const char invalid[] = {static_cast<char>(first), static_cast<char>(second)};
+ CHECK(!js::AtomizeUTF8Chars(cx, invalid, 2));
+ CHECK(JS_IsExceptionPending(cx));
+ JS_ClearPendingException(cx);
+
+ return true;
+}
+END_TEST(testAtomizeTwoByteUTF8)
diff --git a/js/src/jsapi-tests/testAtomizeWithoutActiveZone.cpp b/js/src/jsapi-tests/testAtomizeWithoutActiveZone.cpp
new file mode 100644
index 0000000000..5f4ff36f3e
--- /dev/null
+++ b/js/src/jsapi-tests/testAtomizeWithoutActiveZone.cpp
@@ -0,0 +1,52 @@
+/* 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 "js/Realm.h"
+#include "js/RootingAPI.h"
+#include "jsapi-tests/tests.h"
+#include "vm/CommonPropertyNames.h"
+#include "vm/JSAtomState.h"
+#include "vm/JSContext.h"
+
+BEGIN_TEST(testAtomizeWithoutActiveZone) {
+ // Tests for JS_AtomizeAndPinString when called without an active zone.
+
+ MOZ_ASSERT(cx->zone());
+
+ JS::RootedString testAtom1(cx, JS_AtomizeString(cx, "test1234@!"));
+ CHECK(testAtom1);
+
+ JS::RootedString testAtom2(cx);
+ {
+ JSAutoNullableRealm ar(cx, nullptr);
+ MOZ_ASSERT(!cx->zone());
+
+ // Permanent atom.
+ JSString* atom = JS_AtomizeAndPinString(cx, "boolean");
+ CHECK(atom);
+ CHECK_EQUAL(atom, cx->names().boolean);
+
+ // Static string.
+ atom = JS_AtomizeAndPinString(cx, "8");
+ CHECK(atom);
+ CHECK_EQUAL(atom, cx->staticStrings().getUint(8));
+
+ // Existing atom.
+ atom = JS_AtomizeAndPinString(cx, "test1234@!");
+ CHECK(atom);
+ CHECK_EQUAL(atom, testAtom1);
+
+ // New atom.
+ testAtom2 = JS_AtomizeAndPinString(cx, "asdflkjsdf987_@");
+ CHECK(testAtom2);
+ }
+
+ MOZ_ASSERT(cx->zone());
+ JSString* atom = JS_AtomizeString(cx, "asdflkjsdf987_@");
+ CHECK(atom);
+ CHECK_EQUAL(atom, testAtom2);
+
+ return true;
+}
+END_TEST(testAtomizeWithoutActiveZone)
diff --git a/js/src/jsapi-tests/testAvlTree.cpp b/js/src/jsapi-tests/testAvlTree.cpp
new file mode 100644
index 0000000000..19222ec574
--- /dev/null
+++ b/js/src/jsapi-tests/testAvlTree.cpp
@@ -0,0 +1,383 @@
+/* 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 <set>
+#include <stdio.h>
+
+#include "ds/AvlTree.h"
+
+#include "jsapi-tests/tests.h"
+
+using namespace js;
+
+////////////////////////////////////////////////////////////////////////
+// //
+// AvlTree testing interface. //
+// //
+////////////////////////////////////////////////////////////////////////
+
+// The "standard" AVL interface, `class AvlTree` at the end of
+// js/src/ds/AvlTree.h, is too restrictive to allow proper testing of the AVL
+// tree internals. In particular it disallows insertion of duplicate values
+// and removal of non-present values, and lacks various secondary functions
+// such as for counting the number of nodes.
+//
+// So, for testing, we wrap an alternative interface `AvlTreeTestIF` around
+// the core implementation.
+
+template <class T, class C>
+class AvlTreeTestIF : public AvlTreeImpl<T, C> {
+ // Shorthands for names in the implementation (parent) class.
+ using Impl = AvlTreeImpl<T, C>;
+ using ImplTag = typename AvlTreeImpl<T, C>::Tag;
+ using ImplNode = typename AvlTreeImpl<T, C>::Node;
+ using ImplResult = typename AvlTreeImpl<T, C>::Result;
+ using ImplNodeAndResult = typename AvlTreeImpl<T, C>::NodeAndResult;
+
+ public:
+ explicit AvlTreeTestIF(LifoAlloc* alloc = nullptr) : Impl(alloc) {}
+
+ // Insert `v` if it isn't already there, else leave the tree unchanged.
+ // Returns true iff an insertion happened.
+ bool testInsert(const T& v) {
+ ImplNode* new_root = Impl::insert_worker(v);
+ if (!new_root) {
+ // OOM
+ MOZ_CRASH();
+ }
+ if (uintptr_t(new_root) == uintptr_t(1)) {
+ // Already present
+ return false;
+ }
+ Impl::root_ = new_root;
+ return true;
+ }
+
+ // Remove `v` if it is present. Returns true iff a removal happened.
+ bool testRemove(const T& v) {
+ ImplNodeAndResult pair = Impl::delete_worker(Impl::root_, v);
+ ImplNode* new_root = pair.first;
+ ImplResult res = pair.second;
+ if (res == ImplResult::Error) {
+ // `v` isn't in the tree.
+ return false;
+ } else {
+ Impl::root_ = new_root;
+ return true;
+ }
+ }
+
+ // Count number of elements
+ size_t testSize_worker(ImplNode* n) const {
+ if (n) {
+ return 1 + testSize_worker(n->left) + testSize_worker(n->right);
+ }
+ return 0;
+ }
+ size_t testSize() const { return testSize_worker(Impl::root_); }
+
+ size_t testDepth_worker(ImplNode* n) const {
+ if (n) {
+ size_t depthL = testDepth_worker(n->left);
+ size_t depthR = testDepth_worker(n->right);
+ return 1 + (depthL > depthR ? depthL : depthR);
+ }
+ return 0;
+ }
+ size_t testDepth() const { return testDepth_worker(Impl::root_); }
+
+ bool testContains(const T& v) const {
+ ImplNode* node = Impl::find_worker(v);
+ return node != nullptr;
+ }
+
+ ImplNode* testGetRoot() const { return Impl::root_; }
+ ImplNode* testGetFreeList() const { return Impl::freeList_; }
+
+ bool testFreeListLooksValid(size_t maxElems) {
+ size_t numElems = 0;
+ ImplNode* node = Impl::freeList_;
+ while (node) {
+ numElems++;
+ if (numElems > maxElems) {
+ return false;
+ }
+ if (node->tag != ImplTag::Free || node->right != nullptr) {
+ return false;
+ }
+ node = node->left;
+ }
+ return true;
+ }
+
+ // For debugging only
+ private:
+ void testShow_worker(int depth, const ImplNode* node) const {
+ if (node) {
+ testShow_worker(depth + 1, node->right);
+ for (int i = 0; i < depth; i++) {
+ printf(" ");
+ }
+ char* str = node->item.show();
+ printf("%s\n", str);
+ free(str);
+ testShow_worker(depth + 1, node->left);
+ }
+ }
+
+ public:
+ // For debugging only
+ void testShow() const { testShow_worker(0, Impl::root_); }
+
+ // AvlTree::Iter is also public; it's just pass-through from AvlTreeImpl.
+};
+
+////////////////////////////////////////////////////////////////////////
+// //
+// AvlTree test cases. //
+// //
+////////////////////////////////////////////////////////////////////////
+
+class CmpInt {
+ int x_;
+
+ public:
+ explicit CmpInt(int x) : x_(x) {}
+ ~CmpInt() {}
+ static int compare(const CmpInt& me, const CmpInt& other) {
+ if (me.x_ < other.x_) return -1;
+ if (me.x_ > other.x_) return 1;
+ return 0;
+ }
+ int get() const { return x_; }
+ char* show() const {
+ const size_t length = 16;
+ char* str = (char*)calloc(length, 1);
+ snprintf(str, length, "%d", x_);
+ return str;
+ }
+};
+
+bool TreeIsPlausible(const AvlTreeTestIF<CmpInt, CmpInt>& tree,
+ const std::set<int>& should_be_in_tree, int UNIV_MIN,
+ int UNIV_MAX) {
+ // Same cardinality
+ size_t n_in_set = should_be_in_tree.size();
+ size_t n_in_tree = tree.testSize();
+ if (n_in_set != n_in_tree) {
+ return false;
+ }
+
+ // Tree is not wildly out of balance. Depth should not exceed 1.44 *
+ // log2(size).
+ size_t tree_depth = tree.testDepth();
+ size_t log2_size = 0;
+ {
+ size_t n = n_in_tree;
+ while (n > 0) {
+ n = n >> 1;
+ log2_size += 1;
+ }
+ }
+ // Actually a tighter limit than stated above. For these test cases, the
+ // tree is either perfectly balanced or within one level of being so (hence
+ // the +1).
+ if (tree_depth > log2_size + 1) {
+ return false;
+ }
+
+ // Check that everything that should be in the tree is in it, and vice
+ // versa.
+ for (int i = UNIV_MIN; i < UNIV_MAX; i++) {
+ bool should_be_in = should_be_in_tree.find(i) != should_be_in_tree.end();
+
+ // Look it up with a null comparator (so `contains` compares
+ // directly)
+ bool is_in = tree.testContains(CmpInt(i));
+ if (is_in != should_be_in) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+template <typename T>
+bool SetContains(std::set<T> s, const T& v) {
+ return s.find(v) != s.end();
+}
+
+BEGIN_TEST(testAvlTree_main) {
+ static const int UNIV_MIN = 5000;
+ static const int UNIV_MAX = 5999;
+ static const int UNIV_SIZE = UNIV_MAX - UNIV_MIN + 1;
+
+ LifoAlloc alloc(4096);
+ AvlTreeTestIF<CmpInt, CmpInt> tree(&alloc);
+ std::set<int> should_be_in_tree;
+
+ // Add numbers to the tree, checking as we go.
+ for (int i = UNIV_MIN; i < UNIV_MAX; i++) {
+ // Idiotic but simple
+ if (i % 3 != 0) {
+ continue;
+ }
+ bool was_added = tree.testInsert(CmpInt(i));
+ should_be_in_tree.insert(i);
+ CHECK(was_added);
+ CHECK(TreeIsPlausible(tree, should_be_in_tree, UNIV_MIN, UNIV_MAX));
+ }
+
+ // Then remove the middle half of the tree, also checking.
+ for (int i = UNIV_MIN + UNIV_SIZE / 4; i < UNIV_MIN + 3 * (UNIV_SIZE / 4);
+ i++) {
+ // Note that here, we're asking to delete a bunch of numbers that aren't
+ // in the tree. It should remain valid throughout.
+ bool was_removed = tree.testRemove(CmpInt(i));
+ bool should_have_been_removed = SetContains(should_be_in_tree, i);
+ CHECK(was_removed == should_have_been_removed);
+ should_be_in_tree.erase(i);
+ CHECK(TreeIsPlausible(tree, should_be_in_tree, UNIV_MIN, UNIV_MAX));
+ }
+
+ // Now add some numbers which are already in the tree.
+ for (int i = UNIV_MIN; i < UNIV_MIN + UNIV_SIZE / 4; i++) {
+ if (i % 3 != 0) {
+ continue;
+ }
+ bool was_added = tree.testInsert(CmpInt(i));
+ bool should_have_been_added = !SetContains(should_be_in_tree, i);
+ CHECK(was_added == should_have_been_added);
+ should_be_in_tree.insert(i);
+ CHECK(TreeIsPlausible(tree, should_be_in_tree, UNIV_MIN, UNIV_MAX));
+ }
+
+ // Then remove all numbers from the tree, in reverse order.
+ for (int ir = UNIV_MIN; ir < UNIV_MAX; ir++) {
+ int i = UNIV_MIN + (UNIV_MAX - ir);
+ bool was_removed = tree.testRemove(CmpInt(i));
+ bool should_have_been_removed = SetContains(should_be_in_tree, i);
+ CHECK(was_removed == should_have_been_removed);
+ should_be_in_tree.erase(i);
+ CHECK(TreeIsPlausible(tree, should_be_in_tree, UNIV_MIN, UNIV_MAX));
+ }
+
+ // Now the tree should be empty.
+ CHECK(should_be_in_tree.empty());
+ CHECK(tree.testSize() == 0);
+
+ // Now delete some more stuff. Tree should still be empty :-)
+ for (int i = UNIV_MIN + 10; i < UNIV_MIN + 100; i++) {
+ CHECK(should_be_in_tree.empty());
+ CHECK(tree.testSize() == 0);
+ bool was_removed = tree.testRemove(CmpInt(i));
+ CHECK(!was_removed);
+ CHECK(TreeIsPlausible(tree, should_be_in_tree, UNIV_MIN, UNIV_MAX));
+ }
+
+ // The tree root should be NULL.
+ CHECK(tree.testGetRoot() == nullptr);
+ CHECK(tree.testGetFreeList() != nullptr);
+
+ // Check the freelist to the extent we can: it's non-circular, and the
+ // elements look plausible. If it's not shorter than the specified length
+ // then it must have a cycle, since the tests above won't have resulted in
+ // more than 400 free nodes at the end.
+ CHECK(tree.testFreeListLooksValid(400 /*arbitrary*/));
+
+ // Test iteration, first in a tree with 9 nodes. This tests the general
+ // case.
+ {
+ CHECK(tree.testSize() == 0);
+ for (int i = 10; i < 100; i += 10) {
+ bool was_inserted = tree.testInsert(CmpInt(i));
+ CHECK(was_inserted);
+ }
+
+ // Test iteration across the whole tree.
+ AvlTreeTestIF<CmpInt, CmpInt>::Iter iter(&tree);
+ // `expect` produces (independently) the next expected number. `remaining`
+ // counts the number items of items remaining.
+ int expect = 10;
+ int remaining = 9;
+ while (iter.hasMore()) {
+ CmpInt ci = iter.next();
+ CHECK(ci.get() == expect);
+ expect += 10;
+ remaining--;
+ }
+ CHECK(remaining == 0);
+
+ // Test iteration from a specified start point.
+ for (int i = 10; i < 100; i += 10) {
+ for (int j = i - 1; j <= i + 1; j++) {
+ // Set up `expect` and `remaining`.
+ remaining = (100 - i) / 10;
+ switch (j % 10) {
+ case 0:
+ expect = j;
+ break;
+ case 1:
+ expect = j + 9;
+ remaining--;
+ break;
+ case 9:
+ expect = j + 1;
+ break;
+ default:
+ MOZ_CRASH();
+ }
+ AvlTreeTestIF<CmpInt, CmpInt>::Iter iter(&tree, CmpInt(j));
+ while (iter.hasMore()) {
+ CmpInt ci = iter.next();
+ CHECK(ci.get() == expect);
+ expect += 10;
+ remaining--;
+ }
+ CHECK(remaining == 0);
+ }
+ }
+ }
+
+ // Now with a completely empty tree.
+ {
+ AvlTreeTestIF<CmpInt, CmpInt> emptyTree(&alloc);
+ CHECK(emptyTree.testSize() == 0);
+ // Full tree iteration gets us nothing.
+ AvlTreeTestIF<CmpInt, CmpInt>::Iter iter1(&emptyTree);
+ CHECK(!iter1.hasMore());
+ // Starting iteration with any number gets us nothing.
+ AvlTreeTestIF<CmpInt, CmpInt>::Iter iter2(&emptyTree, CmpInt(42));
+ CHECK(!iter2.hasMore());
+ }
+
+ // Finally with a one-element tree.
+ {
+ AvlTreeTestIF<CmpInt, CmpInt> unitTree(&alloc);
+ bool was_inserted = unitTree.testInsert(CmpInt(1337));
+ CHECK(was_inserted);
+ CHECK(unitTree.testSize() == 1);
+ // Try full tree iteration.
+ AvlTreeTestIF<CmpInt, CmpInt>::Iter iter3(&unitTree);
+ CHECK(iter3.hasMore());
+ CmpInt ci = iter3.next();
+ CHECK(ci.get() == 1337);
+ CHECK(!iter3.hasMore());
+ for (int i = 1336; i <= 1338; i++) {
+ int remaining = i < 1338 ? 1 : 0;
+ int expect = i < 1338 ? 1337 : 99999 /*we'll never use this*/;
+ AvlTreeTestIF<CmpInt, CmpInt>::Iter iter4(&unitTree, CmpInt(i));
+ while (iter4.hasMore()) {
+ CmpInt ci = iter4.next();
+ CHECK(ci.get() == expect);
+ remaining--;
+ // expect doesn't change, we only expect it (or nothing)
+ }
+ CHECK(remaining == 0);
+ }
+ }
+
+ return true;
+}
+END_TEST(testAvlTree_main)
diff --git a/js/src/jsapi-tests/testBigInt.cpp b/js/src/jsapi-tests/testBigInt.cpp
new file mode 100644
index 0000000000..1a1dbfe4ec
--- /dev/null
+++ b/js/src/jsapi-tests/testBigInt.cpp
@@ -0,0 +1,768 @@
+/* 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/FloatingPoint.h" // mozilla::NumberIsInt32
+#include "mozilla/Range.h" // mozilla::Range
+#include "mozilla/Span.h" // mozilla::MakeStringSpan
+
+#include <stdint.h>
+
+#include "js/BigInt.h" // JS::{,Number,String,SimpleString}ToBigInt, JS::ToBig{I,Ui}nt64
+#include "js/CharacterEncoding.h" // JS::Const{Latin1,TwoByte}Chars
+#include "js/Conversions.h" // JS::ToString
+#include "js/ErrorReport.h" // JS::ErrorReportBuilder, JSEXN_SYNTAXERR
+#include "js/Exception.h" // JS::StealPendingExceptionStack, JS_IsExceptionPending
+#include "js/friend/ErrorMessages.h" // JSMSG_*
+#include "js/RootingAPI.h" // JS::Rooted
+#include "js/String.h" // JS_StringEqualsLiteral
+#include "js/Value.h" // JS::FalseValue, JS::Value
+
+#include "jsapi-tests/tests.h"
+#include "util/Text.h" // js::InflateString
+
+struct JS_PUBLIC_API JSContext;
+class JS_PUBLIC_API JSString;
+
+namespace JS {
+
+class JS_PUBLIC_API BigInt;
+
+} // namespace JS
+
+BEGIN_TEST(testToBigInt64) {
+ JS::Rooted<JS::Value> v(cx);
+
+ EVAL("0n", &v);
+ CHECK(v.isBigInt());
+ CHECK(JS::ToBigInt64(v.toBigInt()) == 0);
+
+ EVAL("9223372036854775807n", &v);
+ CHECK(v.isBigInt());
+ CHECK(JS::ToBigInt64(v.toBigInt()) == 9223372036854775807L);
+
+ EVAL("-9223372036854775808n", &v);
+ CHECK(v.isBigInt());
+ CHECK(JS::ToBigInt64(v.toBigInt()) == -9223372036854775807L - 1L);
+
+ return true;
+}
+END_TEST(testToBigInt64)
+
+BEGIN_TEST(testToBigUint64) {
+ JS::Rooted<JS::Value> v(cx);
+
+ EVAL("0n", &v);
+ CHECK(v.isBigInt());
+ CHECK(JS::ToBigUint64(v.toBigInt()) == 0);
+
+ EVAL("18446744073709551615n", &v);
+ CHECK(v.isBigInt());
+ CHECK(JS::ToBigUint64(v.toBigInt()) == 18446744073709551615UL);
+
+ return true;
+}
+END_TEST(testToBigUint64)
+
+#define GENERATE_INTTYPE_TEST(bits) \
+ BEGIN_TEST(testNumberToBigInt_Int##bits) { \
+ int##bits##_t i = INT##bits##_MIN; \
+ JS::BigInt* bi = JS::NumberToBigInt(cx, i); \
+ CHECK(bi); \
+ CHECK(JS::ToBigInt64(bi) == i); \
+ \
+ i = INT##bits##_MAX; \
+ bi = JS::NumberToBigInt(cx, i); \
+ CHECK(bi); \
+ CHECK(JS::ToBigInt64(bi) == i); \
+ \
+ uint##bits##_t u = 0; \
+ bi = JS::NumberToBigInt(cx, u); \
+ CHECK(bi); \
+ CHECK(JS::ToBigUint64(bi) == 0); \
+ \
+ u = UINT##bits##_MAX; \
+ bi = JS::NumberToBigInt(cx, u); \
+ CHECK(bi); \
+ CHECK(JS::ToBigUint64(bi) == u); \
+ \
+ return true; \
+ } \
+ END_TEST(testNumberToBigInt_Int##bits)
+
+GENERATE_INTTYPE_TEST(8);
+GENERATE_INTTYPE_TEST(16);
+GENERATE_INTTYPE_TEST(32);
+GENERATE_INTTYPE_TEST(64);
+
+#undef GENERATE_INTTYPE_TEST
+
+#define GENERATE_SIGNED_VALUE_TEST(type, tag, val) \
+ BEGIN_TEST(testNumberToBigInt_##type##_##tag) { \
+ type v = val; \
+ JS::BigInt* bi = JS::NumberToBigInt(cx, v); \
+ CHECK(bi); \
+ CHECK(JS::ToBigInt64(bi) == (val)); \
+ return true; \
+ } \
+ END_TEST(testNumberToBigInt_##type##_##tag)
+
+#define GENERATE_UNSIGNED_VALUE_TEST(type, tag, val) \
+ BEGIN_TEST(testNumberToBigInt_##type##_##tag) { \
+ type v = val; \
+ JS::BigInt* bi = JS::NumberToBigInt(cx, v); \
+ CHECK(bi); \
+ CHECK(JS::ToBigUint64(bi) == (val)); \
+ return true; \
+ } \
+ END_TEST(testNumberToBigInt_##type##_##tag)
+
+GENERATE_SIGNED_VALUE_TEST(int, zero, 0);
+GENERATE_SIGNED_VALUE_TEST(int, aValue, -42);
+GENERATE_UNSIGNED_VALUE_TEST(unsigned, zero, 0);
+GENERATE_UNSIGNED_VALUE_TEST(unsigned, aValue, 42);
+GENERATE_SIGNED_VALUE_TEST(long, zero, 0);
+GENERATE_SIGNED_VALUE_TEST(long, aValue, -42);
+GENERATE_UNSIGNED_VALUE_TEST(uintptr_t, zero, 0);
+GENERATE_UNSIGNED_VALUE_TEST(uintptr_t, aValue, 42);
+GENERATE_UNSIGNED_VALUE_TEST(size_t, zero, 0);
+GENERATE_UNSIGNED_VALUE_TEST(size_t, aValue, 42);
+GENERATE_SIGNED_VALUE_TEST(double, zero, 0);
+GENERATE_SIGNED_VALUE_TEST(double, aValue, -42);
+
+#undef GENERATE_SIGNED_VALUE_TEST
+#undef GENERATE_UNSIGNED_VALUE_TEST
+
+BEGIN_TEST(testNumberToBigInt_bool) {
+ JS::BigInt* bi = JS::NumberToBigInt(cx, true);
+ CHECK(bi);
+ CHECK(JS::ToBigUint64(bi) == 1);
+
+ bi = JS::NumberToBigInt(cx, false);
+ CHECK(bi);
+ CHECK(JS::ToBigUint64(bi) == 0);
+
+ return true;
+}
+END_TEST(testNumberToBigInt_bool)
+
+BEGIN_TEST(testNumberToBigInt_NonIntegerValueFails) {
+ JS::BigInt* bi = JS::NumberToBigInt(cx, 3.1416);
+ CHECK_NULL(bi);
+ CHECK(JS_IsExceptionPending(cx));
+ JS_ClearPendingException(cx);
+ return true;
+}
+END_TEST(testNumberToBigInt_NonIntegerValueFails)
+
+BEGIN_TEST(testStringToBigInt_FromTwoByteStringSpan) {
+ mozilla::Range<const char16_t> input{
+ mozilla::MakeStringSpan(u"18446744073709551616")};
+ JS::BigInt* bi = JS::StringToBigInt(cx, input);
+ CHECK(bi);
+ JS::Rooted<JS::Value> val(cx, JS::BigIntValue(bi));
+ JS::Rooted<JSString*> str(cx, JS::ToString(cx, val));
+ CHECK(str);
+ bool match;
+ CHECK(JS_StringEqualsLiteral(cx, str, "18446744073709551616", &match));
+ CHECK(match);
+ return true;
+}
+END_TEST(testStringToBigInt_FromTwoByteStringSpan)
+
+BEGIN_TEST(testStringToBigInt_FromLatin1Range) {
+ const JS::Latin1Char string[] = "12345 and some junk at the end";
+ JS::ConstLatin1Chars range(string, 5);
+ JS::BigInt* bi = JS::StringToBigInt(cx, range);
+ CHECK(bi);
+ CHECK(JS::ToBigInt64(bi) == 12345);
+ return true;
+}
+END_TEST(testStringToBigInt_FromLatin1Range)
+
+BEGIN_TEST(testStringToBigInt_FromTwoByteRange) {
+ const char16_t string[] = u"12345 and some junk at the end";
+ JS::ConstTwoByteChars range(string, 5);
+ JS::BigInt* bi = JS::StringToBigInt(cx, range);
+ CHECK(bi);
+ CHECK(JS::ToBigInt64(bi) == 12345);
+ return true;
+}
+END_TEST(testStringToBigInt_FromTwoByteRange)
+
+BEGIN_TEST(testStringToBigInt_AcceptedInput) {
+ CHECK(Allowed(u"", 0));
+ CHECK(Allowed(u"\n", 0));
+ CHECK(Allowed(u" ", 0));
+ CHECK(Allowed(u"0\n", 0));
+ CHECK(Allowed(u"0 ", 0));
+ CHECK(Allowed(u"\n1", 1));
+ CHECK(Allowed(u" 1", 1));
+ CHECK(Allowed(u"\n2 ", 2));
+ CHECK(Allowed(u" 2\n", 2));
+ CHECK(Allowed(u"0b11", 3));
+ CHECK(Allowed(u"0x17", 23));
+ CHECK(Allowed(u"-5", -5));
+ CHECK(Allowed(u"+5", 5));
+ CHECK(Allowed(u"-0", 0));
+
+ CHECK(Fails(u"!!!!!!111one1111one1!1!1!!"));
+ CHECK(Fails(u"3.1416"));
+ CHECK(Fails(u"6.022e23"));
+ CHECK(Fails(u"1e3"));
+ CHECK(Fails(u".25"));
+ CHECK(Fails(u".25e2"));
+ CHECK(Fails(u"1_000_000"));
+ CHECK(Fails(u"3n"));
+ CHECK(Fails(u"-0x3"));
+ CHECK(Fails(u"Infinity"));
+
+ return true;
+}
+
+template <size_t N>
+inline bool Allowed(const char16_t (&str)[N], int64_t expected) {
+ JS::BigInt* bi = JS::StringToBigInt(cx, mozilla::MakeStringSpan(str));
+ CHECK(bi);
+ CHECK(JS::ToBigInt64(bi) == expected);
+ return true;
+}
+
+template <size_t N>
+inline bool Fails(const char16_t (&str)[N]) {
+ JS::BigInt* bi = JS::StringToBigInt(cx, mozilla::MakeStringSpan(str));
+ CHECK_NULL(bi);
+ CHECK(JS_IsExceptionPending(cx));
+
+ JS::ExceptionStack exnStack(cx);
+ CHECK(JS::StealPendingExceptionStack(cx, &exnStack));
+
+ JS::ErrorReportBuilder report(cx);
+ CHECK(report.init(cx, exnStack, JS::ErrorReportBuilder::NoSideEffects));
+ CHECK(report.report()->exnType == JSEXN_SYNTAXERR);
+ CHECK(report.report()->errorNumber == JSMSG_BIGINT_INVALID_SYNTAX);
+
+ CHECK(!JS_IsExceptionPending(cx));
+
+ return true;
+}
+END_TEST(testStringToBigInt_AcceptedInput)
+
+BEGIN_TEST(testSimpleStringToBigInt_AcceptedInput) {
+ CHECK(Allowed("12345", 10, 12345));
+ CHECK(Allowed("+12345", 10, 12345));
+ CHECK(Allowed("-12345", 10, -12345));
+ CHECK(Allowed("775", 8, 0775));
+ CHECK(Allowed("+775", 8, 0775));
+ CHECK(Allowed("-775", 8, -0775));
+ CHECK(Allowed("cAfE", 16, 0xCAFE));
+ CHECK(Allowed("+cAfE", 16, +0xCAFE));
+ CHECK(Allowed("-cAfE", 16, -0xCAFE));
+ CHECK(Allowed("-0", 10, 0));
+
+ CHECK(Fails("", 10));
+ CHECK(Fails("\n", 10));
+ CHECK(Fails(" ", 10));
+ CHECK(Fails("0\n", 10));
+ CHECK(Fails("0 ", 10));
+ CHECK(Fails("\n1", 10));
+ CHECK(Fails(" 1", 10));
+ CHECK(Fails("\n2 ", 10));
+ CHECK(Fails(" 2\n", 10));
+ CHECK(Fails("0b11", 2));
+ CHECK(Fails("0x17", 16));
+ CHECK(Fails("!!!!!!111one1111one1!1!1!!", 10));
+ CHECK(Fails("3.1416", 10));
+ CHECK(Fails("6.022e23", 10));
+ CHECK(Fails("1e3", 10));
+ CHECK(Fails(".25", 10));
+ CHECK(Fails(".25e2", 10));
+ CHECK(Fails("1_000_000", 10));
+ CHECK(Fails("3n", 10));
+ CHECK(Fails("-0x3", 10));
+ CHECK(Fails("Infinity", 10));
+ CHECK(Fails("555", 4));
+ CHECK(Fails("fff", 15));
+
+ return true;
+}
+
+template <size_t N>
+inline bool Allowed(const char (&str)[N], unsigned radix, int64_t expected) {
+ JS::BigInt* bi = JS::SimpleStringToBigInt(cx, {str, N - 1}, radix);
+ CHECK(bi);
+ CHECK(JS::ToBigInt64(bi) == expected);
+ return true;
+}
+
+template <size_t N>
+inline bool Fails(const char (&str)[N], unsigned radix) {
+ JS::BigInt* bi = JS::SimpleStringToBigInt(cx, {str, N - 1}, radix);
+ CHECK_NULL(bi);
+ CHECK(JS_IsExceptionPending(cx));
+
+ JS::ExceptionStack exnStack(cx);
+ CHECK(JS::StealPendingExceptionStack(cx, &exnStack));
+
+ JS::ErrorReportBuilder report(cx);
+ CHECK(report.init(cx, exnStack, JS::ErrorReportBuilder::NoSideEffects));
+ CHECK(report.report()->exnType == JSEXN_SYNTAXERR);
+ CHECK(report.report()->errorNumber == JSMSG_BIGINT_INVALID_SYNTAX);
+
+ CHECK(!JS_IsExceptionPending(cx));
+
+ return true;
+}
+END_TEST(testSimpleStringToBigInt_AcceptedInput)
+
+BEGIN_TEST(testSimpleStringToBigInt_AllPossibleDigits) {
+ const char allPossible[] =
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
+ JS::BigInt* bi =
+ JS::SimpleStringToBigInt(cx, mozilla::MakeStringSpan(allPossible), 36);
+ CHECK(bi);
+ JS::Rooted<JS::Value> val(cx, JS::BigIntValue(bi));
+ JS::Rooted<JSString*> str(cx, JS::ToString(cx, val));
+ CHECK(str);
+
+ // Answer calculated using Python:
+ // int('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890', 36)
+ // Do not trust online base-36 calculators for values > UINT32_MAX!
+ bool match;
+ CHECK(
+ JS_StringEqualsLiteral(cx, str,
+ "8870050151210747660007771095260505028056221996735"
+ "67534007158336222790086855213834764150805438340",
+ &match));
+ CHECK(match);
+ return true;
+}
+END_TEST(testSimpleStringToBigInt_AllPossibleDigits)
+
+BEGIN_TEST(testSimpleStringToBigInt_RadixOutOfRange) {
+ CHECK(RadixOutOfRange(1));
+ CHECK(RadixOutOfRange(37));
+ return true;
+}
+
+inline bool RadixOutOfRange(unsigned radix) {
+ JS::BigInt* bi =
+ JS::SimpleStringToBigInt(cx, mozilla::MakeStringSpan("1"), radix);
+ CHECK_NULL(bi);
+ CHECK(JS_IsExceptionPending(cx));
+
+ JS::ExceptionStack exnStack(cx);
+ CHECK(JS::StealPendingExceptionStack(cx, &exnStack));
+
+ JS::ErrorReportBuilder report(cx);
+ CHECK(report.init(cx, exnStack, JS::ErrorReportBuilder::NoSideEffects));
+ CHECK(report.report()->exnType == JSEXN_RANGEERR);
+ CHECK(report.report()->errorNumber == JSMSG_BAD_RADIX);
+
+ CHECK(!JS_IsExceptionPending(cx));
+
+ return true;
+}
+END_TEST(testSimpleStringToBigInt_RadixOutOfRange)
+
+BEGIN_TEST(testToBigInt_Undefined) {
+ JS::Rooted<JS::Value> v(cx, JS::UndefinedValue());
+ JS::BigInt* bi = JS::ToBigInt(cx, v);
+ CHECK_NULL(bi);
+ CHECK(JS_IsExceptionPending(cx));
+ JS_ClearPendingException(cx);
+ return true;
+}
+END_TEST(testToBigInt_Undefined)
+
+BEGIN_TEST(testToBigInt_Null) {
+ JS::Rooted<JS::Value> v(cx, JS::NullValue());
+ JS::BigInt* bi = JS::ToBigInt(cx, v);
+ CHECK_NULL(bi);
+ CHECK(JS_IsExceptionPending(cx));
+ JS_ClearPendingException(cx);
+ return true;
+}
+END_TEST(testToBigInt_Null)
+
+BEGIN_TEST(testToBigInt_Boolean) {
+ JS::Rooted<JS::Value> v(cx, JS::TrueValue());
+ JS::BigInt* bi = JS::ToBigInt(cx, v);
+ CHECK(bi);
+ CHECK(JS::ToBigInt64(bi) == 1);
+
+ v = JS::FalseValue();
+ bi = JS::ToBigInt(cx, v);
+ CHECK(bi);
+ CHECK(JS::ToBigInt64(bi) == 0);
+
+ return true;
+}
+END_TEST(testToBigInt_Boolean)
+
+BEGIN_TEST(testToBigInt_BigInt) {
+ JS::Rooted<JS::Value> v(cx);
+ EVAL("42n", &v);
+ JS::BigInt* bi = JS::ToBigInt(cx, v);
+ CHECK(bi);
+ CHECK(JS::ToBigInt64(bi) == 42);
+ return true;
+}
+END_TEST(testToBigInt_BigInt)
+
+BEGIN_TEST(testToBigInt_Number) {
+ JS::Rooted<JS::Value> v(cx, JS::Int32Value(42));
+ JS::BigInt* bi = JS::ToBigInt(cx, v);
+ CHECK_NULL(bi);
+ CHECK(JS_IsExceptionPending(cx));
+ JS_ClearPendingException(cx);
+ return true;
+}
+END_TEST(testToBigInt_Number)
+
+BEGIN_TEST(testToBigInt_String) {
+ JS::Rooted<JS::Value> v(cx);
+ EVAL("'42'", &v);
+ JS::BigInt* bi = JS::ToBigInt(cx, v);
+ CHECK(bi);
+ CHECK(JS::ToBigInt64(bi) == 42);
+ return true;
+}
+END_TEST(testToBigInt_String)
+
+BEGIN_TEST(testToBigInt_Symbol) {
+ JS::Rooted<JS::Value> v(cx);
+ EVAL("Symbol.toStringTag", &v);
+ JS::BigInt* bi = JS::ToBigInt(cx, v);
+ CHECK_NULL(bi);
+ CHECK(JS_IsExceptionPending(cx));
+ JS_ClearPendingException(cx);
+ return true;
+}
+END_TEST(testToBigInt_Symbol)
+
+BEGIN_TEST(testBigIntToNumber) {
+ JS::BigInt* bi = JS::NumberToBigInt(cx, 0);
+ CHECK(bi);
+ int32_t result;
+ CHECK(mozilla::NumberIsInt32(JS::BigIntToNumber(bi), &result));
+ CHECK_EQUAL(result, 0);
+
+ bi = JS::NumberToBigInt(cx, 100);
+ CHECK(bi);
+ CHECK(JS::BigIntToNumber(bi) == 100);
+
+ bi = JS::NumberToBigInt(cx, -100);
+ CHECK(bi);
+ CHECK(JS::BigIntToNumber(bi) == -100);
+
+ JS::Rooted<JS::Value> v(cx);
+
+ EVAL("18446744073709551615n", &v);
+ CHECK(v.isBigInt());
+ double numberValue = JS::BigIntToNumber(v.toBigInt());
+ EVAL("Number(18446744073709551615n)", &v);
+ CHECK(v.isNumber());
+ CHECK(numberValue == v.toNumber());
+
+ EVAL((std::string(500, '9') + "n").c_str(), &v);
+ CHECK(v.isBigInt());
+ CHECK(JS::BigIntToNumber(v.toBigInt()) == INFINITY);
+
+ return true;
+}
+END_TEST(testBigIntToNumber)
+
+BEGIN_TEST(testBigIntIsNegative) {
+ JS::BigInt* bi = JS::NumberToBigInt(cx, 0);
+ CHECK(bi);
+ CHECK(!JS::BigIntIsNegative(bi));
+
+ bi = JS::NumberToBigInt(cx, 100);
+ CHECK(bi);
+ CHECK(!JS::BigIntIsNegative(bi));
+
+ bi = JS::NumberToBigInt(cx, -100);
+ CHECK(bi);
+ CHECK(JS::BigIntIsNegative(bi));
+
+ return true;
+}
+END_TEST(testBigIntIsNegative)
+
+#define GENERATE_INTTYPE_TEST(bits) \
+ BEGIN_TEST(testBigIntFits_Int##bits) { \
+ int64_t in = INT##bits##_MIN; \
+ JS::BigInt* bi = JS::NumberToBigInt(cx, in); \
+ CHECK(bi); \
+ int##bits##_t i; \
+ CHECK(JS::BigIntFits(bi, &i)); \
+ CHECK_EQUAL(i, in); \
+ \
+ in = int64_t(INT##bits##_MIN) - 1; \
+ bi = JS::NumberToBigInt(cx, in); \
+ CHECK(bi); \
+ CHECK(!JS::BigIntFits(bi, &i)); \
+ \
+ in = INT64_MIN; \
+ bi = JS::NumberToBigInt(cx, in); \
+ CHECK(bi); \
+ CHECK(!JS::BigIntFits(bi, &i)); \
+ \
+ in = INT##bits##_MAX; \
+ bi = JS::NumberToBigInt(cx, in); \
+ CHECK(bi); \
+ CHECK(JS::BigIntFits(bi, &i)); \
+ CHECK_EQUAL(i, in); \
+ \
+ in = int64_t(INT##bits##_MAX) + 1; \
+ bi = JS::NumberToBigInt(cx, in); \
+ CHECK(bi); \
+ CHECK(!JS::BigIntFits(bi, &i)); \
+ \
+ in = INT64_MAX; \
+ bi = JS::NumberToBigInt(cx, in); \
+ CHECK(bi); \
+ CHECK(!JS::BigIntFits(bi, &i)); \
+ \
+ uint64_t uin = 0; \
+ bi = JS::NumberToBigInt(cx, uin); \
+ CHECK(bi); \
+ uint##bits##_t u; \
+ CHECK(JS::BigIntFits(bi, &u)); \
+ CHECK_EQUAL(u, uin); \
+ \
+ uin = UINT##bits##_MAX; \
+ bi = JS::NumberToBigInt(cx, uin); \
+ CHECK(bi); \
+ CHECK(JS::BigIntFits(bi, &u)); \
+ CHECK_EQUAL(u, uin); \
+ \
+ uin = uint64_t(UINT##bits##_MAX) + 1; \
+ bi = JS::NumberToBigInt(cx, uin); \
+ CHECK(bi); \
+ CHECK(!JS::BigIntFits(bi, &u)); \
+ \
+ uin = UINT64_MAX; \
+ bi = JS::NumberToBigInt(cx, uin); \
+ CHECK(bi); \
+ CHECK(!JS::BigIntFits(bi, &u)); \
+ \
+ return true; \
+ } \
+ END_TEST(testBigIntFits_Int##bits)
+
+GENERATE_INTTYPE_TEST(8);
+GENERATE_INTTYPE_TEST(16);
+GENERATE_INTTYPE_TEST(32);
+
+#undef GENERATE_INTTYPE_TEST
+
+BEGIN_TEST(testBigIntFits_Int64) {
+ int64_t in = INT64_MIN;
+ JS::BigInt* bi = JS::NumberToBigInt(cx, in);
+ CHECK(bi);
+ int64_t i;
+ CHECK(JS::BigIntFits(bi, &i));
+ CHECK_EQUAL(i, in);
+
+ in = INT64_MAX;
+ bi = JS::NumberToBigInt(cx, in);
+ CHECK(bi);
+ CHECK(JS::BigIntFits(bi, &i));
+ CHECK_EQUAL(i, in);
+
+ JS::RootedValue v(cx);
+
+ EVAL((std::string(500, '9') + "n").c_str(), &v);
+ CHECK(v.isBigInt());
+ CHECK(!JS::BigIntFits(v.toBigInt(), &i));
+
+ EVAL(("-" + std::string(500, '9') + "n").c_str(), &v);
+ CHECK(v.isBigInt());
+ CHECK(!JS::BigIntFits(v.toBigInt(), &i));
+
+ return true;
+}
+END_TEST(testBigIntFits_Int64)
+
+BEGIN_TEST(testBigIntFits_Uint64) {
+ uint64_t uin = 0;
+ JS::BigInt* bi = JS::NumberToBigInt(cx, uin);
+ CHECK(bi);
+ uint64_t u;
+ CHECK(JS::BigIntFits(bi, &u));
+ CHECK_EQUAL(u, uin);
+
+ uin = UINT64_MAX;
+ bi = JS::NumberToBigInt(cx, uin);
+ CHECK(bi);
+ CHECK(JS::BigIntFits(bi, &u));
+ CHECK_EQUAL(u, uin);
+
+ JS::RootedValue v(cx);
+
+ EVAL((std::string(500, '9') + "n").c_str(), &v);
+ CHECK(v.isBigInt());
+ CHECK(!JS::BigIntFits(v.toBigInt(), &u));
+
+ return true;
+}
+END_TEST(testBigIntFits_Uint64)
+
+#define GENERATE_SIGNED_VALUE_TEST(type, tag, val) \
+ BEGIN_TEST(testBigIntFits_##type##_##tag) { \
+ int64_t v = val; \
+ JS::BigInt* bi = JS::NumberToBigInt(cx, v); \
+ CHECK(bi); \
+ type result; \
+ CHECK(JS::BigIntFits(bi, &result)); \
+ CHECK_EQUAL(v, result); \
+ return true; \
+ } \
+ END_TEST(testBigIntFits_##type##_##tag)
+
+#define GENERATE_UNSIGNED_VALUE_TEST(type, tag, val) \
+ BEGIN_TEST(testBigIntFits_##type##_##tag) { \
+ uint64_t v = val; \
+ JS::BigInt* bi = JS::NumberToBigInt(cx, v); \
+ CHECK(bi); \
+ type result; \
+ CHECK(JS::BigIntFits(bi, &result)); \
+ CHECK_EQUAL(v, result); \
+ return true; \
+ } \
+ END_TEST(testBigIntFits_##type##_##tag)
+
+GENERATE_SIGNED_VALUE_TEST(int, zero, 0);
+GENERATE_SIGNED_VALUE_TEST(int, aValue, -42);
+GENERATE_UNSIGNED_VALUE_TEST(unsigned, zero, 0);
+GENERATE_UNSIGNED_VALUE_TEST(unsigned, aValue, 42);
+GENERATE_SIGNED_VALUE_TEST(long, zero, 0);
+GENERATE_SIGNED_VALUE_TEST(long, aValue, -42);
+GENERATE_UNSIGNED_VALUE_TEST(uintptr_t, zero, 0);
+GENERATE_UNSIGNED_VALUE_TEST(uintptr_t, aValue, 42);
+GENERATE_UNSIGNED_VALUE_TEST(size_t, zero, 0);
+GENERATE_UNSIGNED_VALUE_TEST(size_t, aValue, 42);
+
+#undef GENERATE_SIGNED_VALUE_TEST
+#undef GENERATE_UNSIGNED_VALUE_TEST
+
+BEGIN_TEST(testBigIntFitsNumber) {
+ JS::BigInt* bi = JS::NumberToBigInt(cx, 0);
+ CHECK(bi);
+ double num;
+ CHECK(JS::BigIntFitsNumber(bi, &num));
+ int32_t result;
+ CHECK(mozilla::NumberIsInt32(num, &result));
+ CHECK_EQUAL(result, 0);
+
+ bi = JS::NumberToBigInt(cx, 100);
+ CHECK(bi);
+ CHECK(JS::BigIntFitsNumber(bi, &num));
+ CHECK(num == 100);
+
+ bi = JS::NumberToBigInt(cx, -100);
+ CHECK(bi);
+ CHECK(JS::BigIntFitsNumber(bi, &num));
+ CHECK(num == -100);
+
+ JS::Rooted<JS::Value> v(cx);
+
+ EVAL("BigInt(Number.MAX_SAFE_INTEGER)", &v);
+ CHECK(v.isBigInt());
+ CHECK(JS::BigIntFitsNumber(v.toBigInt(), &num));
+
+ EVAL("BigInt(Number.MIN_SAFE_INTEGER)", &v);
+ CHECK(v.isBigInt());
+ CHECK(JS::BigIntFitsNumber(v.toBigInt(), &num));
+
+ EVAL("BigInt(Number.MAX_SAFE_INTEGER) + 1n", &v);
+ CHECK(v.isBigInt());
+ CHECK(!JS::BigIntFitsNumber(v.toBigInt(), &num));
+
+ EVAL("BigInt(Number.MIN_SAFE_INTEGER) - 1n", &v);
+ CHECK(v.isBigInt());
+ CHECK(!JS::BigIntFitsNumber(v.toBigInt(), &num));
+
+ EVAL((std::string(500, '9') + "n").c_str(), &v);
+ CHECK(v.isBigInt());
+ CHECK(!JS::BigIntFitsNumber(v.toBigInt(), &num));
+
+ EVAL(("-" + std::string(500, '9') + "n").c_str(), &v);
+ CHECK(v.isBigInt());
+ CHECK(!JS::BigIntFitsNumber(v.toBigInt(), &num));
+
+ return true;
+}
+END_TEST(testBigIntFitsNumber)
+
+BEGIN_TEST(testBigIntToString) {
+ CHECK(Convert(12345, 10, "12345"));
+ CHECK(Convert(-12345, 10, "-12345"));
+ CHECK(Convert(0775, 8, "775"));
+ CHECK(Convert(-0775, 8, "-775"));
+ CHECK(Convert(0xCAFE, 16, "cafe"));
+ CHECK(Convert(-0xCAFE, 16, "-cafe"));
+
+ return true;
+}
+
+template <size_t N>
+inline bool Convert(int64_t input, uint8_t radix, const char (&expected)[N]) {
+ JS::Rooted<JS::BigInt*> bi(cx, JS::NumberToBigInt(cx, input));
+ CHECK(bi);
+ JS::Rooted<JSString*> str(cx, JS::BigIntToString(cx, bi, radix));
+ CHECK(str);
+
+ bool match;
+ CHECK(JS_StringEqualsLiteral(cx, str, expected, &match));
+ CHECK(match);
+
+ return true;
+}
+END_TEST(testBigIntToString)
+
+BEGIN_TEST(testBigIntToString_AllPossibleDigits) {
+ const char allPossible[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
+ JS::Rooted<JS::BigInt*> bi(
+ cx,
+ JS::SimpleStringToBigInt(cx, mozilla::MakeStringSpan(allPossible), 36));
+ CHECK(bi);
+ JS::Rooted<JSString*> str(cx, JS::BigIntToString(cx, bi, 36));
+ CHECK(str);
+
+ bool match;
+ CHECK(JS_StringEqualsLiteral(cx, str, "abcdefghijklmnopqrstuvwxyz1234567890",
+ &match));
+ CHECK(match);
+ return true;
+}
+END_TEST(testBigIntToString_AllPossibleDigits)
+
+BEGIN_TEST(testBigIntToString_RadixOutOfRange) {
+ CHECK(RadixOutOfRange(1));
+ CHECK(RadixOutOfRange(37));
+ return true;
+}
+
+inline bool RadixOutOfRange(uint8_t radix) {
+ JS::Rooted<JS::BigInt*> bi(cx, JS::NumberToBigInt(cx, 1));
+ CHECK(bi);
+ JSString* s = JS::BigIntToString(cx, bi, radix);
+ CHECK_NULL(s);
+ CHECK(JS_IsExceptionPending(cx));
+
+ JS::ExceptionStack exnStack(cx);
+ CHECK(JS::StealPendingExceptionStack(cx, &exnStack));
+
+ JS::ErrorReportBuilder report(cx);
+ CHECK(report.init(cx, exnStack, JS::ErrorReportBuilder::NoSideEffects));
+ CHECK(report.report()->exnType == JSEXN_RANGEERR);
+ CHECK(report.report()->errorNumber == JSMSG_BAD_RADIX);
+
+ CHECK(!JS_IsExceptionPending(cx));
+
+ return true;
+}
+END_TEST(testBigIntToString_RadixOutOfRange)
diff --git a/js/src/jsapi-tests/testBoundFunction.cpp b/js/src/jsapi-tests/testBoundFunction.cpp
new file mode 100644
index 0000000000..74f51ffd35
--- /dev/null
+++ b/js/src/jsapi-tests/testBoundFunction.cpp
@@ -0,0 +1,32 @@
+/* -*- 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 "jsapi-tests/tests.h"
+
+BEGIN_TEST(testBoundFunction) {
+ EXEC("function foo() {}");
+ JS::RootedValue foo(cx);
+ EVAL("foo", &foo);
+ JS::RootedValue bound(cx);
+ EVAL("foo.bind(1)", &bound);
+
+ JS::Rooted<JSObject*> foofun(cx, &foo.toObject());
+ JS::Rooted<JSObject*> boundfun(cx, &bound.toObject());
+
+ CHECK(!JS_ObjectIsBoundFunction(foofun));
+ CHECK(JS_ObjectIsBoundFunction(boundfun));
+
+ CHECK(!JS_GetBoundFunctionTarget(foofun));
+ JSObject* target = JS_GetBoundFunctionTarget(boundfun);
+ CHECK(!!target);
+ CHECK(JS_ObjectIsFunction(target));
+ JS::RootedValue targetVal(cx, JS::ObjectValue(*target));
+ CHECK_SAME(foo, targetVal);
+
+ return true;
+}
+END_TEST(testBoundFunction)
diff --git a/js/src/jsapi-tests/testBug604087.cpp b/js/src/jsapi-tests/testBug604087.cpp
new file mode 100644
index 0000000000..a237a58a19
--- /dev/null
+++ b/js/src/jsapi-tests/testBug604087.cpp
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ *
+ * Tests JS_TransplantObject
+ */
+/* 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 "js/friend/WindowProxy.h" // js::SetWindowProxyClass
+#include "js/GlobalObject.h" // JS_NewGlobalObject
+#include "js/Wrapper.h"
+#include "js/WrapperCallbacks.h"
+#include "jsapi-tests/tests.h"
+#include "vm/JSObject.h"
+#include "vm/ProxyObject.h"
+
+const JSClass OuterWrapperClass = PROXY_CLASS_DEF(
+ "Proxy", JSCLASS_HAS_RESERVED_SLOTS(1) /* additional class flags */);
+
+static JSObject* wrap(JSContext* cx, JS::HandleObject toWrap,
+ JS::HandleObject target) {
+ JSAutoRealm ar(cx, target);
+ JS::RootedObject wrapper(cx, toWrap);
+ if (!JS_WrapObject(cx, &wrapper)) {
+ return nullptr;
+ }
+ return wrapper;
+}
+
+static void PreWrap(JSContext* cx, JS::HandleObject scope,
+ JS::HandleObject origObj, JS::HandleObject obj,
+ JS::HandleObject objectPassedToWrap,
+ JS::MutableHandleObject retObj) {
+ JS_GC(cx);
+ retObj.set(obj);
+}
+
+static JSObject* Wrap(JSContext* cx, JS::HandleObject existing,
+ JS::HandleObject obj) {
+ return js::Wrapper::New(cx, obj, &js::CrossCompartmentWrapper::singleton);
+}
+
+static const JSWrapObjectCallbacks WrapObjectCallbacks = {Wrap, PreWrap};
+
+BEGIN_TEST(testBug604087) {
+ js::SetWindowProxyClass(cx, &OuterWrapperClass);
+
+ js::WrapperOptions options;
+ options.setClass(&OuterWrapperClass);
+ JS::RootedObject outerObj(
+ cx, js::Wrapper::New(cx, global, &js::Wrapper::singleton, options));
+ JS::RealmOptions globalOptions;
+ JS::RootedObject compartment2(
+ cx, JS_NewGlobalObject(cx, getGlobalClass(), nullptr,
+ JS::FireOnNewGlobalHook, globalOptions));
+ CHECK(compartment2 != nullptr);
+ JS::RootedObject compartment3(
+ cx, JS_NewGlobalObject(cx, getGlobalClass(), nullptr,
+ JS::FireOnNewGlobalHook, globalOptions));
+ CHECK(compartment3 != nullptr);
+ JS::RootedObject compartment4(
+ cx, JS_NewGlobalObject(cx, getGlobalClass(), nullptr,
+ JS::FireOnNewGlobalHook, globalOptions));
+ CHECK(compartment4 != nullptr);
+
+ JS::RootedObject c2wrapper(cx, wrap(cx, outerObj, compartment2));
+ CHECK(c2wrapper);
+ c2wrapper->as<js::ProxyObject>().setReservedSlot(0, js::Int32Value(2));
+
+ JS::RootedObject c3wrapper(cx, wrap(cx, outerObj, compartment3));
+ CHECK(c3wrapper);
+ c3wrapper->as<js::ProxyObject>().setReservedSlot(0, js::Int32Value(3));
+
+ JS::RootedObject c4wrapper(cx, wrap(cx, outerObj, compartment4));
+ CHECK(c4wrapper);
+ c4wrapper->as<js::ProxyObject>().setReservedSlot(0, js::Int32Value(4));
+ compartment4 = c4wrapper = nullptr;
+
+ JS::RootedObject next(cx);
+ {
+ JSAutoRealm ar(cx, compartment2);
+ next = js::Wrapper::New(cx, compartment2, &js::Wrapper::singleton, options);
+ CHECK(next);
+ }
+
+ JS_SetWrapObjectCallbacks(cx, &WrapObjectCallbacks);
+ CHECK(JS_TransplantObject(cx, outerObj, next));
+ return true;
+}
+END_TEST(testBug604087)
diff --git a/js/src/jsapi-tests/testCallArgs.cpp b/js/src/jsapi-tests/testCallArgs.cpp
new file mode 100644
index 0000000000..07a1a83cfc
--- /dev/null
+++ b/js/src/jsapi-tests/testCallArgs.cpp
@@ -0,0 +1,85 @@
+/* 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/Utf8.h" // mozilla::Utf8Unit
+
+#include "js/CompilationAndEvaluation.h" // JS::Evaluate
+#include "js/PropertyAndElement.h" // JS_DefineFunction
+#include "js/SourceText.h" // JS::Source{Ownership,Text}
+#include "jsapi-tests/tests.h"
+
+static bool CustomNative(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+
+ MOZ_RELEASE_ASSERT(!JS_IsExceptionPending(cx));
+
+ MOZ_RELEASE_ASSERT(!args.isConstructing());
+ args.rval().setUndefined();
+ MOZ_RELEASE_ASSERT(!args.isConstructing());
+
+ return true;
+}
+
+BEGIN_TEST(testCallArgs_isConstructing_native) {
+ CHECK(JS_DefineFunction(cx, global, "customNative", CustomNative, 0, 0));
+
+ JS::CompileOptions opts(cx);
+ opts.setFileAndLine(__FILE__, __LINE__ + 4);
+
+ JS::RootedValue result(cx);
+
+ static const char code[] = "new customNative();";
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, code, strlen(code), JS::SourceOwnership::Borrowed));
+
+ CHECK(!JS::Evaluate(cx, opts, srcBuf, &result));
+
+ CHECK(JS_IsExceptionPending(cx));
+ JS_ClearPendingException(cx);
+
+ EVAL("customNative();", &result);
+ CHECK(result.isUndefined());
+
+ return true;
+}
+END_TEST(testCallArgs_isConstructing_native)
+
+static bool CustomConstructor(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+
+ MOZ_RELEASE_ASSERT(!JS_IsExceptionPending(cx));
+
+ if (args.isConstructing()) {
+ JSObject* obj = JS_NewPlainObject(cx);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+
+ MOZ_RELEASE_ASSERT(args.isConstructing());
+ } else {
+ args.rval().setUndefined();
+
+ MOZ_RELEASE_ASSERT(!args.isConstructing());
+ }
+
+ return true;
+}
+
+BEGIN_TEST(testCallArgs_isConstructing_constructor) {
+ CHECK(JS_DefineFunction(cx, global, "customConstructor", CustomConstructor, 0,
+ JSFUN_CONSTRUCTOR));
+
+ JS::RootedValue result(cx);
+
+ EVAL("new customConstructor();", &result);
+ CHECK(result.isObject());
+
+ EVAL("customConstructor();", &result);
+ CHECK(result.isUndefined());
+
+ return true;
+}
+END_TEST(testCallArgs_isConstructing_constructor)
diff --git a/js/src/jsapi-tests/testCallNonGenericMethodOnProxy.cpp b/js/src/jsapi-tests/testCallNonGenericMethodOnProxy.cpp
new file mode 100644
index 0000000000..54244ea768
--- /dev/null
+++ b/js/src/jsapi-tests/testCallNonGenericMethodOnProxy.cpp
@@ -0,0 +1,87 @@
+/* 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 "js/CallAndConstruct.h"
+#include "js/GlobalObject.h"
+#include "js/Object.h" // JS::GetClass, JS::GetReservedSlot
+#include "jsapi-tests/tests.h"
+
+using namespace JS;
+
+static const JSClass CustomClass = {"CustomClass",
+ JSCLASS_HAS_RESERVED_SLOTS(1)};
+
+static const uint32_t CUSTOM_SLOT = 0;
+
+static bool IsCustomClass(JS::Handle<JS::Value> v) {
+ return v.isObject() && JS::GetClass(&v.toObject()) == &CustomClass;
+}
+
+static bool CustomMethodImpl(JSContext* cx, const CallArgs& args) {
+ MOZ_RELEASE_ASSERT(IsCustomClass(args.thisv()));
+ args.rval().set(JS::GetReservedSlot(&args.thisv().toObject(), CUSTOM_SLOT));
+ return true;
+}
+
+static bool CustomMethod(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod(cx, IsCustomClass, CustomMethodImpl, args);
+}
+
+BEGIN_TEST(test_CallNonGenericMethodOnProxy) {
+ // Create the first global object and compartment
+ JS::RealmOptions options;
+ JS::RootedObject globalA(
+ cx, JS_NewGlobalObject(cx, getGlobalClass(), nullptr,
+ JS::FireOnNewGlobalHook, options));
+ CHECK(globalA);
+
+ JS::RootedObject customA(cx, JS_NewObject(cx, &CustomClass));
+ CHECK(customA);
+ JS_SetReservedSlot(customA, CUSTOM_SLOT, Int32Value(17));
+
+ JS::RootedFunction customMethodA(
+ cx, JS_NewFunction(cx, CustomMethod, 0, 0, "customMethodA"));
+ CHECK(customMethodA);
+
+ JS::RootedValue rval(cx);
+ CHECK(JS_CallFunction(cx, customA, customMethodA,
+ JS::HandleValueArray::empty(), &rval));
+ CHECK_SAME(rval, Int32Value(17));
+
+ // Now create the second global object and compartment...
+ {
+ JS::RealmOptions options;
+ JS::RootedObject globalB(
+ cx, JS_NewGlobalObject(cx, getGlobalClass(), nullptr,
+ JS::FireOnNewGlobalHook, options));
+ CHECK(globalB);
+
+ // ...and enter it.
+ JSAutoRealm enter(cx, globalB);
+ JS::RootedObject customB(cx, JS_NewObject(cx, &CustomClass));
+ CHECK(customB);
+ JS_SetReservedSlot(customB, CUSTOM_SLOT, Int32Value(42));
+
+ JS::RootedFunction customMethodB(
+ cx, JS_NewFunction(cx, CustomMethod, 0, 0, "customMethodB"));
+ CHECK(customMethodB);
+
+ JS::RootedValue rval(cx);
+ CHECK(JS_CallFunction(cx, customB, customMethodB,
+ JS::HandleValueArray::empty(), &rval));
+ CHECK_SAME(rval, Int32Value(42));
+
+ JS::RootedObject wrappedCustomA(cx, customA);
+ CHECK(JS_WrapObject(cx, &wrappedCustomA));
+
+ JS::RootedValue rval2(cx);
+ CHECK(JS_CallFunction(cx, wrappedCustomA, customMethodB,
+ JS::HandleValueArray::empty(), &rval2));
+ CHECK_SAME(rval, Int32Value(42));
+ }
+
+ return true;
+}
+END_TEST(test_CallNonGenericMethodOnProxy)
diff --git a/js/src/jsapi-tests/testChromeBuffer.cpp b/js/src/jsapi-tests/testChromeBuffer.cpp
new file mode 100644
index 0000000000..251d6a18ad
--- /dev/null
+++ b/js/src/jsapi-tests/testChromeBuffer.cpp
@@ -0,0 +1,257 @@
+/* -*- 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/Utf8.h" // mozilla::Utf8Unit
+
+#include "js/CallAndConstruct.h" // JS_CallFunctionValue
+#include "js/CompilationAndEvaluation.h" // JS::CompileFunction
+#include "js/ContextOptions.h"
+#include "js/GlobalObject.h" // JS_NewGlobalObject
+#include "js/PropertyAndElement.h" // JS_DefineProperty
+#include "js/SourceText.h" // JS::Source{Ownership,Text}
+#include "jsapi-tests/tests.h"
+#include "util/Text.h"
+
+static TestJSPrincipals system_principals(1);
+
+static const JSClass global_class = {"global",
+ JSCLASS_IS_GLOBAL | JSCLASS_GLOBAL_FLAGS,
+ &JS::DefaultGlobalClassOps};
+
+static JS::PersistentRootedObject trusted_glob;
+static JS::PersistentRootedObject trusted_fun;
+
+static bool CallTrusted(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+
+ bool ok = false;
+ {
+ JSAutoRealm ar(cx, trusted_glob);
+ JS::RootedValue funVal(cx, JS::ObjectValue(*trusted_fun));
+ ok = JS_CallFunctionValue(cx, nullptr, funVal,
+ JS::HandleValueArray::empty(), args.rval());
+ }
+ return ok;
+}
+
+BEGIN_TEST(testChromeBuffer) {
+ JS_SetTrustedPrincipals(cx, &system_principals);
+
+ JS::RealmOptions options;
+ trusted_glob.init(cx,
+ JS_NewGlobalObject(cx, &global_class, &system_principals,
+ JS::FireOnNewGlobalHook, options));
+ CHECK(trusted_glob);
+
+ JS::RootedFunction fun(cx);
+
+ /*
+ * Check that, even after untrusted content has exhausted the stack, code
+ * compiled with "trusted principals" can run using reserved trusted-only
+ * buffer space.
+ */
+ {
+ // Disable the JIT because if we don't this test fails. See bug 1160414.
+ uint32_t oldBaselineInterpreterEnabled;
+ CHECK(JS_GetGlobalJitCompilerOption(
+ cx, JSJITCOMPILER_BASELINE_INTERPRETER_ENABLE,
+ &oldBaselineInterpreterEnabled));
+ JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_BASELINE_INTERPRETER_ENABLE,
+ 0);
+ uint32_t oldBaselineJitEnabled;
+ CHECK(JS_GetGlobalJitCompilerOption(cx, JSJITCOMPILER_BASELINE_ENABLE,
+ &oldBaselineJitEnabled));
+ JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_BASELINE_ENABLE, 0);
+ {
+ JSAutoRealm ar(cx, trusted_glob);
+ const char* paramName = "x";
+ static const char bytes[] = "return x ? 1 + trusted(x-1) : 0";
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, bytes, js_strlen(bytes),
+ JS::SourceOwnership::Borrowed));
+
+ JS::CompileOptions options(cx);
+ options.setFileAndLine("", 0);
+
+ JS::RootedObjectVector emptyScopeChain(cx);
+ fun = JS::CompileFunction(cx, emptyScopeChain, options, "trusted", 1,
+ &paramName, srcBuf);
+ CHECK(fun);
+ CHECK(JS_DefineProperty(cx, trusted_glob, "trusted", fun,
+ JSPROP_ENUMERATE));
+ trusted_fun.init(cx, JS_GetFunctionObject(fun));
+ }
+
+ JS::RootedValue v(cx, JS::ObjectValue(*trusted_fun));
+ CHECK(JS_WrapValue(cx, &v));
+
+ const char* paramName = "trusted";
+ static const char bytes[] =
+ "try { "
+ " return untrusted(trusted); "
+ "} catch (e) { "
+ " try { "
+ " return trusted(100); "
+ " } catch(e) { "
+ " return -1; "
+ " } "
+ "} ";
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, bytes, js_strlen(bytes),
+ JS::SourceOwnership::Borrowed));
+
+ JS::CompileOptions options(cx);
+ options.setFileAndLine("", 0);
+
+ JS::RootedObjectVector emptyScopeChain(cx);
+ fun = JS::CompileFunction(cx, emptyScopeChain, options, "untrusted", 1,
+ &paramName, srcBuf);
+ CHECK(fun);
+ CHECK(JS_DefineProperty(cx, global, "untrusted", fun, JSPROP_ENUMERATE));
+
+ JS::RootedValue rval(cx);
+ CHECK(JS_CallFunction(cx, nullptr, fun, JS::HandleValueArray(v), &rval));
+ CHECK(rval.toInt32() == 100);
+ JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_BASELINE_INTERPRETER_ENABLE,
+ oldBaselineInterpreterEnabled);
+ JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_BASELINE_ENABLE,
+ oldBaselineJitEnabled);
+ }
+
+ /*
+ * Check that content called from chrome in the reserved-buffer space
+ * immediately ooms.
+ */
+ {
+ {
+ JSAutoRealm ar(cx, trusted_glob);
+
+ const char* paramName = "untrusted";
+ static const char bytes[] =
+ "try { "
+ " untrusted(); "
+ "} catch (e) { "
+ " /* "
+ " * Careful! We must not reenter JS "
+ " * that might try to push a frame. "
+ " */ "
+ " return 'From trusted: ' + "
+ " e.name + ': ' + e.message; "
+ "} ";
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, bytes, js_strlen(bytes),
+ JS::SourceOwnership::Borrowed));
+
+ JS::CompileOptions options(cx);
+ options.setFileAndLine("", 0);
+
+ JS::RootedObjectVector emptyScopeChain(cx);
+ fun = JS::CompileFunction(cx, emptyScopeChain, options, "trusted", 1,
+ &paramName, srcBuf);
+ CHECK(fun);
+ CHECK(JS_DefineProperty(cx, trusted_glob, "trusted", fun,
+ JSPROP_ENUMERATE));
+ trusted_fun = JS_GetFunctionObject(fun);
+ }
+
+ JS::RootedValue v(cx, JS::ObjectValue(*trusted_fun));
+ CHECK(JS_WrapValue(cx, &v));
+
+ const char* paramName = "trusted";
+ static const char bytes[] =
+ "try { "
+ " return untrusted(trusted); "
+ "} catch (e) { "
+ " return trusted(untrusted); "
+ "} ";
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, bytes, js_strlen(bytes),
+ JS::SourceOwnership::Borrowed));
+
+ JS::CompileOptions options(cx);
+ options.setFileAndLine("", 0);
+
+ JS::RootedObjectVector emptyScopeChain(cx);
+ fun = JS::CompileFunction(cx, emptyScopeChain, options, "untrusted", 1,
+ &paramName, srcBuf);
+ CHECK(fun);
+ CHECK(JS_DefineProperty(cx, global, "untrusted", fun, JSPROP_ENUMERATE));
+
+ JS::RootedValue rval(cx);
+ CHECK(JS_CallFunction(cx, nullptr, fun, JS::HandleValueArray(v), &rval));
+#ifndef JS_SIMULATOR_ARM64
+ // The ARM64 simulator does not share a common implementation with the other
+ // simulators, and has slightly different end-of-stack behavior. Instead of
+ // failing with "too much recursion," it executes one more function call and
+ // fails with a type error. This behavior is not incorrect.
+ bool match;
+ CHECK(JS_StringEqualsAscii(
+ cx, rval.toString(), "From trusted: InternalError: too much recursion",
+ &match));
+ CHECK(match);
+#endif
+ }
+
+ {
+ {
+ JSAutoRealm ar(cx, trusted_glob);
+
+ static const char bytes[] = "return 42";
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, bytes, js_strlen(bytes),
+ JS::SourceOwnership::Borrowed));
+
+ JS::CompileOptions options(cx);
+ options.setFileAndLine("", 0);
+
+ JS::RootedObjectVector emptyScopeChain(cx);
+ fun = JS::CompileFunction(cx, emptyScopeChain, options, "trusted", 0,
+ nullptr, srcBuf);
+ CHECK(fun);
+ CHECK(JS_DefineProperty(cx, trusted_glob, "trusted", fun,
+ JSPROP_ENUMERATE));
+ trusted_fun = JS_GetFunctionObject(fun);
+ }
+
+ JS::RootedFunction fun(
+ cx, JS_NewFunction(cx, CallTrusted, 0, 0, "callTrusted"));
+ JS::RootedObject callTrusted(cx, JS_GetFunctionObject(fun));
+
+ const char* paramName = "f";
+ static const char bytes[] =
+ "try { "
+ " return untrusted(trusted); "
+ "} catch (e) { "
+ " return f(); "
+ "} ";
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, bytes, js_strlen(bytes),
+ JS::SourceOwnership::Borrowed));
+
+ JS::CompileOptions options(cx);
+ options.setFileAndLine("", 0);
+
+ JS::RootedObjectVector emptyScopeChain(cx);
+ fun = JS::CompileFunction(cx, emptyScopeChain, options, "untrusted", 1,
+ &paramName, srcBuf);
+ CHECK(fun);
+ CHECK(JS_DefineProperty(cx, global, "untrusted", fun, JSPROP_ENUMERATE));
+
+ JS::RootedValue arg(cx, JS::ObjectValue(*callTrusted));
+ JS::RootedValue rval(cx);
+ CHECK(JS_CallFunction(cx, nullptr, fun, JS::HandleValueArray(arg), &rval));
+ CHECK(rval.toInt32() == 42);
+ }
+
+ return true;
+}
+END_TEST(testChromeBuffer)
diff --git a/js/src/jsapi-tests/testCompileNonSyntactic.cpp b/js/src/jsapi-tests/testCompileNonSyntactic.cpp
new file mode 100644
index 0000000000..466ac8bfa2
--- /dev/null
+++ b/js/src/jsapi-tests/testCompileNonSyntactic.cpp
@@ -0,0 +1,114 @@
+/* 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/RefPtr.h" // RefPtr
+#include "mozilla/Utf8.h" // mozilla::Utf8Unit
+
+#include <string_view>
+
+#include "js/CompilationAndEvaluation.h" // JS::Compile
+#include "js/CompileOptions.h" // JS::CompileOptions, JS::InstantiateOptions
+#include "js/experimental/JSStencil.h" // JS::Stencil, JS::CompileToStencilOffThread, JS::FinishCompileToStencilOffThread, JS::InstantiateGlobalStencil
+
+#include "js/SourceText.h" // JS::Source{Ownership,Text}
+#include "jsapi-tests/tests.h"
+#include "vm/HelperThreads.h"
+#include "vm/Monitor.h"
+#include "vm/MutexIDs.h"
+
+using namespace JS;
+using js::AutoLockMonitor;
+
+struct OffThreadTask {
+ OffThreadTask() : monitor(js::mutexid::ShellOffThreadState), token(nullptr) {}
+
+ OffThreadToken* waitUntilDone(JSContext* cx) {
+ AutoLockMonitor alm(monitor);
+ while (!token) {
+ alm.wait();
+ }
+ OffThreadToken* result = token;
+ token = nullptr;
+ return result;
+ }
+
+ void markDone(JS::OffThreadToken* tokenArg) {
+ AutoLockMonitor alm(monitor);
+ token = tokenArg;
+ alm.notify();
+ }
+
+ static void OffThreadCallback(OffThreadToken* token, void* context) {
+ auto self = static_cast<OffThreadTask*>(context);
+ self->markDone(token);
+ }
+
+ js::Monitor monitor MOZ_UNANNOTATED;
+ OffThreadToken* token;
+};
+
+BEGIN_TEST(testCompileNonsyntactic) {
+ CHECK(testCompile(true));
+
+ CHECK(testCompile(false));
+ return true;
+}
+
+bool testCompile(bool nonSyntactic) {
+ static constexpr std::string_view src = "42\n";
+ static constexpr std::u16string_view src_16 = u"42\n";
+
+ static_assert(src.length() == src_16.length(),
+ "Source buffers must be same length");
+
+ JS::CompileOptions options(cx);
+ options.setNonSyntacticScope(nonSyntactic);
+
+ JS::SourceText<char16_t> buf16;
+ CHECK(buf16.init(cx, src_16.data(), src_16.length(),
+ JS::SourceOwnership::Borrowed));
+
+ JS::SourceText<mozilla::Utf8Unit> buf8;
+ CHECK(buf8.init(cx, src.data(), src.length(), JS::SourceOwnership::Borrowed));
+
+ JS::RootedScript script(cx);
+
+ script = Compile(cx, options, buf16);
+ CHECK(script);
+ CHECK_EQUAL(script->hasNonSyntacticScope(), nonSyntactic);
+
+ script = Compile(cx, options, buf8);
+ CHECK(script);
+ CHECK_EQUAL(script->hasNonSyntacticScope(), nonSyntactic);
+
+ {
+ JS::SourceText<char16_t> srcBuf;
+ CHECK(srcBuf.init(cx, src_16.data(), src_16.length(),
+ JS::SourceOwnership::Borrowed));
+
+ script = Compile(cx, options, srcBuf);
+ CHECK(script);
+ CHECK_EQUAL(script->hasNonSyntacticScope(), nonSyntactic);
+ }
+
+ options.forceAsync = true;
+ OffThreadTask task;
+ OffThreadToken* token;
+
+ JS::SourceText<char16_t> srcBuf;
+ RefPtr<JS::Stencil> stencil;
+ CHECK(srcBuf.init(cx, src_16.data(), src_16.length(),
+ JS::SourceOwnership::Borrowed));
+
+ CHECK(CompileToStencilOffThread(cx, options, srcBuf, task.OffThreadCallback,
+ &task));
+ CHECK(token = task.waitUntilDone(cx));
+ CHECK(stencil = FinishOffThreadStencil(cx, token));
+ InstantiateOptions instantiateOptions(options);
+ CHECK(script = InstantiateGlobalStencil(cx, instantiateOptions, stencil));
+ CHECK_EQUAL(script->hasNonSyntacticScope(), nonSyntactic);
+
+ return true;
+}
+END_TEST(testCompileNonsyntactic);
diff --git a/js/src/jsapi-tests/testCompileScript.cpp b/js/src/jsapi-tests/testCompileScript.cpp
new file mode 100644
index 0000000000..886af8dbe7
--- /dev/null
+++ b/js/src/jsapi-tests/testCompileScript.cpp
@@ -0,0 +1,242 @@
+/* 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/RefPtr.h" // RefPtr
+#include "mozilla/ScopeExit.h" // MakeScopeExit
+#include "mozilla/Utf8.h" // mozilla::Utf8Unit
+
+#include "frontend/CompilationStencil.h" // JS::Stencil
+#include "js/CompileOptions.h" // JS::CompileOptions, JS::InstantiateOptions
+#include "js/experimental/CompileScript.h" // JS::NewFrontendContext
+#include "js/SourceText.h" // JS::Source{Ownership,Text}
+#include "jsapi-tests/tests.h"
+#include "vm/ErrorReporting.h"
+#include "vm/JSONPrinter.h" // js::JSONPrinter
+
+using namespace JS;
+
+template <typename T>
+static void dump(const T& data) {
+ js::Fprinter printer(stderr);
+ js::JSONPrinter json(printer, true);
+
+ data->dump(json);
+ printer.put("\n");
+}
+
+BEGIN_TEST(testCompileScript) {
+ CHECK(testCompile());
+ CHECK(testNonsyntacticCompile());
+ CHECK(testCompileModule());
+ CHECK(testPrepareForInstantiate());
+
+ return true;
+}
+
+bool testCompile() {
+ static constexpr std::string_view src = "42\n";
+ static constexpr std::u16string_view src_16 = u"42\n";
+
+ static_assert(src.length() == src_16.length(),
+ "Source buffers must be same length");
+
+ JS::CompileOptions options(cx);
+
+ JS::FrontendContext* fc = JS::NewFrontendContext();
+ CHECK(fc);
+ auto destroyFc =
+ mozilla::MakeScopeExit([fc] { JS::DestroyFrontendContext(fc); });
+
+ { // 16-bit characters
+ JS::SourceText<char16_t> buf16;
+ CHECK(buf16.init(cx, src_16.data(), src_16.length(),
+ JS::SourceOwnership::Borrowed));
+
+ JS::CompilationStorage compileStorage;
+ RefPtr<JS::Stencil> stencil =
+ CompileGlobalScriptToStencil(fc, options, buf16, compileStorage);
+ CHECK(stencil);
+ CHECK(stencil->scriptExtra.size() == 1);
+ CHECK(stencil->scriptExtra[0].extent.sourceStart == 0);
+ CHECK(stencil->scriptExtra[0].extent.sourceEnd == 3);
+ CHECK(stencil->scriptData.size() == 1);
+ CHECK(stencil->scriptData[0].hasSharedData()); // has generated bytecode
+ CHECK(stencil->scriptData[0].gcThingsLength == 1);
+ CHECK(compileStorage.hasInput());
+ }
+
+ { // 8-bit characters
+ JS::SourceText<mozilla::Utf8Unit> buf8;
+ CHECK(
+ buf8.init(cx, src.data(), src.length(), JS::SourceOwnership::Borrowed));
+
+ JS::CompilationStorage compileStorage;
+ RefPtr<JS::Stencil> stencil =
+ CompileGlobalScriptToStencil(fc, options, buf8, compileStorage);
+ CHECK(stencil);
+ CHECK(stencil->scriptExtra.size() == 1);
+ CHECK(stencil->scriptExtra[0].extent.sourceStart == 0);
+ CHECK(stencil->scriptExtra[0].extent.sourceEnd == 3);
+ CHECK(stencil->scriptData.size() == 1);
+ CHECK(stencil->scriptData[0].hasSharedData()); // has generated bytecode
+ CHECK(stencil->scriptData[0].gcThingsLength == 1);
+ CHECK(compileStorage.hasInput());
+ }
+
+ { // propagates failures
+ static constexpr std::string_view badSrc = "{ a: 1, b: 3\n";
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, badSrc.data(), badSrc.length(),
+ JS::SourceOwnership::Borrowed));
+
+ JS::CompilationStorage compileStorage;
+ RefPtr<JS::Stencil> stencil =
+ CompileGlobalScriptToStencil(fc, options, srcBuf, compileStorage);
+ CHECK(!stencil);
+ CHECK(fc->maybeError().isSome());
+ const js::CompileError& error = fc->maybeError().ref();
+ CHECK(JSEXN_SYNTAXERR == error.exnType);
+ CHECK(error.lineno == 1);
+ CHECK(error.column == 9);
+ }
+
+ return true;
+}
+
+bool testNonsyntacticCompile() {
+ const char* chars =
+ "function f() { return x; }"
+ "f();";
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, chars, strlen(chars), JS::SourceOwnership::Borrowed));
+
+ JS::CompileOptions options(cx);
+ options.setNonSyntacticScope(true);
+
+ JS::FrontendContext* fc = JS::NewFrontendContext();
+ CHECK(fc);
+ auto destroyFc =
+ mozilla::MakeScopeExit([fc] { JS::DestroyFrontendContext(fc); });
+
+ JS::CompilationStorage compileStorage;
+ RefPtr<JS::Stencil> stencil =
+ CompileGlobalScriptToStencil(fc, options, srcBuf, compileStorage);
+ CHECK(stencil);
+
+ JS::InstantiateOptions instantiateOptions(options);
+ JS::RootedScript script(
+ cx, JS::InstantiateGlobalStencil(cx, instantiateOptions, stencil));
+ CHECK(script);
+ CHECK(script->hasNonSyntacticScope());
+
+ return true;
+}
+
+bool testCompileModule() {
+ static constexpr std::string_view src = "import 'a'; 42\n";
+ static constexpr std::u16string_view src_16 = u"import 'a'; 42\n";
+
+ static_assert(src.length() == src_16.length(),
+ "Source buffers must be same length");
+
+ JS::CompileOptions options(cx);
+
+ JS::FrontendContext* fc = JS::NewFrontendContext();
+ CHECK(fc);
+ auto destroyFc =
+ mozilla::MakeScopeExit([fc] { JS::DestroyFrontendContext(fc); });
+
+ { // 16-bit characters
+ JS::SourceText<char16_t> buf16;
+ CHECK(buf16.init(cx, src_16.data(), src_16.length(),
+ JS::SourceOwnership::Borrowed));
+
+ JS::CompilationStorage compileStorage;
+ RefPtr<JS::Stencil> stencil =
+ CompileModuleScriptToStencil(fc, options, buf16, compileStorage);
+ CHECK(stencil);
+ CHECK(stencil->isModule());
+ CHECK(stencil->scriptExtra.size() == 1);
+ CHECK(stencil->scriptExtra[0].extent.sourceStart == 0);
+ CHECK(stencil->scriptExtra[0].extent.sourceEnd == 15);
+ CHECK(stencil->scriptData.size() == 1);
+ CHECK(stencil->scriptData[0].hasSharedData()); // has generated bytecode
+ CHECK(stencil->scriptData[0].gcThingsLength == 1);
+ CHECK(compileStorage.hasInput());
+ }
+
+ { // 8-bit characters
+ JS::SourceText<mozilla::Utf8Unit> buf8;
+ CHECK(
+ buf8.init(cx, src.data(), src.length(), JS::SourceOwnership::Borrowed));
+
+ JS::CompilationStorage compileStorage;
+ RefPtr<JS::Stencil> stencil =
+ CompileModuleScriptToStencil(fc, options, buf8, compileStorage);
+ CHECK(stencil);
+ CHECK(stencil->scriptExtra.size() == 1);
+ CHECK(stencil->scriptExtra[0].extent.sourceStart == 0);
+ CHECK(stencil->scriptExtra[0].extent.sourceEnd == 15);
+ CHECK(stencil->scriptData.size() == 1);
+ CHECK(stencil->scriptData[0].hasSharedData()); // has generated bytecode
+ CHECK(stencil->scriptData[0].gcThingsLength == 1);
+ CHECK(compileStorage.hasInput());
+ }
+
+ { // propagates failures
+ static constexpr std::string_view badSrc = "{ a: 1, b: 3\n";
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, badSrc.data(), badSrc.length(),
+ JS::SourceOwnership::Borrowed));
+
+ JS::CompilationStorage compileStorage;
+ RefPtr<JS::Stencil> stencil =
+ CompileModuleScriptToStencil(fc, options, srcBuf, compileStorage);
+ CHECK(!stencil);
+ CHECK(fc->maybeError().isSome());
+ const js::CompileError& error = fc->maybeError().ref();
+ CHECK(JSEXN_SYNTAXERR == error.exnType);
+ CHECK(error.lineno == 1);
+ CHECK(error.column == 9);
+ }
+
+ return true;
+}
+
+bool testPrepareForInstantiate() {
+ static constexpr std::u16string_view src_16 =
+ u"function f() { return {'field': 42};}; f()['field']\n";
+
+ JS::CompileOptions options(cx);
+
+ JS::SourceText<char16_t> buf16;
+ CHECK(buf16.init(cx, src_16.data(), src_16.length(),
+ JS::SourceOwnership::Borrowed));
+
+ JS::FrontendContext* fc = JS::NewFrontendContext();
+ CHECK(fc);
+ auto destroyFc =
+ mozilla::MakeScopeExit([fc] { JS::DestroyFrontendContext(fc); });
+
+ JS::CompilationStorage compileStorage;
+ RefPtr<JS::Stencil> stencil =
+ CompileGlobalScriptToStencil(fc, options, buf16, compileStorage);
+ CHECK(stencil);
+ CHECK(stencil->scriptData.size() == 2);
+ CHECK(stencil->scopeData.size() == 1); // function f
+ CHECK(stencil->parserAtomData.size() == 1); // 'field'
+ CHECK(compileStorage.hasInput());
+ CHECK(compileStorage.getInput().atomCache.empty());
+
+ JS::InstantiationStorage storage;
+ CHECK(JS::PrepareForInstantiate(fc, compileStorage, *stencil, storage));
+ CHECK(compileStorage.getInput().atomCache.size() == 1); // 'field'
+ CHECK(storage.isValid());
+ // TODO storage.gcOutput_ is private, so there isn't a good way to check the
+ // scriptData and scopeData capacities
+
+ return true;
+}
+END_TEST(testCompileScript);
diff --git a/js/src/jsapi-tests/testCompileUtf8.cpp b/js/src/jsapi-tests/testCompileUtf8.cpp
new file mode 100644
index 0000000000..8799e70201
--- /dev/null
+++ b/js/src/jsapi-tests/testCompileUtf8.cpp
@@ -0,0 +1,300 @@
+/* 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/ArrayUtils.h"
+#include "mozilla/TextUtils.h"
+#include "mozilla/Utf8.h"
+
+#include <cstring>
+
+#include "js/CharacterEncoding.h"
+#include "js/CompilationAndEvaluation.h" // JS::Compile
+#include "js/Exception.h"
+#include "js/friend/ErrorMessages.h" // JSMSG_*
+#include "js/SourceText.h"
+#include "jsapi-tests/tests.h"
+#include "util/Text.h"
+#include "vm/ErrorReporting.h"
+
+using mozilla::ArrayEqual;
+using mozilla::IsAsciiHexDigit;
+using mozilla::Utf8Unit;
+
+BEGIN_TEST(testUtf8BadBytes) {
+ static const char badLeadingUnit[] = "var x = \x80";
+ CHECK(testBadUtf8(
+ badLeadingUnit, JSMSG_BAD_LEADING_UTF8_UNIT,
+ [this](JS::ConstUTF8CharsZ message) {
+ const char* chars = message.c_str();
+ CHECK(startsWith(chars, "0x80"));
+ CHECK(isBadLeadUnitMessage(chars));
+ return true;
+ },
+ "0x80"));
+
+ static const char badSecondInTwoByte[] = "var x = \xDF\x20";
+ CHECK(testBadUtf8(
+ badSecondInTwoByte, JSMSG_BAD_TRAILING_UTF8_UNIT,
+ [this](JS::ConstUTF8CharsZ message) {
+ const char* chars = message.c_str();
+ CHECK(isBadTrailingBytesMessage(chars));
+ CHECK(contains(chars, "0x20"));
+ return true;
+ },
+ "0xDF 0x20"));
+
+ static const char badSecondInThreeByte[] = "var x = \xEF\x17\xA7";
+ CHECK(testBadUtf8(
+ badSecondInThreeByte, JSMSG_BAD_TRAILING_UTF8_UNIT,
+ [this](JS::ConstUTF8CharsZ message) {
+ const char* chars = message.c_str();
+ CHECK(isBadTrailingBytesMessage(chars));
+ CHECK(contains(chars, "0x17"));
+ return true;
+ },
+ // Validating stops with the first invalid code unit and
+ // shouldn't go beyond that.
+ "0xEF 0x17"));
+
+ static const char lengthTwoTooShort[] = "var x = \xDF";
+ CHECK(testBadUtf8(
+ lengthTwoTooShort, JSMSG_NOT_ENOUGH_CODE_UNITS,
+ [this](JS::ConstUTF8CharsZ message) {
+ const char* chars = message.c_str();
+ CHECK(isNotEnoughUnitsMessage(chars));
+ CHECK(contains(chars, "0xDF"));
+ CHECK(contains(chars, " 1 byte, but 0 bytes were present"));
+ return true;
+ },
+ "0xDF"));
+
+ static const char forbiddenHighSurrogate[] = "var x = \xED\xA2\x87";
+ CHECK(testBadUtf8(
+ forbiddenHighSurrogate, JSMSG_FORBIDDEN_UTF8_CODE_POINT,
+ [this](JS::ConstUTF8CharsZ message) {
+ const char* chars = message.c_str();
+ CHECK(isSurrogateMessage(chars));
+ CHECK(contains(chars, "0xD887"));
+ return true;
+ },
+ "0xED 0xA2 0x87"));
+
+ static const char forbiddenLowSurrogate[] = "var x = \xED\xB7\xAF";
+ CHECK(testBadUtf8(
+ forbiddenLowSurrogate, JSMSG_FORBIDDEN_UTF8_CODE_POINT,
+ [this](JS::ConstUTF8CharsZ message) {
+ const char* chars = message.c_str();
+ CHECK(isSurrogateMessage(chars));
+ CHECK(contains(chars, "0xDDEF"));
+ return true;
+ },
+ "0xED 0xB7 0xAF"));
+
+ static const char oneTooBig[] = "var x = \xF4\x90\x80\x80";
+ CHECK(testBadUtf8(
+ oneTooBig, JSMSG_FORBIDDEN_UTF8_CODE_POINT,
+ [this](JS::ConstUTF8CharsZ message) {
+ const char* chars = message.c_str();
+ CHECK(isTooBigMessage(chars));
+ CHECK(contains(chars, "0x110000"));
+ return true;
+ },
+ "0xF4 0x90 0x80 0x80"));
+
+ static const char notShortestFormZero[] = "var x = \xC0\x80";
+ CHECK(testBadUtf8(
+ notShortestFormZero, JSMSG_FORBIDDEN_UTF8_CODE_POINT,
+ [this](JS::ConstUTF8CharsZ message) {
+ const char* chars = message.c_str();
+ CHECK(isNotShortestFormMessage(chars));
+ CHECK(startsWith(chars, "0x0 isn't "));
+ return true;
+ },
+ "0xC0 0x80"));
+
+ static const char notShortestFormNonzero[] = "var x = \xE0\x87\x80";
+ CHECK(testBadUtf8(
+ notShortestFormNonzero, JSMSG_FORBIDDEN_UTF8_CODE_POINT,
+ [this](JS::ConstUTF8CharsZ message) {
+ const char* chars = message.c_str();
+ CHECK(isNotShortestFormMessage(chars));
+ CHECK(startsWith(chars, "0x1C0 isn't "));
+ return true;
+ },
+ "0xE0 0x87 0x80"));
+
+ return true;
+}
+
+static constexpr size_t LengthOfByte = js_strlen("0xFF");
+
+static bool startsWithByte(const char* str) {
+ return str[0] == '0' && str[1] == 'x' && IsAsciiHexDigit(str[2]) &&
+ IsAsciiHexDigit(str[3]);
+}
+
+static bool startsWith(const char* str, const char* prefix) {
+ return std::strncmp(prefix, str, strlen(prefix)) == 0;
+}
+
+static bool contains(const char* str, const char* substr) {
+ return std::strstr(str, substr) != nullptr;
+}
+
+static bool equals(const char* str, const char* expected) {
+ return std::strcmp(str, expected) == 0;
+}
+
+static bool isBadLeadUnitMessage(const char* str) {
+ return startsWithByte(str) &&
+ equals(str + LengthOfByte,
+ " byte doesn't begin a valid UTF-8 code point");
+}
+
+static bool isBadTrailingBytesMessage(const char* str) {
+ return startsWith(str, "bad trailing UTF-8 byte ");
+}
+
+static bool isNotEnoughUnitsMessage(const char* str) {
+ return startsWithByte(str) &&
+ startsWith(str + LengthOfByte, " byte in UTF-8 must be followed by ");
+}
+
+static bool isForbiddenCodePointMessage(const char* str) {
+ return contains(str, "isn't a valid code point because");
+}
+
+static bool isSurrogateMessage(const char* str) {
+ return isForbiddenCodePointMessage(str) &&
+ contains(str, " it's a UTF-16 surrogate");
+}
+
+static bool isTooBigMessage(const char* str) {
+ return isForbiddenCodePointMessage(str) &&
+ contains(str, "the maximum code point is U+10FFFF");
+}
+
+static bool isNotShortestFormMessage(const char* str) {
+ return isForbiddenCodePointMessage(str) &&
+ contains(str, "it wasn't encoded in shortest possible form");
+}
+
+template <size_t N, typename TestMessage>
+bool testBadUtf8(const char (&chars)[N], unsigned errorNumber,
+ TestMessage testMessage, const char* badBytes) {
+ JS::Rooted<JSScript*> script(cx);
+ {
+ JS::CompileOptions options(cx);
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, chars, N - 1, JS::SourceOwnership::Borrowed));
+
+ script = JS::Compile(cx, options, srcBuf);
+ CHECK(!script);
+ }
+
+ JS::ExceptionStack exnStack(cx);
+ CHECK(JS::StealPendingExceptionStack(cx, &exnStack));
+
+ JS::ErrorReportBuilder report(cx);
+ CHECK(report.init(cx, exnStack, JS::ErrorReportBuilder::WithSideEffects));
+
+ const auto* errorReport = report.report();
+
+ CHECK(errorReport->errorNumber == errorNumber);
+
+ CHECK(testMessage(errorReport->message()));
+
+ {
+ const auto& notes = errorReport->notes;
+ CHECK(notes != nullptr);
+
+ auto iter = notes->begin();
+ CHECK(iter != notes->end());
+
+ const char* noteMessage = (*iter)->message().c_str();
+
+ // The prefix ought always be the same.
+ static constexpr char expectedPrefix[] =
+ "the code units comprising this invalid code point were: ";
+ constexpr size_t expectedPrefixLen = js_strlen(expectedPrefix);
+
+ CHECK(startsWith(noteMessage, expectedPrefix));
+
+ // The end of the prefix is the bad bytes.
+ CHECK(equals(noteMessage + expectedPrefixLen, badBytes));
+
+ ++iter;
+ CHECK(iter == notes->end());
+ }
+
+ static constexpr char16_t expectedContext[] = u"var x = ";
+ constexpr size_t expectedContextLen = js_strlen(expectedContext);
+
+ const char16_t* lineOfContext = errorReport->linebuf();
+ size_t lineOfContextLength = errorReport->linebufLength();
+
+ CHECK(lineOfContext[lineOfContextLength] == '\0');
+ CHECK(lineOfContextLength == expectedContextLen);
+
+ CHECK(std::memcmp(lineOfContext, expectedContext,
+ expectedContextLen * sizeof(char16_t)) == 0);
+
+ return true;
+}
+END_TEST(testUtf8BadBytes)
+
+BEGIN_TEST(testMultiUnitUtf8InWindow) {
+ static const char firstInWindowIsMultiUnit[] =
+ "\xCF\x80\xCF\x80 = 6.283185307; @ bad starts HERE:\x80\xFF\xFF";
+ CHECK(testContext(firstInWindowIsMultiUnit,
+ u"ππ = 6.283185307; @ bad starts HERE:"));
+
+ static const char atTokenOffsetIsMulti[] = "var z = 💯";
+ CHECK(testContext(atTokenOffsetIsMulti, u"var z = 💯"));
+
+ static const char afterTokenOffsetIsMulti[] = "var z = @💯💯💯X";
+ CHECK(testContext(afterTokenOffsetIsMulti, u"var z = @💯💯💯X"));
+
+ static const char atEndIsMulti[] = "var z = @@💯💯💯";
+ CHECK(testContext(atEndIsMulti, u"var z = @@💯💯💯"));
+
+ return true;
+}
+
+template <size_t N, size_t ContextLenWithNull>
+bool testContext(const char (&chars)[N],
+ const char16_t (&expectedContext)[ContextLenWithNull]) {
+ JS::Rooted<JSScript*> script(cx);
+ {
+ JS::CompileOptions options(cx);
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, chars, N - 1, JS::SourceOwnership::Borrowed));
+
+ script = JS::Compile(cx, options, srcBuf);
+ CHECK(!script);
+ }
+
+ JS::ExceptionStack exnStack(cx);
+ CHECK(JS::StealPendingExceptionStack(cx, &exnStack));
+
+ JS::ErrorReportBuilder report(cx);
+ CHECK(report.init(cx, exnStack, JS::ErrorReportBuilder::WithSideEffects));
+
+ const auto* errorReport = report.report();
+
+ CHECK(errorReport->errorNumber == JSMSG_ILLEGAL_CHARACTER);
+
+ const char16_t* lineOfContext = errorReport->linebuf();
+ size_t lineOfContextLength = errorReport->linebufLength();
+
+ CHECK(lineOfContext[lineOfContextLength] == '\0');
+ CHECK(lineOfContextLength == ContextLenWithNull - 1);
+
+ CHECK(ArrayEqual(lineOfContext, expectedContext, ContextLenWithNull));
+
+ return true;
+}
+END_TEST(testMultiUnitUtf8InWindow)
diff --git a/js/src/jsapi-tests/testDateToLocaleString.cpp b/js/src/jsapi-tests/testDateToLocaleString.cpp
new file mode 100644
index 0000000000..3a425ab7a6
--- /dev/null
+++ b/js/src/jsapi-tests/testDateToLocaleString.cpp
@@ -0,0 +1,63 @@
+/* -*- 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 "js/LocaleSensitive.h"
+#include "jsapi-tests/tests.h"
+
+BEGIN_TEST(testDateToLocaleString) {
+ JSRuntime* rt = JS_GetRuntime(cx);
+
+ // This test should only attempt to run if we have Intl support: necessary
+ // to properly assume that changes to the default locale will predictably
+ // affect the behavior of the locale-sensitive Date methods tested here.
+ JS::Rooted<JS::Value> haveIntl(cx);
+ EVAL("typeof Intl !== 'undefined'", &haveIntl);
+ if (!haveIntl.toBoolean()) {
+ return true;
+ }
+
+ // Pervasive assumption: our Intl support includes "de" (German) and
+ // "en" (English) and treats them differently for purposes of
+ // Date.prototype.toLocale{,Date,Time}String behavior.
+
+ // Start with German.
+ CHECK(JS_SetDefaultLocale(rt, "de"));
+
+ // The (constrained) Date object we'll use to test behavior.
+ EXEC("var d = new Date(Date.UTC(2015, 9 - 1, 17));");
+
+ // Test that toLocaleString behavior changes with default locale changes.
+ EXEC("var deAll = d.toLocaleString();");
+
+ CHECK(JS_SetDefaultLocale(rt, "en"));
+ EXEC(
+ "if (d.toLocaleString() === deAll) \n"
+ " throw 'toLocaleString results should have changed with system locale "
+ "change';");
+
+ // Test that toLocaleDateString behavior changes with default locale changes.
+ EXEC("var enDate = d.toLocaleDateString();");
+
+ CHECK(JS_SetDefaultLocale(rt, "de"));
+ EXEC(
+ "if (d.toLocaleDateString() === enDate) \n"
+ " throw 'toLocaleDateString results should have changed with system "
+ "locale change';");
+
+ // Test that toLocaleTimeString behavior changes with default locale changes.
+ EXEC("var deTime = d.toLocaleTimeString();");
+
+ CHECK(JS_SetDefaultLocale(rt, "en"));
+ EXEC(
+ "if (d.toLocaleTimeString() === deTime) \n"
+ " throw 'toLocaleTimeString results should have changed with system "
+ "locale change';");
+
+ JS_ResetDefaultLocale(rt);
+ return true;
+}
+END_TEST(testDateToLocaleString)
diff --git a/js/src/jsapi-tests/testDebugger.cpp b/js/src/jsapi-tests/testDebugger.cpp
new file mode 100644
index 0000000000..62735fa212
--- /dev/null
+++ b/js/src/jsapi-tests/testDebugger.cpp
@@ -0,0 +1,62 @@
+/* -*- 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 "js/CallAndConstruct.h"
+#include "js/GlobalObject.h" // JS_NewGlobalObject
+#include "js/PropertyAndElement.h" // JS_SetProperty
+#include "jsapi-tests/tests.h"
+#include "vm/JSContext.h"
+
+using namespace js;
+
+BEGIN_TEST(testDebugger_newScriptHook) {
+ // Test that top-level indirect eval fires the newScript hook.
+ CHECK(JS_DefineDebuggerObject(cx, global));
+ JS::RealmOptions options;
+ JS::RootedObject g(cx, JS_NewGlobalObject(cx, getGlobalClass(), nullptr,
+ JS::FireOnNewGlobalHook, options));
+ CHECK(g);
+
+ JS::RootedObject gWrapper(cx, g);
+ CHECK(JS_WrapObject(cx, &gWrapper));
+ JS::RootedValue v(cx, JS::ObjectValue(*gWrapper));
+ CHECK(JS_SetProperty(cx, global, "g", v));
+
+ EXEC(
+ "var dbg = Debugger(g);\n"
+ "var hits = 0;\n"
+ "dbg.onNewScript = function (s) {\n"
+ " hits += Number(s instanceof Debugger.Script);\n"
+ "};\n");
+
+ // Since g is a debuggee, g.eval should trigger newScript, regardless of
+ // what scope object we use to enter the compartment.
+ //
+ // Scripts are associated with the global where they're compiled, so we
+ // deliver them only to debuggers that are watching that particular global.
+ //
+ return testIndirectEval(g, "Math.abs(0)");
+}
+
+bool testIndirectEval(JS::HandleObject global, const char* code) {
+ EXEC("hits = 0;");
+
+ {
+ JSAutoRealm ar(cx, global);
+ JSString* codestr = JS_NewStringCopyZ(cx, code);
+ CHECK(codestr);
+ JS::RootedValue arg(cx, JS::StringValue(codestr));
+ JS::RootedValue v(cx);
+ CHECK(JS_CallFunctionName(cx, global, "eval", HandleValueArray(arg), &v));
+ }
+
+ JS::RootedValue hitsv(cx);
+ EVAL("hits", &hitsv);
+ CHECK(hitsv.isInt32(1));
+ return true;
+}
+END_TEST(testDebugger_newScriptHook)
diff --git a/js/src/jsapi-tests/testDecimalNumber.cpp b/js/src/jsapi-tests/testDecimalNumber.cpp
new file mode 100644
index 0000000000..c63051523d
--- /dev/null
+++ b/js/src/jsapi-tests/testDecimalNumber.cpp
@@ -0,0 +1,289 @@
+/* -*- 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/Span.h"
+
+#include <string_view>
+
+#include "builtin/intl/DecimalNumber.h"
+#include "jsapi-tests/tests.h"
+
+using namespace js::intl;
+
+static mozilla::Maybe<js::intl::DecimalNumber> DecimalFrom(
+ std::string_view sv) {
+ auto span = mozilla::Span(reinterpret_cast<const JS::Latin1Char*>(sv.data()),
+ sv.length());
+ return DecimalNumber::from(span);
+}
+
+BEGIN_TEST(test_DecimalNumberParse) {
+ struct TestData {
+ std::string_view string;
+ bool success;
+ bool zero;
+ bool negative;
+ bool exponentTooLarge;
+ int32_t exponent;
+ size_t significandStart;
+ size_t significandEnd;
+ } tests[] = {
+ // "Infinity" and "NaN" aren't accepted.
+ {"Infinity", false},
+ {"+Infinity", false},
+ {"-Infinity", false},
+ {"NaN", false},
+ {"+NaN", false},
+ {"-NaN", false},
+
+ // Non-decimal strings aren't accepted.
+ {"0xbad", false},
+ {"0Xbad", false},
+ {"0o0", false},
+ {"0O0", false},
+ {"0b0", false},
+ {"0B0", false},
+
+ // Doesn't start with sign, digit, or decimal point.
+ {"e0", false},
+ {"Hello", false},
+
+ // Incomplete number strings.
+
+ // 1. Missing digit after sign.
+ {"+", false},
+ {"+ ", false},
+ {"+e0", false},
+ {"-", false},
+ {"- ", false},
+ {"-e0", false},
+
+ // 2. Missing digit when starting with decimal point.
+ {".", false},
+ {". ", false},
+ {".e0", false},
+ {"+.", false},
+ {"+. ", false},
+ {"+.e0", false},
+ {"-.", false},
+ {"-. ", false},
+ {"-.e0", false},
+
+ // 3. Missing digit after exponent.
+ {"1e", false},
+ {"1e ", false},
+ {"1e+", false},
+ {"1e+ ", false},
+ {"1e-", false},
+ {"1e- ", false},
+
+ // Empty and whitespace-only strings parse as zero.
+ {"", true, true, false, false, 0, 0, 0},
+ {" ", true, true, false, false, 0, 0, 0},
+ {" \t ", true, true, false, false, 0, 0, 0},
+
+ // Positive zero.
+ {"0", true, true, false, false, 0, 0, 0},
+ {"0000", true, true, false, false, 0, 0, 0},
+ {"0.0", true, true, false, false, 0, 0, 0},
+ {"0.", true, true, false, false, 0, 0, 0},
+ {".0", true, true, false, false, 0, 0, 0},
+ {"0e0", true, true, false, false, 0, 0, 0},
+ {"0e+1", true, true, false, false, 0, 0, 0},
+ {"0e-1", true, true, false, false, 0, 0, 0},
+
+ {"+0", true, true, false, false, 0, 0, 0},
+ {"+0000", true, true, false, false, 0, 0, 0},
+ {"+0.0", true, true, false, false, 0, 0, 0},
+ {"+0.", true, true, false, false, 0, 0, 0},
+ {"+.0", true, true, false, false, 0, 0, 0},
+ {"+0e0", true, true, false, false, 0, 0, 0},
+ {"+0e+1", true, true, false, false, 0, 0, 0},
+ {"+0e-1", true, true, false, false, 0, 0, 0},
+
+ // Negative zero.
+ {"-0", true, true, true, false, 0, 0, 0},
+ {"-0000", true, true, true, false, 0, 0, 0},
+ {"-0.0", true, true, true, false, 0, 0, 0},
+ {"-0.", true, true, true, false, 0, 0, 0},
+ {"-.0", true, true, true, false, 0, 0, 0},
+ {"-0e0", true, true, true, false, 0, 0, 0},
+ {"-0e+1", true, true, true, false, 0, 0, 0},
+ {"-0e-1", true, true, true, false, 0, 0, 0},
+
+ // Integer numbers.
+ {"1", true, false, false, false, 1, 0, 1},
+ {"10", true, false, false, false, 2, 0, 2},
+ {"123", true, false, false, false, 3, 0, 3},
+ {"123456789012345678901234567890", true, false, false, false, 30, 0, 30},
+ {"1e100", true, false, false, false, 101, 0, 1},
+ {"123e39", true, false, false, false, 42, 0, 3},
+
+ {"+1", true, false, false, false, 1, 1, 2},
+ {"+10", true, false, false, false, 2, 1, 3},
+ {"+123", true, false, false, false, 3, 1, 4},
+ {"+123456789012345678901234567890", true, false, false, false, 30, 1, 31},
+ {"+1e100", true, false, false, false, 101, 1, 2},
+ {"+123e39", true, false, false, false, 42, 1, 4},
+
+ {"-1", true, false, true, false, 1, 1, 2},
+ {"-10", true, false, true, false, 2, 1, 3},
+ {"-123", true, false, true, false, 3, 1, 4},
+ {"-123456789012345678901234567890", true, false, true, false, 30, 1, 31},
+ {"-1e100", true, false, true, false, 101, 1, 2},
+ {"-123e39", true, false, true, false, 42, 1, 4},
+
+ // Fractional numbers.
+ {"0.1", true, false, false, false, 0, 2, 3},
+ {"0.01", true, false, false, false, -1, 3, 4},
+ {"0.001", true, false, false, false, -2, 4, 5},
+ {"1.001", true, false, false, false, 1, 0, 5},
+
+ {".1", true, false, false, false, 0, 1, 2},
+ {".01", true, false, false, false, -1, 2, 3},
+ {".001", true, false, false, false, -2, 3, 4},
+
+ {"+.1", true, false, false, false, 0, 2, 3},
+ {"+.01", true, false, false, false, -1, 3, 4},
+ {"+.001", true, false, false, false, -2, 4, 5},
+
+ {"-.1", true, false, true, false, 0, 2, 3},
+ {"-.01", true, false, true, false, -1, 3, 4},
+ {"-.001", true, false, true, false, -2, 4, 5},
+
+ // Fractional number with exponent part.
+ {".1e0", true, false, false, false, 0, 1, 2},
+ {".01e0", true, false, false, false, -1, 2, 3},
+ {".001e0", true, false, false, false, -2, 3, 4},
+
+ {".1e1", true, false, false, false, 1, 1, 2},
+ {".01e1", true, false, false, false, 0, 2, 3},
+ {".001e1", true, false, false, false, -1, 3, 4},
+
+ {".1e-1", true, false, false, false, -1, 1, 2},
+ {".01e-1", true, false, false, false, -2, 2, 3},
+ {".001e-1", true, false, false, false, -3, 3, 4},
+
+ {"1.1e0", true, false, false, false, 1, 0, 3},
+ {"1.01e0", true, false, false, false, 1, 0, 4},
+ {"1.001e0", true, false, false, false, 1, 0, 5},
+
+ {"1.1e1", true, false, false, false, 2, 0, 3},
+ {"1.01e1", true, false, false, false, 2, 0, 4},
+ {"1.001e1", true, false, false, false, 2, 0, 5},
+
+ {"1.1e-1", true, false, false, false, 0, 0, 3},
+ {"1.01e-1", true, false, false, false, 0, 0, 4},
+ {"1.001e-1", true, false, false, false, 0, 0, 5},
+
+ // Exponent too large.
+ //
+ // The exponent is limited to: |abs(exp) < 2147483648|.
+ {".1e2147483647", true, false, false, false, 2147483647, 1, 2},
+ {".1e-2147483647", true, false, false, false, -2147483647, 1, 2},
+ {"1e2147483646", true, false, false, false, 2147483647, 0, 1},
+ {".01e-2147483646", true, false, false, false, -2147483647, 2, 3},
+ {"1e2147483647", true, false, false, true, 0, 0, 1},
+ {".1e2147483648", true, false, false, true, 0, 1, 2},
+ {".1e-2147483648", true, false, false, true, 0, 1, 2},
+ {".01e-2147483647", true, false, false, true, 0, 2, 3},
+ };
+
+ for (const auto& test : tests) {
+ if (auto decimal = DecimalFrom(test.string)) {
+ CHECK(test.success);
+ CHECK_EQUAL(decimal->isZero(), test.zero);
+ CHECK_EQUAL(decimal->isNegative(), test.negative);
+ CHECK_EQUAL(decimal->exponentTooLarge(), test.exponentTooLarge);
+ CHECK_EQUAL(decimal->exponent(), test.exponent);
+ CHECK_EQUAL(decimal->significandStart(), test.significandStart);
+ CHECK_EQUAL(decimal->significandEnd(), test.significandEnd);
+ } else {
+ CHECK(!test.success);
+ }
+ }
+
+ return true;
+}
+END_TEST(test_DecimalNumberParse)
+
+static int32_t NormalizeResult(int32_t r) { return r < 0 ? -1 : r > 0 ? 1 : 0; }
+
+BEGIN_TEST(test_DecimalNumberCompareTo) {
+ struct TestData {
+ std::string_view left;
+ std::string_view right;
+ int32_t result;
+ } tests[] = {
+ // Compare zeros.
+ {"0", "0", 0},
+ {"-0", "-0", 0},
+ {"-0", "+0", -1},
+ {"+0", "-0", 1},
+
+ // Compare with different signs.
+ {"-1", "+1", -1},
+ {"+1", "-1", 1},
+
+ // Compare zero against non-zero.
+ {"0", "+1", -1},
+ {"0", "-1", 1},
+ {"+1", "0", 1},
+ {"-1", "0", -1},
+
+ // Compare with different exponent.
+ {"1", "0.1", 1},
+ {"1e2", "1000", -1},
+ {"-1", "-0.1", -1},
+ {"-1e2", "-1000", 1},
+
+ // Compare with same exponent.
+ {"1", "2", -1},
+ {"1", "1", 0},
+ {"2", "1", 1},
+
+ // ToString(1e10) is "10000000000".
+ {"1e10", "10000000000", 0},
+ {"1e10", "10000000001", -1},
+ {"-1e10", "-10000000001", 1},
+
+ // ToString(1e30) is "1e+30".
+ {"1e30", "1000000000000000000000000000000", 0},
+ {"1e30", "1000000000000000000000000000001", -1},
+ {"-1e30", "-1000000000000000000000000000001", 1},
+
+ // ToString(1e-5) is "0.00001".
+ {"1e-5", "0.00001", 0},
+ {"1e-5", "0.00002", -1},
+ {"-1e-5", "-0.00002", 1},
+
+ // ToString(1e-10) is "1e-10".
+ {"1e-10", "0.0000000001", 0},
+ {"1e-10", "0.0000000002", -1},
+ {"-1e-10", "-0.0000000002", 1},
+
+ // Additional tests with exponent forms.
+ {"1.23e5", "123000", 0},
+ {".123e-3", "0.000123", 0},
+ {".123e+5", "12300", 0},
+ {".00123e5", "123", 0},
+ };
+
+ for (const auto& test : tests) {
+ auto left = DecimalFrom(test.left);
+ CHECK(left);
+
+ auto right = DecimalFrom(test.right);
+ CHECK(right);
+
+ int32_t result = left->compareTo(*right);
+ CHECK_EQUAL(NormalizeResult(result), test.result);
+ }
+
+ return true;
+}
+END_TEST(test_DecimalNumberCompareTo)
diff --git a/js/src/jsapi-tests/testDeduplication.cpp b/js/src/jsapi-tests/testDeduplication.cpp
new file mode 100644
index 0000000000..6e6ded6b71
--- /dev/null
+++ b/js/src/jsapi-tests/testDeduplication.cpp
@@ -0,0 +1,113 @@
+/* -*- 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 <string.h>
+
+#include "gc/GC.h"
+
+#include "js/RootingAPI.h"
+#include "js/StableStringChars.h"
+#include "js/String.h" // JS::StringToLinearString
+
+#include "jsapi-tests/tests.h"
+
+#include "vm/JSContext.h"
+#include "vm/StringType.h"
+
+#include "vm/JSContext-inl.h"
+
+static bool SameChars(JSContext* cx, JSString* str1, JSString* str2,
+ size_t offset) {
+ JS::AutoCheckCannotGC nogc(cx);
+
+ const JS::Latin1Char* chars1 =
+ JS::StringToLinearString(cx, str1)->latin1Chars(nogc);
+ const JS::Latin1Char* chars2 =
+ JS::StringToLinearString(cx, str2)->latin1Chars(nogc);
+
+ return chars1 == chars2 + offset;
+}
+
+BEGIN_TEST(testDeduplication_ASSC) {
+ // Test with a long enough string to avoid inline chars allocation.
+ const char text[] =
+ "Andthebeastshallcomeforthsurroundedbyaroilingcloudofvengeance."
+ "Thehouseoftheunbelieversshallberazedandtheyshallbescorchedtoth"
+ "eearth.Theirtagsshallblinkuntiltheendofdays.";
+
+ // Create a string to deduplicate later strings to.
+ JS::RootedString original(cx);
+ JS::RootedString str(cx);
+ JS::RootedString dep(cx);
+ JS::RootedString depdep(cx);
+ JS::RootedString str2(cx);
+ JS::RootedString dep2(cx);
+ JS::RootedString depdep2(cx);
+
+ {
+ // This test checks the behavior when GC is performed after allocating
+ // all the following strings.
+ // GC shouldn't happen in between them, even in compacting jobs.
+ js::gc::AutoSuppressGC suppress(cx);
+
+ original = JS_NewStringCopyZ(cx, text);
+ CHECK(original);
+
+ // Create a chain of dependent strings, with a base string whose contents
+ // match `original`'s.
+ str = JS_NewStringCopyZ(cx, text);
+ CHECK(str);
+
+ dep = JS_NewDependentString(cx, str, 10, 100);
+ CHECK(dep);
+
+ depdep = JS_NewDependentString(cx, dep, 10, 80);
+ CHECK(depdep);
+
+ // Repeat. This one will not be prevented from deduplication.
+ str2 = JS_NewStringCopyZ(cx, text);
+ CHECK(str2);
+
+ dep2 = JS_NewDependentString(cx, str2, 10, 100);
+ CHECK(dep2);
+
+ depdep2 = JS_NewDependentString(cx, dep2, 10, 80);
+ CHECK(depdep2);
+ }
+
+ // Initializing an AutoStableStringChars with `depdep` should prevent the
+ // owner of its chars (`str`) from deduplication.
+ JS::AutoStableStringChars stable(cx);
+ CHECK(stable.init(cx, depdep));
+
+ const JS::Latin1Char* chars = stable.latin1Chars();
+ CHECK(memcmp(chars, text + 20, 80 * sizeof(JS::Latin1Char)) == 0);
+
+ // `depdep` should share chars with `str` but not with `original`.
+ CHECK(SameChars(cx, depdep, str, 20));
+ CHECK(!SameChars(cx, depdep, original, 20));
+
+ // Same for `depdep2`.
+ CHECK(SameChars(cx, depdep2, str2, 20));
+ CHECK(!SameChars(cx, depdep2, original, 20));
+
+ // Do a minor GC that will deduplicate `str2` to `original`, and would have
+ // deduplicated `str` as well if it weren't prevented by the
+ // AutoStableStringChars.
+ cx->minorGC(JS::GCReason::API);
+
+ // `depdep` should still share chars with `str` but not with `original`.
+ CHECK(SameChars(cx, depdep, str, 20));
+ CHECK(!SameChars(cx, depdep, original, 20));
+
+ // `depdep2` should now share chars with both `str` and `original`.
+ CHECK(SameChars(cx, depdep2, str2, 20));
+ CHECK(SameChars(cx, depdep2, original, 20));
+
+ return true;
+}
+END_TEST(testDeduplication_ASSC)
diff --git a/js/src/jsapi-tests/testDeepFreeze.cpp b/js/src/jsapi-tests/testDeepFreeze.cpp
new file mode 100644
index 0000000000..a1b62b8ca4
--- /dev/null
+++ b/js/src/jsapi-tests/testDeepFreeze.cpp
@@ -0,0 +1,58 @@
+/* -*- 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 "jsapi-tests/tests.h"
+
+BEGIN_TEST(testDeepFreeze_bug535703) {
+ JS::RootedValue v(cx);
+ EVAL("var x = {}; x;", &v);
+ JS::RootedObject obj(cx, v.toObjectOrNull());
+ CHECK(JS_DeepFreezeObject(cx, obj)); // don't crash
+ EVAL("Object.isFrozen(x)", &v);
+ CHECK(v.isTrue());
+ return true;
+}
+END_TEST(testDeepFreeze_bug535703)
+
+BEGIN_TEST(testDeepFreeze_deep) {
+ JS::RootedValue a(cx), o(cx);
+ EXEC(
+ "var a = {}, o = a;\n"
+ "for (var i = 0; i < 5000; i++)\n"
+ " a = {x: a, y: a};\n");
+ EVAL("a", &a);
+ EVAL("o", &o);
+
+ JS::RootedObject aobj(cx, a.toObjectOrNull());
+ CHECK(JS_DeepFreezeObject(cx, aobj));
+
+ JS::RootedValue b(cx);
+ EVAL("Object.isFrozen(a)", &b);
+ CHECK(b.isTrue());
+ EVAL("Object.isFrozen(o)", &b);
+ CHECK(b.isTrue());
+ return true;
+}
+END_TEST(testDeepFreeze_deep)
+
+BEGIN_TEST(testDeepFreeze_loop) {
+ JS::RootedValue x(cx), y(cx);
+ EXEC("var x = [], y = {x: x}; y.y = y; x.push(x, y);");
+ EVAL("x", &x);
+ EVAL("y", &y);
+
+ JS::RootedObject xobj(cx, x.toObjectOrNull());
+ CHECK(JS_DeepFreezeObject(cx, xobj));
+
+ JS::RootedValue b(cx);
+ EVAL("Object.isFrozen(x)", &b);
+ CHECK(b.isTrue());
+ EVAL("Object.isFrozen(y)", &b);
+ CHECK(b.isTrue());
+ return true;
+}
+END_TEST(testDeepFreeze_loop)
diff --git a/js/src/jsapi-tests/testDefineGetterSetterNonEnumerable.cpp b/js/src/jsapi-tests/testDefineGetterSetterNonEnumerable.cpp
new file mode 100644
index 0000000000..37c57fb3c3
--- /dev/null
+++ b/js/src/jsapi-tests/testDefineGetterSetterNonEnumerable.cpp
@@ -0,0 +1,51 @@
+/* -*- 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 "js/PropertyAndElement.h" // JS_DefineProperty
+#include "js/PropertyDescriptor.h" // JS_GetOwnPropertyDescriptor
+#include "jsapi-tests/tests.h"
+
+static bool NativeGetterSetter(JSContext* cx, unsigned argc, JS::Value* vp) {
+ return true;
+}
+
+BEGIN_TEST(testDefineGetterSetterNonEnumerable) {
+ static const char PROPERTY_NAME[] = "foo";
+
+ JS::RootedValue vobj(cx);
+ JS::RootedObject obj(cx, JS_NewPlainObject(cx));
+ CHECK(obj);
+ vobj.setObject(*obj);
+
+ JSFunction* funGet = JS_NewFunction(cx, NativeGetterSetter, 0, 0, "get");
+ CHECK(funGet);
+ JS::RootedObject funGetObj(cx, JS_GetFunctionObject(funGet));
+ JS::RootedValue vget(cx, JS::ObjectValue(*funGetObj));
+
+ JSFunction* funSet = JS_NewFunction(cx, NativeGetterSetter, 1, 0, "set");
+ CHECK(funSet);
+ JS::RootedObject funSetObj(cx, JS_GetFunctionObject(funSet));
+ JS::RootedValue vset(cx, JS::ObjectValue(*funSetObj));
+
+ JS::RootedObject vObject(cx, vobj.toObjectOrNull());
+ CHECK(JS_DefineProperty(cx, vObject, PROPERTY_NAME, funGetObj, funSetObj,
+ JSPROP_ENUMERATE));
+
+ CHECK(JS_DefineProperty(cx, vObject, PROPERTY_NAME, funGetObj, funSetObj,
+ JSPROP_PERMANENT));
+
+ JS::Rooted<mozilla::Maybe<JS::PropertyDescriptor>> desc(cx);
+ CHECK(JS_GetOwnPropertyDescriptor(cx, vObject, PROPERTY_NAME, &desc));
+ CHECK(desc.isSome());
+ CHECK(desc->hasGetter());
+ CHECK(desc->hasSetter());
+ CHECK(!desc->configurable());
+ CHECK(!desc->enumerable());
+
+ return true;
+}
+END_TEST(testDefineGetterSetterNonEnumerable)
diff --git a/js/src/jsapi-tests/testDefineProperty.cpp b/js/src/jsapi-tests/testDefineProperty.cpp
new file mode 100644
index 0000000000..576e93e19f
--- /dev/null
+++ b/js/src/jsapi-tests/testDefineProperty.cpp
@@ -0,0 +1,26 @@
+/* -*- 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 "js/PropertyAndElement.h" // JS_DefineProperty
+#include "jsapi-tests/tests.h"
+
+BEGIN_TEST(testDefineProperty_bug564344) {
+ JS::RootedValue x(cx);
+ EVAL(
+ "function f() {}\n"
+ "var x = {p: f};\n"
+ "x.p(); // brand x's scope\n"
+ "x;",
+ &x);
+
+ JS::RootedObject obj(cx, x.toObjectOrNull());
+ for (int i = 0; i < 2; i++) {
+ CHECK(JS_DefineProperty(cx, obj, "q", JS::UndefinedHandleValue, 0));
+ }
+ return true;
+}
+END_TEST(testDefineProperty_bug564344)
diff --git a/js/src/jsapi-tests/testDeflateStringToUTF8Buffer.cpp b/js/src/jsapi-tests/testDeflateStringToUTF8Buffer.cpp
new file mode 100644
index 0000000000..b451fc4e2b
--- /dev/null
+++ b/js/src/jsapi-tests/testDeflateStringToUTF8Buffer.cpp
@@ -0,0 +1,162 @@
+/* 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 "jsapi-tests/tests.h"
+
+using namespace JS;
+
+BEGIN_TEST(test_DeflateStringToUTF8Buffer) {
+ JSString* str;
+ JSLinearString* linearStr;
+
+ // DeflateStringToUTF8Buffer does not write a null terminator, so the byte
+ // following the last byte written to the |actual| buffer should retain
+ // the value it held before the call to DeflateStringToUTF8Buffer, which is
+ // initialized to 0x1.
+
+ char actual[100];
+ auto span = mozilla::Span(actual);
+
+ // Test with an ASCII string, which calls JSLinearString::latin1Chars
+ // to retrieve the characters from the string and generates UTF-8 output
+ // that is identical to the ASCII input.
+
+ str = JS_NewStringCopyZ(cx, "Ohai"); // { 0x4F, 0x68, 0x61, 0x69 }
+ MOZ_RELEASE_ASSERT(str);
+ linearStr = JS_EnsureLinearString(cx, str);
+
+ {
+ const char expected[] = {0x4F, 0x68, 0x61, 0x69, 0x1};
+ memset(actual, 0x1, 100);
+ size_t dstlen = JS::DeflateStringToUTF8Buffer(linearStr, span);
+ CHECK_EQUAL(memcmp(actual, expected, sizeof(expected)), 0);
+ CHECK_EQUAL(dstlen, 4u);
+ }
+
+ {
+ const char expected[] = {0x4F, 0x68, 0x61, 0x1};
+ memset(actual, 0x1, 100);
+ size_t dstlen = JS::DeflateStringToUTF8Buffer(linearStr, span.To(3));
+ CHECK_EQUAL(memcmp(actual, expected, sizeof(expected)), 0);
+ CHECK_EQUAL(dstlen, 3u);
+ }
+
+ {
+ const unsigned char expected[] = {0x1};
+ memset(actual, 0x1, 100);
+ size_t dstlen = JS::DeflateStringToUTF8Buffer(linearStr, span.To(0));
+ CHECK_EQUAL(memcmp(actual, expected, sizeof(expected)), 0);
+ CHECK_EQUAL(dstlen, 0u);
+ }
+
+ // Test with a Latin-1 string, which calls JSLinearString::latin1Chars
+ // like with the ASCII string but generates UTF-8 output that is different
+ // from the ASCII input.
+
+ str = JS_NewUCStringCopyZ(cx, u"\xD3\x68\xE3\xEF"); // u"Óhãï"
+ MOZ_RELEASE_ASSERT(str);
+ linearStr = JS_EnsureLinearString(cx, str);
+
+ {
+ const unsigned char expected[] = {0xC3, 0x93, 0x68, 0xC3,
+ 0xA3, 0xC3, 0xAF, 0x1};
+ memset(actual, 0x1, 100);
+ JS::DeflateStringToUTF8Buffer(linearStr, span);
+ CHECK_EQUAL(memcmp(actual, expected, sizeof(expected)), 0);
+ }
+
+ {
+ const unsigned char expected[] = {0xC3, 0x93, 0x68, 0xC3,
+ 0xA3, 0xC3, 0xAF, 0x1};
+ memset(actual, 0x1, 100);
+ size_t dstlen = JS::DeflateStringToUTF8Buffer(linearStr, span.To(7));
+ CHECK_EQUAL(memcmp(actual, expected, sizeof(expected)), 0);
+ CHECK_EQUAL(dstlen, 7u);
+ }
+
+ {
+ // Specify a destination buffer length of 3. That's exactly enough
+ // space to encode the first two characters, which takes three bytes.
+ const unsigned char expected[] = {0xC3, 0x93, 0x68, 0x1};
+ memset(actual, 0x1, 100);
+ size_t dstlen = JS::DeflateStringToUTF8Buffer(linearStr, span.To(3));
+ CHECK_EQUAL(memcmp(actual, expected, sizeof(expected)), 0);
+ CHECK_EQUAL(dstlen, 3u);
+ }
+
+ {
+ // Specify a destination buffer length of 4. That's only enough space
+ // to encode the first two characters, which takes three bytes, because
+ // the third character would take another two bytes.
+ const unsigned char expected[] = {0xC3, 0x93, 0x68, 0x1};
+ memset(actual, 0x1, 100);
+ size_t dstlen = JS::DeflateStringToUTF8Buffer(linearStr, span.To(4));
+ CHECK_EQUAL(memcmp(actual, expected, sizeof(expected)), 0);
+ CHECK_EQUAL(dstlen, 3u);
+ }
+
+ {
+ const unsigned char expected[] = {0x1};
+ memset(actual, 0x1, 100);
+ size_t dstlen = JS::DeflateStringToUTF8Buffer(linearStr, span.To(0));
+ CHECK_EQUAL(memcmp(actual, expected, sizeof(expected)), 0);
+ CHECK_EQUAL(dstlen, 0u);
+ }
+
+ // Test with a UTF-16 string, which calls JSLinearString::twoByteChars
+ // to retrieve the characters from the string.
+
+ str = JS_NewUCStringCopyZ(cx, u"\x038C\x0068\x0203\x0457"); // u"Όhȃї"
+ MOZ_RELEASE_ASSERT(str);
+ linearStr = JS_EnsureLinearString(cx, str);
+
+ {
+ const unsigned char expected[] = {0xCE, 0x8C, 0x68, 0xC8,
+ 0x83, 0xD1, 0x97, 0x1};
+ memset(actual, 0x1, 100);
+ JS::DeflateStringToUTF8Buffer(linearStr, span);
+ CHECK_EQUAL(memcmp(actual, expected, sizeof(expected)), 0);
+ }
+
+ {
+ const unsigned char expected[] = {0xCE, 0x8C, 0x68, 0xC8,
+ 0x83, 0xD1, 0x97, 0x1};
+ memset(actual, 0x1, 100);
+ size_t dstlen = JS::DeflateStringToUTF8Buffer(linearStr, span.To(7));
+ CHECK_EQUAL(memcmp(actual, expected, sizeof(expected)), 0);
+ CHECK_EQUAL(dstlen, 7u);
+ }
+
+ {
+ // Specify a destination buffer length of 3. That's exactly enough
+ // space to encode the first two characters, which takes three bytes.
+ const unsigned char expected[] = {0xCE, 0x8C, 0x68, 0x1};
+ memset(actual, 0x1, 100);
+ size_t dstlen = JS::DeflateStringToUTF8Buffer(linearStr, span.To(3));
+ CHECK_EQUAL(memcmp(actual, expected, sizeof(expected)), 0);
+ CHECK_EQUAL(dstlen, 3u);
+ }
+
+ {
+ // Specify a destination buffer length of 4. That's only enough space
+ // to encode the first two characters, which takes three bytes, because
+ // the third character would take another two bytes.
+ const unsigned char expected[] = {0xCE, 0x8C, 0x68, 0x1};
+ memset(actual, 0x1, 100);
+ size_t dstlen = JS::DeflateStringToUTF8Buffer(linearStr, span.To(4));
+ CHECK_EQUAL(memcmp(actual, expected, sizeof(expected)), 0);
+ CHECK_EQUAL(dstlen, 3u);
+ }
+
+ {
+ const unsigned char expected[] = {0x1};
+ memset(actual, 0x1, 100);
+ size_t dstlen = JS::DeflateStringToUTF8Buffer(linearStr, span.To(0));
+ CHECK_EQUAL(memcmp(actual, expected, sizeof(expected)), 0);
+ CHECK_EQUAL(dstlen, 0u);
+ }
+
+ return true;
+}
+END_TEST(test_DeflateStringToUTF8Buffer)
diff --git a/js/src/jsapi-tests/testDifferentNewTargetInvokeConstructor.cpp b/js/src/jsapi-tests/testDifferentNewTargetInvokeConstructor.cpp
new file mode 100644
index 0000000000..1d623a78c2
--- /dev/null
+++ b/js/src/jsapi-tests/testDifferentNewTargetInvokeConstructor.cpp
@@ -0,0 +1,38 @@
+/* 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 "js/CallAndConstruct.h" // JS::Construct
+#include "jsapi-tests/tests.h"
+
+BEGIN_TEST(testDifferentNewTargetInvokeConstructor) {
+ JS::RootedValue func(cx);
+ JS::RootedValue otherFunc(cx);
+
+ EVAL("(function() { /* This is a different new.target function */ })",
+ &otherFunc);
+
+ EVAL(
+ "(function(expected) { if (expected !== new.target) throw new "
+ "Error('whoops'); })",
+ &func);
+
+ JS::RootedValueArray<1> args(cx);
+ args[0].set(otherFunc);
+
+ JS::RootedObject obj(cx);
+
+ JS::RootedObject newTarget(cx, &otherFunc.toObject());
+
+ CHECK(JS::Construct(cx, func, newTarget, args, &obj));
+
+ // It should fail, though, if newTarget is not a constructor
+ JS::RootedValue plain(cx);
+ EVAL("({})", &plain);
+ args[0].set(plain);
+ newTarget = &plain.toObject();
+ CHECK(!JS::Construct(cx, func, newTarget, args, &obj));
+
+ return true;
+}
+END_TEST(testDifferentNewTargetInvokeConstructor)
diff --git a/js/src/jsapi-tests/testEmptyWindowIsOmitted.cpp b/js/src/jsapi-tests/testEmptyWindowIsOmitted.cpp
new file mode 100644
index 0000000000..c03a8106c0
--- /dev/null
+++ b/js/src/jsapi-tests/testEmptyWindowIsOmitted.cpp
@@ -0,0 +1,155 @@
+/* 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/TextUtils.h"
+#include "mozilla/Utf8.h"
+
+#include <cstring>
+
+#include "js/CharacterEncoding.h"
+#include "js/CompilationAndEvaluation.h" // JS::Compile
+#include "js/Exception.h"
+#include "js/friend/ErrorMessages.h" // JSMSG_*
+#include "js/SourceText.h"
+#include "jsapi-tests/tests.h"
+#include "util/Text.h"
+#include "vm/ErrorReporting.h"
+
+using mozilla::IsAsciiHexDigit;
+using mozilla::Utf8Unit;
+
+BEGIN_TEST(testEmptyWindow) { return testUtf8() && testUtf16(); }
+
+bool testUtf8() {
+ // Bad unit with nothing before it.
+ static const char badLeadingUnit[] = "\x80";
+ CHECK(testOmittedWindow(badLeadingUnit, JSMSG_BAD_LEADING_UTF8_UNIT, "0x80"));
+
+ // Bad unit at start of a fresh line.
+ static const char badStartingFreshLine[] = "var x = 5;\n\x98";
+ CHECK(testOmittedWindow(badStartingFreshLine, JSMSG_BAD_LEADING_UTF8_UNIT,
+ "0x98"));
+
+ // Bad trailing unit in initial code point.
+ static const char badTrailingUnit[] = "\xD8\x20";
+ CHECK(testOmittedWindow(badTrailingUnit, JSMSG_BAD_TRAILING_UTF8_UNIT,
+ "0xD8 0x20"));
+
+ // Bad trailing unit at start of a fresh line.
+ static const char badTrailingUnitFreshLine[] = "var x = 5;\n\xD8\x20";
+ CHECK(testOmittedWindow(badTrailingUnitFreshLine,
+ JSMSG_BAD_TRAILING_UTF8_UNIT, "0xD8 0x20"));
+
+ // Overlong in initial code point.
+ static const char overlongInitial[] = "\xC0\x80";
+ CHECK(testOmittedWindow(overlongInitial, JSMSG_FORBIDDEN_UTF8_CODE_POINT,
+ "0xC0 0x80"));
+
+ // Overlong at start of a fresh line.
+ static const char overlongFreshLine[] = "var x = 5;\n\xC0\x81";
+ CHECK(testOmittedWindow(overlongFreshLine, JSMSG_FORBIDDEN_UTF8_CODE_POINT,
+ "0xC0 0x81"));
+
+ // Not-enough in initial code point.
+ static const char notEnoughInitial[] = "\xF0";
+ CHECK(
+ testOmittedWindow(notEnoughInitial, JSMSG_NOT_ENOUGH_CODE_UNITS, "0xF0"));
+
+ // Not-enough at start of a fresh line.
+ static const char notEnoughFreshLine[] = "var x = 5;\n\xF0";
+ CHECK(testOmittedWindow(notEnoughFreshLine, JSMSG_NOT_ENOUGH_CODE_UNITS,
+ "0xF0"));
+
+ return true;
+}
+
+bool testUtf16() {
+ // Bad unit with nothing before it.
+ static const char16_t badLeadingUnit[] = u"\xDFFF";
+ CHECK(testOmittedWindow(badLeadingUnit, JSMSG_ILLEGAL_CHARACTER));
+
+ // Bad unit at start of a fresh line.
+ static const char16_t badStartingFreshLine[] = u"var x = 5;\n\xDFFF";
+ CHECK(testOmittedWindow(badStartingFreshLine, JSMSG_ILLEGAL_CHARACTER));
+
+ return true;
+}
+
+static bool startsWith(const char* str, const char* prefix) {
+ return std::strncmp(prefix, str, strlen(prefix)) == 0;
+}
+
+static bool equals(const char* str, const char* expected) {
+ return std::strcmp(str, expected) == 0;
+}
+
+JSScript* compile(const char16_t* chars, size_t len) {
+ JS::SourceText<char16_t> source;
+ MOZ_RELEASE_ASSERT(
+ source.init(cx, chars, len, JS::SourceOwnership::Borrowed));
+
+ JS::CompileOptions options(cx);
+ return JS::Compile(cx, options, source);
+}
+
+JSScript* compile(const char* chars, size_t len) {
+ JS::SourceText<Utf8Unit> source;
+ MOZ_RELEASE_ASSERT(
+ source.init(cx, chars, len, JS::SourceOwnership::Borrowed));
+
+ JS::CompileOptions options(cx);
+ return JS::Compile(cx, options, source);
+}
+
+template <typename CharT, size_t N>
+bool testOmittedWindow(const CharT (&chars)[N], unsigned expectedErrorNumber,
+ const char* badCodeUnits = nullptr) {
+ JS::Rooted<JSScript*> script(cx, compile(chars, N - 1));
+ CHECK(!script);
+
+ JS::ExceptionStack exnStack(cx);
+ CHECK(JS::StealPendingExceptionStack(cx, &exnStack));
+
+ JS::ErrorReportBuilder report(cx);
+ CHECK(report.init(cx, exnStack, JS::ErrorReportBuilder::WithSideEffects));
+
+ const auto* errorReport = report.report();
+
+ CHECK(errorReport->errorNumber == expectedErrorNumber);
+
+ if (const auto& notes = errorReport->notes) {
+ CHECK(sizeof(CharT) == 1);
+ CHECK(badCodeUnits != nullptr);
+
+ auto iter = notes->begin();
+ CHECK(iter != notes->end());
+
+ const char* noteMessage = (*iter)->message().c_str();
+
+ // The prefix ought always be the same.
+ static constexpr char expectedPrefix[] =
+ "the code units comprising this invalid code point were: ";
+ constexpr size_t expectedPrefixLen = js_strlen(expectedPrefix);
+
+ CHECK(startsWith(noteMessage, expectedPrefix));
+
+ // The end of the prefix is the bad code units.
+ CHECK(equals(noteMessage + expectedPrefixLen, badCodeUnits));
+
+ ++iter;
+ CHECK(iter == notes->end());
+ } else {
+ CHECK(sizeof(CharT) == 2);
+
+ // UTF-16 encoding "errors" are not categorical errors, so the errors
+ // are just of the invalid-character sort, without an accompanying note
+ // spelling out a series of invalid code units.
+ CHECK(!badCodeUnits);
+ }
+
+ CHECK(!errorReport->linebuf());
+
+ return true;
+}
+END_TEST(testEmptyWindow)
diff --git a/js/src/jsapi-tests/testErrorCopying.cpp b/js/src/jsapi-tests/testErrorCopying.cpp
new file mode 100644
index 0000000000..be451c27ba
--- /dev/null
+++ b/js/src/jsapi-tests/testErrorCopying.cpp
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ *
+ * Tests that the column number of error reports is properly copied over from
+ * other reports when invoked from the C++ api.
+ */
+/* 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 "js/CallAndConstruct.h"
+#include "js/Exception.h"
+#include "jsapi-tests/tests.h"
+
+BEGIN_TEST(testErrorCopying_columnCopied) {
+ // 0 1 2
+ // 1234567890123456789012345678
+ EXEC("function check() { Object; foo; }");
+
+ JS::RootedValue rval(cx);
+ CHECK(!JS_CallFunctionName(cx, global, "check", JS::HandleValueArray::empty(),
+ &rval));
+ JS::ExceptionStack exnStack(cx);
+ CHECK(JS::StealPendingExceptionStack(cx, &exnStack));
+
+ JS::ErrorReportBuilder report(cx);
+ CHECK(report.init(cx, exnStack, JS::ErrorReportBuilder::WithSideEffects));
+
+ CHECK_EQUAL(report.report()->column, 28u);
+ return true;
+}
+
+END_TEST(testErrorCopying_columnCopied)
diff --git a/js/src/jsapi-tests/testErrorInterceptor.cpp b/js/src/jsapi-tests/testErrorInterceptor.cpp
new file mode 100644
index 0000000000..031fa85afa
--- /dev/null
+++ b/js/src/jsapi-tests/testErrorInterceptor.cpp
@@ -0,0 +1,145 @@
+#include <iterator>
+
+#include "js/ErrorInterceptor.h"
+#include "jsapi-tests/tests.h"
+#include "util/StringBuffer.h"
+
+// Tests for JS_GetErrorInterceptorCallback and JS_SetErrorInterceptorCallback.
+
+namespace {
+static JS::PersistentRootedString gLatestMessage;
+
+// An interceptor that stores the error in `gLatestMessage`.
+struct SimpleInterceptor : JSErrorInterceptor {
+ virtual void interceptError(JSContext* cx, JS::HandleValue val) override {
+ js::JSStringBuilder buffer(cx);
+ if (!ValueToStringBuffer(cx, val, buffer)) {
+ MOZ_CRASH("Could not convert to string buffer");
+ }
+ gLatestMessage = buffer.finishString();
+ if (!gLatestMessage) {
+ MOZ_CRASH("Could not convert to string");
+ }
+ }
+};
+
+bool equalStrings(JSContext* cx, JSString* a, JSString* b) {
+ int32_t result = 0;
+ if (!JS_CompareStrings(cx, a, b, &result)) {
+ MOZ_CRASH("Could not compare strings");
+ }
+ return result == 0;
+}
+} // namespace
+
+BEGIN_TEST(testErrorInterceptor) {
+ // Run the following snippets.
+ const char* SAMPLES[] = {
+ "throw new Error('I am an Error')\0",
+ "throw new TypeError('I am a TypeError')\0",
+ "throw new ReferenceError('I am a ReferenceError')\0",
+ "throw new SyntaxError('I am a SyntaxError')\0",
+ "throw 5\0",
+ "foo[0]\0",
+ "b[\0",
+ };
+ // With the simpleInterceptor, we should end up with the following error:
+ const char* TO_STRING[] = {
+ "Error: I am an Error\0",
+ "TypeError: I am a TypeError\0",
+ "ReferenceError: I am a ReferenceError\0",
+ "SyntaxError: I am a SyntaxError\0",
+ "5\0",
+ "ReferenceError: foo is not defined\0",
+ "SyntaxError: expected expression, got end of script\0",
+ };
+ static_assert(std::size(SAMPLES) == std::size(TO_STRING));
+
+ // Save original callback.
+ JSErrorInterceptor* original = JS_GetErrorInterceptorCallback(cx->runtime());
+ gLatestMessage.init(cx);
+
+ // Test without callback.
+ JS_SetErrorInterceptorCallback(cx->runtime(), nullptr);
+ CHECK(gLatestMessage == nullptr);
+
+ for (auto sample : SAMPLES) {
+ if (execDontReport(sample, __FILE__, __LINE__)) {
+ MOZ_CRASH("This sample should have failed");
+ }
+ CHECK(JS_IsExceptionPending(cx));
+ CHECK(gLatestMessage == nullptr);
+ JS_ClearPendingException(cx);
+ }
+
+ // Test with callback.
+ SimpleInterceptor simpleInterceptor;
+ JS_SetErrorInterceptorCallback(cx->runtime(), &simpleInterceptor);
+
+ // Test that we return the right callback.
+ CHECK_EQUAL(JS_GetErrorInterceptorCallback(cx->runtime()),
+ &simpleInterceptor);
+
+ // This shouldn't cause any error.
+ EXEC("function bar() {}");
+ CHECK(gLatestMessage == nullptr);
+
+ // Test error throwing with a callback that succeeds.
+ for (size_t i = 0; i < std::size(SAMPLES); ++i) {
+ // This should cause the appropriate error.
+ if (execDontReport(SAMPLES[i], __FILE__, __LINE__)) {
+ MOZ_CRASH("This sample should have failed");
+ }
+ CHECK(JS_IsExceptionPending(cx));
+
+ // Check result of callback.
+ CHECK(gLatestMessage != nullptr);
+ CHECK(js::StringEqualsAscii(&gLatestMessage->asLinear(), TO_STRING[i]));
+
+ // Check the final error.
+ JS::RootedValue exn(cx);
+ CHECK(JS_GetPendingException(cx, &exn));
+ JS_ClearPendingException(cx);
+
+ js::JSStringBuilder buffer(cx);
+ CHECK(ValueToStringBuffer(cx, exn, buffer));
+ JS::Rooted<JSLinearString*> linear(cx, buffer.finishString());
+ CHECK(equalStrings(cx, linear, gLatestMessage));
+
+ // Cleanup.
+ gLatestMessage = nullptr;
+ }
+
+ // Test again without callback.
+ JS_SetErrorInterceptorCallback(cx->runtime(), nullptr);
+ for (size_t i = 0; i < std::size(SAMPLES); ++i) {
+ if (execDontReport(SAMPLES[i], __FILE__, __LINE__)) {
+ MOZ_CRASH("This sample should have failed");
+ }
+ CHECK(JS_IsExceptionPending(cx));
+
+ // Check that the callback wasn't called.
+ CHECK(gLatestMessage == nullptr);
+
+ // Check the final error.
+ JS::RootedValue exn(cx);
+ CHECK(JS_GetPendingException(cx, &exn));
+ JS_ClearPendingException(cx);
+
+ js::JSStringBuilder buffer(cx);
+ CHECK(ValueToStringBuffer(cx, exn, buffer));
+ JS::Rooted<JSLinearString*> linear(cx, buffer.finishString());
+ CHECK(js::StringEqualsAscii(linear, TO_STRING[i]));
+
+ // Cleanup.
+ gLatestMessage = nullptr;
+ }
+
+ // Cleanup
+ JS_SetErrorInterceptorCallback(cx->runtime(), original);
+ gLatestMessage = nullptr;
+ JS_ClearPendingException(cx);
+
+ return true;
+}
+END_TEST(testErrorInterceptor)
diff --git a/js/src/jsapi-tests/testErrorInterceptorGC.cpp b/js/src/jsapi-tests/testErrorInterceptorGC.cpp
new file mode 100644
index 0000000000..67dac2f2dd
--- /dev/null
+++ b/js/src/jsapi-tests/testErrorInterceptorGC.cpp
@@ -0,0 +1,32 @@
+#include "js/ErrorInterceptor.h"
+#include "jsapi-tests/tests.h"
+
+namespace {
+
+// An interceptor that triggers GC:
+struct ErrorInterceptorWithGC : JSErrorInterceptor {
+ void interceptError(JSContext* cx, JS::HandleValue val) override {
+ JS::PrepareForFullGC(cx);
+ JS::NonIncrementalGC(cx, JS::GCOptions::Shrink, JS::GCReason::DEBUG_GC);
+ }
+};
+
+} // namespace
+
+BEGIN_TEST(testErrorInterceptorGC) {
+ JSErrorInterceptor* original = JS_GetErrorInterceptorCallback(cx->runtime());
+
+ ErrorInterceptorWithGC interceptor;
+ JS_SetErrorInterceptorCallback(cx->runtime(), &interceptor);
+
+ CHECK(!execDontReport("0 = 0;", __FILE__, __LINE__));
+
+ CHECK(JS_IsExceptionPending(cx));
+ JS_ClearPendingException(cx);
+
+ // Restore the original error interceptor.
+ JS_SetErrorInterceptorCallback(cx->runtime(), original);
+
+ return true;
+}
+END_TEST(testErrorInterceptorGC)
diff --git a/js/src/jsapi-tests/testErrorLineOfContext.cpp b/js/src/jsapi-tests/testErrorLineOfContext.cpp
new file mode 100644
index 0000000000..ecfd895527
--- /dev/null
+++ b/js/src/jsapi-tests/testErrorLineOfContext.cpp
@@ -0,0 +1,74 @@
+/* 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 "js/CompilationAndEvaluation.h"
+#include "js/Exception.h"
+#include "js/GlobalObject.h"
+#include "js/SourceText.h"
+#include "jsapi-tests/tests.h"
+#include "vm/ErrorReporting.h"
+
+BEGIN_TEST(testErrorLineOfContext) {
+ static const char16_t fullLineR[] = u"\n var x = @; \r ";
+ CHECK(testLineOfContextHasNoLineTerminator(fullLineR, ' '));
+
+ static const char16_t fullLineN[] = u"\n var x = @; !\n ";
+ CHECK(testLineOfContextHasNoLineTerminator(fullLineN, '!'));
+
+ static const char16_t fullLineLS[] = u"\n var x = @; +\u2028 ";
+ CHECK(testLineOfContextHasNoLineTerminator(fullLineLS, '+'));
+
+ static const char16_t fullLinePS[] = u"\n var x = @; #\u2029 ";
+ CHECK(testLineOfContextHasNoLineTerminator(fullLinePS, '#'));
+
+ static_assert(js::ErrorMetadata::lineOfContextRadius == 60,
+ "current max count past offset is 60, hits 'X' below");
+
+ static const char16_t truncatedLine[] =
+ u"@ + 4567890123456789012345678901234567890123456789012345678XYZW\n";
+ CHECK(testLineOfContextHasNoLineTerminator(truncatedLine, 'X'));
+
+ return true;
+}
+
+bool eval(const char16_t* chars, size_t len, JS::MutableHandleValue rval) {
+ JS::RealmOptions globalOptions;
+ JS::RootedObject global(
+ cx, JS_NewGlobalObject(cx, getGlobalClass(), nullptr,
+ JS::FireOnNewGlobalHook, globalOptions));
+ CHECK(global);
+
+ JSAutoRealm ar(cx, global);
+
+ JS::SourceText<char16_t> srcBuf;
+ CHECK(srcBuf.init(cx, chars, len, JS::SourceOwnership::Borrowed));
+
+ JS::CompileOptions options(cx);
+ return JS::Evaluate(cx, options, srcBuf, rval);
+}
+
+template <size_t N>
+bool testLineOfContextHasNoLineTerminator(const char16_t (&chars)[N],
+ char16_t expectedLast) {
+ JS::RootedValue rval(cx);
+ CHECK(!eval(chars, N - 1, &rval));
+
+ JS::ExceptionStack exnStack(cx);
+ CHECK(JS::StealPendingExceptionStack(cx, &exnStack));
+
+ JS::ErrorReportBuilder report(cx);
+ CHECK(report.init(cx, exnStack, JS::ErrorReportBuilder::WithSideEffects));
+
+ const auto* errorReport = report.report();
+
+ const char16_t* lineOfContext = errorReport->linebuf();
+ size_t lineOfContextLength = errorReport->linebufLength();
+
+ CHECK(lineOfContext[lineOfContextLength] == '\0');
+ char16_t last = lineOfContext[lineOfContextLength - 1];
+ CHECK(last == expectedLast);
+
+ return true;
+}
+END_TEST(testErrorLineOfContext)
diff --git a/js/src/jsapi-tests/testException.cpp b/js/src/jsapi-tests/testException.cpp
new file mode 100644
index 0000000000..6e0d26aa26
--- /dev/null
+++ b/js/src/jsapi-tests/testException.cpp
@@ -0,0 +1,92 @@
+/* -*- 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 "js/CallAndConstruct.h" // JS_CallFunctionValue
+#include "js/PropertyAndElement.h" // JS_GetProperty
+#include "jsapi-tests/tests.h"
+
+BEGIN_TEST(testException_bug860435) {
+ JS::RootedValue fun(cx);
+
+ EVAL("ReferenceError", &fun);
+ CHECK(fun.isObject());
+
+ JS::RootedValue v(cx);
+ CHECK(
+ JS_CallFunctionValue(cx, global, fun, JS::HandleValueArray::empty(), &v));
+ CHECK(v.isObject());
+ JS::RootedObject obj(cx, &v.toObject());
+
+ CHECK(JS_GetProperty(cx, obj, "stack", &v));
+ CHECK(v.isString());
+ return true;
+}
+END_TEST(testException_bug860435)
+
+BEGIN_TEST(testException_getCause) {
+ JS::RootedValue err(cx);
+ EVAL("new Error('message', { cause: new Error('message 2') })", &err);
+ CHECK(err.isObject());
+
+ JS::RootedString msg(cx, JS::ToString(cx, err));
+ CHECK(msg);
+ // Check that we have the outer error
+ bool match;
+ CHECK(JS_StringEqualsLiteral(cx, msg, "Error: message", &match));
+ CHECK(match);
+
+ JS::Rooted<mozilla::Maybe<JS::Value>> maybeCause(
+ cx, JS::GetExceptionCause(&err.toObject()));
+ CHECK(maybeCause.isSome());
+ JS::RootedValue cause(cx, *maybeCause);
+ CHECK(cause.isObject());
+
+ msg = JS::ToString(cx, cause);
+ CHECK(msg);
+ // Check that we have the inner error
+ CHECK(JS_StringEqualsLiteral(cx, msg, "Error: message 2", &match));
+ CHECK(match);
+
+ maybeCause = JS::GetExceptionCause(&cause.toObject());
+ CHECK(maybeCause.isNothing());
+
+ return true;
+}
+END_TEST(testException_getCause)
+
+BEGIN_TEST(testException_getCausePlainObject) {
+ JS::RootedObject plain(cx, JS_NewPlainObject(cx));
+ CHECK(plain);
+ JS::Rooted<mozilla::Maybe<JS::Value>> maybeCause(
+ cx, JS::GetExceptionCause(plain));
+ CHECK(maybeCause.isNothing());
+ return true;
+}
+END_TEST(testException_getCausePlainObject)
+
+BEGIN_TEST(testException_createErrorWithCause) {
+ JS::RootedString empty(cx, JS_GetEmptyString(cx));
+ JS::Rooted<mozilla::Maybe<JS::Value>> cause(
+ cx, mozilla::Some(JS::Int32Value(-1)));
+ JS::RootedValue err(cx);
+ CHECK(JS::CreateError(cx, JSEXN_ERR, nullptr, empty, 1, 1, nullptr, empty,
+ cause, &err));
+ CHECK(err.isObject());
+ JS::Rooted<mozilla::Maybe<JS::Value>> maybeCause(
+ cx, JS::GetExceptionCause(&err.toObject()));
+ CHECK(maybeCause.isSome());
+ CHECK_SAME(*cause, *maybeCause);
+
+ CHECK(JS::CreateError(cx, JSEXN_ERR, nullptr, empty, 1, 1, nullptr, empty,
+ JS::NothingHandleValue, &err));
+ CHECK(err.isObject());
+ maybeCause = JS::GetExceptionCause(&err.toObject());
+ CHECK(maybeCause.isNothing());
+
+ return true;
+}
+END_TEST(testException_createErrorWithCause)
diff --git a/js/src/jsapi-tests/testExecuteInJSMEnvironment.cpp b/js/src/jsapi-tests/testExecuteInJSMEnvironment.cpp
new file mode 100644
index 0000000000..ee42667589
--- /dev/null
+++ b/js/src/jsapi-tests/testExecuteInJSMEnvironment.cpp
@@ -0,0 +1,107 @@
+/* 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/Utf8.h" // mozilla::Utf8Unit
+
+#include "js/CompilationAndEvaluation.h" // JS::Compile
+#include "js/friend/JSMEnvironment.h" // JS::ExecuteInJSMEnvironment, JS::GetJSMEnvironmentOfScriptedCaller, JS::NewJSMEnvironment
+#include "js/PropertyAndElement.h" // JS_DefineFunctions, JS_GetProperty, JS_SetProperty
+#include "js/PropertySpec.h"
+#include "js/SourceText.h" // JS::Source{Ownership,Text}
+#include "jsapi-tests/tests.h"
+#include "util/Text.h"
+#include "vm/EnvironmentObject.h"
+#include "vm/EnvironmentObject-inl.h"
+
+BEGIN_TEST(testExecuteInJSMEnvironment_Basic) {
+ static const char src[] =
+ "var output = input;\n"
+ "\n"
+ "a = 1;\n"
+ "var b = 2;\n"
+ "let c = 3;\n"
+ "this.d = 4;\n"
+ "eval('this.e = 5');\n"
+ "(0,eval)('this.f = 6');\n"
+ "(function() { this.g = 7; })();\n"
+ "function f_h() { this.h = 8; }; f_h();\n";
+
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(__FILE__, __LINE__);
+ options.setNoScriptRval(true);
+ options.setNonSyntacticScope(true);
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, src, js_strlen(src), JS::SourceOwnership::Borrowed));
+
+ JS::RootedScript script(cx, JS::Compile(cx, options, srcBuf));
+ CHECK(script);
+
+ JS::RootedObject varEnv(cx, JS::NewJSMEnvironment(cx));
+ JS::RootedObject lexEnv(cx, JS_ExtensibleLexicalEnvironment(varEnv));
+ CHECK(varEnv && varEnv->is<js::NonSyntacticVariablesObject>());
+ CHECK(lexEnv && js::IsExtensibleLexicalEnvironment(lexEnv));
+ CHECK(lexEnv->enclosingEnvironment() == varEnv);
+
+ JS::RootedValue vi(cx, JS::Int32Value(1000));
+ CHECK(JS_SetProperty(cx, varEnv, "input", vi));
+
+ CHECK(JS::ExecuteInJSMEnvironment(cx, script, varEnv));
+
+ JS::RootedValue v(cx);
+ CHECK(JS_GetProperty(cx, varEnv, "output", &v) && v == vi);
+ CHECK(JS_GetProperty(cx, varEnv, "a", &v) && v == JS::Int32Value(1));
+ CHECK(JS_GetProperty(cx, varEnv, "b", &v) && v == JS::Int32Value(2));
+ CHECK(JS_GetProperty(cx, lexEnv, "c", &v) && v == JS::Int32Value(3));
+ CHECK(JS_GetProperty(cx, varEnv, "d", &v) && v == JS::Int32Value(4));
+ CHECK(JS_GetProperty(cx, varEnv, "e", &v) && v == JS::Int32Value(5));
+ // TODO: Bug 1396050 will fix this
+ // CHECK(JS_GetProperty(cx, varEnv, "f", &v) && v == JS::Int32Value(6));
+ CHECK(JS_GetProperty(cx, varEnv, "g", &v) && v == JS::Int32Value(7));
+ CHECK(JS_GetProperty(cx, varEnv, "h", &v) && v == JS::Int32Value(8));
+
+ return true;
+}
+END_TEST(testExecuteInJSMEnvironment_Basic);
+
+static bool test_callback(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::RootedObject env(cx, JS::GetJSMEnvironmentOfScriptedCaller(cx));
+ if (!env) {
+ return false;
+ }
+
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ args.rval().setObject(*env);
+ return true;
+}
+
+static const JSFunctionSpec testFunctions[] = {
+ JS_FN("callback", test_callback, 0, 0), JS_FS_END};
+
+BEGIN_TEST(testExecuteInJSMEnvironment_Callback) {
+ static const char src[] = "var output = callback();\n";
+
+ CHECK(JS_DefineFunctions(cx, global, testFunctions));
+
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(__FILE__, __LINE__);
+ options.setNoScriptRval(true);
+ options.setNonSyntacticScope(true);
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, src, js_strlen(src), JS::SourceOwnership::Borrowed));
+
+ JS::RootedScript script(cx, JS::Compile(cx, options, srcBuf));
+ CHECK(script);
+
+ JS::RootedObject nsvo(cx, JS::NewJSMEnvironment(cx));
+ CHECK(nsvo);
+ CHECK(JS::ExecuteInJSMEnvironment(cx, script, nsvo));
+
+ JS::RootedValue v(cx);
+ CHECK(JS_GetProperty(cx, nsvo, "output", &v) && v == JS::ObjectValue(*nsvo));
+
+ return true;
+}
+END_TEST(testExecuteInJSMEnvironment_Callback)
diff --git a/js/src/jsapi-tests/testExternalStrings.cpp b/js/src/jsapi-tests/testExternalStrings.cpp
new file mode 100644
index 0000000000..59c1029fb2
--- /dev/null
+++ b/js/src/jsapi-tests/testExternalStrings.cpp
@@ -0,0 +1,52 @@
+/* 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 "jsapi-tests/tests.h"
+#include "util/Text.h"
+
+static const char16_t arr[] = u"hi, don't delete me";
+static const size_t arrlen = js_strlen(arr);
+
+static int finalized1 = 0;
+static int finalized2 = 0;
+
+struct ExternalStringCallbacks : public JSExternalStringCallbacks {
+ int* finalizedCount = nullptr;
+
+ explicit ExternalStringCallbacks(int* finalizedCount)
+ : finalizedCount(finalizedCount) {}
+
+ void finalize(char16_t* chars) const override {
+ MOZ_ASSERT(chars == arr);
+ (*finalizedCount)++;
+ }
+
+ size_t sizeOfBuffer(const char16_t* chars,
+ mozilla::MallocSizeOf mallocSizeOf) const override {
+ MOZ_CRASH("Unexpected call");
+ }
+};
+
+static const ExternalStringCallbacks callbacks1(&finalized1);
+static const ExternalStringCallbacks callbacks2(&finalized2);
+
+BEGIN_TEST(testExternalStrings) {
+ const unsigned N = 1000;
+
+ for (unsigned i = 0; i < N; ++i) {
+ CHECK(JS_NewExternalString(cx, arr, arrlen, &callbacks1));
+ CHECK(JS_NewExternalString(cx, arr, arrlen, &callbacks2));
+ }
+
+ JS_GC(cx);
+
+ // a generous fudge factor to account for strings rooted by conservative gc
+ const unsigned epsilon = 10;
+
+ CHECK((N - finalized1) < epsilon);
+ CHECK((N - finalized2) < epsilon);
+
+ return true;
+}
+END_TEST(testExternalStrings)
diff --git a/js/src/jsapi-tests/testFindSCCs.cpp b/js/src/jsapi-tests/testFindSCCs.cpp
new file mode 100644
index 0000000000..2c0db7daa1
--- /dev/null
+++ b/js/src/jsapi-tests/testFindSCCs.cpp
@@ -0,0 +1,241 @@
+/* -*- 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 <stdarg.h>
+#include <string.h>
+
+#include "gc/FindSCCs.h"
+#include "jsapi-tests/tests.h"
+
+static const unsigned MaxVertices = 10;
+
+using js::gc::ComponentFinder;
+using js::gc::GraphNodeBase;
+
+struct TestNode : public GraphNodeBase<TestNode> {
+ unsigned index;
+};
+
+using TestComponentFinder = ComponentFinder<TestNode>;
+
+static TestNode Vertex[MaxVertices];
+
+BEGIN_TEST(testFindSCCs) {
+ // no vertices
+
+ setup(0);
+ run();
+ CHECK(end());
+
+ // no edges
+
+ setup(1);
+ run();
+ CHECK(group(0, -1));
+ CHECK(end());
+
+ setup(3);
+ run();
+ CHECK(group(2, -1));
+ CHECK(group(1, -1));
+ CHECK(group(0, -1));
+ CHECK(end());
+
+ // linear
+
+ setup(3);
+ CHECK(edge(0, 1));
+ CHECK(edge(1, 2));
+ run();
+ CHECK(group(0, -1));
+ CHECK(group(1, -1));
+ CHECK(group(2, -1));
+ CHECK(end());
+
+ // tree
+
+ setup(3);
+ CHECK(edge(0, 1));
+ CHECK(edge(0, 2));
+ run();
+ CHECK(group(0, -1));
+ if (resultsList && resultsList->index == 1) {
+ CHECK(group(1, -1));
+ CHECK(group(2, -1));
+ } else {
+ CHECK(group(2, -1));
+ CHECK(group(1, -1));
+ }
+ CHECK(end());
+
+ // cycles
+
+ setup(3);
+ CHECK(edge(0, 1));
+ CHECK(edge(1, 2));
+ CHECK(edge(2, 0));
+ run();
+ CHECK(group(0, 1, 2, -1));
+ CHECK(end());
+
+ setup(4);
+ CHECK(edge(0, 1));
+ CHECK(edge(1, 2));
+ CHECK(edge(2, 1));
+ CHECK(edge(2, 3));
+ run();
+ CHECK(group(0, -1));
+ CHECK(group(1, 2, -1));
+ CHECK(group(3, -1));
+ CHECK(end());
+
+ // remaining
+
+ setup(2);
+ CHECK(edge(0, 1));
+ run();
+ CHECK(remaining(0, 1, -1));
+ CHECK(end());
+
+ setup(2);
+ CHECK(edge(0, 1));
+ run();
+ CHECK(group(0, -1));
+ CHECK(remaining(1, -1));
+ CHECK(end());
+
+ setup(2);
+ CHECK(edge(0, 1));
+ run();
+ CHECK(group(0, -1));
+ CHECK(group(1, -1));
+ CHECK(remaining(-1));
+ CHECK(end());
+
+ return true;
+}
+
+unsigned vertex_count;
+TestComponentFinder* finder;
+TestNode* resultsList;
+
+void setup(unsigned count) {
+ vertex_count = count;
+ for (unsigned i = 0; i < MaxVertices; ++i) {
+ TestNode& v = Vertex[i];
+ v.gcGraphEdges.clear();
+ v.gcNextGraphNode = nullptr;
+ v.index = i;
+ }
+}
+
+bool edge(unsigned src_index, unsigned dest_index) {
+ return Vertex[src_index].gcGraphEdges.put(&Vertex[dest_index]);
+}
+
+void run() {
+ finder = new TestComponentFinder(cx);
+ for (unsigned i = 0; i < vertex_count; ++i) {
+ finder->addNode(&Vertex[i]);
+ }
+ resultsList = finder->getResultsList();
+}
+
+bool group(int vertex, ...) {
+ TestNode* v = resultsList;
+
+ va_list ap;
+ va_start(ap, vertex);
+ while (vertex != -1) {
+ CHECK(v != nullptr);
+ CHECK(v->index == unsigned(vertex));
+ v = v->nextNodeInGroup();
+ vertex = va_arg(ap, int);
+ }
+ va_end(ap);
+
+ CHECK(v == nullptr);
+ resultsList = resultsList->nextGroup();
+ return true;
+}
+
+bool remaining(int vertex, ...) {
+ TestNode* v = resultsList;
+
+ va_list ap;
+ va_start(ap, vertex);
+ while (vertex != -1) {
+ CHECK(v != nullptr);
+ CHECK(v->index == unsigned(vertex));
+ v = (TestNode*)v->gcNextGraphNode;
+ vertex = va_arg(ap, int);
+ }
+ va_end(ap);
+
+ CHECK(v == nullptr);
+ resultsList = nullptr;
+ return true;
+}
+
+bool end() {
+ CHECK(resultsList == nullptr);
+
+ delete finder;
+ finder = nullptr;
+ return true;
+}
+END_TEST(testFindSCCs)
+
+BEGIN_TEST(testFindSCCsStackLimit) {
+ /*
+ * Test what happens if recusion causes the stack to become full while
+ * traversing the graph.
+ *
+ * The test case is a large number of vertices, almost all of which are
+ * arranged in a linear chain. The last few are left unlinked to exercise
+ * adding vertices after the stack full condition has already been detected.
+ *
+ * Such an arrangement with no cycles would normally result in one group for
+ * each vertex, but since the stack is exhasted in processing a single group
+ * is returned containing all the vertices.
+ */
+ const unsigned max = 1000000;
+ const unsigned initial = 10;
+
+ TestNode* vertices = new TestNode[max]();
+ for (unsigned i = initial; i < (max - 10); ++i) {
+ CHECK(vertices[i].gcGraphEdges.put(&vertices[i + 1]));
+ }
+
+ TestComponentFinder finder(cx);
+ for (unsigned i = 0; i < max; ++i) {
+ finder.addNode(&vertices[i]);
+ }
+
+ TestNode* r = finder.getResultsList();
+ CHECK(r);
+ TestNode* v = r;
+
+ unsigned count = 0;
+ while (v) {
+ ++count;
+ v = v->nextNodeInGroup();
+ }
+ CHECK(count == max - initial);
+
+ count = 0;
+ v = r->nextGroup();
+ while (v) {
+ ++count;
+ CHECK(!v->nextNodeInGroup());
+ v = v->nextGroup();
+ }
+
+ delete[] vertices;
+ return true;
+}
+END_TEST(testFindSCCsStackLimit)
diff --git a/js/src/jsapi-tests/testForOfIterator.cpp b/js/src/jsapi-tests/testForOfIterator.cpp
new file mode 100644
index 0000000000..45618d037d
--- /dev/null
+++ b/js/src/jsapi-tests/testForOfIterator.cpp
@@ -0,0 +1,50 @@
+/* -*- 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 "js/ForOfIterator.h"
+#include "jsapi-tests/tests.h"
+
+BEGIN_TEST(testForOfIterator_basicNonIterable) {
+ JS::RootedValue v(cx);
+ // Hack to make it simple to produce an object that has a property
+ // named Symbol.iterator.
+ EVAL("({[Symbol.iterator]: 5})", &v);
+ JS::ForOfIterator iter(cx);
+ bool ok = iter.init(v);
+ CHECK(!ok);
+ JS_ClearPendingException(cx);
+ return true;
+}
+END_TEST(testForOfIterator_basicNonIterable)
+
+BEGIN_TEST(testForOfIterator_bug515273_part1) {
+ JS::RootedValue v(cx);
+
+ // Hack to make it simple to produce an object that has a property
+ // named Symbol.iterator.
+ EVAL("({[Symbol.iterator]: 5})", &v);
+
+ JS::ForOfIterator iter(cx);
+ bool ok = iter.init(v, JS::ForOfIterator::AllowNonIterable);
+ CHECK(!ok);
+ JS_ClearPendingException(cx);
+ return true;
+}
+END_TEST(testForOfIterator_bug515273_part1)
+
+BEGIN_TEST(testForOfIterator_bug515273_part2) {
+ JS::RootedObject obj(cx, JS_NewPlainObject(cx));
+ CHECK(obj);
+ JS::RootedValue v(cx, JS::ObjectValue(*obj));
+
+ JS::ForOfIterator iter(cx);
+ bool ok = iter.init(v, JS::ForOfIterator::AllowNonIterable);
+ CHECK(ok);
+ CHECK(!iter.valueIsIterable());
+ return true;
+}
+END_TEST(testForOfIterator_bug515273_part2)
diff --git a/js/src/jsapi-tests/testForceLexicalInitialization.cpp b/js/src/jsapi-tests/testForceLexicalInitialization.cpp
new file mode 100644
index 0000000000..be0468897d
--- /dev/null
+++ b/js/src/jsapi-tests/testForceLexicalInitialization.cpp
@@ -0,0 +1,39 @@
+/* -*- 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 "jsfriendapi.h"
+#include "jsapi-tests/tests.h"
+#include "vm/EnvironmentObject.h"
+
+BEGIN_TEST(testForceLexicalInitialization) {
+ // Attach an uninitialized lexical to a scope and ensure that it's
+ // set to undefined
+ JS::Rooted<js::GlobalObject*> g(cx, cx->global());
+ JS::Rooted<js::GlobalLexicalEnvironmentObject*> env(
+ cx, js::GlobalLexicalEnvironmentObject::create(cx, g));
+
+ JS::RootedValue uninitialized(cx, JS::MagicValue(JS_UNINITIALIZED_LEXICAL));
+ JS::Rooted<js::PropertyName*> name(cx,
+ Atomize(cx, "foopi", 4)->asPropertyName());
+ JS::RootedId id(cx, NameToId(name));
+ unsigned attrs = JSPROP_ENUMERATE | JSPROP_PERMANENT;
+
+ CHECK(NativeDefineDataProperty(cx, env, id, uninitialized, attrs));
+
+ // Verify that "foopi" is uninitialized
+ const JS::Value v = env->getSlot(env->lookup(cx, id)->slot());
+ CHECK(v.isMagic(JS_UNINITIALIZED_LEXICAL));
+
+ ForceLexicalInitialization(cx, env);
+
+ // Verify that "foopi" has been initialized to undefined
+ const JS::Value v2 = env->getSlot(env->lookup(cx, id)->slot());
+ CHECK(v2.isUndefined());
+
+ return true;
+}
+END_TEST(testForceLexicalInitialization)
diff --git a/js/src/jsapi-tests/testForwardSetProperty.cpp b/js/src/jsapi-tests/testForwardSetProperty.cpp
new file mode 100644
index 0000000000..0ab221a0c1
--- /dev/null
+++ b/js/src/jsapi-tests/testForwardSetProperty.cpp
@@ -0,0 +1,98 @@
+/* -*- 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 "js/PropertyAndElement.h" // JS_ForwardSetPropertyTo
+#include "jsapi-tests/tests.h"
+
+using namespace JS;
+
+BEGIN_TEST(testForwardSetProperty) {
+ RootedValue v1(cx);
+ EVAL(
+ "var foundValue; \n"
+ "var obj1 = { set prop(val) { foundValue = this; } }; \n"
+ "obj1;",
+ &v1);
+
+ RootedValue v2(cx);
+ EVAL(
+ "var obj2 = Object.create(obj1); \n"
+ "obj2;",
+ &v2);
+
+ RootedValue v3(cx);
+ EVAL(
+ "var obj3 = {}; \n"
+ "obj3;",
+ &v3);
+
+ RootedObject obj1(cx, &v1.toObject());
+ RootedObject obj2(cx, &v2.toObject());
+ RootedObject obj3(cx, &v3.toObject());
+
+ RootedValue setval(cx, Int32Value(42));
+
+ RootedValue propkey(cx);
+ EVAL("'prop';", &propkey);
+
+ RootedId prop(cx);
+ CHECK(JS_ValueToId(cx, propkey, &prop));
+
+ EXEC(
+ "function assertEq(a, b, msg) \n"
+ "{ \n"
+ " if (!Object.is(a, b)) \n"
+ " throw new Error('Assertion failure: ' + msg); \n"
+ "}");
+
+ // Non-strict setter
+
+ JS::ObjectOpResult result;
+ CHECK(JS_ForwardSetPropertyTo(cx, obj2, prop, setval, v3, result));
+ CHECK(result);
+
+ EXEC("assertEq(foundValue, obj3, 'wrong receiver passed to setter');");
+
+ CHECK(JS_ForwardSetPropertyTo(cx, obj2, prop, setval, setval, result));
+ CHECK(result);
+
+ EXEC(
+ "assertEq(typeof foundValue === 'object', true, \n"
+ " 'passing 42 as receiver to non-strict setter ' + \n"
+ " 'must box');");
+
+ EXEC(
+ "assertEq(foundValue instanceof Number, true, \n"
+ " 'passing 42 as receiver to non-strict setter ' + \n"
+ " 'must box to a Number');");
+
+ EXEC(
+ "assertEq(foundValue.valueOf(), 42, \n"
+ " 'passing 42 as receiver to non-strict setter ' + \n"
+ " 'must box to new Number(42)');");
+
+ // Strict setter
+
+ RootedValue v4(cx);
+ EVAL("({ set prop(val) { 'use strict'; foundValue = this; } })", &v4);
+ RootedObject obj4(cx, &v4.toObject());
+
+ CHECK(JS_ForwardSetPropertyTo(cx, obj4, prop, setval, v3, result));
+ CHECK(result);
+
+ EXEC("assertEq(foundValue, obj3, 'wrong receiver passed to strict setter');");
+
+ CHECK(JS_ForwardSetPropertyTo(cx, obj4, prop, setval, setval, result));
+ CHECK(result);
+
+ EXEC(
+ "assertEq(foundValue, 42, \n"
+ " '42 passed as receiver to strict setter was mangled');");
+
+ return true;
+}
+END_TEST(testForwardSetProperty)
diff --git a/js/src/jsapi-tests/testFreshGlobalEvalRedefinition.cpp b/js/src/jsapi-tests/testFreshGlobalEvalRedefinition.cpp
new file mode 100644
index 0000000000..645bc86c16
--- /dev/null
+++ b/js/src/jsapi-tests/testFreshGlobalEvalRedefinition.cpp
@@ -0,0 +1,63 @@
+/* -*- 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/Utf8.h" // mozilla::Utf8Unit
+
+#include "js/CompilationAndEvaluation.h" // JS::Evaluate
+#include "js/GlobalObject.h" // JS_NewGlobalObject
+#include "js/PropertyAndElement.h" // JS_GetProperty
+#include "js/SourceText.h" // JS::Source{Ownership,Text}
+#include "jsapi-tests/tests.h"
+#include "util/Text.h"
+
+static bool GlobalResolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
+ bool* resolvedp) {
+ return JS_ResolveStandardClass(cx, obj, id, resolvedp);
+}
+
+BEGIN_TEST(testRedefineGlobalEval) {
+ static const JSClassOps clsOps = {
+ nullptr, // addProperty
+ nullptr, // delProperty
+ nullptr, // enumerate
+ JS_NewEnumerateStandardClasses, // newEnumerate
+ GlobalResolve, // resolve
+ nullptr, // mayResolve
+ nullptr, // finalize
+ nullptr, // call
+ nullptr, // construct
+ JS_GlobalObjectTraceHook, // trace
+ };
+
+ static const JSClass cls = {"global", JSCLASS_GLOBAL_FLAGS, &clsOps};
+
+ /* Create the global object. */
+ JS::RealmOptions options;
+ JS::Rooted<JSObject*> g(
+ cx,
+ JS_NewGlobalObject(cx, &cls, nullptr, JS::FireOnNewGlobalHook, options));
+ if (!g) {
+ return false;
+ }
+
+ JSAutoRealm ar(cx, g);
+ JS::Rooted<JS::Value> v(cx);
+ CHECK(JS_GetProperty(cx, g, "Object", &v));
+
+ static const char data[] =
+ "Object.defineProperty(this, 'eval', { configurable: false });";
+
+ JS::CompileOptions opts(cx);
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, data, js_strlen(data), JS::SourceOwnership::Borrowed));
+
+ CHECK(JS::Evaluate(cx, opts.setFileAndLine(__FILE__, __LINE__), srcBuf, &v));
+
+ return true;
+}
+END_TEST(testRedefineGlobalEval)
diff --git a/js/src/jsapi-tests/testFrontendCompileStencil.cpp b/js/src/jsapi-tests/testFrontendCompileStencil.cpp
new file mode 100644
index 0000000000..a148ff133a
--- /dev/null
+++ b/js/src/jsapi-tests/testFrontendCompileStencil.cpp
@@ -0,0 +1,68 @@
+/* -*- 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/RefPtr.h"
+#include "mozilla/Utf8.h"
+
+#include <string>
+
+#include "frontend/FrontendContext.h" // js::FrontendContext
+#include "js/CompileOptions.h"
+#include "js/experimental/CompileScript.h"
+#include "js/SourceText.h"
+#include "js/Stack.h"
+#include "jsapi-tests/tests.h"
+#include "util/NativeStack.h" // js::GetNativeStackBase
+
+using namespace JS;
+
+BEGIN_FRONTEND_TEST(testFrontendContextCompileGlobalScriptToStencil) {
+ JS::FrontendContext* fc = JS::NewFrontendContext();
+ CHECK(fc);
+
+ static constexpr JS::NativeStackSize stackSize = 128 * sizeof(size_t) * 1024;
+
+ JS::SetNativeStackQuota(fc, stackSize);
+
+#ifndef __wasi__
+ CHECK(fc->stackLimit() ==
+ JS::GetNativeStackLimit(js::GetNativeStackBase(), stackSize - 1));
+#endif
+
+ JS::CompileOptions options((JS::CompileOptions::ForFrontendContext()));
+
+ {
+ const char source[] = "var a = 10;";
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(
+ srcBuf.init(fc, source, strlen(source), JS::SourceOwnership::Borrowed));
+ JS::CompilationStorage compileStorage;
+ RefPtr<JS::Stencil> stencil =
+ JS::CompileGlobalScriptToStencil(fc, options, srcBuf, compileStorage);
+ CHECK(stencil);
+ CHECK(compileStorage.hasInput());
+ }
+
+ {
+ const char16_t source[] = u"var a = 10;";
+
+ JS::SourceText<char16_t> srcBuf;
+ CHECK(srcBuf.init(fc, source, std::char_traits<char16_t>::length(source),
+ JS::SourceOwnership::Borrowed));
+ JS::CompilationStorage compileStorage;
+ RefPtr<JS::Stencil> stencil =
+ JS::CompileGlobalScriptToStencil(fc, options, srcBuf, compileStorage);
+ CHECK(stencil);
+ CHECK(compileStorage.hasInput());
+ }
+
+ JS::DestroyFrontendContext(fc);
+
+ return true;
+}
+
+END_TEST(testFrontendContextCompileGlobalScriptToStencil)
diff --git a/js/src/jsapi-tests/testFrontendJSON.cpp b/js/src/jsapi-tests/testFrontendJSON.cpp
new file mode 100644
index 0000000000..d4d109a1b5
--- /dev/null
+++ b/js/src/jsapi-tests/testFrontendJSON.cpp
@@ -0,0 +1,200 @@
+/* -*- 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 <string>
+
+#include "js/JSON.h"
+#include "jsapi-tests/tests.h"
+
+using namespace JS;
+
+BEGIN_FRONTEND_TEST(testIsValidJSONLatin1) {
+ const char* source;
+
+ source = "true";
+ CHECK(IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
+ strlen(source)));
+
+ source = "false";
+ CHECK(IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
+ strlen(source)));
+
+ source = "null";
+ CHECK(IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
+ strlen(source)));
+
+ source = "0";
+ CHECK(IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
+ strlen(source)));
+
+ source = "1";
+ CHECK(IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
+ strlen(source)));
+
+ source = "-1";
+ CHECK(IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
+ strlen(source)));
+
+ source = "1.75";
+ CHECK(IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
+ strlen(source)));
+
+ source = "9000000000";
+ CHECK(IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
+ strlen(source)));
+
+ source = "\"foo\"";
+ CHECK(IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
+ strlen(source)));
+
+ source = "[]";
+ CHECK(IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
+ strlen(source)));
+
+ source = "[1, true]";
+ CHECK(IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
+ strlen(source)));
+
+ source = "{}";
+ CHECK(IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
+ strlen(source)));
+
+ source = "{\"key\": 10}";
+ CHECK(IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
+ strlen(source)));
+
+ source = "{\"key\": 10, \"prop\": 20}";
+ CHECK(IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
+ strlen(source)));
+
+ source = "1 ";
+ CHECK(IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
+ strlen(source)));
+
+ // Invalid cases.
+
+ source = "";
+ CHECK(!IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
+ strlen(source)));
+
+ source = "1 1";
+ CHECK(!IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
+ strlen(source)));
+
+ source = ".1";
+ CHECK(!IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
+ strlen(source)));
+
+ source = "undefined";
+ CHECK(!IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
+ strlen(source)));
+
+ source = "TRUE";
+ CHECK(!IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
+ strlen(source)));
+
+ source = "'foo'";
+ CHECK(!IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
+ strlen(source)));
+
+ source = "[";
+ CHECK(!IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
+ strlen(source)));
+
+ source = "{";
+ CHECK(!IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
+ strlen(source)));
+
+ source = "/a/";
+ CHECK(!IsValidJSON(reinterpret_cast<const JS::Latin1Char*>(source),
+ strlen(source)));
+
+ return true;
+}
+
+END_TEST(testIsValidJSONLatin1)
+
+BEGIN_FRONTEND_TEST(testIsValidJSONTwoBytes) {
+ const char16_t* source;
+
+ source = u"true";
+ CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));
+
+ source = u"false";
+ CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));
+
+ source = u"null";
+ CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));
+
+ source = u"0";
+ CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));
+
+ source = u"1";
+ CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));
+
+ source = u"-1";
+ CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));
+
+ source = u"1.75";
+ CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));
+
+ source = u"9000000000";
+ CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));
+
+ source = u"\"foo\"";
+ CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));
+
+ source = u"[]";
+ CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));
+
+ source = u"[1, true]";
+ CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));
+
+ source = u"{}";
+ CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));
+
+ source = u"{\"key\": 10}";
+ CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));
+
+ source = u"{\"key\": 10, \"prop\": 20}";
+ CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));
+
+ source = u"1 ";
+ CHECK(IsValidJSON(source, std::char_traits<char16_t>::length(source)));
+
+ // Invalid cases.
+
+ source = u"";
+ CHECK(!IsValidJSON(source, std::char_traits<char16_t>::length(source)));
+
+ source = u"1 1";
+ CHECK(!IsValidJSON(source, std::char_traits<char16_t>::length(source)));
+
+ source = u".1";
+ CHECK(!IsValidJSON(source, std::char_traits<char16_t>::length(source)));
+
+ source = u"undefined";
+ CHECK(!IsValidJSON(source, std::char_traits<char16_t>::length(source)));
+
+ source = u"TRUE";
+ CHECK(!IsValidJSON(source, std::char_traits<char16_t>::length(source)));
+
+ source = u"'foo'";
+ CHECK(!IsValidJSON(source, std::char_traits<char16_t>::length(source)));
+
+ source = u"[";
+ CHECK(!IsValidJSON(source, std::char_traits<char16_t>::length(source)));
+
+ source = u"{";
+ CHECK(!IsValidJSON(source, std::char_traits<char16_t>::length(source)));
+
+ source = u"/a/";
+ CHECK(!IsValidJSON(source, std::char_traits<char16_t>::length(source)));
+
+ return true;
+}
+
+END_TEST(testIsValidJSONTwoBytes)
diff --git a/js/src/jsapi-tests/testFunctionBinding.cpp b/js/src/jsapi-tests/testFunctionBinding.cpp
new file mode 100644
index 0000000000..6a69e32349
--- /dev/null
+++ b/js/src/jsapi-tests/testFunctionBinding.cpp
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ *
+ * Test function name binding.
+ */
+/* 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/Utf8.h" // mozilla::Utf8Unit
+
+#include "js/CallAndConstruct.h"
+#include "js/CompilationAndEvaluation.h" // JS::CompileFunction
+#include "js/SourceText.h" // JS::Source{Ownership,Text}
+#include "jsapi-tests/tests.h"
+#include "util/Text.h"
+
+using namespace js;
+
+BEGIN_TEST(test_functionBinding) {
+ RootedFunction fun(cx);
+
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(__FILE__, __LINE__);
+
+ JS::RootedObjectVector emptyScopeChain(cx);
+
+ // Named function shouldn't have it's binding.
+ {
+ static const char s1chars[] = "return (typeof s1) == 'undefined';";
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, s1chars, js_strlen(s1chars),
+ JS::SourceOwnership::Borrowed));
+
+ fun = JS::CompileFunction(cx, emptyScopeChain, options, "s1", 0, nullptr,
+ srcBuf);
+ CHECK(fun);
+ }
+
+ JS::RootedValueVector args(cx);
+ RootedValue rval(cx);
+ CHECK(JS::Call(cx, UndefinedHandleValue, fun, args, &rval));
+ CHECK(rval.isBoolean());
+ CHECK(rval.toBoolean());
+
+ // Named function shouldn't have `anonymous` binding.
+ {
+ static const char s2chars[] = "return (typeof anonymous) == 'undefined';";
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, s2chars, js_strlen(s2chars),
+ JS::SourceOwnership::Borrowed));
+
+ fun = JS::CompileFunction(cx, emptyScopeChain, options, "s2", 0, nullptr,
+ srcBuf);
+ CHECK(fun);
+ }
+
+ CHECK(JS::Call(cx, UndefinedHandleValue, fun, args, &rval));
+ CHECK(rval.isBoolean());
+ CHECK(rval.toBoolean());
+
+ // Anonymous function shouldn't have `anonymous` binding.
+ {
+ static const char s3chars[] = "return (typeof anonymous) == 'undefined';";
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, s3chars, js_strlen(s3chars),
+ JS::SourceOwnership::Borrowed));
+
+ fun = JS::CompileFunction(cx, emptyScopeChain, options, nullptr, 0, nullptr,
+ srcBuf);
+ CHECK(fun);
+ }
+
+ CHECK(JS::Call(cx, UndefinedHandleValue, fun, args, &rval));
+ CHECK(rval.isBoolean());
+ CHECK(rval.toBoolean());
+
+ return true;
+}
+END_TEST(test_functionBinding)
diff --git a/js/src/jsapi-tests/testFunctionNonSyntactic.cpp b/js/src/jsapi-tests/testFunctionNonSyntactic.cpp
new file mode 100644
index 0000000000..b58db08897
--- /dev/null
+++ b/js/src/jsapi-tests/testFunctionNonSyntactic.cpp
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ *
+ * Test function with enclosing non-syntactic scope.
+ */
+/* 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/Utf8.h" // mozilla::Utf8Unit
+
+#include "js/CallAndConstruct.h"
+#include "js/CompilationAndEvaluation.h" // JS::CompileFunction
+#include "js/PropertyAndElement.h" // JS_DefineProperty
+#include "js/SourceText.h" // JS::Source{Ownership,Text}
+#include "jsapi-tests/tests.h"
+#include "util/Text.h"
+#include "vm/JSFunction.h" // JSFunction
+#include "vm/Scope.h" // Scope
+#include "vm/ScopeKind.h" // ScopeKind
+
+using namespace js;
+
+BEGIN_TEST(testFunctionNonSyntactic) {
+ JS::RootedObjectVector scopeChain(cx);
+
+ {
+ JS::RootedObject scopeObj(cx, JS_NewPlainObject(cx));
+ CHECK(scopeObj);
+ JS::RootedValue val(cx);
+ val.setNumber(1);
+ CHECK(JS_DefineProperty(cx, scopeObj, "foo", val, JSPROP_ENUMERATE));
+ CHECK(scopeChain.append(scopeObj));
+ }
+
+ {
+ JS::RootedObject scopeObj(cx, JS_NewPlainObject(cx));
+ CHECK(scopeObj);
+ JS::RootedValue val(cx);
+ val.setNumber(20);
+ CHECK(JS_DefineProperty(cx, scopeObj, "bar", val, JSPROP_ENUMERATE));
+ CHECK(scopeChain.append(scopeObj));
+ }
+
+ {
+ static const char src[] = "return foo + bar;";
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, src, js_strlen(src), JS::SourceOwnership::Borrowed));
+
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(__FILE__, __LINE__);
+ RootedFunction fun(cx, JS::CompileFunction(cx, scopeChain, options, "test",
+ 0, nullptr, srcBuf));
+ CHECK(fun);
+
+ CHECK(fun->enclosingScope()->kind() == ScopeKind::NonSyntactic);
+
+ JS::RootedValue funVal(cx, JS::ObjectValue(*fun));
+ JS::RootedValue rval(cx);
+ CHECK(JS::Call(cx, JS::UndefinedHandleValue, funVal,
+ JS::HandleValueArray::empty(), &rval));
+ CHECK(rval.isNumber());
+ CHECK(rval.toNumber() == 21);
+ }
+
+ // With extra body bar.
+ {
+ const char* args[] = {
+ "a = 300",
+ };
+ static const char src[] = "var x = 4000; return a + x + foo + bar;";
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, src, js_strlen(src), JS::SourceOwnership::Borrowed));
+
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(__FILE__, __LINE__);
+ RootedFunction fun(cx, JS::CompileFunction(cx, scopeChain, options, "test",
+ 1, args, srcBuf));
+ CHECK(fun);
+
+ CHECK(fun->enclosingScope()->kind() == ScopeKind::NonSyntactic);
+
+ JS::RootedValue funVal(cx, JS::ObjectValue(*fun));
+ JS::RootedValue rval(cx);
+ CHECK(JS::Call(cx, JS::UndefinedHandleValue, funVal,
+ JS::HandleValueArray::empty(), &rval));
+ CHECK(rval.isNumber());
+ CHECK(rval.toNumber() == 4321);
+ }
+
+ return true;
+}
+END_TEST(testFunctionNonSyntactic)
diff --git a/js/src/jsapi-tests/testFunctionProperties.cpp b/js/src/jsapi-tests/testFunctionProperties.cpp
new file mode 100644
index 0000000000..01031cc5ea
--- /dev/null
+++ b/js/src/jsapi-tests/testFunctionProperties.cpp
@@ -0,0 +1,26 @@
+/* -*- 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 "js/PropertyAndElement.h" // JS_GetProperty
+#include "jsapi-tests/tests.h"
+
+BEGIN_TEST(testFunctionProperties) {
+ JS::RootedValue x(cx);
+ EVAL("(function f() {})", &x);
+
+ JS::RootedObject obj(cx, x.toObjectOrNull());
+
+ JS::RootedValue y(cx);
+ CHECK(JS_GetProperty(cx, obj, "arguments", &y));
+ CHECK(y.isNull());
+
+ CHECK(JS_GetProperty(cx, obj, "caller", &y));
+ CHECK(y.isNull());
+
+ return true;
+}
+END_TEST(testFunctionProperties)
diff --git a/js/src/jsapi-tests/testGCAllocator.cpp b/js/src/jsapi-tests/testGCAllocator.cpp
new file mode 100644
index 0000000000..c740a3b10b
--- /dev/null
+++ b/js/src/jsapi-tests/testGCAllocator.cpp
@@ -0,0 +1,354 @@
+/* -*- 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 <cstdlib>
+
+#include "gc/Memory.h"
+#include "jsapi-tests/tests.h"
+
+#if defined(XP_WIN)
+# include "util/WindowsWrapper.h"
+# include <psapi.h>
+#elif defined(__wasi__)
+// Nothing.
+#else
+# include <algorithm>
+# include <errno.h>
+# include <sys/mman.h>
+# include <sys/resource.h>
+# include <sys/stat.h>
+# include <sys/types.h>
+# include <unistd.h>
+#endif
+
+BEGIN_TEST(testGCAllocator) {
+#ifdef JS_64BIT
+ // If we're using the scattershot allocator, this test does not apply.
+ if (js::gc::UsingScattershotAllocator()) {
+ return true;
+ }
+#endif
+
+ size_t PageSize = js::gc::SystemPageSize();
+
+ /* Finish any ongoing background free activity. */
+ js::gc::FinishGC(cx);
+
+ bool growUp = false;
+ CHECK(addressesGrowUp(&growUp));
+
+ if (growUp) {
+ return testGCAllocatorUp(PageSize);
+ } else {
+ return testGCAllocatorDown(PageSize);
+ }
+}
+
+static const size_t Chunk = 512 * 1024;
+static const size_t Alignment = 2 * Chunk;
+static const int MaxTempChunks = 4096;
+static const size_t StagingSize = 16 * Chunk;
+
+bool addressesGrowUp(bool* resultOut) {
+ /*
+ * Try to detect whether the OS allocates memory in increasing or decreasing
+ * address order by making several allocations and comparing the addresses.
+ */
+
+ static const unsigned ChunksToTest = 20;
+ static const int ThresholdCount = 15;
+
+ void* chunks[ChunksToTest];
+ for (unsigned i = 0; i < ChunksToTest; i++) {
+ chunks[i] = mapMemory(2 * Chunk);
+ CHECK(chunks[i]);
+ }
+
+ int upCount = 0;
+ int downCount = 0;
+
+ for (unsigned i = 0; i < ChunksToTest - 1; i++) {
+ if (chunks[i] < chunks[i + 1]) {
+ upCount++;
+ } else {
+ downCount++;
+ }
+ }
+
+ for (unsigned i = 0; i < ChunksToTest; i++) {
+ unmapPages(chunks[i], 2 * Chunk);
+ }
+
+ /* Check results were mostly consistent. */
+ CHECK(abs(upCount - downCount) >= ThresholdCount);
+
+ *resultOut = upCount > downCount;
+
+ return true;
+}
+
+size_t offsetFromAligned(void* p) { return uintptr_t(p) % Alignment; }
+
+enum AllocType { UseNormalAllocator, UseLastDitchAllocator };
+
+bool testGCAllocatorUp(const size_t PageSize) {
+ const size_t UnalignedSize = StagingSize + Alignment - PageSize;
+ void* chunkPool[MaxTempChunks];
+ // Allocate a contiguous chunk that we can partition for testing.
+ void* stagingArea = mapMemory(UnalignedSize);
+ if (!stagingArea) {
+ return false;
+ }
+ // Ensure that the staging area is aligned.
+ unmapPages(stagingArea, UnalignedSize);
+ if (offsetFromAligned(stagingArea)) {
+ const size_t Offset = offsetFromAligned(stagingArea);
+ // Place the area at the lowest aligned address.
+ stagingArea = (void*)(uintptr_t(stagingArea) + (Alignment - Offset));
+ }
+ mapMemoryAt(stagingArea, StagingSize);
+ // Make sure there are no available chunks below the staging area.
+ int tempChunks;
+ if (!fillSpaceBeforeStagingArea(tempChunks, stagingArea, chunkPool, false)) {
+ return false;
+ }
+ // Unmap the staging area so we can set it up for testing.
+ unmapPages(stagingArea, StagingSize);
+ // Check that the first chunk is used if it is aligned.
+ CHECK(positionIsCorrect("xxooxxx---------", stagingArea, chunkPool,
+ tempChunks));
+ // Check that the first chunk is used if it can be aligned.
+ CHECK(positionIsCorrect("x-ooxxx---------", stagingArea, chunkPool,
+ tempChunks));
+ // Check that an aligned chunk after a single unalignable chunk is used.
+ CHECK(positionIsCorrect("x--xooxxx-------", stagingArea, chunkPool,
+ tempChunks));
+ // Check that we fall back to the slow path after two unalignable chunks.
+ CHECK(positionIsCorrect("x--xx--xoo--xxx-", stagingArea, chunkPool,
+ tempChunks));
+ // Check that we also fall back after an unalignable and an alignable chunk.
+ CHECK(positionIsCorrect("x--xx---x-oo--x-", stagingArea, chunkPool,
+ tempChunks));
+ // Check that the last ditch allocator works as expected.
+ CHECK(positionIsCorrect("x--xx--xx-oox---", stagingArea, chunkPool,
+ tempChunks, UseLastDitchAllocator));
+ // Check that the last ditch allocator can deal with naturally aligned chunks.
+ CHECK(positionIsCorrect("x--xx--xoo------", stagingArea, chunkPool,
+ tempChunks, UseLastDitchAllocator));
+
+ // Clean up.
+ while (--tempChunks >= 0) {
+ unmapPages(chunkPool[tempChunks], 2 * Chunk);
+ }
+ return true;
+}
+
+bool testGCAllocatorDown(const size_t PageSize) {
+ const size_t UnalignedSize = StagingSize + Alignment - PageSize;
+ void* chunkPool[MaxTempChunks];
+ // Allocate a contiguous chunk that we can partition for testing.
+ void* stagingArea = mapMemory(UnalignedSize);
+ if (!stagingArea) {
+ return false;
+ }
+ // Ensure that the staging area is aligned.
+ unmapPages(stagingArea, UnalignedSize);
+ if (offsetFromAligned(stagingArea)) {
+ void* stagingEnd = (void*)(uintptr_t(stagingArea) + UnalignedSize);
+ const size_t Offset = offsetFromAligned(stagingEnd);
+ // Place the area at the highest aligned address.
+ stagingArea = (void*)(uintptr_t(stagingEnd) - Offset - StagingSize);
+ }
+ mapMemoryAt(stagingArea, StagingSize);
+ // Make sure there are no available chunks above the staging area.
+ int tempChunks;
+ if (!fillSpaceBeforeStagingArea(tempChunks, stagingArea, chunkPool, true)) {
+ return false;
+ }
+ // Unmap the staging area so we can set it up for testing.
+ unmapPages(stagingArea, StagingSize);
+ // Check that the first chunk is used if it is aligned.
+ CHECK(positionIsCorrect("---------xxxooxx", stagingArea, chunkPool,
+ tempChunks));
+ // Check that the first chunk is used if it can be aligned.
+ CHECK(positionIsCorrect("---------xxxoo-x", stagingArea, chunkPool,
+ tempChunks));
+ // Check that an aligned chunk after a single unalignable chunk is used.
+ CHECK(positionIsCorrect("-------xxxoox--x", stagingArea, chunkPool,
+ tempChunks));
+ // Check that we fall back to the slow path after two unalignable chunks.
+ CHECK(positionIsCorrect("-xxx--oox--xx--x", stagingArea, chunkPool,
+ tempChunks));
+ // Check that we also fall back after an unalignable and an alignable chunk.
+ CHECK(positionIsCorrect("-x--oo-x---xx--x", stagingArea, chunkPool,
+ tempChunks));
+ // Check that the last ditch allocator works as expected.
+ CHECK(positionIsCorrect("---xoo-xx--xx--x", stagingArea, chunkPool,
+ tempChunks, UseLastDitchAllocator));
+ // Check that the last ditch allocator can deal with naturally aligned chunks.
+ CHECK(positionIsCorrect("------oox--xx--x", stagingArea, chunkPool,
+ tempChunks, UseLastDitchAllocator));
+
+ // Clean up.
+ while (--tempChunks >= 0) {
+ unmapPages(chunkPool[tempChunks], 2 * Chunk);
+ }
+ return true;
+}
+
+bool fillSpaceBeforeStagingArea(int& tempChunks, void* stagingArea,
+ void** chunkPool, bool addressesGrowDown) {
+ // Make sure there are no available chunks before the staging area.
+ tempChunks = 0;
+ chunkPool[tempChunks++] = mapMemory(2 * Chunk);
+ while (tempChunks < MaxTempChunks && chunkPool[tempChunks - 1] &&
+ (chunkPool[tempChunks - 1] < stagingArea) ^ addressesGrowDown) {
+ chunkPool[tempChunks++] = mapMemory(2 * Chunk);
+ if (!chunkPool[tempChunks - 1]) {
+ break; // We already have our staging area, so OOM here is okay.
+ }
+ if ((chunkPool[tempChunks - 1] < chunkPool[tempChunks - 2]) ^
+ addressesGrowDown) {
+ break; // The address growth direction is inconsistent!
+ }
+ }
+ // OOM also means success in this case.
+ if (!chunkPool[tempChunks - 1]) {
+ --tempChunks;
+ return true;
+ }
+ // Bail if we can't guarantee the right address space layout.
+ if ((chunkPool[tempChunks - 1] < stagingArea) ^ addressesGrowDown ||
+ (tempChunks > 1 &&
+ (chunkPool[tempChunks - 1] < chunkPool[tempChunks - 2]) ^
+ addressesGrowDown)) {
+ while (--tempChunks >= 0) {
+ unmapPages(chunkPool[tempChunks], 2 * Chunk);
+ }
+ unmapPages(stagingArea, StagingSize);
+ return false;
+ }
+ return true;
+}
+
+bool positionIsCorrect(const char* str, void* base, void** chunkPool,
+ int tempChunks,
+ AllocType allocator = UseNormalAllocator) {
+ // str represents a region of memory, with each character representing a
+ // region of Chunk bytes. str should contain only x, o and -, where
+ // x = mapped by the test to set up the initial conditions,
+ // o = mapped by the GC allocator, and
+ // - = unmapped.
+ // base should point to a region of contiguous free memory
+ // large enough to hold strlen(str) chunks of Chunk bytes.
+ int len = strlen(str);
+ int i;
+ // Find the index of the desired address.
+ for (i = 0; i < len && str[i] != 'o'; ++i)
+ ;
+ void* desired = (void*)(uintptr_t(base) + i * Chunk);
+ // Map the regions indicated by str.
+ for (i = 0; i < len; ++i) {
+ if (str[i] == 'x') {
+ mapMemoryAt((void*)(uintptr_t(base) + i * Chunk), Chunk);
+ }
+ }
+ // Allocate using the GC's allocator.
+ void* result;
+ if (allocator == UseNormalAllocator) {
+ result = js::gc::MapAlignedPages(2 * Chunk, Alignment);
+ } else {
+ result = js::gc::TestMapAlignedPagesLastDitch(2 * Chunk, Alignment);
+ }
+ // Clean up the mapped regions.
+ if (result) {
+ js::gc::UnmapPages(result, 2 * Chunk);
+ }
+ for (--i; i >= 0; --i) {
+ if (str[i] == 'x') {
+ js::gc::UnmapPages((void*)(uintptr_t(base) + i * Chunk), Chunk);
+ }
+ }
+ // CHECK returns, so clean up on failure.
+ if (result != desired) {
+ while (--tempChunks >= 0) {
+ js::gc::UnmapPages(chunkPool[tempChunks], 2 * Chunk);
+ }
+ }
+ return result == desired;
+}
+
+#if defined(XP_WIN)
+
+void* mapMemoryAt(void* desired, size_t length) {
+ return VirtualAlloc(desired, length, MEM_COMMIT | MEM_RESERVE,
+ PAGE_READWRITE);
+}
+
+void* mapMemory(size_t length) {
+ return VirtualAlloc(nullptr, length, MEM_COMMIT | MEM_RESERVE,
+ PAGE_READWRITE);
+}
+
+void unmapPages(void* p, size_t size) {
+ MOZ_ALWAYS_TRUE(VirtualFree(p, 0, MEM_RELEASE));
+}
+
+#elif defined(__wasi__)
+
+void* mapMemoryAt(void* desired, size_t length) { return nullptr; }
+
+void* mapMemory(size_t length) {
+ void* addr = nullptr;
+ if (int err = posix_memalign(&addr, js::gc::SystemPageSize(), length)) {
+ MOZ_ASSERT(err == ENOMEM);
+ }
+ MOZ_ASSERT(addr);
+ memset(addr, 0, length);
+ return addr;
+}
+
+void unmapPages(void* p, size_t size) { free(p); }
+
+#else
+
+void* mapMemoryAt(void* desired, size_t length) {
+ void* region = mmap(desired, length, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANON, -1, 0);
+ if (region == MAP_FAILED) {
+ return nullptr;
+ }
+ if (region != desired) {
+ if (munmap(region, length)) {
+ MOZ_RELEASE_ASSERT(errno == ENOMEM);
+ }
+ return nullptr;
+ }
+ return region;
+}
+
+void* mapMemory(size_t length) {
+ int prot = PROT_READ | PROT_WRITE;
+ int flags = MAP_PRIVATE | MAP_ANON;
+ int fd = -1;
+ off_t offset = 0;
+ void* region = mmap(nullptr, length, prot, flags, fd, offset);
+ if (region == MAP_FAILED) {
+ return nullptr;
+ }
+ return region;
+}
+
+void unmapPages(void* p, size_t size) {
+ if (munmap(p, size)) {
+ MOZ_RELEASE_ASSERT(errno == ENOMEM);
+ }
+}
+
+#endif
+
+END_TEST(testGCAllocator)
diff --git a/js/src/jsapi-tests/testGCCellPtr.cpp b/js/src/jsapi-tests/testGCCellPtr.cpp
new file mode 100644
index 0000000000..ef74808b52
--- /dev/null
+++ b/js/src/jsapi-tests/testGCCellPtr.cpp
@@ -0,0 +1,58 @@
+/* -*- 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 "jsapi.h"
+#include "jspubtd.h"
+
+#include "js/CompilationAndEvaluation.h" // JS::Compile
+#include "js/SourceText.h" // JS::Source{Ownership,Text}
+#include "jsapi-tests/tests.h"
+
+JS::GCCellPtr GivesAndTakesCells(JS::GCCellPtr cell) { return cell; }
+
+BEGIN_TEST(testGCCellPtr) {
+ JS::RootedObject obj(cx, JS_NewPlainObject(cx));
+ CHECK(obj);
+
+ JS::RootedString str(cx, JS_NewStringCopyZ(cx, "probably foobar"));
+ CHECK(str);
+
+ const char* code = "function foo() { return 'bar'; }";
+
+ JS::CompileOptions opts(cx);
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, code, strlen(code), JS::SourceOwnership::Borrowed));
+
+ JS::RootedScript script(cx, JS::Compile(cx, opts, srcBuf));
+ CHECK(script);
+
+ CHECK(!JS::GCCellPtr(nullptr));
+
+ CHECK(JS::GCCellPtr(obj.get()));
+ CHECK(JS::GCCellPtr(obj.get()).kind() == JS::TraceKind::Object);
+ CHECK(JS::ObjectValue(*obj).toGCCellPtr().kind() == JS::TraceKind::Object);
+
+ CHECK(JS::GCCellPtr(str.get()));
+ CHECK(JS::GCCellPtr(str.get()).kind() == JS::TraceKind::String);
+ CHECK(JS::StringValue(str).toGCCellPtr().kind() == JS::TraceKind::String);
+
+ CHECK(JS::GCCellPtr(script.get()));
+ CHECK(!JS::GCCellPtr(nullptr));
+ CHECK(JS::GCCellPtr(script.get()).kind() == JS::TraceKind::Script);
+
+ JS::GCCellPtr objcell(obj.get());
+ JS::GCCellPtr scriptcell = JS::GCCellPtr(script.get());
+ CHECK(GivesAndTakesCells(objcell));
+ CHECK(GivesAndTakesCells(scriptcell));
+
+ JS::GCCellPtr copy = objcell;
+ CHECK(copy == objcell);
+
+ return true;
+}
+END_TEST(testGCCellPtr)
diff --git a/js/src/jsapi-tests/testGCChunkPool.cpp b/js/src/jsapi-tests/testGCChunkPool.cpp
new file mode 100644
index 0000000000..12b42330d4
--- /dev/null
+++ b/js/src/jsapi-tests/testGCChunkPool.cpp
@@ -0,0 +1,74 @@
+/* -*- 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 <utility>
+
+#include "gc/GCLock.h"
+#include "gc/GCRuntime.h"
+#include "jsapi-tests/tests.h"
+
+BEGIN_TEST(testGCChunkPool) {
+ using namespace js::gc;
+
+ const int N = 10;
+ ChunkPool pool;
+
+ // Create.
+ for (int i = 0; i < N; ++i) {
+ void* ptr = TenuredChunk::allocate(&cx->runtime()->gc);
+ CHECK(ptr);
+ TenuredChunk* chunk = TenuredChunk::emplace(ptr, &cx->runtime()->gc, true);
+ CHECK(chunk);
+ pool.push(chunk);
+ }
+ MOZ_ASSERT(pool.verify());
+
+ // Iterate.
+ uint32_t i = 0;
+ for (ChunkPool::Iter iter(pool); !iter.done(); iter.next(), ++i) {
+ CHECK(iter.get());
+ }
+ CHECK(i == pool.count());
+ MOZ_ASSERT(pool.verify());
+
+ // Push/Pop.
+ for (int i = 0; i < N; ++i) {
+ TenuredChunk* chunkA = pool.pop();
+ TenuredChunk* chunkB = pool.pop();
+ TenuredChunk* chunkC = pool.pop();
+ pool.push(chunkA);
+ pool.push(chunkB);
+ pool.push(chunkC);
+ }
+ MOZ_ASSERT(pool.verify());
+
+ // Remove.
+ TenuredChunk* chunk = nullptr;
+ int offset = N / 2;
+ for (ChunkPool::Iter iter(pool); !iter.done(); iter.next(), --offset) {
+ if (offset == 0) {
+ chunk = pool.remove(iter.get());
+ break;
+ }
+ }
+ CHECK(chunk);
+ MOZ_ASSERT(!pool.contains(chunk));
+ MOZ_ASSERT(pool.verify());
+ pool.push(chunk);
+
+ // Destruct.
+ js::AutoLockGC lock(cx->runtime());
+ for (ChunkPool::Iter iter(pool); !iter.done();) {
+ TenuredChunk* chunk = iter.get();
+ iter.next();
+ pool.remove(chunk);
+ UnmapPages(chunk, ChunkSize);
+ }
+
+ return true;
+}
+END_TEST(testGCChunkPool)
diff --git a/js/src/jsapi-tests/testGCExactRooting.cpp b/js/src/jsapi-tests/testGCExactRooting.cpp
new file mode 100644
index 0000000000..d53e293f23
--- /dev/null
+++ b/js/src/jsapi-tests/testGCExactRooting.cpp
@@ -0,0 +1,915 @@
+/* -*- 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/Maybe.h"
+#include "mozilla/Result.h"
+#include "mozilla/ResultVariant.h"
+
+#include "ds/TraceableFifo.h"
+#include "gc/Policy.h"
+#include "js/GCHashTable.h"
+#include "js/GCVector.h"
+#include "js/PropertyAndElement.h" // JS_DefineProperty, JS_GetProperty, JS_SetProperty
+#include "js/RootingAPI.h"
+
+#include "jsapi-tests/tests.h"
+
+using namespace js;
+
+using mozilla::Maybe;
+using mozilla::Some;
+
+BEGIN_TEST(testGCExactRooting) {
+ JS::RootedObject rootCx(cx, JS_NewPlainObject(cx));
+
+ JS_GC(cx);
+
+ /* Use the objects we just created to ensure that they are still alive. */
+ JS_DefineProperty(cx, rootCx, "foo", JS::UndefinedHandleValue, 0);
+
+ return true;
+}
+END_TEST(testGCExactRooting)
+
+BEGIN_TEST(testGCSuppressions) {
+ JS::AutoAssertNoGC nogc;
+ JS::AutoCheckCannotGC checkgc;
+ JS::AutoSuppressGCAnalysis noanalysis;
+
+ JS::AutoAssertNoGC nogcCx(cx);
+ JS::AutoCheckCannotGC checkgcCx(cx);
+ JS::AutoSuppressGCAnalysis noanalysisCx(cx);
+
+ return true;
+}
+END_TEST(testGCSuppressions)
+
+struct MyContainer {
+ int whichConstructor;
+ HeapPtr<JSObject*> obj;
+ HeapPtr<JSString*> str;
+
+ MyContainer() : whichConstructor(1), obj(nullptr), str(nullptr) {}
+ explicit MyContainer(double) : MyContainer() { whichConstructor = 2; }
+ explicit MyContainer(JSContext* cx) : MyContainer() { whichConstructor = 3; }
+ MyContainer(JSContext* cx, JSContext* cx2, JSContext* cx3) : MyContainer() {
+ whichConstructor = 4;
+ }
+ MyContainer(const MyContainer& rhs)
+ : whichConstructor(100 + rhs.whichConstructor),
+ obj(rhs.obj),
+ str(rhs.str) {}
+ void trace(JSTracer* trc) {
+ js::TraceNullableEdge(trc, &obj, "test container obj");
+ js::TraceNullableEdge(trc, &str, "test container str");
+ }
+};
+
+struct MyNonCopyableContainer {
+ int whichConstructor;
+ HeapPtr<JSObject*> obj;
+ HeapPtr<JSString*> str;
+
+ MyNonCopyableContainer() : whichConstructor(1), obj(nullptr), str(nullptr) {}
+ explicit MyNonCopyableContainer(double) : MyNonCopyableContainer() {
+ whichConstructor = 2;
+ }
+ explicit MyNonCopyableContainer(JSContext* cx) : MyNonCopyableContainer() {
+ whichConstructor = 3;
+ }
+ explicit MyNonCopyableContainer(JSContext* cx, JSContext* cx2, JSContext* cx3)
+ : MyNonCopyableContainer() {
+ whichConstructor = 4;
+ }
+
+ MyNonCopyableContainer(const MyNonCopyableContainer&) = delete;
+ MyNonCopyableContainer& operator=(const MyNonCopyableContainer&) = delete;
+
+ void trace(JSTracer* trc) {
+ js::TraceNullableEdge(trc, &obj, "test container obj");
+ js::TraceNullableEdge(trc, &str, "test container str");
+ }
+};
+
+namespace js {
+template <typename Wrapper>
+struct MutableWrappedPtrOperations<MyContainer, Wrapper> {
+ HeapPtr<JSObject*>& obj() { return static_cast<Wrapper*>(this)->get().obj; }
+ HeapPtr<JSString*>& str() { return static_cast<Wrapper*>(this)->get().str; }
+ int constructor() {
+ return static_cast<Wrapper*>(this)->get().whichConstructor;
+ }
+};
+
+template <typename Wrapper>
+struct MutableWrappedPtrOperations<MyNonCopyableContainer, Wrapper> {
+ HeapPtr<JSObject*>& obj() { return static_cast<Wrapper*>(this)->get().obj; }
+ HeapPtr<JSString*>& str() { return static_cast<Wrapper*>(this)->get().str; }
+ int constructor() {
+ return static_cast<Wrapper*>(this)->get().whichConstructor;
+ }
+};
+} // namespace js
+
+BEGIN_TEST(testGCRootedStaticStructInternalStackStorageAugmented) {
+ // Test Rooted constructors for a copyable type.
+ JS::Rooted<MyContainer> r1(cx);
+ JS::Rooted<MyContainer> r2(cx, 3.4);
+ JS::Rooted<MyContainer> r3(cx, MyContainer(cx));
+ JS::Rooted<MyContainer> r4(cx, cx);
+ JS::Rooted<MyContainer> r5(cx, cx, cx, cx);
+
+ JS::Rooted<Value> rv(cx);
+
+ CHECK_EQUAL(r1.constructor(), 1); // direct SafelyInitialized<T>
+ CHECK_EQUAL(r2.constructor(), 2); // direct MyContainer(3.4)
+ CHECK_EQUAL(r3.constructor(), 103); // copy of MyContainer(cx)
+ CHECK_EQUAL(r4.constructor(), 3); // direct MyContainer(cx)
+ CHECK_EQUAL(r5.constructor(), 4); // direct MyContainer(cx, cx, cx)
+
+ // Test Rooted constructor forwarding for a non-copyable type.
+ JS::Rooted<MyNonCopyableContainer> nc1(cx);
+ JS::Rooted<MyNonCopyableContainer> nc2(cx, 3.4);
+ // Compile error: cannot copy
+ // JS::Rooted<MyNonCopyableContainer> nc3(cx, MyNonCopyableContainer(cx));
+ JS::Rooted<MyNonCopyableContainer> nc4(cx, cx);
+ JS::Rooted<MyNonCopyableContainer> nc5(cx, cx, cx, cx);
+
+ CHECK_EQUAL(nc1.constructor(), 1); // direct MyNonCopyableContainer()
+ CHECK_EQUAL(nc2.constructor(), 2); // direct MyNonCopyableContainer(3.4)
+ CHECK_EQUAL(nc4.constructor(), 3); // direct MyNonCopyableContainer(cx)
+ CHECK_EQUAL(nc5.constructor(),
+ 4); // direct MyNonCopyableContainer(cx, cx, cx)
+
+ JS::Rooted<MyContainer> container(cx);
+ container.obj() = JS_NewObject(cx, nullptr);
+ container.str() = JS_NewStringCopyZ(cx, "Hello");
+
+ JS_GC(cx);
+ JS_GC(cx);
+
+ JS::RootedObject obj(cx, container.obj());
+ JS::RootedValue val(cx, StringValue(container.str()));
+ CHECK(JS_SetProperty(cx, obj, "foo", val));
+ obj = nullptr;
+ val = UndefinedValue();
+
+ {
+ JS::RootedString actual(cx);
+ bool same;
+
+ // Automatic move from stack to heap.
+ JS::PersistentRooted<MyContainer> heap(cx, container);
+
+ // Copyable types in place.
+ JS::PersistentRooted<MyContainer> cp1(cx);
+ JS::PersistentRooted<MyContainer> cp2(cx, 7.8);
+ JS::PersistentRooted<MyContainer> cp3(cx, cx);
+ JS::PersistentRooted<MyContainer> cp4(cx, cx, cx, cx);
+
+ CHECK_EQUAL(cp1.constructor(), 1); // direct SafelyInitialized<T>
+ CHECK_EQUAL(cp2.constructor(), 2); // direct MyContainer(double)
+ CHECK_EQUAL(cp3.constructor(), 3); // direct MyContainer(cx)
+ CHECK_EQUAL(cp4.constructor(), 4); // direct MyContainer(cx, cx, cx)
+
+ // Construct uncopyable type in place.
+ JS::PersistentRooted<MyNonCopyableContainer> ncp1(cx);
+ JS::PersistentRooted<MyNonCopyableContainer> ncp2(cx, 7.8);
+
+ // We're not just using a 1-arg constructor, right?
+ JS::PersistentRooted<MyNonCopyableContainer> ncp3(cx, cx);
+ JS::PersistentRooted<MyNonCopyableContainer> ncp4(cx, cx, cx, cx);
+
+ CHECK_EQUAL(ncp1.constructor(), 1); // direct SafelyInitialized<T>
+ CHECK_EQUAL(ncp2.constructor(), 2); // direct Ctor(double)
+ CHECK_EQUAL(ncp3.constructor(), 3); // direct Ctor(cx)
+ CHECK_EQUAL(ncp4.constructor(), 4); // direct Ctor(cx, cx, cx)
+
+ // clear prior rooting.
+ container.obj() = nullptr;
+ container.str() = nullptr;
+
+ obj = heap.obj();
+ CHECK(JS_GetProperty(cx, obj, "foo", &val));
+ actual = val.toString();
+ CHECK(JS_StringEqualsLiteral(cx, actual, "Hello", &same));
+ CHECK(same);
+ obj = nullptr;
+ actual = nullptr;
+
+ JS_GC(cx);
+ JS_GC(cx);
+
+ obj = heap.obj();
+ CHECK(JS_GetProperty(cx, obj, "foo", &val));
+ actual = val.toString();
+ CHECK(JS_StringEqualsLiteral(cx, actual, "Hello", &same));
+ CHECK(same);
+ obj = nullptr;
+ actual = nullptr;
+ }
+
+ return true;
+}
+END_TEST(testGCRootedStaticStructInternalStackStorageAugmented)
+
+static JS::PersistentRooted<JSObject*> sLongLived;
+BEGIN_TEST(testGCPersistentRootedOutlivesRuntime) {
+ sLongLived.init(cx, JS_NewObject(cx, nullptr));
+ CHECK(sLongLived);
+ return true;
+}
+END_TEST(testGCPersistentRootedOutlivesRuntime)
+
+// Unlike the above, the following test is an example of an invalid usage: for
+// performance and simplicity reasons, PersistentRooted<Traceable> is not
+// allowed to outlive the container it belongs to. The following commented out
+// test can be used to verify that the relevant assertion fires as expected.
+static JS::PersistentRooted<MyContainer> sContainer;
+BEGIN_TEST(testGCPersistentRootedTraceableCannotOutliveRuntime) {
+ JS::Rooted<MyContainer> container(cx);
+ container.obj() = JS_NewObject(cx, nullptr);
+ container.str() = JS_NewStringCopyZ(cx, "Hello");
+ sContainer.init(cx, container);
+
+ // Commenting the following line will trigger an assertion that the
+ // PersistentRooted outlives the runtime it is attached to.
+ sContainer.reset();
+
+ return true;
+}
+END_TEST(testGCPersistentRootedTraceableCannotOutliveRuntime)
+
+using MyHashMap = js::GCHashMap<js::Shape*, JSObject*>;
+
+BEGIN_TEST(testGCRootedHashMap) {
+ JS::Rooted<MyHashMap> map(cx, MyHashMap(cx, 15));
+
+ for (size_t i = 0; i < 10; ++i) {
+ RootedObject obj(cx, JS_NewObject(cx, nullptr));
+ RootedValue val(cx, UndefinedValue());
+ // Construct a unique property name to ensure that the object creates a
+ // new shape.
+ char buffer[2];
+ buffer[0] = 'a' + i;
+ buffer[1] = '\0';
+ CHECK(JS_SetProperty(cx, obj, buffer, val));
+ CHECK(map.putNew(obj->shape(), obj));
+ }
+
+ JS_GC(cx);
+ JS_GC(cx);
+
+ for (auto r = map.all(); !r.empty(); r.popFront()) {
+ RootedObject obj(cx, r.front().value());
+ CHECK(obj->shape() == r.front().key());
+ }
+
+ return true;
+}
+END_TEST(testGCRootedHashMap)
+
+// Repeat of the test above, but without rooting. This is a rooting hazard. The
+// JS_EXPECT_HAZARDS annotation will cause the hazard taskcluster job to fail
+// if the hazard below is *not* detected.
+BEGIN_TEST_WITH_ATTRIBUTES(testUnrootedGCHashMap, JS_EXPECT_HAZARDS) {
+ MyHashMap map(cx, 15);
+
+ for (size_t i = 0; i < 10; ++i) {
+ RootedObject obj(cx, JS_NewObject(cx, nullptr));
+ RootedValue val(cx, UndefinedValue());
+ // Construct a unique property name to ensure that the object creates a
+ // new shape.
+ char buffer[2];
+ buffer[0] = 'a' + i;
+ buffer[1] = '\0';
+ CHECK(JS_SetProperty(cx, obj, buffer, val));
+ CHECK(map.putNew(obj->shape(), obj));
+ }
+
+ JS_GC(cx);
+
+ // Access map to keep it live across the GC.
+ CHECK(map.count() == 10);
+
+ return true;
+}
+END_TEST(testUnrootedGCHashMap)
+
+BEGIN_TEST(testSafelyUnrootedGCHashMap) {
+ // This is not rooted, but it doesn't use GC pointers as keys or values so
+ // it's ok.
+ js::GCHashMap<uint64_t, uint64_t> map(cx, 15);
+
+ JS_GC(cx);
+ CHECK(map.putNew(12, 13));
+
+ return true;
+}
+END_TEST(testSafelyUnrootedGCHashMap)
+
+static bool FillMyHashMap(JSContext* cx, MutableHandle<MyHashMap> map) {
+ for (size_t i = 0; i < 10; ++i) {
+ RootedObject obj(cx, JS_NewObject(cx, nullptr));
+ RootedValue val(cx, UndefinedValue());
+ // Construct a unique property name to ensure that the object creates a
+ // new shape.
+ char buffer[2];
+ buffer[0] = 'a' + i;
+ buffer[1] = '\0';
+ if (!JS_SetProperty(cx, obj, buffer, val)) {
+ return false;
+ }
+ if (!map.putNew(obj->shape(), obj)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool CheckMyHashMap(JSContext* cx, Handle<MyHashMap> map) {
+ for (auto r = map.all(); !r.empty(); r.popFront()) {
+ RootedObject obj(cx, r.front().value());
+ if (obj->shape() != r.front().key()) {
+ return false;
+ }
+ }
+ return true;
+}
+
+BEGIN_TEST(testGCHandleHashMap) {
+ JS::Rooted<MyHashMap> map(cx, MyHashMap(cx, 15));
+
+ CHECK(FillMyHashMap(cx, &map));
+
+ JS_GC(cx);
+ JS_GC(cx);
+
+ CHECK(CheckMyHashMap(cx, map));
+
+ return true;
+}
+END_TEST(testGCHandleHashMap)
+
+using ShapeVec = GCVector<NativeShape*>;
+
+BEGIN_TEST(testGCRootedVector) {
+ JS::Rooted<ShapeVec> shapes(cx, cx);
+
+ for (size_t i = 0; i < 10; ++i) {
+ RootedObject obj(cx, JS_NewObject(cx, nullptr));
+ RootedValue val(cx, UndefinedValue());
+ // Construct a unique property name to ensure that the object creates a
+ // new shape.
+ char buffer[2];
+ buffer[0] = 'a' + i;
+ buffer[1] = '\0';
+ CHECK(JS_SetProperty(cx, obj, buffer, val));
+ CHECK(shapes.append(obj->as<NativeObject>().shape()));
+ }
+
+ JS_GC(cx);
+ JS_GC(cx);
+
+ for (size_t i = 0; i < 10; ++i) {
+ // Check the shape to ensure it did not get collected.
+ char letter = 'a' + i;
+ bool match;
+ ShapePropertyIter<NoGC> iter(shapes[i]);
+ CHECK(JS_StringEqualsAscii(cx, iter->key().toString(), &letter, 1, &match));
+ CHECK(match);
+ }
+
+ // Ensure iterator enumeration works through the rooted.
+ for (auto shape : shapes) {
+ CHECK(shape);
+ }
+
+ CHECK(receiveConstRefToShapeVector(shapes));
+
+ // Ensure rooted converts to handles.
+ CHECK(receiveHandleToShapeVector(shapes));
+ CHECK(receiveMutableHandleToShapeVector(&shapes));
+
+ return true;
+}
+
+bool receiveConstRefToShapeVector(
+ const JS::Rooted<GCVector<NativeShape*>>& rooted) {
+ // Ensure range enumeration works through the reference.
+ for (auto shape : rooted) {
+ CHECK(shape);
+ }
+ return true;
+}
+
+bool receiveHandleToShapeVector(JS::Handle<GCVector<NativeShape*>> handle) {
+ // Ensure range enumeration works through the handle.
+ for (auto shape : handle) {
+ CHECK(shape);
+ }
+ return true;
+}
+
+bool receiveMutableHandleToShapeVector(
+ JS::MutableHandle<GCVector<NativeShape*>> handle) {
+ // Ensure range enumeration works through the handle.
+ for (auto shape : handle) {
+ CHECK(shape);
+ }
+ return true;
+}
+END_TEST(testGCRootedVector)
+
+BEGIN_TEST(testTraceableFifo) {
+ using ShapeFifo = TraceableFifo<NativeShape*>;
+ JS::Rooted<ShapeFifo> shapes(cx, ShapeFifo(cx));
+ CHECK(shapes.empty());
+
+ for (size_t i = 0; i < 10; ++i) {
+ RootedObject obj(cx, JS_NewObject(cx, nullptr));
+ RootedValue val(cx, UndefinedValue());
+ // Construct a unique property name to ensure that the object creates a
+ // new shape.
+ char buffer[2];
+ buffer[0] = 'a' + i;
+ buffer[1] = '\0';
+ CHECK(JS_SetProperty(cx, obj, buffer, val));
+ CHECK(shapes.pushBack(obj->as<NativeObject>().shape()));
+ }
+
+ CHECK(shapes.length() == 10);
+
+ JS_GC(cx);
+ JS_GC(cx);
+
+ for (size_t i = 0; i < 10; ++i) {
+ // Check the shape to ensure it did not get collected.
+ char letter = 'a' + i;
+ bool match;
+ ShapePropertyIter<NoGC> iter(shapes.front());
+ CHECK(JS_StringEqualsAscii(cx, iter->key().toString(), &letter, 1, &match));
+ CHECK(match);
+ shapes.popFront();
+ }
+
+ CHECK(shapes.empty());
+ return true;
+}
+END_TEST(testTraceableFifo)
+
+using ShapeVec = GCVector<NativeShape*>;
+
+static bool FillVector(JSContext* cx, MutableHandle<ShapeVec> shapes) {
+ for (size_t i = 0; i < 10; ++i) {
+ RootedObject obj(cx, JS_NewObject(cx, nullptr));
+ RootedValue val(cx, UndefinedValue());
+ // Construct a unique property name to ensure that the object creates a
+ // new shape.
+ char buffer[2];
+ buffer[0] = 'a' + i;
+ buffer[1] = '\0';
+ if (!JS_SetProperty(cx, obj, buffer, val)) {
+ return false;
+ }
+ if (!shapes.append(obj->as<NativeObject>().shape())) {
+ return false;
+ }
+ }
+
+ // Ensure iterator enumeration works through the mutable handle.
+ for (auto shape : shapes) {
+ if (!shape) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool CheckVector(JSContext* cx, Handle<ShapeVec> shapes) {
+ for (size_t i = 0; i < 10; ++i) {
+ // Check the shape to ensure it did not get collected.
+ char letter = 'a' + i;
+ bool match;
+ ShapePropertyIter<NoGC> iter(shapes[i]);
+ if (!JS_StringEqualsAscii(cx, iter->key().toString(), &letter, 1, &match)) {
+ return false;
+ }
+ if (!match) {
+ return false;
+ }
+ }
+
+ // Ensure iterator enumeration works through the handle.
+ for (auto shape : shapes) {
+ if (!shape) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+BEGIN_TEST(testGCHandleVector) {
+ JS::Rooted<ShapeVec> vec(cx, ShapeVec(cx));
+
+ CHECK(FillVector(cx, &vec));
+
+ JS_GC(cx);
+ JS_GC(cx);
+
+ CHECK(CheckVector(cx, vec));
+
+ return true;
+}
+END_TEST(testGCHandleVector)
+
+class Foo {
+ public:
+ Foo(int, int) {}
+ void trace(JSTracer*) {}
+};
+
+using FooVector = JS::GCVector<Foo>;
+
+BEGIN_TEST(testGCVectorEmplaceBack) {
+ JS::Rooted<FooVector> vector(cx, FooVector(cx));
+
+ CHECK(vector.emplaceBack(1, 2));
+
+ return true;
+}
+END_TEST(testGCVectorEmplaceBack)
+
+BEGIN_TEST(testRootedMaybeValue) {
+ JS::Rooted<Maybe<Value>> maybeNothing(cx);
+ CHECK(maybeNothing.isNothing());
+ CHECK(!maybeNothing.isSome());
+
+ JS::Rooted<Maybe<Value>> maybe(cx, Some(UndefinedValue()));
+ CHECK(CheckConstOperations<Rooted<Maybe<Value>>&>(maybe));
+ CHECK(CheckConstOperations<Handle<Maybe<Value>>>(maybe));
+ CHECK(CheckConstOperations<MutableHandle<Maybe<Value>>>(&maybe));
+
+ maybe = Some(JS::TrueValue());
+ CHECK(CheckMutableOperations<Rooted<Maybe<Value>>&>(maybe));
+
+ maybe = Some(JS::TrueValue());
+ CHECK(CheckMutableOperations<MutableHandle<Maybe<Value>>>(&maybe));
+
+ CHECK(JS::NothingHandleValue.isNothing());
+
+ return true;
+}
+
+template <typename T>
+bool CheckConstOperations(T someUndefinedValue) {
+ CHECK(someUndefinedValue.isSome());
+ CHECK(someUndefinedValue.value().isUndefined());
+ CHECK(someUndefinedValue->isUndefined());
+ CHECK((*someUndefinedValue).isUndefined());
+ return true;
+}
+
+template <typename T>
+bool CheckMutableOperations(T maybe) {
+ CHECK(maybe->isTrue());
+
+ *maybe = JS::FalseValue();
+ CHECK(maybe->isFalse());
+
+ maybe.reset();
+ CHECK(maybe.isNothing());
+
+ return true;
+}
+
+END_TEST(testRootedMaybeValue)
+
+struct TestErr {};
+struct OtherTestErr {};
+
+struct SimpleTraceable {
+ // I'm using plain objects rather than Heap<T> because Heap<T> would get
+ // traced via the store buffer. Heap<T> would be a more realistic example,
+ // but would require compaction to test for tracing.
+ JSObject* obj;
+ JS::Value val;
+
+ void trace(JSTracer* trc) {
+ TraceRoot(trc, &obj, "obj");
+ TraceRoot(trc, &val, "val");
+ }
+};
+
+namespace JS {
+template <>
+struct GCPolicy<TestErr> : public IgnoreGCPolicy<TestErr> {};
+} // namespace JS
+
+BEGIN_TEST_WITH_ATTRIBUTES(testGCRootedResult, JS_EXPECT_HAZARDS) {
+ AutoLeaveZeal noZeal(cx);
+
+ JSObject* unrootedObj = JS_NewPlainObject(cx);
+ CHECK(js::gc::IsInsideNursery(unrootedObj));
+ Value unrootedVal = ObjectValue(*unrootedObj);
+
+ RootedObject obj(cx, unrootedObj);
+ RootedValue val(cx, unrootedVal);
+
+ Result<Value, TestErr> unrootedValerr(val);
+ Rooted<Result<Value, TestErr>> valerr(cx, val);
+
+ Result<mozilla::Ok, Value> unrootedOkval(val);
+ Rooted<Result<mozilla::Ok, Value>> okval(cx, val);
+
+ Result<mozilla::Ok, TestErr> simple{mozilla::Ok()};
+
+ Result<Value, JSObject*> unrootedValobj1(val);
+ Rooted<Result<Value, JSObject*>> valobj1(cx, val);
+ Result<Value, JSObject*> unrootedValobj2(obj);
+ Rooted<Result<Value, JSObject*>> valobj2(cx, obj);
+
+ // Test nested traceable structures.
+ Result<mozilla::Maybe<mozilla::Ok>, JSObject*> maybeobj(
+ mozilla::Some(mozilla::Ok()));
+ Rooted<Result<mozilla::Maybe<mozilla::Ok>, JSObject*>> rooted_maybeobj(
+ cx, mozilla::Some(mozilla::Ok()));
+
+ // This would fail to compile because Result<> deletes its copy constructor,
+ // which prevents updating after tracing:
+ //
+ // Rooted<Result<Result<mozilla::Ok, JS::Value>, JSObject*>>
+
+ // But this should be fine when no tracing is required.
+ Result<Result<mozilla::Ok, int>, double> dummy(3.4);
+
+ // One thing I didn't realize initially about Result<>: unwrap() takes
+ // ownership of a value. In the case of Result<Maybe>, that means the
+ // contained Maybe is reset to Nothing.
+ Result<mozilla::Maybe<int>, int> confusing(mozilla::Some(7));
+ CHECK(confusing.unwrap().isSome());
+ CHECK(!confusing.unwrap().isSome());
+
+ Result<mozilla::Maybe<JS::Value>, JSObject*> maybevalobj(
+ mozilla::Some(val.get()));
+ Rooted<Result<mozilla::Maybe<JS::Value>, JSObject*>> rooted_maybevalobj(
+ cx, mozilla::Some(val.get()));
+
+ // Custom types that haven't had GCPolicy explicitly specialized.
+ SimpleTraceable s1{obj, val};
+ Result<SimpleTraceable, TestErr> custom(s1);
+ SimpleTraceable s2{obj, val};
+ Rooted<Result<SimpleTraceable, TestErr>> rootedCustom(cx, s2);
+
+ CHECK(obj == unrootedObj);
+ CHECK(val == unrootedVal);
+ CHECK(simple.isOk());
+ CHECK(unrootedValerr.inspect() == unrootedVal);
+ CHECK(valerr.get().inspect() == val);
+ CHECK(unrootedOkval.inspectErr() == unrootedVal);
+ CHECK(okval.get().inspectErr() == val);
+ CHECK(unrootedValobj1.inspect() == unrootedVal);
+ CHECK(valobj1.get().inspect() == val);
+ CHECK(unrootedValobj2.inspectErr() == unrootedObj);
+ CHECK(valobj2.get().inspectErr() == obj);
+ CHECK(*maybevalobj.inspect() == unrootedVal);
+ CHECK(*rooted_maybevalobj.get().inspect() == val);
+ CHECK(custom.inspect().obj == unrootedObj);
+ CHECK(custom.inspect().val == unrootedVal);
+ CHECK(rootedCustom.get().inspect().obj == obj);
+ CHECK(rootedCustom.get().inspect().val == val);
+
+ JS_GC(cx);
+
+ CHECK(obj != unrootedObj);
+ CHECK(val != unrootedVal);
+ CHECK(unrootedValerr.inspect() == unrootedVal);
+ CHECK(valerr.get().inspect() == val);
+ CHECK(unrootedOkval.inspectErr() == unrootedVal);
+ CHECK(okval.get().inspectErr() == val);
+ CHECK(unrootedValobj1.inspect() == unrootedVal);
+ CHECK(valobj1.get().inspect() == val);
+ CHECK(unrootedValobj2.inspectErr() == unrootedObj);
+ CHECK(valobj2.get().inspectErr() == obj);
+ CHECK(*maybevalobj.inspect() == unrootedVal);
+ CHECK(*rooted_maybevalobj.get().inspect() == val);
+ MOZ_ASSERT(custom.inspect().obj == unrootedObj);
+ CHECK(custom.inspect().obj == unrootedObj);
+ CHECK(custom.inspect().val == unrootedVal);
+ CHECK(rootedCustom.get().inspect().obj == obj);
+ CHECK(rootedCustom.get().inspect().val == val);
+
+ mozilla::Result<OtherTestErr, mozilla::Ok> r(mozilla::Ok{});
+ (void)r;
+
+ return true;
+}
+END_TEST(testGCRootedResult)
+
+static int copies = 0;
+
+struct DontCopyMe_Variant {
+ JSObject* obj;
+ explicit DontCopyMe_Variant(JSObject* objArg) : obj(objArg) {}
+ DontCopyMe_Variant(const DontCopyMe_Variant& other) : obj(other.obj) {
+ copies++;
+ }
+ DontCopyMe_Variant(DontCopyMe_Variant&& other) : obj(std::move(other.obj)) {
+ other.obj = nullptr;
+ }
+ void trace(JSTracer* trc) { TraceRoot(trc, &obj, "obj"); }
+};
+
+enum struct TestUnusedZeroEnum : int16_t { Ok = 0, NotOk = 1 };
+
+namespace mozilla::detail {
+template <>
+struct UnusedZero<TestUnusedZeroEnum> : UnusedZeroEnum<TestUnusedZeroEnum> {};
+} // namespace mozilla::detail
+
+namespace JS {
+template <>
+struct GCPolicy<TestUnusedZeroEnum>
+ : public IgnoreGCPolicy<TestUnusedZeroEnum> {};
+} // namespace JS
+
+struct DontCopyMe_NullIsOk {
+ JS::Value val;
+ DontCopyMe_NullIsOk() : val(UndefinedValue()) {}
+ explicit DontCopyMe_NullIsOk(const JS::Value& valArg) : val(valArg) {}
+ DontCopyMe_NullIsOk(const DontCopyMe_NullIsOk& other) = delete;
+ DontCopyMe_NullIsOk(DontCopyMe_NullIsOk&& other)
+ : val(std::move(other.val)) {}
+ DontCopyMe_NullIsOk& operator=(DontCopyMe_NullIsOk&& other) {
+ val = std::move(other.val);
+ other.val = UndefinedValue();
+ return *this;
+ }
+ void trace(JSTracer* trc) { TraceRoot(trc, &val, "val"); }
+};
+
+struct Failed {};
+
+namespace mozilla::detail {
+template <>
+struct UnusedZero<Failed> {
+ using StorageType = uintptr_t;
+
+ static constexpr bool value = true;
+ static constexpr StorageType nullValue = 0;
+ static constexpr StorageType GetDefaultValue() { return 2; }
+
+ static constexpr void AssertValid(StorageType aValue) {}
+ static constexpr Failed Inspect(const StorageType& aValue) {
+ return Failed{};
+ }
+ static constexpr Failed Unwrap(StorageType aValue) { return Failed{}; }
+ static constexpr StorageType Store(Failed aValue) {
+ return GetDefaultValue();
+ }
+};
+} // namespace mozilla::detail
+
+namespace JS {
+template <>
+struct GCPolicy<Failed> : public IgnoreGCPolicy<Failed> {};
+} // namespace JS
+
+struct TriviallyCopyable_LowBitTagIsError {
+ JSObject* obj;
+ TriviallyCopyable_LowBitTagIsError() : obj(nullptr) {}
+ explicit TriviallyCopyable_LowBitTagIsError(JSObject* objArg) : obj(objArg) {}
+ TriviallyCopyable_LowBitTagIsError(
+ const TriviallyCopyable_LowBitTagIsError& other) = default;
+ void trace(JSTracer* trc) { TraceRoot(trc, &obj, "obj"); }
+};
+
+namespace mozilla::detail {
+template <>
+struct HasFreeLSB<TriviallyCopyable_LowBitTagIsError> : HasFreeLSB<JSObject*> {
+};
+} // namespace mozilla::detail
+
+BEGIN_TEST_WITH_ATTRIBUTES(testRootedResultCtors, JS_EXPECT_HAZARDS) {
+ JSObject* unrootedObj = JS_NewPlainObject(cx);
+ CHECK(unrootedObj);
+ Rooted<JSObject*> obj(cx, unrootedObj);
+
+ using mozilla::detail::PackingStrategy;
+
+ static_assert(Result<DontCopyMe_Variant, TestErr>::Strategy ==
+ PackingStrategy::Variant);
+ Rooted<Result<DontCopyMe_Variant, TestErr>> vv(cx, DontCopyMe_Variant{obj});
+ static_assert(Result<mozilla::Ok, DontCopyMe_Variant>::Strategy ==
+ PackingStrategy::Variant);
+ Rooted<Result<mozilla::Ok, DontCopyMe_Variant>> ve(cx,
+ DontCopyMe_Variant{obj});
+
+ static_assert(Result<DontCopyMe_NullIsOk, TestUnusedZeroEnum>::Strategy ==
+ PackingStrategy::NullIsOk);
+ Rooted<Result<DontCopyMe_NullIsOk, TestUnusedZeroEnum>> nv(
+ cx, DontCopyMe_NullIsOk{JS::ObjectValue(*obj)});
+
+ static_assert(Result<TriviallyCopyable_LowBitTagIsError, Failed>::Strategy ==
+ PackingStrategy::LowBitTagIsError);
+ Rooted<Result<TriviallyCopyable_LowBitTagIsError, Failed>> lv(
+ cx, TriviallyCopyable_LowBitTagIsError{obj});
+
+ CHECK(obj == unrootedObj);
+
+ CHECK(vv.get().inspect().obj == obj);
+ CHECK(ve.get().inspectErr().obj == obj);
+ CHECK(nv.get().inspect().val.toObjectOrNull() == obj);
+ CHECK(lv.get().inspect().obj == obj);
+
+ JS_GC(cx);
+ CHECK(obj != unrootedObj);
+
+ CHECK(vv.get().inspect().obj == obj);
+ CHECK(ve.get().inspectErr().obj == obj);
+ CHECK(nv.get().inspect().val.toObjectOrNull() == obj);
+ CHECK(lv.get().inspect().obj == obj);
+ CHECK(copies == 0);
+ return true;
+}
+END_TEST(testRootedResultCtors)
+
+#if defined(HAVE_64BIT_BUILD) && !defined(XP_WIN)
+
+// This depends on a pointer fitting in 48 bits, leaving space for an empty
+// struct and a bool in a packed struct. Windows doesn't seem to do this
+// packing, so we'll skip this test here. We're primarily checking whether
+// copy constructors get called, which should be cross-platform, and
+// secondarily making sure that the Rooted/tracing stuff is compiled and
+// executed properly. There are certainly more clever ways to do this that
+// would work cross-platform, but it doesn't seem worth the bother right now.
+
+struct __attribute__((packed)) DontCopyMe_PackedVariant {
+ uintptr_t obj : 48;
+ static JSObject* Unwrap(uintptr_t packed) {
+ return reinterpret_cast<JSObject*>(packed);
+ }
+ static uintptr_t Store(JSObject* obj) {
+ return reinterpret_cast<uintptr_t>(obj);
+ }
+
+ DontCopyMe_PackedVariant() : obj(0) {}
+ explicit DontCopyMe_PackedVariant(JSObject* objArg)
+ : obj(reinterpret_cast<uintptr_t>(objArg)) {}
+ DontCopyMe_PackedVariant(const DontCopyMe_PackedVariant& other)
+ : obj(other.obj) {
+ copies++;
+ }
+ DontCopyMe_PackedVariant(DontCopyMe_PackedVariant&& other) : obj(other.obj) {
+ other.obj = 0;
+ }
+ void trace(JSTracer* trc) {
+ JSObject* realObj = Unwrap(obj);
+ TraceRoot(trc, &realObj, "obj");
+ obj = Store(realObj);
+ }
+};
+
+static_assert(std::is_default_constructible_v<DontCopyMe_PackedVariant>);
+static_assert(std::is_default_constructible_v<TestErr>);
+static_assert(mozilla::detail::IsPackableVariant<DontCopyMe_PackedVariant,
+ TestErr>::value);
+
+BEGIN_TEST_WITH_ATTRIBUTES(testResultPackedVariant, JS_EXPECT_HAZARDS) {
+ JSObject* unrootedObj = JS_NewPlainObject(cx);
+ CHECK(unrootedObj);
+ Rooted<JSObject*> obj(cx, unrootedObj);
+
+ using mozilla::detail::PackingStrategy;
+
+ static_assert(Result<DontCopyMe_PackedVariant, TestErr>::Strategy ==
+ PackingStrategy::PackedVariant);
+ Rooted<Result<DontCopyMe_PackedVariant, TestErr>> pv(
+ cx, DontCopyMe_PackedVariant{obj});
+ static_assert(Result<mozilla::Ok, DontCopyMe_PackedVariant>::Strategy ==
+ PackingStrategy::PackedVariant);
+ Rooted<Result<mozilla::Ok, DontCopyMe_PackedVariant>> pe(
+ cx, DontCopyMe_PackedVariant{obj});
+
+ CHECK(obj == unrootedObj);
+
+ CHECK(DontCopyMe_PackedVariant::Unwrap(pv.get().inspect().obj) == obj);
+ CHECK(DontCopyMe_PackedVariant::Unwrap(pe.get().inspectErr().obj) == obj);
+
+ JS_GC(cx);
+ CHECK(obj != unrootedObj);
+
+ CHECK(DontCopyMe_PackedVariant::Unwrap(pv.get().inspect().obj) == obj);
+ CHECK(DontCopyMe_PackedVariant::Unwrap(pe.get().inspectErr().obj) == obj);
+
+ return true;
+}
+END_TEST(testResultPackedVariant)
+
+#endif // HAVE_64BIT_BUILD
diff --git a/js/src/jsapi-tests/testGCFinalizeCallback.cpp b/js/src/jsapi-tests/testGCFinalizeCallback.cpp
new file mode 100644
index 0000000000..290356c421
--- /dev/null
+++ b/js/src/jsapi-tests/testGCFinalizeCallback.cpp
@@ -0,0 +1,202 @@
+/* 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 "js/GlobalObject.h"
+#include "jsapi-tests/tests.h"
+
+using namespace js;
+
+static const unsigned BufSize = 20;
+static unsigned FinalizeCalls = 0;
+static JSFinalizeStatus StatusBuffer[BufSize];
+
+BEGIN_TEST(testGCFinalizeCallback) {
+ AutoGCParameter param1(cx, JSGC_INCREMENTAL_GC_ENABLED, true);
+ AutoGCParameter param2(cx, JSGC_PER_ZONE_GC_ENABLED, true);
+
+ /* Full GC, non-incremental. */
+ FinalizeCalls = 0;
+ JS_GC(cx);
+ CHECK(cx->runtime()->gc.isFullGc());
+ CHECK(checkSingleGroup());
+ CHECK(checkFinalizeStatus());
+
+ /* Full GC, incremental. */
+ FinalizeCalls = 0;
+ JS::PrepareForFullGC(cx);
+ SliceBudget startBudget(TimeBudget(1000000));
+ JS::StartIncrementalGC(cx, JS::GCOptions::Normal, JS::GCReason::API,
+ startBudget);
+ while (cx->runtime()->gc.isIncrementalGCInProgress()) {
+ JS::PrepareForFullGC(cx);
+ SliceBudget budget(TimeBudget(1000000));
+ JS::IncrementalGCSlice(cx, JS::GCReason::API, budget);
+ }
+ CHECK(!cx->runtime()->gc.isIncrementalGCInProgress());
+ CHECK(cx->runtime()->gc.isFullGc());
+ CHECK(checkMultipleGroups());
+ CHECK(checkFinalizeStatus());
+
+#ifdef JS_GC_ZEAL
+ // Bug 1377593 - the below tests want to control how many zones are GC'ing,
+ // and some zeal modes will convert them into all-zones GCs.
+ JS_SetGCZeal(cx, 0, 0);
+#endif
+
+ JS::RootedObject global1(cx, createTestGlobal());
+ JS::RootedObject global2(cx, createTestGlobal());
+ JS::RootedObject global3(cx, createTestGlobal());
+ CHECK(global1);
+ CHECK(global2);
+ CHECK(global3);
+
+ /* Zone GC, non-incremental, single zone. */
+ FinalizeCalls = 0;
+ JS::PrepareZoneForGC(cx, global1->zone());
+ JS::NonIncrementalGC(cx, JS::GCOptions::Normal, JS::GCReason::API);
+ CHECK(!cx->runtime()->gc.isFullGc());
+ CHECK(checkSingleGroup());
+ CHECK(checkFinalizeStatus());
+
+ /* Zone GC, non-incremental, multiple zones. */
+ FinalizeCalls = 0;
+ JS::PrepareZoneForGC(cx, global1->zone());
+ JS::PrepareZoneForGC(cx, global2->zone());
+ JS::PrepareZoneForGC(cx, global3->zone());
+ JS::NonIncrementalGC(cx, JS::GCOptions::Normal, JS::GCReason::API);
+ CHECK(!cx->runtime()->gc.isFullGc());
+ CHECK(checkSingleGroup());
+ CHECK(checkFinalizeStatus());
+
+ /* Zone GC, incremental, single zone. */
+ FinalizeCalls = 0;
+ JS::PrepareZoneForGC(cx, global1->zone());
+ SliceBudget budget(TimeBudget(1000000));
+ JS::StartIncrementalGC(cx, JS::GCOptions::Normal, JS::GCReason::API, budget);
+ while (cx->runtime()->gc.isIncrementalGCInProgress()) {
+ JS::PrepareZoneForGC(cx, global1->zone());
+ budget = SliceBudget(TimeBudget(1000000));
+ JS::IncrementalGCSlice(cx, JS::GCReason::API, budget);
+ }
+ CHECK(!cx->runtime()->gc.isIncrementalGCInProgress());
+ CHECK(!cx->runtime()->gc.isFullGc());
+ CHECK(checkSingleGroup());
+ CHECK(checkFinalizeStatus());
+
+ /* Zone GC, incremental, multiple zones. */
+ FinalizeCalls = 0;
+ JS::PrepareZoneForGC(cx, global1->zone());
+ JS::PrepareZoneForGC(cx, global2->zone());
+ JS::PrepareZoneForGC(cx, global3->zone());
+ budget = SliceBudget(TimeBudget(1000000));
+ JS::StartIncrementalGC(cx, JS::GCOptions::Normal, JS::GCReason::API, budget);
+ while (cx->runtime()->gc.isIncrementalGCInProgress()) {
+ JS::PrepareZoneForGC(cx, global1->zone());
+ JS::PrepareZoneForGC(cx, global2->zone());
+ JS::PrepareZoneForGC(cx, global3->zone());
+ budget = SliceBudget(TimeBudget(1000000));
+ JS::IncrementalGCSlice(cx, JS::GCReason::API, budget);
+ }
+ CHECK(!cx->runtime()->gc.isIncrementalGCInProgress());
+ CHECK(!cx->runtime()->gc.isFullGc());
+ CHECK(checkMultipleGroups());
+ CHECK(checkFinalizeStatus());
+
+#ifdef JS_GC_ZEAL
+
+ /* Full GC with reset due to new zone, becoming zone GC. */
+
+ FinalizeCalls = 0;
+ JS_SetGCZeal(cx, 9, 1000000);
+ JS::PrepareForFullGC(cx);
+ budget = SliceBudget(WorkBudget(1));
+ cx->runtime()->gc.startDebugGC(JS::GCOptions::Normal, budget);
+ CHECK(cx->runtime()->gc.state() == gc::State::Mark);
+ CHECK(cx->runtime()->gc.isFullGc());
+
+ JS::RootedObject global4(cx, createTestGlobal());
+ budget = SliceBudget(WorkBudget(1));
+ cx->runtime()->gc.debugGCSlice(budget);
+ while (cx->runtime()->gc.isIncrementalGCInProgress()) {
+ cx->runtime()->gc.debugGCSlice(budget);
+ }
+ CHECK(!cx->runtime()->gc.isIncrementalGCInProgress());
+ CHECK(checkSingleGroup());
+ CHECK(checkFinalizeStatus());
+
+ JS_SetGCZeal(cx, 0, 0);
+
+#endif
+
+ /*
+ * Make some use of the globals here to ensure the compiler doesn't optimize
+ * them away in release builds, causing the zones to be collected and
+ * the test to fail.
+ */
+ CHECK(JS_IsGlobalObject(global1));
+ CHECK(JS_IsGlobalObject(global2));
+ CHECK(JS_IsGlobalObject(global3));
+
+ return true;
+}
+
+JSObject* createTestGlobal() {
+ JS::RealmOptions options;
+ return JS_NewGlobalObject(cx, getGlobalClass(), nullptr,
+ JS::FireOnNewGlobalHook, options);
+}
+
+virtual bool init() override {
+ if (!JSAPIRuntimeTest::init()) {
+ return false;
+ }
+
+ JS_AddFinalizeCallback(cx, FinalizeCallback, nullptr);
+ return true;
+}
+
+virtual void uninit() override {
+ JS_RemoveFinalizeCallback(cx, FinalizeCallback);
+ JSAPIRuntimeTest::uninit();
+}
+
+bool checkSingleGroup() {
+ CHECK(FinalizeCalls < BufSize);
+ CHECK(FinalizeCalls == 4);
+ return true;
+}
+
+bool checkMultipleGroups() {
+ CHECK(FinalizeCalls < BufSize);
+ CHECK(FinalizeCalls % 3 == 1);
+ CHECK((FinalizeCalls - 1) / 3 > 1);
+ return true;
+}
+
+bool checkFinalizeStatus() {
+ /*
+ * The finalize callback should be called twice for each sweep group
+ * finalized, with status JSFINALIZE_GROUP_START and JSFINALIZE_GROUP_END,
+ * and then once more with JSFINALIZE_COLLECTION_END.
+ */
+
+ for (unsigned i = 0; i < FinalizeCalls - 1; i += 3) {
+ CHECK(StatusBuffer[i] == JSFINALIZE_GROUP_PREPARE);
+ CHECK(StatusBuffer[i + 1] == JSFINALIZE_GROUP_START);
+ CHECK(StatusBuffer[i + 2] == JSFINALIZE_GROUP_END);
+ }
+
+ CHECK(StatusBuffer[FinalizeCalls - 1] == JSFINALIZE_COLLECTION_END);
+
+ return true;
+}
+
+static void FinalizeCallback(JS::GCContext* gcx, JSFinalizeStatus status,
+ void* data) {
+ if (FinalizeCalls < BufSize) {
+ StatusBuffer[FinalizeCalls] = status;
+ }
+ ++FinalizeCalls;
+}
+END_TEST(testGCFinalizeCallback)
diff --git a/js/src/jsapi-tests/testGCGrayMarking.cpp b/js/src/jsapi-tests/testGCGrayMarking.cpp
new file mode 100644
index 0000000000..6e9f6d83e4
--- /dev/null
+++ b/js/src/jsapi-tests/testGCGrayMarking.cpp
@@ -0,0 +1,803 @@
+/* -*- 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 <algorithm>
+
+#include "gc/WeakMap.h"
+#include "gc/Zone.h"
+#include "js/PropertyAndElement.h" // JS_DefineProperty, JS_DefinePropertyById
+#include "js/Proxy.h"
+#include "js/WeakMap.h"
+#include "jsapi-tests/tests.h"
+
+using namespace js;
+using namespace js::gc;
+
+static constexpr CellColor AllCellColors[] = {CellColor::White, CellColor::Gray,
+ CellColor::Black};
+
+static constexpr CellColor MarkedCellColors[] = {CellColor::Gray,
+ CellColor::Black};
+
+namespace js {
+
+struct GCManagedObjectWeakMap : public ObjectWeakMap {
+ using ObjectWeakMap::ObjectWeakMap;
+};
+
+} // namespace js
+
+namespace JS {
+
+template <>
+struct MapTypeToRootKind<js::GCManagedObjectWeakMap*> {
+ static const JS::RootKind kind = JS::RootKind::Traceable;
+};
+
+template <>
+struct GCPolicy<js::GCManagedObjectWeakMap*>
+ : public NonGCPointerPolicy<js::GCManagedObjectWeakMap*> {};
+
+} // namespace JS
+
+class AutoNoAnalysisForTest {
+ public:
+ AutoNoAnalysisForTest() {}
+} JS_HAZ_GC_SUPPRESSED;
+
+BEGIN_TEST(testGCGrayMarking) {
+ AutoNoAnalysisForTest disableAnalysis;
+ AutoDisableCompactingGC disableCompactingGC(cx);
+ AutoLeaveZeal nozeal(cx);
+
+ CHECK(InitGlobals());
+ JSAutoRealm ar(cx, global1);
+
+ InitGrayRootTracer();
+
+ // Enable incremental GC.
+ AutoGCParameter param1(cx, JSGC_INCREMENTAL_GC_ENABLED, true);
+ AutoGCParameter param2(cx, JSGC_PER_ZONE_GC_ENABLED, true);
+
+ bool ok = TestMarking() && TestJSWeakMaps() && TestInternalWeakMaps() &&
+ TestCCWs() && TestGrayUnmarking();
+
+ global1 = nullptr;
+ global2 = nullptr;
+ RemoveGrayRootTracer();
+
+ return ok;
+}
+
+bool TestMarking() {
+ JSObject* sameTarget = AllocPlainObject();
+ CHECK(sameTarget);
+
+ JSObject* sameSource = AllocSameCompartmentSourceObject(sameTarget);
+ CHECK(sameSource);
+
+ JSObject* crossTarget = AllocPlainObject();
+ CHECK(crossTarget);
+
+ JSObject* crossSource = GetCrossCompartmentWrapper(crossTarget);
+ CHECK(crossSource);
+
+ // Test GC with black roots marks objects black.
+
+ JS::RootedObject blackRoot1(cx, sameSource);
+ JS::RootedObject blackRoot2(cx, crossSource);
+
+ JS_GC(cx);
+
+ CHECK(IsMarkedBlack(sameSource));
+ CHECK(IsMarkedBlack(crossSource));
+ CHECK(IsMarkedBlack(sameTarget));
+ CHECK(IsMarkedBlack(crossTarget));
+
+ // Test GC with black and gray roots marks objects black.
+
+ grayRoots.grayRoot1 = sameSource;
+ grayRoots.grayRoot2 = crossSource;
+
+ JS_GC(cx);
+
+ CHECK(IsMarkedBlack(sameSource));
+ CHECK(IsMarkedBlack(crossSource));
+ CHECK(IsMarkedBlack(sameTarget));
+ CHECK(IsMarkedBlack(crossTarget));
+
+ CHECK(!JS::ObjectIsMarkedGray(sameSource));
+
+ // Test GC with gray roots marks object gray.
+
+ blackRoot1 = nullptr;
+ blackRoot2 = nullptr;
+
+ JS_GC(cx);
+
+ CHECK(IsMarkedGray(sameSource));
+ CHECK(IsMarkedGray(crossSource));
+ CHECK(IsMarkedGray(sameTarget));
+ CHECK(IsMarkedGray(crossTarget));
+
+ CHECK(JS::ObjectIsMarkedGray(sameSource));
+
+ // Test ExposeToActiveJS marks gray objects black.
+
+ JS::ExposeObjectToActiveJS(sameSource);
+ JS::ExposeObjectToActiveJS(crossSource);
+ CHECK(IsMarkedBlack(sameSource));
+ CHECK(IsMarkedBlack(crossSource));
+ CHECK(IsMarkedBlack(sameTarget));
+ CHECK(IsMarkedBlack(crossTarget));
+
+ // Test a zone GC with black roots marks gray object in other zone black.
+
+ JS_GC(cx);
+
+ CHECK(IsMarkedGray(crossSource));
+ CHECK(IsMarkedGray(crossTarget));
+
+ blackRoot1 = crossSource;
+ CHECK(ZoneGC(crossSource->zone()));
+
+ CHECK(IsMarkedBlack(crossSource));
+ CHECK(IsMarkedBlack(crossTarget));
+
+ blackRoot1 = nullptr;
+ blackRoot2 = nullptr;
+ grayRoots.grayRoot1 = nullptr;
+ grayRoots.grayRoot2 = nullptr;
+
+ return true;
+}
+
+static constexpr CellColor DontMark = CellColor::White;
+
+enum MarkKeyOrDelegate : bool { MarkKey = true, MarkDelegate = false };
+
+bool TestJSWeakMaps() {
+ for (auto keyOrDelegateColor : MarkedCellColors) {
+ for (auto mapColor : MarkedCellColors) {
+ for (auto markKeyOrDelegate : {MarkKey, MarkDelegate}) {
+ CellColor expected = std::min(keyOrDelegateColor, mapColor);
+ CHECK(TestJSWeakMap(markKeyOrDelegate, keyOrDelegateColor, mapColor,
+ expected));
+#ifdef JS_GC_ZEAL
+ CHECK(TestJSWeakMapWithGrayUnmarking(
+ markKeyOrDelegate, keyOrDelegateColor, mapColor, expected));
+#endif
+ }
+ }
+ }
+
+ return true;
+}
+
+bool TestInternalWeakMaps() {
+ for (auto keyMarkColor : AllCellColors) {
+ for (auto delegateMarkColor : AllCellColors) {
+ if (keyMarkColor == CellColor::White &&
+ delegateMarkColor == CellColor::White) {
+ continue;
+ }
+
+ // The map is black. The delegate marks its key via wrapper preservation.
+ // The key maps its delegate and the value. Thus, all three end up the
+ // maximum of the key and delegate colors.
+ CellColor expected = std::max(keyMarkColor, delegateMarkColor);
+ CHECK(TestInternalWeakMap(keyMarkColor, delegateMarkColor, expected));
+
+#ifdef JS_GC_ZEAL
+ CHECK(TestInternalWeakMapWithGrayUnmarking(keyMarkColor,
+ delegateMarkColor, expected));
+#endif
+ }
+ }
+
+ return true;
+}
+
+bool TestJSWeakMap(MarkKeyOrDelegate markKey, CellColor weakMapMarkColor,
+ CellColor keyOrDelegateMarkColor,
+ CellColor expectedValueColor) {
+ using std::swap;
+
+ // Test marking a JS WeakMap object.
+ //
+ // This marks the map and one of the key or delegate. The key/delegate and the
+ // value can end up different colors depending on the color of the map.
+
+ JSObject* weakMap;
+ JSObject* key;
+ JSObject* value;
+
+ // If both map and key are marked the same color, test both possible
+ // orderings.
+ unsigned markOrderings = weakMapMarkColor == keyOrDelegateMarkColor ? 2 : 1;
+
+ for (unsigned markOrder = 0; markOrder < markOrderings; markOrder++) {
+ CHECK(CreateJSWeakMapObjects(&weakMap, &key, &value));
+
+ JSObject* delegate = UncheckedUnwrapWithoutExpose(key);
+ JSObject* keyOrDelegate = markKey ? key : delegate;
+
+ RootedObject blackRoot1(cx);
+ RootedObject blackRoot2(cx);
+
+ RootObject(weakMap, weakMapMarkColor, blackRoot1, grayRoots.grayRoot1);
+ RootObject(keyOrDelegate, keyOrDelegateMarkColor, blackRoot2,
+ grayRoots.grayRoot2);
+
+ if (markOrder != 0) {
+ swap(blackRoot1.get(), blackRoot2.get());
+ swap(grayRoots.grayRoot1, grayRoots.grayRoot2);
+ }
+
+ JS_GC(cx);
+
+ ClearGrayRoots();
+
+ CHECK(weakMap->color() == weakMapMarkColor);
+ CHECK(keyOrDelegate->color() == keyOrDelegateMarkColor);
+ CHECK(value->color() == expectedValueColor);
+ }
+
+ return true;
+}
+
+#ifdef JS_GC_ZEAL
+
+bool TestJSWeakMapWithGrayUnmarking(MarkKeyOrDelegate markKey,
+ CellColor weakMapMarkColor,
+ CellColor keyOrDelegateMarkColor,
+ CellColor expectedValueColor) {
+ // This is like the previous test, but things are marked black by gray
+ // unmarking during incremental GC.
+
+ JSObject* weakMap;
+ JSObject* key;
+ JSObject* value;
+
+ // If both map and key are marked the same color, test both possible
+ // orderings.
+ unsigned markOrderings = weakMapMarkColor == keyOrDelegateMarkColor ? 2 : 1;
+
+ JS_SetGCZeal(cx, uint8_t(ZealMode::YieldWhileGrayMarking), 0);
+
+ for (unsigned markOrder = 0; markOrder < markOrderings; markOrder++) {
+ CHECK(CreateJSWeakMapObjects(&weakMap, &key, &value));
+
+ JSObject* delegate = UncheckedUnwrapWithoutExpose(key);
+ JSObject* keyOrDelegate = markKey ? key : delegate;
+
+ grayRoots.grayRoot1 = keyOrDelegate;
+ grayRoots.grayRoot2 = weakMap;
+
+ // Start an incremental GC and run until gray roots have been pushed onto
+ // the mark stack.
+ JS::PrepareForFullGC(cx);
+ js::SliceBudget budget(TimeBudget(1000000));
+ JS::StartIncrementalGC(cx, JS::GCOptions::Normal, JS::GCReason::DEBUG_GC,
+ budget);
+ MOZ_ASSERT(cx->runtime()->gc.state() == gc::State::Sweep);
+ MOZ_ASSERT(cx->zone()->gcState() == Zone::MarkBlackAndGray);
+
+ // Unmark gray things as specified.
+ if (markOrder != 0) {
+ MaybeExposeObject(weakMap, weakMapMarkColor);
+ MaybeExposeObject(keyOrDelegate, keyOrDelegateMarkColor);
+ } else {
+ MaybeExposeObject(keyOrDelegate, keyOrDelegateMarkColor);
+ MaybeExposeObject(weakMap, weakMapMarkColor);
+ }
+
+ JS::FinishIncrementalGC(cx, JS::GCReason::API);
+
+ ClearGrayRoots();
+
+ CHECK(weakMap->color() == weakMapMarkColor);
+ CHECK(keyOrDelegate->color() == keyOrDelegateMarkColor);
+ CHECK(value->color() == expectedValueColor);
+ }
+
+ JS_UnsetGCZeal(cx, uint8_t(ZealMode::YieldWhileGrayMarking));
+
+ return true;
+}
+
+static void MaybeExposeObject(JSObject* object, CellColor color) {
+ if (color == CellColor::Black) {
+ JS::ExposeObjectToActiveJS(object);
+ }
+}
+
+#endif // JS_GC_ZEAL
+
+bool CreateJSWeakMapObjects(JSObject** weakMapOut, JSObject** keyOut,
+ JSObject** valueOut) {
+ RootedObject key(cx, AllocWeakmapKeyObject());
+ CHECK(key);
+
+ RootedObject value(cx, AllocPlainObject());
+ CHECK(value);
+
+ RootedObject weakMap(cx, JS::NewWeakMapObject(cx));
+ CHECK(weakMap);
+
+ JS::RootedValue valueValue(cx, ObjectValue(*value));
+ CHECK(SetWeakMapEntry(cx, weakMap, key, valueValue));
+
+ *weakMapOut = weakMap;
+ *keyOut = key;
+ *valueOut = value;
+ return true;
+}
+
+bool TestInternalWeakMap(CellColor keyMarkColor, CellColor delegateMarkColor,
+ CellColor expectedColor) {
+ using std::swap;
+
+ // Test marking for internal weakmaps (without an owning JSObject).
+ //
+ // All of the key, delegate and value are expected to end up the same color.
+
+ UniquePtr<GCManagedObjectWeakMap> weakMap;
+ JSObject* key;
+ JSObject* value;
+
+ // If both key and delegate are marked the same color, test both possible
+ // orderings.
+ unsigned markOrderings = keyMarkColor == delegateMarkColor ? 2 : 1;
+
+ for (unsigned markOrder = 0; markOrder < markOrderings; markOrder++) {
+ CHECK(CreateInternalWeakMapObjects(&weakMap, &key, &value));
+
+ JSObject* delegate = UncheckedUnwrapWithoutExpose(key);
+
+ RootedObject blackRoot1(cx);
+ RootedObject blackRoot2(cx);
+
+ Rooted<GCManagedObjectWeakMap*> rootMap(cx, weakMap.get());
+ RootObject(key, keyMarkColor, blackRoot1, grayRoots.grayRoot1);
+ RootObject(delegate, delegateMarkColor, blackRoot2, grayRoots.grayRoot2);
+
+ if (markOrder != 0) {
+ swap(blackRoot1.get(), blackRoot2.get());
+ swap(grayRoots.grayRoot1, grayRoots.grayRoot2);
+ }
+
+ JS_GC(cx);
+
+ ClearGrayRoots();
+
+ CHECK(key->color() == expectedColor);
+ CHECK(delegate->color() == expectedColor);
+ CHECK(value->color() == expectedColor);
+ }
+
+ return true;
+}
+
+#ifdef JS_GC_ZEAL
+
+bool TestInternalWeakMapWithGrayUnmarking(CellColor keyMarkColor,
+ CellColor delegateMarkColor,
+ CellColor expectedColor) {
+ UniquePtr<GCManagedObjectWeakMap> weakMap;
+ JSObject* key;
+ JSObject* value;
+
+ // If both key and delegate are marked the same color, test both possible
+ // orderings.
+ unsigned markOrderings = keyMarkColor == delegateMarkColor ? 2 : 1;
+
+ JS_SetGCZeal(cx, uint8_t(ZealMode::YieldWhileGrayMarking), 0);
+
+ for (unsigned markOrder = 0; markOrder < markOrderings; markOrder++) {
+ CHECK(CreateInternalWeakMapObjects(&weakMap, &key, &value));
+
+ JSObject* delegate = UncheckedUnwrapWithoutExpose(key);
+
+ Rooted<GCManagedObjectWeakMap*> rootMap(cx, weakMap.get());
+ grayRoots.grayRoot1 = key;
+ grayRoots.grayRoot2 = delegate;
+
+ // Start an incremental GC and run until gray roots have been pushed onto
+ // the mark stack.
+ JS::PrepareForFullGC(cx);
+ js::SliceBudget budget(TimeBudget(1000000));
+ JS::StartIncrementalGC(cx, JS::GCOptions::Normal, JS::GCReason::DEBUG_GC,
+ budget);
+ MOZ_ASSERT(cx->runtime()->gc.state() == gc::State::Sweep);
+ MOZ_ASSERT(cx->zone()->gcState() == Zone::MarkBlackAndGray);
+
+ // Unmark gray things as specified.
+ if (markOrder != 0) {
+ MaybeExposeObject(key, keyMarkColor);
+ MaybeExposeObject(delegate, delegateMarkColor);
+ } else {
+ MaybeExposeObject(key, keyMarkColor);
+ MaybeExposeObject(delegate, delegateMarkColor);
+ }
+
+ JS::FinishIncrementalGC(cx, JS::GCReason::API);
+
+ ClearGrayRoots();
+
+ CHECK(key->color() == expectedColor);
+ CHECK(delegate->color() == expectedColor);
+ CHECK(value->color() == expectedColor);
+ }
+
+ JS_UnsetGCZeal(cx, uint8_t(ZealMode::YieldWhileGrayMarking));
+
+ return true;
+}
+
+#endif // JS_GC_ZEAL
+
+bool CreateInternalWeakMapObjects(UniquePtr<GCManagedObjectWeakMap>* weakMapOut,
+ JSObject** keyOut, JSObject** valueOut) {
+ RootedObject key(cx, AllocWeakmapKeyObject());
+ CHECK(key);
+
+ RootedObject value(cx, AllocPlainObject());
+ CHECK(value);
+
+ auto weakMap = cx->make_unique<GCManagedObjectWeakMap>(cx);
+ CHECK(weakMap);
+
+ CHECK(weakMap->add(cx, key, value));
+
+ *weakMapOut = std::move(weakMap);
+ *keyOut = key;
+ *valueOut = value;
+ return true;
+}
+
+void RootObject(JSObject* object, CellColor color, RootedObject& blackRoot,
+ JS::Heap<JSObject*>& grayRoot) {
+ if (color == CellColor::Black) {
+ blackRoot = object;
+ } else if (color == CellColor::Gray) {
+ grayRoot = object;
+ } else {
+ MOZ_RELEASE_ASSERT(color == CellColor::White);
+ }
+}
+
+bool TestCCWs() {
+ JSObject* target = AllocPlainObject();
+ CHECK(target);
+
+ // Test getting a new wrapper doesn't return a gray wrapper.
+
+ RootedObject blackRoot(cx, target);
+ JSObject* wrapper = GetCrossCompartmentWrapper(target);
+ CHECK(wrapper);
+ CHECK(!IsMarkedGray(wrapper));
+
+ // Test getting an existing wrapper doesn't return a gray wrapper.
+
+ grayRoots.grayRoot1 = wrapper;
+ grayRoots.grayRoot2 = nullptr;
+ JS_GC(cx);
+ CHECK(IsMarkedGray(wrapper));
+ CHECK(IsMarkedBlack(target));
+
+ CHECK(GetCrossCompartmentWrapper(target) == wrapper);
+ CHECK(!IsMarkedGray(wrapper));
+
+ // Test getting an existing wrapper doesn't return a gray wrapper
+ // during incremental GC.
+
+ JS_GC(cx);
+ CHECK(IsMarkedGray(wrapper));
+ CHECK(IsMarkedBlack(target));
+
+ JSRuntime* rt = cx->runtime();
+ JS::PrepareForFullGC(cx);
+ js::SliceBudget budget(js::WorkBudget(1));
+ rt->gc.startDebugGC(JS::GCOptions::Normal, budget);
+ while (rt->gc.state() == gc::State::Prepare) {
+ rt->gc.debugGCSlice(budget);
+ }
+ CHECK(JS::IsIncrementalGCInProgress(cx));
+
+ CHECK(!IsMarkedBlack(wrapper));
+ CHECK(wrapper->zone()->isGCMarkingBlackOnly());
+
+ CHECK(GetCrossCompartmentWrapper(target) == wrapper);
+ CHECK(IsMarkedBlack(wrapper));
+
+ JS::FinishIncrementalGC(cx, JS::GCReason::API);
+
+ // Test behaviour of gray CCWs marked black by a barrier during incremental
+ // GC.
+
+ // Initial state: source and target are gray.
+ blackRoot = nullptr;
+ grayRoots.grayRoot1 = wrapper;
+ grayRoots.grayRoot2 = nullptr;
+ JS_GC(cx);
+ CHECK(IsMarkedGray(wrapper));
+ CHECK(IsMarkedGray(target));
+
+ // Incremental zone GC started: the source is now unmarked.
+ JS::PrepareZoneForGC(cx, wrapper->zone());
+ budget = js::SliceBudget(js::WorkBudget(1));
+ rt->gc.startDebugGC(JS::GCOptions::Normal, budget);
+ while (rt->gc.state() == gc::State::Prepare) {
+ rt->gc.debugGCSlice(budget);
+ }
+ CHECK(JS::IsIncrementalGCInProgress(cx));
+ CHECK(wrapper->zone()->isGCMarkingBlackOnly());
+ CHECK(!target->zone()->wasGCStarted());
+ CHECK(!IsMarkedBlack(wrapper));
+ CHECK(!IsMarkedGray(wrapper));
+ CHECK(IsMarkedGray(target));
+
+ // Betweeen GC slices: source marked black by barrier, target is
+ // still gray. Target will be marked gray
+ // eventually. ObjectIsMarkedGray() is conservative and reports
+ // that target is not marked gray; AssertObjectIsNotGray() will
+ // assert.
+ grayRoots.grayRoot1.get();
+ CHECK(IsMarkedBlack(wrapper));
+ CHECK(IsMarkedGray(target));
+ CHECK(!JS::ObjectIsMarkedGray(target));
+
+ // Final state: source and target are black.
+ JS::FinishIncrementalGC(cx, JS::GCReason::API);
+ CHECK(IsMarkedBlack(wrapper));
+ CHECK(IsMarkedBlack(target));
+
+ grayRoots.grayRoot1 = nullptr;
+ grayRoots.grayRoot2 = nullptr;
+
+ return true;
+}
+
+bool TestGrayUnmarking() {
+ const size_t length = 2000;
+
+ JSObject* chain = AllocObjectChain(length);
+ CHECK(chain);
+
+ RootedObject blackRoot(cx, chain);
+ JS_GC(cx);
+ size_t count;
+ CHECK(IterateObjectChain(chain, ColorCheckFunctor(MarkColor::Black, &count)));
+ CHECK(count == length);
+
+ blackRoot = nullptr;
+ grayRoots.grayRoot1 = chain;
+ JS_GC(cx);
+ CHECK(cx->runtime()->gc.areGrayBitsValid());
+ CHECK(IterateObjectChain(chain, ColorCheckFunctor(MarkColor::Gray, &count)));
+ CHECK(count == length);
+
+ JS::ExposeObjectToActiveJS(chain);
+ CHECK(cx->runtime()->gc.areGrayBitsValid());
+ CHECK(IterateObjectChain(chain, ColorCheckFunctor(MarkColor::Black, &count)));
+ CHECK(count == length);
+
+ grayRoots.grayRoot1 = nullptr;
+
+ return true;
+}
+
+struct ColorCheckFunctor {
+ MarkColor color;
+ size_t& count;
+
+ ColorCheckFunctor(MarkColor colorArg, size_t* countArg)
+ : color(colorArg), count(*countArg) {
+ count = 0;
+ }
+
+ bool operator()(JSObject* obj) {
+ if (!CheckCellColor(obj, color)) {
+ return false;
+ }
+
+ NativeObject& nobj = obj->as<NativeObject>();
+ if (!CheckCellColor(nobj.shape(), color)) {
+ return false;
+ }
+
+ NativeShape* shape = nobj.shape();
+ if (!CheckCellColor(shape, color)) {
+ return false;
+ }
+
+ // Shapes and symbols are never marked gray.
+ ShapePropertyIter<NoGC> iter(shape);
+ jsid id = iter->key();
+ if (id.isGCThing() &&
+ !CheckCellColor(id.toGCCellPtr().asCell(), MarkColor::Black)) {
+ return false;
+ }
+
+ count++;
+ return true;
+ }
+};
+
+JS::PersistentRootedObject global1;
+JS::PersistentRootedObject global2;
+
+struct GrayRoots {
+ JS::Heap<JSObject*> grayRoot1;
+ JS::Heap<JSObject*> grayRoot2;
+};
+
+GrayRoots grayRoots;
+
+bool InitGlobals() {
+ global1.init(cx, global);
+ if (!createGlobal()) {
+ return false;
+ }
+ global2.init(cx, global);
+ return global2 != nullptr;
+}
+
+void ClearGrayRoots() {
+ grayRoots.grayRoot1 = nullptr;
+ grayRoots.grayRoot2 = nullptr;
+}
+
+void InitGrayRootTracer() {
+ ClearGrayRoots();
+ JS_SetGrayGCRootsTracer(cx, TraceGrayRoots, &grayRoots);
+}
+
+void RemoveGrayRootTracer() {
+ ClearGrayRoots();
+ JS_SetGrayGCRootsTracer(cx, nullptr, nullptr);
+}
+
+static bool TraceGrayRoots(JSTracer* trc, SliceBudget& budget, void* data) {
+ auto grayRoots = static_cast<GrayRoots*>(data);
+ TraceEdge(trc, &grayRoots->grayRoot1, "gray root 1");
+ TraceEdge(trc, &grayRoots->grayRoot2, "gray root 2");
+ return true;
+}
+
+JSObject* AllocPlainObject() {
+ JS::RootedObject obj(cx, JS_NewPlainObject(cx));
+ EvictNursery();
+
+ MOZ_ASSERT(obj->compartment() == global1->compartment());
+ return obj;
+}
+
+JSObject* AllocSameCompartmentSourceObject(JSObject* target) {
+ JS::RootedObject source(cx, JS_NewPlainObject(cx));
+ if (!source) {
+ return nullptr;
+ }
+
+ JS::RootedObject obj(cx, target);
+ if (!JS_DefineProperty(cx, source, "ptr", obj, 0)) {
+ return nullptr;
+ }
+
+ EvictNursery();
+
+ MOZ_ASSERT(source->compartment() == global1->compartment());
+ return source;
+}
+
+JSObject* GetCrossCompartmentWrapper(JSObject* target) {
+ MOZ_ASSERT(target->compartment() == global1->compartment());
+ JS::RootedObject obj(cx, target);
+ JSAutoRealm ar(cx, global2);
+ if (!JS_WrapObject(cx, &obj)) {
+ return nullptr;
+ }
+
+ EvictNursery();
+
+ MOZ_ASSERT(obj->compartment() == global2->compartment());
+ return obj;
+}
+
+JSObject* AllocWeakmapKeyObject() {
+ JS::RootedObject delegate(cx, JS_NewPlainObject(cx));
+ if (!delegate) {
+ return nullptr;
+ }
+
+ JS::RootedObject key(cx,
+ js::Wrapper::New(cx, delegate, &js::Wrapper::singleton));
+
+ EvictNursery();
+ return key;
+}
+
+JSObject* AllocObjectChain(size_t length) {
+ // Allocate a chain of linked JSObjects.
+
+ // Use a unique property name so the shape is not shared with any other
+ // objects.
+ RootedString nextPropName(cx, JS_NewStringCopyZ(cx, "unique14142135"));
+ RootedId nextId(cx);
+ if (!JS_StringToId(cx, nextPropName, &nextId)) {
+ return nullptr;
+ }
+
+ RootedObject head(cx);
+ for (size_t i = 0; i < length; i++) {
+ RootedValue next(cx, ObjectOrNullValue(head));
+ head = AllocPlainObject();
+ if (!head) {
+ return nullptr;
+ }
+ if (!JS_DefinePropertyById(cx, head, nextId, next, 0)) {
+ return nullptr;
+ }
+ }
+
+ return head;
+}
+
+template <typename F>
+bool IterateObjectChain(JSObject* chain, F f) {
+ RootedObject obj(cx, chain);
+ while (obj) {
+ if (!f(obj)) {
+ return false;
+ }
+
+ // Access the 'next' property via the object's slots to avoid triggering
+ // gray marking assertions when calling JS_GetPropertyById.
+ NativeObject& nobj = obj->as<NativeObject>();
+ MOZ_ASSERT(nobj.slotSpan() == 1);
+ obj = nobj.getSlot(0).toObjectOrNull();
+ }
+
+ return true;
+}
+
+static bool IsMarkedBlack(Cell* cell) {
+ TenuredCell* tc = &cell->asTenured();
+ return tc->isMarkedBlack();
+}
+
+static bool IsMarkedGray(Cell* cell) {
+ TenuredCell* tc = &cell->asTenured();
+ bool isGray = tc->isMarkedGray();
+ MOZ_ASSERT_IF(isGray, tc->isMarkedAny());
+ return isGray;
+}
+
+static bool CheckCellColor(Cell* cell, MarkColor color) {
+ MOZ_ASSERT(color == MarkColor::Black || color == MarkColor::Gray);
+ if (color == MarkColor::Black && !IsMarkedBlack(cell)) {
+ printf("Found non-black cell: %p\n", cell);
+ return false;
+ } else if (color == MarkColor::Gray && !IsMarkedGray(cell)) {
+ printf("Found non-gray cell: %p\n", cell);
+ return false;
+ }
+
+ return true;
+}
+
+void EvictNursery() { cx->runtime()->gc.evictNursery(); }
+
+bool ZoneGC(JS::Zone* zone) {
+ JS::PrepareZoneForGC(cx, zone);
+ cx->runtime()->gc.gc(JS::GCOptions::Normal, JS::GCReason::API);
+ CHECK(!cx->runtime()->gc.isFullGc());
+ return true;
+}
+
+END_TEST(testGCGrayMarking)
diff --git a/js/src/jsapi-tests/testGCHeapBarriers.cpp b/js/src/jsapi-tests/testGCHeapBarriers.cpp
new file mode 100644
index 0000000000..9493049e16
--- /dev/null
+++ b/js/src/jsapi-tests/testGCHeapBarriers.cpp
@@ -0,0 +1,828 @@
+/* -*- 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/Maybe.h"
+#include "mozilla/UniquePtr.h"
+
+#include "gc/AllocKind.h"
+#include "gc/Cell.h"
+#include "gc/GCInternals.h"
+#include "gc/GCRuntime.h"
+#include "js/ArrayBuffer.h" // JS::NewArrayBuffer
+#include "js/experimental/TypedData.h"
+#include "js/PropertyAndElement.h" // JS_DefineProperty, JS_GetProperty
+#include "js/RootingAPI.h"
+#include "jsapi-tests/tests.h"
+#include "vm/Runtime.h"
+#include "vm/TypedArrayObject.h"
+
+#include "vm/JSContext-inl.h"
+
+using namespace js;
+
+static js::gc::CellColor GetColor(JSObject* obj) { return obj->color(); }
+static js::gc::CellColor GetColor(const JS::ArrayBufferOrView& view) {
+ return view.asObjectUnbarriered()->color();
+}
+
+static MOZ_MAYBE_UNUSED bool IsInsideNursery(gc::Cell* cell) {
+ return !cell->isTenured();
+}
+static MOZ_MAYBE_UNUSED bool IsInsideNursery(
+ const JS::ArrayBufferOrView& view) {
+ return IsInsideNursery(view.asObjectUnbarriered());
+}
+
+// A heap-allocated structure containing one of our barriered pointer wrappers
+// to test.
+template <typename W, typename T>
+struct TestStruct {
+ W wrapper;
+
+ void trace(JSTracer* trc) {
+ TraceNullableEdge(trc, &wrapper, "TestStruct::wrapper");
+ }
+
+ TestStruct() {}
+ explicit TestStruct(T init) : wrapper(init) {}
+};
+
+template <typename T>
+static T CreateNurseryGCThing(JSContext* cx) = delete;
+
+template <>
+JSObject* CreateNurseryGCThing(JSContext* cx) {
+ JS::RootedObject obj(cx, JS_NewPlainObject(cx));
+ if (!obj) {
+ return nullptr;
+ }
+ JS_DefineProperty(cx, obj, "x", 42, 0);
+ MOZ_ASSERT(IsInsideNursery(obj));
+ return obj;
+}
+
+template <>
+JSFunction* CreateNurseryGCThing(JSContext* cx) {
+ /*
+ * We don't actually use the function as a function, so here we cheat and
+ * cast a JSObject.
+ */
+ return static_cast<JSFunction*>(CreateNurseryGCThing<JSObject*>(cx));
+}
+
+template <>
+JS::Uint8Array CreateNurseryGCThing(JSContext* cx) {
+ JS::Rooted<JS::Uint8Array> arr(cx, JS::Uint8Array::create(cx, 100));
+ JS::RootedObject obj(cx, arr.asObject());
+ JS_DefineProperty(cx, obj, "x", 42, 0);
+ MOZ_ASSERT(IsInsideNursery(obj));
+ return arr;
+}
+
+template <typename T>
+static T CreateTenuredGCThing(JSContext* cx) = delete;
+
+template <>
+JSObject* CreateTenuredGCThing(JSContext* cx) {
+ // Use ArrayBuffers because they have finalizers, which allows using them in
+ // TenuredHeap<> without awkward conversations about nursery allocatability.
+ // Note that at some point ArrayBuffers might become nursery allocated at
+ // which point this test will have to change.
+ JSObject* obj = JS::NewArrayBuffer(cx, 20);
+ MOZ_ASSERT(!IsInsideNursery(obj));
+ MOZ_ASSERT(obj->getClass()->hasFinalize() &&
+ !(obj->getClass()->flags & JSCLASS_SKIP_NURSERY_FINALIZE));
+ return obj;
+}
+
+template <>
+JS::ArrayBuffer CreateTenuredGCThing(JSContext* cx) {
+ return JS::ArrayBuffer::fromObject(CreateTenuredGCThing<JSObject*>(cx));
+}
+
+template <>
+JS::Uint8Array CreateTenuredGCThing(JSContext* cx) {
+ // Use internal APIs that lets us specify the InitialHeap so we can ensure
+ // that this is tenured.
+ JSObject* obj = js::NewUint8ArrayWithLength(cx, 100, gc::Heap::Tenured);
+ MOZ_ASSERT(!IsInsideNursery(obj));
+ return JS::Uint8Array::fromObject(obj);
+}
+
+template <typename T>
+void* CreateHiddenTenuredGCThing(JSContext* cx) {
+ return CreateTenuredGCThing<T>(cx);
+}
+
+template <>
+void* CreateHiddenTenuredGCThing<JS::ArrayBuffer>(JSContext* cx) {
+ return CreateTenuredGCThing<JS::ArrayBuffer>(cx).asObjectUnbarriered();
+}
+
+template <>
+void* CreateHiddenTenuredGCThing<JS::Uint8Array>(JSContext* cx) {
+ return CreateTenuredGCThing<JS::Uint8Array>(cx).asObjectUnbarriered();
+}
+
+static uintptr_t UnbarrieredCastToInt(gc::Cell* cell) {
+ return reinterpret_cast<uintptr_t>(cell);
+}
+static uintptr_t UnbarrieredCastToInt(const JS::ArrayBufferOrView& view) {
+ return UnbarrieredCastToInt(view.asObjectUnbarriered());
+}
+
+template <typename T>
+T RecoverHiddenGCThing(void* ptr) {
+ return reinterpret_cast<T>(ptr);
+}
+
+template <>
+JS::ArrayBuffer RecoverHiddenGCThing(void* ptr) {
+ return JS::ArrayBuffer::fromObject(RecoverHiddenGCThing<JSObject*>(ptr));
+}
+
+template <>
+JS::Uint8Array RecoverHiddenGCThing(void* ptr) {
+ return JS::Uint8Array::fromObject(RecoverHiddenGCThing<JSObject*>(ptr));
+}
+
+static void MakeGray(JSObject* obj) {
+ gc::TenuredCell* cell = &obj->asTenured();
+ cell->unmark();
+ cell->markIfUnmarked(gc::MarkColor::Gray);
+ MOZ_ASSERT(obj->isMarkedGray());
+}
+
+static void MakeGray(const JS::ArrayBufferOrView& view) {
+ MakeGray(view.asObjectUnbarriered());
+}
+
+// Test post-barrier implementation on wrapper types. The following wrapper
+// types have post barriers:
+// - JS::Heap
+// - GCPtr
+// - HeapPtr
+// - WeakHeapPtr
+BEGIN_TEST(testGCHeapPostBarriers) {
+ AutoLeaveZeal nozeal(cx);
+
+ /* Sanity check - objects start in the nursery and then become tenured. */
+ JS_GC(cx);
+ JS::RootedObject obj(cx, CreateNurseryGCThing<JSObject*>(cx));
+ CHECK(IsInsideNursery(obj.get()));
+ JS_GC(cx);
+ CHECK(!IsInsideNursery(obj.get()));
+ JS::RootedObject tenuredObject(cx, obj);
+
+ /* JSObject and JSFunction objects are nursery allocated. */
+ CHECK(TestHeapPostBarriersForType<JSObject*>());
+ CHECK(TestHeapPostBarriersForType<JSFunction*>());
+ CHECK(TestHeapPostBarriersForType<JS::Uint8Array>());
+ // Bug 1599378: Add string tests.
+
+ return true;
+}
+
+bool CanAccessObject(JSObject* obj) {
+ JS::RootedObject rootedObj(cx, obj);
+ JS::RootedValue value(cx);
+ CHECK(JS_GetProperty(cx, rootedObj, "x", &value));
+ CHECK(value.isInt32());
+ CHECK(value.toInt32() == 42);
+ return true;
+}
+bool CanAccessObject(const JS::ArrayBufferOrView& view) {
+ return CanAccessObject(view.asObject());
+}
+
+template <typename T>
+bool TestHeapPostBarriersForType() {
+ CHECK((TestHeapPostBarriersForWrapper<js::GCPtr, T>()));
+ CHECK((TestHeapPostBarriersForMovableWrapper<JS::Heap, T>()));
+ CHECK((TestHeapPostBarriersForMovableWrapper<js::HeapPtr, T>()));
+ CHECK((TestHeapPostBarriersForMovableWrapper<js::WeakHeapPtr, T>()));
+ return true;
+}
+
+template <template <typename> class W, typename T>
+bool TestHeapPostBarriersForMovableWrapper() {
+ CHECK((TestHeapPostBarriersForWrapper<W, T>()));
+ CHECK((TestHeapPostBarrierMoveConstruction<W<T>, T>()));
+ CHECK((TestHeapPostBarrierMoveAssignment<W<T>, T>()));
+ return true;
+}
+
+template <template <typename> class W, typename T>
+bool TestHeapPostBarriersForWrapper() {
+ CHECK((TestHeapPostBarrierConstruction<W<T>, T>()));
+ CHECK((TestHeapPostBarrierConstruction<const W<T>, T>()));
+ CHECK((TestHeapPostBarrierUpdate<W<T>, T>()));
+ if constexpr (!std::is_same_v<W<T>, GCPtr<T>>) {
+ // It is not allowed to delete heap memory containing GCPtrs on
+ // initialization failure like this and doing so will cause an assertion to
+ // fail in the GCPtr destructor (although we disable this in some places in
+ // this test).
+ CHECK((TestHeapPostBarrierInitFailure<W<T>, T>()));
+ CHECK((TestHeapPostBarrierInitFailure<const W<T>, T>()));
+ }
+ return true;
+}
+
+template <typename W, typename T>
+bool TestHeapPostBarrierConstruction() {
+ T initialObj = CreateNurseryGCThing<T>(cx);
+ CHECK(initialObj);
+ CHECK(IsInsideNursery(initialObj));
+ uintptr_t initialObjAsInt = UnbarrieredCastToInt(initialObj);
+
+ {
+ // We don't root our structure because that would end up tracing it on minor
+ // GC and we're testing that heap post barrier works for things that aren't
+ // roots.
+ JS::AutoSuppressGCAnalysis noAnalysis(cx);
+
+ auto* testStruct = js_new<TestStruct<W, T>>(initialObj);
+ CHECK(testStruct);
+
+ auto& wrapper = testStruct->wrapper;
+ CHECK(wrapper == initialObj);
+
+ cx->minorGC(JS::GCReason::API);
+
+ CHECK(UnbarrieredCastToInt(wrapper.get()) != initialObjAsInt);
+ CHECK(!IsInsideNursery(wrapper.get()));
+ CHECK(CanAccessObject(wrapper.get()));
+
+ // Disable the check that GCPtrs are only destroyed by the GC. What happens
+ // on destruction isn't relevant to the test.
+ gc::AutoSetThreadIsFinalizing threadIsFinalizing;
+
+ js_delete(testStruct);
+ }
+
+ cx->minorGC(JS::GCReason::API);
+
+ return true;
+}
+
+template <typename W, typename T>
+bool TestHeapPostBarrierUpdate() {
+ // Normal case - allocate a heap object, write a nursery pointer into it and
+ // check that it gets updated on minor GC.
+
+ T initialObj = CreateNurseryGCThing<T>(cx);
+ CHECK(initialObj);
+ CHECK(IsInsideNursery(initialObj));
+ uintptr_t initialObjAsInt = UnbarrieredCastToInt(initialObj);
+
+ {
+ // We don't root our structure because that would end up tracing it on minor
+ // GC and we're testing that heap post barrier works for things that aren't
+ // roots.
+ JS::AutoSuppressGCAnalysis noAnalysis(cx);
+
+ auto* testStruct = js_new<TestStruct<W, T>>();
+ CHECK(testStruct);
+
+ auto& wrapper = testStruct->wrapper;
+ CHECK(!wrapper.get());
+
+ wrapper = initialObj;
+ CHECK(wrapper == initialObj);
+
+ cx->minorGC(JS::GCReason::API);
+
+ CHECK(UnbarrieredCastToInt(wrapper.get()) != initialObjAsInt);
+ CHECK(!IsInsideNursery(wrapper.get()));
+ CHECK(CanAccessObject(wrapper.get()));
+
+ // Disable the check that GCPtrs are only destroyed by the GC. What happens
+ // on destruction isn't relevant to the test.
+ gc::AutoSetThreadIsFinalizing threadIsFinalizing;
+
+ js_delete(testStruct);
+ }
+
+ cx->minorGC(JS::GCReason::API);
+
+ return true;
+}
+
+template <typename W, typename T>
+bool TestHeapPostBarrierInitFailure() {
+ // Failure case - allocate a heap object, write a nursery pointer into it
+ // and fail to complete initialization.
+
+ T initialObj = CreateNurseryGCThing<T>(cx);
+ CHECK(initialObj);
+ CHECK(IsInsideNursery(initialObj));
+
+ {
+ // We don't root our structure because that would end up tracing it on minor
+ // GC and we're testing that heap post barrier works for things that aren't
+ // roots.
+ JS::AutoSuppressGCAnalysis noAnalysis(cx);
+
+ auto testStruct = cx->make_unique<TestStruct<W, T>>(initialObj);
+ CHECK(testStruct);
+
+ auto& wrapper = testStruct->wrapper;
+ CHECK(wrapper == initialObj);
+
+ // testStruct deleted here, as if we left this block due to an error.
+ }
+
+ cx->minorGC(JS::GCReason::API);
+
+ return true;
+}
+
+template <typename W, typename T>
+bool TestHeapPostBarrierMoveConstruction() {
+ T initialObj = CreateNurseryGCThing<T>(cx);
+ CHECK(initialObj);
+ CHECK(IsInsideNursery(initialObj));
+ uintptr_t initialObjAsInt = UnbarrieredCastToInt(initialObj);
+
+ {
+ // We don't root our structure because that would end up tracing it on minor
+ // GC and we're testing that heap post barrier works for things that aren't
+ // roots.
+ JS::AutoSuppressGCAnalysis noAnalysis(cx);
+
+ W wrapper1(initialObj);
+ CHECK(wrapper1 == initialObj);
+
+ W wrapper2(std::move(wrapper1));
+ CHECK(wrapper2 == initialObj);
+
+ cx->minorGC(JS::GCReason::API);
+
+ CHECK(UnbarrieredCastToInt(wrapper1.get()) != initialObjAsInt);
+ CHECK(UnbarrieredCastToInt(wrapper2.get()) != initialObjAsInt);
+ CHECK(!IsInsideNursery(wrapper2.get()));
+ CHECK(CanAccessObject(wrapper2.get()));
+ }
+
+ cx->minorGC(JS::GCReason::API);
+
+ return true;
+}
+
+template <typename W, typename T>
+bool TestHeapPostBarrierMoveAssignment() {
+ T initialObj = CreateNurseryGCThing<T>(cx);
+ CHECK(initialObj);
+ CHECK(IsInsideNursery(initialObj));
+ uintptr_t initialObjAsInt = UnbarrieredCastToInt(initialObj);
+
+ {
+ // We don't root our structure because that would end up tracing it on minor
+ // GC and we're testing that heap post barrier works for things that aren't
+ // roots.
+ JS::AutoSuppressGCAnalysis noAnalysis(cx);
+
+ W wrapper1(initialObj);
+ CHECK(wrapper1 == initialObj);
+
+ W wrapper2;
+ wrapper2 = std::move(wrapper1);
+ CHECK(wrapper2 == initialObj);
+
+ cx->minorGC(JS::GCReason::API);
+
+ CHECK(UnbarrieredCastToInt(wrapper1.get()) != initialObjAsInt);
+ CHECK(UnbarrieredCastToInt(wrapper2.get()) != initialObjAsInt);
+ CHECK(!IsInsideNursery(wrapper2.get()));
+ CHECK(CanAccessObject(wrapper2.get()));
+ }
+
+ cx->minorGC(JS::GCReason::API);
+
+ return true;
+}
+
+END_TEST(testGCHeapPostBarriers)
+
+// Test read barrier implementation on wrapper types. The following wrapper
+// types have read barriers:
+// - JS::Heap
+// - JS::TenuredHeap
+// - WeakHeapPtr
+//
+// Also check that equality comparisons on wrappers do not trigger the read
+// barrier.
+BEGIN_TEST(testGCHeapReadBarriers) {
+ AutoLeaveZeal nozeal(cx);
+
+ CHECK((TestWrapperType<JS::Heap<JSObject*>, JSObject*>()));
+ CHECK((TestWrapperType<JS::TenuredHeap<JSObject*>, JSObject*>()));
+ CHECK((TestWrapperType<WeakHeapPtr<JSObject*>, JSObject*>()));
+ CHECK((TestWrapperType<JS::Heap<JS::ArrayBuffer>, JS::ArrayBuffer>()));
+ CHECK((TestWrapperType<JS::Heap<JS::Uint8Array>, JS::Uint8Array>()));
+
+ // JS::Heap has an additional barrier on its move and copy constructors.
+ CHECK((TestConstructorBarrier<JS::Heap<JSObject*>, JSObject*>()));
+ CHECK((TestConstructorBarrier<JS::Heap<JS::ArrayBuffer>, JS::ArrayBuffer>()));
+ CHECK((TestConstructorBarrier<JS::Heap<JS::Uint8Array>, JS::Uint8Array>()));
+
+ return true;
+}
+
+template <typename WrapperT, typename ObjectT>
+bool TestWrapperType() {
+ // Check that the read barrier normally marks gray things black.
+ CHECK((TestReadBarrierUnmarksGray<WrapperT, ObjectT>()));
+
+ // Check that the read barrier marks gray and white things black during an
+ // incremental GC.
+ CHECK((TestReadBarrierMarksBlack<WrapperT, ObjectT>(true)));
+ CHECK((TestReadBarrierMarksBlack<WrapperT, ObjectT>(false)));
+
+ // Allocate test objects and make them gray. We will make sure they stay
+ // gray. (For most reads, the barrier will unmark gray.)
+ Rooted<ObjectT> obj1(cx, CreateTenuredGCThing<ObjectT>(cx));
+ Rooted<ObjectT> obj2(cx, CreateTenuredGCThing<ObjectT>(cx));
+ MakeGray(obj1);
+ MakeGray(obj2);
+
+ WrapperT wrapper1(obj1);
+ WrapperT wrapper2(obj2);
+ const ObjectT constobj1 = obj1;
+ const ObjectT constobj2 = obj2;
+ CHECK((TestUnbarrieredOperations<WrapperT, ObjectT>(obj1, obj2, wrapper1,
+ wrapper2)));
+ CHECK((TestUnbarrieredOperations<WrapperT, ObjectT>(constobj1, constobj2,
+ wrapper1, wrapper2)));
+
+ return true;
+}
+
+template <typename WrapperT, typename ObjectT>
+void Access(const WrapperT& wrapper) {
+ if constexpr (std::is_base_of_v<JS::ArrayBufferOrView, ObjectT>) {
+ (void)wrapper.asObject();
+ } else {
+ (void)*wrapper;
+ }
+}
+
+template <typename WrapperT, typename ObjectT>
+bool TestReadBarrierUnmarksGray() {
+ Rooted<ObjectT> obj(cx, CreateTenuredGCThing<ObjectT>(cx));
+ WrapperT wrapper(obj);
+
+ CHECK(GetColor(obj) == gc::CellColor::White);
+
+ Access<WrapperT, ObjectT>(wrapper);
+
+ CHECK(GetColor(obj) == gc::CellColor::White);
+
+ MakeGray(obj);
+ Access<WrapperT, ObjectT>(wrapper);
+
+ CHECK(GetColor(obj) == gc::CellColor::Black);
+
+ return true;
+}
+
+// Execute thunk |f| between two slices of an incremental GC controlled by zeal
+// mode |mode|.
+template <typename F>
+bool CallDuringIncrementalGC(uint32_t mode, F&& f) {
+#ifndef JS_GC_ZEAL
+ fprintf(stderr, "This test requires building with --enable-gczeal\n");
+#else
+ AutoGCParameter incremental(cx, JSGC_INCREMENTAL_GC_ENABLED, true);
+
+ const int64_t BudgetMS = 10000; // 10S should be long enough for anyone.
+
+ JS_SetGCZeal(cx, mode, 0);
+ JS::PrepareZoneForGC(cx, js::GetContextZone(cx));
+ js::SliceBudget budget{TimeBudget(BudgetMS)};
+ JS::StartIncrementalGC(cx, JS::GCOptions(), JS::GCReason::DEBUG_GC, budget);
+ CHECK(JS::IsIncrementalGCInProgress(cx));
+
+ CHECK(f());
+
+ JS::FinishIncrementalGC(cx, JS::GCReason::DEBUG_GC);
+#endif
+
+ return true;
+}
+
+template <typename WrapperT, typename ObjectT>
+bool TestReadBarrierMarksBlack(bool fromWhite) {
+ AutoLeaveZeal noZeal(cx);
+
+ // Create an object and hide it from the hazard analysis.
+ void* ptr = CreateHiddenTenuredGCThing<ObjectT>(cx);
+ CHECK(ptr);
+
+ CallDuringIncrementalGC(9 /* YieldBeforeSweeping */, [&]() -> bool {
+ CHECK(JS::IsIncrementalBarrierNeeded(cx));
+
+ auto obj = RecoverHiddenGCThing<ObjectT>(ptr);
+
+ WrapperT wrapper(obj);
+
+ CHECK(GetColor(obj) == gc::CellColor::White);
+ if (!fromWhite) {
+ MakeGray(obj);
+ }
+
+ Access<WrapperT, ObjectT>(wrapper);
+
+ CHECK(GetColor(obj) == gc::CellColor::Black);
+
+ return true;
+ });
+
+ return true;
+}
+
+template <typename WrapperT, typename ObjectT>
+bool TestConstructorBarrier() {
+ AutoLeaveZeal noZeal(cx);
+
+ // Create an object and hide it from the hazard analysis.
+ void* ptr = CreateHiddenTenuredGCThing<ObjectT>(cx);
+ CHECK(ptr);
+
+ CallDuringIncrementalGC(9 /* YieldBeforeSweeping */, [&]() -> bool {
+ CHECK(JS::IsIncrementalBarrierNeeded(cx));
+
+ auto obj = RecoverHiddenGCThing<ObjectT>(ptr);
+ WrapperT wrapper(obj);
+ CHECK(GetColor(obj) == gc::CellColor::White);
+
+ WrapperT copiedWrapper(wrapper);
+ CHECK(GetColor(obj) == gc::CellColor::Black);
+
+ return true;
+ });
+
+ ptr = CreateHiddenTenuredGCThing<ObjectT>(cx);
+ CHECK(ptr);
+
+ CallDuringIncrementalGC(9 /* YieldBeforeSweeping */, [&]() -> bool {
+ CHECK(JS::IsIncrementalBarrierNeeded(cx));
+
+ auto obj = RecoverHiddenGCThing<ObjectT>(ptr);
+ WrapperT wrapper(obj);
+ CHECK(GetColor(obj) == gc::CellColor::White);
+
+ WrapperT movedWrapper(std::move(wrapper));
+ CHECK(GetColor(obj) == gc::CellColor::Black);
+
+ return true;
+ });
+
+ return true;
+}
+
+template <typename WrapperT, typename ObjectT>
+bool TestUnbarrieredOperations(ObjectT obj, ObjectT obj2, WrapperT& wrapper,
+ WrapperT& wrapper2) {
+ (void)bool(wrapper);
+ (void)bool(wrapper2);
+ CHECK(GetColor(obj) == gc::CellColor::Gray);
+ CHECK(GetColor(obj2) == gc::CellColor::Gray);
+
+ int x = 0;
+
+ CHECK(GetColor(obj) == gc::CellColor::Gray);
+ CHECK(GetColor(obj2) == gc::CellColor::Gray);
+ x += obj == obj2;
+ CHECK(GetColor(obj) == gc::CellColor::Gray);
+ CHECK(GetColor(obj2) == gc::CellColor::Gray);
+ x += obj == wrapper2;
+ CHECK(GetColor(obj) == gc::CellColor::Gray);
+ CHECK(GetColor(obj2) == gc::CellColor::Gray);
+ x += wrapper == obj2;
+ CHECK(GetColor(obj) == gc::CellColor::Gray);
+ CHECK(GetColor(obj2) == gc::CellColor::Gray);
+ x += wrapper == wrapper2;
+ CHECK(GetColor(obj) == gc::CellColor::Gray);
+ CHECK(GetColor(obj2) == gc::CellColor::Gray);
+
+ CHECK(x == 0);
+
+ x += obj != obj2;
+ CHECK(GetColor(obj) == gc::CellColor::Gray);
+ CHECK(GetColor(obj2) == gc::CellColor::Gray);
+ x += obj != wrapper2;
+ CHECK(GetColor(obj) == gc::CellColor::Gray);
+ CHECK(GetColor(obj2) == gc::CellColor::Gray);
+ x += wrapper != obj2;
+ CHECK(GetColor(obj) == gc::CellColor::Gray);
+ CHECK(GetColor(obj2) == gc::CellColor::Gray);
+ x += wrapper != wrapper2;
+ CHECK(GetColor(obj) == gc::CellColor::Gray);
+ CHECK(GetColor(obj2) == gc::CellColor::Gray);
+
+ CHECK(x == 4);
+
+ return true;
+}
+
+END_TEST(testGCHeapReadBarriers)
+
+using ObjectVector = Vector<JSObject*, 0, SystemAllocPolicy>;
+
+// Test pre-barrier implementation on wrapper types. The following wrapper types
+// have a pre-barrier:
+// - GCPtr
+// - HeapPtr
+// - PreBarriered
+BEGIN_TEST(testGCHeapPreBarriers) {
+ AutoLeaveZeal nozeal(cx);
+
+ AutoGCParameter param1(cx, JSGC_INCREMENTAL_GC_ENABLED, true);
+
+ // Create a bunch of objects. These are unrooted and will be used to test
+ // whether barriers have fired by checking whether they have been marked
+ // black.
+ size_t objectCount = 100; // Increase this if necessary when adding tests.
+ ObjectVector testObjects;
+ for (size_t i = 0; i < objectCount; i++) {
+ JSObject* obj = CreateTenuredGCThing<JSObject*>(cx);
+ CHECK(obj);
+ CHECK(testObjects.append(obj));
+ }
+
+ // Start an incremental GC so we can detect if we cause barriers to fire, as
+ // these will mark objects black.
+ JS::PrepareForFullGC(cx);
+ SliceBudget budget(WorkBudget(1));
+ gc::GCRuntime* gc = &cx->runtime()->gc;
+ gc->startDebugGC(JS::GCOptions::Normal, budget);
+ while (gc->state() != gc::State::Mark) {
+ gc->debugGCSlice(budget);
+ }
+ MOZ_ASSERT(cx->zone()->needsIncrementalBarrier());
+
+ TestWrapper<HeapPtr<JSObject*>>(testObjects);
+ TestWrapper<PreBarriered<JSObject*>>(testObjects);
+
+ // GCPtr is different because 1) it doesn't support move operations as it's
+ // supposed to be part of a GC thing and 2) it doesn't perform a pre-barrier
+ // in its destructor because these are only destroyed as part of a GC where
+ // the barrier is unnecessary.
+ TestGCPtr(testObjects);
+
+ gc::FinishGC(cx, JS::GCReason::API);
+
+ return true;
+}
+
+template <typename Wrapper>
+bool TestWrapper(ObjectVector& testObjects) {
+ CHECK(TestCopyConstruction<Wrapper>(testObjects.popCopy()));
+ CHECK(TestMoveConstruction<Wrapper>(testObjects.popCopy()));
+ CHECK(TestAssignment<Wrapper>(testObjects.popCopy(), testObjects.popCopy()));
+ CHECK(TestMoveAssignment<Wrapper>(testObjects.popCopy(),
+ testObjects.popCopy()));
+ return true;
+}
+
+template <typename Wrapper>
+bool TestCopyConstruction(JSObject* obj) {
+ CHECK(GetColor(obj) == gc::CellColor::White);
+
+ {
+ Wrapper wrapper1(obj);
+ Wrapper wrapper2(wrapper1);
+ CHECK(wrapper1 == obj);
+ CHECK(wrapper2 == obj);
+ CHECK(GetColor(obj) == gc::CellColor::White);
+ }
+
+ // Check destructor performs pre-barrier.
+ CHECK(GetColor(obj) == gc::CellColor::Black);
+
+ return true;
+}
+
+template <typename Wrapper>
+bool TestMoveConstruction(JSObject* obj) {
+ CHECK(GetColor(obj) == gc::CellColor::White);
+
+ {
+ Wrapper wrapper1(obj);
+ MakeGray(obj); // Check that we allow move of gray GC thing.
+ Wrapper wrapper2(std::move(wrapper1));
+ CHECK(!wrapper1);
+ CHECK(wrapper2 == obj);
+ CHECK(GetColor(obj) == gc::CellColor::Gray);
+ }
+
+ // Check destructor performs pre-barrier.
+ CHECK(GetColor(obj) == gc::CellColor::Black);
+
+ return true;
+}
+
+template <typename Wrapper>
+bool TestAssignment(JSObject* obj1, JSObject* obj2) {
+ CHECK(GetColor(obj1) == gc::CellColor::White);
+ CHECK(GetColor(obj2) == gc::CellColor::White);
+
+ {
+ Wrapper wrapper1(obj1);
+ Wrapper wrapper2(obj2);
+
+ wrapper2 = wrapper1;
+
+ CHECK(wrapper1 == obj1);
+ CHECK(wrapper2 == obj1);
+ CHECK(GetColor(obj1) == gc::CellColor::White); // No barrier fired.
+ CHECK(GetColor(obj2) == gc::CellColor::Black); // Pre barrier fired.
+ }
+
+ // Check destructor performs pre-barrier.
+ CHECK(GetColor(obj1) == gc::CellColor::Black);
+
+ return true;
+}
+
+template <typename Wrapper>
+bool TestMoveAssignment(JSObject* obj1, JSObject* obj2) {
+ CHECK(GetColor(obj1) == gc::CellColor::White);
+ CHECK(GetColor(obj2) == gc::CellColor::White);
+
+ {
+ Wrapper wrapper1(obj1);
+ Wrapper wrapper2(obj2);
+
+ MakeGray(obj1); // Check we allow move of gray thing.
+ wrapper2 = std::move(wrapper1);
+
+ CHECK(!wrapper1);
+ CHECK(wrapper2 == obj1);
+ CHECK(GetColor(obj1) == gc::CellColor::Gray); // No barrier fired.
+ CHECK(GetColor(obj2) == gc::CellColor::Black); // Pre barrier fired.
+ }
+
+ // Check destructor performs pre-barrier.
+ CHECK(GetColor(obj1) == gc::CellColor::Black);
+
+ return true;
+}
+
+bool TestGCPtr(ObjectVector& testObjects) {
+ CHECK(TestGCPtrCopyConstruction(testObjects.popCopy()));
+ CHECK(TestGCPtrAssignment(testObjects.popCopy(), testObjects.popCopy()));
+ return true;
+}
+
+bool TestGCPtrCopyConstruction(JSObject* obj) {
+ CHECK(GetColor(obj) == gc::CellColor::White);
+
+ {
+ // Let us destroy GCPtrs ourselves for testing purposes.
+ gc::AutoSetThreadIsFinalizing threadIsFinalizing;
+
+ GCPtr<JSObject*> wrapper1(obj);
+ GCPtr<JSObject*> wrapper2(wrapper1);
+ CHECK(wrapper1 == obj);
+ CHECK(wrapper2 == obj);
+ CHECK(GetColor(obj) == gc::CellColor::White);
+ }
+
+ // GCPtr doesn't perform pre-barrier in destructor.
+ CHECK(GetColor(obj) == gc::CellColor::White);
+
+ return true;
+}
+
+bool TestGCPtrAssignment(JSObject* obj1, JSObject* obj2) {
+ CHECK(GetColor(obj1) == gc::CellColor::White);
+ CHECK(GetColor(obj2) == gc::CellColor::White);
+
+ {
+ // Let us destroy GCPtrs ourselves for testing purposes.
+ gc::AutoSetThreadIsFinalizing threadIsFinalizing;
+
+ GCPtr<JSObject*> wrapper1(obj1);
+ GCPtr<JSObject*> wrapper2(obj2);
+
+ wrapper2 = wrapper1;
+
+ CHECK(wrapper1 == obj1);
+ CHECK(wrapper2 == obj1);
+ CHECK(GetColor(obj1) == gc::CellColor::White); // No barrier fired.
+ CHECK(GetColor(obj2) == gc::CellColor::Black); // Pre barrier fired.
+ }
+
+ // GCPtr doesn't perform pre-barrier in destructor.
+ CHECK(GetColor(obj1) == gc::CellColor::White);
+
+ return true;
+}
+
+END_TEST(testGCHeapPreBarriers)
diff --git a/js/src/jsapi-tests/testGCHooks.cpp b/js/src/jsapi-tests/testGCHooks.cpp
new file mode 100644
index 0000000000..7c6a224995
--- /dev/null
+++ b/js/src/jsapi-tests/testGCHooks.cpp
@@ -0,0 +1,279 @@
+/* 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 "mozilla/UniquePtr.h"
+
+#include <iterator>
+
+#include "jsapi-tests/tests.h"
+
+static unsigned gSliceCallbackCount = 0;
+static bool gSawAllSliceCallbacks;
+static bool gSawAllGCCallbacks;
+
+static void NonIncrementalGCSliceCallback(JSContext* cx,
+ JS::GCProgress progress,
+ const JS::GCDescription& desc) {
+ using namespace JS;
+ static GCProgress expect[] = {GC_CYCLE_BEGIN, GC_SLICE_BEGIN, GC_SLICE_END,
+ GC_CYCLE_END};
+
+ MOZ_RELEASE_ASSERT(gSliceCallbackCount < std::size(expect));
+ MOZ_RELEASE_ASSERT(progress == expect[gSliceCallbackCount++]);
+ MOZ_RELEASE_ASSERT(desc.isZone_ == false);
+ MOZ_RELEASE_ASSERT(desc.options_ == JS::GCOptions::Normal);
+ MOZ_RELEASE_ASSERT(desc.reason_ == JS::GCReason::API);
+ if (progress == GC_CYCLE_END) {
+ mozilla::UniquePtr<char16_t> summary(desc.formatSummaryMessage(cx));
+ mozilla::UniquePtr<char16_t> message(desc.formatSliceMessage(cx));
+ }
+}
+
+BEGIN_TEST(testGCSliceCallback) {
+ gSliceCallbackCount = 0;
+ JS::SetGCSliceCallback(cx, NonIncrementalGCSliceCallback);
+ JS_GC(cx);
+ JS::SetGCSliceCallback(cx, nullptr);
+ CHECK(gSliceCallbackCount == 4);
+ return true;
+}
+END_TEST(testGCSliceCallback)
+
+static void RootsRemovedGCSliceCallback(JSContext* cx, JS::GCProgress progress,
+ const JS::GCDescription& desc) {
+ using namespace JS;
+
+ static constexpr struct {
+ GCProgress progress;
+ GCReason reason;
+ } expect[] = {
+ // Explicitly requested a full GC.
+ {GC_CYCLE_BEGIN, GCReason::DEBUG_GC},
+ {GC_SLICE_BEGIN, GCReason::DEBUG_GC},
+ {GC_SLICE_END, GCReason::DEBUG_GC},
+ {GC_SLICE_BEGIN, GCReason::DEBUG_GC},
+ {GC_SLICE_END, GCReason::DEBUG_GC},
+ {GC_CYCLE_END, GCReason::DEBUG_GC},
+ // Shutdown GC with ROOTS_REMOVED.
+ {GC_CYCLE_BEGIN, GCReason::ROOTS_REMOVED},
+ {GC_SLICE_BEGIN, GCReason::ROOTS_REMOVED},
+ {GC_SLICE_END, GCReason::ROOTS_REMOVED},
+ {GC_CYCLE_END, GCReason::ROOTS_REMOVED}
+ // All done.
+ };
+
+ MOZ_RELEASE_ASSERT(gSliceCallbackCount < std::size(expect));
+ MOZ_RELEASE_ASSERT(progress == expect[gSliceCallbackCount].progress);
+ MOZ_RELEASE_ASSERT(desc.isZone_ == false);
+ MOZ_RELEASE_ASSERT(desc.options_ == JS::GCOptions::Shrink);
+ MOZ_RELEASE_ASSERT(desc.reason_ == expect[gSliceCallbackCount].reason);
+ gSliceCallbackCount++;
+}
+
+BEGIN_TEST(testGCRootsRemoved) {
+ AutoLeaveZeal nozeal(cx);
+
+ AutoGCParameter param1(cx, JSGC_INCREMENTAL_GC_ENABLED, true);
+
+ gSliceCallbackCount = 0;
+ JS::SetGCSliceCallback(cx, RootsRemovedGCSliceCallback);
+ auto byebye =
+ mozilla::MakeScopeExit([=] { JS::SetGCSliceCallback(cx, nullptr); });
+
+ JS::RootedObject obj(cx, JS_NewPlainObject(cx));
+ CHECK(obj);
+
+ JS::PrepareForFullGC(cx);
+ js::SliceBudget budget(js::WorkBudget(1));
+ cx->runtime()->gc.startDebugGC(JS::GCOptions::Shrink, budget);
+ CHECK(JS::IsIncrementalGCInProgress(cx));
+
+ // Trigger another GC after the current one in shrinking / shutdown GCs.
+ cx->runtime()->gc.notifyRootsRemoved();
+
+ JS::FinishIncrementalGC(cx, JS::GCReason::DEBUG_GC);
+ CHECK(!JS::IsIncrementalGCInProgress(cx));
+
+ return true;
+}
+END_TEST(testGCRootsRemoved)
+
+#define ASSERT_MSG(cond, ...) \
+ do { \
+ if (!(cond)) { \
+ fprintf(stderr, __VA_ARGS__); \
+ MOZ_RELEASE_ASSERT(cond); \
+ } \
+ } while (false)
+
+// Trigger some nested GCs to ensure that they get their own reasons and
+// fullGCRequested state.
+//
+// The callbacks will form the following tree:
+//
+// Begin(DEBUG_GC)
+// Begin(API)
+// End(API)
+// End(DEBUG_GC)
+// Begin(MEM_PRESSURE)
+// End(MEM_PRESSURE)
+// Begin(DOM_WINDOW_UTILS)
+// End(DOM_WINDOW_UTILS)
+//
+// JSGC_BEGIN and JSGC_END callbacks will be observed as a preorder traversal
+// of the above tree.
+//
+// Note that the ordering of the *slice* callbacks don't match up simply to the
+// ordering above. If a JSGC_BEGIN triggers another GC, we won't see the outer
+// GC's GC_CYCLE_BEGIN until the inner one is done. The slice callbacks are
+// reporting the actual order that the GCs are happening in.
+//
+// JSGC_END, on the other hand, won't be emitted until the GC is complete and
+// the GC_CYCLE_BEGIN callback has fired. So its ordering is straightforward.
+//
+static void GCTreeCallback(JSContext* cx, JSGCStatus status,
+ JS::GCReason reason, void* data) {
+ using namespace JS;
+
+ static constexpr struct {
+ JSGCStatus expectedStatus;
+ JS::GCReason expectedReason;
+ bool fireGC;
+ JS::GCReason reason;
+ bool requestFullGC;
+ } invocations[] = {
+ {JSGC_BEGIN, GCReason::DEBUG_GC, true, GCReason::API, false},
+ {JSGC_BEGIN, GCReason::API, false},
+ {JSGC_END, GCReason::API, false},
+ {JSGC_END, GCReason::DEBUG_GC, true, GCReason::MEM_PRESSURE, true},
+ {JSGC_BEGIN, GCReason::MEM_PRESSURE, false},
+ {JSGC_END, GCReason::MEM_PRESSURE, true, GCReason::DOM_WINDOW_UTILS,
+ false},
+ {JSGC_BEGIN, GCReason::DOM_WINDOW_UTILS, false},
+ {JSGC_END, GCReason::DOM_WINDOW_UTILS, false}};
+
+ static size_t i = 0;
+ MOZ_RELEASE_ASSERT(i < std::size(invocations));
+ auto& invocation = invocations[i++];
+ if (i == std::size(invocations)) {
+ gSawAllGCCallbacks = true;
+ }
+ ASSERT_MSG(status == invocation.expectedStatus,
+ "GC callback #%zu: got status %d expected %d\n", i, status,
+ invocation.expectedStatus);
+ ASSERT_MSG(reason == invocation.expectedReason,
+ "GC callback #%zu: got reason %s expected %s\n", i,
+ ExplainGCReason(reason),
+ ExplainGCReason(invocation.expectedReason));
+ if (invocation.fireGC) {
+ if (invocation.requestFullGC) {
+ JS::PrepareForFullGC(cx);
+ }
+ js::SliceBudget budget = js::SliceBudget(js::WorkBudget(1));
+ cx->runtime()->gc.startGC(GCOptions::Normal, invocation.reason, budget);
+ MOZ_RELEASE_ASSERT(JS::IsIncrementalGCInProgress(cx));
+
+ JS::FinishIncrementalGC(cx, invocation.reason);
+ MOZ_RELEASE_ASSERT(!JS::IsIncrementalGCInProgress(cx));
+ }
+}
+
+static void GCTreeSliceCallback(JSContext* cx, JS::GCProgress progress,
+ const JS::GCDescription& desc) {
+ using namespace JS;
+
+ static constexpr struct {
+ GCProgress progress;
+ GCReason reason;
+ bool isZonal;
+ } expectations[] = {
+ // JSGC_BEGIN triggers a new GC before we get any slice callbacks from the
+ // original outer GC. So the very first reason observed is API, not
+ // DEBUG_GC.
+ {GC_CYCLE_BEGIN, GCReason::API, true},
+ {GC_SLICE_BEGIN, GCReason::API, true},
+ {GC_SLICE_END, GCReason::API, true},
+ {GC_SLICE_BEGIN, GCReason::API, true},
+ {GC_SLICE_END, GCReason::API, true},
+ {GC_CYCLE_END, GCReason::API, true},
+ // Now the "outer" GC runs. It requested a full GC.
+ {GC_CYCLE_BEGIN, GCReason::DEBUG_GC, false},
+ {GC_SLICE_BEGIN, GCReason::DEBUG_GC, false},
+ {GC_SLICE_END, GCReason::DEBUG_GC, false},
+ {GC_SLICE_BEGIN, GCReason::DEBUG_GC, false},
+ {GC_SLICE_END, GCReason::DEBUG_GC, false},
+ {GC_CYCLE_END, GCReason::DEBUG_GC, false},
+ // The outer JSGC_DEBUG GC's end callback triggers a full MEM_PRESSURE
+ // GC, which runs next. (Its JSGC_BEGIN does not run a GC.)
+ {GC_CYCLE_BEGIN, GCReason::MEM_PRESSURE, false},
+ {GC_SLICE_BEGIN, GCReason::MEM_PRESSURE, false},
+ {GC_SLICE_END, GCReason::MEM_PRESSURE, false},
+ {GC_SLICE_BEGIN, GCReason::MEM_PRESSURE, false},
+ {GC_SLICE_END, GCReason::MEM_PRESSURE, false},
+ {GC_CYCLE_END, GCReason::MEM_PRESSURE, false},
+ // The MEM_PRESSURE's GC's end callback now triggers a (zonal)
+ // DOM_WINDOW_UTILS GC.
+ {GC_CYCLE_BEGIN, GCReason::DOM_WINDOW_UTILS, true},
+ {GC_SLICE_BEGIN, GCReason::DOM_WINDOW_UTILS, true},
+ {GC_SLICE_END, GCReason::DOM_WINDOW_UTILS, true},
+ {GC_SLICE_BEGIN, GCReason::DOM_WINDOW_UTILS, true},
+ {GC_SLICE_END, GCReason::DOM_WINDOW_UTILS, true},
+ {GC_CYCLE_END, GCReason::DOM_WINDOW_UTILS, true},
+ // All done.
+ };
+
+ MOZ_RELEASE_ASSERT(gSliceCallbackCount < std::size(expectations));
+ auto& expect = expectations[gSliceCallbackCount];
+ ASSERT_MSG(progress == expect.progress, "iteration %d: wrong progress\n",
+ gSliceCallbackCount);
+ ASSERT_MSG(desc.reason_ == expect.reason,
+ "iteration %d: expected %s got %s\n", gSliceCallbackCount,
+ JS::ExplainGCReason(expect.reason),
+ JS::ExplainGCReason(desc.reason_));
+ ASSERT_MSG(desc.isZone_ == expect.isZonal, "iteration %d: wrong zonal\n",
+ gSliceCallbackCount);
+ MOZ_RELEASE_ASSERT(desc.options_ == JS::GCOptions::Normal);
+ gSliceCallbackCount++;
+ if (gSliceCallbackCount == std::size(expectations)) {
+ gSawAllSliceCallbacks = true;
+ }
+}
+
+BEGIN_TEST(testGCTree) {
+ AutoLeaveZeal nozeal(cx);
+
+ AutoGCParameter param1(cx, JSGC_INCREMENTAL_GC_ENABLED, true);
+
+ gSliceCallbackCount = 0;
+ gSawAllSliceCallbacks = false;
+ gSawAllGCCallbacks = false;
+ JS::SetGCSliceCallback(cx, GCTreeSliceCallback);
+ JS_SetGCCallback(cx, GCTreeCallback, nullptr);
+
+ // Automate the callback clearing. Otherwise if a CHECK fails, it will get
+ // cluttered with additional failures from the callback unexpectedly firing
+ // during the final shutdown GC.
+ auto byebye = mozilla::MakeScopeExit([=] {
+ JS::SetGCSliceCallback(cx, nullptr);
+ JS_SetGCCallback(cx, nullptr, nullptr);
+ });
+
+ JS::RootedObject obj(cx, JS_NewPlainObject(cx));
+ CHECK(obj);
+
+ // Outer GC is a full GC.
+ JS::PrepareForFullGC(cx);
+ js::SliceBudget budget(js::WorkBudget(1));
+ cx->runtime()->gc.startDebugGC(JS::GCOptions::Normal, budget);
+ CHECK(JS::IsIncrementalGCInProgress(cx));
+
+ JS::FinishIncrementalGC(cx, JS::GCReason::DEBUG_GC);
+ CHECK(!JS::IsIncrementalGCInProgress(cx));
+ CHECK(gSawAllSliceCallbacks);
+ CHECK(gSawAllGCCallbacks);
+
+ return true;
+}
+END_TEST(testGCTree)
diff --git a/js/src/jsapi-tests/testGCMarking.cpp b/js/src/jsapi-tests/testGCMarking.cpp
new file mode 100644
index 0000000000..dc2f1e0f4d
--- /dev/null
+++ b/js/src/jsapi-tests/testGCMarking.cpp
@@ -0,0 +1,448 @@
+/* -*- 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 "js/GlobalObject.h" // JS_NewGlobalObject
+#include "js/PropertyAndElement.h" // JS_DefineProperty, JS_GetProperty, JS_SetProperty
+#include "js/RootingAPI.h"
+#include "js/SliceBudget.h"
+#include "jsapi-tests/tests.h"
+#include "vm/Compartment.h"
+#include "vm/Realm.h"
+
+using namespace js;
+
+static bool ConstructCCW(JSContext* cx, const JSClass* globalClasp,
+ JS::HandleObject global1,
+ JS::MutableHandleObject wrapper,
+ JS::MutableHandleObject global2,
+ JS::MutableHandleObject wrappee) {
+ if (!global1) {
+ fprintf(stderr, "null initial global");
+ return false;
+ }
+
+ // Define a second global in a different zone.
+ JS::RealmOptions options;
+ global2.set(JS_NewGlobalObject(cx, globalClasp, nullptr,
+ JS::FireOnNewGlobalHook, options));
+ if (!global2) {
+ fprintf(stderr, "failed to create second global");
+ return false;
+ }
+
+ // This should always be false, regardless.
+ if (global1->compartment() == global2->compartment()) {
+ fprintf(stderr, "second global claims to be in global1's compartment");
+ return false;
+ }
+
+ // This checks that the API obeys the implicit zone request.
+ if (global1->zone() == global2->zone()) {
+ fprintf(stderr, "global2 is in global1's zone");
+ return false;
+ }
+
+ // Define an object in compartment 2, that is wrapped by a CCW into
+ // compartment 1.
+ {
+ JSAutoRealm ar(cx, global2);
+ wrappee.set(JS_NewPlainObject(cx));
+ if (wrappee->compartment() != global2->compartment()) {
+ fprintf(stderr, "wrappee in wrong compartment");
+ return false;
+ }
+ }
+
+ wrapper.set(wrappee);
+ if (!JS_WrapObject(cx, wrapper)) {
+ fprintf(stderr, "failed to wrap");
+ return false;
+ }
+ if (wrappee == wrapper) {
+ fprintf(stderr, "expected wrapping");
+ return false;
+ }
+ if (wrapper->compartment() != global1->compartment()) {
+ fprintf(stderr, "wrapper in wrong compartment");
+ return false;
+ }
+
+ return true;
+}
+
+class CCWTestTracer final : public JS::CallbackTracer {
+ void onChild(JS::GCCellPtr thing, const char* name) override {
+ numberOfThingsTraced++;
+
+ printf("*thingp = %p\n", thing.asCell());
+ printf("*expectedThingp = %p\n", *expectedThingp);
+
+ printf("kind = %d\n", static_cast<int>(thing.kind()));
+ printf("expectedKind = %d\n", static_cast<int>(expectedKind));
+
+ if (thing.asCell() != *expectedThingp || thing.kind() != expectedKind) {
+ okay = false;
+ }
+ }
+
+ public:
+ bool okay;
+ size_t numberOfThingsTraced;
+ void** expectedThingp;
+ JS::TraceKind expectedKind;
+
+ CCWTestTracer(JSContext* cx, void** expectedThingp,
+ JS::TraceKind expectedKind)
+ : JS::CallbackTracer(cx),
+ okay(true),
+ numberOfThingsTraced(0),
+ expectedThingp(expectedThingp),
+ expectedKind(expectedKind) {}
+};
+
+BEGIN_TEST(testTracingIncomingCCWs) {
+#ifdef JS_GC_ZEAL
+ // Disable zeal modes because this test needs to control exactly when the GC
+ // happens.
+ JS_SetGCZeal(cx, 0, 100);
+#endif
+ JS_GC(cx);
+
+ JS::RootedObject global1(cx, JS::CurrentGlobalOrNull(cx));
+ JS::RootedObject wrapper(cx, JS::CurrentGlobalOrNull(cx));
+ JS::RootedObject global2(cx, JS::CurrentGlobalOrNull(cx));
+ JS::RootedObject wrappee(cx, JS::CurrentGlobalOrNull(cx));
+ CHECK(ConstructCCW(cx, getGlobalClass(), global1, &wrapper, &global2,
+ &wrappee));
+ JS_GC(cx);
+ CHECK(!js::gc::IsInsideNursery(wrappee));
+ CHECK(!js::gc::IsInsideNursery(wrapper));
+
+ JS::RootedValue v(cx, JS::ObjectValue(*wrapper));
+ CHECK(JS_SetProperty(cx, global1, "ccw", v));
+
+ // Ensure that |TraceIncomingCCWs| finds the object wrapped by the CCW.
+
+ JS::CompartmentSet compartments;
+ CHECK(compartments.put(global2->compartment()));
+
+ void* thing = wrappee.get();
+ CCWTestTracer trc(cx, &thing, JS::TraceKind::Object);
+ js::gc::TraceIncomingCCWs(&trc, compartments);
+ CHECK(trc.numberOfThingsTraced == 1);
+ CHECK(trc.okay);
+
+ return true;
+}
+END_TEST(testTracingIncomingCCWs)
+
+static size_t countObjectWrappers(JS::Compartment* comp) {
+ size_t count = 0;
+ for (JS::Compartment::ObjectWrapperEnum e(comp); !e.empty(); e.popFront()) {
+ ++count;
+ }
+ return count;
+}
+
+BEGIN_TEST(testDeadNurseryCCW) {
+#ifdef JS_GC_ZEAL
+ // Disable zeal modes because this test needs to control exactly when the GC
+ // happens.
+ JS_SetGCZeal(cx, 0, 100);
+#endif
+ JS_GC(cx);
+
+ JS::RootedObject global1(cx, JS::CurrentGlobalOrNull(cx));
+ JS::RootedObject wrapper(cx, JS::CurrentGlobalOrNull(cx));
+ JS::RootedObject global2(cx, JS::CurrentGlobalOrNull(cx));
+ JS::RootedObject wrappee(cx, JS::CurrentGlobalOrNull(cx));
+ CHECK(ConstructCCW(cx, getGlobalClass(), global1, &wrapper, &global2,
+ &wrappee));
+ CHECK(js::gc::IsInsideNursery(wrappee));
+ CHECK(js::gc::IsInsideNursery(wrapper));
+
+ // Now let the obj and wrapper die.
+ wrappee = wrapper = nullptr;
+
+ // Now a GC should clear the CCW.
+ CHECK(countObjectWrappers(global1->compartment()) == 1);
+ cx->runtime()->gc.evictNursery();
+ CHECK(countObjectWrappers(global1->compartment()) == 0);
+
+ // Check for corruption of the CCW table by doing a full GC to force sweeping.
+ JS_GC(cx);
+
+ return true;
+}
+END_TEST(testDeadNurseryCCW)
+
+BEGIN_TEST(testLiveNurseryCCW) {
+#ifdef JS_GC_ZEAL
+ // Disable zeal modes because this test needs to control exactly when the GC
+ // happens.
+ JS_SetGCZeal(cx, 0, 100);
+#endif
+ JS_GC(cx);
+
+ JS::RootedObject global1(cx, JS::CurrentGlobalOrNull(cx));
+ JS::RootedObject wrapper(cx, JS::CurrentGlobalOrNull(cx));
+ JS::RootedObject global2(cx, JS::CurrentGlobalOrNull(cx));
+ JS::RootedObject wrappee(cx, JS::CurrentGlobalOrNull(cx));
+ CHECK(ConstructCCW(cx, getGlobalClass(), global1, &wrapper, &global2,
+ &wrappee));
+ CHECK(js::gc::IsInsideNursery(wrappee));
+ CHECK(js::gc::IsInsideNursery(wrapper));
+
+ // Now a GC should not kill the CCW.
+ CHECK(countObjectWrappers(global1->compartment()) == 1);
+ cx->runtime()->gc.evictNursery();
+ CHECK(countObjectWrappers(global1->compartment()) == 1);
+
+ CHECK(!js::gc::IsInsideNursery(wrappee));
+ CHECK(!js::gc::IsInsideNursery(wrapper));
+
+ // Check for corruption of the CCW table by doing a full GC to force sweeping.
+ JS_GC(cx);
+
+ return true;
+}
+END_TEST(testLiveNurseryCCW)
+
+BEGIN_TEST(testLiveNurseryWrapperCCW) {
+#ifdef JS_GC_ZEAL
+ // Disable zeal modes because this test needs to control exactly when the GC
+ // happens.
+ JS_SetGCZeal(cx, 0, 100);
+#endif
+ JS_GC(cx);
+
+ JS::RootedObject global1(cx, JS::CurrentGlobalOrNull(cx));
+ JS::RootedObject wrapper(cx, JS::CurrentGlobalOrNull(cx));
+ JS::RootedObject global2(cx, JS::CurrentGlobalOrNull(cx));
+ JS::RootedObject wrappee(cx, JS::CurrentGlobalOrNull(cx));
+ CHECK(ConstructCCW(cx, getGlobalClass(), global1, &wrapper, &global2,
+ &wrappee));
+ CHECK(js::gc::IsInsideNursery(wrappee));
+ CHECK(js::gc::IsInsideNursery(wrapper));
+
+ // The wrapper contains a strong reference to the wrappee, so just dropping
+ // the reference to the wrappee will not drop the CCW table entry as long
+ // as the wrapper is held strongly. Thus, the minor collection here must
+ // tenure both the wrapper and the wrappee and keep both in the table.
+ wrappee = nullptr;
+
+ // Now a GC should not kill the CCW.
+ CHECK(countObjectWrappers(global1->compartment()) == 1);
+ cx->runtime()->gc.evictNursery();
+ CHECK(countObjectWrappers(global1->compartment()) == 1);
+
+ CHECK(!js::gc::IsInsideNursery(wrapper));
+
+ // Check for corruption of the CCW table by doing a full GC to force sweeping.
+ JS_GC(cx);
+
+ return true;
+}
+END_TEST(testLiveNurseryWrapperCCW)
+
+BEGIN_TEST(testLiveNurseryWrappeeCCW) {
+#ifdef JS_GC_ZEAL
+ // Disable zeal modes because this test needs to control exactly when the GC
+ // happens.
+ JS_SetGCZeal(cx, 0, 100);
+#endif
+ JS_GC(cx);
+
+ JS::RootedObject global1(cx, JS::CurrentGlobalOrNull(cx));
+ JS::RootedObject wrapper(cx, JS::CurrentGlobalOrNull(cx));
+ JS::RootedObject global2(cx, JS::CurrentGlobalOrNull(cx));
+ JS::RootedObject wrappee(cx, JS::CurrentGlobalOrNull(cx));
+ CHECK(ConstructCCW(cx, getGlobalClass(), global1, &wrapper, &global2,
+ &wrappee));
+ CHECK(js::gc::IsInsideNursery(wrappee));
+ CHECK(js::gc::IsInsideNursery(wrapper));
+
+ // Let the wrapper die. The wrapper should drop from the table when we GC,
+ // even though there are other non-cross-compartment edges to it.
+ wrapper = nullptr;
+
+ // Now a GC should not kill the CCW.
+ CHECK(countObjectWrappers(global1->compartment()) == 1);
+ cx->runtime()->gc.evictNursery();
+ CHECK(countObjectWrappers(global1->compartment()) == 0);
+
+ CHECK(!js::gc::IsInsideNursery(wrappee));
+
+ // Check for corruption of the CCW table by doing a full GC to force sweeping.
+ JS_GC(cx);
+
+ return true;
+}
+END_TEST(testLiveNurseryWrappeeCCW)
+
+BEGIN_TEST(testIncrementalRoots) {
+ JSRuntime* rt = cx->runtime();
+
+#ifdef JS_GC_ZEAL
+ // Disable zeal modes because this test needs to control exactly when the GC
+ // happens.
+ JS_SetGCZeal(cx, 0, 100);
+#endif
+
+ // Construct a big object graph to mark. In JS, the resulting object graph
+ // is equivalent to:
+ //
+ // leaf = {};
+ // leaf2 = {};
+ // root = { 'obj': { 'obj': ... { 'obj': leaf, 'leaf2': leaf2 } ... } }
+ //
+ // with leafOwner the object that has the 'obj' and 'leaf2' properties.
+
+ JS::RootedObject obj(cx, JS_NewObject(cx, nullptr));
+ if (!obj) {
+ return false;
+ }
+
+ JS::RootedObject root(cx, obj);
+
+ JS::RootedObject leaf(cx);
+ JS::RootedObject leafOwner(cx);
+
+ for (size_t i = 0; i < 3000; i++) {
+ JS::RootedObject subobj(cx, JS_NewObject(cx, nullptr));
+ if (!subobj) {
+ return false;
+ }
+ if (!JS_DefineProperty(cx, obj, "obj", subobj, 0)) {
+ return false;
+ }
+ leafOwner = obj;
+ obj = subobj;
+ leaf = subobj;
+ }
+
+ // Give the leaf owner a second leaf.
+ {
+ JS::RootedObject leaf2(cx, JS_NewObject(cx, nullptr));
+ if (!leaf2) {
+ return false;
+ }
+ if (!JS_DefineProperty(cx, leafOwner, "leaf2", leaf2, 0)) {
+ return false;
+ }
+ }
+
+ // This is marked during markRuntime
+ JS::RootedObjectVector vec(cx);
+ if (!vec.append(root)) {
+ return false;
+ }
+
+ // Tenure everything so intentionally unrooted objects don't move before we
+ // can use them.
+ cx->runtime()->gc.minorGC(JS::GCReason::API);
+
+ // Release all roots except for the RootedObjectVector.
+ obj = root = nullptr;
+
+ // We need to manipulate interior nodes, but the JSAPI understandably wants
+ // to make it difficult to do that without rooting things on the stack (by
+ // requiring Handle parameters). We can do it anyway by using
+ // fromMarkedLocation. The hazard analysis is OK with this because the
+ // unrooted variables are not live after they've been pointed to via
+ // fromMarkedLocation; you're essentially lying to the analysis, saying
+ // that the unrooted variables are rooted.
+ //
+ // The analysis will report this lie in its listing of "unsafe references",
+ // but we do not break the build based on those as there are too many false
+ // positives.
+ JSObject* unrootedLeaf = leaf;
+ JS::Value unrootedLeafValue = JS::ObjectValue(*leaf);
+ JSObject* unrootedLeafOwner = leafOwner;
+ JS::HandleObject leafHandle =
+ JS::HandleObject::fromMarkedLocation(&unrootedLeaf);
+ JS::HandleValue leafValueHandle =
+ JS::HandleValue::fromMarkedLocation(&unrootedLeafValue);
+ JS::HandleObject leafOwnerHandle =
+ JS::HandleObject::fromMarkedLocation(&unrootedLeafOwner);
+ leaf = leafOwner = nullptr;
+
+ // Do the root marking slice. This should mark 'root' and a bunch of its
+ // descendants. It shouldn't make it all the way through (it gets a budget
+ // of 1000, and the graph is about 3000 objects deep).
+ js::SliceBudget budget(js::WorkBudget(1000));
+ AutoGCParameter param(cx, JSGC_INCREMENTAL_GC_ENABLED, true);
+ rt->gc.startDebugGC(JS::GCOptions::Normal, budget);
+ while (rt->gc.state() != gc::State::Mark) {
+ rt->gc.debugGCSlice(budget);
+ }
+
+ // We'd better be between iGC slices now. There's always a risk that
+ // something will decide that we need to do a full GC (such as gczeal, but
+ // that is turned off.)
+ MOZ_ASSERT(JS::IsIncrementalGCInProgress(cx));
+
+ // And assert that the mark bits are as we expect them to be.
+ MOZ_ASSERT(vec[0]->asTenured().isMarkedBlack());
+ MOZ_ASSERT(!leafHandle->asTenured().isMarkedBlack());
+ MOZ_ASSERT(!leafOwnerHandle->asTenured().isMarkedBlack());
+
+#ifdef DEBUG
+ // Remember the current GC number so we can assert that no GC occurs
+ // between operations.
+ auto currentGCNumber = rt->gc.gcNumber();
+#endif
+
+ // Now do the incremental GC's worst nightmare: rip an unmarked object
+ // 'leaf' out of the graph and stick it into an already-marked region (hang
+ // it off the un-prebarriered root, in fact). The pre-barrier on the
+ // overwrite of the source location should cause this object to be marked.
+ if (!JS_SetProperty(cx, leafOwnerHandle, "obj", JS::UndefinedHandleValue)) {
+ return false;
+ }
+ MOZ_ASSERT(rt->gc.gcNumber() == currentGCNumber);
+ if (!JS_SetProperty(cx, vec[0], "newobj", leafValueHandle)) {
+ return false;
+ }
+ MOZ_ASSERT(rt->gc.gcNumber() == currentGCNumber);
+ MOZ_ASSERT(leafHandle->asTenured().isMarkedBlack());
+
+ // Also take an unmarked object 'leaf2' from the graph and add an
+ // additional edge from the root to it. This will not be marked by any
+ // pre-barrier, but it is still in the live graph so it will eventually get
+ // marked.
+ //
+ // Note that the root->leaf2 edge will *not* be marked through, since the
+ // root is already marked, but that only matters if doing a compacting GC
+ // and the compacting GC repeats the whole marking phase to update
+ // pointers.
+ {
+ JS::RootedValue leaf2(cx);
+ if (!JS_GetProperty(cx, leafOwnerHandle, "leaf2", &leaf2)) {
+ return false;
+ }
+ MOZ_ASSERT(rt->gc.gcNumber() == currentGCNumber);
+ MOZ_ASSERT(!leaf2.toObject().asTenured().isMarkedBlack());
+ if (!JS_SetProperty(cx, vec[0], "leafcopy", leaf2)) {
+ return false;
+ }
+ MOZ_ASSERT(rt->gc.gcNumber() == currentGCNumber);
+ MOZ_ASSERT(!leaf2.toObject().asTenured().isMarkedBlack());
+ }
+
+ // Finish the GC using an unlimited budget.
+ auto unlimited = js::SliceBudget::unlimited();
+ rt->gc.debugGCSlice(unlimited);
+
+ // Access the leaf object to try to trigger a crash if it is dead.
+ if (!JS_SetProperty(cx, leafHandle, "toes", JS::UndefinedHandleValue)) {
+ return false;
+ }
+
+ return true;
+}
+END_TEST(testIncrementalRoots)
diff --git a/js/src/jsapi-tests/testGCOutOfMemory.cpp b/js/src/jsapi-tests/testGCOutOfMemory.cpp
new file mode 100644
index 0000000000..bfd7079584
--- /dev/null
+++ b/js/src/jsapi-tests/testGCOutOfMemory.cpp
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ *
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ * Contributor: Igor Bukanov
+ */
+
+#include "mozilla/Utf8.h" // mozilla::Utf8Unit
+
+#include "gc/GCEnum.h" // js::gc::ZealMode
+#include "js/CompilationAndEvaluation.h" // JS::Evaluate
+#include "js/SourceText.h" // JS::Source{Ownership,Text}
+#include "jsapi-tests/tests.h"
+
+BEGIN_TEST(testGCOutOfMemory) {
+ // Count the number of allocations until we hit OOM, and store it in 'max'.
+ static const char source[] =
+ "var max = 0; (function() {"
+ " var array = [];"
+ " for (; ; ++max)"
+ " array.push({});"
+ " array = []; array.push(0);"
+ "})();";
+
+ JS::CompileOptions opts(cx);
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, source, strlen(source), JS::SourceOwnership::Borrowed));
+
+ JS::RootedValue root(cx);
+ bool ok = JS::Evaluate(cx, opts, srcBuf, &root);
+
+ /* Check that we get OOM. */
+ CHECK(!ok);
+ CHECK(JS_GetPendingException(cx, &root));
+ CHECK(root.isString());
+ bool match = false;
+ CHECK(JS_StringEqualsLiteral(cx, root.toString(), "out of memory", &match));
+ CHECK(match);
+ JS_ClearPendingException(cx);
+
+ JS_GC(cx);
+
+ // The above GC should have discarded everything. Verify that we can now
+ // allocate half as many objects without OOMing.
+ EVAL(
+ "(function() {"
+ " var array = [];"
+ " for (var i = max >> 2; i != 0;) {"
+ " --i;"
+ " array.push({});"
+ " }"
+ "})();",
+ &root);
+ CHECK(!JS_IsExceptionPending(cx));
+ return true;
+}
+
+virtual JSContext* createContext() override {
+ // Note that the max nursery size must be less than the whole heap size, or
+ // the test will fail because 'max' (the number of allocations required for
+ // OOM) will be based on the nursery size, and that will overflow the
+ // tenured heap, which will cause the second pass with max/4 allocations to
+ // OOM. (Actually, this only happens with nursery zeal, because normally
+ // the nursery will start out with only a single chunk before triggering a
+ // major GC.)
+ JSContext* cx = JS_NewContext(4 * 1024 * 1024);
+ if (!cx) {
+ return nullptr;
+ }
+ JS_SetGCParameter(cx, JSGC_MAX_NURSERY_BYTES, js::gc::ChunkSize);
+#ifdef JS_GC_ZEAL
+ JS_UnsetGCZeal(cx, uint8_t(js::gc::ZealMode::GenerationalGC));
+#endif
+ return cx;
+}
+
+END_TEST(testGCOutOfMemory)
diff --git a/js/src/jsapi-tests/testGCStoreBufferRemoval.cpp b/js/src/jsapi-tests/testGCStoreBufferRemoval.cpp
new file mode 100644
index 0000000000..47cd92b403
--- /dev/null
+++ b/js/src/jsapi-tests/testGCStoreBufferRemoval.cpp
@@ -0,0 +1,110 @@
+/* -*- 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 "gc/Barrier.h"
+#include "js/GCAPI.h"
+#include "jsapi-tests/tests.h"
+
+using namespace JS;
+using namespace js;
+
+// Name this constant without creating a GC hazard.
+#define BAD_OBJECT_PTR reinterpret_cast<JSObject*>(1)
+
+BEGIN_TEST(testGCStoreBufferRemoval) {
+ // Sanity check - objects start in the nursery and then become tenured.
+ JS_GC(cx);
+ JS::RootedObject obj(cx, NurseryObject());
+ CHECK(js::gc::IsInsideNursery(obj.get()));
+ JS_GC(cx);
+ CHECK(!js::gc::IsInsideNursery(obj.get()));
+ JS::RootedObject tenuredObject(cx, obj);
+
+ // Test removal of store buffer entries added by HeapPtr<T>.
+ {
+ JSObject* punnedPtr = nullptr;
+ HeapPtr<JSObject*>* relocPtr =
+ reinterpret_cast<HeapPtr<JSObject*>*>(&punnedPtr);
+ new (relocPtr) HeapPtr<JSObject*>;
+ *relocPtr = NurseryObject();
+ relocPtr->~HeapPtr<JSObject*>();
+ punnedPtr = BAD_OBJECT_PTR;
+ JS_GC(cx);
+
+ new (relocPtr) HeapPtr<JSObject*>;
+ *relocPtr = NurseryObject();
+ *relocPtr = tenuredObject;
+ relocPtr->~HeapPtr<JSObject*>();
+ punnedPtr = BAD_OBJECT_PTR;
+ JS_GC(cx);
+
+ new (relocPtr) HeapPtr<JSObject*>;
+ *relocPtr = NurseryObject();
+ *relocPtr = nullptr;
+ relocPtr->~HeapPtr<JSObject*>();
+ punnedPtr = BAD_OBJECT_PTR;
+ JS_GC(cx);
+ }
+
+ // Test removal of store buffer entries added by HeapPtr<Value>.
+ {
+ Value punnedValue;
+ HeapPtr<Value>* relocValue =
+ reinterpret_cast<HeapPtr<Value>*>(&punnedValue);
+ new (relocValue) HeapPtr<Value>;
+ *relocValue = ObjectValue(*NurseryObject());
+ relocValue->~HeapPtr<Value>();
+ punnedValue = js::PoisonedObjectValue(0x48);
+ JS_GC(cx);
+
+ new (relocValue) HeapPtr<Value>;
+ *relocValue = ObjectValue(*NurseryObject());
+ *relocValue = ObjectValue(*tenuredObject);
+ relocValue->~HeapPtr<Value>();
+ punnedValue = js::PoisonedObjectValue(0x48);
+ JS_GC(cx);
+
+ new (relocValue) HeapPtr<Value>;
+ *relocValue = ObjectValue(*NurseryObject());
+ *relocValue = NullValue();
+ relocValue->~HeapPtr<Value>();
+ punnedValue = js::PoisonedObjectValue(0x48);
+ JS_GC(cx);
+ }
+
+ // Test removal of store buffer entries added by Heap<T>.
+ {
+ JSObject* punnedPtr = nullptr;
+ Heap<JSObject*>* heapPtr = reinterpret_cast<Heap<JSObject*>*>(&punnedPtr);
+ new (heapPtr) Heap<JSObject*>;
+ *heapPtr = NurseryObject();
+ heapPtr->~Heap<JSObject*>();
+ punnedPtr = BAD_OBJECT_PTR;
+ JS_GC(cx);
+
+ new (heapPtr) Heap<JSObject*>;
+ *heapPtr = NurseryObject();
+ *heapPtr = tenuredObject;
+ heapPtr->~Heap<JSObject*>();
+ punnedPtr = BAD_OBJECT_PTR;
+ JS_GC(cx);
+
+ new (heapPtr) Heap<JSObject*>;
+ *heapPtr = NurseryObject();
+ *heapPtr = nullptr;
+ heapPtr->~Heap<JSObject*>();
+ punnedPtr = BAD_OBJECT_PTR;
+ JS_GC(cx);
+ }
+
+ return true;
+}
+
+JSObject* NurseryObject() { return JS_NewPlainObject(cx); }
+END_TEST(testGCStoreBufferRemoval)
+
+#undef BAD_OBJECT_PTR
diff --git a/js/src/jsapi-tests/testGCUniqueId.cpp b/js/src/jsapi-tests/testGCUniqueId.cpp
new file mode 100644
index 0000000000..1c5652f280
--- /dev/null
+++ b/js/src/jsapi-tests/testGCUniqueId.cpp
@@ -0,0 +1,125 @@
+/* -*- 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 "js/GCVector.h"
+
+#include "jsapi-tests/tests.h"
+
+#include "gc/StableCellHasher-inl.h"
+
+using namespace js;
+
+static void MinimizeHeap(JSContext* cx) {
+ // The second collection is to force us to wait for the background
+ // sweeping that the first GC started to finish.
+ JS_GC(cx);
+ JS_GC(cx);
+ js::gc::FinishGC(cx);
+}
+
+BEGIN_TEST(testGCUID) {
+ AutoLeaveZeal nozeal(cx);
+
+ uint64_t uid = 0;
+ uint64_t tmp = 0;
+
+ // Ensure the heap is as minimal as it can get.
+ MinimizeHeap(cx);
+
+ JS::RootedObject obj(cx, JS_NewPlainObject(cx));
+ uintptr_t nurseryAddr = uintptr_t(obj.get());
+ CHECK(obj);
+ CHECK(gc::IsInsideNursery(obj));
+
+ // Do not start with an ID.
+ CHECK(!gc::HasUniqueId(obj));
+
+ // Ensure we can get a new UID.
+ CHECK(gc::GetOrCreateUniqueId(obj, &uid));
+ CHECK(uid > gc::LargestTaggedNullCellPointer);
+
+ // We should now have an id.
+ CHECK(gc::HasUniqueId(obj));
+
+ // Calling again should get us the same thing.
+ CHECK(gc::GetOrCreateUniqueId(obj, &tmp));
+ CHECK(uid == tmp);
+
+ // Tenure the thing and check that the UID moved with it.
+ MinimizeHeap(cx);
+ uintptr_t tenuredAddr = uintptr_t(obj.get());
+ CHECK(tenuredAddr != nurseryAddr);
+ CHECK(!gc::IsInsideNursery(obj));
+ CHECK(gc::HasUniqueId(obj));
+ CHECK(gc::GetOrCreateUniqueId(obj, &tmp));
+ CHECK(uid == tmp);
+
+ // Allocate a new nursery thing in the same location and check that we
+ // removed the prior uid that was attached to the location.
+ obj = JS_NewPlainObject(cx);
+ CHECK(obj);
+ CHECK(uintptr_t(obj.get()) == nurseryAddr);
+ CHECK(!gc::HasUniqueId(obj));
+
+ // Try to get another tenured object in the same location and check that
+ // the uid was removed correctly.
+ obj = nullptr;
+ MinimizeHeap(cx);
+ obj = JS_NewPlainObject(cx);
+ MinimizeHeap(cx);
+ CHECK(uintptr_t(obj.get()) == tenuredAddr);
+ CHECK(!gc::HasUniqueId(obj));
+ CHECK(gc::GetOrCreateUniqueId(obj, &tmp));
+ CHECK(uid != tmp);
+ uid = tmp;
+
+ // Allocate a few arenas worth of objects to ensure we get some compaction.
+ const static size_t N = 2049;
+ using ObjectVector = JS::GCVector<JSObject*>;
+ JS::Rooted<ObjectVector> vec(cx, ObjectVector(cx));
+ for (size_t i = 0; i < N; ++i) {
+ obj = JS_NewPlainObject(cx);
+ CHECK(obj);
+ CHECK(vec.append(obj));
+ }
+
+ // Transfer our vector to tenured if it isn't there already.
+ MinimizeHeap(cx);
+
+ // Tear holes in the heap by unrooting the even objects and collecting.
+ JS::Rooted<ObjectVector> vec2(cx, ObjectVector(cx));
+ for (size_t i = 0; i < N; ++i) {
+ if (i % 2 == 1) {
+ CHECK(vec2.append(vec[i]));
+ }
+ }
+ vec.clear();
+
+ // Grab the last object in the vector as our object of interest.
+ obj = vec2.back();
+ CHECK(obj);
+ CHECK(!gc::IsInsideNursery(obj));
+ tenuredAddr = uintptr_t(obj.get());
+ CHECK(gc::GetOrCreateUniqueId(obj, &uid));
+
+ // Force a compaction to move the object and check that the uid moved to
+ // the new tenured heap location.
+ JS::PrepareForFullGC(cx);
+ JS::NonIncrementalGC(cx, JS::GCOptions::Shrink, JS::GCReason::API);
+
+ // There's a very low probability that this check could fail, but it is
+ // possible. If it becomes an annoying intermittent then we should make
+ // this test more robust by recording IDs of many objects and then checking
+ // that some have moved.
+ CHECK(uintptr_t(obj.get()) != tenuredAddr);
+ CHECK(gc::HasUniqueId(obj));
+ CHECK(gc::GetOrCreateUniqueId(obj, &tmp));
+ CHECK(uid == tmp);
+
+ return true;
+}
+END_TEST(testGCUID)
diff --git a/js/src/jsapi-tests/testGCWeakCache.cpp b/js/src/jsapi-tests/testGCWeakCache.cpp
new file mode 100644
index 0000000000..de2a7c8636
--- /dev/null
+++ b/js/src/jsapi-tests/testGCWeakCache.cpp
@@ -0,0 +1,698 @@
+/* -*- 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 "gc/Policy.h"
+#include "gc/Zone.h"
+#include "js/GCHashTable.h"
+#include "js/RootingAPI.h"
+#include "js/SweepingAPI.h"
+
+#include "jsapi-tests/tests.h"
+
+using namespace js;
+
+// Exercise WeakCache<GCHashSet>.
+BEGIN_TEST(testWeakCacheSet) {
+ // Create two objects tenured and two in the nursery. If zeal is on,
+ // this may fail and we'll get more tenured objects. That's fine:
+ // the test will continue to work, it will just not test as much.
+ JS::RootedObject tenured1(cx, JS_NewPlainObject(cx));
+ JS::RootedObject tenured2(cx, JS_NewPlainObject(cx));
+ JS_GC(cx);
+ JS::RootedObject nursery1(cx, JS_NewPlainObject(cx));
+ JS::RootedObject nursery2(cx, JS_NewPlainObject(cx));
+
+ using ObjectSet =
+ GCHashSet<HeapPtr<JSObject*>, StableCellHasher<HeapPtr<JSObject*>>,
+ SystemAllocPolicy>;
+ using Cache = JS::WeakCache<ObjectSet>;
+ Cache cache(JS::GetObjectZone(tenured1));
+
+ cache.put(tenured1);
+ cache.put(tenured2);
+ cache.put(nursery1);
+ cache.put(nursery2);
+
+ // Verify relocation and that we don't sweep too aggressively.
+ JS_GC(cx);
+ CHECK(cache.has(tenured1));
+ CHECK(cache.has(tenured2));
+ CHECK(cache.has(nursery1));
+ CHECK(cache.has(nursery2));
+
+ // Unroot two entries and verify that they get removed.
+ tenured2 = nursery2 = nullptr;
+ JS_GC(cx);
+ CHECK(cache.has(tenured1));
+ CHECK(cache.has(nursery1));
+ CHECK(cache.count() == 2);
+
+ return true;
+}
+END_TEST(testWeakCacheSet)
+
+// Exercise WeakCache<GCHashMap>.
+BEGIN_TEST(testWeakCacheMap) {
+ // Create two objects tenured and two in the nursery. If zeal is on,
+ // this may fail and we'll get more tenured objects. That's fine:
+ // the test will continue to work, it will just not test as much.
+ JS::RootedObject tenured1(cx, JS_NewPlainObject(cx));
+ JS::RootedObject tenured2(cx, JS_NewPlainObject(cx));
+ JS_GC(cx);
+ JS::RootedObject nursery1(cx, JS_NewPlainObject(cx));
+ JS::RootedObject nursery2(cx, JS_NewPlainObject(cx));
+
+ using ObjectMap = js::GCHashMap<HeapPtr<JSObject*>, uint32_t,
+ js::StableCellHasher<HeapPtr<JSObject*>>>;
+ using Cache = JS::WeakCache<ObjectMap>;
+ Cache cache(JS::GetObjectZone(tenured1), cx);
+
+ cache.put(tenured1, 1);
+ cache.put(tenured2, 2);
+ cache.put(nursery1, 3);
+ cache.put(nursery2, 4);
+
+ JS_GC(cx);
+ CHECK(cache.has(tenured1));
+ CHECK(cache.has(tenured2));
+ CHECK(cache.has(nursery1));
+ CHECK(cache.has(nursery2));
+
+ tenured2 = nursery2 = nullptr;
+ JS_GC(cx);
+ CHECK(cache.has(tenured1));
+ CHECK(cache.has(nursery1));
+ CHECK(cache.count() == 2);
+
+ return true;
+}
+END_TEST(testWeakCacheMap)
+
+// Exercise WeakCache<GCVector>.
+BEGIN_TEST(testWeakCacheGCVector) {
+ // Create two objects tenured and two in the nursery. If zeal is on,
+ // this may fail and we'll get more tenured objects. That's fine:
+ // the test will continue to work, it will just not test as much.
+ JS::RootedObject tenured1(cx, JS_NewPlainObject(cx));
+ JS::RootedObject tenured2(cx, JS_NewPlainObject(cx));
+ JS_GC(cx);
+ JS::RootedObject nursery1(cx, JS_NewPlainObject(cx));
+ JS::RootedObject nursery2(cx, JS_NewPlainObject(cx));
+
+ using ObjectVector = JS::WeakCache<GCVector<HeapPtr<JSObject*>>>;
+ ObjectVector cache(JS::GetObjectZone(tenured1), cx);
+
+ CHECK(cache.append(tenured1));
+ CHECK(cache.append(tenured2));
+ CHECK(cache.append(nursery1));
+ CHECK(cache.append(nursery2));
+
+ JS_GC(cx);
+ CHECK(cache.get().length() == 4);
+ CHECK(cache.get()[0] == tenured1);
+ CHECK(cache.get()[1] == tenured2);
+ CHECK(cache.get()[2] == nursery1);
+ CHECK(cache.get()[3] == nursery2);
+
+ tenured2 = nursery2 = nullptr;
+ JS_GC(cx);
+ CHECK(cache.get().length() == 2);
+ CHECK(cache.get()[0] == tenured1);
+ CHECK(cache.get()[1] == nursery1);
+
+ return true;
+}
+END_TEST(testWeakCacheGCVector)
+
+#ifdef JS_GC_ZEAL
+
+// A simple structure that embeds an object pointer. We cripple the hash
+// implementation so that we can test hash table collisions.
+struct ObjectEntry {
+ HeapPtr<JSObject*> obj;
+ explicit ObjectEntry(JSObject* o) : obj(o) {}
+ bool operator==(const ObjectEntry& other) const { return obj == other.obj; }
+ bool traceWeak(JSTracer* trc) {
+ return TraceWeakEdge(trc, &obj, "ObjectEntry::obj");
+ }
+};
+
+namespace js {
+template <>
+struct StableCellHasher<ObjectEntry> {
+ using Key = ObjectEntry;
+ using Lookup = JSObject*;
+
+ static bool maybeGetHash(const Lookup& l, HashNumber* hashOut) {
+ if (!StableCellHasher<JSObject*>::maybeGetHash(l, hashOut)) {
+ return false;
+ }
+ // Reduce hash code to single bit to generate hash collisions.
+ *hashOut &= 0x1;
+ return true;
+ }
+ static bool ensureHash(const Lookup& l, HashNumber* hashOut) {
+ if (!StableCellHasher<JSObject*>::ensureHash(l, hashOut)) {
+ return false;
+ }
+ // Reduce hash code to single bit to generate hash collisions.
+ *hashOut &= 0x1;
+ return true;
+ }
+ static HashNumber hash(const Lookup& l) {
+ // Reduce hash code to single bit to generate hash collisions.
+ return StableCellHasher<HeapPtr<JSObject*>>::hash(l) & 0x1;
+ }
+ static bool match(const Key& k, const Lookup& l) {
+ return StableCellHasher<HeapPtr<JSObject*>>::match(k.obj, l);
+ }
+};
+} // namespace js
+
+// A structure that contains a pointer to a JSObject but is keyed based on an
+// integer. This lets us test replacing dying entries in a set.
+struct NumberAndObjectEntry {
+ uint32_t number;
+ HeapPtr<JSObject*> obj;
+
+ NumberAndObjectEntry(uint32_t n, JSObject* o) : number(n), obj(o) {}
+ bool operator==(const NumberAndObjectEntry& other) const {
+ return number == other.number && obj == other.obj;
+ }
+ bool traceWeak(JSTracer* trc) {
+ return TraceWeakEdge(trc, &obj, "NumberAndObjectEntry::obj");
+ }
+};
+
+struct NumberAndObjectLookup {
+ uint32_t number;
+ HeapPtr<JSObject*> obj;
+
+ NumberAndObjectLookup(uint32_t n, JSObject* o) : number(n), obj(o) {}
+ MOZ_IMPLICIT NumberAndObjectLookup(const NumberAndObjectEntry& entry)
+ : number(entry.number), obj(entry.obj) {}
+};
+
+namespace js {
+template <>
+struct StableCellHasher<NumberAndObjectEntry> {
+ using Key = NumberAndObjectEntry;
+ using Lookup = NumberAndObjectLookup;
+
+ static bool maybeGetHash(const Lookup& l, HashNumber* hashOut) {
+ if (!StableCellHasher<JSObject*>::maybeGetHash(l.obj, hashOut)) {
+ return false;
+ }
+ *hashOut ^= l.number;
+ return true;
+ }
+ static bool ensureHash(const Lookup& l, HashNumber* hashOut) {
+ if (!StableCellHasher<JSObject*>::ensureHash(l.obj, hashOut)) {
+ return false;
+ }
+ *hashOut ^= l.number;
+ return true;
+ }
+ static HashNumber hash(const Lookup& l) {
+ return StableCellHasher<HeapPtr<JSObject*>>::hash(l.obj) ^ l.number;
+ }
+ static bool match(const Key& k, const Lookup& l) {
+ return k.number == l.number &&
+ StableCellHasher<HeapPtr<JSObject*>>::match(k.obj, l.obj);
+ }
+};
+} // namespace js
+
+BEGIN_TEST(testIncrementalWeakCacheSweeping) {
+ AutoLeaveZeal nozeal(cx);
+
+ JS_SetGCParameter(cx, JSGC_INCREMENTAL_GC_ENABLED, true);
+ JS_SetGCZeal(cx, 17, 1000000);
+
+ CHECK(TestSet());
+ CHECK(TestMap());
+ CHECK(TestReplaceDyingInSet());
+ CHECK(TestReplaceDyingInMap());
+ CHECK(TestUniqueIDLookups());
+
+ JS_SetGCZeal(cx, 0, 0);
+ JS_SetGCParameter(cx, JSGC_INCREMENTAL_GC_ENABLED, false);
+
+ return true;
+}
+
+template <typename Cache>
+bool GCUntilCacheSweep(JSContext* cx, const Cache& cache) {
+ CHECK(!IsIncrementalGCInProgress(cx));
+
+ JS::Zone* zone = JS::GetObjectZone(global);
+ JS::PrepareZoneForGC(cx, zone);
+ SliceBudget budget(WorkBudget(1));
+ cx->runtime()->gc.startDebugGC(JS::GCOptions::Normal, budget);
+
+ CHECK(IsIncrementalGCInProgress(cx));
+ CHECK(zone->isGCSweeping());
+ CHECK(cache.needsIncrementalBarrier());
+
+ return true;
+}
+
+template <typename Cache>
+bool SweepCacheAndFinishGC(JSContext* cx, const Cache& cache) {
+ CHECK(IsIncrementalGCInProgress(cx));
+
+ PrepareForIncrementalGC(cx);
+ IncrementalGCSlice(cx, JS::GCReason::API, SliceBudget::unlimited());
+
+ JS::Zone* zone = JS::GetObjectZone(global);
+ CHECK(!IsIncrementalGCInProgress(cx));
+ CHECK(!zone->isCollecting());
+ CHECK(!cache.needsIncrementalBarrier());
+
+ return true;
+}
+
+bool TestSet() {
+ using ObjectSet =
+ GCHashSet<HeapPtr<JSObject*>, StableCellHasher<HeapPtr<JSObject*>>,
+ TempAllocPolicy>;
+ using Cache = JS::WeakCache<ObjectSet>;
+ Cache cache(JS::GetObjectZone(global), cx);
+
+ // Sweep empty cache.
+
+ CHECK(cache.empty());
+ JS_GC(cx);
+ CHECK(cache.empty());
+
+ // Add an entry while sweeping.
+
+ JS::RootedObject obj1(cx, JS_NewPlainObject(cx));
+ JS::RootedObject obj2(cx, JS_NewPlainObject(cx));
+ JS::RootedObject obj3(cx, JS_NewPlainObject(cx));
+ JS::RootedObject obj4(cx, JS_NewPlainObject(cx));
+ CHECK(obj1);
+ CHECK(obj2);
+ CHECK(obj3);
+ CHECK(obj4);
+
+ CHECK(!cache.has(obj1));
+ CHECK(cache.put(obj1));
+ CHECK(cache.count() == 1);
+ CHECK(cache.has(obj1));
+ CHECK(*cache.lookup(obj1) == obj1);
+
+ CHECK(GCUntilCacheSweep(cx, cache));
+
+ CHECK(!cache.has(obj2));
+ CHECK(cache.put(obj2));
+ CHECK(cache.has(obj2));
+ CHECK(*cache.lookup(obj2) == obj2);
+
+ CHECK(SweepCacheAndFinishGC(cx, cache));
+
+ CHECK(cache.count() == 2);
+ CHECK(cache.has(obj1));
+ CHECK(cache.has(obj2));
+
+ // Test dying entries are not found while sweeping.
+
+ CHECK(cache.put(obj3));
+ CHECK(cache.put(obj4));
+ void* old3 = obj3;
+ void* old4 = obj4;
+ obj3 = obj4 = nullptr;
+
+ CHECK(GCUntilCacheSweep(cx, cache));
+
+ CHECK(cache.has(obj1));
+ CHECK(cache.has(obj2));
+ CHECK(!cache.has(static_cast<JSObject*>(old3)));
+ CHECK(!cache.has(static_cast<JSObject*>(old4)));
+
+ size_t count = 0;
+ for (auto r = cache.all(); !r.empty(); r.popFront()) {
+ CHECK(r.front() == obj1 || r.front() == obj2);
+ count++;
+ }
+ CHECK(count == 2);
+
+ CHECK(SweepCacheAndFinishGC(cx, cache));
+
+ CHECK(cache.count() == 2);
+
+ // Test lookupForAdd while sweeping.
+
+ obj3 = JS_NewPlainObject(cx);
+ obj4 = JS_NewPlainObject(cx);
+ CHECK(obj3);
+ CHECK(obj4);
+
+ CHECK(cache.lookupForAdd(obj1));
+ CHECK(*cache.lookupForAdd(obj1) == obj1);
+
+ auto addp = cache.lookupForAdd(obj3);
+ CHECK(!addp);
+ CHECK(cache.add(addp, obj3));
+ CHECK(cache.has(obj3));
+
+ CHECK(GCUntilCacheSweep(cx, cache));
+
+ addp = cache.lookupForAdd(obj4);
+ CHECK(!addp);
+ CHECK(cache.add(addp, obj4));
+ CHECK(cache.has(obj4));
+
+ CHECK(SweepCacheAndFinishGC(cx, cache));
+
+ CHECK(cache.count() == 4);
+ CHECK(cache.has(obj3));
+ CHECK(cache.has(obj4));
+
+ // Test remove while sweeping.
+
+ cache.remove(obj3);
+
+ CHECK(GCUntilCacheSweep(cx, cache));
+
+ cache.remove(obj4);
+
+ CHECK(SweepCacheAndFinishGC(cx, cache));
+
+ CHECK(cache.count() == 2);
+ CHECK(!cache.has(obj3));
+ CHECK(!cache.has(obj4));
+
+ // Test putNew while sweeping.
+
+ CHECK(GCUntilCacheSweep(cx, cache));
+
+ CHECK(cache.putNew(obj3));
+ CHECK(cache.putNew(obj4, obj4));
+
+ CHECK(SweepCacheAndFinishGC(cx, cache));
+
+ CHECK(cache.count() == 4);
+ CHECK(cache.has(obj3));
+ CHECK(cache.has(obj4));
+
+ cache.clear();
+
+ return true;
+}
+
+bool TestMap() {
+ using ObjectMap =
+ GCHashMap<HeapPtr<JSObject*>, uint32_t,
+ StableCellHasher<HeapPtr<JSObject*>>, TempAllocPolicy>;
+ using Cache = JS::WeakCache<ObjectMap>;
+ Cache cache(JS::GetObjectZone(global), cx);
+
+ // Sweep empty cache.
+
+ CHECK(cache.empty());
+ JS_GC(cx);
+ CHECK(cache.empty());
+
+ // Add an entry while sweeping.
+
+ JS::RootedObject obj1(cx, JS_NewPlainObject(cx));
+ JS::RootedObject obj2(cx, JS_NewPlainObject(cx));
+ JS::RootedObject obj3(cx, JS_NewPlainObject(cx));
+ JS::RootedObject obj4(cx, JS_NewPlainObject(cx));
+ CHECK(obj1);
+ CHECK(obj2);
+ CHECK(obj3);
+ CHECK(obj4);
+
+ CHECK(!cache.has(obj1));
+ CHECK(cache.put(obj1, 1));
+ CHECK(cache.count() == 1);
+ CHECK(cache.has(obj1));
+ CHECK(cache.lookup(obj1)->key() == obj1);
+
+ CHECK(GCUntilCacheSweep(cx, cache));
+ CHECK(cache.needsIncrementalBarrier());
+
+ CHECK(!cache.has(obj2));
+ CHECK(cache.put(obj2, 2));
+ CHECK(cache.has(obj2));
+ CHECK(cache.lookup(obj2)->key() == obj2);
+
+ CHECK(SweepCacheAndFinishGC(cx, cache));
+ CHECK(!cache.needsIncrementalBarrier());
+
+ CHECK(cache.count() == 2);
+ CHECK(cache.has(obj1));
+ CHECK(cache.has(obj2));
+
+ // Test iteration.
+
+ CHECK(cache.put(obj3, 3));
+ CHECK(cache.put(obj4, 4));
+ void* old3 = obj3;
+ void* old4 = obj4;
+ obj3 = obj4 = nullptr;
+
+ CHECK(GCUntilCacheSweep(cx, cache));
+
+ CHECK(cache.has(obj1));
+ CHECK(cache.has(obj2));
+ CHECK(!cache.has(static_cast<JSObject*>(old3)));
+ CHECK(!cache.has(static_cast<JSObject*>(old4)));
+
+ size_t count = 0;
+ for (auto r = cache.all(); !r.empty(); r.popFront()) {
+ CHECK(r.front().key() == obj1 || r.front().key() == obj2);
+ count++;
+ }
+ CHECK(count == 2);
+
+ CHECK(SweepCacheAndFinishGC(cx, cache));
+
+ CHECK(cache.count() == 2);
+
+ // Test lookupForAdd while sweeping.
+
+ obj3 = JS_NewPlainObject(cx);
+ obj4 = JS_NewPlainObject(cx);
+ CHECK(obj3);
+ CHECK(obj4);
+
+ CHECK(cache.lookupForAdd(obj1));
+ CHECK(cache.lookupForAdd(obj1)->key() == obj1);
+
+ auto addp = cache.lookupForAdd(obj3);
+ CHECK(!addp);
+ CHECK(cache.add(addp, obj3, 3));
+ CHECK(cache.has(obj3));
+
+ CHECK(GCUntilCacheSweep(cx, cache));
+
+ addp = cache.lookupForAdd(obj4);
+ CHECK(!addp);
+ CHECK(cache.add(addp, obj4, 4));
+ CHECK(cache.has(obj4));
+
+ CHECK(SweepCacheAndFinishGC(cx, cache));
+
+ CHECK(cache.count() == 4);
+ CHECK(cache.has(obj3));
+ CHECK(cache.has(obj4));
+
+ // Test remove while sweeping.
+
+ cache.remove(obj3);
+
+ CHECK(GCUntilCacheSweep(cx, cache));
+
+ cache.remove(obj4);
+
+ CHECK(SweepCacheAndFinishGC(cx, cache));
+
+ CHECK(cache.count() == 2);
+ CHECK(!cache.has(obj3));
+ CHECK(!cache.has(obj4));
+
+ // Test putNew while sweeping.
+
+ CHECK(GCUntilCacheSweep(cx, cache));
+
+ CHECK(cache.putNew(obj3, 3));
+ CHECK(cache.putNew(obj4, 4));
+
+ CHECK(SweepCacheAndFinishGC(cx, cache));
+
+ CHECK(cache.count() == 4);
+ CHECK(cache.has(obj3));
+ CHECK(cache.has(obj4));
+
+ cache.clear();
+
+ return true;
+}
+
+bool TestReplaceDyingInSet() {
+ // Test replacing dying entries with ones that have the same key using the
+ // various APIs.
+
+ using Cache = JS::WeakCache<
+ GCHashSet<NumberAndObjectEntry, StableCellHasher<NumberAndObjectEntry>,
+ TempAllocPolicy>>;
+ Cache cache(JS::GetObjectZone(global), cx);
+
+ RootedObject value1(cx, JS_NewPlainObject(cx));
+ RootedObject value2(cx, JS_NewPlainObject(cx));
+ CHECK(value1);
+ CHECK(value2);
+
+ CHECK(cache.put(NumberAndObjectEntry(1, value1)));
+ CHECK(cache.put(NumberAndObjectEntry(2, value2)));
+ CHECK(cache.put(NumberAndObjectEntry(3, value2)));
+ CHECK(cache.put(NumberAndObjectEntry(4, value2)));
+ CHECK(cache.put(NumberAndObjectEntry(5, value2)));
+ CHECK(cache.put(NumberAndObjectEntry(6, value2)));
+ CHECK(cache.put(NumberAndObjectEntry(7, value2)));
+
+ value2 = nullptr;
+ CHECK(GCUntilCacheSweep(cx, cache));
+
+ CHECK(!cache.has(NumberAndObjectLookup(2, value1)));
+ CHECK(!cache.has(NumberAndObjectLookup(3, value1)));
+ CHECK(!cache.has(NumberAndObjectLookup(4, value1)));
+ CHECK(!cache.has(NumberAndObjectLookup(5, value1)));
+ CHECK(!cache.has(NumberAndObjectLookup(6, value1)));
+
+ auto ptr = cache.lookupForAdd(NumberAndObjectLookup(2, value1));
+ CHECK(!ptr);
+ CHECK(cache.add(ptr, NumberAndObjectEntry(2, value1)));
+
+ auto ptr2 = cache.lookupForAdd(NumberAndObjectLookup(3, value1));
+ CHECK(!ptr2);
+ CHECK(cache.relookupOrAdd(ptr2, NumberAndObjectLookup(3, value1),
+ NumberAndObjectEntry(3, value1)));
+
+ CHECK(cache.put(NumberAndObjectEntry(4, value1)));
+ CHECK(cache.putNew(NumberAndObjectEntry(5, value1)));
+
+ CHECK(cache.putNew(NumberAndObjectLookup(6, value1),
+ NumberAndObjectEntry(6, value1)));
+
+ CHECK(SweepCacheAndFinishGC(cx, cache));
+
+ CHECK(cache.count() == 6);
+ CHECK(cache.has(NumberAndObjectLookup(1, value1)));
+ CHECK(cache.has(NumberAndObjectLookup(2, value1)));
+ CHECK(cache.has(NumberAndObjectLookup(3, value1)));
+ CHECK(cache.has(NumberAndObjectLookup(4, value1)));
+ CHECK(cache.has(NumberAndObjectLookup(5, value1)));
+ CHECK(cache.has(NumberAndObjectLookup(6, value1)));
+
+ return true;
+}
+
+bool TestReplaceDyingInMap() {
+ // Test replacing dying entries with ones that have the same key using the
+ // various APIs.
+
+ using Cache =
+ JS::WeakCache<GCHashMap<uint32_t, HeapPtr<JSObject*>,
+ DefaultHasher<uint32_t>, TempAllocPolicy>>;
+ Cache cache(JS::GetObjectZone(global), cx);
+
+ RootedObject value1(cx, JS_NewPlainObject(cx));
+ RootedObject value2(cx, JS_NewPlainObject(cx));
+ CHECK(value1);
+ CHECK(value2);
+
+ CHECK(cache.put(1, value1));
+ CHECK(cache.put(2, value2));
+ CHECK(cache.put(3, value2));
+ CHECK(cache.put(4, value2));
+ CHECK(cache.put(5, value2));
+ CHECK(cache.put(6, value2));
+
+ value2 = nullptr;
+ CHECK(GCUntilCacheSweep(cx, cache));
+
+ CHECK(!cache.has(2));
+ CHECK(!cache.has(3));
+ CHECK(!cache.has(4));
+ CHECK(!cache.has(5));
+ CHECK(!cache.has(6));
+
+ auto ptr = cache.lookupForAdd(2);
+ CHECK(!ptr);
+ CHECK(cache.add(ptr, 2, value1));
+
+ auto ptr2 = cache.lookupForAdd(3);
+ CHECK(!ptr2);
+ CHECK(cache.add(ptr2, 3, HeapPtr<JSObject*>()));
+
+ auto ptr3 = cache.lookupForAdd(4);
+ CHECK(!ptr3);
+ CHECK(cache.relookupOrAdd(ptr3, 4, value1));
+
+ CHECK(cache.put(5, value1));
+ CHECK(cache.putNew(6, value1));
+
+ CHECK(SweepCacheAndFinishGC(cx, cache));
+
+ CHECK(cache.count() == 6);
+ CHECK(cache.lookup(1)->value() == value1);
+ CHECK(cache.lookup(2)->value() == value1);
+ CHECK(cache.lookup(3)->value() == nullptr);
+ CHECK(cache.lookup(4)->value() == value1);
+ CHECK(cache.lookup(5)->value() == value1);
+ CHECK(cache.lookup(6)->value() == value1);
+
+ return true;
+}
+
+bool TestUniqueIDLookups() {
+ // Test hash table lookups during incremental sweeping where the hash is
+ // generated based on a unique ID. The problem is that the unique ID table
+ // will have already been swept by this point so looking up a dead pointer
+ // in the table will fail. This lookup happens if we try to match a live key
+ // against a dead table entry with the same hash code.
+
+ const size_t DeadFactor = 3;
+ const size_t ObjectCount = 100;
+
+ using Cache = JS::WeakCache<
+ GCHashSet<ObjectEntry, StableCellHasher<ObjectEntry>, TempAllocPolicy>>;
+ Cache cache(JS::GetObjectZone(global), cx);
+
+ Rooted<GCVector<JSObject*, 0, SystemAllocPolicy>> liveObjects(cx);
+
+ for (size_t j = 0; j < ObjectCount; j++) {
+ JSObject* obj = JS_NewPlainObject(cx);
+ CHECK(obj);
+ CHECK(cache.put(obj));
+ if (j % DeadFactor == 0) {
+ CHECK(liveObjects.get().append(obj));
+ }
+ }
+
+ CHECK(cache.count() == ObjectCount);
+
+ CHECK(GCUntilCacheSweep(cx, cache));
+
+ for (size_t j = 0; j < liveObjects.length(); j++) {
+ CHECK(cache.has(liveObjects[j]));
+ }
+
+ CHECK(SweepCacheAndFinishGC(cx, cache));
+
+ CHECK(cache.count() == liveObjects.length());
+
+ return true;
+}
+
+END_TEST(testIncrementalWeakCacheSweeping)
+
+#endif // defined JS_GC_ZEAL
diff --git a/js/src/jsapi-tests/testGetPropertyDescriptor.cpp b/js/src/jsapi-tests/testGetPropertyDescriptor.cpp
new file mode 100644
index 0000000000..f5a76b5b6a
--- /dev/null
+++ b/js/src/jsapi-tests/testGetPropertyDescriptor.cpp
@@ -0,0 +1,54 @@
+/* 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 "js/CallAndConstruct.h" // JS::IsCallable
+#include "js/PropertyAndElement.h"
+#include "js/PropertyDescriptor.h" // JS::FromPropertyDescriptor, JS_GetPropertyDescriptor
+#include "js/RootingAPI.h"
+#include "jsapi-tests/tests.h"
+
+BEGIN_TEST(test_GetPropertyDescriptor) {
+ JS::RootedValue v(cx);
+ EVAL("({ somename : 123 })", &v);
+ CHECK(v.isObject());
+
+ JS::RootedObject obj(cx, &v.toObject());
+ JS::Rooted<mozilla::Maybe<JS::PropertyDescriptor>> desc(cx);
+ JS::RootedObject holder(cx);
+
+ CHECK(JS_GetPropertyDescriptor(cx, obj, "somename", &desc, &holder));
+ CHECK(desc.isSome());
+ CHECK_SAME(desc->value(), JS::Int32Value(123));
+
+ JS::RootedValue descValue(cx);
+ CHECK(JS::FromPropertyDescriptor(cx, desc, &descValue));
+ CHECK(descValue.isObject());
+ JS::RootedObject descObj(cx, &descValue.toObject());
+ JS::RootedValue value(cx);
+ CHECK(JS_GetProperty(cx, descObj, "value", &value));
+ CHECK_EQUAL(value.toInt32(), 123);
+ CHECK(JS_GetProperty(cx, descObj, "get", &value));
+ CHECK(value.isUndefined());
+ CHECK(JS_GetProperty(cx, descObj, "set", &value));
+ CHECK(value.isUndefined());
+ CHECK(JS_GetProperty(cx, descObj, "writable", &value));
+ CHECK(value.isTrue());
+ CHECK(JS_GetProperty(cx, descObj, "configurable", &value));
+ CHECK(value.isTrue());
+ CHECK(JS_GetProperty(cx, descObj, "enumerable", &value));
+ CHECK(value.isTrue());
+
+ CHECK(JS_GetPropertyDescriptor(cx, obj, "not-here", &desc, &holder));
+ CHECK(desc.isNothing());
+
+ CHECK(JS_GetPropertyDescriptor(cx, obj, "toString", &desc, &holder));
+ JS::RootedObject objectProto(cx, JS::GetRealmObjectPrototype(cx));
+ CHECK(objectProto);
+ CHECK_EQUAL(holder, objectProto);
+ CHECK(desc->value().isObject());
+ CHECK(JS::IsCallable(&desc->value().toObject()));
+
+ return true;
+}
+END_TEST(test_GetPropertyDescriptor)
diff --git a/js/src/jsapi-tests/testHashTable.cpp b/js/src/jsapi-tests/testHashTable.cpp
new file mode 100644
index 0000000000..4cd55f4cba
--- /dev/null
+++ b/js/src/jsapi-tests/testHashTable.cpp
@@ -0,0 +1,564 @@
+/* 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/HashFunctions.h"
+
+#include <utility>
+
+#include "ds/OrderedHashTable.h"
+#include "js/HashTable.h"
+#include "js/Utility.h"
+#include "jsapi-tests/tests.h"
+
+// #define FUZZ
+
+typedef js::HashMap<uint32_t, uint32_t, js::DefaultHasher<uint32_t>,
+ js::SystemAllocPolicy>
+ IntMap;
+typedef js::HashSet<uint32_t, js::DefaultHasher<uint32_t>,
+ js::SystemAllocPolicy>
+ IntSet;
+
+/*
+ * The rekeying test as conducted by adding only keys masked with 0x0000FFFF
+ * that are unique. We rekey by shifting left 16 bits.
+ */
+#ifdef FUZZ
+const size_t TestSize = 0x0000FFFF / 2;
+const size_t TestIterations = SIZE_MAX;
+#else
+const size_t TestSize = 10000;
+const size_t TestIterations = 10;
+#endif
+
+static_assert(TestSize <= 0x0000FFFF / 2);
+
+struct LowToHigh {
+ static uint32_t rekey(uint32_t initial) {
+ if (initial > uint32_t(0x0000FFFF)) {
+ return initial;
+ }
+ return initial << 16;
+ }
+
+ static bool shouldBeRemoved(uint32_t initial) { return false; }
+};
+
+struct LowToHighWithRemoval {
+ static uint32_t rekey(uint32_t initial) {
+ if (initial > uint32_t(0x0000FFFF)) {
+ return initial;
+ }
+ return initial << 16;
+ }
+
+ static bool shouldBeRemoved(uint32_t initial) {
+ if (initial >= 0x00010000) {
+ return (initial >> 16) % 2 == 0;
+ }
+ return initial % 2 == 0;
+ }
+};
+
+static bool MapsAreEqual(IntMap& am, IntMap& bm) {
+ bool equal = true;
+ if (am.count() != bm.count()) {
+ equal = false;
+ fprintf(stderr, "A.count() == %u and B.count() == %u\n", am.count(),
+ bm.count());
+ }
+ for (auto iter = am.iter(); !iter.done(); iter.next()) {
+ if (!bm.has(iter.get().key())) {
+ equal = false;
+ fprintf(stderr, "B does not have %x which is in A\n", iter.get().key());
+ }
+ }
+ for (auto iter = bm.iter(); !iter.done(); iter.next()) {
+ if (!am.has(iter.get().key())) {
+ equal = false;
+ fprintf(stderr, "A does not have %x which is in B\n", iter.get().key());
+ }
+ }
+ return equal;
+}
+
+static bool SetsAreEqual(IntSet& am, IntSet& bm) {
+ bool equal = true;
+ if (am.count() != bm.count()) {
+ equal = false;
+ fprintf(stderr, "A.count() == %u and B.count() == %u\n", am.count(),
+ bm.count());
+ }
+ for (auto iter = am.iter(); !iter.done(); iter.next()) {
+ if (!bm.has(iter.get())) {
+ equal = false;
+ fprintf(stderr, "B does not have %x which is in A\n", iter.get());
+ }
+ }
+ for (auto iter = bm.iter(); !iter.done(); iter.next()) {
+ if (!am.has(iter.get())) {
+ equal = false;
+ fprintf(stderr, "A does not have %x which is in B\n", iter.get());
+ }
+ }
+ return equal;
+}
+
+static bool AddLowKeys(IntMap* am, IntMap* bm, int seed) {
+ size_t i = 0;
+ srand(seed);
+ while (i < TestSize) {
+ uint32_t n = rand() & 0x0000FFFF;
+ if (!am->has(n)) {
+ if (bm->has(n)) {
+ return false;
+ }
+
+ if (!am->putNew(n, n) || !bm->putNew(n, n)) {
+ return false;
+ }
+ i++;
+ }
+ }
+ return true;
+}
+
+static bool AddLowKeys(IntSet* as, IntSet* bs, int seed) {
+ size_t i = 0;
+ srand(seed);
+ while (i < TestSize) {
+ uint32_t n = rand() & 0x0000FFFF;
+ if (!as->has(n)) {
+ if (bs->has(n)) {
+ return false;
+ }
+ if (!as->putNew(n) || !bs->putNew(n)) {
+ return false;
+ }
+ i++;
+ }
+ }
+ return true;
+}
+
+template <class NewKeyFunction>
+static bool SlowRekey(IntMap* m) {
+ IntMap tmp;
+
+ for (auto iter = m->iter(); !iter.done(); iter.next()) {
+ if (NewKeyFunction::shouldBeRemoved(iter.get().key())) {
+ continue;
+ }
+ uint32_t hi = NewKeyFunction::rekey(iter.get().key());
+ if (tmp.has(hi)) {
+ return false;
+ }
+ if (!tmp.putNew(hi, iter.get().value())) {
+ return false;
+ }
+ }
+
+ m->clear();
+ for (auto iter = tmp.iter(); !iter.done(); iter.next()) {
+ if (!m->putNew(iter.get().key(), iter.get().value())) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+template <class NewKeyFunction>
+static bool SlowRekey(IntSet* s) {
+ IntSet tmp;
+
+ for (auto iter = s->iter(); !iter.done(); iter.next()) {
+ if (NewKeyFunction::shouldBeRemoved(iter.get())) {
+ continue;
+ }
+ uint32_t hi = NewKeyFunction::rekey(iter.get());
+ if (tmp.has(hi)) {
+ return false;
+ }
+ if (!tmp.putNew(hi)) {
+ return false;
+ }
+ }
+
+ s->clear();
+ for (auto iter = tmp.iter(); !iter.done(); iter.next()) {
+ if (!s->putNew(iter.get())) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+BEGIN_TEST(testHashRekeyManual) {
+ IntMap am, bm;
+ for (size_t i = 0; i < TestIterations; ++i) {
+#ifdef FUZZ
+ fprintf(stderr, "map1: %lu\n", i);
+#endif
+ CHECK(AddLowKeys(&am, &bm, i));
+ CHECK(MapsAreEqual(am, bm));
+
+ for (auto iter = am.modIter(); !iter.done(); iter.next()) {
+ uint32_t tmp = LowToHigh::rekey(iter.get().key());
+ if (tmp != iter.get().key()) {
+ iter.rekey(tmp);
+ }
+ }
+ CHECK(SlowRekey<LowToHigh>(&bm));
+
+ CHECK(MapsAreEqual(am, bm));
+ am.clear();
+ bm.clear();
+ }
+
+ IntSet as, bs;
+ for (size_t i = 0; i < TestIterations; ++i) {
+#ifdef FUZZ
+ fprintf(stderr, "set1: %lu\n", i);
+#endif
+ CHECK(AddLowKeys(&as, &bs, i));
+ CHECK(SetsAreEqual(as, bs));
+
+ for (auto iter = as.modIter(); !iter.done(); iter.next()) {
+ uint32_t tmp = LowToHigh::rekey(iter.get());
+ if (tmp != iter.get()) {
+ iter.rekey(tmp);
+ }
+ }
+ CHECK(SlowRekey<LowToHigh>(&bs));
+
+ CHECK(SetsAreEqual(as, bs));
+ as.clear();
+ bs.clear();
+ }
+
+ return true;
+}
+END_TEST(testHashRekeyManual)
+
+BEGIN_TEST(testHashRekeyManualRemoval) {
+ IntMap am, bm;
+ for (size_t i = 0; i < TestIterations; ++i) {
+#ifdef FUZZ
+ fprintf(stderr, "map2: %lu\n", i);
+#endif
+ CHECK(AddLowKeys(&am, &bm, i));
+ CHECK(MapsAreEqual(am, bm));
+
+ for (auto iter = am.modIter(); !iter.done(); iter.next()) {
+ if (LowToHighWithRemoval::shouldBeRemoved(iter.get().key())) {
+ iter.remove();
+ } else {
+ uint32_t tmp = LowToHighWithRemoval::rekey(iter.get().key());
+ if (tmp != iter.get().key()) {
+ iter.rekey(tmp);
+ }
+ }
+ }
+ CHECK(SlowRekey<LowToHighWithRemoval>(&bm));
+
+ CHECK(MapsAreEqual(am, bm));
+ am.clear();
+ bm.clear();
+ }
+
+ IntSet as, bs;
+ for (size_t i = 0; i < TestIterations; ++i) {
+#ifdef FUZZ
+ fprintf(stderr, "set1: %lu\n", i);
+#endif
+ CHECK(AddLowKeys(&as, &bs, i));
+ CHECK(SetsAreEqual(as, bs));
+
+ for (auto iter = as.modIter(); !iter.done(); iter.next()) {
+ if (LowToHighWithRemoval::shouldBeRemoved(iter.get())) {
+ iter.remove();
+ } else {
+ uint32_t tmp = LowToHighWithRemoval::rekey(iter.get());
+ if (tmp != iter.get()) {
+ iter.rekey(tmp);
+ }
+ }
+ }
+ CHECK(SlowRekey<LowToHighWithRemoval>(&bs));
+
+ CHECK(SetsAreEqual(as, bs));
+ as.clear();
+ bs.clear();
+ }
+
+ return true;
+}
+END_TEST(testHashRekeyManualRemoval)
+
+// A type that is not copyable, only movable.
+struct MoveOnlyType {
+ uint32_t val;
+
+ explicit MoveOnlyType(uint32_t val) : val(val) {}
+
+ MoveOnlyType(MoveOnlyType&& rhs) { val = rhs.val; }
+
+ MoveOnlyType& operator=(MoveOnlyType&& rhs) {
+ MOZ_ASSERT(&rhs != this);
+ this->~MoveOnlyType();
+ new (this) MoveOnlyType(std::move(rhs));
+ return *this;
+ }
+
+ struct HashPolicy {
+ typedef MoveOnlyType Lookup;
+
+ static js::HashNumber hash(const Lookup& lookup) { return lookup.val; }
+
+ static bool match(const MoveOnlyType& existing, const Lookup& lookup) {
+ return existing.val == lookup.val;
+ }
+ };
+
+ private:
+ MoveOnlyType(const MoveOnlyType&) = delete;
+ MoveOnlyType& operator=(const MoveOnlyType&) = delete;
+};
+
+BEGIN_TEST(testHashSetOfMoveOnlyType) {
+ typedef js::HashSet<MoveOnlyType, MoveOnlyType::HashPolicy,
+ js::SystemAllocPolicy>
+ Set;
+
+ Set set;
+
+ MoveOnlyType a(1);
+
+ CHECK(set.put(std::move(a))); // This shouldn't generate a compiler error.
+
+ return true;
+}
+END_TEST(testHashSetOfMoveOnlyType)
+
+#if defined(DEBUG)
+
+// Add entries to a HashMap until either we get an OOM, or the table has been
+// resized a few times.
+static bool GrowUntilResize() {
+ IntMap m;
+
+ // Add entries until we've resized the table four times.
+ size_t lastCapacity = m.capacity();
+ size_t resizes = 0;
+ uint32_t key = 0;
+ while (resizes < 4) {
+ auto p = m.lookupForAdd(key);
+ if (!p && !m.add(p, key, 0)) {
+ return false; // OOM'd in lookupForAdd() or add()
+ }
+
+ size_t capacity = m.capacity();
+ if (capacity != lastCapacity) {
+ resizes++;
+ lastCapacity = capacity;
+ }
+ key++;
+ }
+
+ return true;
+}
+
+BEGIN_TEST(testHashMapGrowOOM) {
+ uint32_t timeToFail;
+ for (timeToFail = 1; timeToFail < 1000; timeToFail++) {
+ js::oom::simulator.simulateFailureAfter(
+ js::oom::FailureSimulator::Kind::OOM, timeToFail, js::THREAD_TYPE_MAIN,
+ false);
+ GrowUntilResize();
+ }
+
+ js::oom::simulator.reset();
+ return true;
+}
+
+END_TEST(testHashMapGrowOOM)
+#endif // defined(DEBUG)
+
+BEGIN_TEST(testHashTableMovableModIterator) {
+ IntSet set;
+
+ // Exercise returning a hash table ModIterator object from a function.
+
+ CHECK(set.put(1));
+ for (auto iter = setModIter(set); !iter.done(); iter.next()) {
+ iter.remove();
+ }
+ CHECK(set.count() == 0);
+
+ // Test moving an ModIterator object explicitly.
+
+ CHECK(set.put(1));
+ CHECK(set.put(2));
+ CHECK(set.put(3));
+ CHECK(set.count() == 3);
+ {
+ auto i1 = set.modIter();
+ CHECK(!i1.done());
+ i1.remove();
+ i1.next();
+
+ auto i2 = std::move(i1);
+ CHECK(!i2.done());
+ i2.remove();
+ i2.next();
+ }
+
+ CHECK(set.count() == 1);
+ return true;
+}
+
+IntSet::ModIterator setModIter(IntSet& set) { return set.modIter(); }
+
+END_TEST(testHashTableMovableModIterator)
+
+BEGIN_TEST(testHashLazyStorage) {
+ // The following code depends on the current capacity computation, which
+ // could change in the future.
+ uint32_t defaultCap = 32;
+ uint32_t minCap = 4;
+
+ IntSet set;
+ CHECK(set.capacity() == 0);
+
+ CHECK(set.put(1));
+ CHECK(set.capacity() == defaultCap);
+
+ set.compact(); // effectively a no-op
+ CHECK(set.capacity() == minCap);
+
+ set.clear();
+ CHECK(set.capacity() == minCap);
+
+ set.compact();
+ CHECK(set.capacity() == 0);
+
+ CHECK(set.putNew(1));
+ CHECK(set.capacity() == minCap);
+
+ set.clear();
+ set.compact();
+ CHECK(set.capacity() == 0);
+
+ auto p = set.lookupForAdd(1);
+ CHECK(set.capacity() == 0);
+ CHECK(set.add(p, 1));
+ CHECK(set.capacity() == minCap);
+ CHECK(set.has(1));
+
+ set.clear();
+ set.compact();
+ CHECK(set.capacity() == 0);
+
+ p = set.lookupForAdd(1);
+ CHECK(set.putNew(2));
+ CHECK(set.capacity() == minCap);
+ CHECK(set.relookupOrAdd(p, 1, 1));
+ CHECK(set.capacity() == minCap);
+ CHECK(set.has(1));
+
+ set.clear();
+ set.compact();
+ CHECK(set.capacity() == 0);
+
+ CHECK(set.putNew(1));
+ p = set.lookupForAdd(1);
+ set.clear();
+ set.compact();
+ CHECK(set.count() == 0);
+ CHECK(set.relookupOrAdd(p, 1, 1));
+ CHECK(set.count() == 1);
+ CHECK(set.capacity() == minCap);
+
+ set.clear();
+ set.compact();
+ CHECK(set.capacity() == 0);
+
+ CHECK(set.reserve(0)); // a no-op
+ CHECK(set.capacity() == 0);
+
+ CHECK(set.reserve(1));
+ CHECK(set.capacity() == minCap);
+
+ CHECK(set.reserve(0)); // a no-op
+ CHECK(set.capacity() == minCap);
+
+ CHECK(set.reserve(2)); // effectively a no-op
+ CHECK(set.capacity() == minCap);
+
+ // No need to clear here because we didn't add anything.
+ set.compact();
+ CHECK(set.capacity() == 0);
+
+ CHECK(set.reserve(128));
+ CHECK(set.capacity() == 256);
+ CHECK(set.reserve(3)); // effectively a no-op
+ CHECK(set.capacity() == 256);
+ for (int i = 0; i < 8; i++) {
+ CHECK(set.putNew(i));
+ }
+ CHECK(set.count() == 8);
+ CHECK(set.capacity() == 256);
+ set.compact();
+ CHECK(set.capacity() == 16);
+ set.compact(); // effectively a no-op
+ CHECK(set.capacity() == 16);
+ for (int i = 8; i < 16; i++) {
+ CHECK(set.putNew(i));
+ }
+ CHECK(set.count() == 16);
+ CHECK(set.capacity() == 32);
+ set.clear();
+ CHECK(set.capacity() == 32);
+ set.compact();
+ CHECK(set.capacity() == 0);
+
+ // Lowest length for which reserve() will fail.
+ static const uint32_t toobig = (1 << 29) + 1;
+ CHECK(!set.reserve(toobig));
+ CHECK(set.capacity() == 0); // unchanged
+ CHECK(set.reserve(16));
+ CHECK(set.capacity() == 32);
+
+ return true;
+}
+END_TEST(testHashLazyStorage)
+
+BEGIN_TEST(testOrderedHashSetWithoutInit) {
+ {
+ struct NonzeroUint32HashPolicy {
+ using Lookup = uint32_t;
+ static js::HashNumber hash(const Lookup& v,
+ const mozilla::HashCodeScrambler& hcs) {
+ return mozilla::HashGeneric(v);
+ }
+ static bool match(const uint32_t& k, const Lookup& l) { return k == l; }
+ static bool isEmpty(const uint32_t& v) { return v == 0; }
+ static void makeEmpty(uint32_t* v) { *v = 0; }
+ };
+
+ using OHS = js::OrderedHashSet<uint32_t, NonzeroUint32HashPolicy,
+ js::SystemAllocPolicy>;
+
+ OHS set(js::SystemAllocPolicy(), mozilla::HashCodeScrambler(17, 42));
+ CHECK(set.count() == 0);
+
+ // This test passes if the set is safely destructible even when |init()| is
+ // never called.
+ }
+
+ return true;
+}
+END_TEST(testOrderedHashSetWithoutInit)
diff --git a/js/src/jsapi-tests/testIndexToString.cpp b/js/src/jsapi-tests/testIndexToString.cpp
new file mode 100644
index 0000000000..9074a4267c
--- /dev/null
+++ b/js/src/jsapi-tests/testIndexToString.cpp
@@ -0,0 +1,120 @@
+/* -*- 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 "jsnum.h"
+
+#include "jsapi-tests/tests.h"
+#include "vm/JSContext.h"
+#include "vm/Realm.h"
+#include "vm/StaticStrings.h"
+
+#include "vm/StringType-inl.h"
+
+static const struct TestPair {
+ uint32_t num;
+ const char* expected;
+} tests[] = {
+ {0, "0"},
+ {1, "1"},
+ {2, "2"},
+ {9, "9"},
+ {10, "10"},
+ {15, "15"},
+ {16, "16"},
+ {17, "17"},
+ {99, "99"},
+ {100, "100"},
+ {255, "255"},
+ {256, "256"},
+ {257, "257"},
+ {999, "999"},
+ {1000, "1000"},
+ {4095, "4095"},
+ {4096, "4096"},
+ {9999, "9999"},
+ {1073741823, "1073741823"},
+ {1073741824, "1073741824"},
+ {1073741825, "1073741825"},
+ {2147483647, "2147483647"},
+ {2147483648u, "2147483648"},
+ {2147483649u, "2147483649"},
+ {4294967294u, "4294967294"},
+};
+
+BEGIN_TEST(testIndexToString) {
+ for (const auto& test : tests) {
+ uint32_t u = test.num;
+ JSString* str = js::IndexToString(cx, u);
+ CHECK(str);
+
+ if (!js::StaticStrings::hasUint(u)) {
+ CHECK(cx->realm()->dtoaCache.lookup(10, u) == str);
+ }
+
+ bool match = false;
+ CHECK(JS_StringEqualsAscii(cx, str, test.expected, &match));
+ CHECK(match);
+ }
+
+ return true;
+}
+END_TEST(testIndexToString)
+
+BEGIN_TEST(testStringIsIndex) {
+ for (const auto& test : tests) {
+ uint32_t u = test.num;
+ JSLinearString* str = js::IndexToString(cx, u);
+ CHECK(str);
+
+ uint32_t n;
+ CHECK(str->isIndex(&n));
+ CHECK(u == n);
+ }
+
+ return true;
+}
+END_TEST(testStringIsIndex)
+
+BEGIN_TEST(testStringToPropertyName) {
+ uint32_t index;
+
+ static const char16_t hiChars[] = {'h', 'i'};
+ JSLinearString* hiStr = NewString(cx, hiChars);
+ CHECK(hiStr);
+ CHECK(!hiStr->isIndex(&index));
+ CHECK(hiStr->toPropertyName(cx) != nullptr);
+
+ static const char16_t maxChars[] = {'4', '2', '9', '4', '9',
+ '6', '7', '2', '9', '4'};
+ JSLinearString* maxStr = NewString(cx, maxChars);
+ CHECK(maxStr);
+ CHECK(maxStr->isIndex(&index));
+ CHECK(index == UINT32_MAX - 1);
+
+ static const char16_t maxPlusOneChars[] = {'4', '2', '9', '4', '9',
+ '6', '7', '2', '9', '5'};
+ JSLinearString* maxPlusOneStr = NewString(cx, maxPlusOneChars);
+ CHECK(maxPlusOneStr);
+ CHECK(!maxPlusOneStr->isIndex(&index));
+ CHECK(maxPlusOneStr->toPropertyName(cx) != nullptr);
+
+ static const char16_t maxNonUint32Chars[] = {'4', '2', '9', '4', '9',
+ '6', '7', '2', '9', '6'};
+ JSLinearString* maxNonUint32Str = NewString(cx, maxNonUint32Chars);
+ CHECK(maxNonUint32Str);
+ CHECK(!maxNonUint32Str->isIndex(&index));
+ CHECK(maxNonUint32Str->toPropertyName(cx) != nullptr);
+
+ return true;
+}
+
+template <size_t N>
+static JSLinearString* NewString(JSContext* cx, const char16_t (&chars)[N]) {
+ return js::NewStringCopyN<js::CanGC>(cx, chars, N);
+}
+
+END_TEST(testStringToPropertyName)
diff --git a/js/src/jsapi-tests/testInformalValueTypeName.cpp b/js/src/jsapi-tests/testInformalValueTypeName.cpp
new file mode 100644
index 0000000000..37b73ccc08
--- /dev/null
+++ b/js/src/jsapi-tests/testInformalValueTypeName.cpp
@@ -0,0 +1,43 @@
+/* -*- 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 "js/Array.h" // JS::NewArrayObject
+#include "js/Symbol.h"
+#include "jsapi-tests/tests.h"
+
+BEGIN_TEST(testInformalValueTypeName) {
+ JS::RootedValue v1(cx, JS::ObjectOrNullValue(JS_NewPlainObject(cx)));
+ CHECK(strcmp(JS::InformalValueTypeName(v1), "Object") == 0);
+
+ JS::RootedValue v2(cx, JS::ObjectOrNullValue(JS::NewArrayObject(cx, 0)));
+ CHECK(strcmp(JS::InformalValueTypeName(v2), "Array") == 0);
+
+ JS::RootedValue v3(cx, JS::StringValue(JS_GetEmptyString(cx)));
+ CHECK(strcmp(JS::InformalValueTypeName(v3), "string") == 0);
+
+ JS::RootedValue v4(cx, JS::SymbolValue(JS::NewSymbol(cx, nullptr)));
+ CHECK(strcmp(JS::InformalValueTypeName(v4), "symbol") == 0);
+
+ JS::RootedValue v5(cx, JS::NumberValue(50.5));
+ CHECK(strcmp(JS::InformalValueTypeName(v5), "number") == 0);
+ JS::RootedValue v6(cx, JS::Int32Value(50));
+ CHECK(strcmp(JS::InformalValueTypeName(v6), "number") == 0);
+ JS::RootedValue v7(cx, JS::Float32Value(50.5));
+ CHECK(strcmp(JS::InformalValueTypeName(v7), "number") == 0);
+
+ JS::RootedValue v8(cx, JS::TrueValue());
+ CHECK(strcmp(JS::InformalValueTypeName(v8), "boolean") == 0);
+
+ JS::RootedValue v9(cx, JS::NullValue());
+ CHECK(strcmp(JS::InformalValueTypeName(v9), "null") == 0);
+
+ JS::RootedValue v10(cx, JS::UndefinedValue());
+ CHECK(strcmp(JS::InformalValueTypeName(v10), "undefined") == 0);
+
+ return true;
+}
+END_TEST(testInformalValueTypeName)
diff --git a/js/src/jsapi-tests/testIntString.cpp b/js/src/jsapi-tests/testIntString.cpp
new file mode 100644
index 0000000000..8733e669ec
--- /dev/null
+++ b/js/src/jsapi-tests/testIntString.cpp
@@ -0,0 +1,49 @@
+/* -*- 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 "jsapi-tests/tests.h"
+
+BEGIN_TEST(testIntString_bug515273) {
+ JS::RootedValue v(cx);
+
+ EVAL("'1';", &v);
+ JSString* str = v.toString();
+ CHECK(JS_StringHasBeenPinned(cx, str));
+ CHECK(JS_LinearStringEqualsLiteral(JS_ASSERT_STRING_IS_LINEAR(str), "1"));
+
+ EVAL("'42';", &v);
+ str = v.toString();
+ CHECK(JS_StringHasBeenPinned(cx, str));
+ CHECK(JS_LinearStringEqualsLiteral(JS_ASSERT_STRING_IS_LINEAR(str), "42"));
+
+ // Short string literal should use atom.
+ EVAL("'111';", &v);
+ str = v.toString();
+ CHECK(JS_StringHasBeenPinned(cx, str));
+ CHECK(JS_LinearStringEqualsLiteral(JS_ASSERT_STRING_IS_LINEAR(str), "111"));
+
+ // Long string literal shouldn't use atom, but just linear string.
+ EVAL("'111222333';", &v);
+ str = v.toString();
+ CHECK(!JS_StringHasBeenPinned(cx, str));
+ CHECK(JS_LinearStringEqualsLiteral(JS_ASSERT_STRING_IS_LINEAR(str),
+ "111222333"));
+
+ /* Test other types of static strings. */
+ EVAL("'a';", &v);
+ str = v.toString();
+ CHECK(JS_StringHasBeenPinned(cx, str));
+ CHECK(JS_LinearStringEqualsLiteral(JS_ASSERT_STRING_IS_LINEAR(str), "a"));
+
+ EVAL("'bc';", &v);
+ str = v.toString();
+ CHECK(JS_StringHasBeenPinned(cx, str));
+ CHECK(JS_LinearStringEqualsLiteral(JS_ASSERT_STRING_IS_LINEAR(str), "bc"));
+
+ return true;
+}
+END_TEST(testIntString_bug515273)
diff --git a/js/src/jsapi-tests/testIntern.cpp b/js/src/jsapi-tests/testIntern.cpp
new file mode 100644
index 0000000000..667d7ce8b1
--- /dev/null
+++ b/js/src/jsapi-tests/testIntern.cpp
@@ -0,0 +1,51 @@
+/* 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 "gc/GCContext.h"
+#include "gc/Marking.h"
+#include "jsapi-tests/tests.h"
+#include "util/Text.h"
+#include "vm/JSAtom.h"
+#include "vm/StringType.h"
+
+BEGIN_TEST(testAtomizedIsNotPinned) {
+ /* Try to pick a string that won't be interned by other tests in this runtime.
+ */
+ static const char someChars[] = "blah blah blah? blah blah blah";
+ JS::Rooted<JSAtom*> atom(cx,
+ js::Atomize(cx, someChars, js_strlen(someChars)));
+ CHECK(!JS_StringHasBeenPinned(cx, atom));
+
+ JS::RootedString string(cx, JS_AtomizeAndPinString(cx, someChars));
+ CHECK(string);
+ CHECK(string == atom);
+
+ CHECK(JS_StringHasBeenPinned(cx, atom));
+ return true;
+}
+END_TEST(testAtomizedIsNotPinned)
+
+struct StringWrapperStruct {
+ JSString* str;
+ bool strOk;
+} sw;
+
+BEGIN_TEST(testPinAcrossGC) {
+ sw.str = JS_AtomizeAndPinString(
+ cx, "wrapped chars that another test shouldn't be using");
+ sw.strOk = false;
+ CHECK(sw.str);
+ JS_AddFinalizeCallback(cx, FinalizeCallback, nullptr);
+ JS_GC(cx);
+ CHECK(sw.strOk);
+ return true;
+}
+
+static void FinalizeCallback(JS::GCContext* gcx, JSFinalizeStatus status,
+ void* data) {
+ if (status == JSFINALIZE_GROUP_START) {
+ sw.strOk = js::gc::IsMarkedUnbarriered(gcx->runtime(), sw.str);
+ }
+}
+END_TEST(testPinAcrossGC)
diff --git a/js/src/jsapi-tests/testIntlAvailableLocales.cpp b/js/src/jsapi-tests/testIntlAvailableLocales.cpp
new file mode 100644
index 0000000000..b3d16eb758
--- /dev/null
+++ b/js/src/jsapi-tests/testIntlAvailableLocales.cpp
@@ -0,0 +1,68 @@
+/* -*- 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 "js/LocaleSensitive.h"
+#include "jsapi-tests/tests.h"
+
+BEGIN_TEST(testIntlAvailableLocales) {
+ JSRuntime* rt = JS_GetRuntime(cx);
+
+ // This test should only attempt to run if we have Intl support.
+ JS::Rooted<JS::Value> haveIntl(cx);
+ EVAL("typeof Intl !== 'undefined'", &haveIntl);
+ if (!haveIntl.toBoolean()) {
+ return true;
+ }
+
+ // Assumption: our Intl support always includes "az" (Azerbaijani) support,
+ // and our Intl support *does not* natively support az-Cyrl-AZ.
+ CHECK(JS_SetDefaultLocale(rt, "az-Cyrl-AZ"));
+
+ EXEC(
+ "if (Intl.Collator().resolvedOptions().locale !== "
+ "'az-Cyrl-AZ') \n"
+ " throw 'unexpected default locale';");
+ EXEC(
+ "var used = Intl.Collator('az-Cyrl').resolvedOptions().locale; \n"
+ "if (used !== 'az-Cyrl') \n"
+ " throw 'bad locale when using truncated default: ' + used;");
+ EXEC(
+ "if (Intl.Collator('az').resolvedOptions().locale !== 'az') \n"
+ " throw 'bad locale when using more-truncated default';");
+ EXEC(
+ "if (Intl.Collator('az-Cyrl-US').resolvedOptions().locale !== 'az-Cyrl') "
+ "\n"
+ " throw 'unexpected default locale';");
+
+ EXEC(
+ "if (Intl.Collator('az-Cyrl-AZ', { localeMatcher: 'lookup' "
+ "}).resolvedOptions().locale !== \n"
+ " 'az-Cyrl-AZ') \n"
+ "{ \n"
+ " throw 'unexpected default locale with lookup matcher'; \n"
+ "}");
+
+ CHECK(JS_SetDefaultLocale(rt, "en-US-u-co-phonebk"));
+ EXEC(
+ "if (Intl.Collator().resolvedOptions().locale !== 'en-US') \n"
+ " throw 'unexpected default locale where proposed default included a "
+ "Unicode extension';");
+
+ CHECK(JS_SetDefaultLocale(rt, "this is not a language tag at all, yo"));
+
+ EXEC(
+ "if (Intl.Collator().resolvedOptions().locale !== 'en-GB') \n"
+ " throw 'unexpected last-ditch locale';");
+ EXEC(
+ "if (Intl.Collator('en-GB').resolvedOptions().locale !== 'en-GB') \n"
+ " throw 'unexpected used locale when specified, with last-ditch "
+ "locale as default';");
+
+ JS_ResetDefaultLocale(rt);
+ return true;
+}
+END_TEST(testIntlAvailableLocales)
diff --git a/js/src/jsapi-tests/testIsInsideNursery.cpp b/js/src/jsapi-tests/testIsInsideNursery.cpp
new file mode 100644
index 0000000000..301d7780d3
--- /dev/null
+++ b/js/src/jsapi-tests/testIsInsideNursery.cpp
@@ -0,0 +1,31 @@
+/* -*- 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 "jsapi-tests/tests.h"
+
+#include "vm/JSContext-inl.h"
+
+BEGIN_TEST(testIsInsideNursery) {
+ /* Non-GC things are never inside the nursery. */
+ CHECK(!cx->nursery().isInside(cx));
+ CHECK(!cx->nursery().isInside((void*)nullptr));
+
+ JS_GC(cx);
+
+ JS::RootedObject object(cx, JS_NewPlainObject(cx));
+
+ /* Objects are initially allocated in the nursery. */
+ CHECK(js::gc::IsInsideNursery(object));
+
+ JS_GC(cx);
+
+ /* And are tenured if still live after a GC. */
+ CHECK(!js::gc::IsInsideNursery(object));
+
+ return true;
+}
+END_TEST(testIsInsideNursery)
diff --git a/js/src/jsapi-tests/testIteratorObject.cpp b/js/src/jsapi-tests/testIteratorObject.cpp
new file mode 100644
index 0000000000..71ac16dabb
--- /dev/null
+++ b/js/src/jsapi-tests/testIteratorObject.cpp
@@ -0,0 +1,34 @@
+/* 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 "js/Class.h" // js::ESClass
+#include "js/Object.h" // JS::GetBuiltinClass
+#include "js/RootingAPI.h" // JS::Rooted
+#include "js/Value.h" // JS::Value
+#include "jsapi-tests/tests.h"
+
+BEGIN_TEST(testIteratorObject) {
+ using js::ESClass;
+
+ JS::Rooted<JS::Value> result(cx);
+
+ EVAL("new Map([['key1', 'value1'], ['key2', 'value2']]).entries()", &result);
+
+ CHECK(result.isObject());
+ JS::Rooted<JSObject*> obj1(cx, &result.toObject());
+ ESClass class1 = ESClass::Other;
+ CHECK(JS::GetBuiltinClass(cx, obj1, &class1));
+ CHECK(class1 == ESClass::MapIterator);
+
+ EVAL("new Set(['value1', 'value2']).entries()", &result);
+
+ CHECK(result.isObject());
+ JS::Rooted<JSObject*> obj2(cx, &result.toObject());
+ ESClass class2 = ESClass::Other;
+ CHECK(JS::GetBuiltinClass(cx, obj2, &class2));
+ CHECK(class2 == ESClass::SetIterator);
+
+ return true;
+}
+END_TEST(testIteratorObject)
diff --git a/js/src/jsapi-tests/testJSEvaluateScript.cpp b/js/src/jsapi-tests/testJSEvaluateScript.cpp
new file mode 100644
index 0000000000..1dfc9ea3cb
--- /dev/null
+++ b/js/src/jsapi-tests/testJSEvaluateScript.cpp
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ */
+
+#include "js/CompilationAndEvaluation.h"
+#include "js/PropertyAndElement.h" // JS_AlreadyHasOwnProperty, JS_HasProperty
+#include "js/SourceText.h"
+#include "jsapi-tests/tests.h"
+#include "util/Text.h"
+
+BEGIN_TEST(testJSEvaluateScript) {
+ JS::RootedObject obj(cx, JS_NewPlainObject(cx));
+ CHECK(obj);
+
+ static const char16_t src[] = u"var x = 5;";
+
+ JS::RootedValue retval(cx);
+ JS::CompileOptions opts(cx);
+ JS::RootedObjectVector scopeChain(cx);
+ CHECK(scopeChain.append(obj));
+
+ JS::SourceText<char16_t> srcBuf;
+ CHECK(srcBuf.init(cx, src, js_strlen(src), JS::SourceOwnership::Borrowed));
+
+ CHECK(JS::Evaluate(cx, scopeChain, opts.setFileAndLine(__FILE__, __LINE__),
+ srcBuf, &retval));
+
+ bool hasProp = true;
+ CHECK(JS_AlreadyHasOwnProperty(cx, obj, "x", &hasProp));
+ CHECK(hasProp);
+
+ hasProp = false;
+ CHECK(JS_HasProperty(cx, global, "x", &hasProp));
+ CHECK(!hasProp);
+
+ return true;
+}
+END_TEST(testJSEvaluateScript)
diff --git a/js/src/jsapi-tests/testJSON.cpp b/js/src/jsapi-tests/testJSON.cpp
new file mode 100644
index 0000000000..5c33e86368
--- /dev/null
+++ b/js/src/jsapi-tests/testJSON.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 <limits>
+#include <string.h>
+
+#include "js/Array.h" // JS::IsArrayObject
+#include "js/Exception.h"
+#include "js/friend/ErrorMessages.h" // JSMSG_*
+#include "js/JSON.h"
+#include "js/MemoryFunctions.h"
+#include "js/Printf.h"
+#include "js/PropertyAndElement.h" // JS_GetProperty
+#include "jsapi-tests/tests.h"
+
+using namespace JS;
+
+static bool Cmp(const char16_t* buffer, uint32_t length,
+ const char16_t* expected) {
+ uint32_t i = 0;
+ while (*expected) {
+ if (i >= length || *expected != *buffer) {
+ return false;
+ }
+ ++expected;
+ ++buffer;
+ ++i;
+ }
+ return i == length;
+}
+
+static bool Callback(const char16_t* buffer, uint32_t length, void* data) {
+ const char16_t** data_ = static_cast<const char16_t**>(data);
+ const char16_t* expected = *data_;
+ *data_ = nullptr;
+ return Cmp(buffer, length, expected);
+}
+
+BEGIN_TEST(testToJSON_same) {
+ // Primitives
+ Rooted<Value> input(cx);
+ input = JS::TrueValue();
+ CHECK(TryToJSON(cx, input, u"true"));
+
+ input = JS::FalseValue();
+ CHECK(TryToJSON(cx, input, u"false"));
+
+ input = JS::NullValue();
+ CHECK(TryToJSON(cx, input, u"null"));
+
+ input.setInt32(0);
+ CHECK(TryToJSON(cx, input, u"0"));
+
+ input.setInt32(1);
+ CHECK(TryToJSON(cx, input, u"1"));
+
+ input.setInt32(-1);
+ CHECK(TryToJSON(cx, input, u"-1"));
+
+ input.setDouble(1);
+ CHECK(TryToJSON(cx, input, u"1"));
+
+ input.setDouble(1.75);
+ CHECK(TryToJSON(cx, input, u"1.75"));
+
+ input.setDouble(9e9);
+ CHECK(TryToJSON(cx, input, u"9000000000"));
+
+ input.setDouble(std::numeric_limits<double>::infinity());
+ CHECK(TryToJSON(cx, input, u"null"));
+ return true;
+}
+
+bool TryToJSON(JSContext* cx, Handle<Value> value, const char16_t* expected) {
+ {
+ Rooted<Value> v(cx, value);
+ const char16_t* expected_ = expected;
+ CHECK(JS_Stringify(cx, &v, nullptr, JS::NullHandleValue, Callback,
+ &expected_));
+ CHECK_NULL(expected_);
+ }
+ {
+ const char16_t* expected_ = expected;
+ CHECK(JS::ToJSON(cx, value, nullptr, JS::NullHandleValue, Callback,
+ &expected_));
+ CHECK_NULL(expected_);
+ }
+ return true;
+}
+END_TEST(testToJSON_same)
+
+BEGIN_TEST(testToJSON_different) {
+ Rooted<Symbol*> symbol(cx, NewSymbol(cx, nullptr));
+ Rooted<Value> value(cx, SymbolValue(symbol));
+
+ CHECK(JS::ToJSON(cx, value, nullptr, JS::NullHandleValue, UnreachedCallback,
+ nullptr));
+ const char16_t* expected = u"null";
+ CHECK(JS_Stringify(cx, &value, nullptr, JS::NullHandleValue, Callback,
+ &expected));
+ CHECK_NULL(expected);
+ return true;
+}
+
+static bool UnreachedCallback(const char16_t*, uint32_t, void*) {
+ MOZ_CRASH("Should not call the callback");
+}
+
+END_TEST(testToJSON_different)
diff --git a/js/src/jsapi-tests/testJitABIcalls.cpp b/js/src/jsapi-tests/testJitABIcalls.cpp
new file mode 100644
index 0000000000..88f5e31134
--- /dev/null
+++ b/js/src/jsapi-tests/testJitABIcalls.cpp
@@ -0,0 +1,718 @@
+/* -*- 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/FloatingPoint.h"
+#include "mozilla/IntegerTypeTraits.h"
+
+#include <iterator>
+
+#include "jit/ABIFunctions.h"
+#include "jit/IonAnalysis.h"
+#include "jit/Linker.h"
+#include "jit/MacroAssembler.h"
+#include "jit/MIRGenerator.h"
+#include "jit/MIRGraph.h"
+#include "jit/ValueNumbering.h"
+#include "jit/VMFunctions.h"
+#include "js/Value.h"
+
+#include "jsapi-tests/tests.h"
+#include "jsapi-tests/testsJit.h"
+
+#include "jit/ABIFunctionList-inl.h"
+#include "jit/MacroAssembler-inl.h"
+#include "jit/VMFunctionList-inl.h"
+
+using namespace js;
+using namespace js::jit;
+
+// This test case relies on VMFUNCTION_LIST, TAIL_CALL_VMFUNCTION_LIST,
+// ABIFUNCTION_LIST, ABIFUNCTION_AND_TYPE_LIST and ABIFUNCTIONSIG_LIST, to
+// create a test case for each function registered, in order to check if the
+// arguments are properly being interpreted after a call from the JIT.
+//
+// This test checks that the interpretation of the C++ compiler matches the
+// interpretation of the JIT. It works by generating a call to a function which
+// has the same signature as the tested function. The function being called
+// re-interprets the arguments' content to ensure that it matches the content
+// given as arguments by the JIT.
+//
+// These tests cases succeed if the content provided by the JIT matches the
+// content read by the C++ code. Otherwise, a failure implies that either the
+// MacroAssembler is not used properly, or that the code used by the JIT to
+// generate the function call does not match the ABI of the targeted system.
+
+// Convert the content of each macro list to a single and unique format which is
+// (Name, Type).
+#define ABIFUN_TO_ALLFUN(Fun) (#Fun, decltype(&::Fun))
+#define ABIFUN_AND_SIG_TO_ALLFUN(Fun, Sig) (#Fun " as " #Sig, Sig)
+#define ABISIG_TO_ALLFUN(Sig) ("(none) as " #Sig, Sig)
+#define VMFUN_TO_ALLFUN(Name, Fun) (#Fun, decltype(&::Fun))
+#define TC_VMFUN_TO_ALLFUN(Name, Fun, Pop) (#Fun, decltype(&::Fun))
+
+#define APPLY(A, B) A B
+
+// Generate macro calls for all the lists which are used to allow, directly or
+// indirectly, calls performed with callWithABI.
+//
+// This macro will delegate to a different macro call based on the type of the
+// list the element is extracted from.
+#define ALL_FUNCTIONS(PREFIX) \
+ ABIFUNCTION_LIST(PREFIX##_ABIFUN_TO_ALLFUN) \
+ ABIFUNCTION_AND_TYPE_LIST(PREFIX##_ABIFUN_AND_SIG_TO_ALLFUN) \
+ ABIFUNCTIONSIG_LIST(PREFIX##_ABISIG_TO_ALLFUN) \
+ VMFUNCTION_LIST(PREFIX##_VMFUN_TO_ALLFUN) \
+ TAIL_CALL_VMFUNCTION_LIST(PREFIX##_TC_VMFUN_TO_ALLFUN)
+
+// sizeof(const T&) is not equal to sizeof(const T*), but references are passed
+// as pointers.
+//
+// "When applied to a reference or a reference type, the result is the size of
+// the referenced type." [expr.sizeof] (5.3.3.2)
+//
+// The following functions avoid this issue by wrapping the type in a structure
+// which will share the same property, even if the wrapped type is a reference.
+template <typename T>
+constexpr size_t ActualSizeOf() {
+ struct Wrapper {
+ T _unused;
+ };
+ return sizeof(Wrapper);
+}
+
+template <typename T>
+constexpr size_t ActualAlignOf() {
+ struct Wrapper {
+ T _unused;
+ };
+ return alignof(Wrapper);
+}
+
+// Given a type, return the integer type which has the same size.
+template <typename T>
+using IntTypeOf_t =
+ typename mozilla::UnsignedStdintTypeForSize<ActualSizeOf<T>()>::Type;
+
+// Concatenate 2 std::integer_sequence, and return an std::integer_sequence with
+// the content of both parameters.
+template <typename Before, typename After>
+struct Concat;
+
+template <typename Int, Int... Before, Int... After>
+struct Concat<std::integer_sequence<Int, Before...>,
+ std::integer_sequence<Int, After...>> {
+ using type = std::integer_sequence<Int, Before..., After...>;
+};
+
+template <typename Before, typename After>
+using Concat_t = typename Concat<Before, After>::type;
+
+static_assert(std::is_same_v<Concat_t<std::integer_sequence<uint8_t, 1, 2>,
+ std::integer_sequence<uint8_t, 3, 4>>,
+ std::integer_sequence<uint8_t, 1, 2, 3, 4>>);
+
+// Generate an std::integer_sequence of `N` elements, where each element is an
+// uint8_t integer with value `Value`.
+template <size_t N, uint8_t Value>
+constexpr auto CstSeq() {
+ if constexpr (N == 0) {
+ return std::integer_sequence<uint8_t>{};
+ } else {
+ return Concat_t<std::integer_sequence<uint8_t, Value>,
+ decltype(CstSeq<N - 1, Value>())>{};
+ }
+}
+
+template <size_t N, uint8_t Value>
+using CstSeq_t = decltype(CstSeq<N, Value>());
+
+static_assert(
+ std::is_same_v<CstSeq_t<4, 2>, std::integer_sequence<uint8_t, 2, 2, 2, 2>>);
+
+// Computes the number of bytes to add before a type in order to align it in
+// memory.
+constexpr size_t PadBytes(size_t size, size_t align) {
+ return (align - (size % align)) % align;
+}
+
+// Request a minimum alignment for the values added to a buffer in order to
+// account for the read size used by the MoveOperand given as an argument of
+// passWithABI. The MoveOperand does not take into consideration the size of
+// the data being transfered, and might load a larger amount of data.
+//
+// This function ensures that the MoveOperand would read the 0x55 padding added
+// after each value, when it reads too much.
+constexpr size_t AtLeastSize() { return sizeof(uintptr_t); }
+
+// Returns the size which needs to be added in addition to the memory consumed
+// by the type, from which the size if given as argument.
+template <typename Type>
+constexpr size_t BackPadBytes() {
+ return std::max(AtLeastSize(), ActualSizeOf<Type>()) - ActualSizeOf<Type>();
+}
+
+// Adds the padding and the reserved size for storing a value in a buffer which
+// can be read by a MoveOperand.
+template <typename Type>
+constexpr size_t PadSize(size_t size) {
+ return PadBytes(size, ActualAlignOf<Type>()) + ActualSizeOf<Type>() +
+ BackPadBytes<Type>();
+}
+
+// Generate an std::integer_sequence of 0:uint8_t elements of the size of the
+// padding needed to align a type in memory.
+template <size_t Align, size_t CurrSize>
+using PadSeq_t = decltype(CstSeq<PadBytes(CurrSize, Align), 0>());
+
+static_assert(std::is_same_v<PadSeq_t<4, 0>, std::integer_sequence<uint8_t>>);
+static_assert(
+ std::is_same_v<PadSeq_t<4, 3>, std::integer_sequence<uint8_t, 0>>);
+static_assert(
+ std::is_same_v<PadSeq_t<4, 2>, std::integer_sequence<uint8_t, 0, 0>>);
+static_assert(
+ std::is_same_v<PadSeq_t<4, 1>, std::integer_sequence<uint8_t, 0, 0, 0>>);
+
+// Spread an integer value `Value` into a new std::integer_sequence of `N`
+// uint8_t elements, using Little Endian ordering of bytes.
+template <size_t N, uint64_t Value, uint8_t... Rest>
+constexpr auto FillLESeq() {
+ if constexpr (N == 0) {
+ return std::integer_sequence<uint8_t, Rest...>{};
+ } else {
+ return FillLESeq<N - 1, (Value >> 8), Rest..., uint8_t(Value & 0xff)>();
+ }
+}
+
+template <size_t N, uint64_t Value>
+using FillSeq_t = decltype(FillLESeq<N, Value>());
+
+static_assert(std::is_same_v<FillSeq_t<4, 2>,
+ std::integer_sequence<uint8_t, 2, 0, 0, 0>>);
+
+// Given a list of template parameters, generate an std::integer_sequence of
+// size_t, where each element is 1 larger than the previous one. The generated
+// sequence starts at 0.
+template <typename... Args>
+using ArgsIndexes_t =
+ std::make_integer_sequence<uint64_t, uint64_t(sizeof...(Args))>;
+
+static_assert(std::is_same_v<ArgsIndexes_t<uint8_t, uint64_t>,
+ std::integer_sequence<uint64_t, 0, 1>>);
+
+// Extract a single bit for each element of an std::integer_sequence. This is
+// used to work around some restrictions with providing boolean arguments,
+// which might be truncated to a single bit.
+template <size_t Bit, typename IntSeq>
+struct ExtractBit;
+
+template <size_t Bit, uint64_t... Values>
+struct ExtractBit<Bit, std::integer_sequence<uint64_t, Values...>> {
+ using type = std::integer_sequence<uint64_t, (Values >> Bit) & 1 ...>;
+};
+
+// Generate an std::integer_sequence of indexes which are filtered for a single
+// bit, such that it can be used with boolean types.
+template <size_t Bit, typename... Args>
+using ArgsBitOfIndexes_t =
+ typename ExtractBit<Bit, ArgsIndexes_t<Args...>>::type;
+
+static_assert(std::is_same_v<ArgsBitOfIndexes_t<0, int, int, int, int>,
+ std::integer_sequence<uint64_t, 0, 1, 0, 1>>);
+static_assert(std::is_same_v<ArgsBitOfIndexes_t<1, int, int, int, int>,
+ std::integer_sequence<uint64_t, 0, 0, 1, 1>>);
+
+// Compute the offset of each argument in a buffer produced by GenArgsBuffer,
+// this is used to fill the MoveOperand displacement field when loading value
+// out of the buffer produced by GenArgsBuffer.
+template <uint64_t Size, typename... Args>
+struct ArgsOffsets;
+
+template <uint64_t Size>
+struct ArgsOffsets<Size> {
+ using type = std::integer_sequence<uint64_t>;
+};
+
+template <uint64_t Size, typename Arg, typename... Args>
+struct ArgsOffsets<Size, Arg, Args...> {
+ using type =
+ Concat_t<std::integer_sequence<
+ uint64_t, Size + PadBytes(Size, ActualAlignOf<Arg>())>,
+ typename ArgsOffsets<Size + PadSize<Arg>(Size), Args...>::type>;
+};
+
+template <uint64_t Size, typename... Args>
+using ArgsOffsets_t = typename ArgsOffsets<Size, Args...>::type;
+
+// Not all 32bits architecture align uint64_t type on 8 bytes, so check the
+// validity of the stored content based on the alignment of the architecture.
+static_assert(ActualAlignOf<uint64_t>() != 8 ||
+ std::is_same_v<ArgsOffsets_t<0, uint8_t, uint64_t, bool>,
+ std::integer_sequence<uint64_t, 0, 8, 16>>);
+
+static_assert(ActualAlignOf<uint64_t>() != 4 ||
+ std::is_same_v<ArgsOffsets_t<0, uint8_t, uint64_t, bool>,
+ std::integer_sequence<uint64_t, 0, 4, 12>>);
+
+// Generate an std::integer_sequence containing the size of each argument in
+// memory.
+template <typename... Args>
+using ArgsSizes_t = std::integer_sequence<uint64_t, ActualSizeOf<Args>()...>;
+
+// Generate an std::integer_sequence containing values where all valid bits for
+// each type are set to 1.
+template <typename Type>
+constexpr uint64_t FillBits() {
+ constexpr uint64_t topBit = uint64_t(1) << ((8 * ActualSizeOf<Type>()) - 1);
+ if constexpr (std::is_same_v<Type, bool>) {
+ return uint64_t(1);
+ } else if constexpr (std::is_same_v<Type, double> ||
+ std::is_same_v<Type, float>) {
+ // A NaN has all the bits of its exponent set to 1. The CPU / C++ does not
+ // garantee keeping the payload of NaN values, when interpreted as floating
+ // point, which could cause some random failures. This removes one bit from
+ // the exponent, such that the floating point value is not converted to a
+ // canonicalized NaN by the time we compare it.
+ constexpr uint64_t lowExpBit =
+ uint64_t(1) << mozilla::FloatingPoint<Type>::kExponentShift;
+ return uint64_t((topBit - 1) + topBit - lowExpBit);
+ } else {
+ // Note: Keep parentheses to avoid unwanted overflow.
+ return uint64_t((topBit - 1) + topBit);
+ }
+}
+
+template <typename... Args>
+using ArgsFillBits_t = std::integer_sequence<uint64_t, FillBits<Args>()...>;
+
+// Given a type, return the MoveOp type used by passABIArg to know how to
+// interpret the value which are given as arguments.
+template <typename Type>
+constexpr MoveOp::Type TypeToMoveOp() {
+ if constexpr (std::is_same_v<Type, float>) {
+ return MoveOp::FLOAT32;
+ } else if constexpr (std::is_same_v<Type, double>) {
+ return MoveOp::DOUBLE;
+ } else {
+ return MoveOp::GENERAL;
+ }
+}
+
+// Generate a sequence which contains the associated MoveOp of each argument.
+// Note, a new class is defined because C++ header of clang are rejecting the
+// option of having an enumerated type as argument of std::integer_sequence.
+template <MoveOp::Type... Val>
+class MoveOpSequence {};
+
+template <typename... Args>
+using ArgsMoveOps_t = MoveOpSequence<TypeToMoveOp<Args>()...>;
+
+// Generate an std::integer_sequence which corresponds to a buffer containing
+// values which are spread at the location where each arguments type would be
+// stored in a buffer.
+template <typename Buffer, typename Values, typename... Args>
+struct ArgsBuffer;
+
+template <uint8_t... Buffer, typename Arg, typename... Args, uint64_t Val,
+ uint64_t... Values>
+struct ArgsBuffer<std::integer_sequence<uint8_t, Buffer...>,
+ std::integer_sequence<uint64_t, Val, Values...>, Arg,
+ Args...> {
+ using type = typename ArgsBuffer<
+ Concat_t<std::integer_sequence<uint8_t, Buffer...>,
+ Concat_t<PadSeq_t<ActualAlignOf<Arg>(), sizeof...(Buffer)>,
+ Concat_t<FillSeq_t<ActualSizeOf<Arg>(), Val>,
+ CstSeq_t<BackPadBytes<Arg>(), 0x55>>>>,
+ std::integer_sequence<uint64_t, Values...>, Args...>::type;
+};
+
+template <typename Buffer>
+struct ArgsBuffer<Buffer, std::integer_sequence<uint64_t>> {
+ using type = Buffer;
+};
+
+template <typename Values, typename... Args>
+using ArgsBuffer_t =
+ typename ArgsBuffer<std::integer_sequence<uint8_t>, Values, Args...>::type;
+
+// NOTE: The representation of the boolean might be surprising in this test
+// case, see AtLeastSize function for an explanation.
+#ifdef JS_64BIT
+static_assert(sizeof(uintptr_t) == 8);
+static_assert(
+ std::is_same_v<
+ ArgsBuffer_t<std::integer_sequence<uint64_t, 42, 51>, uint64_t, bool>,
+ std::integer_sequence<uint8_t, 42, 0, 0, 0, 0, 0, 0, 0, 51, 0x55, 0x55,
+ 0x55, 0x55, 0x55, 0x55, 0x55>>);
+static_assert(
+ std::is_same_v<
+ ArgsBuffer_t<std::integer_sequence<uint64_t, 0xffffffff, 0xffffffff>,
+ uint8_t, uint16_t>,
+ std::integer_sequence<uint8_t, 0xff, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x55>>);
+#else
+static_assert(sizeof(uintptr_t) == 4);
+static_assert(
+ std::is_same_v<
+ ArgsBuffer_t<std::integer_sequence<uint64_t, 42, 51>, uint64_t, bool>,
+ std::integer_sequence<uint8_t, 42, 0, 0, 0, 0, 0, 0, 0, 51, 0x55, 0x55,
+ 0x55>>);
+static_assert(
+ std::is_same_v<
+ ArgsBuffer_t<std::integer_sequence<uint64_t, 0xffffffff, 0xffffffff>,
+ uint8_t, uint16_t>,
+ std::integer_sequence<uint8_t, 0xff, 0x55, 0x55, 0x55, 0xff, 0xff, 0x55,
+ 0x55>>);
+#endif
+
+// Test used to check if any of the types given as template parameters are a
+// `bool`, which is a corner case where a raw integer might be truncated by the
+// C++ compiler.
+template <typename... Args>
+constexpr bool AnyBool_v = (std::is_same_v<Args, bool> || ...);
+
+// Instantiate an std::integer_sequence as a buffer which is readable and
+// addressable at runtime, for reading argument values from the generated code.
+template <typename Seq>
+struct InstanceSeq;
+
+template <typename Int, Int... Values>
+struct InstanceSeq<std::integer_sequence<Int, Values...>> {
+ static constexpr Int table[sizeof...(Values)] = {Values...};
+ static constexpr size_t size = sizeof...(Values);
+};
+
+// Instantiate a buffer for testing the position of arguments when calling a
+// function.
+template <typename... Args>
+using TestArgsPositions =
+ InstanceSeq<ArgsBuffer_t<ArgsIndexes_t<Args...>, Args...>>;
+
+// Instantiate a buffer for testing the position of arguments, one bit at a
+// time, when calling a function.
+template <size_t Bit, typename... Args>
+using TestArgsBitOfPositions =
+ InstanceSeq<ArgsBuffer_t<ArgsBitOfIndexes_t<Bit, Args...>, Args...>>;
+
+// Instantiate a buffer to check that the size of each argument is interpreted
+// correctly when calling a function.
+template <typename... Args>
+using TestArgsSizes = InstanceSeq<ArgsBuffer_t<ArgsSizes_t<Args...>, Args...>>;
+
+// Instantiate a buffer to check that all bits of each argument goes through.
+template <typename... Args>
+using TestArgsFillBits =
+ InstanceSeq<ArgsBuffer_t<ArgsFillBits_t<Args...>, Args...>>;
+
+// ConvertToInt returns the raw value of any argument as an integer value which
+// can be compared with the expected values.
+template <typename Type>
+IntTypeOf_t<Type> ConvertToInt(Type v) {
+ // Simplify working with types by casting the address of the value to the
+ // equivalent `const void*`.
+ auto address = reinterpret_cast<const void*>(&v);
+ // Convert the `void*` to an integer pointer of the same size as the input
+ // type, and return the raw value stored in the integer interpretation.
+ static_assert(ActualSizeOf<Type>() == ActualSizeOf<IntTypeOf_t<Type>>());
+ if constexpr (std::is_reference_v<Type>) {
+ return reinterpret_cast<const IntTypeOf_t<Type>>(address);
+ } else {
+ return *reinterpret_cast<const IntTypeOf_t<Type>*>(address);
+ }
+}
+
+// Attributes used to disable some parts of Undefined Behavior sanitizer. This
+// is needed to keep the signature identical to what is used in production,
+// instead of working around these limitations.
+//
+// * no_sanitize("enum"): Enumerated values given as arguments are checked to
+// see if the value given as argument matches any of the enumerated values.
+// The patterns used to check whether the values are correctly transmitted
+// from the JIT to C++ might go beyond the set of enumerated values, and
+// break this sanitizer check.
+#if defined(__clang__) && defined(__has_attribute) && \
+ __has_attribute(no_sanitize)
+# define NO_ARGS_CHECKS __attribute__((no_sanitize("enum")))
+#else
+# define NO_ARGS_CHECKS
+#endif
+
+// Check if the raw values of arguments are equal to the numbers given in the
+// std::integer_sequence given as the first argument.
+template <typename... Args, typename Int, Int... Val>
+NO_ARGS_CHECKS bool CheckArgsEqual(JSAPIRuntimeTest* instance, int lineno,
+ std::integer_sequence<Int, Val...>,
+ Args... args) {
+ return (instance->checkEqual(ConvertToInt<Args>(args), IntTypeOf_t<Args>(Val),
+ "ConvertToInt<Args>(args)",
+ "IntTypeOf_t<Args>(Val)", __FILE__, lineno) &&
+ ...);
+}
+
+// Generate code to register the value of each argument of the called function.
+// Each argument's content is read from a buffer whose address is stored in the
+// `base` register. The offsets of arguments are given as a third argument
+// which is expected to be generated by `ArgsOffsets`. The MoveOp types of
+// arguments are given as the fourth argument and are expected to be generated
+// by `ArgsMoveOp`.
+template <uint64_t... Off, MoveOp::Type... Move>
+static void passABIArgs(MacroAssembler& masm, Register base,
+ std::integer_sequence<uint64_t, Off...>,
+ MoveOpSequence<Move...>) {
+ (masm.passABIArg(MoveOperand(base, size_t(Off)), Move), ...);
+}
+
+// For each function type given as a parameter, create a few functions with the
+// given type, to be called by the JIT code produced by `generateCalls`. These
+// functions report the result through the instance registered with the
+// `set_instance` function, as we cannot add extra arguments to these functions.
+template <typename Type>
+struct DefineCheckArgs;
+
+template <typename Res, typename... Args>
+struct DefineCheckArgs<Res (*)(Args...)> {
+ void set_instance(JSAPIRuntimeTest* instance, bool* reportTo) {
+ MOZ_ASSERT((!instance_) != (!instance));
+ instance_ = instance;
+ MOZ_ASSERT((!reportTo_) != (!reportTo));
+ reportTo_ = reportTo;
+ }
+ static void report(bool value) { *reportTo_ = *reportTo_ && value; }
+
+ // Check that arguments are interpreted in the same order at compile time and
+ // at runtime.
+ static NO_ARGS_CHECKS Res CheckArgsPositions(Args... args) {
+ AutoUnsafeCallWithABI unsafe;
+ using Indexes = std::index_sequence_for<Args...>;
+ report(CheckArgsEqual<Args...>(instance_, __LINE__, Indexes(),
+ std::forward<Args>(args)...));
+ return Res();
+ }
+
+ // This is the same test as above, but some compilers might clean the boolean
+ // values using `& 1` operations, which corrupt the operand index, thus to
+ // properly check for the position of boolean operands, we have to check the
+ // position of the boolean operand using a single bit at a time.
+ static NO_ARGS_CHECKS Res CheckArgsBitOfPositions0(Args... args) {
+ AutoUnsafeCallWithABI unsafe;
+ using Indexes = ArgsBitOfIndexes_t<0, Args...>;
+ report(CheckArgsEqual<Args...>(instance_, __LINE__, Indexes(),
+ std::forward<Args>(args)...));
+ return Res();
+ }
+
+ static NO_ARGS_CHECKS Res CheckArgsBitOfPositions1(Args... args) {
+ AutoUnsafeCallWithABI unsafe;
+ using Indexes = ArgsBitOfIndexes_t<1, Args...>;
+ report(CheckArgsEqual<Args...>(instance_, __LINE__, Indexes(),
+ std::forward<Args>(args)...));
+ return Res();
+ }
+
+ static NO_ARGS_CHECKS Res CheckArgsBitOfPositions2(Args... args) {
+ AutoUnsafeCallWithABI unsafe;
+ using Indexes = ArgsBitOfIndexes_t<2, Args...>;
+ report(CheckArgsEqual<Args...>(instance_, __LINE__, Indexes(),
+ std::forward<Args>(args)...));
+ return Res();
+ }
+
+ static NO_ARGS_CHECKS Res CheckArgsBitOfPositions3(Args... args) {
+ AutoUnsafeCallWithABI unsafe;
+ using Indexes = ArgsBitOfIndexes_t<3, Args...>;
+ report(CheckArgsEqual<Args...>(instance_, __LINE__, Indexes(),
+ std::forward<Args>(args)...));
+ return Res();
+ }
+
+ // Check that the compile time and run time sizes of each argument are the
+ // same.
+ static NO_ARGS_CHECKS Res CheckArgsSizes(Args... args) {
+ AutoUnsafeCallWithABI unsafe;
+ using Sizes = ArgsSizes_t<Args...>;
+ report(CheckArgsEqual<Args...>(instance_, __LINE__, Sizes(),
+ std::forward<Args>(args)...));
+ return Res();
+ }
+
+ // Check that the compile time and run time all bits of each argument are
+ // correctly passed through.
+ static NO_ARGS_CHECKS Res CheckArgsFillBits(Args... args) {
+ AutoUnsafeCallWithABI unsafe;
+ using FillBits = ArgsFillBits_t<Args...>;
+ report(CheckArgsEqual<Args...>(instance_, __LINE__, FillBits(),
+ std::forward<Args>(args)...));
+ return Res();
+ }
+
+ using FunType = Res (*)(Args...);
+ struct Test {
+ const uint8_t* buffer;
+ const size_t size;
+ const FunType fun;
+ };
+
+ // Generate JIT code for calling the above test functions where each argument
+ // is given a different raw value that can be compared by each called
+ // function.
+ void generateCalls(MacroAssembler& masm, Register base, Register setup) {
+ using ArgsPositions = TestArgsPositions<Args...>;
+ using ArgsBitOfPositions0 = TestArgsBitOfPositions<0, Args...>;
+ using ArgsBitOfPositions1 = TestArgsBitOfPositions<1, Args...>;
+ using ArgsBitOfPositions2 = TestArgsBitOfPositions<2, Args...>;
+ using ArgsBitOfPositions3 = TestArgsBitOfPositions<3, Args...>;
+ using ArgsSizes = TestArgsSizes<Args...>;
+ using ArgsFillBits = TestArgsFillBits<Args...>;
+ static const Test testsWithoutBoolArgs[3] = {
+ {ArgsPositions::table, ArgsPositions::size, CheckArgsPositions},
+ {ArgsSizes::table, ArgsSizes::size, CheckArgsSizes},
+ {ArgsFillBits::table, ArgsFillBits::size, CheckArgsFillBits},
+ };
+ static const Test testsWithBoolArgs[6] = {
+ {ArgsBitOfPositions0::table, ArgsBitOfPositions0::size,
+ CheckArgsBitOfPositions0},
+ {ArgsBitOfPositions1::table, ArgsBitOfPositions1::size,
+ CheckArgsBitOfPositions1},
+ {ArgsBitOfPositions2::table, ArgsBitOfPositions2::size,
+ CheckArgsBitOfPositions2},
+ {ArgsBitOfPositions3::table, ArgsBitOfPositions3::size,
+ CheckArgsBitOfPositions3},
+ {ArgsSizes::table, ArgsSizes::size, CheckArgsSizes},
+ {ArgsFillBits::table, ArgsFillBits::size, CheckArgsFillBits},
+ };
+ const Test* tests = testsWithoutBoolArgs;
+ size_t numTests = std::size(testsWithoutBoolArgs);
+ if (AnyBool_v<Args...>) {
+ tests = testsWithBoolArgs;
+ numTests = std::size(testsWithBoolArgs);
+ }
+
+ for (size_t i = 0; i < numTests; i++) {
+ const Test& test = tests[i];
+ masm.movePtr(ImmPtr(test.buffer), base);
+
+ masm.setupUnalignedABICall(setup);
+ using Offsets = ArgsOffsets_t<0, Args...>;
+ using MoveOps = ArgsMoveOps_t<Args...>;
+ passABIArgs(masm, base, Offsets(), MoveOps());
+ masm.callWithABI(DynFn{JS_FUNC_TO_DATA_PTR(void*, test.fun)},
+ TypeToMoveOp<Res>(),
+ CheckUnsafeCallWithABI::DontCheckOther);
+ }
+ }
+
+ private:
+ // As we are checking specific function signature, we cannot add extra
+ // parameters, thus we rely on static variables to pass the value of the
+ // instance that we are testing.
+ static JSAPIRuntimeTest* instance_;
+ static bool* reportTo_;
+};
+
+template <typename Res, typename... Args>
+JSAPIRuntimeTest* DefineCheckArgs<Res (*)(Args...)>::instance_ = nullptr;
+
+template <typename Res, typename... Args>
+bool* DefineCheckArgs<Res (*)(Args...)>::reportTo_ = nullptr;
+
+// This is a child class of JSAPIRuntimeTest, which is used behind the scenes to
+// register test cases in jsapi-tests. Each instance of it creates a new test
+// case. This class is specialized with the type of the function to check, and
+// initialized with the name of the function with the given signature.
+//
+// When executed, it generates the JIT code to call functions with the same
+// signature and checks that the JIT interpretation of arguments location
+// matches the C++ interpretation. If it differs, the test case will fail.
+template <typename Sig>
+class JitABICall final : public JSAPIRuntimeTest, public DefineCheckArgs<Sig> {
+ public:
+ explicit JitABICall(const char* name) : name_(name) { reuseGlobal = true; }
+ virtual const char* name() override { return name_; }
+ virtual bool run(JS::HandleObject) override {
+ bool result = true;
+ this->set_instance(this, &result);
+
+ TempAllocator temp(&cx->tempLifoAlloc());
+ JitContext jcx(cx);
+ StackMacroAssembler masm(cx, temp);
+ AutoCreatedBy acb(masm, __func__);
+ PrepareJit(masm);
+
+ AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All());
+ // Initialize the base register the same way this is done while reading
+ // arguments in generateVMWrapper, in order to avoid MOZ_RELEASE_ASSERT in
+ // the MoveResolver.
+#if defined(JS_CODEGEN_X86)
+ Register base = regs.takeAny();
+#elif defined(JS_CODEGEN_X64)
+ Register base = r10;
+ regs.take(base);
+#elif defined(JS_CODEGEN_ARM)
+ Register base = r5;
+ regs.take(base);
+#elif defined(JS_CODEGEN_ARM64)
+ Register base = r8;
+ regs.take(base);
+#elif defined(JS_CODEGEN_MIPS32)
+ Register base = t1;
+ regs.take(base);
+#elif defined(JS_CODEGEN_MIPS64)
+ Register base = t1;
+ regs.take(base);
+#elif defined(JS_CODEGEN_LOONG64)
+ Register base = t0;
+ regs.take(base);
+#elif defined(JS_CODEGEN_RISCV64)
+ Register base = t0;
+ regs.take(base);
+#else
+# error "Unknown architecture!"
+#endif
+
+ Register setup = regs.takeAny();
+
+ this->generateCalls(masm, base, setup);
+
+ if (!ExecuteJit(cx, masm)) {
+ return false;
+ }
+ this->set_instance(nullptr, nullptr);
+ return result;
+ };
+
+ private:
+ const char* name_;
+};
+
+// GCC warns when the signature does not have matching attributes (for example
+// [[nodiscard]]). Squelch this warning to avoid a GCC-only footgun.
+#if MOZ_IS_GCC
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wignored-attributes"
+#endif
+
+// For each VMFunction and ABIFunction, create an instance of a JitABICall
+// class to register a jsapi-tests test case.
+#define TEST_INSTANCE(Name, Sig) \
+ static JitABICall<Sig> MOZ_CONCAT(MOZ_CONCAT(cls_jitabicall, __COUNTER__), \
+ _instance)("JIT ABI for " Name);
+#define TEST_INSTANCE_ABIFUN_TO_ALLFUN(...) \
+ APPLY(TEST_INSTANCE, ABIFUN_TO_ALLFUN(__VA_ARGS__))
+#define TEST_INSTANCE_ABIFUN_AND_SIG_TO_ALLFUN(...) \
+ APPLY(TEST_INSTANCE, ABIFUN_AND_SIG_TO_ALLFUN(__VA_ARGS__))
+#define TEST_INSTANCE_ABISIG_TO_ALLFUN(...) \
+ APPLY(TEST_INSTANCE, ABISIG_TO_ALLFUN(__VA_ARGS__))
+#define TEST_INSTANCE_VMFUN_TO_ALLFUN(...) \
+ APPLY(TEST_INSTANCE, VMFUN_TO_ALLFUN(__VA_ARGS__))
+#define TEST_INSTANCE_TC_VMFUN_TO_ALLFUN(...) \
+ APPLY(TEST_INSTANCE, TC_VMFUN_TO_ALLFUN(__VA_ARGS__))
+
+ALL_FUNCTIONS(TEST_INSTANCE)
+
+#if MOZ_IS_GCC
+# pragma GCC diagnostic pop
+#endif
diff --git a/js/src/jsapi-tests/testJitDCEinGVN.cpp b/js/src/jsapi-tests/testJitDCEinGVN.cpp
new file mode 100644
index 0000000000..286d36aa14
--- /dev/null
+++ b/js/src/jsapi-tests/testJitDCEinGVN.cpp
@@ -0,0 +1,147 @@
+/* -*- 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 "jit/IonAnalysis.h"
+#include "jit/MIRGenerator.h"
+#include "jit/MIRGraph.h"
+#include "jit/ValueNumbering.h"
+
+#include "jsapi-tests/testJitMinimalFunc.h"
+#include "jsapi-tests/tests.h"
+
+using namespace js;
+using namespace js::jit;
+
+BEGIN_TEST(testJitDCEinGVN_ins) {
+ MinimalFunc func;
+ MBasicBlock* block = func.createEntryBlock();
+
+ // mul0 = p * p
+ // mul1 = mul0 * mul0
+ // return p
+ MParameter* p = func.createParameter();
+ block->add(p);
+ MMul* mul0 = MMul::New(func.alloc, p, p, MIRType::Double);
+ block->add(mul0);
+ if (!mul0->typePolicy()->adjustInputs(func.alloc, mul0)) {
+ return false;
+ }
+ MMul* mul1 = MMul::New(func.alloc, mul0, mul0, MIRType::Double);
+ block->add(mul1);
+ if (!mul1->typePolicy()->adjustInputs(func.alloc, mul1)) {
+ return false;
+ }
+ MReturn* ret = MReturn::New(func.alloc, p);
+ block->end(ret);
+
+ if (!func.runGVN()) {
+ return false;
+ }
+
+ // mul0 and mul1 should be deleted.
+ for (MInstructionIterator ins = block->begin(); ins != block->end(); ins++) {
+ CHECK(!ins->isMul() ||
+ (ins->getOperand(0) != p && ins->getOperand(1) != p));
+ CHECK(!ins->isMul() ||
+ (ins->getOperand(0) != mul0 && ins->getOperand(1) != mul0));
+ }
+ return true;
+}
+END_TEST(testJitDCEinGVN_ins)
+
+BEGIN_TEST(testJitDCEinGVN_phi) {
+ MinimalFunc func;
+ MBasicBlock* block = func.createEntryBlock();
+ MBasicBlock* thenBlock1 = func.createBlock(block);
+ MBasicBlock* thenBlock2 = func.createBlock(block);
+ MBasicBlock* elifBlock = func.createBlock(block);
+ MBasicBlock* elseBlock = func.createBlock(block);
+ MBasicBlock* joinBlock = func.createBlock(block);
+
+ // if (p) {
+ // x = 1.0;
+ // y = 3.0;
+ // } else if (q) {
+ // x = 2.0;
+ // y = 4.0;
+ // } else {
+ // x = 1.0;
+ // y = 5.0;
+ // }
+ // x = phi(1.0, 2.0, 1.0);
+ // y = phi(3.0, 4.0, 5.0);
+ // z = x * y;
+ // return y;
+
+ MConstant* c1 = MConstant::New(func.alloc, DoubleValue(1.0));
+ block->add(c1);
+ MPhi* x = MPhi::New(func.alloc);
+ MPhi* y = MPhi::New(func.alloc);
+
+ // if (p) {
+ MParameter* p = func.createParameter();
+ block->add(p);
+ block->end(MTest::New(func.alloc, p, thenBlock1, elifBlock));
+
+ // x = 1.0
+ // y = 3.0;
+ MOZ_RELEASE_ASSERT(x->addInputSlow(c1));
+ MConstant* c3 = MConstant::New(func.alloc, DoubleValue(3.0));
+ thenBlock1->add(c3);
+ MOZ_RELEASE_ASSERT(y->addInputSlow(c3));
+ thenBlock1->end(MGoto::New(func.alloc, joinBlock));
+ MOZ_ALWAYS_TRUE(joinBlock->addPredecessor(func.alloc, thenBlock1));
+
+ // } else if (q) {
+ MParameter* q = func.createParameter();
+ elifBlock->add(q);
+ elifBlock->end(MTest::New(func.alloc, q, thenBlock2, elseBlock));
+
+ // x = 2.0
+ // y = 4.0;
+ MConstant* c2 = MConstant::New(func.alloc, DoubleValue(2.0));
+ thenBlock2->add(c2);
+ MOZ_RELEASE_ASSERT(x->addInputSlow(c2));
+ MConstant* c4 = MConstant::New(func.alloc, DoubleValue(4.0));
+ thenBlock2->add(c4);
+ MOZ_RELEASE_ASSERT(y->addInputSlow(c4));
+ thenBlock2->end(MGoto::New(func.alloc, joinBlock));
+ MOZ_ALWAYS_TRUE(joinBlock->addPredecessor(func.alloc, thenBlock2));
+
+ // } else {
+ // x = 1.0
+ // y = 5.0;
+ // }
+ MOZ_RELEASE_ASSERT(x->addInputSlow(c1));
+ MConstant* c5 = MConstant::New(func.alloc, DoubleValue(5.0));
+ elseBlock->add(c5);
+ MOZ_RELEASE_ASSERT(y->addInputSlow(c5));
+ elseBlock->end(MGoto::New(func.alloc, joinBlock));
+ MOZ_ALWAYS_TRUE(joinBlock->addPredecessor(func.alloc, elseBlock));
+
+ // x = phi(1.0, 2.0, 1.0)
+ // y = phi(3.0, 4.0, 5.0)
+ // z = x * y
+ // return y
+ joinBlock->addPhi(x);
+ joinBlock->addPhi(y);
+ MMul* z = MMul::New(func.alloc, x, y, MIRType::Double);
+ joinBlock->add(z);
+ MReturn* ret = MReturn::New(func.alloc, y);
+ joinBlock->end(ret);
+
+ if (!func.runGVN()) {
+ return false;
+ }
+
+ // c1 should be deleted.
+ for (MInstructionIterator ins = block->begin(); ins != block->end(); ins++) {
+ CHECK(!ins->isConstant() || (ins->toConstant()->numberToDouble() != 1.0));
+ }
+ return true;
+}
+END_TEST(testJitDCEinGVN_phi)
diff --git a/js/src/jsapi-tests/testJitFoldsTo.cpp b/js/src/jsapi-tests/testJitFoldsTo.cpp
new file mode 100644
index 0000000000..9537e1dcf7
--- /dev/null
+++ b/js/src/jsapi-tests/testJitFoldsTo.cpp
@@ -0,0 +1,262 @@
+/* -*- 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 "jit/IonAnalysis.h"
+#include "jit/MIRGenerator.h"
+#include "jit/MIRGraph.h"
+#include "jit/ValueNumbering.h"
+
+#include "jsapi-tests/testJitMinimalFunc.h"
+#include "jsapi-tests/tests.h"
+
+using namespace js;
+using namespace js::jit;
+
+BEGIN_TEST(testJitFoldsTo_DivReciprocal) {
+ MinimalFunc func;
+ MBasicBlock* block = func.createEntryBlock();
+
+ // return p / 4.0
+ MParameter* p = func.createParameter();
+ block->add(p);
+ MConstant* c = MConstant::New(func.alloc, DoubleValue(4.0));
+ block->add(c);
+ MDiv* div = MDiv::New(func.alloc, p, c, MIRType::Double);
+ block->add(div);
+ if (!div->typePolicy()->adjustInputs(func.alloc, div)) {
+ return false;
+ }
+ MDefinition* left = div->getOperand(0);
+ MReturn* ret = MReturn::New(func.alloc, div);
+ block->end(ret);
+
+ if (!func.runGVN()) {
+ return false;
+ }
+
+ // Test that the div got folded to p * 0.25.
+ MDefinition* op = ret->getOperand(0);
+ CHECK(op->isMul());
+ CHECK(op->getOperand(0) == left);
+ CHECK(op->getOperand(1)->isConstant());
+ CHECK(op->getOperand(1)->toConstant()->numberToDouble() == 0.25);
+ return true;
+}
+END_TEST(testJitFoldsTo_DivReciprocal)
+
+BEGIN_TEST(testJitFoldsTo_NoDivReciprocal) {
+ MinimalFunc func;
+ MBasicBlock* block = func.createEntryBlock();
+
+ // return p / 5.0
+ MParameter* p = func.createParameter();
+ block->add(p);
+ MConstant* c = MConstant::New(func.alloc, DoubleValue(5.0));
+ block->add(c);
+ MDiv* div = MDiv::New(func.alloc, p, c, MIRType::Double);
+ block->add(div);
+ if (!div->typePolicy()->adjustInputs(func.alloc, div)) {
+ return false;
+ }
+ MDefinition* left = div->getOperand(0);
+ MDefinition* right = div->getOperand(1);
+ MReturn* ret = MReturn::New(func.alloc, div);
+ block->end(ret);
+
+ if (!func.runGVN()) {
+ return false;
+ }
+
+ // Test that the div didn't get folded.
+ MDefinition* op = ret->getOperand(0);
+ CHECK(op->isDiv());
+ CHECK(op->getOperand(0) == left);
+ CHECK(op->getOperand(1) == right);
+ return true;
+}
+END_TEST(testJitFoldsTo_NoDivReciprocal)
+
+BEGIN_TEST(testJitNotNot) {
+ MinimalFunc func;
+ MBasicBlock* block = func.createEntryBlock();
+
+ // return Not(Not(p))
+ MParameter* p = func.createParameter();
+ block->add(p);
+ MNot* not0 = MNot::New(func.alloc, p);
+ block->add(not0);
+ MNot* not1 = MNot::New(func.alloc, not0);
+ block->add(not1);
+ MReturn* ret = MReturn::New(func.alloc, not1);
+ block->end(ret);
+
+ if (!func.runGVN()) {
+ return false;
+ }
+
+ // Test that the nots did not get folded.
+ MDefinition* op = ret->getOperand(0);
+ CHECK(op->isNot());
+ CHECK(op->getOperand(0)->isNot());
+ CHECK(op->getOperand(0)->getOperand(0) == p);
+ return true;
+}
+END_TEST(testJitNotNot)
+
+BEGIN_TEST(testJitNotNotNot) {
+ MinimalFunc func;
+ MBasicBlock* block = func.createEntryBlock();
+
+ // return Not(Not(Not(p)))
+ MParameter* p = func.createParameter();
+ block->add(p);
+ MNot* not0 = MNot::New(func.alloc, p);
+ block->add(not0);
+ MNot* not1 = MNot::New(func.alloc, not0);
+ block->add(not1);
+ MNot* not2 = MNot::New(func.alloc, not1);
+ block->add(not2);
+ MReturn* ret = MReturn::New(func.alloc, not2);
+ block->end(ret);
+
+ if (!func.runGVN()) {
+ return false;
+ }
+
+ // Test that the nots got folded.
+ MDefinition* op = ret->getOperand(0);
+ CHECK(op->isNot());
+ CHECK(op->getOperand(0) == p);
+ return true;
+}
+END_TEST(testJitNotNotNot)
+
+BEGIN_TEST(testJitNotTest) {
+ MinimalFunc func;
+ MBasicBlock* block = func.createEntryBlock();
+ MBasicBlock* then = func.createBlock(block);
+ MBasicBlock* else_ = func.createBlock(block);
+ MBasicBlock* exit = func.createBlock(block);
+
+ // MTest(Not(p))
+ MParameter* p = func.createParameter();
+ block->add(p);
+ MNot* not0 = MNot::New(func.alloc, p);
+ block->add(not0);
+ MTest* test = MTest::New(func.alloc, not0, then, else_);
+ block->end(test);
+
+ then->end(MGoto::New(func.alloc, exit));
+
+ else_->end(MGoto::New(func.alloc, exit));
+
+ MReturn* ret = MReturn::New(func.alloc, p);
+ exit->end(ret);
+
+ MOZ_ALWAYS_TRUE(exit->addPredecessorWithoutPhis(then));
+
+ if (!func.runGVN()) {
+ return false;
+ }
+
+ // Test that the not got folded.
+ test = block->lastIns()->toTest();
+ CHECK(test->getOperand(0) == p);
+ CHECK(test->getSuccessor(0) == else_);
+ CHECK(test->getSuccessor(1) == then);
+ return true;
+}
+END_TEST(testJitNotTest)
+
+BEGIN_TEST(testJitNotNotTest) {
+ MinimalFunc func;
+ MBasicBlock* block = func.createEntryBlock();
+ MBasicBlock* then = func.createBlock(block);
+ MBasicBlock* else_ = func.createBlock(block);
+ MBasicBlock* exit = func.createBlock(block);
+
+ // MTest(Not(Not(p)))
+ MParameter* p = func.createParameter();
+ block->add(p);
+ MNot* not0 = MNot::New(func.alloc, p);
+ block->add(not0);
+ MNot* not1 = MNot::New(func.alloc, not0);
+ block->add(not1);
+ MTest* test = MTest::New(func.alloc, not1, then, else_);
+ block->end(test);
+
+ then->end(MGoto::New(func.alloc, exit));
+
+ else_->end(MGoto::New(func.alloc, exit));
+
+ MReturn* ret = MReturn::New(func.alloc, p);
+ exit->end(ret);
+
+ MOZ_ALWAYS_TRUE(exit->addPredecessorWithoutPhis(then));
+
+ if (!func.runGVN()) {
+ return false;
+ }
+
+ // Test that the nots got folded.
+ test = block->lastIns()->toTest();
+ CHECK(test->getOperand(0) == p);
+ CHECK(test->getSuccessor(0) == then);
+ CHECK(test->getSuccessor(1) == else_);
+ return true;
+}
+END_TEST(testJitNotNotTest)
+
+BEGIN_TEST(testJitFoldsTo_UnsignedDiv) {
+ MinimalFunc func;
+ MBasicBlock* block = func.createEntryBlock();
+
+ // return 1.0 / 0xffffffff
+ MConstant* c0 = MConstant::New(func.alloc, Int32Value(1));
+ block->add(c0);
+ MConstant* c1 = MConstant::New(func.alloc, Int32Value(0xffffffff));
+ block->add(c1);
+ MDiv* div = MDiv::New(func.alloc, c0, c1, MIRType::Int32, /*unsignd=*/true);
+ block->add(div);
+ MReturn* ret = MReturn::New(func.alloc, div);
+ block->end(ret);
+
+ if (!func.runGVN()) {
+ return false;
+ }
+
+ // Test that the div got folded to 0.
+ MConstant* op = ret->getOperand(0)->toConstant();
+ CHECK(mozilla::NumbersAreIdentical(op->numberToDouble(), 0.0));
+ return true;
+}
+END_TEST(testJitFoldsTo_UnsignedDiv)
+
+BEGIN_TEST(testJitFoldsTo_UnsignedMod) {
+ MinimalFunc func;
+ MBasicBlock* block = func.createEntryBlock();
+
+ // return 1.0 % 0xffffffff
+ MConstant* c0 = MConstant::New(func.alloc, Int32Value(1));
+ block->add(c0);
+ MConstant* c1 = MConstant::New(func.alloc, Int32Value(0xffffffff));
+ block->add(c1);
+ MMod* mod = MMod::New(func.alloc, c0, c1, MIRType::Int32, /*unsignd=*/true);
+ block->add(mod);
+ MReturn* ret = MReturn::New(func.alloc, mod);
+ block->end(ret);
+
+ if (!func.runGVN()) {
+ return false;
+ }
+
+ // Test that the mod got folded to 1.
+ MConstant* op = ret->getOperand(0)->toConstant();
+ CHECK(mozilla::NumbersAreIdentical(op->numberToDouble(), 1.0));
+ return true;
+}
+END_TEST(testJitFoldsTo_UnsignedMod)
diff --git a/js/src/jsapi-tests/testJitGVN.cpp b/js/src/jsapi-tests/testJitGVN.cpp
new file mode 100644
index 0000000000..4dd8d64fde
--- /dev/null
+++ b/js/src/jsapi-tests/testJitGVN.cpp
@@ -0,0 +1,299 @@
+/* -*- 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 "jit/IonAnalysis.h"
+#include "jit/MIRGenerator.h"
+#include "jit/MIRGraph.h"
+#include "jit/RangeAnalysis.h"
+#include "jit/ValueNumbering.h"
+
+#include "jsapi-tests/testJitMinimalFunc.h"
+#include "jsapi-tests/tests.h"
+
+using namespace js;
+using namespace js::jit;
+
+static MBasicBlock* FollowTrivialGotos(MBasicBlock* block) {
+ while (block->phisEmpty() && *block->begin() == block->lastIns() &&
+ block->lastIns()->isGoto()) {
+ block = block->lastIns()->toGoto()->getSuccessor(0);
+ }
+ return block;
+}
+
+BEGIN_TEST(testJitGVN_FixupOSROnlyLoop) {
+ // This is a testcase which constructs the very rare circumstances that
+ // require the FixupOSROnlyLoop logic.
+
+ MinimalFunc func;
+
+ MBasicBlock* entry = func.createEntryBlock();
+ MBasicBlock* osrEntry = func.createOsrEntryBlock();
+ MBasicBlock* outerHeader = func.createBlock(entry);
+ MBasicBlock* merge = func.createBlock(outerHeader);
+ MBasicBlock* innerHeader = func.createBlock(merge);
+ MBasicBlock* innerBackedge = func.createBlock(innerHeader);
+ MBasicBlock* outerBackedge = func.createBlock(innerHeader);
+ MBasicBlock* exit = func.createBlock(outerHeader);
+
+ MConstant* c = MConstant::New(func.alloc, BooleanValue(false));
+ entry->add(c);
+ entry->end(MTest::New(func.alloc, c, outerHeader, exit));
+ osrEntry->end(MGoto::New(func.alloc, merge));
+
+ merge->end(MGoto::New(func.alloc, innerHeader));
+
+ // Use Beta nodes to hide the constants and suppress folding.
+ MConstant* x = MConstant::New(func.alloc, BooleanValue(false));
+ outerHeader->add(x);
+ MBeta* xBeta =
+ MBeta::New(func.alloc, x, Range::NewInt32Range(func.alloc, 0, 1));
+ outerHeader->add(xBeta);
+ outerHeader->end(MTest::New(func.alloc, xBeta, merge, exit));
+
+ MConstant* y = MConstant::New(func.alloc, BooleanValue(false));
+ innerHeader->add(y);
+ MBeta* yBeta =
+ MBeta::New(func.alloc, y, Range::NewInt32Range(func.alloc, 0, 1));
+ innerHeader->add(yBeta);
+ innerHeader->end(MTest::New(func.alloc, yBeta, innerBackedge, outerBackedge));
+
+ innerBackedge->end(MGoto::New(func.alloc, innerHeader));
+ outerBackedge->end(MGoto::New(func.alloc, outerHeader));
+
+ MConstant* u = MConstant::New(func.alloc, UndefinedValue());
+ exit->add(u);
+ exit->end(MReturn::New(func.alloc, u));
+
+ MOZ_ALWAYS_TRUE(innerHeader->addPredecessorWithoutPhis(innerBackedge));
+ MOZ_ALWAYS_TRUE(outerHeader->addPredecessorWithoutPhis(outerBackedge));
+ MOZ_ALWAYS_TRUE(exit->addPredecessorWithoutPhis(entry));
+ MOZ_ALWAYS_TRUE(merge->addPredecessorWithoutPhis(osrEntry));
+
+ outerHeader->setLoopHeader(outerBackedge);
+ innerHeader->setLoopHeader(innerBackedge);
+
+ if (!func.runGVN()) {
+ return false;
+ }
+
+ // The loops are no longer reachable from the normal entry. They are
+ // doinated by the osrEntry.
+ MOZ_RELEASE_ASSERT(func.graph.osrBlock() == osrEntry);
+ MBasicBlock* newInner =
+ FollowTrivialGotos(osrEntry->lastIns()->toGoto()->target());
+ MBasicBlock* newOuter =
+ FollowTrivialGotos(newInner->lastIns()->toTest()->ifFalse());
+ MBasicBlock* newExit = FollowTrivialGotos(entry);
+ MOZ_RELEASE_ASSERT(newInner->isLoopHeader());
+ MOZ_RELEASE_ASSERT(newOuter->isLoopHeader());
+ MOZ_RELEASE_ASSERT(newExit->lastIns()->isReturn());
+
+ // One more time.
+ ClearDominatorTree(func.graph);
+ if (!func.runGVN()) {
+ return false;
+ }
+
+ // The loops are no longer reachable from the normal entry. They are
+ // doinated by the osrEntry.
+ MOZ_RELEASE_ASSERT(func.graph.osrBlock() == osrEntry);
+ newInner = FollowTrivialGotos(osrEntry->lastIns()->toGoto()->target());
+ newOuter = FollowTrivialGotos(newInner->lastIns()->toTest()->ifFalse());
+ newExit = FollowTrivialGotos(entry);
+ MOZ_RELEASE_ASSERT(newInner->isLoopHeader());
+ MOZ_RELEASE_ASSERT(newOuter->isLoopHeader());
+ MOZ_RELEASE_ASSERT(newExit->lastIns()->isReturn());
+
+ return true;
+}
+END_TEST(testJitGVN_FixupOSROnlyLoop)
+
+BEGIN_TEST(testJitGVN_FixupOSROnlyLoopNested) {
+ // Same as testJitGVN_FixupOSROnlyLoop but adds another level of loop
+ // nesting for added excitement.
+
+ MinimalFunc func;
+
+ MBasicBlock* entry = func.createEntryBlock();
+ MBasicBlock* osrEntry = func.createOsrEntryBlock();
+ MBasicBlock* outerHeader = func.createBlock(entry);
+ MBasicBlock* middleHeader = func.createBlock(outerHeader);
+ MBasicBlock* merge = func.createBlock(middleHeader);
+ MBasicBlock* innerHeader = func.createBlock(merge);
+ MBasicBlock* innerBackedge = func.createBlock(innerHeader);
+ MBasicBlock* middleBackedge = func.createBlock(innerHeader);
+ MBasicBlock* outerBackedge = func.createBlock(middleHeader);
+ MBasicBlock* exit = func.createBlock(outerHeader);
+
+ MConstant* c = MConstant::New(func.alloc, BooleanValue(false));
+ entry->add(c);
+ entry->end(MTest::New(func.alloc, c, outerHeader, exit));
+ osrEntry->end(MGoto::New(func.alloc, merge));
+
+ merge->end(MGoto::New(func.alloc, innerHeader));
+
+ // Use Beta nodes to hide the constants and suppress folding.
+ MConstant* x = MConstant::New(func.alloc, BooleanValue(false));
+ outerHeader->add(x);
+ MBeta* xBeta =
+ MBeta::New(func.alloc, x, Range::NewInt32Range(func.alloc, 0, 1));
+ outerHeader->add(xBeta);
+ outerHeader->end(MTest::New(func.alloc, xBeta, middleHeader, exit));
+
+ MConstant* y = MConstant::New(func.alloc, BooleanValue(false));
+ middleHeader->add(y);
+ MBeta* yBeta =
+ MBeta::New(func.alloc, y, Range::NewInt32Range(func.alloc, 0, 1));
+ middleHeader->add(yBeta);
+ middleHeader->end(MTest::New(func.alloc, yBeta, merge, outerBackedge));
+
+ MConstant* w = MConstant::New(func.alloc, BooleanValue(false));
+ innerHeader->add(w);
+ MBeta* wBeta =
+ MBeta::New(func.alloc, w, Range::NewInt32Range(func.alloc, 0, 1));
+ innerHeader->add(wBeta);
+ innerHeader->end(
+ MTest::New(func.alloc, wBeta, innerBackedge, middleBackedge));
+
+ innerBackedge->end(MGoto::New(func.alloc, innerHeader));
+ middleBackedge->end(MGoto::New(func.alloc, middleHeader));
+ outerBackedge->end(MGoto::New(func.alloc, outerHeader));
+
+ MConstant* u = MConstant::New(func.alloc, UndefinedValue());
+ exit->add(u);
+ exit->end(MReturn::New(func.alloc, u));
+
+ MOZ_ALWAYS_TRUE(innerHeader->addPredecessorWithoutPhis(innerBackedge));
+ MOZ_ALWAYS_TRUE(middleHeader->addPredecessorWithoutPhis(middleBackedge));
+ MOZ_ALWAYS_TRUE(outerHeader->addPredecessorWithoutPhis(outerBackedge));
+ MOZ_ALWAYS_TRUE(exit->addPredecessorWithoutPhis(entry));
+ MOZ_ALWAYS_TRUE(merge->addPredecessorWithoutPhis(osrEntry));
+
+ outerHeader->setLoopHeader(outerBackedge);
+ middleHeader->setLoopHeader(middleBackedge);
+ innerHeader->setLoopHeader(innerBackedge);
+
+ if (!func.runGVN()) {
+ return false;
+ }
+
+ // The loops are no longer reachable from the normal entry. They are
+ // doinated by the osrEntry.
+ MOZ_RELEASE_ASSERT(func.graph.osrBlock() == osrEntry);
+ MBasicBlock* newInner =
+ FollowTrivialGotos(osrEntry->lastIns()->toGoto()->target());
+ MBasicBlock* newMiddle =
+ FollowTrivialGotos(newInner->lastIns()->toTest()->ifFalse());
+ MBasicBlock* newOuter =
+ FollowTrivialGotos(newMiddle->lastIns()->toTest()->ifFalse());
+ MBasicBlock* newExit = FollowTrivialGotos(entry);
+ MOZ_RELEASE_ASSERT(newInner->isLoopHeader());
+ MOZ_RELEASE_ASSERT(newMiddle->isLoopHeader());
+ MOZ_RELEASE_ASSERT(newOuter->isLoopHeader());
+ MOZ_RELEASE_ASSERT(newExit->lastIns()->isReturn());
+
+ // One more time.
+ ClearDominatorTree(func.graph);
+ if (!func.runGVN()) {
+ return false;
+ }
+
+ // The loops are no longer reachable from the normal entry. They are
+ // doinated by the osrEntry.
+ MOZ_RELEASE_ASSERT(func.graph.osrBlock() == osrEntry);
+ newInner = FollowTrivialGotos(osrEntry->lastIns()->toGoto()->target());
+ newMiddle = FollowTrivialGotos(newInner->lastIns()->toTest()->ifFalse());
+ newOuter = FollowTrivialGotos(newMiddle->lastIns()->toTest()->ifFalse());
+ newExit = FollowTrivialGotos(entry);
+ MOZ_RELEASE_ASSERT(newInner->isLoopHeader());
+ MOZ_RELEASE_ASSERT(newMiddle->isLoopHeader());
+ MOZ_RELEASE_ASSERT(newOuter->isLoopHeader());
+ MOZ_RELEASE_ASSERT(newExit->lastIns()->isReturn());
+
+ return true;
+}
+END_TEST(testJitGVN_FixupOSROnlyLoopNested)
+
+BEGIN_TEST(testJitGVN_PinnedPhis) {
+ // Set up a loop which gets optimized away, with phis which must be
+ // cleaned up, permitting more phis to be cleaned up.
+
+ MinimalFunc func;
+
+ MBasicBlock* entry = func.createEntryBlock();
+ MBasicBlock* outerHeader = func.createBlock(entry);
+ MBasicBlock* outerBlock = func.createBlock(outerHeader);
+ MBasicBlock* innerHeader = func.createBlock(outerBlock);
+ MBasicBlock* innerBackedge = func.createBlock(innerHeader);
+ MBasicBlock* exit = func.createBlock(innerHeader);
+
+ MPhi* phi0 = MPhi::New(func.alloc);
+ MPhi* phi1 = MPhi::New(func.alloc);
+ MPhi* phi2 = MPhi::New(func.alloc);
+ MPhi* phi3 = MPhi::New(func.alloc);
+
+ MParameter* p = func.createParameter();
+ entry->add(p);
+ MConstant* z0 = MConstant::New(func.alloc, Int32Value(0));
+ MConstant* z1 = MConstant::New(func.alloc, Int32Value(1));
+ MConstant* z2 = MConstant::New(func.alloc, Int32Value(2));
+ MConstant* z3 = MConstant::New(func.alloc, Int32Value(2));
+ MOZ_RELEASE_ASSERT(phi0->addInputSlow(z0));
+ MOZ_RELEASE_ASSERT(phi1->addInputSlow(z1));
+ MOZ_RELEASE_ASSERT(phi2->addInputSlow(z2));
+ MOZ_RELEASE_ASSERT(phi3->addInputSlow(z3));
+ entry->add(z0);
+ entry->add(z1);
+ entry->add(z2);
+ entry->add(z3);
+ entry->end(MGoto::New(func.alloc, outerHeader));
+
+ outerHeader->addPhi(phi0);
+ outerHeader->addPhi(phi1);
+ outerHeader->addPhi(phi2);
+ outerHeader->addPhi(phi3);
+ outerHeader->end(MGoto::New(func.alloc, outerBlock));
+
+ outerBlock->end(MGoto::New(func.alloc, innerHeader));
+
+ MConstant* true_ = MConstant::New(func.alloc, BooleanValue(true));
+ innerHeader->add(true_);
+ innerHeader->end(MTest::New(func.alloc, true_, innerBackedge, exit));
+
+ innerBackedge->end(MGoto::New(func.alloc, innerHeader));
+
+ MInstruction* z4 = MAdd::New(func.alloc, phi0, phi1, MIRType::Int32);
+ MConstant* z5 = MConstant::New(func.alloc, Int32Value(4));
+ MInstruction* z6 = MAdd::New(func.alloc, phi2, phi3, MIRType::Int32);
+ MConstant* z7 = MConstant::New(func.alloc, Int32Value(6));
+ MOZ_RELEASE_ASSERT(phi0->addInputSlow(z4));
+ MOZ_RELEASE_ASSERT(phi1->addInputSlow(z5));
+ MOZ_RELEASE_ASSERT(phi2->addInputSlow(z6));
+ MOZ_RELEASE_ASSERT(phi3->addInputSlow(z7));
+ exit->add(z4);
+ exit->add(z5);
+ exit->add(z6);
+ exit->add(z7);
+ exit->end(MGoto::New(func.alloc, outerHeader));
+
+ MOZ_ALWAYS_TRUE(innerHeader->addPredecessorWithoutPhis(innerBackedge));
+ MOZ_ALWAYS_TRUE(outerHeader->addPredecessorWithoutPhis(exit));
+
+ outerHeader->setLoopHeader(exit);
+ innerHeader->setLoopHeader(innerBackedge);
+
+ if (!func.runGVN()) {
+ return false;
+ }
+
+ MOZ_RELEASE_ASSERT(innerHeader->phisEmpty());
+ MOZ_RELEASE_ASSERT(exit->isDead());
+
+ return true;
+}
+END_TEST(testJitGVN_PinnedPhis)
diff --git a/js/src/jsapi-tests/testJitMacroAssembler.cpp b/js/src/jsapi-tests/testJitMacroAssembler.cpp
new file mode 100644
index 0000000000..0cfa71700f
--- /dev/null
+++ b/js/src/jsapi-tests/testJitMacroAssembler.cpp
@@ -0,0 +1,816 @@
+/* -*- 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 "jit/IonAnalysis.h"
+#include "jit/Linker.h"
+#include "jit/MacroAssembler.h"
+#include "jit/MIRGenerator.h"
+#include "jit/MIRGraph.h"
+#include "jit/ValueNumbering.h"
+#include "js/Value.h"
+
+#include "jsapi-tests/tests.h"
+#include "jsapi-tests/testsJit.h"
+
+#include "jit/MacroAssembler-inl.h"
+
+using namespace js;
+using namespace js::jit;
+
+using mozilla::NegativeInfinity;
+using mozilla::PositiveInfinity;
+
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+
+BEGIN_TEST(testJitMacroAssembler_flexibleDivMod) {
+ TempAllocator tempAlloc(&cx->tempLifoAlloc());
+ JitContext jcx(cx);
+ StackMacroAssembler masm(cx, tempAlloc);
+ AutoCreatedBy acb(masm, __func__);
+
+ PrepareJit(masm);
+
+ // Test case divides 9/2;
+ const uintptr_t quotient_result = 4;
+ const uintptr_t remainder_result = 1;
+ const uintptr_t dividend = 9;
+ const uintptr_t divisor = 2;
+
+ AllocatableGeneralRegisterSet leftOutputHandSides(GeneralRegisterSet::All());
+
+ while (!leftOutputHandSides.empty()) {
+ Register lhsOutput = leftOutputHandSides.takeAny();
+
+ AllocatableGeneralRegisterSet rightHandSides(GeneralRegisterSet::All());
+ while (!rightHandSides.empty()) {
+ Register rhs = rightHandSides.takeAny();
+
+ AllocatableGeneralRegisterSet remainders(GeneralRegisterSet::All());
+ while (!remainders.empty()) {
+ Register remainderOutput = remainders.takeAny();
+ if (lhsOutput == rhs || lhsOutput == remainderOutput ||
+ rhs == remainderOutput) {
+ continue;
+ }
+
+ AllocatableRegisterSet regs(RegisterSet::Volatile());
+ LiveRegisterSet save(regs.asLiveSet());
+
+ Label next, fail;
+ masm.mov(ImmWord(dividend), lhsOutput);
+ masm.mov(ImmWord(divisor), rhs);
+ masm.flexibleDivMod32(rhs, lhsOutput, remainderOutput, false, save);
+ masm.branch32(Assembler::NotEqual, AbsoluteAddress(&quotient_result),
+ lhsOutput, &fail);
+ masm.branch32(Assembler::NotEqual, AbsoluteAddress(&remainder_result),
+ remainderOutput, &fail);
+ // Ensure RHS was not clobbered
+ masm.branch32(Assembler::NotEqual, AbsoluteAddress(&divisor), rhs,
+ &fail);
+ masm.jump(&next);
+ masm.bind(&fail);
+ masm.printf("Failed");
+ masm.breakpoint();
+
+ masm.bind(&next);
+ }
+ }
+ }
+
+ return ExecuteJit(cx, masm);
+}
+END_TEST(testJitMacroAssembler_flexibleDivMod)
+
+BEGIN_TEST(testJitMacroAssembler_flexibleRemainder) {
+ TempAllocator tempAlloc(&cx->tempLifoAlloc());
+ JitContext jcx(cx);
+ StackMacroAssembler masm(cx, tempAlloc);
+ AutoCreatedBy acb(masm, __func__);
+
+ PrepareJit(masm);
+
+ // Test case divides 9/2;
+ const uintptr_t dividend = 9;
+ const uintptr_t divisor = 2;
+ const uintptr_t remainder_result = 1;
+
+ AllocatableGeneralRegisterSet leftOutputHandSides(GeneralRegisterSet::All());
+
+ while (!leftOutputHandSides.empty()) {
+ Register lhsOutput = leftOutputHandSides.takeAny();
+
+ AllocatableGeneralRegisterSet rightHandSides(GeneralRegisterSet::All());
+ while (!rightHandSides.empty()) {
+ Register rhs = rightHandSides.takeAny();
+
+ if (lhsOutput == rhs) {
+ continue;
+ }
+
+ AllocatableRegisterSet regs(RegisterSet::Volatile());
+ LiveRegisterSet save(regs.asLiveSet());
+
+ Label next, fail;
+ masm.mov(ImmWord(dividend), lhsOutput);
+ masm.mov(ImmWord(divisor), rhs);
+ masm.flexibleRemainder32(rhs, lhsOutput, false, save);
+ masm.branch32(Assembler::NotEqual, AbsoluteAddress(&remainder_result),
+ lhsOutput, &fail);
+ // Ensure RHS was not clobbered
+ masm.branch32(Assembler::NotEqual, AbsoluteAddress(&divisor), rhs, &fail);
+ masm.jump(&next);
+ masm.bind(&fail);
+ masm.printf("Failed\n");
+ masm.breakpoint();
+
+ masm.bind(&next);
+ }
+ }
+
+ return ExecuteJit(cx, masm);
+}
+END_TEST(testJitMacroAssembler_flexibleRemainder)
+
+BEGIN_TEST(testJitMacroAssembler_flexibleQuotient) {
+ TempAllocator tempAlloc(&cx->tempLifoAlloc());
+ JitContext jcx(cx);
+ StackMacroAssembler masm(cx, tempAlloc);
+ AutoCreatedBy acb(masm, __func__);
+
+ PrepareJit(masm);
+
+ // Test case divides 9/2;
+ const uintptr_t dividend = 9;
+ const uintptr_t divisor = 2;
+ const uintptr_t quotient_result = 4;
+
+ AllocatableGeneralRegisterSet leftOutputHandSides(GeneralRegisterSet::All());
+
+ while (!leftOutputHandSides.empty()) {
+ Register lhsOutput = leftOutputHandSides.takeAny();
+
+ AllocatableGeneralRegisterSet rightHandSides(GeneralRegisterSet::All());
+ while (!rightHandSides.empty()) {
+ Register rhs = rightHandSides.takeAny();
+
+ if (lhsOutput == rhs) {
+ continue;
+ }
+
+ AllocatableRegisterSet regs(RegisterSet::Volatile());
+ LiveRegisterSet save(regs.asLiveSet());
+
+ Label next, fail;
+ masm.mov(ImmWord(dividend), lhsOutput);
+ masm.mov(ImmWord(divisor), rhs);
+ masm.flexibleQuotient32(rhs, lhsOutput, false, save);
+ masm.branch32(Assembler::NotEqual, AbsoluteAddress(&quotient_result),
+ lhsOutput, &fail);
+ // Ensure RHS was not clobbered
+ masm.branch32(Assembler::NotEqual, AbsoluteAddress(&divisor), rhs, &fail);
+ masm.jump(&next);
+ masm.bind(&fail);
+ masm.printf("Failed\n");
+ masm.breakpoint();
+
+ masm.bind(&next);
+ }
+ }
+
+ return ExecuteJit(cx, masm);
+}
+END_TEST(testJitMacroAssembler_flexibleQuotient)
+
+// To make sure ecx isn't being clobbered; globally scoped to ensure it has the
+// right lifetime.
+const uintptr_t guardEcx = 0xfeedbad;
+
+bool shiftTest(JSContext* cx, const char* name,
+ void (*operation)(StackMacroAssembler& masm, Register, Register),
+ const uintptr_t* lhsInput, const uintptr_t* rhsInput,
+ const uintptr_t* result) {
+ TempAllocator tempAlloc(&cx->tempLifoAlloc());
+ JitContext jcx(cx);
+ StackMacroAssembler masm(cx, tempAlloc);
+ AutoCreatedBy acb(masm, __func__);
+
+ PrepareJit(masm);
+
+ JS::AutoSuppressGCAnalysis suppress;
+ AllocatableGeneralRegisterSet leftOutputHandSides(GeneralRegisterSet::All());
+
+ while (!leftOutputHandSides.empty()) {
+ Register lhsOutput = leftOutputHandSides.takeAny();
+
+ AllocatableGeneralRegisterSet rightHandSides(GeneralRegisterSet::All());
+ while (!rightHandSides.empty()) {
+ Register rhs = rightHandSides.takeAny();
+
+ // You can only use shift as the same reg if the values are the same
+ if (lhsOutput == rhs && *lhsInput != *rhsInput) {
+ continue;
+ }
+
+ Label next, outputFail, clobberRhs, clobberEcx, dump;
+ masm.mov(ImmWord(guardEcx), ecx);
+ masm.mov(ImmWord(*lhsInput), lhsOutput);
+ masm.mov(ImmWord(*rhsInput), rhs);
+
+ operation(masm, rhs, lhsOutput);
+
+ // Ensure Result is correct
+ masm.branch32(Assembler::NotEqual, AbsoluteAddress(result), lhsOutput,
+ &outputFail);
+
+ // Ensure RHS was not clobbered, unless it's also the output register.
+ if (lhsOutput != rhs) {
+ masm.branch32(Assembler::NotEqual, AbsoluteAddress(rhsInput), rhs,
+ &clobberRhs);
+ }
+
+ if (lhsOutput != ecx && rhs != ecx) {
+ // If neither lhsOutput nor rhs is ecx, make sure ecx has been
+ // preserved, otherwise it's expected to be covered by the RHS clobber
+ // check above, or intentionally clobbered as the output.
+ masm.branch32(Assembler::NotEqual, AbsoluteAddress(&guardEcx), ecx,
+ &clobberEcx);
+ }
+
+ masm.jump(&next);
+
+ masm.bind(&outputFail);
+ masm.printf("Incorrect output (got %d) ", lhsOutput);
+ masm.jump(&dump);
+
+ masm.bind(&clobberRhs);
+ masm.printf("rhs clobbered %d", rhs);
+ masm.jump(&dump);
+
+ masm.bind(&clobberEcx);
+ masm.printf("ecx clobbered");
+ masm.jump(&dump);
+
+ masm.bind(&dump);
+ masm.mov(ImmPtr(lhsOutput.name()), lhsOutput);
+ masm.printf("(lhsOutput/srcDest) %s ", lhsOutput);
+ masm.mov(ImmPtr(name), lhsOutput);
+ masm.printf("%s ", lhsOutput);
+ masm.mov(ImmPtr(rhs.name()), lhsOutput);
+ masm.printf("(shift/rhs) %s \n", lhsOutput);
+ // Breakpoint to force test failure.
+ masm.breakpoint();
+ masm.bind(&next);
+ }
+ }
+
+ return ExecuteJit(cx, masm);
+}
+
+BEGIN_TEST(testJitMacroAssembler_flexibleRshift) {
+ {
+ // Test case 16 >> 2 == 4;
+ const uintptr_t lhsInput = 16;
+ const uintptr_t rhsInput = 2;
+ const uintptr_t result = 4;
+
+ bool res = shiftTest(
+ cx, "flexibleRshift32",
+ [](StackMacroAssembler& masm, Register rhs, Register lhsOutput) {
+ masm.flexibleRshift32(rhs, lhsOutput);
+ },
+ &lhsInput, &rhsInput, &result);
+ if (!res) {
+ return false;
+ }
+ }
+
+ {
+ // Test case 16 >> 16 == 0 -- this helps cover the case where the same
+ // register can be passed for source and dest.
+ const uintptr_t lhsInput = 16;
+ const uintptr_t rhsInput = 16;
+ const uintptr_t result = 0;
+
+ bool res = shiftTest(
+ cx, "flexibleRshift32",
+ [](StackMacroAssembler& masm, Register rhs, Register lhsOutput) {
+ masm.flexibleRshift32(rhs, lhsOutput);
+ },
+ &lhsInput, &rhsInput, &result);
+ if (!res) {
+ return false;
+ }
+ }
+
+ return true;
+}
+END_TEST(testJitMacroAssembler_flexibleRshift)
+
+BEGIN_TEST(testJitMacroAssembler_flexibleRshiftArithmetic) {
+ {
+ // Test case 4294967295 >> 2 == 4294967295;
+ const uintptr_t lhsInput = 4294967295;
+ const uintptr_t rhsInput = 2;
+ const uintptr_t result = 4294967295;
+ bool res = shiftTest(
+ cx, "flexibleRshift32Arithmetic",
+ [](StackMacroAssembler& masm, Register rhs, Register lhsOutput) {
+ masm.flexibleRshift32Arithmetic(rhs, lhsOutput);
+ },
+ &lhsInput, &rhsInput, &result);
+ if (!res) {
+ return false;
+ }
+ }
+
+ {
+ // Test case 16 >> 16 == 0 -- this helps cover the case where the same
+ // register can be passed for source and dest.
+ const uintptr_t lhsInput = 16;
+ const uintptr_t rhsInput = 16;
+ const uintptr_t result = 0;
+
+ bool res = shiftTest(
+ cx, "flexibleRshift32Arithmetic",
+ [](StackMacroAssembler& masm, Register rhs, Register lhsOutput) {
+ masm.flexibleRshift32Arithmetic(rhs, lhsOutput);
+ },
+ &lhsInput, &rhsInput, &result);
+ if (!res) {
+ return false;
+ }
+ }
+
+ return true;
+}
+END_TEST(testJitMacroAssembler_flexibleRshiftArithmetic)
+
+BEGIN_TEST(testJitMacroAssembler_flexibleLshift) {
+ {
+ // Test case 16 << 2 == 64;
+ const uintptr_t lhsInput = 16;
+ const uintptr_t rhsInput = 2;
+ const uintptr_t result = 64;
+
+ bool res = shiftTest(
+ cx, "flexibleLshift32",
+ [](StackMacroAssembler& masm, Register rhs, Register lhsOutput) {
+ masm.flexibleLshift32(rhs, lhsOutput);
+ },
+ &lhsInput, &rhsInput, &result);
+ if (!res) {
+ return false;
+ }
+ }
+
+ {
+ // Test case 4 << 4 == 64; duplicated input case
+ const uintptr_t lhsInput = 4;
+ const uintptr_t rhsInput = 4;
+ const uintptr_t result = 64;
+
+ bool res = shiftTest(
+ cx, "flexibleLshift32",
+ [](StackMacroAssembler& masm, Register rhs, Register lhsOutput) {
+ masm.flexibleLshift32(rhs, lhsOutput);
+ },
+ &lhsInput, &rhsInput, &result);
+ if (!res) {
+ return false;
+ }
+ }
+
+ return true;
+}
+END_TEST(testJitMacroAssembler_flexibleLshift)
+
+BEGIN_TEST(testJitMacroAssembler_truncateDoubleToInt64) {
+ TempAllocator tempAlloc(&cx->tempLifoAlloc());
+ JitContext jcx(cx);
+ StackMacroAssembler masm(cx, tempAlloc);
+ AutoCreatedBy acb(masm, __func__);
+
+ PrepareJit(masm);
+
+ AllocatableGeneralRegisterSet allRegs(GeneralRegisterSet::All());
+ AllocatableFloatRegisterSet allFloatRegs(FloatRegisterSet::All());
+ FloatRegister input = allFloatRegs.takeAny();
+# ifdef JS_NUNBOX32
+ Register64 output(allRegs.takeAny(), allRegs.takeAny());
+# else
+ Register64 output(allRegs.takeAny());
+# endif
+ Register temp = allRegs.takeAny();
+
+ masm.reserveStack(sizeof(int32_t));
+
+# define TEST(INPUT, OUTPUT) \
+ { \
+ Label next; \
+ masm.loadConstantDouble(double(INPUT), input); \
+ masm.storeDouble(input, Operand(esp, 0)); \
+ masm.truncateDoubleToInt64(Address(esp, 0), Address(esp, 0), temp); \
+ masm.branch64(Assembler::Equal, Address(esp, 0), Imm64(OUTPUT), &next); \
+ masm.printf("truncateDoubleToInt64(" #INPUT ") failed\n"); \
+ masm.breakpoint(); \
+ masm.bind(&next); \
+ }
+
+ TEST(0, 0);
+ TEST(-0, 0);
+ TEST(1, 1);
+ TEST(9223372036854774784.0, 9223372036854774784);
+ TEST(-9223372036854775808.0, 0x8000000000000000);
+ TEST(9223372036854775808.0, 0x8000000000000000);
+ TEST(JS::GenericNaN(), 0x8000000000000000);
+ TEST(PositiveInfinity<double>(), 0x8000000000000000);
+ TEST(NegativeInfinity<double>(), 0x8000000000000000);
+# undef TEST
+
+ masm.freeStack(sizeof(int32_t));
+
+ return ExecuteJit(cx, masm);
+}
+END_TEST(testJitMacroAssembler_truncateDoubleToInt64)
+
+BEGIN_TEST(testJitMacroAssembler_truncateDoubleToUInt64) {
+ TempAllocator tempAlloc(&cx->tempLifoAlloc());
+ JitContext jcx(cx);
+ StackMacroAssembler masm(cx, tempAlloc);
+ AutoCreatedBy acb(masm, __func__);
+
+ PrepareJit(masm);
+
+ AllocatableGeneralRegisterSet allRegs(GeneralRegisterSet::All());
+ AllocatableFloatRegisterSet allFloatRegs(FloatRegisterSet::All());
+ FloatRegister input = allFloatRegs.takeAny();
+ FloatRegister floatTemp = allFloatRegs.takeAny();
+# ifdef JS_NUNBOX32
+ Register64 output(allRegs.takeAny(), allRegs.takeAny());
+# else
+ Register64 output(allRegs.takeAny());
+# endif
+ Register temp = allRegs.takeAny();
+
+ masm.reserveStack(sizeof(int32_t));
+
+# define TEST(INPUT, OUTPUT) \
+ { \
+ Label next; \
+ masm.loadConstantDouble(double(INPUT), input); \
+ masm.storeDouble(input, Operand(esp, 0)); \
+ masm.truncateDoubleToUInt64(Address(esp, 0), Address(esp, 0), temp, \
+ floatTemp); \
+ masm.branch64(Assembler::Equal, Address(esp, 0), Imm64(OUTPUT), &next); \
+ masm.printf("truncateDoubleToUInt64(" #INPUT ") failed\n"); \
+ masm.breakpoint(); \
+ masm.bind(&next); \
+ }
+
+ TEST(0, 0);
+ TEST(1, 1);
+ TEST(9223372036854774784.0, 9223372036854774784);
+ TEST((uint64_t)0x8000000000000000, 0x8000000000000000);
+ TEST((uint64_t)0x8000000000000001, 0x8000000000000000);
+ TEST((uint64_t)0x8006004000000001, 0x8006004000000000);
+ TEST(-0.0, 0);
+ TEST(-0.5, 0);
+ TEST(-0.99, 0);
+ TEST(JS::GenericNaN(), 0x8000000000000000);
+ TEST(PositiveInfinity<double>(), 0x8000000000000000);
+ TEST(NegativeInfinity<double>(), 0x8000000000000000);
+# undef TEST
+
+ masm.freeStack(sizeof(int32_t));
+
+ return ExecuteJit(cx, masm);
+}
+END_TEST(testJitMacroAssembler_truncateDoubleToUInt64)
+
+BEGIN_TEST(testJitMacroAssembler_branchDoubleNotInInt64Range) {
+ TempAllocator tempAlloc(&cx->tempLifoAlloc());
+ JitContext jcx(cx);
+ StackMacroAssembler masm(cx, tempAlloc);
+ AutoCreatedBy acb(masm, __func__);
+
+ PrepareJit(masm);
+
+ AllocatableGeneralRegisterSet allRegs(GeneralRegisterSet::All());
+ AllocatableFloatRegisterSet allFloatRegs(FloatRegisterSet::All());
+ FloatRegister input = allFloatRegs.takeAny();
+# ifdef JS_NUNBOX32
+ Register64 output(allRegs.takeAny(), allRegs.takeAny());
+# else
+ Register64 output(allRegs.takeAny());
+# endif
+ Register temp = allRegs.takeAny();
+
+ masm.reserveStack(sizeof(int32_t));
+
+# define TEST(INPUT, OUTPUT) \
+ { \
+ Label next; \
+ masm.loadConstantDouble(double(INPUT), input); \
+ masm.storeDouble(input, Operand(esp, 0)); \
+ if (OUTPUT) { \
+ masm.branchDoubleNotInInt64Range(Address(esp, 0), temp, &next); \
+ } else { \
+ Label fail; \
+ masm.branchDoubleNotInInt64Range(Address(esp, 0), temp, &fail); \
+ masm.jump(&next); \
+ masm.bind(&fail); \
+ } \
+ masm.printf("branchDoubleNotInInt64Range(" #INPUT ") failed\n"); \
+ masm.breakpoint(); \
+ masm.bind(&next); \
+ }
+
+ TEST(0, false);
+ TEST(-0, false);
+ TEST(1, false);
+ TEST(9223372036854774784.0, false);
+ TEST(-9223372036854775808.0, true);
+ TEST(9223372036854775808.0, true);
+ TEST(JS::GenericNaN(), true);
+ TEST(PositiveInfinity<double>(), true);
+ TEST(NegativeInfinity<double>(), true);
+# undef TEST
+
+ masm.freeStack(sizeof(int32_t));
+
+ return ExecuteJit(cx, masm);
+}
+END_TEST(testJitMacroAssembler_branchDoubleNotInInt64Range)
+
+BEGIN_TEST(testJitMacroAssembler_branchDoubleNotInUInt64Range) {
+ TempAllocator tempAlloc(&cx->tempLifoAlloc());
+ JitContext jcx(cx);
+ StackMacroAssembler masm(cx, tempAlloc);
+ AutoCreatedBy acb(masm, __func__);
+
+ PrepareJit(masm);
+
+ AllocatableGeneralRegisterSet allRegs(GeneralRegisterSet::All());
+ AllocatableFloatRegisterSet allFloatRegs(FloatRegisterSet::All());
+ FloatRegister input = allFloatRegs.takeAny();
+# ifdef JS_NUNBOX32
+ Register64 output(allRegs.takeAny(), allRegs.takeAny());
+# else
+ Register64 output(allRegs.takeAny());
+# endif
+ Register temp = allRegs.takeAny();
+
+ masm.reserveStack(sizeof(int32_t));
+
+# define TEST(INPUT, OUTPUT) \
+ { \
+ Label next; \
+ masm.loadConstantDouble(double(INPUT), input); \
+ masm.storeDouble(input, Operand(esp, 0)); \
+ if (OUTPUT) { \
+ masm.branchDoubleNotInUInt64Range(Address(esp, 0), temp, &next); \
+ } else { \
+ Label fail; \
+ masm.branchDoubleNotInUInt64Range(Address(esp, 0), temp, &fail); \
+ masm.jump(&next); \
+ masm.bind(&fail); \
+ } \
+ masm.printf("branchDoubleNotInUInt64Range(" #INPUT ") failed\n"); \
+ masm.breakpoint(); \
+ masm.bind(&next); \
+ }
+
+ TEST(0, false);
+ TEST(1, false);
+ TEST(9223372036854774784.0, false);
+ TEST((uint64_t)0x8000000000000000, false);
+ TEST((uint64_t)0x8000000000000001, false);
+ TEST((uint64_t)0x8006004000000001, false);
+ TEST(-0.0, true);
+ TEST(-0.5, true);
+ TEST(-0.99, true);
+ TEST(JS::GenericNaN(), true);
+ TEST(PositiveInfinity<double>(), true);
+ TEST(NegativeInfinity<double>(), true);
+# undef TEST
+
+ masm.freeStack(sizeof(int32_t));
+
+ return ExecuteJit(cx, masm);
+}
+END_TEST(testJitMacroAssembler_branchDoubleNotInUInt64Range)
+
+BEGIN_TEST(testJitMacroAssembler_lshift64) {
+ TempAllocator tempAlloc(&cx->tempLifoAlloc());
+ JitContext jcx(cx);
+ StackMacroAssembler masm(cx, tempAlloc);
+ AutoCreatedBy acb(masm, __func__);
+
+ PrepareJit(masm);
+
+ AllocatableGeneralRegisterSet allRegs(GeneralRegisterSet::All());
+ AllocatableFloatRegisterSet allFloatRegs(FloatRegisterSet::All());
+# if defined(JS_CODEGEN_X86)
+ Register shift = ecx;
+ allRegs.take(shift);
+# elif defined(JS_CODEGEN_X64)
+ Register shift = rcx;
+ allRegs.take(shift);
+# else
+ Register shift = allRegs.takeAny();
+# endif
+
+# ifdef JS_NUNBOX32
+ Register64 input(allRegs.takeAny(), allRegs.takeAny());
+# else
+ Register64 input(allRegs.takeAny());
+# endif
+
+ masm.reserveStack(sizeof(int32_t));
+
+# define TEST(SHIFT, INPUT, OUTPUT) \
+ { \
+ Label next; \
+ masm.move64(Imm64(INPUT), input); \
+ masm.move32(Imm32(SHIFT), shift); \
+ masm.lshift64(shift, input); \
+ masm.branch64(Assembler::Equal, input, Imm64(OUTPUT), &next); \
+ masm.printf("lshift64(" #SHIFT ", " #INPUT ") failed\n"); \
+ masm.breakpoint(); \
+ masm.bind(&next); \
+ } \
+ { \
+ Label next; \
+ masm.move64(Imm64(INPUT), input); \
+ masm.lshift64(Imm32(SHIFT & 0x3f), input); \
+ masm.branch64(Assembler::Equal, input, Imm64(OUTPUT), &next); \
+ masm.printf("lshift64(Imm32(" #SHIFT "&0x3f), " #INPUT ") failed\n"); \
+ masm.breakpoint(); \
+ masm.bind(&next); \
+ }
+
+ TEST(0, 1, 1);
+ TEST(1, 1, 2);
+ TEST(2, 1, 4);
+ TEST(32, 1, 0x0000000100000000);
+ TEST(33, 1, 0x0000000200000000);
+ TEST(0, -1, 0xffffffffffffffff);
+ TEST(1, -1, 0xfffffffffffffffe);
+ TEST(2, -1, 0xfffffffffffffffc);
+ TEST(32, -1, 0xffffffff00000000);
+ TEST(0xffffffff, 1, 0x8000000000000000);
+ TEST(0xfffffffe, 1, 0x4000000000000000);
+ TEST(0xfffffffd, 1, 0x2000000000000000);
+ TEST(0x80000001, 1, 2);
+# undef TEST
+
+ masm.freeStack(sizeof(int32_t));
+
+ return ExecuteJit(cx, masm);
+}
+END_TEST(testJitMacroAssembler_lshift64)
+
+BEGIN_TEST(testJitMacroAssembler_rshift64Arithmetic) {
+ TempAllocator tempAlloc(&cx->tempLifoAlloc());
+ JitContext jcx(cx);
+ StackMacroAssembler masm(cx, tempAlloc);
+ AutoCreatedBy acb(masm, __func__);
+
+ PrepareJit(masm);
+
+ AllocatableGeneralRegisterSet allRegs(GeneralRegisterSet::All());
+ AllocatableFloatRegisterSet allFloatRegs(FloatRegisterSet::All());
+# if defined(JS_CODEGEN_X86)
+ Register shift = ecx;
+ allRegs.take(shift);
+# elif defined(JS_CODEGEN_X64)
+ Register shift = rcx;
+ allRegs.take(shift);
+# else
+ Register shift = allRegs.takeAny();
+# endif
+
+# ifdef JS_NUNBOX32
+ Register64 input(allRegs.takeAny(), allRegs.takeAny());
+# else
+ Register64 input(allRegs.takeAny());
+# endif
+
+ masm.reserveStack(sizeof(int32_t));
+
+# define TEST(SHIFT, INPUT, OUTPUT) \
+ { \
+ Label next; \
+ masm.move64(Imm64(INPUT), input); \
+ masm.move32(Imm32(SHIFT), shift); \
+ masm.rshift64Arithmetic(shift, input); \
+ masm.branch64(Assembler::Equal, input, Imm64(OUTPUT), &next); \
+ masm.printf("rshift64Arithmetic(" #SHIFT ", " #INPUT ") failed\n"); \
+ masm.breakpoint(); \
+ masm.bind(&next); \
+ } \
+ { \
+ Label next; \
+ masm.move64(Imm64(INPUT), input); \
+ masm.rshift64Arithmetic(Imm32(SHIFT & 0x3f), input); \
+ masm.branch64(Assembler::Equal, input, Imm64(OUTPUT), &next); \
+ masm.printf("rshift64Arithmetic(Imm32(" #SHIFT "&0x3f), " #INPUT \
+ ") failed\n"); \
+ masm.breakpoint(); \
+ masm.bind(&next); \
+ }
+
+ TEST(0, 0x4000000000000000, 0x4000000000000000);
+ TEST(1, 0x4000000000000000, 0x2000000000000000);
+ TEST(2, 0x4000000000000000, 0x1000000000000000);
+ TEST(32, 0x4000000000000000, 0x0000000040000000);
+ TEST(0, 0x8000000000000000, 0x8000000000000000);
+ TEST(1, 0x8000000000000000, 0xc000000000000000);
+ TEST(2, 0x8000000000000000, 0xe000000000000000);
+ TEST(32, 0x8000000000000000, 0xffffffff80000000);
+ TEST(0xffffffff, 0x8000000000000000, 0xffffffffffffffff);
+ TEST(0xfffffffe, 0x8000000000000000, 0xfffffffffffffffe);
+ TEST(0xfffffffd, 0x8000000000000000, 0xfffffffffffffffc);
+ TEST(0x80000001, 0x8000000000000000, 0xc000000000000000);
+# undef TEST
+
+ masm.freeStack(sizeof(int32_t));
+
+ return ExecuteJit(cx, masm);
+}
+END_TEST(testJitMacroAssembler_rshift64Arithmetic)
+
+BEGIN_TEST(testJitMacroAssembler_rshift64) {
+ TempAllocator tempAlloc(&cx->tempLifoAlloc());
+ JitContext jcx(cx);
+ StackMacroAssembler masm(cx, tempAlloc);
+ AutoCreatedBy acb(masm, __func__);
+
+ PrepareJit(masm);
+
+ AllocatableGeneralRegisterSet allRegs(GeneralRegisterSet::All());
+ AllocatableFloatRegisterSet allFloatRegs(FloatRegisterSet::All());
+# if defined(JS_CODEGEN_X86)
+ Register shift = ecx;
+ allRegs.take(shift);
+# elif defined(JS_CODEGEN_X64)
+ Register shift = rcx;
+ allRegs.take(shift);
+# else
+ Register shift = allRegs.takeAny();
+# endif
+
+# ifdef JS_NUNBOX32
+ Register64 input(allRegs.takeAny(), allRegs.takeAny());
+# else
+ Register64 input(allRegs.takeAny());
+# endif
+
+ masm.reserveStack(sizeof(int32_t));
+
+# define TEST(SHIFT, INPUT, OUTPUT) \
+ { \
+ Label next; \
+ masm.move64(Imm64(INPUT), input); \
+ masm.move32(Imm32(SHIFT), shift); \
+ masm.rshift64(shift, input); \
+ masm.branch64(Assembler::Equal, input, Imm64(OUTPUT), &next); \
+ masm.printf("rshift64(" #SHIFT ", " #INPUT ") failed\n"); \
+ masm.breakpoint(); \
+ masm.bind(&next); \
+ } \
+ { \
+ Label next; \
+ masm.move64(Imm64(INPUT), input); \
+ masm.rshift64(Imm32(SHIFT & 0x3f), input); \
+ masm.branch64(Assembler::Equal, input, Imm64(OUTPUT), &next); \
+ masm.printf("rshift64(Imm32(" #SHIFT "&0x3f), " #INPUT ") failed\n"); \
+ masm.breakpoint(); \
+ masm.bind(&next); \
+ }
+
+ TEST(0, 0x4000000000000000, 0x4000000000000000);
+ TEST(1, 0x4000000000000000, 0x2000000000000000);
+ TEST(2, 0x4000000000000000, 0x1000000000000000);
+ TEST(32, 0x4000000000000000, 0x0000000040000000);
+ TEST(0, 0x8000000000000000, 0x8000000000000000);
+ TEST(1, 0x8000000000000000, 0x4000000000000000);
+ TEST(2, 0x8000000000000000, 0x2000000000000000);
+ TEST(32, 0x8000000000000000, 0x0000000080000000);
+ TEST(0xffffffff, 0x8000000000000000, 0x0000000000000001);
+ TEST(0xfffffffe, 0x8000000000000000, 0x0000000000000002);
+ TEST(0xfffffffd, 0x8000000000000000, 0x0000000000000004);
+ TEST(0x80000001, 0x8000000000000000, 0x4000000000000000);
+# undef TEST
+
+ masm.freeStack(sizeof(int32_t));
+
+ return ExecuteJit(cx, masm);
+}
+END_TEST(testJitMacroAssembler_rshift64)
+
+#endif
diff --git a/js/src/jsapi-tests/testJitMinimalFunc.h b/js/src/jsapi-tests/testJitMinimalFunc.h
new file mode 100644
index 0000000000..3a7dbd3183
--- /dev/null
+++ b/js/src/jsapi-tests/testJitMinimalFunc.h
@@ -0,0 +1,123 @@
+/* -*- 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 jsapi_tests_jitTestGVN_h
+#define jsapi_tests_jitTestGVN_h
+
+#include "jit/IonAnalysis.h"
+#include "jit/MIRGenerator.h"
+#include "jit/MIRGraph.h"
+#include "jit/RangeAnalysis.h"
+#include "jit/ValueNumbering.h"
+
+namespace js {
+namespace jit {
+
+struct MinimalAlloc {
+ LifoAlloc lifo;
+ TempAllocator alloc;
+
+ // We are not testing the fallible allocator in these test cases, thus make
+ // the lifo alloc chunk extremely large for our test cases.
+ MinimalAlloc() : lifo(128 * 1024), alloc(&lifo) {
+ if (!alloc.ensureBallast()) {
+ MOZ_CRASH("[OOM] Not enough RAM for the test.");
+ }
+ }
+};
+
+struct MinimalFunc : MinimalAlloc {
+ JitCompileOptions options;
+ CompileInfo info;
+ MIRGraph graph;
+ MIRGenerator mir;
+ uint32_t numParams;
+
+ MinimalFunc()
+ : options(),
+ info(0),
+ graph(&alloc),
+ mir(static_cast<CompileRealm*>(nullptr), options, &alloc, &graph, &info,
+ static_cast<const OptimizationInfo*>(nullptr)),
+ numParams(0) {}
+
+ MBasicBlock* createEntryBlock() {
+ MBasicBlock* block =
+ MBasicBlock::New(graph, info, nullptr, MBasicBlock::NORMAL);
+ graph.addBlock(block);
+ return block;
+ }
+
+ MBasicBlock* createOsrEntryBlock() {
+ MBasicBlock* block =
+ MBasicBlock::New(graph, info, nullptr, MBasicBlock::NORMAL);
+ graph.addBlock(block);
+ graph.setOsrBlock(block);
+ return block;
+ }
+
+ MBasicBlock* createBlock(MBasicBlock* pred) {
+ MBasicBlock* block =
+ MBasicBlock::New(graph, info, pred, MBasicBlock::NORMAL);
+ graph.addBlock(block);
+ return block;
+ }
+
+ MParameter* createParameter() {
+ MParameter* p = MParameter::New(alloc, numParams++);
+ return p;
+ }
+
+ bool runGVN() {
+ if (!SplitCriticalEdges(graph)) {
+ return false;
+ }
+ RenumberBlocks(graph);
+ if (!BuildDominatorTree(graph)) {
+ return false;
+ }
+ if (!BuildPhiReverseMapping(graph)) {
+ return false;
+ }
+ ValueNumberer gvn(&mir, graph);
+ if (!gvn.run(ValueNumberer::DontUpdateAliasAnalysis)) {
+ return false;
+ }
+ return true;
+ }
+
+ bool runRangeAnalysis() {
+ if (!SplitCriticalEdges(graph)) {
+ return false;
+ }
+ RenumberBlocks(graph);
+ if (!BuildDominatorTree(graph)) {
+ return false;
+ }
+ if (!BuildPhiReverseMapping(graph)) {
+ return false;
+ }
+ RangeAnalysis rangeAnalysis(&mir, graph);
+ if (!rangeAnalysis.addBetaNodes()) {
+ return false;
+ }
+ if (!rangeAnalysis.analyze()) {
+ return false;
+ }
+ if (!rangeAnalysis.addRangeAssertions()) {
+ return false;
+ }
+ if (!rangeAnalysis.removeBetaNodes()) {
+ return false;
+ }
+ return true;
+ }
+};
+
+} // namespace jit
+} // namespace js
+
+#endif
diff --git a/js/src/jsapi-tests/testJitMoveEmitterCycles-mips32.cpp b/js/src/jsapi-tests/testJitMoveEmitterCycles-mips32.cpp
new file mode 100644
index 0000000000..bab11a22f4
--- /dev/null
+++ b/js/src/jsapi-tests/testJitMoveEmitterCycles-mips32.cpp
@@ -0,0 +1,480 @@
+/* -*- 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/. */
+
+#if defined(JS_SIMULATOR_MIPS32)
+# include "jit/Linker.h"
+# include "jit/MacroAssembler.h"
+# include "jit/mips32/Assembler-mips32.h"
+# include "jit/mips32/MoveEmitter-mips32.h"
+# include "jit/mips32/Simulator-mips32.h"
+# include "jit/MoveResolver.h"
+
+# include "jsapi-tests/tests.h"
+
+# include "vm/Runtime.h"
+
+static const int LIFO_ALLOC_PRIMARY_CHUNK_SIZE = 4 * 1024;
+
+static constexpr js::jit::FloatRegister single0(0,
+ js::jit::FloatRegister::Single);
+static constexpr js::jit::FloatRegister single1(1,
+ js::jit::FloatRegister::Single);
+static constexpr js::jit::FloatRegister single2(2,
+ js::jit::FloatRegister::Single);
+static constexpr js::jit::FloatRegister single3(3,
+ js::jit::FloatRegister::Single);
+static constexpr js::jit::FloatRegister single4(4,
+ js::jit::FloatRegister::Single);
+static constexpr js::jit::FloatRegister single5(5,
+ js::jit::FloatRegister::Single);
+static constexpr js::jit::FloatRegister single6(6,
+ js::jit::FloatRegister::Single);
+static constexpr js::jit::FloatRegister single7(7,
+ js::jit::FloatRegister::Single);
+static constexpr js::jit::FloatRegister single8(8,
+ js::jit::FloatRegister::Single);
+static constexpr js::jit::FloatRegister single9(9,
+ js::jit::FloatRegister::Single);
+static constexpr js::jit::FloatRegister single10(
+ 10, js::jit::FloatRegister::Single);
+static constexpr js::jit::FloatRegister single11(
+ 11, js::jit::FloatRegister::Single);
+static constexpr js::jit::FloatRegister single12(
+ 12, js::jit::FloatRegister::Single);
+static constexpr js::jit::FloatRegister single13(
+ 13, js::jit::FloatRegister::Single);
+static constexpr js::jit::FloatRegister single14(
+ 14, js::jit::FloatRegister::Single);
+static constexpr js::jit::FloatRegister single15(
+ 15, js::jit::FloatRegister::Single);
+static constexpr js::jit::FloatRegister single16(
+ 16, js::jit::FloatRegister::Single);
+static constexpr js::jit::FloatRegister single17(
+ 17, js::jit::FloatRegister::Single);
+static constexpr js::jit::FloatRegister single18(
+ 18, js::jit::FloatRegister::Single);
+static constexpr js::jit::FloatRegister single19(
+ 19, js::jit::FloatRegister::Single);
+static constexpr js::jit::FloatRegister single20(
+ 20, js::jit::FloatRegister::Single);
+static constexpr js::jit::FloatRegister single21(
+ 21, js::jit::FloatRegister::Single);
+static constexpr js::jit::FloatRegister single22(
+ 22, js::jit::FloatRegister::Single);
+static constexpr js::jit::FloatRegister single23(
+ 23, js::jit::FloatRegister::Single);
+static constexpr js::jit::FloatRegister single24(
+ 24, js::jit::FloatRegister::Single);
+static constexpr js::jit::FloatRegister single25(
+ 25, js::jit::FloatRegister::Single);
+static constexpr js::jit::FloatRegister single26(
+ 26, js::jit::FloatRegister::Single);
+static constexpr js::jit::FloatRegister single27(
+ 27, js::jit::FloatRegister::Single);
+static constexpr js::jit::FloatRegister single28(
+ 28, js::jit::FloatRegister::Single);
+static constexpr js::jit::FloatRegister single29(
+ 29, js::jit::FloatRegister::Single);
+static constexpr js::jit::FloatRegister single30(
+ 30, js::jit::FloatRegister::Single);
+static constexpr js::jit::FloatRegister single31(
+ 31, js::jit::FloatRegister::Single);
+
+static constexpr js::jit::FloatRegister double0(0,
+ js::jit::FloatRegister::Double);
+static constexpr js::jit::FloatRegister double1(2,
+ js::jit::FloatRegister::Double);
+static constexpr js::jit::FloatRegister double2(4,
+ js::jit::FloatRegister::Double);
+static constexpr js::jit::FloatRegister double3(6,
+ js::jit::FloatRegister::Double);
+static constexpr js::jit::FloatRegister double4(8,
+ js::jit::FloatRegister::Double);
+static constexpr js::jit::FloatRegister double5(10,
+ js::jit::FloatRegister::Double);
+static constexpr js::jit::FloatRegister double6(12,
+ js::jit::FloatRegister::Double);
+static constexpr js::jit::FloatRegister double7(14,
+ js::jit::FloatRegister::Double);
+static constexpr js::jit::FloatRegister double8(16,
+ js::jit::FloatRegister::Double);
+static constexpr js::jit::FloatRegister double9(18,
+ js::jit::FloatRegister::Double);
+static constexpr js::jit::FloatRegister double10(
+ 20, js::jit::FloatRegister::Double);
+static constexpr js::jit::FloatRegister double11(
+ 22, js::jit::FloatRegister::Double);
+static constexpr js::jit::FloatRegister double12(
+ 24, js::jit::FloatRegister::Double);
+static constexpr js::jit::FloatRegister double13(
+ 26, js::jit::FloatRegister::Double);
+static constexpr js::jit::FloatRegister double14(
+ 28, js::jit::FloatRegister::Double);
+static constexpr js::jit::FloatRegister double15(
+ 30, js::jit::FloatRegister::Double);
+
+static js::jit::JitCode* linkAndAllocate(JSContext* cx,
+ js::jit::MacroAssembler* masm) {
+ using namespace js;
+ using namespace js::jit;
+ Linker l(*masm);
+ return l.newCode(cx, CodeKind::Ion);
+}
+
+# define TRY(x) \
+ if (!(x)) return false;
+
+BEGIN_TEST(testJitMoveEmitterCycles_simple) {
+ using namespace js;
+ using namespace js::jit;
+ LifoAlloc lifo(LIFO_ALLOC_PRIMARY_CHUNK_SIZE);
+ TempAllocator alloc(&lifo);
+ JitContext jc(cx, &alloc);
+
+ StackMacroAssembler masm;
+ MoveEmitter mover(masm);
+ MoveResolver mr;
+ mr.setAllocator(alloc);
+ Simulator* sim = Simulator::Current();
+ TRY(mr.addMove(MoveOperand(double0), MoveOperand(double2), MoveOp::DOUBLE));
+ sim->setFpuRegisterDouble(double0.id(), 2.0);
+ TRY(mr.addMove(MoveOperand(double3), MoveOperand(double1), MoveOp::DOUBLE));
+ sim->setFpuRegisterDouble(double3.id(), 1.0);
+ TRY(mr.addMove(MoveOperand(single4), MoveOperand(single0), MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single4.id(), 0.0f);
+ TRY(mr.addMove(MoveOperand(single5), MoveOperand(single6), MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single5.id(), 6.0f);
+ TRY(mr.addMove(MoveOperand(single2), MoveOperand(single1), MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single2.id(), 1.0f);
+ TRY(mr.addMove(MoveOperand(single3), MoveOperand(single7), MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single3.id(), 7.0f);
+ // don't explode!
+ TRY(mr.resolve());
+ mover.emit(mr);
+ mover.finish();
+ masm.abiret();
+ JitCode* code = linkAndAllocate(cx, &masm);
+ sim->call(code->raw(), 1, 1);
+ CHECK(sim->getFpuRegisterDouble(double2.id()) == 2.0);
+ CHECK(int(sim->getFpuRegisterDouble(double1.id())) == 1.0);
+ CHECK(int(sim->getFpuRegisterFloat(single0.id())) == 0.0);
+ CHECK(int(sim->getFpuRegisterFloat(single6.id())) == 6.0);
+ CHECK(int(sim->getFpuRegisterFloat(single1.id())) == 1.0);
+ CHECK(int(sim->getFpuRegisterFloat(single7.id())) == 7.0);
+ return true;
+}
+END_TEST(testJitMoveEmitterCycles_simple)
+BEGIN_TEST(testJitMoveEmitterCycles_autogen) {
+ using namespace js;
+ using namespace js::jit;
+ LifoAlloc lifo(LIFO_ALLOC_PRIMARY_CHUNK_SIZE);
+ TempAllocator alloc(&lifo);
+ JitContext jc(cx, &alloc);
+ StackMacroAssembler masm;
+ MoveEmitter mover(masm);
+ MoveResolver mr;
+ mr.setAllocator(alloc);
+ Simulator* sim = Simulator::Current();
+ sim->setFpuRegisterDouble(double9.id(), 9.0);
+ TRY(mr.addMove(MoveOperand(single24), MoveOperand(single25),
+ MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single24.id(), 24.0f);
+ TRY(mr.addMove(MoveOperand(double3), MoveOperand(double0), MoveOp::DOUBLE));
+ sim->setFpuRegisterDouble(double3.id(), 3.0);
+ TRY(mr.addMove(MoveOperand(single10), MoveOperand(single31),
+ MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single10.id(), 10.0f);
+ TRY(mr.addMove(MoveOperand(double1), MoveOperand(double10), MoveOp::DOUBLE));
+ sim->setFpuRegisterDouble(double1.id(), 1.0);
+ TRY(mr.addMove(MoveOperand(single8), MoveOperand(single10), MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single8.id(), 8.0f);
+ TRY(mr.addMove(MoveOperand(double2), MoveOperand(double7), MoveOp::DOUBLE));
+ sim->setFpuRegisterDouble(double2.id(), 2.0);
+ TRY(mr.addMove(MoveOperand(single1), MoveOperand(single3), MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single1.id(), 1.0f);
+ TRY(mr.addMove(MoveOperand(single17), MoveOperand(single11),
+ MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single17.id(), 17.0f);
+ TRY(mr.addMove(MoveOperand(single22), MoveOperand(single30),
+ MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single22.id(), 22.0f);
+ TRY(mr.addMove(MoveOperand(single31), MoveOperand(single7), MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single31.id(), 31.0f);
+ TRY(mr.addMove(MoveOperand(double3), MoveOperand(double13), MoveOp::DOUBLE));
+ sim->setFpuRegisterDouble(double3.id(), 3.0);
+ TRY(mr.addMove(MoveOperand(single31), MoveOperand(single23),
+ MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single31.id(), 31.0f);
+ TRY(mr.addMove(MoveOperand(single13), MoveOperand(single8), MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single13.id(), 13.0f);
+ TRY(mr.addMove(MoveOperand(single28), MoveOperand(single5), MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single28.id(), 28.0f);
+ TRY(mr.addMove(MoveOperand(single20), MoveOperand(single6), MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single20.id(), 20.0f);
+ TRY(mr.addMove(MoveOperand(single0), MoveOperand(single2), MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single0.id(), 0.0f);
+ TRY(mr.addMove(MoveOperand(double7), MoveOperand(double6), MoveOp::DOUBLE));
+ sim->setFpuRegisterDouble(double7.id(), 7.0);
+ TRY(mr.addMove(MoveOperand(single13), MoveOperand(single9), MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single13.id(), 13.0f);
+ TRY(mr.addMove(MoveOperand(single1), MoveOperand(single4), MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single1.id(), 1.0f);
+ TRY(mr.addMove(MoveOperand(single29), MoveOperand(single22),
+ MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single29.id(), 29.0f);
+ TRY(mr.addMove(MoveOperand(single25), MoveOperand(single24),
+ MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single25.id(), 25.0f);
+ TRY(mr.resolve());
+ mover.emit(mr);
+ mover.finish();
+ masm.abiret();
+ JitCode* code = linkAndAllocate(cx, &masm);
+ sim->call(code->raw(), 1, 1);
+ CHECK(int(sim->getFpuRegisterFloat(single25.id())) == 24.0);
+ CHECK(int(sim->getFpuRegisterDouble(double0.id())) == 3.0);
+ CHECK(int(sim->getFpuRegisterFloat(single31.id())) == 10.0);
+ CHECK(int(sim->getFpuRegisterDouble(double10.id())) == 1.0);
+ CHECK(int(sim->getFpuRegisterFloat(single10.id())) == 8.0);
+ CHECK(int(sim->getFpuRegisterDouble(double7.id())) == 2.0);
+ CHECK(int(sim->getFpuRegisterFloat(single3.id())) == 1.0);
+ CHECK(int(sim->getFpuRegisterFloat(single11.id())) == 17.0);
+ CHECK(int(sim->getFpuRegisterFloat(single30.id())) == 22.0);
+ CHECK(int(sim->getFpuRegisterFloat(single7.id())) == 31.0);
+ CHECK(int(sim->getFpuRegisterDouble(double13.id())) == 3.0);
+ CHECK(int(sim->getFpuRegisterFloat(single23.id())) == 31.0);
+ CHECK(int(sim->getFpuRegisterFloat(single8.id())) == 13.0);
+ CHECK(int(sim->getFpuRegisterFloat(single5.id())) == 28.0);
+ CHECK(int(sim->getFpuRegisterFloat(single6.id())) == 20.0);
+ CHECK(int(sim->getFpuRegisterFloat(single2.id())) == 0.0);
+ CHECK(int(sim->getFpuRegisterDouble(double6.id())) == 7.0);
+ CHECK(int(sim->getFpuRegisterFloat(single9.id())) == 13.0);
+ CHECK(int(sim->getFpuRegisterFloat(single4.id())) == 1.0);
+ CHECK(int(sim->getFpuRegisterFloat(single22.id())) == 29.0);
+ CHECK(int(sim->getFpuRegisterFloat(single24.id())) == 25.0);
+ return true;
+}
+END_TEST(testJitMoveEmitterCycles_autogen)
+
+BEGIN_TEST(testJitMoveEmitterCycles_autogen2) {
+ using namespace js;
+ using namespace js::jit;
+ LifoAlloc lifo(LIFO_ALLOC_PRIMARY_CHUNK_SIZE);
+ TempAllocator alloc(&lifo);
+ JitContext jc(cx, &alloc);
+ StackMacroAssembler masm;
+ MoveEmitter mover(masm);
+ MoveResolver mr;
+ mr.setAllocator(alloc);
+ Simulator* sim = Simulator::Current();
+ TRY(mr.addMove(MoveOperand(double10), MoveOperand(double0), MoveOp::DOUBLE));
+ sim->setFpuRegisterDouble(double10.id(), 10.0);
+ TRY(mr.addMove(MoveOperand(single15), MoveOperand(single3), MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single15.id(), 15.0f);
+ TRY(mr.addMove(MoveOperand(single2), MoveOperand(single28), MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single2.id(), 2.0f);
+ TRY(mr.addMove(MoveOperand(single30), MoveOperand(single25),
+ MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single30.id(), 30.0f);
+ TRY(mr.addMove(MoveOperand(single16), MoveOperand(single2), MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single16.id(), 16.0f);
+ TRY(mr.addMove(MoveOperand(single2), MoveOperand(single29), MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single2.id(), 2.0f);
+ TRY(mr.addMove(MoveOperand(single17), MoveOperand(single10),
+ MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single17.id(), 17.0f);
+ TRY(mr.addMove(MoveOperand(single9), MoveOperand(single26), MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single9.id(), 9.0f);
+ TRY(mr.addMove(MoveOperand(single1), MoveOperand(single23), MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single1.id(), 1.0f);
+ TRY(mr.addMove(MoveOperand(single8), MoveOperand(single6), MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single8.id(), 8.0f);
+ TRY(mr.addMove(MoveOperand(single24), MoveOperand(single16),
+ MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single24.id(), 24.0f);
+ TRY(mr.addMove(MoveOperand(double5), MoveOperand(double6), MoveOp::DOUBLE));
+ sim->setFpuRegisterDouble(double5.id(), 5.0f);
+ TRY(mr.addMove(MoveOperand(single23), MoveOperand(single30),
+ MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single23.id(), 23.0f);
+ TRY(mr.addMove(MoveOperand(single27), MoveOperand(single17),
+ MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single27.id(), 27.0f);
+ TRY(mr.addMove(MoveOperand(double3), MoveOperand(double4), MoveOp::DOUBLE));
+ sim->setFpuRegisterDouble(double3.id(), 3.0f);
+ TRY(mr.addMove(MoveOperand(single14), MoveOperand(single27),
+ MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single14.id(), 14.0f);
+ TRY(mr.addMove(MoveOperand(single2), MoveOperand(single31), MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single2.id(), 2.0f);
+ TRY(mr.addMove(MoveOperand(single2), MoveOperand(single24), MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single2.id(), 2.0f);
+ TRY(mr.addMove(MoveOperand(single31), MoveOperand(single11),
+ MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single31.id(), 31.0f);
+ TRY(mr.addMove(MoveOperand(single24), MoveOperand(single7), MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single24.id(), 24.0f);
+ TRY(mr.addMove(MoveOperand(single0), MoveOperand(single21), MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single0.id(), 0.0f);
+ TRY(mr.addMove(MoveOperand(single27), MoveOperand(single20),
+ MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single27.id(), 27.0f);
+ TRY(mr.addMove(MoveOperand(single14), MoveOperand(single5), MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single14.id(), 14.0f);
+ TRY(mr.addMove(MoveOperand(single2), MoveOperand(single14), MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single2.id(), 2.0f);
+ TRY(mr.addMove(MoveOperand(single12), MoveOperand(single22),
+ MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single12.id(), 12.0f);
+ TRY(mr.resolve());
+ mover.emit(mr);
+ mover.finish();
+ masm.abiret();
+ JitCode* code = linkAndAllocate(cx, &masm);
+ sim->call(code->raw(), 1, 1);
+ CHECK(int(sim->getFpuRegisterDouble(double0.id())) == 10);
+ CHECK(int(sim->getFpuRegisterFloat(single3.id())) == 15);
+ CHECK(int(sim->getFpuRegisterFloat(single28.id())) == 2);
+ CHECK(int(sim->getFpuRegisterFloat(single25.id())) == 30);
+ CHECK(int(sim->getFpuRegisterFloat(single2.id())) == 16);
+ CHECK(int(sim->getFpuRegisterFloat(single29.id())) == 2);
+ CHECK(int(sim->getFpuRegisterFloat(single10.id())) == 17);
+ CHECK(int(sim->getFpuRegisterFloat(single26.id())) == 9);
+ CHECK(int(sim->getFpuRegisterFloat(single23.id())) == 1);
+ CHECK(int(sim->getFpuRegisterFloat(single6.id())) == 8);
+ CHECK(int(sim->getFpuRegisterFloat(single16.id())) == 24);
+ CHECK(int(sim->getFpuRegisterDouble(double6.id())) == 5);
+ CHECK(int(sim->getFpuRegisterFloat(single30.id())) == 23);
+ CHECK(int(sim->getFpuRegisterFloat(single17.id())) == 27);
+ CHECK(int(sim->getFpuRegisterDouble(double4.id())) == 3);
+ CHECK(int(sim->getFpuRegisterFloat(single27.id())) == 14);
+ CHECK(int(sim->getFpuRegisterFloat(single31.id())) == 2);
+ CHECK(int(sim->getFpuRegisterFloat(single24.id())) == 2);
+ CHECK(int(sim->getFpuRegisterFloat(single11.id())) == 31);
+ CHECK(int(sim->getFpuRegisterFloat(single7.id())) == 24);
+ CHECK(int(sim->getFpuRegisterFloat(single21.id())) == 0);
+ CHECK(int(sim->getFpuRegisterFloat(single20.id())) == 27);
+ CHECK(int(sim->getFpuRegisterFloat(single5.id())) == 14);
+ CHECK(int(sim->getFpuRegisterFloat(single14.id())) == 2);
+ CHECK(int(sim->getFpuRegisterFloat(single22.id())) == 12);
+ return true;
+}
+END_TEST(testJitMoveEmitterCycles_autogen2)
+
+BEGIN_TEST(testJitMoveEmitterCycles_autogen3) {
+ using namespace js;
+ using namespace js::jit;
+ LifoAlloc lifo(LIFO_ALLOC_PRIMARY_CHUNK_SIZE);
+ TempAllocator alloc(&lifo);
+ JitContext jc(cx, &alloc);
+ StackMacroAssembler masm;
+ MoveEmitter mover(masm);
+ MoveResolver mr;
+ mr.setAllocator(alloc);
+ Simulator* sim = Simulator::Current();
+ TRY(mr.addMove(MoveOperand(single0), MoveOperand(single21), MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single0.id(), 0.0f);
+ TRY(mr.addMove(MoveOperand(single2), MoveOperand(single26), MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single2.id(), 2.0f);
+ TRY(mr.addMove(MoveOperand(single4), MoveOperand(single24), MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single4.id(), 4.0f);
+ TRY(mr.addMove(MoveOperand(single22), MoveOperand(single9), MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single22.id(), 22.0f);
+ TRY(mr.addMove(MoveOperand(single5), MoveOperand(single28), MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single5.id(), 5.0f);
+ TRY(mr.addMove(MoveOperand(single15), MoveOperand(single7), MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single15.id(), 15.0f);
+ TRY(mr.addMove(MoveOperand(single26), MoveOperand(single14),
+ MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single26.id(), 26.0f);
+ TRY(mr.addMove(MoveOperand(single13), MoveOperand(single30),
+ MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single13.id(), 13.0f);
+ TRY(mr.addMove(MoveOperand(single26), MoveOperand(single22),
+ MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single26.id(), 26.0f);
+ TRY(mr.addMove(MoveOperand(single21), MoveOperand(single6), MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single21.id(), 21.0f);
+ TRY(mr.addMove(MoveOperand(single23), MoveOperand(single31),
+ MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single23.id(), 23.0f);
+ TRY(mr.addMove(MoveOperand(single7), MoveOperand(single12), MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single7.id(), 7.0f);
+ TRY(mr.addMove(MoveOperand(single14), MoveOperand(single10),
+ MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single14.id(), 14.0f);
+ TRY(mr.addMove(MoveOperand(double12), MoveOperand(double8), MoveOp::DOUBLE));
+ sim->setFpuRegisterDouble(double12.id(), 12.0);
+ TRY(mr.addMove(MoveOperand(single5), MoveOperand(single1), MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single5.id(), 5.0f);
+ TRY(mr.addMove(MoveOperand(double12), MoveOperand(double2), MoveOp::DOUBLE));
+ sim->setFpuRegisterDouble(double12.id(), 12.0);
+ TRY(mr.addMove(MoveOperand(single3), MoveOperand(single8), MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single3.id(), 3.0f);
+ TRY(mr.addMove(MoveOperand(single14), MoveOperand(single0), MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single14.id(), 14.0f);
+ TRY(mr.addMove(MoveOperand(single28), MoveOperand(single29),
+ MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single28.id(), 28.0f);
+ TRY(mr.addMove(MoveOperand(single29), MoveOperand(single2), MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single29.id(), 29.0f);
+ TRY(mr.addMove(MoveOperand(single22), MoveOperand(single27),
+ MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single22.id(), 22.0f);
+ TRY(mr.addMove(MoveOperand(single21), MoveOperand(single11),
+ MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single21.id(), 21.0f);
+ TRY(mr.addMove(MoveOperand(single22), MoveOperand(single13),
+ MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single22.id(), 22.0f);
+ TRY(mr.addMove(MoveOperand(single29), MoveOperand(single25),
+ MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single29.id(), 29.0f);
+ TRY(mr.addMove(MoveOperand(single29), MoveOperand(single15),
+ MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single29.id(), 29.0f);
+ TRY(mr.addMove(MoveOperand(single16), MoveOperand(single23),
+ MoveOp::FLOAT32));
+ sim->setFpuRegisterFloat(single16.id(), 16.0f);
+ TRY(mr.resolve());
+ mover.emit(mr);
+ mover.finish();
+ masm.abiret();
+ JitCode* code = linkAndAllocate(cx, &masm);
+ sim->call(code->raw(), 1, 1);
+ CHECK(int(sim->getFpuRegisterFloat(single21.id())) == 0);
+ CHECK(int(sim->getFpuRegisterFloat(single26.id())) == 2);
+ CHECK(int(sim->getFpuRegisterFloat(single24.id())) == 4);
+ CHECK(int(sim->getFpuRegisterFloat(single9.id())) == 22);
+ CHECK(int(sim->getFpuRegisterFloat(single28.id())) == 5);
+ CHECK(int(sim->getFpuRegisterFloat(single7.id())) == 15);
+ CHECK(int(sim->getFpuRegisterFloat(single14.id())) == 26);
+ CHECK(int(sim->getFpuRegisterFloat(single30.id())) == 13);
+ CHECK(int(sim->getFpuRegisterFloat(single22.id())) == 26);
+ CHECK(int(sim->getFpuRegisterFloat(single6.id())) == 21);
+ CHECK(int(sim->getFpuRegisterFloat(single31.id())) == 23);
+ CHECK(int(sim->getFpuRegisterFloat(single12.id())) == 7);
+ CHECK(int(sim->getFpuRegisterFloat(single10.id())) == 14);
+ CHECK(int(sim->getFpuRegisterDouble(double8.id())) == 12);
+ CHECK(int(sim->getFpuRegisterFloat(single1.id())) == 5);
+ CHECK(int(sim->getFpuRegisterDouble(double2.id())) == 12);
+ CHECK(int(sim->getFpuRegisterFloat(single8.id())) == 3);
+ CHECK(int(sim->getFpuRegisterFloat(single0.id())) == 14);
+ CHECK(int(sim->getFpuRegisterFloat(single29.id())) == 28);
+ CHECK(int(sim->getFpuRegisterFloat(single2.id())) == 29);
+ CHECK(int(sim->getFpuRegisterFloat(single27.id())) == 22);
+ CHECK(int(sim->getFpuRegisterFloat(single3.id())) == 3);
+ CHECK(int(sim->getFpuRegisterFloat(single11.id())) == 21);
+ CHECK(int(sim->getFpuRegisterFloat(single13.id())) == 22);
+ CHECK(int(sim->getFpuRegisterFloat(single25.id())) == 29);
+ CHECK(int(sim->getFpuRegisterFloat(single15.id())) == 29);
+ CHECK(int(sim->getFpuRegisterFloat(single23.id())) == 16);
+ return true;
+}
+END_TEST(testJitMoveEmitterCycles_autogen3)
+
+#endif
diff --git a/js/src/jsapi-tests/testJitMoveEmitterCycles.cpp b/js/src/jsapi-tests/testJitMoveEmitterCycles.cpp
new file mode 100644
index 0000000000..521fab7fa9
--- /dev/null
+++ b/js/src/jsapi-tests/testJitMoveEmitterCycles.cpp
@@ -0,0 +1,616 @@
+/* -*- 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/. */
+
+#if defined(JS_SIMULATOR_ARM)
+
+# include "jit/arm/Assembler-arm.h"
+# include "jit/arm/MoveEmitter-arm.h"
+# include "jit/arm/Simulator-arm.h"
+# include "jit/Linker.h"
+# include "jit/MacroAssembler.h"
+# include "jit/MoveResolver.h"
+
+# include "jsapi-tests/tests.h"
+
+# include "vm/Runtime.h"
+
+static const int LIFO_ALLOC_PRIMARY_CHUNK_SIZE = 4 * 1024;
+
+static constexpr js::jit::FloatRegister s0(0, js::jit::VFPRegister::Single);
+static constexpr js::jit::FloatRegister s1(1, js::jit::VFPRegister::Single);
+static constexpr js::jit::FloatRegister s2(2, js::jit::VFPRegister::Single);
+static constexpr js::jit::FloatRegister s3(3, js::jit::VFPRegister::Single);
+static constexpr js::jit::FloatRegister s4(4, js::jit::VFPRegister::Single);
+static constexpr js::jit::FloatRegister s5(5, js::jit::VFPRegister::Single);
+static constexpr js::jit::FloatRegister s6(6, js::jit::VFPRegister::Single);
+static constexpr js::jit::FloatRegister s7(7, js::jit::VFPRegister::Single);
+static constexpr js::jit::FloatRegister s8(8, js::jit::VFPRegister::Single);
+static constexpr js::jit::FloatRegister s9(9, js::jit::VFPRegister::Single);
+static constexpr js::jit::FloatRegister s10(10, js::jit::VFPRegister::Single);
+static constexpr js::jit::FloatRegister s11(11, js::jit::VFPRegister::Single);
+static constexpr js::jit::FloatRegister s12(12, js::jit::VFPRegister::Single);
+static constexpr js::jit::FloatRegister s13(13, js::jit::VFPRegister::Single);
+static constexpr js::jit::FloatRegister s14(14, js::jit::VFPRegister::Single);
+static constexpr js::jit::FloatRegister s15(15, js::jit::VFPRegister::Single);
+static constexpr js::jit::FloatRegister s16(16, js::jit::VFPRegister::Single);
+static constexpr js::jit::FloatRegister s17(17, js::jit::VFPRegister::Single);
+static constexpr js::jit::FloatRegister s18(18, js::jit::VFPRegister::Single);
+static constexpr js::jit::FloatRegister s19(19, js::jit::VFPRegister::Single);
+static constexpr js::jit::FloatRegister s20(20, js::jit::VFPRegister::Single);
+static constexpr js::jit::FloatRegister s21(21, js::jit::VFPRegister::Single);
+static constexpr js::jit::FloatRegister s22(22, js::jit::VFPRegister::Single);
+static constexpr js::jit::FloatRegister s23(23, js::jit::VFPRegister::Single);
+static constexpr js::jit::FloatRegister s24(24, js::jit::VFPRegister::Single);
+static constexpr js::jit::FloatRegister s25(25, js::jit::VFPRegister::Single);
+static constexpr js::jit::FloatRegister s26(26, js::jit::VFPRegister::Single);
+static constexpr js::jit::FloatRegister s27(27, js::jit::VFPRegister::Single);
+static constexpr js::jit::FloatRegister s28(28, js::jit::VFPRegister::Single);
+static constexpr js::jit::FloatRegister s29(29, js::jit::VFPRegister::Single);
+static constexpr js::jit::FloatRegister s30(30, js::jit::VFPRegister::Single);
+static constexpr js::jit::FloatRegister s31(31, js::jit::VFPRegister::Single);
+
+static js::jit::JitCode* linkAndAllocate(JSContext* cx,
+ js::jit::MacroAssembler* masm) {
+ using namespace js;
+ using namespace js::jit;
+ Linker l(*masm);
+ return l.newCode(cx, CodeKind::Ion);
+}
+
+# define TRY(x) \
+ if (!(x)) return false;
+
+BEGIN_TEST(testJitMoveEmitterCycles_simple) {
+ using namespace js;
+ using namespace js::jit;
+ LifoAlloc lifo(LIFO_ALLOC_PRIMARY_CHUNK_SIZE);
+ TempAllocator alloc(&lifo);
+ JitContext jc(cx);
+ StackMacroAssembler masm(cx, alloc);
+ AutoCreatedBy acb(masm, __func__);
+ MoveEmitter mover(masm);
+ MoveResolver mr;
+ mr.setAllocator(alloc);
+ Simulator* sim = Simulator::Current();
+ TRY(mr.addMove(MoveOperand(d0), MoveOperand(d2), MoveOp::DOUBLE));
+ sim->set_d_register_from_double(0, 2);
+ TRY(mr.addMove(MoveOperand(d3), MoveOperand(d1), MoveOp::DOUBLE));
+ sim->set_d_register_from_double(3, 1);
+ TRY(mr.addMove(MoveOperand(s4), MoveOperand(s0), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(4, 0);
+ TRY(mr.addMove(MoveOperand(s5), MoveOperand(s6), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(5, 6);
+ TRY(mr.addMove(MoveOperand(s2), MoveOperand(s1), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(2, 1);
+ TRY(mr.addMove(MoveOperand(s3), MoveOperand(s7), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(3, 7);
+ // don't explode!
+ TRY(mr.resolve());
+ mover.emit(mr);
+ mover.finish();
+ masm.abiret();
+ JitCode* code = linkAndAllocate(cx, &masm);
+ sim->call(code->raw(), 1, 1);
+ float f;
+ double d;
+ sim->get_double_from_d_register(2, &d);
+ CHECK(d == 2);
+ sim->get_double_from_d_register(1, &d);
+ CHECK(int(d) == 1);
+ sim->get_float_from_s_register(0, &f);
+ CHECK(int(f) == 0);
+ sim->get_float_from_s_register(6, &f);
+ CHECK(int(f) == 6);
+ sim->get_float_from_s_register(1, &f);
+ CHECK(int(f) == 1);
+ sim->get_float_from_s_register(7, &f);
+ CHECK(int(f) == 7);
+ return true;
+}
+END_TEST(testJitMoveEmitterCycles_simple)
+BEGIN_TEST(testJitMoveEmitterCycles_autogen) {
+ using namespace js;
+ using namespace js::jit;
+ LifoAlloc lifo(LIFO_ALLOC_PRIMARY_CHUNK_SIZE);
+ TempAllocator alloc(&lifo);
+ JitContext jc(cx);
+ StackMacroAssembler masm(cx, alloc);
+ AutoCreatedBy acb(masm, __func__);
+ MoveEmitter mover(masm);
+ MoveResolver mr;
+ mr.setAllocator(alloc);
+ Simulator* sim = Simulator::Current();
+ TRY(mr.addMove(MoveOperand(d9), MoveOperand(d14), MoveOp::DOUBLE));
+ sim->set_d_register_from_double(9, 9);
+ TRY(mr.addMove(MoveOperand(s24), MoveOperand(s25), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(24, 24);
+ TRY(mr.addMove(MoveOperand(d3), MoveOperand(d0), MoveOp::DOUBLE));
+ sim->set_d_register_from_double(3, 3);
+ TRY(mr.addMove(MoveOperand(s10), MoveOperand(s31), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(10, 10);
+ TRY(mr.addMove(MoveOperand(d1), MoveOperand(d10), MoveOp::DOUBLE));
+ sim->set_d_register_from_double(1, 1);
+ TRY(mr.addMove(MoveOperand(s8), MoveOperand(s10), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(8, 8);
+ TRY(mr.addMove(MoveOperand(d2), MoveOperand(d7), MoveOp::DOUBLE));
+ sim->set_d_register_from_double(2, 2);
+ TRY(mr.addMove(MoveOperand(s20), MoveOperand(s18), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(20, 20);
+ TRY(mr.addMove(MoveOperand(s1), MoveOperand(s3), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(1, 1);
+ TRY(mr.addMove(MoveOperand(s17), MoveOperand(s11), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(17, 17);
+ TRY(mr.addMove(MoveOperand(s22), MoveOperand(s30), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(22, 22);
+ TRY(mr.addMove(MoveOperand(s31), MoveOperand(s7), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(31, 31);
+ TRY(mr.addMove(MoveOperand(d3), MoveOperand(d13), MoveOp::DOUBLE));
+ sim->set_d_register_from_double(3, 3);
+ TRY(mr.addMove(MoveOperand(d9), MoveOperand(d8), MoveOp::DOUBLE));
+ sim->set_d_register_from_double(9, 9);
+ TRY(mr.addMove(MoveOperand(s31), MoveOperand(s23), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(31, 31);
+ TRY(mr.addMove(MoveOperand(s13), MoveOperand(s8), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(13, 13);
+ TRY(mr.addMove(MoveOperand(s28), MoveOperand(s5), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(28, 28);
+ TRY(mr.addMove(MoveOperand(s31), MoveOperand(s19), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(31, 31);
+ TRY(mr.addMove(MoveOperand(s20), MoveOperand(s6), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(20, 20);
+ TRY(mr.addMove(MoveOperand(s0), MoveOperand(s2), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(0, 0);
+ TRY(mr.addMove(MoveOperand(d7), MoveOperand(d6), MoveOp::DOUBLE));
+ sim->set_d_register_from_double(7, 7);
+ TRY(mr.addMove(MoveOperand(s13), MoveOperand(s9), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(13, 13);
+ TRY(mr.addMove(MoveOperand(s1), MoveOperand(s4), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(1, 1);
+ TRY(mr.addMove(MoveOperand(s29), MoveOperand(s22), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(29, 29);
+ TRY(mr.addMove(MoveOperand(s25), MoveOperand(s24), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(25, 25);
+ // don't explode!
+ TRY(mr.resolve());
+ mover.emit(mr);
+ mover.finish();
+ masm.abiret();
+ JitCode* code = linkAndAllocate(cx, &masm);
+ sim->skipCalleeSavedRegsCheck = true;
+ sim->call(code->raw(), 1, 1);
+ double d;
+ float f;
+ sim->get_double_from_d_register(14, &d);
+ CHECK(int(d) == 9);
+ sim->get_float_from_s_register(25, &f);
+ CHECK(int(f) == 24);
+ sim->get_double_from_d_register(0, &d);
+ CHECK(int(d) == 3);
+ sim->get_float_from_s_register(31, &f);
+ CHECK(int(f) == 10);
+ sim->get_double_from_d_register(10, &d);
+ CHECK(int(d) == 1);
+ sim->get_float_from_s_register(10, &f);
+ CHECK(int(f) == 8);
+ sim->get_double_from_d_register(7, &d);
+ CHECK(int(d) == 2);
+ sim->get_float_from_s_register(18, &f);
+ CHECK(int(f) == 20);
+ sim->get_float_from_s_register(3, &f);
+ CHECK(int(f) == 1);
+ sim->get_float_from_s_register(11, &f);
+ CHECK(int(f) == 17);
+ sim->get_float_from_s_register(30, &f);
+ CHECK(int(f) == 22);
+ sim->get_float_from_s_register(7, &f);
+ CHECK(int(f) == 31);
+ sim->get_double_from_d_register(13, &d);
+ CHECK(int(d) == 3);
+ sim->get_double_from_d_register(8, &d);
+ CHECK(int(d) == 9);
+ sim->get_float_from_s_register(23, &f);
+ CHECK(int(f) == 31);
+ sim->get_float_from_s_register(8, &f);
+ CHECK(int(f) == 13);
+ sim->get_float_from_s_register(5, &f);
+ CHECK(int(f) == 28);
+ sim->get_float_from_s_register(19, &f);
+ CHECK(int(f) == 31);
+ sim->get_float_from_s_register(6, &f);
+ CHECK(int(f) == 20);
+ sim->get_float_from_s_register(2, &f);
+ CHECK(int(f) == 0);
+ sim->get_double_from_d_register(6, &d);
+ CHECK(int(d) == 7);
+ sim->get_float_from_s_register(9, &f);
+ CHECK(int(f) == 13);
+ sim->get_float_from_s_register(4, &f);
+ CHECK(int(f) == 1);
+ sim->get_float_from_s_register(22, &f);
+ CHECK(int(f) == 29);
+ sim->get_float_from_s_register(24, &f);
+ CHECK(int(f) == 25);
+ return true;
+}
+END_TEST(testJitMoveEmitterCycles_autogen)
+
+BEGIN_TEST(testJitMoveEmitterCycles_autogen2) {
+ using namespace js;
+ using namespace js::jit;
+ LifoAlloc lifo(LIFO_ALLOC_PRIMARY_CHUNK_SIZE);
+ TempAllocator alloc(&lifo);
+ JitContext jc(cx);
+ StackMacroAssembler masm(cx, alloc);
+ AutoCreatedBy acb(masm, __func__);
+ MoveEmitter mover(masm);
+ MoveResolver mr;
+ mr.setAllocator(alloc);
+ Simulator* sim = Simulator::Current();
+ TRY(mr.addMove(MoveOperand(d10), MoveOperand(d0), MoveOp::DOUBLE));
+ sim->set_d_register_from_double(10, 10);
+ TRY(mr.addMove(MoveOperand(s15), MoveOperand(s3), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(15, 15);
+ TRY(mr.addMove(MoveOperand(s2), MoveOperand(s28), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(2, 2);
+ TRY(mr.addMove(MoveOperand(s30), MoveOperand(s25), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(30, 30);
+ TRY(mr.addMove(MoveOperand(s16), MoveOperand(s2), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(16, 16);
+ TRY(mr.addMove(MoveOperand(s2), MoveOperand(s29), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(2, 2);
+ TRY(mr.addMove(MoveOperand(s17), MoveOperand(s10), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(17, 17);
+ TRY(mr.addMove(MoveOperand(s2), MoveOperand(s19), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(2, 2);
+ TRY(mr.addMove(MoveOperand(s9), MoveOperand(s26), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(9, 9);
+ TRY(mr.addMove(MoveOperand(s1), MoveOperand(s23), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(1, 1);
+ TRY(mr.addMove(MoveOperand(s8), MoveOperand(s6), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(8, 8);
+ TRY(mr.addMove(MoveOperand(s24), MoveOperand(s16), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(24, 24);
+ TRY(mr.addMove(MoveOperand(s19), MoveOperand(s4), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(19, 19);
+ TRY(mr.addMove(MoveOperand(d5), MoveOperand(d6), MoveOp::DOUBLE));
+ sim->set_d_register_from_double(5, 5);
+ TRY(mr.addMove(MoveOperand(s18), MoveOperand(s15), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(18, 18);
+ TRY(mr.addMove(MoveOperand(s23), MoveOperand(s30), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(23, 23);
+ TRY(mr.addMove(MoveOperand(s27), MoveOperand(s17), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(27, 27);
+ TRY(mr.addMove(MoveOperand(d3), MoveOperand(d4), MoveOp::DOUBLE));
+ sim->set_d_register_from_double(3, 3);
+ TRY(mr.addMove(MoveOperand(s14), MoveOperand(s27), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(14, 14);
+ TRY(mr.addMove(MoveOperand(s2), MoveOperand(s31), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(2, 2);
+ TRY(mr.addMove(MoveOperand(s2), MoveOperand(s24), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(2, 2);
+ TRY(mr.addMove(MoveOperand(s31), MoveOperand(s11), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(31, 31);
+ TRY(mr.addMove(MoveOperand(s0), MoveOperand(s18), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(0, 0);
+ TRY(mr.addMove(MoveOperand(s24), MoveOperand(s7), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(24, 24);
+ TRY(mr.addMove(MoveOperand(s0), MoveOperand(s21), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(0, 0);
+ TRY(mr.addMove(MoveOperand(s27), MoveOperand(s20), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(27, 27);
+ TRY(mr.addMove(MoveOperand(s14), MoveOperand(s5), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(14, 14);
+ TRY(mr.addMove(MoveOperand(s2), MoveOperand(s14), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(2, 2);
+ TRY(mr.addMove(MoveOperand(s12), MoveOperand(s22), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(12, 12);
+ // don't explode!
+ TRY(mr.resolve());
+ mover.emit(mr);
+ mover.finish();
+ masm.abiret();
+ JitCode* code = linkAndAllocate(cx, &masm);
+ sim->skipCalleeSavedRegsCheck = true;
+ sim->call(code->raw(), 1, 1);
+
+ double d;
+ float f;
+ sim->get_double_from_d_register(0, &d);
+ CHECK(int(d) == 10);
+ sim->get_float_from_s_register(3, &f);
+ CHECK(int(f) == 15);
+ sim->get_float_from_s_register(28, &f);
+ CHECK(int(f) == 2);
+ sim->get_float_from_s_register(25, &f);
+ CHECK(int(f) == 30);
+ sim->get_float_from_s_register(2, &f);
+ CHECK(int(f) == 16);
+ sim->get_float_from_s_register(29, &f);
+ CHECK(int(f) == 2);
+ sim->get_float_from_s_register(10, &f);
+ CHECK(int(f) == 17);
+ sim->get_float_from_s_register(19, &f);
+ CHECK(int(f) == 2);
+ sim->get_float_from_s_register(26, &f);
+ CHECK(int(f) == 9);
+ sim->get_float_from_s_register(23, &f);
+ CHECK(int(f) == 1);
+ sim->get_float_from_s_register(6, &f);
+ CHECK(int(f) == 8);
+ sim->get_float_from_s_register(16, &f);
+ CHECK(int(f) == 24);
+ sim->get_float_from_s_register(4, &f);
+ CHECK(int(f) == 19);
+ sim->get_double_from_d_register(6, &d);
+ CHECK(int(d) == 5);
+ sim->get_float_from_s_register(15, &f);
+ CHECK(int(f) == 18);
+ sim->get_float_from_s_register(30, &f);
+ CHECK(int(f) == 23);
+ sim->get_float_from_s_register(17, &f);
+ CHECK(int(f) == 27);
+ sim->get_double_from_d_register(4, &d);
+ CHECK(int(d) == 3);
+ sim->get_float_from_s_register(27, &f);
+ CHECK(int(f) == 14);
+ sim->get_float_from_s_register(31, &f);
+ CHECK(int(f) == 2);
+ sim->get_float_from_s_register(24, &f);
+ CHECK(int(f) == 2);
+ sim->get_float_from_s_register(11, &f);
+ CHECK(int(f) == 31);
+ sim->get_float_from_s_register(18, &f);
+ CHECK(int(f) == 0);
+ sim->get_float_from_s_register(7, &f);
+ CHECK(int(f) == 24);
+ sim->get_float_from_s_register(21, &f);
+ CHECK(int(f) == 0);
+ sim->get_float_from_s_register(20, &f);
+ CHECK(int(f) == 27);
+ sim->get_float_from_s_register(5, &f);
+ CHECK(int(f) == 14);
+ sim->get_float_from_s_register(14, &f);
+ CHECK(int(f) == 2);
+ sim->get_float_from_s_register(22, &f);
+ CHECK(int(f) == 12);
+ return true;
+}
+END_TEST(testJitMoveEmitterCycles_autogen2)
+
+BEGIN_TEST(testJitMoveEmitterCycles_autogen3) {
+ using namespace js;
+ using namespace js::jit;
+ LifoAlloc lifo(LIFO_ALLOC_PRIMARY_CHUNK_SIZE);
+ TempAllocator alloc(&lifo);
+ JitContext jc(cx);
+ StackMacroAssembler masm(cx, alloc);
+ AutoCreatedBy acb(masm, __func__);
+ MoveEmitter mover(masm);
+ MoveResolver mr;
+ mr.setAllocator(alloc);
+ Simulator* sim = Simulator::Current();
+ TRY(mr.addMove(MoveOperand(s0), MoveOperand(s21), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(0, 0);
+ TRY(mr.addMove(MoveOperand(s2), MoveOperand(s26), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(2, 2);
+ TRY(mr.addMove(MoveOperand(s19), MoveOperand(s20), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(19, 19);
+ TRY(mr.addMove(MoveOperand(s4), MoveOperand(s24), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(4, 4);
+ TRY(mr.addMove(MoveOperand(s22), MoveOperand(s9), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(22, 22);
+ TRY(mr.addMove(MoveOperand(s5), MoveOperand(s28), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(5, 5);
+ TRY(mr.addMove(MoveOperand(s15), MoveOperand(s7), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(15, 15);
+ TRY(mr.addMove(MoveOperand(s26), MoveOperand(s14), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(26, 26);
+ TRY(mr.addMove(MoveOperand(s13), MoveOperand(s30), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(13, 13);
+ TRY(mr.addMove(MoveOperand(s26), MoveOperand(s22), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(26, 26);
+ TRY(mr.addMove(MoveOperand(s21), MoveOperand(s6), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(21, 21);
+ TRY(mr.addMove(MoveOperand(s23), MoveOperand(s31), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(23, 23);
+ TRY(mr.addMove(MoveOperand(s7), MoveOperand(s12), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(7, 7);
+ TRY(mr.addMove(MoveOperand(s14), MoveOperand(s10), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(14, 14);
+ TRY(mr.addMove(MoveOperand(d12), MoveOperand(d8), MoveOp::DOUBLE));
+ sim->set_d_register_from_double(12, 12);
+ TRY(mr.addMove(MoveOperand(s5), MoveOperand(s1), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(5, 5);
+ TRY(mr.addMove(MoveOperand(d12), MoveOperand(d2), MoveOp::DOUBLE));
+ sim->set_d_register_from_double(12, 12);
+ TRY(mr.addMove(MoveOperand(s3), MoveOperand(s8), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(3, 3);
+ TRY(mr.addMove(MoveOperand(s14), MoveOperand(s0), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(14, 14);
+ TRY(mr.addMove(MoveOperand(s28), MoveOperand(s29), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(28, 28);
+ TRY(mr.addMove(MoveOperand(d12), MoveOperand(d9), MoveOp::DOUBLE));
+ sim->set_d_register_from_double(12, 12);
+ TRY(mr.addMove(MoveOperand(s29), MoveOperand(s2), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(29, 29);
+ TRY(mr.addMove(MoveOperand(s22), MoveOperand(s27), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(22, 22);
+ TRY(mr.addMove(MoveOperand(s19), MoveOperand(s3), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(19, 19);
+ TRY(mr.addMove(MoveOperand(s21), MoveOperand(s11), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(21, 21);
+ TRY(mr.addMove(MoveOperand(s22), MoveOperand(s13), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(22, 22);
+ TRY(mr.addMove(MoveOperand(s29), MoveOperand(s25), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(29, 29);
+ TRY(mr.addMove(MoveOperand(s29), MoveOperand(s15), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(29, 29);
+ TRY(mr.addMove(MoveOperand(s16), MoveOperand(s23), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(16, 16);
+ // don't explode!
+ TRY(mr.resolve());
+ mover.emit(mr);
+ mover.finish();
+ masm.abiret();
+ JitCode* code = linkAndAllocate(cx, &masm);
+ sim->skipCalleeSavedRegsCheck = true;
+ sim->call(code->raw(), 1, 1);
+
+ float f;
+ double d;
+ sim->get_float_from_s_register(21, &f);
+ CHECK(int(f) == 0);
+ sim->get_float_from_s_register(26, &f);
+ CHECK(int(f) == 2);
+ sim->get_float_from_s_register(20, &f);
+ CHECK(int(f) == 19);
+ sim->get_float_from_s_register(24, &f);
+ CHECK(int(f) == 4);
+ sim->get_float_from_s_register(9, &f);
+ CHECK(int(f) == 22);
+ sim->get_float_from_s_register(28, &f);
+ CHECK(int(f) == 5);
+ sim->get_float_from_s_register(7, &f);
+ CHECK(int(f) == 15);
+ sim->get_float_from_s_register(14, &f);
+ CHECK(int(f) == 26);
+ sim->get_float_from_s_register(30, &f);
+ CHECK(int(f) == 13);
+ sim->get_float_from_s_register(22, &f);
+ CHECK(int(f) == 26);
+ sim->get_float_from_s_register(6, &f);
+ CHECK(int(f) == 21);
+ sim->get_float_from_s_register(31, &f);
+ CHECK(int(f) == 23);
+ sim->get_float_from_s_register(12, &f);
+ CHECK(int(f) == 7);
+ sim->get_float_from_s_register(10, &f);
+ CHECK(int(f) == 14);
+ sim->get_double_from_d_register(8, &d);
+ CHECK(int(d) == 12);
+ sim->get_float_from_s_register(1, &f);
+ CHECK(int(f) == 5);
+ sim->get_double_from_d_register(2, &d);
+ CHECK(int(d) == 12);
+ sim->get_float_from_s_register(8, &f);
+ CHECK(int(f) == 3);
+ sim->get_float_from_s_register(0, &f);
+ CHECK(int(f) == 14);
+ sim->get_float_from_s_register(29, &f);
+ CHECK(int(f) == 28);
+ sim->get_double_from_d_register(9, &d);
+ CHECK(int(d) == 12);
+ sim->get_float_from_s_register(2, &f);
+ CHECK(int(f) == 29);
+ sim->get_float_from_s_register(27, &f);
+ CHECK(int(f) == 22);
+ sim->get_float_from_s_register(3, &f);
+ CHECK(int(f) == 19);
+ sim->get_float_from_s_register(11, &f);
+ CHECK(int(f) == 21);
+ sim->get_float_from_s_register(13, &f);
+ CHECK(int(f) == 22);
+ sim->get_float_from_s_register(25, &f);
+ CHECK(int(f) == 29);
+ sim->get_float_from_s_register(15, &f);
+ CHECK(int(f) == 29);
+ sim->get_float_from_s_register(23, &f);
+ CHECK(int(f) == 16);
+ return true;
+}
+END_TEST(testJitMoveEmitterCycles_autogen3)
+BEGIN_TEST(testJitMoveEmitterCycles_bug1299147_1) {
+ using namespace js;
+ using namespace js::jit;
+ LifoAlloc lifo(LIFO_ALLOC_PRIMARY_CHUNK_SIZE);
+ TempAllocator alloc(&lifo);
+ JitContext jc(cx);
+ StackMacroAssembler masm(cx, alloc);
+ AutoCreatedBy acb(masm, __func__);
+ MoveEmitter mover(masm);
+ MoveResolver mr;
+ mr.setAllocator(alloc);
+ Simulator* sim = Simulator::Current();
+ // S2 -> S0
+ // S2 -> S6
+ // S3 -> S1
+ // S3 -> S7
+ // D0 -> D1
+ // D0 -> D2
+ TRY(mr.addMove(MoveOperand(s2), MoveOperand(s0), MoveOp::FLOAT32));
+ TRY(mr.addMove(MoveOperand(s2), MoveOperand(s6), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(2, 2);
+ TRY(mr.addMove(MoveOperand(s3), MoveOperand(s1), MoveOp::FLOAT32));
+ TRY(mr.addMove(MoveOperand(s3), MoveOperand(s7), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(3, 4);
+ TRY(mr.addMove(MoveOperand(d0), MoveOperand(d1), MoveOp::FLOAT32));
+ TRY(mr.addMove(MoveOperand(d0), MoveOperand(d2), MoveOp::FLOAT32));
+ sim->set_d_register_from_double(0, 1);
+ // don't explode!
+ TRY(mr.resolve());
+ mover.emit(mr);
+ mover.finish();
+ masm.abiret();
+ JitCode* code = linkAndAllocate(cx, &masm);
+ sim->call(code->raw(), 1, 1);
+ float f;
+ double d;
+ sim->get_double_from_d_register(1, &d);
+ CHECK(d == 1);
+ sim->get_double_from_d_register(2, &d);
+ CHECK(d == 1);
+ sim->get_float_from_s_register(0, &f);
+ CHECK(int(f) == 2);
+ sim->get_float_from_s_register(6, &f);
+ CHECK(int(f) == 2);
+ sim->get_float_from_s_register(1, &f);
+ CHECK(int(f) == 4);
+ sim->get_float_from_s_register(7, &f);
+ CHECK(int(f) == 4);
+ return true;
+}
+END_TEST(testJitMoveEmitterCycles_bug1299147_1)
+BEGIN_TEST(testJitMoveEmitterCycles_bug1299147) {
+ using namespace js;
+ using namespace js::jit;
+ LifoAlloc lifo(LIFO_ALLOC_PRIMARY_CHUNK_SIZE);
+ TempAllocator alloc(&lifo);
+ JitContext jc(cx);
+ StackMacroAssembler masm(cx, alloc);
+ AutoCreatedBy acb(masm, __func__);
+ MoveEmitter mover(masm);
+ MoveResolver mr;
+ mr.setAllocator(alloc);
+ Simulator* sim = Simulator::Current();
+ // S2 -> S5
+ // S2 -> S6
+ // D0 -> D1
+ TRY(mr.addMove(MoveOperand(s2), MoveOperand(s5), MoveOp::FLOAT32));
+ TRY(mr.addMove(MoveOperand(s2), MoveOperand(s6), MoveOp::FLOAT32));
+ sim->set_s_register_from_float(2, 2);
+ TRY(mr.addMove(MoveOperand(d0), MoveOperand(d1), MoveOp::FLOAT32));
+ sim->set_d_register_from_double(0, 1);
+ // don't explode!
+ TRY(mr.resolve());
+ mover.emit(mr);
+ mover.finish();
+ masm.abiret();
+ JitCode* code = linkAndAllocate(cx, &masm);
+ sim->call(code->raw(), 1, 1);
+ float f;
+ double d;
+ sim->get_double_from_d_register(1, &d);
+ CHECK(d == 1);
+ sim->get_float_from_s_register(5, &f);
+ CHECK(int(f) == 2);
+ sim->get_float_from_s_register(6, &f);
+ CHECK(int(f) == 2);
+ return true;
+}
+END_TEST(testJitMoveEmitterCycles_bug1299147)
+
+#endif // JS_SIMULATOR_ARM
diff --git a/js/src/jsapi-tests/testJitRValueAlloc.cpp b/js/src/jsapi-tests/testJitRValueAlloc.cpp
new file mode 100644
index 0000000000..27242b7652
--- /dev/null
+++ b/js/src/jsapi-tests/testJitRValueAlloc.cpp
@@ -0,0 +1,256 @@
+/* -*- 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 "jit/Snapshots.h"
+
+#include "jsapi-tests/tests.h"
+
+using namespace js;
+using namespace js::jit;
+
+// These tests are checking that all slots of the current architecture can all
+// be encoded and decoded correctly. We iterate on all registers and on many
+// fake stack locations (Fibonacci).
+static RValueAllocation Read(const RValueAllocation& slot) {
+ CompactBufferWriter writer;
+ slot.write(writer);
+
+ // Call hash to run its assertions.
+ slot.hash();
+
+ CompactBufferReader reader(writer);
+ return RValueAllocation::read(reader);
+}
+
+class Fibonacci {
+ class Iterator {
+ public:
+ // std::iterator traits.
+ using iterator_category = std::input_iterator_tag;
+ using value_type = int32_t;
+ using difference_type = int32_t;
+ using pointer = value_type*;
+ using reference = value_type&;
+
+ private:
+ uint32_t value_{};
+ uint32_t last_{};
+
+ Iterator() = default;
+ Iterator(value_type value, value_type last) : value_(value), last_(last) {}
+
+ friend class Fibonacci;
+
+ public:
+ Iterator& operator++() {
+ auto next = value_ + last_;
+ if (next <= static_cast<uint32_t>(INT32_MAX)) {
+ last_ = value_;
+ value_ = next;
+ } else {
+ *this = Iterator{};
+ }
+ return *this;
+ }
+
+ bool operator==(const Iterator& other) const {
+ return value_ == other.value_ && last_ == other.last_;
+ }
+
+ bool operator!=(const Iterator& other) const { return !(*this == other); }
+
+ auto operator*() const { return static_cast<int32_t>(value_); }
+ };
+
+ public:
+ auto begin() { return Iterator{0, 1}; }
+
+ auto end() { return Iterator{}; }
+};
+
+BEGIN_TEST(testJitRValueAlloc_Double) {
+ RValueAllocation s;
+ for (uint32_t i = 0; i < FloatRegisters::Total; i++) {
+ s = RValueAllocation::Double(FloatRegister::FromCode(i));
+ CHECK(s == Read(s));
+ }
+ return true;
+}
+END_TEST(testJitRValueAlloc_Double)
+
+BEGIN_TEST(testJitRValueAlloc_FloatReg) {
+ RValueAllocation s;
+ for (uint32_t i = 0; i < FloatRegisters::Total; i++) {
+ s = RValueAllocation::AnyFloat(FloatRegister::FromCode(i));
+ CHECK(s == Read(s));
+ }
+ return true;
+}
+END_TEST(testJitRValueAlloc_FloatReg)
+
+BEGIN_TEST(testJitRValueAlloc_FloatStack) {
+ RValueAllocation s;
+ for (auto i : Fibonacci{}) {
+ s = RValueAllocation::AnyFloat(i);
+ CHECK(s == Read(s));
+ }
+ return true;
+}
+END_TEST(testJitRValueAlloc_FloatStack)
+
+BEGIN_TEST(testJitRValueAlloc_TypedReg) {
+ RValueAllocation s;
+ for (uint32_t i = 0; i < Registers::Total; i++) {
+#define FOR_EACH_JSVAL(_) \
+ /* _(JSVAL_TYPE_DOUBLE) */ \
+ _(JSVAL_TYPE_INT32) \
+ /* _(JSVAL_TYPE_UNDEFINED) */ \
+ _(JSVAL_TYPE_BOOLEAN) \
+ /* _(JSVAL_TYPE_MAGIC) */ \
+ _(JSVAL_TYPE_STRING) \
+ _(JSVAL_TYPE_SYMBOL) \
+ _(JSVAL_TYPE_BIGINT) \
+ /* _(JSVAL_TYPE_NULL) */ \
+ _(JSVAL_TYPE_OBJECT)
+
+#define CHECK_WITH_JSVAL(jsval) \
+ s = RValueAllocation::Typed(jsval, Register::FromCode(i)); \
+ CHECK(s == Read(s));
+
+ FOR_EACH_JSVAL(CHECK_WITH_JSVAL)
+#undef CHECK_WITH_JSVAL
+#undef FOR_EACH_JSVAL
+ }
+ return true;
+}
+END_TEST(testJitRValueAlloc_TypedReg)
+
+BEGIN_TEST(testJitRValueAlloc_TypedStack) {
+ RValueAllocation s;
+ for (auto i : Fibonacci{}) {
+#define FOR_EACH_JSVAL(_) \
+ _(JSVAL_TYPE_DOUBLE) \
+ _(JSVAL_TYPE_INT32) \
+ /* _(JSVAL_TYPE_UNDEFINED) */ \
+ _(JSVAL_TYPE_BOOLEAN) \
+ /* _(JSVAL_TYPE_MAGIC) */ \
+ _(JSVAL_TYPE_STRING) \
+ _(JSVAL_TYPE_SYMBOL) \
+ _(JSVAL_TYPE_BIGINT) \
+ /* _(JSVAL_TYPE_NULL) */ \
+ _(JSVAL_TYPE_OBJECT)
+
+#define CHECK_WITH_JSVAL(jsval) \
+ s = RValueAllocation::Typed(jsval, i); \
+ CHECK(s == Read(s));
+
+ FOR_EACH_JSVAL(CHECK_WITH_JSVAL)
+#undef CHECK_WITH_JSVAL
+#undef FOR_EACH_JSVAL
+ }
+ return true;
+}
+END_TEST(testJitRValueAlloc_TypedStack)
+
+#if defined(JS_NUNBOX32)
+
+BEGIN_TEST(testJitRValueAlloc_UntypedRegReg) {
+ RValueAllocation s;
+ for (uint32_t i = 0; i < Registers::Total; i++) {
+ for (uint32_t j = 0; j < Registers::Total; j++) {
+ if (i == j) {
+ continue;
+ }
+ s = RValueAllocation::Untyped(Register::FromCode(i),
+ Register::FromCode(j));
+ MOZ_ASSERT(s == Read(s));
+ CHECK(s == Read(s));
+ }
+ }
+ return true;
+}
+END_TEST(testJitRValueAlloc_UntypedRegReg)
+
+BEGIN_TEST(testJitRValueAlloc_UntypedRegStack) {
+ RValueAllocation s;
+ for (uint32_t i = 0; i < Registers::Total; i++) {
+ for (auto j : Fibonacci{}) {
+ s = RValueAllocation::Untyped(Register::FromCode(i), j);
+ CHECK(s == Read(s));
+ }
+ }
+ return true;
+}
+END_TEST(testJitRValueAlloc_UntypedRegStack)
+
+BEGIN_TEST(testJitRValueAlloc_UntypedStackReg) {
+ RValueAllocation s;
+ for (auto i : Fibonacci{}) {
+ for (uint32_t j = 0; j < Registers::Total; j++) {
+ s = RValueAllocation::Untyped(i, Register::FromCode(j));
+ CHECK(s == Read(s));
+ }
+ }
+ return true;
+}
+END_TEST(testJitRValueAlloc_UntypedStackReg)
+
+BEGIN_TEST(testJitRValueAlloc_UntypedStackStack) {
+ RValueAllocation s;
+ for (auto i : Fibonacci{}) {
+ for (auto j : Fibonacci{}) {
+ s = RValueAllocation::Untyped(i, j);
+ CHECK(s == Read(s));
+ }
+ }
+ return true;
+}
+END_TEST(testJitRValueAlloc_UntypedStackStack)
+
+#else
+
+BEGIN_TEST(testJitRValueAlloc_UntypedReg) {
+ RValueAllocation s;
+ for (uint32_t i = 0; i < Registers::Total; i++) {
+ s = RValueAllocation::Untyped(Register::FromCode(i));
+ CHECK(s == Read(s));
+ }
+ return true;
+}
+END_TEST(testJitRValueAlloc_UntypedReg)
+
+BEGIN_TEST(testJitRValueAlloc_UntypedStack) {
+ RValueAllocation s;
+ for (auto i : Fibonacci{}) {
+ s = RValueAllocation::Untyped(i);
+ CHECK(s == Read(s));
+ }
+ return true;
+}
+END_TEST(testJitRValueAlloc_UntypedStack)
+
+#endif
+
+BEGIN_TEST(testJitRValueAlloc_UndefinedAndNull) {
+ RValueAllocation s;
+ s = RValueAllocation::Undefined();
+ CHECK(s == Read(s));
+ s = RValueAllocation::Null();
+ CHECK(s == Read(s));
+ return true;
+}
+END_TEST(testJitRValueAlloc_UndefinedAndNull)
+
+BEGIN_TEST(testJitRValueAlloc_ConstantPool) {
+ RValueAllocation s;
+ for (auto i : Fibonacci{}) {
+ s = RValueAllocation::ConstantPool(i);
+ CHECK(s == Read(s));
+ }
+ return true;
+}
+END_TEST(testJitRValueAlloc_ConstantPool)
diff --git a/js/src/jsapi-tests/testJitRangeAnalysis.cpp b/js/src/jsapi-tests/testJitRangeAnalysis.cpp
new file mode 100644
index 0000000000..3414ef2310
--- /dev/null
+++ b/js/src/jsapi-tests/testJitRangeAnalysis.cpp
@@ -0,0 +1,368 @@
+/* -*- 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 "jit/IonAnalysis.h"
+#include "jit/MIRGenerator.h"
+#include "jit/MIRGraph.h"
+#include "jit/RangeAnalysis.h"
+
+#include "jsapi-tests/testJitMinimalFunc.h"
+#include "jsapi-tests/tests.h"
+
+using namespace js;
+using namespace js::jit;
+
+static bool EquivalentRanges(const Range* a, const Range* b) {
+ if (a->hasInt32UpperBound() != b->hasInt32UpperBound()) {
+ return false;
+ }
+ if (a->hasInt32LowerBound() != b->hasInt32LowerBound()) {
+ return false;
+ }
+ if (a->hasInt32UpperBound() && (a->upper() != b->upper())) {
+ return false;
+ }
+ if (a->hasInt32LowerBound() && (a->lower() != b->lower())) {
+ return false;
+ }
+ if (a->canHaveFractionalPart() != b->canHaveFractionalPart()) {
+ return false;
+ }
+ if (a->canBeNegativeZero() != b->canBeNegativeZero()) {
+ return false;
+ }
+ if (a->canBeNaN() != b->canBeNaN()) {
+ return false;
+ }
+ if (a->canBeInfiniteOrNaN() != b->canBeInfiniteOrNaN()) {
+ return false;
+ }
+ if (!a->canBeInfiniteOrNaN() && (a->exponent() != b->exponent())) {
+ return false;
+ }
+ return true;
+}
+
+BEGIN_TEST(testJitRangeAnalysis_MathSign) {
+ MinimalAlloc func;
+
+ Range* xnan = new (func.alloc) Range();
+
+ Range* ninf = Range::NewDoubleSingletonRange(
+ func.alloc, mozilla::NegativeInfinity<double>());
+ Range* n1_5 = Range::NewDoubleSingletonRange(func.alloc, -1.5);
+ Range* n1_0 = Range::NewDoubleSingletonRange(func.alloc, -1);
+ Range* n0_5 = Range::NewDoubleSingletonRange(func.alloc, -0.5);
+ Range* n0_0 = Range::NewDoubleSingletonRange(func.alloc, -0.0);
+
+ Range* p0_0 = Range::NewDoubleSingletonRange(func.alloc, 0.0);
+ Range* p0_5 = Range::NewDoubleSingletonRange(func.alloc, 0.5);
+ Range* p1_0 = Range::NewDoubleSingletonRange(func.alloc, 1.0);
+ Range* p1_5 = Range::NewDoubleSingletonRange(func.alloc, 1.5);
+ Range* pinf = Range::NewDoubleSingletonRange(
+ func.alloc, mozilla::PositiveInfinity<double>());
+
+ Range* xnanSign = Range::sign(func.alloc, xnan);
+
+ Range* ninfSign = Range::sign(func.alloc, ninf);
+ Range* n1_5Sign = Range::sign(func.alloc, n1_5);
+ Range* n1_0Sign = Range::sign(func.alloc, n1_0);
+ Range* n0_5Sign = Range::sign(func.alloc, n0_5);
+ Range* n0_0Sign = Range::sign(func.alloc, n0_0);
+
+ Range* p0_0Sign = Range::sign(func.alloc, p0_0);
+ Range* p0_5Sign = Range::sign(func.alloc, p0_5);
+ Range* p1_0Sign = Range::sign(func.alloc, p1_0);
+ Range* p1_5Sign = Range::sign(func.alloc, p1_5);
+ Range* pinfSign = Range::sign(func.alloc, pinf);
+
+ CHECK(!xnanSign);
+ CHECK(EquivalentRanges(ninfSign,
+ Range::NewInt32SingletonRange(func.alloc, -1)));
+ CHECK(EquivalentRanges(n1_5Sign,
+ Range::NewInt32SingletonRange(func.alloc, -1)));
+ CHECK(EquivalentRanges(n1_0Sign,
+ Range::NewInt32SingletonRange(func.alloc, -1)));
+
+ // This should ideally be just -1, but range analysis can't represent the
+ // specific fractional range of the constant.
+ CHECK(EquivalentRanges(n0_5Sign, Range::NewInt32Range(func.alloc, -1, 0)));
+
+ CHECK(EquivalentRanges(n0_0Sign,
+ Range::NewDoubleSingletonRange(func.alloc, -0.0)));
+
+ CHECK(!n0_0Sign->canHaveFractionalPart());
+ CHECK(n0_0Sign->canBeNegativeZero());
+ CHECK(n0_0Sign->lower() == 0);
+ CHECK(n0_0Sign->upper() == 0);
+
+ CHECK(
+ EquivalentRanges(p0_0Sign, Range::NewInt32SingletonRange(func.alloc, 0)));
+
+ CHECK(!p0_0Sign->canHaveFractionalPart());
+ CHECK(!p0_0Sign->canBeNegativeZero());
+ CHECK(p0_0Sign->lower() == 0);
+ CHECK(p0_0Sign->upper() == 0);
+
+ // This should ideally be just 1, but range analysis can't represent the
+ // specific fractional range of the constant.
+ CHECK(EquivalentRanges(p0_5Sign, Range::NewInt32Range(func.alloc, 0, 1)));
+
+ CHECK(
+ EquivalentRanges(p1_0Sign, Range::NewInt32SingletonRange(func.alloc, 1)));
+ CHECK(
+ EquivalentRanges(p1_5Sign, Range::NewInt32SingletonRange(func.alloc, 1)));
+ CHECK(
+ EquivalentRanges(pinfSign, Range::NewInt32SingletonRange(func.alloc, 1)));
+
+ return true;
+}
+END_TEST(testJitRangeAnalysis_MathSign)
+
+BEGIN_TEST(testJitRangeAnalysis_MathSignBeta) {
+ MinimalFunc func;
+
+ MBasicBlock* entry = func.createEntryBlock();
+ MBasicBlock* thenBlock = func.createBlock(entry);
+ MBasicBlock* elseBlock = func.createBlock(entry);
+ MBasicBlock* elseThenBlock = func.createBlock(elseBlock);
+ MBasicBlock* elseElseBlock = func.createBlock(elseBlock);
+
+ // if (p < 0)
+ MParameter* p = func.createParameter();
+ entry->add(p);
+ MConstant* c0 = MConstant::New(func.alloc, DoubleValue(0.0));
+ entry->add(c0);
+ MConstant* cm0 = MConstant::New(func.alloc, DoubleValue(-0.0));
+ entry->add(cm0);
+ MCompare* cmp =
+ MCompare::New(func.alloc, p, c0, JSOp::Lt, MCompare::Compare_Double);
+ entry->add(cmp);
+ entry->end(MTest::New(func.alloc, cmp, thenBlock, elseBlock));
+
+ // {
+ // return Math.sign(p + -0);
+ // }
+ MAdd* thenAdd = MAdd::New(func.alloc, p, cm0, MIRType::Double);
+ thenBlock->add(thenAdd);
+ MSign* thenSign = MSign::New(func.alloc, thenAdd, MIRType::Double);
+ thenBlock->add(thenSign);
+ MReturn* thenRet = MReturn::New(func.alloc, thenSign);
+ thenBlock->end(thenRet);
+
+ // else
+ // {
+ // if (p >= 0)
+ MCompare* elseCmp =
+ MCompare::New(func.alloc, p, c0, JSOp::Ge, MCompare::Compare_Double);
+ elseBlock->add(elseCmp);
+ elseBlock->end(MTest::New(func.alloc, elseCmp, elseThenBlock, elseElseBlock));
+
+ // {
+ // return Math.sign(p + -0);
+ // }
+ MAdd* elseThenAdd = MAdd::New(func.alloc, p, cm0, MIRType::Double);
+ elseThenBlock->add(elseThenAdd);
+ MSign* elseThenSign = MSign::New(func.alloc, elseThenAdd, MIRType::Double);
+ elseThenBlock->add(elseThenSign);
+ MReturn* elseThenRet = MReturn::New(func.alloc, elseThenSign);
+ elseThenBlock->end(elseThenRet);
+
+ // else
+ // {
+ // return Math.sign(p + -0);
+ // }
+ // }
+ MAdd* elseElseAdd = MAdd::New(func.alloc, p, cm0, MIRType::Double);
+ elseElseBlock->add(elseElseAdd);
+ MSign* elseElseSign = MSign::New(func.alloc, elseElseAdd, MIRType::Double);
+ elseElseBlock->add(elseElseSign);
+ MReturn* elseElseRet = MReturn::New(func.alloc, elseElseSign);
+ elseElseBlock->end(elseElseRet);
+
+ if (!func.runRangeAnalysis()) {
+ return false;
+ }
+
+ CHECK(!p->range());
+ CHECK(EquivalentRanges(c0->range(),
+ Range::NewDoubleSingletonRange(func.alloc, 0.0)));
+ CHECK(EquivalentRanges(cm0->range(),
+ Range::NewDoubleSingletonRange(func.alloc, -0.0)));
+
+ // On the (p < 0) side, p is negative and not -0 (surprise!)
+ CHECK(EquivalentRanges(
+ thenAdd->range(),
+ new (func.alloc)
+ Range(Range::NoInt32LowerBound, 0, Range::IncludesFractionalParts,
+ Range::ExcludesNegativeZero, Range::IncludesInfinity)));
+
+ // Consequently, its Math.sign value is not -0 either.
+ CHECK(EquivalentRanges(thenSign->range(),
+ new (func.alloc)
+ Range(-1, 0, Range::ExcludesFractionalParts,
+ Range::ExcludesNegativeZero, 0)));
+
+ // On the (p >= 0) side, p is not negative and may be -0 (surprise!)
+ CHECK(EquivalentRanges(
+ elseThenAdd->range(),
+ new (func.alloc)
+ Range(0, Range::NoInt32UpperBound, Range::IncludesFractionalParts,
+ Range::IncludesNegativeZero, Range::IncludesInfinity)));
+
+ // Consequently, its Math.sign value may be -0 too.
+ CHECK(EquivalentRanges(elseThenSign->range(),
+ new (func.alloc)
+ Range(0, 1, Range::ExcludesFractionalParts,
+ Range::IncludesNegativeZero, 0)));
+
+ // Otherwise, p may be NaN.
+ CHECK(elseElseAdd->range()->isUnknown());
+ CHECK(!elseElseSign->range());
+
+ return true;
+}
+END_TEST(testJitRangeAnalysis_MathSignBeta)
+
+BEGIN_TEST(testJitRangeAnalysis_StrictCompareBeta) {
+ MinimalFunc func;
+
+ MBasicBlock* entry = func.createEntryBlock();
+ MBasicBlock* thenBlock = func.createBlock(entry);
+ MBasicBlock* elseBlock = func.createBlock(entry);
+
+ // if (p === 0)
+ MParameter* p = func.createParameter();
+ entry->add(p);
+ MConstant* c0 = MConstant::New(func.alloc, DoubleValue(0.0));
+ entry->add(c0);
+ MCompare* cmp = MCompare::New(func.alloc, p, c0, JSOp::StrictEq,
+ MCompare::Compare_Double);
+ entry->add(cmp);
+ auto* test = MTest::New(func.alloc, cmp, thenBlock, elseBlock);
+ entry->end(test);
+
+ // {
+ // return p + -0;
+ // }
+ MConstant* cm0 = MConstant::New(func.alloc, DoubleValue(-0.0));
+ thenBlock->add(cm0);
+ MAdd* thenAdd = MAdd::New(func.alloc, p, cm0, MIRType::Double);
+ thenBlock->add(thenAdd);
+ MReturn* thenRet = MReturn::New(func.alloc, thenAdd);
+ thenBlock->end(thenRet);
+
+ // else
+ // {
+ // return 0;
+ // }
+ MReturn* elseRet = MReturn::New(func.alloc, c0);
+ elseBlock->end(elseRet);
+
+ // If range analysis inserts a beta node for p, it will be able to compute
+ // a meaningful range for p + -0.
+
+ auto replaceCompare = [&](auto compareType) {
+ auto* newCmp =
+ MCompare::New(func.alloc, p, c0, JSOp::StrictEq, compareType);
+ entry->insertBefore(cmp, newCmp);
+ test->replaceOperand(0, newCmp);
+ cmp = newCmp;
+ };
+
+ // We can't do beta node insertion with StrictEq and a non-numeric
+ // comparison though.
+ for (auto compareType :
+ {MCompare::Compare_Object, MCompare::Compare_String}) {
+ replaceCompare(compareType);
+
+ if (!func.runRangeAnalysis()) {
+ return false;
+ }
+ CHECK(!thenAdd->range() || thenAdd->range()->isUnknown());
+ ClearDominatorTree(func.graph);
+ }
+
+ // We can do it with a numeric comparison.
+ replaceCompare(MCompare::Compare_Double);
+ if (!func.runRangeAnalysis()) {
+ return false;
+ }
+ CHECK(EquivalentRanges(thenAdd->range(),
+ Range::NewDoubleRange(func.alloc, 0.0, 0.0)));
+
+ return true;
+}
+END_TEST(testJitRangeAnalysis_StrictCompareBeta)
+
+static void deriveShiftRightRange(int32_t lhsLower, int32_t lhsUpper,
+ int32_t rhsLower, int32_t rhsUpper,
+ int32_t* min, int32_t* max) {
+ // This is the reference algorithm and should be verifiable by inspection.
+ int64_t i, j;
+ *min = INT32_MAX;
+ *max = INT32_MIN;
+ for (i = lhsLower; i <= lhsUpper; i++) {
+ for (j = rhsLower; j <= rhsUpper; j++) {
+ int32_t r = int32_t(i) >> (int32_t(j) & 0x1f);
+ if (r > *max) *max = r;
+ if (r < *min) *min = r;
+ }
+ }
+}
+
+static bool checkShiftRightRange(int32_t lhsLow, int32_t lhsHigh,
+ int32_t lhsInc, int32_t rhsLow,
+ int32_t rhsHigh, int32_t rhsInc) {
+ MinimalAlloc func;
+ int64_t lhsLower, lhsUpper, rhsLower, rhsUpper;
+
+ for (lhsLower = lhsLow; lhsLower <= lhsHigh; lhsLower += lhsInc) {
+ for (lhsUpper = lhsLower; lhsUpper <= lhsHigh; lhsUpper += lhsInc) {
+ Range* lhsRange = Range::NewInt32Range(func.alloc, lhsLower, lhsUpper);
+ for (rhsLower = rhsLow; rhsLower <= rhsHigh; rhsLower += rhsInc) {
+ for (rhsUpper = rhsLower; rhsUpper <= rhsHigh; rhsUpper += rhsInc) {
+ if (!func.alloc.ensureBallast()) {
+ return false;
+ }
+
+ Range* rhsRange =
+ Range::NewInt32Range(func.alloc, rhsLower, rhsUpper);
+ Range* result = Range::rsh(func.alloc, lhsRange, rhsRange);
+ int32_t min, max;
+ deriveShiftRightRange(lhsLower, lhsUpper, rhsLower, rhsUpper, &min,
+ &max);
+ if (!result->isInt32() || result->lower() != min ||
+ result->upper() != max) {
+ return false;
+ }
+ }
+ }
+ }
+ }
+ return true;
+}
+
+BEGIN_TEST(testJitRangeAnalysis_shiftRight) {
+ CHECK(checkShiftRightRange(-16, 15, 1, 0, 31, 1));
+ CHECK(checkShiftRightRange(-8, 7, 1, -64, 63, 1));
+ return true;
+}
+END_TEST(testJitRangeAnalysis_shiftRight)
+
+BEGIN_TEST(testJitRangeAnalysis_MathCeil) {
+ MinimalAlloc func;
+
+ Range* n0_5 = Range::NewDoubleSingletonRange(func.alloc, -0.5);
+ Range* n0_5Ceil = Range::ceil(func.alloc, n0_5);
+
+ CHECK(n0_5Ceil);
+ CHECK(n0_5Ceil->canBeNegativeZero());
+
+ return true;
+}
+END_TEST(testJitRangeAnalysis_MathCeil)
diff --git a/js/src/jsapi-tests/testJitRegisterSet.cpp b/js/src/jsapi-tests/testJitRegisterSet.cpp
new file mode 100644
index 0000000000..098fb5f9d5
--- /dev/null
+++ b/js/src/jsapi-tests/testJitRegisterSet.cpp
@@ -0,0 +1,211 @@
+/* -*- 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 "jit/RegisterSets.h"
+
+#include "jsapi-tests/tests.h"
+
+using namespace js;
+using namespace js::jit;
+
+static bool CoPrime(size_t a, size_t b) {
+ if (b <= 1) {
+ return a == 1 || b == 1;
+ }
+ return CoPrime(b, a % b);
+}
+
+// This macros are use to iterave over all registers in a large number of
+// non-looping sequences, which does not rely on the getFirst / getLast
+// functions.
+#define BEGIN_INDEX_WALK(RegTotal) \
+ static const size_t Total = RegTotal; \
+ for (size_t walk = 1; walk < RegTotal; walk += 2) { \
+ if (!CoPrime(RegTotal, walk)) continue; \
+ for (size_t start = 0; start < RegTotal; start++) { \
+ size_t index = start;
+
+#define END_INDEX_WALK \
+ } \
+ }
+
+#define BEGIN_All_WALK(RegTotal) \
+ static const size_t Total = RegTotal; \
+ size_t walk = 1; \
+ size_t start = 0; \
+ size_t index = start;
+
+#define FOR_ALL_REGISTERS(Register, reg) \
+ do { \
+ Register reg = Register::FromCode(index);
+
+#define END_FOR_ALL_REGISTERS \
+ index = (index + walk) % Total; \
+ } \
+ while (index != start)
+
+BEGIN_TEST(testJitRegisterSet_GPR) {
+ BEGIN_INDEX_WALK(Registers::Total)
+
+ LiveGeneralRegisterSet liveRegs;
+ AllocatableGeneralRegisterSet pool(GeneralRegisterSet::All());
+ CHECK(liveRegs.empty());
+ CHECK(pool.set() == GeneralRegisterSet::All());
+
+ FOR_ALL_REGISTERS(Register, reg) {
+ CHECK(!pool.has(reg) || !liveRegs.has(reg));
+ if (pool.has(reg)) {
+ CHECK(!liveRegs.has(reg));
+ pool.take(reg);
+ liveRegs.add(reg);
+ CHECK(liveRegs.has(reg));
+ CHECK(!pool.has(reg));
+ }
+ CHECK(!pool.has(reg) || !liveRegs.has(reg));
+ }
+ END_FOR_ALL_REGISTERS;
+
+ CHECK(pool.empty());
+
+ FOR_ALL_REGISTERS(Register, reg) {
+ CHECK(!pool.has(reg) || !liveRegs.has(reg));
+ if (liveRegs.has(reg)) {
+ CHECK(!pool.has(reg));
+ liveRegs.take(reg);
+ pool.add(reg);
+ CHECK(pool.has(reg));
+ CHECK(!liveRegs.has(reg));
+ }
+ CHECK(!pool.has(reg) || !liveRegs.has(reg));
+ }
+ END_FOR_ALL_REGISTERS;
+
+ CHECK(liveRegs.empty());
+ CHECK(pool.set() == GeneralRegisterSet::All());
+
+ END_INDEX_WALK
+ return true;
+}
+END_TEST(testJitRegisterSet_GPR)
+
+BEGIN_TEST(testJitRegisterSet_FPU) {
+ BEGIN_INDEX_WALK(FloatRegisters::Total)
+
+ LiveFloatRegisterSet liveRegs;
+ AllocatableFloatRegisterSet pool(FloatRegisterSet::All());
+ CHECK(liveRegs.empty());
+ CHECK(pool.set() == FloatRegisterSet::All());
+
+ FOR_ALL_REGISTERS(FloatRegister, reg) {
+ CHECK(!pool.has(reg) || !liveRegs.has(reg));
+ if (pool.has(reg)) {
+ CHECK(!liveRegs.has(reg));
+ pool.take(reg);
+ liveRegs.add(reg);
+ CHECK(liveRegs.has(reg));
+ CHECK(!pool.has(reg));
+ }
+ CHECK(!pool.has(reg) || !liveRegs.has(reg));
+ }
+ END_FOR_ALL_REGISTERS;
+
+ CHECK(pool.empty());
+
+ FOR_ALL_REGISTERS(FloatRegister, reg) {
+ CHECK(!pool.has(reg) || !liveRegs.has(reg));
+ if (liveRegs.has(reg)) {
+ CHECK(!pool.has(reg));
+ liveRegs.take(reg);
+ pool.add(reg);
+ CHECK(pool.has(reg));
+ CHECK(!liveRegs.has(reg));
+ }
+ CHECK(!pool.has(reg) || !liveRegs.has(reg));
+ }
+ END_FOR_ALL_REGISTERS;
+
+ CHECK(liveRegs.empty());
+ CHECK(pool.set() == FloatRegisterSet::All());
+
+ END_INDEX_WALK
+ return true;
+}
+END_TEST(testJitRegisterSet_FPU)
+
+void pullAllFpus(AllocatableFloatRegisterSet& set, uint32_t& max_bits,
+ uint32_t bits) {
+ FloatRegisterSet allocSet(set.bits());
+ FloatRegisterSet available_f32(
+ allocSet.allAllocatable<RegTypeName::Float32>());
+ FloatRegisterSet available_f64(
+ allocSet.allAllocatable<RegTypeName::Float64>());
+ FloatRegisterSet available_v128(
+ allocSet.allAllocatable<RegTypeName::Vector128>());
+ for (FloatRegisterIterator it(available_f32); it.more(); ++it) {
+ FloatRegister tmp = *it;
+ set.take(tmp);
+ pullAllFpus(set, max_bits, bits + 32);
+ set.add(tmp);
+ }
+ for (FloatRegisterIterator it(available_f64); it.more(); ++it) {
+ FloatRegister tmp = *it;
+ set.take(tmp);
+ pullAllFpus(set, max_bits, bits + 64);
+ set.add(tmp);
+ }
+ for (FloatRegisterIterator it(available_v128); it.more(); ++it) {
+ FloatRegister tmp = *it;
+ set.take(tmp);
+ pullAllFpus(set, max_bits, bits + 128);
+ set.add(tmp);
+ }
+ if (bits >= max_bits) {
+ max_bits = bits;
+ }
+}
+
+BEGIN_TEST(testJitRegisterSet_FPU_Aliases) {
+ BEGIN_All_WALK(FloatRegisters::Total);
+ FOR_ALL_REGISTERS(FloatRegister, reg) {
+ AllocatableFloatRegisterSet pool;
+ pool.add(reg);
+
+ uint32_t alias_bits = 0;
+ for (uint32_t i = 0; i < reg.numAlignedAliased(); i++) {
+ FloatRegister alias = reg.alignedAliased(i);
+
+ if (alias.isSingle()) {
+ if (alias_bits <= 32) {
+ alias_bits = 32;
+ }
+ } else if (alias.isDouble()) {
+ if (alias_bits <= 64) {
+ alias_bits = 64;
+ }
+ } else if (alias.isSimd128()) {
+ if (alias_bits <= 128) {
+ alias_bits = 128;
+ }
+ }
+ }
+
+ uint32_t max_bits = 0;
+ pullAllFpus(pool, max_bits, 0);
+
+ // By adding one register, we expect that we should not be able to pull
+ // more than any of its aligned aliases. This rule should hold for both
+ // x64 and ARM.
+ CHECK(max_bits <= alias_bits);
+
+ // We added one register, we expect to be able to pull it back.
+ CHECK(max_bits > 0);
+ }
+ END_FOR_ALL_REGISTERS;
+
+ return true;
+}
+END_TEST(testJitRegisterSet_FPU_Aliases)
diff --git a/js/src/jsapi-tests/testLargeArrayBuffers.cpp b/js/src/jsapi-tests/testLargeArrayBuffers.cpp
new file mode 100644
index 0000000000..354847395e
--- /dev/null
+++ b/js/src/jsapi-tests/testLargeArrayBuffers.cpp
@@ -0,0 +1,166 @@
+/* -*- 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 "js/ArrayBuffer.h"
+#include "js/ArrayBufferMaybeShared.h"
+#include "js/experimental/TypedData.h"
+#include "js/SharedArrayBuffer.h"
+
+#include "jsapi-tests/tests.h"
+
+using namespace js;
+
+BEGIN_TEST(testLargeArrayBuffers) {
+#ifdef JS_64BIT
+ constexpr size_t nbytes = size_t(6) * 1024 * 1024 * 1024; // 6 GB.
+
+ RootedObject buffer(cx, JS::NewArrayBuffer(cx, nbytes));
+ CHECK(buffer);
+ CHECK(JS::IsArrayBufferObject(buffer));
+
+ size_t length;
+ bool isShared;
+ uint8_t* data;
+
+ // ArrayBuffer
+ {
+ CHECK_EQUAL(JS::GetArrayBufferByteLength(buffer), nbytes);
+
+ JS::GetArrayBufferLengthAndData(buffer, &length, &isShared, &data);
+ CHECK_EQUAL(length, nbytes);
+
+ length = 0;
+ JS::GetArrayBufferMaybeSharedLengthAndData(buffer, &length, &isShared,
+ &data);
+ CHECK_EQUAL(length, nbytes);
+
+ length = 0;
+ CHECK(GetObjectAsArrayBuffer(buffer, &length, &data));
+ CHECK_EQUAL(length, nbytes);
+ }
+
+ // New Uint8Array
+ {
+ RootedObject tarr(cx, JS_NewUint8Array(cx, nbytes));
+ CHECK(JS_IsTypedArrayObject(tarr));
+ CHECK_EQUAL(JS_GetArrayBufferViewByteOffset(tarr), 0u);
+ CHECK_EQUAL(JS_GetArrayBufferViewByteLength(tarr), nbytes);
+ CHECK_EQUAL(JS_GetTypedArrayByteOffset(tarr), 0u);
+ CHECK_EQUAL(JS_GetTypedArrayByteLength(tarr), nbytes);
+ CHECK_EQUAL(JS_GetTypedArrayLength(tarr), nbytes);
+
+ length = 0;
+ js::GetArrayBufferViewLengthAndData(tarr, &length, &isShared, &data);
+ CHECK_EQUAL(length, nbytes);
+
+ length = 0;
+ js::GetUint8ArrayLengthAndData(tarr, &length, &isShared, &data);
+ CHECK_EQUAL(length, nbytes);
+
+ length = 0;
+ JS::AutoCheckCannotGC nogc(cx);
+ CHECK(data = JS::TypedArray<Scalar::Uint8>::unwrap(tarr).getLengthAndData(
+ &length, &isShared, nogc));
+ CHECK_EQUAL(length, nbytes);
+
+ length = 0;
+ CHECK(JS_GetObjectAsArrayBufferView(tarr, &length, &isShared, &data));
+ CHECK_EQUAL(length, nbytes);
+ }
+
+ // Int16Array
+ {
+ RootedObject tarr(cx,
+ JS_NewInt16ArrayWithBuffer(cx, buffer, 0, nbytes / 2));
+ CHECK(JS_IsTypedArrayObject(tarr));
+ CHECK_EQUAL(JS_GetArrayBufferViewByteOffset(tarr), 0u);
+ CHECK_EQUAL(JS_GetArrayBufferViewByteLength(tarr), nbytes);
+ CHECK_EQUAL(JS_GetTypedArrayByteOffset(tarr), 0u);
+ CHECK_EQUAL(JS_GetTypedArrayByteLength(tarr), nbytes);
+ CHECK_EQUAL(JS_GetTypedArrayLength(tarr), nbytes / 2);
+
+ length = 0;
+ js::GetArrayBufferViewLengthAndData(tarr, &length, &isShared, &data);
+ CHECK_EQUAL(length, nbytes);
+
+ length = 0;
+ int16_t* int16Data;
+ js::GetInt16ArrayLengthAndData(tarr, &length, &isShared, &int16Data);
+ CHECK_EQUAL(length, nbytes / 2);
+
+ length = 0;
+ JS::AutoCheckCannotGC nogc(cx);
+ CHECK(int16Data =
+ JS::TypedArray<Scalar::Int16>::unwrap(tarr).getLengthAndData(
+ &length, &isShared, nogc));
+ CHECK_EQUAL(length, nbytes / 2);
+
+ length = 0;
+ CHECK(JS_GetObjectAsArrayBufferView(tarr, &length, &isShared, &data));
+ CHECK_EQUAL(length, nbytes);
+ }
+
+ // DataView
+ {
+ RootedObject dv(cx, JS_NewDataView(cx, buffer, 0, nbytes - 10));
+ CHECK(JS_IsArrayBufferViewObject(dv));
+ CHECK_EQUAL(JS_GetArrayBufferViewByteOffset(dv), 0u);
+ CHECK_EQUAL(JS_GetArrayBufferViewByteLength(dv), nbytes - 10);
+
+ length = 0;
+ js::GetArrayBufferViewLengthAndData(dv, &length, &isShared, &data);
+ CHECK_EQUAL(length, nbytes - 10);
+
+ length = 0;
+ CHECK(JS_GetObjectAsArrayBufferView(dv, &length, &isShared, &data));
+ CHECK_EQUAL(length, nbytes - 10);
+ }
+
+ // Int8Array with large byteOffset.
+ {
+ RootedObject tarr(cx,
+ JS_NewInt8ArrayWithBuffer(cx, buffer, nbytes - 200, 32));
+ CHECK(JS_IsTypedArrayObject(tarr));
+ CHECK_EQUAL(JS_GetArrayBufferViewByteOffset(tarr), nbytes - 200);
+ CHECK_EQUAL(JS_GetArrayBufferViewByteLength(tarr), 32u);
+ CHECK_EQUAL(JS_GetTypedArrayByteOffset(tarr), nbytes - 200);
+ CHECK_EQUAL(JS_GetTypedArrayByteLength(tarr), 32u);
+ CHECK_EQUAL(JS_GetTypedArrayLength(tarr), 32u);
+ }
+
+ // DataView with large byteOffset.
+ {
+ RootedObject dv(cx, JS_NewDataView(cx, buffer, nbytes - 200, 32));
+ CHECK(JS_IsArrayBufferViewObject(dv));
+ CHECK_EQUAL(JS_GetArrayBufferViewByteOffset(dv), nbytes - 200);
+ CHECK_EQUAL(JS_GetArrayBufferViewByteLength(dv), 32u);
+ }
+#endif
+
+ return true;
+}
+END_TEST(testLargeArrayBuffers)
+
+BEGIN_TEST(testLargeSharedArrayBuffers) {
+#ifdef JS_64BIT
+ constexpr size_t nbytes = size_t(5) * 1024 * 1024 * 1024; // 5 GB.
+
+ RootedObject buffer(cx, JS::NewSharedArrayBuffer(cx, nbytes));
+ CHECK(buffer);
+ CHECK(JS::IsSharedArrayBufferObject(buffer));
+ CHECK_EQUAL(GetSharedArrayBufferByteLength(buffer), nbytes);
+
+ size_t length;
+ bool isShared;
+ uint8_t* data;
+ JS::GetSharedArrayBufferLengthAndData(buffer, &length, &isShared, &data);
+ CHECK_EQUAL(length, nbytes);
+#endif
+
+ return true;
+}
+END_TEST(testLargeSharedArrayBuffers)
diff --git a/js/src/jsapi-tests/testLookup.cpp b/js/src/jsapi-tests/testLookup.cpp
new file mode 100644
index 0000000000..24a1afa6fd
--- /dev/null
+++ b/js/src/jsapi-tests/testLookup.cpp
@@ -0,0 +1,102 @@
+/* -*- 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 "js/PropertyAndElement.h" // JS_DefineProperty, JS_DefinePropertyById, JS_GetProperty
+#include "jsapi-tests/tests.h"
+#include "vm/JSFunction.h" // for js::IsInternalFunctionObject
+
+#include "vm/JSObject-inl.h"
+
+BEGIN_TEST(testLookup_bug522590) {
+ // Define a function that makes method-bearing objects.
+ JS::RootedValue x(cx);
+ EXEC("function mkobj() { return {f: function () {return 2;}} }");
+
+ // Calling mkobj() multiple times must create multiple functions in ES5.
+ EVAL("mkobj().f !== mkobj().f", &x);
+ CHECK(x.isTrue());
+
+ // Now make x.f a method.
+ EVAL("mkobj()", &x);
+ JS::RootedObject xobj(cx, x.toObjectOrNull());
+
+ // This lookup must not return an internal function object.
+ JS::RootedValue r(cx);
+ CHECK(JS_GetProperty(cx, xobj, "f", &r));
+ CHECK(r.isObject());
+ JSObject* funobj = &r.toObject();
+ CHECK(funobj->is<JSFunction>());
+ CHECK(!js::IsInternalFunctionObject(*funobj));
+
+ return true;
+}
+END_TEST(testLookup_bug522590)
+
+static const JSClass DocumentAllClass = {"DocumentAll",
+ JSCLASS_EMULATES_UNDEFINED};
+
+bool document_resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
+ bool* resolvedp) {
+ // If id is "all", resolve document.all=true.
+ JS::RootedValue v(cx);
+ if (!JS_IdToValue(cx, id, &v)) {
+ return false;
+ }
+
+ if (v.isString()) {
+ JSString* str = v.toString();
+ JSLinearString* linearStr = JS_EnsureLinearString(cx, str);
+ if (!linearStr) {
+ return false;
+ }
+ if (JS_LinearStringEqualsLiteral(linearStr, "all")) {
+ JS::Rooted<JSObject*> docAll(cx, JS_NewObject(cx, &DocumentAllClass));
+ if (!docAll) {
+ return false;
+ }
+
+ JS::Rooted<JS::Value> allValue(cx, JS::ObjectValue(*docAll));
+ if (!JS_DefinePropertyById(cx, obj, id, allValue, JSPROP_RESOLVING)) {
+ return false;
+ }
+
+ *resolvedp = true;
+ return true;
+ }
+ }
+
+ *resolvedp = false;
+ return true;
+}
+
+static const JSClassOps document_classOps = {
+ nullptr, // addProperty
+ nullptr, // delProperty
+ nullptr, // enumerate
+ nullptr, // newEnumerate
+ document_resolve, // resolve
+ nullptr, // mayResolve
+ nullptr, // finalize
+ nullptr, // call
+ nullptr, // construct
+ nullptr, // trace
+};
+
+static const JSClass document_class = {"document", 0, &document_classOps};
+
+BEGIN_TEST(testLookup_bug570195) {
+ JS::RootedObject obj(cx, JS_NewObject(cx, &document_class));
+ CHECK(obj);
+ CHECK(JS_DefineProperty(cx, global, "document", obj, 0));
+ JS::RootedValue v(cx);
+ EVAL("document.all ? true : false", &v);
+ CHECK(v.isFalse());
+ EVAL("document.hasOwnProperty('all')", &v);
+ CHECK(v.isTrue());
+ return true;
+}
+END_TEST(testLookup_bug570195)
diff --git a/js/src/jsapi-tests/testLooselyEqual.cpp b/js/src/jsapi-tests/testLooselyEqual.cpp
new file mode 100644
index 0000000000..8fa369f7e7
--- /dev/null
+++ b/js/src/jsapi-tests/testLooselyEqual.cpp
@@ -0,0 +1,186 @@
+/* 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 <limits>
+#include <math.h>
+
+#include "js/Equality.h" // JS::LooselyEqual
+#include "js/GlobalObject.h"
+#include "jsapi-tests/tests.h"
+
+using namespace std;
+
+struct LooseEqualityFixture : public JSAPIRuntimeTest {
+ virtual ~LooseEqualityFixture() {}
+
+ bool leq(JS::HandleValue x, JS::HandleValue y) {
+ bool equal;
+ CHECK(JS::LooselyEqual(cx, x, y, &equal) && equal);
+ CHECK(JS::LooselyEqual(cx, y, x, &equal) && equal);
+ return true;
+ }
+
+ bool nleq(JS::HandleValue x, JS::HandleValue y) {
+ bool equal;
+ CHECK(JS::LooselyEqual(cx, x, y, &equal) && !equal);
+ CHECK(JS::LooselyEqual(cx, y, x, &equal) && !equal);
+ return true;
+ }
+};
+
+struct LooseEqualityData {
+ JS::RootedValue qNaN;
+ JS::RootedValue sNaN;
+ JS::RootedValue d42;
+ JS::RootedValue i42;
+ JS::RootedValue undef;
+ JS::RootedValue null;
+ JS::RootedValue obj;
+ JS::RootedValue poszero;
+ JS::RootedValue negzero;
+
+ explicit LooseEqualityData(JSContext* cx)
+ : qNaN(cx),
+ sNaN(cx),
+ d42(cx),
+ i42(cx),
+ undef(cx),
+ null(cx),
+ obj(cx),
+ poszero(cx),
+ negzero(cx) {
+ qNaN = JS::CanonicalizedDoubleValue(numeric_limits<double>::quiet_NaN());
+ sNaN =
+ JS::CanonicalizedDoubleValue(numeric_limits<double>::signaling_NaN());
+ d42 = JS::DoubleValue(42.0);
+ i42 = JS::Int32Value(42);
+ undef = JS::UndefinedValue();
+ null = JS::NullValue();
+ obj = JS::ObjectOrNullValue(JS::CurrentGlobalOrNull(cx));
+ poszero = JS::DoubleValue(0.0);
+ negzero = JS::DoubleValue(-0.0);
+#ifdef XP_WIN
+# define copysign _copysign
+#endif
+ MOZ_RELEASE_ASSERT(copysign(1.0, poszero.toDouble()) == 1.0);
+ MOZ_RELEASE_ASSERT(copysign(1.0, negzero.toDouble()) == -1.0);
+#ifdef XP_WIN
+# undef copysign
+#endif
+ }
+};
+
+// 11.9.3 1a
+BEGIN_FIXTURE_TEST(LooseEqualityFixture, test_undef_leq_undef) {
+ LooseEqualityData d(cx);
+ CHECK(leq(d.undef, d.undef));
+ return true;
+}
+END_FIXTURE_TEST(LooseEqualityFixture, test_undef_leq_undef)
+
+// 11.9.3 1b
+BEGIN_FIXTURE_TEST(LooseEqualityFixture, test_null_leq_null) {
+ LooseEqualityData d(cx);
+ CHECK(leq(d.null, d.null));
+ return true;
+}
+END_FIXTURE_TEST(LooseEqualityFixture, test_null_leq_null)
+
+// 11.9.3 1ci
+BEGIN_FIXTURE_TEST(LooseEqualityFixture, test_nan_nleq_all) {
+ LooseEqualityData d(cx);
+
+ CHECK(nleq(d.qNaN, d.qNaN));
+ CHECK(nleq(d.qNaN, d.sNaN));
+
+ CHECK(nleq(d.sNaN, d.sNaN));
+ CHECK(nleq(d.sNaN, d.qNaN));
+
+ CHECK(nleq(d.qNaN, d.d42));
+ CHECK(nleq(d.qNaN, d.i42));
+ CHECK(nleq(d.qNaN, d.undef));
+ CHECK(nleq(d.qNaN, d.null));
+ CHECK(nleq(d.qNaN, d.obj));
+
+ CHECK(nleq(d.sNaN, d.d42));
+ CHECK(nleq(d.sNaN, d.i42));
+ CHECK(nleq(d.sNaN, d.undef));
+ CHECK(nleq(d.sNaN, d.null));
+ CHECK(nleq(d.sNaN, d.obj));
+ return true;
+}
+END_FIXTURE_TEST(LooseEqualityFixture, test_nan_nleq_all)
+
+// 11.9.3 1cii
+BEGIN_FIXTURE_TEST(LooseEqualityFixture, test_all_nleq_nan) {
+ LooseEqualityData d(cx);
+
+ CHECK(nleq(d.qNaN, d.qNaN));
+ CHECK(nleq(d.qNaN, d.sNaN));
+
+ CHECK(nleq(d.sNaN, d.sNaN));
+ CHECK(nleq(d.sNaN, d.qNaN));
+
+ CHECK(nleq(d.d42, d.qNaN));
+ CHECK(nleq(d.i42, d.qNaN));
+ CHECK(nleq(d.undef, d.qNaN));
+ CHECK(nleq(d.null, d.qNaN));
+ CHECK(nleq(d.obj, d.qNaN));
+
+ CHECK(nleq(d.d42, d.sNaN));
+ CHECK(nleq(d.i42, d.sNaN));
+ CHECK(nleq(d.undef, d.sNaN));
+ CHECK(nleq(d.null, d.sNaN));
+ CHECK(nleq(d.obj, d.sNaN));
+ return true;
+}
+END_FIXTURE_TEST(LooseEqualityFixture, test_all_nleq_nan)
+
+// 11.9.3 1ciii
+BEGIN_FIXTURE_TEST(LooseEqualityFixture, test_leq_same_nums) {
+ LooseEqualityData d(cx);
+
+ CHECK(leq(d.d42, d.d42));
+ CHECK(leq(d.i42, d.i42));
+ CHECK(leq(d.d42, d.i42));
+ CHECK(leq(d.i42, d.d42));
+ return true;
+}
+END_FIXTURE_TEST(LooseEqualityFixture, test_leq_same_nums)
+
+// 11.9.3 1civ
+BEGIN_FIXTURE_TEST(LooseEqualityFixture, test_pz_leq_nz) {
+ LooseEqualityData d(cx);
+ CHECK(leq(d.poszero, d.negzero));
+ return true;
+}
+END_FIXTURE_TEST(LooseEqualityFixture, test_pz_leq_nz)
+
+// 11.9.3 1cv
+BEGIN_FIXTURE_TEST(LooseEqualityFixture, test_nz_leq_pz) {
+ LooseEqualityData d(cx);
+ CHECK(leq(d.negzero, d.poszero));
+ return true;
+}
+END_FIXTURE_TEST(LooseEqualityFixture, test_nz_leq_pz)
+
+// 1cvi onwards NOT TESTED
+
+// 11.9.3 2
+BEGIN_FIXTURE_TEST(LooseEqualityFixture, test_null_leq_undef) {
+ LooseEqualityData d(cx);
+ CHECK(leq(d.null, d.undef));
+ return true;
+}
+END_FIXTURE_TEST(LooseEqualityFixture, test_null_leq_undef)
+
+// 11.9.3 3
+BEGIN_FIXTURE_TEST(LooseEqualityFixture, test_undef_leq_null) {
+ LooseEqualityData d(cx);
+ CHECK(leq(d.undef, d.null));
+ return true;
+}
+END_FIXTURE_TEST(LooseEqualityFixture, test_undef_leq_null)
+
+// 4 onwards NOT TESTED
diff --git a/js/src/jsapi-tests/testMappedArrayBuffer.cpp b/js/src/jsapi-tests/testMappedArrayBuffer.cpp
new file mode 100644
index 0000000000..b07303f1bd
--- /dev/null
+++ b/js/src/jsapi-tests/testMappedArrayBuffer.cpp
@@ -0,0 +1,195 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ */
+
+#include <fcntl.h>
+#include <stdio.h>
+
+#include "js/Array.h" // JS::NewArrayObject
+#include "js/ArrayBuffer.h" // JS::{{Create,Release}MappedArrayBufferContents,DetachArrayBuffer,GetArrayBuffer{ByteLength,Data},Is{,Detached,Mapped}ArrayBufferObject,NewMappedArrayBufferWithContents,StealArrayBufferContents}
+#include "js/StructuredClone.h"
+#include "jsapi-tests/tests.h"
+#include "vm/ArrayBufferObject.h"
+
+#ifdef XP_WIN
+# include <io.h>
+# define GET_OS_FD(a) int(_get_osfhandle(a))
+#else
+# include <unistd.h>
+# define GET_OS_FD(a) (a)
+#endif
+
+const char test_data[] =
+ "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+const char test_filename[] = "temp-bug945152_MappedArrayBuffer";
+
+BEGIN_TEST(testMappedArrayBuffer_bug945152) {
+ TempFile test_file;
+ FILE* test_stream = test_file.open(test_filename);
+ CHECK(fputs(test_data, test_stream) != EOF);
+ test_file.close();
+
+ // Offset 0.
+ CHECK(TestCreateObject(0, 12));
+
+ // Aligned offset.
+ CHECK(TestCreateObject(8, 12));
+
+ // Unaligned offset.
+ CHECK(CreateNewObject(11, 12) == nullptr);
+
+ // Offset + length greater than file size.
+ CHECK(CreateNewObject(8, sizeof(test_data) - 7) == nullptr);
+
+ // Release the mapped content.
+ CHECK(TestReleaseContents());
+
+ // Detach mapped array buffer.
+ CHECK(TestDetachObject());
+
+ // Clone mapped array buffer.
+ CHECK(TestCloneObject());
+
+ // Steal mapped array buffer contents.
+ CHECK(TestStealContents());
+
+ // Transfer mapped array buffer contents.
+ CHECK(TestTransferObject());
+
+ // GC so we can remove the file we created.
+ GC(cx);
+
+ test_file.remove();
+
+ return true;
+}
+
+JSObject* CreateNewObject(const int offset, const int length) {
+ int fd = open(test_filename, O_RDONLY);
+ void* ptr =
+ JS::CreateMappedArrayBufferContents(GET_OS_FD(fd), offset, length);
+ close(fd);
+ if (!ptr) {
+ return nullptr;
+ }
+ JSObject* obj = JS::NewMappedArrayBufferWithContents(cx, length, ptr);
+ if (!obj) {
+ JS::ReleaseMappedArrayBufferContents(ptr, length);
+ return nullptr;
+ }
+ return obj;
+}
+
+bool VerifyObject(JS::HandleObject obj, uint32_t offset, uint32_t length,
+ const bool mapped) {
+ JS::AutoCheckCannotGC nogc;
+
+ CHECK(obj);
+ CHECK(JS::IsArrayBufferObject(obj));
+ CHECK_EQUAL(JS::GetArrayBufferByteLength(obj), length);
+ if (mapped) {
+ CHECK(JS::IsMappedArrayBufferObject(obj));
+ } else {
+ CHECK(!JS::IsMappedArrayBufferObject(obj));
+ }
+ bool sharedDummy;
+ const char* data = reinterpret_cast<const char*>(
+ JS::GetArrayBufferData(obj, &sharedDummy, nogc));
+ CHECK(data);
+ CHECK(memcmp(data, test_data + offset, length) == 0);
+
+ return true;
+}
+
+bool TestCreateObject(uint32_t offset, uint32_t length) {
+ JS::RootedObject obj(cx, CreateNewObject(offset, length));
+ CHECK(VerifyObject(obj, offset, length, true));
+
+ return true;
+}
+
+bool TestReleaseContents() {
+ int fd = open(test_filename, O_RDONLY);
+ void* ptr = JS::CreateMappedArrayBufferContents(GET_OS_FD(fd), 0, 12);
+ close(fd);
+ if (!ptr) {
+ return false;
+ }
+ JS::ReleaseMappedArrayBufferContents(ptr, 12);
+
+ return true;
+}
+
+bool TestDetachObject() {
+ JS::RootedObject obj(cx, CreateNewObject(8, 12));
+ CHECK(obj);
+ JS::DetachArrayBuffer(cx, obj);
+ CHECK(JS::IsDetachedArrayBufferObject(obj));
+
+ return true;
+}
+
+bool TestCloneObject() {
+ JS::RootedObject obj1(cx, CreateNewObject(8, 12));
+ CHECK(obj1);
+ JSAutoStructuredCloneBuffer cloned_buffer(
+ JS::StructuredCloneScope::SameProcess, nullptr, nullptr);
+ JS::RootedValue v1(cx, JS::ObjectValue(*obj1));
+ CHECK(cloned_buffer.write(cx, v1, nullptr, nullptr));
+ JS::RootedValue v2(cx);
+ CHECK(cloned_buffer.read(cx, &v2, JS::CloneDataPolicy(), nullptr, nullptr));
+ JS::RootedObject obj2(cx, v2.toObjectOrNull());
+ CHECK(VerifyObject(obj2, 8, 12, false));
+
+ return true;
+}
+
+bool TestStealContents() {
+ JS::RootedObject obj(cx, CreateNewObject(8, 12));
+ CHECK(obj);
+ void* contents = JS::StealArrayBufferContents(cx, obj);
+ CHECK(contents);
+ CHECK(memcmp(contents, test_data + 8, 12) == 0);
+ CHECK(JS::IsDetachedArrayBufferObject(obj));
+
+ return true;
+}
+
+bool TestTransferObject() {
+ JS::RootedObject obj1(cx, CreateNewObject(8, 12));
+ CHECK(obj1);
+ JS::RootedValue v1(cx, JS::ObjectValue(*obj1));
+
+ // Create an Array of transferable values.
+ JS::RootedValueVector argv(cx);
+ if (!argv.append(v1)) {
+ return false;
+ }
+
+ JS::RootedObject obj(
+ cx, JS::NewArrayObject(cx, JS::HandleValueArray::subarray(argv, 0, 1)));
+ CHECK(obj);
+ JS::RootedValue transferable(cx, JS::ObjectValue(*obj));
+
+ JSAutoStructuredCloneBuffer cloned_buffer(
+ JS::StructuredCloneScope::SameProcess, nullptr, nullptr);
+ JS::CloneDataPolicy policy;
+ CHECK(cloned_buffer.write(cx, v1, transferable, policy, nullptr, nullptr));
+ JS::RootedValue v2(cx);
+ CHECK(cloned_buffer.read(cx, &v2, policy, nullptr, nullptr));
+ JS::RootedObject obj2(cx, v2.toObjectOrNull());
+ CHECK(VerifyObject(obj2, 8, 12, true));
+ CHECK(JS::IsDetachedArrayBufferObject(obj1));
+
+ return true;
+}
+
+static void GC(JSContext* cx) {
+ JS_GC(cx);
+ // Trigger another to wait for background finalization to end.
+ JS_GC(cx);
+}
+
+END_TEST(testMappedArrayBuffer_bug945152)
+
+#undef GET_OS_FD
diff --git a/js/src/jsapi-tests/testMemoryAssociation.cpp b/js/src/jsapi-tests/testMemoryAssociation.cpp
new file mode 100644
index 0000000000..49e910d63e
--- /dev/null
+++ b/js/src/jsapi-tests/testMemoryAssociation.cpp
@@ -0,0 +1,44 @@
+/* -*- 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 "jsapi.h"
+#include "jspubtd.h"
+
+#include "js/CompilationAndEvaluation.h"
+#include "jsapi-tests/tests.h"
+
+static const JS::MemoryUse TestUse1 = JS::MemoryUse::XPCWrappedNative;
+static const JS::MemoryUse TestUse2 = JS::MemoryUse::DOMBinding;
+
+BEGIN_TEST(testMemoryAssociation) {
+ JS::RootedObject obj(cx, JS_NewPlainObject(cx));
+ CHECK(obj);
+
+ // No association is allowed for nursery objects.
+ JS_GC(cx);
+
+ // Test object/memory association.
+ JS::AddAssociatedMemory(obj, 100, TestUse1);
+ JS::RemoveAssociatedMemory(obj, 100, TestUse1);
+
+ // Test association when object moves due to compacting GC.
+ void* initial = obj;
+ JS::AddAssociatedMemory(obj, 300, TestUse1);
+ JS::PrepareForFullGC(cx);
+ JS::NonIncrementalGC(cx, JS::GCOptions::Shrink, JS::GCReason::DEBUG_GC);
+ CHECK(obj != initial);
+ JS::RemoveAssociatedMemory(obj, 300, TestUse1);
+
+ // Test multiple associations.
+ JS::AddAssociatedMemory(obj, 400, TestUse1);
+ JS::AddAssociatedMemory(obj, 500, TestUse2);
+ JS::RemoveAssociatedMemory(obj, 400, TestUse1);
+ JS::RemoveAssociatedMemory(obj, 500, TestUse2);
+
+ return true;
+}
+END_TEST(testMemoryAssociation)
diff --git a/js/src/jsapi-tests/testMutedErrors.cpp b/js/src/jsapi-tests/testMutedErrors.cpp
new file mode 100644
index 0000000000..12410e0f99
--- /dev/null
+++ b/js/src/jsapi-tests/testMutedErrors.cpp
@@ -0,0 +1,99 @@
+/* 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 "jsfriendapi.h"
+#include "js/CompilationAndEvaluation.h"
+#include "js/Exception.h"
+#include "js/GlobalObject.h"
+#include "js/SourceText.h"
+#include "jsapi-tests/tests.h"
+
+BEGIN_TEST(testMutedErrors) {
+ CHECK(testOuter("function f() {return 1}; f;"));
+ CHECK(testOuter(
+ "function outer() { return (function () {return 2}); }; outer();"));
+ CHECK(testOuter("eval('(function() {return 3})');"));
+ CHECK(
+ testOuter("(function (){ return eval('(function() {return 4})'); })()"));
+ CHECK(
+ testOuter("(function (){ return eval('(function() { return "
+ "eval(\"(function(){return 5})\") })()'); })()"));
+ CHECK(testOuter("new Function('return 6')"));
+ CHECK(testOuter("function f() { return new Function('return 7') }; f();"));
+ CHECK(testOuter("eval('new Function(\"return 8\")')"));
+ CHECK(
+ testOuter("(new Function('return eval(\"(function(){return 9})\")'))()"));
+ CHECK(testOuter("(function(){return function(){return 10}}).bind()()"));
+ CHECK(testOuter(
+ "var e = eval; (function() { return e('(function(){return 11})') })()"));
+
+ CHECK(testError("eval(-)"));
+ CHECK(testError("-"));
+ CHECK(testError("new Function('x', '-')"));
+ CHECK(testError("eval('new Function(\"x\", \"-\")')"));
+
+ /*
+ * NB: uncaught exceptions, when reported, have nothing on the stack so
+ * both the filename and mutedErrors are empty. E.g., this would fail:
+ *
+ * CHECK(testError("throw 3"));
+ */
+ return true;
+}
+
+bool eval(const char* asciiChars, bool mutedErrors,
+ JS::MutableHandleValue rval) {
+ size_t len = strlen(asciiChars);
+ mozilla::UniquePtr<char16_t[]> chars(new char16_t[len + 1]);
+ for (size_t i = 0; i < len; ++i) {
+ chars[i] = asciiChars[i];
+ }
+ chars[len] = 0;
+
+ JS::RealmOptions globalOptions;
+ JS::RootedObject global(
+ cx, JS_NewGlobalObject(cx, getGlobalClass(), nullptr,
+ JS::FireOnNewGlobalHook, globalOptions));
+ CHECK(global);
+ JSAutoRealm ar(cx, global);
+
+ JS::CompileOptions options(cx);
+ options.setMutedErrors(mutedErrors).setFileAndLine("", 0);
+
+ JS::SourceText<char16_t> srcBuf;
+ CHECK(srcBuf.init(cx, chars.get(), len, JS::SourceOwnership::Borrowed));
+
+ return JS::Evaluate(cx, options, srcBuf, rval);
+}
+
+bool testOuter(const char* asciiChars) {
+ CHECK(testInner(asciiChars, false));
+ CHECK(testInner(asciiChars, true));
+ return true;
+}
+
+bool testInner(const char* asciiChars, bool mutedErrors) {
+ JS::RootedValue rval(cx);
+ CHECK(eval(asciiChars, mutedErrors, &rval));
+
+ JS::RootedFunction fun(cx, &rval.toObject().as<JSFunction>());
+ JSScript* script = JS_GetFunctionScript(cx, fun);
+ CHECK(JS_ScriptHasMutedErrors(script) == mutedErrors);
+
+ return true;
+}
+
+bool testError(const char* asciiChars) {
+ JS::RootedValue rval(cx);
+ CHECK(!eval(asciiChars, true, &rval));
+
+ JS::ExceptionStack exnStack(cx);
+ CHECK(JS::StealPendingExceptionStack(cx, &exnStack));
+
+ JS::ErrorReportBuilder report(cx);
+ CHECK(report.init(cx, exnStack, JS::ErrorReportBuilder::WithSideEffects));
+ CHECK(report.report()->isMuted == true);
+ return true;
+}
+END_TEST(testMutedErrors)
diff --git a/js/src/jsapi-tests/testNewObject.cpp b/js/src/jsapi-tests/testNewObject.cpp
new file mode 100644
index 0000000000..bdddede03b
--- /dev/null
+++ b/js/src/jsapi-tests/testNewObject.cpp
@@ -0,0 +1,255 @@
+/* -*- 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 "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject
+#include "js/CallAndConstruct.h" // JS::Construct
+#include "js/Object.h" // JS::GetClass
+#include "js/PropertyAndElement.h" // JS_GetElement, JS_SetElement
+#include "jsapi-tests/tests.h"
+#include "vm/PlainObject.h" // js::PlainObject::class_
+
+#include "vm/NativeObject-inl.h"
+
+using namespace js;
+
+static bool constructHook(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Check that arguments were passed properly from JS_New.
+
+ JS::RootedObject obj(cx, JS_NewPlainObject(cx));
+ if (!obj) {
+ JS_ReportErrorASCII(cx, "test failed, could not construct object");
+ return false;
+ }
+ if (strcmp(JS::GetClass(obj)->name, "Object") != 0) {
+ JS_ReportErrorASCII(cx, "test failed, wrong class for 'this'");
+ return false;
+ }
+ if (args.length() != 3) {
+ JS_ReportErrorASCII(cx, "test failed, argc == %d", args.length());
+ return false;
+ }
+ if (!args[0].isInt32() || args[2].toInt32() != 2) {
+ JS_ReportErrorASCII(cx, "test failed, wrong value in args[2]");
+ return false;
+ }
+ if (!args.isConstructing()) {
+ JS_ReportErrorASCII(cx, "test failed, not constructing");
+ return false;
+ }
+
+ // Perform a side-effect to indicate that this hook was actually called.
+ JS::RootedValue value(cx, args[0]);
+ JS::RootedObject callee(cx, &args.callee());
+ if (!JS_SetElement(cx, callee, 0, value)) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+
+ // trash the argv, perversely
+ args[0].setUndefined();
+ args[1].setUndefined();
+ args[2].setUndefined();
+
+ return true;
+}
+
+BEGIN_TEST(testNewObject_1) {
+ static const size_t N = 1000;
+ JS::RootedValueVector argv(cx);
+ CHECK(argv.resize(N));
+
+ JS::RootedValue Array(cx);
+ EVAL("Array", &Array);
+
+ bool isArray;
+
+ // With no arguments.
+ JS::RootedObject obj(cx);
+ CHECK(JS::Construct(cx, Array, JS::HandleValueArray::empty(), &obj));
+ CHECK(JS::IsArrayObject(cx, obj, &isArray));
+ CHECK(isArray);
+ uint32_t len;
+ CHECK(JS::GetArrayLength(cx, obj, &len));
+ CHECK_EQUAL(len, 0u);
+
+ // With one argument.
+ argv[0].setInt32(4);
+ CHECK(JS::Construct(cx, Array, JS::HandleValueArray::subarray(argv, 0, 1),
+ &obj));
+ CHECK(JS::IsArrayObject(cx, obj, &isArray));
+ CHECK(isArray);
+ CHECK(JS::GetArrayLength(cx, obj, &len));
+ CHECK_EQUAL(len, 4u);
+
+ // With N arguments.
+ for (size_t i = 0; i < N; i++) {
+ argv[i].setInt32(i);
+ }
+ CHECK(JS::Construct(cx, Array, JS::HandleValueArray::subarray(argv, 0, N),
+ &obj));
+ CHECK(JS::IsArrayObject(cx, obj, &isArray));
+ CHECK(isArray);
+ CHECK(JS::GetArrayLength(cx, obj, &len));
+ CHECK_EQUAL(len, N);
+ JS::RootedValue v(cx);
+ CHECK(JS_GetElement(cx, obj, N - 1, &v));
+ CHECK(v.isInt32(N - 1));
+
+ // With JSClass.construct.
+ static const JSClassOps clsOps = {
+ nullptr, // addProperty
+ nullptr, // delProperty
+ nullptr, // enumerate
+ nullptr, // newEnumerate
+ nullptr, // resolve
+ nullptr, // mayResolve
+ nullptr, // finalize
+ nullptr, // call
+ constructHook, // construct
+ nullptr, // trace
+ };
+ static const JSClass cls = {"testNewObject_1", 0, &clsOps};
+ JS::RootedObject ctor(cx, JS_NewObject(cx, &cls));
+ CHECK(ctor);
+ JS::RootedValue ctorVal(cx, JS::ObjectValue(*ctor));
+ CHECK(JS::Construct(cx, ctorVal, JS::HandleValueArray::subarray(argv, 0, 3),
+ &obj));
+ CHECK(JS_GetElement(cx, ctor, 0, &v));
+ CHECK(v.isInt32(0));
+
+ return true;
+}
+END_TEST(testNewObject_1)
+
+BEGIN_TEST(testNewObject_IsMapObject) {
+ // Test IsMapObject and IsSetObject
+
+ JS::RootedValue vMap(cx);
+ EVAL("Map", &vMap);
+
+ bool isMap = false;
+ bool isSet = false;
+ JS::RootedObject mapObj(cx);
+ CHECK(JS::Construct(cx, vMap, JS::HandleValueArray::empty(), &mapObj));
+ CHECK(JS::IsMapObject(cx, mapObj, &isMap));
+ CHECK(isMap);
+ CHECK(JS::IsSetObject(cx, mapObj, &isSet));
+ CHECK(!isSet);
+
+ JS::RootedValue vSet(cx);
+ EVAL("Set", &vSet);
+
+ JS::RootedObject setObj(cx);
+ CHECK(JS::Construct(cx, vSet, JS::HandleValueArray::empty(), &setObj));
+ CHECK(JS::IsMapObject(cx, setObj, &isMap));
+ CHECK(!isMap);
+ CHECK(JS::IsSetObject(cx, setObj, &isSet));
+ CHECK(isSet);
+
+ return true;
+}
+END_TEST(testNewObject_IsMapObject)
+
+static const JSClass Base_class = {
+ "Base",
+ JSCLASS_HAS_RESERVED_SLOTS(8), // flags
+};
+
+BEGIN_TEST(testNewObject_Subclassing) {
+ JSObject* proto =
+ JS_InitClass(cx, global, nullptr, nullptr, "Base", Base_constructor, 0,
+ nullptr, nullptr, nullptr, nullptr);
+ if (!proto) {
+ return false;
+ }
+
+ CHECK_EQUAL(JS::GetClass(proto), &PlainObject::class_);
+
+ // Calling Base without `new` should fail with a TypeError.
+ JS::RootedValue expectedError(cx);
+ EVAL("TypeError", &expectedError);
+ JS::RootedValue actualError(cx);
+ EVAL(
+ "try {\n"
+ " Base();\n"
+ "} catch (e) {\n"
+ " e.constructor;\n"
+ "}\n",
+ &actualError);
+ CHECK_SAME(actualError, expectedError);
+
+ // Check prototype chains when a JS class extends a base class that's
+ // implemented in C++ using JS_NewObjectForConstructor.
+ EXEC(
+ "class MyClass extends Base {\n"
+ " ok() { return true; }\n"
+ "}\n"
+ "let myObj = new MyClass();\n");
+
+ JS::RootedValue result(cx);
+ EVAL("myObj.ok()", &result);
+ CHECK_SAME(result, JS::TrueValue());
+
+ EVAL("myObj.__proto__ === MyClass.prototype", &result);
+ CHECK_SAME(result, JS::TrueValue());
+ EVAL("myObj.__proto__.__proto__ === Base.prototype", &result);
+ CHECK_SAME(result, JS::TrueValue());
+
+ EVAL("myObj", &result);
+ CHECK_EQUAL(JS::GetClass(&result.toObject()), &Base_class);
+
+ // All reserved slots are initialized to undefined.
+ for (uint32_t i = 0; i < JSCLASS_RESERVED_SLOTS(&Base_class); i++) {
+ CHECK_SAME(JS::GetReservedSlot(&result.toObject(), i),
+ JS::UndefinedValue());
+ }
+
+ return true;
+}
+
+static bool Base_constructor(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = CallArgsFromVp(argc, vp);
+ JS::RootedObject obj(cx, JS_NewObjectForConstructor(cx, &Base_class, args));
+ if (!obj) {
+ return false;
+ }
+ args.rval().setObject(*obj);
+ return true;
+}
+
+END_TEST(testNewObject_Subclassing)
+
+static const JSClass TestClass = {"TestObject", JSCLASS_HAS_RESERVED_SLOTS(0)};
+
+BEGIN_TEST(testNewObject_elements) {
+ Rooted<NativeObject*> obj(
+ cx, NewBuiltinClassInstance(cx, &TestClass, GenericObject));
+ CHECK(obj);
+ CHECK(!obj->isTenured());
+ CHECK(obj->hasEmptyElements());
+ CHECK(!obj->hasFixedElements());
+ CHECK(!obj->hasDynamicElements());
+
+ CHECK(obj->ensureElements(cx, 1));
+ CHECK(!obj->hasEmptyElements());
+ CHECK(!obj->hasFixedElements());
+ CHECK(obj->hasDynamicElements());
+
+ RootedObject array(cx, NewArrayObject(cx, 1));
+ CHECK(array);
+ obj = &array->as<NativeObject>();
+ CHECK(!obj->isTenured());
+ CHECK(!obj->hasEmptyElements());
+ CHECK(obj->hasFixedElements());
+ CHECK(!obj->hasDynamicElements());
+
+ return true;
+}
+END_TEST(testNewObject_elements)
diff --git a/js/src/jsapi-tests/testNewTargetInvokeConstructor.cpp b/js/src/jsapi-tests/testNewTargetInvokeConstructor.cpp
new file mode 100644
index 0000000000..6ecc1db721
--- /dev/null
+++ b/js/src/jsapi-tests/testNewTargetInvokeConstructor.cpp
@@ -0,0 +1,25 @@
+/* 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 "js/CallAndConstruct.h" // JS::Construct
+#include "jsapi-tests/tests.h"
+
+BEGIN_TEST(testNewTargetInvokeConstructor) {
+ JS::RootedValue func(cx);
+
+ EVAL(
+ "(function(expected) { if (expected !== new.target) throw new "
+ "Error('whoops'); })",
+ &func);
+
+ JS::RootedValueArray<1> args(cx);
+ args[0].set(func);
+
+ JS::RootedObject obj(cx);
+
+ CHECK(JS::Construct(cx, func, args, &obj));
+
+ return true;
+}
+END_TEST(testNewTargetInvokeConstructor)
diff --git a/js/src/jsapi-tests/testNullRoot.cpp b/js/src/jsapi-tests/testNullRoot.cpp
new file mode 100644
index 0000000000..4819116c2a
--- /dev/null
+++ b/js/src/jsapi-tests/testNullRoot.cpp
@@ -0,0 +1,24 @@
+/* -*- 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 "jsapi-tests/tests.h"
+
+BEGIN_TEST(testNullRoot) {
+ obj.init(cx, nullptr);
+ str.init(cx, nullptr);
+ script.init(cx, nullptr);
+
+ // This used to crash because obj was nullptr.
+ JS_GC(cx);
+
+ return true;
+}
+
+JS::PersistentRootedObject obj;
+JS::PersistentRootedString str;
+JS::PersistentRootedScript script;
+END_TEST(testNullRoot)
diff --git a/js/src/jsapi-tests/testNumberToString.cpp b/js/src/jsapi-tests/testNumberToString.cpp
new file mode 100644
index 0000000000..d5107327c6
--- /dev/null
+++ b/js/src/jsapi-tests/testNumberToString.cpp
@@ -0,0 +1,133 @@
+/* -*- 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/FloatingPoint.h" // mozilla::{PositiveInfinity,UnspecifiedNaN}
+
+#include <stddef.h> // size_t
+#include <string.h> // memcmp, memset
+
+#include "js/Conversions.h" // JS::NumberToString, JS::MaximumNumberToStringLength
+#include "jsapi-tests/tests.h" // BEGIN_TEST, CHECK_EQUAL, END_TEST
+#include "util/Text.h" // js_strlen
+
+#define REST(x) x, (js_strlen(x)), __LINE__
+
+static const struct NumberToStringTest {
+ double number;
+ const char* expected;
+ size_t expectedLength;
+ size_t lineno;
+} numberToStringTests[] = {
+ {5e-324, REST("5e-324")}, // 2**-1074
+ {9.5367431640625e-7, REST("9.5367431640625e-7")}, // 2**-20
+ {0.0000010984284297360395, REST("0.0000010984284297360395")},
+ {0.0000019073486328125, REST("0.0000019073486328125")}, // 2**-19
+ {0.000003814697265625, REST("0.000003814697265625")}, // 2**-18
+ {0.0000057220458984375, REST("0.0000057220458984375")}, // 2**-18 + 2**-19
+ {0.000244140625, REST("0.000244140625")}, // 2**-12
+ {0.125, REST("0.125")},
+ {0.25, REST("0.25")},
+ {0.5, REST("0.5")},
+ {1, REST("1")},
+ {1.5, REST("1.5")},
+ {2, REST("2")},
+ {9, REST("9")},
+ {10, REST("10")},
+ {15, REST("15")},
+ {16, REST("16")},
+ {389427, REST("389427")},
+ {1073741823, REST("1073741823")},
+ {1073741824, REST("1073741824")},
+ {1073741825, REST("1073741825")},
+ {2147483647, REST("2147483647")},
+ {2147483648, REST("2147483648")},
+ {2147483649, REST("2147483649")},
+ {4294967294, REST("4294967294")},
+ {4294967295, REST("4294967295")},
+ {4294967296, REST("4294967296")},
+ {999999999999999900000.0, REST("999999999999999900000")},
+ {999999999999999900000.0 + 65535, REST("999999999999999900000")},
+ {999999999999999900000.0 + 65536, REST("1e+21")},
+ {1.7976931348623157e+308, REST("1.7976931348623157e+308")}, // MAX_VALUE
+};
+
+static constexpr char PoisonChar = 0x37;
+
+struct StorageForNumberToString {
+ char out[JS::MaximumNumberToStringLength];
+ char overflow;
+} storage;
+
+BEGIN_TEST(testNumberToString) {
+ StorageForNumberToString storage;
+
+ if (!testNormalValues(false, storage)) {
+ return false;
+ }
+
+ if (!testNormalValues(true, storage)) {
+ return false;
+ }
+
+ NumberToStringTest zeroTest = {0.0, REST("0")};
+ if (!testOne(zeroTest, false, storage)) {
+ return false;
+ }
+ NumberToStringTest negativeZeroTest = {-0.0, REST("0")};
+ if (!testOne(negativeZeroTest, false, storage)) {
+ return false;
+ }
+
+ NumberToStringTest infTest = {mozilla::PositiveInfinity<double>(),
+ REST("Infinity")};
+ if (!testOne(infTest, false, storage)) {
+ return false;
+ }
+ if (!testOne(infTest, true, storage)) {
+ return false;
+ }
+
+ NumberToStringTest nanTest = {mozilla::UnspecifiedNaN<double>(), REST("NaN")};
+ return testOne(nanTest, false, storage);
+}
+
+bool testNormalValues(bool hasMinusSign, StorageForNumberToString& storage) {
+ for (const auto& test : numberToStringTests) {
+ if (!testOne(test, hasMinusSign, storage)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool testOne(const NumberToStringTest& test, bool hasMinusSign,
+ StorageForNumberToString& storage) {
+ memset(&storage, PoisonChar, sizeof(storage));
+
+ JS::NumberToString(hasMinusSign ? -test.number : test.number, storage.out);
+
+ CHECK_EQUAL(storage.overflow, PoisonChar);
+
+ const char* start = storage.out;
+ if (hasMinusSign) {
+ CHECK_EQUAL(start[0], '-');
+ start++;
+ }
+
+ if (!checkEqual(memcmp(start, test.expected, test.expectedLength), 0, start,
+ test.expected, __FILE__, test.lineno)) {
+ return false;
+ }
+
+ char actualTerminator[] = {start[test.expectedLength], '\0'};
+ return checkEqual(actualTerminator[0], '\0', actualTerminator, "'\\0'",
+ __FILE__, test.lineno);
+}
+END_TEST(testNumberToString)
+
+#undef REST
diff --git a/js/src/jsapi-tests/testOOM.cpp b/js/src/jsapi-tests/testOOM.cpp
new file mode 100644
index 0000000000..a95210786b
--- /dev/null
+++ b/js/src/jsapi-tests/testOOM.cpp
@@ -0,0 +1,114 @@
+/* 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/DebugOnly.h"
+
+#include "jsapi-tests/tests.h"
+
+#include "vm/HelperThreads.h"
+
+BEGIN_TEST(testOOM) {
+ JS::RootedValue v(cx, JS::Int32Value(9));
+ JS::RootedString jsstr(cx, JS::ToString(cx, v));
+ char16_t ch;
+ if (!JS_GetStringCharAt(cx, jsstr, 0, &ch)) {
+ return false;
+ }
+ MOZ_RELEASE_ASSERT(ch == '9');
+ return true;
+}
+
+virtual JSContext* createContext() override {
+ JSContext* cx = JS_NewContext(0);
+ if (!cx) {
+ return nullptr;
+ }
+ JS_SetGCParameter(cx, JSGC_MAX_BYTES, (uint32_t)-1);
+ return cx;
+}
+END_TEST(testOOM)
+
+#ifdef DEBUG // js::oom functions are only available in debug builds.
+
+const uint32_t maxAllocsPerTest = 100;
+
+# define START_OOM_TEST(name) \
+ testName = name; \
+ printf("Test %s: started: ", testName); \
+ for (oomAfter = 1; oomAfter < maxAllocsPerTest; ++oomAfter) { \
+ js::oom::simulator.simulateFailureAfter( \
+ js::oom::FailureSimulator::Kind::OOM, oomAfter, \
+ js::THREAD_TYPE_MAIN, true)
+
+# define END_OOM_TEST \
+ if (!js::oom::HadSimulatedOOM()) { \
+ printf("\nTest %s: finished with %" PRIu64 " allocations\n", testName, \
+ oomAfter - 1); \
+ break; \
+ } \
+ } \
+ js::oom::simulator.reset(); \
+ CHECK(oomAfter != maxAllocsPerTest)
+
+# define MARK_STAR printf("*");
+# define MARK_PLUS printf("+");
+# define MARK_DOT printf(".");
+
+BEGIN_TEST(testNewContextOOM) {
+ uninit(); // Get rid of test harness' original JSContext.
+
+ const char* testName;
+ uint64_t oomAfter;
+ JSContext* cx;
+ START_OOM_TEST("new context");
+ cx = JS_NewContext(8L * 1024 * 1024);
+ if (cx) {
+ MARK_PLUS;
+ JS_DestroyContext(cx);
+ } else {
+ MARK_DOT;
+ CHECK(!JSRuntime::hasLiveRuntimes());
+ }
+ END_OOM_TEST;
+ return true;
+}
+END_TEST(testNewContextOOM)
+
+BEGIN_TEST(testHelperThreadOOM) {
+ const char* testName;
+ uint64_t oomAfter;
+ START_OOM_TEST("helper thread state");
+
+ if (js::CreateHelperThreadsState()) {
+ if (js::EnsureHelperThreadsInitialized()) {
+ MARK_STAR;
+ } else {
+ MARK_PLUS;
+ }
+ } else {
+ MARK_DOT;
+ }
+
+ // Reset the helper threads to ensure they get re-initalised in following
+ // iterations
+ js::DestroyHelperThreadsState();
+
+ END_OOM_TEST;
+
+ return true;
+}
+
+bool init() override {
+ JSAPIRuntimeTest::uninit(); // Discard the just-created JSContext.
+ js::DestroyHelperThreadsState(); // The test creates this state.
+ return true;
+}
+void uninit() override {
+ // Leave things initialized after this test finishes.
+ js::CreateHelperThreadsState();
+}
+
+END_TEST(testHelperThreadOOM)
+
+#endif
diff --git a/js/src/jsapi-tests/testObjectEmulatingUndefined.cpp b/js/src/jsapi-tests/testObjectEmulatingUndefined.cpp
new file mode 100644
index 0000000000..732bde7147
--- /dev/null
+++ b/js/src/jsapi-tests/testObjectEmulatingUndefined.cpp
@@ -0,0 +1,109 @@
+/* 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 "jsapi-tests/tests.h"
+
+static const JSClass ObjectEmulatingUndefinedClass = {
+ "ObjectEmulatingUndefined", JSCLASS_EMULATES_UNDEFINED};
+
+static bool ObjectEmulatingUndefinedConstructor(JSContext* cx, unsigned argc,
+ JS::Value* vp) {
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ JSObject* obj =
+ JS_NewObjectForConstructor(cx, &ObjectEmulatingUndefinedClass, args);
+ if (!obj) {
+ return false;
+ }
+ args.rval().setObject(*obj);
+ return true;
+}
+
+BEGIN_TEST(testObjectEmulatingUndefined_truthy) {
+ CHECK(JS_InitClass(cx, global, nullptr, nullptr, "ObjectEmulatingUndefined",
+ ObjectEmulatingUndefinedConstructor, 0, nullptr, nullptr,
+ nullptr, nullptr));
+
+ JS::RootedValue result(cx);
+
+ EVAL("if (new ObjectEmulatingUndefined()) true; else false;", &result);
+ CHECK(result.isFalse());
+
+ EVAL("if (!new ObjectEmulatingUndefined()) true; else false;", &result);
+ CHECK(result.isTrue());
+
+ EVAL(
+ "var obj = new ObjectEmulatingUndefined(); \n"
+ "var res = []; \n"
+ "for (var i = 0; i < 50; i++) \n"
+ " res.push(Boolean(obj)); \n"
+ "res.every(function(v) { return v === false; });",
+ &result);
+ CHECK(result.isTrue());
+
+ return true;
+}
+END_TEST(testObjectEmulatingUndefined_truthy)
+
+BEGIN_TEST(testObjectEmulatingUndefined_equal) {
+ CHECK(JS_InitClass(cx, global, nullptr, nullptr, "ObjectEmulatingUndefined",
+ ObjectEmulatingUndefinedConstructor, 0, nullptr, nullptr,
+ nullptr, nullptr));
+
+ JS::RootedValue result(cx);
+
+ EVAL("if (new ObjectEmulatingUndefined() == undefined) true; else false;",
+ &result);
+ CHECK(result.isTrue());
+
+ EVAL("if (new ObjectEmulatingUndefined() == null) true; else false;",
+ &result);
+ CHECK(result.isTrue());
+
+ EVAL("if (new ObjectEmulatingUndefined() != undefined) true; else false;",
+ &result);
+ CHECK(result.isFalse());
+
+ EVAL("if (new ObjectEmulatingUndefined() != null) true; else false;",
+ &result);
+ CHECK(result.isFalse());
+
+ EVAL(
+ "var obj = new ObjectEmulatingUndefined(); \n"
+ "var res = []; \n"
+ "for (var i = 0; i < 50; i++) \n"
+ " res.push(obj == undefined); \n"
+ "res.every(function(v) { return v === true; });",
+ &result);
+ CHECK(result.isTrue());
+
+ EVAL(
+ "var obj = new ObjectEmulatingUndefined(); \n"
+ "var res = []; \n"
+ "for (var i = 0; i < 50; i++) \n"
+ " res.push(obj == null); \n"
+ "res.every(function(v) { return v === true; });",
+ &result);
+ CHECK(result.isTrue());
+
+ EVAL(
+ "var obj = new ObjectEmulatingUndefined(); \n"
+ "var res = []; \n"
+ "for (var i = 0; i < 50; i++) \n"
+ " res.push(obj != undefined); \n"
+ "res.every(function(v) { return v === false; });",
+ &result);
+ CHECK(result.isTrue());
+
+ EVAL(
+ "var obj = new ObjectEmulatingUndefined(); \n"
+ "var res = []; \n"
+ "for (var i = 0; i < 50; i++) \n"
+ " res.push(obj != null); \n"
+ "res.every(function(v) { return v === false; });",
+ &result);
+ CHECK(result.isTrue());
+
+ return true;
+}
+END_TEST(testObjectEmulatingUndefined_equal)
diff --git a/js/src/jsapi-tests/testObjectSwap.cpp b/js/src/jsapi-tests/testObjectSwap.cpp
new file mode 100644
index 0000000000..d326df6f37
--- /dev/null
+++ b/js/src/jsapi-tests/testObjectSwap.cpp
@@ -0,0 +1,445 @@
+/* -*- 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/. */
+
+/*
+ * Test JSObject::swap.
+ *
+ * This test creates objects from a description of their configuration. Objects
+ * are given unique property names and values. A list of configurations is
+ * created and the result of swapping every combination checked.
+ */
+
+#include "mozilla/Sprintf.h"
+
+#include "js/AllocPolicy.h"
+#include "js/Vector.h"
+#include "jsapi-tests/tests.h"
+#include "vm/PlainObject.h"
+
+#include "gc/StableCellHasher-inl.h"
+#include "vm/JSObject-inl.h"
+
+using namespace js;
+
+struct NativeConfig {
+ uint32_t propCount;
+ uint32_t elementCount;
+ bool inDictionaryMode;
+};
+
+struct ProxyConfig {
+ bool inlineValues;
+};
+
+struct ObjectConfig {
+ const JSClass* clasp;
+ bool isNative;
+ bool nurseryAllocated;
+ bool hasUniqueId;
+ union {
+ NativeConfig native;
+ ProxyConfig proxy;
+ };
+};
+
+using ObjectConfigVector = Vector<ObjectConfig, 0, SystemAllocPolicy>;
+
+static const JSClass TestProxyClasses[] = {
+ PROXY_CLASS_DEF("TestProxy", JSCLASS_HAS_RESERVED_SLOTS(1 /* Min */)),
+ PROXY_CLASS_DEF("TestProxy", JSCLASS_HAS_RESERVED_SLOTS(2)),
+ PROXY_CLASS_DEF("TestProxy", JSCLASS_HAS_RESERVED_SLOTS(7)),
+ PROXY_CLASS_DEF("TestProxy", JSCLASS_HAS_RESERVED_SLOTS(8)),
+ PROXY_CLASS_DEF("TestProxy", JSCLASS_HAS_RESERVED_SLOTS(14 /* Max */))};
+
+static const JSClass TestDOMClasses[] = {
+ {"TestDOMObject", JSCLASS_IS_DOMJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(0)},
+ {"TestDOMObject", JSCLASS_IS_DOMJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(1)},
+ {"TestDOMObject", JSCLASS_IS_DOMJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(2)},
+ {"TestDOMObject", JSCLASS_IS_DOMJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(7)},
+ {"TestDOMObject", JSCLASS_IS_DOMJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(8)},
+ {"TestDOMObject", JSCLASS_IS_DOMJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(20)}};
+
+static const uint32_t TestPropertyCounts[] = {0, 1, 2, 7, 8, 20};
+
+static const uint32_t TestElementCounts[] = {0, 20};
+
+static bool Verbose = false;
+
+class TenuredProxyHandler final : public Wrapper {
+ public:
+ static const TenuredProxyHandler singleton;
+ constexpr TenuredProxyHandler() : Wrapper(0) {}
+ bool canNurseryAllocate() const override { return false; }
+};
+
+const TenuredProxyHandler TenuredProxyHandler::singleton;
+
+class NurseryProxyHandler final : public Wrapper {
+ public:
+ static const NurseryProxyHandler singleton;
+ constexpr NurseryProxyHandler() : Wrapper(0) {}
+ bool canNurseryAllocate() const override { return true; }
+};
+
+const NurseryProxyHandler NurseryProxyHandler::singleton;
+
+BEGIN_TEST(testObjectSwap) {
+ AutoLeaveZeal noZeal(cx);
+
+ ObjectConfigVector objectConfigs = CreateObjectConfigs();
+
+ for (const ObjectConfig& config1 : objectConfigs) {
+ for (const ObjectConfig& config2 : objectConfigs) {
+ {
+ uint32_t id1;
+ RootedObject obj1(cx, CreateObject(config1, &id1));
+ CHECK(obj1);
+
+ uint32_t id2;
+ RootedObject obj2(cx, CreateObject(config2, &id2));
+ CHECK(obj2);
+
+ if (Verbose) {
+ fprintf(stderr, "Swap %p (%s) and %p (%s)\n", obj1.get(),
+ GetLocation(obj1), obj2.get(), GetLocation(obj2));
+ }
+
+ uint64_t uid1 = 0;
+ if (config1.hasUniqueId) {
+ uid1 = gc::GetUniqueIdInfallible(obj1);
+ }
+ uint64_t uid2 = 0;
+ if (config2.hasUniqueId) {
+ uid2 = gc::GetUniqueIdInfallible(obj2);
+ }
+
+ {
+ AutoEnterOOMUnsafeRegion oomUnsafe;
+ JSObject::swap(cx, obj1, obj2, oomUnsafe);
+ }
+
+ CHECK(CheckObject(obj1, config2, id2));
+ CHECK(CheckObject(obj2, config1, id1));
+
+ CHECK(CheckUniqueIds(obj1, config1.hasUniqueId, uid1, obj2,
+ config2.hasUniqueId, uid2));
+ }
+
+ if (Verbose) {
+ fprintf(stderr, "\n");
+ }
+ }
+
+ // JSObject::swap can suppress GC so ensure we clean up occasionally.
+ JS_GC(cx);
+ }
+
+ return true;
+}
+
+ObjectConfigVector CreateObjectConfigs() {
+ ObjectConfigVector configs;
+
+ ObjectConfig config;
+
+ for (bool nurseryAllocated : {false, true}) {
+ config.nurseryAllocated = nurseryAllocated;
+
+ for (bool hasUniqueId : {false, true}) {
+ config.hasUniqueId = hasUniqueId;
+
+ config.isNative = true;
+ config.native = NativeConfig{0, false};
+
+ for (const JSClass& jsClass : TestDOMClasses) {
+ config.clasp = &jsClass;
+
+ for (uint32_t propCount : TestPropertyCounts) {
+ config.native.propCount = propCount;
+
+ for (uint32_t elementCount : TestElementCounts) {
+ config.native.elementCount = elementCount;
+
+ for (bool inDictionaryMode : {false, true}) {
+ if (inDictionaryMode && propCount == 0) {
+ continue;
+ }
+
+ config.native.inDictionaryMode = inDictionaryMode;
+ MOZ_RELEASE_ASSERT(configs.append(config));
+ }
+ }
+ }
+ }
+
+ config.isNative = false;
+ config.proxy = ProxyConfig{false};
+
+ for (const JSClass& jsClass : TestProxyClasses) {
+ config.clasp = &jsClass;
+
+ for (bool inlineValues : {true, false}) {
+ config.proxy.inlineValues = inlineValues;
+ MOZ_RELEASE_ASSERT(configs.append(config));
+ }
+ }
+ }
+ }
+
+ return configs;
+}
+
+const char* GetLocation(JSObject* obj) {
+ return obj->isTenured() ? "tenured heap" : "nursery";
+}
+
+// Counter used to give slots and property names unique values.
+uint32_t nextId = 0;
+
+JSObject* CreateObject(const ObjectConfig& config, uint32_t* idOut) {
+ *idOut = nextId;
+ JSObject* obj =
+ config.isNative ? CreateNativeObject(config) : CreateProxy(config);
+
+ if (config.hasUniqueId) {
+ uint64_t unused;
+ if (!gc::GetOrCreateUniqueId(obj, &unused)) {
+ return nullptr;
+ }
+ }
+
+ return obj;
+}
+
+JSObject* CreateNativeObject(const ObjectConfig& config) {
+ MOZ_ASSERT(config.isNative);
+
+ NewObjectKind kind = config.nurseryAllocated ? GenericObject : TenuredObject;
+ Rooted<NativeObject*> obj(cx,
+ NewBuiltinClassInstance(cx, config.clasp, kind));
+ if (!obj) {
+ return nullptr;
+ }
+
+ MOZ_RELEASE_ASSERT(IsInsideNursery(obj) == config.nurseryAllocated);
+
+ for (uint32_t i = 0; i < JSCLASS_RESERVED_SLOTS(config.clasp); i++) {
+ JS::SetReservedSlot(obj, i, Int32Value(nextId++));
+ }
+
+ if (config.native.inDictionaryMode) {
+ // Put object in dictionary mode by defining a non-last property and
+ // deleting it later.
+ MOZ_RELEASE_ASSERT(config.native.propCount != 0);
+ if (!JS_DefineProperty(cx, obj, "dummy", 0, JSPROP_ENUMERATE)) {
+ return nullptr;
+ }
+ }
+
+ for (uint32_t i = 0; i < config.native.propCount; i++) {
+ RootedId name(cx, CreatePropName(nextId++));
+ if (name.isVoid()) {
+ return nullptr;
+ }
+
+ if (!JS_DefinePropertyById(cx, obj, name, nextId++, JSPROP_ENUMERATE)) {
+ return nullptr;
+ }
+ }
+
+ if (config.native.elementCount) {
+ if (!obj->ensureElements(cx, config.native.elementCount)) {
+ return nullptr;
+ }
+ for (uint32_t i = 0; i < config.native.elementCount; i++) {
+ obj->setDenseInitializedLength(i + 1);
+ obj->initDenseElement(i, Int32Value(nextId++));
+ }
+ MOZ_ASSERT(obj->hasDynamicElements());
+ }
+
+ if (config.native.inDictionaryMode) {
+ JS::ObjectOpResult result;
+ JS_DeleteProperty(cx, obj, "dummy", result);
+ MOZ_RELEASE_ASSERT(result.ok());
+ }
+
+ MOZ_RELEASE_ASSERT(obj->inDictionaryMode() == config.native.inDictionaryMode);
+
+ return obj;
+}
+
+JSObject* CreateProxy(const ObjectConfig& config) {
+ RootedValue priv(cx, Int32Value(nextId++));
+
+ RootedObject expando(cx, NewPlainObject(cx));
+ RootedValue expandoId(cx, Int32Value(nextId++));
+ if (!expando || !JS_SetProperty(cx, expando, "id", expandoId)) {
+ return nullptr;
+ }
+
+ ProxyOptions options;
+ options.setClass(config.clasp);
+ options.setLazyProto(true);
+
+ const Wrapper* handler;
+ if (config.nurseryAllocated) {
+ handler = &NurseryProxyHandler::singleton;
+ } else {
+ handler = &TenuredProxyHandler::singleton;
+ }
+
+ RootedObject obj(cx, NewProxyObject(cx, handler, priv, nullptr, options));
+ if (!obj) {
+ return nullptr;
+ }
+
+ Rooted<ProxyObject*> proxy(cx, &obj->as<ProxyObject>());
+ proxy->setExpando(expando);
+
+ for (uint32_t i = 0; i < JSCLASS_RESERVED_SLOTS(config.clasp); i++) {
+ JS::SetReservedSlot(proxy, i, Int32Value(nextId++));
+ }
+
+ if (!config.proxy.inlineValues) {
+ // To create a proxy with non-inline values we must swap the proxy with an
+ // object with a different size.
+ NewObjectKind kind =
+ config.nurseryAllocated ? GenericObject : TenuredObject;
+ RootedObject dummy(cx,
+ NewBuiltinClassInstance(cx, &TestDOMClasses[0], kind));
+ if (!dummy) {
+ return nullptr;
+ }
+
+ AutoEnterOOMUnsafeRegion oomUnsafe;
+ JSObject::swap(cx, obj, dummy, oomUnsafe);
+ proxy = &dummy->as<ProxyObject>();
+ }
+
+ MOZ_RELEASE_ASSERT(IsInsideNursery(proxy) == config.nurseryAllocated);
+ MOZ_RELEASE_ASSERT(proxy->usingInlineValueArray() ==
+ config.proxy.inlineValues);
+
+ return proxy;
+}
+
+bool CheckObject(HandleObject obj, const ObjectConfig& config, uint32_t id) {
+ CHECK(obj->is<NativeObject>() == config.isNative);
+ CHECK(obj->getClass() == config.clasp);
+
+ uint32_t reservedSlots = JSCLASS_RESERVED_SLOTS(config.clasp);
+
+ if (Verbose) {
+ fprintf(stderr, "Check %p is a %s object with %u reserved slots", obj.get(),
+ config.isNative ? "native" : "proxy", reservedSlots);
+ if (config.isNative) {
+ fprintf(stderr,
+ ", %u properties, %u elements and %s in dictionary mode\n",
+ config.native.propCount, config.native.elementCount,
+ config.native.inDictionaryMode ? "is" : "is not");
+ } else {
+ fprintf(stderr, " with %s values\n",
+ config.proxy.inlineValues ? "inline" : "out-of-line");
+ }
+ }
+
+ if (!config.isNative) {
+ CHECK(GetProxyPrivate(obj) == Int32Value(id++));
+
+ Value expandoValue = GetProxyExpando(obj);
+ CHECK(expandoValue.isObject());
+
+ RootedObject expando(cx, &expandoValue.toObject());
+ RootedValue expandoId(cx);
+ JS_GetProperty(cx, expando, "id", &expandoId);
+ CHECK(expandoId == Int32Value(id++));
+ }
+
+ for (uint32_t i = 0; i < reservedSlots; i++) {
+ CHECK(JS::GetReservedSlot(obj, i) == Int32Value(id++));
+ }
+
+ if (config.isNative) {
+ Rooted<NativeObject*> nobj(cx, &obj->as<NativeObject>());
+ uint32_t propCount = GetPropertyCount(nobj);
+
+ CHECK(propCount == config.native.propCount);
+
+ for (uint32_t i = 0; i < config.native.propCount; i++) {
+ RootedId name(cx, CreatePropName(id++));
+ CHECK(!name.isVoid());
+
+ RootedValue value(cx);
+ CHECK(JS_GetPropertyById(cx, obj, name, &value));
+ CHECK(value == Int32Value(id++));
+ }
+
+ CHECK(nobj->getDenseInitializedLength() == config.native.elementCount);
+ for (uint32_t i = 0; i < config.native.elementCount; i++) {
+ Value value = nobj->getDenseElement(i);
+ CHECK(value == Int32Value(id++));
+ }
+ }
+
+ return true;
+}
+
+bool CheckUniqueIds(HandleObject obj1, bool hasUniqueId1, uint64_t uid1,
+ HandleObject obj2, bool hasUniqueId2, uint64_t uid2) {
+ if (uid1 && uid2) {
+ MOZ_RELEASE_ASSERT(uid1 != uid2);
+ }
+
+ // Check unique IDs are NOT swapped.
+ CHECK(CheckUniqueId(obj1, hasUniqueId1, uid1));
+ CHECK(CheckUniqueId(obj2, hasUniqueId2, uid2));
+
+ // Check unique IDs are different if present.
+ if (gc::HasUniqueId(obj1) && gc::HasUniqueId(obj2)) {
+ CHECK(gc::GetUniqueIdInfallible(obj1) != gc::GetUniqueIdInfallible(obj2));
+ }
+
+ return true;
+}
+
+bool CheckUniqueId(HandleObject obj, bool hasUniqueId, uint64_t uid) {
+ if (hasUniqueId) {
+ CHECK(gc::HasUniqueId(obj));
+ CHECK(gc::GetUniqueIdInfallible(obj) == uid);
+ } else {
+ // Swap may add a unique ID to an object.
+ }
+
+ if (obj->is<NativeObject>()) {
+ CHECK(!obj->zone()->uniqueIds().has(obj));
+ }
+ return true;
+}
+
+uint32_t GetPropertyCount(NativeObject* obj) {
+ uint32_t count = 0;
+ for (ShapePropertyIter<NoGC> iter(obj->shape()); !iter.done(); iter++) {
+ count++;
+ }
+
+ return count;
+}
+
+jsid CreatePropName(uint32_t id) {
+ char buffer[32];
+ SprintfLiteral(buffer, "prop%u", id);
+
+ RootedString atom(cx, JS_AtomizeString(cx, buffer));
+ if (!atom) {
+ return jsid::Void();
+ }
+
+ return jsid::NonIntAtom(atom);
+}
+
+END_TEST(testObjectSwap)
diff --git a/js/src/jsapi-tests/testParseJSON.cpp b/js/src/jsapi-tests/testParseJSON.cpp
new file mode 100644
index 0000000000..b4dd74d707
--- /dev/null
+++ b/js/src/jsapi-tests/testParseJSON.cpp
@@ -0,0 +1,357 @@
+/* -*- 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 <limits>
+#include <string.h>
+
+#include "js/Array.h" // JS::IsArrayObject
+#include "js/Exception.h"
+#include "js/friend/ErrorMessages.h" // JSMSG_*
+#include "js/JSON.h"
+#include "js/MemoryFunctions.h"
+#include "js/Printf.h"
+#include "js/PropertyAndElement.h" // JS_GetProperty
+#include "jsapi-tests/tests.h"
+
+using namespace js;
+
+class AutoInflatedString {
+ JSContext* const cx;
+ char16_t* chars_;
+ size_t length_;
+
+ public:
+ explicit AutoInflatedString(JSContext* cx)
+ : cx(cx), chars_(nullptr), length_(0) {}
+ ~AutoInflatedString() { JS_free(cx, chars_); }
+
+ template <size_t N>
+ void operator=(const char (&str)[N]) {
+ length_ = N - 1;
+ chars_ = InflateString(cx, str, length_);
+ if (!chars_) {
+ abort();
+ }
+ }
+
+ void operator=(const char* str) {
+ length_ = strlen(str);
+ chars_ = InflateString(cx, str, length_);
+ if (!chars_) {
+ abort();
+ }
+ }
+
+ const char16_t* chars() const { return chars_; }
+ size_t length() const { return length_; }
+};
+
+BEGIN_TEST(testParseJSON_success) {
+ // Primitives
+ JS::RootedValue expected(cx);
+ expected = JS::TrueValue();
+ CHECK(TryParse(cx, "true", expected));
+
+ expected = JS::FalseValue();
+ CHECK(TryParse(cx, "false", expected));
+
+ expected = JS::NullValue();
+ CHECK(TryParse(cx, "null", expected));
+
+ expected.setInt32(0);
+ CHECK(TryParse(cx, "0", expected));
+
+ expected.setInt32(1);
+ CHECK(TryParse(cx, "1", expected));
+
+ expected.setInt32(-1);
+ CHECK(TryParse(cx, "-1", expected));
+
+ expected.setDouble(1);
+ CHECK(TryParse(cx, "1", expected));
+
+ expected.setDouble(1.75);
+ CHECK(TryParse(cx, "1.75", expected));
+
+ expected.setDouble(9e9);
+ CHECK(TryParse(cx, "9e9", expected));
+
+ expected.setDouble(std::numeric_limits<double>::infinity());
+ CHECK(TryParse(cx, "9e99999", expected));
+
+ JS::Rooted<JSLinearString*> str(cx);
+
+ const char16_t emptystr[] = {'\0'};
+ str = js::NewStringCopyN<CanGC>(cx, emptystr, 0);
+ CHECK(str);
+ expected = JS::StringValue(str);
+ CHECK(TryParse(cx, "\"\"", expected));
+
+ const char16_t nullstr[] = {'\0'};
+ str = NewString(cx, nullstr);
+ CHECK(str);
+ expected = JS::StringValue(str);
+ CHECK(TryParse(cx, "\"\\u0000\"", expected));
+
+ const char16_t backstr[] = {'\b'};
+ str = NewString(cx, backstr);
+ CHECK(str);
+ expected = JS::StringValue(str);
+ CHECK(TryParse(cx, "\"\\b\"", expected));
+ CHECK(TryParse(cx, "\"\\u0008\"", expected));
+
+ const char16_t newlinestr[] = {
+ '\n',
+ };
+ str = NewString(cx, newlinestr);
+ CHECK(str);
+ expected = JS::StringValue(str);
+ CHECK(TryParse(cx, "\"\\n\"", expected));
+ CHECK(TryParse(cx, "\"\\u000A\"", expected));
+
+ // Arrays
+ JS::RootedValue v(cx), v2(cx);
+ JS::RootedObject obj(cx);
+
+ bool isArray;
+
+ CHECK(Parse(cx, "[]", &v));
+ CHECK(v.isObject());
+ obj = &v.toObject();
+ CHECK(JS::IsArrayObject(cx, obj, &isArray));
+ CHECK(isArray);
+ CHECK(JS_GetProperty(cx, obj, "length", &v2));
+ CHECK(v2.isInt32(0));
+
+ CHECK(Parse(cx, "[1]", &v));
+ CHECK(v.isObject());
+ obj = &v.toObject();
+ CHECK(JS::IsArrayObject(cx, obj, &isArray));
+ CHECK(isArray);
+ CHECK(JS_GetProperty(cx, obj, "0", &v2));
+ CHECK(v2.isInt32(1));
+ CHECK(JS_GetProperty(cx, obj, "length", &v2));
+ CHECK(v2.isInt32(1));
+
+ // Objects
+ CHECK(Parse(cx, "{}", &v));
+ CHECK(v.isObject());
+ obj = &v.toObject();
+ CHECK(JS::IsArrayObject(cx, obj, &isArray));
+ CHECK(!isArray);
+
+ CHECK(Parse(cx, "{ \"f\": 17 }", &v));
+ CHECK(v.isObject());
+ obj = &v.toObject();
+ CHECK(JS::IsArrayObject(cx, obj, &isArray));
+ CHECK(!isArray);
+ CHECK(JS_GetProperty(cx, obj, "f", &v2));
+ CHECK(v2.isInt32(17));
+
+ return true;
+}
+
+template <size_t N>
+static JSLinearString* NewString(JSContext* cx, const char16_t (&chars)[N]) {
+ return js::NewStringCopyN<CanGC>(cx, chars, N);
+}
+
+template <size_t N>
+inline bool Parse(JSContext* cx, const char (&input)[N],
+ JS::MutableHandleValue vp) {
+ AutoInflatedString str(cx);
+ str = input;
+ CHECK(JS_ParseJSON(cx, str.chars(), str.length(), vp));
+ return true;
+}
+
+template <size_t N>
+inline bool TryParse(JSContext* cx, const char (&input)[N],
+ JS::HandleValue expected) {
+ AutoInflatedString str(cx);
+ RootedValue v(cx);
+ str = input;
+ CHECK(JS_ParseJSON(cx, str.chars(), str.length(), &v));
+ CHECK_SAME(v, expected);
+ return true;
+}
+END_TEST(testParseJSON_success)
+
+BEGIN_TEST(testParseJSON_error) {
+ CHECK(Error(cx, "", 1, 1));
+ CHECK(Error(cx, "\n", 2, 1));
+ CHECK(Error(cx, "\r", 2, 1));
+ CHECK(Error(cx, "\r\n", 2, 1));
+
+ CHECK(Error(cx, "[", 1, 2));
+ CHECK(Error(cx, "[,]", 1, 2));
+ CHECK(Error(cx, "[1,]", 1, 4));
+ CHECK(Error(cx, "{a:2}", 1, 2));
+ CHECK(Error(cx, "{\"a\":2,}", 1, 8));
+ CHECK(Error(cx, "]", 1, 1));
+ CHECK(Error(cx, "\"", 1, 2));
+ CHECK(Error(cx, "{]", 1, 2));
+ CHECK(Error(cx, "[}", 1, 2));
+ CHECK(Error(cx, "'wrongly-quoted string'", 1, 1));
+
+ CHECK(Error(cx, "{\"a\":2 \n b:3}", 2, 2));
+ CHECK(Error(cx, "\n[", 2, 2));
+ CHECK(Error(cx, "\n[,]", 2, 2));
+ CHECK(Error(cx, "\n[1,]", 2, 4));
+ CHECK(Error(cx, "\n{a:2}", 2, 2));
+ CHECK(Error(cx, "\n{\"a\":2,}", 2, 8));
+ CHECK(Error(cx, "\n]", 2, 1));
+ CHECK(Error(cx, "\"bad string\n\"", 1, 12));
+ CHECK(Error(cx, "\r'wrongly-quoted string'", 2, 1));
+ CHECK(Error(cx, "\n\"", 2, 2));
+ CHECK(Error(cx, "\n{]", 2, 2));
+ CHECK(Error(cx, "\n[}", 2, 2));
+ CHECK(Error(cx, "{\"a\":[2,3],\n\"b\":,5,6}", 2, 5));
+
+ CHECK(Error(cx, "{\"a\":2 \r b:3}", 2, 2));
+ CHECK(Error(cx, "\r[", 2, 2));
+ CHECK(Error(cx, "\r[,]", 2, 2));
+ CHECK(Error(cx, "\r[1,]", 2, 4));
+ CHECK(Error(cx, "\r{a:2}", 2, 2));
+ CHECK(Error(cx, "\r{\"a\":2,}", 2, 8));
+ CHECK(Error(cx, "\r]", 2, 1));
+ CHECK(Error(cx, "\"bad string\r\"", 1, 12));
+ CHECK(Error(cx, "\r'wrongly-quoted string'", 2, 1));
+ CHECK(Error(cx, "\r\"", 2, 2));
+ CHECK(Error(cx, "\r{]", 2, 2));
+ CHECK(Error(cx, "\r[}", 2, 2));
+ CHECK(Error(cx, "{\"a\":[2,3],\r\"b\":,5,6}", 2, 5));
+
+ CHECK(Error(cx, "{\"a\":2 \r\n b:3}", 2, 2));
+ CHECK(Error(cx, "\r\n[", 2, 2));
+ CHECK(Error(cx, "\r\n[,]", 2, 2));
+ CHECK(Error(cx, "\r\n[1,]", 2, 4));
+ CHECK(Error(cx, "\r\n{a:2}", 2, 2));
+ CHECK(Error(cx, "\r\n{\"a\":2,}", 2, 8));
+ CHECK(Error(cx, "\r\n]", 2, 1));
+ CHECK(Error(cx, "\"bad string\r\n\"", 1, 12));
+ CHECK(Error(cx, "\r\n'wrongly-quoted string'", 2, 1));
+ CHECK(Error(cx, "\r\n\"", 2, 2));
+ CHECK(Error(cx, "\r\n{]", 2, 2));
+ CHECK(Error(cx, "\r\n[}", 2, 2));
+ CHECK(Error(cx, "{\"a\":[2,3],\r\n\"b\":,5,6}", 2, 5));
+
+ CHECK(Error(cx, "\n\"bad string\n\"", 2, 12));
+ CHECK(Error(cx, "\r\"bad string\r\"", 2, 12));
+ CHECK(Error(cx, "\r\n\"bad string\r\n\"", 2, 12));
+
+ CHECK(Error(cx, "{\n\"a\":[2,3],\r\"b\":,5,6}", 3, 5));
+ CHECK(Error(cx, "{\r\"a\":[2,3],\n\"b\":,5,6}", 3, 5));
+ CHECK(Error(cx, "[\"\\t\\q", 1, 6));
+ CHECK(Error(cx, "[\"\\t\x00", 1, 5));
+ CHECK(Error(cx, "[\"\\t\x01", 1, 5));
+ CHECK(Error(cx, "[\"\\t\\\x00", 1, 6));
+ CHECK(Error(cx, "[\"\\t\\\x01", 1, 6));
+
+ // Unicode escape errors are messy. The first bad character could be
+ // non-hexadecimal, or it could be absent entirely. Include tests where
+ // there's a bad character, followed by zero to as many characters as are
+ // needed to form a complete Unicode escape sequence, plus one. (The extra
+ // characters beyond are valuable because our implementation checks for
+ // too-few subsequent characters first, before checking for subsequent
+ // non-hexadecimal characters. So \u<END>, \u0<END>, \u00<END>, and
+ // \u000<END> are all *detected* as invalid by the same code path, but the
+ // process of computing the first invalid character follows a different
+ // code path for each. And \uQQQQ, \u0QQQ, \u00QQ, and \u000Q are detected
+ // as invalid by the same code path [ignoring which precise subexpression
+ // triggers failure of a single condition], but the computation of the
+ // first invalid character follows a different code path for each.)
+ CHECK(Error(cx, "[\"\\t\\u", 1, 7));
+ CHECK(Error(cx, "[\"\\t\\uZ", 1, 7));
+ CHECK(Error(cx, "[\"\\t\\uZZ", 1, 7));
+ CHECK(Error(cx, "[\"\\t\\uZZZ", 1, 7));
+ CHECK(Error(cx, "[\"\\t\\uZZZZ", 1, 7));
+ CHECK(Error(cx, "[\"\\t\\uZZZZZ", 1, 7));
+
+ CHECK(Error(cx, "[\"\\t\\u0", 1, 8));
+ CHECK(Error(cx, "[\"\\t\\u0Z", 1, 8));
+ CHECK(Error(cx, "[\"\\t\\u0ZZ", 1, 8));
+ CHECK(Error(cx, "[\"\\t\\u0ZZZ", 1, 8));
+ CHECK(Error(cx, "[\"\\t\\u0ZZZZ", 1, 8));
+
+ CHECK(Error(cx, "[\"\\t\\u00", 1, 9));
+ CHECK(Error(cx, "[\"\\t\\u00Z", 1, 9));
+ CHECK(Error(cx, "[\"\\t\\u00ZZ", 1, 9));
+ CHECK(Error(cx, "[\"\\t\\u00ZZZ", 1, 9));
+
+ CHECK(Error(cx, "[\"\\t\\u000", 1, 10));
+ CHECK(Error(cx, "[\"\\t\\u000Z", 1, 10));
+ CHECK(Error(cx, "[\"\\t\\u000ZZ", 1, 10));
+
+ return true;
+}
+
+template <size_t N>
+inline bool Error(JSContext* cx, const char (&input)[N], uint32_t expectedLine,
+ uint32_t expectedColumn) {
+ AutoInflatedString str(cx);
+ RootedValue dummy(cx);
+ str = input;
+
+ bool ok = JS_ParseJSON(cx, str.chars(), str.length(), &dummy);
+ CHECK(!ok);
+
+ JS::ExceptionStack exnStack(cx);
+ CHECK(StealPendingExceptionStack(cx, &exnStack));
+
+ JS::ErrorReportBuilder report(cx);
+ CHECK(report.init(cx, exnStack, JS::ErrorReportBuilder::WithSideEffects));
+ CHECK(report.report()->errorNumber == JSMSG_JSON_BAD_PARSE);
+
+ UniqueChars lineAndColumnASCII =
+ JS_smprintf("line %d column %d", expectedLine, expectedColumn);
+ CHECK(strstr(report.toStringResult().c_str(), lineAndColumnASCII.get()) !=
+ nullptr);
+
+ /* We do not execute JS, so there should be no exception thrown. */
+ CHECK(!JS_IsExceptionPending(cx));
+
+ return true;
+}
+END_TEST(testParseJSON_error)
+
+static bool Censor(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ MOZ_RELEASE_ASSERT(args.length() == 2);
+ MOZ_RELEASE_ASSERT(args[0].isString());
+ args.rval().setNull();
+ return true;
+}
+
+BEGIN_TEST(testParseJSON_reviver) {
+ JSFunction* fun = JS_NewFunction(cx, Censor, 0, 0, "censor");
+ CHECK(fun);
+
+ JS::RootedValue filter(cx, JS::ObjectValue(*JS_GetFunctionObject(fun)));
+
+ CHECK(TryParse(cx, "true", filter));
+ CHECK(TryParse(cx, "false", filter));
+ CHECK(TryParse(cx, "null", filter));
+ CHECK(TryParse(cx, "1", filter));
+ CHECK(TryParse(cx, "1.75", filter));
+ CHECK(TryParse(cx, "[]", filter));
+ CHECK(TryParse(cx, "[1]", filter));
+ CHECK(TryParse(cx, "{}", filter));
+ return true;
+}
+
+template <size_t N>
+inline bool TryParse(JSContext* cx, const char (&input)[N],
+ JS::HandleValue filter) {
+ AutoInflatedString str(cx);
+ JS::RootedValue v(cx);
+ str = input;
+ CHECK(JS_ParseJSONWithReviver(cx, str.chars(), str.length(), filter, &v));
+ CHECK(v.isNull());
+ return true;
+}
+END_TEST(testParseJSON_reviver)
diff --git a/js/src/jsapi-tests/testParserAtom.cpp b/js/src/jsapi-tests/testParserAtom.cpp
new file mode 100644
index 0000000000..8a1fb6ff53
--- /dev/null
+++ b/js/src/jsapi-tests/testParserAtom.cpp
@@ -0,0 +1,445 @@
+/* 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/Range.h" // mozilla::Range
+#include "mozilla/Utf8.h" // mozilla::Utf8Unit
+
+#include <string> // std::char_traits
+#include <utility> // std::initializer_list
+#include <vector> // std::vector
+
+#include "frontend/FrontendContext.h" // AutoReportFrontendContext
+#include "frontend/ParserAtom.h" // js::frontend::ParserAtomsTable, js::frontend::WellKnownParserAtoms
+#include "js/TypeDecls.h" // JS::Latin1Char
+#include "jsapi-tests/tests.h"
+
+// Test empty strings behave consistently.
+BEGIN_TEST(testParserAtom_empty) {
+ using js::frontend::ParserAtom;
+ using js::frontend::ParserAtomsTable;
+ using js::frontend::ParserAtomVector;
+ using js::frontend::TaggedParserAtomIndex;
+
+ js::AutoReportFrontendContext fc(cx);
+ js::LifoAlloc alloc(512);
+ ParserAtomsTable atomTable(alloc);
+
+ const char ascii[] = {};
+ const JS::Latin1Char latin1[] = {};
+ const mozilla::Utf8Unit utf8[] = {};
+ const char16_t char16[] = {};
+
+ // Check that the well-known empty atom matches for different entry points.
+ auto refIndex = TaggedParserAtomIndex::WellKnown::empty();
+ CHECK(atomTable.internAscii(&fc, ascii, 0) == refIndex);
+ CHECK(atomTable.internLatin1(&fc, latin1, 0) == refIndex);
+ CHECK(atomTable.internUtf8(&fc, utf8, 0) == refIndex);
+ CHECK(atomTable.internChar16(&fc, char16, 0) == refIndex);
+
+ return true;
+}
+END_TEST(testParserAtom_empty)
+
+// Test length-1 fast-path is consistent across entry points for ASCII.
+BEGIN_TEST(testParserAtom_tiny1_ASCII) {
+ using js::frontend::ParserAtom;
+ using js::frontend::ParserAtomsTable;
+ using js::frontend::ParserAtomVector;
+ using js::frontend::WellKnownParserAtoms;
+
+ js::AutoReportFrontendContext fc(cx);
+ js::LifoAlloc alloc(512);
+ ParserAtomsTable atomTable(alloc);
+
+ char16_t a = 'a';
+ const char ascii[] = {'a'};
+ JS::Latin1Char latin1[] = {'a'};
+ const mozilla::Utf8Unit utf8[] = {mozilla::Utf8Unit('a')};
+ char16_t char16[] = {'a'};
+
+ auto refIndex = WellKnownParserAtoms::getSingleton().lookupTinyIndex(&a, 1);
+ CHECK(refIndex);
+ CHECK(atomTable.internAscii(&fc, ascii, 1) == refIndex);
+ CHECK(atomTable.internLatin1(&fc, latin1, 1) == refIndex);
+ CHECK(atomTable.internUtf8(&fc, utf8, 1) == refIndex);
+ CHECK(atomTable.internChar16(&fc, char16, 1) == refIndex);
+
+ return true;
+}
+END_TEST(testParserAtom_tiny1_ASCII)
+
+// Test length-1 fast-path is consistent across entry points for non-ASCII.
+BEGIN_TEST(testParserAtom_tiny1_nonASCII) {
+ using js::frontend::ParserAtom;
+ using js::frontend::ParserAtomsTable;
+ using js::frontend::ParserAtomVector;
+ using js::frontend::WellKnownParserAtoms;
+
+ js::AutoReportFrontendContext fc(cx);
+ js::LifoAlloc alloc(512);
+ ParserAtomsTable atomTable(alloc);
+
+ {
+ char16_t euro = 0x0080;
+ JS::Latin1Char latin1[] = {0x80};
+ const mozilla::Utf8Unit utf8[] = {
+ mozilla::Utf8Unit(static_cast<unsigned char>(0xC2)),
+ mozilla::Utf8Unit(static_cast<unsigned char>(0x80))};
+ char16_t char16[] = {0x0080};
+
+ auto refIndex =
+ WellKnownParserAtoms::getSingleton().lookupTinyIndex(&euro, 1);
+ CHECK(refIndex);
+ CHECK(atomTable.internLatin1(&fc, latin1, 1) == refIndex);
+ CHECK(atomTable.internUtf8(&fc, utf8, 2) == refIndex);
+ CHECK(atomTable.internChar16(&fc, char16, 1) == refIndex);
+ }
+
+ {
+ char16_t frac12 = 0x00BD;
+ JS::Latin1Char latin1[] = {0xBD};
+ const mozilla::Utf8Unit utf8[] = {
+ mozilla::Utf8Unit(static_cast<unsigned char>(0xC2)),
+ mozilla::Utf8Unit(static_cast<unsigned char>(0xBD))};
+ char16_t char16[] = {0x00BD};
+
+ auto refIndex =
+ WellKnownParserAtoms::getSingleton().lookupTinyIndex(&frac12, 1);
+ CHECK(refIndex);
+ CHECK(atomTable.internLatin1(&fc, latin1, 1) == refIndex);
+ CHECK(atomTable.internUtf8(&fc, utf8, 2) == refIndex);
+ CHECK(atomTable.internChar16(&fc, char16, 1) == refIndex);
+ }
+
+ {
+ char16_t iquest = 0x00BF;
+ JS::Latin1Char latin1[] = {0xBF};
+ const mozilla::Utf8Unit utf8[] = {
+ mozilla::Utf8Unit(static_cast<unsigned char>(0xC2)),
+ mozilla::Utf8Unit(static_cast<unsigned char>(0xBF))};
+ char16_t char16[] = {0x00BF};
+
+ auto refIndex =
+ WellKnownParserAtoms::getSingleton().lookupTinyIndex(&iquest, 1);
+ CHECK(refIndex);
+ CHECK(atomTable.internLatin1(&fc, latin1, 1) == refIndex);
+ CHECK(atomTable.internUtf8(&fc, utf8, 2) == refIndex);
+ CHECK(atomTable.internChar16(&fc, char16, 1) == refIndex);
+ }
+
+ {
+ char16_t agrave = 0x00C0;
+ JS::Latin1Char latin1[] = {0xC0};
+ const mozilla::Utf8Unit utf8[] = {
+ mozilla::Utf8Unit(static_cast<unsigned char>(0xC3)),
+ mozilla::Utf8Unit(static_cast<unsigned char>(0x80))};
+ char16_t char16[] = {0x00C0};
+
+ auto refIndex =
+ WellKnownParserAtoms::getSingleton().lookupTinyIndex(&agrave, 1);
+ CHECK(refIndex);
+ CHECK(atomTable.internLatin1(&fc, latin1, 1) == refIndex);
+ CHECK(atomTable.internUtf8(&fc, utf8, 2) == refIndex);
+ CHECK(atomTable.internChar16(&fc, char16, 1) == refIndex);
+ }
+
+ {
+ char16_t ae = 0x00E6;
+ JS::Latin1Char latin1[] = {0xE6};
+ const mozilla::Utf8Unit utf8[] = {
+ mozilla::Utf8Unit(static_cast<unsigned char>(0xC3)),
+ mozilla::Utf8Unit(static_cast<unsigned char>(0xA6))};
+ char16_t char16[] = {0x00E6};
+
+ auto refIndex =
+ WellKnownParserAtoms::getSingleton().lookupTinyIndex(&ae, 1);
+ CHECK(refIndex);
+ CHECK(atomTable.internLatin1(&fc, latin1, 1) == refIndex);
+ CHECK(atomTable.internUtf8(&fc, utf8, 2) == refIndex);
+ CHECK(atomTable.internChar16(&fc, char16, 1) == refIndex);
+ }
+
+ {
+ char16_t yuml = 0x00FF;
+ JS::Latin1Char latin1[] = {0xFF};
+ const mozilla::Utf8Unit utf8[] = {
+ mozilla::Utf8Unit(static_cast<unsigned char>(0xC3)),
+ mozilla::Utf8Unit(static_cast<unsigned char>(0xBF))};
+ char16_t char16[] = {0x00FF};
+
+ auto refIndex =
+ WellKnownParserAtoms::getSingleton().lookupTinyIndex(&yuml, 1);
+ CHECK(refIndex);
+ CHECK(atomTable.internLatin1(&fc, latin1, 1) == refIndex);
+ CHECK(atomTable.internUtf8(&fc, utf8, 2) == refIndex);
+ CHECK(atomTable.internChar16(&fc, char16, 1) == refIndex);
+ }
+
+ return true;
+}
+END_TEST(testParserAtom_tiny1_nonASCII)
+
+// Test for tiny1 UTF-8 with valid/invalid code units.
+//
+// NOTE: Passing invalid UTF-8 to internUtf8 hits assertion failure, so
+// test in the opposite way.
+// lookupTinyIndexUTF8 is used inside internUtf8.
+BEGIN_TEST(testParserAtom_tiny1_invalidUTF8) {
+ using js::frontend::ParserAtom;
+ using js::frontend::ParserAtomsTable;
+ using js::frontend::WellKnownParserAtoms;
+
+ js::AutoReportFrontendContext fc(cx);
+ js::LifoAlloc alloc(512);
+ ParserAtomsTable atomTable(alloc);
+
+ {
+ const mozilla::Utf8Unit utf8[] = {
+ mozilla::Utf8Unit(static_cast<unsigned char>(0xC1)),
+ mozilla::Utf8Unit(static_cast<unsigned char>(0x80))};
+
+ CHECK(!WellKnownParserAtoms::getSingleton().lookupTinyIndexUTF8(utf8, 2));
+ }
+
+ {
+ const mozilla::Utf8Unit utf8[] = {
+ mozilla::Utf8Unit(static_cast<unsigned char>(0xC2)),
+ mozilla::Utf8Unit(static_cast<unsigned char>(0x7F))};
+
+ CHECK(!WellKnownParserAtoms::getSingleton().lookupTinyIndexUTF8(utf8, 2));
+ }
+
+ {
+ JS::Latin1Char latin1[] = {0x80};
+ const mozilla::Utf8Unit utf8[] = {
+ mozilla::Utf8Unit(static_cast<unsigned char>(0xC2)),
+ mozilla::Utf8Unit(static_cast<unsigned char>(0x80))};
+
+ auto refIndex =
+ WellKnownParserAtoms::getSingleton().lookupTinyIndexUTF8(utf8, 2);
+ CHECK(refIndex);
+ CHECK(atomTable.internLatin1(&fc, latin1, 1) == refIndex);
+ }
+
+ {
+ JS::Latin1Char latin1[] = {0xBF};
+ const mozilla::Utf8Unit utf8[] = {
+ mozilla::Utf8Unit(static_cast<unsigned char>(0xC2)),
+ mozilla::Utf8Unit(static_cast<unsigned char>(0xBF))};
+
+ auto refIndex =
+ WellKnownParserAtoms::getSingleton().lookupTinyIndexUTF8(utf8, 2);
+ CHECK(refIndex);
+ CHECK(atomTable.internLatin1(&fc, latin1, 1) == refIndex);
+ }
+
+ {
+ const mozilla::Utf8Unit utf8[] = {
+ mozilla::Utf8Unit(static_cast<unsigned char>(0xC2)),
+ mozilla::Utf8Unit(static_cast<unsigned char>(0xC0))};
+
+ CHECK(!WellKnownParserAtoms::getSingleton().lookupTinyIndexUTF8(utf8, 2));
+ }
+
+ {
+ const mozilla::Utf8Unit utf8[] = {
+ mozilla::Utf8Unit(static_cast<unsigned char>(0xC3)),
+ mozilla::Utf8Unit(static_cast<unsigned char>(0x7F))};
+
+ CHECK(!WellKnownParserAtoms::getSingleton().lookupTinyIndexUTF8(utf8, 2));
+ }
+
+ {
+ JS::Latin1Char latin1[] = {0xC0};
+ const mozilla::Utf8Unit utf8[] = {
+ mozilla::Utf8Unit(static_cast<unsigned char>(0xC3)),
+ mozilla::Utf8Unit(static_cast<unsigned char>(0x80))};
+
+ auto refIndex =
+ WellKnownParserAtoms::getSingleton().lookupTinyIndexUTF8(utf8, 2);
+ CHECK(refIndex);
+ CHECK(atomTable.internLatin1(&fc, latin1, 1) == refIndex);
+ }
+
+ {
+ JS::Latin1Char latin1[] = {0xFF};
+ const mozilla::Utf8Unit utf8[] = {
+ mozilla::Utf8Unit(static_cast<unsigned char>(0xC3)),
+ mozilla::Utf8Unit(static_cast<unsigned char>(0xBF))};
+
+ auto refIndex =
+ WellKnownParserAtoms::getSingleton().lookupTinyIndexUTF8(utf8, 2);
+ CHECK(refIndex);
+ CHECK(atomTable.internLatin1(&fc, latin1, 1) == refIndex);
+ }
+
+ {
+ const mozilla::Utf8Unit utf8[] = {
+ mozilla::Utf8Unit(static_cast<unsigned char>(0xC3)),
+ mozilla::Utf8Unit(static_cast<unsigned char>(0xC0))};
+
+ CHECK(!WellKnownParserAtoms::getSingleton().lookupTinyIndexUTF8(utf8, 2));
+ }
+
+ {
+ const mozilla::Utf8Unit utf8[] = {
+ mozilla::Utf8Unit(static_cast<unsigned char>(0xC4)),
+ mozilla::Utf8Unit(static_cast<unsigned char>(0x7F))};
+
+ CHECK(!WellKnownParserAtoms::getSingleton().lookupTinyIndexUTF8(utf8, 2));
+ }
+
+ {
+ const mozilla::Utf8Unit utf8[] = {
+ mozilla::Utf8Unit(static_cast<unsigned char>(0xC4)),
+ mozilla::Utf8Unit(static_cast<unsigned char>(0x80))};
+
+ CHECK(!WellKnownParserAtoms::getSingleton().lookupTinyIndexUTF8(utf8, 2));
+ }
+
+ {
+ const mozilla::Utf8Unit utf8[] = {
+ mozilla::Utf8Unit(static_cast<unsigned char>(0xC4)),
+ mozilla::Utf8Unit(static_cast<unsigned char>(0xBF))};
+
+ CHECK(!WellKnownParserAtoms::getSingleton().lookupTinyIndexUTF8(utf8, 2));
+ }
+
+ {
+ const mozilla::Utf8Unit utf8[] = {
+ mozilla::Utf8Unit(static_cast<unsigned char>(0xC4)),
+ mozilla::Utf8Unit(static_cast<unsigned char>(0xC0))};
+
+ CHECK(!WellKnownParserAtoms::getSingleton().lookupTinyIndexUTF8(utf8, 2));
+ }
+
+ return true;
+}
+END_TEST(testParserAtom_tiny1_invalidUTF8)
+
+// Test length-2 fast-path is consistent across entry points.
+BEGIN_TEST(testParserAtom_tiny2) {
+ using js::frontend::ParserAtom;
+ using js::frontend::ParserAtomsTable;
+ using js::frontend::ParserAtomVector;
+ using js::frontend::WellKnownParserAtoms;
+
+ js::AutoReportFrontendContext fc(cx);
+ js::LifoAlloc alloc(512);
+ ParserAtomsTable atomTable(alloc);
+
+ const char ascii[] = {'a', '0'};
+ JS::Latin1Char latin1[] = {'a', '0'};
+ const mozilla::Utf8Unit utf8[] = {mozilla::Utf8Unit('a'),
+ mozilla::Utf8Unit('0')};
+ char16_t char16[] = {'a', '0'};
+
+ auto refIndex =
+ WellKnownParserAtoms::getSingleton().lookupTinyIndex(ascii, 2);
+ CHECK(refIndex);
+ CHECK(atomTable.internAscii(&fc, ascii, 2) == refIndex);
+ CHECK(atomTable.internLatin1(&fc, latin1, 2) == refIndex);
+ CHECK(atomTable.internUtf8(&fc, utf8, 2) == refIndex);
+ CHECK(atomTable.internChar16(&fc, char16, 2) == refIndex);
+
+ // Note: If Latin1-Extended characters become supported, then UTF-8 behaviour
+ // should be tested.
+ char16_t ae0[] = {0x00E6, '0'};
+ CHECK(!WellKnownParserAtoms::getSingleton().lookupTinyIndex(ae0, 2));
+
+ return true;
+}
+END_TEST(testParserAtom_tiny2)
+
+// Test length-3 fast-path is consistent across entry points.
+BEGIN_TEST(testParserAtom_int) {
+ using js::frontend::ParserAtom;
+ using js::frontend::ParserAtomsTable;
+ using js::frontend::ParserAtomVector;
+ using js::frontend::WellKnownParserAtoms;
+
+ js::AutoReportFrontendContext fc(cx);
+ js::LifoAlloc alloc(512);
+ ParserAtomsTable atomTable(alloc);
+
+ {
+ const char ascii[] = {'1', '0', '0'};
+ JS::Latin1Char latin1[] = {'1', '0', '0'};
+ const mozilla::Utf8Unit utf8[] = {
+ mozilla::Utf8Unit('1'), mozilla::Utf8Unit('0'), mozilla::Utf8Unit('0')};
+ char16_t char16[] = {'1', '0', '0'};
+
+ auto refIndex =
+ WellKnownParserAtoms::getSingleton().lookupTinyIndex(ascii, 3);
+ CHECK(refIndex);
+ CHECK(atomTable.internAscii(&fc, ascii, 3) == refIndex);
+ CHECK(atomTable.internLatin1(&fc, latin1, 3) == refIndex);
+ CHECK(atomTable.internUtf8(&fc, utf8, 3) == refIndex);
+ CHECK(atomTable.internChar16(&fc, char16, 3) == refIndex);
+ }
+
+ {
+ const char ascii[] = {'2', '5', '5'};
+ JS::Latin1Char latin1[] = {'2', '5', '5'};
+ const mozilla::Utf8Unit utf8[] = {
+ mozilla::Utf8Unit('2'), mozilla::Utf8Unit('5'), mozilla::Utf8Unit('5')};
+ char16_t char16[] = {'2', '5', '5'};
+
+ auto refIndex =
+ WellKnownParserAtoms::getSingleton().lookupTinyIndex(ascii, 3);
+ CHECK(refIndex);
+ CHECK(atomTable.internAscii(&fc, ascii, 3) == refIndex);
+ CHECK(atomTable.internLatin1(&fc, latin1, 3) == refIndex);
+ CHECK(atomTable.internUtf8(&fc, utf8, 3) == refIndex);
+ CHECK(atomTable.internChar16(&fc, char16, 3) == refIndex);
+ }
+
+ {
+ const char ascii[] = {'0', '9', '9'};
+
+ CHECK(!WellKnownParserAtoms::getSingleton().lookupTinyIndex(ascii, 3));
+ }
+
+ {
+ const char ascii[] = {'0', 'F', 'F'};
+
+ CHECK(!WellKnownParserAtoms::getSingleton().lookupTinyIndex(ascii, 3));
+ }
+
+ {
+ const char ascii[] = {'1', '0', 'A'};
+
+ CHECK(!WellKnownParserAtoms::getSingleton().lookupTinyIndex(ascii, 3));
+ }
+
+ {
+ const char ascii[] = {'1', '0', 'a'};
+
+ CHECK(!WellKnownParserAtoms::getSingleton().lookupTinyIndex(ascii, 3));
+ }
+
+ {
+ const char ascii[] = {'2', '5', '6'};
+
+ CHECK(!WellKnownParserAtoms::getSingleton().lookupTinyIndex(ascii, 3));
+ }
+
+ {
+ const char ascii[] = {'3', '0', '0'};
+
+ CHECK(!WellKnownParserAtoms::getSingleton().lookupTinyIndex(ascii, 3));
+ }
+
+ return true;
+}
+END_TEST(testParserAtom_int)
+
+// "€" U+0080
+// "½" U+00BD
+// "¿" U+00BF
+// "À" U+00C0
+// "æ" U+00E6
+// "ÿ" U+00FF
+// "π" U+03C0
+// "🍕" U+1F355
diff --git a/js/src/jsapi-tests/testPersistentRooted.cpp b/js/src/jsapi-tests/testPersistentRooted.cpp
new file mode 100644
index 0000000000..4b3ab3050d
--- /dev/null
+++ b/js/src/jsapi-tests/testPersistentRooted.cpp
@@ -0,0 +1,212 @@
+/* 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 "js/Class.h"
+#include "jsapi-tests/tests.h"
+
+using namespace JS;
+
+struct BarkWhenTracedClass {
+ static int finalizeCount;
+ static int traceCount;
+
+ static const JSClass class_;
+ static void finalize(JS::GCContext* gcx, JSObject* obj) { finalizeCount++; }
+ static void trace(JSTracer* trc, JSObject* obj) { traceCount++; }
+ static void reset() {
+ finalizeCount = 0;
+ traceCount = 0;
+ }
+};
+
+int BarkWhenTracedClass::finalizeCount;
+int BarkWhenTracedClass::traceCount;
+
+static const JSClassOps BarkWhenTracedClassClassOps = {
+ nullptr, // addProperty
+ nullptr, // delProperty
+ nullptr, // enumerate
+ nullptr, // newEnumerate
+ nullptr, // resolve
+ nullptr, // mayResolve
+ BarkWhenTracedClass::finalize, // finalize
+ nullptr, // call
+ nullptr, // construct
+ BarkWhenTracedClass::trace, // trace
+};
+
+const JSClass BarkWhenTracedClass::class_ = {"BarkWhenTracedClass",
+ JSCLASS_FOREGROUND_FINALIZE,
+ &BarkWhenTracedClassClassOps};
+
+struct Kennel {
+ PersistentRootedObject obj;
+ Kennel() {}
+ explicit Kennel(JSContext* cx) : obj(cx) {}
+ Kennel(JSContext* cx, const HandleObject& woof) : obj(cx, woof) {}
+ void init(JSContext* cx, const HandleObject& woof) { obj.init(cx, woof); }
+ void clear() { obj = nullptr; }
+};
+
+// A function for allocating a Kennel and a barker. Only allocating
+// PersistentRooteds on the heap, and in this function, helps ensure that the
+// conservative GC doesn't find stray references to the barker. Ugh.
+MOZ_NEVER_INLINE static Kennel* Allocate(JSContext* cx) {
+ RootedObject barker(cx, JS_NewObject(cx, &BarkWhenTracedClass::class_));
+ if (!barker) {
+ return nullptr;
+ }
+
+ return new Kennel(cx, barker);
+}
+
+// Do a GC, expecting |n| barkers to be finalized.
+static bool GCFinalizesNBarkers(JSContext* cx, int n) {
+ int preGCTrace = BarkWhenTracedClass::traceCount;
+ int preGCFinalize = BarkWhenTracedClass::finalizeCount;
+
+ JS_GC(cx);
+
+ return (BarkWhenTracedClass::finalizeCount == preGCFinalize + n &&
+ BarkWhenTracedClass::traceCount > preGCTrace);
+}
+
+// PersistentRooted instances protect their contents from being recycled.
+BEGIN_TEST(test_PersistentRooted) {
+ BarkWhenTracedClass::reset();
+
+ mozilla::UniquePtr<Kennel> kennel(Allocate(cx));
+ CHECK(kennel.get());
+
+ // GC should be able to find our barker.
+ CHECK(GCFinalizesNBarkers(cx, 0));
+
+ kennel = nullptr;
+
+ // Now GC should not be able to find the barker.
+ JS_GC(cx);
+ CHECK(BarkWhenTracedClass::finalizeCount == 1);
+
+ return true;
+}
+END_TEST(test_PersistentRooted)
+
+// GC should not be upset by null PersistentRooteds.
+BEGIN_TEST(test_PersistentRootedNull) {
+ BarkWhenTracedClass::reset();
+
+ Kennel kennel(cx);
+ CHECK(!kennel.obj);
+
+ JS_GC(cx);
+ CHECK(BarkWhenTracedClass::finalizeCount == 0);
+
+ return true;
+}
+END_TEST(test_PersistentRootedNull)
+
+// Copy construction works.
+BEGIN_TEST(test_PersistentRootedCopy) {
+ BarkWhenTracedClass::reset();
+
+ mozilla::UniquePtr<Kennel> kennel(Allocate(cx));
+ CHECK(kennel.get());
+
+ CHECK(GCFinalizesNBarkers(cx, 0));
+
+ // Copy construction! AMAZING!
+ mozilla::UniquePtr<Kennel> newKennel(new Kennel(*kennel));
+
+ CHECK(GCFinalizesNBarkers(cx, 0));
+
+ kennel = nullptr;
+
+ CHECK(GCFinalizesNBarkers(cx, 0));
+
+ newKennel = nullptr;
+
+ // Now that kennel and nowKennel are both deallocated, GC should not be
+ // able to find the barker.
+ JS_GC(cx);
+ CHECK(BarkWhenTracedClass::finalizeCount == 1);
+
+ return true;
+}
+END_TEST(test_PersistentRootedCopy)
+
+// Assignment works.
+BEGIN_TEST(test_PersistentRootedAssign) {
+ BarkWhenTracedClass::reset();
+
+ mozilla::UniquePtr<Kennel> kennel(Allocate(cx));
+ CHECK(kennel.get());
+
+ CHECK(GCFinalizesNBarkers(cx, 0));
+
+ // Allocate a new, empty kennel.
+ mozilla::UniquePtr<Kennel> kennel2(new Kennel(cx));
+
+ // Assignment! ASTONISHING!
+ *kennel2 = *kennel;
+
+ // With both kennels referring to the same barker, it is held alive.
+ CHECK(GCFinalizesNBarkers(cx, 0));
+
+ kennel2 = nullptr;
+
+ // The destination of the assignment alone holds the barker alive.
+ CHECK(GCFinalizesNBarkers(cx, 0));
+
+ // Allocate a second barker.
+ kennel2 = mozilla::UniquePtr<Kennel>(Allocate(cx));
+ CHECK(kennel2.get());
+
+ *kennel = *kennel2;
+
+ // Nothing refers to the first kennel any more.
+ CHECK(GCFinalizesNBarkers(cx, 1));
+
+ kennel = nullptr;
+ kennel2 = nullptr;
+
+ // Now that kennel and kennel2 are both deallocated, GC should not be
+ // able to find the barker.
+ JS_GC(cx);
+ CHECK(BarkWhenTracedClass::finalizeCount == 2);
+
+ return true;
+}
+END_TEST(test_PersistentRootedAssign)
+
+static PersistentRootedObject gGlobalRoot;
+
+// PersistentRooted instances can initialized in a separate step to allow for
+// global PersistentRooteds.
+BEGIN_TEST(test_GlobalPersistentRooted) {
+ BarkWhenTracedClass::reset();
+
+ CHECK(!gGlobalRoot.initialized());
+
+ {
+ RootedObject barker(cx, JS_NewObject(cx, &BarkWhenTracedClass::class_));
+ CHECK(barker);
+
+ gGlobalRoot.init(cx, barker);
+ }
+
+ CHECK(gGlobalRoot.initialized());
+
+ // GC should be able to find our barker.
+ CHECK(GCFinalizesNBarkers(cx, 0));
+
+ gGlobalRoot.reset();
+ CHECK(!gGlobalRoot.initialized());
+
+ // Now GC should not be able to find the barker.
+ JS_GC(cx);
+ CHECK(BarkWhenTracedClass::finalizeCount == 1);
+
+ return true;
+}
+END_TEST(test_GlobalPersistentRooted)
diff --git a/js/src/jsapi-tests/testPreserveJitCode.cpp b/js/src/jsapi-tests/testPreserveJitCode.cpp
new file mode 100644
index 0000000000..e27e63f2d1
--- /dev/null
+++ b/js/src/jsapi-tests/testPreserveJitCode.cpp
@@ -0,0 +1,100 @@
+/* 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/Utf8.h" // mozilla::Utf8Unit
+
+#include "jit/Ion.h" // js::jit::IsIonEnabled
+#include "js/CallAndConstruct.h" // JS::CallFunction
+#include "js/CompilationAndEvaluation.h" // JS::CompileFunction
+#include "js/GlobalObject.h" // JS_NewGlobalObject
+#include "js/SourceText.h" // JS::Source{Ownership,Text}
+#include "jsapi-tests/tests.h"
+#include "util/Text.h"
+
+#include "vm/JSObject-inl.h"
+#include "vm/JSScript-inl.h"
+
+using namespace JS;
+
+static void ScriptCallback(JSRuntime* rt, void* data, js::BaseScript* script,
+ const JS::AutoRequireNoGC& nogc) {
+ unsigned& count = *static_cast<unsigned*>(data);
+ if (script->asJSScript()->hasIonScript()) {
+ ++count;
+ }
+}
+
+BEGIN_TEST(test_PreserveJitCode) {
+ CHECK(testPreserveJitCode(false, 0));
+ CHECK(testPreserveJitCode(true, 1));
+ return true;
+}
+
+unsigned countIonScripts(JSObject* global) {
+ unsigned count = 0;
+ js::IterateScripts(cx, global->nonCCWRealm(), &count, ScriptCallback);
+ return count;
+}
+
+bool testPreserveJitCode(bool preserveJitCode, unsigned remainingIonScripts) {
+ cx->runtime()->setOffthreadIonCompilationEnabled(false);
+
+ RootedObject global(cx, createTestGlobal(preserveJitCode));
+ CHECK(global);
+ JSAutoRealm ar(cx, global);
+
+ // The Ion JIT may be unavailable due to --disable-jit or lack of support
+ // for this platform.
+ if (!js::jit::IsIonEnabled(cx)) {
+ knownFail = true;
+ }
+
+ CHECK_EQUAL(countIonScripts(global), 0u);
+
+ static constexpr char source[] =
+ "var i = 0;\n"
+ "var sum = 0;\n"
+ "while (i < 10) {\n"
+ " sum += i;\n"
+ " ++i;\n"
+ "}\n"
+ "return sum;\n";
+ constexpr unsigned length = js_strlen(source);
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, source, length, JS::SourceOwnership::Borrowed));
+
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(__FILE__, 1);
+
+ JS::RootedFunction fun(cx);
+ JS::RootedObjectVector emptyScopeChain(cx);
+ fun = JS::CompileFunction(cx, emptyScopeChain, options, "f", 0, nullptr,
+ srcBuf);
+ CHECK(fun);
+
+ RootedValue value(cx);
+ for (unsigned i = 0; i < 1500; ++i) {
+ CHECK(JS_CallFunction(cx, global, fun, JS::HandleValueArray::empty(),
+ &value));
+ }
+ CHECK_EQUAL(value.toInt32(), 45);
+ CHECK_EQUAL(countIonScripts(global), 1u);
+
+ NonIncrementalGC(cx, JS::GCOptions::Normal, GCReason::API);
+ CHECK_EQUAL(countIonScripts(global), remainingIonScripts);
+
+ NonIncrementalGC(cx, JS::GCOptions::Shrink, GCReason::API);
+ CHECK_EQUAL(countIonScripts(global), 0u);
+
+ return true;
+}
+
+JSObject* createTestGlobal(bool preserveJitCode) {
+ JS::RealmOptions options;
+ options.creationOptions().setPreserveJitCode(preserveJitCode);
+ return JS_NewGlobalObject(cx, getGlobalClass(), nullptr,
+ JS::FireOnNewGlobalHook, options);
+}
+END_TEST(test_PreserveJitCode)
diff --git a/js/src/jsapi-tests/testPrintError.cpp b/js/src/jsapi-tests/testPrintError.cpp
new file mode 100644
index 0000000000..b29e4f2d2d
--- /dev/null
+++ b/js/src/jsapi-tests/testPrintError.cpp
@@ -0,0 +1,125 @@
+/* -*- 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 <cstdio> // fclose, fflush, open_memstream
+
+#include "js/ErrorReport.h" // JS::PrintError
+#include "js/Warnings.h" // JS::SetWarningReporter, JS::WarnUTF8
+
+#include "jsapi-tests/tests.h"
+
+class AutoStreamBuffer {
+ char* buffer;
+ size_t size;
+ FILE* fp;
+
+ public:
+ AutoStreamBuffer() { fp = open_memstream(&buffer, &size); }
+
+ ~AutoStreamBuffer() {
+ fclose(fp);
+ free(buffer);
+ }
+
+ FILE* stream() { return fp; }
+
+ bool contains(const char* str) {
+ if (fflush(fp) != 0) {
+ fprintf(stderr, "Error flushing stream\n");
+ return false;
+ }
+ if (strcmp(buffer, str) != 0) {
+ fprintf(stderr, "Expected |%s|, got |%s|\n", str, buffer);
+ return false;
+ }
+ return true;
+ }
+};
+
+BEGIN_TEST(testPrintError_Works) {
+ AutoStreamBuffer buf;
+
+ CHECK(!execDontReport("throw null;", "testPrintError_Works.js", 3));
+
+ JS::ExceptionStack exnStack(cx);
+ CHECK(JS::StealPendingExceptionStack(cx, &exnStack));
+
+ JS::ErrorReportBuilder builder(cx);
+ CHECK(builder.init(cx, exnStack, JS::ErrorReportBuilder::NoSideEffects));
+ JS::PrintError(buf.stream(), builder, false);
+
+ CHECK(buf.contains("testPrintError_Works.js:3:1 uncaught exception: null\n"));
+
+ return true;
+}
+END_TEST(testPrintError_Works)
+
+BEGIN_TEST(testPrintError_SkipWarning) {
+ JS::SetWarningReporter(cx, warningReporter);
+ CHECK(JS::WarnUTF8(cx, "warning message"));
+ CHECK(warningSuccess);
+ return true;
+}
+
+static bool warningSuccess;
+
+static void warningReporter(JSContext* cx, JSErrorReport* report) {
+ AutoStreamBuffer buf;
+ JS::PrintError(buf.stream(), report, false);
+ warningSuccess = buf.contains("");
+}
+END_TEST(testPrintError_SkipWarning)
+
+bool cls_testPrintError_SkipWarning::warningSuccess = false;
+
+BEGIN_TEST(testPrintError_PrintWarning) {
+ JS::SetWarningReporter(cx, warningReporter);
+ CHECK(JS::WarnUTF8(cx, "warning message"));
+ CHECK(warningSuccess);
+ return true;
+}
+
+static bool warningSuccess;
+
+static void warningReporter(JSContext* cx, JSErrorReport* report) {
+ AutoStreamBuffer buf;
+ JS::PrintError(buf.stream(), report, true);
+ warningSuccess = buf.contains("warning: warning message\n");
+}
+END_TEST(testPrintError_PrintWarning)
+
+bool cls_testPrintError_PrintWarning::warningSuccess = false;
+
+#define BURRITO "\xF0\x9F\x8C\xAF"
+
+BEGIN_TEST(testPrintError_UTF16CodePoints) {
+ AutoStreamBuffer buf;
+
+ static const char utf8code[] =
+ "function f() {\n var x = `\n" BURRITO "`; " BURRITO "; } f();";
+
+ CHECK(!execDontReport(utf8code, "testPrintError_UTF16CodePoints.js", 1));
+
+ JS::ExceptionStack exnStack(cx);
+ CHECK(JS::StealPendingExceptionStack(cx, &exnStack));
+
+ JS::ErrorReportBuilder builder(cx);
+ CHECK(builder.init(cx, exnStack, JS::ErrorReportBuilder::NoSideEffects));
+ JS::PrintError(buf.stream(), builder, false);
+
+ CHECK(
+ buf.contains("testPrintError_UTF16CodePoints.js:3:4 SyntaxError: illegal "
+ "character U+1F32F:\n"
+ "testPrintError_UTF16CodePoints.js:3:4 " BURRITO
+ "`; " BURRITO "; } f();\n"
+ "testPrintError_UTF16CodePoints.js:3:4 .....^\n"));
+
+ return true;
+}
+END_TEST(testPrintError_UTF16CodePoints)
+
+#undef BURRITO
diff --git a/js/src/jsapi-tests/testPrintf.cpp b/js/src/jsapi-tests/testPrintf.cpp
new file mode 100644
index 0000000000..332d0d85cd
--- /dev/null
+++ b/js/src/jsapi-tests/testPrintf.cpp
@@ -0,0 +1,69 @@
+/* -*- 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/IntegerPrintfMacros.h"
+
+#include <cfloat>
+#include <stdarg.h>
+
+#include "js/Printf.h"
+
+#include "jsapi-tests/tests.h"
+
+static bool MOZ_FORMAT_PRINTF(2, 3)
+ print_one(const char* expect, const char* fmt, ...) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ JS::UniqueChars output = JS_vsmprintf(fmt, ap);
+ va_end(ap);
+
+ return output && !strcmp(output.get(), expect);
+}
+
+static const char* zero() {
+ // gcc 9 is altogether too clever about detecting that this will always
+ // return nullptr. Do not replace 0x10 with 0x1; it will no longer work.
+ return uintptr_t(&zero) == 0x10 ? "never happens" : nullptr;
+}
+
+BEGIN_TEST(testPrintf) {
+ CHECK(print_one("23", "%d", 23));
+ CHECK(print_one("-1", "%d", -1));
+ CHECK(print_one("23", "%u", 23u));
+ CHECK(print_one("0x17", "0x%x", 23u));
+ CHECK(print_one("0xFF", "0x%X", 255u));
+ CHECK(print_one("027", "0%o", 23u));
+ CHECK(print_one("-1", "%hd", (short)-1));
+ // This could be expanded if need be, it's just convenient to do
+ // it this way.
+ if (sizeof(short) == 2) {
+ CHECK(print_one("8000", "%hx", (unsigned short)0x8000));
+ }
+ CHECK(print_one("0xf0f0", "0x%lx", 0xf0f0ul));
+ CHECK(print_one("0xF0F0", "0x%llX", 0xf0f0ull));
+ CHECK(print_one("27270", "%zu", (size_t)27270));
+ CHECK(print_one("27270", "%zu", (size_t)27270));
+ CHECK(print_one("hello", "he%so", "ll"));
+ CHECK(print_one("(null)", "%s", ::zero()));
+ CHECK(print_one("0", "%p", (char*)0));
+ CHECK(print_one("h", "%c", 'h'));
+ CHECK(print_one("1.500000", "%f", 1.5f));
+ CHECK(print_one("1.5", "%g", 1.5));
+
+ // Regression test for bug#1350097. The bug was an assertion
+ // failure caused by printing a very long floating point value.
+ print_one("ignore", "%lf", DBL_MAX);
+
+ CHECK(print_one("2727", "%" PRIu32, (uint32_t)2727));
+ CHECK(print_one("aa7", "%" PRIx32, (uint32_t)2727));
+ CHECK(print_one("2727", "%" PRIu64, (uint64_t)2727));
+ CHECK(print_one("aa7", "%" PRIx64, (uint64_t)2727));
+
+ return true;
+}
+END_TEST(testPrintf)
diff --git a/js/src/jsapi-tests/testPrivateGCThingValue.cpp b/js/src/jsapi-tests/testPrivateGCThingValue.cpp
new file mode 100644
index 0000000000..a4560b68d2
--- /dev/null
+++ b/js/src/jsapi-tests/testPrivateGCThingValue.cpp
@@ -0,0 +1,64 @@
+/* -*- 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/Utf8.h" // mozilla::Utf8Unit
+
+#include "jsapi.h"
+
+#include "js/CompilationAndEvaluation.h" // JS::Compile
+#include "js/HeapAPI.h"
+#include "js/SourceText.h" // JS::Source{Ownership,Text}
+#include "jsapi-tests/tests.h"
+#include "util/Text.h"
+
+class TestTracer final : public JS::CallbackTracer {
+ void onChild(JS::GCCellPtr thing, const char* name) override {
+ if (thing.asCell() == expectedCell && thing.kind() == expectedKind) {
+ found = true;
+ }
+ }
+
+ public:
+ js::gc::Cell* expectedCell;
+ JS::TraceKind expectedKind;
+ bool found;
+
+ explicit TestTracer(JSContext* cx)
+ : JS::CallbackTracer(cx),
+ expectedCell(nullptr),
+ expectedKind(static_cast<JS::TraceKind>(0)),
+ found(false) {}
+};
+
+static const JSClass TestClass = {"TestClass", JSCLASS_HAS_RESERVED_SLOTS(1)};
+
+BEGIN_TEST(testPrivateGCThingValue) {
+ JS::RootedObject obj(cx, JS_NewObject(cx, &TestClass));
+ CHECK(obj);
+
+ // Make a JSScript to stick into a PrivateGCThingValue.
+ static const char code[] = "'objet petit a'";
+
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(__FILE__, __LINE__);
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, code, js_strlen(code), JS::SourceOwnership::Borrowed));
+
+ JS::RootedScript script(cx, JS::Compile(cx, options, srcBuf));
+ CHECK(script);
+ JS_SetReservedSlot(obj, 0, PrivateGCThingValue(script));
+
+ TestTracer trc(cx);
+ trc.expectedCell = script;
+ trc.expectedKind = JS::TraceKind::Script;
+ JS::TraceChildren(&trc, JS::GCCellPtr(obj, JS::TraceKind::Object));
+ CHECK(trc.found);
+
+ return true;
+}
+END_TEST(testPrivateGCThingValue)
diff --git a/js/src/jsapi-tests/testProfileStrings.cpp b/js/src/jsapi-tests/testProfileStrings.cpp
new file mode 100644
index 0000000000..030b31ce8f
--- /dev/null
+++ b/js/src/jsapi-tests/testProfileStrings.cpp
@@ -0,0 +1,225 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ *
+ * Tests the stack-based instrumentation profiler on a JSRuntime
+ */
+/* 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/Atomics.h"
+
+#include "js/CallAndConstruct.h"
+#include "js/ContextOptions.h"
+#include "js/GlobalObject.h"
+#include "js/PropertySpec.h"
+#include "jsapi-tests/tests.h"
+#include "vm/JSContext.h"
+
+static ProfilingStack profilingStack;
+static uint32_t peakStackPointer = 0;
+
+static void reset(JSContext* cx) {
+ profilingStack.stackPointer = 0;
+ cx->runtime()->geckoProfiler().stringsReset();
+ cx->runtime()->geckoProfiler().enableSlowAssertions(true);
+ js::EnableContextProfilingStack(cx, true);
+}
+
+static const JSClass ptestClass = {"Prof", 0};
+
+static bool test_fn(JSContext* cx, unsigned argc, JS::Value* vp) {
+ peakStackPointer = profilingStack.stackPointer;
+ return true;
+}
+
+static bool test_fn2(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::RootedValue r(cx);
+ JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
+ return JS_CallFunctionName(cx, global, "d", JS::HandleValueArray::empty(),
+ &r);
+}
+
+static bool enable(JSContext* cx, unsigned argc, JS::Value* vp) {
+ js::EnableContextProfilingStack(cx, true);
+ return true;
+}
+
+static bool disable(JSContext* cx, unsigned argc, JS::Value* vp) {
+ js::EnableContextProfilingStack(cx, false);
+ return true;
+}
+
+static bool Prof(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ JSObject* obj = JS_NewObjectForConstructor(cx, &ptestClass, args);
+ if (!obj) {
+ return false;
+ }
+ args.rval().setObject(*obj);
+ return true;
+}
+
+static const JSFunctionSpec ptestFunctions[] = {
+ JS_FN("test_fn", test_fn, 0, 0), JS_FN("test_fn2", test_fn2, 0, 0),
+ JS_FN("enable", enable, 0, 0), JS_FN("disable", disable, 0, 0), JS_FS_END};
+
+static JSObject* initialize(JSContext* cx) {
+ js::SetContextProfilingStack(cx, &profilingStack);
+ JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
+ return JS_InitClass(cx, global, nullptr, nullptr, "Prof", Prof, 0, nullptr,
+ ptestFunctions, nullptr, nullptr);
+}
+
+BEGIN_TEST(testProfileStrings_isCalledWithInterpreter) {
+ CHECK(initialize(cx));
+
+ EXEC("function g() { var p = new Prof(); p.test_fn(); }");
+ EXEC("function f() { g(); }");
+ EXEC("function e() { f(); }");
+ EXEC("function d() { e(); }");
+ EXEC("function c() { d(); }");
+ EXEC("function b() { c(); }");
+ EXEC("function a() { b(); }");
+ EXEC("function check() { var p = new Prof(); p.test_fn(); a(); }");
+ EXEC("function check2() { var p = new Prof(); p.test_fn2(); }");
+
+ reset(cx);
+ {
+ JS::RootedValue rval(cx);
+ /* Make sure the stack resets and we have an entry for each stack */
+ CHECK(JS_CallFunctionName(cx, global, "check",
+ JS::HandleValueArray::empty(), &rval));
+ CHECK(profilingStack.stackPointer == 0);
+ CHECK(peakStackPointer >= 8);
+ CHECK(cx->runtime()->geckoProfiler().stringsCount() == 8);
+ /* Make sure the stack resets and we added no new entries */
+ peakStackPointer = 0;
+ CHECK(JS_CallFunctionName(cx, global, "check",
+ JS::HandleValueArray::empty(), &rval));
+ CHECK(profilingStack.stackPointer == 0);
+ CHECK(peakStackPointer >= 8);
+ CHECK(cx->runtime()->geckoProfiler().stringsCount() == 8);
+ }
+ reset(cx);
+ {
+ JS::RootedValue rval(cx);
+ CHECK(JS_CallFunctionName(cx, global, "check2",
+ JS::HandleValueArray::empty(), &rval));
+ CHECK(cx->runtime()->geckoProfiler().stringsCount() == 5);
+ CHECK(peakStackPointer >= 6);
+ CHECK(profilingStack.stackPointer == 0);
+ }
+ return true;
+}
+END_TEST(testProfileStrings_isCalledWithInterpreter)
+
+BEGIN_TEST(testProfileStrings_isCalledWithJIT) {
+ CHECK(initialize(cx));
+
+ EXEC("function g() { var p = new Prof(); p.test_fn(); }");
+ EXEC("function f() { g(); }");
+ EXEC("function e() { f(); }");
+ EXEC("function d() { e(); }");
+ EXEC("function c() { d(); }");
+ EXEC("function b() { c(); }");
+ EXEC("function a() { b(); }");
+ EXEC("function check() { var p = new Prof(); p.test_fn(); a(); }");
+ EXEC("function check2() { var p = new Prof(); p.test_fn2(); }");
+
+ reset(cx);
+ {
+ JS::RootedValue rval(cx);
+ /* Make sure the stack resets and we have an entry for each stack */
+ CHECK(JS_CallFunctionName(cx, global, "check",
+ JS::HandleValueArray::empty(), &rval));
+ CHECK(profilingStack.stackPointer == 0);
+ CHECK(peakStackPointer >= 8);
+
+ /* Make sure the stack resets and we added no new entries */
+ uint32_t cnt = cx->runtime()->geckoProfiler().stringsCount();
+ peakStackPointer = 0;
+ CHECK(JS_CallFunctionName(cx, global, "check",
+ JS::HandleValueArray::empty(), &rval));
+ CHECK(profilingStack.stackPointer == 0);
+ CHECK(cx->runtime()->geckoProfiler().stringsCount() == cnt);
+ CHECK(peakStackPointer >= 8);
+ }
+
+ return true;
+}
+END_TEST(testProfileStrings_isCalledWithJIT)
+
+BEGIN_TEST(testProfileStrings_isCalledWhenError) {
+ CHECK(initialize(cx));
+
+ EXEC("function check2() { throw 'a'; }");
+
+ reset(cx);
+ {
+ JS::RootedValue rval(cx);
+ /* Make sure the stack resets and we have an entry for each stack */
+ bool ok = JS_CallFunctionName(cx, global, "check2",
+ JS::HandleValueArray::empty(), &rval);
+ CHECK(!ok);
+ CHECK(profilingStack.stackPointer == 0);
+ CHECK(cx->runtime()->geckoProfiler().stringsCount() == 1);
+
+ JS_ClearPendingException(cx);
+ }
+
+ return true;
+}
+END_TEST(testProfileStrings_isCalledWhenError)
+
+BEGIN_TEST(testProfileStrings_worksWhenEnabledOnTheFly) {
+ CHECK(initialize(cx));
+
+ EXEC("function b(p) { p.test_fn(); }");
+ EXEC("function a() { var p = new Prof(); p.enable(); b(p); }");
+ reset(cx);
+ js::EnableContextProfilingStack(cx, false);
+ {
+ /* enable it in the middle of JS and make sure things check out */
+ JS::RootedValue rval(cx);
+ JS_CallFunctionName(cx, global, "a", JS::HandleValueArray::empty(), &rval);
+ CHECK(profilingStack.stackPointer == 0);
+ CHECK(peakStackPointer >= 1);
+ CHECK(cx->runtime()->geckoProfiler().stringsCount() == 1);
+ }
+
+ EXEC("function d(p) { p.disable(); }");
+ EXEC("function c() { var p = new Prof(); d(p); }");
+ reset(cx);
+ {
+ /* now disable in the middle of js */
+ JS::RootedValue rval(cx);
+ JS_CallFunctionName(cx, global, "c", JS::HandleValueArray::empty(), &rval);
+ CHECK(profilingStack.stackPointer == 0);
+ }
+
+ EXEC("function e() { var p = new Prof(); d(p); p.enable(); b(p); }");
+ reset(cx);
+ {
+ /* now disable in the middle of js, but re-enable before final exit */
+ JS::RootedValue rval(cx);
+ JS_CallFunctionName(cx, global, "e", JS::HandleValueArray::empty(), &rval);
+ CHECK(profilingStack.stackPointer == 0);
+ CHECK(peakStackPointer >= 3);
+ }
+
+ EXEC("function h() { }");
+ EXEC("function g(p) { p.disable(); for (var i = 0; i < 100; i++) i++; }");
+ EXEC("function f() { g(new Prof()); }");
+ reset(cx);
+ cx->runtime()->geckoProfiler().enableSlowAssertions(false);
+ {
+ JS::RootedValue rval(cx);
+ /* disable, and make sure that if we try to re-enter the JIT the pop
+ * will still happen */
+ JS_CallFunctionName(cx, global, "f", JS::HandleValueArray::empty(), &rval);
+ CHECK(profilingStack.stackPointer == 0);
+ }
+ return true;
+}
+END_TEST(testProfileStrings_worksWhenEnabledOnTheFly)
diff --git a/js/src/jsapi-tests/testPromise.cpp b/js/src/jsapi-tests/testPromise.cpp
new file mode 100644
index 0000000000..2f9cf03048
--- /dev/null
+++ b/js/src/jsapi-tests/testPromise.cpp
@@ -0,0 +1,170 @@
+/* -*- 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 "jsapi.h"
+
+#include "jsapi-tests/tests.h"
+
+using namespace JS;
+
+static bool executor_called = false;
+
+static bool PromiseExecutor(JSContext* cx, unsigned argc, Value* vp) {
+#ifdef DEBUG
+ CallArgs args = CallArgsFromVp(argc, vp);
+#endif // DEBUG
+ MOZ_ASSERT(args.length() == 2);
+ MOZ_ASSERT(args[0].toObject().is<JSFunction>());
+ MOZ_ASSERT(args[1].toObject().is<JSFunction>());
+
+ executor_called = true;
+ return true;
+}
+
+static JSObject* CreatePromise(JSContext* cx) {
+ RootedFunction executor(
+ cx, JS_NewFunction(cx, PromiseExecutor, 2, 0, "executor"));
+ if (!executor) {
+ return nullptr;
+ }
+ return JS::NewPromiseObject(cx, executor);
+}
+
+BEGIN_TEST(testPromise_NewPromise) {
+ RootedObject promise(cx, CreatePromise(cx));
+ CHECK(promise);
+ CHECK(executor_called);
+
+ return true;
+}
+END_TEST(testPromise_NewPromise)
+
+BEGIN_TEST(testPromise_GetPromiseState) {
+ RootedObject promise(cx, CreatePromise(cx));
+ if (!promise) {
+ return false;
+ }
+
+ CHECK(JS::GetPromiseState(promise) == JS::PromiseState::Pending);
+
+ return true;
+}
+END_TEST(testPromise_GetPromiseState)
+
+BEGIN_TEST(testPromise_ResolvePromise) {
+ RootedObject promise(cx, CreatePromise(cx));
+ if (!promise) {
+ return false;
+ }
+
+ RootedValue result(cx);
+ result.setInt32(42);
+ JS::ResolvePromise(cx, promise, result);
+
+ CHECK(JS::GetPromiseState(promise) == JS::PromiseState::Fulfilled);
+
+ return true;
+}
+END_TEST(testPromise_ResolvePromise)
+
+BEGIN_TEST(testPromise_RejectPromise) {
+ RootedObject promise(cx, CreatePromise(cx));
+ if (!promise) {
+ return false;
+ }
+
+ RootedValue result(cx);
+ result.setInt32(42);
+ JS::RejectPromise(cx, promise, result);
+
+ CHECK(JS::GetPromiseState(promise) == JS::PromiseState::Rejected);
+
+ return true;
+}
+END_TEST(testPromise_RejectPromise)
+
+static bool thenHandler_called = false;
+
+static bool PromiseThenHandler(JSContext* cx, unsigned argc, Value* vp) {
+#ifdef DEBUG
+ CallArgs args = CallArgsFromVp(argc, vp);
+#endif // DEBUG
+ MOZ_ASSERT(args.length() == 1);
+
+ thenHandler_called = true;
+ return true;
+}
+
+static bool catchHandler_called = false;
+
+static bool PromiseCatchHandler(JSContext* cx, unsigned argc, Value* vp) {
+#ifdef DEBUG
+ CallArgs args = CallArgsFromVp(argc, vp);
+#endif // DEBUG
+ MOZ_ASSERT(args.length() == 1);
+
+ catchHandler_called = true;
+ return true;
+}
+
+BEGIN_TEST(testPromise_PromiseThen) {
+ RootedObject promise(cx, CreatePromise(cx));
+ if (!promise) {
+ return false;
+ }
+
+ RootedFunction thenHandler(
+ cx, JS_NewFunction(cx, PromiseThenHandler, 1, 0, "thenHandler"));
+ if (!thenHandler) {
+ return false;
+ }
+ RootedFunction catchHandler(
+ cx, JS_NewFunction(cx, PromiseCatchHandler, 1, 0, "catchHandler"));
+ if (!catchHandler) {
+ return false;
+ }
+ JS::AddPromiseReactions(cx, promise, thenHandler, catchHandler);
+
+ RootedValue result(cx);
+ result.setInt32(42);
+ JS::ResolvePromise(cx, promise, result);
+ js::RunJobs(cx);
+
+ CHECK(thenHandler_called);
+
+ return true;
+}
+END_TEST(testPromise_PromiseThen)
+
+BEGIN_TEST(testPromise_PromiseCatch) {
+ RootedObject promise(cx, CreatePromise(cx));
+ if (!promise) {
+ return false;
+ }
+
+ RootedFunction thenHandler(
+ cx, JS_NewFunction(cx, PromiseThenHandler, 1, 0, "thenHandler"));
+ if (!thenHandler) {
+ return false;
+ }
+ RootedFunction catchHandler(
+ cx, JS_NewFunction(cx, PromiseCatchHandler, 1, 0, "catchHandler"));
+ if (!catchHandler) {
+ return false;
+ }
+ JS::AddPromiseReactions(cx, promise, thenHandler, catchHandler);
+
+ RootedValue result(cx);
+ result.setInt32(42);
+ JS::RejectPromise(cx, promise, result);
+ js::RunJobs(cx);
+
+ CHECK(catchHandler_called);
+
+ return true;
+}
+END_TEST(testPromise_PromiseCatch)
diff --git a/js/src/jsapi-tests/testPropCache.cpp b/js/src/jsapi-tests/testPropCache.cpp
new file mode 100644
index 0000000000..90300e6090
--- /dev/null
+++ b/js/src/jsapi-tests/testPropCache.cpp
@@ -0,0 +1,47 @@
+/* -*- 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 "js/PropertyAndElement.h" // JS_DefineObject
+#include "jsapi-tests/tests.h"
+
+static int g_counter;
+
+static bool CounterAdd(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
+ JS::HandleValue v) {
+ g_counter++;
+ return true;
+}
+
+static const JSClassOps CounterClassOps = {
+ CounterAdd, // addProperty
+ nullptr, // delProperty
+ nullptr, // enumerate
+ nullptr, // newEnumerate
+ nullptr, // resolve
+ nullptr, // mayResolve
+ nullptr, // finalize
+ nullptr, // call
+ nullptr, // construct
+ nullptr, // trace
+};
+
+static const JSClass CounterClass = {"Counter", /* name */
+ 0, /* flags */
+ &CounterClassOps};
+
+BEGIN_TEST(testPropCache_bug505798) {
+ g_counter = 0;
+ EXEC("var x = {};");
+ CHECK(JS_DefineObject(cx, global, "y", &CounterClass, JSPROP_ENUMERATE));
+ EXEC(
+ "var arr = [x, y];\n"
+ "for (var i = 0; i < arr.length; i++)\n"
+ " arr[i].p = 1;\n");
+ CHECK_EQUAL(g_counter, 1);
+ return true;
+}
+END_TEST(testPropCache_bug505798)
diff --git a/js/src/jsapi-tests/testPropertyKey.cpp b/js/src/jsapi-tests/testPropertyKey.cpp
new file mode 100644
index 0000000000..aed210a658
--- /dev/null
+++ b/js/src/jsapi-tests/testPropertyKey.cpp
@@ -0,0 +1,69 @@
+/* -*- 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 "js/Id.h" // JS::PropertyKey, JS::GetWellKnownSymbolKey, JS::ToGetterId, JS::ToSetterId
+#include "js/String.h" // JSString, JS_AtomizeString, JS_StringEqualsAscii
+#include "js/Symbol.h" // JS::Symbol, JS::SymbolCode
+#include "jsapi-tests/tests.h"
+
+BEGIN_TEST(testPropertyKeyGetterAndSetter) {
+ JS::Rooted<JSString*> str(cx, JS_AtomizeString(cx, "prop"));
+ CHECK(str);
+
+ JS::Rooted<JS::PropertyKey> strId(cx, JS::PropertyKey::NonIntAtom(str));
+ MOZ_ASSERT(strId.isString());
+
+ JS::Rooted<JS::PropertyKey> symId(
+ cx, JS::GetWellKnownSymbolKey(cx, JS::SymbolCode::iterator));
+ MOZ_ASSERT(symId.isSymbol());
+
+ JS::Rooted<JS::PropertyKey> numId(cx, JS::PropertyKey::Int(42));
+ MOZ_ASSERT(numId.isInt());
+
+ bool match;
+
+ JS::Rooted<JS::PropertyKey> strGetterId(cx);
+ CHECK(JS::ToGetterId(cx, strId, &strGetterId));
+ CHECK(strGetterId.isString());
+ CHECK(JS_StringEqualsAscii(cx, strGetterId.toString(), "get prop", &match));
+ CHECK(match);
+
+ JS::Rooted<JS::PropertyKey> strSetterId(cx);
+ CHECK(JS::ToSetterId(cx, strId, &strSetterId));
+ CHECK(strSetterId.isString());
+ CHECK(JS_StringEqualsAscii(cx, strSetterId.toString(), "set prop", &match));
+ CHECK(match);
+
+ JS::Rooted<JS::PropertyKey> symGetterId(cx);
+ CHECK(JS::ToGetterId(cx, symId, &symGetterId));
+ CHECK(symGetterId.isString());
+ CHECK(JS_StringEqualsAscii(cx, symGetterId.toString(),
+ "get [Symbol.iterator]", &match));
+ CHECK(match);
+
+ JS::Rooted<JS::PropertyKey> symSetterId(cx);
+ CHECK(JS::ToSetterId(cx, symId, &symSetterId));
+ CHECK(symSetterId.isString());
+ CHECK(JS_StringEqualsAscii(cx, symSetterId.toString(),
+ "set [Symbol.iterator]", &match));
+ CHECK(match);
+
+ JS::Rooted<JS::PropertyKey> numGetterId(cx);
+ CHECK(JS::ToGetterId(cx, numId, &numGetterId));
+ CHECK(numGetterId.isString());
+ CHECK(JS_StringEqualsAscii(cx, numGetterId.toString(), "get 42", &match));
+ CHECK(match);
+
+ JS::Rooted<JS::PropertyKey> numSetterId(cx);
+ CHECK(JS::ToSetterId(cx, numId, &numSetterId));
+ CHECK(numSetterId.isString());
+ CHECK(JS_StringEqualsAscii(cx, numSetterId.toString(), "set 42", &match));
+ CHECK(match);
+
+ return true;
+}
+END_TEST(testPropertyKeyGetterAndSetter)
diff --git a/js/src/jsapi-tests/testRecordTupleToSource.cpp b/js/src/jsapi-tests/testRecordTupleToSource.cpp
new file mode 100644
index 0000000000..5fecf9100a
--- /dev/null
+++ b/js/src/jsapi-tests/testRecordTupleToSource.cpp
@@ -0,0 +1,45 @@
+/* -*- 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/. */
+
+#ifdef ENABLE_RECORD_TUPLE
+
+# include <string>
+
+# include "jsfriendapi.h"
+
+# include "jsapi-tests/tests.h"
+
+static const struct TestTuple {
+ const char* expr_string;
+ const char* expected;
+} tests[] = {
+ {"#[]", "#[]"},
+ {"#[1, 2, 3]", "#[1, 2, 3]"},
+ {"#{}", "#{}"},
+ {"#{\"a\": 1, \"b\": \"c\", \"c\": #[]}",
+ "#{\"a\": 1, \"b\": \"c\", \"c\": #[]}"},
+ {"Object(#[])", "#[]"},
+ {"Object(#[1, 2, 3])", "#[1, 2, 3]"},
+ {"Object(#{})", "#{}"},
+ {"Object(#{\"a\": 1, \"b\": \"c\", \"c\": #[]})",
+ "#{\"a\": 1, \"b\": \"c\", \"c\": #[]}"},
+};
+
+BEGIN_TEST(testRecordTupleToSource) {
+ JS::Rooted<JS::Value> result(cx);
+ for (const auto& test : tests) {
+ EVAL(test.expr_string, &result);
+ JSLinearString* prettyPrinted =
+ JS_ASSERT_STRING_IS_LINEAR(JS_ValueToSource(cx, result));
+ CHECK(JS_LinearStringEqualsAscii(prettyPrinted, test.expected));
+ }
+
+ return true;
+}
+END_TEST(testRecordTupleToSource)
+
+#endif
diff --git a/js/src/jsapi-tests/testRegExp.cpp b/js/src/jsapi-tests/testRegExp.cpp
new file mode 100644
index 0000000000..9a2b6f665f
--- /dev/null
+++ b/js/src/jsapi-tests/testRegExp.cpp
@@ -0,0 +1,65 @@
+/* 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 "js/RegExp.h"
+#include "js/RegExpFlags.h"
+#include "jsapi-tests/tests.h"
+
+BEGIN_TEST(testObjectIsRegExp) {
+ JS::RootedValue val(cx);
+
+ bool isRegExp;
+
+ EVAL("new Object", &val);
+ JS::RootedObject obj(cx, val.toObjectOrNull());
+ CHECK(JS::ObjectIsRegExp(cx, obj, &isRegExp));
+ CHECK(!isRegExp);
+
+ EVAL("/foopy/", &val);
+ obj = val.toObjectOrNull();
+ CHECK(JS::ObjectIsRegExp(cx, obj, &isRegExp));
+ CHECK(isRegExp);
+
+ return true;
+}
+END_TEST(testObjectIsRegExp)
+
+BEGIN_TEST(testGetRegExpFlags) {
+ JS::RootedValue val(cx);
+ JS::RootedObject obj(cx);
+
+ EVAL("/foopy/", &val);
+ obj = val.toObjectOrNull();
+ CHECK_EQUAL(JS::GetRegExpFlags(cx, obj),
+ JS::RegExpFlags(JS::RegExpFlag::NoFlags));
+
+ EVAL("/foopy/g", &val);
+ obj = val.toObjectOrNull();
+ CHECK_EQUAL(JS::GetRegExpFlags(cx, obj),
+ JS::RegExpFlags(JS::RegExpFlag::Global));
+
+ EVAL("/foopy/gi", &val);
+ obj = val.toObjectOrNull();
+ CHECK_EQUAL(
+ JS::GetRegExpFlags(cx, obj),
+ JS::RegExpFlags(JS::RegExpFlag::Global | JS::RegExpFlag::IgnoreCase));
+
+ return true;
+}
+END_TEST(testGetRegExpFlags)
+
+BEGIN_TEST(testGetRegExpSource) {
+ JS::RootedValue val(cx);
+ JS::RootedObject obj(cx);
+
+ EVAL("/foopy/", &val);
+ obj = val.toObjectOrNull();
+ JSString* source = JS::GetRegExpSource(cx, obj);
+ CHECK(source);
+ CHECK(JS_LinearStringEqualsLiteral(JS_ASSERT_STRING_IS_LINEAR(source),
+ "foopy"));
+
+ return true;
+}
+END_TEST(testGetRegExpSource)
diff --git a/js/src/jsapi-tests/testResolveRecursion.cpp b/js/src/jsapi-tests/testResolveRecursion.cpp
new file mode 100644
index 0000000000..44b5cd35a3
--- /dev/null
+++ b/js/src/jsapi-tests/testResolveRecursion.cpp
@@ -0,0 +1,184 @@
+/* -*- 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 "js/Object.h" // JS::GetReservedSlot, JS::SetReservedSlot
+#include "js/PropertyAndElement.h" // JS_DefineProperty, JS_DefinePropertyById
+#include "jsapi-tests/tests.h"
+
+/*
+ * Test that resolve hook recursion for the same object and property is
+ * prevented.
+ */
+BEGIN_TEST(testResolveRecursion) {
+ static const JSClassOps my_resolve_classOps = {
+ nullptr, // addProperty
+ nullptr, // delProperty
+ nullptr, // enumerate
+ nullptr, // newEnumerate
+ my_resolve, // resolve
+ nullptr, // mayResolve
+ nullptr, // finalize
+ nullptr, // call
+ nullptr, // construct
+ nullptr, // trace
+ };
+
+ static const JSClass my_resolve_class = {
+ "MyResolve", JSCLASS_HAS_RESERVED_SLOTS(SlotCount), &my_resolve_classOps};
+
+ obj1.init(cx, JS_NewObject(cx, &my_resolve_class));
+ CHECK(obj1);
+ obj2.init(cx, JS_NewObject(cx, &my_resolve_class));
+ CHECK(obj2);
+ JS::SetReservedSlot(obj1, TestSlot, JS::PrivateValue(this));
+ JS::SetReservedSlot(obj2, TestSlot, JS::PrivateValue(this));
+
+ JS::RootedValue obj1Val(cx, JS::ObjectValue(*obj1));
+ JS::RootedValue obj2Val(cx, JS::ObjectValue(*obj2));
+ CHECK(JS_DefineProperty(cx, global, "obj1", obj1Val, 0));
+ CHECK(JS_DefineProperty(cx, global, "obj2", obj2Val, 0));
+
+ resolveEntryCount = 0;
+ resolveExitCount = 0;
+
+ /* Start the essence of the test via invoking the first resolve hook. */
+ JS::RootedValue v(cx);
+ EVAL("obj1.x", &v);
+ CHECK(v.isFalse());
+ CHECK_EQUAL(resolveEntryCount, 4);
+ CHECK_EQUAL(resolveExitCount, 4);
+
+ obj1 = nullptr;
+ obj2 = nullptr;
+ return true;
+}
+
+enum Slots { TestSlot, SlotCount };
+
+JS::PersistentRootedObject obj1;
+JS::PersistentRootedObject obj2;
+int resolveEntryCount;
+int resolveExitCount;
+
+struct AutoIncrCounters {
+ explicit AutoIncrCounters(cls_testResolveRecursion* t) : t(t) {
+ t->resolveEntryCount++;
+ }
+
+ ~AutoIncrCounters() { t->resolveExitCount++; }
+
+ cls_testResolveRecursion* t;
+};
+
+bool doResolve(JS::HandleObject obj, JS::HandleId id, bool* resolvedp) {
+ CHECK_EQUAL(resolveExitCount, 0);
+ AutoIncrCounters incr(this);
+ CHECK(obj == obj1 || obj == obj2);
+
+ CHECK(id.isString());
+
+ JSLinearString* str = JS_EnsureLinearString(cx, id.toString());
+ CHECK(str);
+ JS::RootedValue v(cx);
+ if (JS_LinearStringEqualsLiteral(str, "x")) {
+ if (obj == obj1) {
+ /* First resolve hook invocation. */
+ CHECK_EQUAL(resolveEntryCount, 1);
+ EVAL("obj2.y = true", &v);
+ CHECK(v.isTrue());
+ CHECK(JS_DefinePropertyById(cx, obj, id, JS::FalseHandleValue,
+ JSPROP_RESOLVING));
+ *resolvedp = true;
+ return true;
+ }
+ if (obj == obj2) {
+ CHECK_EQUAL(resolveEntryCount, 4);
+ *resolvedp = false;
+ return true;
+ }
+ } else if (JS_LinearStringEqualsLiteral(str, "y")) {
+ if (obj == obj2) {
+ CHECK_EQUAL(resolveEntryCount, 2);
+ CHECK(JS_DefinePropertyById(cx, obj, id, JS::NullHandleValue,
+ JSPROP_RESOLVING));
+ EVAL("obj1.x", &v);
+ CHECK(v.isUndefined());
+ EVAL("obj1.y", &v);
+ CHECK(v.isInt32(0));
+ *resolvedp = true;
+ return true;
+ }
+ if (obj == obj1) {
+ CHECK_EQUAL(resolveEntryCount, 3);
+ EVAL("obj1.x", &v);
+ CHECK(v.isUndefined());
+ EVAL("obj1.y", &v);
+ CHECK(v.isUndefined());
+ EVAL("obj2.y", &v);
+ CHECK(v.isNull());
+ EVAL("obj2.x", &v);
+ CHECK(v.isUndefined());
+ EVAL("obj1.y = 0", &v);
+ CHECK(v.isInt32(0));
+ *resolvedp = true;
+ return true;
+ }
+ }
+ CHECK(false);
+ return false;
+}
+
+static bool my_resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
+ bool* resolvedp) {
+ void* p = JS::GetReservedSlot(obj, TestSlot).toPrivate();
+ return static_cast<cls_testResolveRecursion*>(p)->doResolve(obj, id,
+ resolvedp);
+}
+END_TEST(testResolveRecursion)
+
+/*
+ * Test that JS_InitStandardClasses does not cause resolve hooks to be called.
+ *
+ * (XPConnect apparently does have global classes, such as the one created by
+ * nsMessageManagerScriptExecutor::InitChildGlobalInternal(), that have resolve
+ * hooks which can call back into JS, and on which JS_InitStandardClasses is
+ * called. Calling back into JS in the middle of resolving `undefined` is bad.)
+ */
+BEGIN_TEST(testResolveRecursion_InitStandardClasses) {
+ CHECK(JS::InitRealmStandardClasses(cx));
+ return true;
+}
+
+const JSClass* getGlobalClass() override {
+ static const JSClassOps myGlobalClassOps = {
+ nullptr, // addProperty
+ nullptr, // delProperty
+ nullptr, // enumerate
+ nullptr, // newEnumerate
+ my_resolve, // resolve
+ nullptr, // mayResolve
+ nullptr, // finalize
+ nullptr, // call
+ nullptr, // construct
+ JS_GlobalObjectTraceHook, // trace
+ };
+
+ static const JSClass myGlobalClass = {
+ "testResolveRecursion_InitStandardClasses_myGlobalClass",
+ JSCLASS_GLOBAL_FLAGS, &myGlobalClassOps};
+
+ return &myGlobalClass;
+}
+
+static bool my_resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
+ bool* resolvedp) {
+ MOZ_ASSERT_UNREACHABLE(
+ "resolve hook should not be called from InitStandardClasses");
+ JS_ReportErrorASCII(cx, "FAIL");
+ return false;
+}
+END_TEST(testResolveRecursion_InitStandardClasses)
diff --git a/js/src/jsapi-tests/testResult.cpp b/js/src/jsapi-tests/testResult.cpp
new file mode 100644
index 0000000000..5d8d0f88ea
--- /dev/null
+++ b/js/src/jsapi-tests/testResult.cpp
@@ -0,0 +1,98 @@
+/* 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 "jsapi.h" // JS_NewPlainObject
+#include "js/Result.h" // Error, Ok, Result
+#include "js/String.h" // JS_NewStringCopyZ
+#include "jsapi-tests/tests.h"
+
+JS::Result<> SimpleSuccess(JSContext*) { return JS::Ok(); }
+
+JS::Result<> SimpleFailure(JSContext*) { return JS::Result<>{JS::Error()}; }
+
+JS::Result<int> PODSuccess(JSContext*) { return 42; }
+
+JS::Result<int> PODFailure(JSContext*) { return JS::Result<int>{JS::Error()}; }
+
+JS::Result<JSObject*> ObjectSuccess(JSContext* cx) {
+ JS::RootedObject obj{cx, JS_NewPlainObject(cx)};
+ MOZ_RELEASE_ASSERT(obj);
+ return obj.get();
+}
+
+JS::Result<JSObject*> ObjectFailure(JSContext*) {
+ return JS::Result<JSObject*>{JS::Error()};
+}
+
+JS::Result<JSString*> StringSuccess(JSContext* cx) {
+ JS::RootedString str{cx, JS_NewStringCopyZ(cx, "foo")};
+ MOZ_RELEASE_ASSERT(str);
+ return str.get();
+}
+
+JS::Result<JSString*> StringFailure(JSContext*) {
+ return JS::Result<JSString*>{JS::Error()};
+}
+
+BEGIN_TEST(testResult_SimpleSuccess) {
+ JS::Result<> result = SimpleSuccess(cx);
+ CHECK(result.isOk());
+ return true;
+}
+END_TEST(testResult_SimpleSuccess)
+
+BEGIN_TEST(testResult_SimpleFailure) {
+ JS::Result<> result = SimpleFailure(cx);
+ CHECK(result.isErr());
+ return true;
+}
+END_TEST(testResult_SimpleFailure)
+
+BEGIN_TEST(testResult_PODSuccess) {
+ JS::Result<int> result = PODSuccess(cx);
+ CHECK(result.isOk());
+ CHECK_EQUAL(result.unwrap(), 42);
+ CHECK_EQUAL(result.inspect(), 42);
+ return true;
+}
+END_TEST(testResult_PODSuccess)
+
+BEGIN_TEST(testResult_PODFailure) {
+ JS::Result<int> result = PODFailure(cx);
+ CHECK(result.isErr());
+ return true;
+}
+END_TEST(testResult_PODFailure)
+
+BEGIN_TEST(testResult_ObjectSuccess) {
+ JS::Result<JSObject*> result = ObjectSuccess(cx);
+ CHECK(result.isOk());
+ CHECK(result.inspect() != nullptr);
+ CHECK(result.unwrap() != nullptr);
+ return true;
+}
+END_TEST(testResult_ObjectSuccess)
+
+BEGIN_TEST(testResult_ObjectFailure) {
+ JS::Result<JSObject*> result = ObjectFailure(cx);
+ CHECK(result.isErr());
+ return true;
+}
+END_TEST(testResult_ObjectFailure)
+
+BEGIN_TEST(testResult_StringSuccess) {
+ JS::Result<JSString*> result = StringSuccess(cx);
+ CHECK(result.isOk());
+ CHECK(result.inspect() != nullptr);
+ CHECK(result.unwrap() != nullptr);
+ return true;
+}
+END_TEST(testResult_StringSuccess)
+
+BEGIN_TEST(testResult_StringFailure) {
+ JS::Result<JSString*> result = StringFailure(cx);
+ CHECK(result.isErr());
+ return true;
+}
+END_TEST(testResult_StringFailure)
diff --git a/js/src/jsapi-tests/testSABAccounting.cpp b/js/src/jsapi-tests/testSABAccounting.cpp
new file mode 100644
index 0000000000..38607bc0aa
--- /dev/null
+++ b/js/src/jsapi-tests/testSABAccounting.cpp
@@ -0,0 +1,31 @@
+#include "builtin/TestingFunctions.h"
+#include "js/SharedArrayBuffer.h"
+#include "jsapi-tests/tests.h"
+
+BEGIN_TEST(testSABAccounting) {
+ // Purge what we can
+ JS::PrepareForFullGC(cx);
+ NonIncrementalGC(cx, JS::GCOptions::Shrink, JS::GCReason::API);
+
+ // Self-hosting and chrome code should not use SABs, or the point of this
+ // predicate is completely lost.
+ CHECK(!JS::ContainsSharedArrayBuffer(cx));
+
+ JS::RootedObject obj(cx), obj2(cx);
+ CHECK(obj = JS::NewSharedArrayBuffer(cx, 4096));
+ CHECK(JS::ContainsSharedArrayBuffer(cx));
+ CHECK(obj2 = JS::NewSharedArrayBuffer(cx, 4096));
+ CHECK(JS::ContainsSharedArrayBuffer(cx));
+
+ // Discard those objects again.
+ obj = nullptr;
+ obj2 = nullptr;
+ JS::PrepareForFullGC(cx);
+ NonIncrementalGC(cx, JS::GCOptions::Shrink, JS::GCReason::API);
+
+ // Should be back to base state.
+ CHECK(!JS::ContainsSharedArrayBuffer(cx));
+
+ return true;
+}
+END_TEST(testSABAccounting)
diff --git a/js/src/jsapi-tests/testSameValue.cpp b/js/src/jsapi-tests/testSameValue.cpp
new file mode 100644
index 0000000000..c2e12207f2
--- /dev/null
+++ b/js/src/jsapi-tests/testSameValue.cpp
@@ -0,0 +1,26 @@
+/* -*- 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 "js/Equality.h" // JS::SameValue
+#include "jsapi-tests/tests.h"
+
+BEGIN_TEST(testSameValue) {
+ /*
+ * NB: passing a double that fits in an integer jsval is API misuse. As a
+ * matter of defense in depth, however, JS::SameValue should return the
+ * correct result comparing a positive-zero double to a negative-zero
+ * double, and this is believed to be the only way to make such a
+ * comparison possible.
+ */
+ JS::RootedValue v1(cx, JS::DoubleValue(0.0));
+ JS::RootedValue v2(cx, JS::DoubleValue(-0.0));
+ bool same;
+ CHECK(JS::SameValue(cx, v1, v2, &same));
+ CHECK(!same);
+ return true;
+}
+END_TEST(testSameValue)
diff --git a/js/src/jsapi-tests/testSavedStacks.cpp b/js/src/jsapi-tests/testSavedStacks.cpp
new file mode 100644
index 0000000000..e99746ec2f
--- /dev/null
+++ b/js/src/jsapi-tests/testSavedStacks.cpp
@@ -0,0 +1,396 @@
+/* -*- 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/Utf8.h" // mozilla::Utf8Unit
+
+#include "builtin/TestingFunctions.h"
+#include "js/CompilationAndEvaluation.h" // JS::Evaluate
+#include "js/Exception.h"
+#include "js/SavedFrameAPI.h"
+#include "js/SourceText.h" // JS::Source{Ownership,Text}
+#include "js/Stack.h"
+#include "jsapi-tests/tests.h"
+#include "util/Text.h"
+#include "vm/ArrayObject.h"
+#include "vm/Realm.h"
+#include "vm/SavedStacks.h"
+
+BEGIN_TEST(testSavedStacks_withNoStack) {
+ JS::Realm* realm = cx->realm();
+ realm->setAllocationMetadataBuilder(&js::SavedStacks::metadataBuilder);
+ JS::RootedObject obj(cx, js::NewDenseEmptyArray(cx));
+ realm->setAllocationMetadataBuilder(nullptr);
+ return true;
+}
+END_TEST(testSavedStacks_withNoStack)
+
+BEGIN_TEST(testSavedStacks_ApiDefaultValues) {
+ JS::Rooted<js::SavedFrame*> savedFrame(cx, nullptr);
+
+ JSPrincipals* principals = cx->realm()->principals();
+
+ // Source
+ JS::RootedString str(cx);
+ JS::SavedFrameResult result =
+ JS::GetSavedFrameSource(cx, principals, savedFrame, &str);
+ CHECK(result == JS::SavedFrameResult::AccessDenied);
+ CHECK(str.get() == cx->runtime()->emptyString);
+
+ // Line
+ uint32_t line = 123;
+ result = JS::GetSavedFrameLine(cx, principals, savedFrame, &line);
+ CHECK(result == JS::SavedFrameResult::AccessDenied);
+ CHECK(line == 0);
+
+ // Column
+ uint32_t column = 123;
+ result = JS::GetSavedFrameColumn(cx, principals, savedFrame, &column);
+ CHECK(result == JS::SavedFrameResult::AccessDenied);
+ CHECK(column == 0);
+
+ // Function display name
+ result =
+ JS::GetSavedFrameFunctionDisplayName(cx, principals, savedFrame, &str);
+ CHECK(result == JS::SavedFrameResult::AccessDenied);
+ CHECK(str.get() == nullptr);
+
+ // Parent
+ JS::RootedObject parent(cx);
+ result = JS::GetSavedFrameParent(cx, principals, savedFrame, &parent);
+ CHECK(result == JS::SavedFrameResult::AccessDenied);
+ CHECK(parent.get() == nullptr);
+
+ // Stack string
+ CHECK(JS::BuildStackString(cx, principals, savedFrame, &str));
+ CHECK(str.get() == cx->runtime()->emptyString);
+
+ return true;
+}
+END_TEST(testSavedStacks_ApiDefaultValues)
+
+BEGIN_TEST(testSavedStacks_RangeBasedForLoops) {
+ CHECK(js::DefineTestingFunctions(cx, global, false, false));
+
+ JS::RootedValue val(cx);
+ CHECK(
+ evaluate("(function one() { \n" // 1
+ " return (function two() { \n" // 2
+ " return (function three() { \n" // 3
+ " return saveStack(); \n" // 4
+ " }()); \n" // 5
+ " }()); \n" // 6
+ "}()); \n", // 7
+ "filename.js", 1, &val));
+
+ CHECK(val.isObject());
+ JS::RootedObject obj(cx, &val.toObject());
+
+ CHECK(obj->is<js::SavedFrame>());
+ JS::Rooted<js::SavedFrame*> savedFrame(cx, &obj->as<js::SavedFrame>());
+
+ JS::Rooted<js::SavedFrame*> rf(cx, savedFrame);
+ for (JS::Handle<js::SavedFrame*> frame :
+ js::SavedFrame::RootedRange(cx, rf)) {
+ JS_GC(cx);
+ CHECK(frame == rf);
+ rf = rf->getParent();
+ }
+ CHECK(rf == nullptr);
+
+ // Stack string
+ static const char SpiderMonkeyStack[] =
+ "three@filename.js:4:14\n"
+ "two@filename.js:5:6\n"
+ "one@filename.js:6:4\n"
+ "@filename.js:7:2\n";
+ static const char V8Stack[] =
+ " at three (filename.js:4:14)\n"
+ " at two (filename.js:5:6)\n"
+ " at one (filename.js:6:4)\n"
+ " at filename.js:7:2";
+ struct {
+ js::StackFormat format;
+ const char* expected;
+ } expectations[] = {{js::StackFormat::Default, SpiderMonkeyStack},
+ {js::StackFormat::SpiderMonkey, SpiderMonkeyStack},
+ {js::StackFormat::V8, V8Stack}};
+ auto CheckStacks = [&]() {
+ for (auto& expectation : expectations) {
+ JS::RootedString str(cx);
+ JSPrincipals* principals = cx->realm()->principals();
+ CHECK(JS::BuildStackString(cx, principals, savedFrame, &str, 0,
+ expectation.format));
+ JSLinearString* lin = str->ensureLinear(cx);
+ CHECK(lin);
+ CHECK(js::StringEqualsAscii(lin, expectation.expected));
+ }
+ return true;
+ };
+
+ CHECK(CheckStacks());
+
+ js::SetStackFormat(cx, js::StackFormat::V8);
+ expectations[0].expected = V8Stack;
+
+ CHECK(CheckStacks());
+
+ return true;
+}
+END_TEST(testSavedStacks_RangeBasedForLoops)
+
+BEGIN_TEST(testSavedStacks_ErrorStackSpiderMonkey) {
+ JS::RootedValue val(cx);
+ CHECK(
+ evaluate("(function one() { \n" // 1
+ " return (function two() { \n" // 2
+ " return (function three() { \n" // 3
+ " return new Error('foo'); \n" // 4
+ " }()); \n" // 5
+ " }()); \n" // 6
+ "}()).stack \n", // 7
+ "filename.js", 1, &val));
+
+ CHECK(val.isString());
+ JS::RootedString stack(cx, val.toString());
+
+ // Stack string
+ static const char SpiderMonkeyStack[] =
+ "three@filename.js:4:14\n"
+ "two@filename.js:5:6\n"
+ "one@filename.js:6:4\n"
+ "@filename.js:7:2\n";
+ JSLinearString* lin = stack->ensureLinear(cx);
+ CHECK(lin);
+ CHECK(js::StringEqualsLiteral(lin, SpiderMonkeyStack));
+
+ return true;
+}
+END_TEST(testSavedStacks_ErrorStackSpiderMonkey)
+
+BEGIN_TEST(testSavedStacks_ErrorStackV8) {
+ js::SetStackFormat(cx, js::StackFormat::V8);
+
+ JS::RootedValue val(cx);
+ CHECK(
+ evaluate("(function one() { \n" // 1
+ " return (function two() { \n" // 2
+ " return (function three() { \n" // 3
+ " return new Error('foo'); \n" // 4
+ " }()); \n" // 5
+ " }()); \n" // 6
+ "}()).stack \n", // 7
+ "filename.js", 1, &val));
+
+ CHECK(val.isString());
+ JS::RootedString stack(cx, val.toString());
+
+ // Stack string
+ static const char V8Stack[] =
+ "Error: foo\n"
+ " at three (filename.js:4:14)\n"
+ " at two (filename.js:5:6)\n"
+ " at one (filename.js:6:4)\n"
+ " at filename.js:7:2";
+ JSLinearString* lin = stack->ensureLinear(cx);
+ CHECK(lin);
+ CHECK(js::StringEqualsLiteral(lin, V8Stack));
+
+ return true;
+}
+END_TEST(testSavedStacks_ErrorStackV8)
+
+BEGIN_TEST(testSavedStacks_selfHostedFrames) {
+ CHECK(js::DefineTestingFunctions(cx, global, false, false));
+
+ JS::RootedValue val(cx);
+ // 0 1 2 3
+ // 0123456789012345678901234567890123456789
+ CHECK(
+ evaluate("(function one() { \n" // 1
+ " try { \n" // 2
+ " [1].map(function two() { \n" // 3
+ " throw saveStack(); \n" // 4
+ " }); \n" // 5
+ " } catch (stack) { \n" // 6
+ " return stack; \n" // 7
+ " } \n" // 8
+ "}()) \n", // 9
+ "filename.js", 1, &val));
+
+ CHECK(val.isObject());
+ JS::RootedObject obj(cx, &val.toObject());
+
+ CHECK(obj->is<js::SavedFrame>());
+ JS::Rooted<js::SavedFrame*> savedFrame(cx, &obj->as<js::SavedFrame>());
+
+ JS::Rooted<js::SavedFrame*> selfHostedFrame(cx, savedFrame->getParent());
+ CHECK(selfHostedFrame->isSelfHosted(cx));
+
+ JSPrincipals* principals = cx->realm()->principals();
+
+ // Source
+ JS::RootedString str(cx);
+ JS::SavedFrameResult result = JS::GetSavedFrameSource(
+ cx, principals, selfHostedFrame, &str, JS::SavedFrameSelfHosted::Exclude);
+ CHECK(result == JS::SavedFrameResult::Ok);
+ JSLinearString* lin = str->ensureLinear(cx);
+ CHECK(lin);
+ CHECK(js::StringEqualsLiteral(lin, "filename.js"));
+
+ // Source, including self-hosted frames
+ result = JS::GetSavedFrameSource(cx, principals, selfHostedFrame, &str,
+ JS::SavedFrameSelfHosted::Include);
+ CHECK(result == JS::SavedFrameResult::Ok);
+ lin = str->ensureLinear(cx);
+ CHECK(lin);
+ CHECK(js::StringEqualsLiteral(lin, "self-hosted"));
+
+ // Line
+ uint32_t line = 123;
+ result = JS::GetSavedFrameLine(cx, principals, selfHostedFrame, &line,
+ JS::SavedFrameSelfHosted::Exclude);
+ CHECK(result == JS::SavedFrameResult::Ok);
+ CHECK_EQUAL(line, 3U);
+
+ // Column
+ uint32_t column = 123;
+ result = JS::GetSavedFrameColumn(cx, principals, selfHostedFrame, &column,
+ JS::SavedFrameSelfHosted::Exclude);
+ CHECK(result == JS::SavedFrameResult::Ok);
+ CHECK_EQUAL(column, 9U);
+
+ // Function display name
+ result = JS::GetSavedFrameFunctionDisplayName(
+ cx, principals, selfHostedFrame, &str, JS::SavedFrameSelfHosted::Exclude);
+ CHECK(result == JS::SavedFrameResult::Ok);
+ lin = str->ensureLinear(cx);
+ CHECK(lin);
+ CHECK(js::StringEqualsLiteral(lin, "one"));
+
+ // Parent
+ JS::RootedObject parent(cx);
+ result = JS::GetSavedFrameParent(cx, principals, savedFrame, &parent,
+ JS::SavedFrameSelfHosted::Exclude);
+ CHECK(result == JS::SavedFrameResult::Ok);
+ // JS::GetSavedFrameParent does this super funky and potentially unexpected
+ // thing where it doesn't return the next subsumed parent but any next
+ // parent. This so that callers can still get the "asyncParent" property
+ // which is only on the first frame of the async parent stack and that frame
+ // might not be subsumed by the caller. It is expected that callers will
+ // still interact with the frame through the JSAPI accessors, so this should
+ // be safe and should not leak privileged info to unprivileged
+ // callers. However, because of that, we don't test that the parent we get
+ // here is the selfHostedFrame's parent (because, as just explained, it
+ // isn't) and instead check that asking for the source property gives us the
+ // expected value.
+ result = JS::GetSavedFrameSource(cx, principals, parent, &str,
+ JS::SavedFrameSelfHosted::Exclude);
+ CHECK(result == JS::SavedFrameResult::Ok);
+ lin = str->ensureLinear(cx);
+ CHECK(lin);
+ CHECK(js::StringEqualsLiteral(lin, "filename.js"));
+
+ return true;
+}
+END_TEST(testSavedStacks_selfHostedFrames)
+
+BEGIN_TEST(test_GetPendingExceptionStack) {
+ CHECK(js::DefineTestingFunctions(cx, global, false, false));
+
+ JSPrincipals* principals = cx->realm()->principals();
+
+ static const char sourceText[] =
+ // 1 2 3
+ // 123456789012345678901234567890123456789
+ "(function one() { \n" // 1
+ " (function two() { \n" // 2
+ " (function three() { \n" // 3
+ " throw 5; \n" // 4
+ " }()); \n" // 5
+ " }()); \n" // 6
+ "}()) \n"; // 7
+
+ JS::CompileOptions opts(cx);
+ opts.setFileAndLine("filename.js", 1U);
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, sourceText, js_strlen(sourceText),
+ JS::SourceOwnership::Borrowed));
+
+ JS::RootedValue val(cx);
+ bool ok = JS::Evaluate(cx, opts, srcBuf, &val);
+
+ CHECK(!ok);
+ CHECK(JS_IsExceptionPending(cx));
+ CHECK(val.isUndefined());
+
+ JS::ExceptionStack exnStack(cx);
+ CHECK(JS::GetPendingExceptionStack(cx, &exnStack));
+ CHECK(exnStack.stack());
+ CHECK(exnStack.stack()->is<js::SavedFrame>());
+ JS::Rooted<js::SavedFrame*> savedFrameStack(
+ cx, &exnStack.stack()->as<js::SavedFrame>());
+
+ CHECK(exnStack.exception().isInt32());
+ CHECK(exnStack.exception().toInt32() == 5);
+
+ struct {
+ uint32_t line;
+ uint32_t column;
+ const char* source;
+ const char* functionDisplayName;
+ } expected[] = {{4, 7, "filename.js", "three"},
+ {5, 6, "filename.js", "two"},
+ {6, 4, "filename.js", "one"},
+ {7, 2, "filename.js", nullptr}};
+
+ size_t i = 0;
+ for (JS::Handle<js::SavedFrame*> frame :
+ js::SavedFrame::RootedRange(cx, savedFrameStack)) {
+ CHECK(i < 4);
+
+ // Line
+ uint32_t line = 123;
+ JS::SavedFrameResult result = JS::GetSavedFrameLine(
+ cx, principals, frame, &line, JS::SavedFrameSelfHosted::Exclude);
+ CHECK(result == JS::SavedFrameResult::Ok);
+ CHECK_EQUAL(line, expected[i].line);
+
+ // Column
+ uint32_t column = 123;
+ result = JS::GetSavedFrameColumn(cx, principals, frame, &column,
+ JS::SavedFrameSelfHosted::Exclude);
+ CHECK(result == JS::SavedFrameResult::Ok);
+ CHECK_EQUAL(column, expected[i].column);
+
+ // Source
+ JS::RootedString str(cx);
+ result = JS::GetSavedFrameSource(cx, principals, frame, &str,
+ JS::SavedFrameSelfHosted::Exclude);
+ CHECK(result == JS::SavedFrameResult::Ok);
+ JSLinearString* linear = str->ensureLinear(cx);
+ CHECK(linear);
+ CHECK(js::StringEqualsAscii(linear, expected[i].source));
+
+ // Function display name
+ result = JS::GetSavedFrameFunctionDisplayName(
+ cx, principals, frame, &str, JS::SavedFrameSelfHosted::Exclude);
+ CHECK(result == JS::SavedFrameResult::Ok);
+ if (auto expectedName = expected[i].functionDisplayName) {
+ CHECK(str);
+ linear = str->ensureLinear(cx);
+ CHECK(linear);
+ CHECK(js::StringEqualsAscii(linear, expectedName));
+ } else {
+ CHECK(!str);
+ }
+
+ i++;
+ }
+
+ return true;
+}
+END_TEST(test_GetPendingExceptionStack)
diff --git a/js/src/jsapi-tests/testScriptInfo.cpp b/js/src/jsapi-tests/testScriptInfo.cpp
new file mode 100644
index 0000000000..780d6f632d
--- /dev/null
+++ b/js/src/jsapi-tests/testScriptInfo.cpp
@@ -0,0 +1,56 @@
+/* -*- 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/Utf8.h" // mozilla::Utf8Unit
+
+#include "jsapi.h"
+
+#include "js/CompilationAndEvaluation.h" // JS::Compile
+#include "js/SourceText.h" // JS::Source{Ownership,Text}
+#include "jsapi-tests/tests.h"
+#include "util/Text.h"
+
+static const char code[] =
+ "xx = 1; \n\
+ \n\
+try { \n\
+ debugger; \n\
+ \n\
+ xx += 1; \n\
+} \n\
+catch (e) \n\
+{ \n\
+ xx += 1; \n\
+}\n\
+//@ sourceMappingURL=http://example.com/path/to/source-map.json";
+
+BEGIN_TEST(testScriptInfo) {
+ unsigned startLine = 1000;
+
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(__FILE__, startLine);
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, code, js_strlen(code), JS::SourceOwnership::Borrowed));
+
+ JS::RootedScript script(cx, JS::Compile(cx, options, srcBuf));
+ CHECK(script);
+
+ CHECK_EQUAL(JS_GetScriptBaseLineNumber(cx, script), startLine);
+ CHECK(strcmp(JS_GetScriptFilename(script), __FILE__) == 0);
+
+ return true;
+}
+static bool CharsMatch(const char16_t* p, const char* q) {
+ while (*q) {
+ if (*p++ != *q++) {
+ return false;
+ }
+ }
+ return true;
+}
+END_TEST(testScriptInfo)
diff --git a/js/src/jsapi-tests/testScriptObject.cpp b/js/src/jsapi-tests/testScriptObject.cpp
new file mode 100644
index 0000000000..10215e9c87
--- /dev/null
+++ b/js/src/jsapi-tests/testScriptObject.cpp
@@ -0,0 +1,214 @@
+/* -*- 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/Utf8.h" // mozilla::Utf8Unit
+
+#include "js/CompilationAndEvaluation.h" // JS::Compile{,Utf8{File,Path}}
+#include "js/PropertyAndElement.h" // JS_SetProperty
+#include "js/SourceText.h" // JS::Source{Ownership,Text}
+#include "jsapi-tests/tests.h"
+
+struct ScriptObjectFixture : public JSAPIRuntimeTest {
+ static const int code_size;
+ static const char code[];
+ static char16_t uc_code[];
+
+ ScriptObjectFixture() {
+ for (int i = 0; i < code_size; i++) {
+ uc_code[i] = code[i];
+ }
+ }
+
+ bool tryScript(JS::HandleScript script) {
+ CHECK(script);
+
+ JS_GC(cx);
+
+ /* After a garbage collection, the script should still work. */
+ JS::RootedValue result(cx);
+ CHECK(JS_ExecuteScript(cx, script, &result));
+
+ return true;
+ }
+};
+
+const char ScriptObjectFixture::code[] =
+ "(function(a, b){return a+' '+b;}('hello', 'world'))";
+const int ScriptObjectFixture::code_size =
+ sizeof(ScriptObjectFixture::code) - 1;
+char16_t ScriptObjectFixture::uc_code[ScriptObjectFixture::code_size];
+
+BEGIN_FIXTURE_TEST(ScriptObjectFixture, bug438633_CompileScript) {
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(__FILE__, __LINE__);
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, code, code_size, JS::SourceOwnership::Borrowed));
+
+ JS::RootedScript script(cx, JS::Compile(cx, options, srcBuf));
+ CHECK(script);
+
+ return tryScript(script);
+}
+END_FIXTURE_TEST(ScriptObjectFixture, bug438633_CompileScript)
+
+BEGIN_FIXTURE_TEST(ScriptObjectFixture, bug438633_CompileScript_empty) {
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(__FILE__, __LINE__);
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, "", 0, JS::SourceOwnership::Borrowed));
+
+ JS::RootedScript script(cx, JS::Compile(cx, options, srcBuf));
+ CHECK(script);
+
+ return tryScript(script);
+}
+END_FIXTURE_TEST(ScriptObjectFixture, bug438633_CompileScript_empty)
+
+BEGIN_FIXTURE_TEST(ScriptObjectFixture, bug438633_CompileScriptForPrincipals) {
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(__FILE__, __LINE__);
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, code, code_size, JS::SourceOwnership::Borrowed));
+
+ JS::RootedScript script(cx, JS::Compile(cx, options, srcBuf));
+
+ return tryScript(script);
+}
+END_FIXTURE_TEST(ScriptObjectFixture, bug438633_CompileScriptForPrincipals)
+
+BEGIN_FIXTURE_TEST(ScriptObjectFixture, bug438633_JS_CompileUCScript) {
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(__FILE__, __LINE__);
+
+ JS::SourceText<char16_t> srcBuf;
+ CHECK(srcBuf.init(cx, uc_code, code_size, JS::SourceOwnership::Borrowed));
+
+ JS::RootedScript script(cx, JS::Compile(cx, options, srcBuf));
+ CHECK(script);
+
+ return tryScript(script);
+}
+END_FIXTURE_TEST(ScriptObjectFixture, bug438633_JS_CompileUCScript)
+
+BEGIN_FIXTURE_TEST(ScriptObjectFixture, bug438633_JS_CompileUCScript_empty) {
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(__FILE__, __LINE__);
+
+ JS::SourceText<char16_t> srcBuf;
+ CHECK(srcBuf.init(cx, uc_code, 0, JS::SourceOwnership::Borrowed));
+
+ JS::RootedScript script(cx, JS::Compile(cx, options, srcBuf));
+ CHECK(script);
+
+ return tryScript(script);
+}
+END_FIXTURE_TEST(ScriptObjectFixture, bug438633_JS_CompileUCScript_empty)
+
+BEGIN_FIXTURE_TEST(ScriptObjectFixture,
+ bug438633_JS_CompileUCScriptForPrincipals) {
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(__FILE__, __LINE__);
+
+ JS::SourceText<char16_t> srcBuf;
+ CHECK(srcBuf.init(cx, uc_code, code_size, JS::SourceOwnership::Borrowed));
+
+ JS::RootedScript script(cx, JS::Compile(cx, options, srcBuf));
+ CHECK(script);
+
+ return tryScript(script);
+}
+END_FIXTURE_TEST(ScriptObjectFixture, bug438633_JS_CompileUCScriptForPrincipals)
+
+BEGIN_FIXTURE_TEST(ScriptObjectFixture, bug438633_JS_CompileFile) {
+ TempFile tempScript;
+ static const char script_filename[] = "temp-bug438633_JS_CompileFile";
+ FILE* script_stream = tempScript.open(script_filename);
+ CHECK(fputs(code, script_stream) != EOF);
+ tempScript.close();
+
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(script_filename, 1);
+
+ JS::RootedScript script(cx,
+ JS::CompileUtf8Path(cx, options, script_filename));
+ CHECK(script);
+
+ tempScript.remove();
+ return tryScript(script);
+}
+END_FIXTURE_TEST(ScriptObjectFixture, bug438633_JS_CompileFile)
+
+BEGIN_FIXTURE_TEST(ScriptObjectFixture, bug438633_JS_CompileFile_empty) {
+ TempFile tempScript;
+ static const char script_filename[] = "temp-bug438633_JS_CompileFile_empty";
+ tempScript.open(script_filename);
+ tempScript.close();
+
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(script_filename, 1);
+
+ JS::RootedScript script(cx,
+ JS::CompileUtf8Path(cx, options, script_filename));
+ CHECK(script);
+
+ tempScript.remove();
+ return tryScript(script);
+}
+END_FIXTURE_TEST(ScriptObjectFixture, bug438633_JS_CompileFile_empty)
+
+BEGIN_FIXTURE_TEST(ScriptObjectFixture, bug438633_JS_CompileFileHandle) {
+ TempFile tempScript;
+ FILE* script_stream = tempScript.open("temp-bug438633_JS_CompileFileHandle");
+ CHECK(fputs(code, script_stream) != EOF);
+ CHECK(fseek(script_stream, 0, SEEK_SET) != EOF);
+
+ JS::CompileOptions options(cx);
+ options.setFileAndLine("temporary file", 1);
+
+ JS::RootedScript script(cx, JS::CompileUtf8File(cx, options, script_stream));
+ CHECK(script);
+
+ return tryScript(script);
+}
+END_FIXTURE_TEST(ScriptObjectFixture, bug438633_JS_CompileFileHandle)
+
+BEGIN_FIXTURE_TEST(ScriptObjectFixture, bug438633_JS_CompileFileHandle_empty) {
+ TempFile tempScript;
+ FILE* script_stream =
+ tempScript.open("temp-bug438633_JS_CompileFileHandle_empty");
+
+ JS::CompileOptions options(cx);
+ options.setFileAndLine("empty temporary file", 1);
+
+ JS::RootedScript script(cx, JS::CompileUtf8File(cx, options, script_stream));
+ CHECK(script);
+
+ return tryScript(script);
+}
+END_FIXTURE_TEST(ScriptObjectFixture, bug438633_JS_CompileFileHandle_empty)
+
+BEGIN_FIXTURE_TEST(ScriptObjectFixture,
+ bug438633_JS_CompileFileHandleForPrincipals) {
+ TempFile tempScript;
+ FILE* script_stream =
+ tempScript.open("temp-bug438633_JS_CompileFileHandleForPrincipals");
+ CHECK(fputs(code, script_stream) != EOF);
+ CHECK(fseek(script_stream, 0, SEEK_SET) != EOF);
+
+ JS::CompileOptions options(cx);
+ options.setFileAndLine("temporary file", 1);
+
+ JS::RootedScript script(cx, JS::CompileUtf8File(cx, options, script_stream));
+ CHECK(script);
+
+ return tryScript(script);
+}
+END_FIXTURE_TEST(ScriptObjectFixture,
+ bug438633_JS_CompileFileHandleForPrincipals)
diff --git a/js/src/jsapi-tests/testScriptSourceCompression.cpp b/js/src/jsapi-tests/testScriptSourceCompression.cpp
new file mode 100644
index 0000000000..3d1dc2c241
--- /dev/null
+++ b/js/src/jsapi-tests/testScriptSourceCompression.cpp
@@ -0,0 +1,551 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ */
+/* 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/Assertions.h" // MOZ_RELEASE_ASSERT
+#include "mozilla/RefPtr.h" // RefPtr
+#include "mozilla/Utf8.h" // mozilla::Utf8Unit
+
+#include <algorithm> // std::all_of, std::equal, std::move, std::transform
+#include <iterator> // std::size
+#include <memory> // std::uninitialized_fill_n
+#include <stddef.h> // size_t
+#include <stdint.h> // uint32_t
+
+#include "jsapi.h" // JS_EnsureLinearString, JS_GC, JS_Get{Latin1,TwoByte}LinearStringChars, JS_GetStringLength, JS_ValueToFunction
+#include "jstypes.h" // JS_PUBLIC_API
+
+#include "gc/GC.h" // js::gc::FinishGC
+#include "js/CompilationAndEvaluation.h" // JS::Evaluate
+#include "js/CompileOptions.h" // JS::CompileOptions, JS::InstantiateOptions
+#include "js/Conversions.h" // JS::ToString
+#include "js/experimental/JSStencil.h" // JS::Stencil, JS::CompileToStencilOffThread, JS::FinishCompileToStencilOffThread, JS::InstantiateGlobalStencil
+#include "js/MemoryFunctions.h" // JS_malloc
+#include "js/OffThreadScriptCompilation.h" // JS::OffThreadToken
+#include "js/RootingAPI.h" // JS::MutableHandle, JS::Rooted
+#include "js/SourceText.h" // JS::SourceOwnership, JS::SourceText
+#include "js/String.h" // JS::GetLatin1LinearStringChars, JS::GetTwoByteLinearStringChars, JS::StringHasLatin1Chars
+#include "js/UniquePtr.h" // js::UniquePtr
+#include "js/Utility.h" // JS::FreePolicy
+#include "js/Value.h" // JS::NullValue, JS::ObjectValue, JS::Value
+#include "jsapi-tests/tests.h"
+#include "util/Text.h" // js_strlen
+#include "vm/Compression.h" // js::Compressor::CHUNK_SIZE
+#include "vm/HelperThreads.h" // js::RunPendingSourceCompressions
+#include "vm/JSFunction.h" // JSFunction::getOrCreateScript
+#include "vm/JSScript.h" // JSScript, js::ScriptSource::MinimumCompressibleLength, js::SynchronouslyCompressSource
+#include "vm/Monitor.h" // js::Monitor, js::AutoLockMonitor
+
+using mozilla::Utf8Unit;
+
+struct JS_PUBLIC_API JSContext;
+class JS_PUBLIC_API JSString;
+
+template <typename Unit>
+using Source = js::UniquePtr<Unit[], JS::FreePolicy>;
+
+constexpr size_t ChunkSize = js::Compressor::CHUNK_SIZE;
+constexpr size_t MinimumCompressibleLength =
+ js::ScriptSource::MinimumCompressibleLength;
+
+// Don't use ' ' to spread stuff across lines.
+constexpr char FillerWhitespace = '\n';
+
+template <typename Unit>
+static Source<Unit> MakeSourceAllWhitespace(JSContext* cx, size_t len) {
+ static_assert(ChunkSize % sizeof(Unit) == 0,
+ "chunk size presumed to be a multiple of char size");
+
+ Source<Unit> source(
+ reinterpret_cast<Unit*>(JS_malloc(cx, len * sizeof(Unit))));
+ if (source) {
+ std::uninitialized_fill_n(source.get(), len, FillerWhitespace);
+ }
+ return source;
+}
+
+template <typename Unit>
+static JSFunction* EvaluateChars(JSContext* cx, Source<Unit> chars, size_t len,
+ char functionName, const char* func) {
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(func, 1);
+
+ // Evaluate the provided source text, containing a function named
+ // |functionName|.
+ JS::SourceText<Unit> sourceText;
+ if (!sourceText.init(cx, std::move(chars), len)) {
+ return nullptr;
+ }
+
+ {
+ JS::Rooted<JS::Value> dummy(cx);
+ if (!JS::Evaluate(cx, options, sourceText, &dummy)) {
+ return nullptr;
+ }
+ }
+
+ // Evaluate the name of that function.
+ JS::Rooted<JS::Value> rval(cx);
+ const char16_t name[] = {char16_t(functionName)};
+ JS::SourceText<char16_t> srcbuf;
+ if (!srcbuf.init(cx, name, std::size(name), JS::SourceOwnership::Borrowed)) {
+ return nullptr;
+ }
+ if (!JS::Evaluate(cx, options, srcbuf, &rval)) {
+ return nullptr;
+ }
+
+ // Return the function.
+ MOZ_RELEASE_ASSERT(rval.isObject());
+ return JS_ValueToFunction(cx, rval);
+}
+
+static void CompressSourceSync(JS::Handle<JSFunction*> fun, JSContext* cx) {
+ JS::Rooted<JSScript*> script(cx, JSFunction::getOrCreateScript(cx, fun));
+ MOZ_RELEASE_ASSERT(script);
+ MOZ_RELEASE_ASSERT(script->scriptSource()->hasSourceText());
+
+ MOZ_RELEASE_ASSERT(js::SynchronouslyCompressSource(cx, script));
+
+ MOZ_RELEASE_ASSERT(script->scriptSource()->hasCompressedSource());
+}
+
+static constexpr char FunctionStart[] = "function @() {";
+constexpr size_t FunctionStartLength = js_strlen(FunctionStart);
+constexpr size_t FunctionNameOffset = 9;
+
+static_assert(FunctionStart[FunctionNameOffset] == '@',
+ "offset must correctly point at the function name location");
+
+static constexpr char FunctionEnd[] = "return 42; }";
+constexpr size_t FunctionEndLength = js_strlen(FunctionEnd);
+
+template <typename Unit>
+static void WriteFunctionOfSizeAtOffset(Source<Unit>& source,
+ size_t usableSourceLen,
+ char functionName,
+ size_t functionLength, size_t offset) {
+ MOZ_RELEASE_ASSERT(functionLength >= MinimumCompressibleLength,
+ "function must be a certain size to be compressed");
+ MOZ_RELEASE_ASSERT(offset <= usableSourceLen,
+ "offset must not exceed usable source");
+ MOZ_RELEASE_ASSERT(functionLength <= usableSourceLen,
+ "function must fit in usable source");
+ MOZ_RELEASE_ASSERT(offset <= usableSourceLen - functionLength,
+ "function must not extend past usable source");
+
+ // Assigning |char| to |char16_t| is permitted, but we deliberately require a
+ // cast to assign |char| to |Utf8Unit|. |std::copy_n| would handle the first
+ // case, but the required transformation for UTF-8 demands |std::transform|.
+ auto TransformToUnit = [](char c) { return Unit(c); };
+
+ // Fill in the function start.
+ std::transform(FunctionStart, FunctionStart + FunctionStartLength,
+ &source[offset], TransformToUnit);
+ source[offset + FunctionNameOffset] = Unit(functionName);
+
+ // Fill in the function end.
+ std::transform(FunctionEnd, FunctionEnd + FunctionEndLength,
+ &source[offset + functionLength - FunctionEndLength],
+ TransformToUnit);
+}
+
+static JSString* DecompressSource(JSContext* cx, JS::Handle<JSFunction*> fun) {
+ JS::Rooted<JS::Value> fval(cx, JS::ObjectValue(*JS_GetFunctionObject(fun)));
+ return JS::ToString(cx, fval);
+}
+
+static bool IsExpectedFunctionString(JS::Handle<JSString*> str,
+ char functionName, JSContext* cx) {
+ JSLinearString* lstr = JS_EnsureLinearString(cx, str);
+ MOZ_RELEASE_ASSERT(lstr);
+
+ size_t len = JS_GetStringLength(str);
+ if (len < FunctionStartLength || len < FunctionEndLength) {
+ return false;
+ }
+
+ JS::AutoAssertNoGC nogc(cx);
+
+ auto CheckContents = [functionName, len](const auto* chars) {
+ // Check the function in parts:
+ //
+ // * "function "
+ // * "A"
+ // * "() {"
+ // * "\n...\n"
+ // * "return 42; }"
+ return std::equal(chars, chars + FunctionNameOffset, FunctionStart) &&
+ chars[FunctionNameOffset] == functionName &&
+ std::equal(chars + FunctionNameOffset + 1,
+ chars + FunctionStartLength,
+ FunctionStart + FunctionNameOffset + 1) &&
+ std::all_of(chars + FunctionStartLength,
+ chars + len - FunctionEndLength,
+ [](auto c) { return c == FillerWhitespace; }) &&
+ std::equal(chars + len - FunctionEndLength, chars + len,
+ FunctionEnd);
+ };
+
+ bool hasExpectedContents;
+ if (JS::StringHasLatin1Chars(str)) {
+ const JS::Latin1Char* chars = JS::GetLatin1LinearStringChars(nogc, lstr);
+ hasExpectedContents = CheckContents(chars);
+ } else {
+ const char16_t* chars = JS::GetTwoByteLinearStringChars(nogc, lstr);
+ hasExpectedContents = CheckContents(chars);
+ }
+
+ return hasExpectedContents;
+}
+
+BEGIN_TEST(testScriptSourceCompression_inOneChunk) {
+ CHECK(run<char16_t>());
+ CHECK(run<Utf8Unit>());
+ return true;
+}
+
+template <typename Unit>
+bool run() {
+ constexpr size_t len = MinimumCompressibleLength + 55;
+ auto source = MakeSourceAllWhitespace<Unit>(cx, len);
+ CHECK(source);
+
+ // Write out a 'b' or 'c' function that is long enough to be compressed,
+ // that starts after source start and ends before source end.
+ constexpr char FunctionName = 'a' + sizeof(Unit);
+ WriteFunctionOfSizeAtOffset(source, len, FunctionName,
+ MinimumCompressibleLength,
+ len - MinimumCompressibleLength);
+
+ JS::Rooted<JSFunction*> fun(cx);
+ fun = EvaluateChars(cx, std::move(source), len, FunctionName, __FUNCTION__);
+ CHECK(fun);
+
+ CompressSourceSync(fun, cx);
+
+ JS::Rooted<JSString*> str(cx, DecompressSource(cx, fun));
+ CHECK(str);
+ CHECK(IsExpectedFunctionString(str, FunctionName, cx));
+
+ return true;
+}
+END_TEST(testScriptSourceCompression_inOneChunk)
+
+BEGIN_TEST(testScriptSourceCompression_endsAtBoundaryInOneChunk) {
+ CHECK(run<char16_t>());
+ CHECK(run<Utf8Unit>());
+ return true;
+}
+
+template <typename Unit>
+bool run() {
+ constexpr size_t len = ChunkSize / sizeof(Unit);
+ auto source = MakeSourceAllWhitespace<Unit>(cx, len);
+ CHECK(source);
+
+ // Write out a 'd' or 'e' function that is long enough to be compressed,
+ // that (for no particular reason) starts after source start and ends
+ // before usable source end.
+ constexpr char FunctionName = 'c' + sizeof(Unit);
+ WriteFunctionOfSizeAtOffset(source, len, FunctionName,
+ MinimumCompressibleLength,
+ len - MinimumCompressibleLength);
+
+ JS::Rooted<JSFunction*> fun(cx);
+ fun = EvaluateChars(cx, std::move(source), len, FunctionName, __FUNCTION__);
+ CHECK(fun);
+
+ CompressSourceSync(fun, cx);
+
+ JS::Rooted<JSString*> str(cx, DecompressSource(cx, fun));
+ CHECK(str);
+ CHECK(IsExpectedFunctionString(str, FunctionName, cx));
+
+ return true;
+}
+END_TEST(testScriptSourceCompression_endsAtBoundaryInOneChunk)
+
+BEGIN_TEST(testScriptSourceCompression_isExactChunk) {
+ CHECK(run<char16_t>());
+ CHECK(run<Utf8Unit>());
+ return true;
+}
+
+template <typename Unit>
+bool run() {
+ constexpr size_t len = ChunkSize / sizeof(Unit);
+ auto source = MakeSourceAllWhitespace<Unit>(cx, len);
+ CHECK(source);
+
+ // Write out a 'f' or 'g' function that occupies the entire source (and
+ // entire chunk, too).
+ constexpr char FunctionName = 'e' + sizeof(Unit);
+ WriteFunctionOfSizeAtOffset(source, len, FunctionName, len, 0);
+
+ JS::Rooted<JSFunction*> fun(cx);
+ fun = EvaluateChars(cx, std::move(source), len, FunctionName, __FUNCTION__);
+ CHECK(fun);
+
+ CompressSourceSync(fun, cx);
+
+ JS::Rooted<JSString*> str(cx, DecompressSource(cx, fun));
+ CHECK(str);
+ CHECK(IsExpectedFunctionString(str, FunctionName, cx));
+
+ return true;
+}
+END_TEST(testScriptSourceCompression_isExactChunk)
+
+BEGIN_TEST(testScriptSourceCompression_crossesChunkBoundary) {
+ CHECK(run<char16_t>());
+ CHECK(run<Utf8Unit>());
+ return true;
+}
+
+template <typename Unit>
+bool run() {
+ constexpr size_t len = ChunkSize / sizeof(Unit) + 293;
+ auto source = MakeSourceAllWhitespace<Unit>(cx, len);
+ CHECK(source);
+
+ // This function crosses a chunk boundary but does not end at one.
+ constexpr size_t FunctionSize = 177 + ChunkSize / sizeof(Unit);
+
+ // Write out a 'h' or 'i' function.
+ constexpr char FunctionName = 'g' + sizeof(Unit);
+ WriteFunctionOfSizeAtOffset(source, len, FunctionName, FunctionSize, 37);
+
+ JS::Rooted<JSFunction*> fun(cx);
+ fun = EvaluateChars(cx, std::move(source), len, FunctionName, __FUNCTION__);
+ CHECK(fun);
+
+ CompressSourceSync(fun, cx);
+
+ JS::Rooted<JSString*> str(cx, DecompressSource(cx, fun));
+ CHECK(str);
+ CHECK(IsExpectedFunctionString(str, FunctionName, cx));
+
+ return true;
+}
+END_TEST(testScriptSourceCompression_crossesChunkBoundary)
+
+BEGIN_TEST(testScriptSourceCompression_crossesChunkBoundary_endsAtBoundary) {
+ CHECK(run<char16_t>());
+ CHECK(run<Utf8Unit>());
+ return true;
+}
+
+template <typename Unit>
+bool run() {
+ // Exactly two chunks.
+ constexpr size_t len = (2 * ChunkSize) / sizeof(Unit);
+ auto source = MakeSourceAllWhitespace<Unit>(cx, len);
+ CHECK(source);
+
+ // This function crosses a chunk boundary, and it ends exactly at the end
+ // of both the second chunk and the full source.
+ constexpr size_t FunctionSize = 1 + ChunkSize / sizeof(Unit);
+
+ // Write out a 'j' or 'k' function.
+ constexpr char FunctionName = 'i' + sizeof(Unit);
+ WriteFunctionOfSizeAtOffset(source, len, FunctionName, FunctionSize,
+ len - FunctionSize);
+
+ JS::Rooted<JSFunction*> fun(cx);
+ fun = EvaluateChars(cx, std::move(source), len, FunctionName, __FUNCTION__);
+ CHECK(fun);
+
+ CompressSourceSync(fun, cx);
+
+ JS::Rooted<JSString*> str(cx, DecompressSource(cx, fun));
+ CHECK(str);
+ CHECK(IsExpectedFunctionString(str, FunctionName, cx));
+
+ return true;
+}
+END_TEST(testScriptSourceCompression_crossesChunkBoundary_endsAtBoundary)
+
+BEGIN_TEST(testScriptSourceCompression_containsWholeChunk) {
+ CHECK(run<char16_t>());
+ CHECK(run<Utf8Unit>());
+ return true;
+}
+
+template <typename Unit>
+bool run() {
+ constexpr size_t len = (2 * ChunkSize) / sizeof(Unit) + 17;
+ auto source = MakeSourceAllWhitespace<Unit>(cx, len);
+ CHECK(source);
+
+ // This function crosses two chunk boundaries and begins/ends in the middle
+ // of chunk boundaries.
+ constexpr size_t FunctionSize = 2 + ChunkSize / sizeof(Unit);
+
+ // Write out a 'l' or 'm' function.
+ constexpr char FunctionName = 'k' + sizeof(Unit);
+ WriteFunctionOfSizeAtOffset(source, len, FunctionName, FunctionSize,
+ ChunkSize / sizeof(Unit) - 1);
+
+ JS::Rooted<JSFunction*> fun(cx);
+ fun = EvaluateChars(cx, std::move(source), len, FunctionName, __FUNCTION__);
+ CHECK(fun);
+
+ CompressSourceSync(fun, cx);
+
+ JS::Rooted<JSString*> str(cx, DecompressSource(cx, fun));
+ CHECK(str);
+ CHECK(IsExpectedFunctionString(str, FunctionName, cx));
+
+ return true;
+}
+END_TEST(testScriptSourceCompression_containsWholeChunk)
+
+BEGIN_TEST(testScriptSourceCompression_containsWholeChunk_endsAtBoundary) {
+ CHECK(run<char16_t>());
+ CHECK(run<Utf8Unit>());
+ return true;
+}
+
+template <typename Unit>
+bool run() {
+ // Exactly three chunks.
+ constexpr size_t len = (3 * ChunkSize) / sizeof(Unit);
+ auto source = MakeSourceAllWhitespace<Unit>(cx, len);
+ CHECK(source);
+
+ // This function crosses two chunk boundaries and ends at a chunk boundary.
+ constexpr size_t FunctionSize = 1 + (2 * ChunkSize) / sizeof(Unit);
+
+ // Write out a 'n' or 'o' function.
+ constexpr char FunctionName = 'm' + sizeof(Unit);
+ WriteFunctionOfSizeAtOffset(source, len, FunctionName, FunctionSize,
+ ChunkSize / sizeof(Unit) - 1);
+
+ JS::Rooted<JSFunction*> fun(cx);
+ fun = EvaluateChars(cx, std::move(source), len, FunctionName, __FUNCTION__);
+ CHECK(fun);
+
+ CompressSourceSync(fun, cx);
+
+ JS::Rooted<JSString*> str(cx, DecompressSource(cx, fun));
+ CHECK(str);
+ CHECK(IsExpectedFunctionString(str, FunctionName, cx));
+
+ return true;
+}
+END_TEST(testScriptSourceCompression_containsWholeChunk_endsAtBoundary)
+
+BEGIN_TEST(testScriptSourceCompression_spansMultipleMiddleChunks) {
+ CHECK(run<char16_t>());
+ CHECK(run<Utf8Unit>());
+ return true;
+}
+
+template <typename Unit>
+bool run() {
+ // Four chunks.
+ constexpr size_t len = (4 * ChunkSize) / sizeof(Unit);
+ auto source = MakeSourceAllWhitespace<Unit>(cx, len);
+ CHECK(source);
+
+ // This function spans the two middle chunks and further extends one
+ // character to each side.
+ constexpr size_t FunctionSize = 2 + (2 * ChunkSize) / sizeof(Unit);
+
+ // Write out a 'p' or 'q' function.
+ constexpr char FunctionName = 'o' + sizeof(Unit);
+ WriteFunctionOfSizeAtOffset(source, len, FunctionName, FunctionSize,
+ ChunkSize / sizeof(Unit) - 1);
+
+ JS::Rooted<JSFunction*> fun(cx);
+ fun = EvaluateChars(cx, std::move(source), len, FunctionName, __FUNCTION__);
+ CHECK(fun);
+
+ CompressSourceSync(fun, cx);
+
+ JS::Rooted<JSString*> str(cx, DecompressSource(cx, fun));
+ CHECK(str);
+ CHECK(IsExpectedFunctionString(str, FunctionName, cx));
+
+ return true;
+}
+END_TEST(testScriptSourceCompression_spansMultipleMiddleChunks)
+
+BEGIN_TEST(testScriptSourceCompression_automatic) {
+ constexpr size_t len = MinimumCompressibleLength + 55;
+ auto chars = MakeSourceAllWhitespace<char16_t>(cx, len);
+ CHECK(chars);
+
+ JS::SourceText<char16_t> source;
+ CHECK(source.init(cx, std::move(chars), len));
+
+ JS::CompileOptions options(cx);
+ JS::Rooted<JSScript*> script(cx, JS::Compile(cx, options, source));
+ CHECK(script);
+
+ // Check that source compression was triggered by the compile. If the
+ // off-thread source compression system is globally disabled, the source will
+ // remain uncompressed.
+ js::RunPendingSourceCompressions(cx->runtime());
+ bool expected = js::IsOffThreadSourceCompressionEnabled();
+ CHECK(script->scriptSource()->hasCompressedSource() == expected);
+
+ return true;
+}
+END_TEST(testScriptSourceCompression_automatic)
+
+BEGIN_TEST(testScriptSourceCompression_offThread) {
+ constexpr size_t len = MinimumCompressibleLength + 55;
+ auto chars = MakeSourceAllWhitespace<char16_t>(cx, len);
+ CHECK(chars);
+
+ JS::SourceText<char16_t> source;
+ CHECK(source.init(cx, std::move(chars), len));
+
+ js::Monitor monitor MOZ_UNANNOTATED(js::mutexid::ShellOffThreadState);
+ JS::CompileOptions options(cx);
+ JS::OffThreadToken* token;
+
+ // Force off-thread even though if this is a small file.
+ options.forceAsync = true;
+
+ CHECK(token = JS::CompileToStencilOffThread(cx, options, source, callback,
+ &monitor));
+
+ {
+ // Finish any active GC in case it is blocking off-thread work.
+ js::gc::FinishGC(cx);
+
+ js::AutoLockMonitor lock(monitor);
+ lock.wait();
+ }
+
+ RefPtr<JS::Stencil> stencil = JS::FinishOffThreadStencil(cx, token);
+ CHECK(stencil);
+ JS::InstantiateOptions instantiateOptions(options);
+ JS::Rooted<JSScript*> script(
+ cx, JS::InstantiateGlobalStencil(cx, instantiateOptions, stencil));
+ CHECK(script);
+
+ // Check that source compression was triggered by the compile. If the
+ // off-thread source compression system is globally disabled, the source will
+ // remain uncompressed.
+ js::RunPendingSourceCompressions(cx->runtime());
+ bool expected = js::IsOffThreadSourceCompressionEnabled();
+ CHECK(script->scriptSource()->hasCompressedSource() == expected);
+
+ return true;
+}
+
+static void callback(JS::OffThreadToken* token, void* context) {
+ js::Monitor& monitor = *static_cast<js::Monitor*>(context);
+
+ js::AutoLockMonitor lock(monitor);
+ lock.notify();
+}
+
+END_TEST(testScriptSourceCompression_offThread)
diff --git a/js/src/jsapi-tests/testSetProperty.cpp b/js/src/jsapi-tests/testSetProperty.cpp
new file mode 100644
index 0000000000..dfc176778a
--- /dev/null
+++ b/js/src/jsapi-tests/testSetProperty.cpp
@@ -0,0 +1,54 @@
+/* -*- 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 "js/Object.h" // JS::GetClass
+#include "js/PropertyAndElement.h" // JS_DefineProperty
+#include "jsapi-tests/tests.h"
+
+BEGIN_TEST(testSetProperty_InheritedGlobalSetter) {
+ // This is a JSAPI test because jsapi-test globals can be set up to not have
+ // a resolve hook and therefore can use the property cache in some cases
+ // where the shell can't.
+ MOZ_RELEASE_ASSERT(!JS::GetClass(global)->getResolve());
+
+ CHECK(JS::InitRealmStandardClasses(cx));
+
+ CHECK(JS_DefineProperty(cx, global, "HOTLOOP", 8, 0));
+ EXEC(
+ "var n = 0;\n"
+ "var global = this;\n"
+ "function f() { n++; }\n"
+ "Object.defineProperty(Object.prototype, 'x', {set: f});\n"
+ "for (var i = 0; i < HOTLOOP; i++)\n"
+ " global.x = i;\n");
+ EXEC(
+ "if (n != HOTLOOP)\n"
+ " throw 'FAIL';\n");
+ return true;
+}
+
+const JSClass* getGlobalClass(void) override {
+ static const JSClassOps noResolveGlobalClassOps = {
+ nullptr, // addProperty
+ nullptr, // delProperty
+ nullptr, // enumerate
+ nullptr, // newEnumerate
+ nullptr, // resolve
+ nullptr, // mayResolve
+ nullptr, // finalize
+ nullptr, // call
+ nullptr, // construct
+ JS_GlobalObjectTraceHook, // trace
+ };
+
+ static const JSClass noResolveGlobalClass = {
+ "testSetProperty_InheritedGlobalSetter_noResolveGlobalClass",
+ JSCLASS_GLOBAL_FLAGS, &noResolveGlobalClassOps};
+
+ return &noResolveGlobalClass;
+}
+END_TEST(testSetProperty_InheritedGlobalSetter)
diff --git a/js/src/jsapi-tests/testSetPropertyIgnoringNamedGetter.cpp b/js/src/jsapi-tests/testSetPropertyIgnoringNamedGetter.cpp
new file mode 100644
index 0000000000..cc4ef9cdc8
--- /dev/null
+++ b/js/src/jsapi-tests/testSetPropertyIgnoringNamedGetter.cpp
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ */
+
+#include "jsfriendapi.h"
+
+#include "js/PropertyAndElement.h" // JS_DefineProperty
+#include "js/Proxy.h"
+
+#include "jsapi-tests/tests.h"
+
+using namespace js;
+using namespace JS;
+
+class CustomProxyHandler : public Wrapper {
+ public:
+ CustomProxyHandler() : Wrapper(0) {}
+
+ bool getOwnPropertyDescriptor(
+ JSContext* cx, HandleObject proxy, HandleId id,
+ MutableHandle<mozilla::Maybe<PropertyDescriptor>> desc) const override {
+ if (id.isString() &&
+ JS_LinearStringEqualsLiteral(id.toLinearString(), "phantom")) {
+ desc.set(mozilla::Some(PropertyDescriptor::Data(
+ Int32Value(42),
+ {PropertyAttribute::Configurable, PropertyAttribute::Enumerable,
+ PropertyAttribute::Writable})));
+ return true;
+ }
+
+ return Wrapper::getOwnPropertyDescriptor(cx, proxy, id, desc);
+ }
+
+ bool set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v,
+ HandleValue receiver, ObjectOpResult& result) const override {
+ Rooted<mozilla::Maybe<PropertyDescriptor>> desc(cx);
+ if (!Wrapper::getOwnPropertyDescriptor(cx, proxy, id, &desc)) {
+ return false;
+ }
+ return SetPropertyIgnoringNamedGetter(cx, proxy, id, v, receiver, desc,
+ result);
+ }
+};
+
+const CustomProxyHandler customProxyHandler;
+
+BEGIN_TEST(testSetPropertyIgnoringNamedGetter_direct) {
+ RootedValue protov(cx);
+ EVAL("Object.prototype", &protov);
+
+ RootedValue targetv(cx);
+ EVAL("({})", &targetv);
+
+ RootedObject proxyObj(cx, NewProxyObject(cx, &customProxyHandler, targetv,
+ &protov.toObject(), ProxyOptions()));
+ CHECK(proxyObj);
+
+ CHECK(JS_DefineProperty(cx, global, "target", targetv, 0));
+ CHECK(JS_DefineProperty(cx, global, "proxy", proxyObj, 0));
+
+ RootedValue v(cx);
+ EVAL("Object.getOwnPropertyDescriptor(proxy, 'phantom').value", &v);
+ CHECK_SAME(v, Int32Value(42));
+
+ EXEC("proxy.phantom = 123");
+ EVAL("Object.getOwnPropertyDescriptor(proxy, 'phantom').value", &v);
+ CHECK_SAME(v, Int32Value(42));
+ EVAL("target.phantom", &v);
+ CHECK_SAME(v, Int32Value(123));
+
+ return true;
+}
+END_TEST(testSetPropertyIgnoringNamedGetter_direct)
diff --git a/js/src/jsapi-tests/testSharedImmutableStringsCache.cpp b/js/src/jsapi-tests/testSharedImmutableStringsCache.cpp
new file mode 100644
index 0000000000..461bd6b21d
--- /dev/null
+++ b/js/src/jsapi-tests/testSharedImmutableStringsCache.cpp
@@ -0,0 +1,72 @@
+/* -*- 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/IntegerRange.h"
+
+#include "js/Vector.h"
+#include "jsapi-tests/tests.h"
+#include "threading/Thread.h"
+#include "util/Text.h"
+#include "vm/SharedImmutableStringsCache.h"
+
+const int NUM_THREADS = 256;
+const int NUM_ITERATIONS = 256;
+
+const int NUM_STRINGS = 4;
+const char16_t* const STRINGS[NUM_STRINGS] = {u"uno", u"dos", u"tres",
+ u"quattro"};
+
+struct CacheAndIndex {
+ js::SharedImmutableStringsCache* cache;
+ int index;
+
+ CacheAndIndex(js::SharedImmutableStringsCache* cache, int index)
+ : cache(cache), index(index) {}
+};
+
+static void getString(CacheAndIndex* cacheAndIndex) {
+ for (int i = 0; i < NUM_ITERATIONS; i++) {
+ auto str = STRINGS[cacheAndIndex->index % NUM_STRINGS];
+
+ auto dupe = js::DuplicateString(str);
+ MOZ_RELEASE_ASSERT(dupe);
+
+ auto deduped =
+ cacheAndIndex->cache->getOrCreate(std::move(dupe), js_strlen(str));
+ MOZ_RELEASE_ASSERT(deduped);
+ MOZ_RELEASE_ASSERT(
+ js::EqualChars(str, deduped.chars(), js_strlen(str) + 1));
+
+ {
+ auto cloned = deduped.clone();
+ // We should be de-duplicating and giving back the same string.
+ MOZ_RELEASE_ASSERT(deduped.chars() == cloned.chars());
+ }
+ }
+
+ js_delete(cacheAndIndex);
+}
+
+BEGIN_TEST(testSharedImmutableStringsCache) {
+ auto& cache = js::SharedImmutableStringsCache::getSingleton();
+
+ js::Vector<js::Thread> threads(cx);
+ CHECK(threads.reserve(NUM_THREADS));
+
+ for (auto i : mozilla::IntegerRange(NUM_THREADS)) {
+ auto cacheAndIndex = js_new<CacheAndIndex>(&cache, i);
+ CHECK(cacheAndIndex);
+ threads.infallibleEmplaceBack();
+ CHECK(threads.back().init(getString, cacheAndIndex));
+ }
+
+ for (auto& thread : threads) {
+ thread.join();
+ }
+
+ return true;
+}
+END_TEST(testSharedImmutableStringsCache)
diff --git a/js/src/jsapi-tests/testSliceBudget.cpp b/js/src/jsapi-tests/testSliceBudget.cpp
new file mode 100644
index 0000000000..a53d98628d
--- /dev/null
+++ b/js/src/jsapi-tests/testSliceBudget.cpp
@@ -0,0 +1,117 @@
+/* -*- 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 "js/SliceBudget.h"
+#include "jsapi-tests/tests.h"
+
+using namespace js;
+
+BEGIN_TEST(testSliceBudgetUnlimited) {
+ SliceBudget budget = SliceBudget::unlimited();
+ CHECK(budget.isUnlimited());
+ CHECK(!budget.isTimeBudget());
+ CHECK(!budget.isWorkBudget());
+
+ CHECK(!budget.isOverBudget());
+
+ budget.step(1000000);
+ CHECK(!budget.isOverBudget());
+
+ return true;
+}
+END_TEST(testSliceBudgetUnlimited)
+
+BEGIN_TEST(testSliceBudgetWork) {
+ SliceBudget budget = SliceBudget(WorkBudget(10000));
+ CHECK(!budget.isUnlimited());
+ CHECK(budget.isWorkBudget());
+ CHECK(!budget.isTimeBudget());
+
+ CHECK(budget.workBudget() == 10000);
+
+ CHECK(!budget.isOverBudget());
+
+ budget.step(5000);
+ CHECK(!budget.isOverBudget());
+
+ budget.step(5000);
+ CHECK(budget.isOverBudget());
+
+ return true;
+}
+END_TEST(testSliceBudgetWork)
+
+BEGIN_TEST(testSliceBudgetTime) {
+ SliceBudget budget = SliceBudget(TimeBudget(10000));
+ CHECK(!budget.isUnlimited());
+ CHECK(!budget.isWorkBudget());
+ CHECK(budget.isTimeBudget());
+
+ CHECK(budget.timeBudget() == 10000);
+
+ CHECK(!budget.isOverBudget());
+
+ budget.step(5000);
+ budget.step(5000);
+ CHECK(!budget.isOverBudget());
+
+ // This doesn't test the deadline is correct as that would require waiting.
+
+ return true;
+}
+END_TEST(testSliceBudgetTime)
+
+BEGIN_TEST(testSliceBudgetTimeZero) {
+ SliceBudget budget = SliceBudget(TimeBudget(0));
+ budget.step(1000);
+ CHECK(budget.isOverBudget());
+
+ return true;
+}
+END_TEST(testSliceBudgetTimeZero)
+
+BEGIN_TEST(testSliceBudgetInterruptibleTime) {
+ SliceBudget::InterruptRequestFlag wantInterrupt(false);
+
+ // Interruptible 100 second budget. This test will finish in well under that
+ // time.
+ static constexpr int64_t LONG_TIME = 100000;
+ SliceBudget budget = SliceBudget(TimeBudget(LONG_TIME), &wantInterrupt);
+ CHECK(!budget.isUnlimited());
+ CHECK(!budget.isWorkBudget());
+ CHECK(budget.isTimeBudget());
+
+ CHECK(budget.timeBudget() == LONG_TIME);
+
+ CHECK(!budget.isOverBudget());
+
+ // We do a little work, very small amount of time passes.
+ budget.step(500);
+
+ // Not enough work to check interrupt, and no interrupt anyway.
+ CHECK(!budget.isOverBudget());
+
+ // External signal: interrupt requested.
+ wantInterrupt = true;
+
+ // Interrupt requested, but not enough work has been done to check for it.
+ CHECK(!budget.isOverBudget());
+
+ // Do enough work for an expensive check.
+ budget.step(1000);
+
+ // Interrupt requested! This will reset the external flag, but internally
+ // remember that an interrupt was requested.
+ CHECK(budget.isOverBudget());
+ CHECK(!wantInterrupt);
+ CHECK(budget.isOverBudget());
+
+ // This doesn't test the deadline is correct as that would require waiting.
+
+ return true;
+}
+END_TEST(testSliceBudgetInterruptibleTime)
diff --git a/js/src/jsapi-tests/testSlowScript.cpp b/js/src/jsapi-tests/testSlowScript.cpp
new file mode 100644
index 0000000000..154120eed1
--- /dev/null
+++ b/js/src/jsapi-tests/testSlowScript.cpp
@@ -0,0 +1,72 @@
+/* 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 "js/PropertyAndElement.h" // JS_DefineFunction
+#include "jsapi-tests/tests.h"
+
+static bool InterruptCallback(JSContext* cx) { return false; }
+
+static unsigned sRemain;
+
+static bool RequestInterruptCallback(JSContext* cx, unsigned argc,
+ JS::Value* vp) {
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ if (!sRemain--) {
+ JS_RequestInterruptCallback(cx);
+ }
+ args.rval().setUndefined();
+ return true;
+}
+
+BEGIN_TEST(testSlowScript) {
+ JS_AddInterruptCallback(cx, InterruptCallback);
+ JS_DefineFunction(cx, global, "requestInterruptCallback",
+ RequestInterruptCallback, 0, 0);
+
+ CHECK(
+ test("while (true)"
+ " for (i in [0,0,0,0])"
+ " requestInterruptCallback();"));
+
+ CHECK(
+ test("while (true)"
+ " for (i in [0,0,0,0])"
+ " for (j in [0,0,0,0])"
+ " requestInterruptCallback();"));
+
+ CHECK(
+ test("while (true)"
+ " for (i in [0,0,0,0])"
+ " for (j in [0,0,0,0])"
+ " for (k in [0,0,0,0])"
+ " requestInterruptCallback();"));
+
+ CHECK(
+ test("function* f() { while (true) yield requestInterruptCallback() }"
+ "for (i of f()) ;"));
+
+ CHECK(
+ test("function* f() { while (true) yield 1 }"
+ "for (i of f())"
+ " requestInterruptCallback();"));
+
+ return true;
+}
+
+bool test(const char* bytes) {
+ JS::RootedValue v(cx);
+
+ sRemain = 0;
+ CHECK(!evaluate(bytes, __FILE__, __LINE__, &v));
+ CHECK(!JS_IsExceptionPending(cx));
+ JS_ClearPendingException(cx);
+
+ sRemain = 1000;
+ CHECK(!evaluate(bytes, __FILE__, __LINE__, &v));
+ CHECK(!JS_IsExceptionPending(cx));
+ JS_ClearPendingException(cx);
+
+ return true;
+}
+END_TEST(testSlowScript)
diff --git a/js/src/jsapi-tests/testSourcePolicy.cpp b/js/src/jsapi-tests/testSourcePolicy.cpp
new file mode 100644
index 0000000000..b6f9d4a8d7
--- /dev/null
+++ b/js/src/jsapi-tests/testSourcePolicy.cpp
@@ -0,0 +1,57 @@
+/* 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/Utf8.h" // mozilla::Utf8Unit
+
+#include "js/CompilationAndEvaluation.h" // JS::CompileFunction, JS::Evaluate
+#include "js/GlobalObject.h" // JS_NewGlobalObject
+#include "js/MemoryFunctions.h"
+#include "js/SourceText.h" // JS::Source{Ownership,Text}
+#include "jsapi-tests/tests.h"
+#include "vm/JSScript.h"
+
+BEGIN_TEST(testBug795104) {
+ JS::RealmOptions options;
+ options.behaviors().setDiscardSource(true);
+
+ JS::RootedObject g(cx, JS_NewGlobalObject(cx, getGlobalClass(), nullptr,
+ JS::FireOnNewGlobalHook, options));
+ CHECK(g);
+
+ JSAutoRealm ar(cx, g);
+
+ const size_t strLen = 60002;
+ char* s = static_cast<char*>(JS_malloc(cx, strLen));
+ CHECK(s);
+
+ s[0] = '"';
+ memset(s + 1, 'x', strLen - 2);
+ s[strLen - 1] = '"';
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, s, strLen, JS::SourceOwnership::Borrowed));
+
+ JS::CompileOptions opts(cx);
+
+ // We don't want an rval for our JS::Evaluate call
+ opts.setNoScriptRval(true);
+
+ JS::RootedValue unused(cx);
+ CHECK(JS::Evaluate(cx, opts, srcBuf, &unused));
+
+ JS::RootedFunction fun(cx);
+ JS::RootedObjectVector emptyScopeChain(cx);
+
+ // But when compiling a function we don't want to use no-rval
+ // mode, since it's not supported for functions.
+ opts.setNoScriptRval(false);
+
+ fun = JS::CompileFunction(cx, emptyScopeChain, opts, "f", 0, nullptr, srcBuf);
+ CHECK(fun);
+
+ JS_free(cx, s);
+
+ return true;
+}
+END_TEST(testBug795104)
diff --git a/js/src/jsapi-tests/testSparseBitmap.cpp b/js/src/jsapi-tests/testSparseBitmap.cpp
new file mode 100644
index 0000000000..bd5e7fee95
--- /dev/null
+++ b/js/src/jsapi-tests/testSparseBitmap.cpp
@@ -0,0 +1,115 @@
+/* -*- 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/PodOperations.h"
+
+#include "ds/Bitmap.h"
+
+#include "jsapi-tests/tests.h"
+
+using namespace js;
+
+BEGIN_TEST(testSparseBitmapBasics) {
+ SparseBitmap bitmap;
+
+ // Test bits in first block are initially zero.
+ for (size_t i = 0; i < 100; i++) {
+ CHECK(!bitmap.getBit(i));
+ }
+
+ // Test bits in different blocks are initially zero.
+ for (size_t i = 0; i < 100; i++) {
+ CHECK(!bitmap.getBit(i * 1000));
+ }
+
+ // Set some bits in the first block and check they are set.
+ for (size_t i = 0; i < 100; i += 2) {
+ bitmap.setBit(i);
+ }
+ for (size_t i = 0; i < 100; i++) {
+ CHECK(bitmap.getBit(i) == ((i % 2) == 0));
+ }
+
+ // Set some bits in different blocks and check they are set.
+ for (size_t i = 0; i < 100; i += 2) {
+ bitmap.setBit(i * 1000);
+ }
+ for (size_t i = 0; i < 100; i++) {
+ CHECK(bitmap.getBit(i * 1000) == ((i % 2) == 0));
+ }
+
+ // Create another bitmap with different bits set.
+ SparseBitmap other;
+ for (size_t i = 1; i < 100; i += 2) {
+ other.setBit(i * 1000);
+ }
+ for (size_t i = 0; i < 100; i++) {
+ CHECK(other.getBit(i * 1000) == ((i % 2) != 0));
+ }
+
+ // OR some bits into this bitmap and check the result.
+ bitmap.bitwiseOrWith(other);
+ for (size_t i = 0; i < 100; i++) {
+ CHECK(bitmap.getBit(i * 1000));
+ }
+
+ // AND some bits into this bitmap and check the result.
+ DenseBitmap dense;
+ size_t wordCount = (100 * 1000) / JS_BITS_PER_WORD + 1;
+ CHECK(dense.ensureSpace(wordCount));
+ other.bitwiseOrInto(dense);
+ bitmap.bitwiseAndWith(dense);
+ for (size_t i = 0; i < 100; i++) {
+ CHECK(bitmap.getBit(i * 1000) == ((i % 2) != 0));
+ }
+
+ return true;
+}
+END_TEST(testSparseBitmapBasics)
+
+BEGIN_TEST(testSparseBitmapExternalOR) {
+ // Testing ORing data into an external array.
+
+ const size_t wordCount = 10;
+
+ // Create a bitmap with one bit set per word so we can tell them apart.
+ SparseBitmap bitmap;
+ for (size_t i = 0; i < wordCount; i++) {
+ bitmap.setBit(i * JS_BITS_PER_WORD + i);
+ }
+
+ // Copy a single word.
+ uintptr_t target[wordCount];
+ mozilla::PodArrayZero(target);
+ bitmap.bitwiseOrRangeInto(0, 1, target);
+ CHECK(target[0] == 1u << 0);
+ CHECK(target[1] == 0);
+
+ // Copy a word at an offset.
+ mozilla::PodArrayZero(target);
+ bitmap.bitwiseOrRangeInto(1, 1, target);
+ CHECK(target[0] == 1u << 1);
+ CHECK(target[1] == 0);
+
+ // Check data is ORed with original target contents.
+ mozilla::PodArrayZero(target);
+ bitmap.bitwiseOrRangeInto(0, 1, target);
+ bitmap.bitwiseOrRangeInto(1, 1, target);
+ CHECK(target[0] == ((1u << 0) | (1u << 1)));
+
+ // Copy multiple words at an offset.
+ mozilla::PodArrayZero(target);
+ bitmap.bitwiseOrRangeInto(2, wordCount - 2, target);
+ for (size_t i = 0; i < wordCount - 2; i++) {
+ CHECK(target[i] == (1u << (i + 2)));
+ }
+ CHECK(target[wordCount - 1] == 0);
+
+ return true;
+}
+
+END_TEST(testSparseBitmapExternalOR)
diff --git a/js/src/jsapi-tests/testStencil.cpp b/js/src/jsapi-tests/testStencil.cpp
new file mode 100644
index 0000000000..fee4e6d881
--- /dev/null
+++ b/js/src/jsapi-tests/testStencil.cpp
@@ -0,0 +1,881 @@
+/* -*- 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 <string.h>
+
+#include "jsapi.h"
+
+#include "frontend/CompilationStencil.h"
+#include "js/CompilationAndEvaluation.h"
+#include "js/experimental/CompileScript.h"
+#include "js/experimental/JSStencil.h"
+#include "js/Modules.h"
+#include "js/OffThreadScriptCompilation.h"
+#include "js/PropertyAndElement.h" // JS_GetProperty, JS_HasOwnProperty, JS_SetProperty
+#include "js/Transcoding.h"
+#include "jsapi-tests/tests.h"
+#include "vm/HelperThreads.h" // js::RunPendingSourceCompressions
+#include "vm/Monitor.h" // js::Monitor, js::AutoLockMonitor
+
+BEGIN_TEST(testStencil_Basic) {
+ const char* chars =
+ "function f() { return 42; }"
+ "f();";
+ auto result = basic_test<char, mozilla::Utf8Unit>(chars);
+ CHECK(result);
+
+ const char16_t* chars16 =
+ u"function f() { return 42; }"
+ u"f();";
+ auto result16 = basic_test<char16_t, char16_t>(chars16);
+ CHECK(result16);
+
+ return true;
+}
+
+template <typename CharT, typename SourceT>
+bool basic_test(const CharT* chars) {
+ size_t length = std::char_traits<CharT>::length(chars);
+
+ JS::SourceText<SourceT> srcBuf;
+ CHECK(srcBuf.init(cx, chars, length, JS::SourceOwnership::Borrowed));
+
+ JS::CompileOptions options(cx);
+ RefPtr<JS::Stencil> stencil =
+ JS::CompileGlobalScriptToStencil(cx, options, srcBuf);
+ CHECK(stencil);
+
+ JS::InstantiateOptions instantiateOptions(options);
+ JS::RootedScript script(
+ cx, JS::InstantiateGlobalStencil(cx, instantiateOptions, stencil));
+ CHECK(script);
+
+ JS::RootedValue rval(cx);
+ CHECK(JS_ExecuteScript(cx, script, &rval));
+ CHECK(rval.isNumber() && rval.toNumber() == 42);
+
+ return true;
+}
+END_TEST(testStencil_Basic)
+
+BEGIN_TEST(testStencil_Module) {
+ const char* chars =
+ "export function f() { return 42; }"
+ "globalThis.x = f();";
+ auto result = basic_test<char, mozilla::Utf8Unit>(chars);
+ CHECK(result);
+
+ const char16_t* chars16 =
+ u"export function f() { return 42; }"
+ u"globalThis.x = f();";
+ auto result16 = basic_test<char16_t, char16_t>(chars16);
+ CHECK(result16);
+
+ return true;
+}
+
+template <typename CharT, typename SourceT>
+bool basic_test(const CharT* chars) {
+ size_t length = std::char_traits<CharT>::length(chars);
+
+ JS::SourceText<SourceT> srcBuf;
+ CHECK(srcBuf.init(cx, chars, length, JS::SourceOwnership::Borrowed));
+
+ JS::CompileOptions options(cx);
+ RefPtr<JS::Stencil> stencil =
+ JS::CompileModuleScriptToStencil(cx, options, srcBuf);
+ CHECK(stencil);
+
+ JS::InstantiateOptions instantiateOptions(options);
+ JS::RootedObject moduleObject(
+ cx, JS::InstantiateModuleStencil(cx, instantiateOptions, stencil));
+ CHECK(moduleObject);
+
+ // Link and evaluate the module graph. The link step used to be call
+ // "instantiate" but is unrelated to the concept in Stencil with same name.
+ JS::RootedValue rval(cx);
+ CHECK(JS::ModuleLink(cx, moduleObject));
+ CHECK(JS::ModuleEvaluate(cx, moduleObject, &rval));
+ CHECK(!rval.isUndefined());
+
+ js::RunJobs(cx);
+ CHECK(JS_GetProperty(cx, global, "x", &rval));
+ CHECK(rval.isNumber() && rval.toNumber() == 42);
+
+ return true;
+}
+END_TEST(testStencil_Module)
+
+BEGIN_TEST(testStencil_NonSyntactic) {
+ const char* chars =
+ "function f() { return x; }"
+ "f();";
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, chars, strlen(chars), JS::SourceOwnership::Borrowed));
+
+ JS::CompileOptions options(cx);
+ options.setNonSyntacticScope(true);
+
+ RefPtr<JS::Stencil> stencil =
+ JS::CompileGlobalScriptToStencil(cx, options, srcBuf);
+ CHECK(stencil);
+
+ JS::InstantiateOptions instantiateOptions(options);
+ JS::RootedScript script(
+ cx, JS::InstantiateGlobalStencil(cx, instantiateOptions, stencil));
+ CHECK(script);
+
+ JS::RootedObject obj(cx, JS_NewPlainObject(cx));
+ JS::RootedValue val(cx, JS::Int32Value(42));
+ CHECK(obj);
+ CHECK(JS_SetProperty(cx, obj, "x", val));
+
+ JS::RootedObjectVector chain(cx);
+ CHECK(chain.append(obj));
+
+ JS::RootedValue rval(cx);
+ CHECK(JS_ExecuteScript(cx, chain, script, &rval));
+ CHECK(rval.isNumber() && rval.toNumber() == 42);
+
+ return true;
+}
+END_TEST(testStencil_NonSyntactic)
+
+BEGIN_TEST(testStencil_MultiGlobal) {
+ const char* chars =
+ "/**************************************/"
+ "/**************************************/"
+ "/**************************************/"
+ "/**************************************/"
+ "/**************************************/"
+ "/**************************************/"
+ "function f() { return 42; }"
+ "f();";
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, chars, strlen(chars), JS::SourceOwnership::Borrowed));
+
+ JS::CompileOptions options(cx);
+ RefPtr<JS::Stencil> stencil =
+ JS::CompileGlobalScriptToStencil(cx, options, srcBuf);
+ CHECK(stencil);
+
+ CHECK(RunInNewGlobal(cx, stencil));
+ CHECK(RunInNewGlobal(cx, stencil));
+ CHECK(RunInNewGlobal(cx, stencil));
+
+ // Start any pending SourceCompressionTasks now to confirm nothing fell apart
+ // when using a JS::Stencil multiple times.
+ CHECK(strlen(chars) > js::ScriptSource::MinimumCompressibleLength);
+ js::RunPendingSourceCompressions(cx->runtime());
+
+ return true;
+}
+bool RunInNewGlobal(JSContext* cx, RefPtr<JS::Stencil> stencil) {
+ JS::RootedObject otherGlobal(cx, createGlobal());
+ CHECK(otherGlobal);
+
+ JSAutoRealm ar(cx, otherGlobal);
+
+ JS::InstantiateOptions instantiateOptions;
+ JS::RootedScript script(
+ cx, JS::InstantiateGlobalStencil(cx, instantiateOptions, stencil));
+ CHECK(script);
+
+ JS::RootedValue rval(cx);
+ CHECK(JS_ExecuteScript(cx, script, &rval));
+ CHECK(rval.isNumber() && rval.toNumber() == 42);
+
+ return true;
+}
+END_TEST(testStencil_MultiGlobal)
+
+BEGIN_TEST(testStencil_Transcode) {
+ JS::SetProcessBuildIdOp(TestGetBuildId);
+
+ JS::TranscodeBuffer buffer;
+
+ {
+ const char* chars =
+ "function f() { return 42; }"
+ "f();";
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, chars, strlen(chars), JS::SourceOwnership::Borrowed));
+
+ JS::CompileOptions options(cx);
+ RefPtr<JS::Stencil> stencil =
+ JS::CompileGlobalScriptToStencil(cx, options, srcBuf);
+ CHECK(stencil);
+
+ // Encode Stencil to XDR
+ JS::TranscodeResult res = JS::EncodeStencil(cx, stencil, buffer);
+ CHECK(res == JS::TranscodeResult::Ok);
+ CHECK(!buffer.empty());
+
+ // Instantiate and Run
+ JS::InstantiateOptions instantiateOptions(options);
+ JS::RootedScript script(
+ cx, JS::InstantiateGlobalStencil(cx, instantiateOptions, stencil));
+ JS::RootedValue rval(cx);
+ CHECK(script);
+ CHECK(JS_ExecuteScript(cx, script, &rval));
+ CHECK(rval.isNumber() && rval.toNumber() == 42);
+ }
+
+ // Create a new global
+ CHECK(createGlobal());
+ JSAutoRealm ar(cx, global);
+
+ // Confirm it doesn't have the old code
+ bool found = false;
+ CHECK(JS_HasOwnProperty(cx, global, "f", &found));
+ CHECK(!found);
+
+ {
+ // Decode the stencil into new range
+ RefPtr<JS::Stencil> stencil;
+
+ {
+ JS::DecodeOptions decodeOptions;
+ JS::TranscodeRange range(buffer.begin(), buffer.length());
+ JS::TranscodeResult res =
+ JS::DecodeStencil(cx, decodeOptions, range, getter_AddRefs(stencil));
+ CHECK(res == JS::TranscodeResult::Ok);
+ }
+
+ {
+ JS::FrontendContext* fc = JS::NewFrontendContext();
+ JS::DecodeOptions decodeOptions;
+ JS::TranscodeRange range(buffer.begin(), buffer.length());
+ JS::TranscodeResult res =
+ JS::DecodeStencil(fc, decodeOptions, range, getter_AddRefs(stencil));
+ CHECK(res == JS::TranscodeResult::Ok);
+ JS::DestroyFrontendContext(fc);
+ }
+
+ // Delete the buffer to verify that the decoded stencil has no dependency
+ // to the buffer.
+ memset(buffer.begin(), 0, buffer.length());
+ buffer.clear();
+
+ // Instantiate and Run
+ JS::InstantiateOptions instantiateOptions;
+ JS::RootedScript script(
+ cx, JS::InstantiateGlobalStencil(cx, instantiateOptions, stencil));
+ stencil = nullptr;
+ JS::RootedValue rval(cx);
+ CHECK(script);
+ CHECK(JS_ExecuteScript(cx, script, &rval));
+ CHECK(rval.isNumber() && rval.toNumber() == 42);
+ }
+
+ return true;
+}
+static bool TestGetBuildId(JS::BuildIdCharVector* buildId) {
+ const char buildid[] = "testXDR";
+ return buildId->append(buildid, sizeof(buildid));
+}
+END_TEST(testStencil_Transcode)
+
+BEGIN_TEST(testStencil_TranscodeBorrowing) {
+ JS::SetProcessBuildIdOp(TestGetBuildId);
+
+ JS::TranscodeBuffer buffer;
+
+ {
+ const char* chars =
+ "function f() { return 42; }"
+ "f();";
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, chars, strlen(chars), JS::SourceOwnership::Borrowed));
+
+ JS::CompileOptions options(cx);
+ RefPtr<JS::Stencil> stencil =
+ JS::CompileGlobalScriptToStencil(cx, options, srcBuf);
+ CHECK(stencil);
+
+ // Encode Stencil to XDR
+ JS::TranscodeResult res = JS::EncodeStencil(cx, stencil, buffer);
+ CHECK(res == JS::TranscodeResult::Ok);
+ CHECK(!buffer.empty());
+ }
+
+ JS::RootedScript script(cx);
+ {
+ JS::TranscodeRange range(buffer.begin(), buffer.length());
+ JS::DecodeOptions decodeOptions;
+ decodeOptions.borrowBuffer = true;
+ RefPtr<JS::Stencil> stencil;
+ JS::TranscodeResult res =
+ JS::DecodeStencil(cx, decodeOptions, range, getter_AddRefs(stencil));
+ CHECK(res == JS::TranscodeResult::Ok);
+
+ JS::InstantiateOptions instantiateOptions;
+ script = JS::InstantiateGlobalStencil(cx, instantiateOptions, stencil);
+ CHECK(script);
+ }
+
+ // Delete the buffer to verify that the instantiated script has no dependency
+ // to the buffer.
+ memset(buffer.begin(), 0, buffer.length());
+ buffer.clear();
+
+ JS::RootedValue rval(cx);
+ CHECK(JS_ExecuteScript(cx, script, &rval));
+ CHECK(rval.isNumber() && rval.toNumber() == 42);
+
+ return true;
+}
+static bool TestGetBuildId(JS::BuildIdCharVector* buildId) {
+ const char buildid[] = "testXDR";
+ return buildId->append(buildid, sizeof(buildid));
+}
+END_TEST(testStencil_TranscodeBorrowing)
+
+BEGIN_TEST(testStencil_OffThread) {
+ const char* chars =
+ "function f() { return 42; }"
+ "f();";
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, chars, strlen(chars), JS::SourceOwnership::Borrowed));
+
+ js::Monitor monitor MOZ_UNANNOTATED(js::mutexid::ShellOffThreadState);
+ JS::CompileOptions options(cx);
+ JS::OffThreadToken* token;
+
+ // Force off-thread even though if this is a small file.
+ options.forceAsync = true;
+
+ CHECK(token = JS::CompileToStencilOffThread(cx, options, srcBuf, callback,
+ &monitor));
+
+ {
+ js::AutoLockMonitor lock(monitor);
+ lock.wait();
+ }
+
+ RefPtr<JS::Stencil> stencil = JS::FinishOffThreadStencil(cx, token);
+ CHECK(stencil);
+
+ JS::InstantiateOptions instantiateOptions(options);
+ JS::RootedScript script(
+ cx, JS::InstantiateGlobalStencil(cx, instantiateOptions, stencil));
+ CHECK(script);
+
+ JS::RootedValue rval(cx);
+ CHECK(JS_ExecuteScript(cx, script, &rval));
+ CHECK(rval.isNumber() && rval.toNumber() == 42);
+
+ return true;
+}
+
+static void callback(JS::OffThreadToken* token, void* context) {
+ js::Monitor& monitor = *static_cast<js::Monitor*>(context);
+
+ js::AutoLockMonitor lock(monitor);
+ lock.notify();
+}
+
+END_TEST(testStencil_OffThread)
+
+BEGIN_TEST(testStencil_OffThreadWithInstantiationStorage) {
+ const char* chars =
+ "function f() { return 42; }"
+ "f();";
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, chars, strlen(chars), JS::SourceOwnership::Borrowed));
+
+ js::Monitor monitor MOZ_UNANNOTATED(js::mutexid::ShellOffThreadState);
+ JS::CompileOptions options(cx);
+ JS::OffThreadToken* token;
+
+ // Force off-thread even though if this is a small file.
+ options.forceAsync = true;
+
+ options.allocateInstantiationStorage = true;
+
+ CHECK(token = JS::CompileToStencilOffThread(cx, options, srcBuf, callback,
+ &monitor));
+
+ {
+ js::AutoLockMonitor lock(monitor);
+ lock.wait();
+ }
+
+ JS::Rooted<JS::InstantiationStorage> storage(cx);
+ RefPtr<JS::Stencil> stencil =
+ JS::FinishOffThreadStencil(cx, token, storage.address());
+ CHECK(stencil);
+
+ JS::InstantiateOptions instantiateOptions(options);
+ JS::RootedScript script(
+ cx, JS::InstantiateGlobalStencil(cx, instantiateOptions, stencil,
+ storage.address()));
+ CHECK(script);
+
+ JS::RootedValue rval(cx);
+ CHECK(JS_ExecuteScript(cx, script, &rval));
+ CHECK(rval.isNumber() && rval.toNumber() == 42);
+
+ return true;
+}
+
+static void callback(JS::OffThreadToken* token, void* context) {
+ js::Monitor& monitor = *static_cast<js::Monitor*>(context);
+
+ js::AutoLockMonitor lock(monitor);
+ lock.notify();
+}
+
+END_TEST(testStencil_OffThreadWithInstantiationStorage)
+
+BEGIN_TEST(testStencil_OffThreadModule) {
+ const char* chars =
+ "export function f() { return 42; }"
+ "globalThis.x = f();";
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, chars, strlen(chars), JS::SourceOwnership::Borrowed));
+
+ js::Monitor monitor MOZ_UNANNOTATED(js::mutexid::ShellOffThreadState);
+ JS::CompileOptions options(cx);
+ JS::OffThreadToken* token;
+
+ // Force off-thread even though if this is a small file.
+ options.forceAsync = true;
+
+ CHECK(token = JS::CompileModuleToStencilOffThread(cx, options, srcBuf,
+ callback, &monitor));
+
+ {
+ js::AutoLockMonitor lock(monitor);
+ lock.wait();
+ }
+
+ RefPtr<JS::Stencil> stencil = JS::FinishOffThreadStencil(cx, token);
+ CHECK(stencil);
+
+ JS::InstantiateOptions instantiateOptions(options);
+ JS::RootedObject moduleObject(
+ cx, JS::InstantiateModuleStencil(cx, instantiateOptions, stencil));
+ CHECK(moduleObject);
+
+ JS::RootedValue rval(cx);
+ CHECK(JS::ModuleLink(cx, moduleObject));
+ CHECK(JS::ModuleEvaluate(cx, moduleObject, &rval));
+ CHECK(!rval.isUndefined());
+
+ js::RunJobs(cx);
+ CHECK(JS_GetProperty(cx, global, "x", &rval));
+ CHECK(rval.isNumber() && rval.toNumber() == 42);
+
+ return true;
+}
+
+static void callback(JS::OffThreadToken* token, void* context) {
+ js::Monitor& monitor = *static_cast<js::Monitor*>(context);
+
+ js::AutoLockMonitor lock(monitor);
+ lock.notify();
+}
+END_TEST(testStencil_OffThreadModule)
+
+BEGIN_TEST(testStencil_OffThreadModuleWithInstantiationStorage) {
+ const char* chars =
+ "export function f() { return 42; }"
+ "globalThis.x = f();";
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, chars, strlen(chars), JS::SourceOwnership::Borrowed));
+
+ js::Monitor monitor MOZ_UNANNOTATED(js::mutexid::ShellOffThreadState);
+ JS::CompileOptions options(cx);
+ JS::OffThreadToken* token;
+
+ // Force off-thread even though if this is a small file.
+ options.forceAsync = true;
+
+ options.allocateInstantiationStorage = true;
+
+ CHECK(token = JS::CompileModuleToStencilOffThread(cx, options, srcBuf,
+ callback, &monitor));
+
+ {
+ js::AutoLockMonitor lock(monitor);
+ lock.wait();
+ }
+
+ JS::Rooted<JS::InstantiationStorage> storage(cx);
+ RefPtr<JS::Stencil> stencil =
+ JS::FinishOffThreadStencil(cx, token, storage.address());
+ CHECK(stencil);
+
+ JS::InstantiateOptions instantiateOptions(options);
+ JS::RootedObject moduleObject(
+ cx, JS::InstantiateModuleStencil(cx, instantiateOptions, stencil,
+ storage.address()));
+ CHECK(moduleObject);
+
+ JS::RootedValue rval(cx);
+ CHECK(JS::ModuleLink(cx, moduleObject));
+ CHECK(JS::ModuleEvaluate(cx, moduleObject, &rval));
+ CHECK(!rval.isUndefined());
+
+ js::RunJobs(cx);
+ CHECK(JS_GetProperty(cx, global, "x", &rval));
+ CHECK(rval.isNumber() && rval.toNumber() == 42);
+
+ return true;
+}
+
+static void callback(JS::OffThreadToken* token, void* context) {
+ js::Monitor& monitor = *static_cast<js::Monitor*>(context);
+
+ js::AutoLockMonitor lock(monitor);
+ lock.notify();
+}
+END_TEST(testStencil_OffThreadModuleWithInstantiationStorage)
+
+BEGIN_TEST(testStencil_OffThreadDecode) {
+ JS::SetProcessBuildIdOp(TestGetBuildId);
+
+ JS::TranscodeBuffer buffer;
+
+ {
+ const char* chars =
+ "function f() { return 42; }"
+ "f();";
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, chars, strlen(chars), JS::SourceOwnership::Borrowed));
+
+ JS::CompileOptions options(cx);
+ RefPtr<JS::Stencil> stencil =
+ JS::CompileGlobalScriptToStencil(cx, options, srcBuf);
+ CHECK(stencil);
+
+ // Encode Stencil to XDR
+ JS::TranscodeResult res = JS::EncodeStencil(cx, stencil, buffer);
+ CHECK(res == JS::TranscodeResult::Ok);
+ CHECK(!buffer.empty());
+
+ // Instantiate and Run
+ JS::InstantiateOptions instantiateOptions(options);
+ JS::RootedScript script(
+ cx, JS::InstantiateGlobalStencil(cx, instantiateOptions, stencil));
+ JS::RootedValue rval(cx);
+ CHECK(script);
+ CHECK(JS_ExecuteScript(cx, script, &rval));
+ CHECK(rval.isNumber() && rval.toNumber() == 42);
+ }
+
+ JS::OffThreadToken* token;
+ {
+ JS::DecodeOptions decodeOptions;
+ js::Monitor monitor MOZ_UNANNOTATED(js::mutexid::ShellOffThreadState);
+ JS::TranscodeRange range(buffer.begin(), buffer.length());
+
+ // Force off-thread even though if this is a small file.
+ decodeOptions.forceAsync = true;
+
+ CHECK(token = JS::DecodeStencilOffThread(cx, decodeOptions, range, callback,
+ &monitor));
+
+ {
+ js::AutoLockMonitor lock(monitor);
+ lock.wait();
+ }
+ }
+
+ RefPtr<JS::Stencil> stencil = JS::FinishOffThreadStencil(cx, token);
+ CHECK(stencil);
+
+ CHECK(!JS::StencilIsBorrowed(stencil));
+
+ JS::InstantiateOptions instantiateOptions;
+ JS::RootedScript script(
+ cx, JS::InstantiateGlobalStencil(cx, instantiateOptions, stencil));
+ CHECK(script);
+
+ JS::RootedValue rval(cx);
+ CHECK(JS_ExecuteScript(cx, script, &rval));
+ CHECK(rval.isNumber() && rval.toNumber() == 42);
+
+ return true;
+}
+static void callback(JS::OffThreadToken* token, void* context) {
+ js::Monitor& monitor = *static_cast<js::Monitor*>(context);
+
+ js::AutoLockMonitor lock(monitor);
+ lock.notify();
+}
+static bool TestGetBuildId(JS::BuildIdCharVector* buildId) {
+ const char buildid[] = "testXDR";
+ return buildId->append(buildid, sizeof(buildid));
+}
+END_TEST(testStencil_OffThreadDecode)
+
+BEGIN_TEST(testStencil_OffThreadDecodeWithInstantiationStorage) {
+ JS::SetProcessBuildIdOp(TestGetBuildId);
+
+ JS::TranscodeBuffer buffer;
+
+ {
+ const char* chars =
+ "function f() { return 42; }"
+ "f();";
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, chars, strlen(chars), JS::SourceOwnership::Borrowed));
+
+ JS::CompileOptions options(cx);
+ RefPtr<JS::Stencil> stencil =
+ JS::CompileGlobalScriptToStencil(cx, options, srcBuf);
+ CHECK(stencil);
+
+ // Encode Stencil to XDR
+ JS::TranscodeResult res = JS::EncodeStencil(cx, stencil, buffer);
+ CHECK(res == JS::TranscodeResult::Ok);
+ CHECK(!buffer.empty());
+
+ // Instantiate and Run
+ JS::InstantiateOptions instantiateOptions(options);
+ JS::RootedScript script(
+ cx, JS::InstantiateGlobalStencil(cx, instantiateOptions, stencil));
+ JS::RootedValue rval(cx);
+ CHECK(script);
+ CHECK(JS_ExecuteScript(cx, script, &rval));
+ CHECK(rval.isNumber() && rval.toNumber() == 42);
+ }
+
+ JS::OffThreadToken* token;
+ {
+ JS::DecodeOptions decodeOptions;
+ js::Monitor monitor MOZ_UNANNOTATED(js::mutexid::ShellOffThreadState);
+ JS::TranscodeRange range(buffer.begin(), buffer.length());
+
+ // Force off-thread even though if this is a small file.
+ decodeOptions.forceAsync = true;
+
+ decodeOptions.allocateInstantiationStorage = true;
+
+ CHECK(token = JS::DecodeStencilOffThread(cx, decodeOptions, range, callback,
+ &monitor));
+
+ {
+ js::AutoLockMonitor lock(monitor);
+ lock.wait();
+ }
+ }
+
+ JS::Rooted<JS::InstantiationStorage> storage(cx);
+ RefPtr<JS::Stencil> stencil =
+ JS::FinishOffThreadStencil(cx, token, storage.address());
+ CHECK(stencil);
+
+ CHECK(!JS::StencilIsBorrowed(stencil));
+
+ JS::InstantiateOptions instantiateOptions;
+ JS::RootedScript script(
+ cx, JS::InstantiateGlobalStencil(cx, instantiateOptions, stencil,
+ storage.address()));
+ CHECK(script);
+
+ JS::RootedValue rval(cx);
+ CHECK(JS_ExecuteScript(cx, script, &rval));
+ CHECK(rval.isNumber() && rval.toNumber() == 42);
+
+ return true;
+}
+static void callback(JS::OffThreadToken* token, void* context) {
+ js::Monitor& monitor = *static_cast<js::Monitor*>(context);
+
+ js::AutoLockMonitor lock(monitor);
+ lock.notify();
+}
+static bool TestGetBuildId(JS::BuildIdCharVector* buildId) {
+ const char buildid[] = "testXDR";
+ return buildId->append(buildid, sizeof(buildid));
+}
+END_TEST(testStencil_OffThreadDecodeWithInstantiationStorage)
+
+BEGIN_TEST(testStencil_OffThreadDecodeBorrow) {
+ JS::SetProcessBuildIdOp(TestGetBuildId);
+
+ JS::TranscodeBuffer buffer;
+
+ {
+ const char* chars =
+ "function f() { return 42; }"
+ "f();";
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, chars, strlen(chars), JS::SourceOwnership::Borrowed));
+
+ JS::CompileOptions options(cx);
+ RefPtr<JS::Stencil> stencil =
+ JS::CompileGlobalScriptToStencil(cx, options, srcBuf);
+ CHECK(stencil);
+
+ // Encode Stencil to XDR
+ JS::TranscodeResult res = JS::EncodeStencil(cx, stencil, buffer);
+ CHECK(res == JS::TranscodeResult::Ok);
+ CHECK(!buffer.empty());
+
+ // Instantiate and Run
+ JS::InstantiateOptions instantiateOptions(options);
+ JS::RootedScript script(
+ cx, JS::InstantiateGlobalStencil(cx, instantiateOptions, stencil));
+ JS::RootedValue rval(cx);
+ CHECK(script);
+ CHECK(JS_ExecuteScript(cx, script, &rval));
+ CHECK(rval.isNumber() && rval.toNumber() == 42);
+ }
+
+ JS::OffThreadToken* token;
+ {
+ JS::DecodeOptions decodeOptions;
+ js::Monitor monitor MOZ_UNANNOTATED(js::mutexid::ShellOffThreadState);
+ JS::TranscodeRange range(buffer.begin(), buffer.length());
+
+ // Force off-thread even though if this is a small file.
+ decodeOptions.forceAsync = true;
+
+ decodeOptions.borrowBuffer = true;
+
+ CHECK(token = JS::DecodeStencilOffThread(cx, decodeOptions, range, callback,
+ &monitor));
+
+ {
+ js::AutoLockMonitor lock(monitor);
+ lock.wait();
+ }
+ }
+
+ RefPtr<JS::Stencil> stencil = JS::FinishOffThreadStencil(cx, token);
+ CHECK(stencil);
+
+ CHECK(JS::StencilIsBorrowed(stencil));
+
+ JS::InstantiateOptions instantiateOptions;
+ JS::RootedScript script(
+ cx, JS::InstantiateGlobalStencil(cx, instantiateOptions, stencil));
+ CHECK(script);
+
+ JS::RootedValue rval(cx);
+ CHECK(JS_ExecuteScript(cx, script, &rval));
+ CHECK(rval.isNumber() && rval.toNumber() == 42);
+
+ return true;
+}
+static void callback(JS::OffThreadToken* token, void* context) {
+ js::Monitor& monitor = *static_cast<js::Monitor*>(context);
+
+ js::AutoLockMonitor lock(monitor);
+ lock.notify();
+}
+static bool TestGetBuildId(JS::BuildIdCharVector* buildId) {
+ const char buildid[] = "testXDR";
+ return buildId->append(buildid, sizeof(buildid));
+}
+END_TEST(testStencil_OffThreadDecodeBorrow)
+
+constexpr size_t PinnedBufferMax = 1024;
+alignas(4) uint8_t pinnedBuffer[PinnedBufferMax];
+size_t pinnedBufferSize = 0;
+
+BEGIN_TEST(testStencil_OffThreadDecodePinned) {
+ JS::SetProcessBuildIdOp(TestGetBuildId);
+
+ JS::TranscodeBuffer buffer;
+
+ {
+ const char* chars =
+ "function f() { return 42; }"
+ "f();";
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, chars, strlen(chars), JS::SourceOwnership::Borrowed));
+
+ JS::CompileOptions options(cx);
+ RefPtr<JS::Stencil> stencil =
+ JS::CompileGlobalScriptToStencil(cx, options, srcBuf);
+ CHECK(stencil);
+
+ // Encode Stencil to XDR
+ JS::TranscodeResult res = JS::EncodeStencil(cx, stencil, buffer);
+ CHECK(res == JS::TranscodeResult::Ok);
+ CHECK(!buffer.empty());
+
+ // Instantiate and Run
+ JS::InstantiateOptions instantiateOptions(options);
+ JS::RootedScript script(
+ cx, JS::InstantiateGlobalStencil(cx, instantiateOptions, stencil));
+ JS::RootedValue rval(cx);
+ CHECK(script);
+ CHECK(JS_ExecuteScript(cx, script, &rval));
+ CHECK(rval.isNumber() && rval.toNumber() == 42);
+ }
+
+ CHECK(buffer.length() < PinnedBufferMax);
+
+ memcpy(pinnedBuffer, buffer.begin(), buffer.length());
+ pinnedBufferSize = buffer.length();
+
+ JS::OffThreadToken* token;
+ {
+ JS::DecodeOptions decodeOptions;
+ js::Monitor monitor MOZ_UNANNOTATED(js::mutexid::ShellOffThreadState);
+ JS::TranscodeRange range(pinnedBuffer, pinnedBufferSize);
+
+ // Force off-thread even though if this is a small file.
+ decodeOptions.forceAsync = true;
+
+ decodeOptions.borrowBuffer = true;
+ decodeOptions.usePinnedBytecode = true;
+
+ CHECK(token = JS::DecodeStencilOffThread(cx, decodeOptions, range, callback,
+ &monitor));
+
+ {
+ js::AutoLockMonitor lock(monitor);
+ lock.wait();
+ }
+ }
+
+ RefPtr<JS::Stencil> stencil = JS::FinishOffThreadStencil(cx, token);
+ CHECK(stencil);
+
+ CHECK(JS::StencilIsBorrowed(stencil));
+
+ JS::InstantiateOptions instantiateOptions;
+ JS::RootedScript script(
+ cx, JS::InstantiateGlobalStencil(cx, instantiateOptions, stencil));
+ CHECK(script);
+
+ JS::RootedValue rval(cx);
+ CHECK(JS_ExecuteScript(cx, script, &rval));
+ CHECK(rval.isNumber() && rval.toNumber() == 42);
+
+ return true;
+}
+static void callback(JS::OffThreadToken* token, void* context) {
+ js::Monitor& monitor = *static_cast<js::Monitor*>(context);
+
+ js::AutoLockMonitor lock(monitor);
+ lock.notify();
+}
+static bool TestGetBuildId(JS::BuildIdCharVector* buildId) {
+ const char buildid[] = "testXDR";
+ return buildId->append(buildid, sizeof(buildid));
+}
+END_TEST(testStencil_OffThreadDecodePinned)
diff --git a/js/src/jsapi-tests/testStringBuffer.cpp b/js/src/jsapi-tests/testStringBuffer.cpp
new file mode 100644
index 0000000000..300524c310
--- /dev/null
+++ b/js/src/jsapi-tests/testStringBuffer.cpp
@@ -0,0 +1,27 @@
+/* -*- 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 "jsapi-tests/tests.h"
+#include "util/StringBuffer.h"
+#include "vm/JSAtom.h"
+
+BEGIN_TEST(testStringBuffer_finishString) {
+ JSString* str = JS_NewStringCopyZ(cx, "foopy");
+ CHECK(str);
+
+ JS::Rooted<JSAtom*> atom(cx, js::AtomizeString(cx, str));
+ CHECK(atom);
+
+ js::StringBuffer buffer(cx);
+ CHECK(buffer.append("foopy"));
+
+ JS::Rooted<JSAtom*> finishedAtom(cx, buffer.finishAtom());
+ CHECK(finishedAtom);
+ CHECK_EQUAL(atom, finishedAtom);
+ return true;
+}
+END_TEST(testStringBuffer_finishString)
diff --git a/js/src/jsapi-tests/testStringIsArrayIndex.cpp b/js/src/jsapi-tests/testStringIsArrayIndex.cpp
new file mode 100644
index 0000000000..7eb90fcbac
--- /dev/null
+++ b/js/src/jsapi-tests/testStringIsArrayIndex.cpp
@@ -0,0 +1,81 @@
+/* -*- 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 <string>
+
+#include "jsfriendapi.h"
+
+#include "jsapi-tests/tests.h"
+
+static const struct TestTuple {
+ /* The string being tested. */
+ const char16_t* string;
+ /* The number of characters from the string to use. */
+ size_t length;
+ /* Whether this string is an index. */
+ bool isindex;
+ /* If it's an index, what index it is. Ignored if not an index. */
+ uint32_t index;
+
+ constexpr TestTuple(const char16_t* string, bool isindex, uint32_t index)
+ : TestTuple(string, std::char_traits<char16_t>::length(string), isindex,
+ index) {}
+
+ constexpr TestTuple(const char16_t* string, size_t length, bool isindex,
+ uint32_t index)
+ : string(string), length(length), isindex(isindex), index(index) {}
+} tests[] = {
+ {u"0", true, 0},
+ {u"1", true, 1},
+ {u"2", true, 2},
+ {u"9", true, 9},
+ {u"10", true, 10},
+ {u"15", true, 15},
+ {u"16", true, 16},
+ {u"17", true, 17},
+ {u"99", true, 99},
+ {u"100", true, 100},
+ {u"255", true, 255},
+ {u"256", true, 256},
+ {u"257", true, 257},
+ {u"999", true, 999},
+ {u"1000", true, 1000},
+ {u"4095", true, 4095},
+ {u"4096", true, 4096},
+ {u"9999", true, 9999},
+ {u"1073741823", true, 1073741823},
+ {u"1073741824", true, 1073741824},
+ {u"1073741825", true, 1073741825},
+ {u"2147483647", true, 2147483647},
+ {u"2147483648", true, 2147483648u},
+ {u"2147483649", true, 2147483649u},
+ {u"4294967294", true, 4294967294u},
+ {u"4294967295", false,
+ 0}, // Not an array index because need to be able to represent length
+ {u"-1", false, 0},
+ {u"abc", false, 0},
+ {u" 0", false, 0},
+ {u"0 ", false, 0},
+ // Tests to make sure the passed-in length is taken into account
+ {u"0 ", 1, true, 0},
+ {u"123abc", 3, true, 123},
+ {u"123abc", 2, true, 12},
+};
+
+BEGIN_TEST(testStringIsArrayIndex) {
+ for (const auto& test : tests) {
+ uint32_t index;
+ bool isindex = js::StringIsArrayIndex(test.string, test.length, &index);
+ CHECK_EQUAL(isindex, test.isindex);
+ if (isindex) {
+ CHECK_EQUAL(index, test.index);
+ }
+ }
+
+ return true;
+}
+END_TEST(testStringIsArrayIndex)
diff --git a/js/src/jsapi-tests/testStructuredClone.cpp b/js/src/jsapi-tests/testStructuredClone.cpp
new file mode 100644
index 0000000000..2604af202c
--- /dev/null
+++ b/js/src/jsapi-tests/testStructuredClone.cpp
@@ -0,0 +1,365 @@
+/* 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 "builtin/TestingFunctions.h"
+#include "js/ArrayBuffer.h" // JS::{IsArrayBufferObject,GetArrayBufferLengthAndData,NewExternalArrayBuffer}
+#include "js/GlobalObject.h" // JS_NewGlobalObject
+#include "js/PropertyAndElement.h" // JS_GetProperty, JS_SetProperty
+#include "js/StructuredClone.h"
+
+#include "jsapi-tests/tests.h"
+
+using namespace js;
+
+#ifdef DEBUG
+// Skip test, since it will abort with an assert in buf->Init(7).
+#else
+BEGIN_TEST(testStructuredClone_invalidLength) {
+ auto buf = js::MakeUnique<JSStructuredCloneData>(
+ JS::StructuredCloneScope::DifferentProcess);
+ CHECK(buf);
+ CHECK(buf->Init(7));
+ RootedValue clone(cx);
+ JS::CloneDataPolicy policy;
+ CHECK(!JS_ReadStructuredClone(cx, *buf, JS_STRUCTURED_CLONE_VERSION,
+ JS::StructuredCloneScope::DifferentProcess,
+ &clone, policy, nullptr, nullptr));
+ return true;
+}
+END_TEST(testStructuredClone_invalidLength)
+#endif
+
+BEGIN_TEST(testStructuredClone_object) {
+ JS::RootedObject g1(cx, createGlobal());
+ JS::RootedObject g2(cx, createGlobal());
+ CHECK(g1);
+ CHECK(g2);
+
+ JS::RootedValue v1(cx);
+
+ {
+ JSAutoRealm ar(cx, g1);
+ JS::RootedValue prop(cx, JS::Int32Value(1337));
+
+ JS::RootedObject obj(cx, JS_NewPlainObject(cx));
+ v1 = JS::ObjectOrNullValue(obj);
+ CHECK(v1.isObject());
+ CHECK(JS_SetProperty(cx, obj, "prop", prop));
+ }
+
+ {
+ JSAutoRealm ar(cx, g2);
+ JS::RootedValue v2(cx);
+
+ CHECK(JS_StructuredClone(cx, v1, &v2, nullptr, nullptr));
+ CHECK(v2.isObject());
+ JS::RootedObject obj(cx, &v2.toObject());
+
+ JS::RootedValue prop(cx);
+ CHECK(JS_GetProperty(cx, obj, "prop", &prop));
+ CHECK(prop.isInt32());
+ CHECK(&v1.toObject() != obj);
+ CHECK_EQUAL(prop.toInt32(), 1337);
+ }
+
+ return true;
+}
+END_TEST(testStructuredClone_object)
+
+BEGIN_TEST(testStructuredClone_string) {
+ JS::RootedObject g1(cx, createGlobal());
+ JS::RootedObject g2(cx, createGlobal());
+ CHECK(g1);
+ CHECK(g2);
+
+ JS::RootedValue v1(cx);
+
+ {
+ JSAutoRealm ar(cx, g1);
+ JS::RootedValue prop(cx, JS::Int32Value(1337));
+
+ v1 = JS::StringValue(JS_NewStringCopyZ(cx, "Hello World!"));
+ CHECK(v1.isString());
+ CHECK(v1.toString());
+ }
+
+ {
+ JSAutoRealm ar(cx, g2);
+ JS::RootedValue v2(cx);
+
+ CHECK(JS_StructuredClone(cx, v1, &v2, nullptr, nullptr));
+ CHECK(v2.isString());
+ CHECK(v2.toString());
+
+ JS::RootedValue expected(
+ cx, JS::StringValue(JS_NewStringCopyZ(cx, "Hello World!")));
+ CHECK_SAME(v2, expected);
+ }
+
+ return true;
+}
+END_TEST(testStructuredClone_string)
+
+BEGIN_TEST(testStructuredClone_externalArrayBuffer) {
+ ExternalData data("One two three four");
+ JS::RootedObject g1(cx, createGlobal());
+ JS::RootedObject g2(cx, createGlobal());
+ CHECK(g1);
+ CHECK(g2);
+
+ JS::RootedValue v1(cx);
+
+ {
+ JSAutoRealm ar(cx, g1);
+
+ JS::RootedObject obj(
+ cx, JS::NewExternalArrayBuffer(cx, data.len(), data.contents(),
+ &ExternalData::freeCallback, &data));
+ CHECK(!data.wasFreed());
+
+ v1 = JS::ObjectOrNullValue(obj);
+ CHECK(v1.isObject());
+ }
+
+ {
+ JSAutoRealm ar(cx, g2);
+ JS::RootedValue v2(cx);
+
+ CHECK(JS_StructuredClone(cx, v1, &v2, nullptr, nullptr));
+ CHECK(v2.isObject());
+
+ JS::RootedObject obj(cx, &v2.toObject());
+ CHECK(&v1.toObject() != obj);
+
+ size_t len;
+ bool isShared;
+ uint8_t* clonedData;
+ JS::GetArrayBufferLengthAndData(obj, &len, &isShared, &clonedData);
+
+ // The contents of the two array buffers should be equal, but not the
+ // same pointer.
+ CHECK_EQUAL(len, data.len());
+ CHECK(clonedData != data.contents());
+ CHECK(strcmp(reinterpret_cast<char*>(clonedData), data.asString()) == 0);
+ CHECK(!data.wasFreed());
+ }
+
+ // GC the array buffer before data goes out of scope
+ v1.setNull();
+ JS_GC(cx);
+ JS_GC(cx); // Trigger another to wait for background finalization to end
+
+ CHECK(data.wasFreed());
+
+ return true;
+}
+END_TEST(testStructuredClone_externalArrayBuffer)
+
+BEGIN_TEST(testStructuredClone_externalArrayBufferDifferentThreadOrProcess) {
+ CHECK(testStructuredCloneCopy(JS::StructuredCloneScope::SameProcess));
+ CHECK(testStructuredCloneCopy(JS::StructuredCloneScope::DifferentProcess));
+ return true;
+}
+
+bool testStructuredCloneCopy(JS::StructuredCloneScope scope) {
+ ExternalData data("One two three four");
+ JS::RootedObject buffer(
+ cx, JS::NewExternalArrayBuffer(cx, data.len(), data.contents(),
+ &ExternalData::freeCallback, &data));
+ CHECK(buffer);
+ CHECK(!data.wasFreed());
+
+ JS::RootedValue v1(cx, JS::ObjectValue(*buffer));
+ JS::RootedValue v2(cx);
+ CHECK(clone(scope, v1, &v2));
+ JS::RootedObject bufferOut(cx, v2.toObjectOrNull());
+ CHECK(bufferOut);
+ CHECK(JS::IsArrayBufferObject(bufferOut));
+
+ size_t len;
+ bool isShared;
+ uint8_t* clonedData;
+ JS::GetArrayBufferLengthAndData(bufferOut, &len, &isShared, &clonedData);
+
+ // Cloning should copy the data, so the contents of the two array buffers
+ // should be equal, but not the same pointer.
+ CHECK_EQUAL(len, data.len());
+ CHECK(clonedData != data.contents());
+ CHECK(strcmp(reinterpret_cast<char*>(clonedData), data.asString()) == 0);
+ CHECK(!data.wasFreed());
+
+ buffer = nullptr;
+ bufferOut = nullptr;
+ v1.setNull();
+ v2.setNull();
+ JS_GC(cx);
+ JS_GC(cx);
+ CHECK(data.wasFreed());
+
+ return true;
+}
+
+bool clone(JS::StructuredCloneScope scope, JS::HandleValue v1,
+ JS::MutableHandleValue v2) {
+ JSAutoStructuredCloneBuffer clonedBuffer(scope, nullptr, nullptr);
+ CHECK(clonedBuffer.write(cx, v1));
+ CHECK(clonedBuffer.read(cx, v2));
+ return true;
+}
+END_TEST(testStructuredClone_externalArrayBufferDifferentThreadOrProcess)
+
+struct StructuredCloneTestPrincipals final : public JSPrincipals {
+ uint32_t rank;
+
+ explicit StructuredCloneTestPrincipals(uint32_t rank, int32_t rc = 1)
+ : rank(rank) {
+ this->refcount = rc;
+ }
+
+ bool write(JSContext* cx, JSStructuredCloneWriter* writer) override {
+ return JS_WriteUint32Pair(writer, rank, 0);
+ }
+
+ bool isSystemOrAddonPrincipal() override { return true; }
+
+ static bool read(JSContext* cx, JSStructuredCloneReader* reader,
+ JSPrincipals** outPrincipals) {
+ uint32_t rank;
+ uint32_t unused;
+ if (!JS_ReadUint32Pair(reader, &rank, &unused)) {
+ return false;
+ }
+
+ *outPrincipals = new StructuredCloneTestPrincipals(rank);
+ return !!*outPrincipals;
+ }
+
+ static void destroy(JSPrincipals* p) {
+ auto p1 = static_cast<StructuredCloneTestPrincipals*>(p);
+ delete p1;
+ }
+
+ static uint32_t getRank(JSPrincipals* p) {
+ if (!p) {
+ return 0;
+ }
+ return static_cast<StructuredCloneTestPrincipals*>(p)->rank;
+ }
+
+ static bool subsumes(JSPrincipals* a, JSPrincipals* b) {
+ return getRank(a) > getRank(b);
+ }
+
+ static JSSecurityCallbacks securityCallbacks;
+
+ static StructuredCloneTestPrincipals testPrincipals;
+};
+
+JSSecurityCallbacks StructuredCloneTestPrincipals::securityCallbacks = {
+ nullptr, // contentSecurityPolicyAllows
+ subsumes};
+
+BEGIN_TEST(testStructuredClone_SavedFrame) {
+ JS_SetSecurityCallbacks(cx,
+ &StructuredCloneTestPrincipals::securityCallbacks);
+ JS_InitDestroyPrincipalsCallback(cx, StructuredCloneTestPrincipals::destroy);
+ JS_InitReadPrincipalsCallback(cx, StructuredCloneTestPrincipals::read);
+
+ auto testPrincipals = new StructuredCloneTestPrincipals(42, 0);
+ CHECK(testPrincipals);
+
+ auto DONE = (JSPrincipals*)0xDEADBEEF;
+
+ struct {
+ const char* name;
+ JSPrincipals* principals;
+ } principalsToTest[] = {
+ {"IsSystem", &js::ReconstructedSavedFramePrincipals::IsSystem},
+ {"IsNotSystem", &js::ReconstructedSavedFramePrincipals::IsNotSystem},
+ {"testPrincipals", testPrincipals},
+ {"nullptr principals", nullptr},
+ {"DONE", DONE}};
+
+ const char* FILENAME = "filename.js";
+
+ for (auto* pp = principalsToTest; pp->principals != DONE; pp++) {
+ fprintf(stderr, "Testing with principals '%s'\n", pp->name);
+
+ JS::RealmOptions options;
+ JS::RootedObject g(cx,
+ JS_NewGlobalObject(cx, getGlobalClass(), pp->principals,
+ JS::FireOnNewGlobalHook, options));
+ CHECK(g);
+ JSAutoRealm ar(cx, g);
+
+ CHECK(js::DefineTestingFunctions(cx, g, false, false));
+
+ JS::RootedValue srcVal(cx);
+ CHECK(
+ evaluate("(function one() { \n" // 1
+ " return (function two() { \n" // 2
+ " return (function three() { \n" // 3
+ " return saveStack(); \n" // 4
+ " }()); \n" // 5
+ " }()); \n" // 6
+ "}()); \n", // 7
+ FILENAME, 1, &srcVal));
+
+ CHECK(srcVal.isObject());
+ JS::RootedObject srcObj(cx, &srcVal.toObject());
+
+ CHECK(srcObj->is<js::SavedFrame>());
+ JS::Rooted<js::SavedFrame*> srcFrame(cx, &srcObj->as<js::SavedFrame>());
+
+ CHECK(srcFrame->getPrincipals() == pp->principals);
+
+ JS::RootedValue destVal(cx);
+ CHECK(JS_StructuredClone(cx, srcVal, &destVal, nullptr, nullptr));
+
+ CHECK(destVal.isObject());
+ JS::RootedObject destObj(cx, &destVal.toObject());
+
+ CHECK(destObj->is<js::SavedFrame>());
+ JS::Handle<js::SavedFrame*> destFrame = destObj.as<js::SavedFrame>();
+
+ size_t framesCopied = 0;
+ for (JS::Handle<js::SavedFrame*> f :
+ js::SavedFrame::RootedRange(cx, destFrame)) {
+ framesCopied++;
+
+ CHECK(f != srcFrame);
+
+ if (pp->principals == testPrincipals) {
+ // We shouldn't get a pointer to the same
+ // StructuredCloneTestPrincipals instance since we should have
+ // serialized and then deserialized it into a new instance.
+ CHECK(f->getPrincipals() != pp->principals);
+
+ // But it should certainly have the same rank.
+ CHECK(StructuredCloneTestPrincipals::getRank(f->getPrincipals()) ==
+ StructuredCloneTestPrincipals::getRank(pp->principals));
+ } else {
+ // For our singleton principals, we should always get the same
+ // pointer back.
+ CHECK(js::ReconstructedSavedFramePrincipals::is(pp->principals) ||
+ pp->principals == nullptr);
+ CHECK(f->getPrincipals() == pp->principals);
+ }
+
+ CHECK(EqualStrings(f->getSource(), srcFrame->getSource()));
+ CHECK(f->getLine() == srcFrame->getLine());
+ CHECK(f->getColumn() == srcFrame->getColumn());
+ CHECK(EqualStrings(f->getFunctionDisplayName(),
+ srcFrame->getFunctionDisplayName()));
+
+ srcFrame = srcFrame->getParent();
+ }
+
+ // Four function frames + one global frame.
+ CHECK(framesCopied == 4);
+ }
+
+ return true;
+}
+END_TEST(testStructuredClone_SavedFrame)
diff --git a/js/src/jsapi-tests/testSymbol.cpp b/js/src/jsapi-tests/testSymbol.cpp
new file mode 100644
index 0000000000..effc416c2d
--- /dev/null
+++ b/js/src/jsapi-tests/testSymbol.cpp
@@ -0,0 +1,79 @@
+/* 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 "js/Symbol.h"
+#include "jsapi-tests/tests.h"
+
+BEGIN_TEST(testSymbol_New) {
+ using namespace JS;
+
+ RootedString desc(cx, nullptr);
+ RootedSymbol sym1(cx);
+ CHECK(sym1 = NewSymbol(cx, desc));
+ CHECK_NULL(GetSymbolDescription(sym1));
+ RootedValue v(cx, SymbolValue(sym1));
+ CHECK_EQUAL(JS_TypeOfValue(cx, v), JSTYPE_SYMBOL);
+
+ RootedSymbol sym2(cx);
+ CHECK(sym2 = NewSymbol(cx, desc));
+ CHECK(sym1 != sym2);
+
+ CHECK(desc = JS_NewStringCopyZ(cx, "ponies"));
+ CHECK(sym2 = NewSymbol(cx, desc));
+ CHECK_SAME(StringValue(GetSymbolDescription(sym2)), StringValue(desc));
+
+ return true;
+}
+END_TEST(testSymbol_New)
+
+BEGIN_TEST(testSymbol_GetSymbolFor) {
+ using namespace JS;
+
+ RootedString desc(cx, JS_NewStringCopyZ(cx, "ponies"));
+ CHECK(desc);
+ RootedSymbol sym1(cx);
+ CHECK(sym1 = GetSymbolFor(cx, desc));
+ CHECK_SAME(StringValue(GetSymbolDescription(sym1)), StringValue(desc));
+
+ // Calling JS::GetSymbolFor again with the same arguments produces the
+ // same Symbol.
+ RootedSymbol sym2(cx);
+ CHECK(sym2 = GetSymbolFor(cx, desc));
+ CHECK_EQUAL(sym1, sym2);
+
+ // Passing a new but equal string also produces the same Symbol.
+ CHECK(desc = JS_NewStringCopyZ(cx, "ponies"));
+ CHECK(sym2 = GetSymbolFor(cx, desc));
+ CHECK_EQUAL(sym1, sym2);
+
+ // But SymbolNew always produces a new distinct Symbol.
+ CHECK(sym2 = NewSymbol(cx, desc));
+ CHECK(sym2 != sym1);
+
+ return true;
+}
+END_TEST(testSymbol_GetSymbolFor)
+
+BEGIN_TEST(testSymbol_GetWellKnownSymbol) {
+ using namespace JS;
+
+ Rooted<Symbol*> sym1(cx);
+ CHECK(sym1 = GetWellKnownSymbol(cx, SymbolCode::iterator));
+ RootedValue v(cx);
+ EVAL("Symbol.iterator", &v);
+ CHECK_SAME(v, SymbolValue(sym1));
+
+ // The description of a well-known symbol is as specified.
+ RootedString desc(cx);
+ CHECK(desc = JS_NewStringCopyZ(cx, "Symbol.iterator"));
+ CHECK_SAME(StringValue(GetSymbolDescription(sym1)), StringValue(desc));
+
+ // GetSymbolFor never returns a well-known symbol.
+ Rooted<Symbol*> sym2(cx);
+ CHECK(sym2 = GetSymbolFor(cx, desc));
+ CHECK(sym2 != sym1);
+
+ return true;
+}
+END_TEST(testSymbol_GetWellKnownSymbol)
diff --git a/js/src/jsapi-tests/testThreadingConditionVariable.cpp b/js/src/jsapi-tests/testThreadingConditionVariable.cpp
new file mode 100644
index 0000000000..1bff194372
--- /dev/null
+++ b/js/src/jsapi-tests/testThreadingConditionVariable.cpp
@@ -0,0 +1,220 @@
+/* -*- 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 "jsapi-tests/tests.h"
+#include "threading/ConditionVariable.h"
+#include "threading/Thread.h"
+#include "vm/MutexIDs.h"
+
+struct TestState {
+ js::Mutex mutex MOZ_UNANNOTATED;
+ js::ConditionVariable condition;
+ bool flag;
+ js::Thread testThread;
+
+ explicit TestState(bool createThread = true)
+ : mutex(js::mutexid::TestMutex), flag(false) {
+ if (createThread) {
+ MOZ_RELEASE_ASSERT(testThread.init(setFlag, this));
+ }
+ }
+
+ static void setFlag(TestState* state) {
+ js::UniqueLock<js::Mutex> lock(state->mutex);
+ state->flag = true;
+ state->condition.notify_one();
+ }
+
+ void join() { testThread.join(); }
+};
+
+BEGIN_TEST(testThreadingConditionVariable) {
+ auto state = mozilla::MakeUnique<TestState>();
+ {
+ js::UniqueLock<js::Mutex> lock(state->mutex);
+ while (!state->flag) {
+ state->condition.wait(lock);
+ }
+ }
+ state->join();
+
+ CHECK(state->flag);
+
+ return true;
+}
+END_TEST(testThreadingConditionVariable)
+
+BEGIN_TEST(testThreadingConditionVariablePredicate) {
+ auto state = mozilla::MakeUnique<TestState>();
+ {
+ js::UniqueLock<js::Mutex> lock(state->mutex);
+ state->condition.wait(lock, [&state]() { return state->flag; });
+ }
+ state->join();
+
+ CHECK(state->flag);
+
+ return true;
+}
+END_TEST(testThreadingConditionVariablePredicate)
+
+BEGIN_TEST(testThreadingConditionVariableUntilOkay) {
+ auto state = mozilla::MakeUnique<TestState>();
+ {
+ js::UniqueLock<js::Mutex> lock(state->mutex);
+ while (!state->flag) {
+ auto to =
+ mozilla::TimeStamp::Now() + mozilla::TimeDuration::FromSeconds(600);
+ js::CVStatus res = state->condition.wait_until(lock, to);
+ CHECK(res == js::CVStatus::NoTimeout);
+ }
+ }
+ state->join();
+
+ CHECK(state->flag);
+
+ return true;
+}
+END_TEST(testThreadingConditionVariableUntilOkay)
+
+BEGIN_TEST(testThreadingConditionVariableUntilTimeout) {
+ auto state = mozilla::MakeUnique<TestState>(false);
+ {
+ js::UniqueLock<js::Mutex> lock(state->mutex);
+ while (!state->flag) {
+ auto to = mozilla::TimeStamp::Now() +
+ mozilla::TimeDuration::FromMilliseconds(10);
+ js::CVStatus res = state->condition.wait_until(lock, to);
+ if (res == js::CVStatus::Timeout) {
+ break;
+ }
+ }
+ }
+ CHECK(!state->flag);
+
+ // Timeout in the past should return with timeout immediately.
+ {
+ js::UniqueLock<js::Mutex> lock(state->mutex);
+ auto to =
+ mozilla::TimeStamp::Now() - mozilla::TimeDuration::FromMilliseconds(10);
+ js::CVStatus res = state->condition.wait_until(lock, to);
+ CHECK(res == js::CVStatus::Timeout);
+ }
+
+ return true;
+}
+END_TEST(testThreadingConditionVariableUntilTimeout)
+
+BEGIN_TEST(testThreadingConditionVariableUntilOkayPredicate) {
+ auto state = mozilla::MakeUnique<TestState>();
+ {
+ js::UniqueLock<js::Mutex> lock(state->mutex);
+ auto to =
+ mozilla::TimeStamp::Now() + mozilla::TimeDuration::FromSeconds(600);
+ bool res = state->condition.wait_until(lock, to,
+ [&state]() { return state->flag; });
+ CHECK(res);
+ }
+ state->join();
+
+ CHECK(state->flag);
+
+ return true;
+}
+END_TEST(testThreadingConditionVariableUntilOkayPredicate)
+
+BEGIN_TEST(testThreadingConditionVariableUntilTimeoutPredicate) {
+ auto state = mozilla::MakeUnique<TestState>(false);
+ {
+ js::UniqueLock<js::Mutex> lock(state->mutex);
+ auto to =
+ mozilla::TimeStamp::Now() + mozilla::TimeDuration::FromMilliseconds(10);
+ bool res = state->condition.wait_until(lock, to,
+ [&state]() { return state->flag; });
+ CHECK(!res);
+ }
+ CHECK(!state->flag);
+
+ return true;
+}
+END_TEST(testThreadingConditionVariableUntilTimeoutPredicate)
+
+BEGIN_TEST(testThreadingConditionVariableForOkay) {
+ auto state = mozilla::MakeUnique<TestState>();
+ {
+ js::UniqueLock<js::Mutex> lock(state->mutex);
+ while (!state->flag) {
+ auto duration = mozilla::TimeDuration::FromSeconds(600);
+ js::CVStatus res = state->condition.wait_for(lock, duration);
+ CHECK(res == js::CVStatus::NoTimeout);
+ }
+ }
+ state->join();
+
+ CHECK(state->flag);
+
+ return true;
+}
+END_TEST(testThreadingConditionVariableForOkay)
+
+BEGIN_TEST(testThreadingConditionVariableForTimeout) {
+ auto state = mozilla::MakeUnique<TestState>(false);
+ {
+ js::UniqueLock<js::Mutex> lock(state->mutex);
+ while (!state->flag) {
+ auto duration = mozilla::TimeDuration::FromMilliseconds(10);
+ js::CVStatus res = state->condition.wait_for(lock, duration);
+ if (res == js::CVStatus::Timeout) {
+ break;
+ }
+ }
+ }
+ CHECK(!state->flag);
+
+ // Timeout in the past should return with timeout immediately.
+ {
+ js::UniqueLock<js::Mutex> lock(state->mutex);
+ auto duration = mozilla::TimeDuration::FromMilliseconds(-10);
+ js::CVStatus res = state->condition.wait_for(lock, duration);
+ CHECK(res == js::CVStatus::Timeout);
+ }
+
+ return true;
+}
+END_TEST(testThreadingConditionVariableForTimeout)
+
+BEGIN_TEST(testThreadingConditionVariableForOkayPredicate) {
+ auto state = mozilla::MakeUnique<TestState>();
+ {
+ js::UniqueLock<js::Mutex> lock(state->mutex);
+ auto duration = mozilla::TimeDuration::FromSeconds(600);
+ bool res = state->condition.wait_for(lock, duration,
+ [&state]() { return state->flag; });
+ CHECK(res);
+ }
+ state->join();
+
+ CHECK(state->flag);
+
+ return true;
+}
+END_TEST(testThreadingConditionVariableForOkayPredicate)
+
+BEGIN_TEST(testThreadingConditionVariableForTimeoutPredicate) {
+ auto state = mozilla::MakeUnique<TestState>(false);
+ {
+ js::UniqueLock<js::Mutex> lock(state->mutex);
+ auto duration = mozilla::TimeDuration::FromMilliseconds(10);
+ bool res = state->condition.wait_for(lock, duration,
+ [&state]() { return state->flag; });
+ CHECK(!res);
+ }
+ CHECK(!state->flag);
+
+ return true;
+}
+END_TEST(testThreadingConditionVariableForTimeoutPredicate)
diff --git a/js/src/jsapi-tests/testThreadingExclusiveData.cpp b/js/src/jsapi-tests/testThreadingExclusiveData.cpp
new file mode 100644
index 0000000000..0a72a639ac
--- /dev/null
+++ b/js/src/jsapi-tests/testThreadingExclusiveData.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/IntegerRange.h"
+#include "js/Vector.h"
+#include "jsapi-tests/tests.h"
+#include "threading/ExclusiveData.h"
+#include "threading/Thread.h"
+
+// One thread for each bit in our counter.
+const static uint8_t NumThreads = 64;
+const static bool ShowDiagnostics = false;
+
+struct CounterAndBit {
+ uint8_t bit;
+ const js::ExclusiveData<uint64_t>& counter;
+
+ CounterAndBit(uint8_t bit, const js::ExclusiveData<uint64_t>& counter)
+ : bit(bit), counter(counter) {
+ MOZ_ASSERT(bit < NumThreads);
+ }
+};
+
+void printDiagnosticMessage(uint8_t bit, uint64_t seen) {
+ if (!ShowDiagnostics) {
+ return;
+ }
+
+ fprintf(stderr, "Thread %d saw ", bit);
+ for (auto i : mozilla::IntegerRange(NumThreads)) {
+ if (seen & (uint64_t(1) << i)) {
+ fprintf(stderr, "1");
+ } else {
+ fprintf(stderr, "0");
+ }
+ }
+ fprintf(stderr, "\n");
+}
+
+void setBitAndCheck(CounterAndBit* counterAndBit) {
+ while (true) {
+ {
+ // Set our bit. Repeatedly setting it is idempotent.
+ auto guard = counterAndBit->counter.lock();
+ printDiagnosticMessage(counterAndBit->bit, guard);
+ guard |= (uint64_t(1) << counterAndBit->bit);
+ }
+
+ {
+ // Check to see if we have observed all the other threads setting
+ // their bit as well.
+ auto guard = counterAndBit->counter.lock();
+ printDiagnosticMessage(counterAndBit->bit, guard);
+ if (guard == UINT64_MAX) {
+ js_delete(counterAndBit);
+ return;
+ }
+ }
+ }
+}
+
+BEGIN_TEST(testExclusiveData) {
+ js::ExclusiveData<uint64_t> counter(js::mutexid::TestMutex, 0);
+
+ js::Vector<js::Thread> threads(cx);
+ CHECK(threads.reserve(NumThreads));
+
+ for (auto i : mozilla::IntegerRange(NumThreads)) {
+ auto counterAndBit = js_new<CounterAndBit>(i, counter);
+ CHECK(counterAndBit);
+ CHECK(threads.emplaceBack());
+ CHECK(threads.back().init(setBitAndCheck, counterAndBit));
+ }
+
+ for (auto& thread : threads) {
+ thread.join();
+ }
+
+ return true;
+}
+END_TEST(testExclusiveData)
diff --git a/js/src/jsapi-tests/testThreadingMutex.cpp b/js/src/jsapi-tests/testThreadingMutex.cpp
new file mode 100644
index 0000000000..e3d2f1155d
--- /dev/null
+++ b/js/src/jsapi-tests/testThreadingMutex.cpp
@@ -0,0 +1,33 @@
+/* -*- 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 "jsapi-tests/tests.h"
+#include "threading/LockGuard.h"
+#include "vm/MutexIDs.h"
+
+BEGIN_TEST(testThreadingMutex) {
+ js::Mutex mutex MOZ_UNANNOTATED(js::mutexid::TestMutex);
+ mutex.lock();
+ mutex.unlock();
+ return true;
+}
+END_TEST(testThreadingMutex)
+
+BEGIN_TEST(testThreadingLockGuard) {
+ js::Mutex mutex MOZ_UNANNOTATED(js::mutexid::TestMutex);
+ js::LockGuard<js::Mutex> guard(mutex);
+ return true;
+}
+END_TEST(testThreadingLockGuard)
+
+BEGIN_TEST(testThreadingUnlockGuard) {
+ js::Mutex mutex MOZ_UNANNOTATED(js::mutexid::TestMutex);
+ js::LockGuard<js::Mutex> guard(mutex);
+ js::UnlockGuard<js::Mutex> unguard(guard);
+ return true;
+}
+END_TEST(testThreadingUnlockGuard)
diff --git a/js/src/jsapi-tests/testThreadingThread.cpp b/js/src/jsapi-tests/testThreadingThread.cpp
new file mode 100644
index 0000000000..68a7a388b0
--- /dev/null
+++ b/js/src/jsapi-tests/testThreadingThread.cpp
@@ -0,0 +1,107 @@
+/* -*- 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/Atomics.h"
+#include "mozilla/IntegerRange.h"
+#include "mozilla/Vector.h"
+
+#include <utility>
+
+#include "js/AllocPolicy.h"
+#include "jsapi-tests/tests.h"
+#include "threading/Thread.h"
+
+BEGIN_TEST(testThreadingThreadJoin) {
+ bool flag = false;
+ js::Thread thread;
+ CHECK(thread.init([](bool* flagp) { *flagp = true; }, &flag));
+ CHECK(thread.joinable());
+ thread.join();
+ CHECK(flag);
+ CHECK(!thread.joinable());
+ return true;
+}
+END_TEST(testThreadingThreadJoin)
+
+BEGIN_TEST(testThreadingThreadDetach) {
+ // We are going to detach this thread. Unlike join, we can't have it pointing
+ // at the stack because it might do the write after we have returned and
+ // pushed a new frame.
+ bool* flag = js_new<bool>(false);
+ js::Thread thread;
+ CHECK(thread.init(
+ [](bool* flag) {
+ *flag = true;
+ js_delete(flag);
+ },
+ std::move(flag)));
+ CHECK(thread.joinable());
+ thread.detach();
+ CHECK(!thread.joinable());
+
+ return true;
+}
+END_TEST(testThreadingThreadDetach)
+
+BEGIN_TEST(testThreadingThreadSetName) {
+ js::Thread thread;
+ CHECK(thread.init([]() { js::ThisThread::SetName("JSAPI Test Thread"); }));
+ thread.detach();
+ return true;
+}
+END_TEST(testThreadingThreadSetName)
+
+BEGIN_TEST(testThreadingThreadId) {
+ CHECK(js::ThreadId() == js::ThreadId());
+ js::ThreadId fromOther;
+ js::Thread thread;
+ CHECK(thread.init(
+ [](js::ThreadId* idp) { *idp = js::ThreadId::ThisThreadId(); },
+ &fromOther));
+ js::ThreadId fromMain = thread.get_id();
+ thread.join();
+ CHECK(fromOther == fromMain);
+ return true;
+}
+END_TEST(testThreadingThreadId)
+
+BEGIN_TEST(testThreadingThreadVectorMoveConstruct) {
+ const static size_t N = 10;
+ mozilla::Atomic<int> count(0);
+ mozilla::Vector<js::Thread, 0, js::SystemAllocPolicy> v;
+ for (auto i : mozilla::IntegerRange(N)) {
+ CHECK(v.emplaceBack());
+ CHECK(v.back().init([](mozilla::Atomic<int>* countp) { (*countp)++; },
+ &count));
+ CHECK(v.length() == i + 1);
+ }
+ for (auto& th : v) {
+ th.join();
+ }
+ CHECK(count == 10);
+ return true;
+}
+END_TEST(testThreadingThreadVectorMoveConstruct)
+
+// This test is checking that args are using "decay" copy, per spec. If we do
+// not use decay copy properly, the rvalue reference |bool&& b| in the
+// constructor will automatically become an lvalue reference |bool& b| in the
+// trampoline, causing us to read through the reference when passing |bool bb|
+// from the trampoline. If the parent runs before the child, the bool may have
+// already become false, causing the trampoline to read the changed value, thus
+// causing the child's assertion to fail.
+BEGIN_TEST(testThreadingThreadArgCopy) {
+ for (size_t i = 0; i < 10000; ++i) {
+ bool b = true;
+ js::Thread thread;
+ CHECK(thread.init([](bool bb) { MOZ_RELEASE_ASSERT(bb); }, b));
+ b = false;
+ thread.join();
+ }
+ return true;
+}
+END_TEST(testThreadingThreadArgCopy)
diff --git a/js/src/jsapi-tests/testToSignedOrUnsignedInteger.cpp b/js/src/jsapi-tests/testToSignedOrUnsignedInteger.cpp
new file mode 100644
index 0000000000..6fc82a9445
--- /dev/null
+++ b/js/src/jsapi-tests/testToSignedOrUnsignedInteger.cpp
@@ -0,0 +1,68 @@
+/* -*- 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 <math.h>
+
+#include "js/Conversions.h"
+
+#include "jsapi-tests/tests.h"
+
+using JS::ToSignedInteger;
+using JS::ToUnsignedInteger;
+
+BEGIN_TEST(testToUint8TwiceUint8Range) {
+ double d = -256;
+ uint8_t expected = 0;
+ do {
+ CHECK(ToUnsignedInteger<uint8_t>(d) == expected);
+
+ d++;
+ expected++;
+ } while (d <= 256);
+ return true;
+}
+END_TEST(testToUint8TwiceUint8Range)
+
+BEGIN_TEST(testToInt8) {
+ double d = -128;
+ int8_t expected = -128;
+ do {
+ CHECK(ToSignedInteger<int8_t>(d) == expected);
+
+ d++;
+ expected++;
+ } while (expected < 127);
+ return true;
+}
+END_TEST(testToInt8)
+
+BEGIN_TEST(testToUint32Large) {
+ CHECK(ToUnsignedInteger<uint32_t>(pow(2.0, 83)) == 0);
+ CHECK(ToUnsignedInteger<uint32_t>(pow(2.0, 83) + pow(2.0, 31)) == (1U << 31));
+ CHECK(ToUnsignedInteger<uint32_t>(pow(2.0, 83) + 2 * pow(2.0, 31)) == 0);
+ CHECK(ToUnsignedInteger<uint32_t>(pow(2.0, 83) + 3 * pow(2.0, 31)) ==
+ (1U << 31));
+ CHECK(ToUnsignedInteger<uint32_t>(pow(2.0, 84)) == 0);
+ CHECK(ToUnsignedInteger<uint32_t>(pow(2.0, 84) + pow(2.0, 31)) == 0);
+ CHECK(ToUnsignedInteger<uint32_t>(pow(2.0, 84) + pow(2.0, 32)) == 0);
+ return true;
+}
+END_TEST(testToUint32Large)
+
+BEGIN_TEST(testToUint64Large) {
+ CHECK(ToUnsignedInteger<uint64_t>(pow(2.0, 115)) == 0);
+ CHECK(ToUnsignedInteger<uint64_t>(pow(2.0, 115) + pow(2.0, 63)) ==
+ (1ULL << 63));
+ CHECK(ToUnsignedInteger<uint64_t>(pow(2.0, 115) + 2 * pow(2.0, 63)) == 0);
+ CHECK(ToUnsignedInteger<uint64_t>(pow(2.0, 115) + 3 * pow(2.0, 63)) ==
+ (1ULL << 63));
+ CHECK(ToUnsignedInteger<uint64_t>(pow(2.0, 116)) == 0);
+ CHECK(ToUnsignedInteger<uint64_t>(pow(2.0, 116) + pow(2.0, 63)) == 0);
+ CHECK(ToUnsignedInteger<uint64_t>(pow(2.0, 116) + pow(2.0, 64)) == 0);
+ return true;
+}
+END_TEST(testToUint64Large)
diff --git a/js/src/jsapi-tests/testTypedArrays.cpp b/js/src/jsapi-tests/testTypedArrays.cpp
new file mode 100644
index 0000000000..08b3af5d62
--- /dev/null
+++ b/js/src/jsapi-tests/testTypedArrays.cpp
@@ -0,0 +1,278 @@
+/* -*- 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 "js/ArrayBuffer.h" // JS::{NewArrayBuffer,IsArrayBufferObject,GetArrayBuffer{ByteLength,Data}}
+#include "js/experimental/TypedData.h" // JS_GetArrayBufferViewBuffer, JS_GetTypedArray{Length,ByteOffset,ByteLength}, JS_Get{{Ui,I}nt{8,16,32},Float{32,64},Uint8Clamped}ArrayData, JS_IsTypedArrayObject, JS_New{{Ui,I}nt{8,16,32},Float{32,64},Uint8Clamped}Array{,FromArray,WithBuffer}
+#include "js/PropertyAndElement.h" // JS_GetElement, JS_SetElement
+#include "js/SharedArrayBuffer.h" // JS::{NewSharedArrayBuffer,GetSharedArrayBufferData}
+#include "jsapi-tests/tests.h"
+#include "vm/Realm.h"
+
+using namespace js;
+
+BEGIN_TEST(testTypedArrays) {
+ bool ok = true;
+
+ ok = ok &&
+ TestPlainTypedArray<JS_NewInt8Array, int8_t, JS_GetInt8ArrayData>(cx) &&
+ TestPlainTypedArray<JS_NewUint8Array, uint8_t, JS_GetUint8ArrayData>(
+ cx) &&
+ TestPlainTypedArray<JS_NewUint8ClampedArray, uint8_t,
+ JS_GetUint8ClampedArrayData>(cx) &&
+ TestPlainTypedArray<JS_NewInt16Array, int16_t, JS_GetInt16ArrayData>(
+ cx) &&
+ TestPlainTypedArray<JS_NewUint16Array, uint16_t, JS_GetUint16ArrayData>(
+ cx) &&
+ TestPlainTypedArray<JS_NewInt32Array, int32_t, JS_GetInt32ArrayData>(
+ cx) &&
+ TestPlainTypedArray<JS_NewUint32Array, uint32_t, JS_GetUint32ArrayData>(
+ cx) &&
+ TestPlainTypedArray<JS_NewFloat32Array, float, JS_GetFloat32ArrayData>(
+ cx) &&
+ TestPlainTypedArray<JS_NewFloat64Array, double, JS_GetFloat64ArrayData>(
+ cx);
+
+ size_t nbytes = sizeof(double) * 8;
+ RootedObject buffer(cx, JS::NewArrayBuffer(cx, nbytes));
+ CHECK(JS::IsArrayBufferObject(buffer));
+
+ RootedObject proto(cx);
+ JS_GetPrototype(cx, buffer, &proto);
+ CHECK(!JS::IsArrayBufferObject(proto));
+
+ {
+ JS::AutoCheckCannotGC nogc;
+ bool isShared;
+ CHECK_EQUAL(JS::GetArrayBufferByteLength(buffer), nbytes);
+ memset(JS::GetArrayBufferData(buffer, &isShared, nogc), 1, nbytes);
+ CHECK(!isShared); // Because ArrayBuffer
+ }
+
+ ok =
+ ok &&
+ TestArrayFromBuffer<JS_NewInt8ArrayWithBuffer, JS_NewInt8ArrayFromArray,
+ int8_t, false, JS_GetInt8ArrayData>(cx) &&
+ TestArrayFromBuffer<JS_NewUint8ArrayWithBuffer, JS_NewUint8ArrayFromArray,
+ uint8_t, false, JS_GetUint8ArrayData>(cx) &&
+ TestArrayFromBuffer<JS_NewUint8ClampedArrayWithBuffer,
+ JS_NewUint8ClampedArrayFromArray, uint8_t, false,
+ JS_GetUint8ClampedArrayData>(cx) &&
+ TestArrayFromBuffer<JS_NewInt16ArrayWithBuffer, JS_NewInt16ArrayFromArray,
+ int16_t, false, JS_GetInt16ArrayData>(cx) &&
+ TestArrayFromBuffer<JS_NewUint16ArrayWithBuffer,
+ JS_NewUint16ArrayFromArray, uint16_t, false,
+ JS_GetUint16ArrayData>(cx) &&
+ TestArrayFromBuffer<JS_NewInt32ArrayWithBuffer, JS_NewInt32ArrayFromArray,
+ int32_t, false, JS_GetInt32ArrayData>(cx) &&
+ TestArrayFromBuffer<JS_NewUint32ArrayWithBuffer,
+ JS_NewUint32ArrayFromArray, uint32_t, false,
+ JS_GetUint32ArrayData>(cx) &&
+ TestArrayFromBuffer<JS_NewFloat32ArrayWithBuffer,
+ JS_NewFloat32ArrayFromArray, float, false,
+ JS_GetFloat32ArrayData>(cx) &&
+ TestArrayFromBuffer<JS_NewFloat64ArrayWithBuffer,
+ JS_NewFloat64ArrayFromArray, double, false,
+ JS_GetFloat64ArrayData>(cx);
+
+ ok =
+ ok &&
+ TestArrayFromBuffer<JS_NewInt8ArrayWithBuffer, JS_NewInt8ArrayFromArray,
+ int8_t, true, JS_GetInt8ArrayData>(cx) &&
+ TestArrayFromBuffer<JS_NewUint8ArrayWithBuffer, JS_NewUint8ArrayFromArray,
+ uint8_t, true, JS_GetUint8ArrayData>(cx) &&
+ TestArrayFromBuffer<JS_NewUint8ClampedArrayWithBuffer,
+ JS_NewUint8ClampedArrayFromArray, uint8_t, true,
+ JS_GetUint8ClampedArrayData>(cx) &&
+ TestArrayFromBuffer<JS_NewInt16ArrayWithBuffer, JS_NewInt16ArrayFromArray,
+ int16_t, true, JS_GetInt16ArrayData>(cx) &&
+ TestArrayFromBuffer<JS_NewUint16ArrayWithBuffer,
+ JS_NewUint16ArrayFromArray, uint16_t, true,
+ JS_GetUint16ArrayData>(cx) &&
+ TestArrayFromBuffer<JS_NewInt32ArrayWithBuffer, JS_NewInt32ArrayFromArray,
+ int32_t, true, JS_GetInt32ArrayData>(cx) &&
+ TestArrayFromBuffer<JS_NewUint32ArrayWithBuffer,
+ JS_NewUint32ArrayFromArray, uint32_t, true,
+ JS_GetUint32ArrayData>(cx) &&
+ TestArrayFromBuffer<JS_NewFloat32ArrayWithBuffer,
+ JS_NewFloat32ArrayFromArray, float, true,
+ JS_GetFloat32ArrayData>(cx) &&
+ TestArrayFromBuffer<JS_NewFloat64ArrayWithBuffer,
+ JS_NewFloat64ArrayFromArray, double, true,
+ JS_GetFloat64ArrayData>(cx);
+
+ return ok;
+}
+
+// Shared memory can only be mapped by a TypedArray by creating the
+// TypedArray with a SharedArrayBuffer explicitly, so no tests here.
+
+template <JSObject* Create(JSContext*, size_t), typename Element,
+ Element* GetData(JSObject*, bool* isShared,
+ const JS::AutoRequireNoGC&)>
+bool TestPlainTypedArray(JSContext* cx) {
+ {
+ RootedObject notArray(cx, Create(cx, SIZE_MAX));
+ CHECK(!notArray);
+ JS_ClearPendingException(cx);
+ }
+
+ RootedObject array(cx, Create(cx, 7));
+ CHECK(JS_IsTypedArrayObject(array));
+ RootedObject proto(cx);
+ JS_GetPrototype(cx, array, &proto);
+ CHECK(!JS_IsTypedArrayObject(proto));
+
+ CHECK_EQUAL(JS_GetTypedArrayLength(array), 7u);
+ CHECK_EQUAL(JS_GetTypedArrayByteOffset(array), 0u);
+ CHECK_EQUAL(JS_GetTypedArrayByteLength(array), sizeof(Element) * 7);
+
+ {
+ JS::AutoCheckCannotGC nogc;
+ Element* data;
+ bool isShared;
+ CHECK(data = GetData(array, &isShared, nogc));
+ CHECK(!isShared); // Because ArrayBuffer
+ *data = 13;
+ }
+ RootedValue v(cx);
+ CHECK(JS_GetElement(cx, array, 0, &v));
+ CHECK_SAME(v, Int32Value(13));
+
+ return true;
+}
+
+template <
+ JSObject* CreateWithBuffer(JSContext*, JS::HandleObject, size_t, int64_t),
+ JSObject* CreateFromArray(JSContext*, JS::HandleObject), typename Element,
+ bool Shared, Element* GetData(JSObject*, bool*, const JS::AutoRequireNoGC&)>
+bool TestArrayFromBuffer(JSContext* cx) {
+ if (Shared &&
+ !cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled()) {
+ return true;
+ }
+
+ size_t elts = 8;
+ size_t nbytes = elts * sizeof(Element);
+ RootedObject buffer(cx, Shared ? JS::NewSharedArrayBuffer(cx, nbytes)
+ : JS::NewArrayBuffer(cx, nbytes));
+ {
+ JS::AutoCheckCannotGC nogc;
+ bool isShared;
+ void* data = Shared ? JS::GetSharedArrayBufferData(buffer, &isShared, nogc)
+ : JS::GetArrayBufferData(buffer, &isShared, nogc);
+ CHECK_EQUAL(Shared, isShared);
+ memset(data, 1, nbytes);
+ }
+
+ {
+ RootedObject notArray(cx, CreateWithBuffer(cx, buffer, UINT32_MAX, -1));
+ CHECK(!notArray);
+ JS_ClearPendingException(cx);
+ }
+
+ RootedObject array(cx, CreateWithBuffer(cx, buffer, 0, -1));
+ CHECK_EQUAL(JS_GetTypedArrayLength(array), elts);
+ CHECK_EQUAL(JS_GetTypedArrayByteOffset(array), 0u);
+ CHECK_EQUAL(JS_GetTypedArrayByteLength(array), nbytes);
+ {
+ bool isShared;
+ CHECK_EQUAL(JS_GetArrayBufferViewBuffer(cx, array, &isShared),
+ (JSObject*)buffer);
+ CHECK_EQUAL(Shared, isShared);
+ }
+
+ {
+ JS::AutoCheckCannotGC nogc;
+ Element* data;
+ bool isShared;
+
+ CHECK(data = GetData(array, &isShared, nogc));
+ CHECK_EQUAL(Shared, isShared);
+
+ CHECK_EQUAL(
+ (void*)data,
+ Shared ? (void*)JS::GetSharedArrayBufferData(buffer, &isShared, nogc)
+ : (void*)JS::GetArrayBufferData(buffer, &isShared, nogc));
+ CHECK_EQUAL(Shared, isShared);
+
+ CHECK_EQUAL(*reinterpret_cast<uint8_t*>(data), 1u);
+ }
+
+ RootedObject shortArray(cx, CreateWithBuffer(cx, buffer, 0, elts / 2));
+ CHECK_EQUAL(JS_GetTypedArrayLength(shortArray), elts / 2);
+ CHECK_EQUAL(JS_GetTypedArrayByteOffset(shortArray), 0u);
+ CHECK_EQUAL(JS_GetTypedArrayByteLength(shortArray), nbytes / 2);
+
+ RootedObject ofsArray(cx, CreateWithBuffer(cx, buffer, nbytes / 2, -1));
+ CHECK_EQUAL(JS_GetTypedArrayLength(ofsArray), elts / 2);
+ CHECK_EQUAL(JS_GetTypedArrayByteOffset(ofsArray), nbytes / 2);
+ CHECK_EQUAL(JS_GetTypedArrayByteLength(ofsArray), nbytes / 2);
+
+ // Make sure all 3 views reflect the same buffer at the expected locations
+ JS::RootedValue v(cx, JS::Int32Value(39));
+ CHECK(JS_SetElement(cx, array, 0, v));
+ JS::RootedValue v2(cx);
+ CHECK(JS_GetElement(cx, array, 0, &v2));
+ CHECK_SAME(v, v2);
+ CHECK(JS_GetElement(cx, shortArray, 0, &v2));
+ CHECK_SAME(v, v2);
+ {
+ JS::AutoCheckCannotGC nogc;
+ Element* data;
+ bool isShared;
+ CHECK(data = GetData(array, &isShared, nogc));
+ CHECK_EQUAL(Shared, isShared);
+ CHECK_EQUAL(long(v.toInt32()), long(reinterpret_cast<Element*>(data)[0]));
+ }
+
+ v.setInt32(40);
+ CHECK(JS_SetElement(cx, array, elts / 2, v));
+ CHECK(JS_GetElement(cx, array, elts / 2, &v2));
+ CHECK_SAME(v, v2);
+ CHECK(JS_GetElement(cx, ofsArray, 0, &v2));
+ CHECK_SAME(v, v2);
+ {
+ JS::AutoCheckCannotGC nogc;
+ Element* data;
+ bool isShared;
+ CHECK(data = GetData(array, &isShared, nogc));
+ CHECK_EQUAL(Shared, isShared);
+ CHECK_EQUAL(long(v.toInt32()),
+ long(reinterpret_cast<Element*>(data)[elts / 2]));
+ }
+
+ v.setInt32(41);
+ CHECK(JS_SetElement(cx, array, elts - 1, v));
+ CHECK(JS_GetElement(cx, array, elts - 1, &v2));
+ CHECK_SAME(v, v2);
+ CHECK(JS_GetElement(cx, ofsArray, elts / 2 - 1, &v2));
+ CHECK_SAME(v, v2);
+ {
+ JS::AutoCheckCannotGC nogc;
+ Element* data;
+ bool isShared;
+ CHECK(data = GetData(array, &isShared, nogc));
+ CHECK_EQUAL(Shared, isShared);
+ CHECK_EQUAL(long(v.toInt32()),
+ long(reinterpret_cast<Element*>(data)[elts - 1]));
+ }
+
+ JS::RootedObject copy(cx, CreateFromArray(cx, array));
+ CHECK(JS_GetElement(cx, array, 0, &v));
+ CHECK(JS_GetElement(cx, copy, 0, &v2));
+ CHECK_SAME(v, v2);
+
+ /* The copy should not see changes in the original */
+ v2.setInt32(42);
+ CHECK(JS_SetElement(cx, array, 0, v2));
+ CHECK(JS_GetElement(cx, copy, 0, &v2));
+ CHECK_SAME(v2, v); /* v is still the original value from 'array' */
+
+ return true;
+}
+
+END_TEST(testTypedArrays)
diff --git a/js/src/jsapi-tests/testUTF8.cpp b/js/src/jsapi-tests/testUTF8.cpp
new file mode 100644
index 0000000000..599bab357e
--- /dev/null
+++ b/js/src/jsapi-tests/testUTF8.cpp
@@ -0,0 +1,47 @@
+/* -*- 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/Range.h"
+
+#include "js/CharacterEncoding.h"
+#include "jsapi-tests/tests.h"
+
+BEGIN_TEST(testUTF8_badUTF8) {
+ static const char badUTF8[] = "...\xC0...";
+ JSString* str = JS_NewStringCopyZ(cx, badUTF8);
+ CHECK(str);
+ char16_t ch;
+ if (!JS_GetStringCharAt(cx, str, 3, &ch)) {
+ return false;
+ }
+ CHECK(ch == 0x00C0);
+ return true;
+}
+END_TEST(testUTF8_badUTF8)
+
+BEGIN_TEST(testUTF8_bigUTF8) {
+ static const char bigUTF8[] = "...\xFB\xBF\xBF\xBF\xBF...";
+ JSString* str = JS_NewStringCopyZ(cx, bigUTF8);
+ CHECK(str);
+ char16_t ch;
+ if (!JS_GetStringCharAt(cx, str, 3, &ch)) {
+ return false;
+ }
+ CHECK(ch == 0x00FB);
+ return true;
+}
+END_TEST(testUTF8_bigUTF8)
+
+BEGIN_TEST(testUTF8_badSurrogate) {
+ static const char16_t badSurrogate[] = {'A', 'B', 'C', 0xDEEE, 'D', 'E', 0};
+ mozilla::Range<const char16_t> tbchars(badSurrogate, js_strlen(badSurrogate));
+ JS::Latin1CharsZ latin1 = JS::LossyTwoByteCharsToNewLatin1CharsZ(cx, tbchars);
+ CHECK(latin1);
+ CHECK(latin1[3] == 0x00EE);
+ return true;
+}
+END_TEST(testUTF8_badSurrogate)
diff --git a/js/src/jsapi-tests/testUbiNode.cpp b/js/src/jsapi-tests/testUbiNode.cpp
new file mode 100644
index 0000000000..bbe11d928b
--- /dev/null
+++ b/js/src/jsapi-tests/testUbiNode.cpp
@@ -0,0 +1,971 @@
+/* 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/Utf8.h" // mozilla::Utf8Unit
+
+#include "builtin/TestingFunctions.h"
+#include "js/CompilationAndEvaluation.h" // JS::Compile
+#include "js/GlobalObject.h" // JS_NewGlobalObject
+#include "js/SourceText.h" // JS::Source{Ownership,Text}
+#include "js/UbiNode.h"
+#include "js/UbiNodeDominatorTree.h"
+#include "js/UbiNodePostOrder.h"
+#include "js/UbiNodeShortestPaths.h"
+#include "jsapi-tests/tests.h"
+#include "util/Text.h"
+#include "vm/Compartment.h"
+#include "vm/Realm.h"
+#include "vm/SavedFrame.h"
+
+#include "vm/JSObject-inl.h"
+
+using JS::RootedObject;
+using JS::RootedScript;
+using JS::RootedString;
+using namespace js;
+
+// A helper JS::ubi::Node concrete implementation that can be used to make mock
+// graphs for testing traversals with.
+struct FakeNode {
+ char name;
+ JS::ubi::EdgeVector edges;
+
+ explicit FakeNode(char name) : name(name), edges() {}
+
+ bool addEdgeTo(FakeNode& referent, const char16_t* edgeName = nullptr) {
+ JS::ubi::Node node(&referent);
+
+ if (edgeName) {
+ auto ownedName = js::DuplicateString(edgeName);
+ MOZ_RELEASE_ASSERT(ownedName);
+ return edges.emplaceBack(ownedName.release(), node);
+ }
+
+ return edges.emplaceBack(nullptr, node);
+ }
+};
+
+namespace JS {
+namespace ubi {
+
+template <>
+class Concrete<FakeNode> : public Base {
+ protected:
+ explicit Concrete(FakeNode* ptr) : Base(ptr) {}
+ FakeNode& get() const { return *static_cast<FakeNode*>(ptr); }
+
+ public:
+ static void construct(void* storage, FakeNode* ptr) {
+ new (storage) Concrete(ptr);
+ }
+
+ UniquePtr<EdgeRange> edges(JSContext* cx, bool wantNames) const override {
+ return UniquePtr<EdgeRange>(js_new<PreComputedEdgeRange>(get().edges));
+ }
+
+ Node::Size size(mozilla::MallocSizeOf) const override { return 1; }
+
+ static const char16_t concreteTypeName[];
+ const char16_t* typeName() const override { return concreteTypeName; }
+};
+
+const char16_t Concrete<FakeNode>::concreteTypeName[] = u"FakeNode";
+
+} // namespace ubi
+} // namespace JS
+
+// ubi::Node::zone works
+BEGIN_TEST(test_ubiNodeZone) {
+ RootedObject global1(cx, JS::CurrentGlobalOrNull(cx));
+ CHECK(global1);
+ CHECK(JS::ubi::Node(global1).zone() == cx->zone());
+
+ JS::RealmOptions globalOptions;
+ RootedObject global2(
+ cx, JS_NewGlobalObject(cx, getGlobalClass(), nullptr,
+ JS::FireOnNewGlobalHook, globalOptions));
+ CHECK(global2);
+ CHECK(global1->zone() != global2->zone());
+ CHECK(JS::ubi::Node(global2).zone() == global2->zone());
+ CHECK(JS::ubi::Node(global2).zone() != global1->zone());
+
+ JS::CompileOptions options(cx);
+
+ // Create a string and a script in the original zone...
+ RootedString string1(
+ cx, JS_NewStringCopyZ(cx, "Simpson's Individual Stringettes!"));
+ CHECK(string1);
+
+ JS::SourceText<mozilla::Utf8Unit> emptySrcBuf;
+ CHECK(emptySrcBuf.init(cx, "", 0, JS::SourceOwnership::Borrowed));
+
+ RootedScript script1(cx, JS::Compile(cx, options, emptySrcBuf));
+ CHECK(script1);
+
+ {
+ // ... and then enter global2's zone and create a string and script
+ // there, too.
+ JSAutoRealm ar(cx, global2);
+
+ RootedString string2(cx,
+ JS_NewStringCopyZ(cx, "A million household uses!"));
+ CHECK(string2);
+ RootedScript script2(cx, JS::Compile(cx, options, emptySrcBuf));
+ CHECK(script2);
+
+ CHECK(JS::ubi::Node(string1).zone() == global1->zone());
+ CHECK(JS::ubi::Node(script1).zone() == global1->zone());
+
+ CHECK(JS::ubi::Node(string2).zone() == global2->zone());
+ CHECK(JS::ubi::Node(script2).zone() == global2->zone());
+ }
+
+ return true;
+}
+END_TEST(test_ubiNodeZone)
+
+// ubi::Node::compartment works
+BEGIN_TEST(test_ubiNodeCompartment) {
+ RootedObject global1(cx, JS::CurrentGlobalOrNull(cx));
+ CHECK(global1);
+ CHECK(JS::ubi::Node(global1).compartment() == cx->compartment());
+ CHECK(JS::ubi::Node(global1).realm() == cx->realm());
+
+ JS::RealmOptions globalOptions;
+ RootedObject global2(
+ cx, JS_NewGlobalObject(cx, getGlobalClass(), nullptr,
+ JS::FireOnNewGlobalHook, globalOptions));
+ CHECK(global2);
+ CHECK(global1->compartment() != global2->compartment());
+ CHECK(JS::ubi::Node(global2).compartment() == global2->compartment());
+ CHECK(JS::ubi::Node(global2).compartment() != global1->compartment());
+ CHECK(JS::ubi::Node(global2).realm() == global2->nonCCWRealm());
+ CHECK(JS::ubi::Node(global2).realm() != global1->nonCCWRealm());
+
+ JS::CompileOptions options(cx);
+
+ JS::SourceText<mozilla::Utf8Unit> emptySrcBuf;
+ CHECK(emptySrcBuf.init(cx, "", 0, JS::SourceOwnership::Borrowed));
+
+ // Create a script in the original realm...
+ RootedScript script1(cx, JS::Compile(cx, options, emptySrcBuf));
+ CHECK(script1);
+
+ {
+ // ... and then enter global2's realm and create a script
+ // there, too.
+ JSAutoRealm ar(cx, global2);
+
+ RootedScript script2(cx, JS::Compile(cx, options, emptySrcBuf));
+ CHECK(script2);
+
+ CHECK(JS::ubi::Node(script1).compartment() == global1->compartment());
+ CHECK(JS::ubi::Node(script2).compartment() == global2->compartment());
+ CHECK(JS::ubi::Node(script1).realm() == global1->nonCCWRealm());
+ CHECK(JS::ubi::Node(script2).realm() == global2->nonCCWRealm());
+
+ // Now create a wrapper for global1 in global2's compartment.
+ RootedObject wrappedGlobal1(cx, global1);
+ CHECK(cx->compartment()->wrap(cx, &wrappedGlobal1));
+
+ // Cross-compartment wrappers have a compartment() but not a realm().
+ CHECK(JS::ubi::Node(wrappedGlobal1).zone() == cx->zone());
+ CHECK(JS::ubi::Node(wrappedGlobal1).compartment() == cx->compartment());
+ CHECK(JS::ubi::Node(wrappedGlobal1).realm() == nullptr);
+ }
+
+ return true;
+}
+END_TEST(test_ubiNodeCompartment)
+
+template <typename F, typename G>
+static bool checkString(const char* expected, F fillBufferFunction,
+ G stringGetterFunction) {
+ auto expectedLength = strlen(expected);
+ char16_t buf[1024];
+ if (fillBufferFunction(mozilla::RangedPtr<char16_t>(buf, 1024), 1024) !=
+ expectedLength ||
+ !EqualChars(buf, expected, expectedLength)) {
+ return false;
+ }
+
+ auto string = stringGetterFunction();
+ // Expecting a |JSAtom*| from a live |JS::ubi::StackFrame|.
+ if (!string.template is<JSAtom*>() ||
+ !StringEqualsAscii(string.template as<JSAtom*>(), expected)) {
+ return false;
+ }
+
+ return true;
+}
+
+BEGIN_TEST(test_ubiStackFrame) {
+ CHECK(js::DefineTestingFunctions(cx, global, false, false));
+
+ JS::RootedValue val(cx);
+ CHECK(
+ evaluate("(function one() { \n" // 1
+ " return (function two() { \n" // 2
+ " return (function three() { \n" // 3
+ " return saveStack(); \n" // 4
+ " }()); \n" // 5
+ " }()); \n" // 6
+ "}()); \n", // 7
+ "filename.js", 1, &val));
+
+ CHECK(val.isObject());
+ JS::RootedObject obj(cx, &val.toObject());
+
+ CHECK(obj->is<SavedFrame>());
+ JS::Rooted<SavedFrame*> savedFrame(cx, &obj->as<SavedFrame>());
+
+ JS::ubi::StackFrame ubiFrame(savedFrame);
+
+ // All frames should be from the "filename.js" source.
+ while (ubiFrame) {
+ CHECK(checkString(
+ "filename.js",
+ [&](mozilla::RangedPtr<char16_t> ptr, size_t length) {
+ return ubiFrame.source(ptr, length);
+ },
+ [&] { return ubiFrame.source(); }));
+ ubiFrame = ubiFrame.parent();
+ }
+
+ ubiFrame = savedFrame;
+
+ auto bufferFunctionDisplayName = [&](mozilla::RangedPtr<char16_t> ptr,
+ size_t length) {
+ return ubiFrame.functionDisplayName(ptr, length);
+ };
+ auto getFunctionDisplayName = [&] { return ubiFrame.functionDisplayName(); };
+
+ CHECK(
+ checkString("three", bufferFunctionDisplayName, getFunctionDisplayName));
+ CHECK(ubiFrame.line() == 4);
+
+ ubiFrame = ubiFrame.parent();
+ CHECK(checkString("two", bufferFunctionDisplayName, getFunctionDisplayName));
+ CHECK(ubiFrame.line() == 5);
+
+ ubiFrame = ubiFrame.parent();
+ CHECK(checkString("one", bufferFunctionDisplayName, getFunctionDisplayName));
+ CHECK(ubiFrame.line() == 6);
+
+ ubiFrame = ubiFrame.parent();
+ CHECK(ubiFrame.functionDisplayName().is<JSAtom*>());
+ CHECK(ubiFrame.functionDisplayName().as<JSAtom*>() == nullptr);
+ CHECK(ubiFrame.line() == 7);
+
+ ubiFrame = ubiFrame.parent();
+ CHECK(!ubiFrame);
+
+ return true;
+}
+END_TEST(test_ubiStackFrame)
+
+BEGIN_TEST(test_ubiCoarseType) {
+ // Test that our explicit coarseType() overrides work as expected.
+
+ JSObject* obj = nullptr;
+ CHECK(JS::ubi::Node(obj).coarseType() == JS::ubi::CoarseType::Object);
+
+ JSScript* script = nullptr;
+ CHECK(JS::ubi::Node(script).coarseType() == JS::ubi::CoarseType::Script);
+
+ js::BaseScript* baseScript = nullptr;
+ CHECK(JS::ubi::Node(baseScript).coarseType() == JS::ubi::CoarseType::Script);
+
+ js::jit::JitCode* jitCode = nullptr;
+ CHECK(JS::ubi::Node(jitCode).coarseType() == JS::ubi::CoarseType::Script);
+
+ JSString* str = nullptr;
+ CHECK(JS::ubi::Node(str).coarseType() == JS::ubi::CoarseType::String);
+
+ // Test that the default when coarseType() is not overridden is Other.
+
+ JS::Symbol* sym = nullptr;
+ CHECK(JS::ubi::Node(sym).coarseType() == JS::ubi::CoarseType::Other);
+
+ return true;
+}
+END_TEST(test_ubiCoarseType)
+
+struct ExpectedEdge {
+ char from;
+ char to;
+
+ ExpectedEdge(FakeNode& fromNode, FakeNode& toNode)
+ : from(fromNode.name), to(toNode.name) {}
+};
+
+namespace mozilla {
+
+template <>
+struct DefaultHasher<ExpectedEdge> {
+ using Lookup = ExpectedEdge;
+
+ static HashNumber hash(const Lookup& l) {
+ return mozilla::AddToHash(l.from, l.to);
+ }
+
+ static bool match(const ExpectedEdge& k, const Lookup& l) {
+ return k.from == l.from && k.to == l.to;
+ }
+};
+
+} // namespace mozilla
+
+BEGIN_TEST(test_ubiPostOrder) {
+ // Construct the following graph:
+ //
+ // .-----.
+ // | |
+ // .-------| r |---------------.
+ // | | | |
+ // | '-----' |
+ // | |
+ // .--V--. .--V--.
+ // | | | |
+ // .------| a |------. .----| e |----.
+ // | | | | | | | |
+ // | '--^--' | | '-----' |
+ // | | | | |
+ // .--V--. | .--V--. .--V--. .--V--.
+ // | | | | | | | | |
+ // | b | '------| c |-----> f |---------> g |
+ // | | | | | | | |
+ // '-----' '-----' '-----' '-----'
+ // | |
+ // | .-----. |
+ // | | | |
+ // '------> d <------'
+ // | |
+ // '-----'
+ //
+
+ FakeNode r('r');
+ FakeNode a('a');
+ FakeNode b('b');
+ FakeNode c('c');
+ FakeNode d('d');
+ FakeNode e('e');
+ FakeNode f('f');
+ FakeNode g('g');
+
+ js::HashSet<ExpectedEdge> expectedEdges(cx);
+
+ auto declareEdge = [&](FakeNode& from, FakeNode& to) {
+ return from.addEdgeTo(to) && expectedEdges.putNew(ExpectedEdge(from, to));
+ };
+
+ CHECK(declareEdge(r, a));
+ CHECK(declareEdge(r, e));
+ CHECK(declareEdge(a, b));
+ CHECK(declareEdge(a, c));
+ CHECK(declareEdge(b, d));
+ CHECK(declareEdge(c, a));
+ CHECK(declareEdge(c, d));
+ CHECK(declareEdge(c, f));
+ CHECK(declareEdge(e, f));
+ CHECK(declareEdge(e, g));
+ CHECK(declareEdge(f, g));
+
+ js::Vector<char, 8, js::SystemAllocPolicy> visited;
+ {
+ // Do a PostOrder traversal, starting from r. Accumulate the names of
+ // the nodes we visit in `visited`. Remove edges we traverse from
+ // `expectedEdges` as we find them to ensure that we only find each edge
+ // once.
+
+ JS::AutoCheckCannotGC nogc(cx);
+ JS::ubi::PostOrder traversal(cx, nogc);
+ CHECK(traversal.addStart(&r));
+
+ auto onNode = [&](const JS::ubi::Node& node) {
+ return visited.append(node.as<FakeNode>()->name);
+ };
+
+ auto onEdge = [&](const JS::ubi::Node& origin, const JS::ubi::Edge& edge) {
+ ExpectedEdge e(*origin.as<FakeNode>(), *edge.referent.as<FakeNode>());
+ if (!expectedEdges.has(e)) {
+ fprintf(stderr, "Error: Unexpected edge from %c to %c!\n",
+ origin.as<FakeNode>()->name,
+ edge.referent.as<FakeNode>()->name);
+ return false;
+ }
+
+ expectedEdges.remove(e);
+ return true;
+ };
+
+ CHECK(traversal.traverse(onNode, onEdge));
+ }
+
+ fprintf(stderr, "visited.length() = %lu\n", (unsigned long)visited.length());
+ for (size_t i = 0; i < visited.length(); i++) {
+ fprintf(stderr, "visited[%lu] = '%c'\n", (unsigned long)i, visited[i]);
+ }
+
+ CHECK(visited.length() == 8);
+ CHECK(visited[0] == 'g');
+ CHECK(visited[1] == 'f');
+ CHECK(visited[2] == 'e');
+ CHECK(visited[3] == 'd');
+ CHECK(visited[4] == 'c');
+ CHECK(visited[5] == 'b');
+ CHECK(visited[6] == 'a');
+ CHECK(visited[7] == 'r');
+
+ // We found all the edges we expected.
+ CHECK(expectedEdges.count() == 0);
+
+ return true;
+}
+END_TEST(test_ubiPostOrder)
+
+BEGIN_TEST(test_JS_ubi_DominatorTree) {
+ // Construct the following graph:
+ //
+ // .-----.
+ // | <--------------------------------.
+ // .--------+--------------| r |--------------. |
+ // | | | | | |
+ // | | '-----' | |
+ // | .--V--. .--V--. |
+ // | | | | | |
+ // | | b | | c |--------. |
+ // | | | | | | |
+ // | '-----' '-----' | |
+ // .--V--. | | .--V--. |
+ // | | | | | | |
+ // | a <-----+ | .----| g | |
+ // | | | .----' | | | |
+ // '-----' | | | '-----' |
+ // | | | | | |
+ // .--V--. | .-----. .--V--. | | |
+ // | | | | | | | | | |
+ // | d <-----+----> e <----. | f | | | |
+ // | | | | | | | | | |
+ // '-----' '-----' | '-----' | | |
+ // | .-----. | | | | .--V--. |
+ // | | | | | | .-' | | |
+ // '-----> l | | | | | | j | |
+ // | | '--. | | | | | |
+ // '-----' | | | | '-----' |
+ // | .--V--. | | .--V--. | |
+ // | | | | | | | | |
+ // '-------> h |-' '---> i <------' |
+ // | | .---------> | |
+ // '-----' | '-----' |
+ // | .-----. | |
+ // | | | | |
+ // '----------> k <---------' |
+ // | | |
+ // '-----' |
+ // | |
+ // '----------------------------'
+ //
+ // This graph has the following dominator tree:
+ //
+ // r
+ // |-- a
+ // |-- b
+ // |-- c
+ // | |-- f
+ // | `-- g
+ // | `-- j
+ // |-- d
+ // | `-- l
+ // |-- e
+ // |-- i
+ // |-- k
+ // `-- h
+ //
+ // This graph and dominator tree are taken from figures 1 and 2 of "A Fast
+ // Algorithm for Finding Dominators in a Flowgraph" by Lengauer et al:
+ // http://www.cs.princeton.edu/courses/archive/spr03/cs423/download/dominators.pdf.
+
+ FakeNode r('r');
+ FakeNode a('a');
+ FakeNode b('b');
+ FakeNode c('c');
+ FakeNode d('d');
+ FakeNode e('e');
+ FakeNode f('f');
+ FakeNode g('g');
+ FakeNode h('h');
+ FakeNode i('i');
+ FakeNode j('j');
+ FakeNode k('k');
+ FakeNode l('l');
+
+ CHECK(r.addEdgeTo(a));
+ CHECK(r.addEdgeTo(b));
+ CHECK(r.addEdgeTo(c));
+ CHECK(a.addEdgeTo(d));
+ CHECK(b.addEdgeTo(a));
+ CHECK(b.addEdgeTo(d));
+ CHECK(b.addEdgeTo(e));
+ CHECK(c.addEdgeTo(f));
+ CHECK(c.addEdgeTo(g));
+ CHECK(d.addEdgeTo(l));
+ CHECK(e.addEdgeTo(h));
+ CHECK(f.addEdgeTo(i));
+ CHECK(g.addEdgeTo(i));
+ CHECK(g.addEdgeTo(j));
+ CHECK(h.addEdgeTo(e));
+ CHECK(h.addEdgeTo(k));
+ CHECK(i.addEdgeTo(k));
+ CHECK(j.addEdgeTo(i));
+ CHECK(k.addEdgeTo(r));
+ CHECK(k.addEdgeTo(i));
+ CHECK(l.addEdgeTo(h));
+
+ mozilla::Maybe<JS::ubi::DominatorTree> maybeTree;
+ {
+ JS::AutoCheckCannotGC noGC(cx);
+ maybeTree = JS::ubi::DominatorTree::Create(cx, noGC, &r);
+ }
+
+ CHECK(maybeTree.isSome());
+ auto& tree = *maybeTree;
+
+ // We return the null JS::ubi::Node for nodes that were not reachable in the
+ // graph when computing the dominator tree.
+ FakeNode m('m');
+ CHECK(tree.getImmediateDominator(&m) == JS::ubi::Node());
+ CHECK(tree.getDominatedSet(&m).isNothing());
+
+ struct {
+ FakeNode& dominated;
+ FakeNode& dominator;
+ } domination[] = {{r, r}, {a, r}, {b, r}, {c, r}, {d, r}, {e, r}, {f, c},
+ {g, c}, {h, r}, {i, r}, {j, g}, {k, r}, {l, d}};
+
+ for (auto& relation : domination) {
+ // Test immediate dominator.
+ fprintf(
+ stderr, "%c's immediate dominator is %c\n", relation.dominated.name,
+ tree.getImmediateDominator(&relation.dominator).as<FakeNode>()->name);
+ CHECK(tree.getImmediateDominator(&relation.dominated) ==
+ JS::ubi::Node(&relation.dominator));
+
+ // Test the dominated set. Build up the expected dominated set based on
+ // the set of nodes immediately dominated by this one in `domination`,
+ // then iterate over the actual dominated set and check against the
+ // expected set.
+
+ auto& node = relation.dominated;
+ fprintf(stderr, "Checking %c's dominated set:\n", node.name);
+
+ js::HashSet<char> expectedDominatedSet(cx);
+ for (auto& rel : domination) {
+ if (&rel.dominator == &node) {
+ fprintf(stderr, " Expecting %c\n", rel.dominated.name);
+ CHECK(expectedDominatedSet.putNew(rel.dominated.name));
+ }
+ }
+
+ auto maybeActualDominatedSet = tree.getDominatedSet(&node);
+ CHECK(maybeActualDominatedSet.isSome());
+ auto& actualDominatedSet = *maybeActualDominatedSet;
+
+ for (const auto& dominated : actualDominatedSet) {
+ fprintf(stderr, " Found %c\n", dominated.as<FakeNode>()->name);
+ CHECK(expectedDominatedSet.has(dominated.as<FakeNode>()->name));
+ expectedDominatedSet.remove(dominated.as<FakeNode>()->name);
+ }
+
+ // Ensure we found them all and aren't still expecting nodes we never
+ // got.
+ CHECK(expectedDominatedSet.count() == 0);
+
+ fprintf(stderr, "Done checking %c's dominated set.\n\n", node.name);
+ }
+
+ struct {
+ FakeNode& node;
+ JS::ubi::Node::Size retainedSize;
+ } sizes[] = {
+ {r, 13}, {a, 1}, {b, 1}, {c, 4}, {d, 2}, {e, 1}, {f, 1},
+ {g, 2}, {h, 1}, {i, 1}, {j, 1}, {k, 1}, {l, 1},
+ };
+
+ for (auto& expected : sizes) {
+ JS::ubi::Node::Size actual = 0;
+ CHECK(tree.getRetainedSize(&expected.node, nullptr, actual));
+ CHECK(actual == expected.retainedSize);
+ }
+
+ return true;
+}
+END_TEST(test_JS_ubi_DominatorTree)
+
+BEGIN_TEST(test_JS_ubi_Node_scriptFilename) {
+ JS::RootedValue val(cx);
+ CHECK(
+ evaluate("(function one() { \n" // 1
+ " return (function two() { \n" // 2
+ " return (function three() { \n" // 3
+ " return function four() {}; \n" // 4
+ " }()); \n" // 5
+ " }()); \n" // 6
+ "}()); \n", // 7
+ "my-cool-filename.js", 1, &val));
+
+ CHECK(val.isObject());
+ JS::RootedObject obj(cx, &val.toObject());
+
+ CHECK(obj->is<JSFunction>());
+ JS::RootedFunction func(cx, &obj->as<JSFunction>());
+
+ JS::RootedScript script(cx, JSFunction::getOrCreateScript(cx, func));
+ CHECK(script);
+ CHECK(script->filename());
+
+ JS::ubi::Node node(script);
+ const char* filename = node.scriptFilename();
+ CHECK(filename);
+ CHECK(strcmp(filename, script->filename()) == 0);
+ CHECK(strcmp(filename, "my-cool-filename.js") == 0);
+
+ return true;
+}
+END_TEST(test_JS_ubi_Node_scriptFilename)
+
+#define LAMBDA_CHECK(cond) \
+ do { \
+ if (!(cond)) { \
+ fprintf(stderr, "%s:%d:CHECK failed: " #cond "\n", __FILE__, __LINE__); \
+ return false; \
+ } \
+ } while (false)
+
+static void dumpPath(JS::ubi::Path& path) {
+ for (size_t i = 0; i < path.length(); i++) {
+ fprintf(stderr, "path[%llu]->predecessor() = '%c'\n", (long long unsigned)i,
+ path[i]->predecessor().as<FakeNode>()->name);
+ }
+}
+
+BEGIN_TEST(test_JS_ubi_ShortestPaths_no_path) {
+ // Create the following graph:
+ //
+ // .---. .---. .---.
+ // | a | <--> | c | | b |
+ // '---' '---' '---'
+ FakeNode a('a');
+ FakeNode b('b');
+ FakeNode c('c');
+ CHECK(a.addEdgeTo(c));
+ CHECK(c.addEdgeTo(a));
+
+ mozilla::Maybe<JS::ubi::ShortestPaths> maybeShortestPaths;
+ {
+ JS::AutoCheckCannotGC noGC(cx);
+
+ JS::ubi::NodeSet targets;
+ CHECK(targets.put(&b));
+
+ maybeShortestPaths =
+ JS::ubi::ShortestPaths::Create(cx, noGC, 10, &a, std::move(targets));
+ }
+
+ CHECK(maybeShortestPaths);
+ auto& paths = *maybeShortestPaths;
+
+ size_t numPathsFound = 0;
+ bool ok = paths.forEachPath(&b, [&](JS::ubi::Path& path) {
+ numPathsFound++;
+ dumpPath(path);
+ return true;
+ });
+ CHECK(ok);
+ CHECK(numPathsFound == 0);
+
+ return true;
+}
+END_TEST(test_JS_ubi_ShortestPaths_no_path)
+
+BEGIN_TEST(test_JS_ubi_ShortestPaths_one_path) {
+ // Create the following graph:
+ //
+ // .---. .---. .---.
+ // | a | <--> | c | --> | b |
+ // '---' '---' '---'
+ FakeNode a('a');
+ FakeNode b('b');
+ FakeNode c('c');
+ CHECK(a.addEdgeTo(c));
+ CHECK(c.addEdgeTo(a));
+ CHECK(c.addEdgeTo(b));
+
+ mozilla::Maybe<JS::ubi::ShortestPaths> maybeShortestPaths;
+ {
+ JS::AutoCheckCannotGC noGC(cx);
+
+ JS::ubi::NodeSet targets;
+ CHECK(targets.put(&b));
+
+ maybeShortestPaths =
+ JS::ubi::ShortestPaths::Create(cx, noGC, 10, &a, std::move(targets));
+ }
+
+ CHECK(maybeShortestPaths);
+ auto& paths = *maybeShortestPaths;
+
+ size_t numPathsFound = 0;
+ bool ok = paths.forEachPath(&b, [&](JS::ubi::Path& path) {
+ numPathsFound++;
+
+ dumpPath(path);
+ LAMBDA_CHECK(path.length() == 2);
+ LAMBDA_CHECK(path[0]->predecessor() == JS::ubi::Node(&a));
+ LAMBDA_CHECK(path[1]->predecessor() == JS::ubi::Node(&c));
+
+ return true;
+ });
+
+ CHECK(ok);
+ CHECK(numPathsFound == 1);
+
+ return true;
+}
+END_TEST(test_JS_ubi_ShortestPaths_one_path)
+
+BEGIN_TEST(test_JS_ubi_ShortestPaths_multiple_paths) {
+ // Create the following graph:
+ //
+ // .---.
+ // .-----| a |-----.
+ // | '---' |
+ // V | V
+ // .---. | .---.
+ // | b | | | d |
+ // '---' | '---'
+ // | | |
+ // V | V
+ // .---. | .---.
+ // | c | | | e |
+ // '---' V '---'
+ // | .---. |
+ // '---->| f |<----'
+ // '---'
+ FakeNode a('a');
+ FakeNode b('b');
+ FakeNode c('c');
+ FakeNode d('d');
+ FakeNode e('e');
+ FakeNode f('f');
+ CHECK(a.addEdgeTo(b));
+ CHECK(a.addEdgeTo(f));
+ CHECK(a.addEdgeTo(d));
+ CHECK(b.addEdgeTo(c));
+ CHECK(c.addEdgeTo(f));
+ CHECK(d.addEdgeTo(e));
+ CHECK(e.addEdgeTo(f));
+
+ mozilla::Maybe<JS::ubi::ShortestPaths> maybeShortestPaths;
+ {
+ JS::AutoCheckCannotGC noGC(cx);
+
+ JS::ubi::NodeSet targets;
+ CHECK(targets.put(&f));
+
+ maybeShortestPaths =
+ JS::ubi::ShortestPaths::Create(cx, noGC, 10, &a, std::move(targets));
+ }
+
+ CHECK(maybeShortestPaths);
+ auto& paths = *maybeShortestPaths;
+
+ size_t numPathsFound = 0;
+ bool ok = paths.forEachPath(&f, [&](JS::ubi::Path& path) {
+ numPathsFound++;
+ dumpPath(path);
+
+ switch (path.back()->predecessor().as<FakeNode>()->name) {
+ case 'a': {
+ LAMBDA_CHECK(path.length() == 1);
+ break;
+ }
+
+ case 'c': {
+ LAMBDA_CHECK(path.length() == 3);
+ LAMBDA_CHECK(path[0]->predecessor() == JS::ubi::Node(&a));
+ LAMBDA_CHECK(path[1]->predecessor() == JS::ubi::Node(&b));
+ LAMBDA_CHECK(path[2]->predecessor() == JS::ubi::Node(&c));
+ break;
+ }
+
+ case 'e': {
+ LAMBDA_CHECK(path.length() == 3);
+ LAMBDA_CHECK(path[0]->predecessor() == JS::ubi::Node(&a));
+ LAMBDA_CHECK(path[1]->predecessor() == JS::ubi::Node(&d));
+ LAMBDA_CHECK(path[2]->predecessor() == JS::ubi::Node(&e));
+ break;
+ }
+
+ default: {
+ // Unexpected path!
+ LAMBDA_CHECK(false);
+ }
+ }
+
+ return true;
+ });
+
+ CHECK(ok);
+ fprintf(stderr, "numPathsFound = %llu\n", (long long unsigned)numPathsFound);
+ CHECK(numPathsFound == 3);
+
+ return true;
+}
+END_TEST(test_JS_ubi_ShortestPaths_multiple_paths)
+
+BEGIN_TEST(test_JS_ubi_ShortestPaths_more_paths_than_max) {
+ // Create the following graph:
+ //
+ // .---.
+ // .-----| a |-----.
+ // | '---' |
+ // V | V
+ // .---. | .---.
+ // | b | | | d |
+ // '---' | '---'
+ // | | |
+ // V | V
+ // .---. | .---.
+ // | c | | | e |
+ // '---' V '---'
+ // | .---. |
+ // '---->| f |<----'
+ // '---'
+ FakeNode a('a');
+ FakeNode b('b');
+ FakeNode c('c');
+ FakeNode d('d');
+ FakeNode e('e');
+ FakeNode f('f');
+ CHECK(a.addEdgeTo(b));
+ CHECK(a.addEdgeTo(f));
+ CHECK(a.addEdgeTo(d));
+ CHECK(b.addEdgeTo(c));
+ CHECK(c.addEdgeTo(f));
+ CHECK(d.addEdgeTo(e));
+ CHECK(e.addEdgeTo(f));
+
+ mozilla::Maybe<JS::ubi::ShortestPaths> maybeShortestPaths;
+ {
+ JS::AutoCheckCannotGC noGC(cx);
+
+ JS::ubi::NodeSet targets;
+ CHECK(targets.put(&f));
+
+ maybeShortestPaths =
+ JS::ubi::ShortestPaths::Create(cx, noGC, 1, &a, std::move(targets));
+ }
+
+ CHECK(maybeShortestPaths);
+ auto& paths = *maybeShortestPaths;
+
+ size_t numPathsFound = 0;
+ bool ok = paths.forEachPath(&f, [&](JS::ubi::Path& path) {
+ numPathsFound++;
+ dumpPath(path);
+ return true;
+ });
+
+ CHECK(ok);
+ fprintf(stderr, "numPathsFound = %llu\n", (long long unsigned)numPathsFound);
+ CHECK(numPathsFound == 1);
+
+ return true;
+}
+END_TEST(test_JS_ubi_ShortestPaths_more_paths_than_max)
+
+BEGIN_TEST(test_JS_ubi_ShortestPaths_multiple_edges_to_target) {
+ // Create the following graph:
+ //
+ // .---.
+ // .-----| a |-----.
+ // | '---' |
+ // | | |
+ // |x |y |z
+ // | | |
+ // | V |
+ // | .---. |
+ // '---->| b |<----'
+ // '---'
+ FakeNode a('a');
+ FakeNode b('b');
+ CHECK(a.addEdgeTo(b, u"x"));
+ CHECK(a.addEdgeTo(b, u"y"));
+ CHECK(a.addEdgeTo(b, u"z"));
+
+ mozilla::Maybe<JS::ubi::ShortestPaths> maybeShortestPaths;
+ {
+ JS::AutoCheckCannotGC noGC(cx);
+
+ JS::ubi::NodeSet targets;
+ CHECK(targets.put(&b));
+
+ maybeShortestPaths =
+ JS::ubi::ShortestPaths::Create(cx, noGC, 10, &a, std::move(targets));
+ }
+
+ CHECK(maybeShortestPaths);
+ auto& paths = *maybeShortestPaths;
+
+ size_t numPathsFound = 0;
+ bool foundX = false;
+ bool foundY = false;
+ bool foundZ = false;
+
+ bool ok = paths.forEachPath(&b, [&](JS::ubi::Path& path) {
+ numPathsFound++;
+ dumpPath(path);
+
+ LAMBDA_CHECK(path.length() == 1);
+ LAMBDA_CHECK(path.back()->name());
+ LAMBDA_CHECK(js_strlen(path.back()->name().get()) == 1);
+
+ auto c = uint8_t(path.back()->name().get()[0]);
+ fprintf(stderr, "Edge name = '%c'\n", c);
+
+ switch (c) {
+ case 'x': {
+ foundX = true;
+ break;
+ }
+ case 'y': {
+ foundY = true;
+ break;
+ }
+ case 'z': {
+ foundZ = true;
+ break;
+ }
+ default: {
+ // Unexpected edge!
+ LAMBDA_CHECK(false);
+ }
+ }
+
+ return true;
+ });
+
+ CHECK(ok);
+ fprintf(stderr, "numPathsFound = %llu\n", (long long unsigned)numPathsFound);
+ CHECK(numPathsFound == 3);
+ CHECK(foundX);
+ CHECK(foundY);
+ CHECK(foundZ);
+
+ return true;
+}
+END_TEST(test_JS_ubi_ShortestPaths_multiple_edges_to_target)
+
+#undef LAMBDA_CHECK
diff --git a/js/src/jsapi-tests/testUncaughtSymbol.cpp b/js/src/jsapi-tests/testUncaughtSymbol.cpp
new file mode 100644
index 0000000000..ce026a48c6
--- /dev/null
+++ b/js/src/jsapi-tests/testUncaughtSymbol.cpp
@@ -0,0 +1,57 @@
+/* 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 "js/Exception.h"
+#include "jsapi-tests/tests.h"
+
+using JS::CreateError;
+using JS::ObjectValue;
+using JS::Rooted;
+using JS::Value;
+
+enum SymbolExceptionType {
+ NONE,
+ SYMBOL_ITERATOR,
+ SYMBOL_FOO,
+ SYMBOL_EMPTY,
+};
+
+BEGIN_TEST(testUncaughtSymbol) {
+ CHECK(!execDontReport("throw Symbol.iterator;", __FILE__, __LINE__));
+ CHECK(GetSymbolExceptionType(cx) == SYMBOL_ITERATOR);
+
+ CHECK(!execDontReport("throw Symbol('foo');", __FILE__, __LINE__));
+ CHECK(GetSymbolExceptionType(cx) == SYMBOL_FOO);
+
+ CHECK(!execDontReport("throw Symbol();", __FILE__, __LINE__));
+ CHECK(GetSymbolExceptionType(cx) == SYMBOL_EMPTY);
+
+ return true;
+}
+
+static SymbolExceptionType GetSymbolExceptionType(JSContext* cx) {
+ JS::ExceptionStack exnStack(cx);
+ MOZ_RELEASE_ASSERT(JS::StealPendingExceptionStack(cx, &exnStack));
+ MOZ_RELEASE_ASSERT(exnStack.exception().isSymbol());
+
+ JS::ErrorReportBuilder report(cx);
+ MOZ_RELEASE_ASSERT(
+ report.init(cx, exnStack, JS::ErrorReportBuilder::WithSideEffects));
+
+ if (strcmp(report.toStringResult().c_str(),
+ "uncaught exception: Symbol(Symbol.iterator)") == 0) {
+ return SYMBOL_ITERATOR;
+ }
+ if (strcmp(report.toStringResult().c_str(),
+ "uncaught exception: Symbol(foo)") == 0) {
+ return SYMBOL_FOO;
+ }
+ if (strcmp(report.toStringResult().c_str(), "uncaught exception: Symbol()") ==
+ 0) {
+ return SYMBOL_EMPTY;
+ }
+ MOZ_CRASH("Unexpected symbol");
+}
+
+END_TEST(testUncaughtSymbol)
diff --git a/js/src/jsapi-tests/testValueABI.cpp b/js/src/jsapi-tests/testValueABI.cpp
new file mode 100644
index 0000000000..298bd7dd99
--- /dev/null
+++ b/js/src/jsapi-tests/testValueABI.cpp
@@ -0,0 +1,53 @@
+/* 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 "js/GlobalObject.h"
+#include "jsapi-tests/tests.h"
+
+/*
+ * Bug 689101 - jsval is technically a non-POD type because it has a private
+ * data member. On gcc, this doesn't seem to matter. On MSVC, this prevents
+ * returning a jsval from a function between C and C++ because it will use a
+ * retparam in C++ and a direct return value in C.
+ *
+ * Bug 712289 - jsval alignment was different on 32-bit platforms between C and
+ * C++ because the default alignments of js::Value and jsval_layout differ.
+ */
+
+extern "C" {
+
+extern bool C_ValueToObject(JSContext* cx, jsval v, JSObject** obj);
+
+extern jsval C_GetEmptyStringValue(JSContext* cx);
+
+extern size_t C_jsvalAlignmentTest();
+}
+
+BEGIN_TEST(testValueABI_retparam) {
+ JS::RootedObject obj(cx, JS::CurrentGlobalOrNull(cx));
+ RootedValue v(cx, ObjectValue(*obj));
+ obj = nullptr;
+ CHECK(C_ValueToObject(cx, v, obj.address()));
+ bool equal;
+ RootedValue v2(cx, ObjectValue(*obj));
+ CHECK(JS_StrictlyEqual(cx, v, v2, &equal));
+ CHECK(equal);
+
+ v = C_GetEmptyStringValue(cx);
+ CHECK(v.isString());
+
+ return true;
+}
+END_TEST(testValueABI_retparam)
+
+BEGIN_TEST(testValueABI_alignment) {
+ typedef struct {
+ char c;
+ jsval v;
+ } AlignTest;
+ CHECK(C_jsvalAlignmentTest() == sizeof(AlignTest));
+
+ return true;
+}
+END_TEST(testValueABI_alignment)
diff --git a/js/src/jsapi-tests/testWasmLEB128.cpp b/js/src/jsapi-tests/testWasmLEB128.cpp
new file mode 100644
index 0000000000..58ded9ab26
--- /dev/null
+++ b/js/src/jsapi-tests/testWasmLEB128.cpp
@@ -0,0 +1,173 @@
+/* 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 <stdlib.h>
+
+#include "jsapi-tests/tests.h"
+
+#include "wasm/WasmValidate.h"
+
+static bool WriteValidBytes(js::wasm::Encoder& encoder, bool* passed) {
+ *passed = false;
+ if (!encoder.empty()) {
+ return true;
+ }
+
+ // These remain the same under LEB128 unsigned encoding
+ if (!encoder.writeVarU32(0x0) || !encoder.writeVarU32(0x1) ||
+ !encoder.writeVarU32(0x42)) {
+ return false;
+ }
+
+ // 0x01 0x80
+ if (!encoder.writeVarU32(0x80)) {
+ return false;
+ }
+
+ // 0x03 0x80
+ if (!encoder.writeVarU32(0x180)) {
+ return false;
+ }
+
+ if (encoder.empty()) {
+ return true;
+ }
+ if (encoder.currentOffset() != 7) {
+ return true;
+ }
+ *passed = true;
+ return true;
+}
+
+BEGIN_TEST(testWasmLEB128_encoding) {
+ using namespace js;
+ using namespace wasm;
+
+ Bytes bytes;
+ Encoder encoder(bytes);
+
+ bool passed;
+ if (!WriteValidBytes(encoder, &passed)) {
+ return false;
+ }
+ CHECK(passed);
+
+ size_t i = 0;
+ CHECK(bytes[i++] == 0x0);
+ CHECK(bytes[i++] == 0x1);
+ CHECK(bytes[i++] == 0x42);
+
+ CHECK(bytes[i++] == 0x80);
+ CHECK(bytes[i++] == 0x01);
+
+ CHECK(bytes[i++] == 0x80);
+ CHECK(bytes[i++] == 0x03);
+
+ if (i + 1 < bytes.length()) {
+ CHECK(bytes[i++] == 0x00);
+ }
+ return true;
+}
+END_TEST(testWasmLEB128_encoding)
+
+BEGIN_TEST(testWasmLEB128_valid_decoding) {
+ using namespace js;
+ using namespace wasm;
+
+ Bytes bytes;
+ if (!bytes.append(0x0) || !bytes.append(0x1) || !bytes.append(0x42)) {
+ return false;
+ }
+
+ if (!bytes.append(0x80) || !bytes.append(0x01)) {
+ return false;
+ }
+
+ if (!bytes.append(0x80) || !bytes.append(0x03)) {
+ return false;
+ }
+
+ {
+ // Fallible decoding
+ Decoder decoder(bytes);
+ uint32_t value;
+
+ CHECK(decoder.readVarU32(&value) && value == 0x0);
+ CHECK(decoder.readVarU32(&value) && value == 0x1);
+ CHECK(decoder.readVarU32(&value) && value == 0x42);
+ CHECK(decoder.readVarU32(&value) && value == 0x80);
+ CHECK(decoder.readVarU32(&value) && value == 0x180);
+
+ CHECK(decoder.done());
+ }
+
+ {
+ // Infallible decoding
+ Decoder decoder(bytes);
+ uint32_t value;
+
+ value = decoder.uncheckedReadVarU32();
+ CHECK(value == 0x0);
+ value = decoder.uncheckedReadVarU32();
+ CHECK(value == 0x1);
+ value = decoder.uncheckedReadVarU32();
+ CHECK(value == 0x42);
+ value = decoder.uncheckedReadVarU32();
+ CHECK(value == 0x80);
+ value = decoder.uncheckedReadVarU32();
+ CHECK(value == 0x180);
+
+ CHECK(decoder.done());
+ }
+ return true;
+}
+END_TEST(testWasmLEB128_valid_decoding)
+
+BEGIN_TEST(testWasmLEB128_invalid_decoding) {
+ using namespace js;
+ using namespace wasm;
+
+ Bytes bytes;
+ // Fill bits as per 28 encoded bits
+ if (!bytes.append(0x80) || !bytes.append(0x80) || !bytes.append(0x80) ||
+ !bytes.append(0x80)) {
+ return false;
+ }
+
+ // Test last valid values
+ if (!bytes.append(0x00)) {
+ return false;
+ }
+
+ for (uint8_t i = 0; i < 0x0F; i++) {
+ bytes[4] = i;
+
+ {
+ Decoder decoder(bytes);
+ uint32_t value;
+ CHECK(decoder.readVarU32(&value));
+ CHECK(value == uint32_t(i << 28));
+ CHECK(decoder.done());
+ }
+
+ {
+ Decoder decoder(bytes);
+ uint32_t value = decoder.uncheckedReadVarU32();
+ CHECK(value == uint32_t(i << 28));
+ CHECK(decoder.done());
+ }
+ }
+
+ // Test all invalid values of the same size
+ for (uint8_t i = 0x10; i < 0xF0; i++) {
+ bytes[4] = i;
+
+ Decoder decoder(bytes);
+ uint32_t value;
+ CHECK(!decoder.readVarU32(&value));
+ }
+
+ return true;
+}
+END_TEST(testWasmLEB128_invalid_decoding)
diff --git a/js/src/jsapi-tests/testWeakMap.cpp b/js/src/jsapi-tests/testWeakMap.cpp
new file mode 100644
index 0000000000..95a291a8a0
--- /dev/null
+++ b/js/src/jsapi-tests/testWeakMap.cpp
@@ -0,0 +1,255 @@
+/* -*- 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 "gc/Zone.h"
+#include "js/Array.h" // JS::GetArrayLength
+#include "js/GlobalObject.h" // JS_NewGlobalObject
+#include "js/PropertyAndElement.h" // JS_DefineProperty
+#include "js/WeakMap.h"
+#include "jsapi-tests/tests.h"
+#include "vm/Realm.h"
+
+using namespace js;
+
+JSObject* keyDelegate = nullptr;
+
+BEGIN_TEST(testWeakMap_basicOperations) {
+ JS::RootedObject map(cx, JS::NewWeakMapObject(cx));
+ CHECK(IsWeakMapObject(map));
+
+ JS::RootedObject key(cx, newKey());
+ CHECK(key);
+ CHECK(!IsWeakMapObject(key));
+
+ JS::RootedValue r(cx);
+ CHECK(GetWeakMapEntry(cx, map, key, &r));
+ CHECK(r.isUndefined());
+
+ CHECK(checkSize(map, 0));
+
+ JS::RootedValue val(cx, JS::Int32Value(1));
+ CHECK(SetWeakMapEntry(cx, map, key, val));
+
+ CHECK(GetWeakMapEntry(cx, map, key, &r));
+ CHECK(r == val);
+ CHECK(checkSize(map, 1));
+
+ JS_GC(cx);
+
+ CHECK(GetWeakMapEntry(cx, map, key, &r));
+ CHECK(r == val);
+ CHECK(checkSize(map, 1));
+
+ key = nullptr;
+ JS_GC(cx);
+
+ CHECK(checkSize(map, 0));
+
+ return true;
+}
+
+JSObject* newKey() { return JS_NewPlainObject(cx); }
+
+bool checkSize(JS::HandleObject map, uint32_t expected) {
+ JS::RootedObject keys(cx);
+ CHECK(JS_NondeterministicGetWeakMapKeys(cx, map, &keys));
+
+ uint32_t length;
+ CHECK(JS::GetArrayLength(cx, keys, &length));
+ CHECK(length == expected);
+
+ return true;
+}
+END_TEST(testWeakMap_basicOperations)
+
+BEGIN_TEST(testWeakMap_keyDelegates) {
+ AutoLeaveZeal nozeal(cx);
+
+ AutoGCParameter param(cx, JSGC_INCREMENTAL_GC_ENABLED, true);
+ JS_GC(cx);
+ JS::RootedObject map(cx, JS::NewWeakMapObject(cx));
+ CHECK(map);
+
+ JS::RootedObject delegate(cx, newDelegate());
+ JS::RootedObject key(cx, delegate);
+ if (!JS_WrapObject(cx, &key)) {
+ return false;
+ }
+ CHECK(key);
+ CHECK(delegate);
+
+ keyDelegate = delegate;
+
+ JS::RootedObject delegateRoot(cx);
+ {
+ JSAutoRealm ar(cx, delegate);
+ delegateRoot = JS_NewPlainObject(cx);
+ CHECK(delegateRoot);
+ JS::RootedValue delegateValue(cx, JS::ObjectValue(*delegate));
+ CHECK(JS_DefineProperty(cx, delegateRoot, "delegate", delegateValue, 0));
+ }
+ delegate = nullptr;
+
+ /*
+ * Perform an incremental GC, introducing an unmarked CCW to force the map
+ * zone to finish marking before the delegate zone.
+ */
+ CHECK(newCCW(map, delegateRoot));
+ performIncrementalGC();
+#ifdef DEBUG
+ CHECK(map->zone()->lastSweepGroupIndex() <
+ delegateRoot->zone()->lastSweepGroupIndex());
+#endif
+
+ /* Add our entry to the weakmap. */
+ JS::RootedValue val(cx, JS::Int32Value(1));
+ CHECK(SetWeakMapEntry(cx, map, key, val));
+ CHECK(checkSize(map, 1));
+
+ /*
+ * Check the delegate keeps the entry alive even if the key is not reachable.
+ */
+ key = nullptr;
+ CHECK(newCCW(map, delegateRoot));
+ performIncrementalGC();
+ CHECK(checkSize(map, 1));
+
+ /*
+ * Check that the zones finished marking at the same time, which is
+ * necessary because of the presence of the delegate and the CCW.
+ */
+#ifdef DEBUG
+ CHECK(map->zone()->lastSweepGroupIndex() ==
+ delegateRoot->zone()->lastSweepGroupIndex());
+#endif
+
+ /* Check that when the delegate becomes unreachable the entry is removed. */
+ delegateRoot = nullptr;
+ keyDelegate = nullptr;
+ JS_GC(cx);
+ CHECK(checkSize(map, 0));
+
+ return true;
+}
+
+static size_t DelegateObjectMoved(JSObject* obj, JSObject* old) {
+ if (!keyDelegate) {
+ return 0; // Object got moved before we set keyDelegate to point to it.
+ }
+
+ MOZ_RELEASE_ASSERT(keyDelegate == old);
+ keyDelegate = obj;
+ return 0;
+}
+
+JSObject* newKey() {
+ static const JSClass keyClass = {
+ "keyWithDelegate", JSCLASS_HAS_RESERVED_SLOTS(1),
+ JS_NULL_CLASS_OPS, JS_NULL_CLASS_SPEC,
+ JS_NULL_CLASS_EXT, JS_NULL_OBJECT_OPS};
+
+ JS::RootedObject key(cx, JS_NewObject(cx, &keyClass));
+ if (!key) {
+ return nullptr;
+ }
+
+ return key;
+}
+
+JSObject* newCCW(JS::HandleObject sourceZone, JS::HandleObject destZone) {
+ /*
+ * Now ensure that this zone will be swept first by adding a cross
+ * compartment wrapper to a new object in the same zone as the
+ * delegate object.
+ */
+ JS::RootedObject object(cx);
+ {
+ JSAutoRealm ar(cx, destZone);
+ object = JS_NewPlainObject(cx);
+ if (!object) {
+ return nullptr;
+ }
+ }
+ {
+ JSAutoRealm ar(cx, sourceZone);
+ if (!JS_WrapObject(cx, &object)) {
+ return nullptr;
+ }
+ }
+
+ // In order to test the SCC algorithm, we need the wrapper/wrappee to be
+ // tenured.
+ cx->runtime()->gc.evictNursery();
+
+ return object;
+}
+
+JSObject* newDelegate() {
+ static const JSClassOps delegateClassOps = {
+ nullptr, // addProperty
+ nullptr, // delProperty
+ nullptr, // enumerate
+ nullptr, // newEnumerate
+ nullptr, // resolve
+ nullptr, // mayResolve
+ nullptr, // finalize
+ nullptr, // call
+ nullptr, // construct
+ JS_GlobalObjectTraceHook, // trace
+ };
+
+ static const js::ClassExtension delegateClassExtension = {
+ DelegateObjectMoved, // objectMovedOp
+ };
+
+ static const JSClass delegateClass = {
+ "delegate",
+ JSCLASS_GLOBAL_FLAGS | JSCLASS_HAS_RESERVED_SLOTS(1),
+ &delegateClassOps,
+ JS_NULL_CLASS_SPEC,
+ &delegateClassExtension,
+ JS_NULL_OBJECT_OPS};
+
+ /* Create the global object. */
+ JS::RealmOptions options;
+ JS::RootedObject global(cx,
+ JS_NewGlobalObject(cx, &delegateClass, nullptr,
+ JS::FireOnNewGlobalHook, options));
+ if (!global) {
+ return nullptr;
+ }
+
+ JS_SetReservedSlot(global, 0, JS::Int32Value(42));
+ return global;
+}
+
+bool checkSize(JS::HandleObject map, uint32_t expected) {
+ JS::RootedObject keys(cx);
+ CHECK(JS_NondeterministicGetWeakMapKeys(cx, map, &keys));
+
+ uint32_t length;
+ CHECK(JS::GetArrayLength(cx, keys, &length));
+ CHECK(length == expected);
+
+ return true;
+}
+
+void performIncrementalGC() {
+ JSRuntime* rt = cx->runtime();
+ js::SliceBudget budget(js::WorkBudget(1000));
+ rt->gc.startDebugGC(JS::GCOptions::Normal, budget);
+
+ // Wait until we've started marking before finishing the GC
+ // non-incrementally.
+ while (rt->gc.state() == gc::State::Prepare) {
+ rt->gc.debugGCSlice(budget);
+ }
+ if (JS::IsIncrementalGCInProgress(cx)) {
+ rt->gc.finishGC(JS::GCReason::DEBUG_GC);
+ }
+}
+END_TEST(testWeakMap_keyDelegates)
diff --git a/js/src/jsapi-tests/testWindowNonConfigurable.cpp b/js/src/jsapi-tests/testWindowNonConfigurable.cpp
new file mode 100644
index 0000000000..a0aa78f90a
--- /dev/null
+++ b/js/src/jsapi-tests/testWindowNonConfigurable.cpp
@@ -0,0 +1,69 @@
+/* -*- 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 "js/PropertyAndElement.h" // JS_DefineProperty
+#include "js/Proxy.h"
+#include "jsapi-tests/tests.h"
+
+class WindowProxyHandler : public js::ForwardingProxyHandler {
+ public:
+ constexpr WindowProxyHandler() : js::ForwardingProxyHandler(&family) {}
+
+ static const char family;
+
+ virtual bool defineProperty(JSContext* cx, JS::HandleObject proxy,
+ JS::HandleId id,
+ JS::Handle<JS::PropertyDescriptor> desc,
+ JS::ObjectOpResult& result) const override {
+ if (desc.hasConfigurable() && !desc.configurable()) {
+ result.failCantDefineWindowNonConfigurable();
+ return true;
+ }
+ return ForwardingProxyHandler::defineProperty(cx, proxy, id, desc, result);
+ }
+};
+const char WindowProxyHandler::family = 0;
+
+static const JSClass windowProxy_class =
+ PROXY_CLASS_DEF("TestWindowProxy", JSCLASS_HAS_RESERVED_SLOTS(1));
+static const WindowProxyHandler windowProxy_handler;
+
+BEGIN_TEST(testWindowNonConfigurable) {
+ JS::RootedObject wrapped(cx, JS_NewObject(cx, nullptr));
+ CHECK(wrapped);
+ JS::RootedValue wrappedVal(cx, JS::ObjectValue(*wrapped));
+ js::ProxyOptions options;
+ options.setClass(&windowProxy_class);
+ JS::RootedObject obj(cx, NewProxyObject(cx, &windowProxy_handler, wrappedVal,
+ nullptr, options));
+ CHECK(obj);
+ CHECK(JS_DefineProperty(cx, global, "windowProxy", obj, 0));
+ JS::RootedValue v(cx);
+ EVAL(
+ "Object.defineProperty(windowProxy, 'bar', {value: 1, configurable: "
+ "false})",
+ &v);
+ CHECK(v.isNull()); // This is the important bit!
+ EVAL(
+ "Object.defineProperty(windowProxy, 'bar', {value: 1, configurable: "
+ "true})",
+ &v);
+ CHECK(&v.toObject() == obj);
+ EVAL(
+ "Reflect.defineProperty(windowProxy, 'foo', {value: 1, configurable: "
+ "false})",
+ &v);
+ CHECK(v.isFalse());
+ EVAL(
+ "Reflect.defineProperty(windowProxy, 'foo', {value: 1, configurable: "
+ "true})",
+ &v);
+ CHECK(v.isTrue());
+
+ return true;
+}
+END_TEST(testWindowNonConfigurable)
diff --git a/js/src/jsapi-tests/tests.cpp b/js/src/jsapi-tests/tests.cpp
new file mode 100644
index 0000000000..d2babb1c8e
--- /dev/null
+++ b/js/src/jsapi-tests/tests.cpp
@@ -0,0 +1,292 @@
+/* -*- 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 "jsapi-tests/tests.h"
+
+#include "mozilla/Utf8.h" // mozilla::Utf8Unit
+
+#include <stdio.h>
+
+#include "js/ArrayBuffer.h"
+#include "js/CompilationAndEvaluation.h" // JS::Evaluate
+#include "js/GlobalObject.h" // JS_NewGlobalObject
+#include "js/Initialization.h"
+#include "js/PropertyAndElement.h" // JS_DefineFunction
+#include "js/RootingAPI.h"
+#include "js/SourceText.h" // JS::Source{Ownership,Text}
+
+JSAPIRuntimeTest* JSAPIRuntimeTest::list;
+JSAPIFrontendTest* JSAPIFrontendTest::list;
+
+bool JSAPIRuntimeTest::init(JSContext* maybeReusableContext) {
+ if (maybeReusableContext && reuseGlobal) {
+ cx = maybeReusableContext;
+ global.init(cx, JS::CurrentGlobalOrNull(cx));
+ return init();
+ }
+
+ MaybeFreeContext(maybeReusableContext);
+
+ cx = createContext();
+ if (!cx) {
+ return false;
+ }
+
+ js::UseInternalJobQueues(cx);
+
+ if (!JS::InitSelfHostedCode(cx)) {
+ return false;
+ }
+ global.init(cx);
+ createGlobal();
+ if (!global) {
+ return false;
+ }
+ JS::EnterRealm(cx, global);
+ return init();
+}
+
+JSContext* JSAPIRuntimeTest::maybeForgetContext() {
+ if (!reuseGlobal) {
+ return nullptr;
+ }
+
+ JSContext* reusableCx = cx;
+ global.reset();
+ cx = nullptr;
+ return reusableCx;
+}
+
+/* static */
+void JSAPIRuntimeTest::MaybeFreeContext(JSContext* maybeCx) {
+ if (maybeCx) {
+ JS::LeaveRealm(maybeCx, nullptr);
+ JS_DestroyContext(maybeCx);
+ }
+}
+
+void JSAPIRuntimeTest::uninit() {
+ global.reset();
+ MaybeFreeContext(cx);
+ cx = nullptr;
+ msgs.clear();
+}
+
+bool JSAPIRuntimeTest::exec(const char* utf8, const char* filename,
+ int lineno) {
+ JS::CompileOptions opts(cx);
+ opts.setFileAndLine(filename, lineno);
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ JS::RootedValue v(cx);
+ return (srcBuf.init(cx, utf8, strlen(utf8), JS::SourceOwnership::Borrowed) &&
+ JS::Evaluate(cx, opts, srcBuf, &v)) ||
+ fail(JSAPITestString(utf8), filename, lineno);
+}
+
+bool JSAPIRuntimeTest::execDontReport(const char* utf8, const char* filename,
+ int lineno) {
+ JS::CompileOptions opts(cx);
+ opts.setFileAndLine(filename, lineno);
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ JS::RootedValue v(cx);
+ return srcBuf.init(cx, utf8, strlen(utf8), JS::SourceOwnership::Borrowed) &&
+ JS::Evaluate(cx, opts, srcBuf, &v);
+}
+
+bool JSAPIRuntimeTest::evaluate(const char* utf8, const char* filename,
+ int lineno, JS::MutableHandleValue vp) {
+ JS::CompileOptions opts(cx);
+ opts.setFileAndLine(filename, lineno);
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ return (srcBuf.init(cx, utf8, strlen(utf8), JS::SourceOwnership::Borrowed) &&
+ JS::Evaluate(cx, opts, srcBuf, vp)) ||
+ fail(JSAPITestString(utf8), filename, lineno);
+}
+
+bool JSAPIRuntimeTest::definePrint() {
+ return JS_DefineFunction(cx, global, "print", (JSNative)print, 0, 0);
+}
+
+JSObject* JSAPIRuntimeTest::createGlobal(JSPrincipals* principals) {
+ /* Create the global object. */
+ JS::RootedObject newGlobal(cx);
+ JS::RealmOptions options;
+ options.creationOptions()
+ .setWeakRefsEnabled(JS::WeakRefSpecifier::EnabledWithCleanupSome)
+ .setSharedMemoryAndAtomicsEnabled(true);
+ newGlobal = JS_NewGlobalObject(cx, getGlobalClass(), principals,
+ JS::FireOnNewGlobalHook, options);
+ if (!newGlobal) {
+ return nullptr;
+ }
+
+ global = newGlobal;
+ return newGlobal;
+}
+
+struct CommandOptions {
+ bool list = false;
+ bool frontendOnly = false;
+ bool help = false;
+ const char* filter = nullptr;
+};
+
+void parseArgs(int argc, char* argv[], CommandOptions& options) {
+ for (int i = 1; i < argc; i++) {
+ if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
+ options.help = true;
+ continue;
+ }
+
+ if (strcmp(argv[i], "--list") == 0) {
+ options.list = true;
+ continue;
+ }
+
+ if (strcmp(argv[i], "--frontend-only") == 0) {
+ options.frontendOnly = true;
+ continue;
+ }
+
+ if (!options.filter) {
+ options.filter = argv[i];
+ continue;
+ }
+
+ printf("error: Unrecognized option: %s\n", argv[i]);
+ options.help = true;
+ }
+}
+
+template <typename TestT>
+void PrintTests(TestT* list) {
+ for (TestT* test = list; test; test = test->next) {
+ printf("%s\n", test->name());
+ }
+}
+
+template <typename TestT, typename InitF, typename RunF, typename BeforeUninitF>
+void RunTests(int& total, int& failures, CommandOptions& options, TestT* list,
+ InitF init, RunF run, BeforeUninitF beforeUninit) {
+ for (TestT* test = list; test; test = test->next) {
+ const char* name = test->name();
+ if (options.filter && strstr(name, options.filter) == nullptr) {
+ continue;
+ }
+
+ total += 1;
+
+ printf("%s\n", name);
+
+ // Make sure the test name is printed before we enter the test that can
+ // crash on failure.
+ fflush(stdout);
+
+ if (!init(test)) {
+ printf("TEST-UNEXPECTED-FAIL | %s | Failed to initialize.\n", name);
+ failures++;
+ test->uninit();
+ continue;
+ }
+
+ if (run(test)) {
+ printf("TEST-PASS | %s | ok\n", name);
+ } else {
+ JSAPITestString messages = test->messages();
+ printf("%s | %s | %.*s\n",
+ (test->knownFail ? "TEST-KNOWN-FAIL" : "TEST-UNEXPECTED-FAIL"),
+ name, (int)messages.length(), messages.begin());
+ if (!test->knownFail) {
+ failures++;
+ }
+ }
+
+ beforeUninit(test);
+
+ test->uninit();
+ }
+}
+
+int main(int argc, char* argv[]) {
+ int total = 0;
+ int failures = 0;
+ CommandOptions options;
+ parseArgs(argc, argv, options);
+
+ if (options.help) {
+ printf("Usage: jsapi-tests [OPTIONS] [FILTER]\n");
+ printf("\n");
+ printf("Options:\n");
+ printf(" -h, --help Display this message\n");
+ printf(" --list List all tests\n");
+ printf(
+ " --frontend-only Run tests for frontend-only APIs, with "
+ "light-weight entry point\n");
+ return 0;
+ }
+
+ if (!options.frontendOnly) {
+ if (!JS_Init()) {
+ printf("TEST-UNEXPECTED-FAIL | jsapi-tests | JS_Init() failed.\n");
+ return 1;
+ }
+ } else {
+ if (!JS_FrontendOnlyInit()) {
+ printf("TEST-UNEXPECTED-FAIL | jsapi-tests | JS_Init() failed.\n");
+ return 1;
+ }
+ }
+
+ if (options.list) {
+ PrintTests(JSAPIRuntimeTest::list);
+ PrintTests(JSAPIFrontendTest::list);
+ return 0;
+ }
+
+ // Reinitializing the global for every test is quite slow, due to having to
+ // recompile all self-hosted builtins. Allow tests to opt-in to reusing the
+ // global.
+ JSContext* maybeReusedContext = nullptr;
+
+ if (!options.frontendOnly) {
+ RunTests(
+ total, failures, options, JSAPIRuntimeTest::list,
+ [&maybeReusedContext](JSAPIRuntimeTest* test) {
+ return test->init(maybeReusedContext);
+ },
+ [](JSAPIRuntimeTest* test) { return test->run(test->global); },
+ [&maybeReusedContext](JSAPIRuntimeTest* test) {
+ // Return a non-nullptr pointer if the context & global can safely be
+ // reused for the next test.
+ maybeReusedContext = test->maybeForgetContext();
+ });
+ }
+ RunTests(
+ total, failures, options, JSAPIFrontendTest::list,
+ [](JSAPIFrontendTest* test) { return test->init(); },
+ [](JSAPIFrontendTest* test) { return test->run(); },
+ [](JSAPIFrontendTest* test) {});
+
+ if (!options.frontendOnly) {
+ JSAPIRuntimeTest::MaybeFreeContext(maybeReusedContext);
+
+ MOZ_RELEASE_ASSERT(!JSRuntime::hasLiveRuntimes());
+ JS_ShutDown();
+ } else {
+ JS_FrontendOnlyShutDown();
+ }
+
+ if (failures) {
+ printf("\n%d unexpected failure%s.\n", failures,
+ (failures == 1 ? "" : "s"));
+ return 1;
+ }
+ printf("\nPassed: ran %d tests.\n", total);
+ return 0;
+}
diff --git a/js/src/jsapi-tests/tests.h b/js/src/jsapi-tests/tests.h
new file mode 100644
index 0000000000..ab9e391e1e
--- /dev/null
+++ b/js/src/jsapi-tests/tests.h
@@ -0,0 +1,621 @@
+/* -*- 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 jsapi_tests_tests_h
+#define jsapi_tests_tests_h
+
+#include "mozilla/Sprintf.h"
+
+#include <errno.h>
+#include <iterator>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <type_traits>
+
+#include "jsapi.h"
+
+#include "gc/GC.h"
+#include "js/AllocPolicy.h"
+#include "js/CharacterEncoding.h"
+#include "js/Conversions.h"
+#include "js/Equality.h" // JS::SameValue
+#include "js/GlobalObject.h" // JS::DefaultGlobalClassOps
+#include "js/RegExpFlags.h" // JS::RegExpFlags
+#include "js/Vector.h"
+#include "js/Warnings.h" // JS::SetWarningReporter
+#include "vm/JSContext.h"
+
+/* Note: Aborts on OOM. */
+class JSAPITestString {
+ js::Vector<char, 0, js::SystemAllocPolicy> chars;
+
+ public:
+ JSAPITestString() {}
+ explicit JSAPITestString(const char* s) { *this += s; }
+ JSAPITestString(const JSAPITestString& s) { *this += s; }
+
+ const char* begin() const { return chars.begin(); }
+ const char* end() const { return chars.end(); }
+ size_t length() const { return chars.length(); }
+ void clear() { chars.clearAndFree(); }
+
+ JSAPITestString& operator+=(const char* s) {
+ if (!chars.append(s, strlen(s))) {
+ abort();
+ }
+ return *this;
+ }
+
+ JSAPITestString& operator+=(const JSAPITestString& s) {
+ if (!chars.append(s.begin(), s.length())) {
+ abort();
+ }
+ return *this;
+ }
+};
+
+inline JSAPITestString operator+(const JSAPITestString& a, const char* b) {
+ JSAPITestString result = a;
+ result += b;
+ return result;
+}
+
+inline JSAPITestString operator+(const JSAPITestString& a,
+ const JSAPITestString& b) {
+ JSAPITestString result = a;
+ result += b;
+ return result;
+}
+
+class JSAPIRuntimeTest;
+
+class JSAPITest {
+ public:
+ bool knownFail;
+ JSAPITestString msgs;
+
+ JSAPITest() : knownFail(false) {}
+
+ virtual ~JSAPITest() {}
+
+ virtual const char* name() = 0;
+
+ virtual void maybeAppendException(JSAPITestString& message) {}
+
+ bool fail(const JSAPITestString& msg = JSAPITestString(),
+ const char* filename = "-", int lineno = 0) {
+ char location[256];
+ SprintfLiteral(location, "%s:%d:", filename, lineno);
+
+ JSAPITestString message(location);
+ message += msg;
+
+ maybeAppendException(message);
+
+ fprintf(stderr, "%.*s\n", int(message.length()), message.begin());
+
+ if (msgs.length() != 0) {
+ msgs += " | ";
+ }
+ msgs += message;
+ return false;
+ }
+
+ JSAPITestString messages() const { return msgs; }
+};
+
+class JSAPIRuntimeTest : public JSAPITest {
+ public:
+ static JSAPIRuntimeTest* list;
+ JSAPIRuntimeTest* next;
+
+ JSContext* cx;
+ JS::PersistentRootedObject global;
+
+ // Whether this test is willing to skip its init() and reuse a global (and
+ // JSContext etc.) from a previous test that also has reuseGlobal=true. It
+ // also means this test is willing to skip its uninit() if it is followed by
+ // another reuseGlobal test.
+ bool reuseGlobal;
+
+ JSAPIRuntimeTest() : JSAPITest(), cx(nullptr), reuseGlobal(false) {
+ next = list;
+ list = this;
+ }
+
+ virtual ~JSAPIRuntimeTest() {
+ MOZ_RELEASE_ASSERT(!cx);
+ MOZ_RELEASE_ASSERT(!global);
+ }
+
+ // Initialize this test, possibly with the cx from a previously run test.
+ bool init(JSContext* maybeReusedContext);
+
+ // If this test is ok with its cx and global being reused, release this
+ // test's cx to be reused by another test.
+ JSContext* maybeForgetContext();
+
+ static void MaybeFreeContext(JSContext* maybeCx);
+
+ // The real initialization happens in init(JSContext*), above, but this
+ // method may be overridden to perform additional initialization after the
+ // JSContext and global have been created.
+ virtual bool init() { return true; }
+ virtual void uninit();
+
+ virtual bool run(JS::HandleObject global) = 0;
+
+#define EXEC(s) \
+ do { \
+ if (!exec(s, __FILE__, __LINE__)) return false; \
+ } while (false)
+
+ bool exec(const char* utf8, const char* filename, int lineno);
+
+ // Like exec(), but doesn't call fail() if JS::Evaluate returns false.
+ bool execDontReport(const char* utf8, const char* filename, int lineno);
+
+#define EVAL(s, vp) \
+ do { \
+ if (!evaluate(s, __FILE__, __LINE__, vp)) return false; \
+ } while (false)
+
+ bool evaluate(const char* utf8, const char* filename, int lineno,
+ JS::MutableHandleValue vp);
+
+ JSAPITestString jsvalToSource(JS::HandleValue v) {
+ JS::Rooted<JSString*> str(cx, JS_ValueToSource(cx, v));
+ if (str) {
+ if (JS::UniqueChars bytes = JS_EncodeStringToUTF8(cx, str)) {
+ return JSAPITestString(bytes.get());
+ }
+ }
+ JS_ClearPendingException(cx);
+ return JSAPITestString("<<error converting value to string>>");
+ }
+
+ JSAPITestString toSource(char c) {
+ char buf[2] = {c, '\0'};
+ return JSAPITestString(buf);
+ }
+
+ JSAPITestString toSource(long v) {
+ char buf[40];
+ SprintfLiteral(buf, "%ld", v);
+ return JSAPITestString(buf);
+ }
+
+ JSAPITestString toSource(unsigned long v) {
+ char buf[40];
+ SprintfLiteral(buf, "%lu", v);
+ return JSAPITestString(buf);
+ }
+
+ JSAPITestString toSource(long long v) {
+ char buf[40];
+ SprintfLiteral(buf, "%lld", v);
+ return JSAPITestString(buf);
+ }
+
+ JSAPITestString toSource(unsigned long long v) {
+ char buf[40];
+ SprintfLiteral(buf, "%llu", v);
+ return JSAPITestString(buf);
+ }
+
+ JSAPITestString toSource(double d) {
+ char buf[40];
+ SprintfLiteral(buf, "%17lg", d);
+ return JSAPITestString(buf);
+ }
+
+ JSAPITestString toSource(unsigned int v) {
+ return toSource((unsigned long)v);
+ }
+
+ JSAPITestString toSource(int v) { return toSource((long)v); }
+
+ JSAPITestString toSource(bool v) {
+ return JSAPITestString(v ? "true" : "false");
+ }
+
+ JSAPITestString toSource(JS::RegExpFlags flags) {
+ JSAPITestString str;
+ if (flags.hasIndices()) {
+ str += "d";
+ }
+ if (flags.global()) {
+ str += "g";
+ }
+ if (flags.ignoreCase()) {
+ str += "i";
+ }
+ if (flags.multiline()) {
+ str += "m";
+ }
+ if (flags.dotAll()) {
+ str += "s";
+ }
+ if (flags.unicode()) {
+ str += "u";
+ }
+ if (flags.sticky()) {
+ str += "y";
+ }
+ return str;
+ }
+
+ JSAPITestString toSource(JSAtom* v) {
+ JS::RootedValue val(cx, JS::StringValue((JSString*)v));
+ return jsvalToSource(val);
+ }
+
+ // Note that in some still-supported GCC versions (we think anything before
+ // GCC 4.6), this template does not work when the second argument is
+ // nullptr. It infers type U = long int. Use CHECK_NULL instead.
+ template <typename T, typename U>
+ bool checkEqual(const T& actual, const U& expected, const char* actualExpr,
+ const char* expectedExpr, const char* filename, int lineno) {
+ static_assert(std::is_signed_v<T> == std::is_signed_v<U>,
+ "using CHECK_EQUAL with different-signed inputs triggers "
+ "compiler warnings");
+ static_assert(
+ std::is_unsigned_v<T> == std::is_unsigned_v<U>,
+ "using CHECK_EQUAL with different-signed inputs triggers compiler "
+ "warnings");
+ return (actual == expected) ||
+ fail(JSAPITestString("CHECK_EQUAL failed: expected (") +
+ expectedExpr + ") = " + toSource(expected) + ", got (" +
+ actualExpr + ") = " + toSource(actual),
+ filename, lineno);
+ }
+
+#define CHECK_EQUAL(actual, expected) \
+ do { \
+ if (!checkEqual(actual, expected, #actual, #expected, __FILE__, __LINE__)) \
+ return false; \
+ } while (false)
+
+ template <typename T>
+ bool checkNull(const T* actual, const char* actualExpr, const char* filename,
+ int lineno) {
+ return (actual == nullptr) ||
+ fail(JSAPITestString("CHECK_NULL failed: expected nullptr, got (") +
+ actualExpr + ") = " + toSource(actual),
+ filename, lineno);
+ }
+
+#define CHECK_NULL(actual) \
+ do { \
+ if (!checkNull(actual, #actual, __FILE__, __LINE__)) return false; \
+ } while (false)
+
+ bool checkSame(const JS::Value& actualArg, const JS::Value& expectedArg,
+ const char* actualExpr, const char* expectedExpr,
+ const char* filename, int lineno) {
+ bool same;
+ JS::RootedValue actual(cx, actualArg), expected(cx, expectedArg);
+ return (JS::SameValue(cx, actual, expected, &same) && same) ||
+ fail(JSAPITestString(
+ "CHECK_SAME failed: expected JS::SameValue(cx, ") +
+ actualExpr + ", " + expectedExpr +
+ "), got !JS::SameValue(cx, " + jsvalToSource(actual) +
+ ", " + jsvalToSource(expected) + ")",
+ filename, lineno);
+ }
+
+#define CHECK_SAME(actual, expected) \
+ do { \
+ if (!checkSame(actual, expected, #actual, #expected, __FILE__, __LINE__)) \
+ return false; \
+ } while (false)
+
+#define CHECK(expr) \
+ do { \
+ if (!(expr)) \
+ return fail(JSAPITestString("CHECK failed: " #expr), __FILE__, \
+ __LINE__); \
+ } while (false)
+
+ void maybeAppendException(JSAPITestString& message) override {
+ if (JS_IsExceptionPending(cx)) {
+ message += " -- ";
+
+ js::gc::AutoSuppressGC gcoff(cx);
+ JS::RootedValue v(cx);
+ JS_GetPendingException(cx, &v);
+ JS_ClearPendingException(cx);
+ JS::Rooted<JSString*> s(cx, JS::ToString(cx, v));
+ if (s) {
+ if (JS::UniqueChars bytes = JS_EncodeStringToLatin1(cx, s)) {
+ message += bytes.get();
+ }
+ }
+ }
+ }
+
+ static const JSClass* basicGlobalClass() {
+ static const JSClass c = {"global", JSCLASS_GLOBAL_FLAGS,
+ &JS::DefaultGlobalClassOps};
+ return &c;
+ }
+
+ protected:
+ static bool print(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+
+ JS::Rooted<JSString*> str(cx);
+ for (unsigned i = 0; i < args.length(); i++) {
+ str = JS::ToString(cx, args[i]);
+ if (!str) {
+ return false;
+ }
+ JS::UniqueChars bytes = JS_EncodeStringToUTF8(cx, str);
+ if (!bytes) {
+ return false;
+ }
+ printf("%s%s", i ? " " : "", bytes.get());
+ }
+
+ putchar('\n');
+ fflush(stdout);
+ args.rval().setUndefined();
+ return true;
+ }
+
+ bool definePrint();
+
+ virtual JSContext* createContext() {
+ JSContext* cx = JS_NewContext(8L * 1024 * 1024);
+ if (!cx) {
+ return nullptr;
+ }
+ JS::SetWarningReporter(cx, &reportWarning);
+ return cx;
+ }
+
+ static void reportWarning(JSContext* cx, JSErrorReport* report) {
+ MOZ_RELEASE_ASSERT(report->isWarning());
+
+ fprintf(stderr, "%s:%u:%s\n",
+ report->filename ? report->filename : "<no filename>",
+ (unsigned int)report->lineno, report->message().c_str());
+ }
+
+ virtual const JSClass* getGlobalClass() { return basicGlobalClass(); }
+
+ virtual JSObject* createGlobal(JSPrincipals* principals = nullptr);
+};
+
+class JSAPIFrontendTest : public JSAPITest {
+ public:
+ static JSAPIFrontendTest* list;
+ JSAPIFrontendTest* next;
+
+ JSAPIFrontendTest() : JSAPITest() {
+ next = list;
+ list = this;
+ }
+
+ virtual ~JSAPIFrontendTest() {}
+
+ virtual bool init() { return true; }
+ virtual void uninit() {}
+
+ virtual bool run() = 0;
+};
+
+#define BEGIN_TEST_WITH_ATTRIBUTES_AND_EXTRA(testname, attrs, extra) \
+ class cls_##testname : public JSAPIRuntimeTest { \
+ public: \
+ virtual const char* name() override { return #testname; } \
+ extra virtual bool run(JS::HandleObject global) override attrs
+
+#define BEGIN_TEST_WITH_ATTRIBUTES(testname, attrs) \
+ BEGIN_TEST_WITH_ATTRIBUTES_AND_EXTRA(testname, attrs, )
+
+#define BEGIN_TEST(testname) BEGIN_TEST_WITH_ATTRIBUTES(testname, )
+
+#define BEGIN_FRONTEND_TEST_WITH_ATTRIBUTES_AND_EXTRA(testname, attrs, extra) \
+ class cls_##testname : public JSAPIFrontendTest { \
+ public: \
+ virtual const char* name() override { return #testname; } \
+ extra virtual bool run() override attrs
+
+#define BEGIN_FRONTEND_TEST_WITH_ATTRIBUTES(testname, attrs) \
+ BEGIN_FRONTEND_TEST_WITH_ATTRIBUTES_AND_EXTRA(testname, attrs, )
+
+#define BEGIN_FRONTEND_TEST(testname) \
+ BEGIN_FRONTEND_TEST_WITH_ATTRIBUTES(testname, )
+
+#define BEGIN_REUSABLE_TEST(testname) \
+ BEGIN_TEST_WITH_ATTRIBUTES_AND_EXTRA( \
+ testname, , cls_##testname() \
+ : JSAPIRuntimeTest() { reuseGlobal = true; })
+
+#define END_TEST(testname) \
+ } \
+ ; \
+ static cls_##testname cls_##testname##_instance;
+
+/*
+ * A "fixture" is a subclass of JSAPIRuntimeTest that holds common definitions
+ * for a set of tests. Each test that wants to use the fixture should use
+ * BEGIN_FIXTURE_TEST and END_FIXTURE_TEST, just as one would use BEGIN_TEST and
+ * END_TEST, but include the fixture class as the first argument. The fixture
+ * class's declarations are then in scope for the test bodies.
+ */
+
+#define BEGIN_FIXTURE_TEST(fixture, testname) \
+ class cls_##testname : public fixture { \
+ public: \
+ virtual const char* name() override { return #testname; } \
+ virtual bool run(JS::HandleObject global) override
+
+#define END_FIXTURE_TEST(fixture, testname) \
+ } \
+ ; \
+ static cls_##testname cls_##testname##_instance;
+
+/*
+ * A class for creating and managing one temporary file.
+ *
+ * We could use the ISO C temporary file functions here, but those try to
+ * create files in the root directory on Windows, which fails for users
+ * without Administrator privileges.
+ */
+class TempFile {
+ const char* name;
+ FILE* stream;
+
+ public:
+ TempFile() : name(), stream() {}
+ ~TempFile() {
+ if (stream) {
+ close();
+ }
+ if (name) {
+ remove();
+ }
+ }
+
+ /*
+ * Return a stream for a temporary file named |fileName|. Infallible.
+ * Use only once per TempFile instance. If the file is not explicitly
+ * closed and deleted via the member functions below, this object's
+ * destructor will clean them up.
+ */
+ FILE* open(const char* fileName) {
+ stream = fopen(fileName, "wb+");
+ if (!stream) {
+ fprintf(stderr, "error opening temporary file '%s': %s\n", fileName,
+ strerror(errno));
+ exit(1);
+ }
+ name = fileName;
+ return stream;
+ }
+
+ /* Close the temporary file's stream. */
+ void close() {
+ if (fclose(stream) == EOF) {
+ fprintf(stderr, "error closing temporary file '%s': %s\n", name,
+ strerror(errno));
+ exit(1);
+ }
+ stream = nullptr;
+ }
+
+ /* Delete the temporary file. */
+ void remove() {
+ if (::remove(name) != 0) {
+ fprintf(stderr, "error deleting temporary file '%s': %s\n", name,
+ strerror(errno));
+ exit(1);
+ }
+ name = nullptr;
+ }
+};
+
+// Just a wrapper around JSPrincipals that allows static construction.
+class TestJSPrincipals : public JSPrincipals {
+ public:
+ explicit TestJSPrincipals(int rc = 0) : JSPrincipals() { refcount = rc; }
+
+ bool write(JSContext* cx, JSStructuredCloneWriter* writer) override {
+ MOZ_ASSERT(false, "not implemented");
+ return false;
+ }
+
+ bool isSystemOrAddonPrincipal() override { return true; }
+};
+
+// A class that simulates externally memory-managed data, for testing with
+// array buffers.
+class ExternalData {
+ char* contents_;
+ size_t len_;
+
+ public:
+ explicit ExternalData(const char* str)
+ : contents_(strdup(str)), len_(strlen(str) + 1) {}
+
+ size_t len() const { return len_; }
+ void* contents() const { return contents_; }
+ char* asString() const { return contents_; }
+ bool wasFreed() const { return !contents_; }
+
+ void free() {
+ MOZ_ASSERT(!wasFreed());
+ ::free(contents_);
+ contents_ = nullptr;
+ }
+
+ static void freeCallback(void* contents, void* userData) {
+ auto self = static_cast<ExternalData*>(userData);
+ MOZ_ASSERT(self->contents() == contents);
+ self->free();
+ }
+};
+
+class AutoGCParameter {
+ JSContext* cx_;
+ JSGCParamKey key_;
+ uint32_t value_;
+
+ public:
+ explicit AutoGCParameter(JSContext* cx, JSGCParamKey key, uint32_t value)
+ : cx_(cx), key_(key), value_() {
+ value_ = JS_GetGCParameter(cx, key);
+ JS_SetGCParameter(cx, key, value);
+ }
+ ~AutoGCParameter() { JS_SetGCParameter(cx_, key_, value_); }
+};
+
+#ifdef JS_GC_ZEAL
+/*
+ * Temporarily disable the GC zeal setting. This is only useful in tests that
+ * need very explicit GC behavior and should not be used elsewhere.
+ */
+class AutoLeaveZeal {
+ JSContext* cx_;
+ uint32_t zealBits_;
+ uint32_t frequency_;
+
+ public:
+ explicit AutoLeaveZeal(JSContext* cx) : cx_(cx), zealBits_(0), frequency_(0) {
+ uint32_t dummy;
+ JS_GetGCZealBits(cx_, &zealBits_, &frequency_, &dummy);
+ JS_SetGCZeal(cx_, 0, 0);
+ JS::PrepareForFullGC(cx_);
+ JS::NonIncrementalGC(cx_, JS::GCOptions::Normal, JS::GCReason::DEBUG_GC);
+ }
+ ~AutoLeaveZeal() {
+ JS_SetGCZeal(cx_, 0, 0);
+ for (size_t i = 0; i < sizeof(zealBits_) * 8; i++) {
+ if (zealBits_ & (1 << i)) {
+ JS_SetGCZeal(cx_, i, frequency_);
+ }
+ }
+
+# ifdef DEBUG
+ uint32_t zealBitsAfter, frequencyAfter, dummy;
+ JS_GetGCZealBits(cx_, &zealBitsAfter, &frequencyAfter, &dummy);
+ MOZ_ASSERT(zealBitsAfter == zealBits_);
+ MOZ_ASSERT(frequencyAfter == frequency_);
+# endif
+ }
+};
+
+#else
+class AutoLeaveZeal {
+ public:
+ explicit AutoLeaveZeal(JSContext* cx) {}
+};
+#endif
+
+#endif /* jsapi_tests_tests_h */
diff --git a/js/src/jsapi-tests/testsJit.cpp b/js/src/jsapi-tests/testsJit.cpp
new file mode 100644
index 0000000000..2ad96c8e5d
--- /dev/null
+++ b/js/src/jsapi-tests/testsJit.cpp
@@ -0,0 +1,80 @@
+/* -*- 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 "jsapi-tests/testsJit.h"
+
+#include "jit/JitCommon.h"
+#include "jit/Linker.h"
+
+#include "jit/MacroAssembler-inl.h"
+
+// On entry to the JIT code, save every register.
+void PrepareJit(js::jit::MacroAssembler& masm) {
+ using namespace js::jit;
+#if defined(JS_CODEGEN_ARM64)
+ masm.Mov(PseudoStackPointer64, sp);
+ masm.SetStackPointer64(PseudoStackPointer64);
+#endif
+ AllocatableRegisterSet regs(RegisterSet::All());
+ LiveRegisterSet save(regs.asLiveSet());
+#if defined(JS_CODEGEN_ARM)
+ save.add(js::jit::d15);
+#endif
+#if defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) || \
+ defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_RISCV64)
+ save.add(js::jit::ra);
+#elif defined(JS_USE_LINK_REGISTER)
+ save.add(js::jit::lr);
+#endif
+ masm.PushRegsInMask(save);
+}
+
+// Generate the exit path of the JIT code, which restores every register. Then,
+// make it executable and run it.
+bool ExecuteJit(JSContext* cx, js::jit::MacroAssembler& masm) {
+ using namespace js::jit;
+ AllocatableRegisterSet regs(RegisterSet::All());
+ LiveRegisterSet save(regs.asLiveSet());
+#if defined(JS_CODEGEN_ARM)
+ save.add(js::jit::d15);
+#endif
+#if defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) || \
+ defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_RISCV64)
+ save.add(js::jit::ra);
+#elif defined(JS_USE_LINK_REGISTER)
+ save.add(js::jit::lr);
+#endif
+ masm.PopRegsInMask(save);
+#if defined(JS_CODEGEN_ARM64)
+ // Return using the value popped into x30.
+ masm.abiret();
+
+ // Reset stack pointer.
+ masm.SetStackPointer64(PseudoStackPointer64);
+#else
+ // Exit the JIT-ed code using the ABI return style.
+ masm.abiret();
+#endif
+
+ if (masm.oom()) {
+ return false;
+ }
+
+ Linker linker(masm);
+ JitCode* code = linker.newCode(cx, CodeKind::Other);
+ if (!code) {
+ return false;
+ }
+ if (!ExecutableAllocator::makeExecutableAndFlushICache(code->raw(),
+ code->bufferSize())) {
+ return false;
+ }
+
+ JS::AutoSuppressGCAnalysis suppress;
+ EnterTest test = code->as<EnterTest>();
+ CALL_GENERATED_0(test);
+ return true;
+}
diff --git a/js/src/jsapi-tests/testsJit.h b/js/src/jsapi-tests/testsJit.h
new file mode 100644
index 0000000000..0984e71c96
--- /dev/null
+++ b/js/src/jsapi-tests/testsJit.h
@@ -0,0 +1,21 @@
+/* -*- 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 jsapi_tests_testsJit_h
+#define jsapi_tests_testsJit_h
+
+#include "jit/MacroAssembler.h"
+
+typedef void (*EnterTest)();
+
+// On entry to the JIT code, save every register.
+void PrepareJit(js::jit::MacroAssembler& masm);
+
+// Generate the exit path of the JIT code, which restores every register. Then,
+// make it executable and run it.
+bool ExecuteJit(JSContext* cx, js::jit::MacroAssembler& masm);
+
+#endif /* !jsapi_tests_testsJit_h */
diff --git a/js/src/jsapi-tests/valueABI.c b/js/src/jsapi-tests/valueABI.c
new file mode 100644
index 0000000000..4dc10e1d48
--- /dev/null
+++ b/js/src/jsapi-tests/valueABI.c
@@ -0,0 +1,25 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sw=2 et tw=80 ft=c:
+ * 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 "jsapi.h"
+
+/* See testValueABI.cpp */
+
+bool C_ValueToObject(JSContext* cx, jsval v, JSObject** obj) {
+ return JS_ValueToObject(cx, v, obj);
+}
+
+jsval C_GetEmptyStringValue(JSContext* cx) {
+ return JS_GetEmptyStringValue(cx);
+}
+
+size_t C_jsvalAlignmentTest() {
+ typedef struct {
+ char c;
+ jsval v;
+ } AlignTest;
+ return sizeof(AlignTest);
+}