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/README174
-rw-r--r--js/src/jsapi-tests/jsapi-tests-gdb.py.in8
-rw-r--r--js/src/jsapi-tests/moz.build157
-rw-r--r--js/src/jsapi-tests/selfTest.cpp20
-rw-r--r--js/src/jsapi-tests/testAddPropertyPropcache.cpp72
-rw-r--r--js/src/jsapi-tests/testArgumentsObject.cpp113
-rw-r--r--js/src/jsapi-tests/testArrayBuffer.cpp227
-rw-r--r--js/src/jsapi-tests/testArrayBufferView.cpp179
-rw-r--r--js/src/jsapi-tests/testAssemblerBuffer.cpp579
-rw-r--r--js/src/jsapi-tests/testBoundFunction.cpp33
-rw-r--r--js/src/jsapi-tests/testBug604087.cpp99
-rw-r--r--js/src/jsapi-tests/testCallArgs.cpp86
-rw-r--r--js/src/jsapi-tests/testCallNonGenericMethodOnProxy.cpp91
-rw-r--r--js/src/jsapi-tests/testChromeBuffer.cpp195
-rw-r--r--js/src/jsapi-tests/testClassGetter.cpp74
-rw-r--r--js/src/jsapi-tests/testCloneScript.cpp161
-rw-r--r--js/src/jsapi-tests/testDateToLocaleString.cpp54
-rw-r--r--js/src/jsapi-tests/testDebugger.cpp65
-rw-r--r--js/src/jsapi-tests/testDeepFreeze.cpp60
-rw-r--r--js/src/jsapi-tests/testDefineGetterSetterNonEnumerable.cpp58
-rw-r--r--js/src/jsapi-tests/testDefineProperty.cpp23
-rw-r--r--js/src/jsapi-tests/testDefinePropertyIgnoredAttributes.cpp100
-rw-r--r--js/src/jsapi-tests/testDeflateStringToUTF8Buffer.cpp286
-rw-r--r--js/src/jsapi-tests/testDifferentNewTargetInvokeConstructor.cpp37
-rw-r--r--js/src/jsapi-tests/testEnclosingFunction.cpp68
-rw-r--r--js/src/jsapi-tests/testErrorCopying.cpp33
-rw-r--r--js/src/jsapi-tests/testException.cpp26
-rw-r--r--js/src/jsapi-tests/testExternalArrayBuffer.cpp48
-rw-r--r--js/src/jsapi-tests/testExternalStrings.cpp61
-rw-r--r--js/src/jsapi-tests/testFindSCCs.cpp285
-rw-r--r--js/src/jsapi-tests/testForOfIterator.cpp52
-rw-r--r--js/src/jsapi-tests/testForceLexicalInitialization.cpp39
-rw-r--r--js/src/jsapi-tests/testForwardSetProperty.cpp90
-rw-r--r--js/src/jsapi-tests/testFreshGlobalEvalRedefinition.cpp53
-rw-r--r--js/src/jsapi-tests/testFunctionProperties.cpp26
-rw-r--r--js/src/jsapi-tests/testGCAllocator.cpp383
-rw-r--r--js/src/jsapi-tests/testGCCellPtr.cpp61
-rw-r--r--js/src/jsapi-tests/testGCChunkPool.cpp71
-rw-r--r--js/src/jsapi-tests/testGCExactRooting.cpp410
-rw-r--r--js/src/jsapi-tests/testGCFinalizeCallback.cpp210
-rw-r--r--js/src/jsapi-tests/testGCHeapPostBarriers.cpp153
-rw-r--r--js/src/jsapi-tests/testGCHooks.cpp36
-rw-r--r--js/src/jsapi-tests/testGCMarking.cpp423
-rw-r--r--js/src/jsapi-tests/testGCOutOfMemory.cpp69
-rw-r--r--js/src/jsapi-tests/testGCStoreBufferRemoval.cpp121
-rw-r--r--js/src/jsapi-tests/testGCUniqueId.cpp123
-rw-r--r--js/src/jsapi-tests/testGCWeakCache.cpp94
-rw-r--r--js/src/jsapi-tests/testGCWeakRef.cpp65
-rw-r--r--js/src/jsapi-tests/testGetPropertyDescriptor.cpp50
-rw-r--r--js/src/jsapi-tests/testHashTable.cpp390
-rw-r--r--js/src/jsapi-tests/testIndexToString.cpp117
-rw-r--r--js/src/jsapi-tests/testIntString.cpp42
-rw-r--r--js/src/jsapi-tests/testIntTypesABI.cpp88
-rw-r--r--js/src/jsapi-tests/testIntern.cpp48
-rw-r--r--js/src/jsapi-tests/testIntlAvailableLocales.cpp58
-rw-r--r--js/src/jsapi-tests/testIsInsideNursery.cpp30
-rw-r--r--js/src/jsapi-tests/testIteratorObject.cpp31
-rw-r--r--js/src/jsapi-tests/testJSEvaluateScript.cpp35
-rw-r--r--js/src/jsapi-tests/testJitDCEinGVN.cpp143
-rw-r--r--js/src/jsapi-tests/testJitFoldsTo.cpp260
-rw-r--r--js/src/jsapi-tests/testJitGVN.cpp286
-rw-r--r--js/src/jsapi-tests/testJitMacroAssembler.cpp476
-rw-r--r--js/src/jsapi-tests/testJitMinimalFunc.h121
-rw-r--r--js/src/jsapi-tests/testJitMoveEmitterCycles-mips32.cpp414
-rw-r--r--js/src/jsapi-tests/testJitMoveEmitterCycles.cpp532
-rw-r--r--js/src/jsapi-tests/testJitRValueAlloc.cpp229
-rw-r--r--js/src/jsapi-tests/testJitRangeAnalysis.cpp338
-rw-r--r--js/src/jsapi-tests/testJitRegisterSet.cpp138
-rw-r--r--js/src/jsapi-tests/testLookup.cpp98
-rw-r--r--js/src/jsapi-tests/testLooselyEqual.cpp195
-rw-r--r--js/src/jsapi-tests/testMappedArrayBuffer.cpp193
-rw-r--r--js/src/jsapi-tests/testMutedErrors.cpp96
-rw-r--r--js/src/jsapi-tests/testNewObject.cpp121
-rw-r--r--js/src/jsapi-tests/testNewTargetInvokeConstructor.cpp25
-rw-r--r--js/src/jsapi-tests/testNullRoot.cpp25
-rw-r--r--js/src/jsapi-tests/testOOM.cpp71
-rw-r--r--js/src/jsapi-tests/testObjectEmulatingUndefined.cpp103
-rw-r--r--js/src/jsapi-tests/testParseJSON.cpp358
-rw-r--r--js/src/jsapi-tests/testPersistentRooted.cpp224
-rw-r--r--js/src/jsapi-tests/testPreserveJitCode.cpp97
-rw-r--r--js/src/jsapi-tests/testPrintf.cpp71
-rw-r--r--js/src/jsapi-tests/testPrivateGCThingValue.cpp57
-rw-r--r--js/src/jsapi-tests/testProfileStrings.cpp274
-rw-r--r--js/src/jsapi-tests/testPromise.cpp91
-rw-r--r--js/src/jsapi-tests/testPropCache.cpp40
-rw-r--r--js/src/jsapi-tests/testRegExp.cpp60
-rw-r--r--js/src/jsapi-tests/testResolveRecursion.cpp188
-rw-r--r--js/src/jsapi-tests/testSameValue.cpp27
-rw-r--r--js/src/jsapi-tests/testSavedStacks.cpp302
-rw-r--r--js/src/jsapi-tests/testScriptInfo.cpp50
-rw-r--r--js/src/jsapi-tests/testScriptObject.cpp200
-rw-r--r--js/src/jsapi-tests/testSetProperty.cpp84
-rw-r--r--js/src/jsapi-tests/testSetPropertyIgnoringNamedGetter.cpp93
-rw-r--r--js/src/jsapi-tests/testSharedImmutableStringsCache.cpp80
-rw-r--r--js/src/jsapi-tests/testSlowScript.cpp86
-rw-r--r--js/src/jsapi-tests/testSourcePolicy.cpp42
-rw-r--r--js/src/jsapi-tests/testStringBuffer.cpp29
-rw-r--r--js/src/jsapi-tests/testStructuredClone.cpp232
-rw-r--r--js/src/jsapi-tests/testSymbol.cpp81
-rw-r--r--js/src/jsapi-tests/testThreadingConditionVariable.cpp221
-rw-r--r--js/src/jsapi-tests/testThreadingExclusiveData.cpp89
-rw-r--r--js/src/jsapi-tests/testThreadingMutex.cpp50
-rw-r--r--js/src/jsapi-tests/testThreadingThread.cpp103
-rw-r--r--js/src/jsapi-tests/testToIntWidth.cpp69
-rw-r--r--js/src/jsapi-tests/testTypedArrays.cpp236
-rw-r--r--js/src/jsapi-tests/testUTF8.cpp51
-rw-r--r--js/src/jsapi-tests/testUbiNode.cpp1009
-rw-r--r--js/src/jsapi-tests/testUncaughtSymbol.cpp53
-rw-r--r--js/src/jsapi-tests/testValueABI.cpp55
-rw-r--r--js/src/jsapi-tests/testWasmLEB128.cpp166
-rw-r--r--js/src/jsapi-tests/testWeakMap.cpp258
-rw-r--r--js/src/jsapi-tests/testXDR.cpp168
-rw-r--r--js/src/jsapi-tests/tests.cpp149
-rw-r--r--js/src/jsapi-tests/tests.h456
-rw-r--r--js/src/jsapi-tests/valueABI.c28
116 files changed, 16640 insertions, 0 deletions
diff --git a/js/src/jsapi-tests/Makefile.in b/js/src/jsapi-tests/Makefile.in
new file mode 100644
index 000000000..2aac79839
--- /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 000000000..06b3fb2b4
--- /dev/null
+++ b/js/src/jsapi-tests/README
@@ -0,0 +1,174 @@
+# 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.
+
+To run the tests:
+
+ cd $OBJDIR/dist/bin
+ ./jsapi-tests
+
+To run the tests in a debugger:
+
+ cd $OBJDIR/dist/bin
+ gdb ./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 "tests.h"
+
+ BEGIN_TEST(testIntString_bug515273)
+ {
+ RootedValue v(cx);
+
+ EVAL("'42';", &v);
+ JSString *str = v.toString();
+ CHECK(JS_StringHasBeenInterned(cx, str));
+ CHECK(JS_FlatStringEqualsAscii(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, 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/jsapi-tests-gdb.py.in b/js/src/jsapi-tests/jsapi-tests-gdb.py.in
new file mode 100644
index 000000000..c5a12f38e
--- /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 000000000..ab42ff384
--- /dev/null
+++ b/js/src/jsapi-tests/moz.build
@@ -0,0 +1,157 @@
+# -*- 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)
+
+UNIFIED_SOURCES += [
+ 'selfTest.cpp',
+ 'testAddPropertyPropcache.cpp',
+ 'testArgumentsObject.cpp',
+ 'testArrayBuffer.cpp',
+ 'testArrayBufferView.cpp',
+ 'testBoundFunction.cpp',
+ 'testBug604087.cpp',
+ 'testCallArgs.cpp',
+ 'testCallNonGenericMethodOnProxy.cpp',
+ 'testChromeBuffer.cpp',
+ 'testClassGetter.cpp',
+ 'testCloneScript.cpp',
+ 'testDateToLocaleString.cpp',
+ 'testDebugger.cpp',
+ 'testDeepFreeze.cpp',
+ 'testDefineGetterSetterNonEnumerable.cpp',
+ 'testDefineProperty.cpp',
+ 'testDefinePropertyIgnoredAttributes.cpp',
+ 'testDeflateStringToUTF8Buffer.cpp',
+ 'testDifferentNewTargetInvokeConstructor.cpp',
+ 'testEnclosingFunction.cpp',
+ 'testErrorCopying.cpp',
+ 'testException.cpp',
+ 'testExternalArrayBuffer.cpp',
+ 'testExternalStrings.cpp',
+ 'testFindSCCs.cpp',
+ 'testForceLexicalInitialization.cpp',
+ 'testForOfIterator.cpp',
+ 'testForwardSetProperty.cpp',
+ 'testFreshGlobalEvalRedefinition.cpp',
+ 'testFunctionProperties.cpp',
+ 'testGCAllocator.cpp',
+ 'testGCCellPtr.cpp',
+ 'testGCChunkPool.cpp',
+ 'testGCExactRooting.cpp',
+ 'testGCFinalizeCallback.cpp',
+ 'testGCHeapPostBarriers.cpp',
+ 'testGCHooks.cpp',
+ 'testGCMarking.cpp',
+ 'testGCOutOfMemory.cpp',
+ 'testGCStoreBufferRemoval.cpp',
+ 'testGCUniqueId.cpp',
+ 'testGCWeakCache.cpp',
+ 'testGCWeakRef.cpp',
+ 'testGetPropertyDescriptor.cpp',
+ 'testHashTable.cpp',
+ 'testIndexToString.cpp',
+ 'testIntern.cpp',
+ 'testIntlAvailableLocales.cpp',
+ 'testIntString.cpp',
+ 'testIntTypesABI.cpp',
+ 'testIsInsideNursery.cpp',
+ 'testIteratorObject.cpp',
+ 'testJSEvaluateScript.cpp',
+ 'testLookup.cpp',
+ 'testLooselyEqual.cpp',
+ 'testMappedArrayBuffer.cpp',
+ 'testMutedErrors.cpp',
+ 'testNewObject.cpp',
+ 'testNewTargetInvokeConstructor.cpp',
+ 'testNullRoot.cpp',
+ 'testObjectEmulatingUndefined.cpp',
+ 'testOOM.cpp',
+ 'testParseJSON.cpp',
+ 'testPersistentRooted.cpp',
+ 'testPreserveJitCode.cpp',
+ 'testPrintf.cpp',
+ 'testPrivateGCThingValue.cpp',
+ 'testProfileStrings.cpp',
+ 'testPropCache.cpp',
+ 'testRegExp.cpp',
+ 'testResolveRecursion.cpp',
+ 'tests.cpp',
+ 'testSameValue.cpp',
+ 'testSavedStacks.cpp',
+ 'testScriptInfo.cpp',
+ 'testScriptObject.cpp',
+ 'testSetProperty.cpp',
+ 'testSetPropertyIgnoringNamedGetter.cpp',
+ 'testSharedImmutableStringsCache.cpp',
+ 'testSourcePolicy.cpp',
+ 'testStringBuffer.cpp',
+ 'testStructuredClone.cpp',
+ 'testSymbol.cpp',
+ 'testThreadingConditionVariable.cpp',
+ 'testThreadingExclusiveData.cpp',
+ 'testThreadingMutex.cpp',
+ 'testThreadingThread.cpp',
+ 'testToIntWidth.cpp',
+ 'testTypedArrays.cpp',
+ 'testUbiNode.cpp',
+ 'testUncaughtSymbol.cpp',
+ 'testUTF8.cpp',
+ 'testWasmLEB128.cpp',
+ 'testWeakMap.cpp',
+ 'testXDR.cpp',
+]
+
+SOURCES += [
+ # There are clashing definitions of js::jit::AssemblerBuffer.
+ 'testAssemblerBuffer.cpp',
+]
+
+if CONFIG['ENABLE_ION']:
+ UNIFIED_SOURCES += [
+ 'testJitDCEinGVN.cpp',
+ 'testJitFoldsTo.cpp',
+ 'testJitGVN.cpp',
+ 'testJitMacroAssembler.cpp',
+ 'testJitMoveEmitterCycles-mips32.cpp',
+ 'testJitMoveEmitterCycles.cpp',
+ 'testJitRangeAnalysis.cpp',
+ 'testJitRegisterSet.cpp',
+ 'testJitRValueAlloc.cpp',
+ ]
+
+if CONFIG['SPIDERMONKEY_PROMISE']:
+ UNIFIED_SOURCES += [
+ 'testPromise.cpp',
+ ]
+
+DEFINES['EXPORT_JS_API'] = True
+
+LOCAL_INCLUDES += [
+ '!..',
+ '..',
+]
+
+if CONFIG['ENABLE_INTL_API'] and CONFIG['MOZ_ICU_DATA_ARCHIVE']:
+ # The ICU libraries linked into libmozjs will not include the ICU data,
+ # so link it directly.
+ USE_LIBS += ['icudata']
+
+USE_LIBS += [
+ 'static:js',
+]
+
+OS_LIBS += CONFIG['MOZ_ZLIB_LIBS']
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-shadow', '-Werror=format']
+
+# This is intended as a temporary workaround to enable VS2015.
+if CONFIG['_MSC_VER']:
+ CXXFLAGS += ['-wd4312']
+
+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 000000000..648167290
--- /dev/null
+++ b/js/src/jsapi-tests/selfTest.cpp
@@ -0,0 +1,20 @@
+/* -*- 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 "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 000000000..b9e109f9c
--- /dev/null
+++ b/js/src/jsapi-tests/testAddPropertyPropcache.cpp
@@ -0,0 +1,72 @@
+/* -*- 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 "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
+};
+
+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_NewPlainObject(cx));
+ CHECK(obj);
+ JS::RootedValue proto(cx, JS::ObjectValue(*obj));
+ JS_InitClass(cx, global, obj, &AddPropertyClass, nullptr, 0, nullptr, nullptr, nullptr,
+ nullptr);
+
+ obj = JS_NewArrayObject(cx, 0);
+ CHECK(obj);
+ JS::RootedValue arr(cx, JS::ObjectValue(*obj));
+
+ CHECK(JS_DefineProperty(cx, global, "arr", arr,
+ JSPROP_ENUMERATE,
+ JS_STUBGETTER, JS_STUBSETTER));
+
+ 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,
+ JS_STUBGETTER, JS_STUBSETTER));
+ }
+
+ // 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 000000000..af7b8ce3a
--- /dev/null
+++ b/js/src/jsapi-tests/testArgumentsObject.cpp
@@ -0,0 +1,113 @@
+/* -*- 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 "jsapi-tests/tests.h"
+
+#include "jsobjinlines.h"
+
+#include "vm/ArgumentsObject-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::AutoValueArray<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::AutoValueArray<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 000000000..ed00f0975
--- /dev/null
+++ b/js/src/jsapi-tests/testArrayBuffer.cpp
@@ -0,0 +1,227 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ */
+
+#include "jsfriendapi.h"
+
+#include "jsapi-tests/tests.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_externalize)
+{
+ if (!testWithSize(cx, 2)) // ArrayBuffer data stored inline in the object.
+ return false;
+ if (!testWithSize(cx, 2000)) // ArrayBuffer data stored out-of-line in a separate heap allocation.
+ return false;
+
+ return true;
+}
+
+bool testWithSize(JSContext* cx, size_t n)
+{
+ JS::RootedObject buffer(cx, JS_NewArrayBuffer(cx, n));
+ CHECK(buffer != nullptr);
+
+ JS::RootedObject view(cx, JS_NewUint8ArrayWithBuffer(cx, buffer, 0, -1));
+ CHECK(view != nullptr);
+
+ void* contents = JS_ExternalizeArrayBufferContents(cx, buffer);
+ CHECK(contents != nullptr);
+ uint32_t actualLength;
+ CHECK(hasExpectedLength(cx, view, &actualLength));
+ CHECK(actualLength == n);
+ CHECK(!JS_IsDetachedArrayBufferObject(buffer));
+ CHECK(JS_GetArrayBufferByteLength(buffer) == uint32_t(n));
+
+ uint8_t* uint8Contents = static_cast<uint8_t*>(contents);
+ CHECK(*uint8Contents == 0);
+ uint8_t randomByte(rand() % UINT8_MAX);
+ *uint8Contents = randomByte;
+
+ JS::RootedValue v(cx);
+ CHECK(JS_GetElement(cx, view, 0, &v));
+ CHECK(v.toInt32() == randomByte);
+
+ view = nullptr;
+ GC(cx);
+
+ CHECK(JS_DetachArrayBuffer(cx, buffer));
+ GC(cx);
+ CHECK(*uint8Contents == randomByte);
+ JS_free(cx, contents);
+ 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
+}
+
+static bool
+hasExpectedLength(JSContext* cx, JS::HandleObject obj, uint32_t* len)
+{
+ JS::RootedValue v(cx);
+ if (!JS_GetProperty(cx, obj, "byteLength", &v))
+ return false;
+ *len = v.toInt32();
+ return true;
+}
+
+END_TEST(testArrayBuffer_externalize)
diff --git a/js/src/jsapi-tests/testArrayBufferView.cpp b/js/src/jsapi-tests/testArrayBufferView.cpp
new file mode 100644
index 000000000..e0b4f3af6
--- /dev/null
+++ b/js/src/jsapi-tests/testArrayBufferView.cpp
@@ -0,0 +1,179 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ */
+
+#include "jscompartment.h"
+#include "jsfriendapi.h"
+
+#include "jsapi-tests/tests.h"
+
+#include "jscompartmentinlines.h"
+
+using namespace js;
+
+BEGIN_TEST(testArrayBufferView_type)
+{
+ CHECK((TestViewType<uint8_t,
+ Create<JS_NewUint8Array, 7>,
+ JS_GetObjectAsUint8Array,
+ js::Scalar::Uint8,
+ 7, 7>(cx)));
+
+ CHECK((TestViewType<int8_t,
+ Create<JS_NewInt8Array, 33>,
+ JS_GetObjectAsInt8Array,
+ js::Scalar::Int8,
+ 33, 33>(cx)));
+
+ CHECK((TestViewType<uint8_t,
+ Create<JS_NewUint8ClampedArray, 7>,
+ JS_GetObjectAsUint8ClampedArray,
+ js::Scalar::Uint8Clamped,
+ 7, 7>(cx)));
+
+ CHECK((TestViewType<uint16_t,
+ Create<JS_NewUint16Array, 3>,
+ JS_GetObjectAsUint16Array,
+ js::Scalar::Uint16,
+ 3, 6>(cx)));
+
+ CHECK((TestViewType<int16_t,
+ Create<JS_NewInt16Array, 17>,
+ JS_GetObjectAsInt16Array,
+ js::Scalar::Int16,
+ 17, 34>(cx)));
+
+ CHECK((TestViewType<uint32_t,
+ Create<JS_NewUint32Array, 15>,
+ JS_GetObjectAsUint32Array,
+ js::Scalar::Uint32,
+ 15, 60>(cx)));
+
+ CHECK((TestViewType<int32_t,
+ Create<JS_NewInt32Array, 8>,
+ JS_GetObjectAsInt32Array,
+ js::Scalar::Int32,
+ 8, 32>(cx)));
+
+ CHECK((TestViewType<float,
+ Create<JS_NewFloat32Array, 7>,
+ JS_GetObjectAsFloat32Array,
+ js::Scalar::Float32,
+ 7, 28>(cx)));
+
+ CHECK((TestViewType<double,
+ Create<JS_NewFloat64Array, 9>,
+ JS_GetObjectAsFloat64Array,
+ js::Scalar::Float64,
+ 9, 72>(cx)));
+
+ CHECK((TestViewType<uint8_t,
+ CreateDataView,
+ JS_GetObjectAsArrayBufferView,
+ js::Scalar::MaxTypedArrayViewType,
+ 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;
+}
+
+static JSObject*
+CreateDataView(JSContext* cx)
+{
+ JS::Rooted<JSObject*> buffer(cx, JS_NewArrayBuffer(cx, 8));
+ if (!buffer)
+ return nullptr;
+ return JS_NewDataView(cx, buffer, 0, 8);
+}
+
+template<JSObject * CreateTypedArray(JSContext* cx, uint32_t length),
+ size_t Length>
+static JSObject*
+Create(JSContext* cx)
+{
+ return CreateTypedArray(cx, Length);
+}
+
+template<typename T,
+ JSObject * CreateViewType(JSContext* cx),
+ JSObject * GetObjectAs(JSObject* obj, uint32_t* length, bool* isSharedMemory, T** data),
+ js::Scalar::Type ExpectedType,
+ uint32_t ExpectedLength,
+ uint32_t ExpectedByteLength>
+bool TestViewType(JSContext* cx)
+{
+ JS::Rooted<JSObject*> obj(cx, CreateViewType(cx));
+ CHECK(obj);
+
+ CHECK(JS_IsArrayBufferViewObject(obj));
+
+ CHECK(JS_GetArrayBufferViewType(obj) == ExpectedType);
+
+ CHECK(JS_GetArrayBufferViewByteLength(obj) == ExpectedByteLength);
+
+ {
+ JS::AutoCheckCannotGC nogc;
+ bool shared1;
+ T* data1 = static_cast<T*>(JS_GetArrayBufferViewData(obj, &shared1, nogc));
+
+ T* data2;
+ bool shared2;
+ uint32_t len;
+ CHECK(obj == GetObjectAs(obj, &len, &shared2, &data2));
+ CHECK(data1 == data2);
+ CHECK(shared1 == shared2);
+ CHECK(len == ExpectedLength);
+ }
+
+ JS::CompartmentOptions options;
+ JS::RootedObject otherGlobal(cx, JS_NewGlobalObject(cx, basicGlobalClass(), nullptr,
+ JS::DontFireOnNewGlobalHook, options));
+ CHECK(otherGlobal);
+
+ JS::Rooted<JSObject*> buffer(cx);
+ {
+ AutoCompartment ac(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/testAssemblerBuffer.cpp b/js/src/jsapi-tests/testAssemblerBuffer.cpp
new file mode 100644
index 000000000..95927cf59
--- /dev/null
+++ b/js/src/jsapi-tests/testAssemblerBuffer.cpp
@@ -0,0 +1,579 @@
+/* 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 "jsatom.h"
+
+#include "jit/shared/IonAssemblerBufferWithConstantPools.h"
+
+#include "jsapi-tests/tests.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());
+ CHECK(!ab.bail());
+
+ 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);
+ }
+};
+}
+
+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());
+ CHECK(!ab.bail());
+
+ // 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, &alloc);
+ cx->getJitRuntime(cx);
+ MacroAssembler masm;
+
+ // 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/testBoundFunction.cpp b/js/src/jsapi-tests/testBoundFunction.cpp
new file mode 100644
index 000000000..112e1103f
--- /dev/null
+++ b/js/src/jsapi-tests/testBoundFunction.cpp
@@ -0,0 +1,33 @@
+/* -*- 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 "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::RootedFunction foofun(cx, JS_ValueToFunction(cx, foo));
+ JS::RootedFunction boundfun(cx, JS_ValueToFunction(cx, bound));
+
+ CHECK(!JS_IsFunctionBound(foofun));
+ CHECK(JS_IsFunctionBound(boundfun));
+
+ CHECK(!JS_GetBoundFunctionTarget(foofun));
+ JSObject* target = JS_GetBoundFunctionTarget(boundfun);
+ CHECK(!!target);
+ CHECK(JS_ObjectIsFunction(cx, 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 000000000..475ad12f5
--- /dev/null
+++ b/js/src/jsapi-tests/testBug604087.cpp
@@ -0,0 +1,99 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ *
+ * 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 "jsobj.h"
+#include "jswrapper.h"
+
+#include "jsapi-tests/tests.h"
+
+#include "vm/ProxyObject.h"
+
+static const js::ClassExtension OuterWrapperClassExtension = PROXY_MAKE_EXT(
+ nullptr /* objectMoved */
+);
+
+const js::Class OuterWrapperClass = PROXY_CLASS_WITH_EXT(
+ "Proxy",
+ 0, /* additional class flags */
+ &OuterWrapperClassExtension);
+
+static JSObject*
+wrap(JSContext* cx, JS::HandleObject toWrap, JS::HandleObject target)
+{
+ JSAutoCompartment ac(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 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);
+ options.setSingleton(true);
+ JS::RootedObject outerObj(cx, js::Wrapper::New(cx, global, &js::Wrapper::singleton, options));
+ JS::CompartmentOptions 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>().setExtra(0, js::Int32Value(2));
+
+ JS::RootedObject c3wrapper(cx, wrap(cx, outerObj, compartment3));
+ CHECK(c3wrapper);
+ c3wrapper->as<js::ProxyObject>().setExtra(0, js::Int32Value(3));
+
+ JS::RootedObject c4wrapper(cx, wrap(cx, outerObj, compartment4));
+ CHECK(c4wrapper);
+ c4wrapper->as<js::ProxyObject>().setExtra(0, js::Int32Value(4));
+ compartment4 = c4wrapper = nullptr;
+
+ JS::RootedObject next(cx);
+ {
+ JSAutoCompartment ac(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 000000000..b924bbe4e
--- /dev/null
+++ b/js/src/jsapi-tests/testCallArgs.cpp
@@ -0,0 +1,86 @@
+/* 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 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;
+}
+
+static bool
+TryConstruct(JSContext* cx, const char* code, const char* filename, decltype(__LINE__) lineno,
+ JS::MutableHandleValue vp)
+{
+ JS::CompileOptions opts(cx);
+ opts.setFileAndLine(filename, lineno);
+ return JS::Evaluate(cx, opts, code, strlen(code), vp);
+}
+
+BEGIN_TEST(testCallArgs_isConstructing_native)
+{
+ CHECK(JS_DefineFunction(cx, global, "customNative", CustomNative, 0, 0));
+
+ JS::RootedValue result(cx);
+
+ CHECK(!TryConstruct(cx, "new customNative();", __FILE__, __LINE__, &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 000000000..060d2f309
--- /dev/null
+++ b/js/src/jsapi-tests/testCallNonGenericMethodOnProxy.cpp
@@ -0,0 +1,91 @@
+/* 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;
+
+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::CompartmentOptions 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::CompartmentOptions options;
+ JS::RootedObject globalB(cx, JS_NewGlobalObject(cx, getGlobalClass(), nullptr,
+ JS::FireOnNewGlobalHook, options));
+ CHECK(globalB);
+
+ // ...and enter it.
+ JSAutoCompartment 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 000000000..f9027245a
--- /dev/null
+++ b/js/src/jsapi-tests/testChromeBuffer.cpp
@@ -0,0 +1,195 @@
+/* -*- 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 "jsapi-tests/tests.h"
+
+static TestJSPrincipals system_principals(1);
+
+static const JSClassOps global_classOps = {
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ JS_GlobalObjectTraceHook
+};
+
+static const JSClass global_class = {
+ "global",
+ JSCLASS_IS_GLOBAL | JSCLASS_GLOBAL_FLAGS,
+ &global_classOps
+};
+
+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;
+ {
+ JSAutoCompartment ac(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::CompartmentOptions 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.
+ JS::ContextOptions oldOptions = JS::ContextOptionsRef(cx);
+ JS::ContextOptionsRef(cx).setIon(false).setBaseline(false);
+ {
+ JSAutoCompartment ac(cx, trusted_glob);
+ const char* paramName = "x";
+ const char* bytes = "return x ? 1 + trusted(x-1) : 0";
+ JS::CompileOptions options(cx);
+ options.setFileAndLine("", 0);
+ JS::AutoObjectVector emptyScopeChain(cx);
+ CHECK(JS::CompileFunction(cx, emptyScopeChain, options, "trusted",
+ 1, &paramName, bytes, strlen(bytes), &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";
+ const char* bytes = "try { "
+ " return untrusted(trusted); "
+ "} catch (e) { "
+ " try { "
+ " return trusted(100); "
+ " } catch(e) { "
+ " return -1; "
+ " } "
+ "} ";
+ JS::CompileOptions options(cx);
+ options.setFileAndLine("", 0);
+ JS::AutoObjectVector emptyScopeChain(cx);
+ CHECK(JS::CompileFunction(cx, emptyScopeChain, options, "untrusted", 1,
+ &paramName, bytes, strlen(bytes), &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::ContextOptionsRef(cx) = oldOptions;
+ }
+
+ /*
+ * Check that content called from chrome in the reserved-buffer space
+ * immediately ooms.
+ */
+ {
+ {
+ JSAutoCompartment ac(cx, trusted_glob);
+ const char* paramName = "untrusted";
+ 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::CompileOptions options(cx);
+ options.setFileAndLine("", 0);
+ JS::AutoObjectVector emptyScopeChain(cx);
+ CHECK(JS::CompileFunction(cx, emptyScopeChain, options, "trusted",
+ 1, &paramName, bytes, strlen(bytes), &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";
+ const char* bytes = "try { "
+ " return untrusted(trusted); "
+ "} catch (e) { "
+ " return trusted(untrusted); "
+ "} ";
+ JS::CompileOptions options(cx);
+ options.setFileAndLine("", 0);
+ JS::AutoObjectVector emptyScopeChain(cx);
+ CHECK(JS::CompileFunction(cx, emptyScopeChain, options, "untrusted", 1,
+ &paramName, bytes, strlen(bytes), &fun));
+ CHECK(JS_DefineProperty(cx, global, "untrusted", fun, JSPROP_ENUMERATE));
+
+ JS::RootedValue rval(cx);
+ CHECK(JS_CallFunction(cx, nullptr, fun, JS::HandleValueArray(v), &rval));
+ bool match;
+ CHECK(JS_StringEqualsAscii(cx, rval.toString(), "From trusted: InternalError: too much recursion", &match));
+ CHECK(match);
+ }
+
+ {
+ {
+ JSAutoCompartment ac(cx, trusted_glob);
+ const char* bytes = "return 42";
+ JS::CompileOptions options(cx);
+ options.setFileAndLine("", 0);
+ JS::AutoObjectVector emptyScopeChain(cx);
+ CHECK(JS::CompileFunction(cx, emptyScopeChain, options, "trusted",
+ 0, nullptr, bytes, strlen(bytes), &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";
+ const char* bytes = "try { "
+ " return untrusted(trusted); "
+ "} catch (e) { "
+ " return f(); "
+ "} ";
+ JS::CompileOptions options(cx);
+ options.setFileAndLine("", 0);
+ JS::AutoObjectVector emptyScopeChain(cx);
+ CHECK(JS::CompileFunction(cx, emptyScopeChain, options, "untrusted", 1,
+ &paramName, bytes, strlen(bytes), &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/testClassGetter.cpp b/js/src/jsapi-tests/testClassGetter.cpp
new file mode 100644
index 000000000..64b4ee5bf
--- /dev/null
+++ b/js/src/jsapi-tests/testClassGetter.cpp
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ *
+ * Tests the JSClass::getProperty hook
+ */
+/* 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 int called_test_fn;
+static int called_test_prop_get;
+
+static bool test_prop_get( JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp )
+{
+ called_test_prop_get++;
+ return true;
+}
+
+static bool
+PTest(JSContext* cx, unsigned argc, JS::Value* vp);
+
+static const JSClassOps ptestClassOps = {
+ nullptr, // addProperty
+ nullptr, // delProperty
+ test_prop_get,
+ nullptr // setProperty
+};
+
+static const JSClass ptestClass = {
+ "PTest",
+ JSCLASS_HAS_PRIVATE,
+ &ptestClassOps
+};
+
+static bool
+PTest(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 bool test_fn(JSContext* cx, unsigned argc, JS::Value* vp)
+{
+ called_test_fn++;
+ return true;
+}
+
+static const JSFunctionSpec ptestFunctions[] = {
+ JS_FS( "test_fn", test_fn, 0, 0 ),
+ JS_FS_END
+};
+
+BEGIN_TEST(testClassGetter_isCalled)
+{
+ CHECK(JS_InitClass(cx, global, nullptr, &ptestClass, PTest, 0,
+ nullptr, ptestFunctions, nullptr, nullptr));
+
+ EXEC("function check() { var o = new PTest(); o.test_fn(); o.test_value1; o.test_value2; o.test_value1; }");
+
+ for (int i = 1; i < 9; i++) {
+ JS::RootedValue rval(cx);
+ CHECK(JS_CallFunctionName(cx, global, "check", JS::HandleValueArray::empty(),
+ &rval));
+ CHECK(called_test_fn == i);
+ CHECK(called_test_prop_get == 4 * i);
+ }
+ return true;
+}
+END_TEST(testClassGetter_isCalled)
diff --git a/js/src/jsapi-tests/testCloneScript.cpp b/js/src/jsapi-tests/testCloneScript.cpp
new file mode 100644
index 000000000..c12831fcb
--- /dev/null
+++ b/js/src/jsapi-tests/testCloneScript.cpp
@@ -0,0 +1,161 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ *
+ * Test script cloning.
+ */
+/* 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"
+
+BEGIN_TEST(test_cloneScript)
+{
+ JS::RootedObject A(cx, createGlobal());
+ JS::RootedObject B(cx, createGlobal());
+
+ CHECK(A);
+ CHECK(B);
+
+ const char* source =
+ "var i = 0;\n"
+ "var sum = 0;\n"
+ "while (i < 10) {\n"
+ " sum += i;\n"
+ " ++i;\n"
+ "}\n"
+ "(sum);\n";
+
+ JS::RootedObject obj(cx);
+
+ // compile for A
+ {
+ JSAutoCompartment a(cx, A);
+ JS::RootedFunction fun(cx);
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(__FILE__, 1);
+ JS::AutoObjectVector emptyScopeChain(cx);
+ CHECK(JS::CompileFunction(cx, emptyScopeChain, options, "f", 0, nullptr,
+ source, strlen(source), &fun));
+ CHECK(obj = JS_GetFunctionObject(fun));
+ }
+
+ // clone into B
+ {
+ JSAutoCompartment b(cx, B);
+ CHECK(JS::CloneFunctionObject(cx, obj));
+ }
+
+ return true;
+}
+END_TEST(test_cloneScript)
+
+struct Principals final : public JSPrincipals
+{
+ public:
+ Principals()
+ {
+ refcount = 0;
+ }
+
+ bool write(JSContext* cx, JSStructuredCloneWriter* writer) override {
+ MOZ_ASSERT(false, "not imlemented");
+ return false;
+ }
+};
+
+class AutoDropPrincipals
+{
+ JSContext* cx;
+ JSPrincipals* principals;
+
+ public:
+ AutoDropPrincipals(JSContext* cx, JSPrincipals* principals)
+ : cx(cx), principals(principals)
+ {
+ JS_HoldPrincipals(principals);
+ }
+
+ ~AutoDropPrincipals()
+ {
+ JS_DropPrincipals(cx, principals);
+ }
+};
+
+static void
+DestroyPrincipals(JSPrincipals* principals)
+{
+ auto p = static_cast<Principals*>(principals);
+ delete p;
+}
+
+BEGIN_TEST(test_cloneScriptWithPrincipals)
+{
+ JS_InitDestroyPrincipalsCallback(cx, DestroyPrincipals);
+
+ JSPrincipals* principalsA = new Principals();
+ AutoDropPrincipals dropA(cx, principalsA);
+ JSPrincipals* principalsB = new Principals();
+ AutoDropPrincipals dropB(cx, principalsB);
+
+ JS::RootedObject A(cx, createGlobal(principalsA));
+ JS::RootedObject B(cx, createGlobal(principalsB));
+
+ CHECK(A);
+ CHECK(B);
+
+ const char* argnames[] = { "arg" };
+ const char* source = "return function() { return arg; }";
+
+ JS::RootedObject obj(cx);
+
+ // Compile in A
+ {
+ JSAutoCompartment a(cx, A);
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(__FILE__, 1);
+ JS::RootedFunction fun(cx);
+ JS::AutoObjectVector emptyScopeChain(cx);
+ JS::CompileFunction(cx, emptyScopeChain, options, "f",
+ mozilla::ArrayLength(argnames), argnames, source,
+ strlen(source), &fun);
+ CHECK(fun);
+
+ JSScript* script;
+ CHECK(script = JS_GetFunctionScript(cx, fun));
+
+ CHECK(JS_GetScriptPrincipals(script) == principalsA);
+ CHECK(obj = JS_GetFunctionObject(fun));
+ }
+
+ // Clone into B
+ {
+ JSAutoCompartment b(cx, B);
+ JS::RootedObject cloned(cx);
+ CHECK(cloned = JS::CloneFunctionObject(cx, obj));
+
+ JS::RootedFunction fun(cx);
+ JS::RootedValue clonedValue(cx, JS::ObjectValue(*cloned));
+ CHECK(fun = JS_ValueToFunction(cx, clonedValue));
+
+ JSScript* script;
+ CHECK(script = JS_GetFunctionScript(cx, fun));
+
+ CHECK(JS_GetScriptPrincipals(script) == principalsB);
+
+ JS::RootedValue v(cx);
+ JS::RootedValue arg(cx, JS::Int32Value(1));
+ CHECK(JS_CallFunctionValue(cx, B, clonedValue, JS::HandleValueArray(arg), &v));
+ CHECK(v.isObject());
+
+ JSObject* funobj = &v.toObject();
+ CHECK(JS_ObjectIsFunction(cx, funobj));
+ CHECK(fun = JS_ValueToFunction(cx, v));
+ CHECK(script = JS_GetFunctionScript(cx, fun));
+ CHECK(JS_GetScriptPrincipals(script) == principalsB);
+ }
+
+ return true;
+}
+END_TEST(test_cloneScriptWithPrincipals)
diff --git a/js/src/jsapi-tests/testDateToLocaleString.cpp b/js/src/jsapi-tests/testDateToLocaleString.cpp
new file mode 100644
index 000000000..cb0be2d00
--- /dev/null
+++ b/js/src/jsapi-tests/testDateToLocaleString.cpp
@@ -0,0 +1,54 @@
+/* -*- 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 "jsapi-tests/tests.h"
+
+BEGIN_TEST(testDateToLocaleString)
+{
+ // 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(cx, "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(cx, "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(cx, "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(cx, "en"));
+ EXEC("if (d.toLocaleTimeString() === deTime) \n"
+ " throw 'toLocaleTimeString results should have changed with system locale change';");
+
+ JS_ResetDefaultLocale(cx);
+ 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 000000000..e03e3ab16
--- /dev/null
+++ b/js/src/jsapi-tests/testDebugger.cpp
@@ -0,0 +1,65 @@
+/* -*- 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 "jscntxt.h"
+
+#include "jsapi-tests/tests.h"
+
+using namespace js;
+
+BEGIN_TEST(testDebugger_newScriptHook)
+{
+ // Test that top-level indirect eval fires the newScript hook.
+ CHECK(JS_DefineDebuggerObject(cx, global));
+ JS::CompartmentOptions options;
+ JS::RootedObject g(cx, JS_NewGlobalObject(cx, getGlobalClass(), nullptr,
+ JS::FireOnNewGlobalHook, options));
+ CHECK(g);
+ {
+ JSAutoCompartment ae(cx, g);
+ CHECK(JS_InitStandardClasses(cx, 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 scope, const char* code)
+{
+ EXEC("hits = 0;");
+
+ {
+ JSAutoCompartment ae(cx, scope);
+ JSString* codestr = JS_NewStringCopyZ(cx, code);
+ CHECK(codestr);
+ JS::RootedValue arg(cx, JS::StringValue(codestr));
+ JS::RootedValue v(cx);
+ CHECK(JS_CallFunctionName(cx, scope, "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/testDeepFreeze.cpp b/js/src/jsapi-tests/testDeepFreeze.cpp
new file mode 100644
index 000000000..d6623775f
--- /dev/null
+++ b/js/src/jsapi-tests/testDeepFreeze.cpp
@@ -0,0 +1,60 @@
+/* -*- 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 "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 000000000..be1495bc9
--- /dev/null
+++ b/js/src/jsapi-tests/testDefineGetterSetterNonEnumerable.cpp
@@ -0,0 +1,58 @@
+/* -*- 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 "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,
+ JS::UndefinedHandleValue,
+ JSPROP_GETTER | JSPROP_SETTER | JSPROP_SHARED | JSPROP_ENUMERATE,
+ JS_DATA_TO_FUNC_PTR(JSNative, (JSObject*) funGetObj),
+ JS_DATA_TO_FUNC_PTR(JSNative, (JSObject*) funSetObj)));
+
+ CHECK(JS_DefineProperty(cx, vObject, PROPERTY_NAME,
+ JS::UndefinedHandleValue,
+ JSPROP_GETTER | JSPROP_SETTER | JSPROP_SHARED | JSPROP_PERMANENT,
+ JS_DATA_TO_FUNC_PTR(JSNative, (JSObject*) funGetObj),
+ JS_DATA_TO_FUNC_PTR(JSNative, (JSObject*) funSetObj)));
+
+ JS::Rooted<JS::PropertyDescriptor> desc(cx);
+ CHECK(JS_GetOwnPropertyDescriptor(cx, vObject, PROPERTY_NAME, &desc));
+ CHECK(desc.object());
+ CHECK(desc.hasGetterObject());
+ CHECK(desc.hasSetterObject());
+ 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 000000000..1c9c1e598
--- /dev/null
+++ b/js/src/jsapi-tests/testDefineProperty.cpp
@@ -0,0 +1,23 @@
+/* -*- 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 "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, JSPROP_SHARED));
+ return true;
+}
+END_TEST(testDefineProperty_bug564344)
diff --git a/js/src/jsapi-tests/testDefinePropertyIgnoredAttributes.cpp b/js/src/jsapi-tests/testDefinePropertyIgnoredAttributes.cpp
new file mode 100644
index 000000000..83de2918e
--- /dev/null
+++ b/js/src/jsapi-tests/testDefinePropertyIgnoredAttributes.cpp
@@ -0,0 +1,100 @@
+/* -*- 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 "jsapi-tests/tests.h"
+
+static bool
+Getter(JSContext* cx, unsigned argc, JS::Value* vp)
+{
+ JS::CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setBoolean(true);
+ return true;
+}
+
+enum PropertyDescriptorKind {
+ DataDescriptor, AccessorDescriptor
+};
+
+static bool
+CheckDescriptor(JS::Handle<JS::PropertyDescriptor> desc, PropertyDescriptorKind kind,
+ bool enumerable, bool writable, bool configurable)
+{
+ if (!desc.object())
+ return false;
+ if (!(kind == DataDescriptor ? desc.isDataDescriptor() : desc.isAccessorDescriptor()))
+ return false;
+ if (desc.enumerable() != enumerable)
+ return false;
+ if (kind == DataDescriptor && desc.writable() != writable)
+ return false;
+ if (desc.configurable() != configurable)
+ return false;
+ return true;
+}
+
+BEGIN_TEST(testDefinePropertyIgnoredAttributes)
+{
+ JS::RootedObject obj(cx, JS_NewPlainObject(cx));
+ JS::Rooted<JS::PropertyDescriptor> desc(cx);
+ JS::RootedValue defineValue(cx);
+
+ // Try a getter. Allow it to fill in the defaults. Because we're passing a
+ // JSNative, JS_DefineProperty will infer JSPROP_GETTER even though we
+ // aren't passing it.
+ CHECK(JS_DefineProperty(cx, obj, "foo", defineValue,
+ JSPROP_IGNORE_ENUMERATE | JSPROP_IGNORE_PERMANENT | JSPROP_SHARED,
+ Getter));
+
+ CHECK(JS_GetOwnPropertyDescriptor(cx, obj, "foo", &desc));
+
+ // Note that JSPROP_READONLY is meaningless for accessor properties.
+ CHECK(CheckDescriptor(desc, AccessorDescriptor, false, true, false));
+
+ // Install another configurable property, so we can futz with it.
+ CHECK(JS_DefineProperty(cx, obj, "bar", defineValue,
+ JSPROP_IGNORE_ENUMERATE | JSPROP_SHARED,
+ Getter));
+ CHECK(JS_GetOwnPropertyDescriptor(cx, obj, "bar", &desc));
+ CHECK(CheckDescriptor(desc, AccessorDescriptor, false, true, true));
+
+ // Rewrite the descriptor to now be enumerable, leaving the configurability
+ // unchanged.
+ CHECK(JS_DefineProperty(cx, obj, "bar", defineValue,
+ JSPROP_IGNORE_PERMANENT | JSPROP_ENUMERATE | JSPROP_SHARED,
+ Getter));
+ CHECK(JS_GetOwnPropertyDescriptor(cx, obj, "bar", &desc));
+ CHECK(CheckDescriptor(desc, AccessorDescriptor, true, true, true));
+
+ // Now try the same game with a value property
+ defineValue.setObject(*obj);
+ CHECK(JS_DefineProperty(cx, obj, "baz", defineValue,
+ JSPROP_IGNORE_ENUMERATE |
+ JSPROP_IGNORE_READONLY |
+ JSPROP_IGNORE_PERMANENT));
+ CHECK(JS_GetOwnPropertyDescriptor(cx, obj, "baz", &desc));
+ CHECK(CheckDescriptor(desc, DataDescriptor, false, false, false));
+
+ // Now again with a configurable property
+ CHECK(JS_DefineProperty(cx, obj, "quux", defineValue,
+ JSPROP_IGNORE_ENUMERATE | JSPROP_IGNORE_READONLY));
+ CHECK(JS_GetOwnPropertyDescriptor(cx, obj, "quux", &desc));
+ CHECK(CheckDescriptor(desc, DataDescriptor, false, false, true));
+
+ // Just make it writable. Leave the old value and everything else alone.
+ defineValue.setUndefined();
+ CHECK(JS_DefineProperty(cx, obj, "quux", defineValue,
+ JSPROP_IGNORE_ENUMERATE |
+ JSPROP_IGNORE_PERMANENT |
+ JSPROP_IGNORE_VALUE));
+
+ CHECK(JS_GetOwnPropertyDescriptor(cx, obj, "quux", &desc));
+ CHECK(CheckDescriptor(desc, DataDescriptor, false, true, true));
+ CHECK_SAME(JS::ObjectValue(*obj), desc.value());
+
+ return true;
+}
+END_TEST(testDefinePropertyIgnoredAttributes)
diff --git a/js/src/jsapi-tests/testDeflateStringToUTF8Buffer.cpp b/js/src/jsapi-tests/testDeflateStringToUTF8Buffer.cpp
new file mode 100644
index 000000000..348665685
--- /dev/null
+++ b/js/src/jsapi-tests/testDeflateStringToUTF8Buffer.cpp
@@ -0,0 +1,286 @@
+/* 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;
+ JSFlatString* flatStr;
+
+ // 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];
+ mozilla::RangedPtr<char> range = mozilla::RangedPtr<char>(actual, 100);
+
+ // Test with an ASCII string, which calls JSFlatString::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);
+ flatStr = JS_FlattenString(cx, str);
+
+ {
+ const char expected[] = { 0x4F, 0x68, 0x61, 0x69, 0x1 };
+ memset(actual, 0x1, 100);
+ JS::DeflateStringToUTF8Buffer(flatStr, range);
+ CHECK_EQUAL(memcmp(actual, expected, sizeof(expected)), 0);
+ }
+
+ {
+ size_t dstlen = 4;
+ const char expected[] = { 0x4F, 0x68, 0x61, 0x69, 0x1 };
+ memset(actual, 0x1, 100);
+ JS::DeflateStringToUTF8Buffer(flatStr, range, &dstlen);
+ CHECK_EQUAL(memcmp(actual, expected, sizeof(expected)), 0);
+ CHECK_EQUAL(dstlen, 4u);
+ }
+
+ {
+ size_t numchars = 0;
+ const char expected[] = { 0x4F, 0x68, 0x61, 0x69, 0x1 };
+ memset(actual, 0x1, 100);
+ JS::DeflateStringToUTF8Buffer(flatStr, range, nullptr, &numchars);
+ CHECK_EQUAL(memcmp(actual, expected, sizeof(expected)), 0);
+ CHECK_EQUAL(numchars, 4u);
+ }
+
+ {
+ size_t dstlen = 4;
+ size_t numchars = 0;
+ const char expected[] = { 0x4F, 0x68, 0x61, 0x69, 0x1 };
+ memset(actual, 0x1, 100);
+ JS::DeflateStringToUTF8Buffer(flatStr, range, &dstlen, &numchars);
+ CHECK_EQUAL(memcmp(actual, expected, sizeof(expected)), 0);
+ CHECK_EQUAL(dstlen, 4u);
+ CHECK_EQUAL(numchars, 4u);
+ }
+
+ {
+ size_t dstlen = 3;
+ size_t numchars = 0;
+ const char expected[] = { 0x4F, 0x68, 0x61, 0x1 };
+ memset(actual, 0x1, 100);
+ JS::DeflateStringToUTF8Buffer(flatStr, range, &dstlen, &numchars);
+ CHECK_EQUAL(memcmp(actual, expected, sizeof(expected)), 0);
+ CHECK_EQUAL(dstlen, 3u);
+ CHECK_EQUAL(numchars, 3u);
+ }
+
+ {
+ size_t dstlen = 100;
+ size_t numchars = 0;
+ const char expected[] = { 0x4F, 0x68, 0x61, 0x69, 0x1 };
+ memset(actual, 0x1, 100);
+ JS::DeflateStringToUTF8Buffer(flatStr, range, &dstlen, &numchars);
+ CHECK_EQUAL(memcmp(actual, expected, sizeof(expected)), 0);
+ CHECK_EQUAL(dstlen, 4u);
+ CHECK_EQUAL(numchars, 4u);
+ }
+
+ {
+ size_t dstlen = 0;
+ size_t numchars = 0;
+ const unsigned char expected[] = { 0x1 };
+ memset(actual, 0x1, 100);
+ JS::DeflateStringToUTF8Buffer(flatStr, range, &dstlen, &numchars);
+ CHECK_EQUAL(memcmp(actual, expected, sizeof(expected)), 0);
+ CHECK_EQUAL(dstlen, 0u);
+ CHECK_EQUAL(numchars, 0u);
+ }
+
+ // Test with a Latin-1 string, which calls JSFlatString::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);
+ flatStr = JS_FlattenString(cx, str);
+
+ {
+ const unsigned char expected[] = { 0xC3, 0x93, 0x68, 0xC3, 0xA3, 0xC3, 0xAF, 0x1 };
+ memset(actual, 0x1, 100);
+ JS::DeflateStringToUTF8Buffer(flatStr, range);
+ CHECK_EQUAL(memcmp(actual, expected, sizeof(expected)), 0);
+ }
+
+ {
+ size_t dstlen = 7;
+ const unsigned char expected[] = { 0xC3, 0x93, 0x68, 0xC3, 0xA3, 0xC3, 0xAF, 0x1 };
+ memset(actual, 0x1, 100);
+ JS::DeflateStringToUTF8Buffer(flatStr, range, &dstlen);
+ CHECK_EQUAL(memcmp(actual, expected, sizeof(expected)), 0);
+ CHECK_EQUAL(dstlen, 7u);
+ }
+
+ {
+ size_t numchars = 0;
+ const unsigned char expected[] = { 0xC3, 0x93, 0x68, 0xC3, 0xA3, 0xC3, 0xAF, 0x1 };
+ memset(actual, 0x1, 100);
+ JS::DeflateStringToUTF8Buffer(flatStr, range, nullptr, &numchars);
+ CHECK_EQUAL(memcmp(actual, expected, sizeof(expected)), 0);
+ CHECK_EQUAL(numchars, 4u);
+ }
+
+ {
+ size_t dstlen = 7;
+ size_t numchars = 0;
+ const unsigned char expected[] = { 0xC3, 0x93, 0x68, 0xC3, 0xA3, 0xC3, 0xAF, 0x1 };
+ memset(actual, 0x1, 100);
+ JS::DeflateStringToUTF8Buffer(flatStr, range, &dstlen, &numchars);
+ CHECK_EQUAL(memcmp(actual, expected, sizeof(expected)), 0);
+ CHECK_EQUAL(dstlen, 7u);
+ CHECK_EQUAL(numchars, 4u);
+ }
+
+ {
+ // Specify a destination buffer length of 3. That's exactly enough
+ // space to encode the first two characters, which takes three bytes.
+ size_t dstlen = 3;
+ size_t numchars = 0;
+ const unsigned char expected[] = { 0xC3, 0x93, 0x68, 0x1 };
+ memset(actual, 0x1, 100);
+ JS::DeflateStringToUTF8Buffer(flatStr, range, &dstlen, &numchars);
+ CHECK_EQUAL(memcmp(actual, expected, sizeof(expected)), 0);
+ CHECK_EQUAL(dstlen, 3u);
+ CHECK_EQUAL(numchars, 2u);
+ }
+
+ {
+ // 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.
+ size_t dstlen = 4;
+ size_t numchars = 0;
+ const unsigned char expected[] = { 0xC3, 0x93, 0x68, 0x1 };
+ memset(actual, 0x1, 100);
+ JS::DeflateStringToUTF8Buffer(flatStr, range, &dstlen, &numchars);
+ CHECK_EQUAL(memcmp(actual, expected, sizeof(expected)), 0);
+ CHECK_EQUAL(dstlen, 3u);
+ CHECK_EQUAL(numchars, 2u);
+ }
+
+ {
+ size_t dstlen = 100;
+ size_t numchars = 0;
+ const unsigned char expected[] = { 0xC3, 0x93, 0x68, 0xC3, 0xA3, 0xC3, 0xAF, 0x1 };
+ memset(actual, 0x1, 100);
+ JS::DeflateStringToUTF8Buffer(flatStr, range, &dstlen, &numchars);
+ CHECK_EQUAL(memcmp(actual, expected, sizeof(expected)), 0);
+ CHECK_EQUAL(dstlen, 7u);
+ CHECK_EQUAL(numchars, 4u);
+ }
+
+ {
+ size_t dstlen = 0;
+ size_t numchars = 0;
+ const unsigned char expected[] = { 0x1 };
+ memset(actual, 0x1, 100);
+ JS::DeflateStringToUTF8Buffer(flatStr, range, &dstlen, &numchars);
+ CHECK_EQUAL(memcmp(actual, expected, sizeof(expected)), 0);
+ CHECK_EQUAL(dstlen, 0u);
+ CHECK_EQUAL(numchars, 0u);
+ }
+
+ // Test with a UTF-16 string, which calls JSFlatString::twoByteChars
+ // to retrieve the characters from the string.
+
+ str = JS_NewUCStringCopyZ(cx, u"\x038C\x0068\x0203\x0457"); // u"Όhȃї"
+ MOZ_RELEASE_ASSERT(str);
+ flatStr = JS_FlattenString(cx, str);
+
+ {
+ const unsigned char expected[] = { 0xCE, 0x8C, 0x68, 0xC8, 0x83, 0xD1, 0x97, 0x1 };
+ memset(actual, 0x1, 100);
+ JS::DeflateStringToUTF8Buffer(flatStr, range);
+ CHECK_EQUAL(memcmp(actual, expected, sizeof(expected)), 0);
+ }
+
+ {
+ size_t dstlen = 7;
+ const unsigned char expected[] = { 0xCE, 0x8C, 0x68, 0xC8, 0x83, 0xD1, 0x97, 0x1 };
+ memset(actual, 0x1, 100);
+ JS::DeflateStringToUTF8Buffer(flatStr, range, &dstlen);
+ CHECK_EQUAL(memcmp(actual, expected, sizeof(expected)), 0);
+ CHECK_EQUAL(dstlen, 7u);
+ }
+
+ {
+ size_t numchars = 0;
+ const unsigned char expected[] = { 0xCE, 0x8C, 0x68, 0xC8, 0x83, 0xD1, 0x97, 0x1 };
+ memset(actual, 0x1, 100);
+ JS::DeflateStringToUTF8Buffer(flatStr, range, nullptr, &numchars);
+ CHECK_EQUAL(memcmp(actual, expected, sizeof(expected)), 0);
+ CHECK_EQUAL(numchars, 4u);
+ }
+
+ {
+ size_t dstlen = 7;
+ size_t numchars = 0;
+ const unsigned char expected[] = { 0xCE, 0x8C, 0x68, 0xC8, 0x83, 0xD1, 0x97, 0x1 };
+ memset(actual, 0x1, 100);
+ JS::DeflateStringToUTF8Buffer(flatStr, range, &dstlen, &numchars);
+ CHECK_EQUAL(memcmp(actual, expected, sizeof(expected)), 0);
+ CHECK_EQUAL(dstlen, 7u);
+ CHECK_EQUAL(numchars, 4u);
+ }
+
+ {
+ // Specify a destination buffer length of 3. That's exactly enough
+ // space to encode the first two characters, which takes three bytes.
+ size_t dstlen = 3;
+ size_t numchars = 0;
+ const unsigned char expected[] = { 0xCE, 0x8C, 0x68, 0x1 };
+ memset(actual, 0x1, 100);
+ JS::DeflateStringToUTF8Buffer(flatStr, range, &dstlen, &numchars);
+ CHECK_EQUAL(memcmp(actual, expected, sizeof(expected)), 0);
+ CHECK_EQUAL(dstlen, 3u);
+ CHECK_EQUAL(numchars, 2u);
+ }
+
+ {
+ // 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.
+ size_t dstlen = 4;
+ size_t numchars = 0;
+ const unsigned char expected[] = { 0xCE, 0x8C, 0x68, 0x1 };
+ memset(actual, 0x1, 100);
+ JS::DeflateStringToUTF8Buffer(flatStr, range, &dstlen, &numchars);
+ CHECK_EQUAL(memcmp(actual, expected, sizeof(expected)), 0);
+ CHECK_EQUAL(dstlen, 3u);
+ CHECK_EQUAL(numchars, 2u);
+ }
+
+ {
+ size_t dstlen = 100;
+ size_t numchars = 0;
+ const unsigned char expected[] = { 0xCE, 0x8C, 0x68, 0xC8, 0x83, 0xD1, 0x97, 0x1 };
+ memset(actual, 0x1, 100);
+ JS::DeflateStringToUTF8Buffer(flatStr, range, &dstlen, &numchars);
+ CHECK_EQUAL(memcmp(actual, expected, sizeof(expected)), 0);
+ CHECK_EQUAL(dstlen, 7u);
+ CHECK_EQUAL(numchars, 4u);
+ }
+
+ {
+ size_t dstlen = 0;
+ size_t numchars = 0;
+ const unsigned char expected[] = { 0x1 };
+ memset(actual, 0x1, 100);
+ JS::DeflateStringToUTF8Buffer(flatStr, range, &dstlen, &numchars);
+ CHECK_EQUAL(memcmp(actual, expected, sizeof(expected)), 0);
+ CHECK_EQUAL(dstlen, 0u);
+ CHECK_EQUAL(numchars, 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 000000000..aec2b9616
--- /dev/null
+++ b/js/src/jsapi-tests/testDifferentNewTargetInvokeConstructor.cpp
@@ -0,0 +1,37 @@
+/* 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"
+
+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::AutoValueArray<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/testEnclosingFunction.cpp b/js/src/jsapi-tests/testEnclosingFunction.cpp
new file mode 100644
index 000000000..4482d4e58
--- /dev/null
+++ b/js/src/jsapi-tests/testEnclosingFunction.cpp
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ *
+ * Test script cloning.
+ */
+/* 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"
+
+using namespace js;
+
+static JSFunction* foundFun = nullptr;
+
+static bool
+CheckEnclosing(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ foundFun = js::GetOutermostEnclosingFunctionOfScriptedCaller(cx);
+
+ args.rval().set(UndefinedValue());
+ return true;
+}
+
+BEGIN_TEST(test_enclosingFunction)
+{
+ CHECK(JS_DefineFunction(cx, global, "checkEnclosing", CheckEnclosing, 0, 0));
+
+ EXEC("checkEnclosing()");
+ CHECK(foundFun == nullptr);
+
+ RootedFunction fun(cx);
+
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(__FILE__, __LINE__);
+
+ const char s1chars[] = "checkEnclosing()";
+ JS::AutoObjectVector emptyScopeChain(cx);
+ CHECK(JS::CompileFunction(cx, emptyScopeChain, options, "s1", 0, nullptr, s1chars,
+ strlen(s1chars), &fun));
+ CHECK(fun);
+ CHECK(JS_DefineProperty(cx, global, "s1", fun, JSPROP_ENUMERATE));
+ EXEC("s1()");
+ CHECK(foundFun == fun);
+
+ const char s2chars[] = "return function() { checkEnclosing() }";
+ CHECK(JS::CompileFunction(cx, emptyScopeChain, options, "s2", 0, nullptr, s2chars,
+ strlen(s2chars), &fun));
+ CHECK(fun);
+ CHECK(JS_DefineProperty(cx, global, "s2", fun, JSPROP_ENUMERATE));
+ EXEC("s2()()");
+ CHECK(foundFun == fun);
+
+ const char s3chars[] = "return function() { { let x; function g() { checkEnclosing() } return g() } }";
+ CHECK(JS::CompileFunction(cx, emptyScopeChain, options, "s3", 0, nullptr, s3chars,
+ strlen(s3chars), &fun));
+ CHECK(fun);
+ CHECK(JS_DefineProperty(cx, global, "s3", fun, JSPROP_ENUMERATE));
+ EXEC("s3()()");
+ CHECK(foundFun == fun);
+
+ return true;
+}
+END_TEST(test_enclosingFunction)
diff --git a/js/src/jsapi-tests/testErrorCopying.cpp b/js/src/jsapi-tests/testErrorCopying.cpp
new file mode 100644
index 000000000..1d52b9ec0
--- /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: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ *
+ * 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 "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::RootedValue exn(cx);
+ CHECK(JS_GetPendingException(cx, &exn));
+ JS_ClearPendingException(cx);
+
+ js::ErrorReport report(cx);
+ CHECK(report.init(cx, exn, js::ErrorReport::WithSideEffects));
+
+ CHECK_EQUAL(report.report()->column, 28u);
+ return true;
+}
+
+END_TEST(testErrorCopying_columnCopied)
diff --git a/js/src/jsapi-tests/testException.cpp b/js/src/jsapi-tests/testException.cpp
new file mode 100644
index 000000000..b515573e3
--- /dev/null
+++ b/js/src/jsapi-tests/testException.cpp
@@ -0,0 +1,26 @@
+/* -*- 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 "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)
diff --git a/js/src/jsapi-tests/testExternalArrayBuffer.cpp b/js/src/jsapi-tests/testExternalArrayBuffer.cpp
new file mode 100644
index 000000000..cb864d37c
--- /dev/null
+++ b/js/src/jsapi-tests/testExternalArrayBuffer.cpp
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ */
+
+#include "jsfriendapi.h"
+#include "jsapi-tests/tests.h"
+#include "vm/ArrayBufferObject.h"
+
+char test_data[] = "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+static void GC(JSContext* cx)
+{
+ JS_GC(cx);
+ // Trigger another to wait for background finalization to end.
+ JS_GC(cx);
+}
+
+BEGIN_TEST(testExternalArrayBuffer)
+{
+ size_t length = sizeof(test_data);
+ JS::RootedObject obj(cx, JS_NewArrayBufferWithExternalContents(cx, length, test_data));
+ GC(cx);
+ CHECK(VerifyObject(obj, length));
+ 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));
+ CHECK(data);
+ CHECK(test_data == data);
+
+ return true;
+}
+
+END_TEST(testExternalArrayBuffer)
diff --git a/js/src/jsapi-tests/testExternalStrings.cpp b/js/src/jsapi-tests/testExternalStrings.cpp
new file mode 100644
index 000000000..9aca388b3
--- /dev/null
+++ b/js/src/jsapi-tests/testExternalStrings.cpp
@@ -0,0 +1,61 @@
+/* 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/PodOperations.h"
+
+#include "jsapi-tests/tests.h"
+
+using mozilla::ArrayLength;
+using mozilla::PodEqual;
+
+static const char16_t arr[] = {
+ 'h', 'i', ',', 'd', 'o', 'n', '\'', 't', ' ', 'd', 'e', 'l', 'e', 't', 'e', ' ', 'm', 'e', '\0'
+};
+static const size_t arrlen = ArrayLength(arr) - 1;
+
+static int finalized1 = 0;
+static int finalized2 = 0;
+
+static void
+finalize_str(JS::Zone* zone, const JSStringFinalizer* fin, char16_t* chars);
+
+static const JSStringFinalizer finalizer1 = { finalize_str };
+static const JSStringFinalizer finalizer2 = { finalize_str };
+
+static void
+finalize_str(JS::Zone* zone, const JSStringFinalizer* fin, char16_t* chars)
+{
+ if (chars && PodEqual(const_cast<const char16_t*>(chars), arr, arrlen)) {
+ if (fin == &finalizer1) {
+ ++finalized1;
+ } else if (fin == &finalizer2) {
+ ++finalized2;
+ }
+ }
+}
+
+BEGIN_TEST(testExternalStrings)
+{
+ const unsigned N = 1000;
+
+ for (unsigned i = 0; i < N; ++i) {
+ CHECK(JS_NewExternalString(cx, arr, arrlen, &finalizer1));
+ CHECK(JS_NewExternalString(cx, arr, arrlen, &finalizer2));
+ }
+
+ // clear that newborn root
+ JS_NewUCStringCopyN(cx, arr, arrlen);
+
+ 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 000000000..66b698a91
--- /dev/null
+++ b/js/src/jsapi-tests/testFindSCCs.cpp
@@ -0,0 +1,285 @@
+/* -*- 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 <stdarg.h>
+#include <string.h>
+
+#include "gc/FindSCCs.h"
+#include "jsapi-tests/tests.h"
+
+static const unsigned MaxVertices = 10;
+
+using js::gc::GraphNodeBase;
+using js::gc::ComponentFinder;
+
+struct TestComponentFinder;
+
+struct TestNode : public GraphNodeBase<TestNode>
+{
+ unsigned index;
+ bool hasEdge[MaxVertices];
+
+ void findOutgoingEdges(TestComponentFinder& finder);
+};
+
+struct TestComponentFinder : public ComponentFinder<TestNode, TestComponentFinder>
+{
+ explicit TestComponentFinder(uintptr_t sl)
+ : ComponentFinder<TestNode, TestComponentFinder>(sl)
+ {}
+};
+
+static TestNode Vertex[MaxVertices];
+
+void
+TestNode::findOutgoingEdges(TestComponentFinder& finder)
+{
+ for (unsigned i = 0; i < MaxVertices; ++i) {
+ if (hasEdge[i])
+ finder.addEdgeTo(&Vertex[i]);
+ }
+}
+
+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);
+ edge(0, 1);
+ edge(1, 2);
+ run();
+ CHECK(group(0, -1));
+ CHECK(group(1, -1));
+ CHECK(group(2, -1));
+ CHECK(end());
+
+ // tree
+
+ setup(3);
+ edge(0, 1);
+ edge(0, 2);
+ run();
+ CHECK(group(0, -1));
+ CHECK(group(2, -1));
+ CHECK(group(1, -1));
+ CHECK(end());
+
+ // cycles
+
+ setup(3);
+ edge(0, 1);
+ edge(1, 2);
+ edge(2, 0);
+ run();
+ CHECK(group(0, 1, 2, -1));
+ CHECK(end());
+
+ setup(4);
+ edge(0, 1);
+ edge(1, 2);
+ edge(2, 1);
+ edge(2, 3);
+ run();
+ CHECK(group(0, -1));
+ CHECK(group(1, 2, -1));
+ CHECK(group(3, -1));
+ CHECK(end());
+
+ // remaining
+
+ setup(2);
+ edge(0, 1);
+ run();
+ CHECK(remaining(0, 1, -1));
+ CHECK(end());
+
+ setup(2);
+ edge(0, 1);
+ run();
+ CHECK(group(0, -1));
+ CHECK(remaining(1, -1));
+ CHECK(end());
+
+ setup(2);
+ 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.gcNextGraphNode = nullptr;
+ v.index = i;
+ memset(&v.hasEdge, 0, sizeof(v.hasEdge));
+ }
+}
+
+void edge(unsigned src_index, unsigned dest_index)
+{
+ Vertex[src_index].hasEdge[dest_index] = true;
+}
+
+void run()
+{
+ finder = new TestComponentFinder(cx->nativeStackLimit[js::StackForSystemCode]);
+ 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)
+
+struct TestComponentFinder2;
+
+struct TestNode2 : public GraphNodeBase<TestNode2>
+{
+ TestNode2* edge;
+
+ TestNode2() : edge(nullptr) {}
+ void findOutgoingEdges(TestComponentFinder2& finder);
+};
+
+struct TestComponentFinder2 : public ComponentFinder<TestNode2, TestComponentFinder2>
+{
+ explicit TestComponentFinder2(uintptr_t sl)
+ : ComponentFinder<TestNode2, TestComponentFinder2>(sl)
+ {}
+};
+
+void
+TestNode2::findOutgoingEdges(TestComponentFinder2& finder)
+{
+ if (edge)
+ finder.addEdgeTo(edge);
+}
+
+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;
+
+ TestNode2* vertices = new TestNode2[max]();
+ for (unsigned i = initial; i < (max - 10); ++i)
+ vertices[i].edge = &vertices[i + 1];
+
+ TestComponentFinder2 finder(cx->nativeStackLimit[js::StackForSystemCode]);
+ for (unsigned i = 0; i < max; ++i)
+ finder.addNode(&vertices[i]);
+
+ TestNode2* r = finder.getResultsList();
+ CHECK(r);
+ TestNode2* 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 000000000..760f92017
--- /dev/null
+++ b/js/src/jsapi-tests/testForOfIterator.cpp
@@ -0,0 +1,52 @@
+/* -*- 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 "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 000000000..c19e15c17
--- /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: 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 "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::RootedGlobalObject g(cx, cx->global());
+ JS::Rooted<js::LexicalEnvironmentObject*> env(
+ cx, js::LexicalEnvironmentObject::createGlobal(cx, g));
+
+ JS::RootedValue uninitialized(cx, JS::MagicValue(JS_UNINITIALIZED_LEXICAL));
+ js::RootedPropertyName name(cx, Atomize(cx, "foopi", 4)->asPropertyName());
+ JS::RootedId id(cx, NameToId(name));
+ unsigned attrs = JSPROP_ENUMERATE | JSPROP_PERMANENT;
+
+ CHECK(NativeDefineProperty(cx, env, id, uninitialized, nullptr, nullptr, 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 000000000..eea07c279
--- /dev/null
+++ b/js/src/jsapi-tests/testForwardSetProperty.cpp
@@ -0,0 +1,90 @@
+/* -*- 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 "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 000000000..e9744a24a
--- /dev/null
+++ b/js/src/jsapi-tests/testFreshGlobalEvalRedefinition.cpp
@@ -0,0 +1,53 @@
+/* -*- 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 "jsapi-tests/tests.h"
+
+static bool
+GlobalEnumerate(JSContext* cx, JS::Handle<JSObject*> obj)
+{
+ return JS_EnumerateStandardClasses(cx, obj);
+}
+
+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, nullptr, nullptr, nullptr,
+ GlobalEnumerate, GlobalResolve, nullptr, nullptr,
+ nullptr, nullptr, nullptr,
+ JS_GlobalObjectTraceHook
+ };
+
+ static const JSClass cls = {
+ "global", JSCLASS_GLOBAL_FLAGS,
+ &clsOps
+ };
+
+ /* Create the global object. */
+ JS::CompartmentOptions options;
+ JS::Rooted<JSObject*> g(cx, JS_NewGlobalObject(cx, &cls, nullptr, JS::FireOnNewGlobalHook, options));
+ if (!g)
+ return false;
+
+ JSAutoCompartment ac(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);
+ CHECK(JS::Evaluate(cx, opts.setFileAndLine(__FILE__, __LINE__),
+ data, mozilla::ArrayLength(data) - 1, &v));
+
+ return true;
+}
+END_TEST(testRedefineGlobalEval)
diff --git a/js/src/jsapi-tests/testFunctionProperties.cpp b/js/src/jsapi-tests/testFunctionProperties.cpp
new file mode 100644
index 000000000..e79f5ec28
--- /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: 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 "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 000000000..2c5c58a29
--- /dev/null
+++ b/js/src/jsapi-tests/testGCAllocator.cpp
@@ -0,0 +1,383 @@
+/* -*- 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 <cstdlib>
+
+#include "gc/GCInternals.h"
+#include "gc/Memory.h"
+#include "jsapi-tests/tests.h"
+
+#if defined(XP_WIN)
+#include "jswin.h"
+#include <psapi.h>
+#elif defined(SOLARIS)
+// This test doesn't apply to Solaris.
+#elif defined(XP_UNIX)
+#include <algorithm>
+#include <errno.h>
+#include <sys/mman.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#else
+#error "Memory mapping functions are not defined for your OS."
+#endif
+
+BEGIN_TEST(testGCAllocator)
+{
+ size_t PageSize = 0;
+#if defined(XP_WIN)
+# if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
+ SYSTEM_INFO sysinfo;
+ GetSystemInfo(&sysinfo);
+ PageSize = sysinfo.dwPageSize;
+# else // Various APIs are unavailable. This test is disabled.
+ return true;
+# endif
+#elif defined(SOLARIS)
+ return true;
+#elif defined(XP_UNIX)
+ PageSize = size_t(sysconf(_SC_PAGESIZE));
+#else
+ return true;
+#endif
+
+ /* Finish any ongoing background free activity. */
+ js::gc::FinishGC(cx);
+
+ bool growUp;
+ CHECK(addressesGrowUp(&growUp));
+
+ if (growUp)
+ return testGCAllocatorUp(PageSize);
+ 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));
+
+ // 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));
+
+ // 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)
+# if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
+
+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));
+}
+
+# else // Various APIs are unavailable. This test is disabled.
+
+void* mapMemoryAt(void* desired, size_t length) { return nullptr; }
+void* mapMemory(size_t length) { return nullptr; }
+void unmapPages(void* p, size_t size) { }
+
+# endif
+#elif defined(SOLARIS) // This test doesn't apply to Solaris.
+
+void* mapMemoryAt(void* desired, size_t length) { return nullptr; }
+void* mapMemory(size_t length) { return nullptr; }
+void unmapPages(void* p, size_t size) { }
+
+#elif defined(XP_UNIX)
+
+void*
+mapMemoryAt(void* desired, size_t length)
+{
+#if defined(__ia64__) || (defined(__sparc64__) && defined(__NetBSD__)) || defined(__aarch64__)
+ MOZ_RELEASE_ASSERT(0xffff800000000000ULL & (uintptr_t(desired) + length - 1) == 0);
+#endif
+ 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;
+ // The test code must be aligned with the implementation in gc/Memory.cpp.
+#if defined(__ia64__) || (defined(__sparc64__) && defined(__NetBSD__))
+ void* region = mmap((void*)0x0000070000000000, length, prot, flags, fd, offset);
+ if (region == MAP_FAILED)
+ return nullptr;
+ if ((uintptr_t(region) + (length - 1)) & 0xffff800000000000) {
+ if (munmap(region, length))
+ MOZ_RELEASE_ASSERT(errno == ENOMEM);
+ return nullptr;
+ }
+ return region;
+#elif defined(__aarch64__)
+ const uintptr_t start = UINT64_C(0x0000070000000000);
+ const uintptr_t end = UINT64_C(0x0000800000000000);
+ const uintptr_t step = js::gc::ChunkSize;
+ uintptr_t hint;
+ void* region = MAP_FAILED;
+ for (hint = start; region == MAP_FAILED && hint + length <= end; hint += step) {
+ region = mmap((void*)hint, length, prot, flags, fd, offset);
+ if (region != MAP_FAILED) {
+ if ((uintptr_t(region) + (length - 1)) & 0xffff800000000000) {
+ if (munmap(region, length)) {
+ MOZ_RELEASE_ASSERT(errno == ENOMEM);
+ }
+ region = MAP_FAILED;
+ }
+ }
+ }
+ return region == MAP_FAILED ? nullptr : region;
+#else
+ void* region = mmap(nullptr, length, prot, flags, fd, offset);
+ if (region == MAP_FAILED)
+ return nullptr;
+ return region;
+#endif
+}
+
+void
+unmapPages(void* p, size_t size)
+{
+ if (munmap(p, size))
+ MOZ_RELEASE_ASSERT(errno == ENOMEM);
+}
+
+#else // !defined(XP_WIN) && !defined(SOLARIS) && !defined(XP_UNIX)
+#error "Memory mapping functions are not defined for your OS."
+#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 000000000..c3b1ca9d5
--- /dev/null
+++ b/js/src/jsapi-tests/testGCCellPtr.cpp
@@ -0,0 +1,61 @@
+/* -*- 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 "jsapi.h"
+#include "jspubtd.h"
+
+#include "gc/Heap.h"
+
+#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::RootedScript script(cx);
+ CHECK(JS_CompileScript(cx, code, strlen(code), opts, &script));
+ CHECK(script);
+
+ CHECK(!JS::GCCellPtr(nullptr));
+
+ CHECK(JS::GCCellPtr(obj.get()));
+ CHECK(JS::GCCellPtr(obj.get()).kind() == JS::TraceKind::Object);
+ CHECK(JS::GCCellPtr(JS::ObjectValue(*obj)).kind() == JS::TraceKind::Object);
+
+ CHECK(JS::GCCellPtr(str.get()));
+ CHECK(JS::GCCellPtr(str.get()).kind() == JS::TraceKind::String);
+ CHECK(JS::GCCellPtr(JS::StringValue(str)).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);
+
+ CHECK(js::gc::detail::GetCellRuntime(scriptcell.asCell()) == cx->runtime());
+
+ 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 000000000..68faa3150
--- /dev/null
+++ b/js/src/jsapi-tests/testGCChunkPool.cpp
@@ -0,0 +1,71 @@
+/* -*- 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/Move.h"
+
+#include "gc/GCRuntime.h"
+#include "gc/Heap.h"
+
+#include "jsapi-tests/tests.h"
+
+BEGIN_TEST(testGCChunkPool)
+{
+ const int N = 10;
+ js::gc::ChunkPool pool;
+
+ // Create.
+ for (int i = 0; i < N; ++i) {
+ js::gc::Chunk* chunk = js::gc::Chunk::allocate(cx);
+ CHECK(chunk);
+ pool.push(chunk);
+ }
+ MOZ_ASSERT(pool.verify());
+
+ // Iterate.
+ uint32_t i = 0;
+ for (js::gc::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) {
+ js::gc::Chunk* chunkA = pool.pop();
+ js::gc::Chunk* chunkB = pool.pop();
+ js::gc::Chunk* chunkC = pool.pop();
+ pool.push(chunkA);
+ pool.push(chunkB);
+ pool.push(chunkC);
+ }
+ MOZ_ASSERT(pool.verify());
+
+ // Remove.
+ js::gc::Chunk* chunk = nullptr;
+ int offset = N / 2;
+ for (js::gc::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);
+ for (js::gc::ChunkPool::Iter iter(pool); !iter.done();) {
+ js::gc::Chunk* chunk = iter.get();
+ iter.next();
+ pool.remove(chunk);
+ js::gc::UnmapPages(chunk, js::gc::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 000000000..912e049d5
--- /dev/null
+++ b/js/src/jsapi-tests/testGCExactRooting.cpp
@@ -0,0 +1,410 @@
+/* -*- 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 "ds/TraceableFifo.h"
+#include "gc/Policy.h"
+#include "js/GCHashTable.h"
+#include "js/GCVector.h"
+#include "js/RootingAPI.h"
+
+#include "jsapi-tests/tests.h"
+
+using namespace js;
+
+BEGIN_TEST(testGCExactRooting)
+{
+ JS::RootedObject rootCx(cx, JS_NewPlainObject(cx));
+ JS::RootedObject rootRootingCx(JS::RootingContext::get(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);
+ JS_DefineProperty(cx, rootRootingCx, "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
+{
+ HeapPtr<JSObject*> obj;
+ HeapPtr<JSString*> str;
+
+ MyContainer() : obj(nullptr), str(nullptr) {}
+ void trace(JSTracer* trc) {
+ js::TraceNullableEdge(trc, &obj, "test container");
+ js::TraceNullableEdge(trc, &str, "test container");
+ }
+};
+
+namespace js {
+template <>
+struct RootedBase<MyContainer> {
+ HeapPtr<JSObject*>& obj() { return static_cast<Rooted<MyContainer>*>(this)->get().obj; }
+ HeapPtr<JSString*>& str() { return static_cast<Rooted<MyContainer>*>(this)->get().str; }
+};
+template <>
+struct PersistentRootedBase<MyContainer> {
+ HeapPtr<JSObject*>& obj() {
+ return static_cast<PersistentRooted<MyContainer>*>(this)->get().obj;
+ }
+ HeapPtr<JSString*>& str() {
+ return static_cast<PersistentRooted<MyContainer>*>(this)->get().str;
+ }
+};
+} // namespace js
+
+BEGIN_TEST(testGCRootedStaticStructInternalStackStorageAugmented)
+{
+ 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);
+
+ // clear prior rooting.
+ container.obj() = nullptr;
+ container.str() = nullptr;
+
+ obj = heap.obj();
+ CHECK(JS_GetProperty(cx, obj, "foo", &val));
+ actual = val.toString();
+ CHECK(JS_StringEqualsAscii(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_StringEqualsAscii(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));
+ CHECK(map.init(15));
+ CHECK(map.initialized());
+
+ 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->as<NativeObject>().lastProperty(), obj));
+ }
+
+ JS_GC(cx);
+ JS_GC(cx);
+
+ for (auto r = map.all(); !r.empty(); r.popFront()) {
+ RootedObject obj(cx, r.front().value());
+ CHECK(obj->as<NativeObject>().lastProperty() == r.front().key());
+ }
+
+ return true;
+}
+END_TEST(testGCRootedHashMap)
+
+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->as<NativeObject>().lastProperty(), 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->as<NativeObject>().lastProperty() != r.front().key())
+ return false;
+ }
+ return true;
+}
+
+BEGIN_TEST(testGCHandleHashMap)
+{
+ JS::Rooted<MyHashMap> map(cx, MyHashMap(cx));
+ CHECK(map.init(15));
+ CHECK(map.initialized());
+
+ CHECK(FillMyHashMap(cx, &map));
+
+ JS_GC(cx);
+ JS_GC(cx);
+
+ CHECK(CheckMyHashMap(cx, map));
+
+ return true;
+}
+END_TEST(testGCHandleHashMap)
+
+using ShapeVec = GCVector<Shape*>;
+
+BEGIN_TEST(testGCRootedVector)
+{
+ JS::Rooted<ShapeVec> shapes(cx, ShapeVec(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>().lastProperty()));
+ }
+
+ 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 buffer[2];
+ buffer[0] = 'a' + i;
+ buffer[1] = '\0';
+ bool match;
+ CHECK(JS_StringEqualsAscii(cx, JSID_TO_STRING(shapes[i]->propid()), buffer, &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<Shape*>>& rooted)
+{
+ // Ensure range enumeration works through the reference.
+ for (auto shape : rooted)
+ CHECK(shape);
+ return true;
+}
+
+bool
+receiveHandleToShapeVector(JS::Handle<GCVector<Shape*>> handle)
+{
+ // Ensure range enumeration works through the handle.
+ for (auto shape : handle)
+ CHECK(shape);
+ return true;
+}
+
+bool
+receiveMutableHandleToShapeVector(JS::MutableHandle<GCVector<Shape*>> 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<Shape*>;
+ 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>().lastProperty()));
+ }
+
+ 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 buffer[2];
+ buffer[0] = 'a' + i;
+ buffer[1] = '\0';
+ bool match;
+ CHECK(JS_StringEqualsAscii(cx, JSID_TO_STRING(shapes.front()->propid()), buffer, &match));
+ CHECK(match);
+ CHECK(shapes.popFront());
+ }
+
+ CHECK(shapes.empty());
+ return true;
+}
+END_TEST(testTraceableFifo)
+
+using ShapeVec = GCVector<Shape*>;
+
+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>().lastProperty()))
+ 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 buffer[2];
+ buffer[0] = 'a' + i;
+ buffer[1] = '\0';
+ bool match;
+ if (!JS_StringEqualsAscii(cx, JSID_TO_STRING(shapes[i]->propid()), buffer, &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)
diff --git a/js/src/jsapi-tests/testGCFinalizeCallback.cpp b/js/src/jsapi-tests/testGCFinalizeCallback.cpp
new file mode 100644
index 000000000..48003ab0c
--- /dev/null
+++ b/js/src/jsapi-tests/testGCFinalizeCallback.cpp
@@ -0,0 +1,210 @@
+/* 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 unsigned BufferSize = 20;
+static unsigned FinalizeCalls = 0;
+static JSFinalizeStatus StatusBuffer[BufferSize];
+static bool IsZoneGCBuffer[BufferSize];
+
+BEGIN_TEST(testGCFinalizeCallback)
+{
+ JS_SetGCParameter(cx, JSGC_MODE, JSGC_MODE_INCREMENTAL);
+
+ /* Full GC, non-incremental. */
+ FinalizeCalls = 0;
+ JS_GC(cx);
+ CHECK(cx->gc.isFullGc());
+ CHECK(checkSingleGroup());
+ CHECK(checkFinalizeStatus());
+ CHECK(checkFinalizeIsZoneGC(false));
+
+ /* Full GC, incremental. */
+ FinalizeCalls = 0;
+ JS::PrepareForFullGC(cx);
+ JS::StartIncrementalGC(cx, GC_NORMAL, JS::gcreason::API, 1000000);
+ while (cx->gc.isIncrementalGCInProgress()) {
+ JS::PrepareForFullGC(cx);
+ JS::IncrementalGCSlice(cx, JS::gcreason::API, 1000000);
+ }
+ CHECK(!cx->gc.isIncrementalGCInProgress());
+ CHECK(cx->gc.isFullGc());
+ CHECK(checkMultipleGroups());
+ CHECK(checkFinalizeStatus());
+ CHECK(checkFinalizeIsZoneGC(false));
+
+ 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(global1->zone());
+ JS::GCForReason(cx, GC_NORMAL, JS::gcreason::API);
+ CHECK(!cx->gc.isFullGc());
+ CHECK(checkSingleGroup());
+ CHECK(checkFinalizeStatus());
+ CHECK(checkFinalizeIsZoneGC(true));
+
+ /* Zone GC, non-incremental, multiple zones. */
+ FinalizeCalls = 0;
+ JS::PrepareZoneForGC(global1->zone());
+ JS::PrepareZoneForGC(global2->zone());
+ JS::PrepareZoneForGC(global3->zone());
+ JS::GCForReason(cx, GC_NORMAL, JS::gcreason::API);
+ CHECK(!cx->gc.isFullGc());
+ CHECK(checkSingleGroup());
+ CHECK(checkFinalizeStatus());
+ CHECK(checkFinalizeIsZoneGC(true));
+
+ /* Zone GC, incremental, single zone. */
+ FinalizeCalls = 0;
+ JS::PrepareZoneForGC(global1->zone());
+ JS::StartIncrementalGC(cx, GC_NORMAL, JS::gcreason::API, 1000000);
+ while (cx->gc.isIncrementalGCInProgress()) {
+ JS::PrepareZoneForGC(global1->zone());
+ JS::IncrementalGCSlice(cx, JS::gcreason::API, 1000000);
+ }
+ CHECK(!cx->gc.isIncrementalGCInProgress());
+ CHECK(!cx->gc.isFullGc());
+ CHECK(checkSingleGroup());
+ CHECK(checkFinalizeStatus());
+ CHECK(checkFinalizeIsZoneGC(true));
+
+ /* Zone GC, incremental, multiple zones. */
+ FinalizeCalls = 0;
+ JS::PrepareZoneForGC(global1->zone());
+ JS::PrepareZoneForGC(global2->zone());
+ JS::PrepareZoneForGC(global3->zone());
+ JS::StartIncrementalGC(cx, GC_NORMAL, JS::gcreason::API, 1000000);
+ while (cx->gc.isIncrementalGCInProgress()) {
+ JS::PrepareZoneForGC(global1->zone());
+ JS::PrepareZoneForGC(global2->zone());
+ JS::PrepareZoneForGC(global3->zone());
+ JS::IncrementalGCSlice(cx, JS::gcreason::API, 1000000);
+ }
+ CHECK(!cx->gc.isIncrementalGCInProgress());
+ CHECK(!cx->gc.isFullGc());
+ CHECK(checkMultipleGroups());
+ CHECK(checkFinalizeStatus());
+ CHECK(checkFinalizeIsZoneGC(true));
+
+#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);
+ js::SliceBudget budget(js::WorkBudget(1));
+ cx->gc.startDebugGC(GC_NORMAL, budget);
+ CHECK(cx->gc.state() == js::gc::State::Mark);
+ CHECK(cx->gc.isFullGc());
+
+ JS::RootedObject global4(cx, createTestGlobal());
+ budget = js::SliceBudget(js::WorkBudget(1));
+ cx->gc.debugGCSlice(budget);
+ while (cx->gc.isIncrementalGCInProgress())
+ cx->gc.debugGCSlice(budget);
+ CHECK(!cx->gc.isIncrementalGCInProgress());
+ CHECK(!cx->gc.isFullGc());
+ CHECK(checkMultipleGroups());
+ CHECK(checkFinalizeStatus());
+
+ for (unsigned i = 0; i < FinalizeCalls - 1; ++i)
+ CHECK(!IsZoneGCBuffer[i]);
+ CHECK(IsZoneGCBuffer[FinalizeCalls - 1]);
+
+ 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::CompartmentOptions options;
+ return JS_NewGlobalObject(cx, getGlobalClass(), nullptr, JS::FireOnNewGlobalHook, options);
+}
+
+virtual bool init() override
+{
+ if (!JSAPITest::init())
+ return false;
+
+ JS_AddFinalizeCallback(cx, FinalizeCallback, nullptr);
+ return true;
+}
+
+virtual void uninit() override
+{
+ JS_RemoveFinalizeCallback(cx, FinalizeCallback);
+ JSAPITest::uninit();
+}
+
+bool checkSingleGroup()
+{
+ CHECK(FinalizeCalls < BufferSize);
+ CHECK(FinalizeCalls == 3);
+ return true;
+}
+
+bool checkMultipleGroups()
+{
+ CHECK(FinalizeCalls < BufferSize);
+ CHECK(FinalizeCalls % 2 == 1);
+ CHECK((FinalizeCalls - 1) / 2 > 1);
+ return true;
+}
+
+bool checkFinalizeStatus()
+{
+ /*
+ * The finalize callback should be called twice for each zone 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 += 2) {
+ CHECK(StatusBuffer[i] == JSFINALIZE_GROUP_START);
+ CHECK(StatusBuffer[i + 1] == JSFINALIZE_GROUP_END);
+ }
+
+ CHECK(StatusBuffer[FinalizeCalls - 1] == JSFINALIZE_COLLECTION_END);
+
+ return true;
+}
+
+bool checkFinalizeIsZoneGC(bool isZoneGC)
+{
+ for (unsigned i = 0; i < FinalizeCalls; ++i)
+ CHECK(IsZoneGCBuffer[i] == isZoneGC);
+
+ return true;
+}
+
+static void
+FinalizeCallback(JSFreeOp* fop, JSFinalizeStatus status, bool isZoneGC, void* data)
+{
+ if (FinalizeCalls < BufferSize) {
+ StatusBuffer[FinalizeCalls] = status;
+ IsZoneGCBuffer[FinalizeCalls] = isZoneGC;
+ }
+ ++FinalizeCalls;
+}
+END_TEST(testGCFinalizeCallback)
diff --git a/js/src/jsapi-tests/testGCHeapPostBarriers.cpp b/js/src/jsapi-tests/testGCHeapPostBarriers.cpp
new file mode 100644
index 000000000..74512a53f
--- /dev/null
+++ b/js/src/jsapi-tests/testGCHeapPostBarriers.cpp
@@ -0,0 +1,153 @@
+/* -*- 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/UniquePtr.h"
+
+#include "js/RootingAPI.h"
+#include "jsapi-tests/tests.h"
+#include "vm/Runtime.h"
+
+template <typename T>
+static T* CreateGCThing(JSContext* cx)
+{
+ MOZ_CRASH();
+ return nullptr;
+}
+
+template <>
+JSObject* CreateGCThing(JSContext* cx)
+{
+ JS::RootedObject obj(cx, JS_NewPlainObject(cx));
+ if (!obj)
+ return nullptr;
+ JS_DefineProperty(cx, obj, "x", 42, 0);
+ return obj;
+}
+
+template <>
+JSFunction* CreateGCThing(JSContext* cx)
+{
+ /*
+ * We don't actually use the function as a function, so here we cheat and
+ * cast a JSObject.
+ */
+ return static_cast<JSFunction*>(CreateGCThing<JSObject>(cx));
+}
+
+BEGIN_TEST(testGCHeapPostBarriers)
+{
+#ifdef JS_GC_ZEAL
+ AutoLeaveZeal nozeal(cx);
+#endif /* JS_GC_ZEAL */
+
+ /* Sanity check - objects start in the nursery and then become tenured. */
+ JS_GC(cx);
+ JS::RootedObject obj(cx, CreateGCThing<JSObject>(cx));
+ CHECK(js::gc::IsInsideNursery(obj.get()));
+ JS_GC(cx);
+ CHECK(!js::gc::IsInsideNursery(obj.get()));
+ JS::RootedObject tenuredObject(cx, obj);
+
+ /* Currently JSObject and JSFunction objects are nursery allocated. */
+ CHECK(TestHeapPostBarriersForType<JSObject>());
+ CHECK(TestHeapPostBarriersForType<JSFunction>());
+
+ 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;
+}
+
+template <typename T>
+bool
+TestHeapPostBarriersForType()
+{
+ CHECK((TestHeapPostBarriersForWrapper<T, JS::Heap<T*>>()));
+ CHECK((TestHeapPostBarriersForWrapper<T, js::GCPtr<T*>>()));
+ CHECK((TestHeapPostBarriersForWrapper<T, js::HeapPtr<T*>>()));
+ return true;
+}
+
+template <typename T, typename W>
+bool
+TestHeapPostBarriersForWrapper()
+{
+ CHECK((TestHeapPostBarrierUpdate<T, W>()));
+ CHECK((TestHeapPostBarrierInitFailure<T, W>()));
+ return true;
+}
+
+template <typename T, typename W>
+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 = CreateGCThing<T>(cx);
+ CHECK(initialObj != nullptr);
+ CHECK(js::gc::IsInsideNursery(initialObj));
+ uintptr_t initialObjAsInt = uintptr_t(initialObj);
+
+ W* ptr = nullptr;
+
+ {
+ auto heapPtr = cx->make_unique<W>();
+ CHECK(heapPtr);
+
+ W& wrapper = *heapPtr;
+ CHECK(wrapper.get() == nullptr);
+ wrapper = initialObj;
+ CHECK(wrapper == initialObj);
+
+ ptr = heapPtr.release();
+ }
+
+ cx->minorGC(JS::gcreason::API);
+
+ CHECK(uintptr_t(ptr->get()) != initialObjAsInt);
+ CHECK(!js::gc::IsInsideNursery(ptr->get()));
+ CHECK(CanAccessObject(ptr->get()));
+
+ return true;
+}
+
+template <typename T, typename W>
+bool
+TestHeapPostBarrierInitFailure()
+{
+ // Failure case - allocate a heap object, write a nursery pointer into it
+ // and fail to complete initialization.
+
+ T* initialObj = CreateGCThing<T>(cx);
+ CHECK(initialObj != nullptr);
+ CHECK(js::gc::IsInsideNursery(initialObj));
+
+ {
+ auto heapPtr = cx->make_unique<W>();
+ CHECK(heapPtr);
+
+ W& wrapper = *heapPtr;
+ CHECK(wrapper.get() == nullptr);
+ wrapper = initialObj;
+ CHECK(wrapper == initialObj);
+ }
+
+ cx->minorGC(JS::gcreason::API);
+
+ return true;
+}
+
+END_TEST(testGCHeapPostBarriers)
diff --git a/js/src/jsapi-tests/testGCHooks.cpp b/js/src/jsapi-tests/testGCHooks.cpp
new file mode 100644
index 000000000..2c6cc0b1f
--- /dev/null
+++ b/js/src/jsapi-tests/testGCHooks.cpp
@@ -0,0 +1,36 @@
+/* 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/UniquePtr.h"
+
+#include "js/GCAPI.h"
+
+#include "jsapi-tests/tests.h"
+
+static unsigned gSliceCallbackCount = 0;
+
+static void
+NonIncrementalGCSliceCallback(JSContext* cx, JS::GCProgress progress, const JS::GCDescription& desc)
+{
+ ++gSliceCallbackCount;
+ MOZ_RELEASE_ASSERT(progress == JS::GC_CYCLE_BEGIN || progress == JS::GC_CYCLE_END);
+ MOZ_RELEASE_ASSERT(desc.isZone_ == false);
+ MOZ_RELEASE_ASSERT(desc.invocationKind_ == GC_NORMAL);
+ MOZ_RELEASE_ASSERT(desc.reason_ == JS::gcreason::API);
+ if (progress == JS::GC_CYCLE_END) {
+ mozilla::UniquePtr<char16_t> summary(desc.formatSummaryMessage(cx));
+ mozilla::UniquePtr<char16_t> message(desc.formatSliceMessage(cx));
+ mozilla::UniquePtr<char16_t> json(desc.formatJSON(cx, 0));
+ }
+}
+
+BEGIN_TEST(testGCSliceCallback)
+{
+ JS::SetGCSliceCallback(cx, NonIncrementalGCSliceCallback);
+ JS_GC(cx);
+ JS::SetGCSliceCallback(cx, nullptr);
+ CHECK(gSliceCallbackCount == 2);
+ return true;
+}
+END_TEST(testGCSliceCallback)
diff --git a/js/src/jsapi-tests/testGCMarking.cpp b/js/src/jsapi-tests/testGCMarking.cpp
new file mode 100644
index 000000000..684397392
--- /dev/null
+++ b/js/src/jsapi-tests/testGCMarking.cpp
@@ -0,0 +1,423 @@
+/* -*- 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 "jsapi.h"
+#include "jscompartment.h"
+
+#include "js/RootingAPI.h"
+#include "js/SliceBudget.h"
+#include "jsapi-tests/tests.h"
+
+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::CompartmentOptions 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.
+ {
+ JSAutoCompartment ac(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 : public JS::CallbackTracer {
+ void onChild(const JS::GCCellPtr& thing) 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.init());
+ CHECK(compartments.put(global2->compartment()));
+
+ void* thing = wrappee.get();
+ CCWTestTracer trc(cx, &thing, JS::TraceKind::Object);
+ JS::TraceIncomingCCWs(&trc, compartments);
+ CHECK(trc.numberOfThingsTraced == 1);
+ CHECK(trc.okay);
+
+ return true;
+}
+END_TEST(testTracingIncomingCCWs)
+
+static size_t
+countWrappers(JSCompartment* comp)
+{
+ size_t count = 0;
+ for (JSCompartment::WrapperEnum 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(countWrappers(global1->compartment()) == 1);
+ cx->gc.evictNursery();
+ CHECK(countWrappers(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(countWrappers(global1->compartment()) == 1);
+ cx->gc.evictNursery();
+ CHECK(countWrappers(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(countWrappers(global1->compartment()) == 1);
+ cx->gc.evictNursery();
+ CHECK(countWrappers(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(countWrappers(global1->compartment()) == 1);
+ cx->gc.evictNursery();
+ CHECK(countWrappers(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::AutoObjectVector vec(cx);
+ if (!vec.append(root))
+ return false;
+
+ // Tenure everything so intentionally unrooted objects don't move before we
+ // can use them.
+ cx->minorGC(JS::gcreason::API);
+
+ // Release all roots except for the AutoObjectVector.
+ 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));
+ JS_SetGCParameter(cx, JSGC_MODE, JSGC_MODE_INCREMENTAL);
+ rt->gc.startDebugGC(GC_NORMAL, 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().isMarked());
+ MOZ_ASSERT(!leafHandle->asTenured().isMarked());
+ MOZ_ASSERT(!leafOwnerHandle->asTenured().isMarked());
+
+#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().isMarked());
+
+ // 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().isMarked());
+ if (!JS_SetProperty(cx, vec[0], "leafcopy", leaf2))
+ return false;
+ MOZ_ASSERT(rt->gc.gcNumber() == currentGCNumber);
+ MOZ_ASSERT(!leaf2.toObject().asTenured().isMarked());
+ }
+
+ // 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 000000000..9f8302890
--- /dev/null
+++ b/js/src/jsapi-tests/testGCOutOfMemory.cpp
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ *
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ * Contributor: Igor Bukanov
+ */
+
+#include "jsapi-tests/tests.h"
+
+BEGIN_TEST(testGCOutOfMemory)
+{
+ JS::RootedValue root(cx);
+
+ // 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);
+ bool ok = JS::Evaluate(cx, opts, source, strlen(source), &root);
+
+ /* Check that we get OOM. */
+ CHECK(!ok);
+ CHECK(JS_GetPendingException(cx, &root));
+ CHECK(root.isString());
+ bool match = false;
+ CHECK(JS_StringEqualsAscii(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(1024 * 1024, 128 * 1024);
+ if (!cx)
+ return nullptr;
+ setNativeStackQuota(cx);
+ return cx;
+}
+
+virtual void destroyContext() override {
+ JS_DestroyContext(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 000000000..70e0b9fd0
--- /dev/null
+++ b/js/src/jsapi-tests/testGCStoreBufferRemoval.cpp
@@ -0,0 +1,121 @@
+/* -*- 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 "gc/Barrier.h"
+#include "jsapi-tests/tests.h"
+
+using namespace JS;
+using namespace js;
+
+struct AutoIgnoreRootingHazards {
+ // Force a nontrivial destructor so the compiler sees the whole RAII scope
+ static volatile int depth;
+ AutoIgnoreRootingHazards() { depth++; }
+ ~AutoIgnoreRootingHazards() { depth--; }
+} JS_HAZ_GC_SUPPRESSED;
+volatile int AutoIgnoreRootingHazards::depth = 0;
+
+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);
+
+ // Hide the horrors herein from the static rooting analysis.
+ AutoIgnoreRootingHazards ignore;
+
+ // Test removal of store buffer entries added by HeapPtr<T>.
+ {
+ JSObject* badObject = reinterpret_cast<JSObject*>(1);
+ JSObject* punnedPtr = nullptr;
+ HeapPtr<JSObject*>* relocPtr =
+ reinterpret_cast<HeapPtr<JSObject*>*>(&punnedPtr);
+ new (relocPtr) HeapPtr<JSObject*>;
+ *relocPtr = NurseryObject();
+ relocPtr->~HeapPtr<JSObject*>();
+ punnedPtr = badObject;
+ JS_GC(cx);
+
+ new (relocPtr) HeapPtr<JSObject*>;
+ *relocPtr = NurseryObject();
+ *relocPtr = tenuredObject;
+ relocPtr->~HeapPtr<JSObject*>();
+ punnedPtr = badObject;
+ JS_GC(cx);
+
+ new (relocPtr) HeapPtr<JSObject*>;
+ *relocPtr = NurseryObject();
+ *relocPtr = nullptr;
+ relocPtr->~HeapPtr<JSObject*>();
+ punnedPtr = badObject;
+ 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 = ObjectValueCrashOnTouch();
+ JS_GC(cx);
+
+ new (relocValue) HeapPtr<Value>;
+ *relocValue = ObjectValue(*NurseryObject());
+ *relocValue = ObjectValue(*tenuredObject);
+ relocValue->~HeapPtr<Value>();
+ punnedValue = ObjectValueCrashOnTouch();
+ JS_GC(cx);
+
+ new (relocValue) HeapPtr<Value>;
+ *relocValue = ObjectValue(*NurseryObject());
+ *relocValue = NullValue();
+ relocValue->~HeapPtr<Value>();
+ punnedValue = ObjectValueCrashOnTouch();
+ JS_GC(cx);
+ }
+
+ // Test removal of store buffer entries added by Heap<T>.
+ {
+ JSObject* badObject = reinterpret_cast<JSObject*>(1);
+ JSObject* punnedPtr = nullptr;
+ Heap<JSObject*>* heapPtr =
+ reinterpret_cast<Heap<JSObject*>*>(&punnedPtr);
+ new (heapPtr) Heap<JSObject*>;
+ *heapPtr = NurseryObject();
+ heapPtr->~Heap<JSObject*>();
+ punnedPtr = badObject;
+ JS_GC(cx);
+
+ new (heapPtr) Heap<JSObject*>;
+ *heapPtr = NurseryObject();
+ *heapPtr = tenuredObject;
+ heapPtr->~Heap<JSObject*>();
+ punnedPtr = badObject;
+ JS_GC(cx);
+
+ new (heapPtr) Heap<JSObject*>;
+ *heapPtr = NurseryObject();
+ *heapPtr = nullptr;
+ heapPtr->~Heap<JSObject*>();
+ punnedPtr = badObject;
+ JS_GC(cx);
+ }
+
+ return true;
+}
+
+JSObject* NurseryObject()
+{
+ return JS_NewPlainObject(cx);
+}
+END_TEST(testGCStoreBufferRemoval)
diff --git a/js/src/jsapi-tests/testGCUniqueId.cpp b/js/src/jsapi-tests/testGCUniqueId.cpp
new file mode 100644
index 000000000..71d66b722
--- /dev/null
+++ b/js/src/jsapi-tests/testGCUniqueId.cpp
@@ -0,0 +1,123 @@
+/* -*- 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 "gc/GCInternals.h"
+#include "gc/Zone.h"
+#include "js/GCVector.h"
+
+#include "jsapi-tests/tests.h"
+
+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)
+{
+#ifdef JS_GC_ZEAL
+ AutoLeaveZeal nozeal(cx);
+#endif /* JS_GC_ZEAL */
+
+ 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(js::gc::IsInsideNursery(obj));
+
+ // Do not start with an ID.
+ CHECK(!obj->zone()->hasUniqueId(obj));
+
+ // Ensure we can get a new UID.
+ CHECK(obj->zone()->getUniqueId(obj, &uid));
+ CHECK(uid > js::gc::LargestTaggedNullCellPointer);
+
+ // We should now have an id.
+ CHECK(obj->zone()->hasUniqueId(obj));
+
+ // Calling again should get us the same thing.
+ CHECK(obj->zone()->getUniqueId(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(!js::gc::IsInsideNursery(obj));
+ CHECK(obj->zone()->hasUniqueId(obj));
+ CHECK(obj->zone()->getUniqueId(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(!obj->zone()->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(!obj->zone()->hasUniqueId(obj));
+ CHECK(obj->zone()->getUniqueId(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)
+ vec2.append(vec[i]);
+ }
+ vec.clear();
+ MinimizeHeap(cx);
+
+ // Grab the last object in the vector as our object of interest.
+ obj = vec2.back();
+ CHECK(obj);
+ tenuredAddr = uintptr_t(obj.get());
+ CHECK(obj->zone()->getUniqueId(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::GCForReason(cx, GC_SHRINK, JS::gcreason::API);
+ MinimizeHeap(cx);
+ CHECK(uintptr_t(obj.get()) != tenuredAddr);
+ CHECK(obj->zone()->hasUniqueId(obj));
+ CHECK(obj->zone()->getUniqueId(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 000000000..b599cc839
--- /dev/null
+++ b/js/src/jsapi-tests/testGCWeakCache.cpp
@@ -0,0 +1,94 @@
+/* -*- 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 "gc/Policy.h"
+#include "js/GCHashTable.h"
+#include "js/RootingAPI.h"
+#include "js/SweepingAPI.h"
+
+#include "jsapi-tests/tests.h"
+
+// 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 = js::GCHashSet<JS::Heap<JSObject*>,
+ js::MovableCellHasher<JS::Heap<JSObject*>>,
+ js::SystemAllocPolicy>;
+ using Cache = JS::WeakCache<ObjectSet>;
+ auto cache = Cache(JS::GetObjectZone(tenured1), ObjectSet());
+ CHECK(cache.init());
+
+ 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<JS::Heap<JSObject*>, uint32_t,
+ js::MovableCellHasher<JS::Heap<JSObject*>>>;
+ using Cache = JS::WeakCache<ObjectMap>;
+ auto cache = Cache(JS::GetObjectZone(tenured1), ObjectMap(cx));
+ CHECK(cache.init());
+
+ 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)
diff --git a/js/src/jsapi-tests/testGCWeakRef.cpp b/js/src/jsapi-tests/testGCWeakRef.cpp
new file mode 100644
index 000000000..d658106b2
--- /dev/null
+++ b/js/src/jsapi-tests/testGCWeakRef.cpp
@@ -0,0 +1,65 @@
+/* -*- 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 "gc/Barrier.h"
+#include "js/RootingAPI.h"
+
+#include "jsapi-tests/tests.h"
+
+struct MyHeap
+{
+ explicit MyHeap(JSObject* obj) : weak(obj) {}
+ js::WeakRef<JSObject*> weak;
+
+ void trace(JSTracer* trc) {
+ js::TraceWeakEdge(trc, &weak, "weak");
+ }
+};
+
+BEGIN_TEST(testGCWeakRef)
+{
+ // Create an object and add a property to it so that we can read the
+ // property back later to verify that object internals are not garbage.
+ JS::RootedObject obj(cx, JS_NewPlainObject(cx));
+ CHECK(obj);
+ CHECK(JS_DefineProperty(cx, obj, "x", 42, 0));
+
+ // Store the object behind a weak pointer and remove other references.
+ JS::Rooted<MyHeap> heap(cx, MyHeap(obj));
+ obj = nullptr;
+
+ cx->gc.minorGC(JS::gcreason::API);
+
+ // The minor collection should have treated the weak ref as a strong ref,
+ // so the object should still be live, despite not having any other live
+ // references.
+ CHECK(heap.get().weak.unbarrieredGet() != nullptr);
+ obj = heap.get().weak;
+ JS::RootedValue v(cx);
+ CHECK(JS_GetProperty(cx, obj, "x", &v));
+ CHECK(v.isInt32());
+ CHECK(v.toInt32() == 42);
+
+ // A full collection with a second ref should keep the object as well.
+ CHECK(obj == heap.get().weak);
+ JS_GC(cx);
+ CHECK(obj == heap.get().weak);
+ v = JS::UndefinedValue();
+ CHECK(JS_GetProperty(cx, obj, "x", &v));
+ CHECK(v.isInt32());
+ CHECK(v.toInt32() == 42);
+
+ // A full collection after nulling the root should collect the object, or
+ // at least null out the weak reference before returning to the mutator.
+ obj = nullptr;
+ JS_GC(cx);
+ CHECK(heap.get().weak == nullptr);
+
+ return true;
+}
+END_TEST(testGCWeakRef)
+
diff --git a/js/src/jsapi-tests/testGetPropertyDescriptor.cpp b/js/src/jsapi-tests/testGetPropertyDescriptor.cpp
new file mode 100644
index 000000000..6310eb6ca
--- /dev/null
+++ b/js/src/jsapi-tests/testGetPropertyDescriptor.cpp
@@ -0,0 +1,50 @@
+/* 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(test_GetPropertyDescriptor)
+{
+ JS::RootedValue v(cx);
+ EVAL("({ somename : 123 })", &v);
+ CHECK(v.isObject());
+
+ JS::RootedObject obj(cx, &v.toObject());
+ JS::Rooted<JS::PropertyDescriptor> desc(cx);
+
+ CHECK(JS_GetPropertyDescriptor(cx, obj, "somename", &desc));
+ CHECK_EQUAL(desc.object(), obj);
+ 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));
+ CHECK_EQUAL(desc.object(), nullptr);
+
+ CHECK(JS_GetPropertyDescriptor(cx, obj, "toString", &desc));
+ JS::RootedObject objectProto(cx, JS_GetObjectPrototype(cx, obj));
+ CHECK(objectProto);
+ CHECK_EQUAL(desc.object(), 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 000000000..a22ff7847
--- /dev/null
+++ b/js/src/jsapi-tests/testHashTable.cpp
@@ -0,0 +1,390 @@
+/* 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/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
+
+JS_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 (IntMap::Range r = am.all(); !r.empty(); r.popFront()) {
+ if (!bm.has(r.front().key())) {
+ equal = false;
+ fprintf(stderr, "B does not have %x which is in A\n", r.front().key());
+ }
+ }
+ for (IntMap::Range r = bm.all(); !r.empty(); r.popFront()) {
+ if (!am.has(r.front().key())) {
+ equal = false;
+ fprintf(stderr, "A does not have %x which is in B\n", r.front().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 (IntSet::Range r = am.all(); !r.empty(); r.popFront()) {
+ if (!bm.has(r.front())) {
+ equal = false;
+ fprintf(stderr, "B does not have %x which is in A\n", r.front());
+ }
+ }
+ for (IntSet::Range r = bm.all(); !r.empty(); r.popFront()) {
+ if (!am.has(r.front())) {
+ equal = false;
+ fprintf(stderr, "A does not have %x which is in B\n", r.front());
+ }
+ }
+ 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;
+ if (!tmp.init())
+ return false;
+
+ for (IntMap::Range r = m->all(); !r.empty(); r.popFront()) {
+ if (NewKeyFunction::shouldBeRemoved(r.front().key()))
+ continue;
+ uint32_t hi = NewKeyFunction::rekey(r.front().key());
+ if (tmp.has(hi))
+ return false;
+ if (!tmp.putNew(hi, r.front().value()))
+ return false;
+ }
+
+ m->clear();
+ for (IntMap::Range r = tmp.all(); !r.empty(); r.popFront()) {
+ if (!m->putNew(r.front().key(), r.front().value()))
+ return false;
+ }
+
+ return true;
+}
+
+template <class NewKeyFunction>
+static bool
+SlowRekey(IntSet* s) {
+ IntSet tmp;
+ if (!tmp.init())
+ return false;
+
+ for (IntSet::Range r = s->all(); !r.empty(); r.popFront()) {
+ if (NewKeyFunction::shouldBeRemoved(r.front()))
+ continue;
+ uint32_t hi = NewKeyFunction::rekey(r.front());
+ if (tmp.has(hi))
+ return false;
+ if (!tmp.putNew(hi))
+ return false;
+ }
+
+ s->clear();
+ for (IntSet::Range r = tmp.all(); !r.empty(); r.popFront()) {
+ if (!s->putNew(r.front()))
+ return false;
+ }
+
+ return true;
+}
+
+BEGIN_TEST(testHashRekeyManual)
+{
+ IntMap am, bm;
+ CHECK(am.init());
+ CHECK(bm.init());
+ 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 (IntMap::Enum e(am); !e.empty(); e.popFront()) {
+ uint32_t tmp = LowToHigh::rekey(e.front().key());
+ if (tmp != e.front().key())
+ e.rekeyFront(tmp);
+ }
+ CHECK(SlowRekey<LowToHigh>(&bm));
+
+ CHECK(MapsAreEqual(am, bm));
+ am.clear();
+ bm.clear();
+ }
+
+ IntSet as, bs;
+ CHECK(as.init());
+ CHECK(bs.init());
+ 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 (IntSet::Enum e(as); !e.empty(); e.popFront()) {
+ uint32_t tmp = LowToHigh::rekey(e.front());
+ if (tmp != e.front())
+ e.rekeyFront(tmp);
+ }
+ CHECK(SlowRekey<LowToHigh>(&bs));
+
+ CHECK(SetsAreEqual(as, bs));
+ as.clear();
+ bs.clear();
+ }
+
+ return true;
+}
+END_TEST(testHashRekeyManual)
+
+BEGIN_TEST(testHashRekeyManualRemoval)
+{
+ IntMap am, bm;
+ CHECK(am.init());
+ CHECK(bm.init());
+ 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 (IntMap::Enum e(am); !e.empty(); e.popFront()) {
+ if (LowToHighWithRemoval::shouldBeRemoved(e.front().key())) {
+ e.removeFront();
+ } else {
+ uint32_t tmp = LowToHighWithRemoval::rekey(e.front().key());
+ if (tmp != e.front().key())
+ e.rekeyFront(tmp);
+ }
+ }
+ CHECK(SlowRekey<LowToHighWithRemoval>(&bm));
+
+ CHECK(MapsAreEqual(am, bm));
+ am.clear();
+ bm.clear();
+ }
+
+ IntSet as, bs;
+ CHECK(as.init());
+ CHECK(bs.init());
+ 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 (IntSet::Enum e(as); !e.empty(); e.popFront()) {
+ if (LowToHighWithRemoval::shouldBeRemoved(e.front())) {
+ e.removeFront();
+ } else {
+ uint32_t tmp = LowToHighWithRemoval::rekey(e.front());
+ if (tmp != e.front())
+ e.rekeyFront(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(mozilla::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;
+ CHECK(set.init());
+
+ MoveOnlyType a(1);
+
+ CHECK(set.put(mozilla::Move(a))); // This shouldn't generate a compiler error.
+
+ return true;
+}
+END_TEST(testHashSetOfMoveOnlyType)
+
+#if defined(DEBUG)
+
+// Add entries to a HashMap using lookupWithDefault until either we get an OOM,
+// or the table has been resized a few times.
+static bool
+LookupWithDefaultUntilResize() {
+ IntMap m;
+
+ if (!m.init())
+ return false;
+
+ // 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) {
+ if (!m.lookupWithDefault(key++, 0))
+ return false;
+
+ size_t capacity = m.capacity();
+ if (capacity != lastCapacity) {
+ resizes++;
+ lastCapacity = capacity;
+ }
+ }
+
+ return true;
+}
+
+BEGIN_TEST(testHashMapLookupWithDefaultOOM)
+{
+ uint32_t timeToFail;
+ for (timeToFail = 1; timeToFail < 1000; timeToFail++) {
+ js::oom::SimulateOOMAfter(timeToFail, js::oom::THREAD_TYPE_MAIN, false);
+ LookupWithDefaultUntilResize();
+ }
+
+ js::oom::ResetSimulatedOOM();
+ return true;
+}
+
+END_TEST(testHashMapLookupWithDefaultOOM)
+#endif // defined(DEBUG)
diff --git a/js/src/jsapi-tests/testIndexToString.cpp b/js/src/jsapi-tests/testIndexToString.cpp
new file mode 100644
index 000000000..067ad8712
--- /dev/null
+++ b/js/src/jsapi-tests/testIndexToString.cpp
@@ -0,0 +1,117 @@
+/* -*- 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 "jscntxt.h"
+#include "jscompartment.h"
+#include "jsnum.h"
+#include "jsstr.h"
+
+#include "jsapi-tests/tests.h"
+
+#include "vm/String-inl.h"
+
+using mozilla::ArrayLength;
+
+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" },
+ { 4294967295u, "4294967295" },
+};
+
+BEGIN_TEST(testIndexToString)
+{
+ for (size_t i = 0, sz = ArrayLength(tests); i < sz; i++) {
+ uint32_t u = tests[i].num;
+ JSString* str = js::IndexToString(cx, u);
+ CHECK(str);
+
+ if (!js::StaticStrings::hasUint(u))
+ CHECK(cx->compartment()->dtoaCache.lookup(10, u) == str);
+
+ bool match = false;
+ CHECK(JS_StringEqualsAscii(cx, str, tests[i].expected, &match));
+ CHECK(match);
+ }
+
+ return true;
+}
+END_TEST(testIndexToString)
+
+BEGIN_TEST(testStringIsIndex)
+{
+ for (size_t i = 0, sz = ArrayLength(tests); i < sz; i++) {
+ uint32_t u = tests[i].num;
+ JSFlatString* 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' };
+ JSFlatString* 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', '5' };
+ JSFlatString* maxStr = NewString(cx, maxChars);
+ CHECK(maxStr);
+ CHECK(maxStr->isIndex(&index));
+ CHECK(index == UINT32_MAX);
+
+ static const char16_t maxPlusOneChars[] = { '4', '2', '9', '4', '9', '6', '7', '2', '9', '6' };
+ JSFlatString* maxPlusOneStr = NewString(cx, maxPlusOneChars);
+ CHECK(maxPlusOneStr);
+ CHECK(!maxPlusOneStr->isIndex(&index));
+ CHECK(maxPlusOneStr->toPropertyName(cx) != nullptr);
+
+ return true;
+}
+
+template<size_t N> static JSFlatString*
+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/testIntString.cpp b/js/src/jsapi-tests/testIntString.cpp
new file mode 100644
index 000000000..646420bee
--- /dev/null
+++ b/js/src/jsapi-tests/testIntString.cpp
@@ -0,0 +1,42 @@
+/* -*- 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 "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_FlatStringEqualsAscii(JS_ASSERT_STRING_IS_FLAT(str), "1"));
+
+ EVAL("'42';", &v);
+ str = v.toString();
+ CHECK(JS_StringHasBeenPinned(cx, str));
+ CHECK(JS_FlatStringEqualsAscii(JS_ASSERT_STRING_IS_FLAT(str), "42"));
+
+ EVAL("'111';", &v);
+ str = v.toString();
+ CHECK(JS_StringHasBeenPinned(cx, str));
+ CHECK(JS_FlatStringEqualsAscii(JS_ASSERT_STRING_IS_FLAT(str), "111"));
+
+ /* Test other types of static strings. */
+ EVAL("'a';", &v);
+ str = v.toString();
+ CHECK(JS_StringHasBeenPinned(cx, str));
+ CHECK(JS_FlatStringEqualsAscii(JS_ASSERT_STRING_IS_FLAT(str), "a"));
+
+ EVAL("'bc';", &v);
+ str = v.toString();
+ CHECK(JS_StringHasBeenPinned(cx, str));
+ CHECK(JS_FlatStringEqualsAscii(JS_ASSERT_STRING_IS_FLAT(str), "bc"));
+
+ return true;
+}
+END_TEST(testIntString_bug515273)
diff --git a/js/src/jsapi-tests/testIntTypesABI.cpp b/js/src/jsapi-tests/testIntTypesABI.cpp
new file mode 100644
index 000000000..066e7ad1b
--- /dev/null
+++ b/js/src/jsapi-tests/testIntTypesABI.cpp
@@ -0,0 +1,88 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * This test exercises the full, deliberately-exposed JSAPI interface to ensure
+ * that no internal integer typedefs leak out. Include every intentionally
+ * public header file (and those headers included by them, for completeness),
+ * even the ones tests.h itself included, to verify this.
+ */
+
+#include "jscpucfg.h"
+#include "jspubtd.h"
+#include "jstypes.h"
+
+#include "js/CallArgs.h"
+#include "js/CallNonGenericMethod.h"
+#include "js/CharacterEncoding.h"
+#include "js/Class.h"
+#include "js/Date.h"
+#include "js/Debug.h"
+#include "js/GCAPI.h"
+#include "js/HashTable.h"
+#include "js/HeapAPI.h"
+#include "js/Id.h"
+/* LegacyIntTypes.h is deliberately exempted from this requirement */
+#include "js/MemoryMetrics.h"
+#include "js/ProfilingStack.h"
+#include "js/RequiredDefines.h"
+#include "js/RootingAPI.h"
+#include "js/SliceBudget.h"
+#include "js/StructuredClone.h"
+#include "js/TracingAPI.h"
+#include "js/TrackedOptimizationInfo.h"
+#include "js/TypeDecls.h"
+#include "js/UbiNode.h"
+#include "js/Utility.h"
+#include "js/Value.h"
+#include "js/Vector.h"
+#include "js/WeakMapPtr.h"
+#include "jsapi-tests/tests.h"
+
+/*
+ * Verify that our public (and intended to be public, versus being that way
+ * because we haven't made them private yet) headers don't define
+ * {u,}int{8,16,32,64} or JS{Ui,I}nt{8,16,32,64} types. If any do, they will
+ * assuredly conflict with a corresponding typedef below mapping to a *struct*.
+ *
+ * Note that tests.h includes a few internal headers; in order that this
+ * jsapi-test be writable, those internal headers must not import the legacy
+ * typedefs.
+ */
+
+struct ConflictingType {
+ uint64_t u64;
+};
+
+typedef ConflictingType uint8;
+typedef ConflictingType uint16;
+typedef ConflictingType uint32;
+typedef ConflictingType uint64;
+
+typedef ConflictingType int8;
+typedef ConflictingType int16;
+typedef ConflictingType int32;
+typedef ConflictingType int64;
+
+typedef ConflictingType JSUint8;
+typedef ConflictingType JSUint16;
+typedef ConflictingType JSUint32;
+typedef ConflictingType JSUint64;
+
+typedef ConflictingType JSInt8;
+typedef ConflictingType JSInt16;
+typedef ConflictingType JSInt32;
+typedef ConflictingType JSInt64;
+
+typedef ConflictingType jsword;
+typedef ConflictingType jsuword;
+typedef ConflictingType JSWord;
+typedef ConflictingType JSUword;
+
+BEGIN_TEST(testIntTypesABI)
+{
+ /* This passes if the typedefs didn't conflict at compile time. */
+ return true;
+}
+END_TEST(testIntTypesABI)
diff --git a/js/src/jsapi-tests/testIntern.cpp b/js/src/jsapi-tests/testIntern.cpp
new file mode 100644
index 000000000..0bcc5fe0d
--- /dev/null
+++ b/js/src/jsapi-tests/testIntern.cpp
@@ -0,0 +1,48 @@
+/* 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 "jsatom.h"
+
+#include "gc/Marking.h"
+#include "jsapi-tests/tests.h"
+#include "vm/String.h"
+
+using mozilla::ArrayLength;
+
+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, ArrayLength(someChars)));
+ CHECK(!JS_StringHasBeenPinned(cx, atom));
+ CHECK(JS_AtomizeAndPinJSString(cx, 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(JSFreeOp* fop, JSFinalizeStatus status, bool isZoneGC, void* data)
+{
+ if (status == JSFINALIZE_GROUP_START)
+ sw.strOk = js::gc::IsMarkedUnbarriered(fop->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 000000000..f8c04b232
--- /dev/null
+++ b/js/src/jsapi-tests/testIntlAvailableLocales.cpp
@@ -0,0 +1,58 @@
+/* -*- 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 "jsapi-tests/tests.h"
+
+BEGIN_TEST(testIntlAvailableLocales)
+{
+ // 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 "de" (German) support,
+ // and our Intl support *does not* natively support de-ZA-ghijk. :-)
+ CHECK(JS_SetDefaultLocale(cx, "de-ZA-abcde-x-private"));
+
+ EXEC("if (Intl.Collator().resolvedOptions().locale !== 'de-ZA-abcde-x-private') \n"
+ " throw 'unexpected default locale';");
+ EXEC("var used = Intl.Collator('de-ZA-abcde').resolvedOptions().locale; \n"
+ "if (used !== 'de-ZA-abcde') \n"
+ " throw 'bad locale when using truncated default: ' + used;");
+ EXEC("if (Intl.Collator('de-ZA').resolvedOptions().locale !== 'de-ZA') \n"
+ " throw 'bad locale when using more-truncated default';");
+ EXEC("if (Intl.Collator('de-ZA-ghijk').resolvedOptions().locale !== 'de-ZA') \n"
+ " throw 'unexpected default locale';");
+
+ EXEC("if (Intl.Collator('de-ZA-abcde-x-private', { localeMatcher: 'lookup' }).resolvedOptions().locale !== \n"
+ " 'de-ZA-abcde-x-private') \n"
+ "{ \n"
+ " throw 'unexpected default locale with lookup matcher'; \n"
+ "}");
+ EXEC("if (Intl.Collator('de-ZA-abcde').resolvedOptions().locale !== 'de-ZA-abcde') \n"
+ " throw 'bad locale when using truncated default';");
+ EXEC("if (Intl.Collator('de-ZA').resolvedOptions().locale !== 'de-ZA') \n"
+ " throw 'bad locale when using more-truncated default';");
+ EXEC("if (Intl.Collator('de').resolvedOptions().locale !== 'de') \n"
+ " throw 'bad locale when using most-truncated default';");
+
+ CHECK(JS_SetDefaultLocale(cx, "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(cx, "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(cx);
+ 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 000000000..83957ef0e
--- /dev/null
+++ b/js/src/jsapi-tests/testIsInsideNursery.cpp
@@ -0,0 +1,30 @@
+/* -*- 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 "jsapi-tests/tests.h"
+
+BEGIN_TEST(testIsInsideNursery)
+{
+ /* Non-GC things are never inside the nursery. */
+ CHECK(!cx->gc.nursery.isInside(cx));
+ CHECK(!cx->gc.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 000000000..097b3eb5f
--- /dev/null
+++ b/js/src/jsapi-tests/testIteratorObject.cpp
@@ -0,0 +1,31 @@
+/* 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(testIteratorObject)
+{
+ using namespace js;
+ JS::RootedValue result(cx);
+
+ EVAL("new Map([['key1', 'value1'], ['key2', 'value2']]).entries()", &result);
+
+ CHECK(result.isObject());
+ JS::RootedObject obj1(cx, &result.toObject());
+ ESClass class1 = ESClass::Other;
+ CHECK(GetBuiltinClass(cx, obj1, &class1));
+ CHECK(class1 == ESClass::MapIterator);
+
+ EVAL("new Set(['value1', 'value2']).entries()", &result);
+
+ CHECK(result.isObject());
+ JS::RootedObject obj2(cx, &result.toObject());
+ ESClass class2 = ESClass::Other;
+ CHECK(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 000000000..2ba22f8fd
--- /dev/null
+++ b/js/src/jsapi-tests/testJSEvaluateScript.cpp
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ */
+
+#include "jsapi-tests/tests.h"
+
+using mozilla::ArrayLength;
+
+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::AutoObjectVector scopeChain(cx);
+ CHECK(scopeChain.append(obj));
+ CHECK(JS::Evaluate(cx, scopeChain, opts.setFileAndLine(__FILE__, __LINE__),
+ src, ArrayLength(src) - 1, &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/testJitDCEinGVN.cpp b/js/src/jsapi-tests/testJitDCEinGVN.cpp
new file mode 100644
index 000000000..8ca016e82
--- /dev/null
+++ b/js/src/jsapi-tests/testJitDCEinGVN.cpp
@@ -0,0 +1,143 @@
+/* -*- 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 "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 000000000..41c52113d
--- /dev/null
+++ b/js/src/jsapi-tests/testJitFoldsTo.cpp
@@ -0,0 +1,260 @@
+/* -*- 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 "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 000000000..613935287
--- /dev/null
+++ b/js/src/jsapi-tests/testJitGVN.cpp
@@ -0,0 +1,286 @@
+/* -*- 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 "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);
+ MConstant* z5 = MConstant::New(func.alloc, Int32Value(4));
+ MInstruction* z6 = MAdd::New(func.alloc, phi2, phi3);
+ 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 000000000..e05a8c9fb
--- /dev/null
+++ b/js/src/jsapi-tests/testJitMacroAssembler.cpp
@@ -0,0 +1,476 @@
+/* -*- 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 "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 "jit/MacroAssembler-inl.h"
+
+using namespace js;
+using namespace js::jit;
+
+using mozilla::PositiveInfinity;
+using mozilla::NegativeInfinity;
+
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+
+typedef void (*EnterTest)();
+
+static bool Prepare(MacroAssembler& masm)
+{
+ AllocatableRegisterSet regs(RegisterSet::Volatile());
+ LiveRegisterSet save(regs.asLiveSet());
+ masm.PushRegsInMask(save);
+ return true;
+}
+
+static bool Execute(JSContext* cx, MacroAssembler& masm)
+{
+ AllocatableRegisterSet regs(RegisterSet::Volatile());
+ LiveRegisterSet save(regs.asLiveSet());
+ masm.PopRegsInMask(save);
+ masm.ret(); // Add return statement to be sure.
+
+ if (masm.oom())
+ return false;
+
+ Linker linker(masm);
+ JitCode* code = linker.newCode<CanGC>(cx, OTHER_CODE);
+ if (!code)
+ return false;
+ if (!ExecutableAllocator::makeExecutable(code->raw(), code->bufferSize()))
+ return false;
+
+ EnterTest test = code->as<EnterTest>();
+ test();
+ return true;
+}
+
+BEGIN_TEST(testJitMacroAssembler_truncateDoubleToInt64)
+{
+ MacroAssembler masm(cx);
+
+ if (!Prepare(masm))
+ return false;
+
+ 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 Execute(cx, masm);
+}
+END_TEST(testJitMacroAssembler_truncateDoubleToInt64)
+
+BEGIN_TEST(testJitMacroAssembler_truncateDoubleToUInt64)
+{
+ MacroAssembler masm(cx);
+
+ if (!Prepare(masm))
+ return false;
+
+ 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 Execute(cx, masm);
+}
+END_TEST(testJitMacroAssembler_truncateDoubleToUInt64)
+
+BEGIN_TEST(testJitMacroAssembler_branchDoubleNotInInt64Range)
+{
+ MacroAssembler masm(cx);
+
+ if (!Prepare(masm))
+ return false;
+
+ 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 Execute(cx, masm);
+}
+END_TEST(testJitMacroAssembler_branchDoubleNotInInt64Range)
+
+BEGIN_TEST(testJitMacroAssembler_branchDoubleNotInUInt64Range)
+{
+ MacroAssembler masm(cx);
+
+ if (!Prepare(masm))
+ return false;
+
+ 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 Execute(cx, masm);
+}
+END_TEST(testJitMacroAssembler_branchDoubleNotInUInt64Range)
+
+BEGIN_TEST(testJitMacroAssembler_lshift64)
+{
+ MacroAssembler masm(cx);
+
+ if (!Prepare(masm))
+ return false;
+
+ 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 Execute(cx, masm);
+}
+END_TEST(testJitMacroAssembler_lshift64)
+
+BEGIN_TEST(testJitMacroAssembler_rshift64Arithmetic)
+{
+ MacroAssembler masm(cx);
+
+ if (!Prepare(masm))
+ return false;
+
+ 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 Execute(cx, masm);
+}
+END_TEST(testJitMacroAssembler_rshift64Arithmetic)
+
+BEGIN_TEST(testJitMacroAssembler_rshift64)
+{
+ MacroAssembler masm(cx);
+
+ if (!Prepare(masm))
+ return false;
+
+ 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 Execute(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 000000000..34bdd2d85
--- /dev/null
+++ b/js/src/jsapi-tests/testJitMinimalFunc.h
@@ -0,0 +1,121 @@
+/* -*- 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/. */
+
+#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<CompileCompartment*>(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++, nullptr);
+ 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.init())
+ return false;
+ 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 000000000..40cf01fc7
--- /dev/null
+++ b/js/src/jsapi-tests/testJitMoveEmitterCycles-mips32.cpp
@@ -0,0 +1,414 @@
+/* -*- 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/. */
+
+#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;
+ AutoFlushICache afc("test");
+ Linker l(*masm);
+ return l.newCode<CanGC>(cx, ION_CODE);
+}
+
+#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);
+ AutoFlushICache afc("test");
+
+ MacroAssembler 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);
+ AutoFlushICache afc("test");
+ MacroAssembler 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);
+ AutoFlushICache afc("test");
+ MacroAssembler 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);
+ AutoFlushICache afc("test");
+ MacroAssembler 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 000000000..416587293
--- /dev/null
+++ b/js/src/jsapi-tests/testJitMoveEmitterCycles.cpp
@@ -0,0 +1,532 @@
+/* -*- 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/. */
+
+#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;
+ AutoFlushICache afc("test");
+ Linker l(*masm);
+ return l.newCode<CanGC>(cx, ION_CODE);
+}
+
+#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);
+ cx->getJitRuntime(cx);
+ MacroAssembler masm;
+ 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, &alloc);
+ cx->getJitRuntime(cx);
+ MacroAssembler masm;
+ 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, &alloc);
+ cx->getJitRuntime(cx);
+ MacroAssembler masm;
+ 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, &alloc);
+ cx->getJitRuntime(cx);
+ MacroAssembler masm;
+ 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)
+
+#endif
diff --git a/js/src/jsapi-tests/testJitRValueAlloc.cpp b/js/src/jsapi-tests/testJitRValueAlloc.cpp
new file mode 100644
index 000000000..b1f82547e
--- /dev/null
+++ b/js/src/jsapi-tests/testJitRValueAlloc.cpp
@@ -0,0 +1,229 @@
+/* -*- 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 "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);
+}
+
+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;
+ int32_t i, last = 0, tmp;
+ for (i = 0; i > 0; tmp = i, i += last, last = tmp) {
+ 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_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;
+ int32_t i, last = 0, tmp;
+ for (i = 0; i > 0; tmp = i, i += last, last = tmp) {
+#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_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++) {
+ int32_t j, last = 0, tmp;
+ for (j = 0; j > 0; tmp = j, j += last, last = tmp) {
+ s = RValueAllocation::Untyped(Register::FromCode(i), j);
+ CHECK(s == Read(s));
+ }
+ }
+ return true;
+}
+END_TEST(testJitRValueAlloc_UntypedRegStack)
+
+BEGIN_TEST(testJitRValueAlloc_UntypedStackReg)
+{
+ RValueAllocation s;
+ int32_t i, last = 0, tmp;
+ for (i = 0; i > 0; tmp = i, i += last, last = tmp) {
+ 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;
+ int32_t i, li = 0, ti;
+ for (i = 0; i > 0; ti = i, i += li, li = ti) {
+ int32_t j, lj = 0, tj;
+ for (j = 0; j > 0; tj = j, j += lj, lj = tj) {
+ 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;
+ int32_t i, last = 0, tmp;
+ for (i = 0; i > 0; tmp = i, i += last, last = tmp) {
+ 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;
+ int32_t i, last = 0, tmp;
+ for (i = 0; i > 0; tmp = i, i += last, last = tmp) {
+ 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 000000000..bcf642cb2
--- /dev/null
+++ b/js/src/jsapi-tests/testJitRangeAnalysis.cpp
@@ -0,0 +1,338 @@
+/* -*- 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/ArrayUtils.h"
+
+#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;
+ MathCache cache;
+
+ 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);
+ cmp->setCompareType(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);
+ MMathFunction* thenSign = MMathFunction::New(func.alloc, thenAdd, MMathFunction::Sign, &cache);
+ 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);
+ elseCmp->setCompareType(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);
+ MMathFunction* elseThenSign = MMathFunction::New(func.alloc, elseThenAdd, MMathFunction::Sign, &cache);
+ 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);
+ MMathFunction* elseElseSign = MMathFunction::New(func.alloc, elseElseAdd, MMathFunction::Sign, &cache);
+ 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);
+ entry->add(cmp);
+ entry->end(MTest::New(func.alloc, cmp, thenBlock, elseBlock));
+
+ // {
+ // 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.
+
+ // We can't do beta node insertion with STRICTEQ and a non-numeric
+ // comparison though.
+ MCompare::CompareType nonNumerics[] = {
+ MCompare::Compare_Unknown,
+ MCompare::Compare_Object,
+ MCompare::Compare_Bitwise,
+ MCompare::Compare_String
+ };
+ for (size_t i = 0; i < mozilla::ArrayLength(nonNumerics); ++i) {
+ cmp->setCompareType(nonNumerics[i]);
+ if (!func.runRangeAnalysis())
+ return false;
+ CHECK(!thenAdd->range() || thenAdd->range()->isUnknown());
+ ClearDominatorTree(func.graph);
+ }
+
+ // We can do it with a numeric comparison.
+ cmp->setCompareType(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)
diff --git a/js/src/jsapi-tests/testJitRegisterSet.cpp b/js/src/jsapi-tests/testJitRegisterSet.cpp
new file mode 100644
index 000000000..61acf834c
--- /dev/null
+++ b/js/src/jsapi-tests/testJitRegisterSet.cpp
@@ -0,0 +1,138 @@
+/* -*- 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 "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 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)
diff --git a/js/src/jsapi-tests/testLookup.cpp b/js/src/jsapi-tests/testLookup.cpp
new file mode 100644
index 000000000..22fb16185
--- /dev/null
+++ b/js/src/jsapi-tests/testLookup.cpp
@@ -0,0 +1,98 @@
+/* -*- 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 "jsfun.h" // for js::IsInternalFunctionObject
+
+#include "jsapi-tests/tests.h"
+
+#include "jsobjinlines.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();
+ JSFlatString* flatStr = JS_FlattenString(cx, str);
+ if (!flatStr)
+ return false;
+ if (JS_FlatStringEqualsAscii(flatStr, "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, nullptr, nullptr, nullptr,
+ nullptr, document_resolve, nullptr
+};
+
+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 000000000..70f5cf896
--- /dev/null
+++ b/js/src/jsapi-tests/testLooselyEqual.cpp
@@ -0,0 +1,195 @@
+/* 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 "jsapi-tests/tests.h"
+
+using namespace std;
+
+struct LooseEqualityFixture : public JSAPITest
+{
+ 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 000000000..716d87c29
--- /dev/null
+++ b/js/src/jsapi-tests/testMappedArrayBuffer.cpp
@@ -0,0 +1,193 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ */
+
+#include <fcntl.h>
+#include <stdio.h>
+
+#include "jsfriendapi.h"
+#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::SameProcessSameThread, 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, 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::AutoValueVector 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::SameProcessSameThread, nullptr, nullptr);
+ CHECK(cloned_buffer.write(cx, v1, transferable, JS::CloneDataPolicy().denySharedArrayBuffer(), nullptr, nullptr));
+ JS::RootedValue v2(cx);
+ CHECK(cloned_buffer.read(cx, &v2, 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/testMutedErrors.cpp b/js/src/jsapi-tests/testMutedErrors.cpp
new file mode 100644
index 000000000..da91afb2a
--- /dev/null
+++ b/js/src/jsapi-tests/testMutedErrors.cpp
@@ -0,0 +1,96 @@
+/* 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"
+
+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::CompartmentOptions globalOptions;
+ JS::RootedObject global(cx, JS_NewGlobalObject(cx, getGlobalClass(), nullptr,
+ JS::FireOnNewGlobalHook, globalOptions));
+ CHECK(global);
+ JSAutoCompartment ac(cx, global);
+ CHECK(JS_InitStandardClasses(cx, global));
+
+
+ JS::CompileOptions options(cx);
+ options.setMutedErrors(mutedErrors)
+ .setFileAndLine("", 0);
+
+ return JS::Evaluate(cx, options, chars.get(), len, 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::RootedValue exn(cx);
+ CHECK(JS_GetPendingException(cx, &exn));
+ JS_ClearPendingException(cx);
+
+ js::ErrorReport report(cx);
+ CHECK(report.init(cx, exn, js::ErrorReport::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 000000000..32bcb5afd
--- /dev/null
+++ b/js/src/jsapi-tests/testNewObject.cpp
@@ -0,0 +1,121 @@
+/* -*- 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 "jsapi-tests/tests.h"
+
+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::AutoValueVector argv(cx);
+ CHECK(argv.resize(N));
+
+ JS::RootedValue v(cx);
+ EVAL("Array", &v);
+ JS::RootedObject Array(cx, v.toObjectOrNull());
+
+ bool isArray;
+
+ // With no arguments.
+ JS::RootedObject obj(cx, JS_New(cx, Array, JS::HandleValueArray::empty()));
+ CHECK(obj);
+ JS::RootedValue rt(cx, JS::ObjectValue(*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);
+ obj = JS_New(cx, Array, JS::HandleValueArray::subarray(argv, 0, 1));
+ CHECK(obj);
+ rt = JS::ObjectValue(*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);
+ obj = JS_New(cx, Array, JS::HandleValueArray::subarray(argv, 0, N));
+ CHECK(obj);
+ rt = JS::ObjectValue(*obj);
+ CHECK(JS_IsArrayObject(cx, obj, &isArray));
+ CHECK(isArray);
+ CHECK(JS_GetArrayLength(cx, obj, &len));
+ CHECK_EQUAL(len, N);
+ CHECK(JS_GetElement(cx, obj, N - 1, &v));
+ CHECK(v.isInt32(N - 1));
+
+ // With JSClass.construct.
+ static const JSClassOps clsOps = {
+ nullptr, nullptr, nullptr, nullptr,
+ nullptr, nullptr, nullptr, nullptr,
+ nullptr, nullptr, constructHook
+ };
+ static const JSClass cls = {
+ "testNewObject_1",
+ 0,
+ &clsOps
+ };
+ JS::RootedObject ctor(cx, JS_NewObject(cx, &cls));
+ CHECK(ctor);
+ JS::RootedValue rt2(cx, JS::ObjectValue(*ctor));
+ obj = JS_New(cx, ctor, JS::HandleValueArray::subarray(argv, 0, 3));
+ CHECK(obj);
+ CHECK(JS_GetElement(cx, ctor, 0, &v));
+ CHECK(v.isInt32(0));
+
+ return true;
+}
+END_TEST(testNewObject_1)
diff --git a/js/src/jsapi-tests/testNewTargetInvokeConstructor.cpp b/js/src/jsapi-tests/testNewTargetInvokeConstructor.cpp
new file mode 100644
index 000000000..e6e946200
--- /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 "jsapi.h"
+
+#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::AutoValueArray<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 000000000..9790213d3
--- /dev/null
+++ b/js/src/jsapi-tests/testNullRoot.cpp
@@ -0,0 +1,25 @@
+/* -*- 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 "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/testOOM.cpp b/js/src/jsapi-tests/testOOM.cpp
new file mode 100644
index 000000000..303bf5403
--- /dev/null
+++ b/js/src/jsapi-tests/testOOM.cpp
@@ -0,0 +1,71 @@
+/* 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"
+
+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);
+ setNativeStackQuota(cx);
+ 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\n", testName); \
+ for (oomAfter = 1; oomAfter < maxAllocsPerTest; ++oomAfter) { \
+ js::oom::SimulateOOMAfter(oomAfter, js::oom::THREAD_TYPE_MAIN, true)
+
+#define OOM_TEST_FINISHED \
+ { \
+ printf("Test %s: finished with %" PRIu64 " allocations\n", \
+ testName, oomAfter - 1); \
+ break; \
+ }
+
+#define END_OOM_TEST \
+ } \
+ js::oom::ResetSimulatedOOM(); \
+ CHECK(oomAfter != maxAllocsPerTest)
+
+BEGIN_TEST(testNewContext)
+{
+ uninit(); // Get rid of test harness' original JSContext.
+
+ JSContext* cx;
+ START_OOM_TEST("new context");
+ cx = JS_NewContext(8L * 1024 * 1024);
+ if (cx)
+ OOM_TEST_FINISHED;
+ END_OOM_TEST;
+ JS_DestroyContext(cx);
+ return true;
+}
+
+const char* testName;
+uint64_t oomAfter;
+END_TEST(testNewContext)
+
+#endif
diff --git a/js/src/jsapi-tests/testObjectEmulatingUndefined.cpp b/js/src/jsapi-tests/testObjectEmulatingUndefined.cpp
new file mode 100644
index 000000000..6d0e7979c
--- /dev/null
+++ b/js/src/jsapi-tests/testObjectEmulatingUndefined.cpp
@@ -0,0 +1,103 @@
+/* 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, &ObjectEmulatingUndefinedClass,
+ 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, &ObjectEmulatingUndefinedClass,
+ 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/testParseJSON.cpp b/js/src/jsapi-tests/testParseJSON.cpp
new file mode 100644
index 000000000..e82c67911
--- /dev/null
+++ b/js/src/jsapi-tests/testParseJSON.cpp
@@ -0,0 +1,358 @@
+/* -*- 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 <limits>
+#include <string.h>
+
+#include "jsprf.h"
+#include "jsstr.h"
+
+#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<JSFlatString*> 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 JSFlatString*
+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);
+
+ RootedValue exn(cx);
+ CHECK(JS_GetPendingException(cx, &exn));
+ JS_ClearPendingException(cx);
+
+ js::ErrorReport report(cx);
+ CHECK(report.init(cx, exn, js::ErrorReport::WithSideEffects));
+ CHECK(report.report()->errorNumber == JSMSG_JSON_BAD_PARSE);
+
+ const char* lineAndColumnASCII = JS_smprintf("line %d column %d", expectedLine, expectedColumn);
+ CHECK(strstr(report.toStringResult().c_str(), lineAndColumnASCII) != nullptr);
+ js_free((void*)lineAndColumnASCII);
+
+ /* 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/testPersistentRooted.cpp b/js/src/jsapi-tests/testPersistentRooted.cpp
new file mode 100644
index 000000000..e765d8c7a
--- /dev/null
+++ b/js/src/jsapi-tests/testPersistentRooted.cpp
@@ -0,0 +1,224 @@
+/* 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(JSFreeOp* fop, 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,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ BarkWhenTracedClass::finalize,
+ nullptr,
+ nullptr,
+ nullptr,
+ BarkWhenTracedClass::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 000000000..6ffedff10
--- /dev/null
+++ b/js/src/jsapi-tests/testPreserveJitCode.cpp
@@ -0,0 +1,97 @@
+/* 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/. */
+
+// For js::jit::IsIonEnabled().
+#include "jit/Ion.h"
+
+#include "jsapi-tests/tests.h"
+
+using namespace JS;
+
+static void
+ScriptCallback(JSRuntime* rt, void* data, JSScript* script)
+{
+ unsigned& count = *static_cast<unsigned*>(data);
+ if (script->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->compartment(), &count, ScriptCallback);
+ return count;
+}
+
+bool
+testPreserveJitCode(bool preserveJitCode, unsigned remainingIonScripts)
+{
+ cx->options().setBaseline(true);
+ cx->options().setIon(true);
+ cx->setOffthreadIonCompilationEnabled(false);
+
+ RootedObject global(cx, createTestGlobal(preserveJitCode));
+ CHECK(global);
+ JSAutoCompartment ac(cx, global);
+
+#ifdef JS_CODEGEN_ARM64
+ // The ARM64 Ion JIT is not yet enabled, so this test will fail with
+ // countIonScripts(global) == 0. Once Ion is enabled for ARM64, this test
+ // should be passing again, and this code can be deleted.
+ // Bug 1208526 - ARM64: Reenable jsapi-tests/testPreserveJitCode once Ion is enabled
+ if (!js::jit::IsIonEnabled(cx))
+ knownFail = true;
+#endif
+
+ CHECK_EQUAL(countIonScripts(global), 0u);
+
+ const char* source =
+ "var i = 0;\n"
+ "var sum = 0;\n"
+ "while (i < 10) {\n"
+ " sum += i;\n"
+ " ++i;\n"
+ "}\n"
+ "return sum;\n";
+ unsigned length = strlen(source);
+
+ JS::RootedFunction fun(cx);
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(__FILE__, 1);
+ JS::AutoObjectVector emptyScopeChain(cx);
+ CHECK(JS::CompileFunction(cx, emptyScopeChain, options, "f", 0, nullptr,
+ source, length, &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);
+
+ GCForReason(cx, GC_NORMAL, gcreason::API);
+ CHECK_EQUAL(countIonScripts(global), remainingIonScripts);
+
+ GCForReason(cx, GC_SHRINK, gcreason::API);
+ CHECK_EQUAL(countIonScripts(global), 0u);
+
+ return true;
+}
+
+JSObject*
+createTestGlobal(bool preserveJitCode)
+{
+ JS::CompartmentOptions options;
+ options.creationOptions().setPreserveJitCode(preserveJitCode);
+ options.behaviors().setVersion(JSVERSION_LATEST);
+ return JS_NewGlobalObject(cx, getGlobalClass(), nullptr, JS::FireOnNewGlobalHook, options);
+}
+END_TEST(test_PreserveJitCode)
diff --git a/js/src/jsapi-tests/testPrintf.cpp b/js/src/jsapi-tests/testPrintf.cpp
new file mode 100644
index 000000000..51486856f
--- /dev/null
+++ b/js/src/jsapi-tests/testPrintf.cpp
@@ -0,0 +1,71 @@
+/* -*- 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/IntegerPrintfMacros.h"
+#include "mozilla/SizePrintfMacros.h"
+
+#include <stdarg.h>
+
+#include "jsprf.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);
+ char *output = JS_vsmprintf (fmt, ap);
+ va_end(ap);
+
+ bool result = output && !strcmp(output, expect);
+ JS_smprintf_free(output);
+
+ return result;
+}
+
+static const char *
+zero()
+{
+ return 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", "%" PRIuSIZE, (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));
+
+ 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 000000000..db0b46fa1
--- /dev/null
+++ b/js/src/jsapi-tests/testPrivateGCThingValue.cpp
@@ -0,0 +1,57 @@
+/* -*- 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 "jsapi.h"
+
+#include "js/HeapAPI.h"
+#include "jsapi-tests/tests.h"
+
+class TestTracer : public JS::CallbackTracer
+{
+ void onChild(const JS::GCCellPtr& thing) 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),
+ 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.
+ JS::RootedScript script(cx);
+ const char code[] = "'objet petit a'";
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(__FILE__, __LINE__);
+ CHECK(JS_CompileScript(cx, code, sizeof(code) - 1, options, &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 000000000..f283161b5
--- /dev/null
+++ b/js/src/jsapi-tests/testProfileStrings.cpp
@@ -0,0 +1,274 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ *
+ * 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 "jscntxt.h"
+
+#include "jsapi-tests/tests.h"
+
+static js::ProfileEntry pstack[10];
+static uint32_t psize = 0;
+static uint32_t max_stack = 0;
+
+static void
+reset(JSContext* cx)
+{
+ psize = max_stack = 0;
+ memset(pstack, 0, sizeof(pstack));
+ cx->spsProfiler.stringsReset();
+ cx->spsProfiler.enableSlowAssertions(true);
+ js::EnableContextProfilingStack(cx, true);
+}
+
+static const JSClass ptestClass = {
+ "Prof", 0
+};
+
+static bool
+test_fn(JSContext* cx, unsigned argc, JS::Value* vp)
+{
+ max_stack = psize;
+ 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_FS("test_fn", test_fn, 0, 0),
+ JS_FS("test_fn2", test_fn2, 0, 0),
+ JS_FS("enable", enable, 0, 0),
+ JS_FS("disable", disable, 0, 0),
+ JS_FS_END
+};
+
+static JSObject*
+initialize(JSContext* cx)
+{
+ js::SetContextProfilingStack(cx, pstack, &psize, 10);
+ JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
+ return JS_InitClass(cx, global, nullptr, &ptestClass, 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(psize == 0);
+ CHECK(max_stack >= 8);
+ CHECK(cx->runtime()->spsProfiler.stringsCount() == 8);
+ /* Make sure the stack resets and we added no new entries */
+ max_stack = 0;
+ CHECK(JS_CallFunctionName(cx, global, "check", JS::HandleValueArray::empty(),
+ &rval));
+ CHECK(psize == 0);
+ CHECK(max_stack >= 8);
+ CHECK(cx->runtime()->spsProfiler.stringsCount() == 8);
+ }
+ reset(cx);
+ {
+ JS::RootedValue rval(cx);
+ CHECK(JS_CallFunctionName(cx, global, "check2", JS::HandleValueArray::empty(),
+ &rval));
+ CHECK(cx->runtime()->spsProfiler.stringsCount() == 5);
+ CHECK(max_stack >= 6);
+ CHECK(psize == 0);
+ }
+ js::EnableContextProfilingStack(cx, false);
+ js::SetContextProfilingStack(cx, pstack, &psize, 3);
+ reset(cx);
+ {
+ JS::RootedValue rval(cx);
+ pstack[3].setLabel((char*) 1234);
+ CHECK(JS_CallFunctionName(cx, global, "check", JS::HandleValueArray::empty(),
+ &rval));
+ CHECK((size_t) pstack[3].label() == 1234);
+ CHECK(max_stack >= 8);
+ CHECK(psize == 0);
+ }
+ return true;
+}
+END_TEST(testProfileStrings_isCalledWithInterpreter)
+
+BEGIN_TEST(testProfileStrings_isCalledWithJIT)
+{
+ CHECK(initialize(cx));
+ JS::ContextOptionsRef(cx).setBaseline(true)
+ .setIon(true);
+
+ 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(psize == 0);
+ CHECK(max_stack >= 8);
+
+ /* Make sure the stack resets and we added no new entries */
+ uint32_t cnt = cx->runtime()->spsProfiler.stringsCount();
+ max_stack = 0;
+ CHECK(JS_CallFunctionName(cx, global, "check", JS::HandleValueArray::empty(),
+ &rval));
+ CHECK(psize == 0);
+ CHECK(cx->runtime()->spsProfiler.stringsCount() == cnt);
+ CHECK(max_stack >= 8);
+ }
+
+ js::EnableContextProfilingStack(cx, false);
+ js::SetContextProfilingStack(cx, pstack, &psize, 3);
+ reset(cx);
+ {
+ /* Limit the size of the stack and make sure we don't overflow */
+ JS::RootedValue rval(cx);
+ pstack[3].setLabel((char*) 1234);
+ CHECK(JS_CallFunctionName(cx, global, "check", JS::HandleValueArray::empty(),
+ &rval));
+ CHECK(psize == 0);
+ CHECK(max_stack >= 8);
+ CHECK((size_t) pstack[3].label() == 1234);
+ }
+ return true;
+}
+END_TEST(testProfileStrings_isCalledWithJIT)
+
+BEGIN_TEST(testProfileStrings_isCalledWhenError)
+{
+ CHECK(initialize(cx));
+ JS::ContextOptionsRef(cx).setBaseline(true)
+ .setIon(true);
+
+ 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(psize == 0);
+ CHECK(cx->runtime()->spsProfiler.stringsCount() == 1);
+
+ JS_ClearPendingException(cx);
+ }
+ return true;
+}
+END_TEST(testProfileStrings_isCalledWhenError)
+
+BEGIN_TEST(testProfileStrings_worksWhenEnabledOnTheFly)
+{
+ CHECK(initialize(cx));
+ JS::ContextOptionsRef(cx).setBaseline(true)
+ .setIon(true);
+
+ 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(psize == 0);
+ CHECK(max_stack >= 1);
+ CHECK(cx->runtime()->spsProfiler.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(psize == 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(psize == 0);
+ CHECK(max_stack >= 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()->spsProfiler.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(psize == 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 000000000..45b9eb12d
--- /dev/null
+++ b/js/src/jsapi-tests/testPromise.cpp
@@ -0,0 +1,91 @@
+/* -*- 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 "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)
diff --git a/js/src/jsapi-tests/testPropCache.cpp b/js/src/jsapi-tests/testPropCache.cpp
new file mode 100644
index 000000000..e36163ecf
--- /dev/null
+++ b/js/src/jsapi-tests/testPropCache.cpp
@@ -0,0 +1,40 @@
+/* -*- 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 "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
+};
+
+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/testRegExp.cpp b/js/src/jsapi-tests/testRegExp.cpp
new file mode 100644
index 000000000..82e260368
--- /dev/null
+++ b/js/src/jsapi-tests/testRegExp.cpp
@@ -0,0 +1,60 @@
+/* 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(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), 0u);
+
+ EVAL("/foopy/g", &val);
+ obj = val.toObjectOrNull();
+ CHECK_EQUAL(JS_GetRegExpFlags(cx, obj), JSREG_GLOB);
+
+ EVAL("/foopy/gi", &val);
+ obj = val.toObjectOrNull();
+ CHECK_EQUAL(JS_GetRegExpFlags(cx, obj), (JSREG_FOLD | JSREG_GLOB));
+
+ 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(JS_FlatStringEqualsAscii(JS_ASSERT_STRING_IS_FLAT(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 000000000..9c38e38c7
--- /dev/null
+++ b/js/src/jsapi-tests/testResolveRecursion.cpp
@@ -0,0 +1,188 @@
+/* -*- 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 "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, // add
+ nullptr, // delete
+ nullptr, // get
+ nullptr, // set
+ nullptr, // enumerate
+ my_resolve
+ };
+
+ static const JSClass my_resolve_class = {
+ "MyResolve",
+ JSCLASS_HAS_PRIVATE,
+ &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_SetPrivate(obj1, this);
+ JS_SetPrivate(obj2, 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;
+}
+
+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(JSID_IS_STRING(id));
+
+ JSFlatString* str = JS_FlattenString(cx, JSID_TO_STRING(id));
+ CHECK(str);
+ JS::RootedValue v(cx);
+ if (JS_FlatStringEqualsAscii(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_FlatStringEqualsAscii(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)
+{
+ return static_cast<cls_testResolveRecursion*>(JS_GetPrivate(obj))->
+ 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_InitStandardClasses(cx, global));
+ return true;
+}
+
+const JSClass* getGlobalClass() override {
+ static const JSClassOps myGlobalClassOps = {
+ nullptr, // add
+ nullptr, // delete
+ nullptr, // get
+ nullptr, // set
+ nullptr, // enumerate
+ my_resolve,
+ nullptr, // mayResolve
+ nullptr, // finalize
+ nullptr, // call
+ nullptr, // hasInstance
+ nullptr, // construct
+ JS_GlobalObjectTraceHook
+ };
+
+ 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/testSameValue.cpp b/js/src/jsapi-tests/testSameValue.cpp
new file mode 100644
index 000000000..d75c47b94
--- /dev/null
+++ b/js/src/jsapi-tests/testSameValue.cpp
@@ -0,0 +1,27 @@
+/* -*- 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 "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 000000000..c329f27ce
--- /dev/null
+++ b/js/src/jsapi-tests/testSavedStacks.cpp
@@ -0,0 +1,302 @@
+/* -*- 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 "jscompartment.h"
+#include "jsfriendapi.h"
+#include "jsstr.h"
+
+#include "builtin/TestingFunctions.h"
+#include "jsapi-tests/tests.h"
+#include "vm/ArrayObject.h"
+#include "vm/SavedStacks.h"
+
+BEGIN_TEST(testSavedStacks_withNoStack)
+{
+ JSCompartment* compartment = js::GetContextCompartment(cx);
+ compartment->setAllocationMetadataBuilder(&js::SavedStacks::metadataBuilder);
+ JS::RootedObject obj(cx, js::NewDenseEmptyArray(cx));
+ compartment->setAllocationMetadataBuilder(nullptr);
+ return true;
+}
+END_TEST(testSavedStacks_withNoStack)
+
+BEGIN_TEST(testSavedStacks_ApiDefaultValues)
+{
+ js::RootedSavedFrame savedFrame(cx, nullptr);
+
+ // Source
+ JS::RootedString str(cx);
+ JS::SavedFrameResult result = JS::GetSavedFrameSource(cx, savedFrame, &str);
+ CHECK(result == JS::SavedFrameResult::AccessDenied);
+ CHECK(str.get() == cx->runtime()->emptyString);
+
+ // Line
+ uint32_t line = 123;
+ result = JS::GetSavedFrameLine(cx, savedFrame, &line);
+ CHECK(result == JS::SavedFrameResult::AccessDenied);
+ CHECK(line == 0);
+
+ // Column
+ uint32_t column = 123;
+ result = JS::GetSavedFrameColumn(cx, savedFrame, &column);
+ CHECK(result == JS::SavedFrameResult::AccessDenied);
+ CHECK(column == 0);
+
+ // Function display name
+ result = JS::GetSavedFrameFunctionDisplayName(cx, savedFrame, &str);
+ CHECK(result == JS::SavedFrameResult::AccessDenied);
+ CHECK(str.get() == nullptr);
+
+ // Parent
+ JS::RootedObject parent(cx);
+ result = JS::GetSavedFrameParent(cx, savedFrame, &parent);
+ CHECK(result == JS::SavedFrameResult::AccessDenied);
+ CHECK(parent.get() == nullptr);
+
+ // Stack string
+ CHECK(JS::BuildStackString(cx, 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::SavedFrame* f = savedFrame.get();
+ for (auto& frame : *savedFrame.get()) {
+ CHECK(&frame == f);
+ f = f->getParent();
+ }
+ CHECK(f == nullptr);
+
+ const js::SavedFrame* cf = savedFrame.get();
+ for (const auto& frame : *savedFrame.get()) {
+ CHECK(&frame == cf);
+ cf = cf->getParent();
+ }
+ CHECK(cf == nullptr);
+
+ 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
+ const char* SpiderMonkeyStack = "three@filename.js:4:14\n"
+ "two@filename.js:3:22\n"
+ "one@filename.js:2:20\n"
+ "@filename.js:1:11\n";
+ const char* V8Stack = " at three (filename.js:4:14)\n"
+ " at two (filename.js:3:22)\n"
+ " at one (filename.js:2:20)\n"
+ " at filename.js:1:11";
+ 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);
+ CHECK(JS::BuildStackString(cx, 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
+ const char* SpiderMonkeyStack = "three@filename.js:4:14\n"
+ "two@filename.js:3:22\n"
+ "one@filename.js:2:20\n"
+ "@filename.js:1:11\n";
+ JSLinearString* lin = stack->ensureLinear(cx);
+ CHECK(lin);
+ CHECK(js::StringEqualsAscii(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
+ const char* V8Stack = "Error: foo\n"
+ " at three (filename.js:4:14)\n"
+ " at two (filename.js:3:22)\n"
+ " at one (filename.js:2:20)\n"
+ " at filename.js:1:11";
+ JSLinearString* lin = stack->ensureLinear(cx);
+ CHECK(lin);
+ CHECK(js::StringEqualsAscii(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));
+
+ // Source
+ JS::RootedString str(cx);
+ JS::SavedFrameResult result = JS::GetSavedFrameSource(cx, selfHostedFrame, &str,
+ JS::SavedFrameSelfHosted::Exclude);
+ CHECK(result == JS::SavedFrameResult::Ok);
+ JSLinearString* lin = str->ensureLinear(cx);
+ CHECK(lin);
+ CHECK(js::StringEqualsAscii(lin, "filename.js"));
+
+ // Source, including self-hosted frames
+ result = JS::GetSavedFrameSource(cx, selfHostedFrame, &str, JS::SavedFrameSelfHosted::Include);
+ CHECK(result == JS::SavedFrameResult::Ok);
+ lin = str->ensureLinear(cx);
+ CHECK(lin);
+ CHECK(js::StringEqualsAscii(lin, "self-hosted"));
+
+ // Line
+ uint32_t line = 123;
+ result = JS::GetSavedFrameLine(cx, selfHostedFrame, &line, JS::SavedFrameSelfHosted::Exclude);
+ CHECK(result == JS::SavedFrameResult::Ok);
+ CHECK_EQUAL(line, 3U);
+
+ // Column
+ uint32_t column = 123;
+ result = JS::GetSavedFrameColumn(cx, selfHostedFrame, &column,
+ JS::SavedFrameSelfHosted::Exclude);
+ CHECK(result == JS::SavedFrameResult::Ok);
+ CHECK_EQUAL(column, 5U);
+
+ // Function display name
+ result = JS::GetSavedFrameFunctionDisplayName(cx, selfHostedFrame, &str,
+ JS::SavedFrameSelfHosted::Exclude);
+ CHECK(result == JS::SavedFrameResult::Ok);
+ lin = str->ensureLinear(cx);
+ CHECK(lin);
+ CHECK(js::StringEqualsAscii(lin, "one"));
+
+ // Parent
+ JS::RootedObject parent(cx);
+ result = JS::GetSavedFrameParent(cx, 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, parent, &str, JS::SavedFrameSelfHosted::Exclude);
+ CHECK(result == JS::SavedFrameResult::Ok);
+ lin = str->ensureLinear(cx);
+ CHECK(lin);
+ CHECK(js::StringEqualsAscii(lin, "filename.js"));
+
+ return true;
+}
+END_TEST(testSavedStacks_selfHostedFrames)
diff --git a/js/src/jsapi-tests/testScriptInfo.cpp b/js/src/jsapi-tests/testScriptInfo.cpp
new file mode 100644
index 000000000..89f925cd8
--- /dev/null
+++ b/js/src/jsapi-tests/testScriptInfo.cpp
@@ -0,0 +1,50 @@
+/* -*- 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 "jsapi.h"
+
+#include "jsapi-tests/tests.h"
+
+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::RootedScript script(cx);
+ CHECK(JS_CompileScript(cx, code, strlen(code), options, &script));
+ 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 000000000..3639d9025
--- /dev/null
+++ b/js/src/jsapi-tests/testScriptObject.cpp
@@ -0,0 +1,200 @@
+/* -*- 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 "jsapi-tests/tests.h"
+
+struct ScriptObjectFixture : public JSAPITest {
+ 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::RootedScript script(cx);
+ CHECK(JS_CompileScript(cx, code, code_size, options, &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::RootedScript script(cx);
+ CHECK(JS_CompileScript(cx, "", 0, options, &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::RootedScript script(cx);
+ CHECK(JS_CompileScript(cx, code, code_size, options, &script));
+ 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::RootedScript script(cx);
+ CHECK(JS_CompileUCScript(cx, uc_code, code_size, options, &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::RootedScript script(cx);
+ CHECK(JS_CompileUCScript(cx, uc_code, 0, options, &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::RootedScript script(cx);
+ CHECK(JS_CompileUCScript(cx, uc_code, code_size, options, &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);
+ CHECK(JS::Compile(cx, options, script_filename, &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);
+ CHECK(JS::Compile(cx, options, script_filename, &script));
+ tempScript.remove();
+ return tryScript(script);
+}
+END_FIXTURE_TEST(ScriptObjectFixture, bug438633_JS_CompileFile_empty)
+
+BEGIN_FIXTURE_TEST(ScriptObjectFixture, bug438633_JS_CompileFileHandle)
+{
+ const char* script_filename = "temporary file";
+ 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(script_filename, 1);
+ JS::RootedScript script(cx);
+ CHECK(JS::Compile(cx, options, script_stream, &script));
+ return tryScript(script);
+}
+END_FIXTURE_TEST(ScriptObjectFixture, bug438633_JS_CompileFileHandle)
+
+BEGIN_FIXTURE_TEST(ScriptObjectFixture, bug438633_JS_CompileFileHandle_empty)
+{
+ const char* script_filename = "empty temporary file";
+ TempFile tempScript;
+ FILE* script_stream = tempScript.open("temp-bug438633_JS_CompileFileHandle_empty");
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(script_filename, 1);
+ JS::RootedScript script(cx);
+ CHECK(JS::Compile(cx, options, script_stream, &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);
+ CHECK(JS::Compile(cx, options, script_stream, &script));
+ return tryScript(script);
+}
+END_FIXTURE_TEST(ScriptObjectFixture, bug438633_JS_CompileFileHandleForPrincipals)
+
+BEGIN_FIXTURE_TEST(ScriptObjectFixture, CloneAndExecuteScript)
+{
+ JS::RootedValue fortyTwo(cx);
+ fortyTwo.setInt32(42);
+ CHECK(JS_SetProperty(cx, global, "val", fortyTwo));
+ JS::RootedScript script(cx);
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(__FILE__, __LINE__);
+ CHECK(JS_CompileScript(cx, "val", 3, options, &script));
+ JS::RootedValue value(cx);
+ CHECK(JS_ExecuteScript(cx, script, &value));
+ CHECK(value.toInt32() == 42);
+ {
+ JS::RootedObject global2(cx, createGlobal());
+ CHECK(global2);
+ JSAutoCompartment ac(cx, global2);
+ CHECK(JS_WrapValue(cx, &fortyTwo));
+ CHECK(JS_SetProperty(cx, global, "val", fortyTwo));
+ JS::RootedValue value2(cx);
+ CHECK(JS::CloneAndExecuteScript(cx, script, &value2));
+ CHECK(value2.toInt32() == 42);
+ }
+ JS::RootedValue value3(cx);
+ CHECK(JS_ExecuteScript(cx, script, &value3));
+ CHECK(value3.toInt32() == 42);
+ return true;
+}
+END_FIXTURE_TEST(ScriptObjectFixture, CloneAndExecuteScript)
diff --git a/js/src/jsapi-tests/testSetProperty.cpp b/js/src/jsapi-tests/testSetProperty.cpp
new file mode 100644
index 000000000..e4ba0174e
--- /dev/null
+++ b/js/src/jsapi-tests/testSetProperty.cpp
@@ -0,0 +1,84 @@
+/* -*- 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 "jsapi-tests/tests.h"
+
+BEGIN_TEST(testSetProperty_NativeGetterStubSetter)
+{
+ JS::RootedObject obj(cx, JS_NewPlainObject(cx));
+ CHECK(obj);
+
+ CHECK(JS_DefineProperty(cx, global, "globalProp", obj, JSPROP_ENUMERATE,
+ JS_STUBGETTER, JS_STUBSETTER));
+
+ CHECK(JS_DefineProperty(cx, obj, "prop", JS::UndefinedHandleValue,
+ JSPROP_SHARED | JSPROP_PROPOP_ACCESSORS,
+ JS_PROPERTYOP_GETTER(NativeGet), JS_STUBSETTER));
+
+ EXEC("'use strict'; \n"
+ "var error, passed = false; \n"
+ "try \n"
+ "{ \n"
+ " this.globalProp.prop = 42; \n"
+ " throw new Error('setting property succeeded!'); \n"
+ "} \n"
+ "catch (e) \n"
+ "{ \n"
+ " error = e; \n"
+ " if (e instanceof TypeError) \n"
+ " passed = true; \n"
+ "} \n"
+ " \n"
+ "if (!passed) \n"
+ " throw error; \n");
+
+ EXEC("var error, passed = false; \n"
+ "try \n"
+ "{ \n"
+ " this.globalProp.prop = 42; \n"
+ " if (this.globalProp.prop === 17) \n"
+ " passed = true; \n"
+ " else \n"
+ " throw new Error('bad value after set!'); \n"
+ "} \n"
+ "catch (e) \n"
+ "{ \n"
+ " error = e; \n"
+ "} \n"
+ " \n"
+ "if (!passed) \n"
+ " throw error; \n");
+
+ return true;
+}
+static bool
+NativeGet(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp)
+{
+ vp.setInt32(17);
+ return true;
+}
+END_TEST(testSetProperty_NativeGetterStubSetter)
+
+BEGIN_TEST(testSetProperty_InheritedGlobalSetter)
+{
+ // This is a JSAPI test because jsapi-test globals do 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_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;
+}
+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 000000000..3150986dd
--- /dev/null
+++ b/js/src/jsapi-tests/testSetPropertyIgnoringNamedGetter.cpp
@@ -0,0 +1,93 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ */
+
+#include "jsfriendapi.h"
+
+#include "js/Proxy.h"
+
+#include "jsapi-tests/tests.h"
+
+using namespace js;
+using namespace JS;
+
+class CustomProxyHandler : public Wrapper
+{
+ public:
+ CustomProxyHandler() : Wrapper(0) {}
+
+ bool getPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
+ MutableHandle<PropertyDescriptor> desc) const override
+ {
+ return impl(cx, proxy, id, desc, false);
+ }
+
+ bool getOwnPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
+ MutableHandle<PropertyDescriptor> desc) const override
+ {
+ return impl(cx, proxy, id, desc, true);
+ }
+
+ bool set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v, HandleValue receiver,
+ ObjectOpResult& result) const override
+ {
+ Rooted<PropertyDescriptor> desc(cx);
+ if (!Wrapper::getPropertyDescriptor(cx, proxy, id, &desc))
+ return false;
+ return SetPropertyIgnoringNamedGetter(cx, proxy, id, v, receiver, desc, result);
+ }
+
+ private:
+ bool impl(JSContext* cx, HandleObject proxy, HandleId id,
+ MutableHandle<PropertyDescriptor> desc, bool ownOnly) const
+ {
+ if (JSID_IS_STRING(id)) {
+ bool match;
+ if (!JS_StringEqualsAscii(cx, JSID_TO_STRING(id), "phantom", &match))
+ return false;
+ if (match) {
+ desc.object().set(proxy);
+ desc.attributesRef() = JSPROP_ENUMERATE;
+ desc.value().setInt32(42);
+ return true;
+ }
+ }
+
+ if (ownOnly)
+ return Wrapper::getOwnPropertyDescriptor(cx, proxy, id, desc);
+ return Wrapper::getPropertyDescriptor(cx, proxy, id, desc);
+ }
+
+};
+
+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 000000000..421d9f8f9
--- /dev/null
+++ b/js/src/jsapi-tests/testSharedImmutableStringsCache.cpp
@@ -0,0 +1,80 @@
+/* -*- 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/IntegerRange.h"
+
+#include "js/Vector.h"
+#include "jsapi-tests/tests.h"
+#include "threading/Thread.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(mozilla::Move(dupe), js_strlen(str));
+ MOZ_RELEASE_ASSERT(deduped.isSome());
+ MOZ_RELEASE_ASSERT(js_strcmp(str, deduped->chars()) == 0);
+
+ {
+ 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 maybeCache = js::SharedImmutableStringsCache::Create();
+ CHECK(maybeCache.isSome());
+ auto& cache = *maybeCache;
+
+ js::Vector<js::Thread> threads(cx);
+ CHECK(threads.reserve(NUM_THREADS));
+
+ for (auto i : mozilla::MakeRange(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/testSlowScript.cpp b/js/src/jsapi-tests/testSlowScript.cpp
new file mode 100644
index 000000000..f4a706a6c
--- /dev/null
+++ b/js/src/jsapi-tests/testSlowScript.cpp
@@ -0,0 +1,86 @@
+/* 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 bool
+InterruptCallback(JSContext* cx)
+{
+ return false;
+}
+
+static unsigned sRemain;
+
+static bool
+RequestInterruptCallback(JSContext* cx, unsigned argc, jsval* 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);
+
+ test("while (true)"
+ " for (i in [0,0,0,0])"
+ " requestInterruptCallback();");
+
+ test("while (true)"
+ " for (i in [0,0,0,0])"
+ " for (j in [0,0,0,0])"
+ " requestInterruptCallback();");
+
+ 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();");
+
+ test("function f() { while (true) yield requestInterruptCallback() }"
+ "for (i in f()) ;");
+
+ test("function f() { while (true) yield 1 }"
+ "for (i in f())"
+ " requestInterruptCallback();");
+
+ test("(function() {"
+ " while (true)"
+ " let (x = 1) { eval('requestInterruptCallback()'); }"
+ "})()");
+
+ return true;
+}
+
+bool
+test(const char* bytes)
+{
+ jsval v;
+
+ JS_SetOptions(cx, JS_GetOptions(cx) & ~(JSOPTION_METHODJIT | JSOPTION_METHODJIT_ALWAYS));
+ sRemain = 0;
+ CHECK(!evaluate(bytes, __FILE__, __LINE__, &v));
+ CHECK(!JS_IsExceptionPending(cx));
+
+ sRemain = 1000;
+ CHECK(!evaluate(bytes, __FILE__, __LINE__, &v));
+ CHECK(!JS_IsExceptionPending(cx));
+
+ JS_SetOptions(cx, JS_GetOptions(cx) | JSOPTION_METHODJIT | JSOPTION_METHODJIT_ALWAYS);
+
+ sRemain = 0;
+ CHECK(!evaluate(bytes, __FILE__, __LINE__, &v));
+ CHECK(!JS_IsExceptionPending(cx));
+
+ sRemain = 1000;
+ CHECK(!evaluate(bytes, __FILE__, __LINE__, &v));
+ CHECK(!JS_IsExceptionPending(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 000000000..6614fe7b2
--- /dev/null
+++ b/js/src/jsapi-tests/testSourcePolicy.cpp
@@ -0,0 +1,42 @@
+/* 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 "jsscript.h"
+
+#include "jsapi-tests/tests.h"
+
+BEGIN_TEST(testBug795104)
+{
+ JS::CompileOptions opts(cx);
+ JS::CompartmentBehaviorsRef(cx->compartment()).setDiscardSource(true);
+
+ 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] = '"';
+
+ // We don't want an rval for our Evaluate call
+ opts.setNoScriptRval(true);
+
+ JS::RootedValue unused(cx);
+ CHECK(JS::Evaluate(cx, opts, s, strLen, &unused));
+
+ JS::RootedFunction fun(cx);
+ JS::AutoObjectVector 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);
+
+ CHECK(JS::CompileFunction(cx, emptyScopeChain, opts, "f", 0, nullptr, s, strLen, &fun));
+ CHECK(fun);
+
+ JS_free(cx, s);
+
+ return true;
+}
+END_TEST(testBug795104)
diff --git a/js/src/jsapi-tests/testStringBuffer.cpp b/js/src/jsapi-tests/testStringBuffer.cpp
new file mode 100644
index 000000000..62cf1c7e5
--- /dev/null
+++ b/js/src/jsapi-tests/testStringBuffer.cpp
@@ -0,0 +1,29 @@
+/* -*- 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 "jsatom.h"
+
+#include "jsapi-tests/tests.h"
+#include "vm/StringBuffer.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/testStructuredClone.cpp b/js/src/jsapi-tests/testStructuredClone.cpp
new file mode 100644
index 000000000..a01afccad
--- /dev/null
+++ b/js/src/jsapi-tests/testStructuredClone.cpp
@@ -0,0 +1,232 @@
+/* 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/StructuredClone.h"
+
+#include "jsapi-tests/tests.h"
+
+using namespace js;
+
+BEGIN_TEST(testStructuredClone_object)
+{
+ JS::RootedObject g1(cx, createGlobal());
+ JS::RootedObject g2(cx, createGlobal());
+ CHECK(g1);
+ CHECK(g2);
+
+ JS::RootedValue v1(cx);
+
+ {
+ JSAutoCompartment ac(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));
+ }
+
+ {
+ JSAutoCompartment ac(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);
+
+ {
+ JSAutoCompartment ac(cx, g1);
+ JS::RootedValue prop(cx, JS::Int32Value(1337));
+
+ v1 = JS::StringValue(JS_NewStringCopyZ(cx, "Hello World!"));
+ CHECK(v1.isString());
+ CHECK(v1.toString());
+ }
+
+ {
+ JSAutoCompartment ac(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)
+
+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);
+ }
+
+ 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::CompartmentOptions options;
+ JS::RootedObject g(cx, JS_NewGlobalObject(cx, getGlobalClass(), pp->principals,
+ JS::FireOnNewGlobalHook, options));
+ CHECK(g);
+ JSAutoCompartment ac(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::RootedSavedFrame 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>());
+ auto destFrame = &destObj->as<js::SavedFrame>();
+
+ size_t framesCopied = 0;
+ for (auto& f : *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 000000000..28c6f7a0c
--- /dev/null
+++ b/js/src/jsapi-tests/testSymbol.cpp
@@ -0,0 +1,81 @@
+/* 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(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 000000000..c7f719a71
--- /dev/null
+++ b/js/src/jsapi-tests/testThreadingConditionVariable.cpp
@@ -0,0 +1,221 @@
+/* -*- 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 "jsapi-tests/tests.h"
+#include "threading/ConditionVariable.h"
+#include "threading/Thread.h"
+#include "vm/MutexIDs.h"
+
+struct TestState {
+ js::Mutex mutex;
+ 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 000000000..bf04b8a8e
--- /dev/null
+++ b/js/src/jsapi-tests/testThreadingExclusiveData.cpp
@@ -0,0 +1,89 @@
+/* -*- 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/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::MakeRange(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::MakeRange(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 000000000..9310ba470
--- /dev/null
+++ b/js/src/jsapi-tests/testThreadingMutex.cpp
@@ -0,0 +1,50 @@
+/* -*- 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 "jsapi-tests/tests.h"
+#include "threading/LockGuard.h"
+#include "vm/MutexIDs.h"
+
+BEGIN_TEST(testThreadingMutex)
+{
+ js::Mutex mutex(js::mutexid::TestMutex);
+ mutex.lock();
+ mutex.unlock();
+ return true;
+}
+END_TEST(testThreadingMutex)
+
+BEGIN_TEST(testThreadingLockGuard)
+{
+ js::Mutex mutex(js::mutexid::TestMutex);
+ js::LockGuard<js::Mutex> guard(mutex);
+ return true;
+}
+END_TEST(testThreadingLockGuard)
+
+BEGIN_TEST(testThreadingUnlockGuard)
+{
+ js::Mutex mutex(js::mutexid::TestMutex);
+ js::LockGuard<js::Mutex> guard(mutex);
+ js::UnlockGuard<js::Mutex> unguard(guard);
+ return true;
+}
+END_TEST(testThreadingUnlockGuard)
+
+BEGIN_TEST(testThreadingMoveMutex)
+{
+ js::Mutex mutex(js::mutexid::TestMutex);
+ mutex.lock();
+ mutex.unlock();
+
+ js::Mutex another(mozilla::Move(mutex));
+ another.lock();
+ another.unlock();
+
+ return true;
+}
+END_TEST(testThreadingMoveMutex)
diff --git a/js/src/jsapi-tests/testThreadingThread.cpp b/js/src/jsapi-tests/testThreadingThread.cpp
new file mode 100644
index 000000000..ead2e8909
--- /dev/null
+++ b/js/src/jsapi-tests/testThreadingThread.cpp
@@ -0,0 +1,103 @@
+/* -*- 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/Atomics.h"
+#include "mozilla/IntegerRange.h"
+#include "mozilla/Move.h"
+#include "mozilla/Vector.h"
+
+#include "jsalloc.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);}, mozilla::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::Thread::Id() == js::Thread::Id());
+ js::Thread::Id fromOther;
+ js::Thread thread;
+ CHECK(thread.init([](js::Thread::Id* idp){*idp = js::ThisThread::GetId();}, &fromOther));
+ js::Thread::Id 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::MakeRange(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/testToIntWidth.cpp b/js/src/jsapi-tests/testToIntWidth.cpp
new file mode 100644
index 000000000..68b425aae
--- /dev/null
+++ b/js/src/jsapi-tests/testToIntWidth.cpp
@@ -0,0 +1,69 @@
+/* -*- 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 <math.h>
+
+#include "js/Conversions.h"
+
+#include "jsapi-tests/tests.h"
+
+using JS::detail::ToIntWidth;
+using JS::detail::ToUintWidth;
+
+BEGIN_TEST(testToUint8TwiceUint8Range)
+{
+ double d = -256;
+ uint8_t expected = 0;
+ do {
+ CHECK(ToUintWidth<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(ToIntWidth<int8_t>(d) == expected);
+
+ d++;
+ expected++;
+ } while (expected < 127);
+ return true;
+}
+END_TEST(testToInt8)
+
+BEGIN_TEST(testToUint32Large)
+{
+ CHECK(ToUintWidth<uint32_t>(pow(2.0, 83)) == 0);
+ CHECK(ToUintWidth<uint32_t>(pow(2.0, 83) + pow(2.0, 31)) == (1U << 31));
+ CHECK(ToUintWidth<uint32_t>(pow(2.0, 83) + 2 * pow(2.0, 31)) == 0);
+ CHECK(ToUintWidth<uint32_t>(pow(2.0, 83) + 3 * pow(2.0, 31)) == (1U << 31));
+ CHECK(ToUintWidth<uint32_t>(pow(2.0, 84)) == 0);
+ CHECK(ToUintWidth<uint32_t>(pow(2.0, 84) + pow(2.0, 31)) == 0);
+ CHECK(ToUintWidth<uint32_t>(pow(2.0, 84) + pow(2.0, 32)) == 0);
+ return true;
+}
+END_TEST(testToUint32Large)
+
+BEGIN_TEST(testToUint64Large)
+{
+ CHECK(ToUintWidth<uint64_t>(pow(2.0, 115)) == 0);
+ CHECK(ToUintWidth<uint64_t>(pow(2.0, 115) + pow(2.0, 63)) == (1ULL << 63));
+ CHECK(ToUintWidth<uint64_t>(pow(2.0, 115) + 2 * pow(2.0, 63)) == 0);
+ CHECK(ToUintWidth<uint64_t>(pow(2.0, 115) + 3 * pow(2.0, 63)) == (1ULL << 63));
+ CHECK(ToUintWidth<uint64_t>(pow(2.0, 116)) == 0);
+ CHECK(ToUintWidth<uint64_t>(pow(2.0, 116) + pow(2.0, 63)) == 0);
+ CHECK(ToUintWidth<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 000000000..7abcbbf85
--- /dev/null
+++ b/js/src/jsapi-tests/testTypedArrays.cpp
@@ -0,0 +1,236 @@
+/* -*- 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 "jscompartment.h"
+#include "jsfriendapi.h"
+
+#include "jsapi-tests/tests.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*, uint32_t),
+ typename Element,
+ Element* GetData(JSObject*, bool* isShared, const JS::AutoCheckCannotGC&)>
+bool
+TestPlainTypedArray(JSContext* cx)
+{
+ {
+ RootedObject notArray(cx, Create(cx, UINT32_MAX));
+ CHECK(!notArray);
+ }
+
+ 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, uint32_t, int32_t),
+ JSObject* CreateFromArray(JSContext*, JS::HandleObject),
+ typename Element,
+ bool Shared,
+ Element* GetData(JSObject*, bool*, const JS::AutoCheckCannotGC&)>
+bool
+TestArrayFromBuffer(JSContext* cx)
+{
+ if (Shared && !cx->compartment()->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);
+ }
+
+ 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 000000000..f0a9941c6
--- /dev/null
+++ b/js/src/jsapi-tests/testUTF8.cpp
@@ -0,0 +1,51 @@
+/* -*- 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/Range.h"
+
+#include "jsapi.h"
+#include "jsstr.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 000000000..075e777d6
--- /dev/null
+++ b/js/src/jsapi-tests/testUbiNode.cpp
@@ -0,0 +1,1009 @@
+/* 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/UbiNode.h"
+#include "js/UbiNodeDominatorTree.h"
+#include "js/UbiNodePostOrder.h"
+#include "js/UbiNodeShortestPaths.h"
+#include "jsapi-tests/tests.h"
+#include "vm/SavedFrame.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::CompartmentOptions 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);
+ RootedScript script1(cx);
+ CHECK(JS::Compile(cx, options, "", 0, &script1));
+
+ {
+ // ... and then enter global2's zone and create a string and script
+ // there, too.
+ JSAutoCompartment ac(cx, global2);
+
+ RootedString string2(cx, JS_NewStringCopyZ(cx, "A million household uses!"));
+ CHECK(string2);
+ RootedScript script2(cx);
+ CHECK(JS::Compile(cx, options, "", 0, &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());
+
+ JS::CompartmentOptions 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());
+
+ JS::CompileOptions options(cx);
+
+ // Create a script in the original compartment...
+ RootedScript script1(cx);
+ CHECK(JS::Compile(cx, options, "", 0, &script1));
+
+ {
+ // ... and then enter global2's compartment and create a script
+ // there, too.
+ JSAutoCompartment ac(cx, global2);
+
+ RootedScript script2(cx);
+ CHECK(JS::Compile(cx, options, "", 0, &script2));
+
+ CHECK(JS::ubi::Node(script1).compartment() == global1->compartment());
+ CHECK(JS::ubi::Node(script2).compartment() == global2->compartment());
+ }
+
+ return true;
+}
+END_TEST(test_ubiNodeCompartment)
+
+BEGIN_TEST(test_ubiNodeJSObjectConstructorName)
+{
+ JS::RootedValue val(cx);
+ EVAL("this.Ctor = function Ctor() {}; new Ctor", &val);
+ CHECK(val.isObject());
+
+ UniqueTwoByteChars ctorName;
+ CHECK(JS::ubi::Node(&val.toObject()).jsObjectConstructorName(cx, ctorName));
+ CHECK(ctorName);
+ CHECK(js_strcmp(ctorName.get(), u"Ctor") == 0);
+
+ return true;
+}
+END_TEST(test_ubiNodeJSObjectConstructorName)
+
+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() == 3);
+
+ ubiFrame = ubiFrame.parent();
+ CHECK(checkString("one", bufferFunctionDisplayName, getFunctionDisplayName));
+ CHECK(ubiFrame.line() == 2);
+
+ ubiFrame = ubiFrame.parent();
+ CHECK(ubiFrame.functionDisplayName().is<JSAtom*>());
+ CHECK(ubiFrame.functionDisplayName().as<JSAtom*>() == nullptr);
+ CHECK(ubiFrame.line() == 1);
+
+ 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::LazyScript* lazyScript = nullptr;
+ CHECK(JS::ubi::Node(lazyScript).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 js {
+
+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 js
+
+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);
+ CHECK(expectedEdges.init());
+
+ 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.init());
+ 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);
+ CHECK(expectedDominatedSet.init());
+ 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, func->getOrCreateScript(cx));
+ 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.init());
+ CHECK(targets.put(&b));
+
+ maybeShortestPaths = JS::ubi::ShortestPaths::Create(cx, noGC, 10, &a,
+ mozilla::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.init());
+ CHECK(targets.put(&b));
+
+ maybeShortestPaths = JS::ubi::ShortestPaths::Create(cx, noGC, 10, &a,
+ mozilla::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.init());
+ CHECK(targets.put(&f));
+
+ maybeShortestPaths = JS::ubi::ShortestPaths::Create(cx, noGC, 10, &a,
+ mozilla::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.init());
+ CHECK(targets.put(&f));
+
+ maybeShortestPaths = JS::ubi::ShortestPaths::Create(cx, noGC, 1, &a,
+ mozilla::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.init());
+ CHECK(targets.put(&b));
+
+ maybeShortestPaths = JS::ubi::ShortestPaths::Create(cx, noGC, 10, &a,
+ mozilla::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 000000000..a916b23d3
--- /dev/null
+++ b/js/src/jsapi-tests/testUncaughtSymbol.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 "jsapi-tests/tests.h"
+
+using JS::CreateError;
+using JS::Rooted;
+using JS::ObjectValue;
+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::RootedValue exn(cx);
+ MOZ_RELEASE_ASSERT(JS_GetPendingException(cx, &exn));
+ MOZ_RELEASE_ASSERT(exn.isSymbol());
+ JS_ClearPendingException(cx);
+
+ js::ErrorReport report(cx);
+ MOZ_RELEASE_ASSERT(report.init(cx, exn, js::ErrorReport::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 000000000..1d9df1397
--- /dev/null
+++ b/js/src/jsapi-tests/testValueABI.cpp
@@ -0,0 +1,55 @@
+/* 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"
+
+/*
+ * 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 000000000..d39658e12
--- /dev/null
+++ b/js/src/jsapi-tests/testWasmLEB128.cpp
@@ -0,0 +1,166 @@
+/* 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/WasmBinaryFormat.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 000000000..8ee78ad73
--- /dev/null
+++ b/js/src/jsapi-tests/testWeakMap.cpp
@@ -0,0 +1,258 @@
+/* -*- 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 "jscompartment.h"
+
+#include "gc/Zone.h"
+
+#include "jsapi-tests/tests.h"
+
+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)
+{
+ JS_SetGCParameter(cx, JSGC_MODE, JSGC_MODE_INCREMENTAL);
+ JS_GC(cx);
+ JS::RootedObject map(cx, JS::NewWeakMapObject(cx));
+ CHECK(map);
+
+ JS::RootedObject key(cx, newKey());
+ CHECK(key);
+
+ JS::RootedObject delegate(cx, newDelegate());
+ CHECK(delegate);
+ keyDelegate = delegate;
+
+ JS::RootedObject delegateRoot(cx);
+ {
+ JSAutoCompartment ac(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));
+ js::SliceBudget budget(js::WorkBudget(1000000));
+ cx->gc.startDebugGC(GC_NORMAL, budget);
+ while (JS::IsIncrementalGCInProgress(cx))
+ cx->gc.debugGCSlice(budget);
+#ifdef DEBUG
+ CHECK(map->zone()->lastZoneGroupIndex() < delegateRoot->zone()->lastZoneGroupIndex());
+#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));
+ budget = js::SliceBudget(js::WorkBudget(100000));
+ cx->gc.startDebugGC(GC_NORMAL, budget);
+ while (JS::IsIncrementalGCInProgress(cx))
+ cx->gc.debugGCSlice(budget);
+ 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()->lastZoneGroupIndex() == delegateRoot->zone()->lastZoneGroupIndex());
+#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 void DelegateObjectMoved(JSObject* obj, const JSObject* old)
+{
+ if (!keyDelegate)
+ return; // Object got moved before we set keyDelegate to point to it.
+
+ MOZ_RELEASE_ASSERT(keyDelegate == old);
+ keyDelegate = obj;
+}
+
+static JSObject* GetKeyDelegate(JSObject* obj)
+{
+ return keyDelegate;
+}
+
+JSObject* newKey()
+{
+ static const js::ClassExtension keyClassExtension = {
+ GetKeyDelegate
+ };
+
+ static const js::Class keyClass = {
+ "keyWithDelegate",
+ JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(1),
+ JS_NULL_CLASS_OPS,
+ JS_NULL_CLASS_SPEC,
+ &keyClassExtension,
+ JS_NULL_OBJECT_OPS
+ };
+
+ JS::RootedObject key(cx, JS_NewObject(cx, Jsvalify(&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 objct in the same zone as the
+ * delegate obejct.
+ */
+ JS::RootedObject object(cx);
+ {
+ JSAutoCompartment ac(cx, destZone);
+ object = JS_NewPlainObject(cx);
+ if (!object)
+ return nullptr;
+ }
+ {
+ JSAutoCompartment ac(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->gc.evictNursery();
+
+ return object;
+}
+
+JSObject* newDelegate()
+{
+ static const js::ClassOps delegateClassOps = {
+ nullptr, /* addProperty */
+ nullptr, /* delProperty */
+ nullptr, /* getProperty */
+ nullptr, /* setProperty */
+ nullptr, /* enumerate */
+ nullptr, /* resolve */
+ nullptr, /* mayResolve */
+ nullptr, /* finalize */
+ nullptr, /* call */
+ nullptr, /* hasInstance */
+ nullptr, /* construct */
+ JS_GlobalObjectTraceHook,
+ };
+
+ static const js::ClassExtension delegateClassExtension = {
+ nullptr,
+ DelegateObjectMoved
+ };
+
+ static const js::Class 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::CompartmentOptions options;
+ options.behaviors().setVersion(JSVERSION_LATEST);
+
+ JS::RootedObject global(cx, JS_NewGlobalObject(cx, Jsvalify(&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;
+}
+END_TEST(testWeakMap_keyDelegates)
diff --git a/js/src/jsapi-tests/testXDR.cpp b/js/src/jsapi-tests/testXDR.cpp
new file mode 100644
index 000000000..806a4b743
--- /dev/null
+++ b/js/src/jsapi-tests/testXDR.cpp
@@ -0,0 +1,168 @@
+/* -*- 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 "jsfriendapi.h"
+#include "jsscript.h"
+#include "jsstr.h"
+
+#include "jsapi-tests/tests.h"
+
+#include "jsscriptinlines.h"
+
+static bool
+GetBuildId(JS::BuildIdCharVector* buildId)
+{
+ const char buildid[] = "testXDR";
+ return buildId->append(buildid, sizeof(buildid));
+}
+
+static JSScript*
+FreezeThaw(JSContext* cx, JS::HandleScript script)
+{
+ JS::SetBuildIdOp(cx, GetBuildId);
+
+ // freeze
+ JS::TranscodeBuffer buffer;
+ JS::TranscodeResult rs = JS::EncodeScript(cx, buffer, script);
+ if (rs != JS::TranscodeResult_Ok)
+ return nullptr;
+
+ // thaw
+ JS::RootedScript script2(cx);
+ rs = JS::DecodeScript(cx, buffer, &script2);
+ if (rs != JS::TranscodeResult_Ok)
+ return nullptr;
+ return script2;
+}
+
+enum TestCase {
+ TEST_FIRST,
+ TEST_SCRIPT = TEST_FIRST,
+ TEST_FUNCTION,
+ TEST_SERIALIZED_FUNCTION,
+ TEST_END
+};
+
+BEGIN_TEST(testXDR_bug506491)
+{
+ const char* s =
+ "function makeClosure(s, name, value) {\n"
+ " eval(s);\n"
+ " Math.sin(value);\n"
+ " let n = name, v = value;\n"
+ " return function () { return String(v); };\n"
+ "}\n"
+ "var f = makeClosure('0;', 'status', 'ok');\n";
+
+ // compile
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(__FILE__, __LINE__);
+ JS::RootedScript script(cx);
+ CHECK(JS_CompileScript(cx, s, strlen(s), options, &script));
+ CHECK(script);
+
+ script = FreezeThaw(cx, script);
+ CHECK(script);
+
+ // execute
+ JS::RootedValue v2(cx);
+ CHECK(JS_ExecuteScript(cx, script, &v2));
+
+ // try to break the Block object that is the parent of f
+ JS_GC(cx);
+
+ // confirm
+ EVAL("f() === 'ok';\n", &v2);
+ JS::RootedValue trueval(cx, JS::TrueValue());
+ CHECK_SAME(v2, trueval);
+ return true;
+}
+END_TEST(testXDR_bug506491)
+
+BEGIN_TEST(testXDR_bug516827)
+{
+ // compile an empty script
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(__FILE__, __LINE__);
+ JS::RootedScript script(cx);
+ CHECK(JS_CompileScript(cx, "", 0, options, &script));
+ CHECK(script);
+
+ script = FreezeThaw(cx, script);
+ CHECK(script);
+
+ // execute with null result meaning no result wanted
+ CHECK(JS_ExecuteScript(cx, script));
+ return true;
+}
+END_TEST(testXDR_bug516827)
+
+BEGIN_TEST(testXDR_source)
+{
+ const char* samples[] = {
+ // This can't possibly fail to compress well, can it?
+ "function f(x) { return x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x + x }",
+ "short",
+ nullptr
+ };
+ for (const char** s = samples; *s; s++) {
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(__FILE__, __LINE__);
+ JS::RootedScript script(cx);
+ CHECK(JS_CompileScript(cx, *s, strlen(*s), options, &script));
+ CHECK(script);
+ script = FreezeThaw(cx, script);
+ CHECK(script);
+ JSString* out = JS_DecompileScript(cx, script, "testing", 0);
+ CHECK(out);
+ bool equal;
+ CHECK(JS_StringEqualsAscii(cx, out, *s, &equal));
+ CHECK(equal);
+ }
+ return true;
+}
+END_TEST(testXDR_source)
+
+BEGIN_TEST(testXDR_sourceMap)
+{
+ const char* sourceMaps[] = {
+ "http://example.com/source-map.json",
+ "file:///var/source-map.json",
+ nullptr
+ };
+ JS::RootedScript script(cx);
+ for (const char** sm = sourceMaps; *sm; sm++) {
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(__FILE__, __LINE__);
+ CHECK(JS_CompileScript(cx, "", 0, options, &script));
+ CHECK(script);
+
+ size_t len = strlen(*sm);
+ JS::UniqueTwoByteChars expected_wrapper(js::InflateString(cx, *sm, &len));
+ char16_t *expected = expected_wrapper.get();
+ CHECK(expected);
+
+ // The script source takes responsibility of free'ing |expected|.
+ CHECK(script->scriptSource()->setSourceMapURL(cx, expected));
+ script = FreezeThaw(cx, script);
+ CHECK(script);
+ CHECK(script->scriptSource());
+ CHECK(script->scriptSource()->hasSourceMapURL());
+
+ const char16_t* actual = script->scriptSource()->sourceMapURL();
+ CHECK(actual);
+
+ while (*expected) {
+ CHECK(*actual);
+ CHECK(*expected == *actual);
+ expected++;
+ actual++;
+ }
+ CHECK(!*actual);
+ }
+ return true;
+}
+END_TEST(testXDR_sourceMap)
diff --git a/js/src/jsapi-tests/tests.cpp b/js/src/jsapi-tests/tests.cpp
new file mode 100644
index 000000000..afd6af06b
--- /dev/null
+++ b/js/src/jsapi-tests/tests.cpp
@@ -0,0 +1,149 @@
+/* -*- 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 "jsapi-tests/tests.h"
+
+#include <stdio.h>
+
+#include "js/Initialization.h"
+#include "js/RootingAPI.h"
+
+JSAPITest* JSAPITest::list;
+
+bool JSAPITest::init()
+{
+ cx = createContext();
+ if (!cx)
+ return false;
+ if (!JS::InitSelfHostedCode(cx))
+ return false;
+ JS_BeginRequest(cx);
+ global.init(cx);
+ createGlobal();
+ if (!global)
+ return false;
+ JS_EnterCompartment(cx, global);
+ return true;
+}
+
+void JSAPITest::uninit()
+{
+ if (oldCompartment) {
+ JS_LeaveCompartment(cx, oldCompartment);
+ oldCompartment = nullptr;
+ }
+ if (global) {
+ JS_LeaveCompartment(cx, nullptr);
+ global = nullptr;
+ }
+ if (cx) {
+ JS_EndRequest(cx);
+ destroyContext();
+ cx = nullptr;
+ }
+}
+
+bool JSAPITest::exec(const char* bytes, const char* filename, int lineno)
+{
+ JS::RootedValue v(cx);
+ JS::CompileOptions opts(cx);
+ opts.setFileAndLine(filename, lineno);
+ return JS::Evaluate(cx, opts, bytes, strlen(bytes), &v) ||
+ fail(JSAPITestString(bytes), filename, lineno);
+}
+
+bool JSAPITest::execDontReport(const char* bytes, const char* filename, int lineno)
+{
+ JS::RootedValue v(cx);
+ JS::CompileOptions opts(cx);
+ opts.setFileAndLine(filename, lineno);
+ return JS::Evaluate(cx, opts, bytes, strlen(bytes), &v);
+}
+
+bool JSAPITest::evaluate(const char* bytes, const char* filename, int lineno,
+ JS::MutableHandleValue vp)
+{
+ JS::CompileOptions opts(cx);
+ opts.setFileAndLine(filename, lineno);
+ return JS::Evaluate(cx, opts, bytes, strlen(bytes), vp) ||
+ fail(JSAPITestString(bytes), filename, lineno);
+}
+
+bool JSAPITest::definePrint()
+{
+ return JS_DefineFunction(cx, global, "print", (JSNative) print, 0, 0);
+}
+
+JSObject* JSAPITest::createGlobal(JSPrincipals* principals)
+{
+ /* Create the global object. */
+ JS::RootedObject newGlobal(cx);
+ JS::CompartmentOptions options;
+ options.behaviors().setVersion(JSVERSION_LATEST);
+ newGlobal = JS_NewGlobalObject(cx, getGlobalClass(), principals, JS::FireOnNewGlobalHook,
+ options);
+ if (!newGlobal)
+ return nullptr;
+
+ JSAutoCompartment ac(cx, newGlobal);
+
+ // Populate the global object with the standard globals like Object and
+ // Array.
+ if (!JS_InitStandardClasses(cx, newGlobal))
+ return nullptr;
+
+ global = newGlobal;
+ return newGlobal;
+}
+
+int main(int argc, char* argv[])
+{
+ int total = 0;
+ int failures = 0;
+ const char* filter = (argc == 2) ? argv[1] : nullptr;
+
+ if (!JS_Init()) {
+ printf("TEST-UNEXPECTED-FAIL | jsapi-tests | JS_Init() failed.\n");
+ return 1;
+ }
+
+ for (JSAPITest* test = JSAPITest::list; test; test = test->next) {
+ const char* name = test->name();
+ if (filter && strstr(name, filter) == nullptr)
+ continue;
+
+ total += 1;
+
+ printf("%s\n", name);
+ if (!test->init()) {
+ printf("TEST-UNEXPECTED-FAIL | %s | Failed to initialize.\n", name);
+ failures++;
+ test->uninit();
+ continue;
+ }
+
+ if (test->run(test->global)) {
+ 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++;
+ }
+ test->uninit();
+ }
+
+ JS_ShutDown();
+
+ 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 000000000..9955621ef
--- /dev/null
+++ b/js/src/jsapi-tests/tests.h
@@ -0,0 +1,456 @@
+/* -*- 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/. */
+
+#ifndef jsapi_tests_tests_h
+#define jsapi_tests_tests_h
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/TypeTraits.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "jsalloc.h"
+#include "jscntxt.h"
+#include "jsgc.h"
+
+#include "js/Vector.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(); }
+
+ 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+(JSAPITestString a, const char* b) { return a += b; }
+inline JSAPITestString operator+(JSAPITestString a, const JSAPITestString& b) { return a += b; }
+
+class JSAPITest
+{
+ public:
+ static JSAPITest* list;
+ JSAPITest* next;
+
+ JSContext* cx;
+ JS::PersistentRootedObject global;
+ bool knownFail;
+ JSAPITestString msgs;
+ JSCompartment* oldCompartment;
+
+ JSAPITest() : cx(nullptr), knownFail(false), oldCompartment(nullptr) {
+ next = list;
+ list = this;
+ }
+
+ virtual ~JSAPITest() {
+ MOZ_RELEASE_ASSERT(!cx);
+ MOZ_RELEASE_ASSERT(!global);
+ }
+
+ virtual bool init();
+ virtual void uninit();
+
+ virtual const char * name() = 0;
+ virtual bool run(JS::HandleObject global) = 0;
+
+#define EXEC(s) do { if (!exec(s, __FILE__, __LINE__)) return false; } while (false)
+
+ bool exec(const char* bytes, const char* filename, int lineno);
+
+ // Like exec(), but doesn't call fail() if JS::Evaluate returns false.
+ bool execDontReport(const char* bytes, const char* filename, int lineno);
+
+#define EVAL(s, vp) do { if (!evaluate(s, __FILE__, __LINE__, vp)) return false; } while (false)
+
+ bool evaluate(const char* bytes, const char* filename, int lineno, JS::MutableHandleValue vp);
+
+ JSAPITestString jsvalToSource(JS::HandleValue v) {
+ JSString* str = JS_ValueToSource(cx, v);
+ if (str) {
+ JSAutoByteString bytes(cx, str);
+ if (!!bytes)
+ return JSAPITestString(bytes.ptr());
+ }
+ JS_ClearPendingException(cx);
+ return JSAPITestString("<<error converting value to string>>");
+ }
+
+ JSAPITestString toSource(long v) {
+ char buf[40];
+ sprintf(buf, "%ld", v);
+ return JSAPITestString(buf);
+ }
+
+ JSAPITestString toSource(unsigned long v) {
+ char buf[40];
+ sprintf(buf, "%lu", v);
+ return JSAPITestString(buf);
+ }
+
+ JSAPITestString toSource(long long v) {
+ char buf[40];
+ sprintf(buf, "%lld", v);
+ return JSAPITestString(buf);
+ }
+
+ JSAPITestString toSource(unsigned long long v) {
+ char buf[40];
+ sprintf(buf, "%llu", v);
+ 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(JSAtom* v) {
+ JS::RootedValue val(cx, JS::StringValue((JSString*)v));
+ return jsvalToSource(val);
+ }
+
+ JSAPITestString toSource(JSVersion v) {
+ return JSAPITestString(JS_VersionToString(v));
+ }
+
+ // 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(mozilla::IsSigned<T>::value == mozilla::IsSigned<U>::value,
+ "using CHECK_EQUAL with different-signed inputs triggers compiler warnings");
+ static_assert(mozilla::IsUnsigned<T>::value == mozilla::IsUnsigned<U>::value,
+ "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)
+
+ bool fail(JSAPITestString msg = JSAPITestString(), const char* filename = "-", int lineno = 0) {
+ if (JS_IsExceptionPending(cx)) {
+ js::gc::AutoSuppressGC gcoff(cx);
+ JS::RootedValue v(cx);
+ JS_GetPendingException(cx, &v);
+ JS_ClearPendingException(cx);
+ JSString* s = JS::ToString(cx, v);
+ if (s) {
+ JSAutoByteString bytes(cx, s);
+ if (!!bytes)
+ msg += bytes.ptr();
+ }
+ }
+ fprintf(stderr, "%s:%d:%.*s\n", filename, lineno, (int) msg.length(), msg.begin());
+ msgs += msg;
+ return false;
+ }
+
+ JSAPITestString messages() const { return msgs; }
+
+ static const JSClass * basicGlobalClass() {
+ static const JSClassOps cOps = {
+ nullptr, nullptr, nullptr, nullptr,
+ nullptr, nullptr, nullptr, nullptr,
+ nullptr, nullptr, nullptr,
+ JS_GlobalObjectTraceHook
+ };
+ static const JSClass c = {
+ "global", JSCLASS_GLOBAL_FLAGS,
+ &cOps
+ };
+ return &c;
+ }
+
+ protected:
+ static bool
+ print(JSContext* cx, unsigned argc, JS::Value* vp)
+ {
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+
+ for (unsigned i = 0; i < args.length(); i++) {
+ JSString* str = JS::ToString(cx, args[i]);
+ if (!str)
+ return false;
+ char* bytes = JS_EncodeString(cx, str);
+ if (!bytes)
+ return false;
+ printf("%s%s", i ? " " : "", bytes);
+ JS_free(cx, bytes);
+ }
+
+ putchar('\n');
+ fflush(stdout);
+ args.rval().setUndefined();
+ return true;
+ }
+
+ bool definePrint();
+
+ static void setNativeStackQuota(JSContext* cx)
+ {
+ const size_t MAX_STACK_SIZE =
+/* Assume we can't use more than 5e5 bytes of C stack by default. */
+#if (defined(DEBUG) && defined(__SUNPRO_CC)) || defined(JS_CPU_SPARC)
+ /*
+ * Sun compiler uses a larger stack space for js::Interpret() with
+ * debug. Use a bigger gMaxStackSize to make "make check" happy.
+ */
+ 5000000
+#else
+ 500000
+#endif
+ ;
+
+ JS_SetNativeStackQuota(cx, MAX_STACK_SIZE);
+ }
+
+ virtual JSContext* createContext() {
+ JSContext* cx = JS_NewContext(8L * 1024 * 1024);
+ if (!cx)
+ return nullptr;
+ JS::SetWarningReporter(cx, &reportWarning);
+ setNativeStackQuota(cx);
+ return cx;
+ }
+
+ virtual void destroyContext() {
+ MOZ_RELEASE_ASSERT(cx);
+ JS_DestroyContext(cx);
+ cx = nullptr;
+ }
+
+ static void reportWarning(JSContext* cx, JSErrorReport* report) {
+ MOZ_RELEASE_ASSERT(report);
+ MOZ_RELEASE_ASSERT(JSREPORT_IS_WARNING(report->flags));
+
+ 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);
+};
+
+#define BEGIN_TEST(testname) \
+ class cls_##testname : public JSAPITest { \
+ public: \
+ virtual const char * name() override { return #testname; } \
+ virtual bool run(JS::HandleObject global) override
+
+#define END_TEST(testname) \
+ }; \
+ static cls_##testname cls_##testname##_instance;
+
+/*
+ * A "fixture" is a subclass of JSAPITest 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;
+ }
+};
+
+#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) {
+ uint32_t dummy;
+ JS_GetGCZealBits(cx_, &zealBits_, &frequency_, &dummy);
+ JS_SetGCZeal(cx_, 0, 0);
+ JS::PrepareForFullGC(cx_);
+ JS::GCForReason(cx_, GC_SHRINK, JS::gcreason::DEBUG_GC);
+ }
+ ~AutoLeaveZeal() {
+ 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
+ }
+};
+#endif /* JS_GC_ZEAL */
+
+#endif /* jsapi_tests_tests_h */
diff --git a/js/src/jsapi-tests/valueABI.c b/js/src/jsapi-tests/valueABI.c
new file mode 100644
index 000000000..9ac5d413c
--- /dev/null
+++ b/js/src/jsapi-tests/valueABI.c
@@ -0,0 +1,28 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sw=4 et tw=99 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);
+}