diff options
Diffstat (limited to 'js/src/jsapi-tests')
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, ¶mName, 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, + ¶mName, 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, ¶mName, 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, + ¶mName, 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, + ¶mName, 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); +} |